diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..90c9bc3 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,59 @@ +[run] +source_pkgs = hummingbot +omit = + hummingbot/core/gateway/* + hummingbot/core/management/* + hummingbot/client/config/config_helpers.py + hummingbot/client/config/conf_migration.py + hummingbot/client/config/security.py + hummingbot/client/hummingbot_application.py + hummingbot/client/command/* + hummingbot/client/settings.py + hummingbot/client/ui/completer.py + hummingbot/client/ui/layout.py + hummingbot/client/tab/* + hummingbot/client/ui/parser.py + hummingbot/connector/connector/balancer* + hummingbot/connector/connector/terra* + hummingbot/connector/connector/uniswap* + hummingbot/connector/connector/uniswap_v3* + hummingbot/connector/derivative/perpetual_finance* + hummingbot/connector/derivative/position.py + hummingbot/connector/exchange/bitfinex* + hummingbot/connector/exchange/coinbase_pro* + hummingbot/connector/exchange/hitbtc* + hummingbot/connector/exchange/paper_trade* + hummingbot/connector/gateway/** + hummingbot/connector/test_support/* + hummingbot/core/utils/gateway_config_utils.py + hummingbot/core/utils/kill_switch.py + hummingbot/core/utils/wallet_setup.py + hummingbot/connector/mock* + hummingbot/strategy/*/start.py + hummingbot/strategy/dev* + hummingbot/user/user_balances.py + hummingbot/smart_components/controllers/* +dynamic_context = test_function +branch = true + +[report] +fail_under = 70 +precision = 2 +skip_empty = true +exclude_lines = + @(abc\.)?abstractmethod + if TYPE_CHECKING: + pragma: no cover + if __name__ == .__main__.: + if 0: + raise AssertionError + raise NotImplementedError + if settings.DEBUG + except asyncio.exceptions.TimeoutError: + +[html] +directory = coverage_html_report +show_contexts = true + +[xml] +output = coverage.xml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4b67699 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +/data +/logs +/build +/dist +/.git +/.github +.DS_Store +/conf_backup +/conf/strategies/*.yml +/conf/*.yml +/conf/gateway_connections.json +/conf/.password_verification +/conf/connectors/*.yml diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..9d195b5 --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +filename = *.py, *.pyx, *.pxd +ignore = E251, E501,E702,W504,W503 +per-file-ignores = + hummingbot/**/*.pyx: E225, E226, E251, E999 + hummingbot/**/*.pxd: E225, E226, E251, E999 + test/**/*.pyx: E225, E226, E251, E999 + test/**/*.pxd: E225, E226, E251, E999 +max-line-length = 120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27c11de --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# .idea +/.idea + +# CMake +cmake-build-debug/ + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +__pycache__ +.ipynb_checkpoints +*.pyc + +# Cython objects +/**/*.so +/**/*.pyd +/hummingbot/**/*.c +/hummingbot/**/*.cpp +!/hummingbot/core/cpp/*.cpp +/hummingbot/wallet/*.c +/hummingbot/wallet/*.cpp +/hummingbot/wallet/ethereum/*.c +/hummingbot/wallet/ethereum/*.cpp +/test/**/*.cpp +/build + +# Local files +**/.DS_Store +portfolio_value.csv +/notebooks +/data +debug.log +/installation/docker-commands/hummingbot_files + +# Distribution files +/build +/dist +/src +/*.egg-info +*.whl + + +# VSCode +.vscode/ +.history/ + +# emacs files +\#*\# +.\#* + +# Documentation build dir +/documentation/site/ + +# Cert files +/certs +*.pem + +# Coverage +.coverage +/cover/ +/coverage_html_report/ +coverage.xml + +# misc +*.srl +*.key +*.crt +*.log + +# Debug console +.debug_console_ssh_host_key + +/conf_backup + +# External SDK files +/**/.chain_cookie +/**/.injective_cookie diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b0bf029 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: flake8 + types: ['file'] + files: \.(py|pyx|pxd)$ + - id: detect-private-key +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.10.0 + hooks: + - id: eslint + files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + types: [file] +- repo: https://github.com/CoinAlpha/git-hooks + rev: 78f0683233a09c68a072fd52740d32c0376d4f0f + hooks: + - id: detect-wallet-private-key + types: [file] + exclude: .json +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + files: "\\.(py)$" + args: [--settings-path=pyproject.toml] diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..604d66e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at operations@hummingbot.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5674312 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Contribution Guidelines + +## General Workflow + +1. Fork the `hummingbot/hummingbot` repository. +2. Create a new branch from the `development` branch in your fork. +3. Commit your changes to your branch. +4. Once you've completed your fix, feature, connector, or documentation: + + - Rebase upstream changes into your branch. + - Create a pull request to the `development` branch. + - Include a detailed description of your changes. + - Ensure to `allow edits by maintainers` before submitting the pull request. + +5. Your code changes will be reviewed and tested by the Foundation QA team. +6. Address any changes requested by your reviewer, fix issues raised, and push your fixes as a single new commit. +7. Once the pull request has been reviewed and accepted, it will be merged by a member of the Hummingbot Foundation team. + +**Note:** Tests are crucial. If your pull request contains new, testable behavior, please submit tests. Refer to the 'Unit Test Coverage' section for more information. + +## Detailed Workflow + +### 1. Fork the Repository + +Use GitHub's interface to fork the repo, add the Hummingbot repo as an upstream remote, and fetch upstream data: + +```bash +git remote add upstream https://github.com/hummingbot/hummingbot.git +git fetch upstream +``` + +### 2. Create Your Branch + +Create your local branch following this naming convention: + +- feat/... +- fix/... +- refactor/... +- doc/... + +Create and switch to a new local branch called feat/[branch_name] based on the development branch of the remote upstream: + +```bash +git checkout -b feat/[branch_name] upstream/development +``` + +### 3. Commit Changes to Your Branch + +Make commits to your branch. Prefix each commit like so: + +- (feat) add a new feature +- (fix) fix inconsistent tests +- (refactor) ... +- (cleanup) ... +- (doc) ... + +Commit messages should be written in the present tense, e.g., "(feat) add unit tests". The first line of your commit message should be a summary of what the commit changes, aiming for about 70 characters max. If you want to explain the commit in more depth, provide a more detailed description after a blank line following the first line. + +### 4. Rebase Upstream Changes + +Rebase upstream changes to the development branch into yours by running this command from your branch: + +```bash +git pull --rebase upstream development +``` + +If there are conflicting changes, git will pause rebasing to allow you to sort out the conflicts. Once you are done fixing conflicts for a specific commit, run: + +```bash +git rebase --continue +``` + +Ensure all tests pass after rebasing. + +### 5. Create a Pull Request + +Create a clear pull request from your fork and branch to the upstream `development` branch, detailing your changes. Check 'Allow edits by maintainers' for the Foundation team to update your branch with development whenever needed. + +If the Foundation team requests changes, make more commits to your branch to address these, then follow this process again from rebasing onwards. Once you've addressed the requests, request further review. + +## Unit Test Coverage + +A minimum of 75% unit test coverage is required for all changes included in a pull request. However, some components, like UI components, are excluded from this validation. + +To calculate the diff-coverage locally on your computer, run `make development-diff-cover` after running all tests. + +## Checklist + +- Did I create my branch from `development` (don't create new branches from existing feature branches)? +- Did I follow the correct naming convention for my branch? +- Is my branch focused on a single main change? +- Do all of my changes directly relate to this change? +- Did I rebase the upstream development branch after I finished all my work? +- Did I write a clear pull request message detailing what changes I made? +- Did I get a code review? +- Did I make any requested changes from that code review? + +If you adhere to these guidelines and make quality changes, you should have no problems getting your contributions accepted. Thank you for contributing! diff --git a/DATA_COLLECTION.md b/DATA_COLLECTION.md new file mode 100644 index 0000000..0ad0b4e --- /dev/null +++ b/DATA_COLLECTION.md @@ -0,0 +1,33 @@ +# Important Disclosure re: Hummingbot Data Collection + +Hummingbot (“Hummingbot”) is open-source software developed and maintained by Hummingbot Foundation ("The Foundation, “we”, or “us”) and available at https://github.com/hummingbot/hummingbot. + +The Foundation collects certain usage information from users that have configured Hummingbot to allow for the sharing of this data. + +## Purpose of data collection + +### Software improvement + +Data collected by the Foundation is used to help improve Hummingbot for all users. It is important that our team understands the usage patterns and detects bugs as soon as possible, so we can best decide how to design future features and prioritize current work. + +### Support + +Users who provide data allow us to more easily diagnose problems, troubleshoot, and resolve any issues encountered when running Hummingbot. + +## Types of data collected + +Hummingbot aggregates and collects total traded volume per exchange denominated in USDT. The data collected is subsequently reported to the Foundation's backend services. + +All data collected will be used exclusively by the Foundation team for analytical and marketing purposes only. The data will be neither accessible nor sold to any third party. + +## Sensitive data + +Hummingbot will never collect and/or report sensitive information, such as private keys, API keys, or passwords. + +## How do I opt-in to or opt-out of data sharing? + +See https://docs.hummingbot.org/faq/#reporting for information on configuring this functionality. + +## How this data is used + +When enabled by a user, data is reported by Hummingbot to the Foundation. The Foundation uses its own, third-party systems, as well as commercial software (such as Datadog Inc.) to collect and process data. diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..eb717a5 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,146 @@ +# Docker + +## Why use Docker Compose? + +Using Docker for Hummingbot deployment offers several benefits, such as simplifying the installation process, enabling easy versioning and scaling, and ensuring a consistent and isolated environment for running the bot. This repository aims to help users get started with deploying Hummingbot using Docker by providing different examples that demonstrate how to set up and customize the bot according to their needs. + +## Install Docker Compose + +The examples below use Docker Compose, a tool for defining and running multi-container Docker applications. You can install Docker Compose either via command line or by running an installer. + +Linux (Ubuntu / Debian): + +```bash +sudo apt-get update +sudo apt-get install docker-compose-plugin +``` + +Mac (Homebrew): + +```bash +brew install docker-compose +``` + +If you want to be guided through the installation, install [Docker Desktop](https://www.docker.com/products/docker-desktop/) includes Docker Compose along with Docker Engine and Docker CLI which are Compose prerequisites: + +* [Linux](https://docs.docker.com/desktop/install/linux-install/) +* [Mac](https://docs.docker.com/desktop/install/mac-install/) +* [Windows](https://docs.docker.com/desktop/install/windows-install/) + + +Verify that Docker Compose is installed correctly by checking the version: + +```bash +docker compose version +``` + +Hummingbot's [deploy-examples](https://github.com/hummingbot/deploy-examples) repository provides various examples of how to deploy Hummingbot using Docker Compose, a tool for defining and running multi-container Docker applications. + +Compiled images of `hummingbot` are available on our official DockerHub: https://hub.docker.com/r/hummingbot/hummingbot + +## Building a Docker Image + +You can also build and run a Docker-based Hummingbot image using the `docker-compose.yml` file in the root folder: +```yml +version: "3.9" +services: + hummingbot: + container_name: hummingbot + build: + context: . + dockerfile: Dockerfile + volumes: + - ./conf:/home/hummingbot/conf + - ./conf/connectors:/home/hummingbot/conf/connectors + - ./conf/strategies:/home/hummingbot/conf/strategies + - ./logs:/home/hummingbot/logs + - ./data:/home/hummingbot/data + - ./scripts:/home/hummingbot/scripts + environment: + # - CONFIG_PASSWORD=a + # - CONFIG_FILE_NAME=directional_strategy_rsi.py + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: 5 + tty: true + stdin_open: true + network_mode: host + + dashboard: + container_name: dashboard + image: hummingbot/dashboard:latest + volumes: + - ./data:/home/dashboard/data + ports: + - "8501:8501" +``` + +Build and launch the image by running: +``` +docker compose up -d +``` + +Uncomment the following lines in the YML file before running the command above if you would like to: +* Bypass the password screen by entering the previously set password +* Auto-starting a script +``` + # environment: + # - CONFIG_PASSWORD=a + # - CONFIG_FILE_NAME=directional_strategy_rsi.py +``` + +## Useful Docker Commands + +Use the commands below or use the Docker Desktop application to manage your containers: + +### Create the Compose project +``` +docker compose up -d +``` + +### Stop the Compose project +``` +docker compose down +``` + +### Update the Compose project for the latest images +``` +docker compose up --force-recreate --build -d +``` + +### Give all users read/write permissions to local files +``` +sudo chmod -R a+rw +``` + +### Attach to the container +``` +docker attach +``` + +### Detach from the container and return to command line + +Press keys Ctrl + P then Ctrl + Q + + +### Update the container to the latest image +``` +docker compose up --force-recreate --build -d +``` + +### List all containers +``` +docker ps -a +``` + +### Stop a container +``` +docker stop +``` + +### Remove a container +``` +docker rm +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bb29244 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,76 @@ +# Set the base image +FROM continuumio/miniconda3:latest AS builder + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y sudo libusb-1.0 gcc g++ python3-dev && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /home/hummingbot + +# Create conda environment +COPY setup/environment.yml /tmp/environment.yml +RUN conda env create -f /tmp/environment.yml && \ + conda clean -afy && \ + rm /tmp/environment.yml + +# Copy remaining files +COPY bin/ bin/ +COPY hummingbot/ hummingbot/ +COPY scripts/ scripts/ +COPY scripts/ scripts-copy/ +COPY setup.py . +COPY LICENSE . +COPY README.md . +COPY DATA_COLLECTION.md . + +# activate hummingbot env when entering the CT +SHELL [ "/bin/bash", "-lc" ] +RUN echo "conda activate hummingbot" >> ~/.bashrc + +RUN python3 setup.py build_ext --inplace -j 8 && \ + rm -rf build/ && \ + find . -type f -name "*.cpp" -delete + + +# Build final image using artifacts from builder +FROM continuumio/miniconda3:latest AS release + +# Dockerfile author / maintainer +LABEL maintainer="Fede Cardoso @dardonacci " + +# Build arguments +ARG BRANCH="" +ARG COMMIT="" +ARG BUILD_DATE="" +LABEL branch=${BRANCH} +LABEL commit=${COMMIT} +LABEL date=${BUILD_DATE} + +# Set ENV variables +ENV COMMIT_SHA=${COMMIT} +ENV COMMIT_BRANCH=${BRANCH} +ENV BUILD_DATE=${DATE} + +ENV INSTALLATION_TYPE=docker + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y sudo libusb-1.0 && \ + rm -rf /var/lib/apt/lists/* + +# Create mount points +RUN mkdir -p /home/hummingbot/conf /home/hummingbot/conf/connectors /home/hummingbot/conf/strategies /home/hummingbot/logs /home/hummingbot/data /home/hummingbot/certs /home/hummingbot/scripts + +WORKDIR /home/hummingbot + +# Copy all build artifacts from builder image +COPY --from=builder /opt/conda/ /opt/conda/ +COPY --from=builder /home/ /home/ + +# Setting bash as default shell because we have .bashrc with customized PATH (setting SHELL affects RUN, CMD and ENTRYPOINT, but not manual commands e.g. `docker run image COMMAND`!) +SHELL [ "/bin/bash", "-lc" ] + +# Set the default command to run when starting the container + +CMD conda activate hummingbot && ./bin/hummingbot_quickstart.py 2>> ./logs/errors.log \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e998fcf --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Hummingbot Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c5e049a --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +.ONESHELL: +.PHONY: test +.PHONY: run_coverage +.PHONY: report_coverage +.PHONY: development-diff-cover +.PHONY: docker +.PHONY: install +.PHONY: uninstall +.PHONY: clean +.PHONY: build + +test: + coverage run -m nose \ + --exclude-dir="test/connector" \ + --exclude-dir="test/debug" \ + --exclude-dir="test/mock" \ + --exclude-dir="test/hummingbot/connector/gateway/amm" \ + --exclude-dir="test/hummingbot/connector/exchange/coinbase_pro" \ + --exclude-dir="test/hummingbot/connector/exchange/kraken" \ + --exclude-dir="test/hummingbot/connector/exchange/hitbtc" \ + --exclude-dir="test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot" \ + --exclude-dir="test/hummingbot/strategy/amm_arb" \ + --exclude-dir="test/hummingbot/core/gateway" \ + --exclude-dir="test/hummingbot/strategy/uniswap_v3_lp" + +run_coverage: test + coverage report + coverage html + +report_coverage: + coverage report + coverage html + +development-diff-cover: + coverage xml + diff-cover --compare-branch=origin/development coverage.xml + +docker: + git clean -xdf && make clean && docker build -t hummingbot/hummingbot${TAG} -f Dockerfile . + +clean: + ./clean + +install: + ./install + +uninstall: + ./uninstall + +build: + ./compile diff --git a/README.md b/README.md index e80e3ea..0c8277f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,137 @@ -# hummingbot +![Hummingbot](https://i.ibb.co/X5zNkKw/blacklogo-with-text.png) -Custom HummingBot for Whitebit \ No newline at end of file +---- +[![License](https://img.shields.io/badge/License-Apache%202.0-informational.svg)](https://github.com/hummingbot/hummingbot/blob/master/LICENSE) +[![Twitter](https://img.shields.io/twitter/url?url=https://twitter.com/_hummingbot?style=social&label=_hummingbot)](https://twitter.com/_hummingbot) +[![Youtube](https://img.shields.io/youtube/channel/subscribers/UCxzzdEnDRbylLMWmaMjywOA)](https://www.youtube.com/@hummingbot) +[![Discord](https://img.shields.io/discord/530578568154054663?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/hummingbot) + +Hummingbot is an open source framework that helps you build automated trading strategies, or **bots** that run on cryptocurrency exchanges. + +This code is free and publicly available under the Apache 2.0 open source license! + +## Why Hummingbot? + +* **Both CEX and DEX connectors**: Hummingbot supports connectors to centralized exchanges like Binance and KuCoin, as well as decentralized exchanges like Uniswap and PancakeSwap on various blockchains (Ethereum, BNB Chain, etc). +* **Community-contributed strategies**: The Hummingbot community has added many customizable templates for market making, arbitrage, and other algo trading strategies. +* **Secure local client**: Hummingbot is a local client software that you install and run on your own devices or cloud virtual machines. It encrypts your API keys and private keys and never exposes them to any third parties. +* **Community modules and events**: Hummingbot is driven by a global community of quant traders and developers. Check out community-maintained modules like Orchestration, join our bi-weekly developer calls, and learn how to build custom strategies using Hummingbot by taking Botcamp! + +Help us **democratize high-frequency trading** and make powerful trading algorithms accessible to everyone in the world! + + +## Quick Links + +* [Docs](https://docs.hummingbot.org): Check out the official Hummingbot documentation +* [Installation](https://hummingbot.org/installation/): Install Hummingbot on various platforms +* [FAQs](https://hummingbot.org/faq/): Answers to all your burning questions +* [Botcamp](https://hummingbot.org/botcamp/): Learn how build your own custom HFT strategy in Hummingbot with our hands-on bootcamp! +* [HBOT](https://hummingbot.org/hbot/): Learn how you can decide how this codebase evolves by voting with HBOT tokens + +## Community + +* [Newsletter](https://hummingbot.substack.com): Get our monthly newletter whenever we ship a new release +* [Discord](https://discord.gg/hummingbot): The main gathering spot for the global Hummingbot community +* [YouTube](https://www.youtube.com/c/hummingbot): Videos that teach you how to get the most of of Hummingbot +* [Twitter](https://twitter.com/_hummingbot): Get the latest announcements about Hummingbot +* [Snapshot](https://snapshot.org/#/hbot-prp.eth): Participate in monthly polls that decide which components should be prioritized and included + +## Exchange Connectors + +Hummingbot connectors standardize trading logic and order types across different exchange types. Currently, we support the following exchange types: + + * **SPOT**: Connectors to central limit order book (CLOB) exchanges that trade spot markets + * **PERP**: Connectors to central limit order book (CLOB) exchanges that trade perpetual swap markets + * **AMM**: Connectors to decentralized exchanges that use the Automatic Market Maker (AMM) methodology + +Exchanges may be centralized (**CEX**), or decentralized (**DEX**), in which case user assets are stored on the blockchain and trading is performed via wallet addresses. + +| Tier | Exchange | Type | Signup code | +|------|----------|------|-------------| +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=GOLD&color=yellow) | [Binance](https://docs.hummingbot.org/exchanges/binance/) | SPOT CEX | [FQQNNGCD](https://www.binance.com/en/register?ref=FQQNNGCD) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=GOLD&color=yellow) | [Binance Futures](https://docs.hummingbot.org/exchanges/binance-perpetual/) | PERP CEX | [hummingbot](https://www.binance.com/en/futures/ref?code=hummingbot) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=GOLD&color=yellow) | [dYdX](https://dydx.exchange/) | PERP DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [Dexalot](https://docs.hummingbot.org/exchanges/dexalot/) | CLOB DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [Gate.io](https://docs.hummingbot.org/exchanges/gate-io/) | SPOT CEX | [5868285](https://www.gate.io/signup/5868285) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [Gate.io Perpetual](https://docs.hummingbot.org/exchanges/gate-io-perpetual/) | PERP CEX | [5868285](https://www.gate.io/signup/5868285) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [Huobi](https://docs.hummingbot.org/exchanges/huobi/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver)| [Injective Helix](https://docs.hummingbot.org/exchanges/injective/) | CLOB DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [KuCoin](https://docs.hummingbot.org/exchanges/kucoin/) | SPOT CEX | [272KvRf](https://www.kucoin.com/ucenter/signup?rcode=272KvRf) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [KuCoin Perpetual](https://docs.hummingbot.org/exchanges/kucoin-perpetual/) | PERP CEX | [272KvRf](https://www.kucoin.com/ucenter/signup?rcode=272KvRf) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=SILVER&color=silver) | [Polkadex](https://docs.hummingbot.org/exchanges/polkadex/) | SPOT DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [AscendEx](https://docs.hummingbot.org/exchanges/ascend-ex/) | SPOT CEX | [UEIXNXKW](https://ascendex.com/register?inviteCode=UEIXNXKW) +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [BTC-Markets](https://docs.hummingbot.org/exchanges/btc-markets/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bit.com](https://docs.hummingbot.org/exchanges/bit-com) | PERP CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [BitMart](https://docs.hummingbot.org/exchanges/bitmart/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bitfinex](https://docs.hummingbot.org/exchanges/bitfinex/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [BitGet](https://docs.hummingbot.org/exchanges/bitget-perpetual/) | PERP CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bitmex](https://docs.hummingbot.org/exchanges/bitmex/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bitmex (perp](https://docs.hummingbot.org/exchanges/bitmex-perpetual/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bybit](https://docs.hummingbot.org/exchanges/bybit/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Bybit (perp)](https://docs.hummingbot.org/exchanges/bitmex-perpetual/) | PERP CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Coinbase](https://docs.hummingbot.org/exchanges/coinbase/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [HitBTC](https://docs.hummingbot.org/exchanges/hitbtc/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Kraken](https://docs.hummingbot.org/exchanges/kraken/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [MEXC](https://docs.hummingbot.org/exchanges/mexc/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Mad Meerkat](https://docs.hummingbot.org/exchanges/mad-meerkat/) | SPOT DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [NDAX](https://docs.hummingbot.org/exchanges/ndax/) | SPOT DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [OKX](https://docs.hummingbot.org/exchanges/okx/) | SPOT CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [OpenOcean](https://docs.hummingbot.org/exchanges/openocean/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Pancakeswap](https://docs.hummingbot.org/exchanges/pancakeswap/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Pangolin](https://docs.hummingbot.org/exchanges/pangolin/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Perpetual Protocol](https://docs.hummingbot.org/exchanges/perp/) | PERP DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Phemex Perpetual](https://docs.hummingbot.org/exchanges/perp/) | PERP CEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Plenty](https://docs.hummingbot.org/exchanges/plenty/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Quickswap](https://docs.hummingbot.org/exchanges/quickswap/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Ref Finance](https://docs.hummingbot.org/exchanges/ref/) | SPOT DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Sushiswap](https://docs.hummingbot.org/exchanges/sushiswap/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Tinyman](https://docs.hummingbot.org/exchanges/tinyman/) | SPOT DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Traderjoe](https://docs.hummingbot.org/exchanges/traderjoe) | AMM +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Uniswap](https://docs.hummingbot.org/exchanges/uniswap/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [VVS Finance](https://docs.hummingbot.org/exchanges/vvs/) | AMM DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [Vertex](https://docs.hummingbot.org/exchanges/vertex/) | CLOB DEX | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [WOO X](https://docs.hummingbot.org/exchanges/woo-x)| CEX CLOB | +| ![](https://img.shields.io/static/v1?label=Hummingbot&message=BRONZE&color=green) | [XSWAP](https://docs.hummingbot.org/exchanges/xswap/) | AMM DEX | + + + +Quarterly [Polls](https://docs.hummingbot.org/governance/polls/) allow the Hummingbot community to vote using HBOT tokens to decide which exchanges should be certified GOLD or SILVER, which means that they are maintained and continually improved by Hummingbot Foundation. In addition, the codebase includes BRONZE exchange connectors that are maintained by community members. See the [Hummingbot documentation](https://docs.hummingbot.org/exchanges) for all exchanges supported. + +## Strategies and Scripts + +We provide customizable strategy templates for core trading strategies that users can configure, extend, and run. Hummingbot Foundation maintains three **CORE** strategies: + +* [Pure Market Making](https://docs.hummingbot.org/strategies/pure-market-making/): Our original single-pair market making strategy +* [Cross Exchange Market Making](https://docs.hummingbot.org/strategies/cross-exchange-market-making/): Provide liquidity while hedging filled orders on another exchange +* [AMM Arbitrage](https://docs.hummingbot.org/strategies/amm-arbitrage/): Exploits price differences between AMM and SPOT exchanges + +**CORE** strategies are selected via HBOT voting through quarterly [Polls](https://docs.hummingbot.org/governance/polls/). In addition, the codebase includes **COMMUNITY** strategies that are maintained by individuals or firms in the community. See the [Hummingbot documentation](https://docs.hummingbot.org/strategies) for all strategies supported. + +### Scripts + +Scripts are a newer, lighter way to build Hummingbot strategies in Python, which let you modify the script's code and re-run it to apply the changes without exiting the Hummingbot interface or re-compiling the code. + +See the [Scripts](https://docs.hummingbot.org/scripts/) section in the documentation for more info, or check out the [/scripts](https://github.com/hummingbot/hummingbot/tree/master/scripts) folder for all Script examples included in the codebase. + +## Other Hummingbot Repos + +* [Hummingbot Site](https://github.com/hummingbot/hummingbot-site): Official documentation for Hummingbot - we welcome contributions here too! +* [Hummingbot Project Management](https://github.com/hummingbot/pm): Agendas and recordings of regular Hummingbot developer and community calls +* [Awesome Hummingbot](https://github.com/hummingbot/awesome-hummingbot): All the Hummingbot links +* [Hummingbot StreamLit Apps](https://github.com/hummingbot/streamlit-apps): Hummingbot-related StreamLit data apps and dashboards +* [Community Tools](https://github.com/hummingbot/community-tools): Community contributed resources related to Hummingbot +* [Brokers](https://github.com/hummingbot/brokers): Different brokers that can be used to communicate with multiple instances of Hummingbot +* [Deploy Examples](https://github.com/hummingbot/deploy-examples): Deploy Hummingbot in various configurations with Docker +* [Remote Client](https://github.com/hummingbot/hbot-remote-client-py): A remote client for Hummingbot in Python +* [Dashboard](https://github.com/hummingbot/dashboard): Hummingbot Dashboard community project + +## Contributions + +Hummingbot belongs to its community, so we welcome contributions! Please review these [guidelines](./CONTRIBUTING.md) first. + +To have your pull request merged into the codebase, submit a [Pull Request Proposal](https://snapshot.org/#/hbot-prp.eth) on our Snapshot. Note that you will need 1 HBOT in your Ethereum wallet to submit a Pull Request Proposal. See [HBOT](https://hummingbot.org/hbot) for more information. + +## Legal + +* **License**: Hummingbot is licensed under [Apache 2.0](./LICENSE). +* **Data collection**: read important information regarding [Hummingbot Data Collection](./DATA_COLLECTION.md). diff --git a/assets/Pangolin-logo.png b/assets/Pangolin-logo.png new file mode 100644 index 0000000..0dd250a Binary files /dev/null and b/assets/Pangolin-logo.png differ diff --git a/assets/altmarkets_logo1.png b/assets/altmarkets_logo1.png new file mode 100644 index 0000000..3164057 Binary files /dev/null and b/assets/altmarkets_logo1.png differ diff --git a/assets/ascendex-logo.jpg b/assets/ascendex-logo.jpg new file mode 100644 index 0000000..56fc8c4 Binary files /dev/null and b/assets/ascendex-logo.jpg differ diff --git a/assets/balancer-logo.jpg b/assets/balancer-logo.jpg new file mode 100644 index 0000000..64f3d52 Binary files /dev/null and b/assets/balancer-logo.jpg differ diff --git a/assets/bamboorelay-logo.jpg b/assets/bamboorelay-logo.jpg new file mode 100644 index 0000000..b78860a Binary files /dev/null and b/assets/bamboorelay-logo.jpg differ diff --git a/assets/beaxy-logo.png b/assets/beaxy-logo.png new file mode 100644 index 0000000..8c8c8c1 Binary files /dev/null and b/assets/beaxy-logo.png differ diff --git a/assets/binance-logo.jpg b/assets/binance-logo.jpg new file mode 100644 index 0000000..f04b9a2 Binary files /dev/null and b/assets/binance-logo.jpg differ diff --git a/assets/binance_futures-logo.jpg b/assets/binance_futures-logo.jpg new file mode 100644 index 0000000..8d974a6 Binary files /dev/null and b/assets/binance_futures-logo.jpg differ diff --git a/assets/binance_us-logo.jpg b/assets/binance_us-logo.jpg new file mode 100644 index 0000000..eb22778 Binary files /dev/null and b/assets/binance_us-logo.jpg differ diff --git a/assets/bitfinex-logo.jpg b/assets/bitfinex-logo.jpg new file mode 100644 index 0000000..c1b30b8 Binary files /dev/null and b/assets/bitfinex-logo.jpg differ diff --git a/assets/bitget-logo.png b/assets/bitget-logo.png new file mode 100644 index 0000000..396b6ea Binary files /dev/null and b/assets/bitget-logo.png differ diff --git a/assets/bitmart-logo.jpg b/assets/bitmart-logo.jpg new file mode 100644 index 0000000..ff235ef Binary files /dev/null and b/assets/bitmart-logo.jpg differ diff --git a/assets/bitmex-logo.png b/assets/bitmex-logo.png new file mode 100644 index 0000000..b2f4271 Binary files /dev/null and b/assets/bitmex-logo.png differ diff --git a/assets/bittrex_global-logo.jpg b/assets/bittrex_global-logo.jpg new file mode 100644 index 0000000..ba62878 Binary files /dev/null and b/assets/bittrex_global-logo.jpg differ diff --git a/assets/btcmarkets-logo.jpg b/assets/btcmarkets-logo.jpg new file mode 100644 index 0000000..da37bcf Binary files /dev/null and b/assets/btcmarkets-logo.jpg differ diff --git a/assets/bybit-logo.jpg b/assets/bybit-logo.jpg new file mode 100644 index 0000000..3e0a834 Binary files /dev/null and b/assets/bybit-logo.jpg differ diff --git a/assets/celo-logo.jpg b/assets/celo-logo.jpg new file mode 100644 index 0000000..16a540e Binary files /dev/null and b/assets/celo-logo.jpg differ diff --git a/assets/coinbase_pro-logo.jpg b/assets/coinbase_pro-logo.jpg new file mode 100644 index 0000000..f825b86 Binary files /dev/null and b/assets/coinbase_pro-logo.jpg differ diff --git a/assets/coinzoom-logo.jpg b/assets/coinzoom-logo.jpg new file mode 100644 index 0000000..844fbeb Binary files /dev/null and b/assets/coinzoom-logo.jpg differ diff --git a/assets/cryptocom-logo.jpg b/assets/cryptocom-logo.jpg new file mode 100644 index 0000000..0ec498c Binary files /dev/null and b/assets/cryptocom-logo.jpg differ diff --git a/assets/digifinex-logo.jpg b/assets/digifinex-logo.jpg new file mode 100644 index 0000000..5f648f8 Binary files /dev/null and b/assets/digifinex-logo.jpg differ diff --git a/assets/dolomite-logo.jpg b/assets/dolomite-logo.jpg new file mode 100644 index 0000000..50aa522 Binary files /dev/null and b/assets/dolomite-logo.jpg differ diff --git a/assets/dydx-logo.jpg b/assets/dydx-logo.jpg new file mode 100644 index 0000000..89799e1 Binary files /dev/null and b/assets/dydx-logo.jpg differ diff --git a/assets/eve_exchange_logo.png b/assets/eve_exchange_logo.png new file mode 100644 index 0000000..0ce59f6 Binary files /dev/null and b/assets/eve_exchange_logo.png differ diff --git a/assets/foxbit-exchange-logo.png b/assets/foxbit-exchange-logo.png new file mode 100644 index 0000000..7edbfb5 Binary files /dev/null and b/assets/foxbit-exchange-logo.png differ diff --git a/assets/ftx-logo.jpg b/assets/ftx-logo.jpg new file mode 100644 index 0000000..63e62da Binary files /dev/null and b/assets/ftx-logo.jpg differ diff --git a/assets/gate-io-logo.jpg b/assets/gate-io-logo.jpg new file mode 100644 index 0000000..9433697 Binary files /dev/null and b/assets/gate-io-logo.jpg differ diff --git a/assets/himalaya_exchange-logo.jpg b/assets/himalaya_exchange-logo.jpg new file mode 100644 index 0000000..136bbea Binary files /dev/null and b/assets/himalaya_exchange-logo.jpg differ diff --git a/assets/hitbtc-logo.jpg b/assets/hitbtc-logo.jpg new file mode 100644 index 0000000..0d11076 Binary files /dev/null and b/assets/hitbtc-logo.jpg differ diff --git a/assets/huobi_global-logo.jpg b/assets/huobi_global-logo.jpg new file mode 100644 index 0000000..08c6a36 Binary files /dev/null and b/assets/huobi_global-logo.jpg differ diff --git a/assets/injective.jpg b/assets/injective.jpg new file mode 100644 index 0000000..3951284 Binary files /dev/null and b/assets/injective.jpg differ diff --git a/assets/kraken-logo.jpg b/assets/kraken-logo.jpg new file mode 100644 index 0000000..1a9a89c Binary files /dev/null and b/assets/kraken-logo.jpg differ diff --git a/assets/kucoin-logo.jpg b/assets/kucoin-logo.jpg new file mode 100644 index 0000000..3c48810 Binary files /dev/null and b/assets/kucoin-logo.jpg differ diff --git a/assets/latoken-logo.png b/assets/latoken-logo.png new file mode 100644 index 0000000..c20723f Binary files /dev/null and b/assets/latoken-logo.png differ diff --git a/assets/lbank.jpg b/assets/lbank.jpg new file mode 100644 index 0000000..15e6d25 Binary files /dev/null and b/assets/lbank.jpg differ diff --git a/assets/lbank.png b/assets/lbank.png new file mode 100644 index 0000000..bee1012 Binary files /dev/null and b/assets/lbank.png differ diff --git a/assets/liquid-logo.jpg b/assets/liquid-logo.jpg new file mode 100644 index 0000000..01c22e7 Binary files /dev/null and b/assets/liquid-logo.jpg differ diff --git a/assets/loopring-logo.jpg b/assets/loopring-logo.jpg new file mode 100644 index 0000000..aa4a103 Binary files /dev/null and b/assets/loopring-logo.jpg differ diff --git a/assets/mexc.jpg b/assets/mexc.jpg new file mode 100644 index 0000000..36d0d00 Binary files /dev/null and b/assets/mexc.jpg differ diff --git a/assets/mm-finance-logo.png b/assets/mm-finance-logo.png new file mode 100644 index 0000000..6928824 Binary files /dev/null and b/assets/mm-finance-logo.png differ diff --git a/assets/ndax-logo.jpg b/assets/ndax-logo.jpg new file mode 100644 index 0000000..39e4ba5 Binary files /dev/null and b/assets/ndax-logo.jpg differ diff --git a/assets/okex-logo.jpg b/assets/okex-logo.jpg new file mode 100644 index 0000000..f4635bc Binary files /dev/null and b/assets/okex-logo.jpg differ diff --git a/assets/pancakeswap-logo.png b/assets/pancakeswap-logo.png new file mode 100644 index 0000000..a17db0f Binary files /dev/null and b/assets/pancakeswap-logo.png differ diff --git a/assets/perpetual_protocol-logo.jpg b/assets/perpetual_protocol-logo.jpg new file mode 100644 index 0000000..8e3d411 Binary files /dev/null and b/assets/perpetual_protocol-logo.jpg differ diff --git a/assets/probit-logo.jpg b/assets/probit-logo.jpg new file mode 100644 index 0000000..5110d66 Binary files /dev/null and b/assets/probit-logo.jpg differ diff --git a/assets/probit_kr-logo.jpg b/assets/probit_kr-logo.jpg new file mode 100644 index 0000000..482fc0f Binary files /dev/null and b/assets/probit_kr-logo.jpg differ diff --git a/assets/quickswap-logo.png b/assets/quickswap-logo.png new file mode 100644 index 0000000..cfa280d Binary files /dev/null and b/assets/quickswap-logo.png differ diff --git a/assets/radar_logo.png b/assets/radar_logo.png new file mode 100644 index 0000000..0cd4880 Binary files /dev/null and b/assets/radar_logo.png differ diff --git a/assets/ref-finance-logo.png b/assets/ref-finance-logo.png new file mode 100644 index 0000000..fbf1269 Binary files /dev/null and b/assets/ref-finance-logo.png differ diff --git a/assets/serum-logo.jpg b/assets/serum-logo.jpg new file mode 100644 index 0000000..cf1a808 Binary files /dev/null and b/assets/serum-logo.jpg differ diff --git a/assets/sushiswap-logo.jpg b/assets/sushiswap-logo.jpg new file mode 100644 index 0000000..75d7c25 Binary files /dev/null and b/assets/sushiswap-logo.jpg differ diff --git a/assets/terra-logo.jpg b/assets/terra-logo.jpg new file mode 100644 index 0000000..d9818a6 Binary files /dev/null and b/assets/terra-logo.jpg differ diff --git a/assets/traderjoe-logo.png b/assets/traderjoe-logo.png new file mode 100644 index 0000000..3fbaa12 Binary files /dev/null and b/assets/traderjoe-logo.png differ diff --git a/assets/uniswap-logo.jpg b/assets/uniswap-logo.jpg new file mode 100644 index 0000000..9b0a1bd Binary files /dev/null and b/assets/uniswap-logo.jpg differ diff --git a/assets/uniswap_v3-logo.jpg b/assets/uniswap_v3-logo.jpg new file mode 100644 index 0000000..d0d1582 Binary files /dev/null and b/assets/uniswap_v3-logo.jpg differ diff --git a/assets/vvs-finance-logo.png b/assets/vvs-finance-logo.png new file mode 100644 index 0000000..8f85bfb Binary files /dev/null and b/assets/vvs-finance-logo.png differ diff --git a/assets/wazirX-logo.jpg b/assets/wazirX-logo.jpg new file mode 100644 index 0000000..e74c8e3 Binary files /dev/null and b/assets/wazirX-logo.jpg differ diff --git a/assets/white-bit.png b/assets/white-bit.png new file mode 100644 index 0000000..fa3c905 Binary files /dev/null and b/assets/white-bit.png differ diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..01f8fd6 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1 @@ +dev.py \ No newline at end of file diff --git a/bin/__init__.py b/bin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/conf_migration_script.py b/bin/conf_migration_script.py new file mode 100755 index 0000000..931a825 --- /dev/null +++ b/bin/conf_migration_script.py @@ -0,0 +1,13 @@ +import argparse + +import path_util # noqa: F401 + +from hummingbot.client.config.conf_migration import migrate_configs +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Migrate the HummingBot confs") + parser.add_argument("password", type=str, help="Required to migrate all encrypted configs.") + args = parser.parse_args() + secrets_manager_ = ETHKeyFileSecretManger(args.password) + migrate_configs(secrets_manager_) diff --git a/bin/hummingbot.py b/bin/hummingbot.py new file mode 100755 index 0000000..52cb112 --- /dev/null +++ b/bin/hummingbot.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +import asyncio +from typing import Coroutine, List, Optional +from weakref import ReferenceType, ref + +import path_util # noqa: F401 + +from hummingbot import chdir_to_data_directory, init_logging +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + create_yml_files_legacy, + load_client_config_map_from_file, + write_config_to_yml, +) +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.client.ui import login_prompt +from hummingbot.client.ui.style import load_style +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import HummingbotUIEvent +from hummingbot.core.utils import detect_available_port +from hummingbot.core.utils.async_utils import safe_gather + + +class UIStartListener(EventListener): + def __init__(self, hummingbot_app: HummingbotApplication, is_script: Optional[bool] = False, is_quickstart: Optional[bool] = False): + super().__init__() + self._hb_ref: ReferenceType = ref(hummingbot_app) + self._is_script = is_script + self._is_quickstart = is_quickstart + + def __call__(self, _): + asyncio.create_task(self.ui_start_handler()) + + @property + def hummingbot_app(self) -> HummingbotApplication: + return self._hb_ref() + + async def ui_start_handler(self): + hb: HummingbotApplication = self.hummingbot_app + if hb.strategy_name is not None: + if not self._is_script: + write_config_to_yml(hb.strategy_config_map, hb.strategy_file_name, hb.client_config_map) + hb.start(log_level=hb.client_config_map.log_level, + script=hb.strategy_name if self._is_script else None, + is_quickstart=self._is_quickstart) + + +async def main_async(client_config_map: ClientConfigAdapter): + await create_yml_files_legacy() + + # This init_logging() call is important, to skip over the missing config warnings. + init_logging("hummingbot_logs.yml", client_config_map) + + AllConnectorSettings.initialize_paper_trade_settings(client_config_map.paper_trade.paper_trade_exchanges) + + hb = HummingbotApplication.main_application(client_config_map) + + # The listener needs to have a named variable for keeping reference, since the event listener system + # uses weak references to remove unneeded listeners. + start_listener: UIStartListener = UIStartListener(hb) + hb.app.add_listener(HummingbotUIEvent.Start, start_listener) + + tasks: List[Coroutine] = [hb.run()] + if client_config_map.debug_console: + if not hasattr(__builtins__, "help"): + import _sitebuiltins + __builtins__["help"] = _sitebuiltins._Helper() + + from hummingbot.core.management.console import start_management_console + management_port: int = detect_available_port(8211) + tasks.append(start_management_console(locals(), host="localhost", port=management_port)) + await safe_gather(*tasks) + + +def main(): + chdir_to_data_directory() + secrets_manager_cls = ETHKeyFileSecretManger + + try: + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + except Exception: + ev_loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() + asyncio.set_event_loop(ev_loop) + + # We need to load a default style for the login screen because the password is required to load the + # real configuration now that it can include secret parameters + style = load_style(ClientConfigAdapter(ClientConfigMap())) + + if login_prompt(secrets_manager_cls, style=style): + client_config_map = load_client_config_map_from_file() + ev_loop.run_until_complete(main_async(client_config_map)) + + +if __name__ == "__main__": + main() diff --git a/bin/hummingbot_quickstart.py b/bin/hummingbot_quickstart.py new file mode 100755 index 0000000..9bb78f4 --- /dev/null +++ b/bin/hummingbot_quickstart.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +import argparse +import asyncio +import grp +import logging +import os +import pwd +import subprocess +from pathlib import Path +from typing import Coroutine, List + +import path_util # noqa: F401 + +from bin.hummingbot import UIStartListener, detect_available_port +from hummingbot import init_logging +from hummingbot.client.config.config_crypt import BaseSecretsManager, ETHKeyFileSecretManger +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + all_configs_complete, + create_yml_files_legacy, + load_client_config_map_from_file, + load_strategy_config_map_from_file, + read_system_configs_from_yml, +) +from hummingbot.client.config.security import Security +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.client.settings import STRATEGIES_CONF_DIR_PATH, AllConnectorSettings +from hummingbot.client.ui import login_prompt +from hummingbot.client.ui.style import load_style +from hummingbot.core.event.events import HummingbotUIEvent +from hummingbot.core.management.console import start_management_console +from hummingbot.core.utils.async_utils import safe_gather + + +class CmdlineParser(argparse.ArgumentParser): + def __init__(self): + super().__init__() + self.add_argument("--config-file-name", "-f", + type=str, + required=False, + help="Specify a file in `conf/` to load as the strategy config file.") + self.add_argument("--config-password", "-p", + type=str, + required=False, + help="Specify the password to unlock your encrypted files.") + self.add_argument("--auto-set-permissions", + type=str, + required=False, + help="Try to automatically set config / logs / data dir permissions, " + "useful for Docker containers.") + + +def autofix_permissions(user_group_spec: str): + uid, gid = [sub_str for sub_str in user_group_spec.split(':')] + + uid = int(uid) if uid.isnumeric() else pwd.getpwnam(uid).pw_uid + gid = int(gid) if gid.isnumeric() else grp.getgrnam(gid).gr_gid + + os.environ["HOME"] = pwd.getpwuid(uid).pw_dir + project_home: str = os.path.realpath(os.path.join(__file__, "../../")) + + gateway_path: str = Path.home().joinpath(".hummingbot-gateway").as_posix() + subprocess.run( + f"cd '{project_home}' && " + f"sudo chown -R {user_group_spec} conf/ data/ logs/ scripts/ {gateway_path}", + capture_output=True, + shell=True + ) + os.setgid(gid) + os.setuid(uid) + + +async def quick_start(args: argparse.Namespace, secrets_manager: BaseSecretsManager): + config_file_name = args.config_file_name + client_config_map = load_client_config_map_from_file() + + if args.auto_set_permissions is not None: + autofix_permissions(args.auto_set_permissions) + + if not Security.login(secrets_manager): + logging.getLogger().error("Invalid password.") + return + + await Security.wait_til_decryption_done() + await create_yml_files_legacy() + init_logging("hummingbot_logs.yml", client_config_map) + await read_system_configs_from_yml() + + AllConnectorSettings.initialize_paper_trade_settings(client_config_map.paper_trade.paper_trade_exchanges) + + hb = HummingbotApplication.main_application(client_config_map=client_config_map) + # Todo: validate strategy and config_file_name before assinging + + strategy_config = None + is_script = False + if config_file_name is not None: + hb.strategy_file_name = config_file_name + if config_file_name.split(".")[-1] == "py": + hb.strategy_name = hb.strategy_file_name + is_script = True + else: + strategy_config = await load_strategy_config_map_from_file( + STRATEGIES_CONF_DIR_PATH / config_file_name + ) + hb.strategy_name = ( + strategy_config.strategy + if isinstance(strategy_config, ClientConfigAdapter) + else strategy_config.get("strategy").value + ) + hb.strategy_config_map = strategy_config + + if strategy_config is not None: + if not all_configs_complete(strategy_config, hb.client_config_map): + hb.status() + + # The listener needs to have a named variable for keeping reference, since the event listener system + # uses weak references to remove unneeded listeners. + start_listener: UIStartListener = UIStartListener(hb, is_script=is_script, is_quickstart=True) + hb.app.add_listener(HummingbotUIEvent.Start, start_listener) + + tasks: List[Coroutine] = [hb.run()] + if client_config_map.debug_console: + management_port: int = detect_available_port(8211) + tasks.append(start_management_console(locals(), host="localhost", port=management_port)) + + await safe_gather(*tasks) + + +def main(): + args = CmdlineParser().parse_args() + + # Parse environment variables from Dockerfile. + # If an environment variable is not empty and it's not defined in the arguments, then we'll use the environment + # variable. + if args.config_file_name is None and len(os.environ.get("CONFIG_FILE_NAME", "")) > 0: + args.config_file_name = os.environ["CONFIG_FILE_NAME"] + if args.config_password is None and len(os.environ.get("CONFIG_PASSWORD", "")) > 0: + args.config_password = os.environ["CONFIG_PASSWORD"] + + # If no password is given from the command line, prompt for one. + secrets_manager_cls = ETHKeyFileSecretManger + client_config_map = load_client_config_map_from_file() + if args.config_password is None: + secrets_manager = login_prompt(secrets_manager_cls, style=load_style(client_config_map)) + if not secrets_manager: + return + else: + secrets_manager = secrets_manager_cls(args.config_password) + + try: + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + except Exception: + ev_loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() + asyncio.set_event_loop(ev_loop) + + ev_loop.run_until_complete(quick_start(args, secrets_manager)) + + +if __name__ == "__main__": + main() diff --git a/bin/path_util.py b/bin/path_util.py new file mode 100644 index 0000000..545d844 --- /dev/null +++ b/bin/path_util.py @@ -0,0 +1,16 @@ +#!/usr/bin/python + +if "hummingbot-dist" in __file__: + # Dist environment. + import os + import sys + sys.path.append(sys.path.pop(0)) + sys.path.insert(0, os.getcwd()) + + import hummingbot + hummingbot.set_prefix_path(os.getcwd()) +else: + # Dev environment. + from os.path import join, realpath + import sys + sys.path.insert(0, realpath(join(__file__, "../../"))) diff --git a/clean b/clean new file mode 100755 index 0000000..a557d34 --- /dev/null +++ b/clean @@ -0,0 +1,11 @@ +#!/bin/bash + +cd $(dirname "$0") + +echo "Cleaning up all intermediate and compiled files generated from Cython compilation..." + +rm -rf build dist +rm -f $(find . -name "*.pyx" | sed s/\.pyx$/\*\.cpp/g) +find . \( -name "*.so" -o -name "*.pyd" \) -exec rm -f {} \; + +echo "Done!" diff --git a/compile b/compile new file mode 100755 index 0000000..c4a9e8c --- /dev/null +++ b/compile @@ -0,0 +1,5 @@ +#!/bin/bash + +cd $(dirname "$0") + +python setup.py build_ext --inplace diff --git a/compile.bat b/compile.bat new file mode 100644 index 0000000..53d2289 --- /dev/null +++ b/compile.bat @@ -0,0 +1,3 @@ +@echo off + +python setup.py build_ext --inplace -j 8 diff --git a/conf/.gitignore b/conf/.gitignore new file mode 100644 index 0000000..4d56153 --- /dev/null +++ b/conf/.gitignore @@ -0,0 +1,3 @@ +*.yml +*.json +.password_verification diff --git a/conf/__init__.py b/conf/__init__.py new file mode 100644 index 0000000..8b9f260 --- /dev/null +++ b/conf/__init__.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +import logging as _logging +import os + +_logger = _logging.getLogger(__name__) + +master_host = "***REMOVED***" +master_user = "***REMOVED***" +master_password = "***REMOVED***" +master_db = "***REMOVED***" + +slave_host = "127.0.0.1" +slave_user = "reader" +slave_password = "falcon" +slave_db = "falcon" + +mysql_master_server = "***REMOVED***" +mysql_slave_server = "***REMOVED***" + +mysql_user = "***REMOVED***" +mysql_password = "***REMOVED***" +mysql_db = "***REMOVED***" + +order_book_db = "***REMOVED***" +sparrow_db = "***REMOVED***" + +order_books_db_2 = { + "host": "***REMOVED***", + "user": "***REMOVED***", + "password": "***REMOVED***", + "db": "**REMOVED***", +} + +# whether to enable api mocking in unit test cases +mock_api_enabled = os.getenv("MOCK_API_ENABLED") + +""" +# AscendEX Tests +ascend_ex_api_key = os.getenv("ASCEND_EX_KEY") +ascend_ex_secret_key = os.getenv("ASCEND_EX_SECRET") + +# Binance Tests +binance_api_key = os.getenv("BINANCE_API_KEY") +binance_api_secret = os.getenv("BINANCE_API_SECRET") + +# Binance Perpetuals Tests +binance_perpetuals_api_key = os.getenv("BINANCE_PERPETUALS_API_KEY") +binance_perpetuals_api_secret = os.getenv("BINANCE_PERPETUALS_API_SECRET") + +# Coinbase Pro Tests +coinbase_pro_api_key = os.getenv("COINBASE_PRO_API_KEY") +coinbase_pro_secret_key = os.getenv("COINBASE_PRO_SECRET_KEY") +coinbase_pro_passphrase = os.getenv("COINBASE_PRO_PASSPHRASE") + + +# Huobi Tests +huobi_api_key = os.getenv("HUOBI_API_KEY") +huobi_secret_key = os.getenv("HUOBI_SECRET_KEY") + +# Bittrex Tests +bittrex_api_key = os.getenv("BITTREX_API_KEY") +bittrex_secret_key = os.getenv("BITTREX_SECRET_KEY") + +# KuCoin Tests +kucoin_api_key = os.getenv("KUCOIN_API_KEY") +kucoin_secret_key = os.getenv("KUCOIN_SECRET_KEY") +kucoin_passphrase = os.getenv("KUCOIN_PASSPHRASE") + +test_web3_provider_list = [os.getenv("WEB3_PROVIDER")] + +# Kraken Tests +kraken_api_key = os.getenv("KRAKEN_API_KEY") +kraken_secret_key = os.getenv("KRAKEN_SECRET_KEY") + +# OKX Test +okx_api_key = os.getenv("OKX_API_KEY") +okx_secret_key = os.getenv("OKX_SECRET_KEY") +okx_passphrase = os.getenv("OKX_PASSPHRASE") + +# BitMart Test +bitmart_api_key = os.getenv("BITMART_API_KEY") +bitmart_secret_key = os.getenv("BITMART_SECRET_KEY") +bitmart_memo = os.getenv("BITMART_MEMO") + +# BTC Markets Test +btc_markets_api_key = os.getenv("BTC_MARKETS_API_KEY") +btc_markets_secret_key = os.getenv("BTC_MARKETS_SECRET_KEY") + +# HitBTC Tests +hitbtc_api_key = os.getenv("HITBTC_API_KEY") +hitbtc_secret_key = os.getenv("HITBTC_SECRET_KEY") + +# Gate.io Tests +gate_io_api_key = os.getenv("GATE_IO_API_KEY") +gate_io_secret_key = os.getenv("GATE_IO_SECRET_KEY") + +# Wallet Tests +test_erc20_token_address = os.getenv("TEST_ERC20_TOKEN_ADDRESS") +web3_test_private_key_a = os.getenv("TEST_WALLET_PRIVATE_KEY_A") +web3_test_private_key_b = os.getenv("TEST_WALLET_PRIVATE_KEY_B") +web3_test_private_key_c = os.getenv("TEST_WALLET_PRIVATE_KEY_C") + +coinalpha_order_book_api_username = "***REMOVED***" +coinalpha_order_book_api_password = "***REMOVED***" +""" diff --git a/conf/connectors/.gitignore b/conf/connectors/.gitignore new file mode 100644 index 0000000..1cda54b --- /dev/null +++ b/conf/connectors/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/conf/connectors/__init__.py b/conf/connectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/conf/strategies/.gitignore b/conf/strategies/.gitignore new file mode 100644 index 0000000..1cda54b --- /dev/null +++ b/conf/strategies/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/conf/strategies/__init__.py b/conf/strategies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ac82395 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +version: "3.9" +services: + hummingbot: + container_name: hummingbot + build: + context: . + dockerfile: Dockerfile + volumes: + - ./conf:/home/hummingbot/conf + - ./conf/connectors:/home/hummingbot/conf/connectors + - ./conf/strategies:/home/hummingbot/conf/strategies + - ./logs:/home/hummingbot/logs + - ./data:/home/hummingbot/data + - ./scripts:/home/hummingbot/scripts + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "5" + tty: true + stdin_open: true + network_mode: host + # environment: + # - CONFIG_PASSWORD=a + # - CONFIG_FILE_NAME=simple_pmm_example.py + + # dashboard: + # container_name: dashboard + # image: hummingbot/dashboard:latest + # volumes: + # - ./data:/home/dashboard/data + # ports: + # - "8501:8501" + + # gateway: + # container_name: gateway + # image: hummingbot/gateway:latest + # ports: + # - "15888:15888" + # - "8080:8080" + # volumes: + # - "./gateway_files/conf:/home/gateway/conf" + # - "./gateway_files/logs:/home/gateway/logs" + # - "./gateway_files/db:/home/gateway/db" + # - "./certs:/home/gateway/certs" + # environment: + # - GATEWAY_PASSPHRASE=a \ No newline at end of file diff --git a/docker/etc/sudoers.d/hummingbot b/docker/etc/sudoers.d/hummingbot new file mode 100644 index 0000000..0559cc5 --- /dev/null +++ b/docker/etc/sudoers.d/hummingbot @@ -0,0 +1 @@ +hummingbot ALL=(ALL:ALL) NOPASSWD: /bin/chmod, /bin/chown diff --git a/hooks/README.md b/hooks/README.md new file mode 100644 index 0000000..3f4e834 --- /dev/null +++ b/hooks/README.md @@ -0,0 +1,5 @@ +# Docker Autobuild Hooks + +This folder containers hooks for docker autobuild. + +[Hummingbot builds](https://hub.docker.com/r/coinalpha/hummingbot/builds) \ No newline at end of file diff --git a/hooks/build b/hooks/build new file mode 100644 index 0000000..d31653c --- /dev/null +++ b/hooks/build @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +# Get the build time stamp +BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +docker build --build-arg COMMIT=$GIT_TAG --build-arg BRANCH=$SOURCE_BRANCH --build-arg BUILD_DATE=$BUILD_DATE -t $IMAGE_NAME -f Dockerfile . \ No newline at end of file diff --git a/hummingbot/README.md b/hummingbot/README.md new file mode 100644 index 0000000..78073bf --- /dev/null +++ b/hummingbot/README.md @@ -0,0 +1,29 @@ +# Hummingbot Source Code + +This folder contains the main source code for Hummingbot. + +## Project Breakdown +``` +hummingbot +├── client # CLI related files +├── core +│ ├── cpp # high performance data types written in .cpp +│ ├── data_type # key data +│ ├── event # defined events and event-tracking related files +│ └── utils # helper functions and bot plugins +├── data_feed # price feeds such as CoinCap +├── logger # handles logging functionality +├── market # connectors to individual exchanges +│ └── # folder for specific exchange ("market") +│ ├── *_market # handles trade execution (buy/sell/cancel) +│ ├── *_data_source # initializes and maintains a websocket connection +│ ├── *_order_book # takes order book data and formats it with a standard API +│ ├── *_order_book_tracker # maintains a copy of the market's real-time order book +│ ├── *_active_order_tracker # for DEXes that require keeping track of +│ └── *_user_stream_tracker # tracker that process data specific to the user running the bot +├── notifier # connectors to services that sends notifications such as Telegram +├── strategy # high level strategies that works with every market +├── templates # templates for config files: general, strategy, and logging +└── wallet # files that read from and submit transactions to blockchains + └── ethereum # files that interact with the ethereum blockchain +``` diff --git a/hummingbot/VERSION b/hummingbot/VERSION new file mode 100644 index 0000000..57807d6 --- /dev/null +++ b/hummingbot/VERSION @@ -0,0 +1 @@ +1.22.0 diff --git a/hummingbot/__init__.py b/hummingbot/__init__.py new file mode 100644 index 0000000..012045b --- /dev/null +++ b/hummingbot/__init__.py @@ -0,0 +1,182 @@ +import logging +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor +from os import listdir, path +from pathlib import Path +from typing import TYPE_CHECKING, List, Optional + +from hummingbot.logger.struct_logger import StructLogger, StructLogRecord + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter as _ClientConfigAdapter + +STRUCT_LOGGER_SET = False +DEV_STRATEGY_PREFIX = "dev" +_prefix_path = None + +# Do not raise exceptions during log handling +logging.setLogRecordFactory(StructLogRecord) +logging.setLoggerClass(StructLogger) + +_shared_executor = None +_data_path = None +_cert_path = None + + +def root_path() -> Path: + from os.path import join, realpath + return Path(realpath(join(__file__, "../../"))) + + +def get_executor() -> ThreadPoolExecutor: + global _shared_executor + if _shared_executor is None: + _shared_executor = ThreadPoolExecutor() + return _shared_executor + + +def prefix_path() -> str: + global _prefix_path + if _prefix_path is None: + from os.path import join, realpath + _prefix_path = realpath(join(__file__, "../../")) + return _prefix_path + + +def set_prefix_path(p: str): + global _prefix_path + _prefix_path = p + + +def data_path() -> str: + global _data_path + if _data_path is None: + from os.path import join, realpath + _data_path = realpath(join(prefix_path(), "data")) + + import os + if not os.path.exists(_data_path): + os.makedirs(_data_path) + return _data_path + + +def set_data_path(path: str): + global _data_path + _data_path = path + + +_independent_package: Optional[bool] = None + + +def is_independent_package() -> bool: + global _independent_package + import os + if _independent_package is None: + _independent_package = not os.path.basename(sys.executable).startswith("python") + return _independent_package + + +def check_dev_mode(): + try: + if is_independent_package(): + return False + if not path.isdir(".git"): + return False + current_branch = subprocess.check_output(["git", "symbolic-ref", "--short", "HEAD"]).decode("utf8").rstrip() + if current_branch != "master": + return True + except Exception: + return False + + +def chdir_to_data_directory(): + if not is_independent_package(): + # Do nothing. + return + + import os + + import appdirs + app_data_dir: str = appdirs.user_data_dir("Hummingbot", "hummingbot.io") + os.makedirs(os.path.join(app_data_dir, "logs"), 0o711, exist_ok=True) + os.makedirs(os.path.join(app_data_dir, "conf"), 0o711, exist_ok=True) + os.makedirs(os.path.join(app_data_dir, "pmm_scripts"), 0o711, exist_ok=True) + os.makedirs(os.path.join(app_data_dir, "certs"), 0o711, exist_ok=True) + os.makedirs(os.path.join(app_data_dir, "scripts"), 0o711, exist_ok=True) + os.chdir(app_data_dir) + set_prefix_path(app_data_dir) + + +def get_logging_conf(conf_filename: str = 'hummingbot_logs.yml'): + import io + from os.path import join + from typing import Dict + + from ruamel.yaml import YAML + + file_path: str = join(prefix_path(), "conf", conf_filename) + yaml_parser: YAML = YAML() + if not path.exists(file_path): + return {} + with open(file_path) as fd: + yml_source: str = fd.read() + io_stream: io.StringIO = io.StringIO(yml_source) + config_dict: Dict = yaml_parser.load(io_stream) + return config_dict + + +def init_logging(conf_filename: str, + client_config_map: "_ClientConfigAdapter", + override_log_level: Optional[str] = None, + strategy_file_path: str = "hummingbot"): + import io + import logging.config + from os.path import join + from typing import Dict + + import pandas as pd + from ruamel.yaml import YAML + + from hummingbot.logger.struct_logger import StructLogger, StructLogRecord + global STRUCT_LOGGER_SET + if not STRUCT_LOGGER_SET: + logging.setLogRecordFactory(StructLogRecord) + logging.setLoggerClass(StructLogger) + STRUCT_LOGGER_SET = True + + # Do not raise exceptions during log handling + logging.raiseExceptions = False + + file_path: str = join(prefix_path(), "conf", conf_filename) + yaml_parser: YAML = YAML() + with open(file_path) as fd: + yml_source: str = fd.read() + yml_source = yml_source.replace("$PROJECT_DIR", prefix_path()) + yml_source = yml_source.replace("$DATETIME", pd.Timestamp.now().strftime("%Y-%m-%d-%H-%M-%S")) + yml_source = yml_source.replace("$STRATEGY_FILE_PATH", strategy_file_path.replace(".yml", "")) + io_stream: io.StringIO = io.StringIO(yml_source) + config_dict: Dict = yaml_parser.load(io_stream) + if override_log_level is not None and "loggers" in config_dict: + for logger in config_dict["loggers"]: + if logger in client_config_map.logger_override_whitelist: + config_dict["loggers"][logger]["level"] = override_log_level + logging.config.dictConfig(config_dict) + + +def get_strategy_list() -> List[str]: + """ + Search `hummingbot.strategy` folder for all available strategies + Automatically hide all strategies that starts with "dev" if on master branch + """ + try: + folder = path.realpath(path.join(__file__, "../strategy")) + # Only include valid directories + strategies = [d for d in listdir(folder) if path.isdir(path.join(folder, d)) and not d.startswith("__")] + on_dev_mode = check_dev_mode() + if not on_dev_mode: + strategies = [s for s in strategies if not s.startswith(DEV_STRATEGY_PREFIX)] + return sorted(strategies) + except Exception as e: + logging.getLogger().warning(f"Error getting strategy set: {str(e)}") + return [] diff --git a/hummingbot/client/__init__.py b/hummingbot/client/__init__.py new file mode 100644 index 0000000..8fa69c0 --- /dev/null +++ b/hummingbot/client/__init__.py @@ -0,0 +1,26 @@ +import logging + +import pandas as pd +import decimal + +FLOAT_PRINTOUT_PRECISION = 8 + + +def format_decimal(n): + """ + Convert the given float to a string without scientific notation + """ + try: + with decimal.localcontext() as ctx: + if isinstance(n, float): + n = ctx.create_decimal(n) + if isinstance(n, decimal.Decimal): + n = round(n, FLOAT_PRINTOUT_PRECISION) + return format(n.normalize(), 'f') + else: + return str(n) + except Exception as e: + logging.getLogger().error(str(e)) + + +pd.options.display.float_format = lambda x: format_decimal(x) diff --git a/hummingbot/client/command/__init__.py b/hummingbot/client/command/__init__.py new file mode 100644 index 0000000..6c4d91b --- /dev/null +++ b/hummingbot/client/command/__init__.py @@ -0,0 +1,43 @@ +from .balance_command import BalanceCommand +from .config_command import ConfigCommand +from .connect_command import ConnectCommand +from .create_command import CreateCommand +from .exit_command import ExitCommand +from .export_command import ExportCommand +from .gateway_command import GatewayCommand +from .help_command import HelpCommand +from .history_command import HistoryCommand +from .import_command import ImportCommand +from .mqtt_command import MQTTCommand +from .order_book_command import OrderBookCommand +from .pmm_script_command import PMMScriptCommand +from .previous_strategy_command import PreviousCommand +from .rate_command import RateCommand +from .silly_commands import SillyCommands +from .start_command import StartCommand +from .status_command import StatusCommand +from .stop_command import StopCommand +from .ticker_command import TickerCommand + +__all__ = [ + BalanceCommand, + ConfigCommand, + ConnectCommand, + CreateCommand, + ExitCommand, + ExportCommand, + GatewayCommand, + HelpCommand, + HistoryCommand, + ImportCommand, + OrderBookCommand, + PMMScriptCommand, + PreviousCommand, + RateCommand, + SillyCommands, + StartCommand, + StatusCommand, + StopCommand, + TickerCommand, + MQTTCommand, +] diff --git a/hummingbot/client/command/balance_command.py b/hummingbot/client/command/balance_command.py new file mode 100644 index 0000000..7e04ebc --- /dev/null +++ b/hummingbot/client/command/balance_command.py @@ -0,0 +1,219 @@ +import asyncio +import threading +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, List + +import pandas as pd + +from hummingbot.client.config.config_validators import validate_decimal, validate_exchange +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.user.user_balances import UserBalances + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + +OPTIONS = [ + "limit", + "paper" +] + + +class BalanceCommand: + def balance(self, # type: HummingbotApplication + option: str = None, + args: List[str] = None + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.balance, option, args) + return + + self.app.clear_input() + if option is None: + safe_ensure_future(self.show_balances()) + + elif option in OPTIONS: + if option == "limit": + balance_asset_limit = self.client_config_map.balance_asset_limit + if args is None or len(args) == 0: + safe_ensure_future(self.show_asset_limits()) + return + if len(args) != 3 or validate_exchange(args[0]) is not None or validate_decimal(args[2]) is not None: + self.notify("Error: Invalid command arguments") + self.notify_balance_limit_set() + return + exchange = args[0] + asset = args[1].upper() + amount = float(args[2]) + if balance_asset_limit.get(exchange) is None: + balance_asset_limit[exchange] = {} + if amount < 0 and asset in balance_asset_limit[exchange].keys(): + balance_asset_limit[exchange].pop(asset) + self.notify(f"Limit for {asset} on {exchange} exchange removed.") + elif amount >= 0: + balance_asset_limit[exchange][asset] = amount + self.notify(f"Limit for {asset} on {exchange} exchange set to {amount}") + self.save_client_config() + + elif option == "paper": + paper_balances = self.client_config_map.paper_trade.paper_trade_account_balance + if args is None or len(args) == 0: + safe_ensure_future(self.show_paper_account_balance()) + return + if len(args) != 2 or validate_decimal(args[1]) is not None: + self.notify("Error: Invalid command arguments") + self.notify_balance_paper_set() + return + asset = args[0].upper() + amount = float(args[1]) + paper_balances[asset] = amount + self.notify(f"Paper balance for {asset} token set to {amount}") + self.save_client_config() + + async def show_balances( + self # type: HummingbotApplication + ): + global_token_symbol = self.client_config_map.global_token.global_token_symbol + total_col_name = f"Total ({global_token_symbol})" + sum_not_for_show_name = "sum_not_for_show" + self.notify("Updating balances, please wait...") + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + all_ex_bals = await asyncio.wait_for( + UserBalances.instance().all_balances_all_exchanges(self.client_config_map), network_timeout + ) + except asyncio.TimeoutError: + self.notify("\nA network error prevented the balances to update. See logs for more details.") + raise + all_ex_avai_bals = UserBalances.instance().all_available_balances_all_exchanges() + + exchanges_total = 0 + + for exchange, bals in all_ex_bals.items(): + self.notify(f"\n{exchange}:") + df, allocated_total = await self.exchange_balances_extra_df(exchange, bals, all_ex_avai_bals.get(exchange, {})) + if df.empty: + self.notify("You have no balance on this exchange.") + else: + lines = [ + " " + line for line in df.drop(sum_not_for_show_name, axis=1).to_string(index=False).split("\n") + ] + self.notify("\n".join(lines)) + self.notify(f"\n Total: {global_token_symbol} " + f"{PerformanceMetrics.smart_round(df[total_col_name].sum())}") + allocated_percentage = 0 + if df[sum_not_for_show_name].sum() != Decimal("0"): + allocated_percentage = allocated_total / df[sum_not_for_show_name].sum() + self.notify(f"Allocated: {allocated_percentage:.2%}") + exchanges_total += df[total_col_name].sum() + + self.notify(f"\n\nExchanges Total: {global_token_symbol} {exchanges_total:.0f} ") + + async def exchange_balances_extra_df(self, # type: HummingbotApplication + exchange: str, + ex_balances: Dict[str, Decimal], + ex_avai_balances: Dict[str, Decimal]): + conn_setting = AllConnectorSettings.get_connector_settings()[exchange] + global_token_symbol = self.client_config_map.global_token.global_token_symbol + total_col_name = f"Total ({global_token_symbol})" + allocated_total = Decimal("0") + rows = [] + for token, bal in ex_balances.items(): + avai = Decimal(ex_avai_balances.get(token.upper(), 0)) if ex_avai_balances is not None else Decimal(0) + # show zero balances if it is a gateway connector (the user manually + # chose to show those values with 'gateway connector-tokens') + if conn_setting.uses_gateway_generic_connector(): + if bal == Decimal(0): + allocated = "0%" + else: + allocated = f"{(bal - avai) / bal:.0%}" + else: + # the exchange is CEX. Only show balance if non-zero. + if bal == Decimal(0): + continue + allocated = f"{(bal - avai) / bal:.0%}" + + rate = await RateOracle.get_instance().get_rate(base_token=token) + rate = Decimal("0") if rate is None else rate + global_value = rate * bal + allocated_total += rate * (bal - avai) + rows.append({"Asset": token.upper(), + "Total": round(bal, 4), + total_col_name: PerformanceMetrics.smart_round(global_value), + "sum_not_for_show": global_value, + "Allocated": allocated, + }) + df = pd.DataFrame(data=rows, columns=["Asset", "Total", total_col_name, "sum_not_for_show", "Allocated"]) + df.sort_values(by=["Asset"], inplace=True) + return df, allocated_total + + async def asset_limits_df(self, + asset_limit_conf: Dict[str, str]): + rows = [] + for token, amount in asset_limit_conf.items(): + rows.append({"Asset": token, "Limit": round(Decimal(amount), 4)}) + + df = pd.DataFrame(data=rows, columns=["Asset", "Limit"]) + df.sort_values(by=["Asset"], inplace=True) + return df + + async def show_asset_limits( + self # type: HummingbotApplication + ): + exchange_limit_conf = self.client_config_map.balance_asset_limit + + if not any(list(exchange_limit_conf.values())): + self.notify("You have not set any limits.") + self.notify_balance_limit_set() + return + + self.notify("Balance Limits per exchange...") + + for exchange, asset_limit_config in exchange_limit_conf.items(): + if asset_limit_config is None: + continue + + self.notify(f"\n{exchange}") + df = await self.asset_limits_df(asset_limit_config) + if df.empty: + self.notify("You have no limits on this exchange.") + else: + lines = [" " + line for line in df.to_string(index=False).split("\n")] + self.notify("\n".join(lines)) + self.notify("\n") + return + + async def paper_acccount_balance_df(self, paper_balances: Dict[str, Decimal]): + rows = [] + for asset, balance in paper_balances.items(): + rows.append({"Asset": asset, "Balance": round(Decimal(str(balance)), 4)}) + df = pd.DataFrame(data=rows, columns=["Asset", "Balance"]) + df.sort_values(by=["Asset"], inplace=True) + return df + + def notify_balance_limit_set(self): + self.notify("To set a balance limit (how much the bot can use): \n" + " balance limit [EXCHANGE] [ASSET] [AMOUNT]\n" + "e.g. balance limit binance BTC 0.1") + + def notify_balance_paper_set(self): + self.notify("To set a paper account balance: \n" + " balance paper [ASSET] [AMOUNT]\n" + "e.g. balance paper BTC 0.1") + + async def show_paper_account_balance( + self # type: HummingbotApplication + ): + paper_balances = self.client_config_map.paper_trade.paper_trade_account_balance + if not paper_balances: + self.notify("You have not set any paper account balance.") + self.notify_balance_paper_set() + return + self.notify("Paper account balances:") + df = await self.paper_acccount_balance_df(paper_balances) + lines = [" " + line for line in df.to_string(index=False).split("\n")] + self.notify("\n".join(lines)) + self.notify("\n") + return diff --git a/hummingbot/client/command/config_command.py b/hummingbot/client/command/config_command.py new file mode 100644 index 0000000..e86926d --- /dev/null +++ b/hummingbot/client/command/config_command.py @@ -0,0 +1,488 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import pandas as pd +from prompt_toolkit.utils import is_windows + +from hummingbot.client.command.gateway_command import GatewayCommand +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + missing_required_configs_legacy, + save_to_yml, + save_to_yml_legacy, +) +from hummingbot.client.config.config_validators import validate_bool, validate_decimal +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.security import Security +from hummingbot.client.config.strategy_config_data_types import BaseTradingStrategyConfigMap +from hummingbot.client.settings import CLIENT_CONFIG_PATH, STRATEGIES_CONF_DIR_PATH +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.client.ui.style import load_style +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.utils import map_df_to_str +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.model.inventory_cost import InventoryCost +from hummingbot.strategy.perpetual_market_making import PerpetualMarketMakingStrategy +from hummingbot.strategy.pure_market_making import PureMarketMakingStrategy +from hummingbot.user.user_balances import UserBalances + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + +no_restart_pmm_keys_in_percentage = ["bid_spread", "ask_spread", "order_level_spread", "inventory_target_base_pct"] +no_restart_pmm_keys = ["order_amount", + "order_levels", + "filled_order_delay", + "inventory_skew_enabled", + "inventory_range_multiplier", + "price_ceiling", + "price_floor", + "moving_price_band_enabled", + "price_ceiling_pct", + "price_floor_pct", + "price_band_refresh_time" + "order_optimization_enabled", + "bid_order_optimization_depth", + "ask_order_optimization_depth" + ] +client_configs_to_display = ["autofill_import", + "kill_switch_mode", + "kill_switch_rate", + "telegram_mode", + "telegram_token", + "telegram_chat_id", + "mqtt_bridge", + "mqtt_host", + "mqtt_port", + "mqtt_namespace", + "mqtt_username", + "mqtt_password", + "mqtt_ssl", + "mqtt_logger", + "mqtt_notifier", + "mqtt_commands", + "mqtt_events", + "mqtt_external_events", + "mqtt_autostart", + "instance_id", + "send_error_logs", + "pmm_script_mode", + "pmm_script_file_path", + "ethereum_chain_name", + "gateway", + "gateway_api_host", + "gateway_api_port", + "rate_oracle_source", + "extra_tokens", + "fetch_pairs_from_all_exchanges", + "global_token", + "global_token_name", + "global_token_symbol", + "rate_limits_share_pct", + "commands_timeout", + "create_command_timeout", + "other_commands_timeout", + "tables_format", + "tick_size", + "market_data_collection", + "market_data_collection_enabled", + "market_data_collection_interval", + "market_data_collection_depth", + ] +color_settings_to_display = ["top_pane", + "bottom_pane", + "output_pane", + "input_pane", + "logs_pane", + "terminal_primary"] +columns = ["Key", "Value"] + + +class ConfigCommand: + def config(self, # type: HummingbotApplication + key: str = None, + value: str = None): + self.app.clear_input() + if key is None: + self.list_configs() + return + else: + if key not in self.configurable_keys(): + self.notify("Invalid key, please choose from the list.") + return + safe_ensure_future(self._config_single_key(key, value), loop=self.ev_loop) + + def list_configs(self, # type: HummingbotApplication + ): + self.list_client_configs() + self.list_strategy_configs() + + def list_client_configs( + self, # type: HummingbotApplication + ): + data = self.build_model_df_data(self.client_config_map, to_print=client_configs_to_display) + df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) + self.notify("\nGlobal Configurations:") + lines = [" " + line for line in format_df_for_printout( + df, + table_format=self.client_config_map.tables_format, + max_col_width=50).split("\n")] + self.notify("\n".join(lines)) + + data = self.build_model_df_data(self.client_config_map, to_print=color_settings_to_display) + df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) + self.notify("\nColor Settings:") + lines = [" " + line for line in format_df_for_printout( + df, + table_format=self.client_config_map.tables_format, + max_col_width=50).split("\n")] + self.notify("\n".join(lines)) + + def list_strategy_configs( + self, # type: HummingbotApplication + ): + if self.strategy_name is not None: + config_map = self.strategy_config_map + data = self.build_df_data_from_config_map(config_map) + df = map_df_to_str(pd.DataFrame(data=data, columns=columns)) + self.notify("\nStrategy Configurations:") + lines = [" " + line for line in format_df_for_printout( + df, + table_format=self.client_config_map.tables_format, + max_col_width=50).split("\n")] + self.notify("\n".join(lines)) + + def build_df_data_from_config_map( + self, # type: HummingbotApplication + config_map: Union[ClientConfigAdapter, Dict[str, ConfigVar]] + ) -> List[Tuple[str, Any]]: + if isinstance(config_map, ClientConfigAdapter): + data = self.build_model_df_data(config_map) + else: # legacy + data = [[cv.printable_key or cv.key, cv.value] for cv in self.strategy_config_map.values() if + not cv.is_secure] + return data + + @staticmethod + def build_model_df_data( + config_map: ClientConfigAdapter, to_print: Optional[List[str]] = None + ) -> List[Tuple[str, Any]]: + model_data = [] + for traversal_item in config_map.traverse(): + if to_print is not None and traversal_item.attr not in to_print: + continue + attr_printout = ( + " " * (traversal_item.depth - 1) + + (u"\u221F " if not is_windows() else " ") + + traversal_item.attr + ) if traversal_item.depth else traversal_item.attr + model_data.append((attr_printout, traversal_item.printable_value)) + return model_data + + def configurable_keys(self, # type: HummingbotApplication + ) -> List[str]: + """ + Returns a list of configurable keys - using config command, excluding exchanges api keys + as they are set from connect command. + """ + keys = [ + traversal_item.config_path + for traversal_item in self.client_config_map.traverse() + if (traversal_item.client_field_data is not None and traversal_item.client_field_data.prompt is not None) + ] + if self.strategy_config_map is not None: + if isinstance(self.strategy_config_map, ClientConfigAdapter): + keys.extend([ + traversal_item.config_path + for traversal_item in self.strategy_config_map.traverse() + if (traversal_item.client_field_data is not None + and traversal_item.client_field_data.prompt is not None) + ]) + else: # legacy + keys.extend( + [c.key for c in self.strategy_config_map.values() if c.prompt is not None and c.key != 'strategy']) + return keys + + async def check_password(self, # type: HummingbotApplication + ): + password = await self.app.prompt(prompt="Enter your password >>> ", is_password=True) + if password != Security.secrets_manager.password.get_secret_value(): + self.notify("Invalid password, please try again.") + return False + else: + return True + + # Make this function static so unit testing can be performed. + @staticmethod + def update_running_mm(mm_strategy, key: str, new_value: Any): + if key in no_restart_pmm_keys_in_percentage: + setattr(mm_strategy, key, new_value / Decimal("100")) + return True + elif key in no_restart_pmm_keys: + setattr(mm_strategy, key, new_value) + return True + return False + + async def _config_single_key(self, # type: HummingbotApplication + key: str, + input_value): + """ + Configure a single variable only. + Prompt the user to finish all configurations if there are remaining empty configs at the end. + """ + + self.placeholder_mode = True + self.app.hide_input = True + + try: + if ( + not isinstance(self.strategy_config_map, (type(None), ClientConfigAdapter)) + and key in self.strategy_config_map + ): + await self._config_single_key_legacy(key, input_value) + else: + client_config_key = key in self.client_config_map.config_paths() + if client_config_key: + config_map = self.client_config_map + file_path = CLIENT_CONFIG_PATH + elif self.strategy is not None: + self.notify("Configuring the strategy while it is running is not currently supported.") + return + else: + config_map = self.strategy_config_map + if self.strategy_file_name is not None: + file_path = STRATEGIES_CONF_DIR_PATH / self.strategy_file_name + else: + self.notify("Strategy file name is not configured.") + return + + if input_value is None: + self.notify("Please follow the prompt to complete configurations: ") + if key == "inventory_target_base_pct": + await self.asset_ratio_maintenance_prompt(config_map, input_value) + elif key == "inventory_price": + await self.inventory_price_prompt(config_map, input_value) + else: + await self.prompt_a_config(config_map, key, input_value, assign_default=False) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + save_to_yml(file_path, config_map) + self.notify("\nNew configuration saved.") + if client_config_key: + self.list_client_configs() + else: + self.list_strategy_configs() + self.app.style = load_style(self.client_config_map) + except asyncio.TimeoutError: + self.logger().error("Prompt timeout") + except Exception as err: + self.logger().error(str(err), exc_info=True) + finally: + self.app.hide_input = False + self.placeholder_mode = False + self.app.change_prompt(prompt=">>> ") + + async def _config_single_key_legacy( + self, # type: HummingbotApplication + key: str, + input_value: Any, + ): # pragma: no cover + config_var, config_map, file_path = None, None, None + if self.strategy_config_map is not None and key in self.strategy_config_map: + config_map = self.strategy_config_map + file_path = STRATEGIES_CONF_DIR_PATH / self.strategy_file_name + config_var = config_map[key] + if config_var.key == "strategy": + self.notify("You cannot change the strategy of a loaded configuration.") + self.notify("Please use 'import xxx.yml' or 'create' to configure the intended strategy") + return + if input_value is None: + self.notify("Please follow the prompt to complete configurations: ") + if config_var.key == "inventory_target_base_pct": + await self.asset_ratio_maintenance_prompt_legacy(config_map, input_value) + elif config_var.key == "inventory_price": + await self.inventory_price_prompt_legacy(config_map, input_value) + else: + await self.prompt_a_config_legacy(config_var, input_value=input_value, assign_default=False) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + missings = missing_required_configs_legacy(config_map) + if missings: + self.notify("\nThere are other configuration required, please follow the prompt to complete them.") + missings = await self._prompt_missing_configs(config_map) + save_to_yml_legacy(str(file_path), config_map) + self.notify("\nNew configuration saved:") + self.notify(f"{key}: {str(config_var.value)}") + self.app.app.style = load_style(self.client_config_map) + for config in missings: + self.notify(f"{config.key}: {str(config.value)}") + if ( + isinstance(self.strategy, PureMarketMakingStrategy) or + isinstance(self.strategy, PerpetualMarketMakingStrategy) + ): + updated = ConfigCommand.update_running_mm(self.strategy, key, config_var.value) + if updated: + self.notify(f"\nThe current {self.strategy_name} strategy has been updated " + f"to reflect the new configuration.") + + async def _prompt_missing_configs(self, # type: HummingbotApplication + config_map): + missings = missing_required_configs_legacy(config_map) + for config in missings: + await self.prompt_a_config_legacy(config) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + if missing_required_configs_legacy(config_map): + return missings + (await self._prompt_missing_configs(config_map)) + return missings + + async def asset_ratio_maintenance_prompt( + self, # type: HummingbotApplication + config_map: BaseTradingStrategyConfigMap, + input_value: Any = None, + ): # pragma: no cover + if input_value: + config_map.inventory_target_base_pct = input_value + else: + exchange = config_map.exchange + market = config_map.market + base, quote = split_hb_trading_pair(market) + if UserBalances.instance().is_gateway_market(exchange): + balances = await GatewayCommand.balance(self, exchange, config_map, base, quote) + else: + balances = await UserBalances.instance().balances(exchange, config_map, base, quote) + if balances is None: + return + base_ratio = await UserBalances.base_amount_ratio(exchange, market, balances) + if base_ratio is None: + return + base_ratio = round(base_ratio, 3) + quote_ratio = 1 - base_ratio + + cvar = ConfigVar(key="temp_config", + prompt=f"On {exchange}, you have {balances.get(base, 0):.4f} {base} and " + f"{balances.get(quote, 0):.4f} {quote}. By market value, " + f"your current inventory split is {base_ratio:.1%} {base} " + f"and {quote_ratio:.1%} {quote}." + f" Would you like to keep this ratio? (Yes/No) >>> ", + required_if=lambda: True, + type_str="bool", + validator=validate_bool) + await self.prompt_a_config_legacy(cvar) + if cvar.value: + config_map.inventory_target_base_pct = round(base_ratio * Decimal('100'), 1) + elif self.app.to_stop_config: + self.app.to_stop_config = False + else: + await self.prompt_a_config(config_map, config="inventory_target_base_pct") + + async def asset_ratio_maintenance_prompt_legacy( + self, # type: HummingbotApplication + config_map, + input_value=None, + ): + if input_value: + config_map['inventory_target_base_pct'].value = Decimal(input_value) + else: + exchange = config_map['exchange'].value + market = config_map["market"].value + base, quote = market.split("-") + if UserBalances.instance().is_gateway_market(exchange): + balances = await GatewayCommand.balance(self, exchange, config_map, base, quote) + else: + balances = await UserBalances.instance().balances(exchange, config_map, base, quote) + if balances is None: + return + base_ratio = await UserBalances.base_amount_ratio(exchange, market, balances) + if base_ratio is None: + return + base_ratio = round(base_ratio, 3) + quote_ratio = 1 - base_ratio + base, quote = config_map["market"].value.split("-") + + cvar = ConfigVar(key="temp_config", + prompt=f"On {exchange}, you have {balances.get(base, 0):.4f} {base} and " + f"{balances.get(quote, 0):.4f} {quote}. By market value, " + f"your current inventory split is {base_ratio:.1%} {base} " + f"and {quote_ratio:.1%} {quote}." + f" Would you like to keep this ratio? (Yes/No) >>> ", + required_if=lambda: True, + type_str="bool", + validator=validate_bool) + await self.prompt_a_config_legacy(cvar) + if cvar.value: + config_map['inventory_target_base_pct'].value = round(base_ratio * Decimal('100'), 1) + else: + if self.app.to_stop_config: + self.app.to_stop_config = False + return + await self.prompt_a_config_legacy(config_map["inventory_target_base_pct"]) + + async def inventory_price_prompt( + self, # type: HummingbotApplication + model: BaseTradingStrategyConfigMap, + input_value=None, + ): + """ + Not currently used. + """ + raise NotImplementedError + + async def inventory_price_prompt_legacy( + self, # type: HummingbotApplication + config_map, + input_value=None, + ): + key = "inventory_price" + if input_value: + config_map[key].value = Decimal(input_value) + else: + exchange = config_map["exchange"].value + market = config_map["market"].value + base_asset, quote_asset = market.split("-") + + if exchange.endswith("paper_trade"): + balances = self.client_config_map.paper_trade.paper_trade_account_balance + elif UserBalances.instance().is_gateway_market(exchange): + balances = await GatewayCommand.balance(self, exchange, config_map, base_asset, quote_asset) + else: + balances = await UserBalances.instance().balances( + exchange, base_asset, quote_asset + ) + if balances.get(base_asset) is None: + return + + cvar = ConfigVar( + key="temp_config", + prompt=f"On {exchange}, you have {balances[base_asset]:.4f} {base_asset}. " + f"What was the price for this amount in {quote_asset}? >>> ", + required_if=lambda: True, + type_str="decimal", + validator=lambda v: validate_decimal( + v, min_value=Decimal("0"), inclusive=True + ), + ) + await self.prompt_a_config_legacy(cvar) + config_map[key].value = cvar.value + + try: + quote_volume = balances[base_asset] * cvar.value + except TypeError: + # TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'NoneType' - bad input / no input + self.notify("Inventory price not updated due to bad input") + return + + with self.trade_fill_db.get_new_session() as session: + with session.begin(): + InventoryCost.add_volume( + session, + base_asset=base_asset, + quote_asset=quote_asset, + base_volume=balances[base_asset], + quote_volume=quote_volume, + overwrite=True, + ) diff --git a/hummingbot/client/command/connect_command.py b/hummingbot/client/command/connect_command.py new file mode 100644 index 0000000..5464480 --- /dev/null +++ b/hummingbot/client/command/connect_command.py @@ -0,0 +1,151 @@ +import asyncio +from typing import TYPE_CHECKING, Dict, Optional + +import pandas as pd + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.security import Security +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.connector.connector_status import get_connector_status +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher +from hummingbot.user.user_balances import UserBalances + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + +OPTIONS = {cs.name for cs in AllConnectorSettings.get_connector_settings().values() + if not cs.use_ethereum_wallet and not cs.uses_gateway_generic_connector() if cs.name != "probit_kr"} + + +class ConnectCommand: + def connect(self, # type: HummingbotApplication + option: str): + if option is None: + safe_ensure_future(self.show_connections()) + else: + safe_ensure_future(self.connect_exchange(option)) + + async def connect_exchange(self, # type: HummingbotApplication + connector_name): + # instruct users to use gateway connect if connector is a gateway connector + if AllConnectorSettings.get_connector_settings()[connector_name].uses_gateway_generic_connector(): + self.notify("This is a gateway connector. Use `gateway connect` command instead.") + return + + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + if connector_name == "kraken": + self.notify("Reminder: Please ensure your Kraken API Key Nonce Window is at least 10.") + connector_config = ClientConfigAdapter(AllConnectorSettings.get_connector_config_keys(connector_name)) + if Security.connector_config_file_exists(connector_name): + await Security.wait_til_decryption_done() + api_key_config = [ + c.printable_value for c in connector_config.traverse(secure=False) if "api_key" in c.attr + ] + if api_key_config: + api_key = api_key_config[0] + prompt = ( + f"Would you like to replace your existing {connector_name} API key {api_key} (Yes/No)? >>> " + ) + else: + prompt = f"Would you like to replace your existing {connector_name} key (Yes/No)? >>> " + answer = await self.app.prompt(prompt=prompt) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + if answer.lower() in ("yes", "y"): + previous_keys = Security.api_keys(connector_name) + await self._perform_connect(connector_config, previous_keys) + else: + await self._perform_connect(connector_config) + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + + async def show_connections(self # type: HummingbotApplication + ): + self.notify("\nTesting connections, please wait...") + df, failed_msgs = await self.connection_df() + lines = [" " + line for line in format_df_for_printout( + df, + table_format=self.client_config_map.tables_format).split("\n")] + if failed_msgs: + lines.append("\nFailed connections:") + lines.extend([" " + k + ": " + v for k, v in failed_msgs.items()]) + self.notify("\n".join(lines)) + + async def connection_df(self # type: HummingbotApplication + ): + await Security.wait_til_decryption_done() + columns = ["Exchange", " Keys Added", " Keys Confirmed", " Tier"] + data = [] + failed_msgs = {} + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + err_msgs = await asyncio.wait_for( + UserBalances.instance().update_exchanges(self.client_config_map, reconnect=True), network_timeout + ) + except asyncio.TimeoutError: + self.notify("\nA network error prevented the connection table to populate. See logs for more details.") + raise + for option in sorted(OPTIONS): + keys_added = "No" + keys_confirmed = "No" + status = get_connector_status(option) + + api_keys = ( + Security.api_keys(option).values() + if not UserBalances.instance().is_gateway_market(option) + else {} + ) + if len(api_keys) > 0: + keys_added = "Yes" + err_msg = err_msgs.get(option) + if err_msg is not None: + failed_msgs[option] = err_msg + else: + keys_confirmed = "Yes" + data.append([option, keys_added, keys_confirmed, status]) + return pd.DataFrame(data=data, columns=columns), failed_msgs + + async def validate_n_connect_connector( + self, # type: HummingbotApplication + connector_name: str, + ) -> Optional[str]: + await Security.wait_til_decryption_done() + api_keys = Security.api_keys(connector_name) + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + err_msg = await asyncio.wait_for( + UserBalances.instance().add_exchange(connector_name, self.client_config_map, **api_keys), + network_timeout, + ) + except asyncio.TimeoutError: + self.notify( + "\nA network error prevented the connection to complete. See logs for more details.") + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + raise + return err_msg + + async def _perform_connect(self, connector_config: ClientConfigAdapter, previous_keys: Optional[Dict] = None): + connector_name = connector_config.connector + original_config = connector_config.full_copy() + await self.prompt_for_model_config(connector_config) + self.app.change_prompt(prompt=">>> ") + if self.app.to_stop_config: + self.app.to_stop_config = False + return + Security.update_secure_config(connector_config) + err_msg = await self.validate_n_connect_connector(connector_name) + if err_msg is None: + self.notify(f"\nYou are now connected to {connector_name}.") + safe_ensure_future(TradingPairFetcher.get_instance(client_config_map=ClientConfigAdapter).fetch_all(client_config_map=ClientConfigAdapter)) + else: + self.notify(f"\nError: {err_msg}") + if previous_keys is not None: + Security.update_secure_config(original_config) diff --git a/hummingbot/client/command/create_command.py b/hummingbot/client/command/create_command.py new file mode 100644 index 0000000..ac6f9b1 --- /dev/null +++ b/hummingbot/client/command/create_command.py @@ -0,0 +1,257 @@ +import asyncio +import copy +import os +import shutil +from pathlib import Path +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + ConfigValidationError, + default_strategy_file_path, + format_config_file_name, + get_strategy_config_map, + get_strategy_template_path, + parse_config_default_to_text, + parse_cvar_value, + save_previous_strategy_value, + save_to_yml, + save_to_yml_legacy, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.strategy_config_data_types import BaseStrategyConfigMap +from hummingbot.client.settings import STRATEGIES_CONF_DIR_PATH, required_exchanges +from hummingbot.client.ui.completer import load_completer +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class CreateCommand: + def create(self, # type: HummingbotApplication + file_name): + if file_name is not None: + file_name = format_config_file_name(file_name) + if (STRATEGIES_CONF_DIR_PATH / file_name).exists(): + self.notify(f"{file_name} already exists.") + return + + safe_ensure_future(self.prompt_for_configuration(file_name)) + + async def prompt_for_configuration( + self, # type: HummingbotApplication + file_name, + ): + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + required_exchanges.clear() + + strategy = await self.get_strategy_name() + + if self.app.to_stop_config: + return + + config_map = get_strategy_config_map(strategy) + self.notify(f"Please see https://docs.hummingbot.org/strategies/{strategy.replace('_', '-')}/ " + f"while setting up these below configuration.") + + if isinstance(config_map, ClientConfigAdapter): + await self.prompt_for_model_config(config_map) + if not self.app.to_stop_config: + file_name = await self.save_config_to_file(file_name, config_map) + elif config_map is not None: + file_name = await self.prompt_for_configuration_legacy(file_name, strategy, config_map) + else: + self.app.to_stop_config = True + + if self.app.to_stop_config: + return + + save_previous_strategy_value(file_name, self.client_config_map) + self.strategy_file_name = file_name + self.strategy_name = strategy + self.strategy_config_map = config_map + # Reload completer here otherwise the new file will not appear + self.app.input_field.completer = load_completer(self) + self.notify(f"A new config file has been created: {self.strategy_file_name}") + self.placeholder_mode = False + self.app.hide_input = False + + await self.verify_status() + + async def get_strategy_name( + self, # type: HummingbotApplication + ) -> Optional[str]: + strategy = None + strategy_config = ClientConfigAdapter(BaseStrategyConfigMap.construct()) + await self.prompt_for_model_config(strategy_config) + if not self.app.to_stop_config: + strategy = strategy_config.strategy + return strategy + + async def prompt_for_model_config( + self, # type: HummingbotApplication + config_map: ClientConfigAdapter, + ): + for key in config_map.keys(): + client_data = config_map.get_client_data(key) + if ( + client_data is not None + and (client_data.prompt_on_new or config_map.is_required(key)) + ): + await self.prompt_a_config(config_map, key) + if self.app.to_stop_config: + break + + async def prompt_for_configuration_legacy( + self, # type: HummingbotApplication + file_name, + strategy: str, + config_map: Dict, + ): + config_map_backup = copy.deepcopy(config_map) + # assign default values and reset those not required + for config in config_map.values(): + if config.required: + config.value = config.default + else: + config.value = None + for config in config_map.values(): + if config.prompt_on_new and config.required: + if not self.app.to_stop_config: + await self.prompt_a_config_legacy(config) + else: + break + else: + config.value = config.default + + if self.app.to_stop_config: + self.restore_config_legacy(config_map, config_map_backup) + self.app.set_text("") + return + + if file_name is None: + file_name = await self.prompt_new_file_name(strategy) + if self.app.to_stop_config: + self.restore_config_legacy(config_map, config_map_backup) + self.app.set_text("") + return + self.app.change_prompt(prompt=">>> ") + strategy_path = STRATEGIES_CONF_DIR_PATH / file_name + template = get_strategy_template_path(strategy) + shutil.copy(template, strategy_path) + save_to_yml_legacy(str(strategy_path), config_map) + return file_name + + async def prompt_a_config( + self, # type: HummingbotApplication + model: ClientConfigAdapter, + config: str, + input_value=None, + assign_default=True, + ): + config_path = config.split(".") + while len(config_path) != 1: + sub_model_attr = config_path.pop(0) + model = getattr(model, sub_model_attr) + config = config_path[0] + if input_value is None: + prompt = await model.get_client_prompt(config) + if prompt is not None: + if assign_default: + default = model.get_default_str_repr(attr_name=config) + self.app.set_text(default) + prompt = f"{prompt} >>> " + client_data = model.get_client_data(config) + input_value = await self.app.prompt(prompt=prompt, is_password=client_data.is_secure) + + new_config_value = None + if not self.app.to_stop_config and input_value is not None: + try: + setattr(model, config, input_value) + new_config_value = getattr(model, config) + except ConfigValidationError as e: + self.notify(str(e)) + new_config_value = await self.prompt_a_config(model, config) + + if not self.app.to_stop_config and isinstance(new_config_value, ClientConfigAdapter): + await self.prompt_for_model_config(new_config_value) + + async def prompt_a_config_legacy( + self, # type: HummingbotApplication + config: ConfigVar, + input_value=None, + assign_default=True, + ): + if config.key == "inventory_price": + await self.inventory_price_prompt_legacy(self.strategy_config_map, input_value) + return + if input_value is None: + if assign_default: + self.app.set_text(parse_config_default_to_text(config)) + prompt = await config.get_prompt() + input_value = await self.app.prompt(prompt=prompt, is_password=config.is_secure) + + if self.app.to_stop_config: + return + value = parse_cvar_value(config, input_value) + err_msg = await config.validate(input_value) + if err_msg is not None: + self.notify(err_msg) + config.value = None + await self.prompt_a_config_legacy(config) + else: + config.value = value + + async def save_config_to_file( + self, # type: HummingbotApplication + file_name: Optional[str], + config_map: ClientConfigAdapter, + ) -> str: + if file_name is None: + file_name = await self.prompt_new_file_name(config_map.strategy) + if self.app.to_stop_config: + self.app.set_text("") + return + self.app.change_prompt(prompt=">>> ") + strategy_path = Path(STRATEGIES_CONF_DIR_PATH) / file_name + save_to_yml(strategy_path, config_map) + return file_name + + async def prompt_new_file_name(self, # type: HummingbotApplication + strategy): + file_name = default_strategy_file_path(strategy) + self.app.set_text(file_name) + input = await self.app.prompt(prompt="Enter a new file name for your configuration >>> ") + input = format_config_file_name(input) + file_path = os.path.join(STRATEGIES_CONF_DIR_PATH, input) + if input is None or input == "": + self.notify("Value is required.") + return await self.prompt_new_file_name(strategy) + elif os.path.exists(file_path): + self.notify(f"{input} file already exists, please enter a new name.") + return await self.prompt_new_file_name(strategy) + else: + return input + + async def verify_status( + self # type: HummingbotApplication + ): + try: + timeout = float(self.client_config_map.commands_timeout.create_command_timeout) + all_status_go = await asyncio.wait_for(self.status_check_all(), timeout) + except asyncio.TimeoutError: + self.notify("\nA network error prevented the connection check to complete. See logs for more details.") + self.strategy_file_name = None + self.strategy_name = None + self.strategy_config = None + raise + if all_status_go: + self.notify("\nEnter \"start\" to start market making.") + + @staticmethod + def restore_config_legacy(config_map: Dict[str, ConfigVar], config_map_backup: Dict[str, ConfigVar]): + for key in config_map: + config_map[key] = config_map_backup[key] diff --git a/hummingbot/client/command/exit_command.py b/hummingbot/client/command/exit_command.py new file mode 100644 index 0000000..5984a2a --- /dev/null +++ b/hummingbot/client/command/exit_command.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import asyncio +from typing import TYPE_CHECKING + +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class ExitCommand: + def exit(self, # type: HummingbotApplication + force: bool = False): + safe_ensure_future(self.exit_loop(force), loop=self.ev_loop) + + async def exit_loop(self, # type: HummingbotApplication + force: bool = False): + if self.strategy_task is not None and not self.strategy_task.cancelled(): + self.strategy_task.cancel() + if force is False and self._trading_required: + success = await self._cancel_outstanding_orders() + if not success: + self.notify('Wind down process terminated: Failed to cancel all outstanding orders. ' + '\nYou may need to manually cancel remaining orders by logging into your chosen exchanges' + '\n\nTo force exit the app, enter "exit -f"') + return + # Freeze screen 1 second for better UI + await asyncio.sleep(1) + + if self._gateway_monitor is not None: + self._gateway_monitor.stop() + + self.notify("Winding down notifiers...") + for notifier in self.notifiers: + notifier.stop() + + self.app.exit() + self.mqtt_stop() diff --git a/hummingbot/client/command/export_command.py b/hummingbot/client/command/export_command.py new file mode 100644 index 0000000..11af94e --- /dev/null +++ b/hummingbot/client/command/export_command.py @@ -0,0 +1,112 @@ +import os +from typing import TYPE_CHECKING, List, Optional + +import pandas as pd +from sqlalchemy.orm import Query, Session + +from hummingbot.client.config.security import Security +from hummingbot.client.settings import DEFAULT_LOG_FILE_PATH +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.model.trade_fill import TradeFill + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class ExportCommand: + def export(self, # type: HummingbotApplication + option): + if option is None or option not in ("keys", "trades"): + self.notify("Invalid export option.") + return + elif option == "keys": + safe_ensure_future(self.export_keys()) + elif option == "trades": + safe_ensure_future(self.export_trades()) + + async def export_keys(self, # type: HummingbotApplication + ): + await Security.wait_til_decryption_done() + if not Security.any_secure_configs(): + self.notify("There are no keys to export.") + return + self.placeholder_mode = True + self.app.hide_input = True + if await self.check_password(): + self.notify("\nWarning: Never disclose API keys or private keys. Anyone with your keys can steal any " + "assets held in your account.") + self.notify("\nAPI keys:") + for key, cm in Security.all_decrypted_values().items(): + for el in cm.traverse(secure=False): + if el.client_field_data is not None and el.client_field_data.is_secure: + self.notify(f"{el.attr}: {el.printable_value}") + self.app.change_prompt(prompt=">>> ") + self.app.hide_input = False + self.placeholder_mode = False + + async def prompt_new_export_file_name(self, # type: HummingbotApplication + path): + input = await self.app.prompt(prompt="Enter a new csv file name >>> ") + if input is None or input == "": + self.notify("Value is required.") + return await self.prompt_new_export_file_name(path) + if input == " ": + return None + if "." not in input: + input = input + ".csv" + file_path = os.path.join(path, input) + if os.path.exists(file_path): + self.notify(f"{input} file already exists, please enter a new name.") + return await self.prompt_new_export_file_name(path) + else: + return input + + async def export_trades(self, # type: HummingbotApplication + ): + with self.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = self._get_trades_from_session( + int(self.init_time * 1e3), + session=session) + if len(trades) == 0: + self.notify("No past trades to export.") + return + self.placeholder_mode = True + self.app.hide_input = True + path = self.client_config_map.log_file_path + if path is None: + path = str(DEFAULT_LOG_FILE_PATH) + file_name = await self.prompt_new_export_file_name(path) + if file_name is None: + return + file_path = os.path.join(path, file_name) + try: + df: pd.DataFrame = TradeFill.to_pandas(trades) + df.to_csv(file_path, header=True) + self.notify(f"Successfully exported trades to {file_path}") + except Exception as e: + self.notify(f"Error exporting trades to {path}: {e}") + self.app.change_prompt(prompt=">>> ") + self.placeholder_mode = False + self.app.hide_input = False + + def _get_trades_from_session(self, # type: HummingbotApplication + start_timestamp: int, + session: Session, + number_of_rows: Optional[int] = None, + config_file_path: str = None) -> List[TradeFill]: + + filters = [TradeFill.timestamp >= start_timestamp] + if config_file_path is not None: + filters.append(TradeFill.config_file_path.like(f"%{config_file_path}%")) + query: Query = (session + .query(TradeFill) + .filter(*filters) + .order_by(TradeFill.timestamp.desc())) + if number_of_rows is None: + result: List[TradeFill] = query.all() or [] + else: + result: List[TradeFill] = query.limit(number_of_rows).all() or [] + + # Get the latest 100 trades in ascending timestamp order + result.reverse() + return result diff --git a/hummingbot/client/command/gateway_api_manager.py b/hummingbot/client/command/gateway_api_manager.py new file mode 100644 index 0000000..2b5e95e --- /dev/null +++ b/hummingbot/client/command/gateway_api_manager.py @@ -0,0 +1,131 @@ +from contextlib import contextmanager +from typing import TYPE_CHECKING, Any, Dict, Generator, Optional + +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +@contextmanager +def begin_placeholder_mode(hb: "HummingbotApplication") -> Generator["HummingbotApplication", None, None]: + hb.app.clear_input() + hb.placeholder_mode = True + hb.app.hide_input = True + try: + yield hb + finally: + hb.app.to_stop_config = False + hb.placeholder_mode = False + hb.app.hide_input = False + hb.app.change_prompt(prompt=">>> ") + + +class GatewayChainApiManager: + """ + Manage and test connections from gateway to chain urls. + """ + + async def _check_node_status(self, chain: str, network: str, node_url: str) -> bool: + """ + Verify that the node url is valid. If it is an empty string, + ignore it, but let the user know they cannot connect to the node. + """ + + resp = await GatewayHttpClient.get_instance().get_network_status(chain, network) + + if resp.get("currentBlockNumber", -1) > 0: + self.notify(f"Successfully pinged the node url for {chain}-{network}: {node_url}.") + return True + return False + + async def _test_node_url(self, chain: str, network: str) -> Optional[str]: + """ + Get the node url from user input, then check that it is valid. + """ + with begin_placeholder_mode(self): + while True: + node_url: str = await self.app.prompt(prompt=f"Enter a node url (with API key if necessary) for {chain}-{network}: >>> ") + + self.app.clear_input() + self.app.change_prompt(prompt="") + + if self.app.to_stop_config: + self.app.to_stop_config = False + self.stop() + return None + try: + node_url = node_url.strip() # help check for an empty string which is valid input + + await self._update_gateway_chain_network_node_url(chain, network, node_url) + + self.notify("Restarting gateway to update with new node url...") + # wait about 30 seconds for the gateway to restart + gateway_live = await self.ping_gateway_api(30) + if not gateway_live: + self.notify("Error: unable to restart gateway. Try 'start' again after gateway is running.") + self.notify("Stopping strategy...") + self.stop() + + success: bool = await self._check_node_status(chain, network, node_url) + if not success: + # the node URL test was unsuccessful, try again + continue + return node_url + except Exception: + self.notify(f"Error occured when trying to ping the node URL: {node_url}.") + + async def _test_node_url_from_gateway_config(self, chain: str, network: str, attempt_connection: bool = True) -> bool: + """ + Check if gateway node URL for a chain and network works + """ + # XXX: This should be removed once nodeAPIKey is deprecated from Gateway service + config_dict: Dict[str, Any] = await GatewayHttpClient.get_instance().get_configuration() + chain_config: Optional[Dict[str, Any]] = config_dict.get(chain) + if chain_config is not None: + networks: Optional[Dict[str, Any]] = chain_config.get("networks") + if networks is not None: + network_config: Optional[Dict[str, Any]] = networks.get(network) + if network_config is not None: + node_url: Optional[str] = network_config.get("nodeURL") + if not attempt_connection: + while True: + change_node: str = await self.app.prompt(prompt=f"Do you want to continue to use node url '{node_url}' for {chain}-{network}? (Yes/No) ") + if self.app.to_stop_config: + return + if change_node in ["Y", "y", "Yes", "yes", "N", "n", "No", "no"]: + break + self.notify("Invalid input. Please try again or exit config [CTRL + x].\n") + + self.app.clear_input() + # they use an existing wallet + if change_node is not None and change_node in ["N", "n", "No", "no"]: + node_url: str = await self.app.prompt(prompt=f"Enter a new node url (with API key if necessary) for {chain}-{network}: >>> ") + await self._update_gateway_chain_network_node_url(chain, network, node_url) + self.notify("Restarting gateway to update with new node url...") + # wait about 30 seconds for the gateway to restart + await self.ping_gateway_api(30) + return True + success: bool = await self._check_node_status(chain, network, node_url) + if not success: + try: + return await self._test_node_url(chain, network) + except Exception: + self.notify(f"Unable to successfully ping the node url for {chain}-{network}: {node_url}. Please try again (it may require an API key).") + return False + else: + self.notify(f"{chain}.networks.{network} was not found in the gateway config.") + return False + else: + self.notify(f"{chain}.networks was not found in the gateway config.") + return False + else: + self.notify(f"{chain} was not found in the gateway config.") + return False + + @staticmethod + async def _update_gateway_chain_network_node_url(chain: str, network: str, node_url: str): + """ + Update a chain and network's node URL in gateway + """ + await GatewayHttpClient.get_instance().update_config(f"{chain}.networks.{network}.nodeURL", node_url) diff --git a/hummingbot/client/command/gateway_command.py b/hummingbot/client/command/gateway_command.py new file mode 100644 index 0000000..b3d15ba --- /dev/null +++ b/hummingbot/client/command/gateway_command.py @@ -0,0 +1,694 @@ +#!/usr/bin/env python +import asyncio +import itertools +import logging +import time +from decimal import Decimal +from functools import lru_cache +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple + +import pandas as pd + +from hummingbot.client.command.gateway_api_manager import GatewayChainApiManager, begin_placeholder_mode +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ( + ReadOnlyClientConfigAdapter, + get_connector_class, + refresh_trade_fees_config, +) +from hummingbot.client.config.security import Security +from hummingbot.client.settings import AllConnectorSettings, GatewayConnectionSetting, gateway_connector_trading_pairs +from hummingbot.client.ui.completer import load_completer +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.connector.connector_status import get_connector_status +from hummingbot.core.gateway import get_gateway_paths +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.gateway.gateway_status_monitor import GatewayStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.gateway_config_utils import ( + build_config_dict_display, + build_connector_display, + build_connector_tokens_display, + build_list_display, + build_wallet_display, + flatten, + native_tokens, + search_configs, +) +from hummingbot.core.utils.ssl_cert import create_self_sign_certs + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +def ensure_gateway_online(func): + def wrapper(self, *args, **kwargs): + if self._gateway_monitor.gateway_status is GatewayStatus.OFFLINE: + self.logger().error("Gateway is offline") + return + return func(self, *args, **kwargs) + return wrapper + + +class GatewayCommand(GatewayChainApiManager): + client_config_map: ClientConfigMap + _market: Dict[str, Any] = {} + + def __init__(self, # type: HummingbotApplication + client_config_map: ClientConfigMap + ): + super().__init__(client_config_map) + self.client_config_map = client_config_map + + @ensure_gateway_online + def gateway_connect(self, connector: str = None): + safe_ensure_future(self._gateway_connect(connector), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_status(self): + safe_ensure_future(self._gateway_status(), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_balance(self): + safe_ensure_future(self._get_balances(), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_connector_tokens(self, connector_chain_network: Optional[str], new_tokens: Optional[str]): + if connector_chain_network is not None and new_tokens is not None: + safe_ensure_future(self._update_gateway_connector_tokens(connector_chain_network, new_tokens), loop=self.ev_loop) + else: + safe_ensure_future(self._show_gateway_connector_tokens(connector_chain_network), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_approve_tokens(self, connector_chain_network: Optional[str], tokens: Optional[str]): + if connector_chain_network is not None and tokens is not None: + safe_ensure_future(self._update_gateway_approve_tokens(connector_chain_network, tokens), loop=self.ev_loop) + else: + self.notify("\nPlease specify the connector_chain_network and a token to approve.\n") + + def generate_certs(self): + safe_ensure_future(self._generate_certs(), loop=self.ev_loop) + + @ensure_gateway_online + def test_connection(self): + safe_ensure_future(self._test_connection(), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_list(self): + safe_ensure_future(self._gateway_list(), loop=self.ev_loop) + + @ensure_gateway_online + def gateway_config(self, + key: Optional[str] = None, + value: str = None): + if value: + safe_ensure_future(self._update_gateway_configuration(key, value), loop=self.ev_loop) + else: + safe_ensure_future(self._show_gateway_configuration(key), loop=self.ev_loop) + + async def _test_connection(self): + # test that the gateway is running + if await self._get_gateway_instance().ping_gateway(): + self.notify("\nSuccessfully pinged gateway.") + else: + self.notify("\nUnable to ping gateway.") + + async def _generate_certs( + self, # type: HummingbotApplication + from_client_password: bool = False, + ): + + certs_path: str = get_gateway_paths(self.client_config_map).local_certs_path.as_posix() + + if not from_client_password: + with begin_placeholder_mode(self): + while True: + pass_phase = await self.app.prompt( + prompt='Enter pass phrase to generate Gateway SSL certifications >>> ', + is_password=True + ) + if pass_phase is not None and len(pass_phase) > 0: + break + self.notify("Error: Invalid pass phrase") + else: + pass_phase = Security.secrets_manager.password.get_secret_value() + create_self_sign_certs(pass_phase, certs_path) + self.notify(f"Gateway SSL certification files are created in {certs_path}.") + self._get_gateway_instance().reload_certs(self.client_config_map) + + async def ping_gateway_api(self, max_wait: int) -> bool: + """ + Try to reach the gateway API for up to max_wait seconds + """ + now = int(time.time()) + gateway_live = await self._get_gateway_instance().ping_gateway() + while not gateway_live: + later = int(time.time()) + if later - now > max_wait: + return False + await asyncio.sleep(0.5) + gateway_live = await self._get_gateway_instance().ping_gateway() + later = int(time.time()) + + return True + + async def _gateway_status(self): + if self._gateway_monitor.gateway_status is GatewayStatus.ONLINE: + try: + status = await self._get_gateway_instance().get_gateway_status() + if status is None or status == []: + self.notify("There are currently no connectors online.") + else: + self.notify(pd.DataFrame(status)) + except Exception: + self.notify("\nError: Unable to fetch status of connected Gateway server.") + else: + self.notify("\nNo connection to Gateway server exists. Ensure Gateway server is running.") + + async def _update_gateway_configuration(self, key: str, value: Any): + try: + response = await self._get_gateway_instance().update_config(key, value) + self.notify(response["message"]) + except Exception: + self.notify("\nError: Gateway configuration update failed. See log file for more details.") + + async def _show_gateway_configuration( + self, # type: HummingbotApplication + key: Optional[str] = None, + ): + host = self.client_config_map.gateway.gateway_api_host + port = self.client_config_map.gateway.gateway_api_port + try: + config_dict: Dict[str, Any] = await self._gateway_monitor._fetch_gateway_configs() + if key is not None: + config_dict = search_configs(config_dict, key) + self.notify(f"\nGateway Configurations ({host}:{port}):") + lines = [] + build_config_dict_display(lines, config_dict) + self.notify("\n".join(lines)) + + except asyncio.CancelledError: + raise + except Exception: + remote_host = ':'.join([host, port]) + self.notify(f"\nError: Connection to Gateway {remote_host} failed") + + async def _gateway_connect( + self, # type: HummingbotApplication + connector: str = None + ): + with begin_placeholder_mode(self): + gateway_connections_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + if connector is None: + if len(gateway_connections_conf) < 1: + self.notify("No existing connection.\n") + else: + connector_df: pd.DataFrame = build_connector_display(gateway_connections_conf) + self.notify(connector_df.to_string(index=False)) + else: + # get available networks + connector_configs: Dict[str, Any] = await self._get_gateway_instance().get_connectors() + connector_config: List[Dict[str, Any]] = [ + d for d in connector_configs["connectors"] if d["name"] == connector + ] + if len(connector_config) < 1: + self.notify(f"No available blockchain networks available for the connector '{connector}'.") + return + available_networks: List[Dict[str, Any]] = connector_config[0]["available_networks"] + trading_type: str = connector_config[0]["trading_type"][0] + chain_type: str = connector_config[0]["chain_type"] + additional_spenders: List[str] = connector_config[0].get("additional_spenders", []) + additional_prompts: Dict[str, str] = connector_config[0].get( # These will be stored locally. + "additional_add_wallet_prompts", # If Gateway requires additional, prompts with secure info, + {} # a new attribute must be added (e.g. additional_secure_add_wallet_prompts) + ) + + # ask user to select a chain. Automatically select if there is only one. + chains: List[str] = [d['chain'] for d in available_networks] + chain: str + + # chains as options + while True: + self.app.input_field.completer.set_gateway_chains(chains) + chain = await self.app.prompt( + prompt=f"Which chain do you want {connector} to connect to? ({', '.join(chains)}) >>> " + ) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + + if chain in chains: + break + self.notify(f"{chain} chain not supported.\n") + + # ask user to select a network. Automatically select if there is only one. + networks: List[str] = list( + itertools.chain.from_iterable([d['networks'] for d in available_networks if d['chain'] == chain]) + ) + + network: str + while True: + self.app.input_field.completer.set_gateway_networks(networks) + network = await self.app.prompt( + prompt=f"Which network do you want {connector} to connect to? ({', '.join(networks)}) >>> " + ) + if self.app.to_stop_config: + return + if network in networks: + break + self.notify("Error: Invalid network") + + # test you can connect to the uri, otherwise request the url + await self._test_node_url_from_gateway_config(chain, network, attempt_connection=False) + + if self.app.to_stop_config: + return + + # get wallets for the selected chain + wallets_response: List[Dict[str, Any]] = await self._get_gateway_instance().get_wallets() + matching_wallets: List[Dict[str, Any]] = [w for w in wallets_response if w["chain"] == chain] + wallets: List[str] + if len(matching_wallets) < 1: + wallets = [] + else: + wallets = matching_wallets[0]['walletAddresses'] + + # if the user has no wallet, ask them to select one + if len(wallets) < 1 or chain == "near" or len(additional_prompts) != 0: + wallet_address, additional_prompt_values = await self._prompt_for_wallet_address( + chain=chain, network=network, additional_prompts=additional_prompts + ) + + # the user has a wallet. Ask if they want to use it or create a new one. + else: + # print table + while True: + use_existing_wallet: str = await self.app.prompt( + prompt=f"Do you want to connect to {chain}-{network} with one of your existing wallets on " + f"Gateway? (Yes/No) >>> " + ) + if self.app.to_stop_config: + return + if use_existing_wallet in ["Y", "y", "Yes", "yes", "N", "n", "No", "no"]: + break + self.notify("Invalid input. Please try again or exit config [CTRL + x].\n") + + self.app.clear_input() + # they use an existing wallet + if use_existing_wallet is not None and use_existing_wallet in ["Y", "y", "Yes", "yes"]: + native_token: str = native_tokens[chain] + wallet_table: List[Dict[str, Any]] = [] + for w in wallets: + balances: Dict[str, Any] = await self._get_gateway_instance().get_balances( + chain, network, w, [native_token], connector + ) + balance = ( + balances['balances'].get(native_token) + or balances['balances']['total'].get(native_token) + ) + wallet_table.append({"balance": balance, "address": w}) + + wallet_df: pd.DataFrame = build_wallet_display(native_token, wallet_table) + self.notify(wallet_df.to_string(index=False)) + self.app.input_field.completer.set_list_gateway_wallets_parameters(wallets_response, chain) + additional_prompt_values = {} + + while True: + wallet_address: str = await self.app.prompt(prompt="Select a gateway wallet >>> ") + if self.app.to_stop_config: + return + if wallet_address in wallets: + self.notify(f"You have selected {wallet_address}.") + break + self.notify("Error: Invalid wallet address") + + # they want to create a new wallet even though they have other ones + else: + while True: + try: + wallet_address, additional_prompt_values = await self._prompt_for_wallet_address( + chain=chain, network=network, additional_prompts=additional_prompts + ) + break + except Exception: + self.notify("Error adding wallet. Check private key.\n") + + # display wallet balance + native_token: str = native_tokens[chain] + balances: Dict[str, Any] = await self._get_gateway_instance().get_balances( + chain, network, wallet_address, [native_token], connector + ) + wallet_table: List[Dict[str, Any]] = [{"balance": balances['balances'].get(native_token) or balances['balances']['total'].get(native_token), "address": wallet_address}] + wallet_df: pd.DataFrame = build_wallet_display(native_token, wallet_table) + self.notify(wallet_df.to_string(index=False)) + + self.app.clear_input() + + # write wallets to Gateway connectors settings. + GatewayConnectionSetting.upsert_connector_spec( + connector_name=connector, + chain=chain, + network=network, + trading_type=trading_type, + chain_type=chain_type, + wallet_address=wallet_address, + additional_spenders=additional_spenders, + additional_prompt_values=additional_prompt_values, + ) + self.notify(f"The {connector} connector now uses wallet {wallet_address} on {chain}-{network}") + + # update AllConnectorSettings and fee overrides. + AllConnectorSettings.create_connector_settings() + AllConnectorSettings.initialize_paper_trade_settings( + self.client_config_map.paper_trade.paper_trade_exchanges + ) + await refresh_trade_fees_config(self.client_config_map) + + # Reload completer here to include newly added gateway connectors + self.app.input_field.completer = load_completer(self) + + async def _prompt_for_wallet_address( + self, # type: HummingbotApplication + chain: str, + network: str, + additional_prompts: Dict[str, str], + ) -> Tuple[Optional[str], Dict[str, str]]: + self.app.clear_input() + self.placeholder_mode = True + wallet_private_key = await self.app.prompt( + prompt=f"Enter your {chain}-{network} wallet private key >>> ", + is_password=True + ) + self.app.clear_input() + if self.app.to_stop_config: + return + + additional_prompt_values = {} + if chain == "near": + wallet_account_id: str = await self.app.prompt( + prompt=f"Enter your {chain}-{network} account Id >>> ", + ) + additional_prompt_values["address"] = wallet_account_id + self.app.clear_input() + if self.app.to_stop_config: + return + + for field, prompt in additional_prompts.items(): + value = await self.app.prompt(prompt=prompt, is_password=True) + self.app.clear_input() + if self.app.to_stop_config: + return + additional_prompt_values[field] = value + + response: Dict[str, Any] = await self._get_gateway_instance().add_wallet( + chain, network, wallet_private_key, **additional_prompt_values + ) + wallet_address: str = response["address"] + return wallet_address, additional_prompt_values + + async def _get_balances(self): + gateway_connections = GatewayConnectionSetting.load() + + sum_not_for_show_name = "sum_not_for_show" + self.notify("Updating gateway balances, please wait...") + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + all_ex_bals = await asyncio.wait_for( + self.all_balances_all_exc(self.client_config_map), network_timeout + ) + except asyncio.TimeoutError: + self.notify("\nA network error prevented the balances to update. See logs for more details.") + raise + + for exchange, bals in all_ex_bals.items(): + # Flag to check if exchange data has been found + exchange_found = False + + for conf in gateway_connections: + conf: Dict[str, Any] = conf + if exchange == (f'{conf["connector"]}_{conf["chain"]}_{conf["network"]}'): + exchange_found = True + address = conf["wallet_address"] + rows = [] + for token, bal in bals.items(): + rows.append({ + "Symbol": token.upper(), + "Balance": round(bal, 4), + "sum_not_for_show": " " + }) + df = pd.DataFrame(data=rows, columns=["Symbol", "Balance", "sum_not_for_show"]) + df.sort_values(by=["Symbol"], inplace=True) + + self.notify(f"\nExchange: {exchange}") + self.notify(f"Address: {address}") + + if df.empty: + self.notify("You have no balance on this exchange.") + else: + lines = [ + " " + line for line in df.drop(sum_not_for_show_name, axis=1).to_string(index=False).split("\n") + ] + self.notify("\n".join(lines)) + # Exit loop once exchange data is found + break + + if not exchange_found: + self.notify(f"No configuration found for exchange: {exchange}") + + def connect_markets(exchange, client_config_map: ClientConfigMap, **api_details): + connector = None + conn_setting = AllConnectorSettings.get_connector_settings()[exchange] + if api_details or conn_setting.uses_gateway_generic_connector(): + connector_class = get_connector_class(exchange) + read_only_client_config = ReadOnlyClientConfigAdapter.lock_config(client_config_map) + init_params = conn_setting.conn_init_parameters( + trading_pairs=gateway_connector_trading_pairs(conn_setting.name), + api_keys=api_details, + client_config_map=read_only_client_config, + ) + + # collect trading pairs from the gateway connector settings + trading_pairs: List[str] = gateway_connector_trading_pairs(conn_setting.name) + + # collect unique trading pairs that are for balance reporting only + if conn_setting.uses_gateway_generic_connector(): + config: Optional[Dict[str, str]] = GatewayConnectionSetting.get_connector_spec_from_market_name(conn_setting.name) + if config is not None: + existing_pairs = set(flatten([x.split("-") for x in trading_pairs])) + + other_tokens: Set[str] = set(config.get("tokens", "").split(",")) + other_tokens.discard("") + tokens: List[str] = [t for t in other_tokens if t not in existing_pairs] + if tokens != [""]: + trading_pairs.append("-".join(tokens)) + + connector = connector_class(**init_params) + return connector + + @staticmethod + async def _update_balances(market) -> Optional[str]: + try: + await market._update_balances() + except Exception as e: + logging.getLogger().debug(f"Failed to update balances for {market}", exc_info=True) + return str(e) + return None + + async def add_gateway_exchange(self, exchange, client_config_map: ClientConfigMap, **api_details) -> Optional[str]: + self._market.pop(exchange, None) + is_gateway_markets = self.is_gateway_markets(exchange) + if is_gateway_markets: + market = GatewayCommand.connect_markets(exchange, client_config_map, **api_details) + if not market: + return "API keys have not been added." + err_msg = await GatewayCommand._update_balances(market) + if err_msg is None: + self._market[exchange] = market + return err_msg + + def all_balance(self, exchange) -> Dict[str, Decimal]: + if exchange not in self._market: + return {} + return self._market[exchange].get_all_balances() + + async def update_exchange_balances(self, exchange_name: str, client_config_map: ClientConfigMap) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]: + is_gateway_markets = self.is_gateway_markets(exchange_name) + if is_gateway_markets and exchange_name in self._market: + del self._market[exchange_name] + if exchange_name in self._market: + return await self._update_balances(self._market[exchange_name]) + else: + await Security.wait_til_decryption_done() + api_keys = Security.api_keys(exchange_name) if not is_gateway_markets else {} + return await self.add_gateway_exchange(exchange_name, client_config_map, **api_keys) + + @staticmethod + @lru_cache(maxsize=10) + def is_gateway_markets(exchange_name: str) -> bool: + return ( + exchange_name in sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_evm_amm_lp_connector_names() + ).union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ) + ) + + # returns error message for each exchange + async def update_exchange( + self, + client_config_map: ClientConfigMap, + reconnect: bool = False, + exchanges: Optional[List[str]] = None + ) -> Dict[str, Optional[str]]: + exchanges = exchanges or [] + tasks = [] + # Update user balances + if len(exchanges) == 0: + exchanges = [cs.name for cs in AllConnectorSettings.get_connector_settings().values()] + exchanges: List[str] = [ + cs.name + for cs in AllConnectorSettings.get_connector_settings().values() + if not cs.use_ethereum_wallet + and cs.name in exchanges + and not cs.name.endswith("paper_trade") + ] + + if reconnect: + self._market.clear() + for exchange in exchanges: + tasks.append(self.update_exchange_balances(exchange, client_config_map)) + results = await safe_gather(*tasks) + return {ex: err_msg for ex, err_msg in zip(exchanges, results)} + + # returns only for non-gateway connectors since balance command no longer reports gateway connector balances + + async def all_balances_all_exc(self, client_config_map: ClientConfigMap) -> Dict[str, Dict[str, Decimal]]: + await self.update_exchange(client_config_map) + return {k: v.get_all_balances() for k, v in sorted(self._market.items(), key=lambda x: x[0])} + + # returns only for non-gateway connectors since balance command no longer reports gateway connector balances + def all_available_balances_all_exc(self) -> Dict[str, Dict[str, Decimal]]: + # refactor to get all available balances and allowances for all exchanges + return {k: v.available_balances for k, v in sorted(self._market.items(), key=lambda x: x[0])} + + async def balance(self, exchange, client_config_map: ClientConfigMap, *symbols) -> Dict[str, Decimal]: + if await self.update_exchange_balance(exchange, client_config_map) is None: + results = {} + for token, bal in self.all_balances(exchange).items(): + matches = [s for s in symbols if s.lower() == token.lower()] + if matches: + results[matches[0]] = bal + return results + + async def _show_gateway_connector_tokens( + self, # type: HummingbotApplication + connector_chain_network: str = None + ): + """ + Display connector tokens that hummingbot will report balances for + """ + if connector_chain_network is None: + gateway_connections_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + if len(gateway_connections_conf) < 1: + self.notify("No existing connection.\n") + else: + connector_df: pd.DataFrame = build_connector_tokens_display(gateway_connections_conf) + self.notify(connector_df.to_string(index=False)) + else: + conf: Optional[Dict[str, str]] = GatewayConnectionSetting.get_connector_spec_from_market_name(connector_chain_network) + if conf is not None: + connector_df: pd.DataFrame = build_connector_tokens_display([conf]) + self.notify(connector_df.to_string(index=False)) + else: + self.notify(f"There is no gateway connection for {connector_chain_network}.\n") + + async def _update_gateway_connector_tokens( + self, # type: HummingbotApplication + connector_chain_network: str, + new_tokens: str, + ): + """ + Allow the user to input tokens whose balances they want to monitor are. + These are not tied to a strategy, rather to the connector-chain-network + tuple. This has no influence on what tokens the user can use with a + connector-chain-network and a particular strategy. This is only for + report balances. + """ + conf: Optional[Dict[str, str]] = GatewayConnectionSetting.get_connector_spec_from_market_name(connector_chain_network) + + if conf is None: + self.notify(f"'{connector_chain_network}' is not available. You can add and review available gateway connectors with the command 'gateway connect'.") + else: + GatewayConnectionSetting.upsert_connector_spec_tokens(connector_chain_network, new_tokens) + self.notify(f"The 'gateway balance' command will now report token balances {new_tokens} for '{connector_chain_network}'.") + + async def _gateway_list( + self # type: HummingbotApplication + ): + connector_list: List[Dict[str, Any]] = await self._get_gateway_instance().get_connectors() + connectors_tiers: List[Dict[str, Any]] = [] + for connector in connector_list["connectors"]: + connector['tier'] = get_connector_status(connector['name']) + available_networks: List[Dict[str, Any]] = connector["available_networks"] + chains: List[str] = [d['chain'] for d in available_networks] + connector['chains'] = chains + connectors_tiers.append(connector) + connectors_df: pd.DataFrame = build_list_display(connectors_tiers) + lines = [" " + line for line in format_df_for_printout( + connectors_df, + table_format=self.client_config_map.tables_format).split("\n")] + self.notify("\n".join(lines)) + + async def _update_gateway_approve_tokens( + self, # type: HummingbotApplication + connector_chain_network: str, + tokens: str, + ): + """ + Allow the user to approve tokens for spending. + """ + # get connector specs + conf: Optional[Dict[str, str]] = GatewayConnectionSetting.get_connector_spec_from_market_name(connector_chain_network) + if conf is None: + self.notify(f"'{connector_chain_network}' is not available. You can add and review available gateway connectors with the command 'gateway connect'.") + else: + self.logger().info(f"Connector {conf['connector']} Tokens {tokens} will now be approved for spending for '{connector_chain_network}'.") + # get wallets for the selected chain + gateway_connections_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + if len(gateway_connections_conf) < 1: + self.notify("No existing wallet.\n") + return + connector_wallet: List[Dict[str, Any]] = [w for w in gateway_connections_conf if w["chain"] == conf['chain'] and w["connector"] == conf['connector'] and w["network"] == conf['network']] + try: + resp: Dict[str, Any] = await self._get_gateway_instance().approve_token(conf['chain'], conf['network'], connector_wallet[0]['wallet_address'], tokens, conf['connector']) + transaction_hash: Optional[str] = resp.get("approval", {}).get("hash") + displayed_pending: bool = False + while True: + pollResp: Dict[str, Any] = await self._get_gateway_instance().get_transaction_status(conf['chain'], conf['network'], transaction_hash) + transaction_status: Optional[str] = pollResp.get("txStatus") + if transaction_status == 1: + self.logger().info(f"Token {tokens} is approved for spending for '{conf['connector']}' for Wallet: {connector_wallet[0]['wallet_address']}.") + self.notify(f"Token {tokens} is approved for spending for '{conf['connector']}' for Wallet: {connector_wallet[0]['wallet_address']}.") + break + elif transaction_status == 2: + if not displayed_pending: + self.logger().info(f"Token {tokens} approval transaction is pending. Transaction hash: {transaction_hash}") + displayed_pending = True + await asyncio.sleep(2) + continue + else: + self.logger().info(f"Tokens {tokens} is not approved for spending. Please use manual approval.") + self.notify(f"Tokens {tokens} is not approved for spending. Please use manual approval.") + break + + except Exception as e: + self.logger().error(f"Error approving tokens: {e}") + return + + def _get_gateway_instance( + self # type: HummingbotApplication + ) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self.client_config_map) + return gateway_instance diff --git a/hummingbot/client/command/help_command.py b/hummingbot/client/command/help_command.py new file mode 100644 index 0000000..199368e --- /dev/null +++ b/hummingbot/client/command/help_command.py @@ -0,0 +1,24 @@ +import argparse +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +class HelpCommand: + def help(self, # type: HummingbotApplication + command: str): + cmd_split = command.split() + if cmd_split[0] == 'all': + self.notify(self.parser.format_help()) + else: + parser = self.parser._actions + for step in cmd_split: + subparsers_actions = [ + action for action in parser if isinstance(action, argparse._SubParsersAction)] + for subparsers_action in subparsers_actions: + subparser = subparsers_action.choices.get(step) + if subparser: + last_subparser = subparser + parser = subparser._actions + self.notify(last_subparser.format_help()) diff --git a/hummingbot/client/command/history_command.py b/hummingbot/client/command/history_command.py new file mode 100644 index 0000000..4c0836b --- /dev/null +++ b/hummingbot/client/command/history_command.py @@ -0,0 +1,246 @@ +import asyncio +import threading +import time +from datetime import datetime +from decimal import Decimal +from typing import TYPE_CHECKING, List, Optional, Set, Tuple + +import pandas as pd + +from hummingbot.client.command.gateway_command import GatewayCommand +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.client.settings import MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT, AllConnectorSettings +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.model.trade_fill import TradeFill +from hummingbot.user.user_balances import UserBalances + +s_float_0 = float(0) +s_decimal_0 = Decimal("0") + + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +def get_timestamp(days_ago: float = 0.) -> float: + return time.time() - (60. * 60. * 24. * days_ago) + + +class HistoryCommand: + def history(self, # type: HummingbotApplication + days: float = 0, + verbose: bool = False, + precision: Optional[int] = None + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.history, days, verbose, precision) + return + + if self.strategy_file_name is None: + self.notify("\n Please first import a strategy config file of which to show historical performance.") + return + start_time = get_timestamp(days) if days > 0 else self.init_time + with self.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = self._get_trades_from_session( + int(start_time * 1e3), + session=session, + config_file_path=self.strategy_file_name) + if not trades: + self.notify("\n No past trades to report.") + return + if verbose: + self.list_trades(start_time) + safe_ensure_future(self.history_report(start_time, trades, precision)) + + def get_history_trades_json(self, # type: HummingbotApplication + days: float = 0): + if self.strategy_file_name is None: + return + start_time = get_timestamp(days) if days > 0 else self.init_time + with self.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = self._get_trades_from_session( + int(start_time * 1e3), + session=session, + config_file_path=self.strategy_file_name) + return list([TradeFill.to_bounty_api_json(t) for t in trades]) + + async def history_report(self, # type: HummingbotApplication + start_time: float, + trades: List[TradeFill], + precision: Optional[int] = None, + display_report: bool = True) -> Decimal: + market_info: Set[Tuple[str, str]] = set((t.market, t.symbol) for t in trades) + if display_report: + self.report_header(start_time) + return_pcts = [] + for market, symbol in market_info: + cur_trades = [t for t in trades if t.market == market and t.symbol == symbol] + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + cur_balances = await asyncio.wait_for(self.get_current_balances(market), network_timeout) + except asyncio.TimeoutError: + self.notify( + "\nA network error prevented the balances retrieval to complete. See logs for more details." + ) + raise + perf = await PerformanceMetrics.create(symbol, cur_trades, cur_balances) + if display_report: + self.report_performance_by_market(market, symbol, perf, precision) + return_pcts.append(perf.return_pct) + avg_return = sum(return_pcts) / len(return_pcts) if len(return_pcts) > 0 else s_decimal_0 + if display_report and len(return_pcts) > 1: + self.notify(f"\nAveraged Return = {avg_return:.2%}") + return avg_return + + async def get_current_balances(self, # type: HummingbotApplication + market: str): + if market in self.markets and self.markets[market].ready: + return self.markets[market].get_all_balances() + elif "Paper" in market: + paper_balances = self.client_config_map.paper_trade.paper_trade_account_balance + if paper_balances is None: + return {} + return {token: Decimal(str(bal)) for token, bal in paper_balances.items()} + else: + if UserBalances.instance().is_gateway_market(market): + await GatewayCommand.update_exchange_balances(self, market, self.client_config_map) + return GatewayCommand.all_balance(self, market) + else: + await UserBalances.instance().update_exchange_balance(market, self.client_config_map) + return UserBalances.instance().all_balances(market) + + def report_header(self, # type: HummingbotApplication + start_time: float): + lines = [] + current_time = get_timestamp() + lines.extend( + [f"\nStart Time: {datetime.fromtimestamp(start_time).strftime('%Y-%m-%d %H:%M:%S')}"] + + [f"Current Time: {datetime.fromtimestamp(current_time).strftime('%Y-%m-%d %H:%M:%S')}"] + + [f"Duration: {pd.Timedelta(seconds=int(current_time - start_time))}"] + ) + self.notify("\n".join(lines)) + + def report_performance_by_market(self, # type: HummingbotApplication + market: str, + trading_pair: str, + perf: PerformanceMetrics, + precision: int): + lines = [] + base, quote = trading_pair.split("-") + lines.extend( + [f"\n{market} / {trading_pair}"] + ) + + trades_columns = ["", "buy", "sell", "total"] + trades_data = [ + [f"{'Number of trades':<27}", perf.num_buys, perf.num_sells, perf.num_trades], + [f"{f'Total trade volume ({base})':<27}", + PerformanceMetrics.smart_round(perf.b_vol_base, precision), + PerformanceMetrics.smart_round(perf.s_vol_base, precision), + PerformanceMetrics.smart_round(perf.tot_vol_base, precision)], + [f"{f'Total trade volume ({quote})':<27}", + PerformanceMetrics.smart_round(perf.b_vol_quote, precision), + PerformanceMetrics.smart_round(perf.s_vol_quote, precision), + PerformanceMetrics.smart_round(perf.tot_vol_quote, precision)], + [f"{'Avg price':<27}", + PerformanceMetrics.smart_round(perf.avg_b_price, precision), + PerformanceMetrics.smart_round(perf.avg_s_price, precision), + PerformanceMetrics.smart_round(perf.avg_tot_price, precision)], + ] + trades_df: pd.DataFrame = pd.DataFrame(data=trades_data, columns=trades_columns) + lines.extend(["", " Trades:"] + [" " + line for line in trades_df.to_string(index=False).split("\n")]) + + assets_columns = ["", "start", "current", "change"] + assets_data = [ + [f"{base:<17}", "-", "-", "-"] if market in AllConnectorSettings.get_derivative_names() else # No base asset for derivatives because they are margined + [f"{base:<17}", + PerformanceMetrics.smart_round(perf.start_base_bal, precision), + PerformanceMetrics.smart_round(perf.cur_base_bal, precision), + PerformanceMetrics.smart_round(perf.tot_vol_base, precision)], + [f"{quote:<17}", + PerformanceMetrics.smart_round(perf.start_quote_bal, precision), + PerformanceMetrics.smart_round(perf.cur_quote_bal, precision), + PerformanceMetrics.smart_round(perf.tot_vol_quote, precision)], + [f"{trading_pair + ' price':<17}", + PerformanceMetrics.smart_round(perf.start_price), + PerformanceMetrics.smart_round(perf.cur_price), + PerformanceMetrics.smart_round(perf.cur_price - perf.start_price)], + [f"{'Base asset %':<17}", "-", "-", "-"] if market in AllConnectorSettings.get_derivative_names() else # No base asset for derivatives because they are margined + [f"{'Base asset %':<17}", + f"{perf.start_base_ratio_pct:.2%}", + f"{perf.cur_base_ratio_pct:.2%}", + f"{perf.cur_base_ratio_pct - perf.start_base_ratio_pct:.2%}"], + ] + assets_df: pd.DataFrame = pd.DataFrame(data=assets_data, columns=assets_columns) + lines.extend(["", " Assets:"] + [" " + line for line in assets_df.to_string(index=False).split("\n")]) + + perf_data = [ + ["Hold portfolio value ", f"{PerformanceMetrics.smart_round(perf.hold_value, precision)} {quote}"], + ["Current portfolio value ", f"{PerformanceMetrics.smart_round(perf.cur_value, precision)} {quote}"], + ["Trade P&L ", f"{PerformanceMetrics.smart_round(perf.trade_pnl, precision)} {quote}"] + ] + perf_data.extend( + ["Fees paid ", f"{PerformanceMetrics.smart_round(fee_amount, precision)} {fee_token}"] + for fee_token, fee_amount in perf.fees.items() + ) + perf_data.extend( + [["Total P&L ", f"{PerformanceMetrics.smart_round(perf.total_pnl, precision)} {quote}"], + ["Return % ", f"{perf.return_pct:.2%}"]] + ) + perf_df: pd.DataFrame = pd.DataFrame(data=perf_data) + lines.extend(["", " Performance:"] + + [" " + line for line in perf_df.to_string(index=False, header=False).split("\n")]) + + self.notify("\n".join(lines)) + + async def calculate_profitability(self, # type: HummingbotApplication + ) -> Decimal: + """ + Determines the profitability of the trading bot. + This function is used by the KillSwitch class. + Must be updated if the method of performance report gets updated. + """ + if not self.markets_recorder: + return s_decimal_0 + if any(not market.ready for market in self.markets.values()): + return s_decimal_0 + + start_time = self.init_time + + with self.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = self._get_trades_from_session( + int(start_time * 1e3), + session=session, + config_file_path=self.strategy_file_name) + avg_return = await self.history_report(start_time, trades, display_report=False) + return avg_return + + def list_trades(self, # type: HummingbotApplication + start_time: float): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.list_trades, start_time) + return + + lines = [] + + with self.trade_fill_db.get_new_session() as session: + queried_trades: List[TradeFill] = self._get_trades_from_session( + int(start_time * 1e3), + session=session, + number_of_rows=MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT + 1, + config_file_path=self.strategy_file_name) + df: pd.DataFrame = TradeFill.to_pandas(queried_trades) + + if len(df) > 0: + # Check if number of trades exceed maximum number of trades to display + if len(df) > MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT: + df = df[:MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT] + self.notify( + f"\n Showing last {MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT} trades in the current session.") + df_lines = format_df_for_printout(df, self.client_config_map.tables_format).split("\n") + lines.extend(["", " Recent trades:"] + + [" " + line for line in df_lines]) + else: + lines.extend(["\n No past trades in this session."]) + self.notify("\n".join(lines)) diff --git a/hummingbot/client/command/import_command.py b/hummingbot/client/command/import_command.py new file mode 100644 index 0000000..d776a52 --- /dev/null +++ b/hummingbot/client/command/import_command.py @@ -0,0 +1,91 @@ +import asyncio +import threading +from typing import TYPE_CHECKING + +from hummingbot.client.config.client_config_map import AutofillImportEnum +from hummingbot.client.config.config_helpers import ( + format_config_file_name, + load_strategy_config_map_from_file, + save_previous_strategy_value, + short_strategy_name, + validate_strategy_file, +) +from hummingbot.client.settings import CONF_PREFIX, STRATEGIES_CONF_DIR_PATH, required_exchanges +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class ImportCommand: + + def import_command(self, # type: HummingbotApplication + file_name): + if file_name is not None: + file_name = format_config_file_name(file_name) + + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.import_command, file_name) + return + safe_ensure_future(self.import_config_file(file_name)) + + async def import_config_file(self, # type: HummingbotApplication + file_name): + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + required_exchanges.clear() + if file_name is None: + file_name = await self.prompt_a_file_name() + if file_name is not None: + save_previous_strategy_value(file_name, self.client_config_map) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + strategy_path = STRATEGIES_CONF_DIR_PATH / file_name + try: + config_map = await load_strategy_config_map_from_file(strategy_path) + except Exception as e: + self.notify(f'Strategy import error: {str(e)}') + # Reset prompt settings + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + raise + self.strategy_file_name = file_name + self.strategy_name = ( + config_map.strategy + if not isinstance(config_map, dict) + else config_map.get("strategy").value # legacy + ) + self.strategy_config_map = config_map + self.notify(f"Configuration from {self.strategy_file_name} file is imported.") + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + try: + all_status_go = await self.status_check_all() + except asyncio.TimeoutError: + self.strategy_file_name = None + self.strategy_name = None + self.strategy_config_map = None + raise + if all_status_go: + self.notify("\nEnter \"start\" to start market making.") + autofill_import = self.client_config_map.autofill_import + if autofill_import != AutofillImportEnum.disabled: + self.app.set_text(autofill_import) + + async def prompt_a_file_name(self # type: HummingbotApplication + ): + example = f"{CONF_PREFIX}{short_strategy_name('pure_market_making')}_{1}.yml" + file_name = await self.app.prompt(prompt=f'Enter path to your strategy file (e.g. "{example}") >>> ') + if self.app.to_stop_config: + return + file_path = STRATEGIES_CONF_DIR_PATH / file_name + err_msg = validate_strategy_file(file_path) + if err_msg is not None: + self.notify(f"Error: {err_msg}") + return await self.prompt_a_file_name() + else: + return file_name diff --git a/hummingbot/client/command/mqtt_command.py b/hummingbot/client/command/mqtt_command.py new file mode 100644 index 0000000..2db6fa9 --- /dev/null +++ b/hummingbot/client/command/mqtt_command.py @@ -0,0 +1,108 @@ +import asyncio +import threading +import time +from typing import TYPE_CHECKING + +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.remote_iface.mqtt import MQTTGateway + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +SUBCOMMANDS = ['start', 'stop', 'restart'] + + +class MQTTCommand: + _mqtt_sleep_rate_connection_check: float = 1.0 + _mqtt_sleep_rate_autostart_retry: float = 10.0 + + def mqtt_start(self, # type: HummingbotApplication + timeout: float = 30.0 + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.mqtt_start, timeout) + return + safe_ensure_future(self.start_mqtt_async(timeout=timeout), + loop=self.ev_loop) + + def mqtt_stop(self, # type: HummingbotApplication + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.mqtt_stop) + return + safe_ensure_future(self.stop_mqtt_async(), + loop=self.ev_loop) + + def mqtt_restart(self, # type: HummingbotApplication + timeout: float = 30.0 + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.mqtt_restart, timeout) + return + safe_ensure_future(self.restart_mqtt_async(timeout=timeout), + loop=self.ev_loop) + + async def start_mqtt_async(self, # type: HummingbotApplication + timeout: float = 30.0 + ): + start_t = time.time() + if self._mqtt is None: + while True: + try: + self._mqtt = MQTTGateway(self) + self._mqtt.start() + self.logger().info('Connecting MQTT Bridge...') + while True: + if time.time() - start_t > timeout: + raise Exception( + f'Connection timed out after {timeout} seconds') + if self._mqtt.health: + self.logger().info('MQTT Bridge connected with success.') + self.notify('MQTT Bridge connected with success.') + break + await asyncio.sleep(self._mqtt_sleep_rate_connection_check) + break + except Exception as e: + if self.client_config_map.mqtt_bridge.mqtt_autostart: + s = self._mqtt_sleep_rate_autostart_retry + self.logger().error( + f'Failed to connect MQTT Bridge: {str(e)}. Retrying in {s} seconds.') + self.notify( + f'MQTT Bridge failed to connect to the broker, retrying in {s} seconds.' + ) + else: + self.logger().error( + f'Failed to connect MQTT Bridge: {str(e)}') + self.notify('MQTT Bridge failed to connect to the broker.') + self._mqtt.stop() + self._mqtt = None + + if self.client_config_map.mqtt_bridge.mqtt_autostart: + await asyncio.sleep(self._mqtt_sleep_rate_autostart_retry) + else: + break + + else: + self.logger().warning("MQTT Bridge is already running!") + self.notify('MQTT Bridge is already running!') + + async def stop_mqtt_async(self, # type: HummingbotApplication + ): + if self._mqtt is not None: + try: + self._mqtt.stop() + self._mqtt = None + self.logger().info("MQTT Bridge disconnected") + self.notify('MQTT Bridge disconnected') + except Exception as e: + self.logger().error(f'Failed to stop MQTT Bridge: {str(e)}') + else: + self.logger().error("MQTT is already stopped!") + self.notify('MQTT Bridge is already stopped!') + + async def restart_mqtt_async(self, # type: HummingbotApplication + timeout: float = 2.0 + ): + await self.stop_mqtt_async() + await self.start_mqtt_async(timeout) diff --git a/hummingbot/client/command/order_book_command.py b/hummingbot/client/command/order_book_command.py new file mode 100644 index 0000000..4fc1f77 --- /dev/null +++ b/hummingbot/client/command/order_book_command.py @@ -0,0 +1,69 @@ +from typing import TYPE_CHECKING + +import pandas as pd + +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + +import threading + + +class OrderBookCommand: + def order_book(self, # type: HummingbotApplication + lines: int = 5, + exchange: str = None, + market: str = None, + live: bool = False): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.order_book, lines, exchange, market, live) + return + safe_ensure_future(self.show_order_book(lines, exchange, market, live)) + + async def show_order_book(self, # type: HummingbotApplication + lines: int = 5, + exchange: str = None, + market: str = None, + live: bool = False): + if len(self.markets.keys()) == 0: + self.notify("There is currently no active market.") + return + if exchange is not None: + if exchange not in self.markets: + self.notify("Invalid exchange") + return + market_connector = self.markets[exchange] + else: + market_connector = list(self.markets.values())[0] + if market is not None: + market = market.upper() + if market not in market_connector.order_books: + self.notify("Invalid market") + return + trading_pair, order_book = market, market_connector.order_books[market] + else: + trading_pair, order_book = next(iter(market_connector.order_books.items())) + + def get_order_book(lines): + bids = order_book.snapshot[0][['price', 'amount']].head(lines) + bids.rename(columns={'price': 'bid_price', 'amount': 'bid_volume'}, inplace=True) + asks = order_book.snapshot[1][['price', 'amount']].head(lines) + asks.rename(columns={'price': 'ask_price', 'amount': 'ask_volume'}, inplace=True) + joined_df = pd.concat([bids, asks], axis=1) + text_lines = [ + " " + line + for line in format_df_for_printout(joined_df, self.client_config_map.tables_format).split("\n") + ] + header = f" market: {market_connector.name} {trading_pair}\n" + return header + "\n".join(text_lines) + + if live: + await self.stop_live_update() + self.app.live_updates = True + while self.app.live_updates: + await self.cls_display_delay(get_order_book(min(lines, 35)) + "\n\n Press escape key to stop update.", 0.5) + self.notify("Stopped live orderbook display update.") + else: + self.notify(get_order_book(lines)) diff --git a/hummingbot/client/command/pmm_script_command.py b/hummingbot/client/command/pmm_script_command.py new file mode 100644 index 0000000..bbbc6e0 --- /dev/null +++ b/hummingbot/client/command/pmm_script_command.py @@ -0,0 +1,12 @@ +from typing import List + + +class PMMScriptCommand: + + def pmm_script_command(self, cmd: str = None, args: List[str] = None): + if self._pmm_script_iterator is not None: + self._pmm_script_iterator.request_command(cmd, args) + else: + self.notify('No PMM script is active, command ignored') + + return True diff --git a/hummingbot/client/command/previous_strategy_command.py b/hummingbot/client/command/previous_strategy_command.py new file mode 100644 index 0000000..4f7bc70 --- /dev/null +++ b/hummingbot/client/command/previous_strategy_command.py @@ -0,0 +1,79 @@ +from typing import TYPE_CHECKING, Optional + +from hummingbot.client.config.config_helpers import parse_config_default_to_text, parse_cvar_value +from hummingbot.client.config.config_validators import validate_bool +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.core.utils.async_utils import safe_ensure_future + +from .import_command import ImportCommand + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +class PreviousCommand: + def previous_strategy( + self, # type: HummingbotApplication + option: str, + ): + if option is not None: + pass + + previous_strategy_file = self.client_config_map.previous_strategy + + if previous_strategy_file is not None: + safe_ensure_future(self.prompt_for_previous_strategy(previous_strategy_file)) + else: + self.notify("No previous strategy found.") + + async def prompt_for_previous_strategy( + self, # type: HummingbotApplication + file_name: str, + ): + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + + previous_strategy = ConfigVar( + key="previous_strategy_answer", + prompt=f"Do you want to import the previously stored config? [{file_name}] (Yes/No) >>>", + type_str="bool", + validator=validate_bool, + ) + + await self.prompt_answer(previous_strategy) + if self.app.to_stop_config: + self.app.to_stop_config = False + return + + if previous_strategy.value: + ImportCommand.import_command(self, file_name) + + # clean + self.app.change_prompt(prompt=">>> ") + + # reset input + self.placeholder_mode = False + self.app.hide_input = False + + async def prompt_answer( + self, # type: HummingbotApplication + config: ConfigVar, + input_value: Optional[str] = None, + assign_default: bool = True, + ): + + if input_value is None: + if assign_default: + self.app.set_text(parse_config_default_to_text(config)) + prompt = await config.get_prompt() + input_value = await self.app.prompt(prompt=prompt) + + if self.app.to_stop_config: + return + config.value = parse_cvar_value(config, input_value) + err_msg = await config.validate(input_value) + if err_msg is not None: + self.notify(err_msg) + config.value = None + await self.prompt_answer(config) diff --git a/hummingbot/client/command/rate_command.py b/hummingbot/client/command/rate_command.py new file mode 100644 index 0000000..8ba161d --- /dev/null +++ b/hummingbot/client/command/rate_command.py @@ -0,0 +1,67 @@ +import threading +from decimal import Decimal +from typing import TYPE_CHECKING + +from hummingbot.connector.utils import split_hb_trading_pair, validate_trading_pair +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.exceptions import OracleRateUnavailable + +s_float_0 = float(0) +s_decimal_0 = Decimal("0") + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class RateCommand: + def rate(self, # type: HummingbotApplication + pair: str, + token: str + ): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.trades) + return + if pair: + safe_ensure_future(self.show_rate(pair)) + elif token: + safe_ensure_future(self.show_token_value(token)) + + async def show_rate(self, # type: HummingbotApplication + pair: str, + ): + if not validate_trading_pair(pair): + self.notify(f"Invalid trading pair {pair}") + else: + try: + msg = await self.oracle_rate_msg(pair) + except OracleRateUnavailable: + msg = "Rate is not available." + self.notify(msg) + + async def oracle_rate_msg(self, # type: HummingbotApplication + pair: str): + if not validate_trading_pair(pair): + self.notify(f"Invalid trading pair {pair}") + else: + pair = pair.upper().strip('\"').strip("'") + rate = await RateOracle.get_instance().rate_async(pair) + if rate is None: + raise OracleRateUnavailable + base, quote = split_hb_trading_pair(pair) + return f"Source: {RateOracle.get_instance().source.name}\n1 {base} = {rate} {quote}" + + async def show_token_value(self, # type: HummingbotApplication + token: str + ): + if "-" in token: + self.notify(f"Expected a single token but got a pair {token}") + else: + self.notify(f"Source: {RateOracle.get_instance().source.name}") + rate = await RateOracle.get_instance().get_rate(base_token=token) + if rate is None: + self.notify("Rate is not available.") + return + global_token = self.client_config_map.global_token.global_token_name + token_symbol = self.client_config_map.global_token.global_token_symbol + self.notify(f"1 {token} = {token_symbol} {rate} {global_token}") diff --git a/hummingbot/client/command/silly_commands.py b/hummingbot/client/command/silly_commands.py new file mode 100644 index 0000000..39b54cc --- /dev/null +++ b/hummingbot/client/command/silly_commands.py @@ -0,0 +1,215 @@ +import asyncio +from typing import ( + TYPE_CHECKING, +) +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + +RESOURCES_PATH = "hummingbot/client/command/silly_resources/" + + +class SillyCommands: + + def be_silly(self, # type: HummingbotApplication + raw_command: str) -> bool: + command = raw_command.split(" ")[0] + if command == "hummingbot": + safe_ensure_future(self.silly_hummingbot()) + return True + elif command == "roger": + safe_ensure_future(self.silly_roger()) + return True + elif command == "fly": + safe_ensure_future(self.silly_victor()) + return True + elif command == "rein": + safe_ensure_future(self.silly_rein()) + return True + elif command in ("jack", "nullably"): + safe_ensure_future(self.silly_jack()) + return True + elif command == "hodl": + safe_ensure_future(self.silly_hodl()) + return True + elif command == "dennis": + safe_ensure_future(self.silly_dennis()) + return True + else: + return False + + async def silly_jack(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + await self.text_n_wait("Hi there,", 1) + await self.text_n_wait("This is Jack.", 1) + await self.text_n_wait("I am the lead developer of Hummingbot.", 1.5) + await self.text_n_wait("If you are reading this.", 1.5) + await self.text_n_wait("I'm probably dea...", 1.5) + for _ in range(3): + await self.text_n_wait(".", 1) + await self.text_n_wait("I'm kidding.", 1.5) + await self.text_n_wait("Don't call police.", 1.5) + await self.text_n_wait("I'm well and busy coding for you all.", 2) + await self.text_n_wait("Get in touch @nullably on Github and Twitter.", 2.5) + await self.text_n_wait("Happy trading and don't get rekt.", 2.5) + jack_1 = open(f"{RESOURCES_PATH}jack_1.txt").readlines() + jack_2 = open(f"{RESOURCES_PATH}jack_2.txt").readlines() + await self.cls_display_delay(jack_1, 1.5) + await self.cls_display_delay(jack_2, 1.5) + self.placeholder_mode = False + self.app.hide_input = False + + async def silly_hodl(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + stay_calm = open(f"{RESOURCES_PATH}hodl_stay_calm.txt").readlines() + and_hodl = open(f"{RESOURCES_PATH}hodl_and_hodl.txt").readlines() + bitcoin = open(f"{RESOURCES_PATH}hodl_bitcoin.txt").readlines() + await self.cls_display_delay(stay_calm, 1.75) + await self.cls_display_delay(and_hodl, 1.75) + for _ in range(3): + await self.cls_display_delay("\n" * 50, 0.25) + await self.cls_display_delay(bitcoin, 0.25) + await self.cls_display_delay(bitcoin, 1.75) + self.placeholder_mode = False + self.app.hide_input = False + + async def silly_hummingbot(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + for _ in range(0, 3): + await self.cls_display_delay(self.display_alert()) + hb_with_flower_1 = open(f"{RESOURCES_PATH}hb_with_flower_1.txt").readlines() + hb_with_flower_2 = open(f"{RESOURCES_PATH}hb_with_flower_2.txt").readlines() + hb_with_flower_up_close_1 = open(f"{RESOURCES_PATH}hb_with_flower_up_close_1.txt").readlines() + hb_with_flower_up_close_2 = open(f"{RESOURCES_PATH}hb_with_flower_up_close_2.txt").readlines() + for _ in range(0, 2): + for _ in range(0, 5): + await self.cls_display_delay(hb_with_flower_1, 0.125) + await self.cls_display_delay(hb_with_flower_2, 0.125) + for _ in range(0, 5): + await self.cls_display_delay(hb_with_flower_up_close_1, 0.125) + await self.cls_display_delay(hb_with_flower_up_close_2, 0.125) + self.placeholder_mode = False + self.app.hide_input = False + + async def silly_roger(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + for _ in range(0, 3): + await self.cls_display_delay(self.display_alert("roger")) + roger_1 = open(f"{RESOURCES_PATH}roger_1.txt").readlines() + roger_2 = open(f"{RESOURCES_PATH}roger_2.txt").readlines() + roger_3 = open(f"{RESOURCES_PATH}roger_3.txt").readlines() + roger_4 = open(f"{RESOURCES_PATH}roger_4.txt").readlines() + for _ in range(0, 2): + for _ in range(0, 3): + await self.cls_display_delay(roger_1, 0.1) + # await asyncio.sleep(0.3) + await self.cls_display_delay(roger_2, 0.35) + await self.cls_display_delay(roger_1, 0.25) + await self.cls_display_delay(roger_3, 0.35) + await self.cls_display_delay(roger_1, 0.25) + # await asyncio.sleep(0.4) + for _ in range(0, 2): + await self.cls_display_delay(roger_4, 0.125) + await self.cls_display_delay(roger_1, 0.3) + await self.cls_display_delay(roger_4, 0.2) + await asyncio.sleep(0.15) + self.placeholder_mode = False + self.app.hide_input = False + + async def silly_victor(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + hb_with_flower_1 = open(f"{RESOURCES_PATH}money-fly_1.txt").readlines() + hb_with_flower_2 = open(f"{RESOURCES_PATH}money-fly_2.txt").readlines() + for _ in range(0, 5): + for _ in range(0, 5): + await self.cls_display_delay(hb_with_flower_1, 0.125) + await self.cls_display_delay(hb_with_flower_2, 0.125) + await asyncio.sleep(0.3) + self.placeholder_mode = False + self.app.hide_input = False + + async def silly_rein(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + for _ in range(0, 2): + await self.cls_display_delay(self.display_alert("rein")) + rein_1 = open(f"{RESOURCES_PATH}rein_1.txt").readlines() + rein_2 = open(f"{RESOURCES_PATH}rein_2.txt").readlines() + rein_3 = open(f"{RESOURCES_PATH}rein_3.txt").readlines() + for _ in range(0, 2): + await self.cls_display_delay(rein_1, 0.5) + await self.cls_display_delay(rein_2, 0.5) + await self.cls_display_delay(rein_3, 0.5) + await asyncio.sleep(0.3) + self.placeholder_mode = False + self.app.hide_input = False + + async def text_n_wait(self, text, delay): + self.app.log(text) + await asyncio.sleep(delay) + + async def silly_dennis(self, # type: HummingbotApplication + ): + self.placeholder_mode = True + self.app.hide_input = True + dennis_loading_1 = open(f"{RESOURCES_PATH}dennis_loading_1.txt").readlines() + dennis_loading_2 = open(f"{RESOURCES_PATH}dennis_loading_2.txt").readlines() + dennis_loading_3 = open(f"{RESOURCES_PATH}dennis_loading_3.txt").readlines() + dennis_loading_4 = open(f"{RESOURCES_PATH}dennis_loading_4.txt").readlines() + for _ in range(0, 1): + await self.cls_display_delay(dennis_loading_1, 1) + await self.cls_display_delay(dennis_loading_2, 1) + await self.cls_display_delay(dennis_loading_3, 1) + await self.cls_display_delay(dennis_loading_4, 1) + # await asyncio.sleep(0.5) + dennis_1 = open(f"{RESOURCES_PATH}dennis_1.txt").readlines() + dennis_2 = open(f"{RESOURCES_PATH}dennis_2.txt").readlines() + dennis_3 = open(f"{RESOURCES_PATH}dennis_3.txt").readlines() + dennis_4 = open(f"{RESOURCES_PATH}dennis_4.txt").readlines() + for _ in range(0, 1): + await self.cls_display_delay(dennis_1, 1) + await self.cls_display_delay(dennis_2, 1) + await self.cls_display_delay(dennis_3, 1) + await self.cls_display_delay(dennis_4, 1) + await asyncio.sleep(4) + self.placeholder_mode = False + self.app.hide_input = False + + async def cls_display_delay(self, lines, delay=0.5): + self.app.output_field.buffer.save_to_undo_stack() + self.app.log("".join(lines), save_log=False) + await asyncio.sleep(delay) + self.app.output_field.buffer.undo() + + async def stop_live_update(self): + if self.app.live_updates is True: + self.app.live_updates = False + await asyncio.sleep(1) + + def display_alert(self, custom_alert = None): + alert = """ + ==================================== + ║ ║ + ║ OPEN SOFTWARE FOR OPEN FINANCE ║ + ║ ║ + ==================================== + """ + if custom_alert is not None: + try: + lines = open(f"{RESOURCES_PATH}{custom_alert}_alert.txt").readlines() + alert = "".join(lines) + except Exception: + pass + return f"{alert}" + ("\n" * 18) diff --git a/hummingbot/client/command/silly_resources/dennis_1.txt b/hummingbot/client/command/silly_resources/dennis_1.txt new file mode 100644 index 0000000..34351b6 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_1.txt @@ -0,0 +1,34 @@ + _______ _______ .__ __. .__ __. __ _______. + | \ | ____|| \ | | | \ | | | | / | + | .--. || |__ | \| | | \| | | | | (----` + | | | || __| | . ` | | . ` | | | \ \ + | '--' || |____ | |\ | | |\ | | | .----) | + |_______/ |_______||__| \__| |__| \__| |__| |_______/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_2.txt b/hummingbot/client/command/silly_resources/dennis_2.txt new file mode 100644 index 0000000..dee3dbc --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_2.txt @@ -0,0 +1,34 @@ + _______ _______ .__ __. .__ __. __ _______. + | \ | ____|| \ | | | \ | | | | / | + | .--. || |__ | \| | | \| | | | | (----` + | | | || __| | . ` | | . ` | | | \ \ + | '--' || |____ | |\ | | |\ | | | .----) | + |_______/ |_______||__| \__| |__| \__| |__| |_______/ + + .___________. _______ _______.___________. __ .__ __. _______ + | || ____| / | || | | \ | | / _____| + `---| |----`| |__ | (----`---| |----`| | | \| | | | __ + | | | __| \ \ | | | | | . ` | | | |_ | + | | | |____.----) | | | | | | |\ | | |__| | + |__| |_______|_______/ |__| |__| |__| \__| \______| + + + + + + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_3.txt b/hummingbot/client/command/silly_resources/dennis_3.txt new file mode 100644 index 0000000..4de31d2 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_3.txt @@ -0,0 +1,34 @@ + _______ _______ .__ __. .__ __. __ _______. + | \ | ____|| \ | | | \ | | | | / | + | .--. || |__ | \| | | \| | | | | (----` + | | | || __| | . ` | | . ` | | | \ \ + | '--' || |____ | |\ | | |\ | | | .----) | + |_______/ |_______||__| \__| |__| \__| |__| |_______/ + + .___________. _______ _______.___________. __ .__ __. _______ + | || ____| / | || | | \ | | / _____| + `---| |----`| |__ | (----`---| |----`| | | \| | | | __ + | | | __| \ \ | | | | | . ` | | | |_ | + | | | |____.----) | | | | | | |\ | | |__| | + |__| |_______|_______/ |__| |__| |__| \__| \______| + + _______. __ __ __ ____ ____ + / || | | | | | \ \ / / + | (----`| | | | | | \ \/ / + \ \ | | | | | | \_ _/ + .----) | | | | `----.| `----. | | + |_______/ |__| |_______||_______| |__| + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_4.txt b/hummingbot/client/command/silly_resources/dennis_4.txt new file mode 100644 index 0000000..0313032 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_4.txt @@ -0,0 +1,34 @@ + _______ _______ .__ __. .__ __. __ _______. + | \ | ____|| \ | | | \ | | | | / | + | .--. || |__ | \| | | \| | | | | (----` + | | | || __| | . ` | | . ` | | | \ \ + | '--' || |____ | |\ | | |\ | | | .----) | + |_______/ |_______||__| \__| |__| \__| |__| |_______/ + + .___________. _______ _______.___________. __ .__ __. _______ + | || ____| / | || | | \ | | / _____| + `---| |----`| |__ | (----`---| |----`| | | \| | | | __ + | | | __| \ \ | | | | | . ` | | | |_ | + | | | |____.----) | | | | | | |\ | | |__| | + |__| |_______|_______/ |__| |__| |__| \__| \______| + + _______. __ __ __ ____ ____ + / || | | | | | \ \ / / + | (----`| | | | | | \ \/ / + \ \ | | | | | | \_ _/ + .----) | | | | `----.| `----. | | + |_______/ |__| |_______||_______| |__| + + ______ ______ .___ ___. .___ ___. ___ .__ __. _______ _______. + / | / __ \ | \/ | | \/ | / \ | \ | | | \ / | + | ,----'| | | | | \ / | | \ / | / ^ \ | \| | | .--. | | (----` + | | | | | | | |\/| | | |\/| | / /_\ \ | . ` | | | | | \ \ + | `----.| `--' | | | | | | | | | / _____ \ | |\ | | '--' |.----) | + \______| \______/ |__| |__| |__| |__| /__/ \__\ |__| \__| |_______/ |_______/ + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_loading_1.txt b/hummingbot/client/command/silly_resources/dennis_loading_1.txt new file mode 100644 index 0000000..ee05767 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_loading_1.txt @@ -0,0 +1,24 @@ + + ======================================= + ║ DENNIS SILLY COMMAND ║ + ║ ║ + ║ LOADING ... ║ + ║ ███ ║ + ║ PROGRESS = 10% ║ + ======================================= + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_loading_2.txt b/hummingbot/client/command/silly_resources/dennis_loading_2.txt new file mode 100644 index 0000000..2e60ca2 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_loading_2.txt @@ -0,0 +1,24 @@ + + ======================================= + ║ DENNIS SILLY COMMAND ║ + ║ ║ + ║ LOADING ... ║ + ║ ███████████ ║ + ║ PROGRESS = 43% ║ + ======================================= + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_loading_3.txt b/hummingbot/client/command/silly_resources/dennis_loading_3.txt new file mode 100644 index 0000000..cf09e4b --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_loading_3.txt @@ -0,0 +1,24 @@ + + ======================================= + ║ DENNIS SILLY COMMAND ║ + ║ ║ + ║ LOADING ... ║ + ║ ████████████████████████████ ║ + ║ PROGRESS = 86% ║ + ======================================= + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/dennis_loading_4.txt b/hummingbot/client/command/silly_resources/dennis_loading_4.txt new file mode 100644 index 0000000..99dd756 --- /dev/null +++ b/hummingbot/client/command/silly_resources/dennis_loading_4.txt @@ -0,0 +1,24 @@ + + ======================================= + ║ DENNIS SILLY COMMAND ║ + ║ ║ + ║ LOADING ... ║ + ║ █████████████████████████████████ ║ + ║ PROGRESS = 100% ║ + ======================================= + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/hb_with_flower_1.txt b/hummingbot/client/command/silly_resources/hb_with_flower_1.txt new file mode 100644 index 0000000..785d692 --- /dev/null +++ b/hummingbot/client/command/silly_resources/hb_with_flower_1.txt @@ -0,0 +1,26 @@ + , + .--'|} _ , + / /}} -====;o`\/ } + .=\.--'`\} \-'\-'----. + //` '---./` \ |-..-'` + || /| /\/\ + \\| | `--` + |\_\\/ + \__/\\ + \\ + \| + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/hb_with_flower_2.txt b/hummingbot/client/command/silly_resources/hb_with_flower_2.txt new file mode 100644 index 0000000..2b1ef63 --- /dev/null +++ b/hummingbot/client/command/silly_resources/hb_with_flower_2.txt @@ -0,0 +1,26 @@ + , + .--'|} _ + / /}} -====;o`\ + .=\.--'`\} .----'-\ + //` '---./` `'-..- | + || /| /\/\ + \\| | `--` + |\_\\/ + \__/\\ + \\ + \| + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/hb_with_flower_up_close_1.txt b/hummingbot/client/command/silly_resources/hb_with_flower_up_close_1.txt new file mode 100644 index 0000000..51ea758 --- /dev/null +++ b/hummingbot/client/command/silly_resources/hb_with_flower_up_close_1.txt @@ -0,0 +1,26 @@ + , + .--'|} _ , + / /}}===;o`\/ } + .=\.--'`\} \-'\-'----. + //` '---./` \ |-..-'` + || /| /\/\ + \\| | `--` + |\_\\/ + \__/\\ + \\ + \| + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/hb_with_flower_up_close_2.txt b/hummingbot/client/command/silly_resources/hb_with_flower_up_close_2.txt new file mode 100644 index 0000000..a76e75b --- /dev/null +++ b/hummingbot/client/command/silly_resources/hb_with_flower_up_close_2.txt @@ -0,0 +1,26 @@ + , + .--'|} _ + / /}}===;o`\ + .=\.--'`\}.----'-\ + //` '---./` `'-..- | + || /| /\/\ + \\| | `--` + |\_\\/ + \__/\\ + \\ + \| + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/hodl_and_hodl.txt b/hummingbot/client/command/silly_resources/hodl_and_hodl.txt new file mode 100644 index 0000000..494df30 --- /dev/null +++ b/hummingbot/client/command/silly_resources/hodl_and_hodl.txt @@ -0,0 +1,26 @@ + ██████ ▄▄▄█████▓ ▄▄▄ ▓██ ██▓ ▄████▄ ▄▄▄ ██▓ ███▄ ▄███▓ +▒██ ▒ ▓ ██▒ ▓▒▒████▄ ▒██ ██▒ ▒██▀ ▀█ ▒████▄ ▓██▒ ▓██▒▀█▀ ██▒ +░ ▓██▄ ▒ ▓██░ ▒░▒██ ▀█▄ ▒██ ██░ ▒▓█ ▄ ▒██ ▀█▄ ▒██░ ▓██ ▓██░ + ▒ ██▒░ ▓██▓ ░ ░██▄▄▄▄██ ░ ▐██▓░ ▒▓▓▄ ▄██▒░██▄▄▄▄██ ▒██░ ▒██ ▒██ +▒██████▒▒ ▒██▒ ░ ▓█ ▓██▒ ░ ██▒▓░ ▒ ▓███▀ ░ ▓█ ▓██▒░██████▒▒██▒ ░██▒ +▒ ▒▓▒ ▒ ░ ▒ ░░ ▒▒ ▓▒█░ ██▒▒▒ ░ ░▒ ▒ ░ ▒▒ ▓▒█░░ ▒░▓ ░░ ▒░ ░ ░ +░ ░▒ ░ ░ ░ ▒ ▒▒ ░▓██ ░▒░ ░ ▒ ▒ ▒▒ ░░ ░ ▒ ░░ ░ ░ +░ ░ ░ ░ ░ ▒ ▒ ▒ ░░ ░ ░ ▒ ░ ░ ░ ░ + ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ░ + ▄▄▄ ███▄ █ ▓█████▄ ██░ ██ ▒█████ ▓█████▄ ██▓ +▒████▄ ██ ▀█ █ ▒██▀ ██▌ ▓██░ ██▒▒██▒ ██▒▒██▀ ██▌▓██▒ +▒██ ▀█▄ ▓██ ▀█ ██▒░██ █▌ ▒██▀▀██░▒██░ ██▒░██ █▌▒██░ +░██▄▄▄▄██ ▓██▒ ▐▌██▒░▓█▄ ▌ ░▓█ ░██ ▒██ ██░░▓█▄ ▌▒██░ + ▓█ ▓██▒▒██░ ▓██░░▒████▓ ░▓█▒░██▓░ ████▓▒░░▒████▓ ░██████▒ + ▒▒ ▓▒█░░ ▒░ ▒ ▒ ▒▒▓ ▒ ▒ ░░▒░▒░ ▒░▒░▒░ ▒▒▓ ▒ ░ ▒░▓ ░ + ▒ ▒▒ ░░ ░░ ░ ▒░ ░ ▒ ▒ ▒ ░▒░ ░ ░ ▒ ▒░ ░ ▒ ▒ ░ ░ ▒ ░ + ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░░ ░ ░ ▒ ░ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ + + + + + + diff --git a/hummingbot/client/command/silly_resources/hodl_bitcoin.txt b/hummingbot/client/command/silly_resources/hodl_bitcoin.txt new file mode 100644 index 0000000..245774f --- /dev/null +++ b/hummingbot/client/command/silly_resources/hodl_bitcoin.txt @@ -0,0 +1,28 @@ + ,.=ctE55ttt553tzs., + ,,c5;z==!!:::: .::7:==it3>., + ,xC;z!:::::: ::::::::::::!=c33x, + ,czz!::::: ::;;..===:..::: ::::!ct3. + ,C;/.:: : ;=c!:::::::::::::::.. !tt3. + /z/.: :;z!:::::J :E3. E:::::::.. !ct3. + ,E;F ::;t::::::::J :E3. E::. ::. \ttL + ;E7. :c::::F****** **. *==c;.. :: Jttk + .EJ. ;::::::L "\:. ::. Jttl + [:. :::::::::773. JE773zs. I:. ::::. It3L + ;:[ L:::::::::::L |t::!::J |:::::::: :Et3 + [:L !::::::::::::L |t::;z2F .Et:::.:::. ::[13 + E:. !::::::::::::L =Et::::::::! ::|13 + E:. (::::::::::::L ....... \:::::::! ::|i3 + [:L !:::: ::L |3t::::!3. ]::::::. ::[13 + !:( .::::: ::L |t::::::3L |:::::; ::::EE3 + E3. :::::::::;z5. Jz;;;z=F. :E:::::.::::II3[ + Jt1. :::::::[ ;z5::::;.::::;3t3 + \z1.::::::::::l...... .. ;.=ct5::::::/.::::;Et3L + \t3.:::::::::::::::J :E3. Et::::::::;!:::::;5E3L + "cz\.:::::::::::::J E3. E:::::::z! ;Zz37` + \z3. ::;:::::::::::::::;=' ./355F + \z3x. ::~=======' ,c253F + "tz3=. ..c5t32^ + "=zz3==... ...=t3z13P^ + `*=zjzczIIII3zzztE3>*^` + + diff --git a/hummingbot/client/command/silly_resources/hodl_stay_calm.txt b/hummingbot/client/command/silly_resources/hodl_stay_calm.txt new file mode 100644 index 0000000..7fc7c2c --- /dev/null +++ b/hummingbot/client/command/silly_resources/hodl_stay_calm.txt @@ -0,0 +1,26 @@ + ██████ ▄▄▄█████▓ ▄▄▄ ▓██ ██▓ ▄████▄ ▄▄▄ ██▓ ███▄ ▄███▓ +▒██ ▒ ▓ ██▒ ▓▒▒████▄ ▒██ ██▒ ▒██▀ ▀█ ▒████▄ ▓██▒ ▓██▒▀█▀ ██▒ +░ ▓██▄ ▒ ▓██░ ▒░▒██ ▀█▄ ▒██ ██░ ▒▓█ ▄ ▒██ ▀█▄ ▒██░ ▓██ ▓██░ + ▒ ██▒░ ▓██▓ ░ ░██▄▄▄▄██ ░ ▐██▓░ ▒▓▓▄ ▄██▒░██▄▄▄▄██ ▒██░ ▒██ ▒██ +▒██████▒▒ ▒██▒ ░ ▓█ ▓██▒ ░ ██▒▓░ ▒ ▓███▀ ░ ▓█ ▓██▒░██████▒▒██▒ ░██▒ +▒ ▒▓▒ ▒ ░ ▒ ░░ ▒▒ ▓▒█░ ██▒▒▒ ░ ░▒ ▒ ░ ▒▒ ▓▒█░░ ▒░▓ ░░ ▒░ ░ ░ +░ ░▒ ░ ░ ░ ▒ ▒▒ ░▓██ ░▒░ ░ ▒ ▒ ▒▒ ░░ ░ ▒ ░░ ░ ░ +░ ░ ░ ░ ░ ▒ ▒ ▒ ░░ ░ ░ ▒ ░ ░ ░ ░ + ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ░ + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/jack_1.txt b/hummingbot/client/command/silly_resources/jack_1.txt new file mode 100644 index 0000000..8d322ee --- /dev/null +++ b/hummingbot/client/command/silly_resources/jack_1.txt @@ -0,0 +1,26 @@ + _ _ ______ _____ _____ _____ _____ _____ _ _ + _| || |_| ___ \_ _|_ _/ __ \ _ |_ _| \ | | +|_ __ _| |_/ / | | | | | / \/ | | | | | | \| | + _| || |_| ___ \ | | | | | | | | | | | | | . ` | +|_ __ _| |_/ /_| |_ | | | \__/\ \_/ /_| |_| |\ | + |_||_| \____/ \___/ \_/ \____/\___/ \___/\_| \_/ + + + + + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/jack_2.txt b/hummingbot/client/command/silly_resources/jack_2.txt new file mode 100644 index 0000000..d2f0a05 --- /dev/null +++ b/hummingbot/client/command/silly_resources/jack_2.txt @@ -0,0 +1,26 @@ + _ _ _ _ _ ____ ______ ________ _ _ _____ ______ _____ _____ + _| || |_| | | | | | | \/ || \/ |_ _| \ | | __ \| ___ \| _ |_ _| +|_ __ _| |_| | | | | . . || . . | | | | \| | | \/| |_/ /| | | | | | + _| || |_| _ | | | | |\/| || |\/| | | | | . ` | | __ | ___ \| | | | | | +|_ __ _| | | | |_| | | | || | | |_| |_| |\ | |_\ \| |_/ /\ \_/ / | | + |_||_| \_| |_/\___/\_| |_/\_| |_/\___/\_| \_/\____/\____/ \___/ \_/ + + + + + + + + + + + + + + + + + + + + diff --git a/hummingbot/client/command/silly_resources/money-fly_1.txt b/hummingbot/client/command/silly_resources/money-fly_1.txt new file mode 100644 index 0000000..ca63cab --- /dev/null +++ b/hummingbot/client/command/silly_resources/money-fly_1.txt @@ -0,0 +1,39 @@ + + Riches have wings, and grandeur is a dream + - William Cowper + + + :-: + -.+ :--: + :-dNNdyo -:::: + :-::: :.hhhhdNmmhyso+::::: + - my+::::: :-hymNNNNMMMNmdhhyss -: + .omNNdys+ :::::::::::: - hymMMNdhNMMNNMMNdsNN-: + `osossyMMMNNmdddhhhhy+-: :-ohhNNNMMdsydmhdMMMNsmd-o:-: + - mNNNNMMMMMMMMMMMMMMMy.: :--sydNNmmmmNdshdmMMNNdyd:oNN:`: + :-:ssNMMMMMMMMMMMMMMMMd-: :.:ohNmhssooshMNMMMMMNdyd:ysdy:`- + -+ymNNNMMMMMMMMMMMMMMm-:::-+o+++ oyssssohMMMMMMNhhh:syh ::+.+ + .:ssssNMMMMMMMNNNNNMd.:ohhhhhhyo : oysNMMMMNmydy-sy+:. ++y. + .ohNNNNNmhs+ :: shhhhhhhhhhhhyo oydNmyhmo+s + yy-+--: + :--------: :.ohhhhhhhhhhhhhhhhhhys+ :so-- - +++++o+ os.- + ::--. yhhhhhhhhhhhhhhhhhhhhhy ``. oso- :os++o:- + -- oyyhdy oyhhhhhhhhhhhhhhhy `-ohNMMMMMNs :-.: + ::+oyhhhhdNMMdssso+ shhhhhhhhhs :smMMMMMMMMMMMMMmy+:--: + :-:+sddhhhmNMMMMMMsoosyssho+ : oyhho:+yysdMMMMMMMMMMMMMMMMMNdso++ :-- + :` mNMMhyNMMNdyhMMMdysysyhdmNNNd++-.shhhhyo +yNMMMMMMMMMMMMMMdoymNMMNy-: + -` smmyydNNhyyyymNMNNNNNNMMNdhhm+-`shhhhhhhy--oNMMMMMMMMMMMNMNdyo :-: + :. +ohdhhhmmhysdMMMMMMNdhhdy+::+.+hhhhhy -: :-yNMMNsydNMMyo+ss .: + -.yNms +o+oyddhhymmddddmhhdho : +++: hhho:-: :-:sdNNmh+- :-:: + :-.:shmms++ :+shdhdmNNmdds+: +++o+-:o -:: ::: -: + :`:so+osdmho++++ossyhs+ : ++- :..-: + -`--:: + oyhhhs++oo : ++so o:-: + :-....-:::: +oshhhyo+oo+shs+o:-: + ::--::-::::+oooshhoomy+so-: + :::-.----: ++hd+ss+: + :::: :: s+oy + +oss :::::+ + + + By + █░█ █ █▀▀ ▀█▀ █▀█ █▀█   ▄▀█ █▀▄ █▀▀ █░░ █▀▀ █▄▀ █▀▀ + ▀▄▀ █ █▄▄ ░█░ █▄█ █▀▄   █▀█ █▄▀ ██▄ █▄▄ ██▄ █░█ ██▄ diff --git a/hummingbot/client/command/silly_resources/money-fly_2.txt b/hummingbot/client/command/silly_resources/money-fly_2.txt new file mode 100644 index 0000000..0654adb --- /dev/null +++ b/hummingbot/client/command/silly_resources/money-fly_2.txt @@ -0,0 +1,39 @@ + + Riches have wings, and grandeur is a dream + - William Cowper + -+.: + :-::My. + -oN+NM+ + -: MMMMN- + `dyMMMMMy. :-:: + --+MMMMMMM:: ` :-.-:: + :.NsNMMMMMMd. .+NNmyo :-:--: :: + : .::`sMMMMMMMMMs. :.smhhhhmmhyyso+:--: :.`. + .-`-:`omMMMMMMMMN.: --hhymmmNNMMNNmdhyyy .: ::-:`: + ::: ::dMMMMMMMMM-: -:hyhNMNmyNNNNNMMNsdMd`:: :::om-: + :-omMMMMMMM.: :.+hymNNMMdssyhydMMMyyN s .-:+hNMy`: + ::oNMMMMN. :.syhNNmmNNNmydmNMMMNdsd.dds ymMMmos-+ + -:dMMMd`:-.sydmdyssssdMMMMMMMMdsh-:symMMMMMmd+. + :.yMm+.-+ +oossohMMMMMMNh++oyNMMMMMMMNh . + :.+-+yhhhhyso :-:+omMMMMNd++yNMMMMMMMMMhshy-: + :.-shhhhhhhhhhhhyo : oymNs NMMMMMMMMMMMMNd:- + -.ohhhhhhhhhhhhhhhhhhhyo .+MMMMMMMMMMMNyoo - + :-..`:shhhhhhhhhhhhhhhhhhhhh +MMMMMMMMMMMMmdy -: + :-- +oshdh+:: +shhhhhhhhhhhhhhy:oMMMMMMMMMMmhs -: + :- +syyydNMMMdoooo+ -: shhhhhhhhs::+MMMNmdyso+.`-: + :-- oddyydmNMMMMMMysosoooso++- ohhho- yhhy+ ...:+ .- + : :mNMMdsNMMNhsyNMMmysooshdmNNNd ::.+hhhhhhhh+. --: + :`:odNyydNNhyyyyhdNMNNNNNMMNmhyhs. shhhhhhhs-.: + :.+oo+ oshyhddyyshNMMMMMNdhhdh+.-+`+hhhhhs-.: + --ds +oso +osyyydmdddmmyhmmy `-- + -hhy+--: + :.:oydho++o o+ +sdmmdddy ..--: +:..o:-: + : :+ ++syho++oyso++sy ..-::-: ..--`-: + -`..-- +syyyo++++:: ::: o+: :-:: + :.......-:: :::+oyhho+:- +ys +:.: + :::-....- + :: oo :yds so-- + :-.--: + do+ss :: + :--:::---:++o:os + +sys :--- + + By + █░█ █ █▀▀ ▀█▀ █▀█ █▀█   ▄▀█ █▀▄ █▀▀ █░░ █▀▀ █▄▀ █▀▀ + ▀▄▀ █ █▄▄ ░█░ █▄█ █▀▄   █▀█ █▄▀ ██▄ █▄▄ ██▄ █░█ ██▄ diff --git a/hummingbot/client/command/silly_resources/rein_1.txt b/hummingbot/client/command/silly_resources/rein_1.txt new file mode 100644 index 0000000..b3cd28e --- /dev/null +++ b/hummingbot/client/command/silly_resources/rein_1.txt @@ -0,0 +1,7 @@ +__ __ ______ _____ _____ _ _ __ __ +\ \ / / | ___ \ ___|_ _| \ | | \ \ / / + \ \ _ __ ___ / / | |_/ / |__ | | | \| | \ \ _ __ ___ / / + \ \ | '_ ` _ \ / / | /| __| | | | . ` | \ \ | '_ ` _ \ / / + \ \| | | | | |/ / | |\ \| |___ _| |_| |\ | \ \| | | | | |/ / + \_\_| |_| |_/_/ \_| \_\____/ \___/\_| \_/ \_\_| |_| |_/_/ + \ No newline at end of file diff --git a/hummingbot/client/command/silly_resources/rein_2.txt b/hummingbot/client/command/silly_resources/rein_2.txt new file mode 100644 index 0000000..450691b --- /dev/null +++ b/hummingbot/client/command/silly_resources/rein_2.txt @@ -0,0 +1,7 @@ +__ __ _ _ ___ _____ __ __ +\ \ / / | | | |/ _ \ / ___| \ \ / / + \ \ _ __ ___ / / | | | / /_\ \\ `--. \ \ _ __ ___ / / + \ \ | '_ ` _ \ / / | |/\| | _ | `--. \ \ \ | '_ ` _ \ / / + \ \| | | | | |/ / \ /\ / | | |/\__/ / \ \| | | | | |/ / + \_\_| |_| |_/_/ \/ \/\_| |_/\____/ \_\_| |_| |_/_/ + \ No newline at end of file diff --git a/hummingbot/client/command/silly_resources/rein_3.txt b/hummingbot/client/command/silly_resources/rein_3.txt new file mode 100644 index 0000000..1315211 --- /dev/null +++ b/hummingbot/client/command/silly_resources/rein_3.txt @@ -0,0 +1,7 @@ +__ __ _ _ ___________ _____ __ __ +\ \ / / | | | || ___| ___ \ ___| \ \ / / + \ \ _ __ ___ / / | |_| || |__ | |_/ / |__ \ \ _ __ ___ / / + \ \ | '_ ` _ \ / / | _ || __|| /| __| \ \ | '_ ` _ \ / / + \ \| | | | | |/ / | | | || |___| |\ \| |___ \ \| | | | | |/ / + \_\_| |_| |_/_/ \_| |_/\____/\_| \_\____/ \_\_| |_| |_/_/ + \ No newline at end of file diff --git a/hummingbot/client/command/silly_resources/roger_1.txt b/hummingbot/client/command/silly_resources/roger_1.txt new file mode 100644 index 0000000..f2e781c --- /dev/null +++ b/hummingbot/client/command/silly_resources/roger_1.txt @@ -0,0 +1,28 @@ + %%% + %%%%%%%***********%%%%%%% + %%%%** **%%%% + %%%* *%%% + %%%* *%%% + %%(* ** .*********** %%%%%% *%%% + %%%* .** *****************.%%%%%%%%%% *%%% + %%* ***************** %%%%%%%%%%%%%% .*%% + %%* *************** %%%%%%%%%%^ *%% + %%* .**** ******* %%%%%%%%% ./%% + %%* ** ****** %%%%%%%%%% *%% + %#. ****** %%%%%%%%%%%%%%%%%%%%% ,%% + %%* **^ %%%%%%%%%%%%%%%%%%%%%%%% *% + %#. %%%%%%%%%%%%%%%%%%%%%%% ,%% + %%* *%%%%%%% %%%%%%%%%%%% . *%% + %%* *%%%%%%% %%%%%%%%%%%%%* .(%% + %%* %%%%%%%%%%% %%%%%%%%%%%%% *%% + %%* %%%%%%%%%% %.%%%%%%%%%%% .*%% + %%%* %%%%%% % %%^ %%%%%%%%%% *%%, + %%#* %%%% % *%%% + %%%* % *%%% + %%%* .*%%% + %%%%** **%%%% + (%%%%%%***********%%%%%%( + + theholyroger.com + + diff --git a/hummingbot/client/command/silly_resources/roger_2.txt b/hummingbot/client/command/silly_resources/roger_2.txt new file mode 100644 index 0000000..ef0170e --- /dev/null +++ b/hummingbot/client/command/silly_resources/roger_2.txt @@ -0,0 +1,28 @@ + %%% + %%%%%%%***********%%%%%%% + %%%%** **%%%% + %%%* ** *%%% + %%%* ** ****** *%%% + %%(* ******************* *%%% + %%%* ******************* %%%%% *%%% + %%* *****************, %%%%%%%% .*%% + %%* *** *********** *%%%%%%%%% *%% + %%* ***********. ,%%%%%%%%%%% ./%% + %%* .%%%%% %%%%%%%%%, *%% + %#. %%%%%%%%%%%%%%%%%%%%%%%%%%%%% . ,%% + %%* %%%%%%%%%%%%%%%%%%%%%%%%%%% *% + %#. %%%%%%%%%%%%%%%%%%%%%%% ,%% + %%* %%%%%%%%%%% %%%%%%%%%% *%% + %%* %%%%%%%%%%% %%%%%%%%%% .(%% + %%* .%%/%%%%% % #%%%%%%%%%%% *%% + %%* %%% %% %% %%%%%%%%%%%%%% .*%% + %%%* %%% %%. %%%%%%%%%%% *%%, + %%#* %%%%%%%%%%% *%%% + %%%* * *%%% + %%%* .*%%% + %%%%** **%%%% + (%%%%%%***********%%%%%%( + + theholyroger.com + + diff --git a/hummingbot/client/command/silly_resources/roger_3.txt b/hummingbot/client/command/silly_resources/roger_3.txt new file mode 100644 index 0000000..9cd016b --- /dev/null +++ b/hummingbot/client/command/silly_resources/roger_3.txt @@ -0,0 +1,28 @@ + %%% + %%%%%%%***********%%%%%%% + %%%%** **%%%% + %%%* *%%% + %%%* *%%% + %%(* ***/%%%%%%%%%% % *%%% + %%%* ,******* %%%%%%%%%%%% *%%% + %%* ************ *%%%%%%%%%%%%% .*%% + %%* **************** %%%%%%%%%% *%% + %%* ******************* %%%%%%%%% ./%% + %%* ******* ***** %%%%%%%%% *%% + %#. ***** ***** %%%%%%%%%%%% % ,%% + %%* ** **** %%%%%%%%%%%%%%%%%%%% (%% % *% + %#. *** %%%%%%%%%%%%%%%%%%%%%%%%%%%% ,%% + %%* ** %%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%( *%% + %%* %%%%%%%%%%, /%%%%%%%%%%%%%%%% .(%% + %%* %%%%%%%% %. %%%%%%%%% *%% + %%* %%%%%%%%%% %% .*%% + %%%* %%%%%%,%%# *%%, + %%#* #%%% %%% *%%% + %%%* %%%% *%%% + %%%* .*%%% + %%%%** **%%%% + (%%%%%%***********%%%%%%( + + theholyroger.com + + diff --git a/hummingbot/client/command/silly_resources/roger_4.txt b/hummingbot/client/command/silly_resources/roger_4.txt new file mode 100644 index 0000000..f7b60d0 --- /dev/null +++ b/hummingbot/client/command/silly_resources/roger_4.txt @@ -0,0 +1,28 @@ + %%% + %%%%%%%***********%%%%%%% + %%%%** **%%%% + %%%* *%%% + %%%* *%%% + %%(* *%%% + %%%* *%%% + %%* .*%% + %%* *%% + %%* ./%% + %%* *%% + %#. ,%% + %%* *% + %#. ,%% + %%* *%% + %%* .(%% + %%* *%% + %%* .*%% + %%%* *%%, + %%#* *%%% + %%%* *%%% + %%%* .*%%% + %%%%** **%%%% + (%%%%%%***********%%%%%%( + + + + diff --git a/hummingbot/client/command/silly_resources/roger_alert.txt b/hummingbot/client/command/silly_resources/roger_alert.txt new file mode 100644 index 0000000..e30120c --- /dev/null +++ b/hummingbot/client/command/silly_resources/roger_alert.txt @@ -0,0 +1,6 @@ + + ======================================= + ║ ║ + ║ SO YOU THINK YOU'RE A REAL ROGER? ║ + ║ ║ + ======================================= diff --git a/hummingbot/client/command/start_command.py b/hummingbot/client/command/start_command.py new file mode 100644 index 0000000..335107b --- /dev/null +++ b/hummingbot/client/command/start_command.py @@ -0,0 +1,302 @@ +import asyncio +import importlib +import inspect +import platform +import sys +import threading +import time +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set + +import pandas as pd + +import hummingbot.client.settings as settings +from hummingbot import init_logging +from hummingbot.client.command.gateway_api_manager import GatewayChainApiManager +from hummingbot.client.command.gateway_command import GatewayCommand +from hummingbot.client.config.config_helpers import get_strategy_starter_file +from hummingbot.client.config.config_validators import validate_bool +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.connector.connector_status import get_connector_status, warning_messages +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.exceptions import InvalidScriptModule, OracleRateUnavailable +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +GATEWAY_READY_TIMEOUT = 300 # seconds + + +class StartCommand(GatewayChainApiManager): + _in_start_check: bool = False + + async def _run_clock(self): + with self.clock as clock: + await clock.run() + + async def wait_till_ready(self, # type: HummingbotApplication + func: Callable, *args, **kwargs): + while True: + all_ready = all([market.ready for market in self.markets.values()]) + if not all_ready: + await asyncio.sleep(0.5) + else: + return func(*args, **kwargs) + + def _strategy_uses_gateway_connector(self, required_exchanges: Set[str]) -> bool: + exchange_settings: List[settings.ConnectorSetting] = [ + settings.AllConnectorSettings.get_connector_settings().get(e, None) + for e in required_exchanges + ] + return any([s.uses_gateway_generic_connector() + for s in exchange_settings]) + + def start(self, # type: HummingbotApplication + log_level: Optional[str] = None, + script: Optional[str] = None, + is_quickstart: Optional[bool] = False): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.start, log_level, script) + return + safe_ensure_future(self.start_check(log_level, script, is_quickstart), loop=self.ev_loop) + + async def start_check(self, # type: HummingbotApplication + log_level: Optional[str] = None, + script: Optional[str] = None, + is_quickstart: Optional[bool] = False): + + if self._in_start_check or (self.strategy_task is not None and not self.strategy_task.done()): + self.notify('The bot is already running - please run "stop" first') + return + + self._in_start_check = True + + if settings.required_rate_oracle: + # If the strategy to run requires using the rate oracle to find FX rates, validate there is a rate for + # each configured token pair + if not (await self.confirm_oracle_conversion_rate()): + self.notify("The strategy failed to start.") + self._in_start_check = False + return + + if self.strategy_file_name and self.strategy_name and is_quickstart: + if self._strategy_uses_gateway_connector(settings.required_exchanges): + try: + await asyncio.wait_for(self._gateway_monitor.ready_event.wait(), timeout=GATEWAY_READY_TIMEOUT) + except asyncio.TimeoutError: + self.notify(f"TimeoutError waiting for gateway service to go online... Please ensure Gateway is configured correctly." + f"Unable to start strategy {self.strategy_name}. ") + self._in_start_check = False + self.strategy_name = None + self.strategy_file_name = None + raise + + if script: + file_name = script.split(".")[0] + self.strategy_file_name = file_name + self.strategy_name = file_name + elif not await self.status_check_all(notify_success=False): + self.notify("Status checks failed. Start aborted.") + self._in_start_check = False + return + if self._last_started_strategy_file != self.strategy_file_name: + init_logging("hummingbot_logs.yml", + self.client_config_map, + override_log_level=log_level.upper() if log_level else None, + strategy_file_path=self.strategy_file_name) + self._last_started_strategy_file = self.strategy_file_name + + # If macOS, disable App Nap. + if platform.system() == "Darwin": + import appnope + appnope.nope() + + self._initialize_notifiers() + try: + self._initialize_strategy(self.strategy_name) + except NotImplementedError: + self._in_start_check = False + self.strategy_name = None + self.strategy_file_name = None + self.notify("Invalid strategy. Start aborted.") + raise + + if any([str(exchange).endswith("paper_trade") for exchange in settings.required_exchanges]): + self.notify("\nPaper Trading Active: All orders are simulated and no real orders are placed.") + + for exchange in settings.required_exchanges: + connector: str = str(exchange) + status: str = get_connector_status(connector) + warning_msg: Optional[str] = warning_messages.get(connector, None) + + # confirm gateway connection + conn_setting: settings.ConnectorSetting = settings.AllConnectorSettings.get_connector_settings()[connector] + if conn_setting.uses_gateway_generic_connector(): + connector_details: Dict[str, Any] = conn_setting.conn_init_parameters() + if connector_details: + data: List[List[str]] = [ + ["chain", connector_details['chain']], + ["network", connector_details['network']], + ["address", connector_details['address']] + ] + + # check for node URL + await self._test_node_url_from_gateway_config(connector_details['chain'], connector_details['network']) + + await GatewayCommand.update_exchange_balances(self, connector, self.client_config_map) + balances: List[str] = [ + f"{str(PerformanceMetrics.smart_round(v, 8))} {k}" + for k, v in GatewayCommand.all_balance(self, connector).items() + ] + data.append(["balances", ""]) + for bal in balances: + data.append(["", bal]) + wallet_df: pd.DataFrame = pd.DataFrame(data=data, columns=["", f"{connector} configuration"]) + self.notify(wallet_df.to_string(index=False)) + + if not is_quickstart: + self.app.clear_input() + self.placeholder_mode = True + use_configuration = await self.app.prompt(prompt="Do you want to continue? (Yes/No) >>> ") + self.placeholder_mode = False + self.app.change_prompt(prompt=">>> ") + + if use_configuration in ["N", "n", "No", "no"]: + self._in_start_check = False + return + + if use_configuration not in ["Y", "y", "Yes", "yes"]: + self.notify("Invalid input. Please execute the `start` command again.") + self._in_start_check = False + return + + # Display custom warning message for specific connectors + elif warning_msg is not None: + self.notify(f"\nConnector status: {status}\n" + f"{warning_msg}") + + # Display warning message if the exchange connector has outstanding issues or not working + elif status.endswith("UNKNOWN"): + self.notify(f"\nConnector status: {status}. This connector has one or more issues.\n" + "Refer to our Github page for more info: https://github.com/hummingbot/hummingbot") + + self.notify(f"\nStatus check complete. Starting '{self.strategy_name}' strategy...") + await self.start_market_making() + + self._in_start_check = False + + # We always start the RateOracle. It is required for PNL calculation. + RateOracle.get_instance().start() + if self._mqtt: + self._mqtt.patch_loggers() + + def start_script_strategy(self): + script_strategy = self.load_script_class() + markets_list = [] + for conn, pairs in script_strategy.markets.items(): + markets_list.append((conn, list(pairs))) + self._initialize_markets(markets_list) + self.strategy = script_strategy(self.markets) + + def load_script_class(self): + """ + Imports the script module based on its name (module file name) and returns the loaded script class + + :param script_name: name of the module where the script class is defined + """ + script_name = self.strategy_file_name + module = sys.modules.get(f"{settings.SCRIPT_STRATEGIES_MODULE}.{script_name}") + if module is not None: + script_module = importlib.reload(module) + else: + script_module = importlib.import_module(f".{script_name}", package=settings.SCRIPT_STRATEGIES_MODULE) + try: + script_class = next((member for member_name, member in inspect.getmembers(script_module) + if inspect.isclass(member) and + (issubclass(member, ScriptStrategyBase) or issubclass(member, DirectionalStrategyBase)) and + member not in [ScriptStrategyBase, DirectionalStrategyBase])) + except StopIteration: + raise InvalidScriptModule(f"The module {script_name} does not contain any subclass of ScriptStrategyBase") + return script_class + + def is_current_strategy_script_strategy(self) -> bool: + script_file_name = settings.SCRIPT_STRATEGIES_PATH / f"{self.strategy_file_name}.py" + return script_file_name.exists() + + async def start_market_making(self, # type: HummingbotApplication + ): + try: + self.start_time = time.time() * 1e3 # Time in milliseconds + tick_size = self.client_config_map.tick_size + self.logger().info(f"Creating the clock with tick size: {tick_size}") + self.clock = Clock(ClockMode.REALTIME, tick_size=tick_size) + for market in self.markets.values(): + if market is not None: + self.clock.add_iterator(market) + self.markets_recorder.restore_market_states(self.strategy_file_name, market) + if len(market.limit_orders) > 0: + self.notify(f"Canceling dangling limit orders on {market.name}...") + await market.cancel_all(5.0) + if self.strategy: + self.clock.add_iterator(self.strategy) + try: + self._pmm_script_iterator = self.client_config_map.pmm_script_mode.get_iterator( + self.strategy_name, list(self.markets.values()), self.strategy + ) + except ValueError as e: + self.notify(f"Error: {e}") + if self._pmm_script_iterator is not None: + self.clock.add_iterator(self._pmm_script_iterator) + self.notify(f"PMM script ({self.client_config_map.pmm_script_mode.pmm_script_file_path}) started.") + self.strategy_task: asyncio.Task = safe_ensure_future(self._run_clock(), loop=self.ev_loop) + self.notify(f"\n'{self.strategy_name}' strategy started.\n" + f"Run `status` command to query the progress.") + self.logger().info("start command initiated.") + + if self._trading_required: + self.kill_switch = self.client_config_map.kill_switch_mode.get_kill_switch(self) + await self.wait_till_ready(self.kill_switch.start) + except Exception as e: + self.logger().error(str(e), exc_info=True) + + def _initialize_strategy(self, strategy_name: str): + if self.is_current_strategy_script_strategy(): + self.start_script_strategy() + else: + start_strategy: Callable = get_strategy_starter_file(strategy_name) + if strategy_name in settings.STRATEGIES: + start_strategy(self) + else: + raise NotImplementedError + + async def confirm_oracle_conversion_rate(self, # type: HummingbotApplication + ) -> bool: + try: + result = False + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + for pair in settings.rate_oracle_pairs: + msg = await self.oracle_rate_msg(pair) + self.notify("\nRate Oracle:\n" + msg) + config = ConfigVar(key="confirm_oracle_use", + type_str="bool", + prompt="Please confirm to proceed if the above oracle source and rates are correct for " + "this strategy (Yes/No) >>> ", + required_if=lambda: True, + validator=lambda v: validate_bool(v)) + await self.prompt_a_config_legacy(config) + if config.value: + result = True + except OracleRateUnavailable: + self.notify("Oracle rate is not available.") + finally: + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + return result diff --git a/hummingbot/client/command/status_command.py b/hummingbot/client/command/status_command.py new file mode 100644 index 0000000..1feab91 --- /dev/null +++ b/hummingbot/client/command/status_command.py @@ -0,0 +1,227 @@ +import asyncio +import threading +import time +from collections import OrderedDict, deque +from typing import TYPE_CHECKING, Dict, List + +import pandas as pd + +from hummingbot import check_dev_mode +from hummingbot.client.command.gateway_command import GatewayCommand +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + get_strategy_config_map, + missing_required_configs_legacy, +) +from hummingbot.client.config.security import Security +from hummingbot.client.settings import ethereum_wallet_required, required_exchanges +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger.application_warning import ApplicationWarning +from hummingbot.user.user_balances import UserBalances + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class StatusCommand: + def _expire_old_application_warnings(self, # type: HummingbotApplication + ): + now: float = time.time() + expiry_threshold: float = now - self.APP_WARNING_EXPIRY_DURATION + while len(self._app_warnings) > 0 and self._app_warnings[0].timestamp < expiry_threshold: + self._app_warnings.popleft() + + def _format_application_warnings(self, # type: HummingbotApplication + ) -> str: + lines: List[str] = [] + if len(self._app_warnings) < 1: + return "" + + lines.append("\n Warnings:") + + if len(self._app_warnings) < self.APP_WARNING_STATUS_LIMIT: + for app_warning in reversed(self._app_warnings): + lines.append(f" * {pd.Timestamp(app_warning.timestamp, unit='s')} - " + f"({app_warning.logger_name}) - {app_warning.warning_msg}") + else: + module_based_warnings: OrderedDict = OrderedDict() + for app_warning in reversed(self._app_warnings): + logger_name: str = app_warning.logger_name + if logger_name not in module_based_warnings: + module_based_warnings[logger_name] = deque([app_warning]) + else: + module_based_warnings[logger_name].append(app_warning) + + warning_lines: List[str] = [] + while len(warning_lines) < self.APP_WARNING_STATUS_LIMIT: + logger_keys: List[str] = list(module_based_warnings.keys()) + for key in logger_keys: + warning_item: ApplicationWarning = module_based_warnings[key].popleft() + if len(module_based_warnings[key]) < 1: + del module_based_warnings[key] + warning_lines.append(f" * {pd.Timestamp(warning_item.timestamp, unit='s')} - " + f"({key}) - {warning_item.warning_msg}") + lines.extend(warning_lines[:self.APP_WARNING_STATUS_LIMIT]) + + return "\n".join(lines) + + async def strategy_status(self, live: bool = False): + active_paper_exchanges = [exchange for exchange in self.markets.keys() if exchange.endswith("paper_trade")] + + paper_trade = "\n Paper Trading Active: All orders are simulated, and no real orders are placed." if len(active_paper_exchanges) > 0 \ + else "" + if asyncio.iscoroutinefunction(self.strategy.format_status): + st_status = await self.strategy.format_status() + else: + st_status = self.strategy.format_status() + status = paper_trade + "\n" + st_status + if self._pmm_script_iterator is not None and live is False: + self._pmm_script_iterator.request_status() + return status + + def application_warning(self): + # Application warnings. + self._expire_old_application_warnings() + if check_dev_mode() and len(self._app_warnings) > 0: + app_warning = self._format_application_warnings() + self.notify(app_warning) + return app_warning + + async def validate_required_connections( + self # type: HummingbotApplication + ) -> Dict[str, str]: + invalid_conns = {} + if not any([str(exchange).endswith("paper_trade") for exchange in required_exchanges]): + if any([UserBalances.instance().is_gateway_market(exchange) for exchange in required_exchanges]): + connections = await GatewayCommand.update_exchange(self, self.client_config_map, exchanges=required_exchanges) + else: + connections = await UserBalances.instance().update_exchanges(self.client_config_map, exchanges=required_exchanges) + invalid_conns.update({ex: err_msg for ex, err_msg in connections.items() + if ex in required_exchanges and err_msg is not None}) + if ethereum_wallet_required(): + err_msg = UserBalances.validate_ethereum_wallet() + if err_msg is not None: + invalid_conns["ethereum"] = err_msg + return invalid_conns + + def missing_configurations_legacy( + self, # type: HummingbotApplication + ) -> List[str]: + config_map = self.strategy_config_map + missing_configs = [] + if not isinstance(config_map, ClientConfigAdapter): + missing_configs = missing_required_configs_legacy( + get_strategy_config_map(self.strategy_name) + ) + return missing_configs + + def validate_configs( + self, # type: HummingbotApplication + ) -> List[str]: + config_map = self.strategy_config_map + validation_errors = config_map.validate_model() if isinstance(config_map, ClientConfigAdapter) else [] + return validation_errors + + def status(self, # type: HummingbotApplication + live: bool = False): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.status, live) + return + + safe_ensure_future(self.status_check_all(live=live), loop=self.ev_loop) + + async def status_check_all(self, # type: HummingbotApplication + notify_success=True, + live=False) -> bool: + + if self.strategy is not None: + if live: + await self.stop_live_update() + self.app.live_updates = True + while self.app.live_updates and self.strategy: + script_status = '\n Status from PMM script would not appear here. ' \ + 'Simply run the status command without "--live" to see PMM script status.' + await self.cls_display_delay( + await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 0.1 + ) + self.app.live_updates = False + self.notify("Stopped live status display update.") + else: + self.notify(await self.strategy_status()) + return True + + # Preliminary checks. + self.notify("\nPreliminary checks:") + if self.strategy_name is None or self.strategy_file_name is None: + self.notify(' - Strategy check: Please import or create a strategy.') + return False + + if not Security.is_decryption_done(): + self.notify(' - Security check: Encrypted files are being processed. Please wait and try again later.') + return False + + missing_configs = self.missing_configurations_legacy() + if missing_configs: + self.notify(" - Strategy check: Incomplete strategy configuration. The following values are missing.") + for config in missing_configs: + self.notify(f" {config.key}") + elif notify_success: + self.notify(' - Strategy check: All required parameters confirmed.') + validation_errors = self.validate_configs() + if len(validation_errors) != 0: + self.notify(" - Strategy check: Validation of the config maps failed. The following errors were flagged.") + for error in validation_errors: + self.notify(f" {error}") + return False + + network_timeout = float(self.client_config_map.commands_timeout.other_commands_timeout) + try: + invalid_conns = await asyncio.wait_for(self.validate_required_connections(), network_timeout) + except asyncio.TimeoutError: + self.notify("\nA network error prevented the connection check to complete. See logs for more details.") + raise + if invalid_conns: + self.notify(' - Exchange check: Invalid connections:') + for ex, err_msg in invalid_conns.items(): + self.notify(f" {ex}: {err_msg}") + elif notify_success: + self.notify(' - Exchange check: All connections confirmed.') + + if invalid_conns or missing_configs or len(validation_errors) != 0: + return False + + loading_markets: List[ConnectorBase] = [] + for market in self.markets.values(): + if not market.ready: + loading_markets.append(market) + + if len(loading_markets) > 0: + self.notify(" - Connectors check: Waiting for connectors " + f"{','.join([m.name.capitalize() for m in loading_markets])}" + " to get ready for trading. \n" + " Please keep the bot running and try to start again in a few minutes. \n") + + for market in loading_markets: + market_status_df = pd.DataFrame(data=market.status_dict.items(), columns=["description", "status"]) + self.notify( + f" - {market.display_name.capitalize()} connector status:\n" + + "\n".join([" " + line for line in market_status_df.to_string(index=False,).split("\n")]) + + "\n" + ) + return False + + elif not all([market.network_status is NetworkStatus.CONNECTED for market in self.markets.values()]): + offline_markets: List[str] = [ + market_name + for market_name, market + in self.markets.items() + if market.network_status is not NetworkStatus.CONNECTED + ] + for offline_market in offline_markets: + self.notify(f" - Connector check: {offline_market} is currently offline.") + return False + + self.notify(" - All checks: Confirmed.") + return True diff --git a/hummingbot/client/command/stop_command.py b/hummingbot/client/command/stop_command.py new file mode 100644 index 0000000..3f9cdf8 --- /dev/null +++ b/hummingbot/client/command/stop_command.py @@ -0,0 +1,68 @@ +import asyncio +import platform +import threading +from typing import TYPE_CHECKING + +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + + +class StopCommand: + def stop(self, # type: HummingbotApplication + skip_order_cancellation: bool = False): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.stop, skip_order_cancellation) + return + safe_ensure_future(self.stop_loop(skip_order_cancellation), loop=self.ev_loop) + + async def stop_loop(self, # type: HummingbotApplication + skip_order_cancellation: bool = False): + self.logger().info("stop command initiated.") + self.notify("\nWinding down...") + + # Restore App Nap on macOS. + if platform.system() == "Darwin": + import appnope + appnope.nap() + + if self._pmm_script_iterator is not None: + self._pmm_script_iterator.stop(self.clock) + + if isinstance(self.strategy, ScriptStrategyBase): + self.strategy.on_stop() + + if self._trading_required and not skip_order_cancellation: + # Remove the strategy from clock before cancelling orders, to + # prevent race condition where the strategy tries to create more + # orders during cancellation. + if self.clock: + self.clock.remove_iterator(self.strategy) + success = await self._cancel_outstanding_orders() + # Give some time for cancellation events to trigger + await asyncio.sleep(0.5) + if success: + # Only erase markets when cancellation has been successful + self.markets = {} + + if self.strategy_task is not None and not self.strategy_task.cancelled(): + self.strategy_task.cancel() + + if RateOracle.get_instance().started: + RateOracle.get_instance().stop() + + if self.markets_recorder is not None: + self.markets_recorder.stop() + + if self.kill_switch is not None: + self.kill_switch.stop() + + self.strategy_task = None + self.strategy = None + self.market_pair = None + self.clock = None + self.markets_recorder = None + self.market_trading_pairs_map.clear() diff --git a/hummingbot/client/command/ticker_command.py b/hummingbot/client/command/ticker_command.py new file mode 100644 index 0000000..0563cc8 --- /dev/null +++ b/hummingbot/client/command/ticker_command.py @@ -0,0 +1,66 @@ +import threading +from typing import TYPE_CHECKING + +import pandas as pd + +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.core.data_type.common import PriceType +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +class TickerCommand: + def ticker(self, # type: HummingbotApplication + live: bool = False, + exchange: str = None, + market: str = None): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.ticker, live, exchange, market) + return + safe_ensure_future(self.show_ticker(live, exchange, market)) + + async def show_ticker(self, # type: HummingbotApplication + live: bool = False, + exchange: str = None, + market: str = None): + if len(self.markets.keys()) == 0: + self.notify("\n This command can only be used while a strategy is running") + return + if exchange is not None: + if exchange not in self.markets: + self.notify("\n Please select a valid exchange from the running strategy") + return + market_connector = self.markets[exchange] + else: + market_connector = list(self.markets.values())[0] + if market is not None: + market = market.upper() + if market not in market_connector.order_books: + self.notify("\n Please select a valid trading pair from the running strategy") + return + trading_pair, order_book = market, market_connector.order_books[market] + else: + trading_pair, order_book = next(iter(market_connector.order_books.items())) + + def get_ticker(): + columns = ["Best Bid", "Best Ask", "Mid Price", "Last Trade"] + data = [[ + float(market_connector.get_price_by_type(trading_pair, PriceType.BestBid)), + float(market_connector.get_price_by_type(trading_pair, PriceType.BestAsk)), + float(market_connector.get_price_by_type(trading_pair, PriceType.MidPrice)), + float(market_connector.get_price_by_type(trading_pair, PriceType.LastTrade)) + ]] + ticker_df = pd.DataFrame(data=data, columns=columns) + ticker_df_str = format_df_for_printout(ticker_df, self.client_config_map.tables_format) + return f" Market: {market_connector.name}\n{ticker_df_str}" + + if live: + await self.stop_live_update() + self.app.live_updates = True + while self.app.live_updates: + await self.cls_display_delay(get_ticker() + "\n\n Press escape key to stop update.", 1) + self.notify("Stopped live ticker display update.") + else: + self.notify(get_ticker()) diff --git a/hummingbot/client/config/__init__.py b/hummingbot/client/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/client/config/client_config_map.py b/hummingbot/client/config/client_config_map.py new file mode 100644 index 0000000..b3a77f8 --- /dev/null +++ b/hummingbot/client/config/client_config_map.py @@ -0,0 +1,1246 @@ +import json +import os.path +import random +import re +from abc import ABC, abstractmethod +from decimal import Decimal +from os.path import dirname +from pathlib import Path +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union + +from pydantic import BaseModel, Field, SecretStr, root_validator, validator +from tabulate import tabulate_formats + +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_methods import using_exchange as using_exchange_pointer +from hummingbot.client.config.config_validators import validate_bool, validate_float +from hummingbot.client.settings import ( + DEFAULT_GATEWAY_CERTS_PATH, + DEFAULT_LOG_FILE_PATH, + PMM_SCRIPTS_PATH, + AllConnectorSettings, +) +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.connector_metrics_collector import ( + DummyMetricsCollector, + MetricsCollector, + TradeVolumeMetricCollector, +) +from hummingbot.connector.exchange.ascend_ex.ascend_ex_utils import AscendExConfigMap +from hummingbot.connector.exchange.binance.binance_utils import BinanceConfigMap +from hummingbot.connector.exchange.gate_io.gate_io_utils import GateIOConfigMap +from hummingbot.connector.exchange.kucoin.kucoin_utils import KuCoinConfigMap +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.rate_oracle.rate_oracle import RATE_ORACLE_SOURCES, RateOracle +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils.kill_switch import ActiveKillSwitch, KillSwitch, PassThroughKillSwitch +from hummingbot.notifier.telegram_notifier import TelegramNotifier +from hummingbot.pmm_script.pmm_script_iterator import PMMScriptIterator +from hummingbot.strategy.strategy_base import StrategyBase + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + +PMM_SCRIPT_ENABLED_KEY = "pmm_script_enabled" +PMM_SCRIPT_FILE_PATH_KEY = "pmm_script_file_path" + + +def generate_client_id() -> str: + vals = [random.choice(range(0, 256)) for i in range(0, 20)] + return "".join([f"{val:02x}" for val in vals]) + + +def using_exchange(exchange: str) -> Callable: + return using_exchange_pointer(exchange) + + +class MQTTBridgeConfigMap(BaseClientModel): + mqtt_host: str = Field( + default="localhost", + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the MQTT hostname to connect to (e.g. localhost)" + ), + ), + ) + mqtt_port: int = Field( + default=1883, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the MQTT port to connect to (e.g. 1883)" + ), + ), + ) + mqtt_username: str = Field( + default="", + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the username for connecting to the MQTT broker" + ), + ), + ) + mqtt_password: str = Field( + default="", + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the password for connecting to the MQTT broker" + ), + ), + ) + mqtt_namespace: str = Field( + default='hbot', + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the mqtt uri namespace (Default='hbot')" + ), + ), + ) + mqtt_ssl: bool = Field( + default=False, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable SSL for MQTT connections" + ), + ), + ) + mqtt_logger: bool = Field( + default=True + ) + mqtt_notifier: bool = Field( + default=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable MQTT Notifier" + ), + ), + ) + mqtt_commands: bool = Field( + default=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable MQTT Commands" + ), + ), + ) + mqtt_events: bool = Field( + default=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable Events forwarding to MQTT broker" + ), + ), + ) + mqtt_external_events: bool = Field( + default=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable External MQTT Event listener" + ), + ), + ) + mqtt_autostart: bool = Field( + default=False, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable autostart" + ), + ), + ) + + class Config: + title = "mqtt_bridge" + + +class MarketDataCollectionConfigMap(BaseClientModel): + market_data_collection_enabled: bool = Field( + default=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enable/Disable Market Data Collection" + ), + ), + ) + market_data_collection_interval: int = Field( + default=60, + ge=1, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the market data collection interval in seconds (Default=60)" + ), + ), + ) + market_data_collection_depth: int = Field( + default=20, + ge=2, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Set the order book collection depth (Default=20)" + ), + ), + ) + + class Config: + title = "market_data_collection" + + +class ColorConfigMap(BaseClientModel): + top_pane: str = Field( + default="#000000", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color of the top pane?", + ), + ) + bottom_pane: str = Field( + default="#000000", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color of the bottom pane?", + ), + ) + output_pane: str = Field( + default="#262626", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color of the output pane?", + ), + ) + input_pane: str = Field( + default="#1C1C1C", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color of the input pane?", + ), + ) + logs_pane: str = Field( + default="#121212", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color of the logs pane?", + ), + ) + terminal_primary: str = Field( + default="#5FFFD7", + client_data=ClientFieldData( + prompt=lambda cm: "What is the terminal primary color?", + ), + ) + primary_label: str = Field( + default="#5FFFD7", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for primary label?", + ), + ) + secondary_label: str = Field( + default="#FFFFFF", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for secondary label?", + ), + ) + success_label: str = Field( + default="#5FFFD7", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for success label?", + ), + ) + warning_label: str = Field( + default="#FFFF00", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for warning label?", + ), + ) + info_label: str = Field( + default="#5FD7FF", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for info label?", + ), + ) + error_label: str = Field( + default="#FF0000", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for error label?", + ), + ) + gold_label: str = Field( + default="#FFD700", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for gold label?", + ), + ) + silver_label: str = Field( + default="#C0C0C0", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for silver label?", + ), + ) + bronze_label: str = Field( + default="#CD7F32", + client_data=ClientFieldData( + prompt=lambda cm: "What is the background color for bronze label?", + ), + ) + + @validator( + "top_pane", + "bottom_pane", + "output_pane", + "input_pane", + "logs_pane", + "terminal_primary", + "primary_label", + "secondary_label", + "success_label", + "warning_label", + "info_label", + "error_label", + "gold_label", + "silver_label", + "bronze_label", + pre=True, + ) + def validate_color(cls, v: str): + if not re.search(r'^#(?:[0-9a-fA-F]{2}){3}$', v): + raise ValueError("Invalid color code") + return v + + +class PaperTradeConfigMap(BaseClientModel): + paper_trade_exchanges: List = Field( + default=[ + BinanceConfigMap.Config.title, + KuCoinConfigMap.Config.title, + AscendExConfigMap.Config.title, + GateIOConfigMap.Config.title, + ], + ) + paper_trade_account_balance: Dict[str, float] = Field( + default={ + "BTC": 1, + "USDT": 1000, + "ONE": 1000, + "USDQ": 1000, + "TUSD": 1000, + "ETH": 10, + "WETH": 10, + "USDC": 1000, + "DAI": 1000, + }, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Enter paper trade balance settings (Input must be valid json — " + "e.g. {\"ETH\": 10, \"USDC\": 50000})" + ), + ), + ) + + @validator("paper_trade_account_balance", pre=True) + def validate_paper_trade_account_balance(cls, v: Union[str, Dict[str, float]]): + if isinstance(v, str): + v = json.loads(v) + return v + + +class KillSwitchMode(BaseClientModel, ABC): + @abstractmethod + def get_kill_switch(self, hb: "HummingbotApplication") -> KillSwitch: + ... + + +class KillSwitchEnabledMode(KillSwitchMode): + kill_switch_rate: Decimal = Field( + default=..., + ge=Decimal(-100), + le=Decimal(100), + client_data=ClientFieldData( + prompt=lambda cm: ( + "At what profit/loss rate would you like the bot to stop?" + " (e.g. -5 equals 5 percent loss)" + ), + ), + ) + + class Config: + title = "kill_switch_enabled" + + def get_kill_switch(self, hb: "HummingbotApplication") -> ActiveKillSwitch: + kill_switch = ActiveKillSwitch(kill_switch_rate=self.kill_switch_rate, hummingbot_application=hb) + return kill_switch + + @validator("kill_switch_rate", pre=True) + def validate_decimal(cls, v: str, field: Field): + """Used for client-friendly error output.""" + return super().validate_decimal(v, field) + + +class KillSwitchDisabledMode(KillSwitchMode): + class Config: + title = "kill_switch_disabled" + + def get_kill_switch(self, hb: "HummingbotApplication") -> PassThroughKillSwitch: + kill_switch = PassThroughKillSwitch() + return kill_switch + + +KILL_SWITCH_MODES = { + KillSwitchEnabledMode.Config.title: KillSwitchEnabledMode, + KillSwitchDisabledMode.Config.title: KillSwitchDisabledMode, +} + + +class AutofillImportEnum(str, ClientConfigEnum): + start = "start" + config = "config" + disabled = "disabled" + + +class TelegramMode(BaseClientModel, ABC): + @abstractmethod + def get_notifiers(self, hb: "HummingbotApplication") -> List[TelegramNotifier]: + ... + + +class TelegramEnabledMode(TelegramMode): + telegram_token: str = Field( + default=..., + client_data=ClientFieldData(prompt=lambda cm: "What is your telegram token?"), + ) + telegram_chat_id: str = Field( + default=..., + client_data=ClientFieldData(prompt=lambda cm: "What is your telegram chat id?"), + ) + + class Config: + title = "telegram_enabled" + + def get_notifiers(self, hb: "HummingbotApplication") -> List[TelegramNotifier]: + notifiers = [ + TelegramNotifier(token=self.telegram_token, chat_id=self.telegram_chat_id, hb=hb) + ] + return notifiers + + +class TelegramDisabledMode(TelegramMode): + class Config: + title = "telegram_disabled" + + def get_notifiers(self, hb: "HummingbotApplication") -> List[TelegramNotifier]: + return [] + + +TELEGRAM_MODES = { + TelegramEnabledMode.Config.title: TelegramEnabledMode, + TelegramDisabledMode.Config.title: TelegramDisabledMode, +} + + +class DBMode(BaseClientModel, ABC): + @abstractmethod + def get_url(self, db_path: str) -> str: + ... + + +class DBSqliteMode(DBMode): + db_engine: str = Field( + default="sqlite", + const=True, + client_data=ClientFieldData( + prompt=lambda cm: ( + "Please enter database engine you want to use (reference: https://docs.sqlalchemy.org/en/13/dialects/)" + ), + ), + ) + + class Config: + title = "sqlite_db_engine" + + def get_url(self, db_path: str) -> str: + return f"{self.db_engine}:///{db_path}" + + +class DBOtherMode(DBMode): + db_engine: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ( + "Please enter database engine you want to use (reference: https://docs.sqlalchemy.org/en/13/dialects/)" + ), + ), + ) + db_host: str = Field( + default="127.0.0.1", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your DB host address", + ), + ) + db_port: int = Field( + default=3306, + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your DB port", + ), + ) + db_username: str = Field( + defaul="username", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your DB username", + ), + ) + db_password: str = Field( + default="password", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your DB password", + ), + ) + db_name: str = Field( + default="dbname", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your the name of your DB", + ), + ) + + class Config: + title = "other_db_engine" + + def get_url(self, db_path: str) -> str: + return f"{self.db_engine}://{self.db_username}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}" + + @validator("db_engine") + def validate_db_engine(cls, v: str): + assert v != "sqlite" + return v + + +DB_MODES = { + DBSqliteMode.Config.title: DBSqliteMode, + DBOtherMode.Config.title: DBOtherMode, +} + + +class PMMScriptMode(BaseClientModel, ABC): + @abstractmethod + def get_iterator( + self, + strategy_name: str, + markets: List[ExchangeBase], + strategy: StrategyBase) -> Optional[PMMScriptIterator]: + ... + + +class PMMScriptDisabledMode(PMMScriptMode): + class Config: + title = "pmm_script_disabled" + + def get_iterator( + self, + strategy_name: str, + markets: List[ExchangeBase], + strategy: StrategyBase) -> Optional[PMMScriptIterator]: + return None + + +class PMMScriptEnabledMode(PMMScriptMode): + pmm_script_file_path: str = Field( + default=..., + client_data=ClientFieldData(prompt=lambda cm: "Enter path to your PMM script file"), + ) + + class Config: + title = "pmm_script_enabled" + + def get_iterator( + self, + strategy_name: str, + markets: List[ExchangeBase], + strategy: StrategyBase) -> Optional[PMMScriptIterator]: + if strategy_name != "pure_market_making": + raise ValueError("PMM script feature is only available for pure_market_making strategy.") + folder = dirname(self.pmm_script_file_path) + pmm_script_file = ( + PMM_SCRIPTS_PATH / self.pmm_script_file_path + if folder == "" + else self.pmm_script_file_path + ) + pmm_script_iterator = PMMScriptIterator( + pmm_script_file, + markets, + strategy, + queue_check_interval=0.1, + ) + return pmm_script_iterator + + @validator("pmm_script_file_path", pre=True) + def validate_pmm_script_file_path(cls, v: str): + import hummingbot.client.settings as settings + file_path = v + path, name = os.path.split(file_path) + if path == "": + file_path = os.path.join(settings.PMM_SCRIPTS_PATH, file_path) + if not os.path.isfile(file_path): + raise ValueError(f"{file_path} file does not exist.") + return file_path + + +PMM_SCRIPT_MODES = { + PMMScriptDisabledMode.Config.title: PMMScriptDisabledMode, + PMMScriptEnabledMode.Config.title: PMMScriptEnabledMode, +} + + +class GatewayConfigMap(BaseClientModel): + gateway_api_host: str = Field( + default="localhost", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your Gateway API host", + ), + ) + gateway_api_port: str = Field( + default="15888", + client_data=ClientFieldData( + prompt=lambda cm: "Please enter your Gateway API port", + ), + ) + + class Config: + title = "gateway" + + +class GlobalTokenConfigMap(BaseClientModel): + global_token_name: str = Field( + default="USDT", + client_data=ClientFieldData( + prompt=lambda + cm: "What is your default display token? (e.g. USD,EUR,BTC)", + ), + ) + global_token_symbol: str = Field( + default="$", + client_data=ClientFieldData( + prompt=lambda + cm: "What is your default display token symbol? (e.g. $,€)", + ), + ) + + class Config: + title = "global_token" + + @validator("global_token_name") + def validate_global_token_name(cls, v: str) -> str: + return v.upper() + + # === post-validations === + + @root_validator() + def post_validations(cls, values: Dict): + cls.global_token_on_validated(values) + return values + + @classmethod + def global_token_on_validated(cls, values: Dict): + RateOracle.get_instance().quote_token = values["global_token_name"] + + +class CommandsTimeoutConfigMap(BaseClientModel): + create_command_timeout: Decimal = Field( + default=Decimal("10"), + gt=Decimal("0"), + client_data=ClientFieldData( + prompt=lambda cm: ( + "Network timeout when fetching the minimum order amount in the create command (in seconds)" + ), + ), + ) + other_commands_timeout: Decimal = Field( + default=Decimal("30"), + gt=Decimal("0"), + client_data=ClientFieldData( + prompt=lambda cm: ( + "Network timeout to apply to the other commands' API calls" + " (i.e. import, connect, balance, history; in seconds)" + ), + ), + ) + + class Config: + title = "commands_timeout" + + @validator( + "create_command_timeout", + "other_commands_timeout", + pre=True, + ) + def validate_decimals(cls, v: str, field: Field): + """Used for client-friendly error output.""" + return super().validate_decimal(v, field) + + +class AnonymizedMetricsMode(BaseClientModel, ABC): + @abstractmethod + def get_collector( + self, + connector: ConnectorBase, + rate_provider: RateOracle, + instance_id: str, + valuation_token: str = "USDT", + ) -> MetricsCollector: + ... + + +class AnonymizedMetricsDisabledMode(AnonymizedMetricsMode): + class Config: + title = "anonymized_metrics_disabled" + + def get_collector( + self, + connector: ConnectorBase, + rate_provider: RateOracle, + instance_id: str, + valuation_token: str = "USDT", + ) -> MetricsCollector: + return DummyMetricsCollector() + + +class AnonymizedMetricsEnabledMode(AnonymizedMetricsMode): + anonymized_metrics_interval_min: Decimal = Field( + default=Decimal("15"), + gt=Decimal("0"), + client_data=ClientFieldData( + prompt=lambda cm: "How often do you want to send the anonymized metrics (Enter 5 for 5 minutes)?", + ), + ) + + class Config: + title = "anonymized_metrics_enabled" + + def get_collector( + self, + connector: ConnectorBase, + rate_provider: RateOracle, + instance_id: str, + valuation_token: str = "USDT", + ) -> MetricsCollector: + instance = TradeVolumeMetricCollector( + connector=connector, + activation_interval=self.anonymized_metrics_interval_min, + rate_provider=rate_provider, + instance_id=instance_id, + valuation_token=valuation_token, + ) + return instance + + @validator("anonymized_metrics_interval_min", pre=True) + def validate_decimal(cls, v: str, field: Field): + """Used for client-friendly error output.""" + return super().validate_decimal(v, field) + + +METRICS_MODES = { + AnonymizedMetricsDisabledMode.Config.title: AnonymizedMetricsDisabledMode, + AnonymizedMetricsEnabledMode.Config.title: AnonymizedMetricsEnabledMode, +} + + +class RateSourceModeBase(BaseClientModel, ABC): + @abstractmethod + def build_rate_source(self) -> RateSourceBase: + ... + + +class ExchangeRateSourceModeBase(RateSourceModeBase): + def build_rate_source(self) -> RateSourceBase: + return RATE_ORACLE_SOURCES[self.Config.title]() + + +class AscendExRateSourceMode(ExchangeRateSourceModeBase): + name: str = Field( + default="ascend_ex", + const=True, + client_data=None, + ) + + class Config: + title = "ascend_ex" + + +class BinanceRateSourceMode(ExchangeRateSourceModeBase): + name: str = Field( + default="binance", + const=True, + client_data=None, + ) + + class Config: + title = "binance" + + +class CoinGeckoRateSourceMode(RateSourceModeBase): + name: str = Field( + default="coin_gecko", + const=True, + client_data=None, + ) + + extra_tokens: List[str] = Field( + default=[], + client_data=ClientFieldData( + prompt=lambda cm: ( + "List of comma-delimited CoinGecko token ids to always include" + " in CoinGecko rates query (e.g. frontier-token,pax-gold,rbtc — empty to skip)" + ), + prompt_on_new=True, + ) + ) + + class Config: + title = "coin_gecko" + + def build_rate_source(self) -> RateSourceBase: + rate_source = RATE_ORACLE_SOURCES[self.Config.title]( + extra_token_ids=self.extra_tokens + ) + rate_source.extra_token_ids = self.extra_tokens + return rate_source + + @validator("extra_tokens", pre=True) + def validate_extra_tokens(cls, value: Union[str, List[str]]): + extra_tokens = value.split(",") if isinstance(value, str) else value + return extra_tokens + + @root_validator() + def post_validations(cls, values: Dict): + RateOracle.get_instance().source.extra_token_ids = values["extra_tokens"] + return values + + +class CoinCapRateSourceMode(RateSourceModeBase): + name: str = Field( + default="coin_cap", + const=True, + client_data=None, + ) + assets_map: Dict[str, str] = Field( + default=",".join( + [ + ":".join(pair) for pair in { + "BTC": "bitcoin", + "ETH": "ethereum", + "USDT": "tether", + "CONV": "convergence", + "FIRO": "zcoin", + "BUSD": "binance-usd", + "ONE": "harmony", + "PDEX": "polkadex", + }.items() + ] + ), + description=( + "The symbol-to-asset ID map for CoinCap. Assets IDs can be found by selecting a symbol" + " on https://coincap.io/ and extracting the last segment of the URL path." + ), + client_data=ClientFieldData( + prompt=lambda cm: ( + "CoinCap symbol-to-asset ID map (e.g. 'BTC:bitcoin,ETH:ethereum', find IDs on https://coincap.io/" + " by selecting a symbol and extracting the last segment of the URL path)" + ), + is_connect_key=True, + prompt_on_new=True, + ), + ) + api_key: SecretStr = Field( + default=SecretStr(""), + description="API key to use to request information from CoinCap (if empty public requests will be used)", + client_data=ClientFieldData( + prompt=lambda cm: "CoinCap API key (optional, but improves rate limits)", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "coin_cap" + + def build_rate_source(self) -> RateSourceBase: + rate_source = RATE_ORACLE_SOURCES["coin_cap"]( + assets_map=self.assets_map, api_key=self.api_key.get_secret_value() + ) + return rate_source + + @validator("assets_map", pre=True) + def validate_extra_tokens(cls, value: Union[str, Dict[str, str]]): + if isinstance(value, str): + value = {key: val for key, val in [v.split(":") for v in value.split(",")]} + return value + + # === post-validations === + + @root_validator() + def post_validations(cls, values: Dict): + cls.rate_oracle_source_on_validated(values) + return values + + @classmethod + def rate_oracle_source_on_validated(cls, values: Dict): + RateOracle.get_instance().source = cls._build_rate_source_cls( + assets_map=values["assets_map"], api_key=values["api_key"] + ) + + @classmethod + def _build_rate_source_cls(cls, assets_map: Dict[str, str], api_key: SecretStr) -> RateSourceBase: + rate_source = RATE_ORACLE_SOURCES["coin_cap"]( + assets_map=assets_map, api_key=api_key.get_secret_value() + ) + return rate_source + + +class KuCoinRateSourceMode(ExchangeRateSourceModeBase): + name: str = Field( + default="kucoin", + const=True, + client_data=None, + ) + + class Config: + title = "kucoin" + + +class GateIoRateSourceMode(ExchangeRateSourceModeBase): + name: str = Field( + default="gate_io", + const=True, + client_data=None, + ) + + class Config: + title: str = "gate_io" + + +RATE_SOURCE_MODES = { + AscendExRateSourceMode.Config.title: AscendExRateSourceMode, + BinanceRateSourceMode.Config.title: BinanceRateSourceMode, + CoinGeckoRateSourceMode.Config.title: CoinGeckoRateSourceMode, + CoinCapRateSourceMode.Config.title: CoinCapRateSourceMode, + KuCoinRateSourceMode.Config.title: KuCoinRateSourceMode, + GateIoRateSourceMode.Config.title: GateIoRateSourceMode, +} + + +class CommandShortcutModel(BaseModel): + command: str + help: str + arguments: List[str] + output: List[str] + + +class ClientConfigMap(BaseClientModel): + instance_id: str = Field( + default=generate_client_id(), + client_data=ClientFieldData( + prompt=lambda cm: "Instance UID of the bot", + ), + ) + fetch_pairs_from_all_exchanges: bool = Field( + default=False, + description="Fetch trading pairs from all exchanges if True, otherwise fetch only from connected exchanges.", + client_data=ClientFieldData( + prompt=lambda cm: "Would you like to fetch from all exchanges? (True/False)", + ), + ) + log_level: str = Field(default="INFO") + debug_console: bool = Field(default=False) + strategy_report_interval: float = Field(default=900) + logger_override_whitelist: List = Field( + default=["hummingbot.strategy.arbitrage", "hummingbot.strategy.cross_exchange_market_making", "conf"] + ) + log_file_path: Path = Field( + default=DEFAULT_LOG_FILE_PATH, + client_data=ClientFieldData( + prompt=lambda cm: f"Where would you like to save your logs? (default '{DEFAULT_LOG_FILE_PATH}')", + ), + ) + kill_switch_mode: Union[tuple(KILL_SWITCH_MODES.values())] = Field( + default=KillSwitchDisabledMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select your kill-switch mode ({'/'.join(list(KILL_SWITCH_MODES.keys()))})", + ), + ) + autofill_import: AutofillImportEnum = Field( + default=AutofillImportEnum.disabled, + description="What to auto-fill in the prompt after each import command (start/config)", + client_data=ClientFieldData( + prompt=lambda cm: ( + f"What to auto-fill in the prompt after each import command? ({'/'.join(list(AutofillImportEnum))})" + ), + ) + ) + telegram_mode: Union[tuple(TELEGRAM_MODES.values())] = Field( + default=TelegramDisabledMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the desired telegram mode ({'/'.join(list(TELEGRAM_MODES.keys()))})" + ) + ) + mqtt_bridge: MQTTBridgeConfigMap = Field( + default=MQTTBridgeConfigMap(), + description=('MQTT Bridge configuration.'), + ) + send_error_logs: bool = Field( + default=True, + description="Error log sharing", + client_data=ClientFieldData( + prompt=lambda cm: "Would you like to send error logs to hummingbot? (Yes/No)", + ), + ) + previous_strategy: Optional[str] = Field( + default=None, + description="Can store the previous strategy ran for quick retrieval." + ) + db_mode: Union[tuple(DB_MODES.values())] = Field( + default=DBSqliteMode(), + description=("Advanced database options, currently supports SQLAlchemy's included dialects" + "\nReference: https://docs.sqlalchemy.org/en/13/dialects/" + "\nTo use an instance of SQLite DB the required configuration is \n db_engine: sqlite" + "\nTo use a DBMS the required configuration is" + "\n db_host: 127.0.0.1\n db_port: 3306\n db_username: username\n db_password: password" + "\n db_name: dbname"), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the desired db mode ({'/'.join(list(DB_MODES.keys()))})", + ), + ) + pmm_script_mode: Union[tuple(PMM_SCRIPT_MODES.values())] = Field( + default=PMMScriptDisabledMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the desired PMM script mode ({'/'.join(list(PMM_SCRIPT_MODES.keys()))})", + ), + ) + balance_asset_limit: Dict[str, Dict[str, Decimal]] = Field( + default={exchange: {} for exchange in AllConnectorSettings.get_exchange_names()}, + description=("Balance Limit Configurations" + "\ne.g. Setting USDT and BTC limits on Binance." + "\nbalance_asset_limit:" + "\n binance:" + "\n BTC: 0.1" + "\n USDT: 1000"), + client_data=ClientFieldData( + prompt=lambda cm: ( + "Use the `balance limit` command e.g. balance limit [EXCHANGE] [ASSET] [AMOUNT]" + ), + ), + ) + manual_gas_price: Decimal = Field( + default=Decimal("50"), + description="Fixed gas price (in Gwei) for Ethereum transactions", + gt=Decimal("0"), + client_data=ClientFieldData( + prompt=lambda cm: "Enter fixed gas price (in Gwei) you want to use for Ethereum transactions", + ), + ) + gateway: GatewayConfigMap = Field( + default=GatewayConfigMap(), + description=("Gateway API Configurations" + "\ndefault host to only use localhost" + "\nPort need to match the final installation port for Gateway"), + ) + certs_path: Path = Field( + default=DEFAULT_GATEWAY_CERTS_PATH, + client_data=ClientFieldData( + prompt=lambda cm: f"Where would you like to save certificates that connect your bot to Gateway? (default '{DEFAULT_GATEWAY_CERTS_PATH}')", + ), + ) + + anonymized_metrics_mode: Union[tuple(METRICS_MODES.values())] = Field( + default=AnonymizedMetricsEnabledMode(), + description="Whether to enable aggregated order and trade data collection", + client_data=ClientFieldData( + prompt=lambda cm: f"Select the desired metrics mode ({'/'.join(list(METRICS_MODES.keys()))})", + ), + ) + command_shortcuts: List[CommandShortcutModel] = Field( + default=[ + CommandShortcutModel( + command="spreads", + help="Set bid and ask spread", + arguments=["Bid Spread", "Ask Spread"], + output=["config bid_spread $1", "config ask_spread $2"] + ) + ], + description=("Command Shortcuts" + "\nDefine abbreviations for often used commands" + "\nor batch grouped commands together"), + ) + rate_oracle_source: Union[tuple(RATE_SOURCE_MODES.values())] = Field( + default=BinanceRateSourceMode(), + description=f"A source for rate oracle, currently {', '.join(RATE_SOURCE_MODES.keys())}", + client_data=ClientFieldData( + prompt=lambda cm: ( + f"What source do you want rate oracle to pull data from? ({'/'.join(RATE_SOURCE_MODES.keys())})" + ), + ), + ) + global_token: GlobalTokenConfigMap = Field( + default=GlobalTokenConfigMap(), + description="A universal token which to display tokens values in, e.g. USD,EUR,BTC" + ) + rate_limits_share_pct: Decimal = Field( + default=Decimal("100"), + description=("Percentage of API rate limits (on any exchange and any end point) allocated to this bot instance." + "\nEnter 50 to indicate 50%. E.g. if the API rate limit is 100 calls per second, and you allocate " + "\n50% to this setting, the bot will have a maximum (limit) of 50 calls per second"), + gt=Decimal("0"), + le=Decimal("100"), + client_data=ClientFieldData( + prompt=lambda cm: ( + "What percentage of API rate limits do you want to allocate to this bot instance?" + " (Enter 50 to indicate 50%)" + ), + ), + ) + commands_timeout: CommandsTimeoutConfigMap = Field(default=CommandsTimeoutConfigMap()) + tables_format: ClientConfigEnum( + value="TabulateFormats", # noqa: F821 + names={e: e for e in tabulate_formats}, + type=str, + ) = Field( + default="psql", + description="Tabulate table format style (https://github.com/astanin/python-tabulate#table-format)", + client_data=ClientFieldData( + prompt=lambda cm: ( + "What tabulate formatting to apply to the tables?" + " [https://github.com/astanin/python-tabulate#table-format]" + ), + ), + ) + paper_trade: PaperTradeConfigMap = Field(default=PaperTradeConfigMap()) + color: ColorConfigMap = Field(default=ColorConfigMap()) + tick_size: float = Field( + default=1.0, + ge=0.1, + description="The tick size is the frequency with which the clock notifies the time iterators by calling the" + "\nc_tick() method, that means for example that if the tick size is 1, the logic of the strategy" + " \nwill run every second.", + client_data=ClientFieldData( + prompt=lambda cm: ( + "What tick size (in seconds) do you want to use? (Enter 0.5 to indicate 0.5 seconds)" + ), + ), + ) + market_data_collection: MarketDataCollectionConfigMap = Field(default=MarketDataCollectionConfigMap()) + + class Config: + title = "client_config_map" + + @validator("kill_switch_mode", pre=True) + def validate_kill_switch_mode(cls, v: Union[(str, Dict) + tuple(KILL_SWITCH_MODES.values())]): + if isinstance(v, tuple(KILL_SWITCH_MODES.values()) + (Dict,)): + sub_model = v + elif v not in KILL_SWITCH_MODES: + raise ValueError( + f"Invalid kill switch mode, please choose a value from {list(KILL_SWITCH_MODES.keys())}." + ) + else: + sub_model = KILL_SWITCH_MODES[v].construct() + return sub_model + + @validator("autofill_import", pre=True) + def validate_autofill_import(cls, v: Union[str, AutofillImportEnum]): + if isinstance(v, str) and v not in AutofillImportEnum.__members__: + raise ValueError(f"The value must be one of {', '.join(list(AutofillImportEnum))}.") + return v + + @validator("telegram_mode", pre=True) + def validate_telegram_mode(cls, v: Union[(str, Dict) + tuple(TELEGRAM_MODES.values())]): + if isinstance(v, tuple(TELEGRAM_MODES.values()) + (Dict,)): + sub_model = v + elif v not in TELEGRAM_MODES: + raise ValueError( + f"Invalid telegram mode, please choose a value from {list(TELEGRAM_MODES.keys())}." + ) + else: + sub_model = TELEGRAM_MODES[v].construct() + return sub_model + + @validator("send_error_logs", "fetch_pairs_from_all_exchanges", pre=True) + def validate_bool(cls, v: str): + """Used for client-friendly error output.""" + if isinstance(v, str): + ret = validate_bool(v) + if ret is not None: + raise ValueError(ret) + return v + + @validator("db_mode", pre=True) + def validate_db_mode(cls, v: Union[(str, Dict) + tuple(DB_MODES.values())]): + if isinstance(v, tuple(DB_MODES.values()) + (Dict,)): + sub_model = v + elif v not in DB_MODES: + raise ValueError( + f"Invalid DB mode, please choose a value from {list(DB_MODES.keys())}." + ) + else: + sub_model = DB_MODES[v].construct() + return sub_model + + @validator("pmm_script_mode", pre=True) + def validate_pmm_script_mode(cls, v: Union[(str, Dict) + tuple(PMM_SCRIPT_MODES.values())]): + if isinstance(v, tuple(PMM_SCRIPT_MODES.values()) + (Dict,)): + sub_model = v + elif v not in PMM_SCRIPT_MODES: + raise ValueError( + f"Invalid PMM script mode, please choose a value from {list(PMM_SCRIPT_MODES.keys())}." + ) + else: + sub_model = PMM_SCRIPT_MODES[v].construct() + return sub_model + + @validator("anonymized_metrics_mode", pre=True) + def validate_anonymized_metrics_mode(cls, v: Union[(str, Dict) + tuple(METRICS_MODES.values())]): + if isinstance(v, tuple(METRICS_MODES.values()) + (Dict,)): + sub_model = v + elif v not in METRICS_MODES: + raise ValueError( + f"Invalid metrics mode, please choose a value from {list(METRICS_MODES.keys())}." + ) + else: + sub_model = METRICS_MODES[v].construct() + return sub_model + + @validator("rate_oracle_source", pre=True) + def validate_rate_oracle_source(cls, v: Union[(str, Dict) + tuple(RATE_SOURCE_MODES.values())]): + if isinstance(v, tuple(RATE_SOURCE_MODES.values()) + (Dict,)): + sub_model = v + elif v not in RATE_SOURCE_MODES: + raise ValueError( + f"Invalid rate source, please choose a value from {list(RATE_SOURCE_MODES.keys())}." + ) + else: + sub_model = RATE_SOURCE_MODES[v].construct() + return sub_model + + @validator("tables_format", pre=True) + def validate_tables_format(cls, v: str): + """Used for client-friendly error output.""" + if v not in tabulate_formats: + raise ValueError("Invalid table format.") + return v + + @validator( + "manual_gas_price", + "rate_limits_share_pct", + pre=True, + ) + def validate_decimals(cls, v: str, field: Field): + """Used for client-friendly error output.""" + return super().validate_decimal(v, field) + + @validator("tick_size", pre=True) + def validate_tick_size(cls, v: float): + """Used for client-friendly error output.""" + ret = validate_float(v, min_value=0.1) + if ret is not None: + raise ValueError(ret) + return v + + # === post-validations === + + @root_validator() + def post_validations(cls, values: Dict): + cls.rate_oracle_source_on_validated(values) + return values + + @classmethod + def rate_oracle_source_on_validated(cls, values: Dict): + rate_source_mode: RateSourceModeBase = values["rate_oracle_source"] + RateOracle.get_instance().source = rate_source_mode.build_rate_source() + RateOracle.get_instance().quote_token = values["global_token"].global_token_name diff --git a/hummingbot/client/config/conf_migration.py b/hummingbot/client/config/conf_migration.py new file mode 100644 index 0000000..8efeba3 --- /dev/null +++ b/hummingbot/client/config/conf_migration.py @@ -0,0 +1,454 @@ +import binascii +import importlib +import logging +import shutil +from os import DirEntry, scandir +from os.path import exists, join +from typing import Any, Dict, List, Optional, Union, cast + +import yaml + +from hummingbot import root_path +from hummingbot.client.config.client_config_map import ( + AnonymizedMetricsDisabledMode, + AnonymizedMetricsEnabledMode, + ClientConfigMap, + ColorConfigMap, + DBOtherMode, + DBSqliteMode, + KillSwitchDisabledMode, + KillSwitchEnabledMode, + PMMScriptDisabledMode, + PMMScriptEnabledMode, + TelegramDisabledMode, + TelegramEnabledMode, +) +from hummingbot.client.config.config_crypt import BaseSecretsManager, store_password_verification +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, save_to_yml +from hummingbot.client.config.security import Security +from hummingbot.client.settings import CLIENT_CONFIG_PATH, CONF_DIR_PATH, STRATEGIES_CONF_DIR_PATH +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, +) +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + CrossExchangeMarketMakingConfigMap, +) + +encrypted_conf_prefix = "encrypted_" +encrypted_conf_postfix = ".json" +conf_dir_path = CONF_DIR_PATH +strategies_conf_dir_path = STRATEGIES_CONF_DIR_PATH + + +def migrate_configs(secrets_manager: BaseSecretsManager) -> List[str]: + logging.getLogger().info("Starting conf migration.") + errors = backup_existing_dir() + if len(errors) == 0: + errors = migrate_global_config() + if len(errors) == 0: + errors.extend(migrate_strategy_confs_paths()) + errors.extend(migrate_connector_confs(secrets_manager)) + store_password_verification(secrets_manager) + logging.getLogger().info("\nConf migration done.") + else: + logging.getLogger().error("\nConf migration failed.") + return errors + + +def migrate_non_secure_configs_only() -> List[str]: + logging.getLogger().info("Starting strategies conf migration.") + errors = backup_existing_dir() + if len(errors) == 0: + errors = migrate_global_config() + if len(errors) == 0: + errors.extend(migrate_strategy_confs_paths()) + logging.getLogger().info("\nConf migration done.") + else: + logging.getLogger().error("\nConf migration failed.") + return errors + + +def backup_existing_dir() -> List[str]: + errors = [] + if conf_dir_path.exists(): + backup_path = conf_dir_path.parent / "conf_backup" + if backup_path.exists(): + errors = [ + ( + f"\nBackup path {backup_path} already exists." + f"\nThe migration script cannot backup you existing" + f"\nconf files without overwriting that directory." + f"\nPlease remove it and run the script again." + ) + ] + else: + shutil.copytree(conf_dir_path, backup_path) + logging.getLogger().info(f"\nCreated a backup of your existing conf directory to {backup_path}") + return errors + + +def migrate_global_config() -> List[str]: + logging.getLogger().info("\nMigrating the global config...") + global_config_path = CONF_DIR_PATH / "conf_global.yml" + errors = [] + if global_config_path.exists(): + with open(str(global_config_path), "r") as f: + data = yaml.safe_load(f) + del data["template_version"] + client_config_map = ClientConfigAdapter(ClientConfigMap()) + _migrate_global_config_modes(client_config_map, data) + data.pop("kraken_api_tier", None) + data.pop("key_file_path", None) + keys = list(data.keys()) + for key in keys: + if key in client_config_map.keys(): + _migrate_global_config_field(client_config_map, data, key) + for key in data: + logging.getLogger().warning(f"Global ConfigVar {key} was not migrated.") + errors.extend(client_config_map.validate_model()) + if len(errors) == 0: + save_to_yml(CLIENT_CONFIG_PATH, client_config_map) + global_config_path.unlink() + logging.getLogger().info("\nSuccessfully migrated the global config.") + else: + errors = [f"client_config_map - {e}" for e in errors] + logging.getLogger().error(f"The migration of the global config map failed with errors: {errors}") + return errors + + +def _migrate_global_config_modes(client_config_map: ClientConfigAdapter, data: Dict): + client_config_map: Union[ClientConfigAdapter, ClientConfigMap] = client_config_map # for IDE autocomplete + + kill_switch_enabled = data.pop("kill_switch_enabled") + kill_switch_rate = data.pop("kill_switch_rate") + if kill_switch_enabled: + client_config_map.kill_switch_mode = KillSwitchEnabledMode(kill_switch_rate=kill_switch_rate) + else: + client_config_map.kill_switch_mode = KillSwitchDisabledMode() + + _migrate_global_config_field( + client_config_map.paper_trade, data, "paper_trade_exchanges" + ) + _migrate_global_config_field( + client_config_map.paper_trade, data, "paper_trade_account_balance" + ) + + telegram_enabled = data.pop("telegram_enabled") + telegram_token = data.pop("telegram_token") + telegram_chat_id = data.pop("telegram_chat_id") + if telegram_enabled: + client_config_map.telegram_mode = TelegramEnabledMode( + telegram_token=telegram_token, + telegram_chat_id=telegram_chat_id, + ) + else: + client_config_map.telegram_mode = TelegramDisabledMode() + + db_engine = data.pop("db_engine") + db_host = data.pop("db_host") + db_port = data.pop("db_port") + db_username = data.pop("db_username") + db_password = data.pop("db_password") + db_name = data.pop("db_name") + if db_engine == "sqlite": + client_config_map.db_mode = DBSqliteMode() + else: + client_config_map.db_mode = DBOtherMode( + db_engine=db_engine, + db_host=db_host, + db_port=db_port, + db_username=db_username, + db_password=db_password, + db_name=db_name, + ) + + pmm_script_enabled = data.pop("pmm_script_enabled") + pmm_script_file_path = data.pop("pmm_script_file_path") + if pmm_script_enabled: + client_config_map.pmm_script_mode = PMMScriptEnabledMode(pmm_script_file_path=pmm_script_file_path) + else: + client_config_map.pmm_script_mode = PMMScriptDisabledMode() + + _migrate_global_config_field( + client_config_map.gateway, data, "gateway_api_host" + ) + _migrate_global_config_field( + client_config_map.gateway, data, "gateway_api_port" + ) + + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_host" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_port" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_username" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_password" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_ssl" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_logger" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_notifier" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_commands" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_events" + ) + _migrate_global_config_field( + client_config_map.mqtt_bridge, data, "mqtt_autostart" + ) + + anonymized_metrics_enabled = data.pop("anonymized_metrics_enabled") + anonymized_metrics_interval_min = data.pop("anonymized_metrics_interval_min") + if anonymized_metrics_enabled: + client_config_map.anonymized_metrics_mode = AnonymizedMetricsEnabledMode( + anonymized_metrics_interval_min=anonymized_metrics_interval_min + ) + else: + client_config_map.anonymized_metrics_mode = AnonymizedMetricsDisabledMode() + + _migrate_global_config_field( + client_config_map.global_token, data, "global_token", "global_token_name" + ) + _migrate_global_config_field( + client_config_map.global_token, data, "global_token_symbol" + ) + + _migrate_global_config_field( + client_config_map.commands_timeout, data, "create_command_timeout" + ) + _migrate_global_config_field( + client_config_map.commands_timeout, data, "other_commands_timeout" + ) + + color_map: Union[ClientConfigAdapter, ColorConfigMap] = client_config_map.color + _migrate_global_config_field(color_map, data, "top-pane", "top_pane") + _migrate_global_config_field(color_map, data, "bottom-pane", "bottom_pane") + _migrate_global_config_field(color_map, data, "output-pane", "output_pane") + _migrate_global_config_field(color_map, data, "input-pane", "input_pane") + _migrate_global_config_field(color_map, data, "logs-pane", "logs_pane") + _migrate_global_config_field(color_map, data, "terminal-primary", "terminal_primary") + _migrate_global_config_field(color_map, data, "primary-label", "primary_label") + _migrate_global_config_field(color_map, data, "secondary-label", "secondary_label") + _migrate_global_config_field(color_map, data, "success-label", "success_label") + _migrate_global_config_field(color_map, data, "warning-label", "warning_label") + _migrate_global_config_field(color_map, data, "info-label", "info_label") + _migrate_global_config_field(color_map, data, "error-label", "error_label") + + balance_asset_limit = data.pop("balance_asset_limit") + if balance_asset_limit is not None: + exchanges = list(balance_asset_limit.keys()) + for e in exchanges: + if balance_asset_limit[e] is None: + balance_asset_limit.pop(e) + else: + assets = balance_asset_limit[e].keys() + for a in assets: + if balance_asset_limit[e][a] is None: + balance_asset_limit[e].pop(a) + client_config_map.balance_asset_limit = balance_asset_limit + + +def _migrate_global_config_field( + cm: ClientConfigAdapter, global_config_data: Dict[str, Any], attr: str, cm_attr: Optional[str] = None +): + value = global_config_data.pop(attr) + cm_attr = cm_attr if cm_attr is not None else attr + if value is not None: + cm.setattr_no_validation(cm_attr, value) + + +def migrate_strategy_confs_paths(): + errors = [] + logging.getLogger().info("\nMigrating strategies...") + for child in conf_dir_path.iterdir(): + if child.is_file() and child.name.endswith(".yml"): + with open(str(child), "r") as f: + conf = yaml.safe_load(f) + if "strategy" in conf and _has_connector_field(conf): + new_path = strategies_conf_dir_path / child.name + child.rename(new_path) + if conf["strategy"] == "avellaneda_market_making": + errors.extend(migrate_amm_confs(conf, new_path)) + elif conf["strategy"] == "cross_exchange_market_making": + errors.extend(migrate_xemm_confs(conf, new_path)) + logging.getLogger().info(f"Migrated conf for {conf['strategy']}") + return errors + + +def migrate_amm_confs(conf, new_path) -> List[str]: + execution_timeframe = conf.pop("execution_timeframe") + if execution_timeframe == "infinite": + conf["execution_timeframe_mode"] = {} + conf.pop("start_time") + conf.pop("end_time") + elif execution_timeframe == "from_date_to_date": + conf["execution_timeframe_mode"] = { + "start_datetime": conf.pop("start_time"), + "end_datetime": conf.pop("end_time"), + } + else: + assert execution_timeframe == "daily_between_times" + conf["execution_timeframe_mode"] = { + "start_time": conf.pop("start_time"), + "end_time": conf.pop("end_time"), + } + order_levels = int(conf.pop("order_levels")) + if order_levels == 1: + conf["order_levels_mode"] = {} + conf.pop("level_distances") + else: + conf["order_levels_mode"] = { + "order_levels": order_levels, + "level_distances": conf.pop("level_distances") + } + hanging_orders_enabled = conf.pop("hanging_orders_enabled") + if not hanging_orders_enabled: + conf["hanging_orders_mode"] = {} + conf.pop("hanging_orders_cancel_pct") + else: + conf["hanging_orders_mode"] = { + "hanging_orders_cancel_pct": conf.pop("hanging_orders_cancel_pct") + } + if "template_version" in conf: + conf.pop("template_version") + try: + config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**conf)) + save_to_yml(new_path, config_map) + errors = [] + except Exception as e: + logging.getLogger().error(str(e)) + errors = [str(e)] + return errors + + +def migrate_xemm_confs(conf, new_path) -> List[str]: + if "active_order_canceling" in conf: + if conf["active_order_canceling"]: + conf["order_refresh_mode"] = {} + else: + conf["order_refresh_mode"] = { + "cancel_order_threshold": conf["cancel_order_threshold"], + "limit_order_min_expiration": conf["limit_order_min_expiration"] + } + conf.pop("active_order_canceling") + conf.pop("cancel_order_threshold") + conf.pop("limit_order_min_expiration") + if "use_oracle_conversion_rate" in conf: + if conf["use_oracle_conversion_rate"]: + conf["conversion_rate_mode"] = {} + else: + conf["conversion_rate_mode"] = { + "taker_to_maker_base_conversion_rate": conf["taker_to_maker_base_conversion_rate"], + "taker_to_maker_quote_conversion_rate": conf["taker_to_maker_quote_conversion_rate"] + } + conf.pop("use_oracle_conversion_rate") + conf.pop("taker_to_maker_base_conversion_rate") + conf.pop("taker_to_maker_quote_conversion_rate") + if "template_version" in conf: + conf.pop("template_version") + try: + config_map = ClientConfigAdapter(CrossExchangeMarketMakingConfigMap(**conf)) + save_to_yml(new_path, config_map) + errors = [] + except Exception as e: + logging.getLogger().error(str(e)) + errors = [str(e)] + return errors + + +def _has_connector_field(conf: Dict) -> bool: + return ( + "exchange" in conf + or "connector_1" in conf # amm arb + or "primary_market" in conf # arbitrage + or "secondary_exchange" in conf # celo arb + or "maker_market" in conf # XEMM + or "market" in conf # dev simple trade + or "maker_exchange" in conf # hedge + or "spot_connector" in conf # spot-perp arb + or "connector" in conf # twap + ) + + +def migrate_connector_confs(secrets_manager: BaseSecretsManager): + logging.getLogger().info("\nMigrating connector secure keys...") + errors = [] + Security.secrets_manager = secrets_manager + connector_exceptions = ["paper_trade"] + type_dirs: List[DirEntry] = [ + cast(DirEntry, f) for f in + scandir(f"{root_path() / 'hummingbot' / 'connector'}") + if f.is_dir() + ] + for type_dir in type_dirs: + connector_dirs: List[DirEntry] = [ + cast(DirEntry, f) for f in scandir(type_dir.path) + if f.is_dir() and exists(join(f.path, "__init__.py")) + ] + for connector_dir in connector_dirs: + if connector_dir.name.startswith("_") or connector_dir.name in connector_exceptions: + continue + try: + suffix = "data_types" if connector_dir.name == "celo" else "utils" + util_module_path: str = ( + f"hummingbot.connector.{type_dir.name}.{connector_dir.name}.{connector_dir.name}_{suffix}" + ) + util_module = importlib.import_module(util_module_path) + config_keys = getattr(util_module, "KEYS", None) + if config_keys is not None: + errors.extend(_maybe_migrate_encrypted_confs(config_keys)) + other_domains = getattr(util_module, "OTHER_DOMAINS", []) + for domain in other_domains: + config_keys = getattr(util_module, "OTHER_DOMAINS_KEYS")[domain] + if config_keys is not None: + errors.extend(_maybe_migrate_encrypted_confs(config_keys)) + except ModuleNotFoundError: + continue + return errors + + +def _maybe_migrate_encrypted_confs(config_keys: BaseConnectorConfigMap) -> List[str]: + cm = ClientConfigAdapter(config_keys) + found_one = False + files_to_remove = [] + missing_fields = [] + for el in cm.traverse(): + if el.client_field_data is not None: + key_path = conf_dir_path / f"{encrypted_conf_prefix}{el.attr}{encrypted_conf_postfix}" + if key_path.exists(): + with open(key_path, 'r') as f: + json_str = f.read() + value = binascii.hexlify(json_str.encode()).decode() + if not el.client_field_data.is_secure: + value = Security.secrets_manager.decrypt_secret_value(el.attr, value) + cm.setattr_no_validation(el.attr, value) + files_to_remove.append(key_path) + found_one = True + else: + missing_fields.append(el.attr) + errors = [] + if found_one: + if len(missing_fields) != 0: + errors = [f"{config_keys.connector} - missing fields: {missing_fields}"] + if len(errors) == 0: + errors = cm.validate_model() + if errors: + errors = [f"{config_keys.connector} - {e}" for e in errors] + logging.getLogger().error(f"The migration of {config_keys.connector} failed with errors: {errors}") + else: + Security.update_secure_config(cm) + logging.getLogger().info(f"Migrated secure keys for {config_keys.connector}") + for f in files_to_remove: + f.unlink() + return errors diff --git a/hummingbot/client/config/config_crypt.py b/hummingbot/client/config/config_crypt.py new file mode 100644 index 0000000..28acd30 --- /dev/null +++ b/hummingbot/client/config/config_crypt.py @@ -0,0 +1,144 @@ +import binascii +import json +from abc import ABC, abstractmethod + +from eth_account import Account +from eth_keyfile.keyfile import ( + DKLEN, + SCRYPT_P, + SCRYPT_R, + Random, + _pbkdf2_hash, + _scrypt_hash, + big_endian_to_int, + encode_hex_no_prefix, + encrypt_aes_ctr, + get_default_work_factor_for_kdf, + int_to_big_endian, + keccak, +) +from pydantic import SecretStr + +from hummingbot.client.settings import CONF_DIR_PATH + +PASSWORD_VERIFICATION_WORD = "HummingBot" +PASSWORD_VERIFICATION_PATH = CONF_DIR_PATH / ".password_verification" + + +class BaseSecretsManager(ABC): + def __init__(self, password: str): + self._password = password + + @property + def password(self) -> SecretStr: + return SecretStr(self._password) + + @abstractmethod + def encrypt_secret_value(self, attr: str, value: str): + pass + + @abstractmethod + def decrypt_secret_value(self, attr: str, value: str) -> str: + pass + + +class ETHKeyFileSecretManger(BaseSecretsManager): + def encrypt_secret_value(self, attr: str, value: str): + if self._password is None: + raise ValueError(f"Could not encrypt secret attribute {attr} because no password was provided.") + password_bytes = self._password.encode() + value_bytes = value.encode() + keyfile_json = _create_v3_keyfile_json(value_bytes, password_bytes) + json_str = json.dumps(keyfile_json) + encrypted_value = binascii.hexlify(json_str.encode()).decode() + return encrypted_value + + def decrypt_secret_value(self, attr: str, value: str) -> str: + if self._password is None: + raise ValueError(f"Could not decrypt secret attribute {attr} because no password was provided.") + value = binascii.unhexlify(value) + decrypted_value = Account.decrypt(value.decode(), self._password).decode() + return decrypted_value + + +def store_password_verification(secrets_manager: BaseSecretsManager): + encrypted_word = secrets_manager.encrypt_secret_value(PASSWORD_VERIFICATION_WORD, PASSWORD_VERIFICATION_WORD) + with open(PASSWORD_VERIFICATION_PATH, "w") as f: + f.write(encrypted_word) + + +def validate_password(secrets_manager: BaseSecretsManager) -> bool: + valid = False + with open(PASSWORD_VERIFICATION_PATH, "r") as f: + encrypted_word = f.read() + try: + decrypted_word = secrets_manager.decrypt_secret_value(PASSWORD_VERIFICATION_WORD, encrypted_word) + valid = decrypted_word == PASSWORD_VERIFICATION_WORD + except ValueError as e: + if str(e) != "MAC mismatch": + raise e + return valid + + +def _create_v3_keyfile_json(message_to_encrypt, password, kdf="pbkdf2", work_factor=None): + """ + Encrypt message by a given password. + Most of this code is copied from eth_key_file.key_file, removed address and is from json result. + """ + salt = Random.get_random_bytes(16) + + if work_factor is None: + work_factor = get_default_work_factor_for_kdf(kdf) + + if kdf == 'pbkdf2': + derived_key = _pbkdf2_hash( + password, + hash_name='sha256', + salt=salt, + iterations=work_factor, + dklen=DKLEN, + ) + kdfparams = { + 'c': work_factor, + 'dklen': DKLEN, + 'prf': 'hmac-sha256', + 'salt': encode_hex_no_prefix(salt), + } + elif kdf == 'scrypt': + derived_key = _scrypt_hash( + password, + salt=salt, + buflen=DKLEN, + r=SCRYPT_R, + p=SCRYPT_P, + n=work_factor, + ) + kdfparams = { + 'dklen': DKLEN, + 'n': work_factor, + 'r': SCRYPT_R, + 'p': SCRYPT_P, + 'salt': encode_hex_no_prefix(salt), + } + else: + raise NotImplementedError("KDF not implemented: {0}".format(kdf)) + + iv = big_endian_to_int(Random.get_random_bytes(16)) + encrypt_key = derived_key[:16] + ciphertext = encrypt_aes_ctr(message_to_encrypt, encrypt_key, iv) + mac = keccak(derived_key[16:32] + ciphertext) + + return { + 'crypto': { + 'cipher': 'aes-128-ctr', + 'cipherparams': { + 'iv': encode_hex_no_prefix(int_to_big_endian(iv)), + }, + 'ciphertext': encode_hex_no_prefix(ciphertext), + 'kdf': kdf, + 'kdfparams': kdfparams, + 'mac': encode_hex_no_prefix(mac), + }, + 'version': 3, + 'alias': '', # Add this line to include the 'alias' field with an empty string value + } diff --git a/hummingbot/client/config/config_data_types.py b/hummingbot/client/config/config_data_types.py new file mode 100644 index 0000000..b4c0b16 --- /dev/null +++ b/hummingbot/client/config/config_data_types.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass +from datetime import datetime +from decimal import Decimal +from enum import Enum +from typing import Any, Callable, Optional + +from pydantic import BaseModel, Extra, Field, validator +from pydantic.schema import default_ref_template + +from hummingbot.client.config.config_methods import strategy_config_schema_encoder +from hummingbot.client.config.config_validators import validate_connector, validate_decimal + + +class ClientConfigEnum(Enum): + def __str__(self): + return self.value + + +@dataclass() +class ClientFieldData: + prompt: Optional[Callable[['BaseClientModel'], str]] = None + prompt_on_new: bool = False + is_secure: bool = False + is_connect_key: bool = False + + +class BaseClientModel(BaseModel): + class Config: + validate_assignment = True + title = None + smart_union = True + extra = Extra.forbid + json_encoders = { + datetime: lambda dt: dt.strftime("%Y-%m-%d %H:%M:%S"), + } + + @classmethod + def schema_json( + cls, *, by_alias: bool = True, ref_template: str = default_ref_template, **dumps_kwargs: Any + ) -> str: + # todo: make it ignore `client_data` all together + return cls.__config__.json_dumps( + cls.schema(by_alias=by_alias, ref_template=ref_template), + default=strategy_config_schema_encoder, + **dumps_kwargs + ) + + @classmethod + def _clear_schema_cache(cls): + cls.__schema_cache__ = {} + + def is_required(self, attr: str) -> bool: + return self.__fields__[attr].required + + def validate_decimal(v: str, field: Field): + """Used for client-friendly error output.""" + field_info = field.field_info + inclusive = field_info.ge is not None or field_info.le is not None + min_value = field_info.gt if field_info.gt is not None else field_info.ge + min_value = Decimal(min_value) if min_value is not None else min_value + max_value = field_info.lt if field_info.lt is not None else field_info.le + max_value = Decimal(max_value) if max_value is not None else max_value + ret = validate_decimal(v, min_value, max_value, inclusive) + if ret is not None: + raise ValueError(ret) + return v + + +class BaseConnectorConfigMap(BaseClientModel): + connector: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda mi: "What is your connector?", + prompt_on_new=True, + ), + ) + + @validator("connector", pre=True) + def validate_connector(cls, v: str): + ret = validate_connector(v) + if ret is not None: + raise ValueError(ret) + return v diff --git a/hummingbot/client/config/config_helpers.py b/hummingbot/client/config/config_helpers.py new file mode 100644 index 0000000..d0718ee --- /dev/null +++ b/hummingbot/client/config/config_helpers.py @@ -0,0 +1,997 @@ +import contextlib +import inspect +import json +import logging +import shutil +from collections import OrderedDict, defaultdict +from dataclasses import dataclass +from datetime import date, datetime, time +from decimal import Decimal +from os import listdir, scandir, unlink +from os.path import isfile, join +from pathlib import Path, PosixPath, PureWindowsPath +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +import ruamel.yaml +import yaml +from pydantic import SecretStr, ValidationError +from pydantic.fields import FieldInfo +from pydantic.main import ModelMetaclass, validate_model +from yaml import SafeDumper + +from hummingbot import get_strategy_list, root_path +from hummingbot.client.config.client_config_map import ClientConfigMap, CommandShortcutModel +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map, init_fee_overrides_config +from hummingbot.client.config.gateway_ssl_config_map import SSLConfigMap +from hummingbot.client.settings import ( + CLIENT_CONFIG_PATH, + CONF_DIR_PATH, + CONF_POSTFIX, + CONF_PREFIX, + CONNECTORS_CONF_DIR_PATH, + GATEWAY_SSL_CONF_FILE, + STRATEGIES_CONF_DIR_PATH, + TEMPLATE_PATH, + TRADE_FEES_CONFIG_PATH, + AllConnectorSettings, +) + + +class ConfigValidationError(Exception): + pass + + +@dataclass() +class ConfigTraversalItem: + depth: int + config_path: str + attr: str + value: Any + printable_value: str + client_field_data: Optional[ClientFieldData] + field_info: FieldInfo + type_: Type + + +class ClientConfigAdapter: + def __init__(self, hb_config: BaseClientModel): + self._hb_config = hb_config + + def __getattr__(self, item): + value = getattr(self._hb_config, item) + if isinstance(value, BaseClientModel): + value = ClientConfigAdapter(value) + return value + + def __setattr__(self, key, value): + if key == "_hb_config": + super().__setattr__(key, value) + else: + try: + self._hb_config.__setattr__(key, value) + except ValidationError as e: + raise ConfigValidationError(retrieve_validation_error_msg(e)) + + def __repr__(self): + return f"{self.__class__.__name__}.{self._hb_config.__repr__()}" + + def __eq__(self, other): + if isinstance(other, ClientConfigAdapter): + eq = self._hb_config.__eq__(other._hb_config) + else: + eq = super().__eq__(other) + return eq + + @property + def hb_config(self) -> BaseClientModel: + return self._hb_config + + @property + def fetch_pairs_from_all_exchanges(self) -> bool: + return ClientConfigMap.fetch_pairs_from_all_exchanges + + @property + def title(self) -> str: + return self._hb_config.Config.title + + def is_required(self, attr: str) -> bool: + return self._hb_config.is_required(attr) + + def keys(self) -> Generator[str, None, None]: + return self._hb_config.__fields__.keys() + + def config_paths(self) -> Generator[str, None, None]: + return (traversal_item.config_path for traversal_item in self.traverse()) + + def traverse(self, secure: bool = True) -> Generator[ConfigTraversalItem, None, None]: + """The intended use for this function is to simplify config map traversals in the client code. + + If the field is missing, its value will be set to `None` and its printable value will be set to + 'MISSING_AND_REQUIRED'. + """ + depth = 0 + for attr, field in self._hb_config.__fields__.items(): + field_info = field.field_info + type_ = field.type_ + if hasattr(self, attr): + value = getattr(self, attr) + printable_value = self._get_printable_value(attr, value, secure) + client_field_data = field_info.extra.get("client_data") + else: + value = None + printable_value = "&cMISSING_AND_REQUIRED" + client_field_data = self.get_client_data(attr) + yield ConfigTraversalItem( + depth=depth, + config_path=attr, + attr=attr, + value=value, + printable_value=printable_value, + client_field_data=client_field_data, + field_info=field_info, + type_=type_, + ) + if isinstance(value, ClientConfigAdapter): + for traversal_item in value.traverse(): + traversal_item.depth += 1 + config_path = f"{attr}.{traversal_item.config_path}" + traversal_item.config_path = config_path + yield traversal_item + + async def get_client_prompt(self, attr_name: str) -> Optional[str]: + prompt = None + client_data = self.get_client_data(attr_name) + if client_data is not None: + prompt_fn = client_data.prompt + if inspect.iscoroutinefunction(prompt_fn): + prompt = await prompt_fn(self._hb_config) + else: + prompt = prompt_fn(self._hb_config) + return prompt + + def is_secure(self, attr_name: str) -> bool: + client_data = self.get_client_data(attr_name) + secure = client_data is not None and client_data.is_secure + return secure + + def get_client_data(self, attr_name: str) -> Optional[ClientFieldData]: + return self._hb_config.__fields__[attr_name].field_info.extra.get("client_data") + + def get_description(self, attr_name: str) -> str: + return self._hb_config.__fields__[attr_name].field_info.description + + def get_default(self, attr_name: str) -> Any: + default = self._hb_config.__fields__[attr_name].field_info.default + if isinstance(default, type(Ellipsis)): + default = None + return default + + def get_default_str_repr(self, attr_name: str) -> str: + """Used to generate default strings for config prompts.""" + default = self.get_default(attr_name=attr_name) + if default is None: + default_str = "" + elif isinstance(default, (List, Tuple)): + default_str = ",".join(default) + elif isinstance(default, BaseClientModel): + default_str = default.Config.title + else: + default_str = str(default) + return default_str + + def get_type(self, attr_name: str) -> Type: + return self._hb_config.__fields__[attr_name].type_ + + def generate_yml_output_str_with_comments(self) -> str: + fragments_with_comments = [self._generate_title()] + self._add_model_fragments(fragments_with_comments) + yml_str = "".join(fragments_with_comments) + return yml_str + + def validate_model(self) -> List[str]: + input_data = self._hb_config.dict() + results = validate_model(model=type(self._hb_config), input_data=input_data) # coerce types + conf_dict = results[0] + for key, value in conf_dict.items(): + self.setattr_no_validation(key, value) + self.decrypt_all_secure_data() + input_data = self._hb_config.dict() + results = validate_model(model=type(self._hb_config), input_data=input_data) # validate decrypted values + conf_dict = results[0] + errors = results[2] + for key, value in conf_dict.items(): + self.setattr_no_validation(key, value) + validation_errors = [] + if errors is not None: + errors = errors.errors() + validation_errors = [ + f"{'.'.join(e['loc'])} - {e['msg']}" + for e in errors + ] + return validation_errors + + def setattr_no_validation(self, attr: str, value: Any): + with self._disable_validation(): + setattr(self, attr, value) + + def full_copy(self): + return self.__class__(hb_config=self._hb_config.copy(deep=True)) + + def decrypt_all_secure_data(self): + from hummingbot.client.config.security import Security # avoids circular import + + secure_config_items = ( + traversal_item + for traversal_item in self.traverse() + if traversal_item.client_field_data is not None and traversal_item.client_field_data.is_secure + ) + for traversal_item in secure_config_items: + value = traversal_item.value + if isinstance(value, SecretStr): + value = value.get_secret_value() + if value == "" or Security.secrets_manager is None: + decrypted_value = value + else: + decrypted_value = Security.secrets_manager.decrypt_secret_value(attr=traversal_item.attr, value=value) + *intermediate_items, final_config_element = traversal_item.config_path.split(".") + config_model = self + if len(intermediate_items) > 0: + for attr in intermediate_items: + config_model = config_model.__getattr__(attr) + setattr(config_model, final_config_element, decrypted_value) + + @contextlib.contextmanager + def _disable_validation(self): + self._hb_config.Config.validate_assignment = False + yield + self._hb_config.Config.validate_assignment = True + + def _get_printable_value(self, attr: str, value: Any, secure: bool) -> str: + if isinstance(value, ClientConfigAdapter): + if self._is_union(self.get_type(attr)): # it is a union of modes + printable_value = value.hb_config.Config.title + else: # it is a collection of settings stored in a submodule + printable_value = "" + elif isinstance(value, SecretStr) and not secure: + printable_value = value.get_secret_value() + else: + printable_value = str(value) + return printable_value + + @staticmethod + def _is_union(t: Type) -> bool: + is_union = hasattr(t, "__origin__") and t.__origin__ == Union + return is_union + + def _dict_in_conf_order(self) -> Dict[str, Any]: + conf_dict = {} + for attr in self._hb_config.__fields__.keys(): + value = getattr(self, attr) + if isinstance(value, ClientConfigAdapter): + value = value._dict_in_conf_order() + conf_dict[attr] = value + self._encrypt_secrets(conf_dict) + return conf_dict + + def _encrypt_secrets(self, conf_dict: Dict[str, Any]): + from hummingbot.client.config.security import Security # avoids circular import + for attr, value in conf_dict.items(): + attr_type = self._hb_config.__fields__[attr].type_ + if attr_type == SecretStr: + clear_text_value = value.get_secret_value() if isinstance(value, SecretStr) else value + conf_dict[attr] = Security.secrets_manager.encrypt_secret_value(attr, clear_text_value) + + def _decrypt_secrets(self, conf_dict: Dict[str, Any]): + from hummingbot.client.config.security import Security # avoids circular import + for attr, value in conf_dict.items(): + attr_type = self._hb_config.__fields__[attr].type_ + if attr_type == SecretStr: + decrypted_value = Security.secrets_manager.decrypt_secret_value(attr, value.get_secret_value()) + conf_dict[attr] = SecretStr(decrypted_value) + + def _decrypt_all_internal_secrets(self): + from hummingbot.client.config.security import Security # avoids circular import + + for traversal_item in self.traverse(): + if traversal_item.type_ == SecretStr: + encrypted_value = traversal_item.value + if isinstance(encrypted_value, SecretStr): + encrypted_value = encrypted_value.get_secret_value() + decrypted_value = Security.secrets_manager.decrypt_secret_value(traversal_item.attr, encrypted_value) + parent_attributes = traversal_item.config_path.split(".")[:-1] + config = self + for parent_attribute in parent_attributes: + config = getattr(config, parent_attribute) + setattr(config, traversal_item.attr, decrypted_value) + + def _generate_title(self) -> str: + title = f"{self._hb_config.Config.title}" + title = self._adorn_title(title) + return title + + @staticmethod + def _adorn_title(title: str) -> str: + if title: + title = f"### {title} config ###" + title_len = len(title) + title = f"{'#' * title_len}\n{title}\n{'#' * title_len}" + return title + + def _add_model_fragments( + self, + fragments_with_comments: List[str], + ): + + fragments_with_comments.append("\n") + first_level_conf_items_generator = (item for item in self.traverse() if item.depth == 0) + + for traversal_item in first_level_conf_items_generator: + fragments_with_comments.append("\n") + + attr_comment = traversal_item.field_info.description + if attr_comment is not None: + comment_prefix = f"{' ' * 2 * traversal_item.depth}# " + attr_comment = "\n".join(f"{comment_prefix}{c}" for c in attr_comment.split("\n")) + fragments_with_comments.append(attr_comment) + fragments_with_comments.append("\n") + + attribute = traversal_item.attr + value = getattr(self, attribute) + if isinstance(value, ClientConfigAdapter): + value = value._dict_in_conf_order() + if isinstance(traversal_item.value, PureWindowsPath): + conf_as_dictionary = {attribute: traversal_item.printable_value} + else: + conf_as_dictionary = {attribute: value} + self._encrypt_secrets(conf_as_dictionary) + + yaml_config = yaml.safe_dump(conf_as_dictionary, sort_keys=False) + fragments_with_comments.append(yaml_config) + + +class ReadOnlyClientConfigAdapter(ClientConfigAdapter): + def __setattr__(self, key, value): + if key == "_hb_config": + super().__setattr__(key, value) + else: + raise AttributeError("Cannot set an attribute on a read-only client adapter") + + @classmethod + def lock_config(cls, config_map: ClientConfigMap): + return cls(config_map._hb_config) + + +# Use ruamel.yaml to preserve order and comments in .yml file +yaml_parser = ruamel.yaml.YAML() # legacy + + +def decimal_representer(dumper: SafeDumper, data: Decimal): + return dumper.represent_float(float(data)) + + +def enum_representer(dumper: SafeDumper, data: ClientConfigEnum): + return dumper.represent_str(str(data)) + + +def date_representer(dumper: SafeDumper, data: date): + return dumper.represent_date(data) + + +def time_representer(dumper: SafeDumper, data: time): + return dumper.represent_str(data.strftime("%H:%M:%S")) + + +def datetime_representer(dumper: SafeDumper, data: datetime): + return dumper.represent_datetime(data) + + +def path_representer(dumper: SafeDumper, data: Path): + return dumper.represent_str(str(data)) + + +def command_shortcut_representer(dumper: SafeDumper, data: CommandShortcutModel): + return dumper.represent_dict(data.__dict__) + + +def client_config_adapter_representer(dumper: SafeDumper, data: ClientConfigAdapter): + return dumper.represent_dict(data._dict_in_conf_order()) + + +def base_client_model_representer(dumper: SafeDumper, data: BaseClientModel): + dictionary_representation = ClientConfigAdapter(data)._dict_in_conf_order() + return dumper.represent_dict(dictionary_representation) + + +yaml.add_representer( + data_type=Decimal, representer=decimal_representer, Dumper=SafeDumper +) +yaml.add_multi_representer( + data_type=ClientConfigEnum, multi_representer=enum_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=date, representer=date_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=time, representer=time_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=datetime, representer=datetime_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=Path, representer=path_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=PosixPath, representer=path_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=CommandShortcutModel, representer=command_shortcut_representer, Dumper=SafeDumper +) +yaml.add_representer( + data_type=ClientConfigAdapter, representer=client_config_adapter_representer, Dumper=SafeDumper +) +yaml.add_multi_representer( + data_type=BaseClientModel, multi_representer=base_client_model_representer, Dumper=SafeDumper +) + + +def parse_cvar_value(cvar: ConfigVar, value: Any) -> Any: + """ + Based on the target type specified in `ConfigVar.type_str`, parses a string value into the target type. + :param cvar: ConfigVar object + :param value: User input from running session or from saved `yml` files. Type is usually string. + :return: value in the correct type + """ + if value is None: + return None + elif cvar.type == 'str': + return str(value) + elif cvar.type == 'list': + if isinstance(value, str): + if len(value) == 0: + return [] + filtered: filter = filter(lambda x: x not in ['[', ']', '"', "'"], list(value)) + value = "".join(filtered).split(",") # create csv and generate list + return [s.strip() for s in value] # remove leading and trailing whitespaces + else: + return value + elif cvar.type == 'json': + if isinstance(value, str): + value_json = value.replace("'", '"') # replace single quotes with double quotes for valid JSON + cvar_value = json.loads(value_json) + else: + cvar_value = value + return cvar_json_migration(cvar, cvar_value) + elif cvar.type == 'float': + try: + return float(value) + except Exception: + logging.getLogger().error(f"\"{value}\" is not valid float.", exc_info=True) + return value + elif cvar.type == 'decimal': + try: + return Decimal(str(value)) + except Exception: + logging.getLogger().error(f"\"{value}\" is not valid decimal.", exc_info=True) + return value + elif cvar.type == 'int': + try: + return int(value) + except Exception: + logging.getLogger().error(f"\"{value}\" is not an integer.", exc_info=True) + return value + elif cvar.type == 'bool': + if isinstance(value, str) and value.lower() in ["true", "yes", "y"]: + return True + elif isinstance(value, str) and value.lower() in ["false", "no", "n"]: + return False + else: + return value + else: + raise TypeError + + +def cvar_json_migration(cvar: ConfigVar, cvar_value: Any) -> Any: + """ + A special function to migrate json config variable when its json type changes, for paper_trade_account_balance + and min_quote_order_amount (deprecated), they were List but change to Dict. + """ + if cvar.key in ("paper_trade_account_balance", "min_quote_order_amount") and isinstance(cvar_value, List): + results = {} + for item in cvar_value: + results[item[0]] = item[1] + return results + return cvar_value + + +def parse_cvar_default_value_prompt(cvar: ConfigVar) -> str: + """ + :param cvar: ConfigVar object + :return: text for default value prompt + """ + if cvar.default is None: + default = "" + elif callable(cvar.default): + default = cvar.default() + elif cvar.type == 'bool' and isinstance(cvar.prompt, str) and "Yes/No" in cvar.prompt: + default = "Yes" if cvar.default else "No" + else: + default = str(cvar.default) + if isinstance(default, Decimal): + default = "{0:.4f}".format(default) + return default + + +async def copy_strategy_template(strategy: str) -> str: + """ + Look up template `.yml` file for a particular strategy in `hummingbot/templates` and copy it to the `conf` folder. + The file name is `conf_{STRATEGY}_strategy_{INDEX}.yml` + :return: The newly created file name + """ + old_path = get_strategy_template_path(strategy) + i = 0 + new_fname = f"{CONF_PREFIX}{strategy}{CONF_POSTFIX}_{i}.yml" + new_path = STRATEGIES_CONF_DIR_PATH / new_fname + while isfile(new_path): + new_fname = f"{CONF_PREFIX}{strategy}{CONF_POSTFIX}_{i}.yml" + new_path = STRATEGIES_CONF_DIR_PATH / new_fname + i += 1 + shutil.copy(old_path, new_path) + return new_fname + + +def get_strategy_template_path(strategy: str) -> Path: + """ + Given the strategy name, return its template config `yml` file name. + """ + return TEMPLATE_PATH / f"{CONF_PREFIX}{strategy}{CONF_POSTFIX}_TEMPLATE.yml" + + +def _merge_dicts(*args: Dict[str, ConfigVar]) -> OrderedDict: + """ + Helper function to merge a few dictionaries into an ordered dictionary. + """ + result: OrderedDict[any] = OrderedDict() + for d in args: + result.update(d) + return result + + +def get_connector_class(connector_name: str) -> Callable: + conn_setting = AllConnectorSettings.get_connector_settings()[connector_name] + mod = __import__(conn_setting.module_path(), + fromlist=[conn_setting.class_name()]) + return getattr(mod, conn_setting.class_name()) + + +def get_strategy_config_map( + strategy: str +) -> Optional[Union[ClientConfigAdapter, Dict[str, ConfigVar]]]: + """ + Given the name of a strategy, find and load strategy-specific config map. + """ + try: + config_cls = get_strategy_pydantic_config_cls(strategy) + if config_cls is None: # legacy + cm_key = f"{strategy}_config_map" + strategy_module = __import__(f"hummingbot.strategy.{strategy}.{cm_key}", + fromlist=[f"hummingbot.strategy.{strategy}"]) + config_map = getattr(strategy_module, cm_key) + else: + hb_config = config_cls.construct() + config_map = ClientConfigAdapter(hb_config) + except Exception: + config_map = defaultdict() + return config_map + + +def get_strategy_starter_file(strategy: str) -> Callable: + """ + Given the name of a strategy, find and load the `start` function in + `hummingbot/strategy/{STRATEGY_NAME}/start.py` file. + """ + if strategy is None: + return lambda: None + try: + strategy_module = __import__(f"hummingbot.strategy.{strategy}.start", + fromlist=[f"hummingbot.strategy.{strategy}"]) + return getattr(strategy_module, "start") + except Exception as e: + logging.getLogger().error(e, exc_info=True) + + +def strategy_name_from_file(file_path: Path) -> str: + data = read_yml_file(file_path) + strategy = data.get("strategy") + return strategy + + +def connector_name_from_file(file_path: Path) -> str: + data = read_yml_file(file_path) + connector = data["connector"] + return connector + + +def validate_strategy_file(file_path: Path) -> Optional[str]: + if not isfile(file_path): + return f"{file_path} file does not exist." + strategy = strategy_name_from_file(file_path) + if strategy is None: + return "Invalid configuration file or 'strategy' field is missing." + if strategy not in get_strategy_list(): + return "Invalid strategy specified in the file." + return None + + +def read_yml_file(yml_path: Path) -> Dict[str, Any]: + with open(yml_path, "r", encoding="utf-8") as file: + data = yaml.safe_load(file) or {} + return dict(data) + + +def get_strategy_pydantic_config_cls(strategy_name: str) -> Optional[ModelMetaclass]: + pydantic_cm_class = None + try: + pydantic_cm_pkg = f"{strategy_name}_config_map_pydantic" + pydantic_cm_path = root_path() / "hummingbot" / "strategy" / strategy_name / f"{pydantic_cm_pkg}.py" + if pydantic_cm_path.exists(): + pydantic_cm_class_name = f"{''.join([s.capitalize() for s in strategy_name.split('_')])}ConfigMap" + pydantic_cm_mod = __import__(f"hummingbot.strategy.{strategy_name}.{pydantic_cm_pkg}", + fromlist=[f"{pydantic_cm_class_name}"]) + pydantic_cm_class = getattr(pydantic_cm_mod, pydantic_cm_class_name) + except ImportError: + logging.getLogger().exception(f"Could not import Pydantic configs for {strategy_name}.") + return pydantic_cm_class + + +async def load_strategy_config_map_from_file(yml_path: Path) -> Union[ClientConfigAdapter, Dict[str, ConfigVar]]: + strategy_name = strategy_name_from_file(yml_path) + config_cls = get_strategy_pydantic_config_cls(strategy_name) + if config_cls is None: # legacy + config_map = get_strategy_config_map(strategy_name) + template_path = get_strategy_template_path(strategy_name) + await load_yml_into_cm_legacy(str(yml_path), str(template_path), config_map) + else: + config_data = read_yml_file(yml_path) + hb_config = config_cls.construct() + config_map = ClientConfigAdapter(hb_config) + _load_yml_data_into_map(config_data, config_map) + return config_map + + +def load_connector_config_map_from_file(yml_path: Path) -> ClientConfigAdapter: + config_data = read_yml_file(yml_path) + connector_name = connector_name_from_file(yml_path) + hb_config = get_connector_hb_config(connector_name) + config_map = ClientConfigAdapter(hb_config) + _load_yml_data_into_map(config_data, config_map) + return config_map + + +def load_client_config_map_from_file() -> ClientConfigAdapter: + yml_path = CLIENT_CONFIG_PATH + if yml_path.exists(): + config_data = read_yml_file(yml_path) + else: + config_data = {} + client_config = ClientConfigMap() + config_map = ClientConfigAdapter(client_config) + config_validation_errors = _load_yml_data_into_map(config_data, config_map) + + if len(config_validation_errors) > 0: + all_errors = "\n".join(config_validation_errors) + raise ConfigValidationError(f"There are errors in the client global configuration (\n{all_errors})") + save_to_yml(yml_path, config_map) + + return config_map + + +def load_ssl_config_map_from_file() -> ClientConfigAdapter: + yml_path = GATEWAY_SSL_CONF_FILE + if yml_path.exists(): + config_data = read_yml_file(yml_path) + else: + config_data = {} + ssl_config = SSLConfigMap() + config_map = ClientConfigAdapter(ssl_config) + config_validation_errors = _load_yml_data_into_map(config_data, config_map) + + if len(config_validation_errors) > 0: + all_errors = "\n".join(config_validation_errors) + raise ConfigValidationError(f"There are errors in the ssl certs configuration (\n{all_errors})") + + if yml_path.exists(): + save_to_yml(yml_path, config_map) + + return config_map + + +def get_connector_hb_config(connector_name: str) -> BaseClientModel: + hb_config = AllConnectorSettings.get_connector_config_keys(connector_name) + return hb_config + + +def reset_connector_hb_config(connector_name: str): + AllConnectorSettings.reset_connector_config_keys(connector_name) + + +def update_connector_hb_config(connector_config: ClientConfigAdapter): + AllConnectorSettings.update_connector_config_keys(connector_config.hb_config) + + +def api_keys_from_connector_config_map(cm: ClientConfigAdapter) -> Dict[str, str]: + api_keys = {} + for c in cm.traverse(): + if c.value is not None and c.client_field_data is not None and c.client_field_data.is_connect_key: + value = c.value.get_secret_value() if isinstance(c.value, SecretStr) else c.value + api_keys[c.attr] = value + return api_keys + + +def get_connector_config_yml_path(connector_name: str) -> Path: + connector_path = Path(CONNECTORS_CONF_DIR_PATH) / f"{connector_name}.yml" + return connector_path + + +def list_connector_configs() -> List[Path]: + connector_configs = [ + Path(f.path) for f in scandir(str(CONNECTORS_CONF_DIR_PATH)) + if f.is_file() and not f.name.startswith("_") and not f.name.startswith(".") + ] + return connector_configs + + +def _load_yml_data_into_map(yml_data: Dict[str, Any], cm: ClientConfigAdapter) -> List[str]: + for key in cm.keys(): + if key in yml_data: + cm.setattr_no_validation(key, yml_data[key]) + + config_validation_errors = cm.validate_model() # try coercing values to appropriate type + return config_validation_errors + + +async def load_yml_into_dict(yml_path: str) -> Dict[str, Any]: + data = {} + if isfile(yml_path): + with open(yml_path, encoding="utf-8") as stream: + data = yaml_parser.load(stream) or {} + + return dict(data.items()) + + +async def save_yml_from_dict(yml_path: str, conf_dict: Dict[str, Any]): + try: + with open(yml_path, "w+", encoding="utf-8") as stream: + data = yaml_parser.load(stream) or {} + for key in conf_dict: + data[key] = conf_dict.get(key) + with open(yml_path, "w+", encoding="utf-8") as outfile: + yaml_parser.dump(data, outfile) + except Exception as e: + logging.getLogger().error(f"Error writing configs: {str(e)}", exc_info=True) + + +async def load_yml_into_cm_legacy(yml_path: str, template_file_path: str, cm: Dict[str, ConfigVar]): + try: + data = {} + conf_version = -1 + if isfile(yml_path): + with open(yml_path, encoding="utf-8") as stream: + data = yaml_parser.load(stream) or {} + conf_version = data.get("template_version", 0) + + with open(template_file_path, "r", encoding="utf-8") as template_fd: + template_data = yaml_parser.load(template_fd) + template_version = template_data.get("template_version", 0) + + for key in template_data: + if key in {"template_version"}: + continue + + cvar = cm.get(key) + if cvar is None: + logging.getLogger().error(f"Cannot find corresponding config to key {key} in template.") + continue + + if cvar.is_secure: + raise DeprecationWarning("Secure values are no longer supported in legacy configs.") + + val_in_file = data.get(key, None) + if (val_in_file is None or val_in_file == "") and cvar.default is not None: + cvar.value = cvar.default + continue + + # Todo: the proper process should be first validate the value then assign it + cvar.value = parse_cvar_value(cvar, val_in_file) + if cvar.value is not None: + err_msg = await cvar.validate(str(cvar.value)) + if err_msg is not None: + # Instead of raising an exception, simply skip over this variable and wait till the user is prompted + logging.getLogger().error( + "Invalid value %s for config variable %s: %s" % (val_in_file, cvar.key, err_msg) + ) + cvar.value = None + + if conf_version < template_version: + # delete old config file + if isfile(yml_path): + unlink(yml_path) + # copy the new file template + shutil.copy(template_file_path, yml_path) + # save the old variables into the new config file + save_to_yml_legacy(yml_path, cm) + except Exception as e: + logging.getLogger().error("Error loading configs. Your config file may be corrupt. %s" % (e,), + exc_info=True) + + +async def read_system_configs_from_yml(): + """ + Read global config and selected strategy yml files and save the values to corresponding config map + If a yml file is outdated, it gets reformatted with the new template + """ + await load_yml_into_cm_legacy( + str(TRADE_FEES_CONFIG_PATH), str(TEMPLATE_PATH / "conf_fee_overrides_TEMPLATE.yml"), fee_overrides_config_map + ) + # In case config maps get updated (due to default values) + save_system_configs_to_yml() + + +def save_system_configs_to_yml(): + save_to_yml_legacy(str(TRADE_FEES_CONFIG_PATH), fee_overrides_config_map) + + +async def refresh_trade_fees_config(client_config_map: ClientConfigAdapter): + """ + Refresh the trade fees config, after new connectors have been added (e.g. gateway connectors). + """ + init_fee_overrides_config() + save_to_yml(CLIENT_CONFIG_PATH, client_config_map) + save_to_yml_legacy(str(TRADE_FEES_CONFIG_PATH), fee_overrides_config_map) + + +def save_to_yml_legacy(yml_path: str, cm: Dict[str, ConfigVar]): + """ + Write current config saved a single config map into each a single yml file + """ + try: + with open(yml_path, encoding="utf-8") as stream: + data = yaml_parser.load(stream) or {} + for key in cm: + cvar = cm.get(key) + if isinstance(cvar.value, Decimal): + data[key] = float(cvar.value) + else: + data[key] = cvar.value + with open(yml_path, "w+", encoding="utf-8") as outfile: + yaml_parser.dump(data, outfile) + except Exception as e: + logging.getLogger().error("Error writing configs: %s" % (str(e),), exc_info=True) + + +def save_to_yml(yml_path: Path, cm: ClientConfigAdapter): + try: + cm_yml_str = cm.generate_yml_output_str_with_comments() + with open(yml_path, "w", encoding="utf-8") as outfile: + outfile.write(cm_yml_str) + except Exception as e: + logging.getLogger().error("Error writing configs: %s" % (str(e),), exc_info=True) + + +def write_config_to_yml( + strategy_config_map: Union[ClientConfigAdapter, Dict], + strategy_file_name: str, + client_config_map: ClientConfigAdapter, +): + strategy_file_path = Path(STRATEGIES_CONF_DIR_PATH) / strategy_file_name + if isinstance(strategy_config_map, ClientConfigAdapter): + save_to_yml(strategy_file_path, strategy_config_map) + else: + save_to_yml_legacy(strategy_file_path, strategy_config_map) + save_to_yml(CLIENT_CONFIG_PATH, client_config_map) + + +async def create_yml_files_legacy(): + """ + Copy `hummingbot_logs.yml` and `conf_global.yml` templates to the `conf` directory on start up + """ + for fname in listdir(TEMPLATE_PATH): + if "_TEMPLATE" in fname and CONF_POSTFIX not in fname: + stripped_fname = fname.replace("_TEMPLATE", "") + template_path = str(TEMPLATE_PATH / fname) + conf_path = join(CONF_DIR_PATH, stripped_fname) + if not isfile(conf_path): + shutil.copy(template_path, conf_path) + + # Only overwrite log config. Updating `conf_global.yml` is handled by `read_configs_from_yml` + if conf_path.endswith("hummingbot_logs.yml"): + with open(template_path, "r", encoding="utf-8") as template_fd: + template_data = yaml_parser.load(template_fd) + template_version = template_data.get("template_version", 0) + with open(conf_path, "r", encoding="utf-8") as conf_fd: + conf_version = 0 + try: + conf_data = yaml_parser.load(conf_fd) + conf_version = conf_data.get("template_version", 0) + except Exception: + pass + if conf_version < template_version: + shutil.copy(template_path, conf_path) + + +def default_strategy_file_path(strategy: str) -> str: + """ + Find the next available file name. + :return: a default file name - `conf_{short_strategy}_{INDEX}.yml` e.g. 'conf_pure_mm_1.yml' + """ + i = 1 + new_fname = f"{CONF_PREFIX}{short_strategy_name(strategy)}_{i}.yml" + new_path = STRATEGIES_CONF_DIR_PATH / new_fname + while new_path.is_file(): + new_fname = f"{CONF_PREFIX}{short_strategy_name(strategy)}_{i}.yml" + new_path = STRATEGIES_CONF_DIR_PATH / new_fname + i += 1 + return new_fname + + +def short_strategy_name(strategy: str) -> str: + if strategy == "pure_market_making": + return "pure_mm" + elif strategy == "cross_exchange_market_making": + return "xemm" + elif strategy == "arbitrage": + return "arb" + else: + return strategy + + +def all_configs_complete(strategy_config: Union[ClientConfigAdapter, Dict], client_config_map: ClientConfigAdapter): + strategy_valid = ( + config_map_complete_legacy(strategy_config) + if isinstance(strategy_config, Dict) + else len(strategy_config.validate_model()) == 0 + ) + client_config_valid = len(client_config_map.validate_model()) == 0 + return client_config_valid and strategy_valid + + +def config_map_complete_legacy(config_map): + return not any(c.required and c.value is None for c in config_map.values()) + + +def missing_required_configs_legacy(config_map): + return [c for c in config_map.values() if c.required and c.value is None and not c.is_connect_key] + + +def format_config_file_name(file_name): + if "." not in file_name: + return file_name + ".yml" + return file_name + + +def parse_config_default_to_text(config: ConfigVar) -> str: + """ + :param config: ConfigVar object + :return: text for default value prompt + """ + if config.default is None: + default = "" + elif callable(config.default): + default = config.default() + elif config.type == 'bool' and isinstance(config.prompt, str) and "Yes/No" in config.prompt: + default = "Yes" if config.default else "No" + else: + default = str(config.default) + if isinstance(default, Decimal): + default = "{0:.4f}".format(default) + return default + + +def retrieve_validation_error_msg(e: ValidationError) -> str: + return e.errors().pop()["msg"] + + +def save_previous_strategy_value(file_name: str, client_config_map: ClientConfigAdapter): + client_config_map.previous_strategy = file_name + save_to_yml(CLIENT_CONFIG_PATH, client_config_map) diff --git a/hummingbot/client/config/config_methods.py b/hummingbot/client/config/config_methods.py new file mode 100644 index 0000000..4c2f671 --- /dev/null +++ b/hummingbot/client/config/config_methods.py @@ -0,0 +1,24 @@ +from typing import Callable + +from pydantic.json import pydantic_encoder + +from hummingbot.client.config.config_var import ConfigVar + + +def new_fee_config_var(key: str, type_str: str = "decimal"): + return ConfigVar(key=key, + prompt=None, + required_if=lambda: False, + type_str=type_str) + + +def using_exchange(exchange: str) -> Callable: + from hummingbot.client.settings import required_exchanges + return lambda: exchange in required_exchanges + + +def strategy_config_schema_encoder(o): + if callable(o): + return None + else: + return pydantic_encoder(o) diff --git a/hummingbot/client/config/config_validators.py b/hummingbot/client/config/config_validators.py new file mode 100644 index 0000000..41cb5d4 --- /dev/null +++ b/hummingbot/client/config/config_validators.py @@ -0,0 +1,164 @@ +""" +hummingbot.client.config.config_var defines ConfigVar. One of its parameters is a validator, a function that takes a +string and determines whether it is valid input. This file contains many validator functions that are used by various +hummingbot ConfigVars. +""" + +import time +from datetime import datetime +from decimal import Decimal +from typing import Optional + + +def validate_exchange(value: str) -> Optional[str]: + """ + Restrict valid exchanges to the exchange file names + """ + from hummingbot.client.settings import AllConnectorSettings + if value not in AllConnectorSettings.get_exchange_names(): + return f"Invalid exchange, please choose value from {AllConnectorSettings.get_exchange_names()}" + + +def validate_derivative(value: str) -> Optional[str]: + """ + restrict valid derivatives to the derivative file names + """ + from hummingbot.client.settings import AllConnectorSettings + if value not in AllConnectorSettings.get_derivative_names(): + return f"Invalid derivative, please choose value from {AllConnectorSettings.get_derivative_names()}" + + +def validate_connector(value: str) -> Optional[str]: + """ + Restrict valid derivatives to the connector file names + """ + from hummingbot.client import settings + from hummingbot.client.settings import AllConnectorSettings + if (value not in AllConnectorSettings.get_connector_settings() + and value not in settings.PAPER_TRADE_EXCHANGES): + return f"Invalid connector, please choose value from {AllConnectorSettings.get_connector_settings().keys()}" + + +def validate_strategy(value: str) -> Optional[str]: + """ + Restrict valid derivatives to the strategy file names + """ + from hummingbot.client.settings import STRATEGIES + if value not in STRATEGIES: + return f"Invalid strategy, please choose value from {STRATEGIES}" + + +def validate_decimal(value: str, min_value: Decimal = None, max_value: Decimal = None, inclusive=True) -> Optional[str]: + """ + Parse a decimal value from a string. This value can also be clamped. + """ + try: + decimal_value = Decimal(value) + except Exception: + return f"{value} is not in decimal format." + if inclusive: + if min_value is not None and max_value is not None: + if not (Decimal(str(min_value)) <= decimal_value <= Decimal(str(max_value))): + return f"Value must be between {min_value} and {max_value}." + elif min_value is not None and not decimal_value >= Decimal(str(min_value)): + return f"Value cannot be less than {min_value}." + elif max_value is not None and not decimal_value <= Decimal(str(max_value)): + return f"Value cannot be more than {max_value}." + else: + if min_value is not None and max_value is not None: + if not (Decimal(str(min_value)) < decimal_value < Decimal(str(max_value))): + return f"Value must be between {min_value} and {max_value} (exclusive)." + elif min_value is not None and not decimal_value > Decimal(str(min_value)): + return f"Value must be more than {min_value}." + elif max_value is not None and not decimal_value < Decimal(str(max_value)): + return f"Value must be less than {max_value}." + + +def validate_market_trading_pair(market: str, value: str) -> Optional[str]: + """ + Since trading pair validation and autocomplete are UI optimizations that do not impact bot performances, + in case of network issues or slow wifi, this check returns true and does not prevent users from proceeding, + """ + from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher + trading_pair_fetcher: TradingPairFetcher = TradingPairFetcher.get_instance() + if trading_pair_fetcher.ready: + trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) + if len(trading_pairs) == 0: + return None + elif value not in trading_pairs: + return f"{value} is not an active market on {market}." + + +def validate_bool(value: str) -> Optional[str]: + """ + Permissively interpret a string as a boolean + """ + valid_values = ('true', 'yes', 'y', 'false', 'no', 'n') + if value.lower() not in valid_values: + return f"Invalid value, please choose value from {valid_values}" + + +def validate_int(value: str, min_value: int = None, max_value: int = None, inclusive=True) -> Optional[str]: + """ + Parse an int value from a string. This value can also be clamped. + """ + try: + int_value = int(value) + except Exception: + return f"{value} is not in integer format." + if inclusive: + if min_value is not None and max_value is not None: + if not (min_value <= int_value <= max_value): + return f"Value must be between {min_value} and {max_value}." + elif min_value is not None and not int_value >= min_value: + return f"Value cannot be less than {min_value}." + elif max_value is not None and not int_value <= max_value: + return f"Value cannot be more than {max_value}." + else: + if min_value is not None and max_value is not None: + if not (min_value < int_value < max_value): + return f"Value must be between {min_value} and {max_value} (exclusive)." + elif min_value is not None and not int_value > min_value: + return f"Value must be more than {min_value}." + elif max_value is not None and not int_value < max_value: + return f"Value must be less than {max_value}." + + +def validate_float(value: str, min_value: float = None, max_value: float = None, inclusive=True) -> Optional[str]: + """ + Parse an float value from a string. This value can also be clamped. + """ + try: + float_value = float(value) + except Exception: + return f"{value} is not in integer format." + if inclusive: + if min_value is not None and max_value is not None: + if not (min_value <= float_value <= max_value): + return f"Value must be between {min_value} and {max_value}." + elif min_value is not None and not float_value >= min_value: + return f"Value cannot be less than {min_value}." + elif max_value is not None and not float_value <= max_value: + return f"Value cannot be more than {max_value}." + else: + if min_value is not None and max_value is not None: + if not (min_value < float_value < max_value): + return f"Value must be between {min_value} and {max_value} (exclusive)." + elif min_value is not None and not float_value > min_value: + return f"Value must be more than {min_value}." + elif max_value is not None and not float_value < max_value: + return f"Value must be less than {max_value}." + + +def validate_datetime_iso_string(value: str) -> Optional[str]: + try: + datetime.strptime(value, '%Y-%m-%d %H:%M:%S') + except ValueError: + return "Incorrect date time format (expected is YYYY-MM-DD HH:MM:SS)" + + +def validate_time_iso_string(value: str) -> Optional[str]: + try: + time.strptime(value, '%H:%M:%S') + except ValueError: + return "Incorrect time format (expected is HH:MM:SS)" diff --git a/hummingbot/client/config/config_var.py b/hummingbot/client/config/config_var.py new file mode 100644 index 0000000..6b57826 --- /dev/null +++ b/hummingbot/client/config/config_var.py @@ -0,0 +1,85 @@ +""" +ConfigVar is variable that is configured by the user via the hummingbot client that controls the trading behavior +of the bot. The client provides a screen prompt to the user, then the user provides input. This input is validated +by ConfigVar. +""" + +from typing import ( + Optional, + Callable, + Union, +) +import inspect + +# function types passed into ConfigVar +RequiredIf = Callable[[str], Optional[bool]] +Validator = Callable[[str], Optional[str]] +Prompt = Union[Callable[[str], Optional[str]], Optional[str]] +OnValidated = Callable + + +class ConfigVar: + def __init__(self, + key: str, + prompt: Prompt, + is_secure: bool = False, + default: any = None, + type_str: str = "str", + # Whether this config will be prompted during the setup process + required_if: RequiredIf = lambda: True, + validator: Validator = lambda *args: None, + on_validated: OnValidated = lambda *args: None, + # Whether to prompt a user for value when new strategy config file is created + prompt_on_new: bool = False, + # Whether this is a config var used in connect command + is_connect_key: bool = False, + printable_key: str = None): + self.prompt = prompt + self.key = key + self.value = None + self.is_secure = is_secure + self.default = default + self.type = type_str + self._required_if = required_if + self._validator = validator + self._on_validated = on_validated + self.prompt_on_new = prompt_on_new + self.is_connect_key = is_connect_key + self.printable_key = printable_key + + async def get_prompt(self): + """ + Call self.prompt if it is a function, otherwise return it as a value. + """ + if inspect.iscoroutinefunction(self.prompt): + return await self.prompt() + elif inspect.isfunction(self.prompt): + return self.prompt() + else: + return self.prompt + + @property + def required(self) -> bool: + assert callable(self._required_if) + return self._required_if() + + async def validate(self, value: str) -> Optional[str]: + """ + Validate user input against the function self._validator, if it is valid, then call self._on_validated, + if it is invalid, then return the error message. + """ + assert callable(self._validator) + assert callable(self._on_validated) + if self.required and (value is None or value == ""): + return "Value is required." + err_msg = None + if inspect.iscoroutinefunction(self._validator): + err_msg = await self._validator(value) + elif inspect.isfunction(self._validator): + err_msg = self._validator(value) + if err_msg is None and self._validator is not None: + if inspect.iscoroutinefunction(self._on_validated): + await self._on_validated(value) + elif inspect.isfunction(self._on_validated): + self._on_validated(value) + return err_msg diff --git a/hummingbot/client/config/fee_overrides_config_map.py b/hummingbot/client/config/fee_overrides_config_map.py new file mode 100644 index 0000000..dc00d51 --- /dev/null +++ b/hummingbot/client/config/fee_overrides_config_map.py @@ -0,0 +1,32 @@ +from typing import Dict + +from hummingbot.client.config.config_methods import new_fee_config_var +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings + +fee_overrides_config_map: Dict[str, ConfigVar] = {} + + +def fee_overrides_dict() -> Dict[str, ConfigVar]: + all_configs: Dict[str, ConfigVar] = {} + for name in AllConnectorSettings.get_connector_settings().keys(): + all_configs.update({ + f"{name}_percent_fee_token": new_fee_config_var(f"{name}_percent_fee_token", type_str="str"), + f"{name}_maker_percent_fee": new_fee_config_var(f"{name}_maker_percent_fee", type_str="decimal"), + f"{name}_taker_percent_fee": new_fee_config_var(f"{name}_taker_percent_fee", type_str="decimal"), + f"{name}_buy_percent_fee_deducted_from_returns": new_fee_config_var( + f"{name}_buy_percent_fee_deducted_from_returns", type_str="bool" + ), + f"{name}_maker_fixed_fees": new_fee_config_var(f"{name}_maker_fixed_fees", type_str="list"), + f"{name}_taker_fixed_fees": new_fee_config_var(f"{name}_taker_fixed_fees", type_str="list"), + }) + return all_configs + + +def init_fee_overrides_config(): + global fee_overrides_config_map + fee_overrides_config_map.clear() + fee_overrides_config_map.update(fee_overrides_dict()) + + +init_fee_overrides_config() diff --git a/hummingbot/client/config/gateway_ssl_config_map.py b/hummingbot/client/config/gateway_ssl_config_map.py new file mode 100644 index 0000000..8f17334 --- /dev/null +++ b/hummingbot/client/config/gateway_ssl_config_map.py @@ -0,0 +1,9 @@ +from pydantic import Field + +from hummingbot.client.config.config_data_types import BaseClientModel + + +class SSLConfigMap(BaseClientModel): + caCertificatePath: str = Field(default="/usr/src/app/certs/ca_cert.pem") + certificatePath: str = Field(default="/usr/src/app/certs/server_cert.pem") + keyPath: str = Field(default="/usr/src/app/certs/server_key.pem") diff --git a/hummingbot/client/config/global_config_map.py b/hummingbot/client/config/global_config_map.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/client/config/security.py b/hummingbot/client/config/security.py new file mode 100644 index 0000000..736bfaf --- /dev/null +++ b/hummingbot/client/config/security.py @@ -0,0 +1,102 @@ +import asyncio +from pathlib import Path +from typing import Dict, Optional + +from hummingbot.client.config.config_crypt import PASSWORD_VERIFICATION_PATH, BaseSecretsManager, validate_password +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + api_keys_from_connector_config_map, + connector_name_from_file, + get_connector_config_yml_path, + list_connector_configs, + load_connector_config_map_from_file, + reset_connector_hb_config, + save_to_yml, + update_connector_hb_config, +) +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler +from hummingbot.core.utils.async_utils import safe_ensure_future + + +class Security: + __instance = None + secrets_manager: Optional[BaseSecretsManager] = None + _secure_configs = {} + _decryption_done = asyncio.Event() + + @staticmethod + def new_password_required() -> bool: + return not PASSWORD_VERIFICATION_PATH.exists() + + @classmethod + def any_secure_configs(cls): + return len(cls._secure_configs) > 0 + + @staticmethod + def connector_config_file_exists(connector_name: str) -> bool: + connector_configs_path = get_connector_config_yml_path(connector_name) + return connector_configs_path.exists() + + @classmethod + def login(cls, secrets_manager: BaseSecretsManager) -> bool: + if not validate_password(secrets_manager): + return False + cls.secrets_manager = secrets_manager + coro = AsyncCallScheduler.shared_instance().call_async(cls.decrypt_all, timeout_seconds=30) + safe_ensure_future(coro) + return True + + @classmethod + def decrypt_all(cls): + cls._secure_configs.clear() + cls._decryption_done.clear() + encrypted_files = list_connector_configs() + for file in encrypted_files: + cls.decrypt_connector_config(file) + cls._decryption_done.set() + + @classmethod + def decrypt_connector_config(cls, file_path: Path): + connector_name = connector_name_from_file(file_path) + cls._secure_configs[connector_name] = load_connector_config_map_from_file(file_path) + + @classmethod + def update_secure_config(cls, connector_config: ClientConfigAdapter): + connector_name = connector_config.connector + file_path = get_connector_config_yml_path(connector_name) + save_to_yml(file_path, connector_config) + update_connector_hb_config(connector_config) + cls._secure_configs[connector_name] = connector_config + + @classmethod + def remove_secure_config(cls, connector_name: str): + file_path = get_connector_config_yml_path(connector_name) + file_path.unlink(missing_ok=True) + reset_connector_hb_config(connector_name) + cls._secure_configs.pop(connector_name) + + @classmethod + def is_decryption_done(cls): + return cls._decryption_done.is_set() + + @classmethod + def decrypted_value(cls, key: str) -> Optional[ClientConfigAdapter]: + return cls._secure_configs.get(key, None) + + @classmethod + def all_decrypted_values(cls) -> Dict[str, ClientConfigAdapter]: + return cls._secure_configs.copy() + + @classmethod + async def wait_til_decryption_done(cls): + await cls._decryption_done.wait() + + @classmethod + def api_keys(cls, connector_name: str) -> Dict[str, Optional[str]]: + connector_config = cls.decrypted_value(connector_name) + keys = ( + api_keys_from_connector_config_map(connector_config) + if connector_config is not None + else {} + ) + return keys diff --git a/hummingbot/client/config/strategy_config_data_types.py b/hummingbot/client/config/strategy_config_data_types.py new file mode 100644 index 0000000..76505b9 --- /dev/null +++ b/hummingbot/client/config/strategy_config_data_types.py @@ -0,0 +1,163 @@ +from typing import Dict + +from pydantic import Field, validator + +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_validators import ( + validate_exchange, + validate_market_trading_pair, + validate_strategy, +) +from hummingbot.client.settings import AllConnectorSettings + + +class BaseStrategyConfigMap(BaseClientModel): + strategy: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda mi: "What is your market making strategy?", + prompt_on_new=True, + ), + ) + + @validator("strategy", pre=True) + def validate_strategy(cls, v: str): + ret = validate_strategy(v) + if ret is not None: + raise ValueError(ret) + return v + + +class BaseTradingStrategyConfigMap(BaseStrategyConfigMap): + exchange: ClientConfigEnum( # rebuild the exchanges enum + value="Exchanges", # noqa: F821 + names={e: e for e in sorted(AllConnectorSettings.get_exchange_names())}, + type=str, + ) = Field( + default=..., + description="The name of the exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Input your maker spot connector", + prompt_on_new=True, + ), + ) + market: str = Field( + default=..., + description="The trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: BaseTradingStrategyConfigMap.trading_pair_prompt(mi), + prompt_on_new=True, + ), + ) + + @classmethod + def trading_pair_prompt(cls, model_instance: 'BaseTradingStrategyConfigMap') -> str: + exchange = model_instance.exchange + example = AllConnectorSettings.get_example_pairs().get(exchange) + return ( + f"Enter the token trading pair you would like to trade on" + f" {exchange}{f' (e.g. {example})' if example else ''}" + ) + + @validator("exchange", pre=True) + def validate_exchange(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_exchange(v) + if ret is not None: + raise ValueError(ret) + return v + + @validator("market", pre=True) + def validate_exchange_trading_pair(cls, v: str, values: Dict): + exchange = values.get("exchange") + ret = validate_market_trading_pair(exchange, v) + if ret is not None: + raise ValueError(ret) + return v + + +class BaseTradingStrategyMakerTakerConfigMap(BaseStrategyConfigMap): + maker_market: ClientConfigEnum( + value="MakerMarkets", # noqa: F821 + names={e: e for e in sorted(AllConnectorSettings.get_exchange_names())}, + type=str, + ) = Field( + default=..., + description="The name of the maker exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter your maker spot connector", + prompt_on_new=True, + ), + ) + taker_market: ClientConfigEnum( + value="TakerMarkets", # noqa: F821 + names={e: e for e in sorted(AllConnectorSettings.get_exchange_names())}, + type=str, + ) = Field( + default=..., + description="The name of the taker exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter your taker spot connector", + prompt_on_new=True, + ), + ) + maker_market_trading_pair: str = Field( + default=..., + description="The name of the maker trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: BaseTradingStrategyMakerTakerConfigMap.trading_pair_prompt(mi, True), + prompt_on_new=True, + ), + ) + taker_market_trading_pair: str = Field( + default=..., + description="The name of the taker trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: BaseTradingStrategyMakerTakerConfigMap.trading_pair_prompt(mi, False), + prompt_on_new=True, + ), + ) + + @classmethod + def trading_pair_prompt(cls, model_instance: 'BaseTradingStrategyMakerTakerConfigMap', is_maker: bool) -> str: + if is_maker: + exchange = model_instance.maker_market + example = AllConnectorSettings.get_example_pairs().get(exchange) + market_type = "maker" + else: + exchange = model_instance.taker_market + example = AllConnectorSettings.get_example_pairs().get(exchange) + market_type = "taker" + return ( + f"Enter the token trading pair you would like to trade on {market_type} market:" + f" {exchange}{f' (e.g. {example})' if example else ''}" + ) + + @validator( + "maker_market", + "taker_market", + pre=True + ) + def validate_exchange(cls, v: str, field: Field): + """Used for client-friendly error output.""" + ret = validate_exchange(v) + if ret is not None: + raise ValueError(ret) + return v + + @validator( + "maker_market_trading_pair", + "taker_market_trading_pair", + pre=True, + ) + def validate_exchange_trading_pair(cls, v: str, values: Dict, field: Field): + ret = None + if field.name == "maker_market_trading_pair": + exchange = values.get("maker_market") + ret = validate_market_trading_pair(exchange, v) + if field.name == "taker_market_trading_pair": + exchange = values.get("taker_market") + ret = validate_market_trading_pair(exchange, v) + if ret is not None: + raise ValueError(ret) + return v diff --git a/hummingbot/client/config/trade_fee_schema_loader.py b/hummingbot/client/config/trade_fee_schema_loader.py new file mode 100644 index 0000000..36c3aec --- /dev/null +++ b/hummingbot/client/config/trade_fee_schema_loader.py @@ -0,0 +1,60 @@ +from decimal import Decimal + +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.core.data_type.trade_fee import TradeFeeSchema, TokenAmount + + +class TradeFeeSchemaLoader: + """ + Utility class that contains the requried logic to load fee schemas applying any override the user + might have configured. + """ + + @classmethod + def configured_schema_for_exchange(cls, exchange_name: str) -> TradeFeeSchema: + if exchange_name not in AllConnectorSettings.get_connector_settings(): + raise Exception(f"Invalid connector. {exchange_name} does not exist in AllConnectorSettings") + trade_fee_schema = AllConnectorSettings.get_connector_settings()[exchange_name].trade_fee_schema + trade_fee_schema = cls._superimpose_overrides(exchange_name, trade_fee_schema) + return trade_fee_schema + + @classmethod + def _superimpose_overrides(cls, exchange: str, trade_fee_schema: TradeFeeSchema): + trade_fee_schema.percent_fee_token = ( + fee_overrides_config_map.get(f"{exchange}_percent_fee_token").value + or trade_fee_schema.percent_fee_token + ) + trade_fee_schema.maker_percent_fee_decimal = ( + fee_overrides_config_map.get(f"{exchange}_maker_percent_fee").value / Decimal("100") + if fee_overrides_config_map.get(f"{exchange}_maker_percent_fee").value is not None + else trade_fee_schema.maker_percent_fee_decimal + ) + trade_fee_schema.taker_percent_fee_decimal = ( + fee_overrides_config_map.get(f"{exchange}_taker_percent_fee").value / Decimal("100") + if fee_overrides_config_map.get(f"{exchange}_taker_percent_fee").value is not None + else trade_fee_schema.taker_percent_fee_decimal + ) + trade_fee_schema.buy_percent_fee_deducted_from_returns = ( + fee_overrides_config_map.get(f"{exchange}_buy_percent_fee_deducted_from_returns").value + if fee_overrides_config_map.get(f"{exchange}_buy_percent_fee_deducted_from_returns").value is not None + else trade_fee_schema.buy_percent_fee_deducted_from_returns + ) + trade_fee_schema.maker_fixed_fees = ( + fee_overrides_config_map.get(f"{exchange}_maker_fixed_fees").value + or trade_fee_schema.maker_fixed_fees + ) + trade_fee_schema.maker_fixed_fees = [ + TokenAmount(*maker_fixed_fee) + for maker_fixed_fee in trade_fee_schema.maker_fixed_fees + ] + trade_fee_schema.taker_fixed_fees = ( + fee_overrides_config_map.get(f"{exchange}_taker_fixed_fees").value + or trade_fee_schema.taker_fixed_fees + ) + trade_fee_schema.taker_fixed_fees = [ + TokenAmount(*taker_fixed_fee) + for taker_fixed_fee in trade_fee_schema.taker_fixed_fees + ] + trade_fee_schema.validate_schema() + return trade_fee_schema diff --git a/hummingbot/client/data_type/__init__.py b/hummingbot/client/data_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/client/data_type/currency_amount.py b/hummingbot/client/data_type/currency_amount.py new file mode 100644 index 0000000..4006fe2 --- /dev/null +++ b/hummingbot/client/data_type/currency_amount.py @@ -0,0 +1,22 @@ + +class CurrencyAmount: + + def __init__(self): + self._token: str = None + self._amount: float = None + + @property + def token(self) -> str: + return self._token + + @token.setter + def token(self, token: str): + self._token = token + + @property + def amount(self) -> float: + return self._amount + + @amount.setter + def amount(self, amount: float): + self._amount = amount diff --git a/hummingbot/client/hummingbot_application.py b/hummingbot/client/hummingbot_application.py new file mode 100644 index 0000000..bf0447a --- /dev/null +++ b/hummingbot/client/hummingbot_application.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python + +import asyncio +import logging +import time +from collections import deque +from typing import Deque, Dict, List, Optional, Tuple, Union + +from hummingbot.client.command import __all__ as commands +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + ReadOnlyClientConfigAdapter, + get_connector_class, + get_strategy_config_map, + load_client_config_map_from_file, + load_ssl_config_map_from_file, + save_to_yml, +) +from hummingbot.client.config.gateway_ssl_config_map import SSLConfigMap +from hummingbot.client.config.security import Security +from hummingbot.client.config.strategy_config_data_types import BaseStrategyConfigMap +from hummingbot.client.settings import CLIENT_CONFIG_PATH, AllConnectorSettings, ConnectorType +from hummingbot.client.tab import __all__ as tab_classes +from hummingbot.client.tab.data_types import CommandTab +from hummingbot.client.ui.completer import load_completer +from hummingbot.client.ui.hummingbot_cli import HummingbotCLI +from hummingbot.client.ui.keybindings import load_key_bindings +from hummingbot.client.ui.parser import ThrowingArgumentParser, load_parser +from hummingbot.connector.exchange.paper_trade import create_paper_trade_market +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import Clock +from hummingbot.core.gateway.gateway_status_monitor import GatewayStatusMonitor +from hummingbot.core.utils.kill_switch import KillSwitch +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher +from hummingbot.data_feed.data_feed_base import DataFeedBase +from hummingbot.exceptions import ArgumentParserError +from hummingbot.logger import HummingbotLogger +from hummingbot.logger.application_warning import ApplicationWarning +from hummingbot.model.sql_connection_manager import SQLConnectionManager +from hummingbot.notifier.notifier_base import NotifierBase +from hummingbot.remote_iface.mqtt import MQTTGateway +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_base import StrategyBase + +s_logger = None + + +class HummingbotApplication(*commands): + KILL_TIMEOUT = 10.0 + APP_WARNING_EXPIRY_DURATION = 3600.0 + APP_WARNING_STATUS_LIMIT = 6 + + _main_app: Optional["HummingbotApplication"] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + @classmethod + def main_application(cls, client_config_map: Optional[ClientConfigAdapter] = None) -> "HummingbotApplication": + if cls._main_app is None: + cls._main_app = HummingbotApplication(client_config_map) + return cls._main_app + + def __init__(self, client_config_map: Optional[ClientConfigAdapter] = None): + self.client_config_map: Union[ClientConfigMap, ClientConfigAdapter] = ( # type-hint enables IDE auto-complete + client_config_map or load_client_config_map_from_file() + ) + self.ssl_config_map: SSLConfigMap = ( # type-hint enables IDE auto-complete + load_ssl_config_map_from_file() + ) + # This is to start fetching trading pairs for auto-complete + TradingPairFetcher.get_instance(self.client_config_map) + self.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.markets: Dict[str, ExchangeBase] = {} + # strategy file name and name get assigned value after import or create command + self._strategy_file_name: Optional[str] = None + self.strategy_name: Optional[str] = None + self._strategy_config_map: Optional[BaseStrategyConfigMap] = None + self.strategy_task: Optional[asyncio.Task] = None + self.strategy: Optional[StrategyBase] = None + self.market_pair: Optional[MakerTakerMarketPair] = None + self.market_trading_pair_tuples: List[MarketTradingPairTuple] = [] + self.clock: Optional[Clock] = None + self.market_trading_pairs_map = {} + self.token_list = {} + + self.init_time: float = time.time() + self.start_time: Optional[int] = None + self.placeholder_mode = False + self.log_queue_listener: Optional[logging.handlers.QueueListener] = None + self.data_feed: Optional[DataFeedBase] = None + self.notifiers: List[NotifierBase] = [] + self.kill_switch: Optional[KillSwitch] = None + self._app_warnings: Deque[ApplicationWarning] = deque() + self._trading_required: bool = True + self._last_started_strategy_file: Optional[str] = None + + self.trade_fill_db: Optional[SQLConnectionManager] = None + self.markets_recorder: Optional[MarketsRecorder] = None + self._pmm_script_iterator = None + self._binance_connector = None + self._shared_client = None + self._mqtt: MQTTGateway = None + + # gateway variables and monitor + self._gateway_monitor = GatewayStatusMonitor(self) + + command_tabs = self.init_command_tabs() + self.parser: ThrowingArgumentParser = load_parser(self, command_tabs) + self.app = HummingbotCLI( + self.client_config_map, + input_handler=self._handle_command, + bindings=load_key_bindings(self), + completer=load_completer(self), + command_tabs=command_tabs + ) + + self._init_gateway_monitor() + # MQTT Bridge + if self.client_config_map.mqtt_bridge.mqtt_autostart: + self.mqtt_start() + + @property + def instance_id(self) -> str: + return self.client_config_map.instance_id + + @property + def fetch_pairs_from_all_exchanges(self) -> bool: + return self.client_config_map.fetch_pairs_from_all_exchanges + + @property + def gateway_config_keys(self) -> List[str]: + return self._gateway_monitor.gateway_config_keys + + @property + def strategy_file_name(self) -> str: + return self._strategy_file_name + + @strategy_file_name.setter + def strategy_file_name(self, value: Optional[str]): + self._strategy_file_name = value + if value is not None: + db_name = value.split(".")[0] + self.trade_fill_db = SQLConnectionManager.get_trade_fills_instance( + self.client_config_map, db_name + ) + else: + self.trade_fill_db = None + + @property + def strategy_config_map(self): + if self._strategy_config_map is not None: + return self._strategy_config_map + if self.strategy_name is not None: + return get_strategy_config_map(self.strategy_name) + return None + + @strategy_config_map.setter + def strategy_config_map(self, config_map: BaseStrategyConfigMap): + self._strategy_config_map = config_map + + def _init_gateway_monitor(self): + try: + # Do not start the gateway monitor during unit tests. + if asyncio.get_running_loop() is not None: + self._gateway_monitor = GatewayStatusMonitor(self) + self._gateway_monitor.start() + except RuntimeError: + pass + + def notify(self, msg: str): + self.app.log(msg) + for notifier in self.notifiers: + notifier.add_msg_to_queue(msg) + + def _handle_shortcut(self, command_split): + shortcuts = self.client_config_map.command_shortcuts + shortcut = None + # see if we match against shortcut command + if shortcuts is not None: + for each_shortcut in shortcuts: + if command_split[0] == each_shortcut.command: + shortcut = each_shortcut + break + + # perform shortcut expansion + if shortcut is not None: + # check number of arguments + num_shortcut_args = len(shortcut.arguments) + if len(command_split) == num_shortcut_args + 1: + # notify each expansion if there's more than 1 + verbose = True if len(shortcut.output) > 1 else False + # do argument replace and re-enter this function with the expanded command + for output_cmd in shortcut.output: + final_cmd = output_cmd + for i in range(1, num_shortcut_args + 1): + final_cmd = final_cmd.replace(f'${i}', command_split[i]) + if verbose is True: + self.notify(f' >>> {final_cmd}') + self._handle_command(final_cmd) + else: + self.notify('Invalid number of arguments for shortcut') + return True + return False + + def _handle_command(self, raw_command: str): + # unset to_stop_config flag it triggered before loading any command + if self.app.to_stop_config: + self.app.to_stop_config = False + + raw_command = raw_command.strip() + # NOTE: Only done for config command + if raw_command.startswith("config"): + command_split = raw_command.split(maxsplit=2) + else: + command_split = raw_command.split() + try: + if self.placeholder_mode: + pass + elif len(command_split) == 0: + pass + else: + # Check if help is requested, if yes, print & terminate + if len(command_split) > 1 and any(arg in ["-h", "--help"] for arg in command_split[1:]): + self.help(raw_command) + return + + if not self._handle_shortcut(command_split): + # regular command + args = self.parser.parse_args(args=command_split) + kwargs = vars(args) + if not hasattr(args, "func"): + self.app.handle_tab_command(self, command_split[0], kwargs) + else: + f = args.func + del kwargs["func"] + f(**kwargs) + except ArgumentParserError as e: + if not self.be_silly(raw_command): + self.notify(str(e)) + except NotImplementedError: + self.notify("Command not yet implemented. This feature is currently under development.") + except Exception as e: + self.logger().error(e, exc_info=True) + + async def _cancel_outstanding_orders(self) -> bool: + success = True + try: + kill_timeout: float = self.KILL_TIMEOUT + self.notify("Canceling outstanding orders...") + + for market_name, market in self.markets.items(): + cancellation_results = await market.cancel_all(kill_timeout) + uncancelled = list(filter(lambda cr: cr.success is False, cancellation_results)) + if len(uncancelled) > 0: + success = False + uncancelled_order_ids = list(map(lambda cr: cr.order_id, uncancelled)) + self.notify("\nFailed to cancel the following orders on %s:\n%s" % ( + market_name, + '\n'.join(uncancelled_order_ids) + )) + except Exception: + self.logger().error("Error canceling outstanding orders.", exc_info=True) + success = False + + if success: + self.notify("All outstanding orders canceled.") + return success + + async def run(self): + await self.app.run() + + def add_application_warning(self, app_warning: ApplicationWarning): + self._expire_old_application_warnings() + self._app_warnings.append(app_warning) + + def clear_application_warning(self): + self._app_warnings.clear() + + @staticmethod + def _initialize_market_assets(market_name: str, trading_pairs: List[str]) -> List[Tuple[str, str]]: + market_trading_pairs: List[Tuple[str, str]] = [(trading_pair.split('-')) for trading_pair in trading_pairs] + return market_trading_pairs + + def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]): + # aggregate trading_pairs if there are duplicate markets + + for market_name, trading_pairs in market_names: + if market_name not in self.market_trading_pairs_map: + self.market_trading_pairs_map[market_name] = [] + for hb_trading_pair in trading_pairs: + self.market_trading_pairs_map[market_name].append(hb_trading_pair) + + for connector_name, trading_pairs in self.market_trading_pairs_map.items(): + conn_setting = AllConnectorSettings.get_connector_settings()[connector_name] + + if connector_name.endswith("paper_trade") and conn_setting.type == ConnectorType.Exchange: + connector = create_paper_trade_market(conn_setting.parent_name, self.client_config_map, trading_pairs) + paper_trade_account_balance = self.client_config_map.paper_trade.paper_trade_account_balance + if paper_trade_account_balance is not None: + for asset, balance in paper_trade_account_balance.items(): + connector.set_balance(asset, balance) + else: + keys = Security.api_keys(connector_name) + read_only_config = ReadOnlyClientConfigAdapter.lock_config(self.client_config_map) + init_params = conn_setting.conn_init_parameters( + trading_pairs=trading_pairs, + trading_required=self._trading_required, + api_keys=keys, + client_config_map=read_only_config, + ) + connector_class = get_connector_class(connector_name) + connector = connector_class(**init_params) + self.markets[connector_name] = connector + + self.markets_recorder = MarketsRecorder( + self.trade_fill_db, + list(self.markets.values()), + self.strategy_file_name, + self.strategy_name, + self.client_config_map.market_data_collection, + ) + self.markets_recorder.start() + if self._mqtt is not None: + self._mqtt.start_market_events_fw() + + def _initialize_notifiers(self): + self.notifiers.extend( + [ + notifier for notifier in self.client_config_map.telegram_mode.get_notifiers(self) + if notifier not in self.notifiers + ] + ) + for notifier in self.notifiers: + notifier.start() + + def init_command_tabs(self) -> Dict[str, CommandTab]: + """ + Initiates and returns a CommandTab dictionary with mostly defaults and None values, These values will be + populated later on by HummingbotCLI + """ + command_tabs: Dict[str, CommandTab] = {} + for tab_class in tab_classes: + name = tab_class.get_command_name() + command_tabs[name] = CommandTab(name, None, None, None, tab_class) + return command_tabs + + def save_client_config(self): + save_to_yml(CLIENT_CONFIG_PATH, self.client_config_map) diff --git a/hummingbot/client/performance.py b/hummingbot/client/performance.py new file mode 100644 index 0000000..5870e47 --- /dev/null +++ b/hummingbot/client/performance.py @@ -0,0 +1,326 @@ +import logging +from collections import defaultdict +from dataclasses import dataclass +from decimal import Decimal +from typing import Any, Dict, List, Optional, Tuple + +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.data_type.common import PositionAction, TradeType +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.logger import HummingbotLogger +from hummingbot.model.trade_fill import TradeFill + +s_decimal_0 = Decimal("0") +s_decimal_nan = Decimal("NaN") + + +@dataclass +class PerformanceMetrics: + _logger = None + + num_buys: int = 0 + num_sells: int = 0 + num_trades: int = 0 + + b_vol_base: Decimal = s_decimal_0 + s_vol_base: Decimal = s_decimal_0 + tot_vol_base: Decimal = s_decimal_0 + + b_vol_quote: Decimal = s_decimal_0 + s_vol_quote: Decimal = s_decimal_0 + tot_vol_quote: Decimal = s_decimal_0 + + avg_b_price: Decimal = s_decimal_0 + avg_s_price: Decimal = s_decimal_0 + avg_tot_price: Decimal = s_decimal_0 + + start_base_bal: Decimal = s_decimal_0 + start_quote_bal: Decimal = s_decimal_0 + cur_base_bal: Decimal = s_decimal_0 + cur_quote_bal: Decimal = s_decimal_0 + start_price: Decimal = s_decimal_0 + cur_price: Decimal = s_decimal_0 + start_base_ratio_pct: Decimal = s_decimal_0 + cur_base_ratio_pct: Decimal = s_decimal_0 + + hold_value: Decimal = s_decimal_0 + cur_value: Decimal = s_decimal_0 + trade_pnl: Decimal = s_decimal_0 + + fee_in_quote: Decimal = s_decimal_0 + total_pnl: Decimal = s_decimal_0 + return_pct: Decimal = s_decimal_0 + + def __init__(self): + # fees is a dictionary of token and total fee amount paid in that token. + self.fees: Dict[str, Decimal] = defaultdict(lambda: s_decimal_0) + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @classmethod + async def create(cls, + trading_pair: str, + trades: List[Any], + current_balances: Dict[str, Decimal]) -> 'PerformanceMetrics': + performance = PerformanceMetrics() + await performance._initialize_metrics(trading_pair, trades, current_balances) + return performance + + @staticmethod + def position_order(open: list, close: list) -> Tuple[Any, Any]: + """ + Pair open position order with close position orders + :param open: a list of orders that may have an open position order + :param close: a list of orders that may have an close position order + :return: A tuple containing a pair of an open order with a close position order + """ + result = None + + try: + first_with_open_position = next((order for order in open if order.position == "OPEN")) + first_with_close_position = next((order for order in close if order.position == "CLOSE")) + open.remove(first_with_open_position) + close.remove(first_with_close_position) + result = (first_with_open_position, first_with_close_position) + except StopIteration: + pass + + return result + + @staticmethod + def aggregate_orders(orders: list) -> list: + grouped_orders = {} + for order in orders: + group = grouped_orders.get(order.order_id, []) + group.append(order) + grouped_orders[order.order_id] = group + + aggregated_orders = [] + for group in grouped_orders.values(): + aggregated_prices = 0 + aggregated_amounts = 0 + for order in group: + aggregated_prices += order.price + aggregated_amounts += order.amount + aggregated = group[0] + aggregated.price = aggregated_prices / len(group) + aggregated.amount = aggregated_amounts + aggregated_orders.append(aggregated) + + return aggregated_orders + + @staticmethod + def aggregate_position_order(buys: list, sells: list) -> Tuple[list, list]: + """ + Aggregate the amount field for orders with multiple fills + :param buys: a list of buy orders + :param sells: a list of sell orders + :return: 2 lists containing aggregated amounts for buy and sell orders. + """ + aggregated_buys = PerformanceMetrics.aggregate_orders(buys) + aggregated_sells = PerformanceMetrics.aggregate_orders(sells) + + return aggregated_buys, aggregated_sells + + @staticmethod + def derivative_pnl(long: list, short: list) -> List[Decimal]: + # It is assumed that the amount and leverage for both open and close orders are the same. + """ + Calculates PnL for a close position + :param long: a list containing pairs of open and closed long position orders + :param short: a list containing pairs of open and closed short position orders + :return: A list containing PnL for each closed positions + """ + pnls = [] + for lg in long: + pnls.append((lg[1].price - lg[0].price) * lg[1].amount) + for st in short: + pnls.append((st[0].price - st[1].price) * st[1].amount) + return pnls + + @staticmethod + def smart_round(value: Decimal, precision: Optional[int] = None) -> Decimal: + if value is None or value.is_nan(): + return value + if precision is not None: + precision = 1 / (10 ** precision) + return Decimal(str(value)).quantize(Decimal(str(precision))) + step = Decimal("1") + if Decimal("10000") > abs(value) > Decimal("100"): + step = Decimal("0.1") + elif Decimal("100") > abs(value) > Decimal("1"): + step = Decimal("0.01") + elif Decimal("1") > abs(value) > Decimal("0.01"): + step = Decimal("0.0001") + elif Decimal("0.01") > abs(value) > Decimal("0.0001"): + step = Decimal("0.00001") + elif Decimal("0.0001") > abs(value) > s_decimal_0: + step = Decimal("0.00000001") + return (value // step) * step + + @staticmethod + def divide(value, divisor): + value = Decimal(str(value)) + divisor = Decimal(str(divisor)) + if divisor == s_decimal_0: + return s_decimal_0 + return value / divisor + + def _is_trade_fill(self, trade): + return type(trade) == TradeFill + + def _are_derivatives(self, trades: List[Any]) -> bool: + return ( + trades + and self._is_trade_fill(trades[0]) + and PositionAction.NIL.value not in [t.position for t in trades] + ) + + def _preprocess_trades_and_group_by_type(self, trades: List[Any]) -> Tuple[List[Any], List[Any]]: + buys = [] + sells = [] + for trade in trades: + if trade.trade_type.upper() == TradeType.BUY.name.upper(): + buys.append(trade) + self.b_vol_base += Decimal(str(trade.amount)) + self.b_vol_quote += Decimal(str(trade.amount)) * Decimal(str(trade.price)) * Decimal("-1") + elif trade.trade_type.upper() == TradeType.SELL.name.upper(): + sells.append(trade) + self.s_vol_base += Decimal(str(trade.amount)) * Decimal("-1") + self.s_vol_quote += Decimal(str(trade.amount)) * Decimal(str(trade.price)) + + self.s_vol_quote += self._process_deducted_fees_impact_in_quote_vol(trade) + + self.tot_vol_base = self.b_vol_base + self.s_vol_base + self.tot_vol_quote = self.b_vol_quote + self.s_vol_quote + + self.avg_b_price = self.divide(self.b_vol_quote, self.b_vol_base) + self.avg_s_price = self.divide(self.s_vol_quote, self.s_vol_base) + self.avg_tot_price = self.divide(abs(self.b_vol_quote) + abs(self.s_vol_quote), + abs(self.b_vol_base) + abs(self.s_vol_base)) + self.avg_b_price = abs(self.avg_b_price) + self.avg_s_price = abs(self.avg_s_price) + + return buys, sells + + def _process_deducted_fees_impact_in_quote_vol(self, trade): + fee_percent = None + fee_type = "" + impact = s_decimal_0 + if self._is_trade_fill(trade): + if trade.trade_fee.get("percent") is not None: + fee_percent = Decimal(trade.trade_fee.get("percent")) + fee_type = trade.trade_fee.get("fee_type") + else: # assume this is Trade object + if trade.trade_fee.percent is not None: + fee_percent = Decimal(trade.trade_fee.percent) + fee_type = trade.trade_fee.type_descriptor_for_json() + if (fee_percent is not None) and (fee_type == DeductedFromReturnsTradeFee.type_descriptor_for_json()): + impact = Decimal(str(trade.amount)) * Decimal(str(trade.price)) * fee_percent * Decimal("-1") + return impact + + async def _calculate_fees(self, quote: str, trades: List[Any]): + for trade in trades: + fee_percent = None + trade_price = None + trade_amount = None + if self._is_trade_fill(trade): + if trade.trade_fee.get("percent") is not None: + trade_price = Decimal(str(trade.price)) + trade_amount = Decimal(str(trade.amount)) + fee_percent = Decimal(str(trade.trade_fee["percent"])) + flat_fees = [TokenAmount(token=flat_fee["token"], amount=Decimal(flat_fee["amount"])) + for flat_fee in trade.trade_fee.get("flat_fees", [])] + else: # assume this is Trade object + if trade.trade_fee.percent is not None: + trade_price = Decimal(trade.price) + trade_amount = Decimal(trade.amount) + fee_percent = Decimal(trade.trade_fee.percent) + flat_fees = trade.trade_fee.flat_fees + + if fee_percent is not None: + self.fees[quote] += trade_price * trade_amount * fee_percent + for flat_fee in flat_fees: + self.fees[flat_fee.token] += flat_fee.amount + + for fee_token, fee_amount in self.fees.items(): + if fee_token == quote: + self.fee_in_quote += fee_amount + else: + rate_pair: str = combine_to_hb_trading_pair(fee_token, quote) + last_price = await RateOracle.get_instance().stored_or_live_rate(rate_pair) + if last_price is not None: + self.fee_in_quote += fee_amount * last_price + else: + self.logger().warning( + f"Could not find exchange rate for {rate_pair} " + f"using {RateOracle.get_instance()}. PNL value will be inconsistent." + ) + + def _calculate_trade_pnl(self, buys: list, sells: list): + self.trade_pnl = self.cur_value - self.hold_value + + # Handle trade_pnl differently for derivatives + if self._are_derivatives(buys) or self._are_derivatives(sells): + buys_copy, sells_copy = self.aggregate_position_order(buys.copy(), sells.copy()) + long = [] + short = [] + + while True: + lng = self.position_order(buys_copy, sells_copy) + if lng is not None: + long.append(lng) + + sht = self.position_order(sells_copy, buys_copy) + if sht is not None: + short.append(sht) + if lng is None and sht is None: + break + + self.trade_pnl = Decimal(str(sum(self.derivative_pnl(long, short)))) + + async def _initialize_metrics(self, + trading_pair: str, + trades: List[Any], + current_balances: Dict[str, Decimal]): + """ + Calculates PnL, fees, Return % and etc... + :param trading_pair: the trading market to get performance metrics + :param trades: the list of TradeFill or Trade object + :param current_balances: current user account balance + """ + + base, quote = split_hb_trading_pair(trading_pair) + buys, sells = self._preprocess_trades_and_group_by_type(trades) + + self.num_buys = len(buys) + self.num_sells = len(sells) + self.num_trades = self.num_buys + self.num_sells + + self.cur_base_bal = current_balances.get(base, s_decimal_0) + self.cur_quote_bal = current_balances.get(quote, s_decimal_0) + self.start_base_bal = self.cur_base_bal - self.tot_vol_base + self.start_quote_bal = self.cur_quote_bal - self.tot_vol_quote + + self.start_price = Decimal(str(trades[0].price)) + self.cur_price = await RateOracle.get_instance().stored_or_live_rate(trading_pair) + if self.cur_price is None: + self.cur_price = Decimal(str(trades[-1].price)) + self.start_base_ratio_pct = self.divide(self.start_base_bal * self.start_price, + (self.start_base_bal * self.start_price) + self.start_quote_bal) + self.cur_base_ratio_pct = self.divide(self.cur_base_bal * self.cur_price, + (self.cur_base_bal * self.cur_price) + self.cur_quote_bal) + + self.hold_value = (self.start_base_bal * self.cur_price) + self.start_quote_bal + self.cur_value = (self.cur_base_bal * self.cur_price) + self.cur_quote_bal + self._calculate_trade_pnl(buys, sells) + + await self._calculate_fees(quote, trades) + + self.total_pnl = self.trade_pnl - self.fee_in_quote + self.return_pct = self.divide(self.total_pnl, self.hold_value) diff --git a/hummingbot/client/platform.py b/hummingbot/client/platform.py new file mode 100644 index 0000000..80dd039 --- /dev/null +++ b/hummingbot/client/platform.py @@ -0,0 +1,21 @@ +import os +import platform +from pathlib import Path + + +def get_system(): + return platform.system() + + +def get_installation_type(): + if os.environ.get("INSTALLATION_TYPE", "").lower() == "docker": + return "docker" + package_dir = Path(__file__).resolve().parent.parent.parent + bin_dir = [f for f in os.scandir(str(package_dir)) if f.name == "bin" and f.is_dir()] + if not bin_dir: + return "binary" + return "source" + + +client_system = get_system() +installation_type = get_installation_type() diff --git a/hummingbot/client/settings.py b/hummingbot/client/settings.py new file mode 100644 index 0000000..c4d7c5d --- /dev/null +++ b/hummingbot/client/settings.py @@ -0,0 +1,615 @@ +import importlib +import json +from decimal import Decimal +from enum import Enum +from os import DirEntry, scandir +from os.path import exists, join, realpath +from types import ModuleType +from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Set, Union, cast + +from pydantic import SecretStr + +from hummingbot import get_strategy_list, root_path +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.utils.gateway_config_utils import SUPPORTED_CHAINS + +if TYPE_CHECKING: + from hummingbot.client.config.config_data_types import BaseConnectorConfigMap + from hummingbot.client.config.config_helpers import ClientConfigAdapter + from hummingbot.connector.connector_base import ConnectorBase + from hummingbot.connector.gateway.clob_spot.data_sources.clob_api_data_source_base import CLOBAPIDataSourceBase + + +# Global variables +required_exchanges: Set[str] = set() +requried_connector_trading_pairs: Dict[str, List[str]] = {} +# Set these two variables if a strategy uses oracle for rate conversion +required_rate_oracle: bool = False +rate_oracle_pairs: List[str] = [] + +# Global static values +KEYFILE_PREFIX = "key_file_" +KEYFILE_POSTFIX = ".yml" +ENCYPTED_CONF_POSTFIX = ".json" +DEFAULT_LOG_FILE_PATH = root_path() / "logs" +DEFAULT_ETHEREUM_RPC_URL = "https://mainnet.coinalpha.com/hummingbot-test-node" +TEMPLATE_PATH = root_path() / "hummingbot" / "templates" +CONF_DIR_PATH = root_path() / "conf" +CLIENT_CONFIG_PATH = CONF_DIR_PATH / "conf_client.yml" +TRADE_FEES_CONFIG_PATH = CONF_DIR_PATH / "conf_fee_overrides.yml" +STRATEGIES_CONF_DIR_PATH = CONF_DIR_PATH / "strategies" +CONNECTORS_CONF_DIR_PATH = CONF_DIR_PATH / "connectors" +CONF_PREFIX = "conf_" +CONF_POSTFIX = "_strategy" +PMM_SCRIPTS_PATH = root_path() / "pmm_scripts" +SCRIPT_STRATEGIES_MODULE = "scripts" +SCRIPT_STRATEGIES_PATH = root_path() / SCRIPT_STRATEGIES_MODULE +DEFAULT_GATEWAY_CERTS_PATH = root_path() / "certs" + +GATEWAY_SSL_CONF_FILE = root_path() / "gateway" / "conf" / "ssl.yml" + +# Certificates for securely communicating with the gateway api +GATEAWAY_CA_CERT_PATH = DEFAULT_GATEWAY_CERTS_PATH / "ca_cert.pem" +GATEAWAY_CLIENT_CERT_PATH = DEFAULT_GATEWAY_CERTS_PATH / "client_cert.pem" +GATEAWAY_CLIENT_KEY_PATH = DEFAULT_GATEWAY_CERTS_PATH / "client_key.pem" + +PAPER_TRADE_EXCHANGES = [ # todo: fix after global config map refactor + "binance_paper_trade", + "kucoin_paper_trade", + "ascend_ex_paper_trade", + "gate_io_paper_trade", + "mock_paper_exchange", +] + +CONNECTOR_SUBMODULES_THAT_ARE_NOT_CEX_TYPES = ["test_support", "utilities", "gateway"] + + +class ConnectorType(Enum): + """ + The types of exchanges that hummingbot client can communicate with. + """ + + AMM = "AMM" + AMM_LP = "AMM_LP" + AMM_Perpetual = "AMM_Perpetual" + CLOB_SPOT = "CLOB_SPOT" + CLOB_PERP = "CLOB_PERP" + Connector = "connector" + Exchange = "exchange" + Derivative = "derivative" + + +class GatewayConnectionSetting: + @staticmethod + def conf_path() -> str: + return realpath(join(CONF_DIR_PATH, "gateway_connections.json")) + + @staticmethod + def load() -> List[Dict[str, str]]: + connections_conf_path: str = GatewayConnectionSetting.conf_path() + if exists(connections_conf_path): + with open(connections_conf_path) as fd: + return json.load(fd) + return [] + + @staticmethod + def save(settings: List[Dict[str, str]]): + connections_conf_path: str = GatewayConnectionSetting.conf_path() + with open(connections_conf_path, "w") as fd: + json.dump(settings, fd) + + @staticmethod + def get_market_name_from_connector_spec(connector_spec: Dict[str, str]) -> str: + return f"{connector_spec['connector']}_{connector_spec['chain']}_{connector_spec['network']}" + + @staticmethod + def get_connector_spec(connector_name: str, chain: str, network: str) -> Optional[Dict[str, str]]: + connector: Optional[Dict[str, str]] = None + connector_config: List[Dict[str, str]] = GatewayConnectionSetting.load() + for spec in connector_config: + if spec["connector"] == connector_name \ + and spec["chain"] == chain \ + and spec["network"] == network: + connector = spec + + return connector + + @staticmethod + def get_connector_spec_from_market_name(market_name: str) -> Optional[Dict[str, str]]: + for chain in SUPPORTED_CHAINS: + if chain in market_name: + connector, network = market_name.split(f"_{chain}_") + return GatewayConnectionSetting.get_connector_spec(connector, chain, network) + return None + + @staticmethod + def upsert_connector_spec( + connector_name: str, + chain: str, + network: str, + trading_type: str, + chain_type: str, + wallet_address: str, + additional_spenders: List[str], + additional_prompt_values: Dict[str, str], + ): + new_connector_spec: Dict[str, str] = { + "connector": connector_name, + "chain": chain, + "network": network, + "trading_type": trading_type, + "chain_type": chain_type, + "wallet_address": wallet_address, + "additional_spenders": additional_spenders, + "additional_prompt_values": additional_prompt_values, + } + updated: bool = False + connectors_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + for i, c in enumerate(connectors_conf): + if c["connector"] == connector_name and c["chain"] == chain and c["network"] == network: + connectors_conf[i] = new_connector_spec + updated = True + break + + if updated is False: + connectors_conf.append(new_connector_spec) + GatewayConnectionSetting.save(connectors_conf) + + @staticmethod + def upsert_connector_spec_tokens(connector_chain_network: str, tokens: List[str]): + updated_connector: Optional[Dict[str, Any]] = GatewayConnectionSetting.get_connector_spec_from_market_name(connector_chain_network) + updated_connector['tokens'] = tokens + + connectors_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + for i, c in enumerate(connectors_conf): + if c["connector"] == updated_connector['connector'] \ + and c["chain"] == updated_connector['chain'] \ + and c["network"] == updated_connector['network']: + connectors_conf[i] = updated_connector + break + + GatewayConnectionSetting.save(connectors_conf) + + +class ConnectorSetting(NamedTuple): + name: str + type: ConnectorType + example_pair: str + centralised: bool + use_ethereum_wallet: bool + trade_fee_schema: TradeFeeSchema + config_keys: Optional["BaseConnectorConfigMap"] + is_sub_domain: bool + parent_name: Optional[str] + domain_parameter: Optional[str] + use_eth_gas_lookup: bool + """ + This class has metadata data about Exchange connections. The name of the connection and the file path location of + the connector file. + """ + + def uses_gateway_generic_connector(self) -> bool: + non_gateway_connectors_types = [ConnectorType.Exchange, ConnectorType.Derivative, ConnectorType.Connector] + return self.type not in non_gateway_connectors_types + + def connector_connected(self) -> str: + from hummingbot.client.config.security import Security + return True if Security.connector_config_file_exists(self.name) else False + + def uses_clob_connector(self) -> bool: + return self.type in [ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + + def module_name(self) -> str: + # returns connector module name, e.g. binance_exchange + if self.uses_gateway_generic_connector(): + if 'AMM' in self.type.name: + # AMMs currently have multiple generic connectors. chain_type is used to determine the right connector to use. + connector_spec: Dict[str, str] = GatewayConnectionSetting.get_connector_spec_from_market_name(self.name) + return f"gateway.{self.type.name.lower()}.gateway_{connector_spec['chain_type'].lower()}_{self._get_module_package()}" + elif 'CLOB' in self.type.name: + return f"gateway.{self.type.name.lower()}.gateway_{self._get_module_package()}" + else: + raise ValueError(f"Unsupported connector type: {self.type}") + return f"{self.base_name()}_{self._get_module_package()}" + + def module_path(self) -> str: + # return connector full path name, e.g. hummingbot.connector.exchange.binance.binance_exchange + if self.uses_gateway_generic_connector(): + return f"hummingbot.connector.{self.module_name()}" + return f"hummingbot.connector.{self._get_module_package()}.{self.base_name()}.{self.module_name()}" + + def class_name(self) -> str: + # return connector class name, e.g. BinanceExchange + if self.uses_gateway_generic_connector(): + file_name = self.module_name().split('.')[-1] + splited_name = file_name.split('_') + for i in range(len(splited_name)): + if splited_name[i] in ['evm', 'amm', 'clob', 'lp', 'sol', 'spot']: + splited_name[i] = splited_name[i].upper() + else: + splited_name[i] = splited_name[i].capitalize() + return "".join(splited_name) + return "".join([o.capitalize() for o in self.module_name().split("_")]) + + def get_api_data_source_module_name(self) -> str: + module_name = "" + if self.uses_clob_connector(): + if self.type == ConnectorType.CLOB_PERP: + module_name = f"{self.name.rsplit(sep='_', maxsplit=2)[0]}_api_data_source" + else: + module_name = f"{self.name.split('_')[0]}_api_data_source" + return module_name + + def get_api_data_source_class_name(self) -> str: + class_name = "" + if self.uses_clob_connector(): + if self.type == ConnectorType.CLOB_PERP: + class_name = f"{self.name.split('_')[0].capitalize()}PerpetualAPIDataSource" + else: + class_name = f"{self.name.split('_')[0].capitalize()}APIDataSource" + return class_name + + def conn_init_parameters( + self, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = False, + api_keys: Optional[Dict[str, Any]] = None, + client_config_map: Optional["ClientConfigAdapter"] = None, + ) -> Dict[str, Any]: + trading_pairs = trading_pairs or [] + api_keys = api_keys or {} + if self.uses_gateway_generic_connector(): # init parameters for gateway connectors + params = {} + if self.config_keys is not None: + params: Dict[str, Any] = {k: v.value for k, v in self.config_keys.items()} + connector_spec: Dict[str, str] = GatewayConnectionSetting.get_connector_spec_from_market_name(self.name) + params.update( + connector_name=connector_spec["connector"], + chain=connector_spec["chain"], + network=connector_spec["network"], + address=connector_spec["wallet_address"], + ) + if not self.uses_clob_connector(): + params["additional_spenders"] = connector_spec.get("additional_spenders", []) + if self.uses_clob_connector(): + params["api_data_source"] = self._load_clob_api_data_source( + trading_pairs=trading_pairs, + trading_required=trading_required, + client_config_map=client_config_map, + connector_spec=connector_spec, + ) + elif not self.is_sub_domain: + params = api_keys + else: + params: Dict[str, Any] = {k.replace(self.name, self.parent_name): v for k, v in api_keys.items()} + params["domain"] = self.domain_parameter + + params["trading_pairs"] = trading_pairs + params["trading_required"] = trading_required + params["client_config_map"] = client_config_map + if (self.config_keys is not None + and type(self.config_keys) is not dict + and "receive_connector_configuration" in self.config_keys.__fields__ + and self.config_keys.receive_connector_configuration): + params["connector_configuration"] = self.config_keys + + return params + + def add_domain_parameter(self, params: Dict[str, Any]) -> Dict[str, Any]: + if not self.is_sub_domain: + return params + else: + params["domain"] = self.domain_parameter + return params + + def base_name(self) -> str: + if self.is_sub_domain: + return self.parent_name + else: + return self.name + + def non_trading_connector_instance_with_default_configuration( + self, + trading_pairs: Optional[List[str]] = None) -> 'ConnectorBase': + from hummingbot.client.config.config_helpers import ClientConfigAdapter + from hummingbot.client.hummingbot_application import HummingbotApplication + + trading_pairs = trading_pairs or [] + connector_class = getattr(importlib.import_module(self.module_path()), self.class_name()) + kwargs = {} + if isinstance(self.config_keys, Dict): + kwargs = {key: (config.value or "") for key, config in self.config_keys.items()} # legacy + elif self.config_keys is not None: + kwargs = { + traverse_item.attr: traverse_item.value.get_secret_value() + if isinstance(traverse_item.value, SecretStr) + else traverse_item.value or "" + for traverse_item + in ClientConfigAdapter(self.config_keys).traverse() + if traverse_item.attr != "connector" + } + kwargs = self.conn_init_parameters( + trading_pairs=trading_pairs, + trading_required=False, + api_keys=kwargs, + client_config_map=HummingbotApplication.main_application().client_config_map, + ) + kwargs = self.add_domain_parameter(kwargs) + connector = connector_class(**kwargs) + + return connector + + def _load_clob_api_data_source( + self, + trading_pairs: List[str], + trading_required: bool, + client_config_map: "ClientConfigAdapter", + connector_spec: Dict[str, str], + ) -> "CLOBAPIDataSourceBase": + module_name = self.get_api_data_source_module_name() + parent_package = f"hummingbot.connector.gateway.{self._get_module_package()}.data_sources" + module_package = self.name.rsplit(sep="_", maxsplit=2)[0] + module_path = f"{parent_package}.{module_package}.{module_name}" + module: ModuleType = importlib.import_module(module_path) + api_data_source_class = getattr(module, self.get_api_data_source_class_name()) + instance = api_data_source_class( + trading_pairs=trading_pairs, + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + return instance + + def _get_module_package(self) -> str: + return self.type.name.lower() + + +class AllConnectorSettings: + all_connector_settings: Dict[str, ConnectorSetting] = {} + + @classmethod + def create_connector_settings(cls): + """ + Iterate over files in specific Python directories to create a dictionary of exchange names to ConnectorSetting. + """ + cls.all_connector_settings = {} # reset + connector_exceptions = ["mock_paper_exchange", "mock_pure_python_paper_exchange", "paper_trade"] + + type_dirs: List[DirEntry] = [ + cast(DirEntry, f) for f in scandir(f"{root_path() / 'hummingbot' / 'connector'}") + if f.is_dir() and f.name not in CONNECTOR_SUBMODULES_THAT_ARE_NOT_CEX_TYPES + ] + for type_dir in type_dirs: + if type_dir.name == 'gateway': + continue + connector_dirs: List[DirEntry] = [ + cast(DirEntry, f) for f in scandir(type_dir.path) + if f.is_dir() and exists(join(f.path, "__init__.py")) + ] + for connector_dir in connector_dirs: + if connector_dir.name.startswith("_") or connector_dir.name in connector_exceptions: + continue + if connector_dir.name in cls.all_connector_settings: + raise Exception(f"Multiple connectors with the same {connector_dir.name} name.") + try: + util_module_path: str = f"hummingbot.connector.{type_dir.name}." \ + f"{connector_dir.name}.{connector_dir.name}_utils" + util_module = importlib.import_module(util_module_path) + except ModuleNotFoundError: + continue + trade_fee_settings: List[float] = getattr(util_module, "DEFAULT_FEES", None) + trade_fee_schema: TradeFeeSchema = cls._validate_trade_fee_schema( + connector_dir.name, trade_fee_settings + ) + cls.all_connector_settings[connector_dir.name] = ConnectorSetting( + name=connector_dir.name, + type=ConnectorType[type_dir.name.capitalize()], + centralised=getattr(util_module, "CENTRALIZED", True), + example_pair=getattr(util_module, "EXAMPLE_PAIR", ""), + use_ethereum_wallet=getattr(util_module, "USE_ETHEREUM_WALLET", False), + trade_fee_schema=trade_fee_schema, + config_keys=getattr(util_module, "KEYS", None), + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=getattr(util_module, "USE_ETH_GAS_LOOKUP", False), + ) + # Adds other domains of connector + other_domains = getattr(util_module, "OTHER_DOMAINS", []) + for domain in other_domains: + trade_fee_settings = getattr(util_module, "OTHER_DOMAINS_DEFAULT_FEES")[domain] + trade_fee_schema = cls._validate_trade_fee_schema(domain, trade_fee_settings) + parent = cls.all_connector_settings[connector_dir.name] + cls.all_connector_settings[domain] = ConnectorSetting( + name=domain, + type=parent.type, + centralised=parent.centralised, + example_pair=getattr(util_module, "OTHER_DOMAINS_EXAMPLE_PAIR")[domain], + use_ethereum_wallet=parent.use_ethereum_wallet, + trade_fee_schema=trade_fee_schema, + config_keys=getattr(util_module, "OTHER_DOMAINS_KEYS")[domain], + is_sub_domain=True, + parent_name=parent.name, + domain_parameter=getattr(util_module, "OTHER_DOMAINS_PARAMETER")[domain], + use_eth_gas_lookup=parent.use_eth_gas_lookup, + ) + + # add gateway connectors + gateway_connections_conf: List[Dict[str, str]] = GatewayConnectionSetting.load() + trade_fee_settings: List[float] = [0.0, 0.0] # we assume no swap fees for now + trade_fee_schema: TradeFeeSchema = cls._validate_trade_fee_schema("gateway", trade_fee_settings) + + for connection_spec in gateway_connections_conf: + market_name: str = GatewayConnectionSetting.get_market_name_from_connector_spec(connection_spec) + cls.all_connector_settings[market_name] = ConnectorSetting( + name=market_name, + type=ConnectorType[connection_spec["trading_type"]], + centralised=False, + example_pair="WETH-USDC", + use_ethereum_wallet=False, + trade_fee_schema=trade_fee_schema, + config_keys=None, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + + return cls.all_connector_settings + + @classmethod + def initialize_paper_trade_settings(cls, paper_trade_exchanges: List[str]): + for e in paper_trade_exchanges: + base_connector_settings: Optional[ConnectorSetting] = cls.all_connector_settings.get(e, None) + if base_connector_settings: + paper_trade_settings = ConnectorSetting( + name=f"{e}_paper_trade", + type=base_connector_settings.type, + centralised=base_connector_settings.centralised, + example_pair=base_connector_settings.example_pair, + use_ethereum_wallet=base_connector_settings.use_ethereum_wallet, + trade_fee_schema=base_connector_settings.trade_fee_schema, + config_keys=base_connector_settings.config_keys, + is_sub_domain=False, + parent_name=base_connector_settings.name, + domain_parameter=None, + use_eth_gas_lookup=base_connector_settings.use_eth_gas_lookup, + ) + cls.all_connector_settings.update({f"{e}_paper_trade": paper_trade_settings}) + + @classmethod + def get_connector_settings(cls) -> Dict[str, ConnectorSetting]: + if len(cls.all_connector_settings) == 0: + cls.all_connector_settings = cls.create_connector_settings() + return cls.all_connector_settings + + @classmethod + def get_connector_config_keys(cls, connector: str) -> Optional["BaseConnectorConfigMap"]: + return cls.get_connector_settings()[connector].config_keys + + @classmethod + def reset_connector_config_keys(cls, connector: str): + current_settings = cls.get_connector_settings()[connector] + current_keys = current_settings.config_keys + new_keys = ( + current_keys if current_keys is None else current_keys.__class__.construct() + ) + cls.update_connector_config_keys(new_keys) + + @classmethod + def update_connector_config_keys(cls, new_config_keys: "BaseConnectorConfigMap"): + current_settings = cls.get_connector_settings()[new_config_keys.connector] + new_keys_settings_dict = current_settings._asdict() + new_keys_settings_dict.update({"config_keys": new_config_keys}) + cls.get_connector_settings()[new_config_keys.connector] = ConnectorSetting( + **new_keys_settings_dict + ) + + @classmethod + def get_exchange_names(cls) -> Set[str]: + return { + cs.name for cs in cls.get_connector_settings().values() + if cs.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + }.union(set(PAPER_TRADE_EXCHANGES)) + + @classmethod + def get_derivative_names(cls) -> Set[str]: + return {cs.name for cs in cls.all_connector_settings.values() if cs.type in [ConnectorType.Derivative, ConnectorType.AMM_Perpetual, ConnectorType.CLOB_PERP]} + + @classmethod + def get_derivative_dex_names(cls) -> Set[str]: + return {cs.name for cs in cls.all_connector_settings.values() if cs.type is ConnectorType.AMM_Perpetual} + + @classmethod + def get_other_connector_names(cls) -> Set[str]: + return {cs.name for cs in cls.all_connector_settings.values() if cs.type is ConnectorType.Connector} + + @classmethod + def get_eth_wallet_connector_names(cls) -> Set[str]: + return {cs.name for cs in cls.all_connector_settings.values() if cs.use_ethereum_wallet} + + @classmethod + def get_gateway_amm_connector_names(cls) -> Set[str]: + return {cs.name for cs in cls.get_connector_settings().values() if cs.type == ConnectorType.AMM} + + @classmethod + def get_gateway_evm_amm_lp_connector_names(cls) -> Set[str]: + return {cs.name for cs in cls.all_connector_settings.values() if cs.type == ConnectorType.AMM_LP} + + @classmethod + def get_gateway_clob_connector_names(cls) -> Set[str]: + return { + cs.name for cs in cls.all_connector_settings.values() + if cs.type == ConnectorType.CLOB_SPOT + } + + @classmethod + def get_example_pairs(cls) -> Dict[str, str]: + return {name: cs.example_pair for name, cs in cls.get_connector_settings().items()} + + @classmethod + def get_example_assets(cls) -> Dict[str, str]: + return {name: cs.example_pair.split("-")[0] for name, cs in cls.get_connector_settings().items()} + + @staticmethod + def _validate_trade_fee_schema( + exchange_name: str, trade_fee_schema: Optional[Union[TradeFeeSchema, List[float]]] + ) -> TradeFeeSchema: + if not isinstance(trade_fee_schema, TradeFeeSchema): + # backward compatibility + maker_percent_fee_decimal = ( + Decimal(str(trade_fee_schema[0])) / Decimal("100") if trade_fee_schema is not None else Decimal("0") + ) + taker_percent_fee_decimal = ( + Decimal(str(trade_fee_schema[1])) / Decimal("100") if trade_fee_schema is not None else Decimal("0") + ) + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=maker_percent_fee_decimal, + taker_percent_fee_decimal=taker_percent_fee_decimal, + ) + return trade_fee_schema + + +def ethereum_wallet_required() -> bool: + """ + Check if an Ethereum wallet is required for any of the exchanges the user's config uses. + """ + return any(e in AllConnectorSettings.get_eth_wallet_connector_names() for e in required_exchanges) + + +def ethereum_gas_station_required() -> bool: + """ + Check if the user's config needs to look up gas costs from an Ethereum gas station. + """ + return any(name for name, con_set in AllConnectorSettings.get_connector_settings().items() if name in required_exchanges + and con_set.use_eth_gas_lookup) + + +def ethereum_required_trading_pairs() -> List[str]: + """ + Check if the trading pairs require an ethereum wallet (ERC-20 tokens). + """ + ret_val = [] + for conn, t_pair in requried_connector_trading_pairs.items(): + if AllConnectorSettings.get_connector_settings()[conn].use_ethereum_wallet: + ret_val += t_pair + return ret_val + + +def gateway_connector_trading_pairs(connector: str) -> List[str]: + """ + Returns trading pair used by specified gateway connnector. + """ + ret_val = [] + for conn, t_pair in requried_connector_trading_pairs.items(): + if AllConnectorSettings.get_connector_settings()[conn].uses_gateway_generic_connector() and \ + conn == connector: + ret_val += t_pair + return ret_val + + +MAXIMUM_OUTPUT_PANE_LINE_COUNT = 1000 +MAXIMUM_LOG_PANE_LINE_COUNT = 1000 +MAXIMUM_TRADE_FILLS_DISPLAY_OUTPUT = 100 + +STRATEGIES: List[str] = get_strategy_list() +GATEWAY_CONNECTORS: List[str] = [] diff --git a/hummingbot/client/tab/__init__.py b/hummingbot/client/tab/__init__.py new file mode 100644 index 0000000..3330b20 --- /dev/null +++ b/hummingbot/client/tab/__init__.py @@ -0,0 +1,7 @@ +from .tab_example_tab import TabExampleTab +from .order_book_tab import OrderBookTab + +__all__ = [ + OrderBookTab, + TabExampleTab +] diff --git a/hummingbot/client/tab/data_types.py b/hummingbot/client/tab/data_types.py new file mode 100644 index 0000000..7ebc883 --- /dev/null +++ b/hummingbot/client/tab/data_types.py @@ -0,0 +1,23 @@ +import asyncio + +from dataclasses import dataclass +from prompt_toolkit.widgets import Button +from typing import Type, Optional + +from hummingbot.client.ui.custom_widgets import CustomTextArea +from .tab_base import TabBase + + +@dataclass +class CommandTab: + """ + Defines all data points for a tab. + """ + name: str # Command name of the tab + button: Optional[Button] # Tab toggle button + close_button: Optional[Button] # Tab close button + output_field: Optional[CustomTextArea] # Output pane where tab messages display + tab_class: Type[TabBase] # The tab class (Subclass of TabBase) + is_selected: bool = False # If the tab is currently selected by a user + tab_index: int = 0 # The index position of the tab in relation of all other displayed tabs + task: Optional[asyncio.Task] = None # The currently running task, None if there isn't one diff --git a/hummingbot/client/tab/order_book_tab.py b/hummingbot/client/tab/order_book_tab.py new file mode 100644 index 0000000..3bbb75c --- /dev/null +++ b/hummingbot/client/tab/order_book_tab.py @@ -0,0 +1,73 @@ +import asyncio +import pandas as pd + +from typing import TYPE_CHECKING, Dict, Any +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + +from hummingbot.client.ui.custom_widgets import CustomTextArea +from .tab_base import TabBase + + +class OrderBookTab(TabBase): + @classmethod + def get_command_name(cls) -> str: + return "order_book" + + @classmethod + def get_command_help_message(cls) -> str: + return "Display current order book" + + @classmethod + def get_command_arguments(cls) -> Dict[str, Dict[str, Any]]: + return { + "--lines": {'type': int, 'default': 5, 'dest': "lines", 'help': "Number of lines to display"}, + "--exchange": {'type': str, 'dest': "exchange", 'help': "The exchange of the market"}, + "--market": {'type': str, 'dest': "market", 'help': "The market (trading pair) of the order book"}, + "--live": {'default': False, 'action': "store_true", 'dest': "live", 'help': "Show order book updates"} + } + + @classmethod + async def display(cls, + output_field: CustomTextArea, + hummingbot: "HummingbotApplication", + lines: int = 5, + exchange: str = None, + market: str = None, + live: bool = False): + if len(hummingbot.markets.keys()) == 0: + output_field.log("There is currently no active market.") + return + if exchange is not None: + if exchange not in hummingbot.markets: + output_field.log("Invalid exchange") + return + market_connector = hummingbot.markets[exchange] + else: + market_connector = list(hummingbot.markets.values())[0] + if market is not None: + market = market.upper() + if market not in market_connector.order_books: + output_field.log("Invalid market") + return + trading_pair, order_book = market, market_connector.order_books[market] + else: + trading_pair, order_book = next(iter(market_connector.order_books.items())) + + def get_order_book_text(no_lines: int): + bids = order_book.snapshot[0][['price', 'amount']].head(no_lines) + bids.rename(columns={'price': 'bid_price', 'amount': 'bid_volume'}, inplace=True) + asks = order_book.snapshot[1][['price', 'amount']].head(no_lines) + asks.rename(columns={'price': 'ask_price', 'amount': 'ask_volume'}, inplace=True) + joined_df = pd.concat([bids, asks], axis=1) + text_lines = ["" + line for line in joined_df.to_string(index=False).split("\n")] + header = f"market: {market_connector.name} {trading_pair}\n" + return header + "\n".join(text_lines) + + if live: + while True: + order_book_text = get_order_book_text(min(lines, 35)) + output_field.log(order_book_text, save_log=False) + await asyncio.sleep(0.5) + else: + output_field.log(get_order_book_text(lines)) diff --git a/hummingbot/client/tab/tab_base.py b/hummingbot/client/tab/tab_base.py new file mode 100644 index 0000000..442946f --- /dev/null +++ b/hummingbot/client/tab/tab_base.py @@ -0,0 +1,55 @@ +from abc import ( + ABCMeta, + abstractmethod, +) +from typing import TYPE_CHECKING, Dict, Any +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + +from hummingbot.client.ui.custom_widgets import CustomTextArea + + +class TabBase(metaclass=ABCMeta): + """ + Defines functions needed to be implemented by all tab classes. + """ + + @classmethod + @abstractmethod + def get_command_name(cls) -> str: + """ + Returns a command name for the tab, once issued a new tab will be created. The command name will also appear on + auto complete list of commands. + """ + raise NotImplementedError + + @classmethod + @abstractmethod + def get_command_help_message(cls) -> str: + """ + Returns a help message to describe what the command does. + """ + + raise NotImplementedError + + @classmethod + @abstractmethod + def get_command_arguments(cls) -> Dict[str, Dict[str, Any]]: + """ + Returns a dictionary of command argument and all its properties. See hummingbot.client.ui.parser for examples. + """ + + raise NotImplementedError + + @classmethod + @abstractmethod + async def display(cls, output_field: CustomTextArea, hummingbot: "HummingbotApplication", **kwargs): + """ + Displays message on the tab + :param output_field: The output pane for the tab messages + :param hummingbot: The current running Hummingbot application including strategy, connectors and all + other application properties + :param **kargs: All the command arguments defined in get_command_arguments method will be supplied here + """ + + raise NotImplementedError diff --git a/hummingbot/client/tab/tab_example_tab.py b/hummingbot/client/tab/tab_example_tab.py new file mode 100644 index 0000000..fe94ae0 --- /dev/null +++ b/hummingbot/client/tab/tab_example_tab.py @@ -0,0 +1,26 @@ +from typing import TYPE_CHECKING, Dict, Any +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.client.ui.custom_widgets import CustomTextArea +from .tab_base import TabBase + + +class TabExampleTab(TabBase): + @classmethod + def get_command_name(cls) -> str: + return "tab_example" + + @classmethod + def get_command_help_message(cls) -> str: + return "Display hello world" + + @classmethod + def get_command_arguments(cls) -> Dict[str, Dict[str, Any]]: + return {} + + @classmethod + async def display(cls, + output_field: CustomTextArea, + hummingbot: "HummingbotApplication", + ): + output_field.log("Hello World!") diff --git a/hummingbot/client/ui/__init__.py b/hummingbot/client/ui/__init__.py new file mode 100644 index 0000000..e5e41f5 --- /dev/null +++ b/hummingbot/client/ui/__init__.py @@ -0,0 +1,226 @@ +import os +import sys +from os.path import dirname, join, realpath +from typing import Type + +from prompt_toolkit.shortcuts import input_dialog, message_dialog +from prompt_toolkit.styles import Style + +from hummingbot import root_path +from hummingbot.client.config.conf_migration import migrate_configs, migrate_non_secure_configs_only +from hummingbot.client.config.config_crypt import BaseSecretsManager, store_password_verification +from hummingbot.client.config.security import Security +from hummingbot.client.settings import CONF_DIR_PATH + +sys.path.insert(0, str(root_path())) + + +with open(realpath(join(dirname(__file__), '../../VERSION'))) as version_file: + version = version_file.read().strip() + + +def login_prompt(secrets_manager_cls: Type[BaseSecretsManager], style: Style): + err_msg = None + secrets_manager = None + if Security.new_password_required(): + if legacy_confs_exist(): + secrets_manager = migrate_configs_prompt(secrets_manager_cls, style) + else: + show_welcome(style) + password = input_dialog( + title="Set Password", + text=""" + Create a password to protect your sensitive data. + This password is not shared with us nor with anyone else, so please store it securely. + + Enter your new password:""", + password=True, + style=style).run() + if password is None: + return None + if password == str(): + err_msg = "The password must not be empty." + else: + re_password = input_dialog( + title="Set Password", + text="Please re-enter your password:", + password=True, + style=style).run() + if re_password is None: + return None + if password != re_password: + err_msg = "Passwords entered do not match, please try again." + else: + secrets_manager = secrets_manager_cls(password) + store_password_verification(secrets_manager) + else: + password = input_dialog( + title="Welcome back to Hummingbot", + text="Enter your password:", + password=True, + style=style).run() + if password is None: + return None + secrets_manager = secrets_manager_cls(password) + if err_msg is None and not Security.login(secrets_manager): + err_msg = "Invalid password - please try again." + if err_msg is not None: + message_dialog( + title='Error', + text=err_msg, + style=style).run() + return login_prompt(secrets_manager_cls, style) + return secrets_manager + + +def legacy_confs_exist() -> bool: + encrypted_conf_prefix = "encrypted_" + encrypted_conf_postfix = ".json" + exist = False + for f in sorted(os.listdir(CONF_DIR_PATH)): + f_path = CONF_DIR_PATH / f + if os.path.isfile(f_path) and f.startswith(encrypted_conf_prefix) and f.endswith(encrypted_conf_postfix): + exist = True + break + return exist + + +def migrate_configs_prompt(secrets_manager_cls: Type[BaseSecretsManager], style: Style) -> BaseSecretsManager: + message_dialog( + title='Configs Migration', + text=""" + + + CONFIGS MIGRATION: + + We have recently refactored the way hummingbot handles configurations. + To migrate your legacy configuration files to the new format, + please enter your password on the following screen. + + """, + style=style).run() + password = input_dialog( + title="Input Password", + text="\n\nEnter your previous password:", + password=True, + style=style).run() + if password is None: + raise ValueError("Wrong password.") + secrets_manager = secrets_manager_cls(password) + errors = migrate_configs(secrets_manager) + if len(errors) != 0: + _migration_errors_dialog(errors, style) + else: + message_dialog( + title='Configs Migration Success', + text=""" + + + CONFIGS MIGRATION SUCCESS: + + The migration process was completed successfully. + + """, + style=style).run() + return secrets_manager + + +def migrate_non_secure_only_prompt(style: Style): + message_dialog( + title='Configs Migration', + text=""" + + + CONFIGS MIGRATION: + + We have recently refactored the way hummingbot handles configurations. + We will now attempt to migrate any legacy config files to the new format. + + """, + style=style).run() + errors = migrate_non_secure_configs_only() + if len(errors) != 0: + _migration_errors_dialog(errors, style) + else: + message_dialog( + title='Configs Migration Success', + text=""" + + + CONFIGS MIGRATION SUCCESS: + + The migration process was completed successfully. + + """, + style=style).run() + + +def _migration_errors_dialog(errors, style: Style): + padding = "\n " + errors_str = padding + padding.join(errors) + message_dialog( + title='Configs Migration Errors', + text=f""" + + + CONFIGS MIGRATION ERRORS: + + {errors_str} + + """, + style=style).run() + + +def show_welcome(style: Style): + message_dialog( + title='Welcome to Hummingbot', + text=""" + + ██╗ ██╗██╗ ██╗███╗ ███╗███╗ ███╗██╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ████████╗ + ██║ ██║██║ ██║████╗ ████║████╗ ████║██║████╗ ██║██╔════╝ ██╔══██╗██╔═══██╗╚══██╔══╝ + ███████║██║ ██║██╔████╔██║██╔████╔██║██║██╔██╗ ██║██║ ███╗██████╔╝██║ ██║ ██║ + ██╔══██║██║ ██║██║╚██╔╝██║██║╚██╔╝██║██║██║╚██╗██║██║ ██║██╔══██╗██║ ██║ ██║ + ██║ ██║╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║██║██║ ╚████║╚██████╔╝██████╔╝╚██████╔╝ ██║ + ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ + + ======================================================================================= + + Version: {version} + Codebase: https://github.com/hummingbot/hummingbot + + + """.format(version=version), + style=style).run() + message_dialog( + title='Important Warning', + text=""" + + + PLEASE READ THIS CAREFULLY BEFORE USING HUMMINGBOT: + + Hummingbot is a free and open source software client that helps you build algorithmic + crypto trading strategies. + + Algorithmic crypto trading is a risky activity. You will be building a "bot" that + automatically places orders and trades based on parameters that you set. Please take + the time to understand how each strategy works before you risk real capital with it. + You are solely responsible for the trades that you perform using Hummingbot. + + """, + style=style).run() + message_dialog( + title='Important Warning', + text=""" + + + SET A SECURE PASSWORD: + + To use Hummingbot, you will need to give it access to your crypto assets by entering + your exchange API keys and/or wallet private keys. These keys are not shared with + anyone, including us. + + On the next screen, you will set a password to protect these keys and other sensitive + data. Please store this password safely since there is no way to reset it. + + """, + style=style).run() diff --git a/hummingbot/client/ui/completer.py b/hummingbot/client/ui/completer.py new file mode 100644 index 0000000..35d88f4 --- /dev/null +++ b/hummingbot/client/ui/completer.py @@ -0,0 +1,413 @@ +import re +from os import listdir +from os.path import exists, isfile, join +from typing import List + +from prompt_toolkit.completion import CompleteEvent, Completer, WordCompleter +from prompt_toolkit.document import Document + +from hummingbot.client.command.connect_command import OPTIONS as CONNECT_OPTIONS +from hummingbot.client.settings import ( + GATEWAY_CONNECTORS, + PMM_SCRIPTS_PATH, + SCRIPT_STRATEGIES_PATH, + STRATEGIES, + STRATEGIES_CONF_DIR_PATH, + AllConnectorSettings, +) +from hummingbot.client.ui.parser import ThrowingArgumentParser +from hummingbot.core.rate_oracle.rate_oracle import RATE_ORACLE_SOURCES +from hummingbot.core.utils.gateway_config_utils import list_gateway_wallets +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher + + +def file_name_list(path, file_extension): + if not exists(path): + return [] + return sorted([f for f in listdir(path) if isfile(join(path, f)) and f.endswith(file_extension)]) + + +class HummingbotCompleter(Completer): + def __init__(self, hummingbot_application): + super(HummingbotCompleter, self).__init__() + self.hummingbot_application = hummingbot_application + self._path_completer = WordCompleter(file_name_list(str(STRATEGIES_CONF_DIR_PATH), "yml")) + self._command_completer = WordCompleter(self.parser.commands, ignore_case=True) + self._exchange_completer = WordCompleter(sorted(AllConnectorSettings.get_connector_settings().keys()), ignore_case=True) + self._spot_exchange_completer = WordCompleter(sorted(AllConnectorSettings.get_exchange_names()), ignore_case=True) + self._exchange_amm_completer = WordCompleter( + sorted( + AllConnectorSettings.get_exchange_names().union( + AllConnectorSettings.get_gateway_amm_connector_names() + ).union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ), ignore_case=True + ) + self._exchange_clob_completer = WordCompleter(sorted(AllConnectorSettings.get_exchange_names().union( + AllConnectorSettings.get_gateway_clob_connector_names())), ignore_case=True) + self._evm_amm_lp_completer = WordCompleter(sorted(AllConnectorSettings.get_gateway_evm_amm_lp_connector_names()), ignore_case=True) + self._trading_timeframe_completer = WordCompleter(["infinite", "from_date_to_date", "daily_between_times"], ignore_case=True) + self._derivative_completer = WordCompleter(AllConnectorSettings.get_derivative_names(), ignore_case=True) + self._derivative_exchange_completer = WordCompleter(AllConnectorSettings.get_derivative_names().difference(AllConnectorSettings.get_derivative_dex_names()), ignore_case=True) + self._connect_option_completer = WordCompleter(CONNECT_OPTIONS, ignore_case=True) + self._export_completer = WordCompleter(["keys", "trades"], ignore_case=True) + self._balance_completer = WordCompleter(["limit", "paper"], ignore_case=True) + self._history_completer = WordCompleter(["--days", "--verbose", "--precision"], ignore_case=True) + self._gateway_completer = WordCompleter(["balance", "config", "connect", "connector-tokens", "generate-certs", "test-connection", "list", "approve-tokens"], ignore_case=True) + self._gateway_connect_completer = WordCompleter(GATEWAY_CONNECTORS, ignore_case=True) + self._gateway_connector_tokens_completer = WordCompleter( + sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ), ignore_case=True + ) + self._gateway_approve_tokens_completer = WordCompleter( + sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ), ignore_case=True + ) + self._gateway_config_completer = WordCompleter(hummingbot_application.gateway_config_keys, ignore_case=True) + self._strategy_completer = WordCompleter(STRATEGIES, ignore_case=True) + self._py_file_completer = WordCompleter(file_name_list(str(PMM_SCRIPTS_PATH), "py")) + self._script_strategy_completer = WordCompleter(file_name_list(str(SCRIPT_STRATEGIES_PATH), "py")) + self._rate_oracle_completer = WordCompleter(list(RATE_ORACLE_SOURCES.keys()), ignore_case=True) + self._mqtt_completer = WordCompleter(["start", "stop", "restart"], ignore_case=True) + self._gateway_chains = [] + self._gateway_networks = [] + self._list_gateway_wallets_parameters = {"wallets": [], "chain": ""} + + def set_gateway_chains(self, gateway_chains): + self._gateway_chains = gateway_chains + + def set_gateway_networks(self, gateway_networks): + self._gateway_networks = gateway_networks + + def set_list_gateway_wallets_parameters(self, wallets, chain): + self._list_gateway_wallets_parameters = {"wallets": wallets, "chain": chain} + + @property + def prompt_text(self) -> str: + return self.hummingbot_application.app.prompt_text + + @property + def parser(self) -> ThrowingArgumentParser: + return self.hummingbot_application.parser + + def get_subcommand_completer(self, first_word: str) -> Completer: + subcommands: List[str] = self.parser.subcommands_from(first_word) + return WordCompleter(subcommands, ignore_case=True) + + @property + def _trading_pair_completer(self) -> Completer: + trading_pair_fetcher = TradingPairFetcher.get_instance() + market = "" + for exchange in sorted(list(AllConnectorSettings.get_connector_settings().keys()), key=len, reverse=True): + if exchange in self.prompt_text: + market = exchange + break + trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready and market else [] + return WordCompleter(trading_pairs, ignore_case=True, sentence=True) + + @property + def _gateway_chain_completer(self): + return WordCompleter(self._gateway_chains, ignore_case=True) + + @property + def _gateway_network_completer(self): + return WordCompleter(self._gateway_networks, ignore_case=True) + + @property + def _gateway_wallet_address_completer(self): + return WordCompleter(list_gateway_wallets(self._list_gateway_wallets_parameters["wallets"], self._list_gateway_wallets_parameters["chain"]), ignore_case=True) + + @property + def _option_completer(self): + outer = re.compile(r"\((.+)\)") + inner_str = outer.search(self.prompt_text).group(1) + options = inner_str.split("/") if "/" in inner_str else [] + return WordCompleter(options, ignore_case=True) + + @property + def _config_completer(self): + config_keys = self.hummingbot_application.configurable_keys() + return WordCompleter(config_keys, ignore_case=True) + + def _complete_strategies(self, document: Document) -> bool: + return "strategy" in self.prompt_text and "strategy file" not in self.prompt_text + + def _complete_pmm_script_files(self, document: Document) -> bool: + return "PMM script file" in self.prompt_text + + def _complete_configs(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return "config" in text_before_cursor + + def _complete_options(self, document: Document) -> bool: + return "(" in self.prompt_text and ")" in self.prompt_text and "/" in self.prompt_text + + def _complete_exchanges(self, document: Document) -> bool: + return any(x for x in ("exchange name", "name of exchange", "name of the exchange") + if x in self.prompt_text.lower()) + + def _complete_derivatives(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return "perpetual" in text_before_cursor or \ + any(x for x in ("derivative connector", "derivative name", "name of derivative", "name of the derivative") + if x in self.prompt_text.lower()) + + def _complete_connect_options(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("connect ") + + def _complete_exchange_amm_connectors(self, document: Document) -> bool: + return "(Exchange/AMM/CLOB)" in self.prompt_text + + def _complete_exchange_clob_connectors(self, document: Document) -> bool: + return "(Exchange/AMM/CLOB)" in self.prompt_text + + def _complete_spot_exchanges(self, document: Document) -> bool: + return "spot" in self.prompt_text + + def _complete_lp_connector(self, document: Document) -> bool: + return "LP" in self.prompt_text + + def _complete_trading_timeframe(self, document: Document) -> bool: + return any(x for x in ("trading timeframe", "execution timeframe") + if x in self.prompt_text.lower()) + + def _complete_export_options(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return "export" in text_before_cursor + + def _complete_balance_options(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("balance ") + + def _complete_history_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("history ") + + def _complete_gateway_connect_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("gateway connect ") + + def _complete_gateway_connector_tokens_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("gateway connector-tokens ") + + def _complete_gateway_approve_tokens_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("gateway approve-tokens ") + + def _complete_gateway_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("gateway ") and not text_before_cursor.startswith("gateway config ") + + def _complete_gateway_config_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("gateway config ") + + def _complete_script_strategy_files(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("start --script ") + + def _complete_trading_pairs(self, document: Document) -> bool: + return "trading pair" in self.prompt_text + + def _complete_paths(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return (("path" in self.prompt_text and "file" in self.prompt_text) or + "import" in text_before_cursor) + + def _complete_gateway_chain(self, document: Document) -> bool: + return "Which chain do you want" in self.prompt_text + + def _complete_gateway_network(self, document: Document) -> bool: + return "Which network do you want" in self.prompt_text + + def _complete_gateway_wallet_addresses(self, document: Document) -> bool: + return "Select a gateway wallet" in self.prompt_text + + def _complete_command(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return " " not in text_before_cursor and len(self.prompt_text.replace(">>> ", "")) == 0 + + def _complete_subcommand(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + index: int = text_before_cursor.index(' ') + return text_before_cursor[0:index] in self.parser.commands + + def _complete_balance_limit_exchanges(self, document: Document): + text_before_cursor: str = document.text_before_cursor + command_args = text_before_cursor.split(" ") + return len(command_args) == 3 and command_args[0] == "balance" and command_args[1] == "limit" + + def _complete_rate_oracle_source(self, document: Document): + return all(x in self.prompt_text for x in ("source", "rate oracle")) + + def _complete_mqtt_arguments(self, document: Document) -> bool: + text_before_cursor: str = document.text_before_cursor + return text_before_cursor.startswith("mqtt ") + + def get_completions(self, document: Document, complete_event: CompleteEvent): + """ + Get completions for the current scope. This is the defining function for the completer + :param document: + :param complete_event: + """ + if self._complete_pmm_script_files(document): + for c in self._py_file_completer.get_completions(document, complete_event): + yield c + + elif self._complete_script_strategy_files(document): + for c in self._script_strategy_completer.get_completions(document, complete_event): + yield c + + elif self._complete_paths(document): + for c in self._path_completer.get_completions(document, complete_event): + yield c + + elif self._complete_strategies(document): + for c in self._strategy_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_chain(document): + for c in self._gateway_chain_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_network(document): + for c in self._gateway_network_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_wallet_addresses(document): + for c in self._gateway_wallet_address_completer.get_completions(document, complete_event): + yield c + + if self._complete_lp_connector(document): + for c in self._evm_amm_lp_completer.get_completions(document, complete_event): + yield c + + elif self._complete_exchange_amm_connectors(document): + if self._complete_spot_exchanges(document): + for c in self._spot_exchange_completer.get_completions(document, complete_event): + yield c + else: + for c in self._exchange_amm_completer.get_completions(document, complete_event): + yield c + + elif self._complete_exchange_clob_connectors(document): + if self._complete_spot_exchanges(document): + for c in self._spot_exchange_completer.get_completions(document, complete_event): + yield c + elif self._complete_derivatives(document): + for c in self._derivative_exchange_completer.get_completions(document, complete_event): + yield c + else: + for c in self._exchange_clob_completer.get_completions(document, complete_event): + yield c + + elif self._complete_spot_exchanges(document): + for c in self._spot_exchange_completer.get_completions(document, complete_event): + yield c + + elif self._complete_trading_timeframe(document): + for c in self._trading_timeframe_completer.get_completions(document, complete_event): + yield c + + elif self._complete_connect_options(document): + for c in self._connect_option_completer.get_completions(document, complete_event): + yield c + + elif self._complete_export_options(document): + for c in self._export_completer.get_completions(document, complete_event): + yield c + + elif self._complete_balance_limit_exchanges(document): + for c in self._connect_option_completer.get_completions(document, complete_event): + yield c + + elif self._complete_balance_options(document): + for c in self._balance_completer.get_completions(document, complete_event): + yield c + + elif self._complete_history_arguments(document): + for c in self._history_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_connect_arguments(document): + for c in self._gateway_connect_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_connector_tokens_arguments(document): + for c in self._gateway_connector_tokens_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_approve_tokens_arguments(document): + for c in self._gateway_approve_tokens_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_arguments(document): + for c in self._gateway_completer.get_completions(document, complete_event): + yield c + + elif self._complete_gateway_config_arguments(document): + for c in self._gateway_config_completer.get_completions(document, complete_event): + yield c + + elif self._complete_derivatives(document): + if self._complete_exchanges(document): + for c in self._derivative_exchange_completer.get_completions(document, complete_event): + yield c + elif "(Exchange/CLOB)" in self.prompt_text: + for c in self._derivative_completer.get_completions(document, complete_event): + yield c + else: + for c in self._derivative_completer.get_completions(document, complete_event): + yield c + + elif self._complete_exchanges(document): + for c in self._exchange_completer.get_completions(document, complete_event): + yield c + + elif self._complete_trading_pairs(document): + for c in self._trading_pair_completer.get_completions(document, complete_event): + yield c + + elif self._complete_command(document): + for c in self._command_completer.get_completions(document, complete_event): + yield c + + elif self._complete_configs(document): + for c in self._config_completer.get_completions(document, complete_event): + yield c + + elif self._complete_options(document): + for c in self._option_completer.get_completions(document, complete_event): + yield c + + elif self._complete_rate_oracle_source(document): + for c in self._rate_oracle_completer.get_completions(document, complete_event): + yield c + + elif self._complete_mqtt_arguments(document): + for c in self._mqtt_completer.get_completions(document, complete_event): + yield c + + else: + text_before_cursor: str = document.text_before_cursor + try: + first_word: str = text_before_cursor[0:text_before_cursor.index(' ')] + except ValueError: + return + subcommand_completer: Completer = self.get_subcommand_completer(first_word) + if complete_event.completion_requested or self._complete_subcommand(document): + for c in subcommand_completer.get_completions(document, complete_event): + yield c + + +def load_completer(hummingbot_application): + return HummingbotCompleter(hummingbot_application) diff --git a/hummingbot/client/ui/custom_widgets.py b/hummingbot/client/ui/custom_widgets.py new file mode 100644 index 0000000..147a2b2 --- /dev/null +++ b/hummingbot/client/ui/custom_widgets.py @@ -0,0 +1,254 @@ +from __future__ import unicode_literals + +import re +from collections import deque +from typing import Callable, Deque, Dict, List, Tuple + +import six +from prompt_toolkit.auto_suggest import DynamicAutoSuggest +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.completion import DynamicCompleter +from prompt_toolkit.document import Document +from prompt_toolkit.filters import Condition, has_focus, is_done, is_true, to_filter +from prompt_toolkit.formatted_text.base import StyleAndTextTuples +from prompt_toolkit.layout.containers import Window, WindowAlign +from prompt_toolkit.layout.controls import BufferControl +from prompt_toolkit.layout.margins import NumberedMargin, ScrollbarMargin +from prompt_toolkit.layout.processors import AppendAutoSuggestion, BeforeInput, ConditionalProcessor, PasswordProcessor +from prompt_toolkit.lexers import DynamicLexer +from prompt_toolkit.lexers.base import Lexer +from prompt_toolkit.widgets.toolbars import SearchToolbar + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.ui.style import load_style, text_ui_style + + +class CustomBuffer(Buffer): + def validate_and_handle(self): + valid = self.validate(set_cursor=True) + if valid: + if self.accept_handler: + keep_text = self.accept_handler(self) + else: + keep_text = False + if not keep_text: + self.reset() + + +class FormattedTextLexer(Lexer): + + PROMPT_TEXT = ">>> " + + def __init__(self, client_config_map: ClientConfigAdapter) -> None: + super().__init__() + self.html_tag_css_style_map: Dict[str, str] = { + style: css for style, css in load_style(client_config_map).style_rules + } + self.html_tag_css_style_map.update({ + ti.attr: ti.value + for ti in client_config_map.color.traverse() + if ti.attr not in self.html_tag_css_style_map + }) + + # Maps specific text to its corresponding UI styles + self.text_style_tag_map: Dict[str, str] = text_ui_style + + def get_css_style(self, tag: str) -> str: + style = self.html_tag_css_style_map.get(tag, "") + return style + + def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]: + lines = document.lines + + def get_line(lineno: int) -> StyleAndTextTuples: + "Return the tokens for the given line." + try: + current_line = lines[lineno] + + # Apply styling to command prompt + if current_line.startswith(self.PROMPT_TEXT): + return [(self.get_css_style("primary_label"), current_line)] + + matched_indexes: List[Tuple[int, int, str]] = [(match.start(), match.end(), style) + for special_word, style in self.text_style_tag_map.items() + for match in list(re.finditer(special_word, current_line)) + ] + if len(matched_indexes) == 0: + return [("", current_line)] + + previous_idx = 0 + line_fragments = [] + for start_idx, end_idx, style in matched_indexes: + line_fragments.extend([ + ("", current_line[previous_idx:start_idx]), + (self.get_css_style("output_pane"), current_line[start_idx:start_idx + 2]), + (self.get_css_style(style), current_line[start_idx + 2:end_idx]) + ]) + previous_idx = end_idx + + line_fragments.append(("", current_line[previous_idx:])) + + return line_fragments + except IndexError: + return [] + + return get_line + + +class CustomTextArea: + def __init__(self, text='', multiline=True, password=False, + lexer=None, auto_suggest=None, completer=None, + complete_while_typing=True, accept_handler=None, history=None, + focusable=True, focus_on_click=False, wrap_lines=True, + read_only=False, width=None, height=None, + dont_extend_height=False, dont_extend_width=False, + line_numbers=False, get_line_prefix=None, scrollbar=False, + style='', search_field=None, preview_search=True, prompt='', + input_processors=None, max_line_count=1000, initial_text="", align=WindowAlign.LEFT): + assert isinstance(text, six.text_type) + assert search_field is None or isinstance(search_field, SearchToolbar) + + if search_field is None: + search_control = None + elif isinstance(search_field, SearchToolbar): + search_control = search_field.control + + if input_processors is None: + input_processors = [] + + # Writeable attributes. + self.completer = completer + self.complete_while_typing = complete_while_typing + self.lexer = lexer + self.auto_suggest = auto_suggest + self.read_only = read_only + self.wrap_lines = wrap_lines + self.max_line_count = max_line_count + + self.buffer = CustomBuffer( + document=Document(text, 0), + multiline=multiline, + read_only=Condition(lambda: is_true(self.read_only)), + completer=DynamicCompleter(lambda: self.completer), + complete_while_typing=Condition( + lambda: is_true(self.complete_while_typing)), + auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), + accept_handler=accept_handler, + history=history) + + self.control = BufferControl( + buffer=self.buffer, + lexer=DynamicLexer(lambda: self.lexer), + input_processors=[ + ConditionalProcessor( + AppendAutoSuggestion(), + has_focus(self.buffer) & ~is_done), + ConditionalProcessor( + processor=PasswordProcessor(), + filter=to_filter(password) + ), + BeforeInput(prompt, style='class:text-area.prompt'), + ] + input_processors, + search_buffer_control=search_control, + preview_search=preview_search, + focusable=focusable, + focus_on_click=focus_on_click) + + if multiline: + if scrollbar: + right_margins = [ScrollbarMargin(display_arrows=True)] + else: + right_margins = [] + if line_numbers: + left_margins = [NumberedMargin()] + else: + left_margins = [] + else: + left_margins = [] + right_margins = [] + + style = 'class:text-area ' + style + + self.window = Window( + height=height, + width=width, + dont_extend_height=dont_extend_height, + dont_extend_width=dont_extend_width, + content=self.control, + style=style, + wrap_lines=Condition(lambda: is_true(self.wrap_lines)), + left_margins=left_margins, + right_margins=right_margins, + get_line_prefix=get_line_prefix, + align=align) + + self.log_lines: Deque[str] = deque() + self.log(initial_text) + + @property + def text(self): + """ + The `Buffer` text. + """ + return self.buffer.text + + @text.setter + def text(self, value): + self.buffer.set_document(Document(value, 0), bypass_readonly=True) + + @property + def document(self): + """ + The `Buffer` document (text + cursor position). + """ + return self.buffer.document + + @document.setter + def document(self, value): + self.buffer.document = value + + @property + def accept_handler(self): + """ + The accept handler. Called when the user accepts the input. + """ + return self.buffer.accept_handler + + @accept_handler.setter + def accept_handler(self, value): + self.buffer.accept_handler = value + + def __pt_container__(self): + return self.window + + def log(self, text: str, save_log: bool = True, silent: bool = False): + # Getting the max width of the window area + if self.window.render_info is None: + max_width = 100 + else: + max_width = self.window.render_info.window_width - 2 + + # remove simple formatting tags used by telegram + repls = (('', ''), ('', ''), ('
', ''), ('
', '')) + for r in repls: + text = text.replace(*r) + + # Split the string into multiple lines if there is a "\n" or if the string exceeds max window width + # This operation should not be too expensive because only the newly added lines are processed + new_lines_raw: List[str] = str(text).split('\n') + new_lines = [] + for line in new_lines_raw: + while len(line) > max_width: + new_lines.append(line[0:max_width]) + line = line[max_width:] + new_lines.append(line) + + if save_log: + self.log_lines.extend(new_lines) + while len(self.log_lines) > self.max_line_count: + self.log_lines.popleft() + new_text: str = "\n".join(self.log_lines) + else: + new_text: str = "\n".join(new_lines) + if not silent: + self.buffer.document = Document(text=new_text, cursor_position=len(new_text)) diff --git a/hummingbot/client/ui/hummingbot_cli.py b/hummingbot/client/ui/hummingbot_cli.py new file mode 100644 index 0000000..7bd5fff --- /dev/null +++ b/hummingbot/client/ui/hummingbot_cli.py @@ -0,0 +1,277 @@ +import asyncio +import logging +import threading +from contextlib import ExitStack +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union + +from prompt_toolkit.application import Application +from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard +from prompt_toolkit.completion import Completer +from prompt_toolkit.document import Document +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.processors import BeforeInput, PasswordProcessor + +from hummingbot import init_logging +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.tab.data_types import CommandTab +from hummingbot.client.ui.interface_utils import start_process_monitor, start_timer, start_trade_monitor +from hummingbot.client.ui.layout import ( + create_input_field, + create_live_field, + create_log_field, + create_log_toggle, + create_output_field, + create_process_monitor, + create_search_field, + create_tab_button, + create_timer, + create_trade_monitor, + generate_layout, +) +from hummingbot.client.ui.stdout_redirection import patch_stdout +from hummingbot.client.ui.style import load_style +from hummingbot.core.event.events import HummingbotUIEvent +from hummingbot.core.pubsub import PubSub +from hummingbot.core.utils.async_utils import safe_ensure_future + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +# Monkey patching here as _handle_exception gets the UI hanged into Press ENTER screen mode +def _handle_exception_patch(self, loop, context): + if "exception" in context: + logging.getLogger(__name__).error(f"Unhandled error in prompt_toolkit: {context.get('exception')}", + exc_info=True) + + +Application._handle_exception = _handle_exception_patch + + +class HummingbotCLI(PubSub): + def __init__(self, + client_config_map: ClientConfigAdapter, + input_handler: Callable, + bindings: KeyBindings, + completer: Completer, + command_tabs: Dict[str, CommandTab]): + super().__init__() + self.client_config_map: Union[ClientConfigAdapter, ClientConfigMap] = client_config_map + self.command_tabs = command_tabs + self.search_field = create_search_field() + self.input_field = create_input_field(completer=completer) + self.output_field = create_output_field(client_config_map) + self.log_field = create_log_field(self.search_field) + self.right_pane_toggle = create_log_toggle(self.toggle_right_pane) + self.live_field = create_live_field() + self.log_field_button = create_tab_button("Log-pane", self.log_button_clicked) + self.timer = create_timer() + self.process_usage = create_process_monitor() + self.trade_monitor = create_trade_monitor() + self.layout, self.layout_components = generate_layout(self.input_field, self.output_field, self.log_field, + self.right_pane_toggle, self.log_field_button, + self.search_field, self.timer, + self.process_usage, self.trade_monitor, + self.command_tabs) + # add self.to_stop_config to know if cancel is triggered + self.to_stop_config: bool = False + + self.live_updates = False + self.bindings = bindings + self.input_handler = input_handler + self.input_field.accept_handler = self.accept + self.app: Optional[Application] = None + + # settings + self.prompt_text = ">>> " + self.pending_input = None + self.input_event = None + self.hide_input = False + + # stdout redirection stack + self._stdout_redirect_context: ExitStack = ExitStack() + + # start ui tasks + loop = asyncio.get_event_loop() + loop.create_task(start_timer(self.timer)) + loop.create_task(start_process_monitor(self.process_usage)) + loop.create_task(start_trade_monitor(self.trade_monitor)) + + def did_start_ui(self): + self._stdout_redirect_context.enter_context(patch_stdout(log_field=self.log_field)) + + log_level = self.client_config_map.log_level + init_logging("hummingbot_logs.yml", self.client_config_map, override_log_level=log_level) + + self.trigger_event(HummingbotUIEvent.Start, self) + + async def run(self): + self.app = Application( + layout=self.layout, + full_screen=True, + key_bindings=self.bindings, + style=load_style(self.client_config_map), + mouse_support=True, + clipboard=PyperclipClipboard(), + ) + await self.app.run_async(pre_run=self.did_start_ui) + self._stdout_redirect_context.close() + + def accept(self, buff): + self.pending_input = self.input_field.text.strip() + + if self.input_event: + self.input_event.set() + + try: + if self.hide_input: + output = '' + else: + output = '\n>>> {}'.format(self.input_field.text,) + self.input_field.buffer.append_to_history() + except BaseException as e: + output = str(e) + + self.log(output) + self.input_handler(self.input_field.text) + + def clear_input(self): + self.pending_input = None + + def log(self, text: str, save_log: bool = True): + if save_log: + if self.live_updates: + self.output_field.log(text, silent=True) + else: + self.output_field.log(text) + else: + self.output_field.log(text, save_log=False) + + def change_prompt(self, prompt: str, is_password: bool = False): + self.prompt_text = prompt + processors = [] + if is_password: + processors.append(PasswordProcessor()) + processors.append(BeforeInput(prompt)) + self.input_field.control.input_processors = processors + + async def prompt(self, prompt: str, is_password: bool = False) -> str: + self.change_prompt(prompt, is_password) + self.app.invalidate() + self.input_event = asyncio.Event() + await self.input_event.wait() + + temp = self.pending_input + self.clear_input() + self.input_event = None + + if is_password: + masked_string = "*" * len(temp) + self.log(f"{prompt}{masked_string}") + else: + self.log(f"{prompt}{temp}") + return temp + + def set_text(self, new_text: str): + self.input_field.document = Document(text=new_text, cursor_position=len(new_text)) + + def toggle_hide_input(self): + self.hide_input = not self.hide_input + + def toggle_right_pane(self): + if self.layout_components["pane_right"].filter(): + self.layout_components["pane_right"].filter = lambda: False + self.layout_components["item_top_toggle"].text = '< log pane' + else: + self.layout_components["pane_right"].filter = lambda: True + self.layout_components["item_top_toggle"].text = '> log pane' + + def log_button_clicked(self): + for tab in self.command_tabs.values(): + tab.is_selected = False + self.redraw_app() + + def tab_button_clicked(self, command_name: str): + for tab in self.command_tabs.values(): + tab.is_selected = False + self.command_tabs[command_name].is_selected = True + self.redraw_app() + + def exit(self): + self.app.exit() + + def redraw_app(self): + self.layout, self.layout_components = generate_layout(self.input_field, self.output_field, self.log_field, + self.right_pane_toggle, self.log_field_button, + self.search_field, self.timer, + self.process_usage, self.trade_monitor, self.command_tabs) + self.app.layout = self.layout + self.app.invalidate() + + def tab_navigate_left(self): + selected_tabs = [t for t in self.command_tabs.values() if t.is_selected] + if not selected_tabs: + return + selected_tab: CommandTab = selected_tabs[0] + if selected_tab.tab_index == 1: + self.log_button_clicked() + else: + left_tab = [t for t in self.command_tabs.values() if t.tab_index == selected_tab.tab_index - 1][0] + self.tab_button_clicked(left_tab.name) + + def tab_navigate_right(self): + current_tabs = [t for t in self.command_tabs.values() if t.tab_index > 0] + if not current_tabs: + return + selected_tab = [t for t in current_tabs if t.is_selected] + if selected_tab: + right_tab = [t for t in current_tabs if t.tab_index == selected_tab[0].tab_index + 1] + else: + right_tab = [t for t in current_tabs if t.tab_index == 1] + if right_tab: + self.tab_button_clicked(right_tab[0].name) + + def close_buton_clicked(self, command_name: str): + self.command_tabs[command_name].button = None + self.command_tabs[command_name].close_button = None + self.command_tabs[command_name].output_field = None + self.command_tabs[command_name].is_selected = False + for tab in self.command_tabs.values(): + if tab.tab_index > self.command_tabs[command_name].tab_index: + tab.tab_index -= 1 + self.command_tabs[command_name].tab_index = 0 + if self.command_tabs[command_name].task is not None: + self.command_tabs[command_name].task.cancel() + self.command_tabs[command_name].task = None + self.redraw_app() + + def handle_tab_command(self, hummingbot: "HummingbotApplication", command_name: str, kwargs: Dict[str, Any]): + if command_name not in self.command_tabs: + return + cmd_tab = self.command_tabs[command_name] + if "close" in kwargs and kwargs["close"]: + if cmd_tab.close_button is not None: + self.close_buton_clicked(command_name) + return + if "close" in kwargs: + kwargs.pop("close") + if cmd_tab.button is None: + cmd_tab.button = create_tab_button(command_name, lambda: self.tab_button_clicked(command_name)) + cmd_tab.close_button = create_tab_button("x", lambda: self.close_buton_clicked(command_name), 1, '', ' ') + cmd_tab.output_field = create_live_field() + cmd_tab.tab_index = max(t.tab_index for t in self.command_tabs.values()) + 1 + self.tab_button_clicked(command_name) + self.display_tab_output(cmd_tab, hummingbot, kwargs) + + def display_tab_output(self, + command_tab: CommandTab, + hummingbot: "HummingbotApplication", + kwargs: Dict[Any, Any]): + if command_tab.task is not None and not command_tab.task.done(): + return + if threading.current_thread() != threading.main_thread(): + hummingbot.ev_loop.call_soon_threadsafe(self.display_tab_output, command_tab, hummingbot, kwargs) + return + command_tab.task = safe_ensure_future(command_tab.tab_class.display(command_tab.output_field, hummingbot, + **kwargs)) diff --git a/hummingbot/client/ui/interface_utils.py b/hummingbot/client/ui/interface_utils.py new file mode 100644 index 0000000..98ba7f4 --- /dev/null +++ b/hummingbot/client/ui/interface_utils.py @@ -0,0 +1,117 @@ +import asyncio +from decimal import Decimal +from typing import List, Optional, Set, Tuple + +import pandas as pd +import psutil +import tabulate + +from hummingbot.client.config.config_data_types import ClientConfigEnum +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.model.trade_fill import TradeFill + +s_decimal_0 = Decimal("0") + + +def format_bytes(size): + for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]: + if abs(size) < 1024.0: + return f"{size:.2f} {unit}" + size /= 1024.0 + return f"{size:.2f} YB" + + +async def start_timer(timer): + count = 1 + while True: + count += 1 + + mins, sec = divmod(count, 60) + hour, mins = divmod(mins, 60) + days, hour = divmod(hour, 24) + + timer.log(f"Uptime: {days:>3} day(s), {hour:02}:{mins:02}:{sec:02}") + await _sleep(1) + + +async def _sleep(delay): + """ + A wrapper function that facilitates patching the sleep in unit tests without affecting the asyncio module + """ + await asyncio.sleep(delay) + + +async def start_process_monitor(process_monitor): + hb_process = psutil.Process() + while True: + with hb_process.oneshot(): + threads = hb_process.num_threads() + process_monitor.log("CPU: {:>5}%, ".format(hb_process.cpu_percent()) + + "Mem: {:>10} ({}), ".format( + format_bytes(hb_process.memory_info().vms / threads), + format_bytes(hb_process.memory_info().rss)) + + "Threads: {:>3}, ".format(threads) + ) + await _sleep(1) + + +async def start_trade_monitor(trade_monitor): + from hummingbot.client.hummingbot_application import HummingbotApplication + hb = HummingbotApplication.main_application() + trade_monitor.log("Trades: 0, Total P&L: 0.00, Return %: 0.00%") + return_pcts = [] + pnls = [] + + while True: + try: + if hb.strategy_task is not None and not hb.strategy_task.done(): + if all(market.ready for market in hb.markets.values()): + with hb.trade_fill_db.get_new_session() as session: + trades: List[TradeFill] = hb._get_trades_from_session( + int(hb.init_time * 1e3), + session=session, + config_file_path=hb.strategy_file_name) + if len(trades) > 0: + market_info: Set[Tuple[str, str]] = set((t.market, t.symbol) for t in trades) + for market, symbol in market_info: + cur_trades = [t for t in trades if t.market == market and t.symbol == symbol] + cur_balances = await hb.get_current_balances(market) + perf = await PerformanceMetrics.create(symbol, cur_trades, cur_balances) + return_pcts.append(perf.return_pct) + pnls.append(perf.total_pnl) + avg_return = sum(return_pcts) / len(return_pcts) if len(return_pcts) > 0 else s_decimal_0 + quote_assets = set(t.symbol.split("-")[1] for t in trades) + if len(quote_assets) == 1: + total_pnls = f"{PerformanceMetrics.smart_round(sum(pnls))} {list(quote_assets)[0]}" + else: + total_pnls = "N/A" + trade_monitor.log(f"Trades: {len(trades)}, Total P&L: {total_pnls}, " + f"Return %: {avg_return:.2%}") + return_pcts.clear() + pnls.clear() + await _sleep(2) # sleeping for longer to manage resources + except asyncio.CancelledError: + raise + except Exception: + hb.logger().exception("start_trade_monitor failed.") + + +def format_df_for_printout( + df: pd.DataFrame, table_format: ClientConfigEnum, max_col_width: Optional[int] = None, index: bool = False +) -> str: + if max_col_width is not None: # in anticipation of the next release of tabulate which will include maxcolwidth + max_col_width = max(max_col_width, 4) + df = df.astype(str).apply( + lambda s: s.apply( + lambda e: e if len(e) < max_col_width else f"{e[:max_col_width - 3]}..." + ) + ) + df.columns = [c if len(c) < max_col_width else f"{c[:max_col_width - 3]}..." for c in df.columns] + + original_preserve_whitespace = tabulate.PRESERVE_WHITESPACE + tabulate.PRESERVE_WHITESPACE = True + try: + formatted_df = tabulate.tabulate(df, tablefmt=table_format, showindex=index, headers="keys") + finally: + tabulate.PRESERVE_WHITESPACE = original_preserve_whitespace + return formatted_df diff --git a/hummingbot/client/ui/keybindings.py b/hummingbot/client/ui/keybindings.py new file mode 100644 index 0000000..2c515e6 --- /dev/null +++ b/hummingbot/client/ui/keybindings.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import is_searching, to_filter +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.search import SearchDirection, do_incremental_search, start_search, stop_search + +from hummingbot.client.ui.scroll_handlers import scroll_down, scroll_up +from hummingbot.client.ui.style import reset_style +from hummingbot.core.utils.async_utils import safe_ensure_future + + +def load_key_bindings(hb) -> KeyBindings: + bindings = KeyBindings() + + @bindings.add("c-c", "c-c") + def exit_(event): + hb.app.log("\n[Double CTRL + C] keyboard exit") + safe_ensure_future(hb.exit_loop()) + + @bindings.add("c-x") + def stop_configuration(event): + hb.app.log("\n[CTRL + X] Exiting config...") + hb.app.to_stop_config = True + hb.app.pending_input = " " + hb.app.input_event.set() + hb.app.change_prompt(prompt=">>> ") + hb.placeholder_mode = False + hb.app.hide_input = False + + @bindings.add("c-s") + def status(event): + hb.app.log("\n[CTRL + S] Status") + hb.status() + + @bindings.add("c-f", filter=to_filter(not is_searching())) + def do_find(event): + start_search(hb.app.log_field.control) + + @bindings.add("c-f", filter=is_searching) + def do_exit_find(event): + stop_search() + get_app().layout.focus(hb.app.input_field.control) + get_app().invalidate() + + @bindings.add("c-z") + def do_undo(event): + get_app().layout.current_buffer.undo() + + @bindings.add("c-m", filter=is_searching) + def do_find_next(event): + do_incremental_search(direction=SearchDirection.FORWARD) + + @bindings.add("c-c") + def do_copy(event): + data = get_app().layout.current_buffer.copy_selection() + get_app().clipboard.set_data(data) + + @bindings.add("c-v") + def do_paste(event): + get_app().layout.current_buffer.paste_clipboard_data(get_app().clipboard.get_data()) + + @bindings.add("c-a") + def do_select_all(event): + current_buffer = get_app().layout.current_buffer + current_buffer.cursor_position = 0 + current_buffer.start_selection() + current_buffer.cursor_position = len(current_buffer.text) + + @bindings.add("c-d") + def scroll_down_output(event): + event.app.layout.current_window = hb.app.output_field.window + event.app.layout.focus = hb.app.output_field.buffer + scroll_down(event, hb.app.output_field.window, hb.app.output_field.buffer) + event.app.layout.current_window = hb.app.input_field.window + event.app.layout.focus = hb.app.input_field.buffer + + @bindings.add("c-e") + def scroll_up_output(event): + event.app.layout.current_window = hb.app.output_field.window + event.app.layout.focus = hb.app.output_field.buffer + scroll_up(event, hb.app.output_field.window, hb.app.output_field.buffer) + event.app.layout.current_window = hb.app.input_field.window + event.app.layout.focus = hb.app.input_field.buffer + + @bindings.add("escape") + def stop_live_update(event): + hb.app.live_updates = False + + @bindings.add("c-r") + def do_reset_style(event): + hb.app.app.style = reset_style(hb.client_config_map) + + @bindings.add("c-t") + def toggle_logs(event): + hb.app.toggle_right_pane() + + @bindings.add('c-b') + def do_tab_navigate_left(event): + hb.app.tab_navigate_left() + + @bindings.add('c-n') + def do_tab_navigate_right(event): + hb.app.tab_navigate_right() + + return bindings diff --git a/hummingbot/client/ui/layout.py b/hummingbot/client/ui/layout.py new file mode 100644 index 0000000..5ebe80b --- /dev/null +++ b/hummingbot/client/ui/layout.py @@ -0,0 +1,282 @@ +from os.path import dirname, join, realpath +from typing import Dict + +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.completion import Completer +from prompt_toolkit.layout import Dimension +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + FloatContainer, + HSplit, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu +from prompt_toolkit.widgets import Box, Button, SearchToolbar + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.settings import MAXIMUM_LOG_PANE_LINE_COUNT, MAXIMUM_OUTPUT_PANE_LINE_COUNT +from hummingbot.client.tab.data_types import CommandTab +from hummingbot.client.ui.custom_widgets import CustomTextArea as TextArea, FormattedTextLexer + +HEADER = """ + *,. + *,,,* + ,,,,,,, * + ,,,,,,,, ,,,, + *,,,,,,,,( .,,,,,, + /,,,,,,,,,, .*,,,,,,,, + .,,,,,,,,,,,. ,,,,,,,,,,,* + ,,,,,,,,,,,,,,,,,,,,,,,,,,, + // ,,,,,,,,,,,,,,,,,,,,,,,,,,,,#*% + .,,,,,,,,. *,,,,,,,,,,,,,,,,,,,,,,,,,,,%%%%%%&@ + ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%%%%%%%& + ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%%%%%%%& + /*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,(((((%%& + **. #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,((((((((((#. + ** *,,,,,,,,,,,,,,,,,,,,,,,,**/(((((((((((((* + ,,,,,,,,,,,,,,,,,,,,*********(((((((((((( + ,,,,,,,,,,,,,,,**************((((((((@ + (,,,,,,,,,,,,,,,***************(# + *,,,,,,,,,,,,,,,,**************/ + ,,,,,,,,,,,,,,,***************/ + ,,,,,,,,,,,,,,**************** + .,,,,,,,,,,,,**************/ + ,,,,,,,,*******, + *,,,,,,,,******** + ,,,,,,,,,/******/ + ,,,,,,,,,@ /****/ + ,,,,,,,, + , */ + + +██ ██ ██ ██ ███ ███ ███ ███ ██ ███ ██ ██████ ██████ ██████ ████████ +██ ██ ██ ██ ████ ████ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███ ██████ ██ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██████ ██ ██ ██ ██ ██ ██ ████ ██████ ██████ ██████ ██ + +====================================================================================== +Hummingbot is an open source software client that helps you build and run +market making, arbitrage, and other high-frequency trading bots. + +- Official repo: https://github.com/hummingbot/hummingbot +- Join the community: https://discord.gg/hummingbot +- Learn market making: https://hummingbot.org/botcamp + +Useful Commands: +- connect List available exchanges and add API keys to them +- balance See your exchange balances +- start Start a script or strategy +- help List all commands + +""" + +with open(realpath(join(dirname(__file__), '../../VERSION'))) as version_file: + version = version_file.read().strip() + + +def create_input_field(lexer=None, completer: Completer = None): + return TextArea( + height=10, + prompt='>>> ', + style='class:input_field', + multiline=False, + focus_on_click=True, + lexer=lexer, + auto_suggest=AutoSuggestFromHistory(), + completer=completer, + complete_while_typing=True, + ) + + +def create_output_field(client_config_map: ClientConfigAdapter): + return TextArea( + style='class:output_field', + focus_on_click=False, + read_only=False, + scrollbar=True, + max_line_count=MAXIMUM_OUTPUT_PANE_LINE_COUNT, + initial_text=HEADER, + lexer=FormattedTextLexer(client_config_map) + ) + + +def create_timer(): + return TextArea( + style='class:footer', + focus_on_click=False, + read_only=False, + scrollbar=False, + max_line_count=1, + width=30, + ) + + +def create_process_monitor(): + return TextArea( + style='class:footer', + focus_on_click=False, + read_only=False, + scrollbar=False, + max_line_count=1, + align=WindowAlign.RIGHT + ) + + +def create_trade_monitor(): + return TextArea( + style='class:footer', + focus_on_click=False, + read_only=False, + scrollbar=False, + max_line_count=1, + ) + + +def create_search_field() -> SearchToolbar: + return SearchToolbar(text_if_not_searching=[('class:primary', "[CTRL + F] to start searching.")], + forward_search_prompt=[('class:primary', "Search logs [Press CTRL + F to hide search] >>> ")], + ignore_case=True) + + +def create_log_field(search_field: SearchToolbar): + return TextArea( + style='class:log_field', + text="Running Logs\n", + focus_on_click=False, + read_only=False, + scrollbar=True, + max_line_count=MAXIMUM_LOG_PANE_LINE_COUNT, + initial_text="Running Logs \n", + search_field=search_field, + preview_search=False, + ) + + +def create_live_field(): + return TextArea( + style='class:log_field', + focus_on_click=False, + read_only=False, + scrollbar=True, + max_line_count=MAXIMUM_OUTPUT_PANE_LINE_COUNT, + ) + + +def create_log_toggle(function): + return Button( + text='> log pane', + width=13, + handler=function, + left_symbol='', + right_symbol='', + ) + + +def create_tab_button(text, function, margin=2, left_symbol=' ', right_symbol=' '): + return Button( + text=text, + width=len(text) + margin, + handler=function, + left_symbol=left_symbol, + right_symbol=right_symbol + ) + + +def get_version(): + return [("class:header", f"Version: {version}")] + + +def get_active_strategy(): + from hummingbot.client.hummingbot_application import HummingbotApplication + hb = HummingbotApplication.main_application() + style = "class:log_field" + return [(style, f"Strategy: {hb.strategy_name}")] + + +def get_strategy_file(): + from hummingbot.client.hummingbot_application import HummingbotApplication + hb = HummingbotApplication.main_application() + style = "class:log_field" + return [(style, f"Strategy File: {hb._strategy_file_name}")] + + +def get_gateway_status(): + from hummingbot.client.hummingbot_application import HummingbotApplication + hb = HummingbotApplication.main_application() + gateway_status = hb._gateway_monitor.gateway_status.name + style = "class:log_field" + return [(style, f"Gateway: {gateway_status}")] + + +def generate_layout(input_field: TextArea, + output_field: TextArea, + log_field: TextArea, + right_pane_toggle: Button, + log_field_button: Button, + search_field: SearchToolbar, + timer: TextArea, + process_monitor: TextArea, + trade_monitor: TextArea, + command_tabs: Dict[str, CommandTab], + ): + components = {} + + components["item_top_version"] = Window(FormattedTextControl(get_version), style="class:header") + components["item_top_active"] = Window(FormattedTextControl(get_active_strategy), style="class:header") + components["item_top_file"] = Window(FormattedTextControl(get_strategy_file), style="class:header") + components["item_top_gateway"] = Window(FormattedTextControl(get_gateway_status), style="class:header") + components["item_top_toggle"] = right_pane_toggle + components["pane_top"] = VSplit([components["item_top_version"], + components["item_top_active"], + components["item_top_file"], + components["item_top_gateway"], + components["item_top_toggle"]], height=1) + components["pane_bottom"] = VSplit([trade_monitor, + process_monitor, + timer], height=1) + output_pane = Box(body=output_field, padding=0, padding_left=2, style="class:output_field") + input_pane = Box(body=input_field, padding=0, padding_left=2, padding_top=1, style="class:input_field") + components["pane_left"] = HSplit([output_pane, input_pane], width=Dimension(weight=1)) + if all(not t.is_selected for t in command_tabs.values()): + log_field_button.window.style = "class:tab_button.focused" + else: + log_field_button.window.style = "class:tab_button" + tab_buttons = [log_field_button] + for tab in sorted(command_tabs.values(), key=lambda x: x.tab_index): + if tab.button is not None: + if tab.is_selected: + tab.button.window.style = "class:tab_button.focused" + else: + tab.button.window.style = "class:tab_button" + tab.close_button.window.style = tab.button.window.style + tab_buttons.append(VSplit([tab.button, tab.close_button])) + pane_right_field = log_field + focused_right_field = [tab.output_field for tab in command_tabs.values() if tab.is_selected] + if focused_right_field: + pane_right_field = focused_right_field[0] + components["pane_right_top"] = VSplit(tab_buttons, height=1, style="class:log_field", padding_char=" ", padding=2) + components["pane_right"] = ConditionalContainer( + Box(body=HSplit([components["pane_right_top"], pane_right_field, search_field], width=Dimension(weight=1)), + padding=0, padding_left=2, style="class:log_field"), + filter=True + ) + components["hint_menus"] = [Float(xcursor=True, + ycursor=True, + transparent=True, + content=CompletionsMenu(max_height=16, + scroll_offset=1))] + + root_container = HSplit([ + components["pane_top"], + VSplit( + [FloatContainer(components["pane_left"], components["hint_menus"]), + components["pane_right"]]), + components["pane_bottom"], + ]) + return Layout(root_container, focused_element=input_field), components diff --git a/hummingbot/client/ui/parser.py b/hummingbot/client/ui/parser.py new file mode 100644 index 0000000..c31d91b --- /dev/null +++ b/hummingbot/client/ui/parser.py @@ -0,0 +1,200 @@ +import argparse +from typing import TYPE_CHECKING, Any, List + +from hummingbot.client.command.connect_command import OPTIONS as CONNECT_OPTIONS +from hummingbot.exceptions import ArgumentParserError + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +class ThrowingArgumentParser(argparse.ArgumentParser): + def error(self, message): + raise ArgumentParserError(message) + + def exit(self, status=0, message=None): + pass + + def print_help(self, file=None): + pass + + @property + def subparser_action(self): + for action in self._actions: + if isinstance(action, argparse._SubParsersAction): + return action + + @property + def commands(self) -> List[str]: + return list(self.subparser_action._name_parser_map.keys()) + + def subcommands_from(self, top_level_command: str) -> List[str]: + parser: argparse.ArgumentParser = self.subparser_action._name_parser_map.get(top_level_command) + if parser is None: + return [] + subcommands = parser._optionals._option_string_actions.keys() + filtered = list(filter(lambda sub: sub.startswith("--"), subcommands)) + return filtered + + +def load_parser(hummingbot: "HummingbotApplication", command_tabs) -> [ThrowingArgumentParser, Any]: + parser = ThrowingArgumentParser(prog="", add_help=False) + subparsers = parser.add_subparsers() + + connect_parser = subparsers.add_parser("connect", help="List available exchanges and add API keys to them") + connect_parser.add_argument("option", nargs="?", choices=CONNECT_OPTIONS, help="Name of the exchange that you want to connect") + connect_parser.set_defaults(func=hummingbot.connect) + + create_parser = subparsers.add_parser("create", help="Create a new bot") + create_parser.add_argument("file_name", nargs="?", default=None, help="Name of the configuration file") + create_parser.set_defaults(func=hummingbot.create) + + import_parser = subparsers.add_parser("import", help="Import an existing bot by loading the configuration file") + import_parser.add_argument("file_name", nargs="?", default=None, help="Name of the configuration file") + import_parser.set_defaults(func=hummingbot.import_command) + + help_parser = subparsers.add_parser("help", help="List available commands") + help_parser.add_argument("command", nargs="?", default="all", help="Enter ") + help_parser.set_defaults(func=hummingbot.help) + + balance_parser = subparsers.add_parser("balance", help="Display your asset balances across all connected exchanges") + balance_parser.add_argument("option", nargs="?", choices=["limit", "paper"], default=None, + help="Option for balance configuration") + balance_parser.add_argument("args", nargs="*") + balance_parser.set_defaults(func=hummingbot.balance) + + config_parser = subparsers.add_parser("config", help="Display the current bot's configuration") + config_parser.add_argument("key", nargs="?", default=None, help="Name of the parameter you want to change") + config_parser.add_argument("value", nargs="?", default=None, help="New value for the parameter") + config_parser.set_defaults(func=hummingbot.config) + + start_parser = subparsers.add_parser("start", help="Start the current bot") + # start_parser.add_argument("--log-level", help="Level of logging") + start_parser.add_argument("--script", type=str, dest="script", help="Script strategy file name") + + start_parser.set_defaults(func=hummingbot.start) + + stop_parser = subparsers.add_parser('stop', help="Stop the current bot") + stop_parser.set_defaults(func=hummingbot.stop) + + status_parser = subparsers.add_parser("status", help="Get the market status of the current bot") + status_parser.add_argument("--live", default=False, action="store_true", dest="live", help="Show status updates") + status_parser.set_defaults(func=hummingbot.status) + + history_parser = subparsers.add_parser("history", help="See the past performance of the current bot") + history_parser.add_argument("-d", "--days", type=float, default=0, dest="days", + help="How many days in the past (can be decimal value)") + history_parser.add_argument("-v", "--verbose", action="store_true", default=False, + dest="verbose", help="List all trades") + history_parser.add_argument("-p", "--precision", default=None, type=int, + dest="precision", help="Level of precions for values displayed") + history_parser.set_defaults(func=hummingbot.history) + + gateway_parser = subparsers.add_parser("gateway", help="Helper comands for Gateway server.") + gateway_subparsers = gateway_parser.add_subparsers() + + gateway_balance_parser = gateway_subparsers.add_parser("balance", help="Display your asset balances and allowances across all connected gateway connectors") + gateway_balance_parser.set_defaults(func=hummingbot.gateway_balance) + + gateway_config_parser = gateway_subparsers.add_parser("config", help="View or update gateway configuration") + gateway_config_parser.add_argument("key", nargs="?", default=None, help="Name of the parameter you want to view/change") + gateway_config_parser.add_argument("value", nargs="?", default=None, help="New value for the parameter") + gateway_config_parser.set_defaults(func=hummingbot.gateway_config) + + gateway_connect_parser = gateway_subparsers.add_parser("connect", help="Create/view connection info for gateway connector") + gateway_connect_parser.add_argument("connector", nargs="?", default=None, help="Name of connector you want to create a profile for") + gateway_connect_parser.set_defaults(func=hummingbot.gateway_connect) + + gateway_connector_tokens_parser = gateway_subparsers.add_parser("connector-tokens", help="Report token balances for gateway connectors") + gateway_connector_tokens_parser.add_argument("connector_chain_network", nargs="?", default=None, help="Name of connector you want to edit reported tokens for") + gateway_connector_tokens_parser.add_argument("new_tokens", nargs="?", default=None, help="Report balance of these tokens - separate multiple tokens with commas (,)") + gateway_connector_tokens_parser.set_defaults(func=hummingbot.gateway_connector_tokens) + + gateway_approve_tokens_parser = gateway_subparsers.add_parser("approve-tokens", help="Approve tokens for gateway connectors") + gateway_approve_tokens_parser.add_argument("connector_chain_network", nargs="?", default=None, help="Name of connector you want to approve tokens for") + gateway_approve_tokens_parser.add_argument("tokens", nargs="?", default=None, help="Approve these tokens") + gateway_approve_tokens_parser.set_defaults(func=hummingbot.gateway_approve_tokens) + + gateway_cert_parser = gateway_subparsers.add_parser("generate-certs", help="Create ssl certifcate for gateway") + gateway_cert_parser.set_defaults(func=hummingbot.generate_certs) + + gateway_list_parser = gateway_subparsers.add_parser("list", help="List gateway connectors and chains and tiers") + gateway_list_parser.set_defaults(func=hummingbot.gateway_list) + + gateway_test_parser = gateway_subparsers.add_parser("test-connection", help="Ping gateway api server") + gateway_test_parser.set_defaults(func=hummingbot.test_connection) + + exit_parser = subparsers.add_parser("exit", help="Exit and cancel all outstanding orders") + exit_parser.add_argument("-f", "--force", action="store_true", help="Force exit without canceling outstanding orders", + default=False) + exit_parser.set_defaults(func=hummingbot.exit) + + export_parser = subparsers.add_parser("export", help="Export secure information") + export_parser.add_argument("option", nargs="?", choices=("keys", "trades"), help="Export choices") + export_parser.set_defaults(func=hummingbot.export) + + ticker_parser = subparsers.add_parser("ticker", help="Show market ticker of current order book") + ticker_parser.add_argument("--live", default=False, action="store_true", dest="live", help="Show ticker updates") + ticker_parser.add_argument("--exchange", type=str, dest="exchange", help="The exchange of the market") + ticker_parser.add_argument("--market", type=str, dest="market", help="The market (trading pair) of the order book") + ticker_parser.set_defaults(func=hummingbot.ticker) + + pmm_script_parser = subparsers.add_parser("pmm_script", help="Send command to running PMM script instance") + pmm_script_parser.add_argument("cmd", nargs="?", default=None, help="Command") + pmm_script_parser.add_argument("args", nargs="*", default=None, help="Arguments") + pmm_script_parser.set_defaults(func=hummingbot.pmm_script_command) + + previous_strategy_parser = subparsers.add_parser("previous", help="Imports the last strategy used") + previous_strategy_parser.add_argument("option", nargs="?", choices=["Yes,No"], default=None) + previous_strategy_parser.set_defaults(func=hummingbot.previous_strategy) + + mqtt_parser = subparsers.add_parser("mqtt", help="Manage MQTT Bridge to Message brokers") + mqtt_subparsers = mqtt_parser.add_subparsers() + mqtt_start_parser = mqtt_subparsers.add_parser("start", help="Start the MQTT Bridge") + mqtt_start_parser.add_argument( + "-t", + "--timeout", + default=30.0, + type=float, + dest="timeout", + help="Bridge connection timeout" + ) + mqtt_start_parser.set_defaults(func=hummingbot.mqtt_start) + mqtt_stop_parser = mqtt_subparsers.add_parser("stop", help="Stop the MQTT Bridge") + mqtt_stop_parser.set_defaults(func=hummingbot.mqtt_stop) + mqtt_restart_parser = mqtt_subparsers.add_parser("restart", help="Restart the MQTT Bridge") + mqtt_restart_parser.add_argument( + "-t", + "--timeout", + default=30.0, + type=float, + dest="timeout", + help="Bridge connection timeout" + ) + mqtt_restart_parser.set_defaults(func=hummingbot.mqtt_restart) + + # add shortcuts so they appear in command help + shortcuts = hummingbot.client_config_map.command_shortcuts + for shortcut in shortcuts: + help_str = shortcut.help + command = shortcut.command + shortcut_parser = subparsers.add_parser(command, help=help_str) + args = shortcut.arguments + for i in range(len(args)): + shortcut_parser.add_argument(f'${i+1}', help=args[i]) + + rate_parser = subparsers.add_parser('rate', help="Show rate of a given trading pair") + rate_parser.add_argument("-p", "--pair", default=None, + dest="pair", help="The market trading pair for which you want to get a rate.") + rate_parser.add_argument("-t", "--token", default=None, + dest="token", help="The token who's value you want to get.") + rate_parser.set_defaults(func=hummingbot.rate) + + for name, command_tab in command_tabs.items(): + o_parser = subparsers.add_parser(name, help=command_tab.tab_class.get_command_help_message()) + for arg_name, arg_properties in command_tab.tab_class.get_command_arguments().items(): + o_parser.add_argument(arg_name, **arg_properties) + o_parser.add_argument("-c", "--close", default=False, action="store_true", dest="close", + help=f"To close the {name} tab.") + + return parser diff --git a/hummingbot/client/ui/scroll_handlers.py b/hummingbot/client/ui/scroll_handlers.py new file mode 100644 index 0000000..ba23474 --- /dev/null +++ b/hummingbot/client/ui/scroll_handlers.py @@ -0,0 +1,54 @@ +from prompt_toolkit.layout.containers import Window +from prompt_toolkit.buffer import Buffer +from typing import Optional + + +def scroll_down(event, window: Optional[Window] = None, buffer: Optional[Buffer] = None): + w = window or event.app.layout.current_window + b = buffer or event.app.current_buffer + + if w and w.render_info: + info = w.render_info + ui_content = info.ui_content + + # Height to scroll. + scroll_height = info.window_height // 2 + + # Calculate how many lines is equivalent to that vertical space. + y = b.document.cursor_position_row + 1 + height = 0 + while y < ui_content.line_count: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y += 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) + + +def scroll_up(event, window: Optional[Window] = None, buffer: Optional[Buffer] = None): + w = window or event.app.layout.current_window + b = buffer or event.app.current_buffer + + if w and w.render_info: + info = w.render_info + + # Height to scroll. + scroll_height = info.window_height // 2 + + # Calculate how many lines is equivalent to that vertical space. + y = max(0, b.document.cursor_position_row - 1) + height = 0 + while y > 0: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y -= 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) diff --git a/hummingbot/client/ui/stdout_redirection.py b/hummingbot/client/ui/stdout_redirection.py new file mode 100644 index 0000000..3a74dc5 --- /dev/null +++ b/hummingbot/client/ui/stdout_redirection.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +from __future__ import unicode_literals +from asyncio import get_event_loop + +from contextlib import contextmanager +import threading +import sys + +__all__ = [ + 'patch_stdout', + 'StdoutProxy', +] + + +@contextmanager +def patch_stdout(raw=False, log_field=None): + proxy = StdoutProxy(raw=raw, log_field=log_field) + + original_stdout = sys.stdout + original_stderr = sys.stderr + + # Enter. + sys.stdout = proxy + sys.stderr = proxy + + try: + yield + finally: + # Exit. + proxy.flush() + + sys.stdout = original_stdout + sys.stderr = original_stderr + + +class StdoutProxy(object): + """ + Proxy object for stdout which captures everything and prints output inside + the current application. + """ + def __init__(self, raw=False, original_stdout=None, log_field=None): + assert isinstance(raw, bool) + original_stdout = original_stdout or sys.__stdout__ + + self.original_stdout = original_stdout + + self._lock = threading.RLock() + self._raw = raw + self._buffer = [] + + self.errors = original_stdout.errors + self.encoding = original_stdout.encoding + self.log_field = log_field + self._ev_loop = get_event_loop() + + def _write_and_flush(self, text): + if not text: + return + + def write_and_flush(): + self.log_field.log(text) + + def schedule_write_and_flush(): + self._ev_loop.run_in_executor(None, write_and_flush) + + self._ev_loop.call_soon_threadsafe(schedule_write_and_flush) + + def _write(self, data): + if '\n' in data: + # When there is a newline in the data, write everything before the newline, including the newline itself. + before, after = data.rsplit('\n', 1) + to_write = self._buffer + [before, '\n'] + self._buffer = [after] + + text = ''.join(to_write) + self._write_and_flush(text) + else: + # Otherwise, cache in buffer. + self._buffer.append(data) + + def _flush(self): + text = ''.join(self._buffer) + self._buffer = [] + self._write_and_flush(text) + + def write(self, data): + with self._lock: + self._write(data) + + def flush(self): + """ + Flush buffered output. + """ + with self._lock: + self._flush() + + @staticmethod + def isatty() -> bool: + return False + + def fileno(self) -> int: + return self.original_stdout.fileno() diff --git a/hummingbot/client/ui/style.py b/hummingbot/client/ui/style.py new file mode 100644 index 0000000..4fa0b32 --- /dev/null +++ b/hummingbot/client/ui/style.py @@ -0,0 +1,228 @@ +from typing import Union + +from prompt_toolkit.styles import Style +from prompt_toolkit.utils import is_windows + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, save_to_yml +from hummingbot.client.settings import CLIENT_CONFIG_PATH + + +def load_style(config_map: ClientConfigAdapter): + """ + Return a dict mapping {ui_style_name -> style_dict}. + """ + config_map: Union[ClientConfigAdapter, ClientConfigMap] = config_map # to enable IDE auto-complete + # Load config + color_top_pane = config_map.color.top_pane + color_bottom_pane = config_map.color.bottom_pane + color_output_pane = config_map.color.output_pane + color_input_pane = config_map.color.input_pane + color_logs_pane = config_map.color.logs_pane + color_terminal_primary = config_map.color.terminal_primary + + color_primary_label = config_map.color.primary_label + color_secondary_label = config_map.color.secondary_label + color_success_label = config_map.color.success_label + color_warning_label = config_map.color.warning_label + color_info_label = config_map.color.info_label + color_error_label = config_map.color.error_label + color_gold_label = config_map.color.gold_label + color_silver_label = config_map.color.silver_label + color_bronze_label = config_map.color.bronze_label + + if is_windows(): + # Load default style for Windows + style = win32_code_style + + # Translate HEX to ANSI + color_top_pane = hex_to_ansi(color_top_pane) + color_bottom_pane = hex_to_ansi(color_bottom_pane) + color_output_pane = hex_to_ansi(color_output_pane) + color_input_pane = hex_to_ansi(color_input_pane) + color_logs_pane = hex_to_ansi(color_logs_pane) + color_terminal_primary = hex_to_ansi(color_terminal_primary) + + color_primary_label = hex_to_ansi(color_primary_label) + color_secondary_label = hex_to_ansi(color_secondary_label) + color_success_label = hex_to_ansi(color_success_label) + color_warning_label = hex_to_ansi(color_warning_label) + color_info_label = hex_to_ansi(color_info_label) + color_error_label = hex_to_ansi(color_error_label) + color_gold_label = hex_to_ansi(color_gold_label) + color_silver_label = hex_to_ansi(color_silver_label) + color_bronze_label = hex_to_ansi(color_bronze_label) + + # Apply custom configuration + style["output_field"] = "bg:" + color_output_pane + " " + color_terminal_primary + style["input_field"] = "bg:" + color_input_pane + " " + style["input_field"].split(' ')[-1] + style["log_field"] = "bg:" + color_logs_pane + " " + style["log_field"].split(' ')[-1] + style["tab_button.focused"] = "bg:" + color_terminal_primary + " " + color_logs_pane + style["tab_button"] = style["tab_button"].split(' ')[0] + " " + color_logs_pane + style["header"] = "bg:" + color_top_pane + " " + style["header"].split(' ')[-1] + style["footer"] = "bg:" + color_bottom_pane + " " + style["footer"].split(' ')[-1] + style["primary"] = color_terminal_primary + style["dialog.body"] = style["dialog.body"].split(' ')[0] + " " + color_terminal_primary + style["dialog frame.label"] = "bg:" + color_terminal_primary + " " + style["dialog frame.label"].split(' ')[-1] + style["text-area"] = style["text-area"].split(' ')[0] + " " + color_terminal_primary + style["search"] = color_terminal_primary + style["search.current"] = color_terminal_primary + + style["primary_label"] = "bg:" + color_primary_label + " " + color_output_pane + style["secondary_label"] = "bg:" + color_secondary_label + " " + color_output_pane + style["success_label"] = "bg:" + color_success_label + " " + color_output_pane + style["warning_label"] = "bg:" + color_warning_label + " " + color_output_pane + style["info_label"] = "bg:" + color_info_label + " " + color_output_pane + style["error_label"] = "bg:" + color_error_label + " " + color_output_pane + style["gold_label"] = "bg:" + color_output_pane + " " + color_gold_label + style["silver_label"] = "bg:" + color_output_pane + " " + color_silver_label + style["bronze_label"] = "bg:" + color_output_pane + " " + color_bronze_label + + return Style.from_dict(style) + + else: + # Load default style + style = default_ui_style + + # Apply custom configuration + style["output_field"] = "bg:" + color_output_pane + " " + color_terminal_primary + style["input_field"] = "bg:" + color_input_pane + " " + style["input_field"].split(' ')[-1] + style["log_field"] = "bg:" + color_logs_pane + " " + style["log_field"].split(' ')[-1] + style["header"] = "bg:" + color_top_pane + " " + style["header"].split(' ')[-1] + style["footer"] = "bg:" + color_bottom_pane + " " + style["footer"].split(' ')[-1] + style["primary"] = color_terminal_primary + style["dialog.body"] = style["dialog.body"].split(' ')[0] + " " + color_terminal_primary + style["dialog frame.label"] = "bg:" + color_terminal_primary + " " + style["dialog frame.label"].split(' ')[-1] + style["text-area"] = style["text-area"].split(' ')[0] + " " + color_terminal_primary + style["tab_button.focused"] = "bg:" + color_terminal_primary + " " + color_logs_pane + style["tab_button"] = style["tab_button"].split(' ')[0] + " " + color_logs_pane + + style["primary_label"] = "bg:" + color_primary_label + " " + color_output_pane + style["secondary_label"] = "bg:" + color_secondary_label + " " + color_output_pane + style["success_label"] = "bg:" + color_success_label + " " + color_output_pane + style["warning_label"] = "bg:" + color_warning_label + " " + color_output_pane + style["info_label"] = "bg:" + color_info_label + " " + color_output_pane + style["error_label"] = "bg:" + color_error_label + " " + color_output_pane + style["gold_label"] = "bg:" + color_output_pane + " " + color_gold_label + style["silver_label"] = "bg:" + color_output_pane + " " + color_silver_label + style["bronze_label"] = "bg:" + color_output_pane + " " + color_bronze_label + return Style.from_dict(style) + + +def reset_style(config_map: ClientConfigAdapter, save=True): + # Reset config + + config_map.color.top_pane = config_map.color.get_default("top_pane") + config_map.color.bottom_pane = config_map.color.get_default("bottom_pane") + config_map.color.output_pane = config_map.color.get_default("output_pane") + config_map.color.input_pane = config_map.color.get_default("input_pane") + config_map.color.logs_pane = config_map.color.get_default("logs_pane") + config_map.color.terminal_primary = config_map.color.get_default("terminal_primary") + + config_map.color.primary_label = config_map.color.get_default("primary_label") + config_map.color.secondary_label = config_map.color.get_default("secondary_label") + config_map.color.success_label = config_map.color.get_default("success_label") + config_map.color.warning_label = config_map.color.get_default("warning_label") + config_map.color.info_label = config_map.color.get_default("info_label") + config_map.color.error_label = config_map.color.get_default("error_label") + config_map.color.gold_label = config_map.color.get_default("gold_label") + config_map.color.silver_label = config_map.color.get_default("silver_label") + config_map.color.bronze_label = config_map.color.get_default("bronze_label") + + # Save configuration + if save: + save_to_yml(CLIENT_CONFIG_PATH, config_map) + + # Apply & return style + return load_style(config_map) + + +def hex_to_ansi(color_hex): + ansi_palette = {"000000": "ansiblack", + "FF0000": "ansired", + "00FF00": "ansigreen", + "FFFF00": "ansiyellow", + "0000FF": "ansiblue", + "FF00FF": "ansimagenta", + "00FFFF": "ansicyan", + "F0F0F0": "ansigray", + "FFFFFF": "ansiwhite", + "FFD700": "ansiyellow", + "C0C0C0": "ansilightgray", + "CD7F32": "ansibrown" + } + + # Sanitization + color_hex = color_hex.replace('#', '') + + # Calculate distance, choose the closest ANSI color + hex_r = int(color_hex[0:2], 16) + hex_g = int(color_hex[2:4], 16) + hex_b = int(color_hex[4:6], 16) + + distance_min = None + + for ansi_hex in ansi_palette: + ansi_r = int(ansi_hex[0:2], 16) + ansi_g = int(ansi_hex[2:4], 16) + ansi_b = int(ansi_hex[4:6], 16) + + distance = abs(ansi_r - hex_r) + abs(ansi_g - hex_g) + abs(ansi_b - hex_b) + + if distance_min is None or distance < distance_min: + distance_min = distance + color_ansi = ansi_palette[ansi_hex] + + return "#" + color_ansi + + +text_ui_style = { + "&cGOLD": "gold_label", + "&cSILVER": "silver_label", + "&cBRONZE": "bronze_label", +} + +default_ui_style = { + "output_field": "bg:#171E2B #1CD085", # noqa: E241 + "input_field": "bg:#000000 #FFFFFF", # noqa: E241 + "log_field": "bg:#171E2B #FFFFFF", # noqa: E241 + "header": "bg:#000000 #AAAAAA", # noqa: E241 + "footer": "bg:#000000 #AAAAAA", # noqa: E241 + "search": "bg:#000000 #93C36D", # noqa: E241 + "search.current": "bg:#000000 #1CD085", # noqa: E241 + "primary": "#1CD085", # noqa: E241 + "warning": "#93C36D", # noqa: E241 + "error": "#F5634A", # noqa: E241 + "tab_button.focused": "bg:#1CD085 #171E2B", # noqa: E241 + "tab_button": "bg:#FFFFFF #000000", # noqa: E241 + "dialog": "bg:#171E2B", + "dialog frame.label": "bg:#FFFFFF #000000", + "dialog.body": "bg:#000000 ", + "dialog shadow": "bg:#171E2B", + "button": "bg:#000000", + "text-area": "bg:#000000 #FFFFFF", +} + + +# Style for an older version of Windows consoles. They support only 16 colors, +# so we choose a combination that displays nicely. +win32_code_style = { + "output_field": "#ansigreen", # noqa: E241 + "input_field": "#ansiwhite", # noqa: E241 + "log_field": "#ansiwhite", # noqa: E241 + "header": "#ansiwhite", # noqa: E241 + "footer": "#ansiwhite", # noqa: E241 + "search": "#ansigreen", # noqa: E241 + "search.current": "#ansigreen", # noqa: E241 + "primary": "#ansigreen", # noqa: E241 + "warning": "#ansibrightyellow", # noqa: E241 + "error": "#ansired", # noqa: E241 + "tab_button.focused": "bg:#ansigreen #ansiblack", # noqa: E241 + "tab_button": "bg:#ansiwhite #ansiblack", # noqa: E241 + "dialog": "bg:#ansigreen", + "dialog frame.label": "bg:#ansiwhite #ansiblack", + "dialog.body": "bg:#ansiblack ", + "dialog shadow": "bg:#ansigreen", + "button": "bg:#ansigreen", + "text-area": "bg:#ansiblack #ansigreen", +} diff --git a/hummingbot/connector/__init__.py b/hummingbot/connector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/budget_checker.py b/hummingbot/connector/budget_checker.py new file mode 100644 index 0000000..9f19db0 --- /dev/null +++ b/hummingbot/connector/budget_checker.py @@ -0,0 +1,161 @@ +import typing +from collections import defaultdict +from copy import copy +from decimal import Decimal +from typing import Dict, List + +from hummingbot.core.data_type.order_candidate import OrderCandidate + +if typing.TYPE_CHECKING: # avoid circular import problems + from hummingbot.connector.exchange_base import ExchangeBase + + +class BudgetChecker: + def __init__(self, exchange: "ExchangeBase"): + """ + Provides utilities for strategies to check the potential impact of order proposals on the user account balances. + + Mainly used to determine if sufficient balance is available to place a set of strategy-proposed orders. + The strategy can size a list of proposed order candidates by calling the `adjust_candidates` method. + + For a more fine-grained control, the strategy can call `adjust_candidate_and_lock_available_collateral` + for each one of the orders it intends to place. On each call, the `BudgetChecker` locks in the collateral + amount needed for that order and makes it unavailable for the following hypothetical orders. + Once the orders are sent to the exchange, the strategy must call `reset_locked_collateral` to + free the hypothetically locked assets for the next set of checks. + + :param exchange: The exchange against which available collateral assets will be checked. + """ + self._exchange = exchange + self._locked_collateral: Dict[str, Decimal] = defaultdict(lambda: Decimal("0")) + + def reset_locked_collateral(self): + """ + Frees collateral assets locked for hypothetical orders. + """ + self._locked_collateral.clear() + + def adjust_candidates( + self, order_candidates: List[OrderCandidate], all_or_none: bool = True + ) -> List[OrderCandidate]: + """ + Fills in the collateral and returns fields of the order candidates. + If there is insufficient assets to cover the collateral requirements, the order amount is adjusted. + + See the doc string for `adjust_candidate` to learn more about how the adjusted order + amount is derived. + + :param order_candidates: A list of candidate orders to check and adjust. + :param all_or_none: Should the order amount be set to zero on insufficient balance. + :return: The list of adjusted order candidates. + """ + self.reset_locked_collateral() + adjusted_candidates = [ + self.adjust_candidate_and_lock_available_collateral(order_candidate, all_or_none) + for order_candidate in order_candidates + ] + self.reset_locked_collateral() + return adjusted_candidates + + def adjust_candidate_and_lock_available_collateral( + self, order_candidate: OrderCandidate, all_or_none: bool = True + ) -> OrderCandidate: + """ + Fills in the collateral and returns fields of the order candidates. + If there is insufficient assets to cover the collateral requirements, the order amount is adjusted. + + See the doc string for `adjust_candidate` to learn more about how the adjusted order + amount is derived. + + This method also locks in the collateral amount for the given collateral token and makes + it unavailable on subsequent calls to this method until the `reset_locked_collateral` + method is called. + + :param order_candidate: The candidate order to check and adjust. + :param all_or_none: Should the order amount be set to zero on insufficient balance. + :return: The adjusted order candidate. + """ + adjusted_candidate = self.adjust_candidate(order_candidate, all_or_none) + self._lock_available_collateral(adjusted_candidate) + return adjusted_candidate + + def adjust_candidate( + self, order_candidate: OrderCandidate, all_or_none: bool = True + ) -> OrderCandidate: + """ + Fills in the collateral and returns fields of the order candidates. + + If there is insufficient collateral to cover the proposed order amount and + the `all_or_none` parameter is set to `False`, the order amount will be adjusted + to the greatest amount that the remaining collateral can provide for. If the parameter + is set to `True`, the order amount is set to zero. + + :param order_candidate: The candidate order to be checked and adjusted. + :param all_or_none: Should the order amount be set to zero on insufficient balance. + :return: The adjusted order candidate. + """ + order_candidate = self.populate_collateral_entries(order_candidate) + available_balances = self._get_available_balances(order_candidate) + order_candidate.adjust_from_balances(available_balances) + if order_candidate.resized: + if all_or_none: + order_candidate.set_to_zero() + else: + order_candidate = self._quantize_adjusted_order(order_candidate) + return order_candidate + + def populate_collateral_entries(self, order_candidate: OrderCandidate) -> OrderCandidate: + """ + Populates the collateral and returns fields of the order candidates. + + This implementation assumes a spot-specific configuration for collaterals (i.e. the quote + token for buy orders, and base token for sell orders). It can be overridden to provide other + configurations. + + :param order_candidate: The candidate order to check and adjust. + :return: The adjusted order candidate. + """ + order_candidate = copy(order_candidate) + order_candidate.populate_collateral_entries(self._exchange) + return order_candidate + + def _get_available_balances(self, order_candidate: OrderCandidate) -> Dict[str, Decimal]: + available_balances = {} + balance_fn = ( + self._exchange.get_available_balance + if not order_candidate.from_total_balances + else self._exchange.get_balance + ) + + if order_candidate.order_collateral is not None: + token, _ = order_candidate.order_collateral + available_balances[token] = ( + balance_fn(token) - self._locked_collateral[token] + ) + if order_candidate.percent_fee_collateral is not None: + token, _ = order_candidate.percent_fee_collateral + available_balances[token] = ( + balance_fn(token) - self._locked_collateral[token] + ) + for entry in order_candidate.fixed_fee_collaterals: + token, _ = entry + available_balances[token] = ( + balance_fn(token) - self._locked_collateral[token] + ) + + return available_balances + + def _quantize_adjusted_order(self, order_candidate: OrderCandidate) -> OrderCandidate: + trading_pair = order_candidate.trading_pair + adjusted_amount = order_candidate.amount + quantized_amount = self._exchange.quantize_order_amount(trading_pair, adjusted_amount) + + if adjusted_amount != quantized_amount: + order_candidate.amount = quantized_amount + order_candidate = self.populate_collateral_entries(order_candidate) + + return order_candidate + + def _lock_available_collateral(self, order_candidate: OrderCandidate): + for token, amount in order_candidate.collateral_dict.items(): + self._locked_collateral[token] += amount diff --git a/hummingbot/connector/client_order_tracker.py b/hummingbot/connector/client_order_tracker.py new file mode 100644 index 0000000..77d02df --- /dev/null +++ b/hummingbot/connector/client_order_tracker.py @@ -0,0 +1,443 @@ +import asyncio +import logging +from collections import defaultdict +from decimal import Decimal +from itertools import chain +from typing import TYPE_CHECKING, Callable, Dict, Optional + +from cachetools import TTLCache + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.trade_fee import TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.connector_base import ConnectorBase + +cot_logger = None + + +class ClientOrderTracker: + + MAX_CACHE_SIZE = 1000 + CACHED_ORDER_TTL = 30.0 # seconds + TRADE_FILLS_WAIT_TIMEOUT = 5 # seconds + + @classmethod + def logger(cls) -> HummingbotLogger: + global cot_logger + if cot_logger is None: + cot_logger = logging.getLogger(__name__) + return cot_logger + + def __init__(self, connector: "ConnectorBase", lost_order_count_limit: int = 3) -> None: + """ + Provides utilities for connectors to update in-flight orders and also handle order errors. + Also it maintains cached orders to allow for additional updates to occur after the original order + is determined to no longer be active. + An error constitutes, but is not limited to, the following: + (1) Order not found on exchange. + (2) Cannot retrieve exchange_order_id of an order + (3) Error thrown by exchange when fetching order status + """ + self._connector: ConnectorBase = connector + self._lost_order_count_limit = lost_order_count_limit + self._in_flight_orders: Dict[str, InFlightOrder] = {} + self._cached_orders: TTLCache = TTLCache(maxsize=self.MAX_CACHE_SIZE, ttl=self.CACHED_ORDER_TTL) + self._lost_orders: Dict[str, InFlightOrder] = {} + + self._order_tracking_task: Optional[asyncio.Task] = None + self._last_poll_timestamp: int = -1 + self._order_not_found_records: Dict[str, int] = defaultdict(lambda: 0) + + @property + def active_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns orders that are actively tracked + """ + return self._in_flight_orders + + @property + def cached_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns orders that are no longer actively tracked. + """ + return {client_order_id: order for client_order_id, order in self._cached_orders.items()} + + @property + def all_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns both active and cached order. + """ + return {**self.active_orders, **self.cached_orders} + + @property + def all_fillable_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns all orders that could still be impacted by trades: active orders, cached orders and lost orders + """ + return {**self.active_orders, **self.cached_orders, **self.lost_orders} + + @property + def all_fillable_orders_by_exchange_order_id(self) -> Dict[str, InFlightOrder]: + """ + Same as `all_fillable_orders`, but the orders are mapped by exchange order ID. + """ + orders_map = { + order.exchange_order_id: order + for order in chain(self.active_orders.values(), self.cached_orders.values(), self.lost_orders.values()) + } + return orders_map + + @property + def all_updatable_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns all orders that could receive status updates + """ + return {**self.active_orders, **self.lost_orders} + + @property + def all_updatable_orders_by_exchange_order_id(self) -> Dict[str, InFlightOrder]: + """ + Same as `all_updatable_orders`, but the orders are mapped by exchange order ID. + """ + orders_map = { + order.exchange_order_id: order for order in chain(self.active_orders.values(), self.lost_orders.values()) + } + return orders_map + + @property + def current_timestamp(self) -> int: + """ + Returns current timestamp in seconds. + """ + return self._connector.current_timestamp + + @property + def lost_orders(self) -> Dict[str, InFlightOrder]: + """ + Returns a dictionary of all orders marked as failed after not being found more times than the configured limit + """ + return {client_order_id: order for client_order_id, order in self._lost_orders.items()} + + @property + def lost_order_count_limit(self) -> int: + return self._lost_order_count_limit + + @lost_order_count_limit.setter + def lost_order_count_limit(self, value: int): + self._lost_order_count_limit = value + + def start_tracking_order(self, order: InFlightOrder): + self._in_flight_orders[order.client_order_id] = order + + def stop_tracking_order(self, client_order_id: str): + if client_order_id in self._in_flight_orders: + self._cached_orders[client_order_id] = self._in_flight_orders[client_order_id] + del self._in_flight_orders[client_order_id] + if client_order_id in self._order_not_found_records: + del self._order_not_found_records[client_order_id] + + def restore_tracking_states(self, tracking_states: Dict[str, any]): + """ + Restore in-flight orders from saved tracking states. + :param tracking_states: a dictionary associating order ids with the serialized order (JSON format). + """ + for serialized_order in tracking_states.values(): + order = self._restore_order_from_json(serialized_order=serialized_order) + if order.is_open: + self.start_tracking_order(order) + elif order.is_failure: + # If the order is marked as failed but is still in the tracking states, it was a lost order + self._lost_orders[order.client_order_id] = order + + def fetch_tracked_order(self, client_order_id: str) -> Optional[InFlightOrder]: + return self._in_flight_orders.get(client_order_id, None) + + def fetch_cached_order(self, client_order_id: str) -> Optional[InFlightOrder]: + return self._cached_orders.get(client_order_id, None) + + def fetch_order( + self, client_order_id: Optional[str] = None, exchange_order_id: Optional[str] = None + ) -> Optional[InFlightOrder]: + found_order = None + + if client_order_id in self.all_orders: + found_order = self.all_orders[client_order_id] + elif exchange_order_id is not None: + found_order = next( + (order for order in self.all_orders.values() if order.exchange_order_id == exchange_order_id), None + ) + + return found_order + + def fetch_lost_order( + self, client_order_id: Optional[str] = None, exchange_order_id: Optional[str] = None + ) -> Optional[InFlightOrder]: + found_order = None + + if client_order_id in self._lost_orders: + found_order = self._lost_orders[client_order_id] + elif exchange_order_id is not None: + found_order = next( + (order for order in self._lost_orders.values() if order.exchange_order_id == exchange_order_id), + None) + + return found_order + + def process_order_update(self, order_update: OrderUpdate): + return safe_ensure_future(self._process_order_update(order_update)) + + def process_trade_update(self, trade_update: TradeUpdate): + client_order_id: str = trade_update.client_order_id + + tracked_order: Optional[InFlightOrder] = self.all_fillable_orders.get(client_order_id) + + if tracked_order: + previous_executed_amount_base: Decimal = tracked_order.executed_amount_base + + updated: bool = tracked_order.update_with_trade_update(trade_update) + if updated: + self._trigger_order_fills( + tracked_order=tracked_order, + prev_executed_amount_base=previous_executed_amount_base, + fill_amount=trade_update.fill_base_amount, + fill_price=trade_update.fill_price, + fill_fee=trade_update.fee, + trade_id=trade_update.trade_id, + exchange_order_id=trade_update.exchange_order_id, + ) + + async def process_order_not_found(self, client_order_id: str): + """ + Increments and checks if the order specified has exceeded the order_not_found_count_limit. + A failed event is triggered if necessary. + + :param client_order_id: Client order id of an order. + :type client_order_id: str + """ + # Only concerned with active orders. + tracked_order: Optional[InFlightOrder] = self.fetch_tracked_order(client_order_id=client_order_id) + + if tracked_order is not None: + self._order_not_found_records[client_order_id] += 1 + if self._order_not_found_records[client_order_id] > self._lost_order_count_limit: + # Only mark the order as failed if it has not been marked as done already asynchronously + if tracked_order.current_state not in [OrderState.CANCELED, OrderState.FILLED, OrderState.FAILED]: + self.logger().warning( + f"The order {client_order_id}({tracked_order.exchange_order_id}) will be " + f"considered lost. Please check its status in the exchange." + ) + order_update: OrderUpdate = OrderUpdate( + client_order_id=client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + await self._process_order_update(order_update) + del self._cached_orders[client_order_id] + self._lost_orders[tracked_order.client_order_id] = tracked_order + else: + lost_order = self._lost_orders.get(client_order_id) + if lost_order is not None: + self.logger().info( + f"The lost order {client_order_id}({lost_order.exchange_order_id}) was not found " + f"and will be removed" + ) + order_update: OrderUpdate = OrderUpdate( + client_order_id=client_order_id, + trading_pair=lost_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + await self._process_order_update(order_update) + else: + self.logger().debug(f"Order is not/no longer being tracked ({client_order_id})") + + async def _process_order_update(self, order_update: OrderUpdate): + if not order_update.client_order_id and not order_update.exchange_order_id: + self.logger().error("OrderUpdate does not contain any client_order_id or exchange_order_id", exc_info=True) + return + + tracked_order: Optional[InFlightOrder] = self.fetch_order( + order_update.client_order_id, order_update.exchange_order_id + ) + + if tracked_order: + if order_update.new_state == OrderState.FILLED and not tracked_order.is_done: + try: + await asyncio.wait_for( + tracked_order.wait_until_completely_filled(), timeout=self.TRADE_FILLS_WAIT_TIMEOUT + ) + except asyncio.TimeoutError: + self.logger().warning( + f"The order fill updates did not arrive on time for {tracked_order.client_order_id}. " + f"The complete update will be processed with incomplete information." + ) + + previous_state: OrderState = tracked_order.current_state + + updated: bool = tracked_order.update_with_order_update(order_update) + if updated: + self._trigger_order_creation(tracked_order, previous_state, order_update.new_state) + self._trigger_order_completion(tracked_order, order_update) + else: + lost_order = self.fetch_lost_order( + client_order_id=order_update.client_order_id, exchange_order_id=order_update.exchange_order_id + ) + if lost_order: + if order_update.new_state in [OrderState.CANCELED, OrderState.FILLED, OrderState.FAILED]: + # If the order officially reaches a final state after being lost it should be removed from the lost list + del self._lost_orders[lost_order.client_order_id] + else: + self.logger().debug(f"Order is not/no longer being tracked ({order_update})") + + def _trigger_created_event(self, order: InFlightOrder): + event_tag = MarketEvent.BuyOrderCreated if order.trade_type is TradeType.BUY else MarketEvent.SellOrderCreated + event_class: Callable = BuyOrderCreatedEvent if order.trade_type is TradeType.BUY else SellOrderCreatedEvent + self._connector.trigger_event( + event_tag, + event_class( + self.current_timestamp, + order.order_type, + order.trading_pair, + order.amount, + order.price, + order.client_order_id, + order.creation_timestamp, + exchange_order_id=order.exchange_order_id, + leverage=order.leverage, + position=order.position.value, + ), + ) + + def _trigger_cancelled_event(self, order: InFlightOrder): + self._connector.trigger_event( + MarketEvent.OrderCancelled, + OrderCancelledEvent( + timestamp=self.current_timestamp, + order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + ), + ) + + def _trigger_filled_event( + self, + order: InFlightOrder, + fill_amount: Decimal, + fill_price: Decimal, + fill_fee: TradeFeeBase, + trade_id: str, + exchange_order_id: str, + ): + self._connector.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + timestamp=self.current_timestamp, + order_id=order.client_order_id, + trading_pair=order.trading_pair, + trade_type=order.trade_type, + order_type=order.order_type, + price=fill_price, + amount=fill_amount, + trade_fee=fill_fee, + exchange_trade_id=trade_id, + leverage=int(order.leverage), + position=order.position.value, + exchange_order_id=exchange_order_id, + ), + ) + + def _trigger_completed_event(self, order: InFlightOrder): + event_tag = ( + MarketEvent.BuyOrderCompleted if order.trade_type is TradeType.BUY else MarketEvent.SellOrderCompleted + ) + event_class = BuyOrderCompletedEvent if order.trade_type is TradeType.BUY else SellOrderCompletedEvent + self._connector.trigger_event( + event_tag, + event_class( + self.current_timestamp, + order.client_order_id, + order.base_asset, + order.quote_asset, + order.executed_amount_base, + order.executed_amount_quote, + order.order_type, + order.exchange_order_id, + ), + ) + + def _trigger_failure_event(self, order: InFlightOrder): + self._connector.trigger_event( + MarketEvent.OrderFailure, + MarketOrderFailureEvent( + timestamp=self.current_timestamp, + order_id=order.client_order_id, + order_type=order.order_type, + ), + ) + + def _trigger_order_creation(self, tracked_order: InFlightOrder, previous_state: OrderState, new_state: OrderState): + if previous_state == OrderState.PENDING_CREATE and new_state == OrderState.OPEN: + self.logger().info(tracked_order.build_order_created_message()) + self._trigger_created_event(tracked_order) + + def _trigger_order_fills(self, + tracked_order: InFlightOrder, + prev_executed_amount_base: Decimal, + fill_amount: Decimal, + fill_price: Decimal, + fill_fee: TradeFeeBase, + trade_id: str, + exchange_order_id: str): + if prev_executed_amount_base < tracked_order.executed_amount_base: + self.logger().info( + f"The {tracked_order.trade_type.name.upper()} order {tracked_order.client_order_id} " + f"amounting to {tracked_order.executed_amount_base}/{tracked_order.amount} " + f"{tracked_order.base_asset} has been filled." + ) + self._trigger_filled_event( + order=tracked_order, + fill_amount=fill_amount, + fill_price=fill_price, + fill_fee=fill_fee, + trade_id=trade_id, + exchange_order_id=exchange_order_id, + ) + + def _trigger_order_completion(self, tracked_order: InFlightOrder, order_update: Optional[OrderUpdate] = None): + if tracked_order.is_open: + return + + if tracked_order.is_cancelled: + self._trigger_cancelled_event(tracked_order) + self.logger().info(f"Successfully canceled order {tracked_order.client_order_id}.") + + elif tracked_order.is_filled: + self._trigger_completed_event(tracked_order) + self.logger().info( + f"{tracked_order.trade_type.name.upper()} order {tracked_order.client_order_id} completely filled." + ) + + elif tracked_order.is_failure: + self._trigger_failure_event(tracked_order) + self.logger().info(f"Order {tracked_order.client_order_id} has failed. Order Update: {order_update}") + + self.stop_tracking_order(tracked_order.client_order_id) + + @staticmethod + def _restore_order_from_json(serialized_order: Dict): + order = InFlightOrder.from_json(serialized_order) + return order diff --git a/hummingbot/connector/connector_base.pxd b/hummingbot/connector/connector_base.pxd new file mode 100644 index 0000000..4ac7497 --- /dev/null +++ b/hummingbot/connector/connector_base.pxd @@ -0,0 +1,31 @@ +from hummingbot.core.event.event_logger cimport EventLogger +from hummingbot.core.event.event_reporter cimport EventReporter +from hummingbot.core.network_iterator cimport NetworkIterator + +cdef class ConnectorBase(NetworkIterator): + cdef: + EventReporter _event_reporter + EventLogger _event_logger + public bint _trading_required + public dict _account_available_balances + public dict _account_balances + public bint _real_time_balance_update + public dict _in_flight_orders_snapshot + public double _in_flight_orders_snapshot_timestamp + public set _current_trade_fills + public dict _exchange_order_ids + public object _trade_fee_schema + public object _trade_volume_metric_collector + public object _client_config + + cdef str c_buy(self, str trading_pair, object amount, object order_type=*, object price=*, dict kwargs=*) + cdef str c_sell(self, str trading_pair, object amount, object order_type=*, object price=*, dict kwargs=*) + cdef c_cancel(self, str trading_pair, str client_order_id) + cdef c_stop_tracking_order(self, str order_id) + cdef object c_get_balance(self, str currency) + cdef object c_get_available_balance(self, str currency) + cdef object c_get_price(self, str trading_pair, bint is_buy) + cdef object c_get_order_price_quantum(self, str trading_pair, object price) + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size) + cdef object c_quantize_order_price(self, str trading_pair, object price) + cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=*) diff --git a/hummingbot/connector/connector_base.pyx b/hummingbot/connector/connector_base.pyx new file mode 100644 index 0000000..3295e06 --- /dev/null +++ b/hummingbot/connector/connector_base.pyx @@ -0,0 +1,559 @@ +import asyncio +import time +from decimal import Decimal +from typing import Dict, List, Set, Tuple, TYPE_CHECKING, Union + +from hummingbot.client.config.trade_fee_schema_loader import TradeFeeSchemaLoader +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.connector.utils import split_hb_trading_pair, TradeFillOrderDetails +from hummingbot.connector.constants import s_decimal_NaN, s_decimal_0 +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.network_iterator import NetworkIterator +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.estimate_fee import estimate_fee + +if TYPE_CHECKING: + from hummingbot.client.config.client_config_map import ClientConfigMap + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +cdef class ConnectorBase(NetworkIterator): + MARKET_EVENTS = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.WithdrawAsset, + MarketEvent.OrderCancelled, + MarketEvent.OrderFilled, + MarketEvent.OrderExpired, + MarketEvent.OrderFailure, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.FundingPaymentCompleted, + MarketEvent.RangePositionLiquidityAdded, + MarketEvent.RangePositionLiquidityRemoved, + MarketEvent.RangePositionUpdate, + MarketEvent.RangePositionUpdateFailure, + MarketEvent.RangePositionFeeCollected, + ] + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__() + + self._event_reporter = EventReporter(event_source=self.display_name) + self._event_logger = EventLogger(event_source=self.display_name) + for event_tag in self.MARKET_EVENTS: + self.c_add_listener(event_tag.value, self._event_reporter) + self.c_add_listener(event_tag.value, self._event_logger) + + self._account_balances = {} # Dict[asset_name:str, Decimal] + self._account_available_balances = {} # Dict[asset_name:str, Decimal] + # _real_time_balance_update is used to flag whether the connector provides real time balance updates. + # if not, the available will be calculated based on what happened since snapshot taken. + self._real_time_balance_update = True + # If _real_time_balance_update is set to False, Sub classes of this connector class need to set values + # for _in_flight_orders_snapshot and _in_flight_orders_snapshot_timestamp when the update user balances. + self._in_flight_orders_snapshot = {} # Dict[order_id:str, InFlightOrderBase] + self._in_flight_orders_snapshot_timestamp = 0.0 + self._current_trade_fills = set() + self._exchange_order_ids = dict() + self._trade_fee_schema = None + self._trade_volume_metric_collector = client_config_map.anonymized_metrics_mode.get_collector( + connector=self, + rate_provider=RateOracle.get_instance(), + instance_id=client_config_map.instance_id, + ) + self._client_config: Union[ClientConfigAdapter, ClientConfigMap] = client_config_map # for IDE autocomplete + + @property + def real_time_balance_update(self) -> bool: + return self._real_time_balance_update + + @real_time_balance_update.setter + def real_time_balance_update(self, value: bool): + self._real_time_balance_update = value + + @property + def in_flight_orders_snapshot(self) -> Dict[str, InFlightOrderBase]: + return self._in_flight_orders_snapshot + + @in_flight_orders_snapshot.setter + def in_flight_orders_snapshot(self, value: Dict[str, InFlightOrderBase]): + self._in_flight_orders_snapshot = value + + @property + def in_flight_orders_snapshot_timestamp(self) -> float: + return self._in_flight_orders_snapshot_timestamp + + @in_flight_orders_snapshot_timestamp.setter + def in_flight_orders_snapshot_timestamp(self, value: float): + self._in_flight_orders_snapshot_timestamp = value + + def estimate_fee_pct(self, is_maker: bool) -> Decimal: + """ + Estimate the trading fee for maker or taker type of order + :param is_maker: Whether to get trading for maker or taker order + :returns An estimated fee in percentage value + """ + return estimate_fee(self.name, is_maker).percent + + @staticmethod + def split_trading_pair(trading_pair: str) -> Tuple[str, str]: + return split_hb_trading_pair(trading_pair) + + def in_flight_asset_balances(self, in_flight_orders: Dict[str, InFlightOrderBase]) -> Dict[str, Decimal]: + """ + Calculates total asset balances locked in in_flight_orders including fee (estimated) + For BUY order, this is the quote asset balance locked in the order + For SELL order, this is the base asset balance locked in the order + :param in_flight_orders: a dictionary of in-flight orders + :return A dictionary of tokens and their balance locked in the orders + """ + asset_balances = {} + if in_flight_orders is None: + return asset_balances + for order in (o for o in in_flight_orders.values() if not (o.is_done or o.is_failure or o.is_cancelled)): + outstanding_amount = order.amount - order.executed_amount_base + if order.trade_type is TradeType.BUY: + outstanding_value = outstanding_amount * order.price + if order.quote_asset not in asset_balances: + asset_balances[order.quote_asset] = s_decimal_0 + fee = self.estimate_fee_pct(True) + outstanding_value *= Decimal(1) + fee + asset_balances[order.quote_asset] += outstanding_value + else: + if order.base_asset not in asset_balances: + asset_balances[order.base_asset] = s_decimal_0 + asset_balances[order.base_asset] += outstanding_amount + return asset_balances + + def order_filled_balances(self, starting_timestamp = 0) -> Dict[str, Decimal]: + """ + Calculates total asset balance changes from filled orders since the timestamp + For BUY filled order, the quote balance goes down while the base balance goes up, and for SELL order, it's the + opposite. This does not account for fee. + :param starting_timestamp: The starting timestamp to include filter order filled events + :returns A dictionary of tokens and their balance + """ + order_filled_events = list(filter(lambda e: isinstance(e, OrderFilledEvent), self.event_logs)) + order_filled_events = [o for o in order_filled_events if o.timestamp > starting_timestamp] + balances = {} + for event in order_filled_events: + base, quote = event.trading_pair.split("-")[0], event.trading_pair.split("-")[1] + if event.trade_type is TradeType.BUY: + quote_value = Decimal("-1") * event.price * event.amount + base_value = event.amount + else: + quote_value = event.price * event.amount + base_value = Decimal("-1") * event.amount + if base not in balances: + balances[base] = s_decimal_0 + if quote not in balances: + balances[quote] = s_decimal_0 + balances[base] += base_value + balances[quote] += quote_value + return balances + + def get_exchange_limit_config(self, market: str) -> Dict[str, object]: + """ + Retrieves the Balance Limits for the specified market. + """ + exchange_limits = self._client_config.balance_asset_limit.get(market, {}) + return exchange_limits if exchange_limits is not None else {} + + @property + def status_dict(self) -> Dict[str, bool]: + """ + A dictionary of statuses of various connector's components. + """ + raise NotImplementedError + + @property + def display_name(self) -> str: + return self.name + + @property + def name(self) -> str: + return self.__class__.__name__ + + @property + def event_logs(self) -> List[any]: + return self._event_logger.event_log + + @property + def ready(self) -> bool: + """ + Indicates whether the connector is ready to be used. + """ + raise NotImplementedError + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrderBase]: + raise NotImplementedError + + @property + def tracking_states(self) -> Dict[str, any]: + return {} + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + Restores the tracking states from a previously saved state. + :param saved_states: Previously saved tracking states from `tracking_states` property. + """ + pass + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + """ + pass + + cdef c_tick(self, double timestamp): + NetworkIterator.c_tick(self, timestamp) + self.tick(timestamp) + self._trade_volume_metric_collector.process_tick(timestamp) + + cdef c_start(self, Clock clock, double timestamp): + self.start(clock=clock, timestamp=timestamp) + + def start(self, Clock clock, double timestamp): + NetworkIterator.c_start(self, clock, timestamp) + self._trade_volume_metric_collector.start() + + cdef c_stop(self, Clock clock): + NetworkIterator.c_stop(self, clock) + self._trade_volume_metric_collector.stop() + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all in-flight orders and waits for cancellation results. + Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) + :param timeout_seconds: The timeout at which the operation will be canceled. + :returns List of CancellationResult which indicates whether each order is successfully canceled. + """ + raise NotImplementedError + + def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs) -> str: + """ + Buys an amount of base asset (of the given trading pair). + :param trading_pair: The market (e.g. BTC-USDT) to buy from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price (note: this is no longer optional) + :returns An order id + """ + raise NotImplementedError + + cdef str c_buy(self, str trading_pair, object amount, object order_type=OrderType.MARKET, + object price=s_decimal_NaN, dict kwargs={}): + return self.buy(trading_pair, amount, order_type, price, **kwargs) + + def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs) -> str: + """ + Sells an amount of base asset (of the given trading pair). + :param trading_pair: The market (e.g. BTC-USDT) to sell from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price (note: this is no longer optional) + :returns An order id + """ + raise NotImplementedError + + def batch_order_create( + self, orders_to_create: List[Union[LimitOrder, MarketOrder]] + ) -> List[Union[LimitOrder, MarketOrder]]: + """ + Issues a batch order creation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_create: A list of LimitOrder or MarketOrder objects representing the orders to create. The + order IDs can be blanc. + :returns: A list of LimitOrder or MarketOrder objects representing the created orders, complete with the + generated order IDs. + """ + creation_results = [] + for order in orders_to_create: + order_type = OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET + size = order.quantity if order_type == OrderType.LIMIT else order.amount + if order.is_buy: + client_order_id = self.buy( + trading_pair=order.trading_pair, + amount=size, + order_type=order_type, + price=order.price if order_type == OrderType.LIMIT else s_decimal_NaN + ) + else: + client_order_id = self.sell( + trading_pair=order.trading_pair, + amount=size, + order_type=order_type, + price=order.price if order_type == OrderType.LIMIT else s_decimal_NaN, + ) + if order_type == OrderType.LIMIT: + creation_results.append( + LimitOrder( + client_order_id=client_order_id, + trading_pair=order.trading_pair, + is_buy=order.is_buy, + base_currency=order.base_currency, + quote_currency=order.quote_currency, + price=order.price, + quantity=size, + filled_quantity=order.filled_quantity, + creation_timestamp=order.creation_timestamp, + status=order.status, + ) + ) + else: + creation_results.append( + MarketOrder( + order_id=client_order_id, + trading_pair=order.trading_pair, + is_buy=order.is_buy, + base_asset=order.base_asset, + quote_asset=order.quote_asset, + amount=size, + timestamp=order.timestamp, + ) + ) + return creation_results + + cdef str c_sell(self, str trading_pair, object amount, object order_type=OrderType.MARKET, + object price=s_decimal_NaN, dict kwargs={}): + return self.sell(trading_pair, amount, order_type, price, **kwargs) + + cdef c_cancel(self, str trading_pair, str client_order_id): + self.cancel(trading_pair, client_order_id) + + def cancel(self, trading_pair: str, client_order_id: str): + """ + Cancel an order. + :param trading_pair: The market (e.g. BTC-USDT) of the order. + :param client_order_id: The internal order id (also called client_order_id) + """ + raise NotImplementedError + + def batch_order_cancel(self, orders_to_cancel: List[LimitOrder]): + """ + Issues a batch order cancelation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_cancel: A list of the orders to cancel. + """ + for order in orders_to_cancel: + self.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + + cdef c_stop_tracking_order(self, str order_id): + raise NotImplementedError + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an in-flight order. + """ + raise NotImplementedError + + def get_all_balances(self) -> Dict[str, Decimal]: + """ + :return: Dict[asset_name: asst_balance]: Total balances of all assets + """ + return self._account_balances.copy() + + cdef object c_get_balance(self, str currency): + return self.get_balance(currency) + + def get_balance(self, currency: str) -> Decimal: + """ + :param currency: The currency (token) name + :return: A balance for the given currency (token) + """ + return self._account_balances.get(currency, s_decimal_0) + + def apply_balance_limit(self, currency: str, available_balance: Decimal, limit: Decimal) -> Decimal: + """ + Apply budget limit on an available balance, the limit is calculated as followings: + - Minus balance used in outstanding orders (in flight orders), if the budget is 1 ETH and the bot has already + used 0.5 ETH to put a maker buy order, the budget is now 0.5 + - Plus balance accredited from filled orders (since the bot started), if the budget is 1 ETH and the bot has + bought LINK (for 0.5 ETH), the ETH budget is now 0.5. However if later on the bot has sold LINK (for 0.5 ETH) + the budget is now 1 ETH + :param currency: The currency (token) name + :param available_balance: The available balance of the token + :param limit: The balance limit for the token + :returns An available balance after the limit has been applied + """ + in_flight_balance = self.in_flight_asset_balances(self.in_flight_orders).get(currency, s_decimal_0) + limit -= in_flight_balance + filled_balance = self.order_filled_balances().get(currency, s_decimal_0) + limit += filled_balance + limit = max(limit, s_decimal_0) + return min(available_balance, limit) + + def apply_balance_update_since_snapshot(self, currency: str, available_balance: Decimal) -> Decimal: + """ + Applies available balance update as followings + :param currency: the token symbol + :param available_balance: the current available_balance, this is also the snap balance taken since last + _update_balances() + :returns the real available that accounts for changes in flight orders and filled orders + """ + snapshot_bal = self.in_flight_asset_balances(self._in_flight_orders_snapshot).get(currency, s_decimal_0) + in_flight_bal = self.in_flight_asset_balances(self.in_flight_orders).get(currency, s_decimal_0) + orders_filled_bal = self.order_filled_balances(self._in_flight_orders_snapshot_timestamp).get(currency, + s_decimal_0) + actual_available = available_balance + snapshot_bal - in_flight_bal + orders_filled_bal + return actual_available + + cdef object c_get_available_balance(self, str currency): + return self.get_available_balance(currency) + + def get_available_balance(self, currency: str) -> Decimal: + """ + Return available balance for a given currency. The function accounts for balance changes since the last time + the snapshot was taken if no real time balance update. The function applied limit if configured. + :param currency: The currency (token) name + :returns: Balance available for trading for the specified currency + """ + available_balance = self._account_available_balances.get(currency, s_decimal_0) + if not self._real_time_balance_update: + available_balance = self.apply_balance_update_since_snapshot(currency, available_balance) + balance_limits = self.get_exchange_limit_config(self.name) + if currency in balance_limits: + balance_limit = Decimal(str(balance_limits[currency])) + available_balance = self.apply_balance_limit(currency, available_balance, balance_limit) + return available_balance + + cdef object c_get_price(self, str trading_pair, bint is_buy): + return self.get_price(trading_pair, is_buy) + + def get_price(self, trading_pair: str, is_buy: bool, amount: Decimal = s_decimal_NaN) -> Decimal: + """ + Get price for the market trading pair. + :param trading_pair: The market trading pair + :param is_buy: Whether to buy or sell the underlying asset + :param amount: The amount (to buy or sell) (optional) + :returns The price + """ + raise NotImplementedError + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + return self.get_order_price_quantum(trading_pair, price) + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + """ + Returns a price step, a minimum price increment for a given trading pair. + """ + raise NotImplementedError + + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size): + return self.get_order_size_quantum(trading_pair, order_size) + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + """ + Returns an order amount step, a minimum amount increment for a given trading pair. + """ + raise NotImplementedError + + cdef object c_quantize_order_price(self, str trading_pair, object price): + if price.is_nan(): + return price + price_quantum = self.c_get_order_price_quantum(trading_pair, price) + return (price // price_quantum) * price_quantum + + def quantize_order_price(self, trading_pair: str, price: Decimal) -> Decimal: + """ + Applies trading rule to quantize order price. + """ + return self.c_quantize_order_price(trading_pair, price) + + cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=s_decimal_NaN): + order_size_quantum = self.c_get_order_size_quantum(trading_pair, amount) + return (amount // order_size_quantum) * order_size_quantum + + def quantize_order_amount(self, trading_pair: str, amount: Decimal) -> Decimal: + """ + Applies trading rule to quantize order amount. + """ + return self.c_quantize_order_amount(trading_pair, amount) + + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + """ + Returns a quote price (or exchange rate) for a given amount, like asking how much does it cost to buy 4 apples? + :param trading_pair: The market trading pair + :param is_buy: True for buy order, False for sell order + :param amount: The order amount + :return The quoted price + """ + raise NotImplementedError + + async def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + """ + Returns a price required for order submission, this price could differ from the quote price (e.g. for + an exchange with order book). + :param trading_pair: The market trading pair + :param is_buy: True for buy order, False for sell order + :param amount: The order amount + :return The price to specify in an order. + """ + raise NotImplementedError + + @property + def available_balances(self) -> Dict[str, Decimal]: + return self._account_available_balances + + def add_trade_fills_from_market_recorder(self, current_trade_fills: Set[TradeFillOrderDetails]): + """ + Gets updates from new records in TradeFill table. This is used in method is_confirmed_new_order_filled_event + """ + self._current_trade_fills.update(current_trade_fills) + + def add_exchange_order_ids_from_market_recorder(self, current_exchange_order_ids: Dict[str, str]): + """ + Gets updates from new orders in Order table. This is used in method connector _history_reconciliation + """ + self._exchange_order_ids.update(current_exchange_order_ids) + + def is_confirmed_new_order_filled_event(self, exchange_trade_id: str, exchange_order_id: str, trading_pair: str): + """ + Returns True if order to be filled is not already present in TradeFill entries. + This is intended to avoid duplicated order fills in local DB. + """ + # Assume (market, exchange_trade_id, trading_pair) are unique. Also order has to be recorded in Order table + return (not TradeFillOrderDetails(self.display_name, exchange_trade_id, trading_pair) in self._current_trade_fills) and \ + (exchange_order_id in set(self._exchange_order_ids.keys())) + + def trade_fee_schema(self): + if self._trade_fee_schema is None: + self._trade_fee_schema = TradeFeeSchemaLoader.configured_schema_for_exchange(exchange_name=self.name) + return self._trade_fee_schema + + async def all_trading_pairs(self) -> List[str]: + """ + List of all trading pairs supported by the connector + + :return: List of trading pair symbols in the Hummingbot format + """ + raise NotImplementedError + + async def _update_balances(self): + """ + Update local balances requesting the latest information from the exchange. + """ + raise NotImplementedError + + def _time(self) -> float: + """ + Method created to enable tests to mock the machine time + :return: The machine time (time.time()) + """ + return time.time() + + async def _sleep(self, delay: float): + """ + Method created to enable tests to prevent processes from sleeping + """ + await asyncio.sleep(delay) diff --git a/hummingbot/connector/connector_metrics_collector.py b/hummingbot/connector/connector_metrics_collector.py new file mode 100644 index 0000000..4305f8d --- /dev/null +++ b/hummingbot/connector/connector_metrics_collector.py @@ -0,0 +1,169 @@ +import asyncio +import json +import logging +import platform +from abc import ABC, abstractmethod +from decimal import Decimal +from os.path import dirname, join, realpath +from typing import TYPE_CHECKING, List, Tuple + +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger +from hummingbot.logger.log_server_client import LogServerClient + +if TYPE_CHECKING: + from hummingbot.connector.connector_base import ConnectorBase + +with open(realpath(join(dirname(__file__), '../VERSION'))) as version_file: + CLIENT_VERSION = version_file.read().strip() + + +class MetricsCollector(ABC): + + DEFAULT_METRICS_SERVER_URL = "https://api.coinalpha.com/reporting-proxy-v2" + + @abstractmethod + def start(self): + raise NotImplementedError + + @abstractmethod + def stop(self): + raise NotImplementedError + + @abstractmethod + def process_tick(self, timestamp: float): + raise NotImplementedError + + +class DummyMetricsCollector(MetricsCollector): + + def start(self): + # Nothing is required + pass + + def stop(self): + # Nothing is required + pass + + def process_tick(self, timestamp: float): + # Nothing is required + pass + + +class TradeVolumeMetricCollector(MetricsCollector): + + _logger = None + + METRIC_NAME = "filled_usdt_volume" + + def __init__(self, + connector: 'ConnectorBase', + activation_interval: Decimal, + rate_provider: RateOracle, + instance_id: str, + valuation_token: str = "USDT"): + super().__init__() + self._connector = connector + self._activation_interval = activation_interval + self._dispatcher = LogServerClient(log_server_url=self.DEFAULT_METRICS_SERVER_URL) + self._rate_provider = rate_provider + self._instance_id = instance_id + self._client_version = CLIENT_VERSION + self._valuation_token = valuation_token + self._last_process_tick_timestamp = 0 + self._last_executed_collection_process = None + self._collected_events = [] + + self._fill_event_forwarder = EventForwarder(self._register_fill_event) + + self._event_pairs: List[Tuple[MarketEvent, EventForwarder]] = [ + (MarketEvent.OrderFilled, self._fill_event_forwarder), + ] + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def start(self): + self._dispatcher.start() + for event_pair in self._event_pairs: + self._connector.add_listener(event_pair[0], event_pair[1]) + + def stop(self): + self.trigger_metrics_collection_process() + for event_pair in self._event_pairs: + self._connector.remove_listener(event_pair[0], event_pair[1]) + self._dispatcher.stop() + + def process_tick(self, timestamp: float): + inactivity_time = timestamp - self._last_process_tick_timestamp + if inactivity_time >= self._activation_interval: + self._last_process_tick_timestamp = timestamp + self.trigger_metrics_collection_process() + + def trigger_metrics_collection_process(self): + events_to_process = self._collected_events + self._collected_events = [] + self._last_executed_collection_process = safe_ensure_future( + self.collect_metrics(events=events_to_process)) + + async def collect_metrics(self, events: List[OrderFilledEvent]): + try: + total_volume = Decimal("0") + + for fill_event in events: + trade_base, trade_quote = split_hb_trading_pair(fill_event.trading_pair) + from_quote_conversion_pair = combine_to_hb_trading_pair(base=trade_quote, quote=self._valuation_token) + rate = await self._rate_provider.stored_or_live_rate(from_quote_conversion_pair) + + if rate is not None: + total_volume += fill_event.amount * fill_event.price * rate + else: + from_base_conversion_pair = combine_to_hb_trading_pair(base=trade_base, quote=self._valuation_token) + rate = await self._rate_provider.stored_or_live_rate(from_base_conversion_pair) + if rate is not None: + total_volume += fill_event.amount * rate + else: + self.logger().debug(f"Could not find a conversion rate rate using Rate Oracle for any of " + f"the pairs {from_quote_conversion_pair} or {from_base_conversion_pair}") + + if total_volume > Decimal("0"): + self._dispatch_trade_volume(total_volume) + except asyncio.CancelledError: + raise + except Exception: + self._collected_events.extend(events) + + def _dispatch_trade_volume(self, volume: Decimal): + metric_request = { + "url": f"{self._dispatcher.log_server_url}/client_metrics", + "method": "POST", + "request_obj": { + "headers": { + 'Content-Type': "application/json" + }, + "data": json.dumps({ + "source": "hummingbot", + "name": self.METRIC_NAME, + "instance_id": self._instance_id, + "exchange": self._connector.name, + "version": self._client_version, + "system": f"{platform.system()} {platform.release()}({platform.platform()})", + "value": str(volume)}), + "params": {"ddtags": f"instance_id:{self._instance_id}," + f"client_version:{self._client_version}," + f"type:metrics", + "ddsource": "hummingbot-client"} + } + } + + self._dispatcher.request(metric_request) + + def _register_fill_event(self, event: OrderFilledEvent): + self._collected_events.append(event) diff --git a/hummingbot/connector/connector_status.py b/hummingbot/connector/connector_status.py new file mode 100644 index 0000000..7c61ce2 --- /dev/null +++ b/hummingbot/connector/connector_status.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +connector_status = { + # client connectors + 'ascend_ex': 'bronze', + 'binance': 'gold', + 'binance_perpetual': 'gold', + 'binance_perpetual_testnet': 'gold', + 'binance_us': 'bronze', + 'bitfinex': 'bronze', + 'bitget_perpetual': 'bronze', + 'bitmart': 'bronze', + 'bitmex': 'bronze', + 'bitmex_perpetual': 'bronze', + 'bitmex_testnet': 'bronze', + 'bitmex_perpetual_testnet': 'bronze', + 'bit_com_perpetual': 'bronze', + 'bit_com_perpetual_testnet': 'bronze', + 'btc_markets': 'bronze', + 'bybit_perpetual': 'bronze', + 'bybit_perpetual_testnet': 'bronze', + 'bybit_testnet': 'bronze', + 'bybit': 'bronze', + 'coinbase_pro': 'bronze', + 'dydx_perpetual': 'gold', + 'foxbit': 'bronze', + 'gate_io': 'silver', + 'gate_io_perpetual': 'silver', + 'injective_v2': 'silver', + 'injective_v2_perpetual': 'silver', + 'hitbtc': 'bronze', + 'huobi': 'silver', + 'kraken': 'bronze', + 'kucoin': 'silver', + 'kucoin_perpetual': 'silver', + 'mexc': 'bronze', + 'ndax': 'bronze', + 'ndax_testnet': 'bronze', + 'okx': 'bronze', + 'phemex_perpetual': 'bronze', + 'phemex_perpetual_testnet': 'bronze', + 'polkadex': 'silver', + 'vertex': 'bronze', + 'vertex_testnet': 'bronze', + # gateway connectors + 'curve': 'bronze', + 'dexalot': 'silver', + 'defira': 'bronze', + 'kujira': 'bronze', + 'mad_meerkat': 'bronze', + 'openocean': 'bronze', + 'quickswap': 'bronze', + 'pancakeswap': 'bronze', + 'pangolin': 'bronze', + 'perp': 'bronze', + 'plenty': 'bronze', + 'ref': 'bronze', + 'sushiswap': 'bronze', + 'tinyman': 'bronze', + 'traderjoe': 'bronze', + 'uniswap': 'bronze', + 'uniswapLP': 'bronze', + 'vvs': 'bronze', + 'woo_x': 'bronze', + 'woo_x_testnet': 'bronze', + 'xswap': 'bronze', +} + +warning_messages = { +} + + +def get_connector_status(connector_name: str) -> str: + """ + Indicator whether a connector is working properly or not. + UNKNOWN means the connector is not in connector_status dict. + RED means a connector doesn't work. + YELLOW means the connector is either new or has one or more issues. + GREEN means a connector is working properly. + """ + if connector_name not in connector_status.keys(): + status = "UNKNOWN" + else: + return f"&c{connector_status[connector_name].upper()}" + return status diff --git a/hummingbot/connector/constants.py b/hummingbot/connector/constants.py new file mode 100644 index 0000000..ce1ebd0 --- /dev/null +++ b/hummingbot/connector/constants.py @@ -0,0 +1,10 @@ +from decimal import Decimal + +NaN = float("nan") +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) +SECOND = 1 +MINUTE = 60 +TWELVE_HOURS = MINUTE * 60 * 12 + +FUNDING_FEE_POLL_INTERVAL = 120 diff --git a/hummingbot/connector/derivative/__init__.py b/hummingbot/connector/derivative/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/binance_perpetual/__init__.py b/hummingbot/connector/derivative/binance_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..277aa99 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_api_order_book_data_source.py @@ -0,0 +1,204 @@ +import asyncio +import time +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import ( + BinancePerpetualDerivative, + ) + + +class BinancePerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + _bpobds_logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, Mapping[str, str]] = {} + _mapping_initialization_lock = asyncio.Lock() + + def __init__( + self, + trading_pairs: List[str], + connector: 'BinancePerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DOMAIN + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._trading_pairs: List[str] = trading_pairs + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + self._trade_messages_queue_key = CONSTANTS.TRADE_STREAM_ID + self._diff_messages_queue_key = CONSTANTS.DIFF_STREAM_ID + self._funding_info_messages_queue_key = CONSTANTS.FUNDING_INFO_STREAM_ID + self._snapshot_messages_queue_key = "order_book_snapshot" + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + symbol_info: Dict[str, Any] = await self._request_complete_funding_info(trading_pair) + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(symbol_info["indexPrice"]), + mark_price=Decimal(symbol_info["markPrice"]), + next_funding_utc_timestamp=int(symbol_info["nextFundingTime"]), + rate=Decimal(symbol_info["lastFundingRate"]), + ) + return funding_info + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + params = { + "symbol": ex_trading_pair, + "limit": "1000" + } + + data = await self._connector._api_get( + path_url=CONSTANTS.SNAPSHOT_REST_URL, + params=params) + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = time.time() + snapshot_response.update({"trading_pair": trading_pair}) + snapshot_msg: OrderBookMessage = OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": snapshot_response["trading_pair"], + "update_id": snapshot_response["lastUpdateId"], + "bids": snapshot_response["bids"], + "asks": snapshot_response["asks"] + }, timestamp=snapshot_timestamp) + return snapshot_msg + + async def _connected_websocket_assistant(self) -> WSAssistant: + url = f"{web_utils.wss_url(CONSTANTS.PUBLIC_WS_ENDPOINT, self._domain)}" + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + stream_id_channel_pairs = [ + (CONSTANTS.DIFF_STREAM_ID, "@depth"), + (CONSTANTS.TRADE_STREAM_ID, "@aggTrade"), + (CONSTANTS.FUNDING_INFO_STREAM_ID, "@markPrice"), + ] + for stream_id, channel in stream_id_channel_pairs: + params = [] + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + params.append(f"{symbol.lower()}{channel}") + payload = { + "method": "SUBSCRIBE", + "params": params, + "id": stream_id, + } + subscribe_request: WSJSONRequest = WSJSONRequest(payload) + await ws.send(subscribe_request) + self.logger().info("Subscribed to public order book, trade and funding info channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "result" not in event_message: + stream_name = event_message.get("stream") + if "@depth" in stream_name: + channel = self._diff_messages_queue_key + elif "@aggTrade" in stream_name: + channel = self._trade_messages_queue_key + elif "@markPrice" in stream_name: + channel = self._funding_info_messages_queue_key + return channel + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + timestamp: float = time.time() + raw_message["data"]["s"] = await self._connector.trading_pair_associated_to_exchange_symbol( + raw_message["data"]["s"]) + data = raw_message["data"] + order_book_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": data["s"], + "update_id": data["u"], + "bids": data["b"], + "asks": data["a"] + }, timestamp=timestamp) + message_queue.put_nowait(order_book_message) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + raw_message["data"]["s"] = await self._connector.trading_pair_associated_to_exchange_symbol( + raw_message["data"]["s"]) + data = raw_message["data"] + trade_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": data["s"], + "trade_type": float(TradeType.SELL.value) if data["m"] else float(TradeType.BUY.value), + "trade_id": data["a"], + "update_id": data["E"], + "price": data["p"], + "amount": data["q"] + }, timestamp=data["E"] * 1e-3) + + message_queue.put_nowait(trade_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + for trading_pair in self._trading_pairs: + snapshot_msg: OrderBookMessage = await self._order_book_snapshot(trading_pair) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + delta = CONSTANTS.ONE_HOUR - time.time() % CONSTANTS.ONE_HOUR + await self._sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...", exc_info=True + ) + await self._sleep(5.0) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + + data: Dict[str, Any] = raw_message["data"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(data["s"]) + + if trading_pair not in self._trading_pairs: + return + funding_info = FundingInfoUpdate( + trading_pair=trading_pair, + index_price=Decimal(data["i"]), + mark_price=Decimal(data["p"]), + next_funding_utc_timestamp=int(data["T"]), + rate=Decimal(data["r"]), + ) + + message_queue.put_nowait(funding_info) + + async def _request_complete_funding_info(self, trading_pair: str): + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + data = await self._connector._api_get( + path_url=CONSTANTS.MARK_PRICE_URL, + params={"symbol": ex_trading_pair}, + is_auth_required=True) + return data diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py new file mode 100644 index 0000000..560854a --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_auth.py @@ -0,0 +1,54 @@ +import hashlib +import hmac +import json +from collections import OrderedDict +from typing import Any, Dict +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class BinancePerpetualAuth(AuthBase): + """ + Auth class required by Binance Perpetual API + """ + + def __init__(self, api_key: str, api_secret: str, time_provider: TimeSynchronizer): + self._api_key: str = api_key + self._api_secret: str = api_secret + self._time_provider: TimeSynchronizer = time_provider + + def generate_signature_from_payload(self, payload: str) -> str: + secret = bytes(self._api_secret.encode("utf-8")) + signature = hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() + return signature + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + if request.method == RESTMethod.POST: + request.data = self.add_auth_to_params(params=json.loads(request.data)) + else: + request.params = self.add_auth_to_params(request.params) + + request.headers = self.header_for_authentication() + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + return request # pass-through + + def add_auth_to_params(self, + params: Dict[str, Any]): + timestamp = int(self._time_provider.time() * 1e3) + + request_params = OrderedDict(params or {}) + request_params["timestamp"] = timestamp + + payload = urlencode(request_params) + request_params["signature"] = self.generate_signature_from_payload(payload=payload) + + return request_params + + def header_for_authentication(self) -> Dict[str, str]: + return {"X-MBX-APIKEY": self._api_key} diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_constants.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_constants.py new file mode 100644 index 0000000..1930342 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_constants.py @@ -0,0 +1,133 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "binance_perpetual" +BROKER_ID = "x-3QreWesy" +MAX_ORDER_ID_LEN = 32 + +DOMAIN = EXCHANGE_NAME +TESTNET_DOMAIN = "binance_perpetual_testnet" + +PERPETUAL_BASE_URL = "https://fapi.binance.com/fapi/" +TESTNET_BASE_URL = "https://testnet.binancefuture.com/fapi/" + +PERPETUAL_WS_URL = "wss://fstream.binance.com/" +TESTNET_WS_URL = "wss://stream.binancefuture.com/" + +PUBLIC_WS_ENDPOINT = "stream" +PRIVATE_WS_ENDPOINT = "ws" + +TIME_IN_FORCE_GTC = "GTC" # Good till cancelled +TIME_IN_FORCE_GTX = "GTX" # Good Till Crossing +TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel +TIME_IN_FORCE_FOK = "FOK" # Fill or kill + +# Public API v1 Endpoints +SNAPSHOT_REST_URL = "v1/depth" +TICKER_PRICE_URL = "v1/ticker/bookTicker" +TICKER_PRICE_CHANGE_URL = "v1/ticker/24hr" +EXCHANGE_INFO_URL = "v1/exchangeInfo" +RECENT_TRADES_URL = "v1/trades" +PING_URL = "v1/ping" +MARK_PRICE_URL = "v1/premiumIndex" +SERVER_TIME_PATH_URL = "v1/time" + +# Private API v1 Endpoints +ORDER_URL = "v1/order" +CANCEL_ALL_OPEN_ORDERS_URL = "v1/allOpenOrders" +ACCOUNT_TRADE_LIST_URL = "v1/userTrades" +SET_LEVERAGE_URL = "v1/leverage" +GET_INCOME_HISTORY_URL = "v1/income" +CHANGE_POSITION_MODE_URL = "v1/positionSide/dual" + +POST_POSITION_MODE_LIMIT_ID = f"POST{CHANGE_POSITION_MODE_URL}" +GET_POSITION_MODE_LIMIT_ID = f"GET{CHANGE_POSITION_MODE_URL}" + +# Private API v2 Endpoints +ACCOUNT_INFO_URL = "v2/account" +POSITION_INFORMATION_URL = "v2/positionRisk" + +# Private API Endpoints +BINANCE_USER_STREAM_ENDPOINT = "v1/listenKey" + +# Funding Settlement Time Span +FUNDING_SETTLEMENT_DURATION = (0, 30) # seconds before snapshot, seconds after snapshot + +# Order Statuses +ORDER_STATE = { + "NEW": OrderState.OPEN, + "FILLED": OrderState.FILLED, + "PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED, + "CANCELED": OrderState.CANCELED, + "EXPIRED": OrderState.CANCELED, + "REJECTED": OrderState.FAILED, +} + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +ORDERS_1MIN = "ORDERS_1MIN" +ORDERS_1SEC = "ORDERS_1SEC" + +DIFF_STREAM_ID = 1 +TRADE_STREAM_ID = 2 +FUNDING_INFO_STREAM_ID = 3 +HEARTBEAT_TIME_INTERVAL = 30.0 + +# Rate Limit time intervals +ONE_HOUR = 3600 +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 2400 + +RATE_LIMITS = [ + # Pool Limits + RateLimit(limit_id=REQUEST_WEIGHT, limit=2400, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1MIN, limit=1200, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1SEC, limit=300, time_interval=10), + # Weight Limits for individual endpoints + RateLimit(limit_id=SNAPSHOT_REST_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=20)]), + RateLimit(limit_id=TICKER_PRICE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=2)]), + RateLimit(limit_id=TICKER_PRICE_CHANGE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=EXCHANGE_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=40)]), + RateLimit(limit_id=RECENT_TRADES_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=BINANCE_USER_STREAM_ENDPOINT, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=PING_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=ORDER_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1), + LinkedLimitWeightPair(ORDERS_1MIN, weight=1), + LinkedLimitWeightPair(ORDERS_1SEC, weight=1)]), + RateLimit(limit_id=CANCEL_ALL_OPEN_ORDERS_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=ACCOUNT_TRADE_LIST_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), + RateLimit(limit_id=SET_LEVERAGE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=GET_INCOME_HISTORY_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=30)]), + RateLimit(limit_id=POST_POSITION_MODE_LIMIT_ID, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=GET_POSITION_MODE_LIMIT_ID, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=30)]), + RateLimit(limit_id=ACCOUNT_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), + RateLimit(limit_id=POSITION_INFORMATION_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, weight=5, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), + RateLimit(limit_id=MARK_PRICE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, weight=1, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), +] + +ORDER_NOT_EXIST_ERROR_CODE = -2013 +ORDER_NOT_EXIST_MESSAGE = "Order does not exist" +UNKNOWN_ORDER_ERROR_CODE = -2011 +UNKNOWN_ORDER_MESSAGE = "Unknown order sent" diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py new file mode 100644 index 0000000..44e76cd --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_derivative.py @@ -0,0 +1,803 @@ +import asyncio +import time +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.derivative.binance_perpetual import ( + binance_perpetual_constants as CONSTANTS, + binance_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_api_order_book_data_source import ( + BinancePerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_auth import BinancePerpetualAuth +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_user_stream_data_source import ( + BinancePerpetualUserStreamDataSource, +) +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +bpm_logger = None + + +class BinancePerpetualDerivative(PerpetualDerivativePyBase): + web_utils = web_utils + SHORT_POLL_INTERVAL = 5.0 + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + LONG_POLL_INTERVAL = 120.0 + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + binance_perpetual_api_key: str = None, + binance_perpetual_api_secret: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DOMAIN, + ): + self.binance_perpetual_api_key = binance_perpetual_api_key + self.binance_perpetual_secret_key = binance_perpetual_api_secret + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._position_mode = None + self._last_trade_history_timestamp = None + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> BinancePerpetualAuth: + return BinancePerpetualAuth(self.binance_perpetual_api_key, self.binance_perpetual_secret_key, + self._time_synchronizer) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.BROKER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.PING_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 600 + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def supported_position_modes(self): + """ + This method needs to be overridden to provide the accurate information depending on the exchange. + """ + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = ("-1021" in error_description + and "Timestamp for this request" in error_description) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_EXIST_ERROR_CODE) in str( + status_update_exception + ) and CONSTANTS.ORDER_NOT_EXIST_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.UNKNOWN_ORDER_ERROR_CODE) in str( + cancelation_exception + ) and CONSTANTS.UNKNOWN_ORDER_MESSAGE in str(cancelation_exception) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BinancePerpetualAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BinancePerpetualUserStreamDataSource( + auth=self._auth, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _status_polling_loop_fetch_updates(self): + await safe_gather( + self._update_order_fills_from_trades(), + self._update_order_status(), + self._update_balances(), + self._update_positions(), + ) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + api_params = { + "origClientOrderId": order_id, + "symbol": symbol, + } + cancel_result = await self._api_delete( + path_url=CONSTANTS.ORDER_URL, + params=api_params, + is_auth_required=True) + if cancel_result.get("code") == -2011 and "Unknown order sent." == cancel_result.get("msg", ""): + self.logger().debug(f"The order {order_id} does not exist on Binance Perpetuals. " + f"No cancelation needed.") + await self._order_tracker.process_order_not_found(order_id) + raise IOError(f"{cancel_result.get('code')} - {cancel_result['msg']}") + if cancel_result.get("status") == "CANCELED": + return True + return False + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + + amount_str = f"{amount:f}" + price_str = f"{price:f}" + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"symbol": symbol, + "side": "BUY" if trade_type is TradeType.BUY else "SELL", + "quantity": amount_str, + "type": "MARKET" if order_type is OrderType.MARKET else "LIMIT", + "newClientOrderId": order_id + } + if order_type.is_limit_type(): + api_params["price"] = price_str + if order_type == OrderType.LIMIT: + api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTC + if order_type == OrderType.LIMIT_MAKER: + api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTX + if self._position_mode == PositionMode.HEDGE: + if position_action == PositionAction.OPEN: + api_params["positionSide"] = "LONG" if trade_type is TradeType.BUY else "SHORT" + else: + api_params["positionSide"] = "SHORT" if trade_type is TradeType.BUY else "LONG" + try: + order_result = await self._api_post( + path_url=CONSTANTS.ORDER_URL, + data=api_params, + is_auth_required=True) + o_id = str(order_result["orderId"]) + transact_time = order_result["updateTime"] * 1e-3 + except IOError as e: + error_description = str(e) + is_server_overloaded = ("status is 503" in error_description + and "Unknown error, please check your request or try again later." in error_description) + if is_server_overloaded: + o_id = "UNKNOWN" + transact_time = time.time() + else: + raise + return o_id, transact_time + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + try: + exchange_order_id = await order.get_exchange_order_id() + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.ACCOUNT_TRADE_LIST_URL, + params={ + "symbol": trading_pair, + }, + is_auth_required=True) + + for trade in all_fills_response: + order_id = str(trade.get("orderId")) + if order_id == exchange_order_id: + position_side = trade["positionSide"] + position_action = (PositionAction.OPEN + if (order.trade_type is TradeType.BUY and position_side == "LONG" + or order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=order.client_order_id, + exchange_order_id=trade["orderId"], + trading_pair=order.trading_pair, + fill_timestamp=trade["time"] * 1e-3, + fill_price=Decimal(trade["price"]), + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fee=fee, + ) + trade_updates.append(trade_update) + + except asyncio.TimeoutError: + raise IOError(f"Skipped order update with order fills for {order.client_order_id} " + "- waiting for exchange order id.") + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + order_update = await self._api_get( + path_url=CONSTANTS.ORDER_URL, + params={ + "symbol": trading_pair, + "origClientOrderId": tracked_order.client_order_id + }, + is_auth_required=True) + if "code" in order_update: + if self._is_request_exception_related_to_time_synchronizer(request_exception=order_update): + _order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + client_order_id=tracked_order.client_order_id, + ) + return _order_update + _order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + + update_timestamp=order_update["updateTime"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[order_update["status"]], + client_order_id=order_update["clientOrderId"], + exchange_order_id=order_update["orderId"], + ) + return _order_update + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from Binance. Check API key and network connection.", + ) + await self._sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Wait for new messages from _user_stream_tracker.user_stream queue and processes them according to their + message channels. The respective UserStreamDataSource queues these messages. + """ + async for event_message in self._iter_user_event_queue(): + try: + await self._process_user_stream_event(event_message) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Unexpected error in user stream listener loop: {e}", exc_info=True) + await self._sleep(5.0) + + async def _process_user_stream_event(self, event_message: Dict[str, Any]): + event_type = event_message.get("e") + if event_type == "ORDER_TRADE_UPDATE": + order_message = event_message.get("o") + client_order_id = order_message.get("c", None) + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + if tracked_order is not None: + trade_id: str = str(order_message["t"]) + + if trade_id != "0": # Indicates that there has been a trade + + fee_asset = order_message.get("N", tracked_order.quote_asset) + fee_amount = Decimal(order_message.get("n", "0")) + position_side = order_message.get("ps", "LONG") + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "LONG" + or tracked_order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_id, + client_order_id=client_order_id, + exchange_order_id=str(order_message["i"]), + trading_pair=tracked_order.trading_pair, + fill_timestamp=order_message["T"] * 1e-3, + fill_price=Decimal(order_message["L"]), + fill_base_amount=Decimal(order_message["l"]), + fill_quote_amount=Decimal(order_message["L"]) * Decimal(order_message["l"]), + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update) + + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if tracked_order is not None: + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=event_message["T"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[order_message["X"]], + client_order_id=client_order_id, + exchange_order_id=str(order_message["i"]), + ) + + self._order_tracker.process_order_update(order_update) + + elif event_type == "ACCOUNT_UPDATE": + update_data = event_message.get("a", {}) + # update balances + for asset in update_data.get("B", []): + asset_name = asset["a"] + self._account_balances[asset_name] = Decimal(asset["wb"]) + self._account_available_balances[asset_name] = Decimal(asset["cw"]) + + # update position + for asset in update_data.get("P", []): + trading_pair = asset["s"] + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(trading_pair) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + + side = PositionSide[asset['ps']] + position = self._perpetual_trading.get_position(hb_trading_pair, side) + if position is not None: + amount = Decimal(asset["pa"]) + if amount == Decimal("0"): + pos_key = self._perpetual_trading.position_key(hb_trading_pair, side) + self._perpetual_trading.remove_position(pos_key) + else: + position.update_position(position_side=PositionSide[asset["ps"]], + unrealized_pnl=Decimal(asset["up"]), + entry_price=Decimal(asset["ep"]), + amount=Decimal(asset["pa"])) + else: + await self._update_positions() + elif event_type == "MARGIN_CALL": + positions = event_message.get("p", []) + total_maint_margin_required = Decimal(0) + # total_pnl = 0 + negative_pnls_msg = "" + for position in positions: + trading_pair = position["s"] + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(trading_pair) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + existing_position = self._perpetual_trading.get_position(hb_trading_pair, PositionSide[position['ps']]) + if existing_position is not None: + existing_position.update_position(position_side=PositionSide[position["ps"]], + unrealized_pnl=Decimal(position["up"]), + amount=Decimal(position["pa"])) + total_maint_margin_required += Decimal(position.get("mm", "0")) + if float(position.get("up", 0)) < 1: + negative_pnls_msg += f"{hb_trading_pair}: {position.get('up')}, " + self.logger().warning("Margin Call: Your position risk is too high, and you are at risk of " + "liquidation. Close your positions or add additional margin to your wallet.") + self.logger().info(f"Margin Required: {total_maint_margin_required}. " + f"Negative PnL assets: {negative_pnls_msg}.") + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + + Parameters + ---------- + exchange_info_dict: + Trading rules dictionary response from the exchange + """ + rules: list = exchange_info_dict.get("symbols", []) + return_val: list = [] + for rule in rules: + try: + if web_utils.is_exchange_information_valid(rule): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["symbol"]) + filters = rule["filters"] + filt_dict = {fil["filterType"]: fil for fil in filters} + + min_order_size = Decimal(filt_dict.get("LOT_SIZE").get("minQty")) + step_size = Decimal(filt_dict.get("LOT_SIZE").get("stepSize")) + tick_size = Decimal(filt_dict.get("PRICE_FILTER").get("tickSize")) + min_notional = Decimal(filt_dict.get("MIN_NOTIONAL").get("notional")) + collateral_token = rule["marginAsset"] + + return_val.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=Decimal(tick_size), + min_base_amount_increment=Decimal(step_size), + min_notional_size=Decimal(min_notional), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + ) + except Exception as e: + self.logger().error( + f"Error parsing the trading pair rule {rule}. Error: {e}. Skipping...", exc_info=True + ) + return return_val + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(web_utils.is_exchange_information_valid, exchange_info.get("symbols", [])): + exchange_symbol = symbol_data["pair"] + base = symbol_data["baseAsset"] + quote = symbol_data["quoteAsset"] + trading_pair = combine_to_hb_trading_pair(base, quote) + if trading_pair in mapping.inverse: + self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote) + else: + mapping[exchange_symbol] = trading_pair + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + params = {"symbol": exchange_symbol} + response = await self._api_get( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL, + params=params) + price = float(response["lastPrice"]) + return price + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error( + f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}") + mapping.pop(current_exchange_symbol) + + async def _update_balances(self): + """ + Calls the REST API to update total and available balances. + """ + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get(path_url=CONSTANTS.ACCOUNT_INFO_URL, + is_auth_required=True) + assets = account_info.get("assets") + for asset in assets: + asset_name = asset.get("asset") + available_balance = Decimal(asset.get("availableBalance")) + wallet_balance = Decimal(asset.get("walletBalance")) + self._account_available_balances[asset_name] = available_balance + self._account_balances[asset_name] = wallet_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _update_positions(self): + positions = await self._api_get(path_url=CONSTANTS.POSITION_INFORMATION_URL, + is_auth_required=True) + for position in positions: + trading_pair = position.get("symbol") + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(trading_pair) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + position_side = PositionSide[position.get("positionSide")] + unrealized_pnl = Decimal(position.get("unRealizedProfit")) + entry_price = Decimal(position.get("entryPrice")) + amount = Decimal(position.get("positionAmt")) + leverage = Decimal(position.get("leverage")) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side) + if amount != 0: + _position = Position( + trading_pair=await self.trading_pair_associated_to_exchange_symbol(trading_pair), + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage + ) + self._perpetual_trading.set_position(pos_key, _position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _update_order_fills_from_trades(self): + last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL) + current_tick = int(self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL) + if current_tick > last_tick and len(self._order_tracker.active_orders) > 0: + trading_pairs_to_order_map: Dict[str, Dict[str, Any]] = defaultdict(lambda: {}) + for order in self._order_tracker.active_orders.values(): + trading_pairs_to_order_map[order.trading_pair][order.exchange_order_id] = order + trading_pairs = list(trading_pairs_to_order_map.keys()) + tasks = [ + self._api_get( + path_url=CONSTANTS.ACCOUNT_TRADE_LIST_URL, + params={"symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair)}, + is_auth_required=True, + ) + for trading_pair in trading_pairs + ] + self.logger().debug(f"Polling for order fills of {len(tasks)} trading_pairs.") + results = await safe_gather(*tasks, return_exceptions=True) + for trades, trading_pair in zip(results, trading_pairs): + order_map = trading_pairs_to_order_map.get(trading_pair) + if isinstance(trades, Exception): + self.logger().network( + f"Error fetching trades update for the order {trading_pair}: {trades}.", + app_warning_msg=f"Failed to fetch trade update for {trading_pair}." + ) + continue + for trade in trades: + order_id = str(trade.get("orderId")) + if order_id in order_map: + tracked_order: InFlightOrder = order_map.get(order_id) + position_side = trade["positionSide"] + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "LONG" + or tracked_order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade["orderId"], + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade["time"] * 1e-3, + fill_price=Decimal(trade["price"]), + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update) + + async def _update_order_status(self): + """ + Calls the REST API to get order/trade updates for each in-flight order. + """ + last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL) + current_tick = int(self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL) + if current_tick > last_tick and len(self._order_tracker.active_orders) > 0: + tracked_orders = list(self._order_tracker.active_orders.values()) + tasks = [ + self._api_get( + path_url=CONSTANTS.ORDER_URL, + params={ + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair), + "origClientOrderId": order.client_order_id + }, + is_auth_required=True, + return_err=True, + ) + for order in tracked_orders + ] + self.logger().debug(f"Polling for order status updates of {len(tasks)} orders.") + results = await safe_gather(*tasks, return_exceptions=True) + + for order_update, tracked_order in zip(results, tracked_orders): + client_order_id = tracked_order.client_order_id + if client_order_id not in self._order_tracker.all_orders: + continue + if isinstance(order_update, Exception) or "code" in order_update: + if not isinstance(order_update, Exception) and \ + (order_update["code"] == -2013 or order_update["msg"] == "Order does not exist."): + await self._order_tracker.process_order_not_found(client_order_id) + else: + self.logger().network( + f"Error fetching status update for the order {client_order_id}: " f"{order_update}." + ) + continue + + new_order_update: OrderUpdate = OrderUpdate( + trading_pair=await self.trading_pair_associated_to_exchange_symbol(order_update['symbol']), + update_timestamp=order_update["updateTime"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[order_update["status"]], + client_order_id=order_update["clientOrderId"], + exchange_order_id=order_update["orderId"], + ) + + self._order_tracker.process_order_update(new_order_update) + + async def _get_position_mode(self) -> Optional[PositionMode]: + # To-do: ensure there's no active order or contract before changing position mode + if self._position_mode is None: + response = await self._api_get( + path_url=CONSTANTS.CHANGE_POSITION_MODE_URL, + is_auth_required=True, + limit_id=CONSTANTS.GET_POSITION_MODE_LIMIT_ID, + return_err=True + ) + self._position_mode = PositionMode.HEDGE if response.get("dualSidePosition") else PositionMode.ONEWAY + + return self._position_mode + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + msg = "" + success = True + initial_mode = await self._get_position_mode() + if initial_mode != mode: + params = { + "dualSidePosition": mode.value + } + response = await self._api_post( + path_url=CONSTANTS.CHANGE_POSITION_MODE_URL, + data=params, + is_auth_required=True, + limit_id=CONSTANTS.POST_POSITION_MODE_LIMIT_ID, + return_err=True + ) + if not (response["msg"] == "success" and response["code"] == 200): + success = False + return success, str(response) + self._position_mode = mode + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = {'symbol': symbol, 'leverage': leverage} + set_leverage = await self._api_post( + path_url=CONSTANTS.SET_LEVERAGE_URL, + data=params, + is_auth_required=True, + ) + success = False + msg = "" + if set_leverage["leverage"] == leverage: + success = True + else: + msg = 'Unable to set leverage' + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + payment_response = await self._api_get( + path_url=CONSTANTS.GET_INCOME_HISTORY_URL, + params={ + "symbol": exchange_symbol, + "incomeType": "FUNDING_FEE", + }, + is_auth_required=True, + ) + funding_info_response = await self._api_get( + path_url=CONSTANTS.MARK_PRICE_URL, + params={ + "symbol": exchange_symbol, + }, + ) + sorted_payment_response = sorted(payment_response, key=lambda a: a.get('time', 0), reverse=True) + if len(sorted_payment_response) < 1: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + return timestamp, funding_rate, payment + funding_payment = sorted_payment_response[0] + _payment = Decimal(funding_payment["income"]) + funding_rate = Decimal(funding_info_response["lastFundingRate"]) + timestamp = funding_payment["time"] + if _payment != Decimal("0"): + payment = _payment + else: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + return timestamp, funding_rate, payment diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..305e0bb --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_user_stream_data_source.py @@ -0,0 +1,144 @@ +import asyncio +import time +from typing import TYPE_CHECKING, List, Optional + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_auth import BinancePerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import ( + BinancePerpetualDerivative, + ) + + +class BinancePerpetualUserStreamDataSource(UserStreamTrackerDataSource): + LISTEN_KEY_KEEP_ALIVE_INTERVAL = 1800 # Recommended to Ping/Update listen key to keep connection alive + HEARTBEAT_TIME_INTERVAL = 30.0 + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: BinancePerpetualAuth, + connector: 'BinancePerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DOMAIN, + ): + + super().__init__() + self._domain = domain + self._api_factory = api_factory + self._auth = auth + self._ws_assistants: List[WSAssistant] = [] + self._connector = connector + self._current_listen_key = None + self._listen_for_user_stream_task = None + self._last_listen_key_ping_ts = None + + self._manage_listen_key_task = None + self._listen_key_initialized_event: asyncio.Event = asyncio.Event() + + @property + def last_recv_time(self) -> float: + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def _get_listen_key(self): + rest_assistant = await self._api_factory.get_rest_assistant() + try: + data = await rest_assistant.execute_request( + url=web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, + headers=self._auth.header_for_authentication() + ) + except asyncio.CancelledError: + raise + except Exception as exception: + raise IOError(f"Error fetching user stream listen key. Error: {exception}") + + return data["listenKey"] + + async def _ping_listen_key(self) -> bool: + try: + data = await self._connector._api_put( + path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, + params={"listenKey": self._current_listen_key}, + is_auth_required=True, + return_err=True) + if "code" in data: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {data}") + return False + + except asyncio.CancelledError: + raise + except Exception as exception: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {exception}") + return False + + return True + + async def _manage_listen_key_task_loop(self): + try: + while True: + now = int(time.time()) + if self._current_listen_key is None: + self._current_listen_key = await self._get_listen_key() + self.logger().info(f"Successfully obtained listen key {self._current_listen_key}") + self._listen_key_initialized_event.set() + self._last_listen_key_ping_ts = int(time.time()) + + if now - self._last_listen_key_ping_ts >= self.LISTEN_KEY_KEEP_ALIVE_INTERVAL: + success: bool = await self._ping_listen_key() + if not success: + self.logger().error("Error occurred renewing listen key ...") + break + else: + self.logger().info(f"Refreshed listen key {self._current_listen_key}.") + self._last_listen_key_ping_ts = int(time.time()) + else: + await self._sleep(self.LISTEN_KEY_KEEP_ALIVE_INTERVAL) + finally: + self._current_listen_key = None + self._listen_key_initialized_event.clear() + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + self._manage_listen_key_task = safe_ensure_future(self._manage_listen_key_task_loop()) + await self._listen_key_initialized_event.wait() + + ws: WSAssistant = await self._get_ws_assistant() + url = f"{web_utils.wss_url(CONSTANTS.PRIVATE_WS_ENDPOINT, self._domain)}/{self._current_listen_key}" + await ws.connect(ws_url=url, ping_timeout=self.HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + Binance does not require any channel subscription. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + pass + + async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): + websocket_assistant and await websocket_assistant.disconnect() + self._manage_listen_key_task and self._manage_listen_key_task.cancel() + self._current_listen_key = None + self._listen_key_initialized_event.clear() + await self._sleep(5) diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py new file mode 100644 index 0000000..dd86d56 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_utils.py @@ -0,0 +1,76 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0002"), + taker_percent_fee_decimal=Decimal("0.0004"), + buy_percent_fee_deducted_from_returns=True +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + +BROKER_ID = "x-3QreWesy" + + +class BinancePerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="binance_perpetual", client_data=None) + binance_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + binance_perpetual_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance Perpetual API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + +KEYS = BinancePerpetualConfigMap.construct() + +OTHER_DOMAINS = ["binance_perpetual_testnet"] +OTHER_DOMAINS_PARAMETER = {"binance_perpetual_testnet": "binance_perpetual_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"binance_perpetual_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"binance_perpetual_testnet": [0.02, 0.04]} + + +class BinancePerpetualTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="binance_perpetual_testnet", client_data=None) + binance_perpetual_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance Perpetual testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + binance_perpetual_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance Perpetual testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "binance_perpetual" + + +OTHER_DOMAINS_KEYS = {"binance_perpetual_testnet": BinancePerpetualTestnetConfigMap.construct()} diff --git a/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_web_utils.py b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_web_utils.py new file mode 100644 index 0000000..b253a99 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/binance_perpetual_web_utils.py @@ -0,0 +1,100 @@ +from typing import Any, Callable, Dict, Optional + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BinancePerpetualRESTPreProcessor(RESTPreProcessorBase): + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + request.headers["Content-Type"] = ( + "application/json" if request.method == RESTMethod.POST else "application/x-www-form-urlencoded" + ) + return request + + +def public_rest_url(path_url: str, domain: str = "binance_perpetual"): + base_url = CONSTANTS.PERPETUAL_BASE_URL if domain == "binance_perpetual" else CONSTANTS.TESTNET_BASE_URL + return base_url + path_url + + +def private_rest_url(path_url: str, domain: str = "binance_perpetual"): + base_url = CONSTANTS.PERPETUAL_BASE_URL if domain == "binance_perpetual" else CONSTANTS.TESTNET_BASE_URL + return base_url + path_url + + +def wss_url(endpoint: str, domain: str = "binance_perpetual"): + base_ws_url = CONSTANTS.PERPETUAL_WS_URL if domain == "binance_perpetual" else CONSTANTS.TESTNET_WS_URL + return base_ws_url + endpoint + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + BinancePerpetualRESTPreProcessor(), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory( + throttler=throttler, + rest_pre_processors=[BinancePerpetualRESTPreProcessor()]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["serverTime"] + return server_time + + +def is_exchange_information_valid(rule: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + if rule["contractType"] == "PERPETUAL" and rule["status"] == "TRADING": + valid = True + else: + valid = False + return valid diff --git a/hummingbot/connector/derivative/binance_perpetual/dummy.pxd b/hummingbot/connector/derivative/binance_perpetual/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/binance_perpetual/dummy.pyx b/hummingbot/connector/derivative/binance_perpetual/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/binance_perpetual/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/bit_com_perpetual/__init__.py b/hummingbot/connector/derivative/bit_com_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..8041976 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_api_order_book_data_source.py @@ -0,0 +1,211 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_derivative import BitComPerpetualDerivative + + +class BitComPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + _bpobds_logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, Mapping[str, str]] = {} + _mapping_initialization_lock = asyncio.Lock() + + def __init__( + self, + trading_pairs: List[str], + connector: 'BitComPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DOMAIN + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._trading_pairs: List[str] = trading_pairs + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + self._snapshot_messages_queue_key = "order_book_snapshot" + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + symbol_info: Dict[str, Any] = await self._request_complete_funding_info(trading_pair) + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(symbol_info["data"]["index_price"]), + mark_price=Decimal(symbol_info["data"]["mark_price"]), + next_funding_utc_timestamp=int(symbol_info["data"]["time"]) + CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND, + rate=Decimal(symbol_info["data"]["funding_rate"]), + ) + return funding_info + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + params = { + "instrument_id": ex_trading_pair, + "level": "50" + } + + data = await self._connector._api_get( + path_url=CONSTANTS.SNAPSHOT_REST_URL, + params=params) + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_response.update({"trading_pair": trading_pair}) + snapshot_msg: OrderBookMessage = OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": snapshot_response["trading_pair"], + "update_id": int(snapshot_response['data']["timestamp"]), + "bids": snapshot_response['data']["bids"], + "asks": snapshot_response['data']["asks"] + }, timestamp=int(snapshot_response['data']["timestamp"])) + return snapshot_msg + + async def _connected_websocket_assistant(self) -> WSAssistant: + url = f"{web_utils.wss_url(self._domain)}" + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + trades_payload = { + "type": "subscribe", + "instruments": [symbol], + "channels": [CONSTANTS.TRADES_ENDPOINT_NAME], + "interval": "raw", + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + order_book_payload = { + "type": "subscribe", + "instruments": [symbol], + "channels": [CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME], + "interval": "raw", + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) + + funding_rate_payload = { + "type": "subscribe", + "instruments": [symbol], + "channels": [CONSTANTS.FUNDING_INFO_STREAM_NAME], + "interval": "100ms", + } + subscribe_fundingrate_request: WSJSONRequest = WSJSONRequest(payload=funding_rate_payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + await ws.send(subscribe_fundingrate_request) + + self.logger().info("Subscribed to public order book, trade and fundingrate channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error occurred subscribing to order book data streams.") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "result" not in event_message: + stream_name = event_message.get("channel") + if "depth" in stream_name: + if event_message["data"]["type"] == "update": + channel = self._diff_messages_queue_key + elif event_message["data"]["type"] == "snapshot": + channel = self._snapshot_messages_queue_key + elif "trade" in stream_name: + channel = self._trade_messages_queue_key + elif "ticker" in stream_name: + channel = self._funding_info_messages_queue_key + return channel + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + timestamp: float = raw_message["timestamp"] * 1e-3 + raw_message["data"]["instrument_id"] = await self._connector.trading_pair_associated_to_exchange_symbol( + raw_message["data"]["instrument_id"]) + data = raw_message["data"] + order_book_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": data["instrument_id"], + "update_id": data["sequence"], + "bids": [[i[1], i[2]] for i in data["changes"] if i[0] == 'buy'], + "asks": [[i[1], i[2]] for i in data["changes"] if i[0] == 'sell'], + }, timestamp=timestamp) + message_queue.put_nowait(order_book_message) + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + timestamp: float = raw_message["timestamp"] * 1e-3 + raw_message["data"]["instrument_id"] = await self._connector.trading_pair_associated_to_exchange_symbol( + raw_message["data"]["instrument_id"]) + data = raw_message["data"] + order_book_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": data["instrument_id"], + "update_id": data["sequence"], + "bids": data["bids"], + "asks": data["asks"] + }, timestamp=timestamp) + message_queue.put_nowait(order_book_message) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + timestamp: float = raw_message["timestamp"] * 1e-3 + + data = raw_message["data"] + for trade_data in data: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(trade_data["instrument_id"]) + trade_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": trading_pair, + "trade_type": float(TradeType.SELL.value) if trade_data["side"] == "sell" else float( + TradeType.BUY.value), + "trade_id": trade_data["trade_id"], + "price": trade_data["price"], + "amount": trade_data["qty"] + }, timestamp=timestamp) + + message_queue.put_nowait(trade_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + + data: Dict[str, Any] = raw_message["data"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(data["instrument_id"]) + + if trading_pair not in self._trading_pairs: + return + funding_info = FundingInfoUpdate( + trading_pair=trading_pair, + mark_price=Decimal(data["mark_price"]), + next_funding_utc_timestamp=(int(data["time"] / CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND) + 1) * + CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND, + rate=Decimal(data["funding_rate"]), + ) + + message_queue.put_nowait(funding_info) + + async def _request_complete_funding_info(self, trading_pair: str): + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + data = await self._connector._api_get( + path_url=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL, + params={"instrument_id": ex_trading_pair}) + return data diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_auth.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_auth.py new file mode 100644 index 0000000..2c37ea0 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_auth.py @@ -0,0 +1,96 @@ +import hashlib +import hmac +import json +import time +from collections import OrderedDict +from typing import Any, Dict +from urllib.parse import urlparse + +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class BitComPerpetualAuth(AuthBase): + """ + Auth class required by BitCom Perpetual API + """ + + def __init__(self, api_key: str, api_secret: str): + self._api_key: str = api_key + self._api_secret: str = api_secret + + def generate_signature_from_payload(self, payload: str) -> str: + secret = bytes(self._api_secret.encode("utf-8")) + signature = hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() + return signature + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + _path = urlparse(request.url).path + if request.method == RESTMethod.POST: + request.data = self.add_auth_to_params_post(request.data, _path) + else: + request.params = self.add_auth_to_params(request.params, _path) + request.headers = {"X-Bit-Access-Key": self._api_key} + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + return request # pass-through + + def _encode_list(self, item_list): + list_val = [] + for item in item_list: + obj_val = self._encode_object(item) + list_val.append(obj_val) + sorted_list = sorted(list_val) + output = '&'.join(sorted_list) + output = '[' + output + ']' + return output + + def _encode_object(self, param_map): + sorted_keys = sorted(param_map.keys()) + ret_list = [] + for key in sorted_keys: + val = param_map[key] + if isinstance(val, list): + list_val = self._encode_list(val) + ret_list.append(f'{key}={list_val}') + elif isinstance(val, dict): + # call encode_object recursively + dict_val = self._encode_object(val) + ret_list.append(f'{key}={dict_val}') + elif isinstance(val, bool): + bool_val = str(val).lower() + ret_list.append(f'{key}={bool_val}') + else: + general_val = str(val) + ret_list.append(f'{key}={general_val}') + + sorted_list = sorted(ret_list) + output = '&'.join(sorted_list) + return output + + def add_auth_to_params(self, params: Dict[str, Any], path): + timestamp = int(self._get_timestamp() * 1e3) + + request_params = OrderedDict(params or {}) + request_params.update({'timestamp': timestamp}) + str_to_sign = path + '&' + self._encode_object(request_params) + request_params["signature"] = self.generate_signature_from_payload(payload=str_to_sign) + + return request_params + + def add_auth_to_params_post(self, params: str, path): + timestamp = int(self._get_timestamp() * 1e3) + + data = json.loads(params) if params is not None else {} + + request_params = OrderedDict(data or {}) + request_params.update({'timestamp': timestamp}) + str_to_sign = path + '&' + self._encode_object(request_params) + request_params["signature"] = self.generate_signature_from_payload(payload=str_to_sign) + res = json.dumps(request_params) + return res + + @staticmethod + def _get_timestamp(): + return time.time() diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_constants.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_constants.py new file mode 100644 index 0000000..3a5ac62 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_constants.py @@ -0,0 +1,114 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "bit_com_perpetual" +BROKER_ID = "HBOT" +MAX_ORDER_ID_LEN = 32 + +DOMAIN = EXCHANGE_NAME +TESTNET_DOMAIN = "bit_com_perpetual_testnet" + +PERPETUAL_BASE_URL = "https://api.bit.com" + +TESTNET_BASE_URL = "https://betaapi.bitexch.dev" + +PERPETUAL_WS_URL = "wss://ws.bit.com" + +TESTNET_WS_URL = "wss://betaws.bitexch.dev" + +FUNDING_RATE_INTERNAL_MIL_SECOND = 10 * 1000 + +CURRENCY = "USD" + +SNAPSHOT_REST_URL = "/linear/v1/orderbooks" + +TICKER_PRICE_CHANGE_URL = "/linear/v1/tickers" + +EXCHANGE_INFO_URL = "/linear/v1/instruments" + +PING_URL = "/linear/v1/system/time" + +GET_LAST_FUNDING_RATE_PATH_URL = "/linear/v1/funding_rate" + +# Private API v1 Endpoints + +CANCEL_ORDER_URL = "/linear/v1/cancel_orders" + +ACCOUNT_TRADE_LIST_URL = "/linear/v1/user/trades" + +ORDER_URL = "/linear/v1/orders" + +CREATE_ORDER_URL = "/linear/v1/orders" + +SET_LEVERAGE_URL = "/linear/v1/leverage_ratio" +USERSTREAM_AUTH_URL = "/v1/ws/auth" +USER_TRADES_ENDPOINT_NAME = "user_trade" +USER_ORDERS_ENDPOINT_NAME = "order" +USER_POSITIONS_ENDPOINT_NAME = "position" +USER_BALANCES_ENDPOINT_NAME = "um_account" +ORDERS_UPDATE_ENDPOINT_NAME = "depth" +TRADES_ENDPOINT_NAME = "trade" +FUNDING_INFO_STREAM_NAME = "ticker" + +ACCOUNT_INFO_URL = "/um/v1/accounts" + +POSITION_INFORMATION_URL = "/linear/v1/positions" + +# Order Statuses + +ORDER_STATE = { + "pending": OrderState.OPEN, + "open": OrderState.OPEN, + "filled": OrderState.FILLED, + "cancelled": OrderState.CANCELED, + "expired": OrderState.CANCELED, + "rejected": OrderState.FAILED, +} + +PUBLIC_URL_POINTS_LIMIT_ID = "PublicPoints" +PRIVATE_TRADE_URL_POINTS_LIMIT_ID = "PrivateTradePoints" # includes place-orders +PRIVATE_OTHER_URL_POINTS_LIMIT_ID = "PrivateOtherPoints" # includes place-orders +UM_PUBLIC_URL_POINTS_LIMIT_ID = "UmPublicPoints" +UM_PRIVATE_URL_POINTS_LIMIT_ID = "UmPrivatePoints" # includes place-orders + +HEARTBEAT_TIME_INTERVAL = 30.0 + +MAX_REQUEST = 10 + +RATE_LIMITS = [ + # Pool Limits + + RateLimit(limit_id=PUBLIC_URL_POINTS_LIMIT_ID, limit=10, time_interval=1), + RateLimit(limit_id=PRIVATE_TRADE_URL_POINTS_LIMIT_ID, limit=10, time_interval=1), + RateLimit(limit_id=PRIVATE_OTHER_URL_POINTS_LIMIT_ID, limit=10, time_interval=1), + RateLimit(limit_id=UM_PUBLIC_URL_POINTS_LIMIT_ID, limit=10, time_interval=1), + RateLimit(limit_id=UM_PRIVATE_URL_POINTS_LIMIT_ID, limit=10, time_interval=1), + + # Weight Limits for individual endpoints + RateLimit(limit_id=SNAPSHOT_REST_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=TICKER_PRICE_CHANGE_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=EXCHANGE_INFO_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=PING_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_OTHER_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=CREATE_ORDER_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_TRADE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=CANCEL_ORDER_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_TRADE_URL_POINTS_LIMIT_ID)]), + + RateLimit(limit_id=ACCOUNT_TRADE_LIST_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_OTHER_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=SET_LEVERAGE_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_OTHER_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ACCOUNT_INFO_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(UM_PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=POSITION_INFORMATION_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_OTHER_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=GET_LAST_FUNDING_RATE_PATH_URL, limit=MAX_REQUEST, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=USERSTREAM_AUTH_URL, limit=MAX_REQUEST, time_interval=1), +] diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_derivative.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_derivative.py new file mode 100644 index 0000000..9f8b55e --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_derivative.py @@ -0,0 +1,768 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.derivative.bit_com_perpetual import ( + bit_com_perpetual_constants as CONSTANTS, + bit_com_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_api_order_book_data_source import ( + BitComPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_auth import BitComPerpetualAuth +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_user_stream_data_source import ( + BitComPerpetualUserStreamDataSource, +) +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +bpm_logger = None + + +class BitComPerpetualDerivative(PerpetualDerivativePyBase): + web_utils = web_utils + SHORT_POLL_INTERVAL = 5.0 + LONG_POLL_INTERVAL = 120.0 + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + bit_com_perpetual_api_key: str = None, + bit_com_perpetual_api_secret: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DOMAIN, + ): + self.bit_com_perpetual_api_key = bit_com_perpetual_api_key + self.bit_com_perpetual_secret_key = bit_com_perpetual_api_secret + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._position_mode = None + self._last_trade_history_timestamp = None + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> BitComPerpetualAuth: + return BitComPerpetualAuth(self.bit_com_perpetual_api_key, self.bit_com_perpetual_secret_key) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.BROKER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.PING_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def supported_position_modes(self): + """ + This method needs to be overridden to provide the accurate information depending on the exchange. + """ + return [PositionMode.ONEWAY] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + auth=self._auth) + + async def _make_trading_rules_request(self) -> Any: + exchange_info = await self._api_get(path_url=self.trading_rules_request_path, + params={"currency": CONSTANTS.CURRENCY}) + return exchange_info + + async def _make_trading_pairs_request(self) -> Any: + exchange_info = await self._api_get(path_url=self.trading_pairs_request_path, + params={"currency": CONSTANTS.CURRENCY}) + return exchange_info + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _update_trading_rules(self): + exchange_info = await self._api_get(path_url=self.trading_rules_request_path, + params={"currency": CONSTANTS.CURRENCY}) + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + + async def _initialize_trading_pair_symbol_map(self): + try: + exchange_info = await self._api_get(path_url=self.trading_pairs_request_path, + params={"currency": CONSTANTS.CURRENCY}) + + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BitComPerpetualAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BitComPerpetualUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + api_params = { + "label": order_id, + "currency": CONSTANTS.CURRENCY, + } + cancel_result = await self._api_post( + path_url=CONSTANTS.CANCEL_ORDER_URL, + data=api_params, + is_auth_required=True) + if cancel_result['data']['num_cancelled'] == 1: + return True + return False + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + + amount_str = f"{amount:f}" + price_str = f"{price:f}" + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"instrument_id": symbol, + "price": price_str, + "qty": amount_str, + "side": "buy" if trade_type is TradeType.BUY else "sell", + "order_type": "market" if order_type is OrderType.MARKET else "limit", + "label": order_id + } + if order_type == OrderType.MARKET: + del api_params["price"] + if order_type == OrderType.LIMIT_MAKER: + api_params["post_only"] = False + api_params["reject_post_only"] = True + + order_result = await self._api_post( + path_url=CONSTANTS.CREATE_ORDER_URL, + data=api_params, + is_auth_required=True) + o_id = str(order_result['data']["order_id"]) + transact_time = order_result['data']["updated_at"] + return (o_id, transact_time) + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + # This method in the base ExchangePyBase, makes an API call for each order. + # Given the rate limit of the API method and the breadth of info provided by the method + # the mitigation proposal is to collect all orders in one shot, then parse them + # Note that this is limited to 100 orders (pagination) + all_trades_updates: List[TradeUpdate] = [] + if len(orders) > 0: + try: + all_trades_updates: List[TradeUpdate] = await self._all_trade_updates(orders=orders) + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Failed to fetch trade updates. Error: {request_error}", + exc_info=request_error, + ) + for trade_update in all_trades_updates: + self._order_tracker.process_trade_update(trade_update) + + async def _all_trade_updates(self, orders: List[InFlightOrder]) -> List[TradeUpdate]: + trade_updates = [] + if len(orders) > 0: + trading_pairs_to_order_map: Dict[str, Dict[str, Any]] = defaultdict(lambda: {}) + for order in orders: + trading_pairs_to_order_map[order.trading_pair][order.exchange_order_id] = order + trading_pairs = list(trading_pairs_to_order_map.keys()) + tasks = [ + self._api_get( + path_url=CONSTANTS.ACCOUNT_TRADE_LIST_URL, + params={ + "currency": CONSTANTS.CURRENCY, + "instrument_id": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + }, + is_auth_required=True) + for trading_pair in trading_pairs + ] + self.logger().debug(f"Polling for order fills of {len(tasks)} trading_pairs.") + results = await safe_gather(*tasks, return_exceptions=True) + for trades, trading_pair in zip(results, trading_pairs): + order_map = trading_pairs_to_order_map.get(trading_pair) + if isinstance(trades, Exception) or trades.get("code") != 0: + self.logger().network( + f"Error fetching trades update for the order {trading_pair}: {trades}.", + app_warning_msg=f"Failed to fetch trade update for {trading_pair}." + ) + continue + for trade in trades.get("data", []): + order_id = str(trade.get("order_id")) + if order_id in order_map: + tracked_order: InFlightOrder = order_map.get(order_id) + position_side = "LONG" if trade["side"] == "buy" else "SHORT" + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "LONG" + or tracked_order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + fee_asset = tracked_order.quote_asset + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=[TokenAmount(amount=Decimal(trade["fee"]), token=fee_asset)] + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=str(trade["trade_id"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade["order_id"], + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade["created_at"] * 1e-3, + fill_price=Decimal(trade["price"]), + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["price"]) * Decimal(trade["qty"]), + fee=fee, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + raise Exception("Developer: This method should not be called, it is obsoleted for bit_com") + trade_updates = [] + try: + exchange_order_id = await order.get_exchange_order_id() + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.ACCOUNT_TRADE_LIST_URL, + params={ + "currency": CONSTANTS.CURRENCY, + "instrument_id": trading_pair, + }, + is_auth_required=True) + + for trade in all_fills_response["data"]: + order_id = str(trade.get("order_id")) + if order_id == exchange_order_id: + position_side = "LONG" if trade["side"] == "buy" else "SHORT" + position_action = (PositionAction.OPEN + if (order.trade_type is TradeType.BUY and position_side == "LONG" + or order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + fee_asset = order.quote_asset + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=[TokenAmount(amount=Decimal(trade["fee"]), token=fee_asset)] + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=str(trade["trade_id"]), + client_order_id=order.client_order_id, + exchange_order_id=trade["order_id"], + trading_pair=order.trading_pair, + fill_timestamp=trade["created_at"] * 1e-3, + fill_price=Decimal(trade["price"]), + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["price"]) * Decimal(trade["qty"]), + fee=fee, + ) + trade_updates.append(trade_update) + + except asyncio.TimeoutError: + raise IOError(f"Skipped order update with order fills for {order.client_order_id} " + "- waiting for exchange order id.") + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + order_update = await self._api_get( + path_url=CONSTANTS.ORDER_URL, + params={ + "currency": CONSTANTS.CURRENCY, + "instrument_id": trading_pair, + "label": tracked_order.client_order_id + }, + is_auth_required=True) + current_state = order_update["data"][0]["status"] + _order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=order_update["data"][0]["updated_at"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[current_state], + client_order_id=order_update["data"][0]["label"], + exchange_order_id=order_update["data"][0]["order_id"], + ) + return _order_update + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from Bit. Check API key and network connection.", + ) + await self._sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Listens to messages from _user_stream_tracker.user_stream queue. + Traders, Orders, and Balance updates from the WS. + """ + user_channels = [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCES_ENDPOINT_NAME, + ] + async for event_message in self._iter_user_event_queue(): + try: + if isinstance(event_message, dict): + channel: str = event_message.get("channel", None) + results: List[Dict[str, Any]] = event_message.get("data", None) + elif event_message is asyncio.CancelledError: + raise asyncio.CancelledError + else: + raise Exception(event_message) + if channel not in user_channels: + self.logger().error( + f"Unexpected message in user stream: {event_message}.", exc_info=True) + continue + + if channel == CONSTANTS.USER_TRADES_ENDPOINT_NAME: + for trade_msg in results: + self._process_trade_message(trade_msg) + elif channel == CONSTANTS.USER_ORDERS_ENDPOINT_NAME: + for order_msg in results: + self._process_order_message(order_msg) + elif channel == CONSTANTS.USER_POSITIONS_ENDPOINT_NAME: + for position_msg in results: + await self._process_account_position_message(position_msg) + elif channel == CONSTANTS.USER_BALANCES_ENDPOINT_NAME: + self._process_balance_message_ws(results) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + def _process_trade_message(self, trade: Dict[str, Any], client_order_id: Optional[str] = None): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + Example Trade: + """ + client_order_id = client_order_id or str(trade.get("label", "")) + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + + if tracked_order is None: + self.logger().debug(f"Ignoring trade message with id {client_order_id}: not in in_flight_orders.") + else: + position_side = "LONG" if trade["side"] == "buy" else "SHORT" + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "LONG" + or tracked_order.trade_type is TradeType.SELL and position_side == "SHORT") + else PositionAction.CLOSE) + fee_asset = tracked_order.quote_asset + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=[TokenAmount(amount=Decimal(trade["fee"]), token=fee_asset)] + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=str(trade["trade_id"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade["order_id"], + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade["created_at"] * 1e-3, + fill_price=Decimal(trade["price"]), + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["price"]) * Decimal(trade["qty"]), + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update) + + async def _process_account_position_message(self, position_msg: Dict[str, Any]): + """ + Updates position + :param position_msg: The position event message payload + """ + ex_trading_pair = position_msg["instrument_id"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + amount = Decimal(position_msg["qty"]) + position_side = PositionSide.LONG if Decimal(position_msg.get("qty")) > 0 else PositionSide.SHORT + + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + entry_price = Decimal(str(position_msg["avg_price"])) + position = self._perpetual_trading.get_position(trading_pair, position_side) + if position is not None: + if amount == Decimal("0"): + self._perpetual_trading.remove_position(pos_key) + else: + position.update_position(position_side=position_side, + unrealized_pnl=Decimal(position_msg['position_session_upl']), + entry_price=entry_price, + amount=amount) + else: + await self._update_positions() + + def _process_order_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancelation or failure event if needed. + + :param order_msg: The order response from either REST or web socket API (they are of the same format) + + Example Order: + """ + client_order_id = str(order_msg.get("label", "")) + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if not tracked_order: + self.logger().debug(f"Ignoring order message with id {client_order_id}: not in in_flight_orders.") + return + # order_update = self._create_order_update_with_order_status_data(order_status=order_msg, order=tracked_order) + current_state = order_msg["status"] + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=order_msg["updated_at"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[current_state], + client_order_id=order_msg["label"], + exchange_order_id=order_msg["order_id"], + ) + self._order_tracker.process_order_update(order_update=order_update) + + def _process_balance_message_ws(self, balance_update): + for account in balance_update["details"]: + asset_name = account["currency"] + self._account_available_balances[asset_name] = Decimal(str(account["available_balance"])) + self._account_balances[asset_name] = Decimal(str(account["equity"])) + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + + Parameters + ---------- + exchange_info_dict: + Trading rules dictionary response from the exchange + """ + rules: list = exchange_info_dict.get("data", []) + return_val: list = [] + for rule in rules: + try: + if web_utils.is_exchange_information_valid(rule): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["instrument_id"]) + min_order_size = Decimal(rule.get("min_size")) + step_size = Decimal(rule.get("size_step")) + tick_size = Decimal(rule.get("price_step")) + collateral_token = rule["quote_currency"] + + return_val.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=tick_size, + min_base_amount_increment=step_size, + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) + return return_val + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(web_utils.is_exchange_information_valid, exchange_info.get("data", [])): + exchange_symbol = symbol_data["instrument_id"] + base = symbol_data["base_currency"] + quote = symbol_data["quote_currency"] + trading_pair = combine_to_hb_trading_pair(base, quote) + if trading_pair in mapping.inverse: + self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote) + else: + mapping[exchange_symbol] = trading_pair + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + params = {"instrument_id": exchange_symbol} + response = await self._api_get( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL, + params=params) + price = float(response["data"]["last_price"]) + return price + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error( + f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}") + mapping.pop(current_exchange_symbol) + + async def _update_balances(self): + """ + Calls the REST API to update total and available balances. + """ + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get(path_url=CONSTANTS.ACCOUNT_INFO_URL, + is_auth_required=True, + ) + assets = account_info["data"].get("details") + for asset in assets: + asset_name = asset.get("currency") + available_balance = Decimal(asset.get("available_balance")) + wallet_balance = Decimal(asset.get("equity")) + self._account_available_balances[asset_name] = available_balance + self._account_balances[asset_name] = wallet_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _update_positions(self): + positions = await self._api_get(path_url=CONSTANTS.POSITION_INFORMATION_URL, + params={"currency": CONSTANTS.CURRENCY}, + is_auth_required=True, + ) + for position in positions["data"]: + ex_trading_pair = position.get("instrument_id") + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(ex_trading_pair) + + position_side = PositionSide.LONG if Decimal(position.get("qty")) > 0 else PositionSide.SHORT + unrealized_pnl = Decimal(position.get("position_session_upl")) + entry_price = Decimal(position.get("avg_price")) + amount = Decimal(position.get("qty")) + leverage = Decimal(position.get("leverage")) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side) + if amount != 0: + _position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage + ) + self._perpetual_trading.set_position(pos_key, _position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _get_position_mode(self) -> Optional[PositionMode]: + # To-do: ensure there's no active order or contract before changing position mode + return PositionMode.ONEWAY + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + msg = "" + success = True + initial_mode = await self._get_position_mode() + if initial_mode != mode: + msg = "bit_com only supports the ONEWAY position mode." + success = False + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + pair = trading_pair + params = {'pair': pair, 'leverage_ratio': str(leverage)} + try: + set_leverage = await self._api_request( + path_url=CONSTANTS.SET_LEVERAGE_URL, + data=params, + method=RESTMethod.POST, + is_auth_required=True, + ) + success = False + msg = "" + if Decimal(set_leverage["data"]["leverage_ratio"]) == Decimal(str(leverage)): + success = True + else: + msg = 'Unable to set leverage' + return success, msg + except Exception as exception: + success = False + msg = f"There was an error setting the leverage for {trading_pair} ({exception})" + + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + payment_response = await self._api_request( + path_url=CONSTANTS.POSITION_INFORMATION_URL, + params={ + "currency": CONSTANTS.CURRENCY, + "instrument_id": exchange_symbol, + }, + method=RESTMethod.GET, + is_auth_required=True, + ) + funding_info_response = await self._api_request( + path_url=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL, + params={ + "instrument_id": exchange_symbol, + }, + method=RESTMethod.GET, + ) + # todo + # if len(sorted_payment_response) < 1: + # TypeError: object of type 'NoneType' has no len() + sorted_payment_response = payment_response["data"] if payment_response["data"] else [] + if len(sorted_payment_response) < 1: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + return timestamp, funding_rate, payment + funding_payment = sorted_payment_response[0] + _payment = Decimal(funding_payment["session_funding"]) + funding_rate = Decimal(funding_info_response["data"]["funding_rate"]) + timestamp = funding_info_response["data"]["time"] * 1e-3 + if _payment != Decimal("0"): + payment = _payment + else: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + return timestamp, funding_rate, payment diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..dabbe3b --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_user_stream_data_source.py @@ -0,0 +1,132 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_web_utils as web_utils +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_derivative import BitComPerpetualDerivative + + +class BitComPerpetualUserStreamDataSource(UserStreamTrackerDataSource): + LISTEN_KEY_KEEP_ALIVE_INTERVAL = 1800 # Recommended to Ping/Update listen key to keep connection alive + HEARTBEAT_TIME_INTERVAL = 30.0 + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: AuthBase, + trading_pairs: List[str], + connector: 'BitComPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DOMAIN, + ): + + super().__init__() + self._domain = domain + self._api_factory = api_factory + self._auth = auth + self._ws_assistants: List[WSAssistant] = [] + self._connector = connector + self._current_listen_key = None + self._listen_for_user_stream_task = None + self._last_listen_key_ping_ts = None + self._trading_pairs: List[str] = trading_pairs + + self.token = None + + @property + def last_recv_time(self) -> float: + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def get_token(self): + data = None + try: + data = await self._connector._api_get(path_url=CONSTANTS.USERSTREAM_AUTH_URL, params={}, + is_auth_required=True) + except asyncio.CancelledError: + raise + except Exception as exception: + raise IOError( + f"Error fetching BitCom Perpetual user stream token. " + f"The response was {data}. Error: {exception}" + ) + return data['data']['token'] + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + ws: WSAssistant = await self._get_ws_assistant() + url = f"{web_utils.wss_url(self._domain)}" + await ws.connect(ws_url=url, ping_timeout=self.HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + self.token = await self.get_token() + + symbols = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + pairs = self._trading_pairs + payload = { + "type": "subscribe", + "instruments": symbols, + "channels": [CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_BALANCES_ENDPOINT_NAME, + ], + "pairs": pairs, + "categories": ["future"], + "interval": "raw", + "token": self.token, + } + subscribe_request: WSJSONRequest = WSJSONRequest( + payload=payload) + await websocket_assistant.send(subscribe_request) + + self.logger().info("Subscribed to private order changes channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): + websocket_assistant and await websocket_assistant.disconnect() + self.token = None + await self._sleep(5) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if event_message.get("channel") == 'subscription' and event_message['data']['code'] != 0: + err_msg = event_message + raise IOError({ + "label": "WSS_ERROR", + "message": f"Error received via websocket - {err_msg}." + }) + elif event_message.get("channel") in [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCES_ENDPOINT_NAME, + ]: + queue.put_nowait(event_message) diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_utils.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_utils.py new file mode 100644 index 0000000..c004b6c --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_utils.py @@ -0,0 +1,76 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0002"), + taker_percent_fee_decimal=Decimal("0.0005"), + buy_percent_fee_deducted_from_returns=True +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + +BROKER_ID = "x-3QreWesy" + + +class BitComPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bit_com_perpetual", client_data=None) + bit_com_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitCom Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bit_com_perpetual_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitCom Perpetual API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + +KEYS = BitComPerpetualConfigMap.construct() + +OTHER_DOMAINS = ["bit_com_perpetual_testnet"] +OTHER_DOMAINS_PARAMETER = {"bit_com_perpetual_testnet": "bit_com_perpetual_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"bit_com_perpetual_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"bit_com_perpetual_testnet": [0.02, 0.05]} + + +class BitComPerpetualTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bit_com_perpetual_testnet", client_data=None) + bit_com_perpetual_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitCom Perpetual testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bit_com_perpetual_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitCom Perpetual testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bit_com_perpetual" + + +OTHER_DOMAINS_KEYS = {"bit_com_perpetual_testnet": BitComPerpetualTestnetConfigMap.construct()} diff --git a/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_web_utils.py b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_web_utils.py new file mode 100644 index 0000000..3530e19 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/bit_com_perpetual_web_utils.py @@ -0,0 +1,82 @@ +import time +from typing import Any, Dict, Optional + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitComPerpetualRESTPreProcessor(RESTPreProcessorBase): + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + request.headers["Content-Type"] = ( + "application/json" + ) + return request + + +def private_rest_url(*args, **kwargs) -> str: + return rest_url(*args, **kwargs) + + +def public_rest_url(*args, **kwargs) -> str: + return rest_url(*args, **kwargs) + + +def rest_url(path_url: str, domain: str = "bit_com_perpetual"): + base_url = CONSTANTS.PERPETUAL_BASE_URL if domain == "bit_com_perpetual" else CONSTANTS.TESTNET_BASE_URL + return base_url + path_url + + +def wss_url(domain: str = "bit_com_perpetual"): + base_ws_url = CONSTANTS.PERPETUAL_WS_URL if domain == "bit_com_perpetual" else CONSTANTS.TESTNET_WS_URL + return base_ws_url + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + rest_pre_processors=[BitComPerpetualRESTPreProcessor()], + auth=auth) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory( + throttler=throttler, + rest_pre_processors=[BitComPerpetualRESTPreProcessor()]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler, + domain +) -> float: + return time.time() + + +def is_exchange_information_valid(rule: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + if rule["category"] == "future" and rule["active"]: + valid = True + else: + valid = False + return valid diff --git a/hummingbot/connector/derivative/bit_com_perpetual/dummy.pxd b/hummingbot/connector/derivative/bit_com_perpetual/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/bit_com_perpetual/dummy.pyx b/hummingbot/connector/derivative/bit_com_perpetual/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/bit_com_perpetual/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/bitget_perpetual/__init__.py b/hummingbot/connector/derivative/bitget_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..7dae381 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_api_order_book_data_source.py @@ -0,0 +1,274 @@ +import asyncio +import sys +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.derivative.bitget_perpetual import ( + bitget_perpetual_constants as CONSTANTS, + bitget_perpetual_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest, WSPlainTextRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import BitgetPerpetualDerivative + + +class BitgetPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + + FULL_ORDER_BOOK_RESET_DELTA_SECONDS = sys.maxsize + + def __init__( + self, + trading_pairs: List[str], + connector: 'BitgetPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = "" + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._diff_messages_queue_key = "books" + self._trade_messages_queue_key = "trade" + self._funding_info_messages_queue_key = "ticker" + self._pong_response_event = None + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + funding_info_response = await self._request_complete_funding_info(trading_pair) + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(funding_info_response["amount"]), + mark_price=Decimal(funding_info_response["markPrice"]), + next_funding_utc_timestamp=int(int(funding_info_response["fundingTime"]) * 1e-3), + rate=Decimal(funding_info_response["fundingRate"]), + ) + return funding_info + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + await asyncio.wait_for( + super()._process_websocket_messages(websocket_assistant=websocket_assistant), + timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + except asyncio.TimeoutError: + if self._pong_response_event and not self._pong_response_event.is_set(): + # The PONG response for the previous PING request was never received + raise IOError("The user stream channel is unresponsive (pong response not received)") + self._pong_response_event = asyncio.Event() + await self._send_ping(websocket_assistant=websocket_assistant) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if event_message == CONSTANTS.WS_PONG_RESPONSE and self._pong_response_event: + self._pong_response_event.set() + elif "event" in event_message: + if event_message["event"] == "error": + raise IOError(f"Public channel subscription failed ({event_message})") + elif "arg" in event_message: + channel = event_message["arg"].get("channel") + if channel == CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC and event_message.get("action") == "snapshot": + channel = self._snapshot_messages_queue_key + + return channel + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + data = raw_message.get("data", {}) + inst_id = raw_message["arg"]["instId"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_instrument_id(instrument_id=inst_id) + + for book in data: + update_id = int(book["ts"]) + timestamp = update_id * 1e-3 + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": book["bids"], + "asks": book["asks"], + } + diff_message = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=order_book_message_content, + timestamp=timestamp + ) + + message_queue.put_nowait(diff_message) + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + data = raw_message.get("data", {}) + inst_id = raw_message["arg"]["instId"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_instrument_id(instrument_id=inst_id) + + for book in data: + update_id = int(book["ts"]) + timestamp = update_id * 1e-3 + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": book["bids"], + "asks": book["asks"], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=order_book_message_content, + timestamp=timestamp + ) + message_queue.put_nowait(snapshot_msg) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + data = raw_message.get("data", []) + inst_id = raw_message["arg"]["instId"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_instrument_id(instrument_id=inst_id) + + for trade_data in data: + ts_ms = int(trade_data[0]) + trade_type = float(TradeType.BUY.value) if trade_data[3] == "buy" else float(TradeType.SELL.value) + message_content = { + "trade_id": ts_ms, + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": trade_data[2], + "price": trade_data[1], + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=ts_ms * 1e-3, + ) + message_queue.put_nowait(trade_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + entries = raw_message.get("data", []) + inst_id = raw_message["arg"]["instId"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_instrument_id(instrument_id=inst_id) + + for entry in entries: + info_update = FundingInfoUpdate(trading_pair) + info_update.index_price = Decimal(entry["indexPrice"]) + info_update.mark_price = Decimal(entry["markPrice"]) + info_update.next_funding_utc_timestamp = int(entry["nextSettleTime"]) * 1e-3 + info_update.rate = Decimal(entry["capitalRate"]) + message_queue.put_nowait(info_update) + + async def _request_complete_funding_info(self, trading_pair: str) -> Dict[str, Any]: + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair), + } + + rest_assistant = await self._api_factory.get_rest_assistant() + endpoints = [ + CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL, + CONSTANTS.OPEN_INTEREST_PATH_URL, + CONSTANTS.MARK_PRICE_PATH_URL, + CONSTANTS.FUNDING_SETTLEMENT_TIME_PATH_URL + ] + tasks = [] + for endpoint in endpoints: + tasks.append(rest_assistant.execute_request( + url=web_utils.get_rest_url_for_endpoint(endpoint=endpoint), + throttler_limit_id=endpoint, + params=params, + method=RESTMethod.GET, + )) + results = await safe_gather(*tasks) + funding_info = {} + for result in results: + funding_info.update(result["data"]) + return funding_info + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect( + ws_url=CONSTANTS.WSS_URL, message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE + ) + return ws + + async def _subscribe_channels(self, ws: WSAssistant): + try: + payloads = [] + + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair_without_product_type( + trading_pair=trading_pair + ) + + for channel in [ + self._diff_messages_queue_key, + self._trade_messages_queue_key, + self._funding_info_messages_queue_key, + ]: + payloads.append({ + "instType": "mc", + "channel": channel, + "instId": symbol + }) + final_payload = { + "op": "subscribe", + "args": payloads, + } + subscribe_request = WSJSONRequest(payload=final_payload) + await ws.send(subscribe_request) + self.logger().info("Subscribed to public order book, trade and funding info channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + params = { + "symbol": symbol, + "limit": "100", + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.ORDER_BOOK_ENDPOINT), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.ORDER_BOOK_ENDPOINT, + ) + + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_data: Dict[str, Any] = snapshot_response["data"] + update_id: int = int(snapshot_data["timestamp"]) + snapshot_timestamp: float = update_id * 1e-3 + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(price, amount) for price, amount in snapshot_data.get("bids", [])], + "asks": [(price, amount) for price, amount in snapshot_data.get("asks", [])], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + snapshot_timestamp) + + return snapshot_msg + + async def _send_ping(self, websocket_assistant: WSAssistant): + ping_request = WSPlainTextRequest(payload=CONSTANTS.WS_PING_REQUEST) + await websocket_assistant.send(ping_request) diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_auth.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_auth.py new file mode 100644 index 0000000..2f224a6 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_auth.py @@ -0,0 +1,74 @@ +import base64 +import hmac +from typing import Any, Dict, List +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class BitgetPerpetualAuth(AuthBase): + """ + Auth class required by Bitget Perpetual API + """ + def __init__(self, api_key: str, secret_key: str, passphrase: str, time_provider: TimeSynchronizer): + self._api_key: str = api_key + self._secret_key: str = secret_key + self._passphrase: str = passphrase + self._time_provider: TimeSynchronizer = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + headers = {} + headers["Content-Type"] = "application/json" + headers["ACCESS-KEY"] = self._api_key + headers["ACCESS-TIMESTAMP"] = str(int(self._time_provider.time() * 1e3)) + headers["ACCESS-PASSPHRASE"] = self._passphrase + # headers["locale"] = "en-US" + + path = request.throttler_limit_id + if request.method is RESTMethod.GET: + path += "?" + urlencode(request.params) + + payload = str(request.data) + headers["ACCESS-SIGN"] = self._sign( + self._pre_hash(headers["ACCESS-TIMESTAMP"], request.method.value, path, payload), + self._secret_key) + request.headers.update(headers) + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. OKX does not use this + functionality + """ + return request # pass-through + + def get_ws_auth_payload(self) -> List[Dict[str, Any]]: + """ + Generates a dictionary with all required information for the authentication process + :return: a dictionary of authentication info including the request signature + """ + timestamp = str(int(self._time_provider.time())) + signature = self._sign(self._pre_hash(timestamp, "GET", "/user/verify", ""), self._secret_key) + auth_info = [ + { + "apiKey": self._api_key, + "passphrase": self._passphrase, + "timestamp": timestamp, + "sign": signature + } + ] + return auth_info + + @staticmethod + def _sign(message, secret_key): + mac = hmac.new(bytes(secret_key, encoding='utf8'), bytes(message, encoding='utf-8'), digestmod='sha256') + d = mac.digest() + return base64.b64encode(d).decode().strip() + + @staticmethod + def _pre_hash(timestamp: str, method: str, request_path: str, body: str): + if body in ["None", "null"]: + body = "" + return str(timestamp) + method.upper() + request_path + body diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_constants.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_constants.py new file mode 100644 index 0000000..9f2b2c7 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_constants.py @@ -0,0 +1,180 @@ +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionMode +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "bitget_perpetual" + +DEFAULT_DOMAIN = "" + +DEFAULT_TIME_IN_FORCE = "normal" + +REST_URL = "https://api.bitget.com" +WSS_URL = "wss://ws.bitget.com/mix/v1/stream" + +SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE = 20 # According to the documentation this has to be less than 30 seconds + +ORDER_TYPE_MAP = { + OrderType.LIMIT: "limit", + OrderType.MARKET: "market", +} + +POSITION_MODE_API_ONEWAY = "fixed" +POSITION_MODE_API_HEDGE = "crossed" +POSITION_MODE_MAP = { + PositionMode.ONEWAY: POSITION_MODE_API_ONEWAY, + PositionMode.HEDGE: POSITION_MODE_API_HEDGE, +} + +SYMBOL_AND_PRODUCT_TYPE_SEPARATOR = "_" +USDT_PRODUCT_TYPE = "UMCBL" +USDC_PRODUCT_TYPE = "CMCBL" +USD_PRODUCT_TYPE = "DMCBL" +ALL_PRODUCT_TYPES = [USDT_PRODUCT_TYPE, USDC_PRODUCT_TYPE, USD_PRODUCT_TYPE] + +# REST API Public Endpoints +LATEST_SYMBOL_INFORMATION_ENDPOINT = "/api/mix/v1/market/ticker" +QUERY_SYMBOL_ENDPOINT = "/api/mix/v1/market/contracts" +ORDER_BOOK_ENDPOINT = "/api/mix/v1/market/depth" +SERVER_TIME_PATH_URL = "/api/mix/v1/" +GET_LAST_FUNDING_RATE_PATH_URL = "/api/mix/v1/market/current-fundRate" +OPEN_INTEREST_PATH_URL = "/api/mix/v1/market/open-interest" +MARK_PRICE_PATH_URL = "/api/mix/v1/market/mark-price" + +# REST API Private Endpoints +SET_LEVERAGE_PATH_URL = "/api/mix/v1/account/setLeverage" +GET_POSITIONS_PATH_URL = "/api/mix/v1/position/allPosition" +PLACE_ACTIVE_ORDER_PATH_URL = "/api/mix/v1/order/placeOrder" +CANCEL_ACTIVE_ORDER_PATH_URL = "/api/mix/v1/order/cancel-order" +CANCEL_ALL_ACTIVE_ORDERS_PATH_URL = "/api/mix/v1/order/cancel-batch-orders" +QUERY_ACTIVE_ORDER_PATH_URL = "/api/mix/v1/order/detail" +USER_TRADE_RECORDS_PATH_URL = "/api/mix/v1/order/fills" +GET_WALLET_BALANCE_PATH_URL = "/api/mix/v1/account/accounts" +SET_POSITION_MODE_URL = "/api/mix/v1/account/setMarginMode" +GET_FUNDING_FEES_PATH_URL = "/api/mix/v1/account/accountBill" + +# Funding Settlement Time Span +FUNDING_SETTLEMENT_TIME_PATH_URL = "/api/mix/v1/market/funding-time" + +# WebSocket Public Endpoints +WS_PING_REQUEST = "ping" +WS_PONG_RESPONSE = "pong" +WS_ORDER_BOOK_EVENTS_TOPIC = "books" +WS_TRADES_TOPIC = "trade" +WS_INSTRUMENTS_INFO_TOPIC = "tickers" +WS_AUTHENTICATE_USER_ENDPOINT_NAME = "login" +WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME = "positions" +WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME = "orders" +WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME = "account" + +# Order Statuses +ORDER_STATE = { + "new": OrderState.OPEN, + "filled": OrderState.FILLED, + "full-fill": OrderState.FILLED, + "partial-fill": OrderState.PARTIALLY_FILLED, + "partially_filled": OrderState.PARTIALLY_FILLED, + "canceled": OrderState.CANCELED, + "cancelled": OrderState.CANCELED, +} + +# Request error codes +RET_CODE_OK = "00000" +RET_CODE_PARAMS_ERROR = "40007" +RET_CODE_API_KEY_INVALID = "40006" +RET_CODE_AUTH_TIMESTAMP_ERROR = "40005" +RET_CODE_ORDER_NOT_EXISTS = "43025" +RET_CODE_API_KEY_EXPIRED = "40014" + + +RATE_LIMITS = [ + RateLimit( + limit_id=LATEST_SYMBOL_INFORMATION_ENDPOINT, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=QUERY_SYMBOL_ENDPOINT, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=ORDER_BOOK_ENDPOINT, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=SERVER_TIME_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=GET_LAST_FUNDING_RATE_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=OPEN_INTEREST_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=MARK_PRICE_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=FUNDING_SETTLEMENT_TIME_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=SET_LEVERAGE_PATH_URL, + limit=5, + time_interval=2, + ), + RateLimit( + limit_id=GET_POSITIONS_PATH_URL, + limit=5, + time_interval=2, + ), + RateLimit( + limit_id=PLACE_ACTIVE_ORDER_PATH_URL, + limit=10, + time_interval=1, + ), + RateLimit( + limit_id=CANCEL_ACTIVE_ORDER_PATH_URL, + limit=10, + time_interval=1, + ), + RateLimit( + limit_id=CANCEL_ALL_ACTIVE_ORDERS_PATH_URL, + limit=10, + time_interval=1, + ), + RateLimit( + limit_id=QUERY_ACTIVE_ORDER_PATH_URL, + limit=20, + time_interval=1, + ), + RateLimit( + limit_id=USER_TRADE_RECORDS_PATH_URL, + limit=20, + time_interval=2, + ), + RateLimit( + limit_id=GET_WALLET_BALANCE_PATH_URL, + limit=20, + time_interval=2, + ), + RateLimit( + limit_id=SET_POSITION_MODE_URL, + limit=5, + time_interval=1, + ), + RateLimit( + limit_id=GET_FUNDING_FEES_PATH_URL, + limit=10, + time_interval=1, + ), +] diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_derivative.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_derivative.py new file mode 100644 index 0000000..9e20152 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_derivative.py @@ -0,0 +1,913 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +from bidict import bidict + +import hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bitget_perpetual import ( + bitget_perpetual_utils, + bitget_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_api_order_book_data_source import ( + BitgetPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_auth import BitgetPerpetualAuth +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_user_stream_data_source import ( + BitgetPerpetualUserStreamDataSource, +) +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + + +class BitgetPerpetualDerivative(PerpetualDerivativePyBase): + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + bitget_perpetual_api_key: str = None, + bitget_perpetual_secret_key: str = None, + bitget_perpetual_passphrase: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = "", + ): + + self.bitget_perpetual_api_key = bitget_perpetual_api_key + self.bitget_perpetual_secret_key = bitget_perpetual_secret_key + self.bitget_perpetual_passphrase = bitget_perpetual_passphrase + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._last_trade_history_timestamp = None + + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> BitgetPerpetualAuth: + return BitgetPerpetualAuth( + api_key=self.bitget_perpetual_api_key, + secret_key=self.bitget_perpetual_secret_key, + passphrase=self.bitget_perpetual_passphrase, + time_provider=self._time_synchronizer) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + # No instruction about client_oid length in the doc + return None + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.LIMIT, OrderType.MARKET] + + def supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules.get(trading_pair, None) + if trading_rule is None: + collateral_token = self._collateral_token_based_on_product_type(trading_pair=trading_pair) + else: + collateral_token = trading_rule.buy_order_collateral_token + + return collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + return self.get_buy_collateral_token(trading_pair=trading_pair) + + def start(self, clock: Clock, timestamp: float): + super().start(clock, timestamp) + if self.is_trading_required: + self.set_position_mode(PositionMode.HEDGE) + + async def check_network(self) -> NetworkStatus: + """ + Checks connectivity with the exchange using the API + + We need to reimplement this for Bitget exchange because the endpoint that returns the server status and time + by default responds with a 400 status that includes a valid content. + """ + result = NetworkStatus.NOT_CONNECTED + try: + response = await self._api_get(path_url=self.check_network_request_path, return_err=True) + if response.get("flag", False): + result = NetworkStatus.CONNECTED + except asyncio.CancelledError: + raise + except Exception: + result = NetworkStatus.NOT_CONNECTED + return result + + async def exchange_symbol_associated_to_pair_without_product_type(self, trading_pair: str) -> str: + full_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + return self._symbol_and_product_type(full_symbol=full_symbol)[0] + + async def trading_pair_associated_to_exchange_instrument_id(self, instrument_id: str) -> str: + symbol_without_product_type = instrument_id + + full_symbol = None + for product_type in [CONSTANTS.USDT_PRODUCT_TYPE, CONSTANTS.USD_PRODUCT_TYPE, CONSTANTS.USDC_PRODUCT_TYPE]: + candidate_symbol = (f"{symbol_without_product_type}" + f"{CONSTANTS.SYMBOL_AND_PRODUCT_TYPE_SEPARATOR}" + f"{product_type}") + try: + full_symbol = await self.trading_pair_associated_to_exchange_symbol(symbol=candidate_symbol) + except KeyError: + # If the trading pair was not found, the product type is not the correct one. Continue to keep trying + continue + else: + break + + if full_symbol is None: + raise ValueError(f"No trading pair associated to instrument ID {instrument_id}") + + return full_symbol + + async def product_type_for_trading_pair(self, trading_pair: str) -> str: + full_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + return self._symbol_and_product_type(full_symbol=full_symbol)[-1] + + def _symbol_and_product_type(self, full_symbol: str) -> str: + return full_symbol.split(CONSTANTS.SYMBOL_AND_PRODUCT_TYPE_SEPARATOR) + + def _collateral_token_based_on_product_type(self, trading_pair: str) -> str: + base, quote = split_hb_trading_pair(trading_pair=trading_pair) + + if quote == "USD": + collateral_token = base + else: + collateral_token = quote + + return collateral_token + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + ts_error_target_str = "Request timestamp expired" + is_time_synchronizer_related = ( + ts_error_target_str in error_description + ) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + data = { + "symbol": await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair), + "marginCoin": self.get_buy_collateral_token(tracked_order.trading_pair), + "orderId": tracked_order.exchange_order_id + } + cancel_result = await self._api_post( + path_url=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL, + data=data, + is_auth_required=True, + ) + response_code = cancel_result["code"] + + if response_code != CONSTANTS.RET_CODE_OK: + if response_code == CONSTANTS.RET_CODE_ORDER_NOT_EXISTS: + await self._order_tracker.process_order_not_found(order_id) + formatted_ret_code = self._format_ret_code_for_print(response_code) + raise IOError(f"{formatted_ret_code} - {cancel_result['msg']}") + + return True + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + if position_action is PositionAction.OPEN: + contract = "long" if trade_type == TradeType.BUY else "short" + else: + contract = "short" if trade_type == TradeType.BUY else "long" + margin_coin = (self.get_buy_collateral_token(trading_pair) + if trade_type == TradeType.BUY + else self.get_sell_collateral_token(trading_pair)) + data = { + "side": f"{position_action.name.lower()}_{contract}", + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair), + "marginCoin": margin_coin, + "size": str(amount), + "orderType": "limit" if order_type.is_limit_type() else "market", + "timeInForceValue": CONSTANTS.DEFAULT_TIME_IN_FORCE, + "clientOid": order_id, + } + if order_type.is_limit_type(): + data["price"] = str(price) + + resp = await self._api_post( + path_url=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL, + data=data, + is_auth_required=True, + ) + + if resp["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(resp["code"]) + raise IOError(f"Error submitting order {order_id}: {formatted_ret_code} - {resp['msg']}") + + return str(resp["data"]["orderId"]), self.current_timestamp + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fee_schema: TradeFeeSchema = self._trading_fees[trading_pair] + fee_rate = fee_schema.maker_percent_fee_decimal if is_maker else fee_schema.taker_percent_fee_decimal + fee = TradeFeeBase.new_spot_fee( + fee_schema=fee_schema, + trade_type=order_side, + percent=fee_rate, + ) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + symbol_data = [] + product_types = CONSTANTS.ALL_PRODUCT_TYPES + + for product_type in product_types: + exchange_info = await self._api_get( + path_url=self.trading_rules_request_path, + params={"productType": product_type.lower()}) + symbol_data.extend(exchange_info["data"]) + + for symbol_details in symbol_data: + if bitget_perpetual_utils.is_exchange_information_valid(exchange_info=symbol_details): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=symbol_details["symbol"]) + self._trading_fees[trading_pair] = TradeFeeSchema( + maker_percent_fee_decimal=Decimal(symbol_details["makerFeeRate"]), + taker_percent_fee_decimal=Decimal(symbol_details["takerFeeRate"]) + ) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth, + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BitgetPerpetualAPIOrderBookDataSource( + self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BitgetPerpetualUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + async def _update_balances(self): + """ + Calls REST API to update total and available balances + """ + balances = {} + trading_pairs_product_types = set([await self.product_type_for_trading_pair(trading_pair=trading_pair) + for trading_pair in self.trading_pairs]) + product_types = trading_pairs_product_types or CONSTANTS.ALL_PRODUCT_TYPES + + for product_type in product_types: + body_params = {"productType": product_type.lower()} + wallet_balance: Dict[str, Union[str, List[Dict[str, Any]]]] = await self._api_get( + path_url=CONSTANTS.GET_WALLET_BALANCE_PATH_URL, + params=body_params, + is_auth_required=True, + ) + + if wallet_balance["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(wallet_balance["code"]) + raise IOError(f"{formatted_ret_code} - {wallet_balance['msg']}") + + balances[product_type] = wallet_balance["data"] + + self._account_available_balances.clear() + self._account_balances.clear() + for product_type_balances in balances.values(): + for balance_data in product_type_balances: + asset_name = balance_data["marginCoin"] + current_available = self._account_available_balances.get(asset_name, Decimal(0)) + queried_available = (Decimal(str(balance_data["fixedMaxAvailable"])) + if self.position_mode is PositionMode.ONEWAY + else Decimal(str(balance_data["crossMaxAvailable"]))) + self._account_available_balances[asset_name] = current_available + queried_available + current_total = self._account_balances.get(asset_name, Decimal(0)) + queried_total = Decimal(str(balance_data["equity"])) + self._account_balances[asset_name] = current_total + queried_total + + async def _update_positions(self): + """ + Retrieves all positions using the REST API. + """ + position_data = [] + product_types = CONSTANTS.ALL_PRODUCT_TYPES + + for product_type in product_types: + body_params = {"productType": product_type.lower()} + raw_response: Dict[str, Any] = await self._api_get( + path_url=CONSTANTS.GET_POSITIONS_PATH_URL, + params=body_params, + is_auth_required=True, + ) + position_data.extend(raw_response["data"]) + + # Initial parsing of responses. + for position in position_data: + data = position + ex_trading_pair = data.get("symbol") + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(ex_trading_pair) + position_side = PositionSide.LONG if data["holdSide"] == "long" else PositionSide.SHORT + unrealized_pnl = Decimal(str(data["unrealizedPL"])) + entry_price = Decimal(str(data["averageOpenPrice"])) + amount = Decimal(str(data["total"])) + leverage = Decimal(str(data["leverage"])) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side) + if amount != s_decimal_0: + position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + try: + all_fills_response = await self._request_order_fills(order=order) + fills_data = all_fills_response.get("data", []) + + for fill_data in fills_data: + trade_update = self._parse_trade_update(trade_msg=fill_data, tracked_order=order) + trade_updates.append(trade_update) + except IOError as ex: + if not self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + raise + + return trade_updates + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(order.trading_pair) + body_params = { + "orderId": order.exchange_order_id, + "symbol": exchange_symbol, + } + res = await self._api_get( + path_url=CONSTANTS.USER_TRADE_RECORDS_PATH_URL, + params=body_params, + is_auth_required=True, + ) + return res + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + try: + order_status_data = await self._request_order_status_data(tracked_order=tracked_order) + order_msg = order_status_data["data"] + client_order_id = str(order_msg["clientOid"]) + + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=CONSTANTS.ORDER_STATE[order_msg["state"]], + client_order_id=client_order_id, + exchange_order_id=order_msg["orderId"], + ) + + return order_update + + except IOError as ex: + if self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + ) + else: + raise + + return order_update + + async def _request_order_status_data(self, tracked_order: InFlightOrder) -> Dict: + exchange_symbol = await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair) + query_params = { + "symbol": exchange_symbol, + "clientOid": tracked_order.client_order_id + } + if tracked_order.exchange_order_id is not None: + query_params["orderId"] = tracked_order.exchange_order_id + + resp = await self._api_get( + path_url=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, + params=query_params, + is_auth_required=True, + ) + + return resp + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = {"symbol": exchange_symbol} + + resp_json = await self._api_get( + path_url=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT, + params=params, + ) + + price = float(resp_json["data"]["last"]) + return price + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + if len(self.account_positions) > 0: + return False, "Cannot change position because active positions exist" + + msg = "" + success = True + + try: + api_mode = CONSTANTS.POSITION_MODE_MAP[mode] + + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + data = { + "symbol": exchange_symbol, + "marginMode": api_mode, + "marginCoin": self.get_buy_collateral_token(trading_pair) + } + + response = await self._api_post( + path_url=CONSTANTS.SET_POSITION_MODE_URL, + data=data, + is_auth_required=True, + ) + + response_code = response["code"] + + if response_code != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(response_code) + msg = f"{formatted_ret_code} - {response['msg']}" + success = False + except Exception as exception: + success = False + msg = f"There was an error changing the position mode ({exception})" + + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + if len(self.account_positions) > 0: + return False, "cannot change leverage because active positions exist" + + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + success = True + msg = "" + + try: + data = { + "symbol": exchange_symbol, + "marginCoin": self.get_buy_collateral_token(trading_pair), + "leverage": leverage + } + + resp: Dict[str, Any] = await self._api_post( + path_url=CONSTANTS.SET_LEVERAGE_PATH_URL, + data=data, + is_auth_required=True, + ) + + if resp["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(resp["code"]) + success = False + msg = f"{formatted_ret_code} - {resp['msg']}" + except Exception as exception: + success = False + msg = f"There was an error setting the leverage for {trading_pair} ({exception})" + + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + now = self._time_synchronizer.time() + start_time = self._last_funding_fee_payment_ts.get(trading_pair, now - (2 * self.funding_fee_poll_interval)) + params = { + "symbol": exchange_symbol, + "marginCoin": self.get_buy_collateral_token(trading_pair), + "startTime": str(int(start_time * 1e3)), + "endTime": str(int(now * 1e3)), + } + raw_response: Dict[str, Any] = await self._api_get( + path_url=CONSTANTS.GET_FUNDING_FEES_PATH_URL, + params=params, + is_auth_required=True, + ) + data: Dict[str, Any] = raw_response["data"]["result"] + settlement_fee: Optional[Dict[str, Any]] = next( + (fee_payment for fee_payment in data if "settle_fee" in fee_payment.get("business", "")), + None) + + if settlement_fee is None: + # An empty funding fee/payment is retrieved. + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + else: + funding_info = self._perpetual_trading._funding_info.get(trading_pair) + payment: Decimal = Decimal(str(settlement_fee["amount"])) + funding_rate: Decimal = funding_info.rate if funding_info is not None else Decimal(0) + timestamp: float = int(settlement_fee["cTime"]) * 1e-3 + return timestamp, funding_rate, payment + + async def _user_stream_event_listener(self): + """ + Listens to message in _user_stream_tracker.user_stream queue. + """ + async for event_message in self._iter_user_event_queue(): + try: + endpoint = event_message["arg"]["channel"] + payload = event_message["data"] + + if endpoint == CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME: + await self._process_account_position_event(payload) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME: + for order_msg in payload: + self._process_trade_event_message(order_msg) + self._process_order_event_message(order_msg) + self._process_balance_update_from_order_event(order_msg) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME: + for wallet_msg in payload: + self._process_wallet_event_message(wallet_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + + async def _process_account_position_event(self, position_entries: List[Dict[str, Any]]): + """ + Updates position + :param position_msg: The position event message payload + """ + all_position_keys = [] + + for position_msg in position_entries: + ex_trading_pair = position_msg["instId"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + position_side = PositionSide.LONG if position_msg["holdSide"] == "long" else PositionSide.SHORT + entry_price = Decimal(str(position_msg["averageOpenPrice"])) + amount = Decimal(str(position_msg["total"])) + leverage = Decimal(str(position_msg["leverage"])) + unrealized_pnl = Decimal(str(position_msg["upl"])) + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + all_position_keys.append(pos_key) + if amount != s_decimal_0: + position = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + # Bitget sends position events as snapshots. If a position is closed it is just not included in the snapshot + position_keys = list(self.account_positions.keys()) + positions_to_remove = (position_key for position_key in position_keys + if position_key not in all_position_keys) + for position_key in positions_to_remove: + self._perpetual_trading.remove_position(position_key) + + def _process_order_event_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancellation or failure event if needed. + + :param order_msg: The order event message payload + """ + order_status = CONSTANTS.ORDER_STATE[order_msg["status"]] + client_order_id = str(order_msg["clOrdId"]) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + if updatable_order is not None: + new_order_update: OrderUpdate = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=order_status, + client_order_id=client_order_id, + exchange_order_id=order_msg["ordId"], + ) + self._order_tracker.process_order_update(new_order_update) + + def _process_balance_update_from_order_event(self, order_msg: Dict[str, Any]): + order_status = CONSTANTS.ORDER_STATE[order_msg["status"]] + position_side = PositionSide[order_msg["posSide"].upper()] + trade_type = TradeType[order_msg["side"].upper()] + collateral_token = order_msg["tgtCcy"] + states_to_consider = [OrderState.OPEN, OrderState.CANCELED] + + is_open_long = position_side == PositionSide.LONG and trade_type == TradeType.BUY + is_open_short = position_side == PositionSide.SHORT and trade_type == TradeType.SELL + + order_amount = Decimal(order_msg["sz"]) + order_price = Decimal(order_msg["px"]) + margin_amount = (order_amount * order_price) / Decimal(order_msg["lever"]) + + if (collateral_token in self._account_available_balances + and order_status in states_to_consider + and (is_open_long or is_open_short)): + + multiplier = Decimal(-1) if order_status == OrderState.OPEN else Decimal(1) + self._account_available_balances[collateral_token] += margin_amount * multiplier + + def _process_trade_event_message(self, trade_msg: Dict[str, Any]): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + + :param trade_msg: The trade event message payload + """ + + client_order_id = str(trade_msg["clOrdId"]) + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + + if fillable_order is not None and "tradeId" in trade_msg: + trade_update = self._parse_websocket_trade_update(trade_msg=trade_msg, tracked_order=fillable_order) + if trade_update: + self._order_tracker.process_trade_update(trade_update) + + def _parse_websocket_trade_update(self, trade_msg: Dict, tracked_order: InFlightOrder) -> TradeUpdate: + trade_id: str = trade_msg["tradeId"] + + if trade_id is not None: + trade_id = str(trade_id) + fee_asset = trade_msg["fillFeeCcy"] + fee_amount = Decimal(trade_msg["fillFee"]) + position_side = trade_msg["side"] + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "buy" + or tracked_order.trade_type is TradeType.SELL and position_side == "sell") + else PositionAction.CLOSE) + + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + + exec_price = Decimal(trade_msg["fillPx"]) if "fillPx" in trade_msg else Decimal(trade_msg["px"]) + exec_time = int(trade_msg["fillTime"]) * 1e-3 + + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(trade_msg["ordId"]), + trading_pair=tracked_order.trading_pair, + fill_timestamp=exec_time, + fill_price=exec_price, + fill_base_amount=Decimal(trade_msg["fillSz"]), + fill_quote_amount=exec_price * Decimal(trade_msg["fillSz"]), + fee=fee, + ) + + return trade_update + + def _parse_trade_update(self, trade_msg: Dict, tracked_order: InFlightOrder) -> TradeUpdate: + trade_id: str = str(trade_msg["tradeId"]) + + fee_asset = tracked_order.quote_asset + fee_amount = Decimal(trade_msg["fee"]) + position_action = (PositionAction.OPEN if "open" == trade_msg["side"] else PositionAction.CLOSE) + + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + + exec_price = Decimal(trade_msg["price"]) + exec_time = int(trade_msg["cTime"]) * 1e-3 + + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(trade_msg["orderId"]), + trading_pair=tracked_order.trading_pair, + fill_timestamp=exec_time, + fill_price=exec_price, + fill_base_amount=Decimal(trade_msg["sizeQty"]), + fill_quote_amount=exec_price * Decimal(trade_msg["sizeQty"]), + fee=fee, + ) + + return trade_update + + def _process_wallet_event_message(self, wallet_msg: Dict[str, Any]): + """ + Updates account balances. + :param wallet_msg: The account balance update message payload + """ + symbol = wallet_msg.get("marginCoin", None) + if symbol is not None: + available = Decimal(str(wallet_msg["maxOpenPosAvailable"])) + total = Decimal(str(wallet_msg["equity"])) + self._account_balances[symbol] = total + self._account_available_balances[symbol] = available + + @staticmethod + def _format_ret_code_for_print(ret_code: Union[str, int]) -> str: + return f"ret_code <{ret_code}>" + + async def _market_data_for_all_product_types(self) -> List[Dict[str, Any]]: + all_exchange_info = [] + product_types = [CONSTANTS.USDT_PRODUCT_TYPE, CONSTANTS.USD_PRODUCT_TYPE] + + for product_type in product_types: + exchange_info = await self._api_get( + path_url=self.trading_pairs_request_path, + params={"productType": product_type.lower()}) + all_exchange_info.extend(exchange_info["data"]) + + # For USDC collateralized products we need to change the quote asset from USD to USDC, to avoid colitions + # in the trading pairs with markets for product type DMCBL + exchange_info = await self._api_get( + path_url=self.trading_pairs_request_path, + params={"productType": CONSTANTS.USDC_PRODUCT_TYPE.lower()}) + markets = exchange_info["data"] + for market_info in markets: + market_info["quoteCoin"] = market_info["supportMarginCoins"][0] + all_exchange_info.extend(markets) + + return all_exchange_info + + async def _initialize_trading_pair_symbol_map(self): + try: + all_exchange_info = await self._market_data_for_all_product_types() + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=all_exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: List[Dict[str, Any]]): + mapping = bidict() + for symbol_data in exchange_info: + if bitget_perpetual_utils.is_exchange_information_valid(exchange_info=symbol_data): + try: + exchange_symbol = symbol_data["symbol"] + base = symbol_data["baseCoin"] + quote = symbol_data["quoteCoin"] + trading_pair = combine_to_hb_trading_pair(base, quote) + mapping[exchange_symbol] = trading_pair + except Exception as exception: + self.logger().error(f"There was an error parsing a trading pair information ({exception})") + self._set_trading_pair_symbol_map(mapping) + + async def _update_trading_rules(self): + markets_data = await self._market_data_for_all_product_types() + trading_rules_list = await self._format_trading_rules(markets_data) + + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=markets_data) + + async def _format_trading_rules(self, instruments_info: List[Dict[str, Any]]) -> List[TradingRule]: + """ + Converts JSON API response into a local dictionary of trading rules. + + :param instrument_info_dict: The JSON API response. + + :returns: A dictionary of trading pair to its respective TradingRule. + """ + trading_rules = {} + for instrument in instruments_info: + if bitget_perpetual_utils.is_exchange_information_valid(exchange_info=instrument): + try: + exchange_symbol = instrument["symbol"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=exchange_symbol) + collateral_token = instrument["supportMarginCoins"][0] + trading_rules[trading_pair] = TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(str(instrument["minTradeNum"])), + min_price_increment=(Decimal(str(instrument["priceEndStep"])) + * Decimal(f"1e-{instrument['pricePlace']}")), + min_base_amount_increment=Decimal(str(instrument["sizeMultiplier"])), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule: {instrument}. Skipping.") + return list(trading_rules.values()) diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..3ca9a62 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_user_stream_data_source.py @@ -0,0 +1,131 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.derivative.bitget_perpetual import bitget_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_auth import BitgetPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSPlainTextRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import BitgetPerpetualDerivative + + +class BitgetPerpetualUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: BitgetPerpetualAuth, + trading_pairs: List[str], + connector: 'BitgetPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = None, + ): + super().__init__() + self._domain = domain + self._api_factory = api_factory + self._auth = auth + self._trading_pairs = trading_pairs + self._connector = connector + self._pong_response_event = None + + async def _authenticate(self, ws: WSAssistant): + """ + Authenticates user to websocket + """ + auth_payload: List[str] = self._auth.get_ws_auth_payload() + payload = {"op": "login", "args": auth_payload} + login_request: WSJSONRequest = WSJSONRequest(payload=payload) + await ws.send(login_request) + response: WSResponse = await ws.receive() + message = response.data + + if ( + message["event"] != "login" + and message["code"] != "0" + ): + self.logger().error("Error authenticating the private websocket connection") + raise IOError("Private websocket connection authentication failed") + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + await asyncio.wait_for( + super()._process_websocket_messages(websocket_assistant=websocket_assistant, queue=queue), + timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + except asyncio.TimeoutError: + if self._pong_response_event and not self._pong_response_event.is_set(): + # The PONG response for the previous PING request was never received + raise IOError("The user stream channel is unresponsive (pong response not received)") + self._pong_response_event = asyncio.Event() + await self._send_ping(websocket_assistant=websocket_assistant) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if event_message == CONSTANTS.WS_PONG_RESPONSE and self._pong_response_event: + self._pong_response_event.set() + elif "event" in event_message: + if event_message["event"] == "error": + raise IOError(f"Private channel subscription failed ({event_message})") + else: + await super()._process_event_message(event_message=event_message, queue=queue) + + async def _send_ping(self, websocket_assistant: WSAssistant): + ping_request = WSPlainTextRequest(payload=CONSTANTS.WS_PING_REQUEST) + await websocket_assistant.send(ping_request) + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + try: + product_types = set([await self._connector.product_type_for_trading_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs]) + subscription_payloads = [] + + for product_type in product_types: + subscription_payloads.append( + { + "instType": product_type.upper(), + "channel": CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME, + "instId": "default" + } + ) + subscription_payloads.append( + { + "instType": product_type.upper(), + "channel": CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME, + "instId": "default" + } + ) + subscription_payloads.append( + { + "instType": product_type.upper(), + "channel": CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME, + "instId": "default" + } + ) + + payload = { + "op": "subscribe", + "args": subscription_payloads + } + subscription_request = WSJSONRequest(payload) + + await websocket_assistant.send(subscription_request) + + self.logger().info("Subscribed to private account, position and orders channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred subscribing to account, position and orders channels..." + ) + raise + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect( + ws_url=CONSTANTS.WSS_URL, + message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + await self._authenticate(ws) + return ws diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_utils.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_utils.py new file mode 100644 index 0000000..60a3178 --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_utils.py @@ -0,0 +1,64 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +# Bitget fees: https://www.bitget.com/en/rate?tab=1 +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0002"), + taker_percent_fee_decimal=Decimal("0.0006"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + symbol = exchange_info.get("symbol") + return symbol is not None and symbol.count("_") <= 1 + + +class BitgetPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitget_perpetual", client_data=None) + bitget_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitget Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitget_perpetual_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitget Perpetual secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitget_perpetual_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitget Perpetual passphrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitget_perpetual" + + +KEYS = BitgetPerpetualConfigMap.construct() diff --git a/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_web_utils.py b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_web_utils.py new file mode 100644 index 0000000..7e2ad9d --- /dev/null +++ b/hummingbot/connector/derivative/bitget_perpetual/bitget_perpetual_web_utils.py @@ -0,0 +1,80 @@ +from typing import Callable, Dict, Optional + +from hummingbot.connector.derivative.bitget_perpetual import bitget_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = None) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Bitget domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return get_rest_url_for_endpoint(path_url, domain) + + +def private_rest_url(path_url: str, domain: str = None) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: the Bitget domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return get_rest_url_for_endpoint(path_url, domain) + + +def get_rest_url_for_endpoint(endpoint: Dict[str, str], domain: str = None): + return CONSTANTS.REST_URL + endpoint + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ], + ) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + return throttler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = "" +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + url = public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL) + response = await rest_assistant.execute_request( + url=url, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + method=RESTMethod.GET, + return_err=True, + ) + server_time = float(response["requestTime"]) + + return server_time diff --git a/hummingbot/connector/derivative/bitmex_perpetual/__init__.py b/hummingbot/connector/derivative/bitmex_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..1d43633 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_api_order_book_data_source.py @@ -0,0 +1,493 @@ +import asyncio +import copy +import json +import logging +import time +from collections import defaultdict +from datetime import datetime +from decimal import Decimal +from typing import Any, Dict, List, Mapping, Optional + +from bidict import ValueDuplicationError, bidict + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_utils as utils +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_book import BitmexPerpetualOrderBook +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BitmexPerpetualAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _bpobds_logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, Mapping[str, str]] = {} + _mapping_initialization_lock = asyncio.Lock() + + def __init__( + self, + trading_pairs: List[str] = None, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__(trading_pairs) + self._api_factory: WebAssistantsFactory = WebAssistantsFactory(throttler) + self._api_factory: WebAssistantsFactory = api_factory or web_utils.build_api_factory() + self._ws_assistant: Optional[WSAssistant] = None + self._order_book_create_function = lambda: OrderBook() + self._domain = domain + self._throttler = throttler or self._get_throttler_instance() + self._funding_info: Dict[str, FundingInfo] = {} + + self._message_queue: Dict[int, asyncio.Queue] = defaultdict(asyncio.Queue) + + @property + def funding_info(self) -> Dict[str, FundingInfo]: + return copy.deepcopy(self._funding_info) + + def is_funding_info_initialized(self) -> bool: + return all(trading_pair in self._funding_info for trading_pair in self._trading_pairs) + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpobds_logger is None: + cls._bpobds_logger = logging.getLogger(__name__) + return cls._bpobds_logger + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + @classmethod + async def get_last_traded_prices(cls, + trading_pairs: List[str], + domain: str = CONSTANTS.DOMAIN) -> Dict[str, float]: + tasks = [cls.get_last_traded_price(t_pair, domain) for t_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + @classmethod + async def get_last_traded_price(cls, + trading_pair: str, + domain: str = CONSTANTS.DOMAIN, + api_factory: WebAssistantsFactory = None) -> float: + api_factory = api_factory or web_utils.build_api_factory() + + throttler = cls._get_throttler_instance() + + params = { + "symbol": await cls.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=domain, + throttler=throttler)} + + resp_json = await web_utils.api_request( + CONSTANTS.EXCHANGE_INFO_URL, + api_factory, + throttler, + domain, + params=params + ) + return float(resp_json[0]["lastPrice"]) + + @classmethod + def trading_pair_symbol_map_ready(cls, domain: str = CONSTANTS.DOMAIN): + """ + Checks if the mapping from exchange symbols to client trading pairs has been initialized + :param domain: the domain of the exchange being used + :return: True if the mapping has been initialized, False otherwise + """ + return domain in cls._trading_pair_symbol_map and len(cls._trading_pair_symbol_map[domain]) > 0 + + @classmethod + async def trading_pair_symbol_map( + cls, + domain: Optional[str] = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> Mapping[str, str]: + if not cls.trading_pair_symbol_map_ready(domain=domain): + api_factory = WebAssistantsFactory(throttler) + async with cls._mapping_initialization_lock: + # Check condition again (could have been initialized while waiting for the lock to be released) + if not cls.trading_pair_symbol_map_ready(domain=domain): + await cls.init_trading_pair_symbols(domain, throttler, api_factory) + + return cls._trading_pair_symbol_map[domain] + + @classmethod + async def init_trading_pair_symbols( + cls, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ): + """Initialize _trading_pair_symbol_map class variable""" + mapping = bidict() + + api_factory = api_factory or web_utils.build_api_factory() + + throttler = throttler or cls._get_throttler_instance() + params = {"filter": json.dumps({"typ": "FFWCSX"})} + + try: + data = await web_utils.api_request( + CONSTANTS.EXCHANGE_INFO_URL, + api_factory, + throttler, + domain, + params + ) + for symbol_data in data: + try: + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair( + symbol_data["rootSymbol"], + symbol_data["quoteCurrency"]) + except ValueDuplicationError: + continue + + except Exception as ex: + cls.logger().error(f"There was an error requesting exchange info ({str(ex)})") + + cls._trading_pair_symbol_map[domain] = mapping + + @staticmethod + async def fetch_trading_pairs( + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> List[str]: + ob_source_cls = BitmexPerpetualAPIOrderBookDataSource + trading_pair_list: List[str] = [] + symbols_map = await ob_source_cls.trading_pair_symbol_map(domain=domain, throttler=throttler, api_factory=api_factory) + trading_pair_list.extend(list(symbols_map.values())) + + return trading_pair_list + + @classmethod + async def convert_from_exchange_trading_pair( + cls, + exchange_trading_pair: str, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None) -> str: + + symbol_map = await cls.trading_pair_symbol_map( + domain=domain, + throttler=throttler) + try: + pair = symbol_map[exchange_trading_pair] + except KeyError: + raise ValueError(f"There is no symbol mapping for exchange trading pair {exchange_trading_pair}") + + return pair + + @classmethod + async def convert_to_exchange_trading_pair( + cls, + hb_trading_pair: str, + domain = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None) -> str: + + symbol_map = await cls.trading_pair_symbol_map( + domain=domain, + throttler=throttler) + try: + symbol = symbol_map.inverse[hb_trading_pair] + except KeyError: + raise ValueError(f"There is no symbol mapping for trading pair {hb_trading_pair}") + + return symbol + + @staticmethod + async def get_snapshot( + trading_pair: str, + limit: int = 1000, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> Dict[str, Any]: + ob_source_cls = BitmexPerpetualAPIOrderBookDataSource + try: + api_factory = api_factory or web_utils.build_api_factory() + + params = {"symbol": await ob_source_cls.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=domain, + throttler=throttler)} + if limit != 0: + params.update({"limit": str(limit)}) + + throttler = throttler or ob_source_cls._get_throttler_instance() + data = await web_utils.api_request( + CONSTANTS.SNAPSHOT_REST_URL, + api_factory, + throttler, + domain, + params + ) + return data + except asyncio.CancelledError: + raise + except Exception: + raise + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + snapshot: List[Dict[str, Any]] = await self.get_snapshot( + trading_pair, + 1000, + self._domain, + self._throttler, + self._api_factory + ) + bids = [] + asks = [] + size_currency = await utils.get_trading_pair_size_currency( + await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + ) + if size_currency.is_base: + for order in snapshot: + order_details = [order['price'], order['size']] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + else: + for order in snapshot: + order_details = [order['price'], order['size'] / order['price']] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + + snapshot_dict = { + "bids": bids, + "asks": asks, + "update_id": snapshot[-1]["id"] + } + + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitmexPerpetualOrderBook.snapshot_message_from_exchange( + snapshot_dict, snapshot_timestamp, metadata={"trading_pair": trading_pair} + ) + order_book = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + return order_book + + async def _get_funding_info_from_exchange(self, trading_pair: str) -> FundingInfo: + """ + Fetches the funding information of the given trading pair from the exchange REST API. Parses and returns the + respsonse as a FundingInfo data object. + + :param trading_pair: Trading pair of which its Funding Info is to be fetched + :type trading_pair: str + :return: Funding Information of the given trading pair + :rtype: FundingInfo + """ + api_factory = web_utils.build_api_factory() + + params = {"symbol": await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler)} + + path = CONSTANTS.EXCHANGE_INFO_URL + data = await web_utils.api_request( + path, + api_factory, + self._throttler, + self._domain, + params + ) + data = data[0] + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(data["lastPrice"]), + mark_price=Decimal(data["fairPrice"]), + next_funding_utc_timestamp=datetime.timestamp(datetime.strptime(data["fundingTimestamp"], "%Y-%m-%dT%H:%M:%S.000Z")), + rate=Decimal(data["fundingRate"]), + ) + + return funding_info + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + """ + Returns the FundingInfo of the specified trading pair. If it does not exist, it will query the REST API. + """ + if trading_pair not in self._funding_info: + self._funding_info[trading_pair] = await self._get_funding_info_from_exchange(trading_pair) + return self._funding_info[trading_pair] + + async def _subscribe_to_order_book_streams(self) -> WSAssistant: + url = web_utils.wss_url("", self._domain) + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect(ws_url=url) + + stream_channels = [ + "trade", + "orderBookL2", + "funding" + ] + for channel in stream_channels: + params = [] + for trading_pair in self._trading_pairs: + symbol = await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler) + params.append(f"{channel}:{symbol}") + payload = { + "op": "subscribe", + "args": params, + } + subscribe_request: WSJSONRequest = WSJSONRequest( + payload=payload, + is_auth_required=False + ) + await ws.send(subscribe_request) + + return ws + + async def listen_for_subscriptions(self): + ws = None + while True: + try: + ws = await self._subscribe_to_order_book_streams() + + async for msg in ws.iter_messages(): + if "result" in msg.data: + continue + if 'table' in msg.data: + if "orderBookL2" in msg.data["table"]: + self._message_queue[CONSTANTS.DIFF_STREAM_ID].put_nowait(msg) + elif "trade" in msg.data["table"]: + self._message_queue[CONSTANTS.TRADE_STREAM_ID].put_nowait(msg) + elif "funding" in msg.data["table"]: + self._message_queue[CONSTANTS.FUNDING_INFO_STREAM_ID].put_nowait(msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error with Websocket connection. Retrying after 30 seconds...", exc_info=True + ) + await self._sleep(30.0) + finally: + ws and await ws.disconnect() + + async def order_id_to_price(self, trading_pair: str, order_id: int): + exchange_trading_pair = await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + id_tick = await utils.get_trading_pair_index_and_tick_size(exchange_trading_pair) + trading_pair_id = id_tick.index + instrument_tick_size = id_tick.tick_size + price = ((1e8 * trading_pair_id) - order_id) * instrument_tick_size + return price + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + msg = await self._message_queue[CONSTANTS.DIFF_STREAM_ID].get() + timestamp: float = time.time() + if msg.data["action"] in ["update", "insert", "delete"]: + msg.data["data_dict"] = {} + exchange_trading_pair = msg.data["data"][0]["symbol"] + trading_pair = await self.convert_from_exchange_trading_pair( + exchange_trading_pair=exchange_trading_pair, + domain=self._domain, + throttler=self._throttler) + + size_currency = await utils.get_trading_pair_size_currency(exchange_trading_pair) + + def size_fn(size, price): + final_size = size if size_currency.is_base else size / price + return final_size + + msg.data["data_dict"]["symbol"] = trading_pair + asks = [] + bids = [] + for order in msg.data["data"]: + price = await self.order_id_to_price(trading_pair, order["id"]) + amount = 0.0 if msg.data['action'] == "delete" else size_fn(order["size"], price) + order_details = [price, amount] + asks.append(order_details) if order["side"] == "Sell" else bids.append(order_details) + + msg.data["data_dict"]["bids"] = bids + msg.data["data_dict"]["asks"] = asks + order_book_message: OrderBookMessage = BitmexPerpetualOrderBook.diff_message_from_exchange( + msg.data, timestamp + ) + output.put_nowait(order_book_message) + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + msg = await self._message_queue[CONSTANTS.TRADE_STREAM_ID].get() + msg.data["data_dict"] = {} + trading_pair = await self.convert_from_exchange_trading_pair( + exchange_trading_pair=msg.data["data"][0]["symbol"], + domain=self._domain, + throttler=self._throttler) + for trade in msg.data["data"]: + trade["symbol"] = trading_pair + trade_message: OrderBookMessage = BitmexPerpetualOrderBook.trade_message_from_exchange(trade) + output.put_nowait(trade_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + for trading_pair in self._trading_pairs: + snapshot: Dict[str, Any] = await self.get_snapshot( + trading_pair, domain=self._domain, throttler=self._throttler, api_factory=self._api_factory + ) + + snapshot_timestamp: float = time.time() + bids = [] + asks = [] + size_currency = await utils.get_trading_pair_size_currency( + await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + ) + if size_currency.is_base: + for order in snapshot: + order_details = [order['price'], order['size']] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + else: + for order in snapshot: + order_details = [order['price'], order['size'] / order['price']] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + + snapshot_dict = { + "bids": bids, + "asks": asks, + "update_id": snapshot[-1]["id"] + } + snapshot_msg: OrderBookMessage = BitmexPerpetualOrderBook.snapshot_message_from_exchange( + snapshot_dict, snapshot_timestamp, metadata={"trading_pair": trading_pair} + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + delta = CONSTANTS.ONE_HOUR - time.time() % CONSTANTS.ONE_HOUR + await self._sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...", exc_info=True + ) + await self._sleep(5.0) diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_auth.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_auth.py new file mode 100644 index 0000000..fd83f29 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_auth.py @@ -0,0 +1,57 @@ +import hashlib +import hmac +import json +import time +from urllib.parse import urlencode, urlparse + +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + +EXPIRATION = 25 # seconds + + +class BitmexPerpetualAuth(AuthBase): + """ + Auth class required by Bitmex Perpetual API + """ + + def __init__(self, api_key: str, api_secret: str): + self._api_key: str = api_key + self._api_secret: str = api_secret + + @property + def api_key(self): + return self._api_key + + def generate_signature_from_payload(self, payload: str) -> str: + secret = bytes(self._api_secret.encode("utf-8")) + signature = hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() + return signature + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + verb = str(request.method) + expires = str(int(time.time()) + EXPIRATION) + data = json.dumps(request.data) if request.data is not None else '' + parsed_url = urlparse(request.url) + path = parsed_url.path + query = urlencode(request.params) if request.params is not None else '' + if not (query == ''): + query = '?' + query + payload = verb + path + query + expires + data + signature = self.generate_signature_from_payload(payload) + + request.headers = { + "api-expires": expires, + "api-key": self._api_key, + "api-signature": signature, + } + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + return request # pass-through + + async def generate_ws_signature(self, ts: str): + payload = 'GET/realtime' + ts + signature = self.generate_signature_from_payload(payload) + return signature diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_derivative.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_derivative.py new file mode 100644 index 0000000..b4a3fbb --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_derivative.py @@ -0,0 +1,1192 @@ +import asyncio +import json +import logging +import time +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Tuple + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_utils as utils +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_api_order_book_data_source import ( + BitmexPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import BitmexPerpetualAuth +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_in_flight_order import ( + BitmexPerpetualInFlightOrder, +) +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_book_tracker import ( + BitmexPerpetualOrderBookTracker, +) +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_user_stream_tracker import ( + BitmexPerpetualUserStreamTracker, +) +from hummingbot.connector.derivative.perpetual_budget_checker import PerpetualBudgetChecker +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange_base import ExchangeBase, s_decimal_NaN +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.transaction_tracker import TransactionTracker +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import ( + AccountEvent, + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + PositionModeChangeEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +bpm_logger = None + + +def now(): + return int(time.time()) * 1000 + + +BUY_ORDER_COMPLETED_EVENT = MarketEvent.BuyOrderCompleted +SELL_ORDER_COMPLETED_EVENT = MarketEvent.SellOrderCompleted +ORDER_CANCELLED_EVENT = MarketEvent.OrderCancelled +ORDER_EXPIRED_EVENT = MarketEvent.OrderExpired +ORDER_FILLED_EVENT = MarketEvent.OrderFilled +ORDER_FAILURE_EVENT = MarketEvent.OrderFailure +MARKET_FUNDING_PAYMENT_COMPLETED_EVENT_TAG = MarketEvent.FundingPaymentCompleted +BUY_ORDER_CREATED_EVENT = MarketEvent.BuyOrderCreated +SELL_ORDER_CREATED_EVENT = MarketEvent.SellOrderCreated +API_CALL_TIMEOUT = 10.0 + +# ========================================================== +UNRECOGNIZED_ORDER_DEBOUCE = 60 # seconds + + +class LatchingEventResponder(EventListener): + def __init__(self, callback: any, num_expected: int): + super().__init__() + self._callback = callback + self._completed = asyncio.Event() + self._num_remaining = num_expected + + def __call__(self, arg: any): + if self._callback(arg): + self._reduce() + + def _reduce(self): + self._num_remaining -= 1 + if self._num_remaining <= 0: + self._completed.set() + + async def wait_for_completion(self, timeout: float): + try: + await asyncio.wait_for(self._completed.wait(), timeout=timeout) + except asyncio.TimeoutError: + pass + return self._completed.is_set() + + def cancel_one(self): + self._reduce() + + +class BitmexPerpetualDerivativeTransactionTracker(TransactionTracker): + def __init__(self, owner): + super().__init__() + self._owner = owner + + def did_timeout_tx(self, tx_id: str): + TransactionTracker.c_did_timeout_tx(self, tx_id) + self._owner.did_timeout_tx(tx_id) + + +class BitmexPerpetualDerivative(ExchangeBase, PerpetualTrading): + MARKET_FUNDING_PAYMENT_COMPLETED_EVENT_TAG = MarketEvent.FundingPaymentCompleted + + API_CALL_TIMEOUT = 10.0 + SHORT_POLL_INTERVAL = 5.0 + LONG_POLL_INTERVAL = 12.0 + ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + HEARTBEAT_TIME_INTERVAL = 30.0 + UPDATE_ORDERS_INTERVAL = 10.0 + + @classmethod + def logger(cls) -> HummingbotLogger: + global bpm_logger + if bpm_logger is None: + bpm_logger = logging.getLogger(__name__) + return bpm_logger + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + bitmex_perpetual_api_key: str = None, + bitmex_perpetual_api_secret: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DOMAIN, + ): + + self._bitmex_time_synchronizer = TimeSynchronizer() + self._auth: BitmexPerpetualAuth = BitmexPerpetualAuth(api_key=bitmex_perpetual_api_key, + api_secret=bitmex_perpetual_api_secret) + + self._trading_pairs = trading_pairs + self._trading_required = trading_required + self._throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self._domain = domain + self._api_factory = web_utils.build_api_factory( + auth=self._auth) + self._rest_assistant: Optional[RESTAssistant] = None + self._ws_assistant: Optional[WSAssistant] = None + + ExchangeBase.__init__(self, client_config_map=client_config_map) + PerpetualTrading.__init__(self, trading_pairs=trading_pairs) + + self._user_stream_tracker = BitmexPerpetualUserStreamTracker( + auth=self._auth, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory, + time_synchronizer=self._bitmex_time_synchronizer + ) + self._order_book_tracker = BitmexPerpetualOrderBookTracker( + trading_pairs=trading_pairs, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory) + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + self._funding_fee_poll_notifier = asyncio.Event() + self._order_not_found_records = defaultdict(int) + self._last_timestamp = 0 + self._trading_rules = {} + self._in_flight_orders = {} + self._position_mode = None + self._status_polling_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._user_stream_tracker_task = None + self._last_poll_timestamp = 0 + self._budget_checker = PerpetualBudgetChecker(self) + self._client_order_tracker: ClientOrderTracker = ClientOrderTracker(connector=self) + self._trading_pair_to_size_type = {} + self._trading_pair_price_estimate_for_quantize = {} + self._token_multiplier = {} + + @property + def name(self) -> str: + # Note: domain here refers to the entire exchange name. i.e. bitmex_perpetual or bitmex_perpetual_testnet + return self._domain + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self._order_book_tracker.order_books + + @property + def ready(self): + return all(self.status_dict.values()) + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrder]: + return self._in_flight_orders + + @property + def status_dict(self): + sd = { + "symbols_mapping_initialized": BitmexPerpetualAPIOrderBookDataSource.trading_pair_symbol_map_ready( + domain=self._domain), + "order_books_initialized": self._order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0, + "position_mode": self.position_mode is not None, + "funding_info_initialized": self._order_book_tracker.is_funding_info_initialized(), + "user_stream_initialized": self._user_stream_tracker.data_source.last_recv_time > 0, + } + return sd + + @property + def limit_orders(self) -> List[LimitOrder]: + return [order.to_limit_order() for order in self._client_order_tracker.all_orders.values()] + + @property + def budget_checker(self) -> PerpetualBudgetChecker: + return self._budget_checker + + @property + def tracking_states(self) -> Dict[str, any]: + """ + :return active in-flight orders in json format, is used to save in sqlite db. + """ + return { + client_order_id: in_flight_order.to_json() + for client_order_id, in_flight_order in self._client_order_tracker.active_orders.items() + if not in_flight_order.is_done + } + + def restore_tracking_states(self, saved_states: Dict[str, any]): + for order_id, in_flight_repr in saved_states.items(): + if isinstance(in_flight_repr, dict): + in_flight_json: Dict[str, Any] = in_flight_repr + else: + in_flight_json: Dict[str, Any] = json.loads(in_flight_repr) + order = BitmexPerpetualInFlightOrder.from_json(in_flight_json) + if not order.is_done: + self._in_flight_orders[order_id] = order + + def supported_order_types(self) -> List[OrderType]: + """ + Returns list of OrderType supported by this connector. + """ + return [OrderType.LIMIT, OrderType.MARKET] + + async def start_network(self): + """ + This function is required by the NetworkIterator base class and is called automatically. + It starts tracking order books, polling trading rules, updating statuses, and tracking user data. + """ + self._order_book_tracker.start() + await self._update_trading_rules() + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + if self._trading_required: + await self._get_position_mode() + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + for trading_pair in self._trading_pairs: + await self._order_book_tracker.data_source.get_funding_info(trading_pair) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + async def stop_network(self): + """ + This function is required by the NetworkIterator base class and is called automatically. + It performs the necessary shut down procedure. + """ + self._stop_network() + + async def check_network(self) -> NetworkStatus: + """ + This function is required by NetworkIterator base class and is called periodically to check + the network connection. Ping the network (or call any lightweight public API). + """ + try: + await self._api_request(path=CONSTANTS.PING_URL) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + def quantize_order_amount(self, trading_pair: str, amount: object, price: object = Decimal(0)): + trading_rule: TradingRule = self._trading_rules[trading_pair] + size_type = self._trading_pair_to_size_type[trading_pair] + # current_price: object = self.get_price(trading_pair, False) + notional_size: object + if size_type.is_base: + quantized_amount = ExchangeBase.quantize_order_amount(self, trading_pair, amount) + if quantized_amount < trading_rule.min_order_size: + return Decimal(0) + else: + if price == Decimal(0): + price = self._trading_pair_price_estimate_for_quantize[trading_pair] + notional_size = price * amount + if notional_size < trading_rule.min_notional_size: + return Decimal(0) + quantized_amount = amount + + return quantized_amount + + def get_order_price_quantum(self, trading_pair: str, price: object): + """ + Returns a price step, a minimum price increment for a given trading pair. + + Parameters + ---------- + trading_pair: + The pair to which the quantization will apply + price: + Price to be quantized + """ + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + def get_order_size_quantum(self, trading_pair: str, order_size: object): + """ + Returns an order amount step, a minimum amount increment for a given trading pair. + + Parameters + ---------- + trading_pair: + The pair to which the quantization will apply + order_size: + Size to be quantized + """ + trading_rule: TradingRule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + def start_tracking_order( + self, + order_side: TradeType, + client_order_id: str, + order_type: OrderType, + created_at: float, + hash: str, + trading_pair: str, + price: Decimal, + amount: Decimal, + leverage: int, + position: str, + ): + in_flight_order = BitmexPerpetualInFlightOrder( + client_order_id, + None, + trading_pair, + order_type, + order_side, + price, + amount, + created_at, + leverage, + position + ) + self._in_flight_orders[in_flight_order.client_order_id] = in_flight_order + + def stop_tracking_order(self, order_id: str): + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + def time_now_s(self) -> float: + return time.time() + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + now = time.time() + poll_interval = (self.SHORT_POLL_INTERVAL + if now - self._user_stream_tracker.last_recv_time > 60.0 + else self.LONG_POLL_INTERVAL) + last_tick = int(self._last_timestamp / poll_interval) + current_tick = int(timestamp / poll_interval) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + + self._last_timestamp = timestamp + + def get_order_book(self, trading_pair: str) -> OrderBook: + """ + They are used by the OrderBookCommand to display the order book in the terminal. + + Parameters + ---------- + trading_pair: + The pair for which the order book should be obtained + """ + order_books: dict = self._order_book_tracker.order_books + if trading_pair not in order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return order_books[trading_pair] + + def get_funding_info(self, trading_pair: str) -> Optional[FundingInfo]: + """ + Retrieves the Funding Info for the specified trading pair. + Note: This function should NOT be called when the connector is not yet ready. + :param: trading_pair: The specified trading pair. + """ + if trading_pair in self._order_book_tracker.data_source.funding_info: + return self._order_book_tracker.data_source.funding_info[trading_pair] + else: + self.logger().error(f"Funding Info for {trading_pair} not found. Proceeding to fetch using REST API.") + safe_ensure_future(self._order_book_tracker.data_source.get_funding_info(trading_pair)) + return None + + def set_leverage(self, trading_pair: str, leverage: int = 1): + self._leverage[trading_pair] = leverage + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def _stop_network(self): + # Reset timestamps and _poll_notifier for status_polling_loop + self._last_poll_timestamp = 0 + self._last_timestamp = 0 + self._poll_notifier = asyncio.Event() + self._funding_fee_poll_notifier = asyncio.Event() + + self._order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._status_polling_task = self._user_stream_tracker_task = \ + self._user_stream_event_listener_task = None + + async def _update_order_status(self): + last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDERS_INTERVAL) + current_tick = int(self.current_timestamp / self.UPDATE_ORDERS_INTERVAL) + + if current_tick > last_tick and len(self._in_flight_orders) > 0: + in_flight_orders_copy = self._in_flight_orders.copy() + for trading_pair in self._trading_pairs: + exchange_trading_pair = await BitmexPerpetualAPIOrderBookDataSource.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler, + ) + response = await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + method=RESTMethod.GET, + params={"symbol": exchange_trading_pair} + ) + orders = response + for order in orders: + client_order_id = order.get('clOrdID') + if client_order_id is not None: + tracked_order = self._in_flight_orders.get(client_order_id) + if tracked_order is not None: + del in_flight_orders_copy[client_order_id] + self._update_inflight_order(tracked_order, order) + for client_order_id, in_flight_order in in_flight_orders_copy.items(): + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # We'll just have to assume that this order doesn't exist + cancellation_event = OrderCancelledEvent(now(), client_order_id) + self.stop_tracking_order(client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from Bitmex. Check API key and network connection.", + ) + await self._sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Wait for new messages from _user_stream_tracker.user_stream queue and processes them according to their + message channels. The respective UserStreamDataSource queues these messages. + """ + async for event_message in self._iter_user_event_queue(): + try: + await self._process_user_stream_event(event_message) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Unexpected error in user stream listener loop: {e}", exc_info=True) + await self._sleep(5.0) + + async def _process_user_stream_event(self, event_message: Dict[str, Any]): + topic = event_message.get("table") + data = event_message.get("data") + if topic == "position": + for position in data: + if 'avgEntryPrice' in position: + await self.update_position_from_exchange_data(position) + elif topic == "wallet": + for currency_info in data: + await self.set_balance(currency_info) + elif topic == "order": + for order in data: + client_order_id = order.get("clOrdID") + if client_order_id is not None: + tracked_order = self._in_flight_orders.get(client_order_id) + if tracked_order is not None: + self._update_inflight_order(tracked_order, order) + + async def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + size_currency_type = self._trading_pair_to_size_type[trading_pair] + if size_currency_type.is_base: + return await ExchangeBase.get_order_price(self, trading_pair, is_buy, amount) + else: + order_book: OrderBook = self.get_order_book(trading_pair) + result = order_book.get_price_for_volume(is_buy, float(amount)) + return Decimal(str(result.result_price)) + + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + size_currency_type = self._trading_pair_to_size_type[trading_pair] + if size_currency_type.is_base: + return await ExchangeBase.get_quote_price(self, trading_pair, is_buy, amount) + else: + order_book: OrderBook = self.get_order_book(trading_pair) + result = order_book.get_vwap_for_volume(is_buy, float(amount)) + return Decimal(str(result.result_price)) + + async def _update_trading_rules(self): + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + """ + last_tick = int(self._last_timestamp / 60.0) + current_tick = int(self.current_timestamp / 60.0) + if current_tick > last_tick or len(self._trading_rules) < 1: + exchange_info = await self._api_request(path=CONSTANTS.EXCHANGE_INFO_URL, + method=RESTMethod.GET, + params={"filter": json.dumps({"typ": "FFWCSX"})} + ) + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + await self._update_trading_pair_prices_for_quantize() + + async def _format_trading_rules(self, exchange_info_list: List[Dict[str, Any]]) -> List[TradingRule]: + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + + Parameters + ---------- + exchange_info_dict: + Trading rules dictionary response from the exchange + """ + return_val: list = [] + for rule in exchange_info_list: + try: + if rule["symbol"][-4:] == "_ETH": + continue + trading_pair = combine_to_hb_trading_pair(rule["rootSymbol"], rule["quoteCurrency"]) + + if trading_pair in self._trading_pairs: + size_currency_type = await utils.get_trading_pair_size_currency(rule['symbol']) + self._trading_pair_to_size_type[trading_pair] = size_currency_type + max_order_size = Decimal(str(rule.get("maxOrderQty"))) + if size_currency_type.is_base: + min_order_size = Decimal(str(rule.get("lotSize"))) + multiplier = size_currency_type.multiplier + if multiplier is not None: + multiplier = Decimal(multiplier) + min_order_size /= multiplier + tick_size = Decimal(str(rule.get("tickSize"))) + collateral_token = rule["settlCurrency"].upper() # not a typo + return_val.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=Decimal(tick_size), + min_base_amount_increment=Decimal(min_order_size), + max_order_size=max_order_size, + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + ) + else: + min_notional_size = Decimal(str(rule.get("lotSize"))) + tick_size = Decimal(str(rule.get("tickSize"))) + collateral_token = rule["settlCurrency"].upper() # not a typo + return_val.append( + TradingRule( + trading_pair, + min_notional_size=min_notional_size, + min_price_increment=Decimal(tick_size), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + max_order_size=max_order_size + ) + ) + + except Exception as e: + self.logger().error( + f"Error parsing the trading pair rule {rule}. Error: {e}. Skipping...", exc_info=True + ) + return return_val + + async def _update_trading_pair_prices_for_quantize(self): + for trading_pair in self._trading_pairs: + price = await BitmexPerpetualAPIOrderBookDataSource.get_last_traded_price( + trading_pair, + self._domain + ) + self._trading_pair_price_estimate_for_quantize[trading_pair] = Decimal(price) + + async def _trading_rules_polling_loop(self): + """ + An asynchronous task that periodically updates trading rules. + """ + while True: + try: + await safe_gather(self._update_trading_rules()) + await self._sleep(CONSTANTS.ONE_HOUR) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading rules.", + exc_info=True, + app_warning_msg="Could not fetch new trading rules from Bitmex Perpetuals. " + "Check network connection.", + ) + await self._sleep(0.5) + + async def _status_polling_loop(self): + """ + Periodically update user balances and order status via REST API. This serves as a fallback measure for + socket API updates. Calling of both _update_balances() and _update_order_status() functions is + determined by the _poll_notifier variable. + """ + while True: + try: + await self._poll_notifier.wait() + # await self._update_time_synchronizer() + await safe_gather( + self._update_balances(), + self._update_positions(), + ) + await self._update_order_status() + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error while fetching account updates.", exc_info=True, + app_warning_msg="Could not fetch account updates from Bitmex Perpetuals. " + "Check API key and network connection.") + await self._sleep(0.5) + finally: + self._poll_notifier = asyncio.Event() + + def _update_inflight_order(self, tracked_order: BitmexPerpetualInFlightOrder, event: Dict[str, Any]): + size_currency_type = self._trading_pair_to_size_type[tracked_order.trading_pair] + if size_currency_type.is_base: + event["amount_remaining"] = Decimal(str(event["leavesQty"])) / size_currency_type.multiplier + else: + event["quote_amount_remaining"] = Decimal(str(event["leavesQty"])) + issuable_events: List[MarketEvent] = tracked_order.update(event) + + # Issue relevent events + for (market_event, new_amount, new_price, new_fee) in issuable_events: + base, quote = self.split_trading_pair(tracked_order.trading_pair) + if market_event == MarketEvent.OrderFilled: + self.trigger_event(ORDER_FILLED_EVENT, + OrderFilledEvent(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + new_price, + new_amount, + build_perpetual_trade_fee( + self._domain, + True, + base, + quote, + tracked_order.order_type, + tracked_order.trade_type, + new_amount, + new_price + ), + tracked_order.client_order_id)) + elif market_event == MarketEvent.OrderCancelled: + self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id}") + self.stop_tracking_order(tracked_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, + OrderCancelledEvent(self.current_timestamp, + tracked_order.client_order_id)) + elif market_event == MarketEvent.BuyOrderCompleted: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.trigger_event(BUY_ORDER_COMPLETED_EVENT, + BuyOrderCompletedEvent(self.current_timestamp, + tracked_order.client_order_id, + base, + quote, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type, + tracked_order.exchange_order_id)) + elif market_event == MarketEvent.SellOrderCompleted: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.trigger_event(SELL_ORDER_COMPLETED_EVENT, + SellOrderCompletedEvent(self.current_timestamp, + tracked_order.client_order_id, + base, + quote, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type, + tracked_order.exchange_order_id)) + # Complete the order if relevent + if tracked_order.is_done: + self.stop_tracking_order(tracked_order.client_order_id) + + async def _update_positions(self): + positions = await self._api_request( + path=CONSTANTS.POSITION_INFORMATION_URL, + is_auth_required=True, + ) + for position in positions: + await self.update_position_from_exchange_data(position) + + async def update_position_from_exchange_data(self, data: Dict[str, Any]): + trading_pair = await BitmexPerpetualAPIOrderBookDataSource.convert_from_exchange_trading_pair( + exchange_trading_pair=data.get("symbol"), + domain=self._domain, + throttler=self._throttler, + ) + if trading_pair in self._trading_pairs: + size_currency_type = self._trading_pair_to_size_type[trading_pair] + open_order_buy_qty = data.get("execBuyQty") + if open_order_buy_qty is not None: + position_side = PositionSide.LONG if data.get("execBuyQty") > 0 else PositionSide.SHORT + else: + position_side = PositionSide.SHORT + pos_key = self.position_key(trading_pair, position_side) + amount = Decimal(data.get("currentQty")) + if size_currency_type.is_base: + if size_currency_type.multiplier is not None: + amount /= size_currency_type.multiplier + if amount != 0: + unrealized_pnl = Decimal(data.get("unrealisedPnl")) + entry_price = Decimal(data.get("avgEntryPrice")) + if not size_currency_type.is_base: + amount /= entry_price + leverage = self._leverage[trading_pair] + self._account_positions[pos_key] = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage + ) + else: + if pos_key in self._account_positions: + del self._account_positions[pos_key] + + async def _get_position_mode(self): + self._position_mode = PositionMode.ONEWAY + return self._position_mode + + def supported_position_modes(self): + return [PositionMode.ONEWAY] + + def set_position_mode(self, position_mode: PositionMode): + for trading_pair in self._trading_pairs: + self._position_mode = PositionMode.ONEWAY + self.trigger_event( + AccountEvent.PositionModeChangeSucceeded, + PositionModeChangeEvent( + self.current_timestamp, + trading_pair, + position_mode + )) + + def adjust_quote_based_amounts( + self, + trading_pair: str, + price: Decimal, + amount: Decimal + ) -> Tuple[Decimal, Decimal]: + trading_rule = self._trading_rules[trading_pair] + lot_size = trading_rule.min_notional_size + quote_amount = amount * price + strp_amount = int(quote_amount) % int(lot_size) + quote_amount = int(quote_amount - strp_amount) + base_amount = Decimal(quote_amount) / price + return base_amount, quote_amount + + async def place_order( + self, + client_order_id: str, + trading_pair: str, + amount: Decimal, + is_buy: bool, + order_type: OrderType, + price: Decimal + ) -> Dict[str, Any]: + + symbol = await BitmexPerpetualAPIOrderBookDataSource.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler, + ) + size_currency_type = self._trading_pair_to_size_type[trading_pair] + if size_currency_type.is_base: + amount = float(amount) * size_currency_type.multiplier + else: + _, amount = self.adjust_quote_based_amounts(trading_pair, price, amount) + + order_side = "Buy" if is_buy else "Sell" + bitmex_order_type = "Limit" if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "Market" + + params = { + "symbol": symbol, + "side": order_side, + "orderQty": str(float(amount)), + "clOrdID": client_order_id, + "ordType": bitmex_order_type + } + + if bitmex_order_type == "Limit": + params['price'] = str(float(price)) + + return await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + params=params, + method=RESTMethod.POST + ) + + async def execute_order( + self, order_side, client_order_id, trading_pair, amount, order_type, position_action, price + ): + """ + Completes the common tasks from execute_buy and execute_sell. Quantizes the order's amount and price, and + validates the order against the trading rules before placing this order. + """ + if position_action not in [PositionAction.OPEN, PositionAction.CLOSE]: + raise ValueError("Specify either OPEN_POSITION or CLOSE_POSITION position_action.") + # Quantize order + price = self.quantize_order_price(trading_pair, price) + amount = self.quantize_order_amount(trading_pair, amount, price) + + if amount == Decimal(0): + raise ValueError( + "Order amount or notional size is insufficient" + ) + # Check trading rules + trading_rule = self._trading_rules[trading_pair] + + if amount > trading_rule.max_order_size: + raise ValueError( + f"Order amount({str(amount)}) is greater than the maximum allowable amount({str(trading_rule.max_order_size)})" + ) + + try: + created_at = self.time_now_s() + size_currency_type = self._trading_pair_to_size_type[trading_pair] + + if not size_currency_type.is_base: + base_amount, _ = self.adjust_quote_based_amounts(trading_pair, price, amount) + else: + base_amount = amount + self.start_tracking_order( + order_side, + client_order_id, + order_type, + created_at, + None, + trading_pair, + price, + base_amount, + self._leverage[trading_pair], + position_action.name, + ) + + try: + creation_response = await self.place_order( + client_order_id, + trading_pair, + amount, + order_side is TradeType.BUY, + order_type, + price + ) + except asyncio.TimeoutError: + return + + # Verify the response from the exchange + order = creation_response + + status = order["ordStatus"] + if status not in ["New", "PartiallyFilled", "Filled"]: + raise Exception(status) + + bitmex_order_id = order["orderID"] + + in_flight_order = self._in_flight_orders.get(client_order_id) + if in_flight_order is not None: + # Begin tracking order + in_flight_order.update_exchange_order_id(bitmex_order_id) + self.logger().info( + f"Created order {client_order_id} for {amount} {trading_pair}." + ) + else: + self.logger().info(f"Created order {client_order_id} for {amount} {trading_pair}.") + + except Exception as e: + self.logger().warning( + f"Error submitting {order_side.name} {order_type.name} order to bitmex for " + f"{amount} {trading_pair} at {price}." + ) + self.logger().info(e, exc_info=True) + + # Stop tracking this order + self.stop_tracking_order(client_order_id) + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), client_order_id, order_type)) + + async def execute_buy( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction, + price: Optional[Decimal] = Decimal("NaN"), + ): + try: + await self.execute_order(TradeType.BUY, order_id, trading_pair, amount, order_type, position_action, price) + tracked_order = self.in_flight_orders.get(order_id) + if tracked_order is not None: + self.trigger_event( + BUY_ORDER_CREATED_EVENT, + BuyOrderCreatedEvent( + now(), + order_type, + trading_pair, + Decimal(amount), + Decimal(price), + order_id, + tracked_order.creation_timestamp), + ) + + except ValueError as e: + # never tracked, so no need to stop tracking + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), order_id, order_type)) + self.logger().warning(f"Failed to place {order_id} on bitmex. {str(e)}") + + async def execute_sell( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction, + price: Optional[Decimal] = Decimal("NaN"), + ): + try: + await self.execute_order(TradeType.SELL, order_id, trading_pair, amount, order_type, position_action, price) + tracked_order = self.in_flight_orders.get(order_id) + if tracked_order is not None: + self.trigger_event( + SELL_ORDER_CREATED_EVENT, + SellOrderCreatedEvent( + now(), + order_type, + trading_pair, + Decimal(amount), + Decimal(price), + order_id, + tracked_order.creation_timestamp, + ), + ) + + except ValueError as e: + # never tracked, so no need to stop tracking + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), order_id, order_type)) + self.logger().warning(f"Failed to place {order_id} on bitmex. {str(e)}") + + def buy( + self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, **kwargs + ) -> str: + client_order_id: str = get_new_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + safe_ensure_future( + self.execute_buy(client_order_id, trading_pair, amount, order_type, kwargs["position_action"], price) + ) + return client_order_id + + def sell( + self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, **kwargs + ) -> str: + client_order_id: str = get_new_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + safe_ensure_future( + self.execute_sell(client_order_id, trading_pair, amount, order_type, kwargs["position_action"], price) + ) + return client_order_id + + # ---------------------------------------- + # Cancellation + + async def cancel_order(self, client_order_id: str): + in_flight_order = self._in_flight_orders.get(client_order_id) + cancellation_event = OrderCancelledEvent(now(), client_order_id) + exchange_order_id = in_flight_order.exchange_order_id + + if in_flight_order is None: + self.logger().warning("Cancelled an untracked order {client_order_id}") + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + + try: + if exchange_order_id is None: + # Note, we have no way of canceling an order or querying for information about the order + # without an exchange_order_id + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # We'll just have to assume that this order doesn't exist + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + params = {"clOrdID": client_order_id} + await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + params=params, + method=RESTMethod.DELETE + ) + return True + + except Exception as e: + if "Not Found" in str(e): + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # Order didn't exist on exchange, mark this as canceled + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + else: + raise Exception( + f"order {client_order_id} does not yet exist on the exchange and could not be cancelled." + ) + except Exception as e: + self.logger().warning(f"Failed to cancel order {client_order_id}") + self.logger().info(e) + return False + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + cancellation_queue = self._in_flight_orders.copy() + if len(cancellation_queue) == 0: + return [] + + order_status = {o.client_order_id: o.is_done for o in cancellation_queue.values()} + + def set_cancellation_status(oce: OrderCancelledEvent): + if oce.order_id in order_status: + order_status[oce.order_id] = True + return True + return False + + cancel_verifier = LatchingEventResponder(set_cancellation_status, len(cancellation_queue)) + self.add_listener(ORDER_CANCELLED_EVENT, cancel_verifier) + + for order_id, in_flight in cancellation_queue.items(): + try: + if order_status[order_id]: + cancel_verifier.cancel_one() + elif not await self.cancel_order(order_id): + # this order did not exist on the exchange + cancel_verifier.cancel_one() + order_status[order_id] = True + except Exception: + cancel_verifier.cancel_one() + order_status[order_id] = True + + await cancel_verifier.wait_for_completion(timeout_seconds) + self.remove_listener(ORDER_CANCELLED_EVENT, cancel_verifier) + + return [CancellationResult(order_id=order_id, success=success) for order_id, success in order_status.items()] + + def cancel(self, trading_pair: str, client_order_id: str): + return safe_ensure_future(self.cancel_order(client_order_id)) + + async def _initialize_token_decimals(self): + token_info = await self._api_request( + path=CONSTANTS.TOKEN_INFO_URL + ) + for asset in token_info: + zeros = asset['scale'] + currency = asset['asset'] + self._token_multiplier[currency] = Decimal(f"1e{zeros}") + if currency == "USDT": + self._token_multiplier['USD'] = Decimal(f"1e{zeros}") + + async def _update_balances(self): + account_info = await self._api_request( + path=CONSTANTS.ACCOUNT_INFO_URL, + is_auth_required=True, + params={"currency": "all"} + ) + for currency_info in account_info: + await self.set_balance(currency_info) + + async def set_balance(self, data: Dict[str, Any]): + if not (len(self._token_multiplier) > 0): + await self._initialize_token_decimals() + asset_name = data['currency'].upper() + asset_name = "ETH" if asset_name == "GWEI" else asset_name + total_balance = Decimal(str(data['amount'])) + pending_credit = Decimal(str(data['pendingCredit'])) + pending_debit = Decimal(str(data['pendingDebit'])) + available_balance = total_balance - pending_credit + pending_debit + + multiplier: Decimal = self._token_multiplier[asset_name] + self._account_balances[asset_name] = Decimal(total_balance) / multiplier + self._account_available_balances[asset_name] = Decimal(available_balance) / multiplier + if asset_name == "XBT": + self._account_balances["USD"] = Decimal('1000') * Decimal(total_balance) / multiplier + self._account_available_balances["USD"] = Decimal('1000') * Decimal(available_balance) / multiplier + + async def _api_request(self, + path: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None): + + try: + return await web_utils.api_request( + path=path, + api_factory=self._api_factory, + throttler=self._throttler, + domain=self._domain, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + limit_id=limit_id) + except Exception as e: + self.logger().error(f"Error fetching {path}", exc_info=True) + self.logger().warning(f"{e}") + raise e + + async def all_trading_pairs(self) -> List[str]: + return await BitmexPerpetualAPIOrderBookDataSource.fetch_trading_pairs() + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_in_flight_order.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_in_flight_order.py new file mode 100644 index 0000000..b250ab1 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_in_flight_order.py @@ -0,0 +1,136 @@ +from decimal import Decimal +from typing import Any, Dict, List, Optional + +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_status import BitmexPerpetualOrderStatus +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.event.events import MarketEvent, OrderType, TradeType + + +class BitmexPerpetualInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + created_at: float, + leverage: int, + position_action: str, + initial_state: str = "New"): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + created_at, + initial_state + ) + self.created_at = created_at + self.state = BitmexPerpetualOrderStatus.New + self.leverage = leverage + self.position = position_action + + def __repr__(self) -> str: + return f"super().__repr__()" \ + f"created_at='{str(self.created_at)}'')" + + def to_json(self) -> Dict[str, Any]: + response = super().to_json() + response["created_at"] = str(self.created_at) + response["leverage"] = self.leverage + response["position"] = self.position + return response + + @property + def is_done(self) -> bool: + return self.state in [BitmexPerpetualOrderStatus.Canceled, BitmexPerpetualOrderStatus.Filled, BitmexPerpetualOrderStatus.FAILURE] + + @property + def is_failure(self) -> bool: + return self.state is BitmexPerpetualOrderStatus.FAILURE or self.is_cancelled + + @property + def is_cancelled(self) -> bool: + return self.state is BitmexPerpetualOrderStatus.Canceled and self.executed_amount_base < self.amount + + def set_status(self, status: str): + self.last_state = status + self.state = BitmexPerpetualOrderStatus[status] + + @property + def order_type_description(self) -> str: + order_type = "market" if self.order_type is OrderType.MARKET else "limit" + side = "buy" if self.trade_type is TradeType.BUY else "sell" + return f"{order_type} {side}" + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase: + retval: BitmexPerpetualInFlightOrder = BitmexPerpetualInFlightOrder( + data["client_order_id"], + data["exchange_order_id"], + data["trading_pair"], + getattr(OrderType, data["order_type"]), + getattr(TradeType, data["trade_type"]), + Decimal(data["price"]), + Decimal(data["amount"]), + float(data["created_at"] if "created_at" in data else 0), + data["leverage"], + data["position"], + data["last_state"], + ) + retval.executed_amount_base = Decimal(data.get("executed_amount_base", '0')) + retval.executed_amount_quote = Decimal(data.get("executed_amount_quote", '0')) + last_state = int(data["last_state"]) + retval.state = BitmexPerpetualOrderStatus(last_state) + return retval + + def update(self, data: Dict[str, Any]) -> List[Any]: + events: List[Any] = [] + + new_status: BitmexPerpetualOrderStatus = BitmexPerpetualOrderStatus[data["ordStatus"]] + old_executed_base: Decimal = self.executed_amount_base + old_executed_quote: Decimal = self.executed_amount_quote + if new_status == BitmexPerpetualOrderStatus.Canceled: + overall_executed_base = self.executed_amount_base + overall_remaining_size = self.amount - overall_executed_base + overall_executed_quote = self.executed_amount_quote + else: + if "amount_remaining" in data: + overall_remaining_size: Decimal = data["amount_remaining"] + overall_executed_base: Decimal = self.amount - overall_remaining_size + else: + overall_remaining_quote: Decimal = Decimal(str(data["quote_amount_remaining"])) + overall_remaining_size: Decimal = overall_remaining_quote / Decimal(str(data["price"])) + overall_executed_base: Decimal = self.amount - overall_remaining_size + + if data.get("avgPx") is not None: + overall_executed_quote: Decimal = overall_executed_base * Decimal(str(data["avgPx"])) + else: + overall_executed_quote: Decimal = Decimal("0") + + diff_base: Decimal = overall_executed_base - old_executed_base + diff_quote: Decimal = overall_executed_quote - old_executed_quote + + if diff_base > 0: + diff_price: Decimal = diff_quote / diff_base + events.append((MarketEvent.OrderFilled, diff_base, diff_price, None)) + self.executed_amount_base = overall_executed_base + self.executed_amount_quote = overall_executed_quote + + if not self.is_done and new_status in [BitmexPerpetualOrderStatus.Canceled, BitmexPerpetualOrderStatus.Filled]: + if overall_remaining_size > 0: + events.append((MarketEvent.OrderCancelled, None, None, None)) + elif self.trade_type is TradeType.BUY: + events.append((MarketEvent.BuyOrderCompleted, overall_executed_base, overall_executed_quote, None)) + else: + events.append((MarketEvent.SellOrderCompleted, overall_executed_base, overall_executed_quote, None)) + + self.state = new_status + self.last_state = new_status.name + + return events diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book.py new file mode 100644 index 0000000..86f70f5 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book.py @@ -0,0 +1,58 @@ +import logging +from datetime import datetime +from typing import Dict, Optional + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.event.events import TradeType +from hummingbot.logger import HummingbotLogger + + +class BitmexPerpetualOrderBook(OrderBook): + _bpob_logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._baobds_logger is None: + cls._baobds_logger = logging.getLogger(__name__) + return cls._baobds_logger + + @classmethod + def snapshot_message_from_exchange(cls, msg: Dict[str, any], timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": timestamp, + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, msg: Dict[str, any], timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + data = msg["data_dict"] + if metadata: + data.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": data["symbol"], + "update_id": timestamp, + "bids": data["bids"], + "asks": data["asks"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + data = msg + if metadata: + data.update(metadata) + timestamp = datetime.timestamp(datetime.strptime(data["timestamp"], "%Y-%m-%dT%H:%M:%S.%fZ")) + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": data["symbol"], + "trade_type": float(TradeType.SELL.value) if data["side"] == "Sell" else float(TradeType.BUY.value), + "trade_id": timestamp, + "update_id": timestamp, + "price": data["price"], + "amount": data["size"] + }, timestamp=timestamp) diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book_tracker.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book_tracker.py new file mode 100644 index 0000000..d92683b --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_book_tracker.py @@ -0,0 +1,63 @@ +import asyncio +import logging +from collections import defaultdict, deque +from typing import Deque, Dict, List, Optional + +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_api_order_book_data_source import ( + BitmexPerpetualAPIOrderBookDataSource, +) +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class BitmexPerpetualOrderBookTracker(OrderBookTracker): + _bpobt_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpobt_logger is None: + cls._bpobt_logger = logging.getLogger(__name__) + return cls._bpobt_logger + + def __init__(self, + trading_pairs: Optional[List[str]] = None, + domain: str = "bitmex_perpetual", + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None): + super().__init__(data_source=BitmexPerpetualAPIOrderBookDataSource( + trading_pairs=trading_pairs, + domain=domain, + throttler=throttler, + api_factory=api_factory), + trading_pairs=trading_pairs, + domain=domain + ) + + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._saved_messages_queues: Dict[str, Deque[OrderBookMessage]] = defaultdict(lambda: deque(maxlen=1000)) + self._trading_pairs: Optional[List[str]] = trading_pairs + self._domain = domain + + self._order_book_stream_listener_task: Optional[asyncio.Task] = None + self._order_book_funding_info_listener_task: Optional[asyncio.Task] = None + + def is_funding_info_initialized(self) -> bool: + return self._data_source.is_funding_info_initialized() + + @property + def exchange_name(self) -> str: + return self._domain + + def start(self): + super().start() + + def stop(self): + self._order_book_stream_listener_task and self._order_book_stream_listener_task.cancel() + self._order_book_funding_info_listener_task and self._order_book_funding_info_listener_task.cancel() + super().stop() diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_status.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_status.py new file mode 100644 index 0000000..60d8713 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_order_status.py @@ -0,0 +1,29 @@ +from enum import Enum + + +class BitmexPerpetualOrderStatus(Enum): + New = 0 + PartiallyFilled = 101 + Canceled = 200 + Filled = 201 + FAILURE = 300 + + def __ge__(self, other): + if self.__class__ is other.__class__: + return self.value >= other.value + return NotImplemented + + def __gt__(self, other): + if self.__class__ is other.__class__: + return self.value > other.value + return NotImplemented + + def __le__(self, other): + if self.__class__ is other.__class__: + return self.value <= other.value + return NotImplemented + + def __lt__(self, other): + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..a5615a1 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_data_source.py @@ -0,0 +1,105 @@ +import asyncio +import logging +import time +from typing import Optional + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import BitmexPerpetualAuth +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BitmexPerpetualUserStreamDataSource(UserStreamTrackerDataSource): + + _bpusds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpusds_logger is None: + cls._bpusds_logger = logging.getLogger(__name__) + return cls._bpusds_logger + + def __init__( + self, + auth: BitmexPerpetualAuth, + domain: str = "bitmex_perpetual", + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + ): + super().__init__() + self._time_synchronizer = time_synchronizer + self._domain = domain + self._throttler = throttler + self._api_factory: WebAssistantsFactory = api_factory or web_utils.build_api_factory( + auth=auth + ) + self._auth = auth + self._ws_assistant: Optional[WSAssistant] = None + + @property + def last_recv_time(self) -> float: + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + ws = None + while True: + try: + expires = int(time.time()) + 25 + url = web_utils.wss_url("", self._domain) + # # establish initial connection to websocket + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect(ws_url=url) + + # # send auth request + API_KEY = self._auth.api_key + signature = await self._auth.generate_ws_signature(str(expires)) + auth_payload = {"op": "authKeyExpires", "args": [API_KEY, expires, signature]} + + auth_request: WSJSONRequest = WSJSONRequest( + payload=auth_payload, + is_auth_required=False + ) + await ws.send(auth_request) + # await ws.ping() # to update last_recv_timestamp + + # # send subscribe + # position - Updates on your positions + # order - Live updates on your orders + # margin - Updates on your current account balance and margin requirements + # wallet - Bitcoin address balance data, including total deposits & withdrawals + subscribe_payload = {"op": "subscribe", "args": ["position", "order", "margin", "wallet"]} + subscribe_request: WSJSONRequest = WSJSONRequest( + payload=subscribe_payload, + is_auth_required=False + ) + await ws.send(subscribe_request) + + async for msg in ws.iter_messages(): + if len(msg.data) > 0: + output.put_nowait(msg.data) + + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error( + f"Unexpected error while listening to user stream. Retrying after 5 seconds... " + f"Error: {e}", + exc_info=True, + ) + finally: + # Make sure no background task is leaked. + ws and await ws.disconnect() + await self._sleep(5) diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_tracker.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_tracker.py new file mode 100644 index 0000000..138a38c --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_user_stream_tracker.py @@ -0,0 +1,69 @@ +import asyncio +import logging +from typing import Optional + +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import BitmexPerpetualAuth +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_user_stream_data_source import ( + BitmexPerpetualUserStreamDataSource, +) +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class BitmexPerpetualUserStreamTracker(UserStreamTracker): + + _bpust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bust_logger is None: + cls._bust_logger = logging.getLogger(__name__) + return cls._bust_logger + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + def __init__(self, + auth: BitmexPerpetualAuth, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + time_synchronizer: Optional[TimeSynchronizer] = None): + self._data_source: Optional[UserStreamTrackerDataSource] = None + self._auth: BitmexPerpetualAuth = auth + self._domain = domain + self._throttler = throttler + self._api_factory = api_factory + self._time_synchronizer = time_synchronizer + + super().__init__(self.data_source) + self._ev_loop: asyncio.events.AbstractEventLoop = asyncio.get_event_loop() + self._user_stream_tracking_task: Optional[asyncio.Task] = None + + @property + def exchange_name(self) -> str: + return self._domain + + @property + def data_source(self) -> UserStreamTrackerDataSource: + if self._data_source is None: + self._data_source = BitmexPerpetualUserStreamDataSource( + auth=self._auth, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory, + time_synchronizer=self._time_synchronizer) + return self._data_source + + async def start(self): + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._ev_loop, self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_utils.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_utils.py new file mode 100644 index 0000000..24cad21 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_utils.py @@ -0,0 +1,129 @@ +from collections import namedtuple + +from pydantic import Field, SecretStr + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData + +CENTRALIZED = True + + +EXAMPLE_PAIR = "ETH-XBT" + + +DEFAULT_FEES = [0.01, 0.075] + + +class BitmexPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitmex_perpetual", client_data=None) + bitmex_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmex_perpetual_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Perpetual API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + +KEYS = BitmexPerpetualConfigMap.construct() + +OTHER_DOMAINS = ["bitmex_perpetual_testnet"] +OTHER_DOMAINS_PARAMETER = {"bitmex_perpetual_testnet": "bitmex_perpetual_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"bitmex_perpetual_testnet": "ETH-XBT"} +OTHER_DOMAINS_DEFAULT_FEES = {"bitmex_perpetual_testnet": [0.02, 0.04]} + + +class BitmexPerpetualTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitmex_perpetual_testnet", client_data=None) + bitmex_perpetual_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Perpetual testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmex_perpetual_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Perpetual testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitmex_perpetual" + + +OTHER_DOMAINS_KEYS = {"bitmex_perpetual_testnet": BitmexPerpetualTestnetConfigMap.construct()} + + +TRADING_PAIR_INDICES: dict = {} +TRADING_PAIR_INDEX = namedtuple('TradingPairIndex', 'index tick_size') +TRADING_PAIR_SIZE_CURRENCY: dict = {} +TRADING_PAIR_SIZE = namedtuple('TradingPairSize', 'currency is_base multiplier') + + +async def get_trading_pair_size_currency(exchange_trading_pair): + if exchange_trading_pair in TRADING_PAIR_SIZE_CURRENCY: + return TRADING_PAIR_SIZE_CURRENCY[exchange_trading_pair] + else: + instrument = await web_utils.api_request( + path = CONSTANTS.EXCHANGE_INFO_URL, + domain = "bitmex_perpetual", + params = {"symbol": exchange_trading_pair} + ) + trading_pair_info = instrument[0] + base, quote = trading_pair_info['rootSymbol'], trading_pair_info['quoteCurrency'] + multiplier = trading_pair_info.get("underlyingToPositionMultiplier") + if trading_pair_info['positionCurrency'] == quote: + TRADING_PAIR_SIZE_CURRENCY[exchange_trading_pair] = TRADING_PAIR_SIZE(quote, False, multiplier) + else: + TRADING_PAIR_SIZE_CURRENCY[exchange_trading_pair] = TRADING_PAIR_SIZE(base, True, multiplier) + return TRADING_PAIR_SIZE_CURRENCY[exchange_trading_pair] + + +async def get_trading_pair_index_and_tick_size(exchange_trading_pair): + if exchange_trading_pair in TRADING_PAIR_INDICES: + return TRADING_PAIR_INDICES[exchange_trading_pair] + else: + index = 0 + multiplier = 0 + while True: + offset = 500 * multiplier + instruments = await web_utils.api_request( + path = CONSTANTS.EXCHANGE_INFO_URL, + domain = "bitmex_perpetual", + params = { + "count": 500, + "start": offset + } + ) + for instrument in instruments: + if instrument['symbol'] == exchange_trading_pair: + TRADING_PAIR_INDICES[exchange_trading_pair] = TRADING_PAIR_INDEX( + index, + instrument['tickSize'] + ) + return TRADING_PAIR_INDICES[exchange_trading_pair] + else: + index += 1 + if len(instruments) < 500: + return False + else: + multiplier += 1 diff --git a/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_web_utils.py b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_web_utils.py new file mode 100644 index 0000000..cde7f47 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/bitmex_perpetual_web_utils.py @@ -0,0 +1,89 @@ +from typing import Any, Dict, Optional + +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitmexPerpetualRESTPreProcessor(RESTPreProcessorBase): + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + request.headers["Content-Type"] = ( + "application/json" if request.method == RESTMethod.POST else "application/x-www-form-urlencoded" + ) + return request + + +def rest_url(path_url: str, domain: str = "bitmex_perpetual"): + base_url = CONSTANTS.PERPETUAL_BASE_URL if domain == "bitmex_perpetual" else CONSTANTS.TESTNET_BASE_URL + return base_url + path_url + + +def wss_url(endpoint: str, domain: str = "bitmex_perpetual"): + base_ws_url = CONSTANTS.PERPETUAL_WS_URL if domain == "bitmex_perpetual" else CONSTANTS.TESTNET_WS_URL + return base_ws_url + endpoint + + +def build_api_factory( + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + + throttler = create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + BitmexPerpetualRESTPreProcessor(), + ]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def api_request(path: str, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DOMAIN, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + api_version: str = "v1", + limit_id: Optional[str] = None, + timeout: Optional[float] = None): + + throttler = throttler or create_throttler() + + api_factory = api_factory or build_api_factory() + rest_assistant = await api_factory.get_rest_assistant() + + async with throttler.execute_task(limit_id=limit_id if limit_id else path): + url = rest_url(path, domain) + + request = RESTRequest( + method=method, + url=url, + params=params, + data=data, + is_auth_required=is_auth_required, + throttler_limit_id=limit_id if limit_id else path + ) + response = await rest_assistant.call(request=request, timeout=timeout) + + if response.status != 200: + if return_err: + error_response = await response.json() + return error_response + else: + error_response = await response.text() + raise IOError(f"Error executing request {method.name} {path}. " + f"HTTP status is {response.status}. " + f"Error: {error_response}") + return await response.json() diff --git a/hummingbot/connector/derivative/bitmex_perpetual/constants.py b/hummingbot/connector/derivative/bitmex_perpetual/constants.py new file mode 100644 index 0000000..d4f5244 --- /dev/null +++ b/hummingbot/connector/derivative/bitmex_perpetual/constants.py @@ -0,0 +1,80 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +EXCHANGE_NAME = "bitmex_perpetual" +BROKER_ID = "bitmex_perp" +MAX_ORDER_ID_LEN = 36 + +DOMAIN = EXCHANGE_NAME +TESTNET_DOMAIN = "bitmex_perpetual_testnet" + +PERPETUAL_BASE_URL = "https://www.bitmex.com/api/v1" +TESTNET_BASE_URL = "https://testnet.bitmex.com/api/v1" + +PERPETUAL_WS_URL = "wss://ws.bitmex.com/realtime" +TESTNET_WS_URL = "wss://ws.testnet.bitmex.com/realtime" + +# Public API v1 Endpoints +SNAPSHOT_REST_URL = "/orderBook/L2" +TICKER_PRICE_URL = "/instrument" +TICKER_PRICE_CHANGE_URL = "/instrument" +EXCHANGE_INFO_URL = "/instrument" +RECENT_TRADES_URL = "/trades" +PING_URL = "" +SERVER_TIME_PATH_URL = "" +POSITION_INFORMATION_URL = "/position" + +# Private API v1 Endpoints +ORDER_URL = "/order" +ACCOUNT_INFO_URL = "/user/wallet" +TOKEN_INFO_URL = "/wallet/assets" + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +ORDERS_1MIN = "ORDERS_1MIN" +ORDERS_1SEC = "ORDERS_1SEC" + +HEARTBEAT_TIME_INTERVAL = 30.0 + +# Rate Limit time intervals +ONE_HOUR = 3600 +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 2400 + +DIFF_STREAM_ID = 1 +TRADE_STREAM_ID = 2 +FUNDING_INFO_STREAM_ID = 3 + +RATE_LIMITS = [ + # Pool Limits + RateLimit(limit_id=REQUEST_WEIGHT, limit=2400, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1MIN, limit=1200, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1SEC, limit=300, time_interval=10), + # Weight Limits for individual endpoints + RateLimit(limit_id=SNAPSHOT_REST_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=20)]), + RateLimit(limit_id=TICKER_PRICE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=2)]), + RateLimit(limit_id=TICKER_PRICE_CHANGE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=EXCHANGE_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=40)]), + RateLimit(limit_id=TOKEN_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=40)]), + RateLimit(limit_id=RECENT_TRADES_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=PING_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=ORDER_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1), + LinkedLimitWeightPair(ORDERS_1MIN, weight=1), + LinkedLimitWeightPair(ORDERS_1SEC, weight=1)]), + RateLimit(limit_id=POSITION_INFORMATION_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, weight=5, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), + RateLimit(limit_id=ACCOUNT_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), +] diff --git a/hummingbot/connector/derivative/bitmex_perpetual/dummy.pxd b/hummingbot/connector/derivative/bitmex_perpetual/dummy.pxd new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bitmex_perpetual/dummy.pyx b/hummingbot/connector/derivative/bitmex_perpetual/dummy.pyx new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bybit_perpetual/__init__.py b/hummingbot/connector/derivative/bybit_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..ea0b474 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_api_order_book_data_source.py @@ -0,0 +1,351 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import pandas as pd + +from hummingbot.connector.derivative.bybit_perpetual import ( + bybit_perpetual_constants as CONSTANTS, + bybit_perpetual_utils, + bybit_perpetual_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book import OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_derivative import BybitPerpetualDerivative + + +class BybitPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: 'BybitPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._nonce_provider = NonceCreator.for_microseconds() + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + funding_info_response = await self._request_complete_funding_info(trading_pair) + general_info = funding_info_response[0]["result"][0] + predicted_funding = funding_info_response[1]["result"] + + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(str(general_info["index_price"])), + mark_price=Decimal(str(general_info["mark_price"])), + next_funding_utc_timestamp=int(pd.Timestamp(general_info["next_funding_time"]).timestamp()), + rate=Decimal(str(predicted_funding["predicted_funding_rate"])), + ) + return funding_info + + async def listen_for_subscriptions(self): + """ + Subscribe to all required events and start the listening cycle. + """ + tasks_future = None + try: + linear_trading_pairs, non_linear_trading_pairs = bybit_perpetual_utils.get_linear_non_linear_split( + self._trading_pairs + ) + + tasks = [] + if linear_trading_pairs: + tasks.append(self._listen_for_subscriptions_on_url( + url=web_utils.wss_linear_public_url(self._domain), + trading_pairs=linear_trading_pairs)) + if non_linear_trading_pairs: + tasks.append(self._listen_for_subscriptions_on_url( + url=web_utils.wss_non_linear_public_url(self._domain), + trading_pairs=non_linear_trading_pairs)) + + if tasks: + tasks_future = asyncio.gather(*tasks) + await tasks_future + + except asyncio.CancelledError: + tasks_future and tasks_future.cancel() + raise + + async def _listen_for_subscriptions_on_url(self, url: str, trading_pairs: List[str]): + """ + Subscribe to all required events and start the listening cycle. + :param url: the wss url to connect to + :param trading_pairs: the trading pairs for which the function should listen events + """ + + ws: Optional[WSAssistant] = None + while True: + try: + ws = await self._get_connected_websocket_assistant(url) + await self._subscribe_to_channels(ws, trading_pairs) + await self._process_websocket_messages(ws) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error occurred when listening to order book streams {url}. Retrying in 5 seconds..." + ) + await self._sleep(5.0) + finally: + ws and await ws.disconnect() + + async def _get_connected_websocket_assistant(self, ws_url: str) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect( + ws_url=ws_url, message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE + ) + return ws + + async def _subscribe_to_channels(self, ws: WSAssistant, trading_pairs: List[str]): + try: + symbols = [ + await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in trading_pairs + ] + symbols_str = "|".join(symbols) + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_TRADES_TOPIC}.{symbols_str}"], + } + subscribe_trade_request = WSJSONRequest(payload=payload) + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC}.{symbols_str}"], + } + subscribe_orderbook_request = WSJSONRequest(payload=payload) + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_INSTRUMENTS_INFO_TOPIC}.{symbols_str}"], + } + subscribe_instruments_request = WSJSONRequest(payload=payload) + + await ws.send(subscribe_trade_request) # not rate-limited + await ws.send(subscribe_orderbook_request) # not rate-limited + await ws.send(subscribe_instruments_request) # not rate-limited + self.logger().info("Subscribed to public order book, trade and funding info channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + await super()._process_websocket_messages(websocket_assistant=websocket_assistant) + except asyncio.TimeoutError: + ping_request = WSJSONRequest(payload={"op": "ping"}) + await websocket_assistant.send(ping_request) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "success" not in event_message: + event_channel = event_message["topic"] + event_channel = ".".join(event_channel.split(".")[:-1]) + if event_channel == CONSTANTS.WS_TRADES_TOPIC: + channel = self._trade_messages_queue_key + elif event_channel == CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC: + channel = self._diff_messages_queue_key + elif event_channel == CONSTANTS.WS_INSTRUMENTS_INFO_TOPIC: + channel = self._funding_info_messages_queue_key + return channel + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + event_type = raw_message["type"] + + if event_type == "delta": + symbol = raw_message["topic"].split(".")[-1] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + timestamp_us = int(raw_message["timestamp_e6"]) + update_id = self._nonce_provider.get_tracking_nonce(timestamp=timestamp_us * 1e-6) + diffs_data = raw_message["data"] + bids, asks = self._get_bids_and_asks_from_ws_msg_data(diffs_data) + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + diff_message = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=order_book_message_content, + timestamp=timestamp_us * 1e-6, + ) + message_queue.put_nowait(diff_message) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_updates = raw_message["data"] + + for trade_data in trade_updates: + symbol = trade_data["symbol"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + ts_ms = int(trade_data["trade_time_ms"]) + trade_type = float(TradeType.BUY.value) if trade_data["side"] == "Buy" else float(TradeType.SELL.value) + message_content = { + "trade_id": trade_data["trade_id"], + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": trade_data["size"], + "price": trade_data["price"], + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=ts_ms * 1e-3, + ) + message_queue.put_nowait(trade_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + event_type = raw_message["type"] + if event_type == "delta": + symbol = raw_message["topic"].split(".")[-1] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + entries = raw_message["data"]["update"] + for entry in entries: + info_update = FundingInfoUpdate(trading_pair) + if "index_price" in entry: + info_update.index_price = Decimal(str(entry["index_price"])) + if "mark_price" in entry: + info_update.mark_price = Decimal(str(entry["mark_price"])) + if "next_funding_time" in entry: + info_update.next_funding_utc_timestamp = int( + pd.Timestamp(str(entry["next_funding_time"]), tz="UTC").timestamp() + ) + if "predicted_funding_rate_e6" in entry: + info_update.rate = ( + Decimal(str(entry["predicted_funding_rate_e6"])) * Decimal(1e-6) + ) + message_queue.put_nowait(info_update) + + async def _request_complete_funding_info(self, trading_pair: str): + tasks = [] + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint_info = CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT + url_info = web_utils.get_rest_url_for_endpoint(endpoint=endpoint_info, trading_pair=trading_pair, domain=self._domain) + limit_id = web_utils.get_rest_api_limit_id_for_endpoint(endpoint_info) + tasks.append(rest_assistant.execute_request( + url=url_info, + throttler_limit_id=limit_id, + params=params, + method=RESTMethod.GET, + )) + endpoint_predicted = CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL + url_predicted = web_utils.get_rest_url_for_endpoint(endpoint=endpoint_predicted, trading_pair=trading_pair, domain=self._domain) + limit_id_predicted = web_utils.get_rest_api_limit_id_for_endpoint(endpoint_predicted, trading_pair) + tasks.append(rest_assistant.execute_request( + url=url_predicted, + throttler_limit_id=limit_id_predicted, + params=params, + method=RESTMethod.GET, + is_auth_required=True + )) + + responses = await asyncio.gather(*tasks) + return responses + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response = await self._request_order_book_snapshot(trading_pair) + snapshot_data = snapshot_response["result"] + timestamp = float(snapshot_response["time_now"]) + update_id = self._nonce_provider.get_tracking_nonce(timestamp=timestamp) + + bids, asks = self._get_bids_and_asks_from_rest_msg_data(snapshot_data) + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=order_book_message_content, + timestamp=timestamp, + ) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint, trading_pair=trading_pair, domain=self._domain) + limit_id = web_utils.get_rest_api_limit_id_for_endpoint(endpoint) + data = await rest_assistant.execute_request( + url=url, + throttler_limit_id=limit_id, + params=params, + method=RESTMethod.GET, + ) + + return data + + @staticmethod + def _get_bids_and_asks_from_rest_msg_data( + snapshot: List[Dict[str, Union[str, int, float]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + bisect_idx = 0 + for i, row in enumerate(snapshot): + if row["side"] == "Sell": + bisect_idx = i + break + bids = [ + (float(row["price"]), float(row["size"])) + for row in snapshot[:bisect_idx] + ] + asks = [ + (float(row["price"]), float(row["size"])) + for row in snapshot[bisect_idx:] + ] + return bids, asks + + @staticmethod + def _get_bids_and_asks_from_ws_msg_data( + snapshot: Dict[str, List[Dict[str, Union[str, int, float]]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + bids = [] + asks = [] + for action, rows_list in snapshot.items(): + if action not in ["delete", "update", "insert"]: + continue + is_delete = action == "delete" + for row_dict in rows_list: + row_price = row_dict["price"] + row_size = 0.0 if is_delete else row_dict["size"] + row_tuple = (row_price, row_size) + if row_dict["side"] == "Buy": + bids.append(row_tuple) + else: + asks.append(row_tuple) + return bids, asks + + async def _connected_websocket_assistant(self) -> WSAssistant: + pass # unused + + async def _subscribe_channels(self, ws: WSAssistant): + pass # unused diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py new file mode 100644 index 0000000..061d612 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_auth.py @@ -0,0 +1,81 @@ +import hashlib +import hmac +import json +import time +from decimal import Decimal +from typing import Any, Dict, List + +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class BybitPerpetualAuth(AuthBase): + """ + Auth class required by Bybit Perpetual API + """ + def __init__(self, api_key: str, secret_key: str): + self._api_key: str = api_key + self._secret_key: str = secret_key + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + if request.method == RESTMethod.GET: + request = await self._authenticate_get(request) + elif request.method == RESTMethod.POST: + request = await self._authenticate_post(request) + else: + raise NotImplementedError + return request + + async def _authenticate_get(self, request: RESTRequest) -> RESTRequest: + params = request.params or {} + request.params = self._extend_params_with_authentication_info(params) + return request + + async def _authenticate_post(self, request: RESTRequest) -> RESTRequest: + data = json.loads(request.data) if request.data is not None else {} + data = self._extend_params_with_authentication_info(data) + data = {key: value for key, value in sorted(data.items())} + request.data = json.dumps(data) + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. OKX does not use this + functionality + """ + return request # pass-through + + def get_ws_auth_payload(self) -> List[str]: + """ + Generates a dictionary with all required information for the authentication process + :return: a dictionary of authentication info including the request signature + """ + expires = self._get_expiration_timestamp() + raw_signature = "GET/realtime" + expires + signature = hmac.new( + self._secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256 + ).hexdigest() + auth_info = [self._api_key, expires, signature] + + return auth_info + + def _extend_params_with_authentication_info(self, params: Dict[str, Any]) -> Dict[str, Any]: + params["timestamp"] = self._get_timestamp() + params["api_key"] = self._api_key + key_value_elements = [] + for key, value in sorted(params.items()): + converted_value = float(value) if type(value) is Decimal else value + converted_value = converted_value if type(value) is str else json.dumps(converted_value) + key_value_elements.append(str(key) + "=" + converted_value) + raw_signature = "&".join(key_value_elements) + signature = hmac.new(self._secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256).hexdigest() + params["sign"] = signature + return params + + @staticmethod + def _get_timestamp(): + return str(int(time.time() * 1e3)) + + @staticmethod + def _get_expiration_timestamp(): + return str(int((round(time.time()) + 5) * 1e3)) diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_constants.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_constants.py new file mode 100644 index 0000000..cdff50c --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_constants.py @@ -0,0 +1,147 @@ +from hummingbot.core.data_type.common import OrderType, PositionMode +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "bybit_perpetual" + +DEFAULT_DOMAIN = "bybit_perpetual_main" + +DEFAULT_TIME_IN_FORCE = "GoodTillCancel" + +REST_URLS = {"bybit_perpetual_main": "https://api.bybit.com/", + "bybit_perpetual_testnet": "https://api-testnet.bybit.com/"} +WSS_NON_LINEAR_PUBLIC_URLS = {"bybit_perpetual_main": "wss://stream.bybit.com/realtime", + "bybit_perpetual_testnet": "wss://stream-testnet.bybit.com/realtime"} +WSS_NON_LINEAR_PRIVATE_URLS = WSS_NON_LINEAR_PUBLIC_URLS +WSS_LINEAR_PUBLIC_URLS = {"bybit_perpetual_main": "wss://stream.bybit.com/realtime_public", + "bybit_perpetual_testnet": "wss://stream-testnet.bybit.com/realtime_public"} +WSS_LINEAR_PRIVATE_URLS = {"bybit_perpetual_main": "wss://stream.bybit.com/realtime_private", + "bybit_perpetual_testnet": "wss://stream-testnet.bybit.com/realtime_private"} + +REST_API_VERSION = "v2" + +HBOT_BROKER_ID = "Hummingbot" + +MAX_ID_LEN = 36 +SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE = 30 +POSITION_IDX_ONEWAY = 0 +POSITION_IDX_HEDGE_BUY = 1 +POSITION_IDX_HEDGE_SELL = 2 + +ORDER_TYPE_MAP = { + OrderType.LIMIT: "Limit", + OrderType.MARKET: "Market", +} + +POSITION_MODE_API_ONEWAY = "MergedSingle" +POSITION_MODE_API_HEDGE = "BothSide" +POSITION_MODE_MAP = { + PositionMode.ONEWAY: POSITION_MODE_API_ONEWAY, + PositionMode.HEDGE: POSITION_MODE_API_HEDGE, +} + +# REST API Public Endpoints +LINEAR_MARKET = "linear" +NON_LINEAR_MARKET = "non_linear" + +LATEST_SYMBOL_INFORMATION_ENDPOINT = { + LINEAR_MARKET: f"{REST_API_VERSION}/public/tickers", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/public/tickers"} +QUERY_SYMBOL_ENDPOINT = { + LINEAR_MARKET: f"{REST_API_VERSION}/public/symbols", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/public/symbols"} +ORDER_BOOK_ENDPOINT = { + LINEAR_MARKET: f"{REST_API_VERSION}/public/orderBook/L2", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/public/orderBook/L2"} +SERVER_TIME_PATH_URL = { + LINEAR_MARKET: f"{REST_API_VERSION}/public/time", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/public/time" +} + +# REST API Private Endpoints +SET_LEVERAGE_PATH_URL = { + LINEAR_MARKET: "private/linear/position/set-leverage", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/position/leverage/save"} +GET_LAST_FUNDING_RATE_PATH_URL = { + LINEAR_MARKET: "private/linear/funding/prev-funding", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/funding/prev-funding"} +GET_PREDICTED_FUNDING_RATE_PATH_URL = { + LINEAR_MARKET: "/private/linear/funding/predicted-funding", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/funding/predicted-funding" +} +GET_POSITIONS_PATH_URL = { + LINEAR_MARKET: "private/linear/position/list", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/position/list"} +PLACE_ACTIVE_ORDER_PATH_URL = { + LINEAR_MARKET: "private/linear/order/create", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/order/create"} +CANCEL_ACTIVE_ORDER_PATH_URL = { + LINEAR_MARKET: "private/linear/order/cancel", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/order/cancel"} +CANCEL_ALL_ACTIVE_ORDERS_PATH_URL = { + LINEAR_MARKET: "private/linear/order/cancelAll", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/order/cancelAll"} +QUERY_ACTIVE_ORDER_PATH_URL = { + LINEAR_MARKET: "private/linear/order/search", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/order"} +USER_TRADE_RECORDS_PATH_URL = { + LINEAR_MARKET: "private/linear/trade/execution/list", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/execution/list"} +GET_WALLET_BALANCE_PATH_URL = { + LINEAR_MARKET: f"{REST_API_VERSION}/private/wallet/balance", + NON_LINEAR_MARKET: f"{REST_API_VERSION}/private/wallet/balance"} +SET_POSITION_MODE_URL = { + LINEAR_MARKET: "private/linear/position/switch-mode"} + +# Funding Settlement Time Span +FUNDING_SETTLEMENT_DURATION = (5, 5) # seconds before snapshot, seconds after snapshot + +# WebSocket Public Endpoints +WS_PING_REQUEST = "ping" +WS_ORDER_BOOK_EVENTS_TOPIC = "orderBook_200.100ms" +WS_TRADES_TOPIC = "trade" +WS_INSTRUMENTS_INFO_TOPIC = "instrument_info.100ms" +WS_AUTHENTICATE_USER_ENDPOINT_NAME = "auth" +WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME = "position" +WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME = "order" +WS_SUBSCRIPTION_EXECUTIONS_ENDPOINT_NAME = "execution" +WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME = "wallet" + +# Order Statuses +ORDER_STATE = { + "Created": OrderState.OPEN, + "New": OrderState.OPEN, + "Filled": OrderState.FILLED, + "PartiallyFilled": OrderState.PARTIALLY_FILLED, + "Cancelled": OrderState.CANCELED, + "PendingCancel": OrderState.PENDING_CANCEL, + "Rejected": OrderState.FAILED, +} + +GET_LIMIT_ID = "GETLimit" +POST_LIMIT_ID = "POSTLimit" +GET_RATE = 49 # per second +POST_RATE = 19 # per second + +NON_LINEAR_PRIVATE_BUCKET_100_LIMIT_ID = "NonLinearPrivateBucket100" +NON_LINEAR_PRIVATE_BUCKET_600_LIMIT_ID = "NonLinearPrivateBucket600" +NON_LINEAR_PRIVATE_BUCKET_75_LIMIT_ID = "NonLinearPrivateBucket75" +NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID = "NonLinearPrivateBucket120B" +NON_LINEAR_PRIVATE_BUCKET_120_C_LIMIT_ID = "NonLinearPrivateBucket120C" + +LINEAR_PRIVATE_BUCKET_100_LIMIT_ID = "LinearPrivateBucket100" +LINEAR_PRIVATE_BUCKET_600_LIMIT_ID = "LinearPrivateBucket600" +LINEAR_PRIVATE_BUCKET_75_LIMIT_ID = "LinearPrivateBucket75" +LINEAR_PRIVATE_BUCKET_120_A_LIMIT_ID = "LinearPrivateBucket120A" + +# Request error codes +RET_CODE_OK = 0 +RET_CODE_PARAMS_ERROR = 10001 +RET_CODE_API_KEY_INVALID = 10003 +RET_CODE_AUTH_TIMESTAMP_ERROR = 10021 +RET_CODE_ORDER_NOT_EXISTS = 20001 +RET_CODE_MODE_POSITION_NOT_EMPTY = 30082 +RET_CODE_MODE_NOT_MODIFIED = 30083 +RET_CODE_MODE_ORDER_NOT_EMPTY = 30086 +RET_CODE_API_KEY_EXPIRED = 33004 +RET_CODE_LEVERAGE_NOT_MODIFIED = 34036 +RET_CODE_POSITION_ZERO = 130125 diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py new file mode 100644 index 0000000..7b259d0 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_derivative.py @@ -0,0 +1,881 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import pandas as pd +from bidict import bidict + +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_utils as bybit_utils +from hummingbot.connector.derivative.bybit_perpetual import bybit_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_api_order_book_data_source import ( + BybitPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth import BybitPerpetualAuth +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_user_stream_data_source import ( + BybitPerpetualUserStreamDataSource, +) +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + + +class BybitPerpetualDerivative(PerpetualDerivativePyBase): + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + bybit_perpetual_api_key: str = None, + bybit_perpetual_secret_key: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + + self.bybit_perpetual_api_key = bybit_perpetual_api_key + self.bybit_perpetual_secret_key = bybit_perpetual_secret_key + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._last_trade_history_timestamp = None + + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> BybitPerpetualAuth: + return BybitPerpetualAuth(self.bybit_perpetual_api_key, self.bybit_perpetual_secret_key) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return web_utils.build_rate_limits(self.trading_pairs) + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.HBOT_BROKER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.LIMIT, OrderType.MARKET] + + def supported_position_modes(self) -> List[PositionMode]: + if all(bybit_utils.is_linear_perpetual(tp) for tp in self._trading_pairs): + return [PositionMode.ONEWAY, PositionMode.HEDGE] + elif all(not bybit_utils.is_linear_perpetual(tp) for tp in self._trading_pairs): + # As of ByBit API v2, we only support ONEWAY mode for non-linear perpetuals + return [PositionMode.ONEWAY] + else: + self.logger().warning( + "Currently there is no support for both linear and non-linear markets concurrently." + " Please start another hummingbot instance." + ) + return [] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def start(self, clock: Clock, timestamp: float): + super().start(clock, timestamp) + if self._domain == CONSTANTS.DEFAULT_DOMAIN and self.is_trading_required: + self.set_position_mode(PositionMode.HEDGE) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + ts_error_target_str = self._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_AUTH_TIMESTAMP_ERROR) + param_error_target_str = ( + f"{self._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_PARAMS_ERROR)} - invalid timestamp" + ) + is_time_synchronizer_related = ( + ts_error_target_str in error_description + or param_error_target_str in error_description + ) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + data = {"symbol": await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair)} + if tracked_order.exchange_order_id: + data["order_id"] = tracked_order.exchange_order_id + else: + data["order_link_id"] = tracked_order.client_order_id + cancel_result = await self._api_post( + path_url=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL, + data=data, + is_auth_required=True, + trading_pair=tracked_order.trading_pair, + ) + response_code = cancel_result["ret_code"] + + if response_code != CONSTANTS.RET_CODE_OK: + if response_code == CONSTANTS.RET_CODE_ORDER_NOT_EXISTS: + await self._order_tracker.process_order_not_found(order_id) + formatted_ret_code = self._format_ret_code_for_print(response_code) + raise IOError(f"{formatted_ret_code} - {cancel_result['ret_msg']}") + + return True + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + position_idx = self._get_position_idx(trade_type, position_action) + data = { + "side": "Buy" if trade_type == TradeType.BUY else "Sell", + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair), + "qty": float(amount), + "time_in_force": CONSTANTS.DEFAULT_TIME_IN_FORCE, + "close_on_trigger": position_action == PositionAction.CLOSE, + "order_link_id": order_id, + "reduce_only": position_action == PositionAction.CLOSE, + "position_idx": position_idx, + "order_type": CONSTANTS.ORDER_TYPE_MAP[order_type], + } + if order_type.is_limit_type(): + data["price"] = float(price) + + resp = await self._api_post( + path_url=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL, + data=data, + is_auth_required=True, + trading_pair=trading_pair, + headers={"referer": CONSTANTS.HBOT_BROKER_ID}, + **kwargs, + ) + + if resp["ret_code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(resp['ret_code']) + raise IOError(f"Error submitting order {order_id}: {formatted_ret_code} - {resp['ret_msg']}") + + return str(resp["result"]["order_id"]), self.current_timestamp + + def _get_position_idx(self, trade_type: TradeType, position_action: PositionAction) -> int: + if position_action == PositionAction.NIL: + raise NotImplementedError + if self.position_mode == PositionMode.ONEWAY: + position_idx = CONSTANTS.POSITION_IDX_ONEWAY + elif trade_type == TradeType.BUY: + if position_action == PositionAction.CLOSE: + position_idx = CONSTANTS.POSITION_IDX_HEDGE_SELL + else: # position_action == PositionAction.Open + position_idx = CONSTANTS.POSITION_IDX_HEDGE_BUY + elif trade_type == TradeType.SELL: + if position_action == PositionAction.CLOSE: + position_idx = CONSTANTS.POSITION_IDX_HEDGE_BUY + else: # position_action == PositionAction.Open + position_idx = CONSTANTS.POSITION_IDX_HEDGE_SELL + else: # trade_type == TradeType.RANGE + raise NotImplementedError + + return position_idx + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + pass + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth, + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BybitPerpetualAPIOrderBookDataSource( + self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BybitPerpetualUserStreamDataSource( + auth=self._auth, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + async def _status_polling_loop_fetch_updates(self): + await safe_gather( + self._update_trade_history(), + self._update_order_status(), + self._update_balances(), + self._update_positions(), + ) + + async def _update_trade_history(self): + """ + Calls REST API to get trade history (order fills) + """ + + trade_history_tasks = [] + + for trading_pair in self._trading_pairs: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + body_params = { + "symbol": exchange_symbol, + "limit": 200, + } + if self._last_trade_history_timestamp: + body_params["start_time"] = int(int(self._last_trade_history_timestamp) * 1e3) + + trade_history_tasks.append( + asyncio.create_task(self._api_get( + path_url=CONSTANTS.USER_TRADE_RECORDS_PATH_URL, + params=body_params, + is_auth_required=True, + trading_pair=trading_pair, + )) + ) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*trade_history_tasks, return_exceptions=True) + + # Initial parsing of responses. Joining all the responses + parsed_history_resps: List[Dict[str, Any]] = [] + for trading_pair, resp in zip(self._trading_pairs, raw_responses): + if not isinstance(resp, Exception): + self._last_trade_history_timestamp = float(resp["time_now"]) + trade_entries = (resp["result"]["trade_list"] + if "trade_list" in resp["result"] + else resp["result"]["data"]) + if trade_entries: + parsed_history_resps.extend(trade_entries) + else: + self.logger().network( + f"Error fetching status update for {trading_pair}: {resp}.", + app_warning_msg=f"Failed to fetch status update for {trading_pair}." + ) + + # Trade updates must be handled before any order status updates. + for trade in parsed_history_resps: + self._process_trade_event_message(trade) + + async def _update_order_status(self): + """ + Calls REST API to get order status + """ + + active_orders: List[InFlightOrder] = list(self.in_flight_orders.values()) + + tasks = [] + for active_order in active_orders: + tasks.append(asyncio.create_task(self._request_order_status_data(tracked_order=active_order))) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*tasks, return_exceptions=True) + + # Initial parsing of responses. Removes Exceptions. + parsed_status_responses: List[Dict[str, Any]] = [] + for resp, active_order in zip(raw_responses, active_orders): + if not isinstance(resp, Exception): + parsed_status_responses.append(resp["result"]) + else: + self.logger().network( + f"Error fetching status update for the order {active_order.client_order_id}: {resp}.", + app_warning_msg=f"Failed to fetch status update for the order {active_order.client_order_id}." + ) + await self._order_tracker.process_order_not_found(active_order.client_order_id) + + for order_status in parsed_status_responses: + self._process_order_event_message(order_status) + + async def _update_balances(self): + """ + Calls REST API to update total and available balances + """ + wallet_balance: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.GET_WALLET_BALANCE_PATH_URL, + is_auth_required=True, + ) + + if wallet_balance["ret_code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(wallet_balance['ret_code']) + raise IOError(f"{formatted_ret_code} - {wallet_balance['ret_msg']}") + + self._account_available_balances.clear() + self._account_balances.clear() + + if wallet_balance["result"] is not None: + for asset_name, balance_json in wallet_balance["result"].items(): + self._account_balances[asset_name] = Decimal(str(balance_json["wallet_balance"])) + self._account_available_balances[asset_name] = Decimal(str(balance_json["available_balance"])) + + async def _update_positions(self): + """ + Retrieves all positions using the REST API. + """ + position_tasks = [] + + for trading_pair in self._trading_pairs: + ex_trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair) + body_params = {"symbol": ex_trading_pair} + position_tasks.append( + asyncio.create_task(self._api_get( + path_url=CONSTANTS.GET_POSITIONS_PATH_URL, + params=body_params, + is_auth_required=True, + trading_pair=trading_pair, + )) + ) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*position_tasks, return_exceptions=True) + + # Initial parsing of responses. Joining all the responses + parsed_resps: List[Dict[str, Any]] = [] + for resp, trading_pair in zip(raw_responses, self._trading_pairs): + if not isinstance(resp, Exception): + result = resp["result"] + if result: + position_entries = result if isinstance(result, list) else [result] + parsed_resps.extend(position_entries) + else: + self.logger().error(f"Error fetching positions for {trading_pair}. Response: {resp}") + + for position in parsed_resps: + data = position + ex_trading_pair = data.get("symbol") + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(ex_trading_pair) + position_side = PositionSide.LONG if data["side"] == "Buy" else PositionSide.SHORT + unrealized_pnl = Decimal(str(data["unrealised_pnl"])) + entry_price = Decimal(str(data["entry_price"])) + amount = Decimal(str(data["size"])) + leverage = Decimal(str(data["leverage"])) if bybit_utils.is_linear_perpetual(hb_trading_pair) \ + else Decimal(str(data["effective_leverage"])) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side) + if amount != s_decimal_0: + position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + try: + all_fills_response = await self._request_order_fills(order=order) + trades_list_key = "data" if bybit_utils.is_linear_perpetual(order.trading_pair) else "trade_list" + fills_data = all_fills_response["result"].get(trades_list_key, []) + + if fills_data is not None: + for fill_data in fills_data: + trade_update = self._parse_trade_update(trade_msg=fill_data, tracked_order=order) + trade_updates.append(trade_update) + except IOError as ex: + if not self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + raise + + return trade_updates + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + body_params = { + "order_id": order.exchange_order_id, + "symbol": exchange_symbol, + } + res = await self._api_get( + path_url=CONSTANTS.USER_TRADE_RECORDS_PATH_URL, + params=body_params, + is_auth_required=True, + trading_pair=order.trading_pair, + ) + return res + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + try: + order_status_data = await self._request_order_status_data(tracked_order=tracked_order) + order_msg = order_status_data["result"] + client_order_id = str(order_msg["order_link_id"]) + + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=CONSTANTS.ORDER_STATE[order_msg["order_status"]], + client_order_id=client_order_id, + exchange_order_id=order_msg["order_id"], + ) + + return order_update + + except IOError as ex: + if self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + ) + else: + raise + + return order_update + + async def _request_order_status_data(self, tracked_order: InFlightOrder) -> Dict: + exchange_symbol = await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair) + query_params = { + "symbol": exchange_symbol, + "order_link_id": tracked_order.client_order_id + } + if tracked_order.exchange_order_id is not None: + query_params["order_id"] = tracked_order.exchange_order_id + + resp = await self._api_get( + path_url=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, + params=query_params, + is_auth_required=True, + trading_pair=tracked_order.trading_pair, + ) + + return resp + + async def _user_stream_event_listener(self): + """ + Listens to message in _user_stream_tracker.user_stream queue. + """ + async for event_message in self._iter_user_event_queue(): + try: + endpoint = web_utils.endpoint_from_message(event_message) + payload = web_utils.payload_from_message(event_message) + + if endpoint == CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME: + for position_msg in payload: + await self._process_account_position_event(position_msg) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME: + for order_msg in payload: + self._process_order_event_message(order_msg) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_EXECUTIONS_ENDPOINT_NAME: + for trade_msg in payload: + self._process_trade_event_message(trade_msg) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME: + for wallet_msg in payload: + self._process_wallet_event_message(wallet_msg) + elif endpoint is None: + self.logger().error(f"Could not extract endpoint from {event_message}.") + raise ValueError + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _process_account_position_event(self, position_msg: Dict[str, Any]): + """ + Updates position + :param position_msg: The position event message payload + """ + ex_trading_pair = position_msg["symbol"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + position_side = PositionSide.LONG if position_msg["side"] == "Buy" else PositionSide.SHORT + position_value = Decimal(str(position_msg["position_value"])) + entry_price = Decimal(str(position_msg["entry_price"])) + amount = Decimal(str(position_msg["size"])) + leverage = Decimal(str(position_msg["leverage"])) + unrealized_pnl = position_value - (amount * entry_price * leverage) + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + if amount != s_decimal_0: + position = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * (Decimal("-1.0") if position_side == PositionSide.SHORT else Decimal("1.0")), + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + # Trigger balance update because Bybit doesn't have balance updates through the websocket + safe_ensure_future(self._update_balances()) + + def _process_trade_event_message(self, trade_msg: Dict[str, Any]): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + :param trade_msg: The trade event message payload + """ + + client_order_id = str(trade_msg["order_link_id"]) + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + + if fillable_order is not None: + trade_update = self._parse_trade_update(trade_msg=trade_msg, tracked_order=fillable_order) + self._order_tracker.process_trade_update(trade_update) + + def _parse_trade_update(self, trade_msg: Dict, tracked_order: InFlightOrder) -> TradeUpdate: + trade_id: str = str(trade_msg["exec_id"]) + + fee_asset = tracked_order.quote_asset + fee_amount = Decimal(trade_msg["exec_fee"]) + position_side = trade_msg["side"] + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "Buy" + or tracked_order.trade_type is TradeType.SELL and position_side == "Sell") + else PositionAction.CLOSE) + + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + + exec_price = Decimal(trade_msg["exec_price"]) if "exec_price" in trade_msg else Decimal(trade_msg["price"]) + exec_time = ( + trade_msg["exec_time"] + if "exec_time" in trade_msg + else pd.Timestamp(trade_msg["trade_time"]).timestamp() + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(trade_msg["order_id"]), + trading_pair=tracked_order.trading_pair, + fill_timestamp=exec_time, + fill_price=exec_price, + fill_base_amount=Decimal(trade_msg["exec_qty"]), + fill_quote_amount=exec_price * Decimal(trade_msg["exec_qty"]), + fee=fee, + ) + + return trade_update + + def _process_order_event_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancellation or failure event if needed. + :param order_msg: The order event message payload + """ + order_status = CONSTANTS.ORDER_STATE[order_msg["order_status"]] + client_order_id = str(order_msg["order_link_id"]) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + if updatable_order is not None: + new_order_update: OrderUpdate = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=order_status, + client_order_id=client_order_id, + exchange_order_id=order_msg["order_id"], + ) + self._order_tracker.process_order_update(new_order_update) + + def _process_wallet_event_message(self, wallet_msg: Dict[str, Any]): + """ + Updates account balances. + :param wallet_msg: The account balance update message payload + """ + if "coin" in wallet_msg: # non-linear + symbol = wallet_msg["coin"] + else: # linear + symbol = "USDT" + self._account_balances[symbol] = Decimal(str(wallet_msg["wallet_balance"])) + self._account_available_balances[symbol] = Decimal(str(wallet_msg["available_balance"])) + + async def _format_trading_rules(self, instrument_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Converts JSON API response into a local dictionary of trading rules. + :param instrument_info_dict: The JSON API response. + :returns: A dictionary of trading pair to its respective TradingRule. + """ + trading_rules = {} + symbol_map = await self.trading_pair_symbol_map() + for instrument in instrument_info_dict["result"]: + try: + exchange_symbol = instrument["name"] + if exchange_symbol in symbol_map: + trading_pair = combine_to_hb_trading_pair(instrument['base_currency'], instrument['quote_currency']) + is_linear = bybit_utils.is_linear_perpetual(trading_pair) + collateral_token = instrument["quote_currency"] if is_linear else instrument["base_currency"] + trading_rules[trading_pair] = TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(str(instrument["lot_size_filter"]["min_trading_qty"])), + max_order_size=Decimal(str(instrument["lot_size_filter"]["max_trading_qty"])), + min_price_increment=Decimal(str(instrument["price_filter"]["tick_size"])), + min_base_amount_increment=Decimal(str(instrument["lot_size_filter"]["qty_step"])), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule: {instrument}. Skipping...") + return list(trading_rules.values()) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(bybit_utils.is_exchange_information_valid, exchange_info["result"]): + exchange_symbol = symbol_data["name"] + base = symbol_data["base_currency"] + quote = symbol_data["quote_currency"] + trading_pair = combine_to_hb_trading_pair(base, quote) + if trading_pair in mapping.inverse: + self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote) + else: + mapping[exchange_symbol] = trading_pair + self._set_trading_pair_symbol_map(mapping) + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error(f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}") + mapping.pop(current_exchange_symbol) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = {"symbol": exchange_symbol} + + resp_json = await self._api_get( + path_url=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT, + params=params, + ) + + price = float(resp_json["result"][0]["last_price"]) + return price + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + msg = "" + success = True + + api_mode = CONSTANTS.POSITION_MODE_MAP[mode] + is_linear = bybit_utils.is_linear_perpetual(trading_pair) + + if is_linear: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + data = {"symbol": exchange_symbol, "mode": api_mode} + + response = await self._api_post( + path_url=CONSTANTS.SET_POSITION_MODE_URL, + data=data, + is_auth_required=True, + ) + + response_code = response["ret_code"] + + if response_code not in [CONSTANTS.RET_CODE_OK, CONSTANTS.RET_CODE_MODE_NOT_MODIFIED]: + formatted_ret_code = self._format_ret_code_for_print(response_code) + msg = f"{formatted_ret_code} - {response['ret_msg']}" + success = False + else: + # Inverse Perpetuals don't have set_position_mode() + msg = "Inverse Perpetuals don't allow for a position mode change." + success = False + + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + + if bybit_utils.is_linear_perpetual(trading_pair): + data = { + "symbol": exchange_symbol, + "buy_leverage": leverage, + "sell_leverage": leverage + } + else: + data = { + "symbol": exchange_symbol, + "leverage": leverage + } + + resp: Dict[str, Any] = await self._api_post( + path_url=CONSTANTS.SET_LEVERAGE_PATH_URL, + data=data, + is_auth_required=True, + trading_pair=trading_pair, + ) + + success = False + msg = "" + if resp["ret_code"] == CONSTANTS.RET_CODE_OK or (resp["ret_code"] == CONSTANTS.RET_CODE_LEVERAGE_NOT_MODIFIED and resp["ret_msg"] == "leverage not modified"): + success = True + else: + formatted_ret_code = self._format_ret_code_for_print(resp['ret_code']) + msg = f"{formatted_ret_code} - {resp['ret_msg']}" + + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + + params = { + "symbol": exchange_symbol + } + raw_response: Dict[str, Any] = await self._api_get( + path_url=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL, + params=params, + is_auth_required=True, + trading_pair=trading_pair, + ) + data: Dict[str, Any] = raw_response["result"] + + if not data: + # An empty funding fee/payment is retrieved. + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + else: + funding_rate: Decimal = Decimal(str(data["funding_rate"])) + position_size: Decimal = Decimal(str(data["size"])) + payment: Decimal = funding_rate * position_size + if bybit_utils.is_linear_perpetual(trading_pair): + timestamp: int = int(pd.Timestamp(data["exec_time"], tz="UTC").timestamp()) + else: + timestamp: int = int(data["exec_timestamp"]) + + return timestamp, funding_rate, payment + + async def _api_request(self, + path_url, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + trading_pair: Optional[str] = None, + **kwargs) -> Dict[str, Any]: + + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + if limit_id is None: + limit_id = web_utils.get_rest_api_limit_id_for_endpoint( + endpoint=path_url, + trading_pair=trading_pair, + ) + url = web_utils.get_rest_url_for_endpoint(endpoint=path_url, trading_pair=trading_pair, domain=self._domain) + + resp = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + throttler_limit_id=limit_id if limit_id else path_url, + ) + return resp + + @staticmethod + def _format_ret_code_for_print(ret_code: Union[str, int]) -> str: + return f"ret_code <{ret_code}>" diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..f7c8b24 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_user_stream_data_source.py @@ -0,0 +1,171 @@ +import asyncio +from typing import List, Optional + +from hummingbot.connector.derivative.bybit_perpetual import ( + bybit_perpetual_constants as CONSTANTS, + bybit_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth import BybitPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BybitPerpetualUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: BybitPerpetualAuth, + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__() + self._domain = domain + self._api_factory = api_factory + self._auth = auth + self._ws_assistants: List[WSAssistant] = [] + + @property + def last_recv_time(self) -> float: + """ + Returns the time of the last received message + + :return: the timestamp of the last received message in seconds + """ + t = 0.0 + if len(self._ws_assistants) > 0: + t = min([wsa.last_recv_time for wsa in self._ws_assistants]) + return t + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + Connects to the user private channel in the exchange using a websocket connection. With the established + connection listens to all balance events and order updates provided by the exchange, and stores them in the + output queue + + :param output: the queue to use to store the received messages + """ + tasks_future = None + try: + tasks = [] + tasks.append( + self._listen_for_user_stream_on_url( + url=web_utils.wss_linear_private_url(self._domain), output=output + ) + ) + tasks.append( + self._listen_for_user_stream_on_url( + url=web_utils.wss_non_linear_private_url(self._domain), output=output + ) + ) + + tasks_future = asyncio.gather(*tasks) + await tasks_future + + except asyncio.CancelledError: + tasks_future and tasks_future.cancel() + raise + + async def _listen_for_user_stream_on_url(self, url: str, output: asyncio.Queue): + ws: Optional[WSAssistant] = None + while True: + try: + ws = await self._get_connected_websocket_assistant(url) + self._ws_assistants.append(ws) + await self._subscribe_to_channels(ws, url) + await ws.ping() # to update last_recv_timestamp + await self._process_websocket_messages(websocket_assistant=ws, queue=output) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error while listening to user stream {url}. Retrying after 5 seconds..." + ) + await self._sleep(5.0) + finally: + await self._on_user_stream_interruption(ws) + ws and self._ws_assistants.remove(ws) + + async def _get_connected_websocket_assistant(self, ws_url: str) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=ws_url, message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + await self._authenticate(ws) + return ws + + async def _authenticate(self, ws: WSAssistant): + """ + Authenticates user to websocket + """ + auth_payload: List[str] = self._auth.get_ws_auth_payload() + payload = {"op": "auth", "args": auth_payload} + login_request: WSJSONRequest = WSJSONRequest(payload=payload) + await ws.send(login_request) + response: WSResponse = await ws.receive() + message = response.data + + if ( + message["success"] is not True + or not message["request"] + or not message["request"]["op"] + or message["request"]["op"] != "auth" + ): + self.logger().error("Error authenticating the private websocket connection") + raise IOError("Private websocket connection authentication failed") + + async def _subscribe_to_channels(self, ws: WSAssistant, url: str): + try: + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME}"], + } + subscribe_positions_request = WSJSONRequest(payload) + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME}"], + } + subscribe_orders_request = WSJSONRequest(payload) + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_SUBSCRIPTION_EXECUTIONS_ENDPOINT_NAME}"], + } + subscribe_executions_request = WSJSONRequest(payload) + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME}"], + } + subscribe_wallet_request = WSJSONRequest(payload) + + await ws.send(subscribe_positions_request) + await ws.send(subscribe_orders_request) + await ws.send(subscribe_executions_request) + await ws.send(subscribe_wallet_request) + + self.logger().info( + f"Subscribed to private account and orders channels {url}..." + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error occurred subscribing to order book trading and delta streams {url}..." + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + await super()._process_websocket_messages( + websocket_assistant=websocket_assistant, + queue=queue) + except asyncio.TimeoutError: + ping_request = WSJSONRequest(payload={"op": "ping"}) + await websocket_assistant.send(ping_request) + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + pass # unused + + async def _connected_websocket_assistant(self) -> WSAssistant: + pass # unused diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py new file mode 100644 index 0000000..a9fd2b2 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_utils.py @@ -0,0 +1,127 @@ +from decimal import Decimal +from typing import Any, Dict, List, Tuple + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +# Bybit fees: https://help.bybit.com/hc/en-us/articles/360039261154 +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0006"), + taker_percent_fee_decimal=Decimal("0.0001"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USD" + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + status = exchange_info.get("status") + valid = status is not None and status in ["Trading", "Settling"] + return valid + + +def get_linear_non_linear_split(trading_pairs: List[str]) -> Tuple[List[str], List[str]]: + linear_trading_pairs = [] + non_linear_trading_pairs = [] + for trading_pair in trading_pairs: + if is_linear_perpetual(trading_pair): + linear_trading_pairs.append(trading_pair) + else: + non_linear_trading_pairs.append(trading_pair) + return linear_trading_pairs, non_linear_trading_pairs + + +def is_linear_perpetual(trading_pair: str) -> bool: + """ + Returns True if trading_pair is in USDT(Linear) Perpetual + """ + _, quote_asset = split_hb_trading_pair(trading_pair) + return quote_asset == "USDT" + + +def get_next_funding_timestamp(current_timestamp: float) -> float: + # On ByBit Perpetuals, funding occurs every 8 hours at 00:00UTC, 08:00UTC and 16:00UTC. + # Reference: https://help.bybit.com/hc/en-us/articles/360039261134-Funding-fee-calculation + int_ts = int(current_timestamp) + eight_hours = 8 * 60 * 60 + mod = int_ts % eight_hours + return float(int_ts - mod + eight_hours) + + +class BybitPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bybit_perpetual", client_data=None) + bybit_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bybit_perpetual_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Perpetual secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bybit_perpetual" + + +KEYS = BybitPerpetualConfigMap.construct() + +OTHER_DOMAINS = ["bybit_perpetual_testnet"] +OTHER_DOMAINS_PARAMETER = {"bybit_perpetual_testnet": "bybit_perpetual_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"bybit_perpetual_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = { + "bybit_perpetual_testnet": TradeFeeSchema( + maker_percent_fee_decimal=Decimal("-0.00025"), + taker_percent_fee_decimal=Decimal("0.00075"), + ) +} + + +class BybitPerpetualTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bybit_perpetual_testnet", client_data=None) + bybit_perpetual_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Perpetual Testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bybit_perpetual_testnet_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Perpetual Testnet secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bybit_perpetual_testnet" + + +OTHER_DOMAINS_KEYS = { + "bybit_perpetual_testnet": BybitPerpetualTestnetConfigMap.construct() +} diff --git a/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_web_utils.py b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_web_utils.py new file mode 100644 index 0000000..cd77ba6 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/bybit_perpetual_web_utils.py @@ -0,0 +1,433 @@ +from typing import Any, Callable, Dict, List, Optional + +from hummingbot.connector.derivative.bybit_perpetual import bybit_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_utils import is_linear_perpetual +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class HeadersContentRESTPreProcessor(RESTPreProcessorBase): + async def pre_process(self, request: RESTRequest) -> RESTRequest: + request.headers = request.headers or {} + request.headers["Content-Type"] = "application/json" + return request + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + HeadersContentRESTPreProcessor(), + ], + ) + return api_factory + + +def create_throttler(trading_pairs: List[str] = None) -> AsyncThrottler: + throttler = AsyncThrottler(build_rate_limits(trading_pairs)) + return throttler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + endpoint = CONSTANTS.SERVER_TIME_PATH_URL + url = get_rest_url_for_endpoint(endpoint=endpoint, domain=domain) + limit_id = get_rest_api_limit_id_for_endpoint(endpoint) + response = await rest_assistant.execute_request( + url=url, + throttler_limit_id=limit_id, + method=RESTMethod.GET, + ) + server_time = float(response["time_now"]) + + return server_time + + +def endpoint_from_message(message: Dict[str, Any]) -> Optional[str]: + endpoint = None + if "request" in message: + message = message["request"] + if isinstance(message, dict): + if "op" in message.keys(): + endpoint = message["op"] + elif endpoint is None and "topic" in message.keys(): + endpoint = message["topic"] + return endpoint + + +def payload_from_message(message: Dict[str, Any]) -> List[Dict[str, Any]]: + payload = message + if "data" in message: + payload = message["data"] + return payload + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def get_rest_url_for_endpoint( + endpoint: Dict[str, str], trading_pair: Optional[str] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +): + market = _get_rest_api_market_for_endpoint(trading_pair) + variant = domain if domain else CONSTANTS.DEFAULT_DOMAIN + return CONSTANTS.REST_URLS.get(variant) + endpoint[market] + + +def get_pair_specific_limit_id(base_limit_id: str, trading_pair: str) -> str: + limit_id = f"{base_limit_id}-{trading_pair}" + return limit_id + + +def get_rest_api_limit_id_for_endpoint(endpoint: Dict[str, str], trading_pair: Optional[str] = None) -> str: + market = _get_rest_api_market_for_endpoint(trading_pair) + limit_id = endpoint[market] + if trading_pair is not None: + limit_id = get_pair_specific_limit_id(limit_id, trading_pair) + return limit_id + + +def _wss_url(endpoint: Dict[str, str], connector_variant_label: Optional[str]) -> str: + variant = connector_variant_label if connector_variant_label else CONSTANTS.DEFAULT_DOMAIN + return endpoint.get(variant) + + +def wss_linear_public_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_LINEAR_PUBLIC_URLS, connector_variant_label) + + +def wss_linear_private_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_LINEAR_PRIVATE_URLS, connector_variant_label) + + +def wss_non_linear_public_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_NON_LINEAR_PUBLIC_URLS, connector_variant_label) + + +def wss_non_linear_private_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_NON_LINEAR_PRIVATE_URLS, connector_variant_label) + + +def build_rate_limits(trading_pairs: Optional[List[str]] = None) -> List[RateLimit]: + trading_pairs = trading_pairs or [] + rate_limits = [] + + rate_limits.extend(_build_global_rate_limits()) + rate_limits.extend(_build_public_rate_limits()) + rate_limits.extend(_build_private_rate_limits(trading_pairs)) + + return rate_limits + + +def _build_private_general_rate_limits() -> List[RateLimit]: + rate_limits = [ + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.GET_WALLET_BALANCE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID)], + ), + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.SET_POSITION_MODE_URL[CONSTANTS.LINEAR_MARKET], + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID)], + ), + ] + return rate_limits + + +def _build_global_rate_limits() -> List[RateLimit]: + rate_limits = [ + RateLimit(limit_id=CONSTANTS.GET_LIMIT_ID, limit=CONSTANTS.GET_RATE, time_interval=1), + RateLimit(limit_id=CONSTANTS.POST_LIMIT_ID, limit=CONSTANTS.POST_RATE, time_interval=1), + ] + return rate_limits + + +def _build_public_rate_limits(): + public_rate_limits = [ + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit=CONSTANTS.GET_RATE, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], + ), + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.QUERY_SYMBOL_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit=CONSTANTS.GET_RATE, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], + ), + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.ORDER_BOOK_ENDPOINT[CONSTANTS.NON_LINEAR_MARKET], + limit=CONSTANTS.GET_RATE, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], + ), + RateLimit( # same for linear and non-linear + limit_id=CONSTANTS.SERVER_TIME_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + limit=CONSTANTS.GET_RATE, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], + ) + ] + return public_rate_limits + + +def _build_private_rate_limits(trading_pairs: List[str]) -> List[RateLimit]: + rate_limits = [] + + rate_limits.extend(_build_private_pair_specific_rate_limits(trading_pairs)) + rate_limits.extend(_build_private_general_rate_limits()) + + return rate_limits + + +def _build_private_pair_specific_rate_limits(trading_pairs: List[str]) -> List[RateLimit]: + rate_limits = [] + + for trading_pair in trading_pairs: + market = _get_rest_api_market_for_endpoint(trading_pair) + if market == CONSTANTS.NON_LINEAR_MARKET: + rate_limits.extend(_build_private_pair_specific_non_linear_rate_limits(trading_pair)) + else: + rate_limits.extend(_build_private_pair_specific_linear_rate_limits(trading_pair)) + + return rate_limits + + +def _get_rest_api_market_for_endpoint(trading_pair: Optional[str] = None) -> str: + # The default selection should be linear because general requests such as setting position mode + # exists only for linear market and is without a trading pair + if trading_pair is None or is_linear_perpetual(trading_pair): + market = CONSTANTS.LINEAR_MARKET + else: + market = CONSTANTS.NON_LINEAR_MARKET + return market + + +def _build_private_pair_specific_non_linear_rate_limits(trading_pair: str) -> List[RateLimit]: + pair_specific_non_linear_private_bucket_100_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_100_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_non_linear_private_bucket_600_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_600_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_non_linear_private_bucket_75_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_75_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_non_linear_private_bucket_120_b_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_B_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_non_linear_private_bucket_120_c_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.NON_LINEAR_PRIVATE_BUCKET_120_C_LIMIT_ID, trading_pair=trading_pair + ) + + rate_limits = [ + RateLimit(limit_id=pair_specific_non_linear_private_bucket_100_limit_id, limit=100, time_interval=60), + RateLimit(limit_id=pair_specific_non_linear_private_bucket_600_limit_id, limit=600, time_interval=60), + RateLimit(limit_id=pair_specific_non_linear_private_bucket_75_limit_id, limit=75, time_interval=60), + RateLimit(limit_id=pair_specific_non_linear_private_bucket_120_b_limit_id, limit=120, time_interval=60), + RateLimit(limit_id=pair_specific_non_linear_private_bucket_120_c_limit_id, limit=120, time_interval=60), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], trading_pair=trading_pair + ), + limit=75, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_75_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_120_c_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_120_c_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], trading_pair=trading_pair + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_120_b_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=100, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_100_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=100, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_100_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=600, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_non_linear_private_bucket_600_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[CONSTANTS.NON_LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID)], + ), + ] + + return rate_limits + + +def _build_private_pair_specific_linear_rate_limits(trading_pair: str) -> List[RateLimit]: + pair_specific_linear_private_bucket_100_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.LINEAR_PRIVATE_BUCKET_100_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_linear_private_bucket_600_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.LINEAR_PRIVATE_BUCKET_600_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_linear_private_bucket_75_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.LINEAR_PRIVATE_BUCKET_75_LIMIT_ID, trading_pair=trading_pair + ) + pair_specific_linear_private_bucket_120_a_limit_id = get_pair_specific_limit_id( + base_limit_id=CONSTANTS.LINEAR_PRIVATE_BUCKET_120_A_LIMIT_ID, trading_pair=trading_pair + ) + + rate_limits = [ + RateLimit(limit_id=pair_specific_linear_private_bucket_100_limit_id, limit=100, time_interval=60), + RateLimit(limit_id=pair_specific_linear_private_bucket_600_limit_id, limit=600, time_interval=60), + RateLimit(limit_id=pair_specific_linear_private_bucket_75_limit_id, limit=75, time_interval=60), + RateLimit(limit_id=pair_specific_linear_private_bucket_120_a_limit_id, limit=120, time_interval=60), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.SET_LEVERAGE_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=75, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_75_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL[CONSTANTS.LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL[CONSTANTS.LINEAR_MARKET], + trading_pair=trading_pair, + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.GET_POSITIONS_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=100, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_100_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=100, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.POST_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_100_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=600, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_600_limit_id)], + ), + RateLimit( + limit_id=get_pair_specific_limit_id( + base_limit_id=CONSTANTS.USER_TRADE_RECORDS_PATH_URL[CONSTANTS.LINEAR_MARKET], trading_pair=trading_pair + ), + limit=120, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.GET_LIMIT_ID), + LinkedLimitWeightPair(pair_specific_linear_private_bucket_120_a_limit_id)], + ), + ] + + return rate_limits diff --git a/hummingbot/connector/derivative/bybit_perpetual/dummy.pxd b/hummingbot/connector/derivative/bybit_perpetual/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/bybit_perpetual/dummy.pyx b/hummingbot/connector/derivative/bybit_perpetual/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/bybit_perpetual/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/dydx_perpetual/__init__.py b/hummingbot/connector/derivative/dydx_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..5be27f5 --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_api_order_book_data_source.py @@ -0,0 +1,286 @@ +import asyncio +import sys +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import dateutil.parser as dp + +from hummingbot.connector.derivative.dydx_perpetual import ( + dydx_perpetual_constants as CONSTANTS, + dydx_perpetual_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book import OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_derivative import DydxPerpetualDerivative + + +class DydxPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + FULL_ORDER_BOOK_RESET_DELTA_SECONDS = sys.maxsize + + def __init__( + self, + trading_pairs: List[str], + connector: "DydxPerpetualDerivative", + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._nonce_provider = NonceCreator.for_microseconds() + + def _time(self): + return time.time() + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + funding_info_response = await self._request_complete_funding_info(trading_pair) + market_info: Dict[str, Any] = funding_info_response["markets"][trading_pair] + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(str(market_info["indexPrice"])), + mark_price=Decimal(str(market_info["oraclePrice"])), + next_funding_utc_timestamp=int(dp.parse(market_info["nextFundingAt"]).timestamp()), + rate=Decimal(str(market_info["nextFundingRate"])), + ) + return funding_info + + async def _subscribe_channels(self, ws: WSAssistant): + try: + for trading_pair in self._trading_pairs: + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest( + payload={ + "type": CONSTANTS.WS_TYPE_SUBSCRIBE, + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "id": trading_pair, + }, + is_auth_required=False, + ) + subscribe_trades_request: WSJSONRequest = WSJSONRequest( + payload={ + "type": CONSTANTS.WS_TYPE_SUBSCRIBE, + "channel": CONSTANTS.WS_CHANNEL_TRADES, + "id": trading_pair, + }, + is_auth_required=False, + ) + subscribe_markets_request: WSJSONRequest = WSJSONRequest( + payload={ + "type": CONSTANTS.WS_TYPE_SUBSCRIBE, + "channel": CONSTANTS.WS_CHANNEL_MARKETS, + "id": trading_pair, + }, + is_auth_required=False, + ) + await ws.send(subscribe_orderbook_request) + await ws.send(subscribe_trades_request) + await ws.send(subscribe_markets_request) + self.logger().info("Subscribed to public orderbook and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "channel" in event_message: + event_channel = event_message["channel"] + event_type = event_message["type"] + if event_channel == CONSTANTS.WS_CHANNEL_TRADES: + channel = self._trade_messages_queue_key + elif event_channel == CONSTANTS.WS_CHANNEL_ORDERBOOK: + if event_type == CONSTANTS.WS_TYPE_SUBSCRIBED: + channel = self._snapshot_messages_queue_key + if event_type == CONSTANTS.WS_TYPE_CHANNEL_DATA: + channel = self._diff_messages_queue_key + elif event_channel == CONSTANTS.WS_CHANNEL_MARKETS: + channel = self._funding_info_messages_queue_key + return channel + + async def _make_order_book_message( + self, + raw_message: Dict[str, Any], + message_queue: asyncio.Queue, + bids: List[Tuple[float, float]], + asks: List[Tuple[float, float]], + message_type: OrderBookMessageType, + ): + symbol = raw_message["id"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + + timestamp_s = self._time() + update_id = self._nonce_provider.get_tracking_nonce(timestamp=timestamp_s) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + message = OrderBookMessage( + message_type=message_type, + content=order_book_message_content, + timestamp=timestamp_s, + ) + message_queue.put_nowait(message) + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if raw_message["type"] in ["subscribed", "channel_data"]: + bids, asks = self._get_bids_and_asks_from_snapshot(raw_message["contents"]) + await self._make_order_book_message( + raw_message=raw_message, + message_queue=message_queue, + bids=bids, + asks=asks, + message_type=OrderBookMessageType.SNAPSHOT, + ) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if raw_message["type"] in ["subscribed", "channel_data"]: + bids, asks = self._get_bids_and_asks_from_diff(raw_message["contents"]) + await self._make_order_book_message( + raw_message=raw_message, + message_queue=message_queue, + bids=bids, + asks=asks, + message_type=OrderBookMessageType.DIFF, + ) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if raw_message["type"] == "channel_data": + symbol = raw_message["id"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + + trade_updates = raw_message["contents"]["trades"] + + for trade_data in trade_updates: + ts_ms = dp.parse(trade_data["createdAt"]).timestamp() * 1e3 + trade_type = float(TradeType.BUY.value) if trade_data["side"] == "BUY" else float(TradeType.SELL.value) + message_content = { + "trade_id": ts_ms, + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": trade_data["size"], + "price": trade_data["price"], + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=ts_ms * 1e-3, + ) + message_queue.put_nowait(trade_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if raw_message["type"] == "channel_data": + for trading_pair in raw_message["contents"].keys(): + if trading_pair in self._trading_pairs: + market_info = raw_message["contents"][trading_pair] + + if any( + info in ["indexPrice", "oraclePrice", "nextFundingRate", "nextFundingAt"] + for info in market_info.keys() + ): + + info_update = FundingInfoUpdate(trading_pair) + + if "indexPrice" in market_info.keys(): + info_update.index_price = Decimal(market_info["indexPrice"]) + if "oraclePrice" in market_info.keys(): + info_update.mark_price = Decimal(market_info["oraclePrice"]) + if "nextFundingRate" in market_info.keys(): + info_update.rate = Decimal(market_info["nextFundingRate"]) + if "nextFundingAt" in market_info.keys(): + info_update.next_funding_utc_timestamp = dp.parse(market_info["nextFundingAt"]).timestamp() + + message_queue.put_nowait(info_update) + + async def _request_complete_funding_info(self, trading_pair: str) -> Dict[str, Any]: + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint = CONSTANTS.PATH_MARKETS + url = web_utils.public_rest_url(path_url=endpoint) + data = await rest_assistant.execute_request( + url=url, + throttler_limit_id=endpoint, + params=params, + method=RESTMethod.GET, + ) + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response = await self._request_order_book_snapshot(trading_pair) + + timestamp = self._time() + update_id = self._nonce_provider.get_tracking_nonce(timestamp=timestamp) + + bids, asks = self._get_bids_and_asks_from_snapshot(snapshot_response) + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=order_book_message_content, + timestamp=timestamp, + ) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint = CONSTANTS.PATH_SNAPSHOT + url = web_utils.public_rest_url(path_url=endpoint + "/" + trading_pair) + data = await rest_assistant.execute_request( + url=url, + throttler_limit_id=endpoint, + method=RESTMethod.GET, + ) + + return data + + @staticmethod + def _get_bids_and_asks_from_snapshot( + snapshot: Dict[str, List[Dict[str, Union[str, int, float]]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + + bids = [(Decimal(bid["price"]), Decimal(bid["size"])) for bid in snapshot["bids"]] + asks = [(Decimal(ask["price"]), Decimal(ask["size"])) for ask in snapshot["asks"]] + + return bids, asks + + @staticmethod + def _get_bids_and_asks_from_diff( + diff: Dict[str, List[Dict[str, Union[str, int, float]]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + + bids = [(Decimal(bid[0]), Decimal(bid[1])) for bid in diff["bids"]] + asks = [(Decimal(ask[0]), Decimal(ask[1])) for ask in diff["asks"]] + + return bids, asks + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.DYDX_WS_URL, ping_timeout=CONSTANTS.HEARTBEAT_INTERVAL) + return ws + + async def _request_order_book_snapshots(self, output: asyncio.Queue): + pass # unused diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py new file mode 100644 index 0000000..ffcf7cd --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_auth.py @@ -0,0 +1,129 @@ +import json + +from dydx3 import Client +from dydx3.helpers.db import get_account_id as dydx3_get_acount_id +from dydx3.helpers.request_helpers import generate_now_iso, generate_query_path, remove_nones +from dydx3.starkex.order import SignableOrder + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class DydxPerpetualAuth(AuthBase): + def __init__( + self, + dydx_perpetual_api_key: str, + dydx_perpetual_api_secret: str, + dydx_perpetual_passphrase: str, + dydx_perpetual_ethereum_address: str, + dydx_perpetual_stark_private_key: str, + ): + self._dydx_perpetual_api_key = dydx_perpetual_api_key + self._dydx_perpetual_api_secret = dydx_perpetual_api_secret + self._dydx_perpetual_passphrase = dydx_perpetual_passphrase + self._dydx_perpetual_ethereum_address = dydx_perpetual_ethereum_address + self._dydx_perpetual_stark_private_key = dydx_perpetual_stark_private_key + self._dydx_stark_private_key = ( + None if dydx_perpetual_stark_private_key == "" else dydx_perpetual_stark_private_key + ) + + self._dydx_client = None + + @property + def dydx_client(self): + if self._dydx_client is None: + api_credentials = { + "key": self._dydx_perpetual_api_key, + "secret": self._dydx_perpetual_api_secret, + "passphrase": self._dydx_perpetual_passphrase, + } + + self._dydx_client = Client( + host=CONSTANTS.DYDX_REST_BASE_URL, + api_key_credentials=api_credentials, + stark_private_key=self._dydx_stark_private_key, + ) + return self._dydx_client + + def get_account_id(self): + return dydx3_get_acount_id(self._dydx_perpetual_ethereum_address) + + def _get_iso_timestamp(self): + return generate_now_iso() + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + ts = self._get_iso_timestamp() + + endpoint_url = request.url.replace(CONSTANTS.DYDX_REST_BASE_URL, "") + if request.params is not None: + request_path = generate_query_path(endpoint_url, request.params) + else: + request_path = endpoint_url + + data = request.data if request.data is not None else "{}" + + signature = self.dydx_client.private.sign( + request_path=request_path, + method=str(request.method), + iso_timestamp=ts, + data=remove_nones(json.loads(data)), + ) + + headers = { + "DYDX-SIGNATURE": signature, + "DYDX-API-KEY": self._dydx_perpetual_api_key, + "DYDX-TIMESTAMP": ts, + "DYDX-PASSPHRASE": self._dydx_perpetual_passphrase, + } + + if request.headers is not None: + headers.update(request.headers) + + request.headers = headers + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + ts = self._get_iso_timestamp() + + channel = request.payload["channel"] + request_path = CONSTANTS.WS_CHANNEL_TO_PATH[channel] + + signature = self.dydx_client.private.sign( + request_path=request_path, + method="GET", + iso_timestamp=ts, + data={}, + ) + + request.payload["apiKey"] = self._dydx_perpetual_api_key + request.payload["passphrase"] = self._dydx_perpetual_passphrase + request.payload["timestamp"] = ts + request.payload["signature"] = signature + + return request + + def get_order_signature( + self, + position_id: str, + client_id: str, + market: str, + side: str, + size: str, + price: str, + limit_fee: str, + expiration_epoch_seconds: int, + ) -> str: + order_to_sign = SignableOrder( + network_id=self.dydx_client.network_id, + position_id=position_id, + client_id=client_id, + market=market, + side=side, + human_size=size, + human_price=price, + limit_fee=limit_fee, + expiration_epoch_seconds=expiration_epoch_seconds, + ) + order_signature = order_to_sign.sign(self._dydx_perpetual_stark_private_key) + return order_signature diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_constants.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_constants.py new file mode 100644 index 0000000..6a18e12 --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_constants.py @@ -0,0 +1,143 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import OrderState + +# A single source of truth for constant variables related to the exchange + +EXCHANGE_NAME = "dydx_perpetual" +DEFAULT_DOMAIN = "com" + +API_VERSION = "v3" + +HBOT_BROKER_ID = "Hummingbot" +MAX_ID_LEN = 40 +HEARTBEAT_INTERVAL = 30.0 +ORDER_EXPIRATION = 2419200 # 28 days +LIMIT_FEE = 0.015 + +# API Base URLs +DYDX_REST_BASE_URL = "https://api.dydx.exchange" +DYDX_REST_URL = "{}/{}".format(DYDX_REST_BASE_URL, API_VERSION) +DYDX_WS_URL = "wss://api.dydx.exchange/{}/ws".format(API_VERSION) + +# Public REST Endpoints + +PATH_MARKETS = "/markets" +PATH_TICKER = "/stats" +PATH_SNAPSHOT = "/orderbook" +PATH_TIME = "/time" +PATH_ORDERS = "/orders" +PATH_ACTIVE_ORDERS = "/active-orders" +PATH_FILLS = "/fills" + +PATH_ACCOUNTS = "/accounts" +PATH_CONFIG = "/config" +PATH_FUNDING = "/funding" + + +# WS Endpoints +WS_PATH_ACCOUNTS = "/ws/accounts" + +# WS Channels +WS_CHANNEL_TRADES = "v3_trades" +WS_CHANNEL_ORDERBOOK = "v3_orderbook" +WS_CHANNEL_MARKETS = "v3_markets" +WS_CHANNEL_ACCOUNTS = "v3_accounts" + +WS_TYPE_SUBSCRIBE = "subscribe" +WS_TYPE_SUBSCRIBED = "subscribed" +WS_TYPE_CHANNEL_DATA = "channel_data" + + +TIF_GOOD_TIL_TIME = "GTT" +TIF_FILL_OR_KILL = "FOK" +TIF_IMMEDIATE_OR_CANCEL = "IOC" +FEES_KEY = "*" +FEE_MAKER_KEY = "maker" +FEE_TAKER_KEY = "taker" + +ORDER_TYPE_MAP = { + OrderType.LIMIT: "LIMIT", + OrderType.LIMIT_MAKER: "LIMIT", + OrderType.MARKET: "MARKET", +} + +ORDER_STATE = { + "PENDING": OrderState.OPEN, + "OPEN": OrderState.OPEN, + "FILLED": OrderState.FILLED, + "CANCELED": OrderState.CANCELED, +} + +WS_CHANNEL_TO_PATH = {WS_CHANNEL_ACCOUNTS: WS_PATH_ACCOUNTS} + + +ERR_MSG_NO_ORDER_FOUND = "No order found with id" +ERR_MSG_NO_ORDER_FOR_MARKET = "No order for market" + +LAST_FEE_PAYMENTS_MAX = 1 +LAST_FILLS_MAX = 100 + + +ONE_SECOND = 1 + +LIMIT_ID_GET = "LIMIT_ID_GET" +LIMIT_ID_ORDER_CANCEL = "LIMIT_ID_ORDER_CANCEL" +LIMIT_ID_ORDERS_CANCEL = "LIMIT_ID_ORDERS_CANCEL" +LIMIT_ID_ORDER_PLACE = "LIMIT_ID_ORDER_PLACE" + +MAX_REQUESTS_GET = 175 + +RATE_LIMITS = [ + # Pools + RateLimit(limit_id=LIMIT_ID_GET, limit=MAX_REQUESTS_GET, time_interval=ONE_SECOND * 10), + # Weighted limits + RateLimit( + limit_id=PATH_CONFIG, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_FILLS, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_ORDERS, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_FUNDING, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_ACCOUNTS, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_MARKETS, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_TIME, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), + RateLimit( + limit_id=PATH_SNAPSHOT, + limit=MAX_REQUESTS_GET, + time_interval=ONE_SECOND * 10, + linked_limits=[LinkedLimitWeightPair(LIMIT_ID_GET, 1)], + ), +] diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py new file mode 100644 index 0000000..882e39d --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_derivative.py @@ -0,0 +1,898 @@ +import asyncio +import time +from copy import deepcopy +from decimal import Decimal +from math import ceil +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict +from dateutil.parser import parse as dateparse +from dydx3.helpers.request_helpers import epoch_seconds_to_iso, generate_now_iso, iso_to_epoch_seconds + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.derivative.dydx_perpetual import dydx_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source import ( + DydxPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth import DydxPerpetualAuth +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_user_stream_data_source import ( + DydxPerpetualUserStreamDataSource, +) +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_utils import clamp +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.events import AccountEvent, PositionModeChangeEvent +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class DydxPerpetualDerivative(PerpetualDerivativePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + dydx_perpetual_api_key: str, + dydx_perpetual_api_secret: str, + dydx_perpetual_passphrase: str, + dydx_perpetual_ethereum_address: str, + dydx_perpetual_stark_private_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self._dydx_perpetual_api_key = dydx_perpetual_api_key + self._dydx_perpetual_api_secret = dydx_perpetual_api_secret + self._dydx_perpetual_passphrase = dydx_perpetual_passphrase + self._dydx_perpetual_ethereum_address = dydx_perpetual_ethereum_address + self._dydx_perpetual_stark_private_key = dydx_perpetual_stark_private_key + self._trading_pairs = trading_pairs + self._trading_required = trading_required + self._domain = domain + + self._order_notional_amounts = {} + self._current_place_order_requests = 0 + self._rate_limits_config = {} + self._margin_fractions = {} + self._position_id = None + + self._allocated_collateral = {} + self._allocated_collateral_sum = Decimal("0") + + super().__init__(client_config_map=client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> DydxPerpetualAuth: + return DydxPerpetualAuth( + dydx_perpetual_api_key=self._dydx_perpetual_api_key, + dydx_perpetual_api_secret=self._dydx_perpetual_api_secret, + dydx_perpetual_passphrase=self._dydx_perpetual_passphrase, + dydx_perpetual_ethereum_address=self._dydx_perpetual_ethereum_address, + dydx_perpetual_stark_private_key=self._dydx_perpetual_stark_private_key, + ) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + rate_limits = deepcopy(CONSTANTS.RATE_LIMITS) + + # Order cancelation + for trading_pair in self._trading_pairs: + rate_limits += [ + RateLimit( + limit_id=CONSTANTS.LIMIT_ID_ORDER_CANCEL + "_" + trading_pair, + limit=425, + time_interval=CONSTANTS.ONE_SECOND * 10, + ) + ] + + # Order placement + if "placeOrderRateLimiting" in self._rate_limits_config: + for trading_pair in self._trading_pairs: + rate_limits += [ + RateLimit( + limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair, + limit=self._rate_limits_config["placeOrderRateLimiting"]["maxPoints"], + time_interval=self._rate_limits_config["placeOrderRateLimiting"]["windowSec"], + ) + ] + + for amount, amount_id in self._order_notional_amounts.items(): + weight = clamp( + ceil(self._rate_limits_config["placeOrderRateLimiting"]["targetNotional"] / amount), + self._rate_limits_config["placeOrderRateLimiting"]["minLimitConsumption"], + self._rate_limits_config["placeOrderRateLimiting"]["maxOrderConsumption"], + ) + + for trading_pair in self._trading_pairs: + rate_limits += [ + RateLimit( + limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair + "_" + str(amount_id), + limit=self._rate_limits_config["placeOrderRateLimiting"]["maxPoints"], + time_interval=self._rate_limits_config["placeOrderRateLimiting"]["windowSec"], + linked_limits=[ + LinkedLimitWeightPair(CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair, weight) + ], + ) + ] + + return rate_limits + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.HBOT_BROKER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.PATH_MARKETS + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.PATH_MARKETS + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.PATH_TIME + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + return False + + def _is_request_result_an_error_related_to_time_synchronizer(self, request_result: Dict[str, Any]) -> bool: + if "errors" in request_result and "msg" in request_result["errors"]: + if "Timestamp must be within" in request_result["errors"]["msg"]: + return True + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + try: + body_params = { + "market": await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair), + "side": "BUY" if tracked_order.trade_type == TradeType.BUY else "SELL", + "id": tracked_order.exchange_order_id, + } + + resp = await self._api_delete( + path_url=CONSTANTS.PATH_ACTIVE_ORDERS, + params=body_params, + is_auth_required=True, + limit_id=CONSTANTS.LIMIT_ID_ORDER_CANCEL + "_" + tracked_order.trading_pair, + ) + if "cancelOrders" not in resp.keys(): + raise IOError(f"Error canceling order {order_id}.") + except IOError as e: + if any(error in str(e) for error in [CONSTANTS.ERR_MSG_NO_ORDER_FOR_MARKET]): + await self._order_tracker.process_order_not_found(order_id) + else: + raise + return True + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + if self._current_place_order_requests == 0: + # No requests are under way, the dictionary can be cleaned + self._order_notional_amounts = {} + + # Increment number of currently undergoing requests + self._current_place_order_requests += 1 + + if order_type.is_limit_type(): + time_in_force = CONSTANTS.TIF_GOOD_TIL_TIME + else: + time_in_force = CONSTANTS.TIF_IMMEDIATE_OR_CANCEL + if trade_type.name.lower() == 'buy': + # The price needs to be relatively high before the transaction, whether the test will be cancelled + price = Decimal("1.5") * self.get_price_for_volume( + trading_pair, + True, + amount + ).result_price + else: + price = Decimal("0.75") * self.get_price_for_volume( + trading_pair, + False, + amount + ).result_price + price = self.quantize_order_price(trading_pair, price) + notional_amount = amount * price + if notional_amount not in self._order_notional_amounts.keys(): + self._order_notional_amounts[notional_amount] = len(self._order_notional_amounts.keys()) + # Set updated rate limits + self._throttler.set_rate_limits(self.rate_limits_rules) + size = str(amount) + price = str(price) + side = "BUY" if trade_type == TradeType.BUY else "SELL" + expiration = int(time.time()) + CONSTANTS.ORDER_EXPIRATION + limit_fee = str(CONSTANTS.LIMIT_FEE) + reduce_only = False + + post_only = order_type is OrderType.LIMIT_MAKER + market = await self.exchange_symbol_associated_to_pair(trading_pair) + + signature = self._auth.get_order_signature( + position_id=self._position_id, + client_id=order_id, + market=market, + side=side, + size=size, + price=price, + limit_fee=limit_fee, + expiration_epoch_seconds=expiration, + ) + + data = { + "clientId": order_id, + "market": market, + "side": side, + "price": str(price), + "size": str(amount), + "type": CONSTANTS.ORDER_TYPE_MAP[order_type], + "limitFee": limit_fee, + "expiration": epoch_seconds_to_iso(expiration), + "timeInForce": time_in_force, + "reduceOnly": reduce_only, + "postOnly": post_only, + "signature": signature, + } + + try: + resp = await self._api_post( + path_url=CONSTANTS.PATH_ORDERS, + data=data, + is_auth_required=True, + limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE + + "_" + + trading_pair + + "_" + + str(self._order_notional_amounts[notional_amount]), + ) + except Exception: + self._current_place_order_requests -= 1 + raise + + # Decrement number of currently undergoing requests + self._current_place_order_requests -= 1 + + if "order" not in resp: + raise IOError(f"Error submitting order {order_id}.") + + return str(resp["order"]["id"]), iso_to_epoch_seconds(resp["order"]["createdAt"]) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or False + if CONSTANTS.FEES_KEY not in self._trading_fees.keys(): + fee = build_perpetual_trade_fee( + self.name, + is_maker, + position_action=position_action, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + else: + fee_data = self._trading_fees[CONSTANTS.FEES_KEY] + if is_maker: + fee_value = Decimal(fee_data[CONSTANTS.FEE_MAKER_KEY]) + else: + fee_value = Decimal(fee_data[CONSTANTS.FEE_TAKER_KEY]) + fee = AddedToCostTradeFee(percent=fee_value) + return fee + + async def start_network(self): + await super().start_network() + await self._update_rate_limits() + await self._update_position_id() + + async def _update_rate_limits(self): + # Initialize rate limits for statically allocatedrequests + self._throttler.set_rate_limits(self.rate_limits_rules) + + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_CONFIG, + is_auth_required=False, + ) + + self._rate_limits_config["cancelOrderRateLimiting"] = response["cancelOrderRateLimiting"] + self._rate_limits_config["placeOrderRateLimiting"] = response["placeOrderRateLimiting"] + + # Update rate limits with the obtained information + self._throttler.set_rate_limits(self.rate_limits_rules) + + async def _update_position_id(self): + if self._position_id is None: + account = await self._get_account() + self._position_id = account["positionId"] + + async def _update_trading_fees(self): + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_CONFIG, + is_auth_required=False, + ) + + self._trading_fees[CONSTANTS.FEES_KEY] = { + CONSTANTS.FEE_MAKER_KEY: response["defaultMakerFee"], + CONSTANTS.FEE_TAKER_KEY: response["defaultTakerFee"], + } + + async def _user_stream_event_listener(self): + async for event_message in self._iter_user_event_queue(): + try: + event: Dict[str, Any] = event_message + data: Dict[str, Any] = event["contents"] + if "account" in data.keys() and len(data["account"]) > 0: + quote = "USD" + self._account_balances[quote] = Decimal(data["account"]["equity"]) + # freeCollateral is sent only on start + self._account_available_balances[quote] = ( + Decimal(data["account"]["quoteBalance"]) - self._allocated_collateral_sum + ) + if "openPositions" in data["account"]: + await self._process_open_positions(data["account"]["openPositions"]) + + if "accounts" in data.keys() and len(data["accounts"]) > 0: + for account in data["accounts"]: + quote = "USD" + # freeCollateral is sent only on start + self._account_available_balances[quote] = ( + Decimal(account["quoteBalance"]) - self._allocated_collateral_sum + ) + + if "orders" in data.keys() and len(data["orders"]) > 0: + for order in data["orders"]: + client_order_id: str = order["clientId"] + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if tracked_order is not None: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(order["market"]) + state = CONSTANTS.ORDER_STATE[order["status"]] + new_order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + ) + self._order_tracker.process_order_update(new_order_update) + # Processing all orders of the account, not just the client's + if order["status"] in ["OPEN"]: + initial_margin_requirement = ( + Decimal(order["price"]) + * Decimal(order["size"]) + * self._margin_fractions[trading_pair]["initial"] + ) + initial_margin_requirement = abs(initial_margin_requirement) + self._allocated_collateral[order["id"]] = initial_margin_requirement + self._allocated_collateral_sum += initial_margin_requirement + self._account_available_balances[quote] -= initial_margin_requirement + if order["status"] in ["FILLED", "CANCELED"]: + if order["id"] in self._allocated_collateral: + # Only deduct orders that were previously in the OPEN state + # Some orders are filled instantly and reach only the PENDING state + self._allocated_collateral_sum -= self._allocated_collateral[order["id"]] + self._account_available_balances[quote] += self._allocated_collateral[order["id"]] + del self._allocated_collateral[order["id"]] + + if "fills" in data.keys() and len(data["fills"]) > 0: + trade_updates = self._process_ws_fills(data["fills"]) + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update) + + if "positions" in data.keys() and len(data["positions"]) > 0: + # this is hit when a position is closed + positions = data["positions"] + for position in positions: + trading_pair = position["market"] + if trading_pair not in self._trading_pairs: + continue + position_side = PositionSide[position["side"]] + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + amount = Decimal(position.get("size")) + + if amount != s_decimal_0: + position = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=Decimal(position.get("unrealizedPnl", 0)), + entry_price=Decimal(position.get("entryPrice")), + amount=amount, + leverage=self.get_leverage(trading_pair), + ) + self._perpetual_trading.set_position(pos_key, position) + else: + if self._perpetual_trading.get_position(pos_key): + self._perpetual_trading.remove_position(pos_key) + + if "fundingPayments" in data: + if event["type"] != CONSTANTS.WS_TYPE_SUBSCRIBED: # Only subsequent funding payments + funding_payments = await self._process_funding_payments(data["fundingPayments"]) + for trading_pair in funding_payments: + timestamp = funding_payments[trading_pair]["timestamp"] + funding_rate = funding_payments[trading_pair]["funding_rate"] + payment = funding_payments[trading_pair]["payment"] + self._emit_funding_payment_event(trading_pair, timestamp, funding_rate, payment, False) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + trading_rules = [] + markets_info = exchange_info_dict["markets"] + for market_name in markets_info: + market = markets_info[market_name] + try: + collateral_token = market["quoteAsset"] + trading_rules += [ + TradingRule( + trading_pair=market_name, + min_order_size=Decimal(market["minOrderSize"]), + min_price_increment=Decimal(market["tickSize"]), + min_base_amount_increment=Decimal(market["stepSize"]), + min_notional_size=Decimal(market["minOrderSize"]) * Decimal(market["tickSize"]), + supports_limit_orders=True, + supports_market_orders=True, + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + ] + self._margin_fractions[market_name] = { + "initial": Decimal(market["initialMarginFraction"]), + "maintenance": Decimal(market["maintenanceMarginFraction"]), + } + except Exception: + self.logger().exception("Error updating trading rules") + return trading_rules + + async def _update_balances(self): + account = await self._get_account() + quote = "USD" + self._account_balances[quote] = Decimal(account["equity"]) + # freeCollateral is sent only on start + self._account_available_balances[quote] = Decimal(account["quoteBalance"]) - self._allocated_collateral_sum + + def _process_ws_fills(self, fills_data: List) -> List[TradeUpdate]: + trade_updates = [] + all_fillable_orders = self._order_tracker.all_fillable_orders + for fill_data in fills_data: + client_order_id: str = fill_data["orderClientId"] + order = all_fillable_orders.get(client_order_id) + trade_update = self._process_order_fills(fill_data=fill_data, order=order) + if trade_update is not None: + trade_updates.append(trade_update) + return trade_updates + + async def _process_funding_payments(self, funding_payments: List): + data = {} + + for trading_pair in self._trading_pairs: + data[trading_pair] = {} + data[trading_pair]["timestamp"] = 0 + data[trading_pair]["funding_rate"] = Decimal("-1") + data[trading_pair]["payment"] = Decimal("-1") + if trading_pair in self._last_funding_fee_payment_ts: + data[trading_pair]["timestamp"] = self._last_funding_fee_payment_ts[trading_pair] + + prev_timestamps = {} + + for funding_payment in funding_payments: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(funding_payment["market"]) + + if trading_pair not in prev_timestamps.keys(): + prev_timestamps[trading_pair] = None + if ( + prev_timestamps[trading_pair] is not None + and dateparse(funding_payment["effectiveAt"]).timestamp() <= prev_timestamps[trading_pair] + ): + continue + timestamp = dateparse(funding_payment["effectiveAt"]).timestamp() + funding_rate: Decimal = Decimal(funding_payment["rate"]) + payment: Decimal = Decimal(funding_payment["payment"]) + + if trading_pair not in data: + data[trading_pair] = {} + + data[trading_pair]["timestamp"] = timestamp + data[trading_pair]["funding_rate"] = funding_rate + data[trading_pair]["payment"] = payment + + prev_timestamps[trading_pair] = timestamp + + return data + + async def _process_open_positions(self, open_positions: Dict): + for market, position in open_positions.items(): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=market) + position_side = PositionSide[position["side"]] + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + amount = Decimal(position.get("size")) + if amount != s_decimal_0: + entry_price = Decimal(position.get("entryPrice")) + unrealized_pnl = Decimal(position.get("unrealizedPnl")) + position = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=self.get_leverage(trading_pair), + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + try: + all_fills_response = await self._request_order_fills(order=order) + fills_data = all_fills_response["fills"] + trade_updates_tmp = self._process_rest_fills(fills_data) + trade_updates += trade_updates_tmp + + except IOError as ex: + if not self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + raise + return trade_updates + + def _process_rest_fills(self, fills_data: List) -> List[TradeUpdate]: + trade_updates = [] + all_fillable_orders_by_exchange_order_id = { + order.exchange_order_id: order for order in self._order_tracker.all_fillable_orders.values() + } + for fill_data in fills_data: + exchange_order_id: str = fill_data["orderId"] + order = all_fillable_orders_by_exchange_order_id.get(exchange_order_id) + trade_update = self._process_order_fills(fill_data=fill_data, order=order) + if trade_update is not None: + trade_updates.append(trade_update) + return trade_updates + + def _process_order_fills(self, fill_data: Dict, order: InFlightOrder) -> Optional[TradeUpdate]: + trade_update = None + if order is not None: + fee_asset = order.quote_asset + fee_amount = Decimal(fill_data["fee"]) + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + position_side = fill_data["side"] + position_action = ( + PositionAction.OPEN + if ( + order.trade_type is TradeType.BUY + and position_side == "BUY" + or order.trade_type is TradeType.SELL + and position_side == "SELL" + ) + else PositionAction.CLOSE + ) + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + + trade_update = TradeUpdate( + trade_id=fill_data["createdAt"], + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fill_timestamp=fill_data["createdAt"], + fill_price=Decimal(fill_data["price"]), + fill_base_amount=Decimal(fill_data["size"]), + fill_quote_amount=Decimal(fill_data["price"]) * Decimal(fill_data["size"]), + fee=fee, + ) + return trade_update + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + + body_params = { + "orderId": order.exchange_order_id, + "limit": CONSTANTS.LAST_FILLS_MAX, + } + + res = await self._api_get( + path_url=CONSTANTS.PATH_FILLS, + params=body_params, + is_auth_required=True, + ) + return res + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + if tracked_order.exchange_order_id is None: + # Order placement failed previously + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + else: + try: + order_status_data = await self._request_order_status_data(tracked_order=tracked_order) + order = order_status_data["order"] + client_order_id = str(order["clientId"]) + + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=CONSTANTS.ORDER_STATE[order["status"]], + client_order_id=client_order_id, + exchange_order_id=order["id"], + ) + except IOError as ex: + if self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + ) + else: + raise + return order_update + + async def _request_order_status_data(self, tracked_order: InFlightOrder) -> Dict: + try: + resp = await self._api_get( + path_url=CONSTANTS.PATH_ORDERS + "/" + str(tracked_order.exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.PATH_ORDERS, + ) + except IOError as e: + if any(error in str(e) for error in [CONSTANTS.ERR_MSG_NO_ORDER_FOUND]): + await self._order_tracker.process_order_not_found(tracked_order.client_order_id) + raise + return resp + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth, + ) + + def _create_order_book_data_source(self) -> DydxPerpetualAPIOrderBookDataSource: + return DydxPerpetualAPIOrderBookDataSource( + self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return DydxPerpetualUserStreamDataSource(dydx_auth=self._auth, api_factory=self._web_assistants_factory) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + markets = exchange_info["markets"] + + mapping = bidict() + for key, val in markets.items(): + if val["status"] == "ONLINE": + exchange_symbol = val["market"] + base = val["baseAsset"] + quote = val["quoteAsset"] + trading_pair = combine_to_hb_trading_pair(base, quote) + if trading_pair in mapping.inverse: + self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote) + else: + mapping[exchange_symbol] = trading_pair + + self._set_trading_pair_symbol_map(mapping) + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error( + f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}" + ) + mapping.pop(current_exchange_symbol) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = {"market": exchange_symbol} + + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_MARKETS, params=params, is_auth_required=False + ) + + price = float(response["markets"][exchange_symbol]["indexPrice"]) + return price + + def supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + async def _update_positions(self): + account = await self._get_account() + await self._process_open_positions(account["openPositions"]) + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + """ + :return: A tuple of boolean (true if success) and error message if the exchange returns one on failure. + """ + if mode != PositionMode.ONEWAY: + self.trigger_event( + AccountEvent.PositionModeChangeFailed, + PositionModeChangeEvent( + self.current_timestamp, trading_pair, mode, "dYdX only supports the ONEWAY position mode." + ), + ) + self.logger().debug( + f"dYdX encountered a problem switching position mode to " + f"{mode} for {trading_pair}" + f" (dYdX only supports the ONEWAY position mode)" + ) + else: + self._position_mode = PositionMode.ONEWAY + super().set_position_mode(PositionMode.ONEWAY) + self.trigger_event( + AccountEvent.PositionModeChangeSucceeded, + PositionModeChangeEvent(self.current_timestamp, trading_pair, mode), + ) + self.logger().debug(f"dYdX switching position mode to " f"{mode} for {trading_pair} succeeded.") + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + success = True + msg = "" + + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_MARKETS, + is_auth_required=False, + ) + + if "markets" not in response: + msg = "Failed to obtain markets information." + success = False + + if success: + markets_info = response["markets"] + + self._margin_fractions[trading_pair] = { + "initial": Decimal(markets_info[trading_pair]["initialMarginFraction"]), + "maintenance": Decimal(markets_info[trading_pair]["maintenanceMarginFraction"]), + } + + max_leverage = int(Decimal("1") / self._margin_fractions[trading_pair]["initial"]) + if leverage > max_leverage: + self._perpetual_trading.set_leverage(trading_pair=trading_pair, leverage=max_leverage) + self.logger().warning(f"Leverage has been reduced to {max_leverage}") + else: + self._perpetual_trading.set_leverage(trading_pair=trading_pair, leverage=leverage) + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + """ + Returns a tuple of the latest funding payment timestamp, funding rate, and payment amount. + If no payment exists, return (0, -1, -1) + """ + market = await self.exchange_symbol_associated_to_pair(trading_pair) + + body_params = { + "market": market, + "limit": CONSTANTS.LAST_FEE_PAYMENTS_MAX, + "effectiveBeforeOrAt": generate_now_iso(), + } + + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_FUNDING, + params=body_params, + is_auth_required=True, + ) + + funding_payments = await self._process_funding_payments(response["fundingPayments"]) + + timestamp = funding_payments[trading_pair]["timestamp"] + funding_rate = funding_payments[trading_pair]["funding_rate"] + payment = funding_payments[trading_pair]["payment"] + + return timestamp, funding_rate, payment + + async def _get_account(self): + account_id = self._auth.get_account_id() + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.PATH_ACCOUNTS + "/" + str(account_id), + limit_id=CONSTANTS.PATH_ACCOUNTS, + is_auth_required=True, + ) + + if "account" not in response: + raise IOError(f"Unable to get account info for account id {account_id}") + + return response["account"] diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..2bb48c4 --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_user_stream_data_source.py @@ -0,0 +1,58 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth import DydxPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class DydxPerpetualUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, dydx_auth: DydxPerpetualAuth, api_factory: Optional[WebAssistantsFactory]): + self._dydx_auth: DydxPerpetualAuth = dydx_auth + self._api_factory: WebAssistantsFactory = api_factory + self._ws_assistant: Optional[WSAssistant] = None + super().__init__() + + @property + def last_recv_time(self): + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return -1 + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + pass + + async def _connected_websocket_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self.logger().info(f"Connecting to {CONSTANTS.DYDX_WS_URL}") + self._ws_assistant = await self._api_factory.get_ws_assistant() + await self._ws_assistant.connect(ws_url=CONSTANTS.DYDX_WS_URL, ping_timeout=CONSTANTS.HEARTBEAT_INTERVAL) + + auth_params = { + "type": "subscribe", + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "accountNumber": "0", + } + + auth_request: WSJSONRequest = WSJSONRequest(payload=auth_params, is_auth_required=True) + await self._ws_assistant.send(auth_request) + self.logger().info("Authenticated user stream...") + return self._ws_assistant + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if event_message.get("type", "") in [CONSTANTS.WS_TYPE_SUBSCRIBED, CONSTANTS.WS_TYPE_CHANNEL_DATA]: + await super()._process_event_message(event_message=event_message, queue=queue) diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py new file mode 100644 index 0000000..5ff9db0 --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_utils.py @@ -0,0 +1,74 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USD" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0005"), + taker_percent_fee_decimal=Decimal("0.002"), +) + + +def clamp(value, minvalue, maxvalue): + return max(minvalue, min(value, maxvalue)) + + +class DydxPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="dydx_perpetual", client_data=None) + dydx_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your dydx Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + dydx_perpetual_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your dydx Perpetual API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + dydx_perpetual_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your dydx Perpetual API passphrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + dydx_perpetual_stark_private_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your stark private key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + dydx_perpetual_ethereum_address: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your ethereum wallet address", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "dydx_perpetual" + + +KEYS = DydxPerpetualConfigMap.construct() diff --git a/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_web_utils.py b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_web_utils.py new file mode 100644 index 0000000..0a0b67b --- /dev/null +++ b/hummingbot/connector/derivative/dydx_perpetual/dydx_perpetual_web_utils.py @@ -0,0 +1,66 @@ +from typing import Callable, Optional + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the dYdX domain to connect to ("exchange" or "us"). The default value is "exchange" + :return: the full URL to the endpoint + """ + return CONSTANTS.DYDX_REST_URL + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: the dYdX domain to connect to ("exchange" or "us"). The default value is "exchange" + :return: the full URL to the endpoint + """ + return CONSTANTS.DYDX_REST_URL + path_url + + +def build_api_factory( + throttler: AsyncThrottler, + time_synchronizer: TimeSynchronizer, + auth: AuthBase, + time_provider: Optional[Callable] = None, +) -> WebAssistantsFactory: + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ], + ) + return api_factory + + +async def get_current_server_time(throttler: AsyncThrottler, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> float: + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + url = public_rest_url(CONSTANTS.PATH_TIME) + limit_id = CONSTANTS.LIMIT_ID_GET + response = await rest_assistant.execute_request( + url=url, + throttler_limit_id=limit_id, + method=RESTMethod.GET, + ) + server_time = float(response["epoch"]) + + return server_time + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory diff --git a/hummingbot/connector/derivative/gate_io_perpetual/__init__.py b/hummingbot/connector/derivative/gate_io_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/gate_io_perpetual/dummy.pxd b/hummingbot/connector/derivative/gate_io_perpetual/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/gate_io_perpetual/dummy.pyx b/hummingbot/connector/derivative/gate_io_perpetual/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..5bfc472 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_api_order_book_data_source.py @@ -0,0 +1,224 @@ +import asyncio +import json +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import pandas as pd + +from hummingbot.connector.derivative.gate_io_perpetual import ( + gate_io_perpetual_constants as CONSTANTS, + gate_io_perpetual_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book import OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_derivative import GateIoPerpetualDerivative + + +class GateIoPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: 'GateIoPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._trading_pairs: List[str] = trading_pairs + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + funding_info_response = await self._request_complete_funding_info(trading_pair) + symbol_info: Dict[str, Any] = funding_info_response + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(str(symbol_info["index_price"])), + mark_price=Decimal(str(symbol_info["mark_price"])), + next_funding_utc_timestamp=int(symbol_info["funding_next_apply"]), + rate=Decimal(str(symbol_info["funding_rate_indicative"])), + ) + return funding_info + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = self._time() + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + { + "trading_pair": trading_pair, + "update_id": snapshot_response["id"], + "bids": [[i['p'], self._connector._format_size_to_amount(trading_pair, Decimal(str(i['s'])))] for i in + snapshot_response["bids"]], + "asks": [[i['p'], self._connector._format_size_to_amount(trading_pair, Decimal(str(i['s'])))] for i in + snapshot_response["asks"]], + }, + timestamp=snapshot_timestamp) + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "contract": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "with_id": json.dumps(True) + } + + rest_assistant = await self._api_factory.get_rest_assistant() + return await rest_assistant.execute_request( + url=web_utils.public_rest_url(endpoint=CONSTANTS.ORDER_BOOK_PATH_URL), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.ORDER_BOOK_PATH_URL, + ) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + for trade_data in raw_message["result"]: + trade_timestamp: int = trade_data["create_time"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=trade_data["contract"]) + message_content = { + "trading_pair": trading_pair, + "trade_type": (float(TradeType.SELL.value) + if trade_data["size"] < 0 + else float(TradeType.BUY.value)), + "trade_id": trade_data["id"], + "update_id": trade_timestamp, + "price": trade_data["price"], + "amount": abs(self._connector._format_size_to_amount(trading_pair, (Decimal(str(trade_data["size"]))))) + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=trade_timestamp) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_data: [str, Any] = raw_message["result"] + timestamp: float = (diff_data["t"]) * 1e-3 + update_id: int = diff_data["u"] + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=diff_data["s"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "first_update_id": diff_data["U"], + "bids": [[i['p'], self._connector._format_size_to_amount(trading_pair, Decimal(str(i['s'])))] for i in + diff_data["b"]], + "asks": [[i['p'], self._connector._format_size_to_amount(trading_pair, Decimal(str(i['s'])))] for i in + diff_data["a"]], + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + order_book_message_content, + timestamp) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + trades_payload = { + "time": int(self._time()), + "channel": CONSTANTS.TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": [symbol] + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + order_book_payload = { + "time": int(self._time()), + "channel": CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME, + "event": "subscribe", + "payload": [symbol, "100ms"] + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error occurred subscribing to order book data streams.") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if event_message.get("error") is not None: + err_msg = event_message.get("error", {}).get("message", event_message.get("error")) + raise IOError(f"Error event received from the server ({err_msg})") + elif event_message.get("event") == "update": + if event_message.get("channel") == CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME: + channel = self._diff_messages_queue_key + elif event_message.get("channel") == CONSTANTS.TRADES_ENDPOINT_NAME: + channel = self._trade_messages_queue_key + + return channel + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=CONSTANTS.PING_TIMEOUT) + return ws + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + event_type = raw_message["event"] + if event_type == "update": + symbol = raw_message['result'][0]["contract"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + entries = raw_message['result'] + for entry in entries: + info_update = FundingInfoUpdate(trading_pair) + if "index_price" in entry: + info_update.index_price = Decimal(str(entry["index_price"])) + if "mark_price" in entry: + info_update.mark_price = Decimal(str(entry["mark_price"])) + if "next_funding_time" in entry: + info_update.next_funding_utc_timestamp = int( + pd.Timestamp(str(entry["next_funding_time"]), tz="UTC").timestamp() + ) + if "funding_rate_indicative" in entry: + info_update.rate = ( + Decimal(str(entry["funding_rate_indicative"])) + ) + message_queue.put_nowait(info_update) + + async def _request_complete_funding_info(self, trading_pair: str): + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(endpoint=CONSTANTS.MARK_PRICE_URL.format(id=ex_trading_pair)), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.MARK_PRICE_URL, + ) + return data diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_auth.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_auth.py new file mode 100644 index 0000000..a85f854 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_auth.py @@ -0,0 +1,100 @@ +import hashlib +import hmac +import json +import time +from typing import Any, Dict +from urllib.parse import urlparse + +import six + +from hummingbot.connector.derivative.gate_io_perpetual import gate_io_perpetual_constants as CONSTANTS +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class GateIoPerpetualAuth(AuthBase): + """ + Auth Gate.io API + https://www.gate.io/docs/apiv4/en/#authentication + """ + def __init__(self, api_key: str, secret_key: str): + self.api_key = api_key + self.secret_key = secret_key + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self._get_auth_headers(request)) + request.headers = headers + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + request.payload["auth"] = self._get_auth_headers_ws(payload=request.payload) + return request + + def _get_auth_headers_ws(self, payload: Dict[str, Any] = None) -> Dict[str, Any]: + """ + Generates authn for Gate.io websockets + + :return: a dictionary with headers + """ + sig = self._sign_payload_ws(payload['channel'], payload['event'], payload['time']) + headers = { + "method": "api_key", + "KEY": f"{self.api_key}", + "SIGN": f"{sig}", + } + return headers + + def _get_auth_headers(self, request: RESTRequest) -> Dict[str, Any]: + """ + Generates authentication headers for Gate.io REST API + + :return: a dictionary with headers + """ + sign, ts = self._sign_payload(request) + headers = { + "X-Gate-Channel-Id": CONSTANTS.HBOT_BROKER_ID, + "KEY": f"{self.api_key}", + "Timestamp": f"{ts}", + "SIGN": f"{sign}", + "Content-Type": "application/json", + } + return headers + + def _sign_payload_ws(self, channel, event, time) -> str: + return self._sign(f"channel={channel}&event={event}&time={time}") + + def _sign_payload(self, r: RESTRequest) -> (str, int): + query_string = "" + body = r.data + + ts = self._get_timestamp() + m = hashlib.sha512() + path = urlparse(r.url).path + + if body is not None: + if not isinstance(r.data, six.string_types): + body = json.dumps(r.data) + m.update(body.encode('utf-8')) + body_hash = m.hexdigest() + + if r.params: + qs = [] + for k, v in r.params.items(): + qs.append(f"{k}={v}") + query_string = "&".join(qs) + + s = f'{r.method}\n{path}\n{query_string}\n{body_hash}\n{ts}' + return self._sign(s), ts + + def _sign(self, payload) -> str: + return hmac.new( + self.secret_key.encode('utf-8'), + payload.encode('utf-8'), + hashlib.sha512).hexdigest() + + @staticmethod + def _get_timestamp(): + return time.time() diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_constants.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_constants.py new file mode 100644 index 0000000..286bba0 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_constants.py @@ -0,0 +1,102 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +EXCHANGE_NAME = "gate_io_perpetual" +DEFAULT_DOMAIN = "" +HBOT_BROKER_ID = "hummingbot" +HBOT_ORDER_ID = "t-HBOT" +MAX_ID_LEN = 30 + +REST_URL = "https://api.gateio.ws/api/v4" +REST_URL_AUTH = "/api/v4" +WS_URL = "wss://fx-ws.gateio.ws/v4/ws/usdt" + +# Public API v4 Endpoints +EXCHANGE_INFO_URL = "futures/usdt/contracts" +TICKER_PATH_URL = "futures/usdt/tickers" +ORDER_BOOK_PATH_URL = "futures/usdt/order_book" +MY_TRADES_PATH_URL = "futures/usdt/my_trades" +MARK_PRICE_URL = "futures/usdt/contracts/{id}" +NETWORK_CHECK_PATH_URL = "futures/usdt/contracts/BTC_USDT" +FUNDING_RATE_TIME_PATH_URL = "futures/usdt/funding_rate" + +ORDER_CREATE_PATH_URL = "futures/usdt/orders" +ORDER_DELETE_PATH_URL = "futures/usdt/orders/{id}" +USER_BALANCES_PATH_URL = "futures/usdt/accounts" +POSITION_INFORMATION_URL = "futures/usdt/positions" +ORDER_STATUS_PATH_URL = "futures/usdt/orders/{id}" +USER_ORDERS_PATH_URL = "futures/usdt/orders" +SET_POSITION_MODE_URL = "futures/usdt/dual_mode" +ONEWAY_SET_LEVERAGE_PATH_URL = "futures/usdt/positions/{contract}/leverage" +HEDGE_SET_LEVERAGE_PATH_URL = "futures/usdt/dual_comp/positions/{contract}/leverage" + +TICKER_ENDPOINT_NAME = "futures.tickers" +TRADES_ENDPOINT_NAME = "futures.trades" +ORDER_SNAPSHOT_ENDPOINT_NAME = "futures.order_book" +ORDERS_UPDATE_ENDPOINT_NAME = "futures.order_book_update" +USER_TRADES_ENDPOINT_NAME = "futures.usertrades" +USER_ORDERS_ENDPOINT_NAME = "futures.orders" +USER_BALANCE_ENDPOINT_NAME = "futures.balances" +USER_POSITIONS_ENDPOINT_NAME = "futures.positions" +PONG_CHANNEL_NAME = "futures.pong" + +# Timeouts +MESSAGE_TIMEOUT = 30.0 +PING_TIMEOUT = 10.0 +API_CALL_TIMEOUT = 10.0 +API_MAX_RETRIES = 4 + +# Funding Settlement Time Span +FUNDING_SETTLEMENT_DURATION = (0, 30) # seconds before snapshot, seconds after snapshot + +# Intervals +# Only used when nothing is received from WS +SHORT_POLL_INTERVAL = 5.0 +# 45 seconds should be fine since we get trades, orders and balances via WS +LONG_POLL_INTERVAL = 45.0 +# One minute should be fine since we get trades, orders and balances via WS +UPDATE_ORDER_STATUS_INTERVAL = 60.0 +# 10 minute interval to update trading rules, these would likely never change whilst running. +INTERVAL_TRADING_RULES = 600 + +PUBLIC_URL_POINTS_LIMIT_ID = "PublicPoints" +PRIVATE_URL_POINTS_LIMIT_ID = "PrivatePoints" # includes place-orders +CANCEL_ORDERS_LIMITS_ID = "CancelOrders" +ORDER_DELETE_LIMIT_ID = "OrderDelete" +ORDER_STATUS_LIMIT_ID = "OrderStatus" +RATE_LIMITS = [ + RateLimit(limit_id=PUBLIC_URL_POINTS_LIMIT_ID, limit=300, time_interval=1), + RateLimit(limit_id=PRIVATE_URL_POINTS_LIMIT_ID, limit=400, time_interval=1), + RateLimit(limit_id=CANCEL_ORDERS_LIMITS_ID, limit=400, time_interval=1), + RateLimit(limit_id=NETWORK_CHECK_PATH_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=EXCHANGE_INFO_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_CREATE_PATH_URL, limit=100, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_DELETE_LIMIT_ID, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(CANCEL_ORDERS_LIMITS_ID)]), + RateLimit(limit_id=USER_BALANCES_PATH_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=SET_POSITION_MODE_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=POSITION_INFORMATION_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_STATUS_LIMIT_ID, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ONEWAY_SET_LEVERAGE_PATH_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=HEDGE_SET_LEVERAGE_PATH_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=USER_ORDERS_PATH_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=TICKER_PATH_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=MARK_PRICE_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=FUNDING_RATE_TIME_PATH_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_BOOK_PATH_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=400, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), +] diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_derivative.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_derivative.py new file mode 100644 index 0000000..33d7af3 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_derivative.py @@ -0,0 +1,815 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.derivative.gate_io_perpetual import ( + gate_io_perpetual_constants as CONSTANTS, + gate_io_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_api_order_book_data_source import ( + GateIoPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_auth import GateIoPerpetualAuth +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_user_stream_data_source import ( + GateIoPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GateIoPerpetualDerivative(PerpetualDerivativePyBase): + """ + GateIoPerpetualExchange connects with Gate.io Derivative and provides order book pricing, user account tracking and + trading functionality. + """ + DEFAULT_DOMAIN = "" + + # Using 120 seconds here as Gate.io websocket is quiet + TICK_INTERVAL_LIMIT = 120.0 + + web_utils = web_utils + + # ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + # ORDER_NOT_EXIST_CANCEL_COUNT = 2 + + def __init__(self, + client_config_map: "ClientConfigAdapter", + gate_io_perpetual_api_key: str, + gate_io_perpetual_secret_key: str, + gate_io_perpetual_user_id: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = DEFAULT_DOMAIN): + """ + :param gate_io_perpetual_api_key: The API key to connect to private Gate.io APIs. + :param gate_io_perpetual_secret_key: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + self._gate_io_perpetual_api_key = gate_io_perpetual_api_key + self._gate_io_perpetual_secret_key = gate_io_perpetual_secret_key + self._gate_io_perpetual_user_id = gate_io_perpetual_user_id + self._domain = domain + self._position_mode = None + self._trading_required = trading_required + self._trading_pairs = trading_pairs + + super().__init__(client_config_map) + + self._real_time_balance_update = False + + @property + def authenticator(self): + return GateIoPerpetualAuth( + api_key=self._gate_io_perpetual_api_key, + secret_key=self._gate_io_perpetual_secret_key) + + @property + def name(self) -> str: + return "gate_io_perpetual" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def check_network_request_path(self): + return CONSTANTS.NETWORK_CHECK_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def _format_amount_to_size(self, trading_pair, amount: Decimal) -> Decimal: + trading_rule = self._trading_rules[trading_pair] + quanto_multiplier = Decimal(trading_rule.min_base_amount_increment) + size = amount / quanto_multiplier + return size + + def _format_size_to_amount(self, trading_pair, size: Decimal) -> Decimal: + trading_rule = self._trading_rules[trading_pair] + quanto_multiplier = Decimal(trading_rule.min_base_amount_increment) + amount = size * quanto_multiplier + return amount + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector. + Note that Market order type is no longer required and will not be used. + """ + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def supported_position_modes(self): + """ + This method needs to be overridden to provide the accurate information depending on the exchange. + """ + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def start(self, clock: Clock, timestamp: float): + """ + This function is called automatically by the clock. + """ + super().start(clock, timestamp) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # API documentation does not clarify the error message for timestamp related problems + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return GateIoPerpetualAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return GateIoPerpetualAPIUserStreamDataSource( + auth=self._auth, + user_id=self._gate_io_perpetual_user_id, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + async def start_network(self): + """ + Start all required tasks to update the status of the connector. + """ + await self._update_trading_rules() + await super().start_network() + + async def _format_trading_rules(self, raw_trading_pair_info) -> List[TradingRule]: + """ + Converts json API response into a dictionary of trading rules. + :param symbols_info: The json API response + :return A dictionary of trading rules. + Response Example: + [ + { + "name": "BTC_USDT", + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": false, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + ] + """ + result = {} + + for rule in raw_trading_pair_info: + try: + if not web_utils.is_exchange_information_valid(rule): + continue + + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["name"]) + + min_amount_inc = Decimal(f"{rule['quanto_multiplier']}") + min_price_inc = Decimal(f"{rule['order_price_round']}") + min_amount = min_amount_inc + min_notional = Decimal(str(1)) + result[trading_pair] = TradingRule(trading_pair, + min_order_size=min_amount, + min_price_increment=min_price_inc, + min_base_amount_increment=min_amount_inc, + min_notional_size=min_notional, + min_order_value=min_notional, + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) + return list(result.values()) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + size = self._format_amount_to_size(trading_pair, amount) + data = { + "text": order_id, + "contract": symbol, + "size": float(-size) if trade_type.name.lower() == 'sell' else float(size), + } + if order_type.is_limit_type(): + data.update({ + "price": f"{price:f}", + "tif": "gtc", + }) + if order_type is OrderType.LIMIT_MAKER: + data.update({"tif": "poc"}) + else: + data.update({ + "price": "0", + "tif": "ioc", + }) + + # RESTRequest does not support json, and if we pass a dict + # the underlying aiohttp will encode it to params + data = data + endpoint = CONSTANTS.ORDER_CREATE_PATH_URL + order_result = await self._api_post( + path_url=endpoint, + data=data, + is_auth_required=True, + limit_id=endpoint, + ) + if order_result.get('finish_as') in {"cancelled", "expired", "failed", "ioc"}: + raise IOError({"label": "ORDER_REJECTED", "message": "Order rejected."}) + exchange_order_id = str(order_result["id"]) + return exchange_order_id, self.current_timestamp + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation-specific method is called by _cancel + returns True if successful + """ + canceled = False + exchange_order_id = await tracked_order.get_exchange_order_id() + resp = await self._api_delete( + path_url=CONSTANTS.ORDER_DELETE_PATH_URL.format(id=exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.ORDER_DELETE_LIMIT_ID, + ) + canceled = resp.get("finish_as") == "cancelled" + return canceled + + async def _update_balances(self): + """ + Calls REST API to update total and available balances. + """ + account_info = "" + try: + account_info = await self._api_get( + path_url=CONSTANTS.USER_BALANCES_PATH_URL, + is_auth_required=True, + limit_id=CONSTANTS.USER_BALANCES_PATH_URL + ) + self._process_balance_message(account_info) + except Exception as e: + self.logger().network( + f"Unexpected error while fetching balance update - {str(e)}", exc_info=True, + app_warning_msg=(f"Could not fetch balance update from {self.name_cap}")) + raise e + return account_info + + async def check_network(self) -> NetworkStatus: + try: + await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.NETWORK_CHECK_PATH_URL, + ) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + def _process_balance_message(self, account): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + asset_name = account["currency"] + self._account_available_balances[asset_name] = Decimal(str(account["available"])) + self._account_balances[asset_name] = Decimal(str(account["total"])) + remote_asset_names.add(asset_name) + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _process_balance_message_ws(self, balance_update): + for account in balance_update: + asset_name = "USDT" + self._account_available_balances[asset_name] = Decimal(str(account["balance"])) - Decimal( + str(account["change"])) + self._account_balances[asset_name] = Decimal(str(account["balance"])) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + try: + exchange_order_id = await order.get_exchange_order_id() + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "contract": trading_pair, + "order": exchange_order_id + }, + is_auth_required=True, + limit_id=CONSTANTS.MY_TRADES_PATH_URL) + + for trade_fill in all_fills_response: + trade_update = self._create_trade_update_with_order_fill_data( + order_fill=trade_fill, + order=order) + trade_updates.append(trade_update) + + except asyncio.TimeoutError: + raise IOError(f"Skipped order update with order fills for {order.client_order_id} " + "- waiting for exchange order id.") + + return trade_updates + + def _create_trade_update_with_order_fill_data( + self, + order_fill: Dict[str, Any], + order: InFlightOrder): + fee_asset = order.quote_asset + # no "position_action" in return, should use AddedToCostTradeFee, same as new_spot_fee + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=fee_asset, + flat_fees=[TokenAmount( + amount=Decimal(order_fill["fee"]), + token=fee_asset + )] + ) + + trade_update = TradeUpdate( + trade_id=str(order_fill["id"]), + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=abs(self._format_size_to_amount(order.trading_pair, (Decimal(str(order_fill["size"]))))), + fill_quote_amount=abs( + self._format_size_to_amount(order.trading_pair, (Decimal(str(order_fill["size"])))) * Decimal( + order_fill["price"])), + fill_price=Decimal(order_fill["price"]), + fill_timestamp=order_fill["create_time"], + ) + return trade_update + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + try: + exchange_order_id = await tracked_order.get_exchange_order_id() + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.ORDER_STATUS_LIMIT_ID) + + order_update = self._create_order_update_with_order_status_data( + order_status=updated_order_data, + order=tracked_order) + except asyncio.TimeoutError: + raise IOError(f"Skipped order status update for {tracked_order.client_order_id}" + f" - waiting for exchange order id.") + + return order_update + + def _create_order_update_with_order_status_data(self, order_status: Dict[str, Any], order: InFlightOrder): + client_order_id = str(order_status.get("text", "")) + state = self._normalise_order_message_state(order_status, order) or order.current_state + + order_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=int(order_status["create_time"]), + new_state=state, + client_order_id=client_order_id, + exchange_order_id=str(order_status["id"]), + ) + return order_update + + def _normalise_order_message_state(self, order_msg: Dict[str, Any], tracked_order): + state = None + # we do not handle: + # "failed" because it is handled by create order + # "put" as the exchange order id is returned in the create order response + # "open" for same reason + + # same field for both WS and REST + amount_left = Decimal(str(order_msg.get("left"))) + status = order_msg.get("status") + finish_as = order_msg.get("finish_as") + size = Decimal(str(order_msg.get("size"))) + if status == "finished": + if finish_as == 'filled': + state = OrderState.FILLED + else: + state = OrderState.CANCELED + else: + if amount_left > 0 and amount_left != size: + state = OrderState.PARTIALLY_FILLED + return state + + # use bybitperpetual sample,not gateio sample + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + Listens to messages from _user_stream_tracker.user_stream queue. + Traders, Orders, and Balance updates from the WS. + """ + user_channels = [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + ] + async for event_message in self._iter_user_event_queue(): + try: + if isinstance(event_message, dict): + channel: str = event_message.get("channel", None) + results: List[Dict[str, Any]] = event_message.get("result", None) + elif event_message is asyncio.CancelledError: + raise asyncio.CancelledError + else: + raise Exception(event_message) + if channel not in user_channels: + self.logger().error( + f"Unexpected message in user stream: {event_message}.", exc_info=True) + continue + + if channel == CONSTANTS.USER_TRADES_ENDPOINT_NAME: + for trade_msg in results: + self._process_trade_message(trade_msg) + elif channel == CONSTANTS.USER_ORDERS_ENDPOINT_NAME: + for order_msg in results: + self._process_order_message(order_msg) + elif channel == CONSTANTS.USER_POSITIONS_ENDPOINT_NAME: + for position_msg in results: + await self._process_account_position_message(position_msg) + elif channel == CONSTANTS.USER_BALANCE_ENDPOINT_NAME: + self._process_balance_message_ws(results) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + def _process_trade_message(self, trade: Dict[str, Any], client_order_id: Optional[str] = None): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + Example Trade: + https://www.gate.io/docs/apiv4/en/#retrieve-market-trades + """ + client_order_id = client_order_id or str(trade.get("text", "")) + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + + if tracked_order is None: + self.logger().debug(f"Ignoring trade message with id {client_order_id}: not in in_flight_orders.") + else: + trade_update = self._create_trade_update_with_order_fill_data( + order_fill=trade, + order=tracked_order) + self._order_tracker.process_trade_update(trade_update) + + async def _process_account_position_message(self, position_msg: Dict[str, Any]): + """ + Updates position + :param position_msg: The position event message payload + """ + ex_trading_pair = position_msg["contract"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + amount = Decimal(str(position_msg["size"])) + trading_rule = self._trading_rules[trading_pair] + amount_precision = Decimal(trading_rule.min_base_amount_increment) + position_side = PositionSide.LONG if Decimal(position_msg.get("size")) > 0 else PositionSide.SHORT + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + entry_price = Decimal(str(position_msg["entry_price"])) + position = self._perpetual_trading.get_position(trading_pair, position_side) + if position is not None: + if amount == Decimal("0"): + self._perpetual_trading.remove_position(pos_key) + else: + position.update_position(position_side=position_side, + unrealized_pnl=None, + entry_price=entry_price, + amount=amount * amount_precision) + else: + await self._update_positions() + + def _process_order_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancelation or failure event if needed. + + :param order_msg: The order response from either REST or web socket API (they are of the same format) + + Example Order: + https://www.gate.io/docs/apiv4/en/#list-orders + """ + client_order_id = str(order_msg.get("text", "")) + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if not tracked_order: + self.logger().debug(f"Ignoring order message with id {client_order_id}: not in in_flight_orders.") + return + + order_update = self._create_order_update_with_order_status_data(order_status=order_msg, order=tracked_order) + self._order_tracker.process_order_update(order_update=order_update) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(web_utils.is_exchange_information_valid, exchange_info): + exchange_symbol = symbol_data["name"] + base = symbol_data["name"].split('_')[0] + quote = symbol_data["name"].split('_')[1] + trading_pair = combine_to_hb_trading_pair(base, quote) + if trading_pair in mapping.inverse: + self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote) + else: + mapping[exchange_symbol] = trading_pair + self._set_trading_pair_symbol_map(mapping) + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}_{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error( + f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}") + mapping.pop(current_exchange_symbol) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "contract": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.TICKER_PATH_URL, + params=params + ) + + return float(resp_json[0]["last"]) + + async def _update_positions(self): + """ + Retrieves all positions using the REST API. + """ + + positions = await self._api_get( + path_url=CONSTANTS.POSITION_INFORMATION_URL, + is_auth_required=True, + limit_id=CONSTANTS.POSITION_INFORMATION_URL + ) + + for position in positions: + ex_trading_pair = position.get("contract") + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(ex_trading_pair) + + amount = Decimal(position.get("size")) + ex_mode = position.get("mode") + if ex_mode == 'single': + mode = PositionMode.ONEWAY + position_side = PositionSide.LONG if Decimal(position.get("size")) > 0 else PositionSide.SHORT + else: + mode = PositionMode.HEDGE + position_side = PositionSide.LONG if ex_mode == "dual_long" else PositionSide.SHORT + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side, mode) + + if amount != 0: + trading_rule = self._trading_rules[hb_trading_pair] + amount_precision = Decimal(trading_rule.min_base_amount_increment) + + unrealized_pnl = Decimal(position.get("unrealised_pnl")) + entry_price = Decimal(position.get("entry_price")) + leverage = Decimal(position.get("leverage")) + position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * amount_precision, + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + + else: + self._perpetual_trading.remove_position(pos_key) + + async def _get_position_mode(self) -> Optional[PositionMode]: + if self._position_mode is None: + response = await self._api_get( + path_url=CONSTANTS.POSITION_INFORMATION_URL, + is_auth_required=True, + limit_id=CONSTANTS.POSITION_INFORMATION_URL + ) + self._position_mode = PositionMode.ONEWAY if response[0]["mode"] == 'single' else PositionMode.HEDGE + return self._position_mode + + async def _execute_set_position_mode_for_pairs( + # To-do: ensure there's no active order or contract before changing position mode + self, mode: PositionMode, trading_pairs: List[str] + ) -> Tuple[bool, List[str], str]: + successful_pairs = [] + success = True + msg = "" + + for trading_pair in trading_pairs: + initial_mode = await self._get_position_mode() + if mode != initial_mode: + success, msg = await self._trading_pair_position_mode_set(mode, trading_pair) + self._position_mode = mode + if success: + successful_pairs.append(trading_pair) + else: + self.logger().network(f"Error switching {trading_pair} mode to {mode}: {msg}") + break + + return success, successful_pairs, msg + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + msg = "" + success = True + + dual_mode = 'true' if mode is PositionMode.HEDGE else 'false' + + data = {"dual_mode": dual_mode} + + response = await self._api_post( + path_url=CONSTANTS.SET_POSITION_MODE_URL, + params=data, + is_auth_required=True, + limit_id=CONSTANTS.SET_POSITION_MODE_URL, + ) + if 'detail' in response: + success = False + msg = response['detail'] + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + success = True + msg = "" + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + if self.position_mode is PositionMode.ONEWAY: + endpoint = CONSTANTS.ONEWAY_SET_LEVERAGE_PATH_URL.format(contract=exchange_symbol) + else: + endpoint = CONSTANTS.HEDGE_SET_LEVERAGE_PATH_URL.format(contract=exchange_symbol) + data = { + "leverage": leverage, + } + resp = await self._api_post( + path_url=endpoint, + params=data, + is_auth_required=True, + limit_id=CONSTANTS.ONEWAY_SET_LEVERAGE_PATH_URL, + ) + if isinstance(resp, dict): + return_leverage = resp['leverage'] + else: + return_leverage = resp[0]['leverage'] + if int(return_leverage) != leverage: + success = False + msg = "leverage is diff" + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + pass + + async def _update_funding_payment(self, trading_pair: str, fire_event_on_new: bool) -> bool: + return True diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_user_stream_data_source.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..26120bf --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_user_stream_data_source.py @@ -0,0 +1,101 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.derivative.gate_io_perpetual import gate_io_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_auth import GateIoPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_derivative import GateIoPerpetualExchange + + +class GateIoPerpetualAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: GateIoPerpetualAuth, + user_id: str, + trading_pairs: List[str], + connector: 'GateIoPerpetualExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._api_factory = api_factory + self._auth: GateIoPerpetualAuth = auth + self._user_id = user_id + self._trading_pairs: List[str] = trading_pairs + self._connector = connector + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=CONSTANTS.PING_TIMEOUT) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + user_info_symbols = [self._user_id] + symbols = ["!all"] + user_info_symbols.extend(symbols) + orders_change_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "payload": user_info_symbols + } + subscribe_order_change_request: WSJSONRequest = WSJSONRequest( + payload=orders_change_payload, + is_auth_required=True) + + trades_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": user_info_symbols + } + subscribe_trades_request: WSJSONRequest = WSJSONRequest( + payload=trades_payload, + is_auth_required=True) + positions_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + "event": "subscribe", + "payload": user_info_symbols + } + subscribe_positions_request: WSJSONRequest = WSJSONRequest( + payload=positions_payload, + is_auth_required=True) + await websocket_assistant.send(subscribe_order_change_request) + await websocket_assistant.send(subscribe_trades_request) + await websocket_assistant.send(subscribe_positions_request) + + self.logger().info("Subscribed to private order changes channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if event_message.get("error") is not None: + err_msg = event_message.get("error", {}).get("message", event_message.get("error")) + raise IOError({ + "label": "WSS_ERROR", + "message": f"Error received via websocket - {err_msg}." + }) + elif event_message.get("event") == "update" and event_message.get("channel") in [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + CONSTANTS.TICKER_ENDPOINT_NAME, + ]: + queue.put_nowait(event_message) diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_utils.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_utils.py new file mode 100644 index 0000000..9185a1a --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_utils.py @@ -0,0 +1,51 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.derivative.gate_io_perpetual import gate_io_perpetual_constants as CONSTANTS +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC_USDT" +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.00015"), + taker_percent_fee_decimal=Decimal("0.0005"), +) + + +class GateIOPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="gate_io_perpetual", client_data=None) + gate_io_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {CONSTANTS.EXCHANGE_NAME} API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + gate_io_perpetual_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {CONSTANTS.EXCHANGE_NAME} secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + gate_io_perpetual_user_id: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {CONSTANTS.EXCHANGE_NAME} user id", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "gate_io_perpetual" + + +KEYS = GateIOPerpetualConfigMap.construct() diff --git a/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_web_utils.py b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_web_utils.py new file mode 100644 index 0000000..1bdf093 --- /dev/null +++ b/hummingbot/connector/derivative/gate_io_perpetual/gate_io_perpetual_web_utils.py @@ -0,0 +1,55 @@ +import time +from typing import Any, Dict, Optional + +import hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + + :param endpoint: a public REST endpoint + :param domain: unused + + :return: the full URL to the endpoint + """ + if CONSTANTS.REST_URL[-1] != "/" and endpoint[0] != "/": + endpoint = "/" + endpoint + return CONSTANTS.REST_URL + endpoint + + +def private_rest_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(endpoint, domain) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + return time.time() + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("in_delisting", None) is False diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/README.md b/hummingbot/connector/derivative/injective_v2_perpetual/README.md new file mode 100644 index 0000000..162d794 --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/README.md @@ -0,0 +1,4 @@ +## Injective v2 Perpetual + +This is a perpetual connector created by **[Injective Labs](https://injectivelabs.org/)**. +The description and configuration steps for the perpetual connector are identical to the spot connector. Please check the README file in the Injective v2 spot connector folder. diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/__init__.py b/hummingbot/connector/derivative/injective_v2_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/injective_constants.py b/hummingbot/connector/derivative/injective_v2_perpetual/injective_constants.py new file mode 100644 index 0000000..e474272 --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/injective_constants.py @@ -0,0 +1,12 @@ +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS + +EXCHANGE_NAME = "injective_v2_perpetual" + +DEFAULT_DOMAIN = "" +TESTNET_DOMAIN = "testnet" + +TRANSACTIONS_CHECK_INTERVAL = CONSTANTS.TRANSACTIONS_CHECK_INTERVAL + +ORDER_STATE_MAP = CONSTANTS.ORDER_STATE_MAP + +ORDER_NOT_FOUND_ERROR_MESSAGE = CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..19ce377 --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_api_order_book_data_source.py @@ -0,0 +1,93 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.derivative.injective_v2_perpetual import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import MarketEvent, OrderBookDataSourceEvent + +if TYPE_CHECKING: + from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_derivative import ( + InjectiveV2Dericative, + ) + + +class InjectiveV2PerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + + def __init__( + self, + trading_pairs: List[str], + connector: "InjectiveV2Dericative", + data_source: InjectiveDataSource, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs=trading_pairs) + self._ev_loop = asyncio.get_event_loop() + self._connector = connector + self._data_source = data_source + self._domain = domain + self._forwarders = [] + self._configure_event_forwarders() + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + market_id = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + funding_info = await self._data_source.funding_info(market_id=market_id) + + return funding_info + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def listen_for_subscriptions(self): + # Subscriptions to streams is handled by the data_source + # Here we just make sure the data_source is listening to the streams + market_ids = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + await self._data_source.start(market_ids=market_ids) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + snapshot = await self._data_source.perpetual_order_book_snapshot(market_id=symbol, trading_pair=trading_pair) + return snapshot + + async def _parse_order_book_diff_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Injective 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + async def _parse_trade_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Injective 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + # In Injective 'raw_message' is not a raw message, but the FundingInfoUpdate created + # by the data source + message_queue.put_nowait(raw_message) + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_order_book_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener( + event_tag=OrderBookDataSourceEvent.DIFF_EVENT, listener=event_forwarder + ) + + event_forwarder = EventForwarder(to_function=self._process_public_trade_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_funding_info_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.FundingInfo, listener=event_forwarder) + + def _process_order_book_event(self, order_book_diff: OrderBookMessage): + self._message_queue[self._diff_messages_queue_key].put_nowait(order_book_diff) + + def _process_public_trade_event(self, trade_update: OrderBookMessage): + self._message_queue[self._trade_messages_queue_key].put_nowait(trade_update) + + def _process_funding_info_event(self, funding_info_update: FundingInfoUpdate): + self._message_queue[self._funding_info_messages_queue_key].put_nowait(funding_info_update) diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_derivative.py b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_derivative.py new file mode 100644 index 0000000..74f2bdf --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_derivative.py @@ -0,0 +1,1133 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union + +from async_timeout import timeout + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.constants import FUNDING_FEE_POLL_INTERVAL, s_decimal_NaN +from hummingbot.connector.derivative.injective_v2_perpetual import ( + injective_constants as CONSTANTS, + injective_v2_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_api_order_book_data_source import ( + InjectiveV2PerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_utils import InjectiveConfigMap +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange.injective_v2.injective_events import InjectiveEvent +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayPerpetualInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.data_type.trade_fee import TradeFeeBase, TradeFeeSchema +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent, PositionUpdateEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class InjectiveV2PerpetualDerivative(PerpetualDerivativePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + connector_configuration: InjectiveConfigMap, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + **kwargs, + ): + self._orders_processing_delta_time = 0.5 + + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._data_source = connector_configuration.create_data_source() + self._rate_limits = connector_configuration.network.rate_limits() + + super().__init__(client_config_map=client_config_map) + self._data_source.configure_throttler(throttler=self._throttler) + self._forwarders = [] + self._configure_event_forwarders() + self._latest_polled_order_fill_time: float = self._time() + self._orders_transactions_check_task: Optional[asyncio.Task] = None + self._last_received_message_timestamp = 0 + self._orders_queued_to_create: List[GatewayPerpetualInFlightOrder] = [] + self._orders_queued_to_cancel: List[GatewayPerpetualInFlightOrder] = [] + + self._orders_transactions_check_task = None + self._queued_orders_task = None + self._all_trading_events_queue = asyncio.Queue() + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> AuthBase: + return None + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return self._rate_limits + + @property + def domain(self) -> str: + return self._data_source.network_name + + @property + def client_order_id_max_length(self) -> int: + return None + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def trading_rules_request_path(self) -> str: + raise NotImplementedError + + @property + def trading_pairs_request_path(self) -> str: + raise NotImplementedError + + @property + def check_network_request_path(self) -> str: + raise NotImplementedError + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return FUNDING_FEE_POLL_INTERVAL + + def supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + @property + def status_dict(self) -> Dict[str, bool]: + status = super().status_dict + status["data_source_initialized"] = self._data_source.is_started() + return status + + async def start_network(self): + await super().start_network() + + market_ids = [ + await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs + ] + await self._data_source.start(market_ids=market_ids) + + if self.is_trading_required: + self._orders_transactions_check_task = safe_ensure_future(self._check_orders_transactions()) + self._queued_orders_task = safe_ensure_future(self._process_queued_orders()) + + async def stop_network(self): + """ + This function is executed when the connector is stopped. It performs a general cleanup and stops all background + tasks that require the connection with the exchange to work. + """ + await super().stop_network() + await self._data_source.stop() + self._forwarders = [] + if self._orders_transactions_check_task is not None: + self._orders_transactions_check_task.cancel() + self._orders_transactions_check_task = None + if self._queued_orders_task is not None: + self._queued_orders_task.cancel() + self._queued_orders_task = None + + def supported_order_types(self) -> List[OrderType]: + return self._data_source.supported_order_types() + + def start_tracking_order( + self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ): + leverage = self.get_leverage(trading_pair=trading_pair) + self._order_tracker.start_tracking_order( + GatewayPerpetualInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp, + leverage=leverage, + position=position_action, + ) + ) + + def batch_order_create(self, orders_to_create: List[Union[MarketOrder, LimitOrder]]) -> List[LimitOrder]: + """ + Issues a batch order creation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_create: A list of LimitOrder or MarketOrder objects representing the orders to create. The order IDs + can be blanc. + :returns: A tuple composed of LimitOrder or MarketOrder objects representing the created orders, complete with the generated + order IDs. + """ + orders_with_ids_to_create = [] + for order in orders_to_create: + client_order_id = get_new_client_order_id( + is_buy=order.is_buy, + trading_pair=order.trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + orders_with_ids_to_create.append(order.copy_with_id(client_order_id=client_order_id)) + safe_ensure_future(self._execute_batch_order_create(orders_to_create=orders_with_ids_to_create)) + return orders_with_ids_to_create + + def batch_order_cancel(self, orders_to_cancel: List[LimitOrder]): + """ + Issues a batch order cancelation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_cancel: A list of the orders to cancel. + """ + safe_ensure_future(coro=self._execute_batch_cancel(orders_to_cancel=orders_to_cancel)) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all currently active orders. The cancellations are performed in parallel tasks. + + :param timeout_seconds: the maximum time (in seconds) the cancel logic should run + + :return: a list of CancellationResult instances, one for each of the orders to be cancelled + """ + incomplete_orders = {} + limit_orders = [] + successful_cancellations = [] + + for order in self.in_flight_orders.values(): + if not order.is_done: + incomplete_orders[order.client_order_id] = order + limit_orders.append(order.to_limit_order()) + + if len(limit_orders) > 0: + try: + async with timeout(timeout_seconds): + cancellation_results = await self._execute_batch_cancel(orders_to_cancel=limit_orders) + for cr in cancellation_results: + if cr.success: + del incomplete_orders[cr.order_id] + successful_cancellations.append(CancellationResult(cr.order_id, True)) + except Exception: + self.logger().network( + "Unexpected error cancelling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order. Check API key and network connection." + ) + failed_cancellations = [CancellationResult(oid, False) for oid in incomplete_orders.keys()] + return successful_cancellations + failed_cancellations + + async def cancel_all_subaccount_orders(self): + markets_ids = [await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self.trading_pairs] + await self._data_source.cancel_all_subaccount_orders(perpetual_markets_ids=markets_ids) + + async def check_network(self) -> NetworkStatus: + """ + Checks connectivity with the exchange using the API + """ + try: + status = await self._data_source.check_network() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + def trigger_event(self, event_tag: Enum, message: any): + # Reimplemented because Injective connector has trading pairs with modified token names, because market tickers + # are not always unique. + # We need to change the original trading pair in all events to the real tokens trading pairs to not impact the + # bot events processing + trading_pair = getattr(message, "trading_pair", None) + if trading_pair is not None: + new_trading_pair = self._data_source.real_tokens_perpetual_trading_pair(unique_trading_pair=trading_pair) + if isinstance(message, tuple): + message = message._replace(trading_pair=new_trading_pair) + else: + setattr(message, "trading_pair", new_trading_pair) + + super().trigger_event(event_tag=event_tag, message=message) + + async def _update_positions(self): + positions = await self._data_source.account_positions() + self._perpetual_trading.account_positions.clear() + + for position in positions: + position_key = self._perpetual_trading.position_key( + trading_pair=position.trading_pair, + side=position.position_side, + ) + self._perpetual_trading.set_position(pos_key=position_key, position=position) + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + # Injective supports only one mode. It can't be changes in the chain + return True, "" + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + """ + Leverage is set on a per order basis. See place_order() + """ + return True, "" + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + last_funding_rate = Decimal("-1") + market_id = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + payment_amount, payment_timestamp = await self._data_source.last_funding_payment(market_id=market_id) + + if payment_amount != Decimal(-1) and payment_timestamp != 0: + last_funding_rate = await self._data_source.last_funding_rate(market_id=market_id) + + return payment_timestamp, last_funding_rate, payment_amount + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # For Injective the cancelation is done by sending a transaction to the chain. + # The cancel request is not validated until the transaction is included in a block, and so this does not apply + return False + + async def _place_cancel(self, order_id: str, tracked_order: GatewayPerpetualInFlightOrder): + # Not required because of _execute_order_cancel redefinition + raise NotImplementedError + + async def _execute_order_cancel(self, order: GatewayPerpetualInFlightOrder) -> str: + # Order cancelation requests for single orders are queued to be executed in batch if possible + self._orders_queued_to_cancel.append(order) + return None + + async def _place_order(self, order_id: str, trading_pair: str, amount: Decimal, trade_type: TradeType, + order_type: OrderType, price: Decimal, **kwargs) -> Tuple[str, float]: + # Not required because of _place_order_and_process_update redefinition + raise NotImplementedError + + async def _create_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ): + """ + Creates an order in the exchange using the parameters to configure it + + :param trade_type: the side of the order (BUY of SELL) + :param order_id: the id that should be assigned to the order (the client id) + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :param position_action: is the order opening or closing a position + """ + try: + if price is None or price.is_nan(): + calculated_price = self.get_price_for_volume( + trading_pair=trading_pair, + is_buy=trade_type == TradeType.BUY, + volume=amount, + ).result_price + else: + calculated_price = price + + calculated_price = self.quantize_order_price(trading_pair, calculated_price) + + await super()._create_order( + trade_type=trade_type, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=calculated_price, + position_action=position_action, + **kwargs + ) + + except asyncio.CancelledError: + raise + except Exception as ex: + self._on_order_failure( + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + trade_type=trade_type, + order_type=order_type, + price=price, + exception=ex, + **kwargs, + ) + + async def _place_order_and_process_update(self, order: GatewayPerpetualInFlightOrder, **kwargs) -> str: + # Order creation requests for single orders are queued to be executed in batch if possible + self._orders_queued_to_create.append(order) + return None + + async def _execute_batch_order_create(self, orders_to_create: List[Union[MarketOrder, LimitOrder]]): + inflight_orders_to_create = [] + for order in orders_to_create: + valid_order = await self._start_tracking_and_validate_order( + trade_type=TradeType.BUY if order.is_buy else TradeType.SELL, + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.quantity, + order_type=order.order_type(), + price=order.price, + position_action=order.position, + ) + if valid_order is not None: + inflight_orders_to_create.append(valid_order) + await self._execute_batch_inflight_order_create(inflight_orders_to_create=inflight_orders_to_create) + + async def _execute_batch_inflight_order_create(self, inflight_orders_to_create: List[GatewayPerpetualInFlightOrder]): + try: + place_order_results = await self._data_source.create_orders( + perpetual_orders=inflight_orders_to_create + ) + for place_order_result, in_flight_order in ( + zip(place_order_results, inflight_orders_to_create) + ): + if place_order_result.exception: + self._on_order_creation_failure( + order_id=in_flight_order.client_order_id, + trading_pair=in_flight_order.trading_pair, + amount=in_flight_order.amount, + trade_type=in_flight_order.trade_type, + order_type=in_flight_order.order_type, + price=in_flight_order.price, + exception=place_order_result.exception, + ) + else: + self._update_order_after_creation_success( + exchange_order_id=place_order_result.exchange_order_id, + order=in_flight_order, + update_timestamp=self.current_timestamp, + misc_updates=place_order_result.misc_updates, + ) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().network("Batch order create failed.") + for order in inflight_orders_to_create: + self._on_order_creation_failure( + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.amount, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + exception=ex, + ) + + async def _start_tracking_and_validate_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + **kwargs + ) -> Optional[GatewayPerpetualInFlightOrder]: + trading_rule = self._trading_rules[trading_pair] + + if price is None: + calculated_price = self.get_price_for_volume( + trading_pair=trading_pair, + is_buy=trade_type == TradeType.BUY, + volume=amount, + ).result_price + calculated_price = self.quantize_order_price(trading_pair, calculated_price) + else: + calculated_price = price + + price = self.quantize_order_price(trading_pair, calculated_price) + amount = self.quantize_order_amount(trading_pair=trading_pair, amount=amount) + + self.start_tracking_order( + order_id=order_id, + exchange_order_id=None, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=amount, + **kwargs, + ) + order = self._order_tracker.active_orders[order_id] + + if order_type not in self.supported_order_types(): + self.logger().error(f"{order_type} is not in the list of supported order types") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + elif amount < trading_rule.min_order_size: + self.logger().warning(f"{trade_type.name.title()} order amount {amount} is lower than the minimum order" + f" size {trading_rule.min_order_size}. The order will not be created.") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + elif price is not None and amount * price < trading_rule.min_notional_size: + self.logger().warning(f"{trade_type.name.title()} order notional {amount * price} is lower than the " + f"minimum notional size {trading_rule.min_notional_size}. " + "The order will not be created.") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + + return order + + def _update_order_after_creation_success( + self, + exchange_order_id: Optional[str], + order: GatewayPerpetualInFlightOrder, + update_timestamp: float, + misc_updates: Optional[Dict[str, Any]] = None + ): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=order.current_state, + misc_updates=misc_updates, + ) + self.logger().debug(f"\nCreated order {order.client_order_id} ({exchange_order_id}) with TX {misc_updates}") + self._order_tracker.process_order_update(order_update) + + def _on_order_creation_failure( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Optional[Decimal], + exception: Exception, + ): + self.logger().network( + f"Error submitting {trade_type.name.lower()} {order_type.name.upper()} order to {self.name_cap} for " + f"{amount} {trading_pair} {price}.", + exc_info=exception, + app_warning_msg=f"Failed to submit buy order to {self.name_cap}. Check API key and network connection." + ) + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + + def _update_order_after_creation_failure(self, order_id: str, trading_pair: str): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + self._order_tracker.process_order_update(order_update) + + async def _execute_batch_cancel(self, orders_to_cancel: List[LimitOrder]) -> List[CancellationResult]: + results = [] + tracked_orders_to_cancel = [] + + for order in orders_to_cancel: + tracked_order = self._order_tracker.all_updatable_orders.get(order.client_order_id) + if tracked_order is not None: + tracked_orders_to_cancel.append(tracked_order) + else: + results.append(CancellationResult(order_id=order.client_order_id, success=False)) + + if len(tracked_orders_to_cancel) > 0: + results.extend(await self._execute_batch_order_cancel(orders_to_cancel=tracked_orders_to_cancel)) + + return results + + async def _execute_batch_order_cancel( + self, orders_to_cancel: List[GatewayPerpetualInFlightOrder], + ) -> List[CancellationResult]: + try: + cancel_order_results = await self._data_source.cancel_orders(perpetual_orders=orders_to_cancel) + cancelation_results = [] + for cancel_order_result in cancel_order_results: + success = True + if cancel_order_result.not_found: + self.logger().warning( + f"Failed to cancel the order {cancel_order_result.client_order_id} due to the order" + f" not being found." + ) + await self._order_tracker.process_order_not_found( + client_order_id=cancel_order_result.client_order_id + ) + success = False + elif cancel_order_result.exception is not None: + self.logger().error( + f"Failed to cancel order {cancel_order_result.client_order_id}", + exc_info=cancel_order_result.exception, + ) + success = False + else: + order_update: OrderUpdate = OrderUpdate( + client_order_id=cancel_order_result.client_order_id, + trading_pair=cancel_order_result.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + misc_updates=cancel_order_result.misc_updates, + ) + self._order_tracker.process_order_update(order_update) + cancelation_results.append( + CancellationResult(order_id=cancel_order_result.client_order_id, success=success) + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Failed to cancel orders {', '.join([o.client_order_id for o in orders_to_cancel])}", + exc_info=True, + ) + cancelation_results = [ + CancellationResult(order_id=order.client_order_id, success=False) + for order in orders_to_cancel + ] + + return cancelation_results + + def _update_order_after_cancelation_success(self, order: GatewayPerpetualInFlightOrder): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + ) + self._order_tracker.process_order_update(order_update) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fee_schema: TradeFeeSchema = self._trading_fees[trading_pair] + fee_rate = fee_schema.maker_percent_fee_decimal if is_maker else fee_schema.taker_percent_fee_decimal + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=fee_schema, + position_action=position_action, + percent=fee_rate, + percent_token=fee_schema.percent_fee_token, + ) + else: + fee = build_perpetual_trade_fee( + self.name, + is_maker, + position_action=position_action, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + self._trading_fees = await self._data_source.get_derivative_trading_fees() + + async def _user_stream_event_listener(self): + while True: + try: + event_message = await self._all_trading_events_queue.get() + channel = event_message["channel"] + event_data = event_message["data"] + + if channel == "transaction": + transaction_hash = event_data["hash"] + await self._check_created_orders_status_for_transaction(transaction_hash=transaction_hash) + elif channel == "trade": + trade_update = event_data + tracked_order = self._order_tracker.all_fillable_orders_by_exchange_order_id.get( + trade_update.exchange_order_id + ) + if tracked_order is not None: + new_trade_update = TradeUpdate( + trade_id=trade_update.trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_update.exchange_order_id, + trading_pair=trade_update.trading_pair, + fill_timestamp=trade_update.fill_timestamp, + fill_price=trade_update.fill_price, + fill_base_amount=trade_update.fill_base_amount, + fill_quote_amount=trade_update.fill_quote_amount, + fee=trade_update.fee, + is_taker=trade_update.is_taker, + ) + self._order_tracker.process_trade_update(new_trade_update) + elif channel == "order": + order_update = event_data + tracked_order = self._order_tracker.all_updatable_orders_by_exchange_order_id.get( + order_update.exchange_order_id) + if tracked_order is not None: + new_order_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=order_update.new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + self._order_tracker.process_order_update(order_update=new_order_update) + elif channel == "balance": + if event_data.total_balance is not None: + self._account_balances[event_data.asset_name] = event_data.total_balance + if event_data.available_balance is not None: + self._account_available_balances[event_data.asset_name] = event_data.available_balance + elif channel == "position": + position_update: PositionUpdateEvent = event_data + position_key = self._perpetual_trading.position_key( + position_update.trading_pair, position_update.position_side + ) + if position_update.amount == Decimal("0"): + self._perpetual_trading.remove_position(post_key=position_key) + else: + position: Position = self._perpetual_trading.get_position( + trading_pair=position_update.trading_pair, side=position_update.position_side + ) + if position is not None: + position.update_position( + position_side=position_update.position_side, + unrealized_pnl=position_update.unrealized_pnl, + entry_price=position_update.entry_price, + amount=position_update.amount, + leverage=position_update.leverage, + ) + else: + position = Position( + trading_pair=position_update.trading_pair, + position_side=position_update.position_side, + unrealized_pnl=position_update.unrealized_pnl, + entry_price=position_update.entry_price, + amount=position_update.amount, + leverage=position_update.leverage, + ) + self._perpetual_trading.set_position(pos_key=position_key, position=position) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop") + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + # Not used in Injective + raise NotImplementedError # pragma: no cover + + async def _update_trading_rules(self): + await self._data_source.update_markets() + await self._initialize_trading_pair_symbol_map() + trading_rules_list = await self._data_source.derivative_trading_rules() + trading_rules = {} + for trading_rule in trading_rules_list: + trading_rules[trading_rule.trading_pair] = trading_rule + self._trading_rules.clear() + self._trading_rules.update(trading_rules) + + async def _update_balances(self): + all_balances = await self._data_source.all_account_balances() + + self._account_available_balances.clear() + self._account_balances.clear() + + for token, token_balance_info in all_balances.items(): + self._account_balances[token] = token_balance_info["total_balance"] + self._account_available_balances[token] = token_balance_info["available_balance"] + + async def _all_trade_updates_for_order(self, order: GatewayPerpetualInFlightOrder) -> List[TradeUpdate]: + # Not required because of _update_orders_fills redefinition + raise NotImplementedError + + async def _update_orders_fills(self, orders: List[GatewayPerpetualInFlightOrder]): + oldest_order_creation_time = self.current_timestamp + all_market_ids = set() + orders_by_hash = {} + + for order in orders: + oldest_order_creation_time = min(oldest_order_creation_time, order.creation_timestamp) + all_market_ids.add(await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair)) + if order.exchange_order_id is not None: + orders_by_hash[order.exchange_order_id] = order + + try: + start_time = min(oldest_order_creation_time, self._latest_polled_order_fill_time) + trade_updates = await self._data_source.perpetual_trade_updates(market_ids=all_market_ids, start_time=start_time) + for trade_update in trade_updates: + tracked_order = orders_by_hash.get(trade_update.exchange_order_id) + if tracked_order is not None: + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=tracked_order.position, + percent_token=trade_update.fee.percent_token, + flat_fees=trade_update.fee.flat_fees, + ) + new_trade_update = TradeUpdate( + trade_id=trade_update.trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_update.exchange_order_id, + trading_pair=trade_update.trading_pair, + fill_timestamp=trade_update.fill_timestamp, + fill_price=trade_update.fill_price, + fill_base_amount=trade_update.fill_base_amount, + fill_quote_amount=trade_update.fill_quote_amount, + fee=fee, + is_taker=trade_update.is_taker, + ) + self._latest_polled_order_fill_time = max(self._latest_polled_order_fill_time, + trade_update.fill_timestamp) + self._order_tracker.process_trade_update(new_trade_update) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().warning( + f"Failed to fetch trade updates. Error: {ex}", + exc_info=ex, + ) + + async def _request_order_status(self, tracked_order: GatewayPerpetualInFlightOrder) -> OrderUpdate: + # Not required due to the redefinition of _update_orders_with_error_handler + raise NotImplementedError + + async def _update_orders_with_error_handler(self, orders: List[GatewayPerpetualInFlightOrder], error_handler: Callable): + oldest_order_creation_time = self.current_timestamp + all_market_ids = set() + orders_by_hash = {} + + for order in orders: + oldest_order_creation_time = min(oldest_order_creation_time, order.creation_timestamp) + all_market_ids.add(await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair)) + if order.exchange_order_id is not None: + orders_by_hash[order.exchange_order_id] = order + + try: + order_updates = await self._data_source.perpetual_order_updates( + market_ids=all_market_ids, + start_time=oldest_order_creation_time - self.LONG_POLL_INTERVAL + ) + + for order_update in order_updates: + tracked_order = orders_by_hash.get(order_update.exchange_order_id) + if tracked_order is not None: + try: + new_order_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=order_update.new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + + if tracked_order.current_state == OrderState.PENDING_CREATE and new_order_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=OrderState.OPEN, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + self._order_tracker.process_order_update(open_update) + + del orders_by_hash[order_update.exchange_order_id] + self._order_tracker.process_order_update(new_order_update) + except asyncio.CancelledError: + raise + except Exception as ex: + await error_handler(tracked_order, ex) + + if len(orders_by_hash) > 0: + # await self._data_source.check_order_hashes_synchronization(orders=orders_by_hash.values()) + for order in orders_by_hash.values(): + not_found_error = RuntimeError( + f"There was a problem updating order {order.client_order_id} " + f"({CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE})" + ) + await error_handler(order, not_found_error) + except asyncio.CancelledError: + raise + except Exception as request_error: + for order in orders_by_hash.values(): + await error_handler(order, request_error) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return WebAssistantsFactory(throttler=self._throttler) + + def _create_order_tracker(self) -> ClientOrderTracker: + tracker = GatewayOrderTracker(connector=self) + return tracker + + def _create_order_book_data_source(self) -> PerpetualAPIOrderBookDataSource: + return InjectiveV2PerpetualAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, + connector=self, + data_source=self._data_source, + domain=self.domain + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + # Not used in Injective + raise NotImplementedError # pragma: no cover + + def _is_user_stream_initialized(self): + # Injective does not have private websocket endpoints + return self._data_source.is_started() + + def _create_user_stream_tracker(self): + # Injective does not use a tracker for the private streams + return None + + def _create_user_stream_tracker_task(self): + # Injective does not use a tracker for the private streams + return None + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + # Not used in Injective + raise NotImplementedError() # pragma: no cover + + async def _initialize_trading_pair_symbol_map(self): + exchange_info = None + try: + mapping = await self._data_source.derivative_market_and_trading_pair_map() + self._set_trading_pair_symbol_map(mapping) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + return exchange_info + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_user_trade_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_user_order_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_balance_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_position_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=AccountEvent.PositionUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_transaction_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=InjectiveEvent.ChainTransactionEvent, listener=event_forwarder) + + def _process_balance_event(self, event: BalanceUpdateEvent): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "balance", "data": event} + ) + + def _process_position_event(self, event: BalanceUpdateEvent): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "position", "data": event} + ) + + def _process_user_order_update(self, order_update: OrderUpdate): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "order", "data": order_update} + ) + + def _process_user_trade_update(self, trade_update: TradeUpdate): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "trade", "data": trade_update} + ) + + def _process_transaction_event(self, transaction_event: Dict[str, Any]): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "transaction", "data": transaction_event} + ) + + async def _check_orders_transactions(self): + while True: + try: + # Executing the process shielded from this async task to isolate it from network disconnections + # (network disconnections cancel this task) + task = asyncio.create_task(self._check_orders_creation_transactions()) + await asyncio.shield(task) + await self._sleep(CONSTANTS.TRANSACTIONS_CHECK_INTERVAL) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while running the transactions check process", exc_info=True) + await self._sleep(0.5) + + async def _check_orders_creation_transactions(self): + orders: List[GatewayPerpetualInFlightOrder] = self._order_tracker.active_orders.values() + orders_by_creation_tx = defaultdict(list) + orders_with_inconsistent_hash = [] + + for order in orders: + if order.creation_transaction_hash is not None and order.is_pending_create: + orders_by_creation_tx[order.creation_transaction_hash].append(order) + + for transaction_hash, orders in orders_by_creation_tx.items(): + all_orders = orders.copy() + try: + order_updates = await self._data_source.order_updates_for_transaction( + transaction_hash=transaction_hash, perpetual_orders=orders + ) + + for order_update in order_updates: + tracked_order = self._order_tracker.active_orders.get(order_update.client_order_id) + if tracked_order is not None: + all_orders.remove(tracked_order) + if (tracked_order.exchange_order_id is not None + and tracked_order.exchange_order_id != order_update.exchange_order_id): + tracked_order.update_exchange_order_id(order_update.exchange_order_id) + orders_with_inconsistent_hash.append(tracked_order) + self._order_tracker.process_order_update(order_update=order_update) + + for not_found_order in all_orders: + self._update_order_after_failure( + order_id=not_found_order.client_order_id, + trading_pair=not_found_order.trading_pair + ) + + except ValueError: + self.logger().debug(f"Transaction not included in a block yet ({transaction_hash})") + + if len(orders_with_inconsistent_hash) > 0: + async with self._data_source.order_creation_lock: + active_orders = [ + order for order in self._order_tracker.active_orders.values() + if order not in orders_with_inconsistent_hash and order.current_state == OrderState.PENDING_CREATE + ] + await self._data_source.reset_order_hash_generator(active_orders=active_orders) + + async def _check_created_orders_status_for_transaction(self, transaction_hash: str): + transaction_orders = [] + order: GatewayPerpetualInFlightOrder + for order in self.in_flight_orders.values(): + if order.creation_transaction_hash == transaction_hash and order.is_pending_create: + transaction_orders.append(order) + + if len(transaction_orders) > 0: + order_updates = await self._data_source.order_updates_for_transaction( + transaction_hash=transaction_hash, perpetual_orders=transaction_orders + ) + + for order_update in order_updates: + tracked_order = self._order_tracker.active_orders.get(order_update.client_order_id) + if (tracked_order is not None + and tracked_order.exchange_order_id is not None + and tracked_order.exchange_order_id != order_update.exchange_order_id): + tracked_order.update_exchange_order_id(order_update.exchange_order_id) + self._order_tracker.process_order_update(order_update=order_update) + + async def _process_queued_orders(self): + while True: + try: + # Executing the batch cancelation and creation process shielded from this async task to isolate the + # creation/cancelation process from network disconnections (network disconnections cancel this task) + task = asyncio.create_task(self._cancel_and_create_queued_orders()) + await asyncio.shield(task) + sleep_time = (self.clock.tick_size * 0.5 + if self.clock is not None + else self._orders_processing_delta_time) + await self._sleep(sleep_time) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while processing queued individual orders", exc_info=True) + await self._sleep(self.clock.tick_size * 0.5) + + async def _cancel_and_create_queued_orders(self): + if len(self._orders_queued_to_cancel) > 0: + orders = [order.to_limit_order() for order in self._orders_queued_to_cancel] + self._orders_queued_to_cancel = [] + await self._execute_batch_cancel(orders_to_cancel=orders) + if len(self._orders_queued_to_create) > 0: + orders = self._orders_queued_to_create + self._orders_queued_to_create = [] + await self._execute_batch_inflight_order_create(inflight_orders_to_create=orders) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + market_id = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + last_price = await self._data_source.last_traded_price(market_id=market_id) + return float(last_price) + + def _get_poll_interval(self, timestamp: float) -> float: + last_recv_diff = timestamp - self._last_received_message_timestamp + poll_interval = ( + self.SHORT_POLL_INTERVAL + if last_recv_diff > self.TICK_INTERVAL_LIMIT + else self.LONG_POLL_INTERVAL + ) + return poll_interval diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_utils.py b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_utils.py new file mode 100644 index 0000000..da2c346 --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_utils.py @@ -0,0 +1,82 @@ +from decimal import Decimal +from typing import Dict, Union + +from pydantic import Field +from pydantic.class_validators import validator + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + ACCOUNT_MODES, + NETWORK_MODES, + InjectiveMainnetNetworkMode, + InjectiveReadOnlyAccountMode, +) +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = False +EXAMPLE_PAIR = "INJ-USDT" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0"), + taker_percent_fee_decimal=Decimal("0"), +) + + +class InjectiveConfigMap(BaseConnectorConfigMap): + # Setting a default dummy configuration to allow the bot to create a dummy instance to fetch all trading pairs + connector: str = Field(default="injective_v2_perpetual", const=True, client_data=None) + receive_connector_configuration: bool = Field( + default=True, const=True, + client_data=ClientFieldData(), + ) + network: Union[tuple(NETWORK_MODES.values())] = Field( + default=InjectiveMainnetNetworkMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the network ({'/'.join(list(NETWORK_MODES.keys()))})", + prompt_on_new=True, + ), + ) + account_type: Union[tuple(ACCOUNT_MODES.values())] = Field( + default=InjectiveReadOnlyAccountMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the type of account configuration ({'/'.join(list(ACCOUNT_MODES.keys()))})", + prompt_on_new=True, + ), + ) + + class Config: + title = "injective_v2_perpetual" + + @validator("network", pre=True) + def validate_network(cls, v: Union[(str, Dict) + tuple(NETWORK_MODES.values())]): + if isinstance(v, tuple(NETWORK_MODES.values()) + (Dict,)): + sub_model = v + elif v not in NETWORK_MODES: + raise ValueError( + f"Invalid network, please choose a value from {list(NETWORK_MODES.keys())}." + ) + else: + sub_model = NETWORK_MODES[v].construct() + return sub_model + + @validator("account_type", pre=True) + def validate_account_type(cls, v: Union[(str, Dict) + tuple(ACCOUNT_MODES.values())]): + if isinstance(v, tuple(ACCOUNT_MODES.values()) + (Dict,)): + sub_model = v + elif v not in ACCOUNT_MODES: + raise ValueError( + f"Invalid account type, please choose a value from {list(ACCOUNT_MODES.keys())}." + ) + else: + sub_model = ACCOUNT_MODES[v].construct() + return sub_model + + def create_data_source(self): + return self.account_type.create_data_source( + network=self.network.network(), + use_secure_connection=self.network.use_secure_connection(), + rate_limits=self.network.rate_limits(), + ) + + +KEYS = InjectiveConfigMap.construct() diff --git a/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_web_utils.py b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_web_utils.py new file mode 100644 index 0000000..082f232 --- /dev/null +++ b/hummingbot/connector/derivative/injective_v2_perpetual/injective_v2_perpetual_web_utils.py @@ -0,0 +1,15 @@ +import time +from typing import Optional + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + return _time() * 1e3 + + +def _time() -> float: + return time.time() diff --git a/hummingbot/connector/derivative/kucoin_perpetual/__init__.py b/hummingbot/connector/derivative/kucoin_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/kucoin_perpetual/dummy.pxd b/hummingbot/connector/derivative/kucoin_perpetual/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/kucoin_perpetual/dummy.pyx b/hummingbot/connector/derivative/kucoin_perpetual/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..0549ab7 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_order_book_data_source.py @@ -0,0 +1,297 @@ +import asyncio +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import pandas as pd + +from hummingbot.connector.derivative.kucoin_perpetual import ( + kucoin_perpetual_constants as CONSTANTS, + kucoin_perpetual_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative import KucoinPerpetualDerivative + + +class KucoinPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: 'KucoinPerpetualDerivative', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._nonce_provider = NonceCreator.for_microseconds() + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + funding_info_response = await self._request_complete_funding_info(trading_pair) + if "symbol" in funding_info_response["data"]: + symbol_info = funding_info_response["data"] + else: + symbol_info = funding_info_response["data"][0] + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(str(symbol_info["indexPrice"])), + mark_price=Decimal(str(symbol_info["markPrice"])), + next_funding_utc_timestamp=int(pd.Timestamp(symbol_info["nextFundingRateTime"]).timestamp()), + rate=Decimal(str(symbol_info["predictedFundingFeeRate"])), + ) + return funding_info + + async def _subscribe_channels(self, ws: WSAssistant): + try: + symbols = ",".join([await self._connector.exchange_symbol_associated_to_pair(trading_pair=pair) + for pair in self._trading_pairs]) + + trades_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"/contractMarket/ticker:{symbols}", + "privateChannel": False, + "response": False, + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + order_book_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"/contractMarket/level2:{symbols}", + "privateChannel": False, + "response": False, + } + subscribe_orderbook_request = WSJSONRequest(payload=order_book_payload) + + instrument_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"/contract/instrument:{symbols}", + "privateChannel": False, + "response": False, + } + subscribe_instruments_request = WSJSONRequest(payload=instrument_payload) + await ws.send(subscribe_trade_request) # not rate-limited + await ws.send(subscribe_orderbook_request) # not rate-limited + await ws.send(subscribe_instruments_request) # not rate-limited + self.logger().info("Subscribed to public order book, trade and funding info channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + await asyncio.wait_for(super()._process_websocket_messages(websocket_assistant=websocket_assistant), + timeout=CONSTANTS.WS_CONNECTION_TIME_INTERVAL) + except asyncio.TimeoutError: + payload = { + "id": web_utils.next_message_id(), + "type": "ping", + } + ping_request = WSJSONRequest(payload=payload) + self._last_ws_message_sent_timestamp = self._time() + await websocket_assistant.send(request=ping_request) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "data" in event_message and event_message.get("type") == "message": + event_channel = event_message.get("topic") + if CONSTANTS.WS_TRADES_TOPIC in event_channel: + channel = self._trade_messages_queue_key + elif CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC in event_channel: + channel = self._diff_messages_queue_key + elif CONSTANTS.WS_INSTRUMENTS_INFO_TOPIC in event_channel: + channel = self._funding_info_messages_queue_key + return channel + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + + event_type = raw_message["type"] + + if event_type == "message": + symbol = raw_message["topic"].split(":")[-1] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + diffs_data = raw_message["data"] + timestamp: float = float(diffs_data["timestamp"]) * 1e-3 + bids = [] + asks = [] + price = diffs_data["change"].split(",")[0] + side = diffs_data["change"].split(",")[1] + quantity = Decimal(diffs_data["change"].split(",")[2]) + row_tuple = (price, self._connector.get_value_of_contracts(trading_pair, quantity)) + if side == "buy": + bids.append(row_tuple) + else: + asks.append(row_tuple) + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": diffs_data["sequence"], + "bids": bids, + "asks": asks, + } + diff_message = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=order_book_message_content, + timestamp=timestamp, + ) + message_queue.put_nowait(diff_message) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_data: Dict[str, Any] = raw_message["data"] + timestamp: float = int(trade_data["time"]) * 1e-9 + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=trade_data["symbol"]) + message_content = { + "trade_id": str(trade_data["tradeId"]), + "update_id": int(trade_data["sequence"]), + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) if trade_data["side"] == "buy" else float( + TradeType.SELL.value), + "amount": self._connector.get_value_of_contracts(trading_pair, Decimal(trade_data["size"])), + "price": Decimal(trade_data["price"]) + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=timestamp) + + message_queue.put_nowait(trade_message) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + event_type = raw_message["subject"] + if event_type == "funding.rate" or event_type == "mark.index.price" or event_type == "position.settlement": + symbol = raw_message["topic"].split(":")[-1] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol) + entries = raw_message["data"] + info_update = FundingInfoUpdate(trading_pair) + if "indexPrice" in entries: + info_update.index_price = Decimal(str(entries["indexPrice"])) + if "markPrice" in entries: + info_update.mark_price = Decimal(str(entries["markPrice"])) + if "fundingRate" in entries: + info_update.rate = Decimal(str(entries["fundingRate"])) + message_queue.put_nowait(info_update) + + async def _request_complete_funding_info(self, trading_pair: str) -> Dict[str, Any]: + exchange_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint = CONSTANTS.GET_CONTRACT_INFO_PATH_URL.format(symbol=exchange_symbol[0]) + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint, domain=self._domain) + data = await rest_assistant.execute_request( + url=url, + throttler_limit_id=CONSTANTS.GET_CONTRACT_INFO_PATH_URL, + method=RESTMethod.GET, + is_auth_required=True, + ) + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + exchange_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + if len(exchange_symbol) > 0: + exchange_symbol = exchange_symbol[0] + snapshot_response = await self._request_order_book_snapshot(exchange_symbol) + snapshot_data = snapshot_response["data"] + if "time" in snapshot_data: + timestamp = float(snapshot_data["time"]) * 1e-3 + elif "ts" in snapshot_data: + timestamp = float(snapshot_data["ts"]) * 1e-9 + else: + timestamp = time.time() + if "sequence" in snapshot_data: + update_id = int(snapshot_data["sequence"]) + else: + update_id = self._nonce_provider.get_tracking_nonce(timestamp=timestamp) + bids, asks = self._get_bids_and_asks_from_rest_msg_data(trading_pair, snapshot_data) + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=order_book_message_content, + timestamp=timestamp, + ) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + rest_assistant = await self._api_factory.get_rest_assistant() + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint.format(symbol=trading_pair)) + limit_id = web_utils.get_rest_api_limit_id_for_endpoint(endpoint) + data = await rest_assistant.execute_request( + url=url, + throttler_limit_id=limit_id, + method=RESTMethod.GET, + ) + + return data + + def _get_bids_and_asks_from_rest_msg_data( + self, trading_pair, snapshot: List[Dict[str, Union[str, int, float]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + bids = [ + (float(row[0]), self._connector.get_value_of_contracts(trading_pair, Decimal(row[1]))) + for row in snapshot['bids'] + ] + asks = [ + (float(row[0]), self._connector.get_value_of_contracts(trading_pair, Decimal(row[1]))) + for row in snapshot['asks'] + ] + return bids, asks + + @staticmethod + def _get_bids_and_asks_from_ws_msg_data( + snapshot: Dict[str, List[Dict[str, Union[str, int, float]]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + bids = [] + asks = [] + for action, rows_list in snapshot.items(): + if action not in ["delete", "update", "insert"]: + continue + is_delete = action == "delete" + for row_dict in rows_list: + row_price = row_dict["price"] + row_size = 0.0 if is_delete else row_dict["size"] + row_tuple = (row_price, row_size) + if row_dict["side"] == "Buy": + bids.append(row_tuple) + else: + asks.append(row_tuple) + return bids, asks + + async def _connected_websocket_assistant(self) -> WSAssistant: + rest_assistant = await self._api_factory.get_rest_assistant() + connection_info = await rest_assistant.execute_request( + url=web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, + ) + + ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] + self._ping_interval = int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3 + # message_timeout = int(connection_info["data"]["instanceServers"][0]["pingTimeout"]) * 0.8 * 1e-3 + token = connection_info["data"]["token"] + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{ws_url}?token={token}", ping_timeout=self._ping_interval) + # await ws.connect(ws_url=f"{ws_url}?token={token}", ping_timeout=self._ping_interval, message_timeout=message_timeout) + return ws diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_user_stream_data_source.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_user_stream_data_source.py new file mode 100644 index 0000000..997d3dd --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_api_user_stream_data_source.py @@ -0,0 +1,187 @@ +import asyncio +from typing import TYPE_CHECKING, List, Optional + +from hummingbot.connector.derivative.kucoin_perpetual import ( + kucoin_perpetual_constants as CONSTANTS, + kucoin_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_auth import KucoinPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative import KucoinPerpetualDerivative + + +class KucoinPerpetualAPIUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: 'KucoinPerpetualDerivative', + auth: KucoinPerpetualAuth, + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__() + self._domain = domain + self._connector = connector + self._trading_pairs = trading_pairs + self._api_factory = api_factory + self._auth = auth + self._ws_assistants: List[WSAssistant] = [] + self._current_listen_key = None + self._listen_for_user_stream_task = None + self._last_listen_key_ping_ts = None + + self._manage_listen_key_task = None + self._listen_key_initialized_event: asyncio.Event = asyncio.Event() + + @property + def last_recv_time(self) -> float: + """ + Returns the time of the last received message + + :return: the timestamp of the last received message in seconds + """ + t = 0.0 + if len(self._ws_assistants) > 0: + t = min([wsa.last_recv_time for wsa in self._ws_assistants]) + return t + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + Connects to the user private channel in the exchange using a websocket connection. With the established + connection listens to all balance events and order updates provided by the exchange, and stores them in the + output queue + + :param output: the queue to use to store the received messages + """ + tasks_future = None + try: + tasks = [] + tasks.append( + self._listen_for_user_stream_on_url( + url=web_utils.wss_private_url(self._domain), output=output + ) + ) + + tasks_future = asyncio.gather(*tasks) + await tasks_future + + except asyncio.CancelledError: + tasks_future and tasks_future.cancel() + raise + + async def _listen_for_user_stream_on_url(self, url: str, output: asyncio.Queue): + ws: Optional[WSAssistant] = None + while True: + try: + ws = await self._get_connected_websocket_assistant(url) + self._ws_assistants.append(ws) + await self._subscribe_to_channels(ws, url, self._trading_pairs) + await ws.ping() # to update last_recv_timestamp + await self._process_websocket_messages(websocket_assistant=ws, queue=output) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error while listening to user stream {url}. Retrying after 5 seconds..." + ) + await self._sleep(5.0) + finally: + await self._on_user_stream_interruption(ws) + ws and self._ws_assistants.remove(ws) + + async def _get_connected_websocket_assistant(self, ws_url: str) -> WSAssistant: + rest_assistant = await self._api_factory.get_rest_assistant() + connection_info = await rest_assistant.execute_request( + url=web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PRIVATE_WS_DATA_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.PRIVATE_WS_DATA_PATH_URL, + is_auth_required=True, + ) + + ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] + self._ping_interval = int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3 + message_timeout = int(connection_info["data"]["instanceServers"][0]["pingTimeout"]) * 0.8 * 1e-3 + token = connection_info["data"]["token"] + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{ws_url}?token={token}", ping_timeout=self._ping_interval, message_timeout=message_timeout) + return ws + + async def _subscribe_to_channels(self, ws: WSAssistant, url: str, trading_pairs: List[str]): + try: + symbols = [ + await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in trading_pairs + ] + symbols_str = ",".join(symbols) + + order_change_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": CONSTANTS.WS_TRADES_TOPIC, + "privateChannel": True, + "response": False, + } + subscribe_orders_request = WSJSONRequest(order_change_payload) + position_change_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"{CONSTANTS.WS_POSITION_CHANGE_TOPIC}:{symbols_str}", + "privateChannel": True, + "response": False, + } + subscribe_positions_request = WSJSONRequest(position_change_payload) + + wallet_change_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": CONSTANTS.WS_WALLET_INFO_TOPIC, + "privateChannel": True, + "response": False, + } + subscribe_wallet_request = WSJSONRequest(wallet_change_payload) + + await ws.send(subscribe_orders_request) + await ws.send(subscribe_positions_request) + await ws.send(subscribe_wallet_request) + + self.logger().info( + f"Subscribed to private account and orders channels {url}..." + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error occurred subscribing to order book trading and delta streams {url}..." + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + await asyncio.wait_for(super()._process_websocket_messages( + websocket_assistant=websocket_assistant, + queue=queue), + timeout=CONSTANTS.WS_CONNECTION_TIME_INTERVAL) + except asyncio.TimeoutError: + payload = { + "id": web_utils.next_message_id(), + "type": "ping", + } + ping_request = WSJSONRequest(payload=payload) + self._last_ws_message_sent_timestamp = self._time() + await websocket_assistant.send(request=ping_request) + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + pass # unused + + async def _connected_websocket_assistant(self) -> WSAssistant: + pass # unused diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_auth.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_auth.py new file mode 100644 index 0000000..f11a3c5 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_auth.py @@ -0,0 +1,153 @@ +import base64 +import hashlib +import hmac +import json +import time +from collections import OrderedDict +from decimal import Decimal +from typing import Any, Dict, List +from urllib.parse import urlencode + +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class KucoinPerpetualAuth(AuthBase): + """ + Auth class required by Kucoin Perpetual API + """ + + def __init__(self, api_key: str, passphrase: str, secret_key: str, time_provider: TimeSynchronizer): + self._api_key: str = api_key + self._passphrase: str = passphrase + self._secret_key: str = secret_key + self._time_provider: TimeSynchronizer = time_provider + + @staticmethod + def keysort(dictionary: Dict[str, str]) -> Dict[str, str]: + return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) + + async def rest_authenticate(self, request: RESTRequest, use_time_provider=0) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + + :param request: the request to be configured for authenticated interaction + """ + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.authentication_headers(request=request, use_time_provider=use_time_provider)) + request.headers = headers + + return request + + async def _authenticate_get(self, request: RESTRequest) -> RESTRequest: + params = request.params or {} + request.params = self._extend_params_with_authentication_info(params) + return request + + async def _authenticate_post(self, request: RESTRequest) -> RESTRequest: + data = json.loads(request.data) if request.data is not None else {} + data = self._extend_params_with_authentication_info(data) + data = {key: value for key, value in sorted(data.items())} + request.data = json.dumps(data) + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. OKX does not use this + functionality + """ + return request # pass-through + + def get_ws_auth_payload(self) -> List[str]: + """ + Generates a dictionary with all required information for the authentication process + :return: a dictionary of authentication info including the request signature + """ + expires = self._get_expiration_timestamp() + raw_signature = "GET/realtime" + expires + signature = hmac.new( + self._secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256 + ).hexdigest() + auth_info = [self._api_key, expires, signature] + + return auth_info + + def _extend_params_with_authentication_info(self, params: Dict[str, Any]) -> Dict[str, Any]: + params["timestamp"] = self._get_timestamp() + params["api_key"] = self._api_key + key_value_elements = [] + for key, value in sorted(params.items()): + converted_value = float(value) if type(value) is Decimal else value + converted_value = converted_value if type(value) is str else json.dumps(converted_value) + key_value_elements.append(str(key) + "=" + converted_value) + raw_signature = "&".join(key_value_elements) + signature = hmac.new(self._secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256).hexdigest() + params["sign"] = signature + return params + + def partner_header(self, timestamp: str): + partner_payload = timestamp + CONSTANTS.HB_PARTNER_ID + self._api_key + partner_signature = base64.b64encode( + hmac.new( + CONSTANTS.HB_PARTNER_KEY.encode("utf-8"), + partner_payload.encode("utf-8"), + hashlib.sha256).digest()) + third_party = { + "KC-API-PARTNER": CONSTANTS.HB_PARTNER_ID, + "KC-API-PARTNER-SIGN": str(partner_signature, "utf-8") + } + return third_party + + def authentication_headers(self, request: RESTRequest, use_time_provider) -> Dict[str, Any]: + if use_time_provider == 1 and self._time_provider.time() > 0: + timestamp = self._time_provider.time() + else: + timestamp = int(self._get_timestamp()) + + header = { + "KC-API-KEY": self._api_key, + "KC-API-TIMESTAMP": str(timestamp), + "KC-API-KEY-VERSION": "2" + } + + path_url = f"/api{request.url.split('/api')[-1]}" + if request.params: + sorted_params = self.keysort(request.params) + query_string_components = urlencode(sorted_params, safe=',') + path_url = f"{path_url}?{query_string_components}" + + if request.data is not None: + body = request.data + else: + body = "" + payload = str(timestamp) + request.method.value.upper() + path_url + body + + signature = base64.b64encode( + hmac.new( + self._secret_key.encode("utf-8"), + payload.encode("utf-8"), + hashlib.sha256).digest()) + passphrase = base64.b64encode( + hmac.new( + self._secret_key.encode('utf-8'), + self._passphrase.encode('utf-8'), + hashlib.sha256).digest()) + header["KC-API-SIGN"] = str(signature, "utf-8") + header["KC-API-PASSPHRASE"] = str(passphrase, "utf-8") + partner_headers = self.partner_header(str(timestamp)) + header.update(partner_headers) + return header + + @staticmethod + def _get_timestamp(): + return str(int(time.time() * 1e3)) + + @staticmethod + def _get_expiration_timestamp(): + return str(int((round(time.time()) + 5) * 1e3)) diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_constants.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_constants.py new file mode 100644 index 0000000..181728e --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_constants.py @@ -0,0 +1,132 @@ +import sys + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionMode +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "kucoin_perpetual" + +DEFAULT_DOMAIN = "kucoin_perpetual_main" + +DEFAULT_TIME_IN_FORCE = "GTC" + +REST_URLS = {"kucoin_perpetual_main": "https://api-futures.kucoin.com/"} +WSS_PUBLIC_URLS = {"kucoin_perpetual_main": "wss://stream.kucoin.com/realtime_public"} +WSS_PRIVATE_URLS = {"kucoin_perpetual_main": "wss://stream.kucoin.com/realtime_private"} +REST_API_VERSION = "api/v1" + +HB_PARTNER_ID = "Hummingbot" +HB_PARTNER_KEY = "8fb50686-81a8-408a-901c-07c5ac5bd758" + +MAX_ID_LEN = 40 +SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE = 30 + +ONE_HOUR = 3600 + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +ORDER_TYPE_MAP = { + OrderType.LIMIT: "limit", + OrderType.LIMIT_MAKER: "limit", + OrderType.MARKET: "market", +} + +POSITION_MODE_API_ONEWAY = "MergedSingle" +POSITION_MODE_API_HEDGE = "BothSide" +POSITION_MODE_MAP = { + PositionMode.ONEWAY: POSITION_MODE_API_ONEWAY, + PositionMode.HEDGE: POSITION_MODE_API_HEDGE, +} + +# REST API Public Endpoints +QUERY_SYMBOL_ENDPOINT = f"{REST_API_VERSION}/contracts/active" +LATEST_SYMBOL_INFORMATION_ENDPOINT = f"{REST_API_VERSION}/ticker?symbol={{symbol}}" +ORDER_BOOK_ENDPOINT = f"{REST_API_VERSION}/level2/depth100?symbol={{symbol}}" +SERVER_TIME_PATH_URL = f"{REST_API_VERSION}/timestamp" +GET_LAST_FUNDING_RATE_PATH_URL = f"{REST_API_VERSION}/funding-rate/{{symbol}}/current" +GET_FUNDING_HISTORY_PATH_URL = f"{REST_API_VERSION}/funding-history?symbol={{symbol}}" +GET_CONTRACT_INFO_PATH_URL = f"{REST_API_VERSION}/contracts/{{symbol}}" + +# REST API Private Endpoints +CREATE_ORDER_PATH_URL = f"{REST_API_VERSION}/orders" +CANCEL_ORDER_PATH_URL = f"{REST_API_VERSION}/orders/{{orderid}}" +QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL = f"{REST_API_VERSION}/orders/{{orderid}}" +QUERY_ORDER_BY_CLIENT_ORDER_ID_PATH_URL = f"{REST_API_VERSION}/orders/byClientOid?clientOid={{clientorderid}}" +GET_RISK_LIMIT_LEVEL_PATH_URL = f"{REST_API_VERSION}/contracts/risk-limit/{{symbol}}" +SET_LEVERAGE_PATH_URL = f"{REST_API_VERSION}/position/risk-limit-level/change" +GET_RECENT_FILLS_INFO_PATH_URL = f"{REST_API_VERSION}/recentFills" +GET_FILL_INFO_PATH_URL = f"{REST_API_VERSION}/fills?orderId={{orderid}}" +GET_WALLET_BALANCE_PATH_URL = f"{REST_API_VERSION}/account-overview?currency={{currency}}" +GET_POSITIONS_PATH_URL = f"{REST_API_VERSION}/positions" +QUERY_ACTIVE_ORDER_PATH_URL = f"{REST_API_VERSION}/orders?status=active" +QUERY_ALL_ORDER_PATH_URL = f"{REST_API_VERSION}/orders" + +# Websocket +PUBLIC_WS_DATA_PATH_URL = f"{REST_API_VERSION}/bullet-public" +PRIVATE_WS_DATA_PATH_URL = f"{REST_API_VERSION}/bullet-private" + +WS_PING_REQUEST = "ping" +WS_ORDER_BOOK_EVENTS_TOPIC = "/contractMarket/level2" +WS_TRADES_TOPIC = "/contractMarket/tradeOrders" +WS_INSTRUMENTS_INFO_TOPIC = "/contract/instrument" +WS_TICKER_INFO_TOPIC = "/contractMarket/ticker" +WS_WALLET_INFO_TOPIC = "/contractAccount/wallet" +WS_EXECUTION_DATA_TOPIC = "/contractMarket/execution" +WS_POSITION_CHANGE_TOPIC = "/contract/position" +WS_AUTHENTICATE_USER_ENDPOINT_NAME = "auth" +WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME = "position.change" +WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME = "orderChange" +WS_SUBSCRIPTION_EXECUTIONS_ENDPOINT_NAME = "match" +WS_SUBSCRIPTION_WELCOME_MESSAGE = "welcome" +WS_ADJUST_RISK_LIMIT_MESSAGE = "position.adjustRiskLimit" + +WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME = "availableBalance.change" + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_CONNECTION_LIMIT = 30 +WS_CONNECTION_TIME_INTERVAL = 20 +WS_REQUEST_LIMIT_ID = "WSRequest" + +# Order Statuses +ORDER_STATE = { + "open": OrderState.OPEN, + "done": OrderState.FILLED, + "cancelExist": OrderState.CANCELED, +} + +# Request error codes +RET_CODE_OK = "200000" +RET_CODE_PARAMS_ERROR = "100001" +RET_CODE_API_KEY_INVALID = "400001" +RET_CODE_ORDER_NOT_EXISTS = "20001" +RET_CODE_MODE_POSITION_NOT_EMPTY = "30082" +RET_CODE_MODE_NOT_MODIFIED = "300010" +RET_CODE_LEVERAGE_NOT_MODIFIED = "300016" +RET_CODE_POSITION_ZERO = "300009" +RET_CODE_AUTH_TIMESTAMP_ERROR = "400002" + +NO_LIMIT = sys.maxsize +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=WS_CONNECTION_LIMIT, time_interval=WS_CONNECTION_TIME_INTERVAL), + RateLimit(WS_REQUEST_LIMIT_ID, limit=100, time_interval=10), + RateLimit(limit_id=PUBLIC_WS_DATA_PATH_URL, limit=100, time_interval=10), + RateLimit(limit_id=PRIVATE_WS_DATA_PATH_URL, limit=100, time_interval=10), + RateLimit(limit_id=LATEST_SYMBOL_INFORMATION_ENDPOINT, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=QUERY_SYMBOL_ENDPOINT, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=ORDER_BOOK_ENDPOINT, limit=30, time_interval=3), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=SET_LEVERAGE_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=GET_LAST_FUNDING_RATE_PATH_URL, limit=9, time_interval=3), + RateLimit(limit_id=GET_POSITIONS_PATH_URL, limit=9, time_interval=3), + RateLimit(limit_id=QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL, limit=30, time_interval=3), + RateLimit(limit_id=QUERY_ORDER_BY_CLIENT_ORDER_ID_PATH_URL, limit=30, time_interval=3), + RateLimit(limit_id=QUERY_ACTIVE_ORDER_PATH_URL, limit=30, time_interval=3), + RateLimit(limit_id=GET_WALLET_BALANCE_PATH_URL, limit=30, time_interval=3), + RateLimit(limit_id=GET_CONTRACT_INFO_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=CREATE_ORDER_PATH_URL, limit=25, time_interval=3), + RateLimit(limit_id=CANCEL_ORDER_PATH_URL, limit=35, time_interval=3), + RateLimit(limit_id=GET_FILL_INFO_PATH_URL, limit=9, time_interval=3), + RateLimit(limit_id=GET_RECENT_FILLS_INFO_PATH_URL, limit=9, time_interval=3), + RateLimit(limit_id=GET_FUNDING_HISTORY_PATH_URL, limit=9, time_interval=3), + RateLimit(limit_id=GET_RISK_LIMIT_LEVEL_PATH_URL, limit=9, time_interval=3), +] diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_derivative.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_derivative.py new file mode 100644 index 0000000..5cf27e9 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_derivative.py @@ -0,0 +1,954 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import pandas as pd +from bidict import ValueDuplicationError, bidict + +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_utils as kucoin_utils +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_api_order_book_data_source import ( + KucoinPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_api_user_stream_data_source import ( + KucoinPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_auth import KucoinPerpetualAuth +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + + +class KucoinPerpetualDerivative(PerpetualDerivativePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + kucoin_perpetual_api_key: str = None, + kucoin_perpetual_secret_key: str = None, + kucoin_perpetual_passphrase: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + + self.kucoin_perpetual_api_key = kucoin_perpetual_api_key + self.kucoin_perpetual_secret_key = kucoin_perpetual_secret_key + self.kucoin_perpetual_passphrase = kucoin_perpetual_passphrase + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._last_trade_history_timestamp = None + + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> KucoinPerpetualAuth: + return KucoinPerpetualAuth(self.kucoin_perpetual_api_key, + self.kucoin_perpetual_passphrase, + self.kucoin_perpetual_secret_key, + time_provider=self._time_synchronizer) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.HB_PARTNER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.QUERY_SYMBOL_ENDPOINT + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def supported_position_modes(self): + # KuCoin only supports ONEWAY mode for all perpetuals, no hedge mode + return [PositionMode.ONEWAY] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def get_quantity_of_contracts(self, trading_pair: str, amount: float) -> int: + trading_rule: TradingRule = self._trading_rules[trading_pair] + num_contracts = int(amount / trading_rule.min_base_amount_increment) + return num_contracts + + def get_value_of_contracts(self, trading_pair: str, number: int) -> Decimal: + if len(self._trading_rules) > 0: + trading_rule: TradingRule = self._trading_rules[trading_pair] + contract_value = Decimal(number * trading_rule.min_base_amount_increment) + else: + contract_value = Decimal(number * 0.001) + return contract_value + + def start(self, clock: Clock, timestamp: float): + super().start(clock, timestamp) + self.set_position_mode(PositionMode.ONEWAY) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + ts_error_target_str = self._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_AUTH_TIMESTAMP_ERROR) + param_error_target_str = ( + "KC-API-TIMESTAMP Invalid -- Time differs from server time by more than 5 seconds" + ) + is_time_synchronizer_related = ( + ts_error_target_str in error_description or param_error_target_str in error_description + ) + return is_time_synchronizer_related + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + cancel_result = await self._api_delete( + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL.format(orderid=tracked_order.exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.CANCEL_ORDER_PATH_URL, + data={ + "order_id": tracked_order.exchange_order_id, + } + ) + response_code = cancel_result["code"] + + if response_code != CONSTANTS.RET_CODE_OK: + if response_code == CONSTANTS.RET_CODE_ORDER_NOT_EXISTS: + await self._order_tracker.process_order_not_found(order_id) + formatted_ret_code = self._format_ret_code_for_print(response_code) + raise IOError(f"{formatted_ret_code} - {cancel_result['msg']}") + + return True + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + data = { + "side": "buy" if trade_type is TradeType.BUY else "sell", + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair), + # size needs to be number of contracts, not amount of currency + "size": self.get_quantity_of_contracts(trading_pair, amount), + "timeInForce": CONSTANTS.DEFAULT_TIME_IN_FORCE, + "clientOid": order_id, + "reduceOnly": position_action == PositionAction.CLOSE, + "type": CONSTANTS.ORDER_TYPE_MAP[order_type], + "leverage": str(self.get_leverage(trading_pair)), + } + if order_type.is_limit_type(): + data["price"] = float(price) + if order_type is OrderType.LIMIT_MAKER: + data["postOnly"] = True + else: + data["timeInForce"] = "IOC" + + resp = await self._api_post( + path_url=CONSTANTS.CREATE_ORDER_PATH_URL, + data=data, + is_auth_required=True, + trading_pair=trading_pair, + headers={"referer": CONSTANTS.HB_PARTNER_ID}, + **kwargs, + ) + + if resp["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(resp['code']) + raise IOError(f"Error submitting order {order_id}: {formatted_ret_code} - {resp['msg']}") + return str(resp["data"]["orderId"]), self.current_timestamp + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data["makerFeeRate"]) if is_maker else Decimal(fees_data["takerFeeRate"]) + fee = AddedToCostTradeFee(percent=fee_value) + else: + fee = build_perpetual_trade_fee( + self.name, + is_maker, + position_action=position_action, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + pass + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth, + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return KucoinPerpetualAPIOrderBookDataSource( + self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return KucoinPerpetualAPIUserStreamDataSource( + trading_pairs=self.trading_pairs, + connector=self, + auth=self._auth, + api_factory=self._web_assistants_factory, + domain=self._domain, + ) + + async def _status_polling_loop_fetch_updates(self): + await safe_gather( + self._update_trade_history(), + self._update_order_status(), + self._update_balances(), + self._update_positions(), + ) + + async def _update_trade_history(self): + """ + Calls REST API to get trade history (order fills) + """ + trade_updates: List[TradeUpdate] = [] + orders = list(self._order_tracker.all_fillable_orders.values()) + if len(orders) > 0: + exchange_to_client = {o.exchange_order_id: o for o in orders} + trade_history_tasks = [] + for trading_pair in self._trading_pairs: + trade_history_tasks.append( + asyncio.create_task(self._api_get( + path_url=CONSTANTS.GET_RECENT_FILLS_INFO_PATH_URL, + is_auth_required=True, + trading_pair=trading_pair, + )) + ) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*trade_history_tasks, return_exceptions=True) + + # Initial parsing of responses. Joining all the responses + parsed_history_resps: List[Dict[str, Any]] = [] + for trading_pair, resp in zip(self._trading_pairs, raw_responses): + if not isinstance(resp, Exception): + trade_entries = resp["data"] + if trade_entries: + if "totalNum" in trade_entries: + number_entries = int(trade_entries["totalNum"]) + if (number_entries > 0): + if "items" in trade_entries: + trade_entries = trade_entries["items"] + self._last_trade_history_timestamp = float( + trade_entries[0]["tradeTime"] * 1e-9) # Time passed in nanoseconds + else: + self._last_trade_history_timestamp = float( + trade_entries[0]["tradeTime"] * 1e-9) # Time passed in nanoseconds + parsed_history_resps.extend(trade_entries) + else: + parsed_history_resps.extend(trade_entries) + else: + self.logger().network( + f"Error fetching status update for {trading_pair}: {resp}.", + app_warning_msg=f"Failed to fetch status update for {trading_pair}." + ) + + # Trade updates must be handled before any order status updates. + for trade in parsed_history_resps: + if str(trade["orderId"]) in exchange_to_client: + tracked_order = exchange_to_client[str(trade["orderId"])] + position_side = trade["side"] + + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "buy" + or tracked_order.trade_type is TradeType.SELL and position_side == "sell") + else PositionAction.CLOSE) + + fee_amount = Decimal(trade["fee"]) + fee_asset = trade["feeCurrency"] + flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)] + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=fee_asset, + flat_fees=flat_fees, + ) + contract_value = Decimal( + self.get_value_of_contracts(tracked_order.trading_pair, int(trade.get("size", "0")))) + + trade_update = TradeUpdate( + trade_id=str(trade["tradeId"]), + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + exchange_order_id=str(trade["orderId"]), + fee=fee, + fill_base_amount=contract_value, + fill_quote_amount=Decimal(trade["value"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["createdAt"] * 1e-3, + ) + trade_updates.append(trade_update) + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update) + + async def _update_order_status(self): + """ + Calls REST API to get order status + """ + + active_orders: List[InFlightOrder] = list(self.in_flight_orders.values()) + + tasks = [] + for active_order in active_orders: + tasks.append(asyncio.create_task(self._request_order_status_data(tracked_order=active_order))) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*tasks, return_exceptions=True) + + # Initial parsing of responses. Removes Exceptions. + parsed_status_responses: List[Dict[str, Any]] = [] + for resp, active_order in zip(raw_responses, active_orders): + if not isinstance(resp, Exception) and "data" in resp: + parsed_status_responses.append(resp["data"]) + else: + self.logger().network( + f"Error fetching status update for the order {active_order.client_order_id}: {resp}.", + app_warning_msg=f"Failed to fetch status update for the order {active_order.client_order_id}." + ) + await self._order_tracker.process_order_not_found(active_order.client_order_id) + + for order_status in parsed_status_responses: + self._process_order_event_message(order_status) + + async def _update_balances(self): + """ + Calls REST API to update total and available balances + """ + wallet_balance: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.GET_WALLET_BALANCE_PATH_URL.format(currency="USDT"), + is_auth_required=True, + limit_id=CONSTANTS.GET_WALLET_BALANCE_PATH_URL, + ) + + if wallet_balance["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(wallet_balance['code']) + raise IOError(f"{formatted_ret_code} - {wallet_balance['msg']}") + + self._account_available_balances.clear() + self._account_balances.clear() + + if wallet_balance["data"] is not None: + if isinstance(wallet_balance["data"], list): + for balance_data in wallet_balance["data"]: + currency = str(balance_data["currency"]) + self._account_balances[currency] = Decimal(str(balance_data["marginBalance"])) + self._account_available_balances[currency] = Decimal(str(balance_data["availableBalance"])) + else: + currency = str(wallet_balance["data"]["currency"]) + self._account_balances[currency] = Decimal(str(wallet_balance["data"]["marginBalance"])) + self._account_available_balances[currency] = Decimal(str(wallet_balance["data"]["availableBalance"])) + + async def _update_positions(self): + """ + Retrieves all positions using the REST API. + """ + + raw_responses: List[Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.GET_POSITIONS_PATH_URL, + is_auth_required=True, + limit_id=CONSTANTS.GET_POSITIONS_PATH_URL, + ) + + # Initial parsing of responses. Joining all the responses + parsed_resps: List[Dict[str, Any]] = [] + if len(raw_responses["data"]) > 0: + for resp, trading_pair in zip(raw_responses["data"], self._trading_pairs): + if not isinstance(resp, Exception): + result = resp + if result: + position_entries = result if isinstance(result, list) else [result] + parsed_resps.extend(position_entries) + else: + self.logger().error(f"Error fetching positions for {trading_pair}. Response: {resp}") + + for position in parsed_resps: + data = position + ex_trading_pair = data.get("symbol") + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(ex_trading_pair) + amount = self.get_value_of_contracts(hb_trading_pair, int(data["currentQty"])) + position_side = PositionSide.SHORT if amount < 0 else PositionSide.LONG + unrealized_pnl = Decimal(str(data["unrealisedPnl"])) + entry_price = Decimal(str(data["avgEntryPrice"])) + leverage = Decimal(str(data["realLeverage"])) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side) + if amount != s_decimal_0: + position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + # not used + trade_updates = [] + + if order.exchange_order_id is not None: + try: + all_fills_response = await self._request_order_fills(order=order) + trades_list_key = "items" + fills_data = all_fills_response["data"].get(trades_list_key, []) + + if fills_data is not None: + for fill_data in fills_data: + trade_update = self._parse_trade_update(trade_msg=fill_data, tracked_order=order) + trade_updates.append(trade_update) + except IOError as ex: + if not self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + raise + + return trade_updates + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + url = CONSTANTS.GET_FILL_INFO_PATH_URL.format(orderid=order.exchange_order_id) + res = await self._api_get( + path_url=url, + is_auth_required=True, + trading_pair=order.trading_pair, + limit_id=CONSTANTS.GET_FILL_INFO_PATH_URL, + ) + return res + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + try: + order_status_data = await self._request_order_status_data(tracked_order=tracked_order) + order_msg = order_status_data["data"] + client_order_id = str(order_msg["clientOid"]) + + ordered_canceled = order_msg["cancelExist"] + is_active = order_msg["isActive"] + new_state = tracked_order.current_state + if ordered_canceled: + new_state = OrderState.CANCELED + elif not is_active: + new_state = OrderState.FILLED + + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + client_order_id=client_order_id, + exchange_order_id=order_msg["id"], + ) + + return order_update + + except IOError as ex: + if self._is_request_exception_related_to_time_synchronizer(request_exception=ex): + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + ) + else: + raise + + return order_update + + async def _request_order_status_data(self, tracked_order: InFlightOrder) -> Dict: + resp = await self._api_get( + path_url=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format( + orderid=tracked_order.exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL, + ) + + return resp + + async def _user_stream_event_listener(self): + """ + Listens to message in _user_stream_tracker.user_stream queue. + """ + async for event_message in self._iter_user_event_queue(): + try: + endpoint = web_utils.endpoint_from_message(event_message) + payload = web_utils.payload_from_message(event_message) + + if endpoint == CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME: + await self._process_account_position_event(payload) + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME: + order_event_type = payload["type"] + client_order_id: Optional[str] = payload.get("clientOid") + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + event_timestamp = payload["ts"] * 1e-9 + if order_event_type == "match": + self._process_trade_event_message(payload) + if updatable_order is not None: + updated_status = updatable_order.current_state + if order_event_type == "open": + updated_status = OrderState.OPEN + elif order_event_type == "match": + updated_status = OrderState.PARTIALLY_FILLED + elif order_event_type == "filled": + updated_status = OrderState.FILLED + elif order_event_type == "canceled": + updated_status = OrderState.CANCELED + + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=event_timestamp, + new_state=updated_status, + client_order_id=client_order_id, + exchange_order_id=payload["orderId"], + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif endpoint == CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME: + if isinstance(payload, list): + for wallet_msg in payload: + self._process_wallet_event_message(wallet_msg) + else: + self._process_wallet_event_message(payload) + elif endpoint is None: + self.logger().error(f"Could not extract endpoint from {event_message}.") + raise ValueError + elif endpoint == "error": + self.logger().error(f"Error returned via WS: {payload}.") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _process_account_position_event(self, position_msg: Dict[str, Any]): + """ + Updates position + :param position_msg: The position event message payload + """ + if "changeReason" in position_msg and position_msg["changeReason"] != "markPriceChange": + ex_trading_pair = position_msg["symbol"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + amount = self.get_value_of_contracts(trading_pair, int(position_msg["currentQty"])) + position_side = PositionSide.SHORT if amount < 0 else PositionSide.LONG + entry_price = Decimal(str(position_msg["avgEntryPrice"])) + leverage = Decimal(str(position_msg["realLeverage"])) + unrealized_pnl = Decimal(str(position_msg["unrealisedPnl"])) + pos_key = self._perpetual_trading.position_key(trading_pair, position_side) + if amount != s_decimal_0: + position = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, position) + else: + self._perpetual_trading.remove_position(pos_key) + + elif "changeReason" in position_msg and position_msg["changeReason"] == "markPriceChange": + ex_trading_pair = position_msg["symbol"] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=ex_trading_pair) + existing_position = self._perpetual_trading.get_position(trading_pair) + if existing_position is not None: + existing_position.update_position(unrealized_pnl=Decimal(str(position_msg["unrealisedPnl"]))) + + def _process_trade_event_message(self, trade_msg: Dict[str, Any]): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + :param trade_msg: The trade event message payload + """ + client_order_id = str(trade_msg.get("clientOid")) + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + if fillable_order is not None: + trade_update = self._parse_trade_update(trade_msg=trade_msg, tracked_order=fillable_order) + self._order_tracker.process_trade_update(trade_update) + + def _parse_trade_update(self, trade_msg: Dict, tracked_order: InFlightOrder) -> TradeUpdate: + trade_id = trade_msg["tradeId"] + order_id = trade_msg["orderId"] + + position_side = trade_msg["side"] + position_action = (PositionAction.OPEN + if (tracked_order.trade_type is TradeType.BUY and position_side == "buy" + or tracked_order.trade_type is TradeType.SELL and position_side == "sell") + else PositionAction.CLOSE) + execute_amount_diff = Decimal(trade_msg["matchSize"]) + execute_price = Decimal(trade_msg["matchPrice"]) + fee = self.get_fee( + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.order_type, + tracked_order.trade_type, + position_action, + execute_amount_diff, + execute_price, + is_maker=trade_msg.get("liquidity") == "maker" + ) + exec_price = Decimal(trade_msg["matchPrice"]) + exec_time = ( + trade_msg["ts"] * 1e-9 + if "ts" in trade_msg + else pd.Timestamp(trade_msg["ts"]).timestamp() + ) + if int(trade_msg["matchSize"]) == 0: + contract_value = 0 + exec_price = 0 + else: + contract_value = Decimal( + self.get_value_of_contracts(tracked_order.trading_pair, int(trade_msg["matchSize"]))) + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_id, + trading_pair=tracked_order.trading_pair, + fill_timestamp=exec_time, + fill_price=exec_price, + fill_base_amount=contract_value, + fill_quote_amount=exec_price * contract_value, + fee=fee, + ) + + return trade_update + + def _process_order_event_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancellation or failure event if needed. + :param order_msg: The order event message payload + """ + ordered_canceled = order_msg["cancelExist"] + is_active = order_msg["isActive"] + client_order_id = str(order_msg["clientOid"]) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + new_state = updatable_order.current_state + if ordered_canceled: + new_state = OrderState.CANCELED + elif not is_active: + new_state = OrderState.FILLED + + if updatable_order is not None: + new_order_update: OrderUpdate = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + client_order_id=client_order_id, + exchange_order_id=order_msg["id"], + ) + self._order_tracker.process_order_update(new_order_update) + + def _process_wallet_event_message(self, wallet_msg: Dict[str, Any]): + """ + Updates account balances. + :param wallet_msg: The account balance update message payload + """ + if "currency" in wallet_msg: + symbol = wallet_msg["currency"] + else: + symbol = "USDT" + + available_balance = Decimal(str(wallet_msg["availableBalance"])) + self._account_balances[symbol] = Decimal(available_balance + Decimal(str(wallet_msg["holdBalance"]))) + self._account_available_balances[symbol] = available_balance + + async def start_network(self): + """ + Start all required tasks to update the status of the connector. + """ + await self._update_trading_rules() + await super().start_network() + + async def _format_trading_rules(self, instrument_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Converts JSON API response into a local dictionary of trading rules. + :param instrument_info_dict: The JSON API response. + :returns: A dictionary of trading pair to its respective TradingRule. + """ + trading_rules = {} + symbol_map = await self.trading_pair_symbol_map() + for instrument in instrument_info_dict["data"]: + try: + exchange_symbol = instrument["symbol"] + if exchange_symbol in symbol_map: + multiplier = Decimal(str(instrument["multiplier"])) + trading_pair = combine_to_hb_trading_pair(instrument['baseCurrency'], instrument['quoteCurrency']) + collateral_token = instrument["quoteCurrency"] + trading_rules[trading_pair] = TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(str(instrument["lotSize"])) * multiplier, + max_order_size=Decimal(str(instrument["maxOrderQty"])) * multiplier, + min_price_increment=Decimal(str(instrument["tickSize"])), + min_base_amount_increment=multiplier, + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule: {instrument}. Skipping...") + return list(trading_rules.values()) + + async def _market_data_for_all_product_types(self) -> List[Dict[str, Any]]: + all_exchange_info = [] + + exchange_info = await self._api_get( + path_url=self.trading_pairs_request_path + ) + all_exchange_info.extend(exchange_info["data"]) + + return all_exchange_info + + async def _initialize_trading_pair_symbol_map(self): + try: + all_exchange_info = await self._market_data_for_all_product_types() + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=all_exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + if "data" in exchange_info: + exchange_info = exchange_info["data"] + for symbol_data in filter(kucoin_utils.is_exchange_information_valid, exchange_info): + try: + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["baseCurrency"], + quote=symbol_data["quoteCurrency"]) + except ValueDuplicationError: + # We can safely ignore this, KuCoin API returns a duplicate entry for XBT-USDT + pass + self._set_trading_pair_symbol_map(mapping) + + def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str): + """Resolves name conflicts provoked by futures contracts. + + If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise, + the trading pair is removed from the map and an error is logged. + """ + expected_exchange_symbol = f"{base}{quote}" + trading_pair = combine_to_hb_trading_pair(base, quote) + current_exchange_symbol = mapping.inverse[trading_pair] + if current_exchange_symbol == expected_exchange_symbol: + pass + elif new_exchange_symbol == expected_exchange_symbol: + mapping.pop(current_exchange_symbol) + mapping[new_exchange_symbol] = trading_pair + else: + self.logger().error( + f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}") + mapping.pop(current_exchange_symbol) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + + resp_json = await self._api_get( + path_url=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT.format(symbol=exchange_symbol), + limit_id=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT, + ) + if isinstance(resp_json["data"], list): + if "lastTradePrice" in resp_json["data"][0]: + price = float(resp_json["data"][0]["lastTradePrice"]) + else: + price = float(resp_json["data"][0]["price"]) + else: + if "lastTradePrice" in resp_json["data"]: + price = float(resp_json["data"]["lastTradePrice"]) + else: + price = float(resp_json["data"]["price"]) + return price + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + msg = "" + success = True + + if mode == PositionMode.HEDGE: + msg = "KuCoin Perpetuals don't allow for a position mode change." + success = False + else: + msg = "Success" + success = True + + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + resp: Dict[str, Any] = await self._api_get( + path_url=CONSTANTS.GET_RISK_LIMIT_LEVEL_PATH_URL.format(symbol=exchange_symbol), + is_auth_required=True, + trading_pair=trading_pair, + limit_id=CONSTANTS.GET_RISK_LIMIT_LEVEL_PATH_URL, + ) + if resp["code"] != CONSTANTS.RET_CODE_OK: + formatted_ret_code = self._format_ret_code_for_print(resp['code']) + return False, f"{formatted_ret_code} - Some problem" + max_leverage = resp['data'][0]['maxLeverage'] + if leverage > max_leverage: + self.logger().error(f"Max leverage for {trading_pair} is {max_leverage}.") + return False, f"Max leverage for {trading_pair} is {max_leverage}." + return True, "" + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + + raw_response: Dict[str, Any] = await self._api_get( + path_url=CONSTANTS.GET_FUNDING_HISTORY_PATH_URL.format(symbol=exchange_symbol), + limit_id=CONSTANTS.GET_FUNDING_HISTORY_PATH_URL, + is_auth_required=True, + trading_pair=trading_pair, + ) + + if "dataList" in raw_response and len(raw_response["dataList"][0]) == 0: + # An empty funding fee/payment is retrieved. + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + elif "data" in raw_response and len(raw_response["data"]["dataList"]) == 0: + # An empty funding fee/payment is retrieved. + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + else: + if "dataList" in raw_response: + data: Dict[str, Any] = raw_response["dataList"][0] + else: + data: Dict[str, Any] = raw_response["data"]["dataList"][0] + funding_rate: Decimal = Decimal(str(data["fundingRate"])) + position_size: Decimal = Decimal(str(data["positionQty"])) + payment: Decimal = funding_rate * position_size + if "timePoint" in data: + timestamp: int = int(pd.Timestamp(data["timePoint"], tz="UTC").timestamp()) + else: + timestamp: int = self.current_timestamp + return timestamp, funding_rate, payment + + async def _api_request(self, + path_url, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + trading_pair: Optional[str] = None, + currency: Optional[str] = None, + exchange_order_id: Optional[str] = None, + client_order_id: Optional[str] = None, + **kwargs) -> Dict[str, Any]: + + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + if limit_id is None: + limit_id = web_utils.get_rest_api_limit_id_for_endpoint( + endpoint=path_url, + ) + url = web_utils.get_rest_url_for_endpoint(endpoint=path_url, + domain=self._domain) + + resp = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + throttler_limit_id=limit_id if limit_id else path_url, + ) + return resp + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + @staticmethod + def _format_ret_code_for_print(ret_code: Union[str, int]) -> str: + return f"ret_code <{ret_code}>" diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_utils.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_utils.py new file mode 100644 index 0000000..b907dbe --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_utils.py @@ -0,0 +1,67 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +# Kucoin Futures fees: https://www.kucoin.com/vip/level +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0002"), + taker_percent_fee_decimal=Decimal("0.0006"), + percent_fee_token="USDT") + +CENTRALIZED = True + +EXAMPLE_PAIR = "XBT-USDT" + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + status = exchange_info.get("status") + valid = status is not None and status in ["Open"] + return valid + + +class KucoinPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="kucoin_perpetual", client_data=None) + kucoin_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Kucoin Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kucoin_perpetual_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Kucoin Perpetual secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kucoin_perpetual_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your KuCoin Perpetual passphrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "kucoin_perpetual" + + +KEYS = KucoinPerpetualConfigMap.construct() diff --git a/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_web_utils.py b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_web_utils.py new file mode 100644 index 0000000..77025b7 --- /dev/null +++ b/hummingbot/connector/derivative/kucoin_perpetual/kucoin_perpetual_web_utils.py @@ -0,0 +1,165 @@ +from typing import Any, Callable, Dict, List, Optional + +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class HeadersContentRESTPreProcessor(RESTPreProcessorBase): + async def pre_process(self, request: RESTRequest) -> RESTRequest: + request.headers = request.headers or {} + request.headers["Content-Type"] = "application/json" + return request + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + HeadersContentRESTPreProcessor(), + ], + ) + return api_factory + + +def create_throttler(trading_pairs: List[str] = None) -> AsyncThrottler: + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + return throttler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + endpoint = CONSTANTS.SERVER_TIME_PATH_URL + url = get_rest_url_for_endpoint(endpoint=endpoint, domain=domain) + limit_id = get_rest_api_limit_id_for_endpoint(endpoint) + response = await rest_assistant.execute_request( + url=url, + throttler_limit_id=limit_id, + method=RESTMethod.GET, + ) + server_time = response["data"] + + return server_time * 1e-3 + + +def endpoint_from_message(message: Dict[str, Any]) -> Optional[str]: + endpoint = None + if "request" in message: + message = message["request"] + elif "type" in message: + endpoint = message["type"] + if isinstance(message, dict): + if "subject" in message.keys(): + endpoint = message["subject"] + elif endpoint is None and "topic" in message.keys(): + endpoint = message["topic"] + return endpoint + + +def payload_from_message(message: Dict[str, Any]) -> Dict[str, Any]: + payload = message + if "data" in message: + payload = message["data"] + return payload + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def get_rest_url_for_endpoint( + endpoint: str, + domain: str = CONSTANTS.DEFAULT_DOMAIN +): + variant = domain if domain else CONSTANTS.DEFAULT_DOMAIN + return CONSTANTS.REST_URLS.get(variant) + endpoint + + +def get_pair_specific_limit_id(base_limit_id: str, trading_pair: str) -> str: + limit_id = f"{base_limit_id}-{trading_pair}" + return limit_id + + +def get_rest_api_limit_id_for_endpoint(endpoint: Dict[str, str]) -> str: + return endpoint + + +def _wss_url(endpoint: Dict[str, str], connector_variant_label: Optional[str]) -> str: + variant = connector_variant_label if connector_variant_label else CONSTANTS.DEFAULT_DOMAIN + return endpoint.get(variant) + + +def wss_public_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_PUBLIC_URLS, connector_variant_label) + + +def wss_private_url(connector_variant_label: Optional[str]) -> str: + return _wss_url(CONSTANTS.WSS_PRIVATE_URLS, connector_variant_label) + + +def next_message_id() -> str: + return str(get_tracking_nonce()) + + +async def api_request(path: str, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + api_version: str = "v1", + limit_id: Optional[str] = None, + timeout: Optional[float] = None): + + throttler = throttler or create_throttler() + + api_factory = api_factory or build_api_factory() + rest_assistant = await api_factory.get_rest_assistant() + + async with throttler.execute_task(limit_id=limit_id if limit_id else path): + url = get_rest_url_for_endpoint(endpoint=path, domain=domain) + + request = RESTRequest( + method=method, + url=url, + params=params, + data=data, + is_auth_required=is_auth_required, + throttler_limit_id=limit_id if limit_id else path + ) + response = await rest_assistant.call(request=request, timeout=timeout) + + if response.status != 200: + if return_err: + error_response = await response.json() + return error_response + else: + error_response = await response.text() + raise IOError(f"Error executing request {method.name} {path}. " + f"HTTP status is {response.status}. " + f"Error: {error_response}") + return await response.json() diff --git a/hummingbot/connector/derivative/perpetual_budget_checker.py b/hummingbot/connector/derivative/perpetual_budget_checker.py new file mode 100644 index 0000000..5c26962 --- /dev/null +++ b/hummingbot/connector/derivative/perpetual_budget_checker.py @@ -0,0 +1,30 @@ +import typing + +from hummingbot.connector.budget_checker import BudgetChecker +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.core.data_type.order_candidate import PerpetualOrderCandidate + +if typing.TYPE_CHECKING: # avoid circular import problems + from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase + + +class PerpetualBudgetChecker(BudgetChecker): + def __init__(self, exchange: "PerpetualDerivativePyBase"): + """ + In the case of derived instruments, the collateral can be any token. + To get this information, this class uses the `get_buy_collateral_token` + and `get_sell_collateral_token` methods provided by the `PerpetualTrading` interface. + """ + super().__init__(exchange) + self._validate_perpetual_connector() + + def _validate_perpetual_connector(self): + from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase + if not isinstance(self._exchange, (PerpetualTrading, PerpetualDerivativePyBase)): + raise TypeError( + f"{self.__class__} must be passed an exchange implementing the {PerpetualTrading} interface." + ) + + def _lock_available_collateral(self, order_candidate: PerpetualOrderCandidate): + if not order_candidate.position_close: + super()._lock_available_collateral(order_candidate) diff --git a/hummingbot/connector/derivative/phemex_perpetual/__init__.py b/hummingbot/connector/derivative/phemex_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_order_book_data_source.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..bbe97e2 --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_order_book_data_source.py @@ -0,0 +1,248 @@ +import asyncio +import time +from collections import defaultdict, namedtuple +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_web_utils as web_utils +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_derivative import PhemexPerpetualDerivative + + +TradeStructure = namedtuple("Trade", "timestamp side price amount") + + +class PhemexPerpetualAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: "PhemexPerpetualDerivative", + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._domain = domain + self._trading_pairs: List[str] = trading_pairs + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + self._trade_messages_queue_key = "trades_p" + self._diff_messages_queue_key = "orderbook_p" + self._funding_info_messages_queue_key = "perp_market24h_pack_p.update" + self._snapshot_messages_queue_key = "snapshot" + self.pong_received_event = asyncio.Event() + + def _get_messages_queue_keys(self) -> List[str]: + return [self._funding_info_messages_queue_key, self._diff_messages_queue_key, self._trade_messages_queue_key] + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + symbol_info: Dict[str, Any] = (await self._request_complete_funding_info(trading_pair))["result"] + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(symbol_info["indexPriceRp"]), + mark_price=Decimal(symbol_info["markPriceRp"]), + next_funding_utc_timestamp=self._next_funding_time(), + rate=Decimal(symbol_info["fundingRateRr"]), + ) + return funding_info + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + params = {"symbol": ex_trading_pair} + + data = await self._connector._api_get(path_url=CONSTANTS.SNAPSHOT_REST_URL, params=params) + return data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = (await self._request_order_book_snapshot(trading_pair))["result"] + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + { + "trading_pair": trading_pair, + "update_id": snapshot_response["sequence"], + "bids": snapshot_response[self._diff_messages_queue_key]["bids"], + "asks": snapshot_response[self._diff_messages_queue_key]["asks"], + }, + timestamp=snapshot_response["timestamp"] * 1e-9, + ) + return snapshot_msg + + async def _connected_websocket_assistant(self) -> WSAssistant: + url = web_utils.wss_url(CONSTANTS.PUBLIC_WS_ENDPOINT, self._domain) + ws: WSAssistant = await self._api_factory.get_ws_assistant() + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_CONNECTION_LIMIT_ID): + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.WS_HEARTBEAT) + return ws + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + stream_id_channel_pairs = [ + CONSTANTS.DIFF_STREAM_METHOD, + CONSTANTS.TRADE_STREAM_METHOD, + CONSTANTS.FUNDING_INFO_STREAM_METHOD, + ] + for stream_method in stream_id_channel_pairs: + params = [] + for trading_pair in self._trading_pairs: + params.append(await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair)) + payload = { + "id": 0, + "method": stream_method, + "params": params if stream_method is not CONSTANTS.FUNDING_INFO_STREAM_METHOD else [], + } + subscribe_request: WSJSONRequest = WSJSONRequest(payload) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_MESSAGE_LIMIT_ID): + await ws.send(subscribe_request) + self.logger().info("Subscribed to public order book, trade and funding info channels...") + safe_ensure_future(self.ping_loop(ws=ws)) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if self._diff_messages_queue_key in event_message: + channel = self._diff_messages_queue_key + elif self._trade_messages_queue_key in event_message: + channel = self._trade_messages_queue_key + elif event_message.get("method", None) == self._funding_info_messages_queue_key: + channel = self._funding_info_messages_queue_key + return channel + + async def _process_message_for_unknown_channel( + self, event_message: Dict[str, Any], websocket_assistant: WSAssistant + ): + if event_message.get("result", None) == "pong": + self.pong_received_event.set() + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + timestamp: float = time.time() + raw_message["symbol"] = await self._connector.trading_pair_associated_to_exchange_symbol(raw_message["symbol"]) + if raw_message["type"] == "incremental": + book = raw_message[self._diff_messages_queue_key] + order_book_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + { + "trading_pair": raw_message["symbol"], + "update_id": raw_message["sequence"], + "bids": book["bids"], + "asks": book["asks"], + }, + timestamp=timestamp, + ) + message_queue.put_nowait(order_book_message) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + raw_message["symbol"] = await self._connector.trading_pair_associated_to_exchange_symbol(raw_message["symbol"]) + for trade in raw_message[self._trade_messages_queue_key]: + mapped_trade = TradeStructure(*trade) + trade_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.TRADE, + { + "trading_pair": raw_message["symbol"], + "trade_type": mapped_trade.side.upper(), + "trade_id": mapped_trade.timestamp, + "update_id": raw_message["sequence"], + "price": mapped_trade.price, + "amount": mapped_trade.amount, + }, + timestamp=mapped_trade.timestamp * 1e-9, + ) + + message_queue.put_nowait(trade_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + for trading_pair in self._trading_pairs: + snapshot_msg: OrderBookMessage = await self._order_book_snapshot(trading_pair) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + delta = CONSTANTS.ONE_HOUR - time.time() % CONSTANTS.ONE_HOUR + await self._sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...", exc_info=True + ) + await self._sleep(5.0) + + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if raw_message["type"] == "snapshot": + fields = raw_message.get("fields", []) + self.index_price_index = fields.index("indexRp") + self.mark_price_index = fields.index("markRp") + self.symbol_index = fields.index("symbol") + self.funding_rate_index = fields.index("fundingRateRr") + + for data in raw_message.get("data", []): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(data[self.symbol_index]) + + if trading_pair not in self._trading_pairs: + continue + funding_info = FundingInfoUpdate( + trading_pair=trading_pair, + index_price=Decimal(data[self.index_price_index]), + mark_price=Decimal(data[self.mark_price_index]), + next_funding_utc_timestamp=self._next_funding_time(), + rate=Decimal(data[self.funding_rate_index]), + ) + + message_queue.put_nowait(funding_info) + + async def _request_complete_funding_info(self, trading_pair: str): + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + data = await self._connector._api_get( + path_url=CONSTANTS.MARK_PRICE_URL, params={"symbol": ex_trading_pair}, is_auth_required=False + ) + return data + + def _next_funding_time(self) -> int: + """ + Funding settlement occurs every 8 hours as mentioned in https://phemex.com/user-guides/funding-rate + """ + return ((time.time() // 28800) + 1) * 28800 + + async def ping_loop(self, ws: WSAssistant): + count = 0 + while ws._connection.connected: + ping_request: WSJSONRequest = WSJSONRequest({"id": 0, "method": "server.ping", "params": []}) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_MESSAGE_LIMIT_ID): + await ws.send(ping_request) + try: + await asyncio.wait_for(self.pong_received_event.wait(), timeout=5) + self.pong_received_event.clear() + count = 0 + await self._sleep(5.0) + except asyncio.TimeoutError: + count += 1 + except asyncio.CancelledError: + raise + except Exception: + await ws._connection.disconnect() + finally: + if count == 3: + await ws._connection.disconnect() diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_user_stream_data_source.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_user_stream_data_source.py new file mode 100644 index 0000000..9c0be23 --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_api_user_stream_data_source.py @@ -0,0 +1,116 @@ +import asyncio +from typing import Any, Dict, Optional + +from hummingbot.connector.derivative.phemex_perpetual import ( + phemex_perpetual_constants as CONSTANTS, + phemex_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_auth import PhemexPerpetualAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class PhemexPerpetualAPIUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: PhemexPerpetualAuth, + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__() + self._auth = auth + self._api_factory = api_factory + self._domain = domain + self.pong_received_event = asyncio.Event() + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._get_ws_assistant() + url = web_utils.wss_url(CONSTANTS.PRIVATE_WS_ENDPOINT, self._domain) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_CONNECTION_LIMIT_ID): + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.WS_HEARTBEAT) # protocol level ping used because it is not enforced at the application level + + await self._authenticate_ws(ws) + + return ws + + async def _authenticate_ws(self, ws: WSAssistant) -> WSAssistant: + auth_request = self._auth.get_ws_auth_payload() + login_request = WSJSONRequest(payload=auth_request) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_MESSAGE_LIMIT_ID): + await ws.send(login_request) + + response: WSResponse = await ws.receive() + message = response.data + if message["error"] is not None or message.get("result").get("status") != "success": + self.logger().error("Error authenticating the private websocket connection") + raise IOError(f"Private websocket connection authentication failed: {message}.") + + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + try: + payload = { + "id": 0, + "method": "aop_p.subscribe", + "params": [] + } + subscription_request = WSJSONRequest(payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_MESSAGE_LIMIT_ID): + await websocket_assistant.send(subscription_request) + + response: WSResponse = await websocket_assistant.receive() + message = response.data + if message["error"] is not None or message.get("result").get("status") != "success": + raise IOError(f"Private account channel subscription failed: {message}.") + + self.logger().info("Subscribed to the private account channel...") + safe_ensure_future(self.ping_loop(ws=websocket_assistant)) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred subscribing to the private account channel..." + ) + raise + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def ping_loop(self, ws: WSAssistant): + count = 0 + while ws._connection.connected: + ping_request: WSJSONRequest = WSJSONRequest({"id": 0, "method": "server.ping", "params": []}) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WSS_MESSAGE_LIMIT_ID): + await ws.send(ping_request) + try: + await asyncio.wait_for(self.pong_received_event.wait(), timeout=5) + self.pong_received_event.clear() + count = 0 + await self._sleep(5.0) + except asyncio.TimeoutError: + count += 1 + except asyncio.CancelledError: + raise + except Exception: + await ws._connection.disconnect() + finally: + if count == 3: + await ws._connection.disconnect() + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0: + if event_message.get("result", "") == "pong": + self.pong_received_event.set() + else: + queue.put_nowait(event_message) diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_auth.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_auth.py new file mode 100644 index 0000000..10fd0c5 --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_auth.py @@ -0,0 +1,55 @@ +import hashlib +import hmac +from urllib.parse import urlencode, urlsplit + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class PhemexPerpetualAuth(AuthBase): + """ + Auth class required by Phemex Perpetual API + https://phemex-docs.github.io/#rest-request-header + """ + + def __init__(self, api_key: str, api_secret: str, time_provider: TimeSynchronizer): + self._api_key: str = api_key + self._api_secret: str = api_secret + self._time_provider: TimeSynchronizer = time_provider + + def generate_signature_from_payload(self, payload: str) -> str: + signature = hmac.new(self._api_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).hexdigest() + return signature + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + expiry_timestamp = str(int(self._time_provider.time()) + CONSTANTS.ONE_MINUTE) # expirary recommended to be set to 1 minuete + request.headers = {"x-phemex-access-token": self._api_key} + + payload = urlsplit(request.url).path + payload += urlencode(request.params) if request.params is not None else "" + payload += expiry_timestamp + payload += request.data if request.data is not None else "" + + request.headers["x-phemex-request-signature"] = self.generate_signature_from_payload(payload=payload) + request.headers["x-phemex-request-expiry"] = expiry_timestamp + request.headers["Content-Type"] = "application/json" + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + return request # pass-through + + def get_ws_auth_payload(self) -> dict: + expiry_timestamp = int(self._time_provider.time()) + CONSTANTS.ONE_SECOND * 2 + signature = self.generate_signature_from_payload(payload=f"{self._api_key}{expiry_timestamp}") + return { + "method": "user.auth", + "params": [ + "API", + self._api_key, + signature, + expiry_timestamp, + ], + "id": 0 + } diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_constants.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_constants.py new file mode 100644 index 0000000..01e329b --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_constants.py @@ -0,0 +1,203 @@ +import sys + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "phemex_perpetual" +MAX_ORDER_ID_LEN = 40 + +HB_PARTNER_ID = "HBOT" + +DEFAULT_DOMAIN = "" +TESTNET_DOMAIN = "phemex_perpetual_testnet" + +BASE_URLS = { + DEFAULT_DOMAIN: "https://api.phemex.com", + TESTNET_DOMAIN: "https://testnet-api.phemex.com", +} + +WSS_URLS = { + DEFAULT_DOMAIN: "wss://ws.phemex.com", + TESTNET_DOMAIN: "wss://testnet.phemex.com", +} + +PUBLIC_WS_ENDPOINT = "" +PRIVATE_WS_ENDPOINT = "" + +WS_HEARTBEAT = 5 # https://phemex-docs.github.io/#heartbeat + +COLLATERAL_TOKEN = "USDT" + +# Public API Endpoints +SNAPSHOT_REST_URL = "/md/v2/orderbook" +TICKER_PRICE_URL = "/md/v2/ticker/24hr" +TICKER_PRICE_CHANGE_URL = "/exchange/public/md/v2/kline/last" +SERVER_TIME_PATH_URL = "/public/time" +MARK_PRICE_URL = TICKER_PRICE_URL +EXCHANGE_INFO_URL = "/public/products" + +# Private API Endpoints +ACCOUNT_INFO = "/g-accounts/accountPositions" +PLACE_ORDERS = "/g-orders" +CANCEL_ORDERS = "/g-orders/cancel" +CANCEL_ALL_ORDERS = "/g-orders/all" +GET_ORDERS = "/api-data/g-futures/orders/by-order-id" +GET_TRADES = "/api-data/g-futures/trades" +POSITION_INFO = "/g-accounts/accountPositions" +POSITION_MODE = "/g-positions/switch-pos-mode-sync" +POSITION_LEVERAGE = "/g-positions/leverage" +USER_TRADE = "/exchange/order/v2/tradingList" +FUNDING_PAYMENT = "/api-data/g-futures/funding-fees" + + +# Funding Settlement Time Span +FUNDING_SETTLEMENT_DURATION = (0, 30) # seconds before snapshot, seconds after snapshot + + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +API_CONTRACT_GENERAL_LIMIT = "API_CONTRACT_GENERAL_LIMIT" +WSS_CONNECTION_LIMIT_ID = "phemexWSSConnectionLimitID" +WSS_MESSAGE_LIMIT_ID = "phemexWSSMessageLimitID" + +DIFF_STREAM_METHOD = "orderbook_p.subscribe" +TRADE_STREAM_METHOD = "trade_p.subscribe" +FUNDING_INFO_STREAM_METHOD = "perp_market24h_pack_p.subscribe" +HEARTBEAT_TIME_INTERVAL = 5.0 + +# Rate Limit time intervals +ONE_HOUR = 3600 +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +NO_LIMIT = sys.maxsize + + +RATE_LIMITS = [ + # Pool Limits + RateLimit(limit_id=REQUEST_WEIGHT, limit=100, time_interval=ONE_MINUTE), + RateLimit(limit_id=API_CONTRACT_GENERAL_LIMIT, limit=500, time_interval=ONE_MINUTE), + # WSS rate limits + RateLimit(limit_id=WSS_CONNECTION_LIMIT_ID, limit=NO_LIMIT, time_interval=ONE_SECOND), + RateLimit(limit_id=WSS_MESSAGE_LIMIT_ID, limit=NO_LIMIT, time_interval=ONE_SECOND), + # Weight Limits for individual endpoints + RateLimit( + limit_id=SNAPSHOT_REST_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=TICKER_PRICE_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=TICKER_PRICE_CHANGE_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + weight=10, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=SERVER_TIME_PATH_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=MARK_PRICE_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=EXCHANGE_INFO_URL, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT)], + ), + RateLimit( + limit_id=ACCOUNT_INFO, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=PLACE_ORDERS, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=CANCEL_ORDERS, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=CANCEL_ALL_ORDERS, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=GET_ORDERS, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=GET_TRADES, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=POSITION_INFO, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=POSITION_MODE, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=POSITION_LEVERAGE, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=USER_TRADE, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), + RateLimit( + limit_id=FUNDING_PAYMENT, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(API_CONTRACT_GENERAL_LIMIT)], + ), +] + +# Order Statuses +ORDER_STATE = { + "Created": OrderState.PENDING_CREATE, + "Init": OrderState.PENDING_CREATE, + "New": OrderState.OPEN, + "PartiallyFilled": OrderState.PARTIALLY_FILLED, + "Filled": OrderState.FILLED, + "Canceled": OrderState.CANCELED, + "Rejected": OrderState.FAILED, +} + +SUCCESSFUL_RETURN_CODE = 0 +ORDER_NOT_FOUND_ERROR_CODE = 10002 +ORDER_NOT_FOUND_ERROR_MESSAGE = "OM_ORDER_NOT_FOUND" diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_derivative.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_derivative.py new file mode 100644 index 0000000..dc355bb --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_derivative.py @@ -0,0 +1,725 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from async_timeout import timeout + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.derivative.phemex_perpetual import ( + phemex_perpetual_constants as CONSTANTS, + phemex_perpetual_utils, + phemex_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_order_book_data_source import ( + PhemexPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_user_stream_data_source import ( + PhemexPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_auth import PhemexPerpetualAuth +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange_base import bidict +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class PhemexPerpetualDerivative(PerpetualDerivativePyBase): + _position_mode: PositionMode + web_utils = web_utils + SHORT_POLL_INTERVAL = 5.0 + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + LONG_POLL_INTERVAL = 120.0 + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + phemex_perpetual_api_key: str = None, + phemex_perpetual_api_secret: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.phemex_perpetual_api_key = phemex_perpetual_api_key + self.phemex_perpetual_secret_key = phemex_perpetual_api_secret + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._position_mode = None + self._last_trade_history_timestamp = None + super().__init__(client_config_map) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> PhemexPerpetualAuth: + return PhemexPerpetualAuth( + self.phemex_perpetual_api_key, self.phemex_perpetual_secret_key, self._time_synchronizer + ) + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> int: + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.HB_PARTNER_ID + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.EXCHANGE_INFO_URL + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def funding_fee_poll_interval(self) -> int: + return 120 + + async def start_network(self): + """ + Start all required tasks to update the status of the connector. + """ + await super().start_network() + if self.is_trading_required: + await self._get_position_mode() + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector + """ + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def supported_position_modes(self): + """ + This method needs to be overridden to provide the accurate information depending on the exchange. + """ + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + """ + Documentation doesn't make this clear. + To-do: Confirm manually or from their team. + """ + return 'Error: {"code": "401","msg": "401 Request Expired at' in request_exception.args[0] + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return "Order not found for Client ID" in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_FOUND_ERROR_CODE) in str( + cancelation_exception + ) and CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE in str(cancelation_exception) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, time_synchronizer=self._time_synchronizer, domain=self._domain, auth=self._auth + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return PhemexPerpetualAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return PhemexPerpetualAPIUserStreamDataSource( + auth=self._auth, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _status_polling_loop_fetch_updates(self): + await safe_gather( + self._update_order_status(), + self._update_balances(), + self._update_positions(), + ) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all currently active orders. The cancellations are performed in parallel tasks. + + :param timeout_seconds: the maximum time (in seconds) the cancel logic should run + + :return: a list of CancellationResult instances, one for each of the orders to be cancelled + """ + trading_pairs_to_orders_map = {} + tasks = [] + incomplete_orders = [o for o in self.in_flight_orders.values() if not o.is_done] + for order in incomplete_orders: + if trading_pairs_to_orders_map.get(order.trading_pair, None) is None: + trading_pairs_to_orders_map[order.trading_pair] = [] + trading_pairs_to_orders_map[order.trading_pair].append(order.client_order_id) + + for pair in trading_pairs_to_orders_map: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=pair) + tasks.append( + self._api_delete(path_url=CONSTANTS.CANCEL_ALL_ORDERS, params={"symbol": symbol}, is_auth_required=True) + ) + successful_cancellations = [] + failed_cancellations = [] + + try: + async with timeout(timeout_seconds): + cancellation_results = await safe_gather(*tasks, return_exceptions=True) + for trading_pair, cr in zip(trading_pairs_to_orders_map, cancellation_results): + if isinstance(cr, Exception) or cr["code"] != 0: + [ + failed_cancellations.append(CancellationResult(client_order_id, False)) + for client_order_id in trading_pairs_to_orders_map[trading_pair] + ] + else: + [ + successful_cancellations.append(CancellationResult(client_order_id, True)) + for client_order_id in trading_pairs_to_orders_map[trading_pair] + ] + except Exception: + self.logger().network( + "Unexpected error cancelling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order. Check API key and network connection.", + ) + + return successful_cancellations + failed_cancellations + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + if self._position_mode is PositionMode.ONEWAY: + pos_side = "Merged" + else: + if tracked_order.position is PositionAction.OPEN: + pos_side = "Long" if tracked_order.trade_type is TradeType.BUY else "Short" + else: + pos_side = "Short" if tracked_order.trade_type is TradeType.BUY else "Long" + api_params = {"clOrdID": order_id, "symbol": symbol, "posSide": pos_side} + cancel_result = await self._api_delete( + path_url=CONSTANTS.CANCEL_ORDERS, params=api_params, is_auth_required=True + ) + + if cancel_result["code"] != CONSTANTS.SUCCESSFUL_RETURN_CODE: + code = cancel_result["code"] + message = cancel_result["msg"] + raise IOError(f"{code} - {message}") + is_order_canceled = CONSTANTS.ORDER_STATE[cancel_result["data"]["ordStatus"]] == OrderState.CANCELED + + return is_order_canceled + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + + amount_str = f"{amount:f}" + price_str = f"{price:f}" + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = { + "symbol": symbol, + "side": "Buy" if trade_type is TradeType.BUY else "Sell", + "orderQtyRq": amount_str, + "ordType": "Market" if order_type is OrderType.MARKET else "Limit", + "clOrdID": order_id, + "posSide": "Merged", + } + if order_type.is_limit_type(): + api_params["priceRp"] = price_str + if order_type == OrderType.LIMIT_MAKER: + api_params["timeInForce"] = "PostOnly" + elif order_type == OrderType.MARKET: + api_params["timeInForce"] = "ImmediateOrCancel" + if self._position_mode is PositionMode.HEDGE: + api_params["posSide"] = "Long" if trade_type is TradeType.BUY else "Short" + if position_action == PositionAction.CLOSE: + api_params["posSide"] = "Short" if trade_type is TradeType.BUY else "Long" + + order_result = await self._api_post(path_url=CONSTANTS.PLACE_ORDERS, data=api_params, is_auth_required=True) + code = order_result.get("code") + if code == 0 and order_result.get("data", {}).get("orderID", None) is not None: + o_id = str(order_result["data"]["orderID"]) + transact_time = order_result["data"]["actionTimeNs"] * 1e-9 + return o_id, transact_time + else: + raise IOError(f"{code} - {order_result.get('msg')}") + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + # Not required in Phemex because it reimplements _update_orders_fills + return await self._update_orders_fills(orders=[order]) + + async def _all_trades_details(self, trading_pair: str, start_time: float) -> List[Dict[str, Any]]: + result = {} + try: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + result = await self._api_get( + path_url=CONSTANTS.GET_TRADES, + params={"symbol": symbol, "start": int(start_time * 1e3), "limit": 200}, + is_auth_required=True, + ) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().warning(f"There was an error requesting trades history for Phemex ({ex})") + + return result + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + # Reimplementing this method because Phemex does not provide an endpoint to request trades for a particular + # order + + if len(orders) > 0: + orders_by_id = dict() + min_order_creation_time = defaultdict(lambda: self.current_timestamp) + trading_pairs = set() + for order in orders: + orders_by_id[order.client_order_id] = order + trading_pair = order.trading_pair + trading_pairs.add(trading_pair) + min_order_creation_time[trading_pair] = min(min_order_creation_time[trading_pair], order.creation_timestamp) + tasks = [ + safe_ensure_future( + self._all_trades_details(trading_pair=trading_pair, start_time=min_order_creation_time[trading_pair]) + ) + for trading_pair in trading_pairs + ] + + trades_data = [] + results = await safe_gather(*tasks) + for result in results: + for trades_for_market in result.get("data", {}).get("rows", []): + trades_data.append(trades_for_market) + + for trade_info in trades_data: + client_order_id = trade_info["clOrdID"] + tracked_order = orders_by_id.get(client_order_id) + + if tracked_order is not None: + position_action = tracked_order.position + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=CONSTANTS.COLLATERAL_TOKEN, + flat_fees=[ + TokenAmount(amount=Decimal(trade_info["execFeeRv"]), token=CONSTANTS.COLLATERAL_TOKEN) + ], + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_info["execID"], + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_info["orderID"], + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade_info["transactTimeNs"] * 1e-9, + fill_price=Decimal(trade_info["execPriceRp"]), + fill_base_amount=Decimal(trade_info["execQtyRq"]), + fill_quote_amount=Decimal(trade_info["execValueRv"]), + fee=fee, + ) + + self._order_tracker.process_trade_update(trade_update=trade_update) + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + response = await self._api_get( + path_url=CONSTANTS.GET_ORDERS, + params={"symbol": trading_pair, "clOrdID": tracked_order.client_order_id}, + is_auth_required=True, + ) + + orders_data = response.get("data", {}).get("rows", []) + + if len(orders_data) == 0: + raise IOError(f"Order not found for Client ID {tracked_order.client_order_id}") + + order_info = orders_data[0] + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=CONSTANTS.ORDER_STATE[order_info["ordStatus"]], + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_info["orderId"], + ) + return order_update + + async def _user_stream_event_listener(self): + """ + Wait for new messages from _user_stream_tracker.user_stream queue and processes them according to their + message channels. The respective UserStreamDataSource queues these messages. + """ + async for event_message in self._iter_user_event_queue(): + try: + await self._process_user_stream_event(event_message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _process_user_stream_event(self, event_message: Dict[str, Any]): + for balance in event_message.get("accounts_p", []): + total_balance = Decimal(str(balance["accountBalanceRv"])) + locked_balance = Decimal(str(balance["totalUsedBalanceRv"])) + asset = balance["currency"] + self._account_balances[asset] = total_balance + self._account_available_balances[asset] = total_balance - locked_balance + + for trade_info in event_message.get("orders_p", []): + client_order_id = trade_info["clOrdID"] + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + if tracked_order is not None: + position_action = tracked_order.position + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=self.trade_fee_schema(), + position_action=position_action, + percent_token=CONSTANTS.COLLATERAL_TOKEN, + flat_fees=[TokenAmount(amount=Decimal(trade_info["execFeeRv"]), token=CONSTANTS.COLLATERAL_TOKEN)], + ) + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_info["execID"], + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_info["orderID"], + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade_info["transactTimeNs"] * 1e-9, + fill_price=Decimal(trade_info["execPriceRp"]), + fill_base_amount=Decimal(trade_info["execQty"]), + fill_quote_amount=Decimal(trade_info["execValueRv"]), + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update=trade_update) + + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=trade_info["transactTimeNs"] * 1e-9, + new_state=CONSTANTS.ORDER_STATE[trade_info["ordStatus"]], + client_order_id=client_order_id, + exchange_order_id=trade_info["orderID"], + ) + + self._order_tracker.process_order_update(order_update) + + for position in event_message.get("positions_p", []): + total_maint_margin_required = Decimal("0") + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(position["symbol"]) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + if position["posSide"] == "Merged": + position_side: PositionSide = PositionSide.LONG if position["side"] == "Buy" else PositionSide.SHORT + else: + position_side: PositionSide = PositionSide.LONG if position["posSide"] == "Long" else PositionSide.SHORT + position_mode = PositionMode.HEDGE if position["posMode"] == "Hedged" else PositionMode.ONEWAY + amount = Decimal(position["size"]) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side, position_mode) + if amount != Decimal("0"): + _position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=Decimal(position["unrealisedPnlRv"]), + entry_price=Decimal(position["avgEntryPriceRp"]), + amount=amount * Decimal("-1") if position_side is PositionSide.SHORT else amount, + leverage=Decimal(position.get("leverageRr")), + ) + self._perpetual_trading.set_position(pos_key, _position) + else: + self._perpetual_trading.remove_position(pos_key) + total_maint_margin_required += Decimal(position["maintMarginReqRr"]) + if Decimal(position["deleveragePercentileRr"]) > Decimal("0.90"): + negative_pnls_msg = f"{hb_trading_pair}: {position['deleveragePercentileRr']}, " + self.logger().warning( + "Margin Call: Your position risk is too high, and you are at risk of " + "liquidation. Close your positions or add additional margin to your wallet." + ) + self.logger().info( + f"Margin Required: {total_maint_margin_required}. Negative PnL assets: {negative_pnls_msg}." + ) + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + + :param exchange_info_dict: Trading rules dictionary response from the exchange + """ + rules: list = exchange_info_dict.get("data", {}).get("perpProductsV2", []) + return_val: list = [] + for rule in rules: + try: + if web_utils.is_exchange_information_valid(rule): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["symbol"]) + + min_order_size = Decimal(rule["qtyStepSize"]) + step_size = Decimal(rule["qtyStepSize"]) + tick_size = Decimal(rule["tickSize"]) + min_notional = Decimal(rule["minOrderValueRv"]) + collateral_token = rule["settleCurrency"] + + return_val.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=Decimal(tick_size), + min_base_amount_increment=Decimal(step_size), + min_notional_size=Decimal(min_notional), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule: {rule}. Skipping.") + return return_val + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter( + phemex_perpetual_utils.is_exchange_information_valid, + exchange_info.get("data", {}).get("perpProductsV2", []), + ): + exchange_symbol = symbol_data["symbol"] + base = symbol_data["contractUnderlyingAssets"] + quote = symbol_data["settleCurrency"] + trading_pair = combine_to_hb_trading_pair(base, quote) + mapping[exchange_symbol] = trading_pair + self._set_trading_pair_symbol_map(mapping) + + async def _update_balances(self): + """ + Calls the REST API to update total and available balances. + """ + account_info = await self._api_get( + path_url=CONSTANTS.ACCOUNT_INFO, + params={"currency": CONSTANTS.COLLATERAL_TOKEN}, + is_auth_required=True, + ) + + if account_info["code"] != CONSTANTS.SUCCESSFUL_RETURN_CODE: + code = account_info["code"] + message = account_info["msg"] + raise IOError(f"{code} - {message}") + + account_data = account_info["data"]["account"] + + self._account_available_balances.clear() + self._account_balances.clear() + total_balance = Decimal(str(account_data["accountBalanceRv"])) + locked_balance = Decimal(str(account_data["totalUsedBalanceRv"])) + self._account_balances[account_data["currency"]] = total_balance + self._account_available_balances[account_data["currency"]] = total_balance - locked_balance + + async def _request_positions(self): + positions = await self._api_get( + path_url=CONSTANTS.POSITION_INFO, + params={"currency": CONSTANTS.COLLATERAL_TOKEN}, + is_auth_required=True, + ) + return positions if positions is not None else {} + + async def _update_positions(self): + positions = await self._request_positions() + for position in positions.get("data", {}).get("positions", []): + trading_pair = position.get("symbol") + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(trading_pair) + if hb_trading_pair not in self.trading_pairs: + continue + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + + mid_price = self.get_mid_price(hb_trading_pair) + if mid_price != s_decimal_NaN: + + position_mode = PositionMode.HEDGE if position["posMode"] == "Hedged" else PositionMode.ONEWAY + if position["posSide"] == "Merged": + position_side: PositionSide = PositionSide.LONG if position["side"] == "Buy" else PositionSide.SHORT + else: + position_side: PositionSide = PositionSide.LONG if position["posSide"] == "Long" else PositionSide.SHORT + + entry_price = Decimal(position.get("avgEntryPriceRp")) + + price_diff = mid_price - entry_price + amount = Decimal(position.get("size")) + unrealized_pnl = ( + price_diff * amount if position_side == PositionSide.LONG else price_diff * amount * Decimal("-1") + ) + leverage = Decimal(position.get("leverageRr")) + pos_key = self._perpetual_trading.position_key(hb_trading_pair, position_side, position_mode) + if amount != Decimal("0"): + _position = Position( + trading_pair=hb_trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * Decimal("-1") if position_side is PositionSide.SHORT else amount, + leverage=leverage, + ) + self._perpetual_trading.set_position(pos_key, _position) + else: + self._perpetual_trading.remove_position(pos_key) + + async def _get_position_mode(self) -> Optional[PositionMode]: + self._position_mode = PositionMode.ONEWAY + positions = await self._request_positions() + for position in positions.get("data", {}).get("positions", []): + trading_pair = position.get("symbol") + try: + hb_trading_pair = await self.trading_pair_associated_to_exchange_symbol(trading_pair) + if hb_trading_pair in self.trading_pairs: + self._position_mode = PositionMode.HEDGE if position["posMode"] == "Hedged" else PositionMode.ONEWAY + except KeyError: + continue + return self._position_mode + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + response = await self._api_put( + path_url=CONSTANTS.POSITION_MODE, + params={ + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "targetPosMode": "OneWay" if mode == PositionMode.ONEWAY else "Hedged", + }, + is_auth_required=True, + ) + success = False + msg = response["msg"] + if msg == "" and response["code"] == 0: + success = True + self._position_mode = mode + return success, msg + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + if self._position_mode is PositionMode.ONEWAY: + params["leverageRr"] = str(leverage) + else: + params["longLeverageRr"] = str(leverage) + params["shortLeverageRr"] = str(leverage) + response = await self._api_put( + path_url=CONSTANTS.POSITION_LEVERAGE, + params=params, + is_auth_required=True, + ) + success = False + msg = response["msg"] + if msg == "" and response["code"] == 0: + success = True + return success, msg + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + payment_response = await self._api_get( + path_url=CONSTANTS.FUNDING_PAYMENT, + params={ + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "offset": 0, + "limit": 200, + }, + is_auth_required=True, + ) + payments = payment_response.get("data", {}).get("rows", []) + if len(payments) > 0: + funding_payment = payments[0] + payment = Decimal(funding_payment["execFeeRv"]) + funding_rate = Decimal(funding_payment["feeRateRr"]) + timestamp = funding_payment["createTime"] + return timestamp, funding_rate, payment + + async def _get_last_traded_price(self, trading_pair: str) -> float: + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = {"symbol": exchange_symbol, "resolution": 60} + + resp_json = await self._api_get( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL, + params=params, + ) + + price = 0 + kline = resp_json.get("data", {}).get("rows", []) + if len(kline) > 0: + price = float(kline[0][2]) + return price diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_utils.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_utils.py new file mode 100644 index 0000000..b83cd56 --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_utils.py @@ -0,0 +1,82 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.derivative.phemex_perpetual import phemex_perpetual_constants as CONSTANTS +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + percent_fee_token=CONSTANTS.COLLATERAL_TOKEN, + maker_percent_fee_decimal=Decimal("0.0001"), + taker_percent_fee_decimal=Decimal("0.0006"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + status = exchange_info.get("status") + valid = status == "Listed" + return valid + + +class PhemexPerpetualConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="phemex_perpetual", client_data=None) + phemex_perpetual_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Phemex Perpetual API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + phemex_perpetual_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Phemex Perpetual API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + +KEYS = PhemexPerpetualConfigMap.construct() + +OTHER_DOMAINS = ["phemex_perpetual_testnet"] +OTHER_DOMAINS_PARAMETER = {"phemex_perpetual_testnet": "phemex_perpetual_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"phemex_perpetual_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"phemex_perpetual_testnet": [0.01, 0.06]} + + +class PhemexPerpetualTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="phemex_perpetual_testnet", client_data=None) + phemex_perpetual_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Phemex Perpetual testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + phemex_perpetual_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Phemex Perpetual testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "phemex_perpetual" + + +OTHER_DOMAINS_KEYS = {"phemex_perpetual_testnet": PhemexPerpetualTestnetConfigMap.construct()} diff --git a/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_web_utils.py b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_web_utils.py new file mode 100644 index 0000000..fad6a20 --- /dev/null +++ b/hummingbot/connector/derivative/phemex_perpetual/phemex_perpetual_web_utils.py @@ -0,0 +1,101 @@ +from typing import Any, Callable, Dict, Optional + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class PhemexPerpetualRESTPreProcessor(RESTPreProcessorBase): + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + request.headers["Content-Type"] = ( + "application/json" if request.method is not RESTMethod.GET else "application/x-www-form-urlencoded" + ) + return request + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + base_url = CONSTANTS.BASE_URLS[domain] + return base_url + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(path_url=path_url, domain=domain) + + +def wss_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN): + base_ws_url = CONSTANTS.WSS_URLS[domain] + return base_ws_url + endpoint + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or ( + lambda: get_current_server_time( + throttler=throttler, + domain=domain, + ) + ) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + PhemexPerpetualRESTPreProcessor(), + ], + ) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler, rest_pre_processors=[PhemexPerpetualRESTPreProcessor()]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["data"]["serverTime"] + + return server_time + + +def is_exchange_information_valid(rule: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + if rule["type"] == "PerpetualV2" and rule["status"] == "Listed": + valid = True + else: + valid = False + return valid diff --git a/hummingbot/connector/derivative/position.py b/hummingbot/connector/derivative/position.py new file mode 100644 index 0000000..5e27eb4 --- /dev/null +++ b/hummingbot/connector/derivative/position.py @@ -0,0 +1,67 @@ +from decimal import Decimal + +from hummingbot.core.data_type.common import PositionSide + + +class Position: + def __init__(self, + trading_pair: str, + position_side: PositionSide, + unrealized_pnl: Decimal, + entry_price: Decimal, + amount: Decimal, + leverage: Decimal): + self._trading_pair = trading_pair + self._position_side = position_side + self._unrealized_pnl = unrealized_pnl + self._entry_price = entry_price + self._amount = amount + self._leverage = leverage + + def __repr__(self) -> str: + return ( + f"Position(" + f" trading_pair={self._trading_pair}," + f" position_side={self._position_side}," + f" unrealized_pnl={self._unrealized_pnl}," + f" entry_price={self._entry_price}," + f" amount={self._amount}," + f" leverage={self._leverage}" + f")" + ) + + @property + def trading_pair(self) -> str: + return self._trading_pair + + @property + def position_side(self) -> PositionSide: + return self._position_side + + @property + def unrealized_pnl(self) -> Decimal: + return self._unrealized_pnl + + @property + def entry_price(self) -> Decimal: + return self._entry_price + + @property + def amount(self) -> Decimal: + return self._amount + + @property + def leverage(self) -> Decimal: + return self._leverage + + def update_position(self, + position_side: PositionSide = None, + unrealized_pnl: Decimal = None, + entry_price: Decimal = None, + amount: Decimal = None, + leverage: Decimal = None): + self._position_side = position_side if position_side is not None else self._position_side + self._unrealized_pnl = unrealized_pnl if unrealized_pnl is not None else self._unrealized_pnl + self._entry_price = entry_price if entry_price is not None else self._entry_price + self._amount = amount if amount is not None else self._amount + self._leverage = leverage if leverage is not None else self._leverage diff --git a/hummingbot/connector/derivative_base.py b/hummingbot/connector/derivative_base.py new file mode 100644 index 0000000..e08fafb --- /dev/null +++ b/hummingbot/connector/derivative_base.py @@ -0,0 +1,65 @@ +from decimal import Decimal +from typing import TYPE_CHECKING + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import PositionMode + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +NaN = float("nan") +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + + +class DerivativeBase(ExchangeBase): + """ + DerivativeBase provide extra funtionality in addition to the ExchangeBase for derivative exchanges + """ + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + self._funding_info = {} + self._account_positions = {} + self._position_mode = None + self._leverage = {} + self._funding_payment_span = [0, 0] # time span(in seconds) before and after funding period when exchanges consider active positions eligible for funding payment + + def set_position_mode(self, position_mode: PositionMode): + """ + Should set the _position_mode parameter. i.e self._position_mode = position_mode + This should also be overwritten if the derivative exchange requires interraction to set mode, + in addition to setting the _position_mode object. + :param position_mode: ONEWAY or HEDGE position mode + """ + self._position_mode = position_mode + return + + def set_leverage(self, trading_pair: str, leverage: int = 1): + """ + Should set the _leverage parameter. i.e self._leverage = leverage + This should also be overwritten if the derivative exchange requires interraction to set leverage, + in addition to setting the _leverage object. + :param _leverage: leverage to be used + """ + self._leverage = leverage + return + + def supported_position_modes(self): + """ + returns a list containing the modes supported by the derivative + ONEWAY and/or HEDGE modes + """ + return [PositionMode.ONEWAY] + + def get_funding_info(self, trading_pair): + """ + return a dictionary as follows: + self._trading_info[trading_pair] = { + "indexPrice": (i.e "21.169488483519444444") + "markPrice": price used for both pnl on most derivatives (i.e "21.210103847902463671") + "nextFundingTime": next funding time in unix timestamp (i.e "1612780270") + "rate": next funding rate as a decimal and not percentage (i.e 0.00007994084744229488) + } + """ + raise NotImplementedError diff --git a/hummingbot/connector/exchange/__init__.py b/hummingbot/connector/exchange/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/ascend_ex/__init__.py b/hummingbot/connector/exchange/ascend_ex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py new file mode 100644 index 0000000..10ffdc1 --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py @@ -0,0 +1,154 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS, ascend_ex_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange + + +class AscendExAPIOrderBookDataSource(OrderBookTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: "AscendExExchange", + api_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__(trading_pairs) + self._connector = connector + self._trade_messages_queue_key = CONSTANTS.TRADE_TOPIC_ID + self._diff_messages_queue_key = CONSTANTS.DIFF_TOPIC_ID + self._api_factory = api_factory + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = {"symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair)} + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.DEPTH_PATH_URL, + ) + + return data + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + trading_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for topic in [CONSTANTS.DIFF_TOPIC_ID, CONSTANTS.TRADE_TOPIC_ID]: + payload = {"op": CONSTANTS.SUB_ENDPOINT_NAME, "ch": f"{topic}:{trading_symbol}"} + await ws.send(WSJSONRequest(payload=payload)) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", exc_info=True + ) + raise + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{CONSTANTS.WS_URL}/{CONSTANTS.STREAM_PATH_URL}") + return ws + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp = float(snapshot_response["data"]["data"]["ts"]) / 1000 + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": snapshot_timestamp, + "bids": snapshot_response["data"]["data"]["bids"], + "asks": snapshot_response["data"]["data"]["asks"], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, order_book_message_content, snapshot_timestamp + ) + + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["symbol"]) + for trade_data in raw_message["data"]: + timestamp: float = trade_data["ts"] / 1000 + message_content = { + "trade_id": timestamp, # trade id isn't provided so using timestamp instead + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) if trade_data["bm"] else float(TradeType.SELL.value), + "amount": Decimal(trade_data["q"]), + "price": Decimal(trade_data["p"]), + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, content=message_content, timestamp=timestamp + ) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_data: Dict[str, Any] = raw_message["data"] + timestamp: float = diff_data["ts"] / 1000 + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["symbol"]) + + message_content = { + "trading_pair": trading_pair, + "update_id": timestamp, + "bids": diff_data["bids"], + "asks": diff_data["asks"], + } + diff_message: OrderBookMessage = OrderBookMessage(OrderBookMessageType.DIFF, message_content, timestamp) + + message_queue.put_nowait(diff_message) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "data" in event_message: + event_channel = event_message.get("m") + if event_channel == CONSTANTS.TRADE_TOPIC_ID: + channel = self._trade_messages_queue_key + if event_channel == CONSTANTS.DIFF_TOPIC_ID: + channel = self._diff_messages_queue_key + return channel + + async def _process_message_for_unknown_channel( + self, event_message: Dict[str, Any], websocket_assistant: WSAssistant + ): + """ + Processes a message coming from a not identified channel. + Does nothing by default but allows subclasses to reimplement + + :param event_message: the event received through the websocket connection + :param websocket_assistant: the websocket connection to use to interact with the exchange + """ + if event_message.get("m") == "ping": + pong_payloads = {"op": "pong"} + pong_request = WSJSONRequest(payload=pong_payloads) + await websocket_assistant.send(request=pong_request) diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py new file mode 100755 index 0000000..cd6af0f --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py @@ -0,0 +1,81 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange + + +class AscendExAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: AscendExAuth, + trading_pairs: List[str], + connector: "AscendExExchange", + api_factory: WebAssistantsFactory, + ): + super().__init__() + self._ascend_ex_auth: AscendExAuth = auth + self._api_factory = api_factory + self._trading_pairs = trading_pairs or [] + self._connector = connector + self._last_ws_message_sent_timestamp = 0 + + async def _connected_websocket_assistant(self) -> WSAssistant: + group_id = self._connector.ascend_ex_group_id + headers = self._ascend_ex_auth.get_auth_headers(CONSTANTS.STREAM_PATH_URL) + ws_url = f"{CONSTANTS.PRIVATE_WS_URL.format(group_id=group_id)}/{CONSTANTS.STREAM_PATH_URL}" + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=ws_url, ws_headers=headers) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events and balance events. + + :param ws: the websocket assistant used to connect to the exchange + """ + try: + payload = {"op": CONSTANTS.SUB_ENDPOINT_NAME, "ch": "order:cash"} + subscribe_request: WSJSONRequest = WSJSONRequest(payload) + + await websocket_assistant.send(subscribe_request) + + self._last_ws_message_sent_timestamp = self._time() + self.logger().info("Subscribed to private order changes and balance updates channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + if data is not None: # data will be None when the websocket is disconnected + await self._process_event_message( + event_message=data, queue=queue, websocket_assistant=websocket_assistant + ) + + async def _process_event_message( + self, event_message: Dict[str, Any], queue: asyncio.Queue, websocket_assistant: WSAssistant + ): + if len(event_message) > 0: + message_type = event_message.get("m") + if message_type == "ping": + pong_payloads = {"op": "pong"} + pong_request = WSJSONRequest(payload=pong_payloads) + await websocket_assistant.send(request=pong_request) + elif message_type == CONSTANTS.ORDER_CHANGE_EVENT_TYPE and event_message.get("ac") == "CASH": + queue.put_nowait(event_message) diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py new file mode 100755 index 0000000..7e46b04 --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py @@ -0,0 +1,67 @@ +import hashlib +import hmac +import time +from typing import Any, Dict + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class AscendExAuth(AuthBase): + """ + Auth class required by AscendEx API + Learn more at https://ascendex.github.io/ascendex-pro-api/#authenticate-a-restful-request + """ + + def __init__(self, api_key: str, secret_key: str): + self.api_key = api_key + self.secret_key = secret_key + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + # Generates auth headers + path = request.throttler_limit_id + if path != CONSTANTS.BALANCE_HISTORY_PATH_URL: + path = path.replace("cash/", "").replace("spot/", "") + headers_auth = self.get_auth_headers(path) + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(headers_auth) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. AscendEx does not use this + functionality + """ + return request # pass-through + + def get_auth_headers(self, path_url: str, data: Dict[str, Any] = None): + """ + Generates authentication signature and return it in a dictionary along with other inputs + :param path_url: URL of the auth API endpoint + :param data: data to be included in the headers + :return: a dictionary of request info including the request signature + """ + + timestamp = str(int(self._time() * 1e3)) + message = timestamp + path_url + signature = hmac.new(self.secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).hexdigest() + + return { + "x-auth-key": self.api_key, + "x-auth-signature": signature, + "x-auth-timestamp": timestamp, + } + + def _time(self) -> float: + return time.time() diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py new file mode 100644 index 0000000..3bda764 --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py @@ -0,0 +1,141 @@ +# A single source of truth for constant variables related to the exchange +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +# Max order id allowed by AscendEx is 32. We need to configure it in 22 to unify the timestamp and client_id +# components in the order id, because AscendEx uses the last 9 characters or the order id to generate the +# exchange order id (the last 9 characters would be part of the client id if it is not mixed with the timestamp). +# But AscendEx only uses milliseconds for the timestamp in the exchange order id, causing duplicated exchange order ids +# when running strategies with multi level orders created very fast. +MAX_ORDER_ID_LEN = 22 + +PING_TIMEOUT = 15.0 +DEFAULT_DOMAIN = "" +HBOT_ORDER_ID_PREFIX = "HMBot" + +EXCHANGE_NAME = "ascend_ex" +PUBLIC_REST_URL = "https://ascendex.com/api/pro/v1/" +PRIVATE_REST_URL = "https://ascendex.com/{group_id}/api/pro/v1/" +WS_URL = "wss://ascendex.com:443/api/pro/v1/websocket-for-hummingbot-liq-mining" +PRIVATE_WS_URL = "wss://ascendex.com:443/{group_id}/api/pro/v1/websocket-for-hummingbot-liq-mining" + +# REST API ENDPOINTS +ORDER_PATH_URL = "cash/order" +ORDER_BATCH_PATH_URL = "cash/order/batch" +ORDER_OPEN_PATH_URL = "cash/order/open" +ORDER_STATUS_PATH_URL = "cash/order/status" +BALANCE_PATH_URL = "cash/balance" +BALANCE_HISTORY_PATH_URL = "data/v1/cash/balance/history" +HIST_PATH_URL = "cash/order/hist/current" +FEE_PATH_URL = "spot/fee" +TICKER_PATH_URL = "spot/ticker" +PRODUCTS_PATH_URL = "cash/products" +TRADES_PATH_URL = "trades" +DEPTH_PATH_URL = "depth" +INFO_PATH_URL = "info" +STREAM_PATH_URL = "stream" + +SERVER_LIMIT_INFO = "risk-limit-info" + +# WS API ENDPOINTS +SUB_ENDPOINT_NAME = "sub" +PONG_ENDPOINT_NAME = "pong" +TRADE_TOPIC_ID = "trades" +DIFF_TOPIC_ID = "depth" +PING_TOPIC_ID = "ping" +ACCOUNT_TYPE = "CASH" +BALANCE_EVENT_TYPE = "balance" +ORDER_CHANGE_EVENT_TYPE = "order" + +# OrderStates + +ORDER_STATE = { + "PendingNew": OrderState.PENDING_CREATE, + "New": OrderState.OPEN, + "Filled": OrderState.FILLED, + "PartiallyFilled": OrderState.PARTIALLY_FILLED, + "Canceled": OrderState.CANCELED, + "Rejected": OrderState.FAILED, +} + +# AscendEx has multiple pools for API request limits +# Any call increases call rate in ALL pool, so e.g. a cash/order call will contribute to both ALL and cash/order pools. +ALL_ENDPOINTS_LIMIT = "All" +RATE_LIMITS = [ + RateLimit(limit_id=ALL_ENDPOINTS_LIMIT, limit=100, time_interval=1), + RateLimit( + limit_id=ORDER_PATH_URL, limit=50, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=SERVER_LIMIT_INFO, + limit=50, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=ORDER_BATCH_PATH_URL, + limit=50, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=ORDER_OPEN_PATH_URL, + limit=50, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=ORDER_STATUS_PATH_URL, + limit=50, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=BALANCE_PATH_URL, + limit=100, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=BALANCE_HISTORY_PATH_URL, + limit=8, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=HIST_PATH_URL, limit=60, time_interval=60, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=TICKER_PATH_URL, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=PRODUCTS_PATH_URL, + limit=100, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=FEE_PATH_URL, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=TRADES_PATH_URL, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=DEPTH_PATH_URL, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=INFO_PATH_URL, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=SUB_ENDPOINT_NAME, + limit=100, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=PONG_ENDPOINT_NAME, + limit=100, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), +] diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py new file mode 100644 index 0000000..3f5d9f9 --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py @@ -0,0 +1,574 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.ascend_ex import ( + ascend_ex_constants as CONSTANTS, + ascend_ex_utils as utils, + ascend_ex_web_utils as web_utils, +) +from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source import AscendExAPIOrderBookDataSource +from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_user_stream_data_source import ( + AscendExAPIUserStreamDataSource, +) +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class AscendExExchange(ExchangePyBase): + """ + AscendExExchange connects with AscendEx exchange and provides order book pricing, user account tracking and + trading functionality. + """ + + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + ascend_ex_api_key: str, + ascend_ex_secret_key: str, + ascend_ex_group_id: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ): + """ + :param client_config_map: The config map of the client instance. + :param ascend_ex_api_key: The API key to connect to private AscendEx APIs. + :param ascend_ex_secret_key: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + self.ascend_ex_api_key = ascend_ex_api_key + self.ascend_ex_secret_key = ascend_ex_secret_key + self.ascend_ex_group_id = ascend_ex_group_id + self._trading_required = trading_required + self._trading_pairs = trading_pairs + super().__init__(client_config_map=client_config_map) + + self._last_known_sequence_number = 0 + + @property + def domain(self): + return CONSTANTS.DEFAULT_DOMAIN + + @property + def authenticator(self): + return AscendExAuth(self.ascend_ex_api_key, self.ascend_ex_secret_key) + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.PRODUCTS_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.PRODUCTS_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.SERVER_LIMIT_INFO + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + async def get_all_pairs_prices(self) -> Dict[str, Any]: + """ + This method executes a request to the exchange to get the current price for all trades. + It returns the response of the exchange (expected to be used by the AscendEx RateSource for the RateOracle) + + :return: the response from the tickers endpoint + """ + symbol_to_trading_pair_map = await self.trading_pair_symbol_map() + pairs_prices = await self._api_get(path_url=CONSTANTS.TICKER_PATH_URL) + spot_valid_token_entries = [ + data_dict for data_dict in pairs_prices["data"] if data_dict["symbol"] in symbol_to_trading_pair_map + ] + pairs_prices["data"] = spot_valid_token_entries + return pairs_prices + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # API documentation does not clarify the error message for timestamp related problems + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _api_request_url(self, path_url: str, is_auth_required: bool = False) -> str: + url = await super()._api_request_url(path_url, is_auth_required) + + if is_auth_required: + url = url.format(group_id=self.ascend_ex_group_id) + + return url + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory(throttler=self._throttler, auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return AscendExAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return AscendExAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + ) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> AddedToCostTradeFee: + + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data["maker"]) if is_maker else Decimal(fees_data["taker"]) + fee = AddedToCostTradeFee(percent=fee_value) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(utils.is_pair_information_valid, exchange_info.get("data", [])): + if len(symbol_data["symbol"].split("/")) == 2: + base, quote = symbol_data["symbol"].split("/") + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base, quote) + self._set_trading_pair_symbol_map(mapping) + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + side = trade_type.name.lower() + timestamp = utils.get_ms_timestamp() + data = { + "time": timestamp, + "orderQty": str(amount), + "id": order_id, + "side": side, + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + if order_type.is_limit_type(): + data["orderPrice"] = str(price) + data["orderType"] = "limit" + data["timeInForce"] = "GTC" + else: + data["orderType"] = "market" + data["timeInForce"] = "IOC" + if order_type is OrderType.LIMIT_MAKER: + data["postOnly"] = True + exchange_order = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + data=data, + is_auth_required=True, + ) + + if exchange_order.get("code") == 0: + return ( + str(exchange_order["data"]["info"]["orderId"]), + int(exchange_order["data"]["info"].get("timestamp") or exchange_order["data"]["info"] + ["lastExecTime"]) * 1e-3, + ) + else: + raise IOError(str(exchange_order)) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation specific function is called by _cancel, and returns True if successful + """ + exchange_order_id = await tracked_order.get_exchange_order_id() + timestamp = utils.get_ms_timestamp() + data = { + "time": timestamp, + "orderId": exchange_order_id, + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair), + } + cancel_result = await self._api_delete( + path_url=CONSTANTS.ORDER_PATH_URL, + data=data, + is_auth_required=True, + ) + if cancel_result.get("code") == 0: + return True + return False + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + acct_type = event_message.get("ac") + event_subject = event_message.get("m") + execution_data = event_message.get("data") + + # Refer to https://ascendex.github.io/ascendex-pro-api/#channel-order-and-balance + if acct_type == CONSTANTS.ACCOUNT_TYPE and event_subject == CONSTANTS.ORDER_CHANGE_EVENT_TYPE: + order_event_type = execution_data["st"] + order_id: Optional[str] = execution_data.get("orderId") + event_timestamp = execution_data["t"] * 1e-3 + updated_status = CONSTANTS.ORDER_STATE[order_event_type] + + fillable_order_list = list( + filter( + lambda order: order.exchange_order_id == order_id, + list(self._order_tracker.all_fillable_orders.values()), + ) + ) + updatable_order_list = list( + filter( + lambda order: order.exchange_order_id == order_id, + list(self._order_tracker.all_updatable_orders.values()), + ) + ) + + fillable_order = None + if len(fillable_order_list) > 0: + fillable_order = fillable_order_list[0] + + updatable_order = None + if len(updatable_order_list) > 0: + updatable_order = updatable_order_list[0] + + if fillable_order is not None and updated_status in [ + OrderState.PARTIALLY_FILLED, + OrderState.FILLED, + ]: + executed_amount_diff = Decimal(execution_data["cfq"]) - fillable_order.executed_amount_base + execute_price = Decimal(execution_data["ap"]) + fee_asset = execution_data["fa"] + total_order_fee = Decimal(execution_data["cf"]) + current_accumulated_fee = 0 + for fill in fillable_order.order_fills.values(): + current_accumulated_fee += sum( + (fee.amount for fee in fill.fee.flat_fees if fee.token == fee_asset) + ) + + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=fillable_order.trade_type, + percent_token=fee_asset, + flat_fees=[TokenAmount(amount=total_order_fee - current_accumulated_fee, token=fee_asset)], + ) + + trade_update = TradeUpdate( + trade_id=str(execution_data["sn"]), + client_order_id=fillable_order.client_order_id, + exchange_order_id=order_id, + trading_pair=updatable_order.trading_pair, + fee=fee, + fill_base_amount=executed_amount_diff, + fill_quote_amount=executed_amount_diff * execute_price, + fill_price=execute_price, + fill_timestamp=event_timestamp, + ) + self._order_tracker.process_trade_update(trade_update) + + if updatable_order is not None: + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=event_timestamp, + new_state=updated_status, + client_order_id=fillable_order.client_order_id, + exchange_order_id=order_id, + ) + self._order_tracker.process_order_update(order_update=order_update) + + # Update the balance with the balance status details included in the order event + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=execution_data["s"]) + base_asset, quote_asset = split_hb_trading_pair(trading_pair=trading_pair) + self._account_balances.update({base_asset: Decimal(execution_data["btb"])}) + self._account_available_balances.update({base_asset: Decimal(execution_data["bab"])}) + self._account_balances.update({quote_asset: Decimal(execution_data["qtb"])}) + self._account_available_balances.update({quote_asset: Decimal(execution_data["qab"])}) + + # The balance event is not processed because it only sends transfers information + # We need to use the offline balance estimation for AscendEx + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + response = await self._api_get(path_url=CONSTANTS.BALANCE_PATH_URL, is_auth_required=True) + + if response.get("code") == 0: + for balance_entry in response["data"]: + asset_name = balance_entry["asset"] + self._account_available_balances[asset_name] = Decimal(balance_entry["availableBalance"]) + self._account_balances[asset_name] = Decimal(balance_entry["totalBalance"]) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + else: + self.logger().error(f"There was an error during the balance request to AscendEx ({response})") + raise IOError(f"Error requesting balances from AscendEx ({response})") + + async def _format_trading_rules(self, raw_trading_pair_info: Dict[str, Any]) -> List[TradingRule]: + trading_rules = [] + + for info in filter(utils.is_pair_information_valid, raw_trading_pair_info.get("data", [])): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=info.get("symbol")) + trading_rules.append( + TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(info["minQty"]), + max_order_size=Decimal(info["maxQty"]), + min_price_increment=Decimal(info["tickSize"]), + min_base_amount_increment=Decimal(info["lotSize"]), + min_notional_size=Decimal(info["minNotional"]), + ) + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {info}. Skipping.", exc_info=True) + return trading_rules + + async def _update_trading_fees(self): + resp = await self._api_get( + path_url=CONSTANTS.FEE_PATH_URL, + is_auth_required=True, + ) + fees_json = resp.get("data", {}).get("fees", []) + for fee_json in fees_json: + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=fee_json["symbol"]) + self._trading_fees[trading_pair] = fee_json["fee"] + except asyncio.CancelledError: + raise + except Exception: + pass + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + # AscendEx does not have an endpoint to retrieve trades for a particular order + # Thus it overrides the _update_orders_fills method + pass + + def _trade_update_from_fill_data(self, fill_data: Dict[str, Any], order: InFlightOrder) -> TradeUpdate: + trade_id = str(fill_data["sn"]) + timestamp = fill_data["transactTime"] * 1e3 + asset_amount_detail = {} + fee_amount = 0 + fee_asset = order.quote_asset + + for asset_detail in fill_data["data"]: + asset = asset_detail["asset"] + amount = abs(Decimal(str(asset_detail["deltaQty"]))) + if asset_detail["dataType"] == "fee": + fee_asset = asset + fee_amount = amount + else: + asset_amount_detail[asset] = amount + + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=fee_asset, + flat_fees=[TokenAmount(amount=fee_amount, token=fee_asset)], + ) + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=asset_amount_detail[order.base_asset], + fill_quote_amount=asset_amount_detail[order.quote_asset], + fill_price=asset_amount_detail[order.quote_asset] / asset_amount_detail[order.base_asset], + fill_timestamp=timestamp, + ) + + return trade_update + + async def _all_trade_updates_for_orders( + self, orders: List[InFlightOrder], sequence_number: int + ) -> Tuple[List[TradeUpdate], int]: + # This endpoint determines the URL in an adhoc way because it is very different compare to the other endpoints + url = await self._api_request_url(path_url="") + balance_hist_url = url.replace("/v1/", f"/{CONSTANTS.BALANCE_HISTORY_PATH_URL}") + params = {"sn": sequence_number, "limit": 500} + trade_updates = [] + orders_to_process = {order.exchange_order_id: order for order in orders if order.exchange_order_id is not None} + should_request_next_page = True + max_sequence_number = -1 + + # If there are many pages of result, query at most two pages each time, to not delay the update status loop + for _ in range(2): + result = await self._api_get( + path_url=CONSTANTS.BALANCE_HISTORY_PATH_URL, + params=params, + is_auth_required=True, + overwrite_url=balance_hist_url, + ) + + if "order" in result: + for order_fill_data in result["order"]: + max_sequence_number = max(max_sequence_number, order_fill_data["sn"]) + if order_fill_data["orderId"] in orders_to_process: + order_id = order_fill_data["orderId"] + try: + trade_update = self._trade_update_from_fill_data( + fill_data=order_fill_data, order=orders_to_process[order_id] + ) + trade_updates.append(trade_update) + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Failed to fetch trade updates for order {order_id}. Error: {request_error}" + ) + params["sn"] = max_sequence_number + should_request_next_page = len(result["order"]) + len(result.get("balance", [])) == params["limit"] + if not should_request_next_page: + break + else: + self.logger().warning(f"An error occurred when requesting order fills ({result})") + break + + return trade_updates, max_sequence_number + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + if orders: + # Since we are keeping the last order fill sequence number referenced to improve the query performance + # it is necessary to evaluate updates for all possible fillable orders every time (to avoid loosing updates) + candidate_orders = list(self._order_tracker.all_fillable_orders.values()) + try: + if candidate_orders: + trade_updates, max_sequence_number = await self._all_trade_updates_for_orders( + orders=candidate_orders, sequence_number=self._last_known_sequence_number + ) + # Update the _last_known_sequence_number to reduce the amount of information requested next time + self._last_known_sequence_number = max(self._last_known_sequence_number, max_sequence_number) + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update) + except asyncio.CancelledError: + raise + except Exception as request_error: + order_ids = [order.client_order_id for order in candidate_orders] + self.logger().warning(f"Failed to fetch trade updates for orders {order_ids}. Error: {request_error}") + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + exchange_order_id = await tracked_order.get_exchange_order_id() + params = {"orderId": exchange_order_id} + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_STATUS_PATH_URL, params=params, is_auth_required=True + ) + + if updated_order_data.get("code") == 0: + order_update_data = updated_order_data["data"] + ordered_state = order_update_data["status"] + new_state = CONSTANTS.ORDER_STATE[ordered_state] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update_data["orderId"], + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + ) + + return order_update + else: + raise IOError(f"Error requesting status for order {tracked_order.client_order_id} ({updated_order_data})") + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = {"symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair)} + + resp_json = await self._api_request(path_url=CONSTANTS.TICKER_PATH_URL, method=RESTMethod.GET, params=params) + + return float(resp_json["data"]["close"]) diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py new file mode 100644 index 0000000..bf1f40c --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py @@ -0,0 +1,77 @@ +import time +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + + +def is_pair_information_valid(pair_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its market information + + :param pair_info: the market information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + return pair_info.get("statusCode") == "Normal" + + +def get_ms_timestamp() -> int: + return int(_time() * 1e3) + + +class AscendExConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="ascend_ex", client_data=None) + ascend_ex_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your AscendEx API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + ascend_ex_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your AscendEx secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + ascend_ex_group_id: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your AscendEx group Id", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "ascend_ex" + + +KEYS = AscendExConfigMap.construct() + + +def _time(): + """ + Private function created just to have a method that can be safely patched during unit tests and make tests + independent from real time + """ + return time.time() diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_web_utils.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_web_utils.py new file mode 100644 index 0000000..d90aa8f --- /dev/null +++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_web_utils.py @@ -0,0 +1,76 @@ +import time +from typing import Any, Dict, Optional + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class AscendExRESTPreProcessor(RESTPreProcessorBase): + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + # Generates generic headers required by AscendEx + headers_generic = {} + headers_generic["Accept"] = "application/json" + headers_generic["Content-Type"] = "application/json" + # Headers signature to identify user as an HB liquidity provider. + request.headers = dict( + list(request.headers.items()) + list(headers_generic.items()) + list(get_hb_id_headers().items()) + ) + return request + + +def get_hb_id_headers() -> Dict[str, Any]: + """ + Headers signature to identify user as an HB liquidity provider. + + :return: a custom HB signature header + """ + return { + "request-source": "hummingbot-liq-mining", + } + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: domain to connect to + :return: the full URL to the endpoint + """ + return CONSTANTS.PUBLIC_REST_URL + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: the domain to connect to + :return: None, we use overwrite_url instead + """ + return CONSTANTS.PRIVATE_REST_URL + path_url + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory(throttler=throttler, auth=auth, rest_pre_processors=[AscendExRESTPreProcessor()]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> int: + return int(time.time() * 1e3) diff --git a/hummingbot/connector/exchange/binance/__init__.py b/hummingbot/connector/exchange/binance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py b/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py new file mode 100755 index 0000000..1fc8a53 --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_api_order_book_data_source.py @@ -0,0 +1,141 @@ +import asyncio +import time +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.exchange.binance.binance_order_book import BinanceOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange + + +class BinanceAPIOrderBookDataSource(OrderBookTrackerDataSource): + HEARTBEAT_TIME_INTERVAL = 30.0 + TRADE_STREAM_ID = 1 + DIFF_STREAM_ID = 2 + ONE_HOUR = 60 * 60 + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'BinanceExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__(trading_pairs) + self._connector = connector + self._trade_messages_queue_key = CONSTANTS.TRADE_EVENT_TYPE + self._diff_messages_queue_key = CONSTANTS.DIFF_EVENT_TYPE + self._domain = domain + self._api_factory = api_factory + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "limit": "1000" + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self._domain), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SNAPSHOT_PATH_URL, + ) + + return data + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + trade_params = [] + depth_params = [] + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + trade_params.append(f"{symbol.lower()}@trade") + depth_params.append(f"{symbol.lower()}@depth@100ms") + payload = { + "method": "SUBSCRIBE", + "params": trade_params, + "id": 1 + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) + + payload = { + "method": "SUBSCRIBE", + "params": depth_params, + "id": 2 + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + raise + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WSS_URL.format(self._domain), + ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BinanceOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if "result" not in raw_message: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + trade_message = BinanceOrderBook.trade_message_from_exchange( + raw_message, {"trading_pair": trading_pair}) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if "result" not in raw_message: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + order_book_message: OrderBookMessage = BinanceOrderBook.diff_message_from_exchange( + raw_message, time.time(), {"trading_pair": trading_pair}) + message_queue.put_nowait(order_book_message) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "result" not in event_message: + event_type = event_message.get("e") + channel = (self._diff_messages_queue_key if event_type == CONSTANTS.DIFF_EVENT_TYPE + else self._trade_messages_queue_key) + return channel diff --git a/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py b/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py new file mode 100755 index 0000000..69825d0 --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_api_user_stream_data_source.py @@ -0,0 +1,136 @@ +import asyncio +import time +from typing import TYPE_CHECKING, List, Optional + +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.exchange.binance.binance_auth import BinanceAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange + + +class BinanceAPIUserStreamDataSource(UserStreamTrackerDataSource): + + LISTEN_KEY_KEEP_ALIVE_INTERVAL = 1800 # Recommended to Ping/Update listen key to keep connection alive + HEARTBEAT_TIME_INTERVAL = 30.0 + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: BinanceAuth, + trading_pairs: List[str], + connector: 'BinanceExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._auth: BinanceAuth = auth + self._current_listen_key = None + self._domain = domain + self._api_factory = api_factory + + self._listen_key_initialized_event: asyncio.Event = asyncio.Event() + self._last_listen_key_ping_ts = 0 + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + self._manage_listen_key_task = safe_ensure_future(self._manage_listen_key_task_loop()) + await self._listen_key_initialized_event.wait() + + ws: WSAssistant = await self._get_ws_assistant() + url = f"{CONSTANTS.WSS_URL.format(self._domain)}/{self._current_listen_key}" + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + Binance does not require any channel subscription. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + pass + + async def _get_listen_key(self): + rest_assistant = await self._api_factory.get_rest_assistant() + try: + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, + headers=self._auth.header_for_authentication() + ) + except asyncio.CancelledError: + raise + except Exception as exception: + raise IOError(f"Error fetching user stream listen key. Error: {exception}") + + return data["listenKey"] + + async def _ping_listen_key(self) -> bool: + rest_assistant = await self._api_factory.get_rest_assistant() + try: + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain), + params={"listenKey": self._current_listen_key}, + method=RESTMethod.PUT, + return_err=True, + throttler_limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, + headers=self._auth.header_for_authentication() + ) + + if "code" in data: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {data}") + return False + + except asyncio.CancelledError: + raise + except Exception as exception: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {exception}") + return False + + return True + + async def _manage_listen_key_task_loop(self): + try: + while True: + now = int(time.time()) + if self._current_listen_key is None: + self._current_listen_key = await self._get_listen_key() + self.logger().info(f"Successfully obtained listen key {self._current_listen_key}") + self._listen_key_initialized_event.set() + self._last_listen_key_ping_ts = int(time.time()) + + if now - self._last_listen_key_ping_ts >= self.LISTEN_KEY_KEEP_ALIVE_INTERVAL: + success: bool = await self._ping_listen_key() + if not success: + self.logger().error("Error occurred renewing listen key ...") + break + else: + self.logger().info(f"Refreshed listen key {self._current_listen_key}.") + self._last_listen_key_ping_ts = int(time.time()) + else: + await self._sleep(self.LISTEN_KEY_KEEP_ALIVE_INTERVAL) + finally: + self._current_listen_key = None + self._listen_key_initialized_event.clear() + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): + await super()._on_user_stream_interruption(websocket_assistant=websocket_assistant) + self._manage_listen_key_task and self._manage_listen_key_task.cancel() + self._current_listen_key = None + self._listen_key_initialized_event.clear() + await self._sleep(5) diff --git a/hummingbot/connector/exchange/binance/binance_auth.py b/hummingbot/connector/exchange/binance/binance_auth.py new file mode 100644 index 0000000..f8bb760 --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_auth.py @@ -0,0 +1,64 @@ +import hashlib +import hmac +import json +from collections import OrderedDict +from typing import Any, Dict +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class BinanceAuth(AuthBase): + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + if request.method == RESTMethod.POST: + request.data = self.add_auth_to_params(params=json.loads(request.data)) + else: + request.params = self.add_auth_to_params(params=request.params) + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.header_for_authentication()) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. Binance does not use this + functionality + """ + return request # pass-through + + def add_auth_to_params(self, + params: Dict[str, Any]): + timestamp = int(self.time_provider.time() * 1e3) + + request_params = OrderedDict(params or {}) + request_params["timestamp"] = timestamp + + signature = self._generate_signature(params=request_params) + request_params["signature"] = signature + + return request_params + + def header_for_authentication(self) -> Dict[str, str]: + return {"X-MBX-APIKEY": self.api_key} + + def _generate_signature(self, params: Dict[str, Any]) -> str: + + encoded_params_str = urlencode(params) + digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() + return digest diff --git a/hummingbot/connector/exchange/binance/binance_constants.py b/hummingbot/connector/exchange/binance/binance_constants.py new file mode 100644 index 0000000..786d18b --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_constants.py @@ -0,0 +1,114 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +DEFAULT_DOMAIN = "com" + +HBOT_ORDER_ID_PREFIX = "x-XEKWYICX" +MAX_ORDER_ID_LEN = 32 + +# Base URL +REST_URL = "https://api.binance.{}/api/" +WSS_URL = "wss://stream.binance.{}:9443/ws" + +PUBLIC_API_VERSION = "v3" +PRIVATE_API_VERSION = "v3" + +# Public API endpoints or BinanceClient function +TICKER_PRICE_CHANGE_PATH_URL = "/ticker/24hr" +TICKER_BOOK_PATH_URL = "/ticker/bookTicker" +EXCHANGE_INFO_PATH_URL = "/exchangeInfo" +PING_PATH_URL = "/ping" +SNAPSHOT_PATH_URL = "/depth" +SERVER_TIME_PATH_URL = "/time" + +# Private API endpoints or BinanceClient function +ACCOUNTS_PATH_URL = "/account" +MY_TRADES_PATH_URL = "/myTrades" +ORDER_PATH_URL = "/order" +BINANCE_USER_STREAM_PATH_URL = "/userDataStream" + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +# Binance params + +SIDE_BUY = "BUY" +SIDE_SELL = "SELL" + +TIME_IN_FORCE_GTC = "GTC" # Good till cancelled +TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel +TIME_IN_FORCE_FOK = "FOK" # Fill or kill + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +ORDERS = "ORDERS" +ORDERS_24HR = "ORDERS_24HR" +RAW_REQUESTS = "RAW_REQUESTS" + +# Rate Limit time intervals +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 5000 + +# Order States +ORDER_STATE = { + "PENDING": OrderState.PENDING_CREATE, + "NEW": OrderState.OPEN, + "FILLED": OrderState.FILLED, + "PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED, + "PENDING_CANCEL": OrderState.OPEN, + "CANCELED": OrderState.CANCELED, + "REJECTED": OrderState.FAILED, + "EXPIRED": OrderState.FAILED, +} + +# Websocket event types +DIFF_EVENT_TYPE = "depthUpdate" +TRADE_EVENT_TYPE = "trade" + +RATE_LIMITS = [ + # Pools + RateLimit(limit_id=REQUEST_WEIGHT, limit=6000, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS, limit=50, time_interval=10 * ONE_SECOND), + RateLimit(limit_id=ORDERS_24HR, limit=160000, time_interval=ONE_DAY), + RateLimit(limit_id=RAW_REQUESTS, limit=61000, time_interval= 5 * ONE_MINUTE), + # Weighted Limits + RateLimit(limit_id=TICKER_PRICE_CHANGE_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 2), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=TICKER_BOOK_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 4), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=EXCHANGE_INFO_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 20), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=SNAPSHOT_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 100), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=BINANCE_USER_STREAM_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 2), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 1), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=PING_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 1), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 20), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 20), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]), + RateLimit(limit_id=ORDER_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 4), + LinkedLimitWeightPair(ORDERS, 1), + LinkedLimitWeightPair(ORDERS_24HR, 1), + LinkedLimitWeightPair(RAW_REQUESTS, 1)]) +] + +ORDER_NOT_EXIST_ERROR_CODE = -2013 +ORDER_NOT_EXIST_MESSAGE = "Order does not exist" +UNKNOWN_ORDER_ERROR_CODE = -2011 +UNKNOWN_ORDER_MESSAGE = "Unknown order sent" diff --git a/hummingbot/connector/exchange/binance/binance_exchange.py b/hummingbot/connector/exchange/binance/binance_exchange.py new file mode 100755 index 0000000..0aa9fa9 --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_exchange.py @@ -0,0 +1,553 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.binance import ( + binance_constants as CONSTANTS, + binance_utils, + binance_web_utils as web_utils, +) +from hummingbot.connector.exchange.binance.binance_api_order_book_data_source import BinanceAPIOrderBookDataSource +from hummingbot.connector.exchange.binance.binance_api_user_stream_data_source import BinanceAPIUserStreamDataSource +from hummingbot.connector.exchange.binance.binance_auth import BinanceAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import TradeFillOrderDetails, combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class BinanceExchange(ExchangePyBase): + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + binance_api_key: str, + binance_api_secret: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.api_key = binance_api_key + self.secret_key = binance_api_secret + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._last_trades_poll_binance_timestamp = 1.0 + super().__init__(client_config_map) + + @staticmethod + def binance_order_type(order_type: OrderType) -> str: + return order_type.name.upper() + + @staticmethod + def to_hb_order_type(binance_type: str) -> OrderType: + return OrderType[binance_type] + + @property + def authenticator(self): + return BinanceAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + if self._domain == "com": + return "binance" + else: + return f"binance_{self._domain}" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.PING_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + async def get_all_pairs_prices(self) -> List[Dict[str, str]]: + pairs_prices = await self._api_get(path_url=CONSTANTS.TICKER_BOOK_PATH_URL) + return pairs_prices + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = ("-1021" in error_description + and "Timestamp for this request" in error_description) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_EXIST_ERROR_CODE) in str( + status_update_exception + ) and CONSTANTS.ORDER_NOT_EXIST_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.UNKNOWN_ORDER_ERROR_CODE) in str( + cancelation_exception + ) and CONSTANTS.UNKNOWN_ORDER_MESSAGE in str(cancelation_exception) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BinanceAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BinanceAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = order_type is OrderType.LIMIT_MAKER + return DeductedFromReturnsTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + order_result = None + amount_str = f"{amount:f}" + type_str = BinanceExchange.binance_order_type(order_type) + side_str = CONSTANTS.SIDE_BUY if trade_type is TradeType.BUY else CONSTANTS.SIDE_SELL + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"symbol": symbol, + "side": side_str, + "quantity": amount_str, + "type": type_str, + "newClientOrderId": order_id} + if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: + price_str = f"{price:f}" + api_params["price"] = price_str + if order_type == OrderType.LIMIT: + api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTC + + try: + order_result = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + data=api_params, + is_auth_required=True) + o_id = str(order_result["orderId"]) + transact_time = order_result["transactTime"] * 1e-3 + except IOError as e: + error_description = str(e) + is_server_overloaded = ("status is 503" in error_description + and "Unknown error, please check your request or try again later." in error_description) + if is_server_overloaded: + o_id = "UNKNOWN" + transact_time = self._time_synchronizer.time() + else: + raise + return o_id, transact_time + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + api_params = { + "symbol": symbol, + "origClientOrderId": order_id, + } + cancel_result = await self._api_delete( + path_url=CONSTANTS.ORDER_PATH_URL, + params=api_params, + is_auth_required=True) + if cancel_result.get("status") == "CANCELED": + return True + return False + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Example: + { + "symbol": "ETHBTC", + "baseAssetPrecision": 8, + "quotePrecision": 8, + "orderTypes": ["LIMIT", "MARKET"], + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000" + } + ] + } + """ + trading_pair_rules = exchange_info_dict.get("symbols", []) + retval = [] + for rule in filter(binance_utils.is_exchange_information_valid, trading_pair_rules): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule.get("symbol")) + filters = rule.get("filters") + price_filter = [f for f in filters if f.get("filterType") == "PRICE_FILTER"][0] + lot_size_filter = [f for f in filters if f.get("filterType") == "LOT_SIZE"][0] + min_notional_filter = [f for f in filters if f.get("filterType") in ["MIN_NOTIONAL", "NOTIONAL"]][0] + + min_order_size = Decimal(lot_size_filter.get("minQty")) + tick_size = price_filter.get("tickSize") + step_size = Decimal(lot_size_filter.get("stepSize")) + min_notional = Decimal(min_notional_filter.get("minNotional")) + + retval.append( + TradingRule(trading_pair, + min_order_size=min_order_size, + min_price_increment=Decimal(tick_size), + min_base_amount_increment=Decimal(step_size), + min_notional_size=Decimal(min_notional))) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule}. Skipping.") + return retval + + async def _status_polling_loop_fetch_updates(self): + await self._update_order_fills_from_trades() + await super()._status_polling_loop_fetch_updates() + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("e") + # Refer to https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md + # As per the order update section in Binance the ID of the order being canceled is under the "C" key + if event_type == "executionReport": + execution_type = event_message.get("x") + if execution_type != "CANCELED": + client_order_id = event_message.get("c") + else: + client_order_id = event_message.get("C") + + if execution_type == "TRADE": + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + if tracked_order is not None: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=event_message["N"], + flat_fees=[TokenAmount(amount=Decimal(event_message["n"]), token=event_message["N"])] + ) + trade_update = TradeUpdate( + trade_id=str(event_message["t"]), + client_order_id=client_order_id, + exchange_order_id=str(event_message["i"]), + trading_pair=tracked_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(event_message["l"]), + fill_quote_amount=Decimal(event_message["l"]) * Decimal(event_message["L"]), + fill_price=Decimal(event_message["L"]), + fill_timestamp=event_message["T"] * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if tracked_order is not None: + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=event_message["E"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[event_message["X"]], + client_order_id=client_order_id, + exchange_order_id=str(event_message["i"]), + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif event_type == "outboundAccountPosition": + balances = event_message["B"] + for balance_entry in balances: + asset_name = balance_entry["a"] + free_balance = Decimal(balance_entry["f"]) + total_balance = Decimal(balance_entry["f"]) + Decimal(balance_entry["l"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + async def _update_order_fills_from_trades(self): + """ + This is intended to be a backup measure to get filled events with trade ID for orders, + in case Binance's user stream events are not working. + NOTE: It is not required to copy this functionality in other connectors. + This is separated from _update_order_status which only updates the order status without producing filled + events, since Binance's get order endpoint does not return trade IDs. + The minimum poll interval for order status is 10 seconds. + """ + small_interval_last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + small_interval_current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + long_interval_last_tick = self._last_poll_timestamp / self.LONG_POLL_INTERVAL + long_interval_current_tick = self.current_timestamp / self.LONG_POLL_INTERVAL + + if (long_interval_current_tick > long_interval_last_tick + or (self.in_flight_orders and small_interval_current_tick > small_interval_last_tick)): + query_time = int(self._last_trades_poll_binance_timestamp * 1e3) + self._last_trades_poll_binance_timestamp = self._time_synchronizer.time() + order_by_exchange_id_map = {} + for order in self._order_tracker.all_fillable_orders.values(): + order_by_exchange_id_map[order.exchange_order_id] = order + + tasks = [] + trading_pairs = self.trading_pairs + for trading_pair in trading_pairs: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + if self._last_poll_timestamp > 0: + params["startTime"] = query_time + tasks.append(self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params=params, + is_auth_required=True)) + + self.logger().debug(f"Polling for order fills of {len(tasks)} trading pairs.") + results = await safe_gather(*tasks, return_exceptions=True) + + for trades, trading_pair in zip(results, trading_pairs): + + if isinstance(trades, Exception): + self.logger().network( + f"Error fetching trades update for the order {trading_pair}: {trades}.", + app_warning_msg=f"Failed to fetch trade update for {trading_pair}." + ) + continue + for trade in trades: + exchange_order_id = str(trade["orderId"]) + if exchange_order_id in order_by_exchange_id_map: + # This is a fill for a tracked order + tracked_order = order_by_exchange_id_map[exchange_order_id] + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["time"] * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + elif self.is_confirmed_new_order_filled_event(str(trade["id"]), exchange_order_id, trading_pair): + # This is a fill of an order registered in the DB but not tracked any more + self._current_trade_fills.add(TradeFillOrderDetails( + market=self.display_name, + exchange_trade_id=str(trade["id"]), + symbol=trading_pair)) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + timestamp=float(trade["time"]) * 1e-3, + order_id=self._exchange_order_ids.get(str(trade["orderId"]), None), + trading_pair=trading_pair, + trade_type=TradeType.BUY if trade["isBuyer"] else TradeType.SELL, + order_type=OrderType.LIMIT_MAKER if trade["isMaker"] else OrderType.LIMIT, + price=Decimal(trade["price"]), + amount=Decimal(trade["qty"]), + trade_fee=DeductedFromReturnsTradeFee( + flat_fees=[ + TokenAmount( + trade["commissionAsset"], + Decimal(trade["commission"]) + ) + ] + ), + exchange_trade_id=str(trade["id"]) + )) + self.logger().info(f"Recreating missing trade in TradeFill: {trade}") + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = int(order.exchange_order_id) + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "symbol": trading_pair, + "orderId": exchange_order_id + }, + is_auth_required=True, + limit_id=CONSTANTS.MY_TRADES_PATH_URL) + + for trade in all_fills_response: + exchange_order_id = str(trade["orderId"]) + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["time"] * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_PATH_URL, + params={ + "symbol": trading_pair, + "origClientOrderId": tracked_order.client_order_id}, + is_auth_required=True) + + new_state = CONSTANTS.ORDER_STATE[updated_order_data["status"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(updated_order_data["orderId"]), + trading_pair=tracked_order.trading_pair, + update_timestamp=updated_order_data["updateTime"] * 1e-3, + new_state=new_state, + ) + + return order_update + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get( + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + is_auth_required=True) + + balances = account_info["balances"] + for balance_entry in balances: + asset_name = balance_entry["asset"] + free_balance = Decimal(balance_entry["free"]) + total_balance = Decimal(balance_entry["free"]) + Decimal(balance_entry["locked"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(binance_utils.is_exchange_information_valid, exchange_info["symbols"]): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["baseAsset"], + quote=symbol_data["quoteAsset"]) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, + params=params + ) + + return float(resp_json["lastPrice"]) diff --git a/hummingbot/connector/exchange/binance/binance_order_book.py b/hummingbot/connector/exchange/binance/binance_order_book.py new file mode 100644 index 0000000..429ab4e --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_order_book.py @@ -0,0 +1,74 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType +) + + +class BinanceOrderBook(OrderBook): + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": msg["lastUpdateId"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["trading_pair"], + "first_update_id": msg["U"], + "update_id": msg["u"], + "bids": msg["b"], + "asks": msg["a"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["E"] + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["trading_pair"], + "trade_type": float(TradeType.SELL.value) if msg["m"] else float(TradeType.BUY.value), + "trade_id": msg["t"], + "update_id": ts, + "price": msg["p"], + "amount": msg["q"] + }, timestamp=ts * 1e-3) diff --git a/hummingbot/connector/exchange/binance/binance_utils.py b/hummingbot/connector/exchange/binance/binance_utils.py new file mode 100644 index 0000000..b0e47c4 --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_utils.py @@ -0,0 +1,86 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "ZRX-ETH" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), + buy_percent_fee_deducted_from_returns=True +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("status", None) == "TRADING" and "SPOT" in exchange_info.get("permissions", list()) + + +class BinanceConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="binance", const=True, client_data=None) + binance_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + binance_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "binance" + + +KEYS = BinanceConfigMap.construct() + +OTHER_DOMAINS = ["binance_us"] +OTHER_DOMAINS_PARAMETER = {"binance_us": "us"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"binance_us": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"binance_us": DEFAULT_FEES} + + +class BinanceUSConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="binance_us", const=True, client_data=None) + binance_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance US API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + binance_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Binance US API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "binance_us" + + +OTHER_DOMAINS_KEYS = {"binance_us": BinanceUSConfigMap.construct()} diff --git a/hummingbot/connector/exchange/binance/binance_web_utils.py b/hummingbot/connector/exchange/binance/binance_web_utils.py new file mode 100644 index 0000000..9213abb --- /dev/null +++ b/hummingbot/connector/exchange/binance/binance_web_utils.py @@ -0,0 +1,75 @@ +from typing import Callable, Optional + +import hummingbot.connector.exchange.binance.binance_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Binance domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PUBLIC_API_VERSION + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: the Binance domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PRIVATE_API_VERSION + path_url + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["serverTime"] + return server_time diff --git a/hummingbot/connector/exchange/binance/dummy.pxd b/hummingbot/connector/exchange/binance/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/binance/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/binance/dummy.pyx b/hummingbot/connector/exchange/binance/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/binance/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/bitfinex/__init__.py b/hummingbot/connector/exchange/bitfinex/__init__.py new file mode 100644 index 0000000..b9c9e2a --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/__init__.py @@ -0,0 +1,52 @@ +from decimal import Decimal + +BITFINEX_REST_URL_V1 = "https://api.bitfinex.com/v1" +BITFINEX_REST_URL = "https://api-pub.bitfinex.com/v2" +BITFINEX_REST_AUTH_URL = "https://api.bitfinex.com/v2" +BITFINEX_WS_URI = "wss://api-pub.bitfinex.com/ws/2" +BITFINEX_WS_AUTH_URI = "wss://api.bitfinex.com/ws/2" + +# this values ​​set by empirically way, because the bitfinex-market does not have +# these values. maybe later it will be in market-api. +TAKER_FEE = Decimal("0.002") +MAKER_FEE = Decimal("0.001") +AFF_CODE = "-dxCUrjvc" + + +class SubmitOrder: + OID = 0 + + def __init__(self, oid): + self.oid = str(oid) + + @classmethod + def parse(cls, order_snapshot): + return cls(order_snapshot[cls.OID]) + + +class OrderStatus: + """ + full statuses, not all uses. + Order Status: + ACTIVE, + EXECUTED @ PRICE(AMOUNT) e.g. "EXECUTED @ 107.6(-0.2)", + PARTIALLY FILLED @ PRICE(AMOUNT), + CANCELED, + RSN_DUST + RSN_PAUSE + """ + ACTIVE = "ACTIVE" + CANCELED = "CANCELED" + PARTIALLY = "PARTIALLY" + EXECUTED = "EXECUTED" + + +class ContentEventType: + ORDER_UPDATE = "ou" + TRADE_UPDATE = "tu" + TRADE_EXECUTE = "te" + WALLET_SNAPSHOT = "ws" + WALLET_UPDATE = "wu" + HEART_BEAT = "hb" + AUTH = "auth" + INFO = "info" diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pxd b/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pxd new file mode 100644 index 0000000..5de38bc --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pxd @@ -0,0 +1,10 @@ +# distutils: language=c++ +cimport numpy as np + +cdef class BitfinexActiveOrderTracker: + cdef dict _active_bids + cdef dict _active_asks + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message) + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message) + cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pyx b/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pyx new file mode 100644 index 0000000..f3bd202 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_active_order_tracker.pyx @@ -0,0 +1,233 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp + +import logging +import numpy as np +from decimal import Decimal +from typing import Dict + +from hummingbot.logger import HummingbotLogger +from hummingbot.core.data_type.order_book_row import OrderBookRow + +_tracker_logger = None +s_empty_diff = np.ndarray(shape=(0, 4), dtype="float64") + +TRACKING_DICT_TYPE = Dict[Decimal, Dict[str, Dict[str, any]]] + +TYPE_OPEN = "open" +TYPE_CHANGE = "change" +TYPE_MATCH = "match" +TYPE_DONE = "done" +SIDE_BUY = "buy" +SIDE_SELL = "sell" + + +cdef class BitfinexActiveOrderTracker: + + def __init__(self, + active_asks: TRACKING_DICT_TYPE = None, + active_bids: TRACKING_DICT_TYPE = None): + super().__init__() + self._active_asks = active_asks or {} + self._active_bids = active_bids or {} + + @classmethod + def logger(cls) -> HummingbotLogger: + global _tracker_logger + if _tracker_logger is None: + _tracker_logger = logging.getLogger(__name__) + return _tracker_logger + + @property + def active_asks(self) -> TRACKING_DICT_TYPE: + """ + Get all asks on the order book in dictionary format + :returns: Dict[price, Dict[order_id, order_book_message]] + """ + return self._active_asks + + @property + def active_bids(self) -> TRACKING_DICT_TYPE: + """ + Get all bids on the order book in dictionary format + :returns: Dict[price, Dict[order_id, order_book_message]] + """ + return self._active_bids + + def volume_for_ask_price(self, price) -> float: + """ + For a certain price, get the volume sum of all ask order book rows with that price + :returns: volume sum + """ + return sum([float(msg["remaining_size"]) for msg in self._active_asks[price].values()]) + + def volume_for_bid_price(self, price) -> float: + """ + For a certain price, get the volume sum of all bid order book rows with that price + :returns: volume sum + """ + return sum([float(msg["remaining_size"]) for msg in self._active_bids[price].values()]) + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message): + cdef: + dict content = message.content + list bid_entries = content["bids"] + list ask_entries = content["asks"] + double order_id + object price + dict order_dict + double timestamp = message.timestamp + double quantity = 0 + + bids = s_empty_diff + asks = s_empty_diff + + if len(bid_entries) > 0: + bids = np.array( + [ + [ + float(timestamp), + float(price), + float(quantity), + float(message.update_id) + ] + for order_id, price, quantity in bid_entries + ], + dtype="float64", + ndmin=2 + ) + + if len(ask_entries) > 0: + asks = np.array( + [ + [ + float(timestamp), + float(price), + float(quantity), + float(message.update_id) + ] + for order_id, price, quantity in ask_entries + ], + dtype="float64", + ndmin=2 + ) + + return bids, asks + + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message): + """ + Interpret an incoming snapshot message and apply changes to the order book accordingly + :returns: new order book rows: Tuple(np.array (bids), np.array (asks)) + """ + cdef: + object price + double order_id + double amount + dict order_dict + + # Refresh all order tracking. + self._active_bids.clear() + self._active_asks.clear() + for snapshot_orders, active_orders in [(message.content.get("bids", 0), self._active_bids), + (message.content.get("asks", 0), self._active_asks)]: + for order in snapshot_orders: + price = Decimal(order[0]) + order_id = order[2] + amount = order[1] + order_dict = { + "order_id": order_id, + "remaining_size": amount + } + + if price in active_orders: + active_orders[price][order_id] = order_dict + else: + active_orders[price] = { + order_id: order_dict + } + + # Return the sorted snapshot tables. + cdef: + np.ndarray[np.float64_t, ndim=2] bids = np.array( + [ + [ + message.timestamp, + float(price), + sum( + [ + float(order_dict["remaining_size"]) + for order_dict in self._active_bids[price].values() + ] + ), + message.update_id + ] + for price in sorted(self._active_bids.keys(), reverse=True) + ], + dtype="float64", + ndmin=2 + ) + np.ndarray[np.float64_t, ndim=2] asks = np.array( + [ + [ + message.timestamp, + float(price), + sum( + [ + float(order_dict["remaining_size"]) + for order_dict in self._active_asks[price].values() + ] + ), + message.update_id + ] + for price in sorted(self._active_asks.keys(), reverse=True) + ], + dtype="float64", + ndmin=2 + ) + + # If there're no rows, the shape would become (1, 0) and not (0, 4). + # Reshape to fix that. + if bids.shape[1] != 4: + bids = bids.reshape((0, 4)) + if asks.shape[1] != 4: + asks = asks.reshape((0, 4)) + + return bids, asks + + cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message): + """ + Interpret an incoming trade message and apply changes to the order book accordingly + :returns: new order book rows: Tuple[np.array (bids), np.array (asks)] + """ + cdef: + double trade_type_value = 1.0 if message.content["side"] == SIDE_SELL else 2.0 + + return np.array( + [ + message.timestamp, + trade_type_value, + float(message.content["price"]), + float(message.content["size"]) + ], + dtype="float64" + ) + + def convert_snapshot_message_to_order_book_row(self, message): + """ + Convert an incoming snapshot message to Tuple of np.arrays, and then convert to OrderBookRow + :returns: Tuple(List[bids_row], List[asks_row]) + """ + np_bids, np_asks = self.c_convert_snapshot_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row + + def convert_diff_message_to_order_book_row(self, message): + """ + Convert an incoming diff message to Tuple of np.arrays, and then convert to OrderBookRow + :returns: Tuple(List[bids_row], List[asks_row]) + """ + np_bids, np_asks = self.c_convert_diff_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py b/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py new file mode 100644 index 0000000..b6c7016 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_api_order_book_data_source.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python +from collections import namedtuple +import logging +import time +import aiohttp +import asyncio +import ujson +import pandas as pd +from typing import ( + Any, + AsyncIterable, + Dict, + List, + Optional, +) +import websockets +from websockets.exceptions import ConnectionClosed + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.order_book_tracker_entry import ( + OrderBookTrackerEntry +) +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.logger import HummingbotLogger +from hummingbot.connector.exchange.bitfinex import ( + BITFINEX_REST_URL, + BITFINEX_WS_URI, + ContentEventType, +) +from hummingbot.connector.exchange.bitfinex.bitfinex_utils import ( + join_paths, + convert_to_exchange_trading_pair, + convert_from_exchange_trading_pair, +) +from hummingbot.connector.exchange.bitfinex.bitfinex_active_order_tracker import BitfinexActiveOrderTracker +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book import BitfinexOrderBook +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_message import \ + BitfinexOrderBookMessage +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_tracker_entry import \ + BitfinexOrderBookTrackerEntry + +BOOK_RET_TYPE = List[Dict[str, Any]] +RESPONSE_SUCCESS = 200 +NaN = float("nan") +MAIN_FIAT = ("USD", "USDC", "USDS", "DAI", "PAX", "TUSD", "USDT") + +Ticker = namedtuple( + "Ticker", + "bid bid_size ask ask_size daily_change daily_change_percent last_price volume high low" +) +BookStructure = namedtuple("Book", "price count amount") +TradeStructure = namedtuple("Trade", "id mts amount price") +# n0-n9 no documented, we dont' know, maybe later market write docs +ConfStructure = namedtuple("Conf", "n0 n1 n2 min max n5 n6 n7 n8 n9") + + +class BitfinexAPIOrderBookDataSource(OrderBookTrackerDataSource): + MESSAGE_TIMEOUT = 30.0 + STEP_TIME_SLEEP = 1.0 + REQUEST_TTL = 60 * 30 + TIME_SLEEP_BETWEEN_REQUESTS = 5.0 + CACHE_SIZE = 1 + SNAPSHOT_LIMIT_SIZE = 100 + + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pairs: Optional[List[str]] = None): + super().__init__(trading_pairs) + self._trading_pairs: Optional[List[str]] = trading_pairs + # Dictionary that maps Order IDs to book enties (i.e. price, amount, and update_id the + # way it is stored in Hummingbot order book, usually timestamp) + self._tracked_book_entries: Dict[int, OrderBookRow] = {} + + @staticmethod + async def fetch_trading_pairs() -> List[str]: + try: + async with aiohttp.ClientSession() as client: + async with client.get("https://api-pub.bitfinex.com/v2/conf/pub:list:pair:exchange", timeout=10) as response: + if response.status == 200: + data = await response.json() + trading_pair_list: List[str] = [] + for trading_pair in data[0]: + # change the following line accordingly + converted_trading_pair: Optional[str] = \ + convert_from_exchange_trading_pair(trading_pair) + if converted_trading_pair is not None: + trading_pair_list.append(converted_trading_pair) + else: + logging.getLogger(__name__).info(f"Could not parse the trading pair " + f"{trading_pair}, skipping it...") + return trading_pair_list + except Exception: + # Do nothing if the request fails -- there will be no autocomplete available + pass + + return [] + + @staticmethod + def _convert_volume(raw_prices: Dict[str, Any]) -> BOOK_RET_TYPE: + converters = {} + prices = [] + + for price in [v for v in raw_prices.values() if v["quoteAsset"] in MAIN_FIAT]: + raw_symbol = f"{price['baseAsset']}-{price['quoteAsset']}" + symbol = f"{price['baseAsset']}{price['quoteAsset']}" + prices.append( + { + **price, + "symbol": symbol, + "USDVolume": price["volume"] * price["price"] + } + ) + converters[price["baseAsset"]] = price["price"] + del raw_prices[raw_symbol] + + for raw_symbol, item in raw_prices.items(): + symbol = f"{item['baseAsset']}{item['quoteAsset']}" + if item["baseAsset"] in converters: + prices.append( + { + **item, + "symbol": symbol, + "USDVolume": item["volume"] * converters[item["baseAsset"]] + } + ) + if item["quoteAsset"] not in converters: + converters[item["quoteAsset"]] = item["price"] / converters[item["baseAsset"]] + continue + + if item["quoteAsset"] in converters: + prices.append( + { + **item, + "symbol": symbol, + "USDVolume": item["volume"] * item["price"] * converters[item["quoteAsset"]] + } + ) + if item["baseAsset"] not in converters: + converters[item["baseAsset"]] = item["price"] * converters[item["quoteAsset"]] + continue + + prices.append({ + **item, + "symbol": symbol, + "volume": NaN}) + + return prices + + @staticmethod + def _prepare_snapshot(pair: str, raw_snapshot: List[BookStructure]) -> Dict[str, Any]: + """ + Return structure of three elements: + symbol: traded pair symbol + bids: List of OrderBookRow for bids + asks: List of OrderBookRow for asks + """ + update_id = time.time() + bids = [OrderBookRow(i.price, i.amount, update_id) for i in raw_snapshot if i.amount > 0] + asks = [OrderBookRow(i.price, abs(i.amount), update_id) for i in raw_snapshot if i.amount < 0] + + return { + "symbol": pair, + "bids": bids, + "asks": asks, + } + + def _prepare_trade(self, raw_response: str) -> Optional[Dict[str, Any]]: + *_, content = ujson.loads(raw_response) + if content == ContentEventType.HEART_BEAT: + return None + try: + trade = TradeStructure(*content) + except Exception as err: + self.logger().error(err) + self.logger().error(raw_response) + else: + return { + "id": trade.id, + "mts": trade.mts, + "amount": trade.amount, + "price": trade.price, + } + + async def _get_response(self, ws: websockets.WebSocketClientProtocol) -> AsyncIterable[str]: + try: + while True: + msg: str = await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) + yield msg + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + return + except ConnectionClosed: + return + finally: + await ws.close() + + def _generate_delete_message(self, symbol: str, price: float, amount: str): + side_key = "bids" if amount == 1 else "asks" + timestamp = time.time() + msg = { + "symbol": symbol, + side_key: OrderBookRow(price, 0, timestamp), # 0 amount will force the order to be deleted + "update_id": time.time() # Assume every update is incremental + } + return BitfinexOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp) + + def _generate_add_message(self, symbol: str, price: float, amount: float): + side_key = "bids" if amount > 0 else "asks" + timestamp = time.time() + msg = { + "symbol": symbol, + side_key: OrderBookRow(price, abs(amount), timestamp), + "update_id": timestamp # Assume every update is incremental + } + return BitfinexOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp) + + def _parse_raw_update(self, pair: str, raw_response: str) -> OrderBookMessage: + """ + Parses raw update, if price for a tracked order identified by ID is 0, then order is deleted + Returns OrderBookMessage + """ + + *_, content = ujson.loads(raw_response) + + if isinstance(content, list) and len(content) == 3: + price = content[0] + count = content[1] + amount = content[2] + + if count > 0: + return self._generate_add_message(pair, price, amount) + else: + return self._generate_delete_message(pair, price, amount) + + return None + + @classmethod + async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: + tasks = [cls.get_last_traded_price(t_pair) for t_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + @classmethod + async def get_last_traded_price(cls, trading_pair: str) -> float: + async with aiohttp.ClientSession() as client: + # https://api-pub.bitfinex.com/v2/ticker/tBTCUSD + ticker_url: str = join_paths(BITFINEX_REST_URL, f"ticker/{convert_to_exchange_trading_pair(trading_pair)}") + try: + resp = await client.get(ticker_url) + resp_json = await resp.json() + if "error" in resp_json: + raise ValueError(f"There was an error requesting ticker information {trading_pair} ({resp_json})") + ticker = Ticker(*resp_json) + last_price = float(ticker.last_price) + except Exception as ex: + details = resp_json or resp + cls.logger().error( + f"Error encountered requesting ticker information. The response was: {details} ({str(ex)})") + last_price = 0.0 + return last_price + + async def get_trading_pairs(self) -> List[str]: + """ + Get a list of active trading pairs + (if the market class already specifies a list of trading pairs, + returns that list instead of all active trading pairs) + :returns: A list of trading pairs defined by the market class, + or all active trading pairs from the rest API + """ + if not self._trading_pairs: + try: + self._trading_pairs = await self.fetch_trading_pairs() + except Exception: + msg = "Error getting active exchange information. Check network connection." + self._trading_pairs = [] + self.logger().network( + "Error getting active exchange information.", + exc_info=True, + app_warning_msg=msg + ) + + return self._trading_pairs + + async def get_snapshot(self, client: aiohttp.ClientSession, trading_pair: str) -> Dict[str, Any]: + request_url: str = f"{BITFINEX_REST_URL}/book/{convert_to_exchange_trading_pair(trading_pair)}/P0" + # by default it's = 50, 25 asks + 25 bids. + # set 100: 100 asks + 100 bids + # Exchange only allow: 1, 25, 100 ((( + params = { + "len": self.SNAPSHOT_LIMIT_SIZE + } + + async with client.get(request_url, params=params) as response: + response: aiohttp.ClientResponse = response + if response.status != RESPONSE_SUCCESS: + raise IOError(f"Error fetching Bitfinex market snapshot for {trading_pair}. " + f"HTTP status is {response.status}.") + + raw_data: Dict[str, Any] = await response.json() + return self._prepare_snapshot(trading_pair, [BookStructure(*i) for i in raw_data]) + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + async with aiohttp.ClientSession() as client: + snapshot: Dict[str, any] = await self.get_snapshot(client, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitfinexOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp + ) + active_order_tracker: BitfinexActiveOrderTracker = BitfinexActiveOrderTracker() + bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg) + order_book = self.order_book_create_function() + order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) + return order_book + + async def get_tracking_pairs(self) -> Dict[str, OrderBookTrackerEntry]: + result: Dict[str, OrderBookTrackerEntry] = {} + + trading_pairs: List[str] = await self.get_trading_pairs() + number_of_pairs: int = len(trading_pairs) + + async with aiohttp.ClientSession() as client: + for idx, trading_pair in enumerate(trading_pairs): + try: + snapshot: Dict[str, Any] = await self.get_snapshot(client, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitfinexOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp + ) + + order_book: OrderBook = self.order_book_create_function() + active_order_tracker: BitfinexActiveOrderTracker = BitfinexActiveOrderTracker() + order_book.apply_snapshot( + snapshot_msg.bids, + snapshot_msg.asks, + snapshot_msg.update_id + ) + + result[trading_pair] = BitfinexOrderBookTrackerEntry( + trading_pair, snapshot_timestamp, order_book, active_order_tracker + ) + + self.logger().info( + f"Initialized order book for {trading_pair}. " + f"{idx+1}/{number_of_pairs} completed." + ) + await asyncio.sleep(self.STEP_TIME_SLEEP) + except IOError: + self.logger().network( + f"Error getting snapshot for {trading_pair}.", + exc_info=True, + app_warning_msg=f"Error getting snapshot for {trading_pair}. " + "Check network connection." + ) + except Exception: + self.logger().error( + f"Error initializing order book for {trading_pair}. ", + exc_info=True + ) + + return result + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + trading_pairs: List[str] = await self.get_trading_pairs() + + for trading_pair in trading_pairs: + async with websockets.connect(BITFINEX_WS_URI) as ws: + payload: Dict[str, Any] = { + "event": "subscribe", + "channel": "trades", + "symbol": convert_to_exchange_trading_pair(trading_pair), + } + await ws.send(ujson.dumps(payload)) + await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # response + await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # subscribe info + await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # snapshot + + async for raw_msg in self._get_response(ws): + msg = self._prepare_trade(raw_msg) + if msg: + msg_book: OrderBookMessage = BitfinexOrderBook.trade_message_from_exchange( + msg, + metadata={"symbol": f"{trading_pair}"} + ) + output.put_nowait(msg_book) + + except Exception as err: + self.logger().error(err) + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg="Unexpected error with WebSocket connection. " + f"Retrying in {int(self.MESSAGE_TIMEOUT)} seconds. " + "Check network connection." + ) + await asyncio.sleep(5) + + async def listen_for_order_book_diffs(self, + ev_loop: asyncio.BaseEventLoop, + output: asyncio.Queue): + while True: + try: + trading_pairs: List[str] = await self.get_trading_pairs() + + for trading_pair in trading_pairs: + async with websockets.connect(BITFINEX_WS_URI) as ws: + payload: Dict[str, Any] = { + "event": "subscribe", + "channel": "book", + "prec": "P0", + "symbol": convert_to_exchange_trading_pair(trading_pair), + } + await ws.send(ujson.dumps(payload)) + await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # response + await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # subscribe info + raw_snapshot = await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) # snapshot + snapshot = self._prepare_snapshot(trading_pair, [BookStructure(*i) for i in ujson.loads(raw_snapshot)[1]]) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitfinexOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp + ) + output.put_nowait(snapshot_msg) + + async for raw_msg in self._get_response(ws): + msg = self._parse_raw_update(trading_pair, raw_msg) + if msg is not None: + output.put_nowait(msg) + + except Exception as err: + self.logger().error(err) + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg="Unexpected error with WebSocket connection. " + f"Retrying in {int(self.MESSAGE_TIMEOUT)} seconds. " + "Check network connection." + ) + await asyncio.sleep(5) + + async def listen_for_order_book_snapshots(self, + ev_loop: asyncio.BaseEventLoop, + output: asyncio.Queue): + while True: + trading_pairs: List[str] = await self.get_trading_pairs() + + try: + async with aiohttp.ClientSession() as client: + for trading_pair in trading_pairs: + try: + snapshot: Dict[str, Any] = await self.get_snapshot(client, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitfinexOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + + await asyncio.sleep(self.TIME_SLEEP_BETWEEN_REQUESTS) + except asyncio.CancelledError: + raise + except Exception as err: + self.logger().error("Listening snapshots", err) + self.logger().network( + "Unexpected error with HTTP connection.", + exc_info=True, + app_warning_msg="Unexpected error with HTTP connection. " + f"Retrying in {self.TIME_SLEEP_BETWEEN_REQUESTS} sec." + "Check network connection." + ) + await asyncio.sleep(self.TIME_SLEEP_BETWEEN_REQUESTS) + this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace( + minute=0, second=0, microsecond=0 + ) + next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) + delta: float = next_hour.timestamp() - time.time() + await asyncio.sleep(delta) + except asyncio.CancelledError: + raise + except Exception as err: + self.logger().error("Listening snapshots", err) + self.logger().error("Unexpected error", exc_info=True) + await asyncio.sleep(self.TIME_SLEEP_BETWEEN_REQUESTS) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_api_user_stream_data_source.py b/hummingbot/connector/exchange/bitfinex/bitfinex_api_user_stream_data_source.py new file mode 100644 index 0000000..4ec516e --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_api_user_stream_data_source.py @@ -0,0 +1,60 @@ +import asyncio +import logging + +from typing import List, Optional + +from hummingbot.connector.exchange.bitfinex import ContentEventType +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book import BitfinexOrderBook +from hummingbot.connector.exchange.bitfinex.bitfinex_websocket import BitfinexWebsocket +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.logger import HummingbotLogger + + +class BitfinexAPIUserStreamDataSource(UserStreamTrackerDataSource): + MESSAGE_TIMEOUT = 30.0 + + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, bitfinex_auth: BitfinexAuth, trading_pairs: Optional[List[str]] = None): + if trading_pairs is None: + trading_pairs = [] + self._bitfinex_auth: BitfinexAuth = bitfinex_auth + self._trading_pairs = trading_pairs + self._current_listen_key = None + self._listen_for_user_stream_task = None + self._last_recv_time: float = 0 + super().__init__() + + @property + def order_book_class(self): + return BitfinexOrderBook + + @property + def last_recv_time(self) -> float: + return self._last_recv_time + + async def listen_for_user_stream(self, output: asyncio.Queue): + while True: + try: + ws = await BitfinexWebsocket(self._bitfinex_auth).connect() + await ws.authenticate() + + async for msg in ws.messages(): + if msg[1] not in [ContentEventType.HEART_BEAT, ContentEventType.AUTH, ContentEventType.INFO]: + output.put_nowait(msg) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error with Bitfinex WebSocket connection. " "Retrying after 30 seconds...", + exc_info=True, + ) + await asyncio.sleep(self.MESSAGE_TIMEOUT) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py b/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py new file mode 100644 index 0000000..79deb64 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_auth.py @@ -0,0 +1,61 @@ +import hashlib +import hmac +import time + + +class BitfinexAuth(): + def __init__(self, api_key: str, secret_key: str): + self.api_key = api_key + self.secret_key = secret_key + self.last_nonce = 0 + + def _sign_payload(self, payload) -> str: + sig = hmac.new(self.secret_key.encode('utf8'), + payload.encode('utf8'), + hashlib.sha384).hexdigest() + return sig + + def get_nonce(self) -> int: + nonce = int(round(time.time() * 1_000_000)) + + if self.last_nonce == nonce: + nonce = nonce + 1 + elif self.last_nonce > nonce: + nonce = self.last_nonce + 1 + + self.last_nonce = nonce + + return nonce + + def generate_auth_payload(self, payload, nonce = None): + """ + Sign payload + """ + nonce = nonce if nonce is not None else self.get_nonce() + sig = self._sign_payload(payload) + + payload = { + "apiKey": self.api_key, + "authSig": sig, + "authNonce": nonce, + "authPayload": payload, + "event": 'auth', + } + + return payload + + def generate_api_headers(self, path, body): + """ + Generate headers for a signed payload + """ + nonce = str(self.get_nonce()) + signature = "/api/" + path + nonce + body + + sig = self._sign_payload(signature) + + return { + "bfx-nonce": nonce, + "bfx-apikey": self.api_key, + "bfx-signature": sig, + "content-type": "application/json" + } diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pxd b/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pxd new file mode 100644 index 0000000..9be812c --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pxd @@ -0,0 +1,40 @@ +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.data_type.transaction_tracker cimport TransactionTracker + + +cdef class BitfinexExchange(ExchangeBase): + cdef: + object _ev_loop + object _poll_notifier + public object _user_stream_tracker + public object _bitfinex_auth + list trading_pairs + public object _user_stream_tracker_task + TransactionTracker _tx_tracker + + double _last_timestamp + double _last_order_update_timestamp + double _poll_interval + dict _in_flight_orders + dict _trading_rules + dict _order_not_found_records + object _data_source_type + object _coro_queue + public object _status_polling_task + public object _order_tracker_task + public object _coro_scheduler_task + public object _user_stream_event_listener_task + public object _trading_rules_polling_task + public object _shared_client + public list _pending_requests + public object _ws + public object _ws_task + + cdef c_start_tracking_order(self, + str order_id, + str trading_pair, + object trade_type, + object order_type, + object price, + object amount) + cdef c_stop_tracking_order(self, str order_id) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pyx b/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pyx new file mode 100644 index 0000000..1c945c2 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_exchange.pyx @@ -0,0 +1,1368 @@ +import asyncio +import collections +import json +import logging +import time +import uuid +from decimal import Decimal +from typing import Any, AsyncIterable, Dict, List, Optional, TYPE_CHECKING + +import aiohttp +from libc.stdint cimport int64_t + +from hummingbot.connector.exchange.bitfinex import ( + AFF_CODE, + BITFINEX_REST_AUTH_URL, + BITFINEX_REST_URL, + BITFINEX_REST_URL_V1, + ContentEventType, + OrderStatus, +) +from hummingbot.connector.exchange.bitfinex.bitfinex_api_order_book_data_source import BitfinexAPIOrderBookDataSource +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth +from hummingbot.connector.exchange.bitfinex.bitfinex_in_flight_order cimport BitfinexInFlightOrder +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_tracker import BitfinexOrderBookTracker +from hummingbot.connector.exchange.bitfinex.bitfinex_user_stream_tracker import BitfinexUserStreamTracker +from hummingbot.connector.exchange.bitfinex.bitfinex_utils import ( + convert_from_exchange_token, + convert_from_exchange_trading_pair, + convert_to_exchange_trading_pair, + get_precision, +) +from hummingbot.connector.exchange.bitfinex.bitfinex_websocket import BitfinexWebsocket +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.trading_rule cimport TradingRule +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.data_type.transaction_tracker import TransactionTracker +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import estimate_fee +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal(0) +s_decimal_nan = Decimal("nan") + +Wallet = collections.namedtuple('Wallet', + 'wallet_type currency balance unsettled_interest balance_available') + +OrderRetrieved = collections.namedtuple( + "OrderRetrived", + "id gid cid symbol mts_create mts_update " + "amount amount_orig type type_prev n1 n2 " + "flags status n3 n4 price price_exec" +) # 18 + + +cdef class BitfinexExchangeTransactionTracker(TransactionTracker): + cdef: + BitfinexExchange _owner + + def __init__(self, owner: BitfinexExchange): + super().__init__() + self._owner = owner + + cdef c_did_timeout_tx(self, str tx_id): + TransactionTracker.c_did_timeout_tx(self, tx_id) + self._owner.c_did_timeout_tx(tx_id) + + +cdef class BitfinexExchange(ExchangeBase): + MARKET_RECEIVED_ASSET_EVENT_TAG = MarketEvent.ReceivedAsset.value + MARKET_BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value + MARKET_SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value + MARKET_ORDER_CANCELED_EVENT_TAG = MarketEvent.OrderCancelled.value + MARKET_TRANSACTION_FAILURE_EVENT_TAG = MarketEvent.TransactionFailure.value + MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value + MARKET_ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value + MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value + MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value + + API_CALL_TIMEOUT = 10.0 + UPDATE_ORDERS_INTERVAL = 10.0 + ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def __init__(self, + client_config_map: "ClientConfigAdapter", + bitfinex_api_key: str, + bitfinex_secret_key: str, + # interval which the class periodically pulls status from the rest API + poll_interval: float = 5.0, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True): + super().__init__(client_config_map) + + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + + self._trading_required = trading_required + self._bitfinex_auth = BitfinexAuth(bitfinex_api_key, bitfinex_secret_key) + self._set_order_book_tracker(BitfinexOrderBookTracker(trading_pairs)) + self._user_stream_tracker = BitfinexUserStreamTracker( + bitfinex_auth=self._bitfinex_auth, trading_pairs=trading_pairs) + self._tx_tracker = BitfinexExchangeTransactionTracker(self) + + self._last_timestamp = 0 + self._last_order_update_timestamp = 0 + self._poll_interval = poll_interval + self._in_flight_orders = {} + self._trading_rules = {} + self._order_not_found_records = {} + self._status_polling_task = None + self._order_tracker_task = None + self._user_stream_tracker_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._shared_client = None + self._pending_requests = [] + self._ws = BitfinexWebsocket(self._bitfinex_auth) + self._ws_task = None + + @property + def name(self) -> str: + """ + *required + :return: A lowercase name / id for the market. Must stay consistent with market name in global settings. + """ + return "bitfinex" + + @property + def bitfinex_auth(self) -> BitfinexAuth: + """ + """ + return self._bitfinex_auth + + cdef c_tick(self, double timestamp): + """ + *required + Used by top level Clock to orchestrate components of the bot. + This function is called frequently with every clock tick + """ + cdef: + int64_t last_tick = (self._last_timestamp / self._poll_interval) + int64_t current_tick = (timestamp / self._poll_interval) + + ExchangeBase.c_tick(self, timestamp) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + self._last_timestamp = timestamp + + @property + def ready(self) -> bool: + """ + *required + :return: a boolean value that indicates if the market is ready for trading + """ + return all(self.status_dict.values()) + + @property + def order_books(self) -> Dict[str, OrderBook]: + """ + *required + Get mapping of all the order books that are being tracked. + :return: Dict[trading_pair : OrderBook] + """ + return self.order_book_tracker.order_books + + @property + def status_dict(self) -> Dict[str]: + """ + *required + :return: a dictionary of relevant status checks. + This is used by `ready` method below to determine if a market is ready for trading. + """ + return { + # info about bids| ask and other stuffs + "order_books_initialized": self.order_book_tracker.ready, + # info from wallets + "account_balance": len( + self._account_balances) > 0 if self._trading_required else True, + # take info about trading pairs + "trading_rule_initialized": + len(self._trading_rules) > 0 if self._trading_required else True + } + + @property + def in_flight_orders(self) -> Dict[str, BitfinexInFlightOrder]: + return self._in_flight_orders + + async def get_ws(self): + if self._ws._client is None or self._ws._client.open is False: + await self._ws.connect() + await self._ws.authenticate() + + return self._ws + + cdef object c_get_fee(self, + str base_currency, + str quote_currency, + object order_type, + object order_side, + object amount, + object price, + object is_maker = None): + """ + *required + function to calculate fees for a particular order + :returns: TradeFee class that includes fee percentage and flat fees + """ + # There is no API for checking user's fee tier + # Fee info from https://www.bitfinex.com/fees + # cdef: + # object maker_fee = MAKER_FEE + # object taker_fee = TAKER_FEE + + # return TradeFee( + # percent=maker_fee if order_type is OrderType.LIMIT else taker_fee + # ) + + is_maker = order_type is OrderType.LIMIT + return estimate_fee("bitfinex", is_maker) + + async def _request_calc(self, currencies): + await self._ws.emit([ + 0, + "calc", + None, + list(map( + lambda currency: [f"wallet_exchange_{currency}"], + currencies + )) + ]) + + # NOTICE: we only use WS to get balance data due to replay attack protection + # TODO: reduce '_update_balances' timer and re-enable it + async def _update_balances(self): + """ + Pulls the API for updated balances + """ + + currencies = list(self._account_balances.keys()) + await self._request_calc(currencies) + + cdef: + dict account_info + list balances + str asset_name + set local_asset_names = set(self._account_balances.keys()) + set remote_asset_names = set() + set asset_names_to_remove + + account_balances = await self._api_balance() + + # push trading-pairs-info, that set in config + for balance_entry in account_balances: + if balance_entry.wallet_type != "exchange": + continue + + asset_name = balance_entry.currency + asset_name = convert_from_exchange_token(asset_name) + # None or 0 + self._account_balances[asset_name] = Decimal(balance_entry.balance or 0) + self._account_available_balances[asset_name] = Decimal(balance_entry.balance) - Decimal(balance_entry.unsettled_interest) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def check_network(self) -> NetworkStatus: + """ + *required + Async function used by NetworkBase class to check if the market is online / offline. + """ + try: + await self._api_platform_status() + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + async def start_network(self): + """ + *required + Async function used by NetworkBase class to handle when a single market goes online + """ + # when exchange is online start streams + self._order_tracker_task = self.order_book_tracker.start() + if self._trading_required: + self._ws_task = safe_ensure_future(self._ws_message_listener()) + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + async def stop_network(self): + """ + *required + Async wrapper for `self._stop_network`. Used by NetworkBase class to handle when a single market goes offline. + """ + self._stop_network() + + def _stop_network(self): + """ + Synchronous function that handles when a single market goes offline + """ + if self._order_tracker_task is not None: + self._order_tracker_task.cancel() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + self._order_tracker_task = self._status_polling_task = self._user_stream_tracker_task = \ + self._user_stream_event_listener_task = None + if self._ws_task is not None: + self._ws_task.cancel() + + async def _ws_message_listener(self): + while True: + try: + await self._ws.connect() + await self._ws.authenticate() + + async for msg in self._ws.messages(): + pass + + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + "Unexpected error while listeneing to messages.", + exc_info=True, + app_warning_msg=f"Could not listen to Bitfinex messages. " + ) + + async def _status_polling_loop(self): + """ + Background process that periodically pulls for changes from the rest API + """ + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + # await self._update_balances() + await self._update_order_status() + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching account updates.", + exc_info=True, + app_warning_msg=f"Could not fetch account updates on Bitfinex. " + ) + + async def _trading_rules_polling_loop(self): + """ + Separate background process that periodically pulls for trading rule changes + (Since trading rules don't get updated often, it is pulled less often.) + """ + while True: + try: + await safe_gather(self._update_trading_rules()) + await asyncio.sleep(60) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading rules.", + exc_info=True, + app_warning_msg=f"Could not fetch trading rule updates on Bitfinex. " + f"Check network connection." + ) + await asyncio.sleep(0.5) + + async def _api_balance(self): + path_url = "auth/r/wallets" + account_balances = await self._api_private("post", path_url=path_url, data={}) + wallets = [] + for balance_entry in account_balances: + wallets.append(Wallet._make(balance_entry[:5])) + return wallets + + async def _api_platform_status(self): + path_url = "platform/status" + platform_status = await self._api_public("get", path_url=path_url) + return platform_status + + async def _api_platform_config_pair_info(self): + path_url = "symbols_details" + info = await self._api_public_v1("get", path_url=path_url) + return info + + async def _api_public_v1( + self, + http_method: str, + path_url, + data: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + url = f"{BITFINEX_REST_URL_V1}/{path_url}" + req = await self._api_do_request(http_method, url, None, data) + return req + + async def _api_public(self, + http_method: str, + path_url, + data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + + url = f"{BITFINEX_REST_URL}/{path_url}" + req = await self._api_do_request(http_method, url, None, data) + return req + + async def _api_private_fn(self, + http_method: str, + path_url, + data: Optional[Dict[Any]] = None) -> Dict[Any]: + url = f"{BITFINEX_REST_AUTH_URL}/{path_url}" + data_str = json.dumps(data) + # because BITFINEX_REST_AUTH_URL already have v2 postfix, but v2 need + # for generate right signature for path + headers = self.bitfinex_auth.generate_api_headers(f"v2/{path_url}", data_str) + + req = await self._api_do_request(http_method=http_method, + url=url, + headers=headers, + data_str=data) + return req + + # here we queue authenticated rest api calls due to reply attack protection + async def _api_private(self, + http_method: str, + path_url, + data: Optional[Dict[Any]] = None) -> Dict[Any]: + + id = uuid.uuid4() + self._pending_requests.append(id) + + while (self._pending_requests[0] != id): + await asyncio.sleep(0.1) + + try: + return await self._api_private_fn(http_method, path_url, data) + finally: + self._pending_requests.pop(0) + + async def _api_do_request(self, + http_method: str, + url, + headers, + data_str = None) -> list: + """ + A wrapper for submitting API requests to Bitfinex + :returns: json data from the endpoints + """ + + try: + client = await self._http_client() + async with client.request(http_method, + url=url, timeout=self.API_CALL_TIMEOUT, json=data_str, + headers=headers) as response: + data = await response.json() + + if response.status != 200: + raise IOError( + f"Error fetching data from {url}. HTTP status is {response.status}. {data}") + + return data + except Exception as e: + self.logger().network( + f"Failed to do order", + exc_info=True, + app_warning_msg=f"Failed to do order on Bitfinex. Check API key and network connection." + ) + + return None + + async def _http_client(self) -> aiohttp.ClientSession: + """ + :returns: Shared client session instance + """ + if self._shared_client is None: + self._shared_client = aiohttp.ClientSession() + return self._shared_client + + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size): + """ + *required + Get the minimum increment interval for order size (e.g. 0.01 USD) + :return: Min order size increment in Decimal format + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_base_amount_increment + + cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=s_decimal_0): + """ + *required + Note: Bitfinex does not provide API for correct order sizing. Hardcoded 0.05 minimum and 0.01 precision + until better information is available. + :return: Valid order amount in Decimal format + """ + + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + global s_decimal_0 + + quantized_amount = ExchangeBase.c_quantize_order_amount(self, trading_pair, amount) + # Check against min_order_size. If not passing either check, return 0. + if quantized_amount < trading_rule.min_order_size: + return s_decimal_0 + + # Check against max_order_size. If not passing either check, return 0. + if quantized_amount > trading_rule.max_order_size: + return s_decimal_0 + + return quantized_amount + + cdef OrderBook c_get_order_book(self, str trading_pair): + """ + :returns: OrderBook for a specific trading pair + """ + cdef: + dict order_books = self.order_book_tracker.order_books + + if trading_pair not in order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return order_books[trading_pair] + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + """ + *required + Get the minimum increment interval for price + :return: Min order price increment in Decimal format + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + async def _update_trading_rules(self): + """ + Pulls the API for trading rules (min / max order size, etc) + """ + cdef: + int64_t last_tick = (self._last_timestamp / 60.0) + int64_t current_tick = (self._current_timestamp / 60.0) + + if current_tick > last_tick or len(self._trading_rules) <= 0: + info = await self._api_platform_config_pair_info() + trading_rules_list = self._format_trading_rules(info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + + def _format_trading_rules(self, raw_trading_rules: List[Any]) -> List[TradingRule]: + """ + Turns json data from API into TradingRule instances + :returns: List of TradingRule + """ + cdef: + list retval = [] + for rule in raw_trading_rules: + try: + exchange_trading_pair = rule["pair"].upper() + trading_pair = convert_from_exchange_trading_pair(exchange_trading_pair) + precision = get_precision(rule["price_precision"]) + + retval.append( + TradingRule( + trading_pair, + min_price_increment=precision, + min_base_amount_increment=precision, + min_quote_amount_increment=precision, + min_order_size=Decimal(str(rule["minimum_order_size"])), + max_order_size=Decimal(str(rule["maximum_order_size"])), + ) + ) + except Exception: + self.logger().error( + f"Error parsing the trading_pair rule {rule}. Skipping.", + exc_info=True) + return retval + + async def place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + is_buy: bool, + order_type: OrderType, + price: Decimal): + """ + Async wrapper for placing orders through the rest API. + :returns: json response from the API + """ + exchange_trading_pair = convert_to_exchange_trading_pair(trading_pair) + + data = [ + 0, + "on", + None, + { + "type": { + OrderType.LIMIT.name: "EXCHANGE LIMIT", + OrderType.MARKET.name: "MARKET", + }[order_type.name], + "symbol": exchange_trading_pair, + "price": str(price), + "amount": str(amount), + "meta": { + "order_id": order_id, + "aff_code": AFF_CODE + } + } + ] + + def waitFor(msg): + isN = msg[1] == "n" + okEvent = msg[2][1] == "on-req" + okOrderId = msg[2][4][31]["order_id"] == order_id + isSuccess = msg[2][6] == "SUCCESS" + + if isN and okEvent and okOrderId: + if isSuccess: + return True + else: + raise IOError(f"Couldn't place order {order_id}") + + return False + + ws = await self.get_ws() + await ws.emit(data) + + async for response in ws.messages(waitFor=waitFor): + return response + + def start_tracking_order(self, + order_id: str, + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal,): + self.c_start_tracking_order(order_id, trading_pair, order_type, trade_type, price, amount) + + cdef c_start_tracking_order(self, + str client_order_id, + str trading_pair, + object order_type, + object trade_type, + object price, + object amount): + """ + Add new order to self._in_flight_orders mapping + """ + self._in_flight_orders[client_order_id] = BitfinexInFlightOrder( + client_order_id, + None, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp=self.current_timestamp + ) + + cdef str c_buy(self, str trading_pair, object amount, + object order_type=OrderType.MARKET, object price=s_decimal_0, + dict kwargs={}): + """ + *required + Synchronous wrapper that generates a client-side order ID and schedules the buy order. + """ + cdef: + int64_t tracking_nonce = (time.time() * 1e6) + str order_id = str(f"buy-{trading_pair}-{tracking_nonce}") + + safe_ensure_future(self.execute_buy(order_id, trading_pair, amount, order_type, price)) + return order_id + + async def execute_buy(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = s_decimal_0): + """ + Function that takes strategy inputs, auto corrects itself with trading rule, + and submit an API request to place a buy order + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + decimal_amount = self.quantize_order_amount(trading_pair, amount) + decimal_price = self.quantize_order_price(trading_pair, price) + + if decimal_amount < trading_rule.min_order_size: + raise ValueError( + f"Buy order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + self.c_start_tracking_order( + order_id, + trading_pair, + order_type, + TradeType.BUY, + decimal_price, + decimal_amount + ) + order_result = await self.place_order(order_id, trading_pair, + decimal_amount, True, order_type, + decimal_price) + + # TODO: order_result needs to be ID + exchange_order_id = str(order_result[2][4][0]) + + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info( + f"Created {order_type} buy order {order_id} for {decimal_amount} {trading_pair}." + ) + tracked_order.update_exchange_order_id(exchange_order_id) + + self.c_trigger_event( + self.MARKET_BUY_ORDER_CREATED_EVENT_TAG, + BuyOrderCreatedEvent( + self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp + ) + ) + except asyncio.CancelledError: + raise + except Exception: + self.c_stop_tracking_order(order_id) + order_type_str = "MARKET" if order_type == OrderType.MARKET else "LIMIT" + self.logger().network( + f"Error submitting buy {order_type_str} order to Bitfinex for " + f"{decimal_amount} {trading_pair} {price}.", + exc_info=True, + app_warning_msg="Failed to submit buy order to Bitfinex. " + "Check API key and network connection." + ) + self.c_trigger_event( + self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent( + self._current_timestamp, + order_id, order_type + ) + ) + + cdef c_stop_tracking_order(self, str order_id): + """ + Delete an order from self._in_flight_orders mapping + """ + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + cdef str c_sell(self, + str trading_pair, + object amount, + object order_type=OrderType.MARKET, + object price=s_decimal_0, + dict kwargs={}): + """ + *required + Synchronous wrapper that generates a client-side order ID and schedules the sell order. + """ + cdef: + int64_t tracking_nonce = (time.time() * 1e6) + str order_id = str(f"sell-{trading_pair}-{tracking_nonce}") + safe_ensure_future(self.execute_sell(order_id, trading_pair, amount, order_type, price)) + return order_id + + async def execute_sell(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = s_decimal_0): + """ + Function that takes strategy inputs, auto corrects itself with trading rule, + and submit an API request to place a sell order + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + decimal_amount = self.quantize_order_amount(trading_pair, abs(amount)) + decimal_price = self.quantize_order_price(trading_pair, price) + if decimal_amount < trading_rule.min_order_size: + raise ValueError(f"Sell order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + # sell - is negative amount + decimal_amount *= -1 + self.c_start_tracking_order(order_id, trading_pair, order_type, TradeType.SELL, decimal_price, decimal_amount) + order_result = await self.place_order(order_id, trading_pair, decimal_amount, False, order_type, decimal_price) + + # TODO: order_result needs to be ID + exchange_order_id = str(order_result[2][4][0]) + + tracked_order = self._in_flight_orders.get(order_id) + + if tracked_order is not None: + self.logger().info(f"Created {order_type} sell order {order_id} for {decimal_amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + + self.c_trigger_event(self.MARKET_SELL_ORDER_CREATED_EVENT_TAG, + SellOrderCreatedEvent(self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp)) + except asyncio.CancelledError: + raise + except Exception: + self.c_stop_tracking_order(order_id) + order_type_str = "MARKET" if order_type == OrderType.MARKET else "LIMIT" + self.logger().network( + f"Error submitting sell {order_type_str} order to Bitfinex for " + f"{decimal_amount} {trading_pair} {price}.", + exc_info=True, + app_warning_msg="Failed to submit sell order to Bitfinex. " + "Check API key and network connection." + ) + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, order_type)) + + async def execute_cancel(self, trading_pair: str, order_id: str): + """ + Function that makes API request to cancel an active order + """ + try: + exchange_order_id = await self._in_flight_orders.get(order_id).get_exchange_order_id() + + data = [ + 0, + "oc", + None, + { + "id": int(exchange_order_id) + } + ] + + def waitFor(msg): + okEvent = msg[1] == "oc" + okOrderId = msg[2][31]["order_id"] == order_id + + return okEvent and okOrderId + + ws = await self.get_ws() + await ws.emit(data) + + response = None + async for _response in ws.messages(waitFor=waitFor): + response = _response + break + + self.logger().info(f"Successfully canceled order {order_id}.") + self.c_stop_tracking_order(order_id) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, order_id)) + return order_id + + except IOError as e: + if "order not found" in e.message: + # The order was never there to begin with. So cancelling it is a no-op but semantically successful. + self.logger().info(f"The order {order_id} does not exist on Bitfinex. No cancelation needed.") + self.c_stop_tracking_order(order_id) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, order_id)) + return order_id + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Failed to cancel order {order_id}: {str(e)}", + exc_info=True, + app_warning_msg=f"Failed to cancel the order {order_id} on Bitfinex. " + f"Check API key and network connection." + ) + return None + + cdef c_cancel(self, str trading_pair, str order_id): + """ + *required + Synchronous wrapper that schedules cancelling an order. + """ + safe_ensure_future(self.execute_cancel(trading_pair, order_id)) + return order_id + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, Any]]: + """ + Iterator for incoming messages from the user stream. + """ + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unknown error. Retrying after 1 seconds.", exc_info=True) + await asyncio.sleep(1.0) + + def parse_message_content(self, oid, _type, content=None): + # listen only this events + if _type not in [ContentEventType.TRADE_UPDATE]: + return + data = { + "trade_id": "", + "type": _type, + "order_id": 0, + "maker_order_id": 0, + "taker_order_id": 0, + "price": 0, + "amount": 0, + "reason": "", + } + + # [CHAN_ID, TYPE, [ID, SYMBOL, MTS_CREATE, ORDER_ID, EXEC_AMOUNT.... + # [0, 'te', [40xx, 'tETHUSD', 157613xxxx, 3566953xxx, 0.04, ..... + # .. EXEC_PRICE, ORDER_TYPE, ORDER_PRICE, MAKER, FEE, FEE_CURRENCY, CID]] + # .. 142.88, 'EXCHANGE LIMIT', 142.88, 1, None, None, 1576132037108]] + if _type == ContentEventType.TRADE_UPDATE: + data["order_id"] = content[3] + # if amount is negative it mean sell, if positive is's buy. + # zero no can, because minimal step is present. fot eth is 0.04 + # maker_order_id - this “makes” the marketplace; like products on + # a store shelf - buy + # taker_order_id - “taker” consumes the book liquidity by ‘taking’ + # an order from the order book - sell + data["maker_order_id"] = content[3] if content[4] > 0 else None + data["taker_order_id"] = content[3] if content[4] < 0 else None + data["trade_id"] = content[0] + data["price"] = content[5] + data["amount"] = content[4] + data["fee"] = content[9] + data["fee_currency"] = content[10] + + return data + + async def _user_stream_event_listener(self): + """ + Update order statuses from incoming messages from the user stream + """ + async for event_message in self._iter_user_event_queue(): + try: + isWallet = event_message[1] in [ContentEventType.WALLET_SNAPSHOT, ContentEventType.WALLET_UPDATE] + + # update balances + if isWallet: + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + asset_names_to_remove = set() + + event_type = event_message[1] + content = event_message[2] + wallets = content if event_type == ContentEventType.WALLET_SNAPSHOT else [content] + + for wallet in wallets: + wallet_type = wallet[0] + if (wallet_type != "exchange"): + continue + + asset_name = convert_from_exchange_token(wallet[1]) + balance = wallet[2] + balance_available = wallet[4] + + self._account_balances[asset_name] = Decimal(balance or 0) + self._account_available_balances[asset_name] = Decimal(balance_available or 0) + # remote_asset_names.add(asset_name) + + # asset_names_to_remove = local_asset_names.difference(remote_asset_names) + # for asset_name in asset_names_to_remove: + # del self._account_available_balances[asset_name] + # del self._account_balances[asset_name] + + # else (previous author) + else: + self._process_trade_event(event_message) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await asyncio.sleep(5.0) + + def _process_trade_event(self, event_message: List[Any]): + content = self.parse_message_content(*event_message) + if not content: + return + event_type = content.get("type") + # str - because from exchange come int; order.exchange_order_id is str + exchange_order_ids = [ + str(content.get("order_id")), + str(content.get("maker_order_id")), + str(content.get("taker_order_id")) + ] + + tracked_order = None + for order in self._in_flight_orders.values(): + if order.exchange_order_id in exchange_order_ids: + tracked_order = order + break + if tracked_order is None: + return + + order_type_description = tracked_order.order_type_description + execute_price = Decimal(content.get("price", 0.0)) + execute_amount_diff = s_decimal_0 + + # trade update is like rollup state. each event increment + # amount and price. When amount is 0, it will meant order fill. + if event_type in [ContentEventType.TRADE_UPDATE]: + updated = tracked_order.update_with_trade_update(content) + + if updated: + amount_come = Decimal(str(content["amount"])) + execute_amount_diff = (abs(tracked_order.amount) - abs(amount_come)).quantize(Decimal('1e-8')) + + self.logger().info( + f"Order filled {amount_come} out of {tracked_order.amount} of the " + f"{order_type_description} order {tracked_order.client_order_id}" + ) + self.c_trigger_event( + self.MARKET_ORDER_FILLED_EVENT_TAG, + OrderFilledEvent( + self._current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + execute_price, + tracked_order.executed_amount_base, + AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(content.get("fee"))))] + ), + exchange_trade_id=str(content["trade_id"]) + ) + ) + + if tracked_order.is_done and not tracked_order.is_cancelled: + if tracked_order.trade_type == TradeType.BUY: + event_type = self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG + event_class = BuyOrderCompletedEvent + else: + event_type = self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG + event_class = SellOrderCompletedEvent + + self.logger().info( + f"The market {tracked_order.trade_type.name.lower()} " + f"order {tracked_order.client_order_id} has completed " + "according to Bitfinex user stream." + ) + + self.c_trigger_event( + event_type, + event_class( + self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type + ) + ) + self.c_stop_tracking_order(tracked_order.client_order_id) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + try: + tracked_orders = self._in_flight_orders.copy().values() + client_oids = list(map(lambda order: order.client_order_id, tracked_orders)) + exchange_oids = list(map(lambda order: int(order.exchange_order_id), tracked_orders)) + + data = [ + 0, + "oc_multi", + None, + { + "id": exchange_oids + } + ] + + def waitFor(msg): + return msg[1] == "n" and msg[2][1] == "oc_multi-req" + + ws = await self.get_ws() + await ws.emit(data) + + response = None + cancellation_results = [] + async for _response in ws.messages(waitFor=waitFor): + cancelled_client_oids = [o[-1]['order_id'] for o in _response[2][4]] + self.logger().info(f"Succesfully canceled orders: {cancelled_client_oids}") + for c_oid in cancelled_client_oids: + cancellation_results.append(CancellationResult(c_oid, True)) + break + + return cancellation_results + except Exception as e: + self.logger().network( + f"Failed to cancel all orders: {client_oids}", + exc_info=True, + app_warning_msg=f"Failed to cancel all orders on Bitfinex. Check API key and network connection." + ) + return list(map(lambda client_order_id: CancellationResult(client_order_id, False), client_oids)) + + @property + def limit_orders(self) -> List[LimitOrder]: + """ + *required + :return: list of active limit orders + """ + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + # sqlite + @property + def tracking_states(self) -> Dict[str, any]: + """ + *required + :return: Dict[client_order_id: InFlightOrder] + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + return { + key: value.to_json() + for key, value in self._in_flight_orders.items() + } + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + *required + Updates inflight order statuses from API results + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + self._in_flight_orders.update({ + key: BitfinexInFlightOrder.from_json(value) + for key, value in saved_states.items() + }) + + # list of active orders + async def list_orders(self) -> List[OrderRetrieved]: + """ + Gets a list of the user's active orders via rest API + :returns: json response + """ + path_url = "auth/r/orders" + result = await self._api_private("post", path_url=path_url, data={}) + orders = [OrderRetrieved._make(res[:18]) for res in result] + return orders + + # history list of orders + async def list_orders_history(self) -> List[OrderRetrieved]: + """ + Gets a list of the user's active orders via rest API + :returns: json response + """ + path_url = "auth/r/orders/hist" + result = await self._api_private("post", path_url=path_url, data={}) + orders = [OrderRetrieved._make(res[:18]) for res in result] + return orders + + def calculate_fee(self, price, amount, _type, order_type): + # fee_percent dependent only from order_type + fee_percent = self.c_get_fee(None, None, order_type, None, None, None).percent + if _type == TradeType.BUY: + fee = price * fee_percent + else: + fee = price * Decimal(abs(amount)) * fee_percent + return fee + + async def _update_order_status(self): + """ + Pulls the rest API for for latest order statuses and update local order statuses. + """ + cdef: + dict order_dict + double current_timestamp = self._current_timestamp + + if current_timestamp - self._last_order_update_timestamp <= self.UPDATE_ORDERS_INTERVAL: + return + tracked_orders = list(self._in_flight_orders.values()) + active_orders = await self.list_orders() + inactive_orders = await self.list_orders_history() + all_orders = active_orders + inactive_orders + order_dict = dict((str(order.id), order) for order in all_orders) + + for tracked_order in tracked_orders: + client_order_id = tracked_order.client_order_id + exchange_order_id = tracked_order.exchange_order_id + order_update = order_dict.get(str(exchange_order_id)) + + if order_update is None: + self._order_not_found_records[client_order_id] = \ + self._order_not_found_records.get(client_order_id, 0) + 1 + + if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT: + # Wait until the order not found error have repeated for a few times before actually treating + # it as a fail. See: https://github.com/CoinAlpha/hummingbot/issues/601 + continue + + tracked_order.last_state = OrderStatus.CANCELED + self.c_trigger_event( + self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, + client_order_id, + tracked_order.order_type) + ) + self.c_stop_tracking_order(client_order_id) + self.logger().network( + f"Error fetching status update for the order {client_order_id}: " + f"{tracked_order}", + app_warning_msg=f"Could not fetch updates for the order {client_order_id}. " + f"Check API key and network connection." + ) + continue + + # Calculate the newly executed amount for this update. + original_amount = Decimal(abs(order_update.amount_orig)) + rest_amount = Decimal(abs(order_update.amount)) + base_execute_amount_diff = original_amount - rest_amount + base_execute_price = Decimal(order_update.price_exec) + + client_order_id = tracked_order.client_order_id + order_type_description = tracked_order.order_type_description + order_type = OrderType.MARKET if tracked_order.order_type == OrderType.MARKET else OrderType.LIMIT + + # Emit event if executed amount is greater than 0. + if base_execute_amount_diff > s_decimal_0: + order_filled_event = OrderFilledEvent( + self._current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + order_type, + base_execute_price, + base_execute_amount_diff, + self.c_get_fee( + tracked_order.base_asset, + tracked_order.quote_asset, + order_type, + tracked_order.trade_type, + base_execute_amount_diff, + base_execute_price, + ), + exchange_trade_id=str(int(self._time() * 1e6)), + ) + self.logger().info(f"Filled {base_execute_amount_diff} out of {tracked_order.amount} of the " + f"{order_type_description} order {client_order_id}.") + self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event) + + # Update the tracked order + tracked_order.set_status(order_update.status) + tracked_order.executed_amount_base = base_execute_amount_diff + tracked_order.executed_amount_quote = base_execute_amount_diff * base_execute_price + tracked_order.fee_paid = self.calculate_fee(base_execute_amount_diff, + order_update.price_exec, + tracked_order.trade_type, + tracked_order.order_type + ) + if tracked_order.is_done: + if not tracked_order.is_failure: + if tracked_order.trade_type == TradeType.BUY: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + order_type)) + else: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + order_type)) + else: + self.logger().info(f"The market order {tracked_order.client_order_id} has failed/been canceled " + f"according to order status API.") + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent( + self._current_timestamp, + tracked_order.client_order_id + )) + + self.c_stop_tracking_order(tracked_order.client_order_id) + self._last_order_update_timestamp = current_timestamp + + def get_price(self, trading_pair: str, is_buy: bool) -> Decimal: + return self.c_get_price(trading_pair, is_buy) + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_nan, **kwargs) -> str: + return self.c_buy(trading_pair, amount, order_type, price, kwargs) + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_nan, **kwargs) -> str: + return self.c_sell(trading_pair, amount, order_type, price, kwargs) + + def cancel(self, trading_pair: str, client_order_id: str): + return self.c_cancel(trading_pair, client_order_id) + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_nan, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + return self.c_get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + def get_order_book(self, trading_pair: str) -> OrderBook: + return self.c_get_order_book(trading_pair) + + async def all_trading_pairs(self) -> List[str]: + # This method should be removed and instead we should implement _initialize_trading_pair_symbol_map + return await BitfinexAPIOrderBookDataSource.fetch_trading_pairs() + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + # This method should be removed and instead we should implement _get_last_traded_price + return await BitfinexAPIOrderBookDataSource.get_last_traded_prices(trading_pairs=trading_pairs) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pxd b/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pxd new file mode 100644 index 0000000..4c81261 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pxd @@ -0,0 +1,5 @@ +from hummingbot.connector.in_flight_order_base cimport InFlightOrderBase + +cdef class BitfinexInFlightOrder(InFlightOrderBase): + cdef: + object trade_id_set diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pyx b/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pyx new file mode 100644 index 0000000..fc6125f --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_in_flight_order.pyx @@ -0,0 +1,116 @@ +from decimal import Decimal +from typing import ( + Any, + Dict, + Optional, +) + +from hummingbot.connector.exchange.bitfinex import OrderStatus +from hummingbot.connector.exchange.bitfinex.bitfinex_utils import convert_from_exchange_token, split_trading_pair +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import ( + OrderType, + TradeType, +) + +cdef class BitfinexInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + initial_state: str = OrderStatus.ACTIVE): + + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp, + initial_state, + ) + + self.trade_id_set = set() + + @property + def is_open(self) -> bool: + if self.last_state.startswith("PARTIALLY"): + return True + return self.last_state in {OrderStatus.ACTIVE} + + @property + def is_done(self) -> bool: + return self.last_state in {OrderStatus.EXECUTED, OrderStatus.CANCELED} + + @property + def is_failure(self) -> bool: + # This is the only known canceled state + return self.last_state == OrderStatus.CANCELED + + @property + def is_cancelled(self) -> bool: + return self.last_state == OrderStatus.CANCELED + + @property + def order_type_description(self) -> str: + """ + :return: Order description string . One of ["limit buy" / "limit sell" / "market buy" / "market sell"] + """ + order_type = "market" if self.order_type is OrderType.MARKET else "limit" + side = "buy" if self.trade_type == TradeType.BUY else "sell" + return f"{order_type} {side}" + + def set_status(self, order_status: str): + statuses = list(filter( + lambda s: order_status.startswith(s), + [ + OrderStatus.ACTIVE, + OrderStatus.CANCELED, + OrderStatus.PARTIALLY, + OrderStatus.EXECUTED, + ] + )) + + if (len(statuses) < 1): + raise Exception(f"status not found for order_status {order_status}") + + self.last_state = statuses[0] + + @property + def base_asset(self) -> str: + return split_trading_pair(self.trading_pair)[0] + + @property + def quote_asset(self) -> str: + return split_trading_pair(self.trading_pair)[1] + + def update_with_trade_update(self, trade_update: Dict[str, Any]) -> bool: + """ + Updates the in flight order with trade update (from GET /trade_history end point) + return: True if the order gets updated otherwise False + """ + trade_id = trade_update["trade_id"] + if str(trade_update["order_id"]) != self.exchange_order_id or trade_id in self.trade_id_set: + return False + self.trade_id_set.add(trade_id) + trade_amount = abs(Decimal(str(trade_update["amount"]))) + trade_price = Decimal(str(trade_update.get("price", 0.0))) + quote_amount = trade_amount * trade_price + + self.executed_amount_base += trade_amount + self.executed_amount_quote += quote_amount + self.fee_paid += Decimal(str(trade_update.get("fee"))) + self.fee_asset = convert_from_exchange_token(trade_update["fee_currency"]) + + if (abs(self.amount) - self.executed_amount_base).quantize(Decimal('1e-8')) <= 0: + self.last_state = OrderStatus.EXECUTED + else: + self.last_state = OrderStatus.PARTIALLY + return True diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pxd b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pxd new file mode 100644 index 0000000..9ea6723 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pxd @@ -0,0 +1,5 @@ +from hummingbot.core.data_type.order_book cimport OrderBook + + +cdef class BitfinexOrderBook(OrderBook): + pass diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pyx b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pyx new file mode 100644 index 0000000..55ad0f4 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book.pyx @@ -0,0 +1,83 @@ +import logging +from typing import Any, Dict, List, Optional + +import pandas as pd + +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_message import BitfinexOrderBookMessage +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.logger import HummingbotLogger + +_logger = None + + +cdef class BitfinexOrderBook(OrderBook): + + @classmethod + def logger(cls) -> HummingbotLogger: + global _logger + + if _logger is None: + _logger = logging.getLogger(__name__) + + return _logger + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, Any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return BitfinexOrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=msg, + timestamp=timestamp + ) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + if "time" in msg: + msg_time = pd.Timestamp(msg["time"]).timestamp() + + return BitfinexOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp or msg_time) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, Any], metadata: Optional[Dict] = None): + if metadata: + msg.update(metadata) + + timestamp = msg["mts"] + trade_type = TradeType.SELL if int(msg["amount"]) < 0 else TradeType.BUY + return BitfinexOrderBookMessage( + OrderBookMessageType.TRADE, + { + "trading_pair": msg["symbol"], + "trade_type": float(trade_type.value), + "trade_id": msg["id"], + "update_id": timestamp, + "price": msg["price"], + "amount": abs(msg["amount"]), + }, + timestamp= timestamp * 1e-3 + ) + + @classmethod + def from_snapshot(cls, snapshot: OrderBookMessage): + raise NotImplementedError("Bitfinex order book needs to retain individual order data.") + + @classmethod + def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): + raise NotImplementedError("Bitfinex order book needs to retain individual order data.") diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_message.py b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_message.py new file mode 100644 index 0000000..ce15970 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_message.py @@ -0,0 +1,47 @@ +from typing import ( + Dict, + Optional, +) + +import pandas as pd + +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) + + +class BitfinexOrderBookMessage(OrderBookMessage): + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + if timestamp is None: + if message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError( + "timestamp must not be None when initializing snapshot messages.") + + timestamp = pd.Timestamp(content["time"], tz="UTC").timestamp() + + return super(BitfinexOrderBookMessage, cls).__new__( + cls, message_type, content, timestamp=timestamp, *args, **kwargs + ) + + @property + def update_id(self) -> int: + return int(self.timestamp) + + @property + def trade_id(self) -> int: + return self.content["trade_id"] + + @property + def trading_pair(self) -> str: + if "trading_pair" in self.content: + return self.content["trading_pair"] + elif "symbol" in self.content: + return self.content["symbol"] diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py new file mode 100644 index 0000000..d6d9088 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker.py @@ -0,0 +1,219 @@ +import asyncio +import bisect +import logging +import time + +from collections import defaultdict, deque +from typing import Deque, Dict, List, Optional, Set + +from hummingbot.connector.exchange.bitfinex.bitfinex_active_order_tracker import BitfinexActiveOrderTracker +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book import BitfinexOrderBook +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_message import BitfinexOrderBookMessage +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_tracker_entry import BitfinexOrderBookTrackerEntry +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + +from .bitfinex_api_order_book_data_source import BitfinexAPIOrderBookDataSource + +SAVED_MESSAGES_QUEUE_SIZE = 1000 +CALC_STAT_MINUTE = 60.0 + +QUEUE_TYPE = Dict[str, Deque[OrderBookMessage]] +TRACKER_TYPE = Dict[str, BitfinexOrderBookTrackerEntry] + + +class BitfinexOrderBookTracker(OrderBookTracker): + _logger: Optional[HummingbotLogger] = None + + EXCEPTION_TIME_SLEEP = 5.0 + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pairs: List[str]): + super().__init__( + BitfinexAPIOrderBookDataSource(trading_pairs), + trading_pairs + ) + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._saved_message_queues: QUEUE_TYPE = defaultdict( + lambda: deque(maxlen=SAVED_MESSAGES_QUEUE_SIZE) + ) + self._trading_pairs: Optional[List[str]] = trading_pairs + self._active_order_trackers: TRACKER_TYPE = defaultdict(BitfinexActiveOrderTracker) + + @property + def exchange_name(self) -> str: + return "bitfinex" + + async def _refresh_tracking_tasks(self): + """ + Starts tracking for any new trading pairs, and stop tracking for any inactive trading pairs. + """ + tracking_trading_pair: Set[str] = set( + [key for key in self._tracking_tasks.keys() if not self._tracking_tasks[key].done()] + ) + available_pairs: Dict[str, BitfinexOrderBookTrackerEntry] = await self.data_source.get_tracking_pairs() + available_trading_pair: Set[str] = set(available_pairs.keys()) + new_trading_pair: Set[str] = available_trading_pair - tracking_trading_pair + deleted_trading_pair: Set[str] = tracking_trading_pair - available_trading_pair + + for trading_pair in new_trading_pair: + order_book_tracker_entry: BitfinexOrderBookTrackerEntry = available_pairs[trading_pair] + self._active_order_trackers[trading_pair] = order_book_tracker_entry.active_order_tracker + self._order_books[trading_pair] = order_book_tracker_entry.order_book + self._tracking_message_queues[trading_pair] = asyncio.Queue() + self._tracking_tasks[trading_pair] = safe_ensure_future(self._track_single_book(trading_pair)) + self.logger().info(f"Started order book tracking for {trading_pair}.") + + for trading_pair in deleted_trading_pair: + self._tracking_tasks[trading_pair].cancel() + del self._tracking_tasks[trading_pair] + del self._order_books[trading_pair] + del self._active_order_trackers[trading_pair] + del self._tracking_message_queues[trading_pair] + self.logger().info(f"Stopped order book tracking for {trading_pair}.") + + async def _order_book_diff_router(self): + last_message_timestamp: float = time.time() + messages_queued: int = 0 + messages_accepted: int = 0 + messages_rejected: int = 0 + + while True: + try: + order_book_message: OrderBookMessage = await self._order_book_diff_stream.get() + trading_pair: str = order_book_message.trading_pair + + if trading_pair not in self._tracking_message_queues: + messages_queued += 1 + # Save diff messages received before snapshots are ready + self._saved_message_queues[trading_pair].append(order_book_message) + continue + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: OrderBook = self._order_books[trading_pair] + + if order_book.snapshot_uid > order_book_message.update_id: + messages_rejected += 1 + continue + + await message_queue.put(order_book_message) + messages_accepted += 1 + + # Log some statistics. + now: float = time.time() + if int(now / CALC_STAT_MINUTE) > int(last_message_timestamp / CALC_STAT_MINUTE): + self.logger().debug(f"Diff messages processed: {messages_accepted}, " + f"rejected: {messages_rejected}, queued: {messages_queued}") + messages_accepted = 0 + messages_rejected = 0 + messages_queued = 0 + + last_message_timestamp = now + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error routing order book messages.", + exc_info=True, + app_warning_msg="Unexpected error routing order book messages. " + f"Retrying after {int(self.EXCEPTION_TIME_SLEEP)} seconds." + ) + await asyncio.sleep(self.EXCEPTION_TIME_SLEEP) + + async def _track_single_book(self, trading_pair: str): + past_diffs_window: Deque[BitfinexOrderBookMessage] = deque() + self._past_diffs_windows[trading_pair] = past_diffs_window + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: BitfinexOrderBook = self._order_books[trading_pair] + active_order_tracker: BitfinexActiveOrderTracker = self._active_order_trackers[trading_pair] + + last_message_timestamp: float = time.time() + diff_messages_accepted: int = 0 + + while True: + try: + saved_messages: Deque[BitfinexOrderBookMessage] = self._saved_message_queues[trading_pair] + + if len(saved_messages) > 0: + message = saved_messages.popleft() + else: + message = await message_queue.get() + + if message.type is OrderBookMessageType.DIFF: + bids, asks = self._convert_diff_message_to_order_book_row(message) + order_book.apply_diffs(bids, asks, message.update_id) + + past_diffs_window.append(message) + while len(past_diffs_window) > self.PAST_DIFF_WINDOW_SIZE: + past_diffs_window.popleft() + diff_messages_accepted += 1 + + # Output some statistics periodically. + now: float = time.time() + if int(now / CALC_STAT_MINUTE) > int(last_message_timestamp / CALC_STAT_MINUTE): + self.logger().debug(f"Processed {diff_messages_accepted} order book diffs for {trading_pair}.") + diff_messages_accepted = 0 + + last_message_timestamp = now + elif message.type is OrderBookMessageType.SNAPSHOT: + past_diffs: List[BitfinexOrderBookMessage] = list(past_diffs_window) + + # only replay diffs later than snapshot, + # first update active order with snapshot then replay diffs + replay_position = bisect.bisect_right(past_diffs, message) + replay_diffs = past_diffs[replay_position:] + s_bids, s_asks = active_order_tracker.convert_snapshot_message_to_order_book_row( + message + ) + order_book.apply_snapshot(s_bids, s_asks, message.update_id) + for diff_message in replay_diffs: + d_bids, d_asks = active_order_tracker.convert_snapshot_message_to_order_book_row( + diff_message + ) + order_book.apply_diffs(d_bids, d_asks, diff_message.update_id) + + self.logger().debug(f"Processed order book snapshot for {trading_pair}.") + except asyncio.CancelledError: + raise + except Exception as err: + self.logger().error(err) + self.logger().network( + f"Unexpected error processing order book messages for {trading_pair}.", + exc_info=True, + app_warning_msg="Unexpected error processing order book messages. " + f"Retrying after {int(self.EXCEPTION_TIME_SLEEP)} seconds." + ) + await asyncio.sleep(self.EXCEPTION_TIME_SLEEP) + + def _convert_diff_message_to_order_book_row(self, message): + """ + Convert an incoming diff message to Tuple of np.arrays, and then convert to OrderBookRow + :returns: Tuple(List[bids_row], List[asks_row]) + """ + bids = [message.content["bids"]] if "bids" in message.content else [] + asks = [message.content["asks"]] if "asks" in message.content else [] + return bids, asks + + # def _convert_snapshot_message_to_order_book_row(self, message): + # """ + # Convert an incoming snapshot message to Tuple of np.arrays, and then convert to OrderBookRow + # :returns: Tuple(List[bids_row], List[asks_row]) + # """ + # bids = [message.content["bids"]] if "bids" in message.content else [] + # asks = [message.content["asks"]] if "asks" in message.content else [] + # return bids, asks diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker_entry.py b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker_entry.py new file mode 100644 index 0000000..b42f5cd --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_order_book_tracker_entry.py @@ -0,0 +1,25 @@ +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry +from hummingbot.connector.exchange.bitfinex.bitfinex_active_order_tracker import \ + BitfinexActiveOrderTracker + + +class BitfinexOrderBookTrackerEntry(OrderBookTrackerEntry): + def __init__(self, + symbol: str, + timestamp: float, + order_book: OrderBook, + active_order_tracker: BitfinexActiveOrderTracker): + self._active_order_tracker = active_order_tracker + super(BitfinexOrderBookTrackerEntry, self).__init__(symbol, timestamp, + order_book) + + def __repr__(self) -> str: + return ( + f"BitfinexOrderBookTrackerEntry(symbol='{self._symbol}', timestamp='{self._timestamp}', " + f"order_book='{self._order_book}')" + ) + + @property + def active_order_tracker(self) -> BitfinexActiveOrderTracker: + return self._active_order_tracker diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_user_stream_tracker.py b/hummingbot/connector/exchange/bitfinex/bitfinex_user_stream_tracker.py new file mode 100644 index 0000000..3cd3d06 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_user_stream_tracker.py @@ -0,0 +1,60 @@ +import logging +from typing import List, Optional + +from hummingbot.connector.exchange.bitfinex.bitfinex_api_user_stream_data_source import BitfinexAPIUserStreamDataSource +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + + +class BitfinexUserStreamTracker(UserStreamTracker): + _cbpust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bust_logger is None: + cls._bust_logger = logging.getLogger(__name__) + return cls._bust_logger + + def __init__( + self, + bitfinex_auth: Optional[BitfinexAuth] = None, + trading_pairs=None, + ): + self._bitfinex_auth: BitfinexAuth = bitfinex_auth + self._trading_pairs: List[str] = trading_pairs or [] + super().__init__(data_source=BitfinexAPIUserStreamDataSource( + bitfinex_auth=self._bitfinex_auth, + trading_pairs=self._trading_pairs + )) + + @property + def data_source(self) -> UserStreamTrackerDataSource: + """ + + """ + if not self._data_source: + self._data_source = BitfinexAPIUserStreamDataSource( + bitfinex_auth=self._bitfinex_auth, trading_pairs=self._trading_pairs + ) + return self._data_source + + @property + def exchange_name(self) -> str: + """ + *required + Name of the current exchange + """ + return "bitfinex" + + async def start(self): + """ + *required + Start all listeners and tasks + """ + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py b/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py new file mode 100644 index 0000000..95dedd5 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_utils.py @@ -0,0 +1,137 @@ +import math +from decimal import Decimal +from typing import Dict, List, Optional, Tuple + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData + +CENTRALIZED = True + + +EXAMPLE_PAIR = "ETH-USD" + +# BitFinex list Tether(USDT) as 'UST' +EXCHANGE_TO_HB_CONVERSION = {"UST": "USDT"} +HB_TO_EXCHANGE_CONVERSION = {v: k for k, v in EXCHANGE_TO_HB_CONVERSION.items()} + +DEFAULT_FEES = [0.1, 0.2] + + +class BitfinexConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitfinex", client_data=None) + bitfinex_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitfinex API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitfinex_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitfinex secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitfinex" + + +KEYS = BitfinexConfigMap.construct() + + +# deeply merge two dictionaries +def merge_dicts(source: Dict, destination: Dict) -> Dict: + for key, value in source.items(): + if isinstance(value, dict): + # get node or create one + node = destination.setdefault(key, {}) + merge_dicts(value, node) + else: + destination[key] = value + + return destination + + +# join paths +def join_paths(*paths: List[str]) -> str: + return "/".join(paths) + + +# get precision decimal from a number +def get_precision(precision: int) -> Decimal: + return Decimal(1) / Decimal(math.pow(10, precision)) + + +def split_trading_pair(trading_pair: str) -> Tuple[str, str]: + try: + base, quote = trading_pair.split("-") + return base, quote + # exceptions are now logged as warnings in trading pair fetcher + except Exception as e: + raise e + + +def split_trading_pair_from_exchange(trading_pair: str) -> Tuple[str, str]: + # sometimes the exchange returns trading pairs like tBTCUSD + isTradingPair = trading_pair[0].islower() and trading_pair[1].isupper() and trading_pair[0] == "t" + pair = trading_pair[1:] if isTradingPair else trading_pair + + if ":" in pair: + base, quote = pair.split(":") + elif len(pair) == 6: + base, quote = pair[:3], pair[3:] + else: + return None + return base, quote + + +def valid_exchange_trading_pair(trading_pair: str) -> bool: + try: + base, quote = split_trading_pair_from_exchange(trading_pair) + return True + except Exception: + return False + + +def convert_from_exchange_token(token: str) -> str: + if token in EXCHANGE_TO_HB_CONVERSION: + token = EXCHANGE_TO_HB_CONVERSION[token] + return token + + +def convert_to_exchange_token(token: str) -> str: + if token in HB_TO_EXCHANGE_CONVERSION: + token = HB_TO_EXCHANGE_CONVERSION[token] + return token + + +def convert_from_exchange_trading_pair(exchange_trading_pair: str) -> Optional[str]: + try: + base_asset, quote_asset = split_trading_pair_from_exchange(exchange_trading_pair) + + base_asset = convert_from_exchange_token(base_asset) + quote_asset = convert_from_exchange_token(quote_asset) + + return f"{base_asset}-{quote_asset}" + except Exception as e: + raise e + + +def convert_to_exchange_trading_pair(hb_trading_pair: str) -> str: + base_asset, quote_asset = hb_trading_pair.split("-") + + base_asset = convert_to_exchange_token(base_asset) + quote_asset = convert_to_exchange_token(quote_asset) + + if len(base_asset) > 3: # Adds ':' delimiter if base asset > 3 characters + trading_pair = f"t{base_asset}:{quote_asset}" + else: + trading_pair = f"t{base_asset}{quote_asset}" + return trading_pair diff --git a/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py b/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py new file mode 100644 index 0000000..b024fc7 --- /dev/null +++ b/hummingbot/connector/exchange/bitfinex/bitfinex_websocket.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +import asyncio +import logging +import websockets +import ujson +import uuid + +from typing import Optional, AsyncIterable, Any +from websockets.exceptions import ConnectionClosed +from async_timeout import timeout +from hummingbot.logger import HummingbotLogger +from hummingbot.connector.exchange.bitfinex import BITFINEX_WS_URI +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth + + +# reusable websocket class +class BitfinexWebsocket(): + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, auth: Optional[BitfinexAuth]): + self._client = None + self._auth = auth + self._consumers = dict() + self._listening = False + + # connect to exchange + async def connect(self): + try: + self._client = await websockets.connect(BITFINEX_WS_URI) + return self + except Exception as e: + self.logger().error(f"Websocket error: '{str(e)}'") + + # disconnect from exchange + async def disconnect(self): + if self._client is None: + return + + await self._client.wait_closed() + + # listen for new websocket messages and add them to queue + async def _listen_to_ws(self) -> AsyncIterable[Any]: + if self._listening: + raise AssertionError("cannot listen twice") + + self._listening = True + + try: + while True: + try: + msg_str: str = await asyncio.wait_for(self._client.recv(), timeout=self.MESSAGE_TIMEOUT) + msg = ujson.loads(msg_str) + # print("received", msg) + + for queue in self._consumers.values(): + await queue.put(msg) + + yield msg + except asyncio.TimeoutError: + pong_waiter = await self._client.ping() + await asyncio.wait_for(pong_waiter, timeout=self.PING_TIMEOUT) + except asyncio.TimeoutError: + self.logger().error("WebSocket ping timed out. Going to reconnect...") + return + except ConnectionClosed: + return + finally: + self._listening = False + await self.disconnect() + + # listen to consumer's queue updates + async def _listen_to_queue(self, consumer_id: str) -> AsyncIterable[Any]: + try: + msg = self._consumers[consumer_id].get_nowait() + yield msg + except asyncio.QueueEmpty: + yield None + except Exception as e: + self.logger().error(f"_listen_to_queue error {str(e)}", exc_info=True) + raise e + + async def _listen(self, consumer_id: str) -> AsyncIterable[Any]: + try: + while True: + if self._listening: + async for msg in self._listen_to_queue(consumer_id): + if msg is not None: + yield msg + + await asyncio.sleep(0.5) + else: + async for msg in self._listen_to_ws(): + yield msg + except asyncio.CancelledError: + pass + except Exception as e: + raise e + + async def messages(self, waitFor: Optional[Any] = None) -> AsyncIterable[Any]: + consumer_id = uuid.uuid4() + self._consumers[consumer_id] = asyncio.Queue() + + try: + async for msg in self._listen(consumer_id): + if waitFor is None: + yield msg + else: + async with timeout(self.PING_TIMEOUT): + try: + if (waitFor(msg) is True): + yield msg + except IOError as e: + raise e + except Exception: + pass + except Exception as e: + self.logger().error(f"_listen error {str(e)}", exc_info=True) + raise e + finally: + self._consumers.pop(consumer_id, None) + + # emit messages + async def emit(self, data): + if (self._listening is False): + await self.connect() + + # print("send", data) + await self._client.send(ujson.dumps(data)) + + # authenticate: authenticate session + async def authenticate(self): + if self._auth is None: + raise "auth not provided" + + payload = self._auth.generate_auth_payload('AUTH{nonce}'.format(nonce=self._auth.get_nonce())) + + def waitFor(msg): + isAuthEvent = msg.get("event", None) == "auth" + isStatusOk = msg.get("status", None) == "OK" + return isAuthEvent and isStatusOk + + await self.emit(payload) + + async for msg in self.messages(waitFor): + return msg diff --git a/hummingbot/connector/exchange/bitmart/__init__.py b/hummingbot/connector/exchange/bitmart/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py b/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py new file mode 100644 index 0000000..55c10b6 --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_api_order_book_data_source.py @@ -0,0 +1,222 @@ +import asyncio +import json +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.bitmart import ( + bitmart_constants as CONSTANTS, + bitmart_utils as utils, + bitmart_web_utils as web_utils, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.bitmart.bitmart_exchange import BitmartExchange + + +class BitmartAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'BitmartExchange', + api_factory: WebAssistantsFactory): + super().__init__(trading_pairs) + self._connector: BitmartExchange = connector + self._api_factory = api_factory + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Bitmart only sends full snapshots, they never send diff messages. That is why this method is overwritten to + do nothing. + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created diff messages + """ + pass + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Bitmart sends always full snapshots through the depth channel. That is why they are processed here. + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created diff messages + """ + message_queue = self._message_queue[self._diff_messages_queue_key] + while True: + try: + snapshot_event = await message_queue.get() + await self._parse_order_book_snapshot_message(raw_message=snapshot_event, message_queue=output) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public order book updates from exchange") + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_data: Dict[str, Any] = snapshot_response["data"] + snapshot_timestamp: float = int(snapshot_data["timestamp"]) * 1e-3 + update_id: int = int(snapshot_data["timestamp"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid["price"], bid["amount"]) for bid in snapshot_data["buys"]], + "asks": [(ask["price"], ask["amount"]) for ask in snapshot_data["sells"]], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + snapshot_timestamp) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "size": 200 + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.GET_ORDER_BOOK_PATH_URL), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.GET_ORDER_BOOK_PATH_URL, + ) + + return data + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_updates = raw_message["data"] + + for trade_data in trade_updates: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=trade_data["symbol"]) + message_content = { + "trade_id": int(trade_data["s_t"]), + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) if trade_data["side"] == "buy" else float( + TradeType.SELL.value), + "amount": trade_data["size"], + "price": trade_data["price"] + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=int(trade_data["s_t"])) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + # Bitmart never sends diff messages. This method will never be called + pass + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_updates: Dict[str, Any] = raw_message["data"] + + for diff_data in diff_updates: + timestamp: float = int(diff_data["ms_t"]) * 1e-3 + update_id: int = int(diff_data["ms_t"]) + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=diff_data["symbol"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in diff_data["bids"]], + "asks": [(ask[0], ask[1]) for ask in diff_data["asks"]], + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + timestamp) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + try: + symbols = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME}:{symbol}" for symbol in symbols] + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME}:{symbol}" for symbol in symbols] + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIBE): + await ws.send(subscribe_trade_request) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIBE): + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + decompressed_data = utils.decompress_ws_message(data) + try: + if type(decompressed_data) == str: + json_data = json.loads(decompressed_data) + else: + json_data = decompressed_data + except Exception: + self.logger().warning(f"Invalid event message received through the order book data source " + f"connection ({decompressed_data})") + continue + + if "errorCode" in json_data or "errorMessage" in json_data: + raise ValueError(f"Error message received in the order book data source: {json_data}") + + channel: str = self._channel_originating_message(event_message=json_data) + if channel in [self._diff_messages_queue_key, self._trade_messages_queue_key]: + self._message_queue[channel].put_nowait(json_data) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "data" in event_message: + event_channel = event_message["table"] + if event_channel == CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME: + channel = self._trade_messages_queue_key + if event_channel == CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME: + channel = self._diff_messages_queue_key + + return channel + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_CONNECT): + await ws.connect( + ws_url=CONSTANTS.WSS_PUBLIC_URL, + ping_timeout=CONSTANTS.WS_PING_TIMEOUT) + return ws diff --git a/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py b/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py new file mode 100755 index 0000000..6296c99 --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_api_user_stream_data_source.py @@ -0,0 +1,110 @@ +import asyncio +import json +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.bitmart import bitmart_constants as CONSTANTS, bitmart_utils as utils +from hummingbot.connector.exchange.bitmart.bitmart_auth import BitmartAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.bitmart.bitmart_exchange import BitmartExchange + + +class BitmartAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: BitmartAuth, + trading_pairs: List[str], + connector: 'BitmartExchange', + api_factory: WebAssistantsFactory + ): + super().__init__() + self._auth: BitmartAuth = auth + self._trading_pairs = trading_pairs + self._connector = connector + self._api_factory = api_factory + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect( + ws_url=CONSTANTS.WSS_PRIVATE_URL, + ping_timeout=CONSTANTS.WS_PING_TIMEOUT) + + payload = { + "op": "login", + "args": self._auth.websocket_login_parameters() + } + + login_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIBE): + await ws.send(login_request) + + response: WSResponse = await ws.receive() + message = response.data + if "errorCode" in message or "error_code" in message or message.get("event") != "login": + self.logger().error("Error authenticating the private websocket connection") + raise IOError(f"Private websocket connection authentication failed ({message})") + + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + try: + symbols = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + + payload = { + "op": "subscribe", + "args": [f"{CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME}:{symbol}" for symbol in symbols] + } + subscribe_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIBE): + await websocket_assistant.send(subscribe_request) + self.logger().info("Subscribed to private account and orders channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + decompressed_data = utils.decompress_ws_message(data) + try: + if type(decompressed_data) == str: + json_data = json.loads(decompressed_data) + else: + json_data = decompressed_data + except asyncio.CancelledError: + raise + except Exception: + self.logger().warning(f"Invalid event message received through the order book data source " + f"connection ({decompressed_data})") + continue + + if "errorCode" in json_data or "errorMessage" in json_data: + raise ValueError(f"Error message received in the order book data source: {json_data}") + + await self._process_event_message(event_message=json_data, queue=queue) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0 and "table" in event_message and "data" in event_message: + queue.put_nowait(event_message) + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant diff --git a/hummingbot/connector/exchange/bitmart/bitmart_auth.py b/hummingbot/connector/exchange/bitmart/bitmart_auth.py new file mode 100755 index 0000000..37677a0 --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_auth.py @@ -0,0 +1,85 @@ +import hashlib +import hmac +import json +from typing import Any, Dict, List, Optional + +from hummingbot.connector.exchange.bitmart import bitmart_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class BitmartAuth(AuthBase): + """ + Auth class required by BitMart API + Learn more at https://developer-pro.bitmart.com/en/part2/auth.html + """ + + def __init__(self, api_key: str, secret_key: str, memo: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.memo = memo + self.time_provider: TimeSynchronizer = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + + :param request: the request to be configured for authenticated interaction + + :return: The RESTRequest with auth information included + """ + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.authentication_headers(request=request)) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. OKX does not use this + functionality + """ + return request # pass-through + + def _generate_signature(self, timestamp: str, body: Optional[str] = None) -> str: + body = body or "" + unsigned_signature = f"{str(timestamp)}#{self.memo}#{body}" + + signature = hmac.new( + self.secret_key.encode("utf-8"), + unsigned_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + + return signature + + def authentication_headers(self, request: RESTRequest) -> Dict[str, Any]: + timestamp = str(int(self.time_provider.time() * 1e3)) + + params = json.dumps(request.params) if request.params is not None else request.data + + sign = self._generate_signature(timestamp=timestamp, body=params) + + header = { + "X-BM-KEY": self.api_key, + "X-BM-SIGN": sign, + "X-BM-TIMESTAMP": timestamp, + "X-BM-BROKER-ID": CONSTANTS.BROKER_ID, + } + + return header + + def websocket_login_parameters(self) -> List[str]: + timestamp = str(int(self.time_provider.time() * 1e3)) + + return [ + self.api_key, + timestamp, + self._generate_signature( + timestamp=timestamp, + body="bitmart.WebSocket") + ] diff --git a/hummingbot/connector/exchange/bitmart/bitmart_constants.py b/hummingbot/connector/exchange/bitmart/bitmart_constants.py new file mode 100644 index 0000000..476bada --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_constants.py @@ -0,0 +1,62 @@ +# A single source of truth for constant variables related to the exchange + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "bitmart" +REST_URL = "https://api-cloud.bitmart.com" +WSS_PUBLIC_URL = "wss://ws-manager-compress.bitmart.com/api?protocol=1.1" +WSS_PRIVATE_URL = "wss://ws-manager-compress.bitmart.com/user?protocol=1.1" +WS_PING_TIMEOUT = 20 * 0.8 + +DEFAULT_DOMAIN = "" +MAX_ORDER_ID_LEN = 32 +HBOT_ORDER_ID_PREFIX = "" +BROKER_ID = "hummingbotfound" + +PUBLIC_TRADE_CHANNEL_NAME = "spot/trade" +PUBLIC_DEPTH_CHANNEL_NAME = "spot/depth50" +PRIVATE_ORDER_PROGRESS_CHANNEL_NAME = "spot/user/order" + +# REST API ENDPOINTS +CHECK_NETWORK_PATH_URL = "system/service" +GET_TRADING_RULES_PATH_URL = "spot/v1/symbols/details" +GET_LAST_TRADING_PRICES_PATH_URL = "spot/v1/ticker" +GET_ORDER_BOOK_PATH_URL = "spot/v1/symbols/book" +CREATE_ORDER_PATH_URL = "spot/v1/submit_order" +CANCEL_ORDER_PATH_URL = "spot/v2/cancel_order" +GET_ACCOUNT_SUMMARY_PATH_URL = "spot/v1/wallet" +GET_ORDER_DETAIL_PATH_URL = "spot/v2/order_detail" +GET_TRADE_DETAIL_PATH_URL = "spot/v1/trades" +SERVER_TIME_PATH = "system/time" + +# WS API ENDPOINTS +WS_CONNECT = "WSConnect" +WS_SUBSCRIBE = "WSSubscribe" + +# BitMart has a per method API limit +RATE_LIMITS = [ + RateLimit(limit_id=CHECK_NETWORK_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=GET_TRADING_RULES_PATH_URL, limit=30, time_interval=5), + RateLimit(limit_id=GET_LAST_TRADING_PRICES_PATH_URL, limit=30, time_interval=5), + RateLimit(limit_id=GET_ORDER_BOOK_PATH_URL, limit=30, time_interval=5), + RateLimit(limit_id=CREATE_ORDER_PATH_URL, limit=150, time_interval=5), + RateLimit(limit_id=CANCEL_ORDER_PATH_URL, limit=150, time_interval=5), + RateLimit(limit_id=GET_ACCOUNT_SUMMARY_PATH_URL, limit=30, time_interval=5), + RateLimit(limit_id=GET_ORDER_DETAIL_PATH_URL, limit=150, time_interval=5), + RateLimit(limit_id=GET_TRADE_DETAIL_PATH_URL, limit=30, time_interval=5), + RateLimit(limit_id=SERVER_TIME_PATH, limit=10, time_interval=1), + RateLimit(limit_id=WS_CONNECT, limit=30, time_interval=60), + RateLimit(limit_id=WS_SUBSCRIBE, limit=100, time_interval=10), +] + +ORDER_STATE = { + "1": OrderState.FAILED, + "2": OrderState.OPEN, + "3": OrderState.FAILED, + "4": OrderState.OPEN, + "5": OrderState.PARTIALLY_FILLED, + "6": OrderState.FILLED, + "7": OrderState.PENDING_CANCEL, + "8": OrderState.CANCELED, +} diff --git a/hummingbot/connector/exchange/bitmart/bitmart_exchange.py b/hummingbot/connector/exchange/bitmart/bitmart_exchange.py new file mode 100644 index 0000000..243385a --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_exchange.py @@ -0,0 +1,436 @@ +import asyncio +import math +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.bitmart import ( + bitmart_constants as CONSTANTS, + bitmart_utils, + bitmart_web_utils as web_utils, +) +from hummingbot.connector.exchange.bitmart.bitmart_api_order_book_data_source import BitmartAPIOrderBookDataSource +from hummingbot.connector.exchange.bitmart.bitmart_api_user_stream_data_source import BitmartAPIUserStreamDataSource +from hummingbot.connector.exchange.bitmart.bitmart_auth import BitmartAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class BitmartExchange(ExchangePyBase): + """ + BitmartExchange connects with BitMart exchange and provides order book pricing, user account tracking and + trading functionality. + """ + API_CALL_TIMEOUT = 10.0 + POLL_INTERVAL = 1.0 + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + UPDATE_TRADE_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + bitmart_api_key: str, + bitmart_secret_key: str, + bitmart_memo: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ): + """ + :param bitmart_api_key: The API key to connect to private BitMart APIs. + :param bitmart_secret_key: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + self._api_key: str = bitmart_api_key + self._secret_key: str = bitmart_secret_key + self._memo: str = bitmart_memo + self._trading_required = trading_required + self._trading_pairs = trading_pairs + + super().__init__(client_config_map) + self.real_time_balance_update = False + + @property + def authenticator(self): + return BitmartAuth( + api_key=self._api_key, + secret_key=self._secret_key, + memo=self._memo, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "bitmart" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return CONSTANTS.DEFAULT_DOMAIN + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.GET_TRADING_RULES_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.GET_TRADING_RULES_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.CHECK_NETWORK_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector. + Note that Market order type is no longer required and will not be used. + """ + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = ("Header X-BM-TIMESTAMP" in error_description + and ("30007" in error_description or "30008" in error_description)) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BitmartAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BitmartAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = order_type is OrderType.LIMIT_MAKER + return AddedToCostTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + + api_params = {"symbol": await self.exchange_symbol_associated_to_pair(trading_pair), + "side": trade_type.name.lower(), + "type": "limit", + "size": f"{amount:f}", + "price": f"{price:f}", + "clientOrderId": order_id, + } + order_result = await self._api_post( + path_url=CONSTANTS.CREATE_ORDER_PATH_URL, + data=api_params, + is_auth_required=True) + exchange_order_id = str(order_result["data"]["order_id"]) + + return exchange_order_id, self.current_timestamp + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + api_params = { + "clientOrderId": order_id, + } + cancel_result = await self._api_post( + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, + data=api_params, + is_auth_required=True) + return cancel_result.get("data", {}).get("result", False) + + async def _format_trading_rules(self, symbols_details: Dict[str, Any]) -> List[TradingRule]: + """ + Converts json API response into a dictionary of trading rules. + :param symbols_details: The json API response + :return A dictionary of trading rules. + Response Example: + { + "code": 1000, + "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "symbols": [ + { + "symbol":"GXC_BTC", + "symbol_id":1024, + "base_currency":"GXC", + "quote_currency":"BTC", + "quote_increment":"1.00000000", + "base_min_size":"1.00000000", + "price_min_precision":6, + "price_max_precision":8, + "expiration":"NA", + "min_buy_amount":"0.00010000", + "min_sell_amount":"0.00010000" + }, + ... + ] + } + } + """ + result = [] + for rule in symbols_details["data"]["symbols"]: + if bitmart_utils.is_exchange_information_valid(rule): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(rule["symbol"]) + price_decimals = Decimal(str(rule["price_max_precision"])) + # E.g. a price decimal of 2 means 0.01 incremental. + price_step = Decimal("1") / Decimal(str(math.pow(10, price_decimals))) + result.append(TradingRule(trading_pair=trading_pair, + min_order_size=Decimal(str(rule["base_min_size"])), + min_order_value=Decimal(str(rule["min_buy_amount"])), + min_base_amount_increment=Decimal(str(rule["base_min_size"])), + min_price_increment=price_step)) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule}. Skipping.") + return result + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _update_balances(self): + """ + Calls REST API to update total and available balances. + """ + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + account_info = await self._api_get( + path_url=CONSTANTS.GET_ACCOUNT_SUMMARY_PATH_URL, + is_auth_required=True) + for account in account_info["data"]["wallet"]: + asset_name = account["id"] + self._account_available_balances[asset_name] = Decimal(str(account["available"])) + self._account_balances[asset_name] = Decimal(str(account["available"])) + Decimal(str(account["frozen"])) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _request_order_update(self, order: InFlightOrder) -> Dict[str, Any]: + return await self._api_get( + path_url=CONSTANTS.GET_ORDER_DETAIL_PATH_URL, + params={"order_id": order.exchange_order_id}, + is_auth_required=True) + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + return await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.GET_TRADE_DETAIL_PATH_URL, + params={ + "symbol": await self.exchange_symbol_associated_to_pair(order.trading_pair), + "order_id": await order.get_exchange_order_id()}, + is_auth_required=True) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + try: + if order.exchange_order_id is not None: + all_fills_response = await self._request_order_fills(order=order) + updates = self._create_order_fill_updates(order=order, fill_update=all_fills_response) + trade_updates.extend(updates) + except asyncio.CancelledError: + raise + except Exception as ex: + is_error_caused_by_unexistent_order = '"code":50005' in str(ex) + if not is_error_caused_by_unexistent_order: + raise + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._request_order_update(order=tracked_order) + + order_update = self._create_order_update(order=tracked_order, order_update=updated_order_data) + return order_update + + def _create_order_fill_updates(self, order: InFlightOrder, fill_update: Dict[str, Any]) -> List[TradeUpdate]: + updates = [] + fills_data = fill_update["data"]["trades"] + + for fill_data in fills_data: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=fill_data["fee_coin_name"], + flat_fees=[TokenAmount(amount=Decimal(fill_data["fees"]), token=fill_data["fee_coin_name"])] + ) + trade_update = TradeUpdate( + trade_id=str(fill_data["detail_id"]), + client_order_id=order.client_order_id, + exchange_order_id=str(fill_data["order_id"]), + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(fill_data["size"]), + fill_quote_amount=Decimal(fill_data["size"]) * Decimal(fill_data["price_avg"]), + fill_price=Decimal(fill_data["price_avg"]), + fill_timestamp=int(fill_data["create_time"]) * 1e-3, + ) + updates.append(trade_update) + + return updates + + def _create_order_update(self, order: InFlightOrder, order_update: Dict[str, Any]) -> OrderUpdate: + order_data = order_update["data"] + new_state = CONSTANTS.ORDER_STATE[order_data["status"]] + update = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=str(order_data["order_id"]), + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + ) + return update + + async def _user_stream_event_listener(self): + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("table") + execution_data = event_message.get("data", []) + + # Refer to https://developer-pro.bitmart.com/en/spot/#private-order-progress + if event_type == CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME: + for each_event in execution_data: + try: + client_order_id: Optional[str] = each_event.get("client_order_id") + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + new_state = CONSTANTS.ORDER_STATE[each_event["state"]] + event_timestamp = int(each_event["ms_t"]) * 1e-3 + + if fillable_order is not None: + is_fill_candidate_by_state = new_state in [OrderState.PARTIALLY_FILLED, + OrderState.FILLED] + is_fill_candidate_by_amount = fillable_order.executed_amount_base < Decimal( + each_event["filled_size"]) + if is_fill_candidate_by_state and is_fill_candidate_by_amount: + try: + trade_fills: Dict[str, Any] = await self._request_order_fills(fillable_order) + trade_updates = self._create_order_fill_updates( + order=fillable_order, + fill_update=trade_fills) + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error requesting order fills for " + f"{fillable_order.client_order_id}") + if updatable_order is not None: + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=event_timestamp, + new_state=new_state, + client_order_id=client_order_id, + exchange_order_id=each_event["order_id"], + ) + self._order_tracker.process_order_update(order_update=order_update) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(bitmart_utils.is_exchange_information_valid, exchange_info["data"]["symbols"]): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["base_currency"], + quote=symbol_data["quote_currency"]) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_get( + path_url=CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL, + params=params + ) + + return float(resp_json["data"]["tickers"][0]["last_price"]) diff --git a/hummingbot/connector/exchange/bitmart/bitmart_utils.py b/hummingbot/connector/exchange/bitmart/bitmart_utils.py new file mode 100644 index 0000000..fc110aa --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_utils.py @@ -0,0 +1,85 @@ +import zlib +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True + +EXAMPLE_PAIR = "ETH-USDT" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0025"), + taker_percent_fee_decimal=Decimal("0.0025"), +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("trade_status", None) == "trading" + + +# Decompress WebSocket messages +def decompress_ws_message(message): + if type(message) == bytes: + decompress = zlib.decompressobj(-zlib.MAX_WBITS) + inflated = decompress.decompress(message) + inflated += decompress.flush() + return inflated.decode('UTF-8') + else: + return message + + +def compress_ws_message(message): + if type(message) == str: + message = message.encode() + compress = zlib.compressobj(wbits=-zlib.MAX_WBITS) + deflated = compress.compress(message) + deflated += compress.flush() + return deflated + else: + return message + + +class BitmartConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitmart", client_data=None) + bitmart_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitMart API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmart_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitMart secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmart_memo: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BitMart API Memo", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitmart" + + +KEYS = BitmartConfigMap.construct() diff --git a/hummingbot/connector/exchange/bitmart/bitmart_web_utils.py b/hummingbot/connector/exchange/bitmart/bitmart_web_utils.py new file mode 100644 index 0000000..6b33edd --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/bitmart_web_utils.py @@ -0,0 +1,66 @@ +from typing import Callable, Optional +from urllib.parse import urljoin + +import hummingbot.connector.exchange.bitmart.bitmart_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, **kwargs) -> str: + """ + Creates a full URL for provided REST endpoint + + :param path_url: a public REST endpoint + + :return: the full URL to the endpoint + """ + return urljoin(CONSTANTS.REST_URL, path_url) + + +def private_rest_url(path_url: str, **kwargs) -> str: + return public_rest_url(path_url) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN) -> float: + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH, + ) + server_time = float(response["data"]["server_time"]) + + return server_time diff --git a/hummingbot/connector/exchange/bitmart/dummy.pxd b/hummingbot/connector/exchange/bitmart/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/bitmart/dummy.pyx b/hummingbot/connector/exchange/bitmart/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/bitmart/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/bitmex/__init__.py b/hummingbot/connector/exchange/bitmex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/bitmex/bitmex_api_order_book_data_source.py b/hummingbot/connector/exchange/bitmex/bitmex_api_order_book_data_source.py new file mode 100644 index 0000000..79280a6 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_api_order_book_data_source.py @@ -0,0 +1,424 @@ +import asyncio +import json +import logging +import time +from collections import defaultdict +from typing import Any, Dict, List, Mapping, Optional + +from bidict import ValueDuplicationError, bidict + +import hummingbot.connector.exchange.bitmex.bitmex_utils as utils +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.exchange.bitmex.bitmex_order_book import BitmexOrderBook +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BitmexAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _bpobds_logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, Mapping[str, str]] = {} + _mapping_initialization_lock = asyncio.Lock() + + def __init__( + self, + trading_pairs: List[str] = None, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__(trading_pairs) + self._api_factory: WebAssistantsFactory = api_factory or web_utils.build_api_factory() + self._ws_assistant: Optional[WSAssistant] = None + self._order_book_create_function = lambda: OrderBook() + self._domain = domain + self._throttler = throttler or self._get_throttler_instance() + + self._message_queue: Dict[int, asyncio.Queue] = defaultdict(asyncio.Queue) + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpobds_logger is None: + cls._bpobds_logger = logging.getLogger(__name__) + return cls._bpobds_logger + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + @classmethod + async def get_last_traded_prices(cls, + trading_pairs: List[str], + domain: str = CONSTANTS.DOMAIN) -> Dict[str, float]: + tasks = [cls.get_last_traded_price(t_pair, domain) for t_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + @classmethod + async def get_last_traded_price(cls, + trading_pair: str, + domain: str = CONSTANTS.DOMAIN, + api_factory: WebAssistantsFactory = None) -> float: + api_factory = api_factory or web_utils.build_api_factory() + + throttler = cls._get_throttler_instance() + + params = { + "symbol": await cls.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=domain, + throttler=throttler)} + + resp_json = await web_utils.api_request( + CONSTANTS.EXCHANGE_INFO_URL, + api_factory, + throttler, + domain, + params=params + ) + return float(resp_json[0]["lastPrice"]) + + @classmethod + def trading_pair_symbol_map_ready(cls, domain: str = CONSTANTS.DOMAIN): + """ + Checks if the mapping from exchange symbols to client trading pairs has been initialized + :param domain: the domain of the exchange being used + :return: True if the mapping has been initialized, False otherwise + """ + return domain in cls._trading_pair_symbol_map and len(cls._trading_pair_symbol_map[domain]) > 0 + + @classmethod + async def trading_pair_symbol_map( + cls, + domain: Optional[str] = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> Mapping[str, str]: + if not cls.trading_pair_symbol_map_ready(domain=domain): + api_factory = WebAssistantsFactory(throttler) + async with cls._mapping_initialization_lock: + # Check condition again (could have been initialized while waiting for the lock to be released) + if not cls.trading_pair_symbol_map_ready(domain=domain): + await cls.init_trading_pair_symbols(domain, throttler, api_factory) + + return cls._trading_pair_symbol_map[domain] + + @classmethod + async def init_trading_pair_symbols( + cls, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ): + """Initialize _trading_pair_symbol_map class variable""" + mapping = bidict() + + api_factory = api_factory or web_utils.build_api_factory() + + throttler = throttler or cls._get_throttler_instance() + params = {"filter": json.dumps({"typ": "IFXXXP"})} + + try: + data = await web_utils.api_request( + CONSTANTS.EXCHANGE_INFO_URL, + api_factory, + throttler, + domain, + params + ) + for symbol_data in data: + try: + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair( + symbol_data["rootSymbol"], + symbol_data["quoteCurrency"]) + except ValueDuplicationError: + continue + + except Exception as ex: + cls.logger().error(f"There was an error requesting exchange info ({str(ex)})") + + cls._trading_pair_symbol_map[domain] = mapping + + @staticmethod + async def fetch_trading_pairs( + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> List[str]: + ob_source_cls = BitmexAPIOrderBookDataSource + trading_pair_list: List[str] = [] + symbols_map = await ob_source_cls.trading_pair_symbol_map(domain=domain, throttler=throttler, api_factory=api_factory) + trading_pair_list.extend(list(symbols_map.values())) + + return trading_pair_list + + @classmethod + async def convert_from_exchange_trading_pair( + cls, + exchange_trading_pair: str, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None) -> str: + + symbol_map = await cls.trading_pair_symbol_map( + domain=domain, + throttler=throttler) + try: + pair = symbol_map[exchange_trading_pair] + except KeyError: + raise ValueError(f"There is no symbol mapping for exchange trading pair {exchange_trading_pair}") + + return pair + + @classmethod + async def convert_to_exchange_trading_pair( + cls, + hb_trading_pair: str, + domain = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None) -> str: + + symbol_map = await cls.trading_pair_symbol_map( + domain=domain, + throttler=throttler) + try: + symbol = symbol_map.inverse[hb_trading_pair] + except KeyError: + raise ValueError(f"There is no symbol mapping for trading pair {hb_trading_pair}") + + return symbol + + @staticmethod + async def get_snapshot( + trading_pair: str, + limit: int = 1000, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: WebAssistantsFactory = None + ) -> Dict[str, Any]: + ob_source_cls = BitmexAPIOrderBookDataSource + try: + api_factory = api_factory or web_utils.build_api_factory() + + params = {"symbol": await ob_source_cls.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=domain, + throttler=throttler)} + if limit != 0: + params.update({"limit": str(limit)}) + + throttler = throttler or ob_source_cls._get_throttler_instance() + data = await web_utils.api_request( + CONSTANTS.SNAPSHOT_REST_URL, + api_factory, + throttler, + domain, + params + ) + return data + except asyncio.CancelledError: + raise + except Exception: + raise + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + snapshot: List[Dict[str, Any]] = await self.get_snapshot( + trading_pair, + 1000, + self._domain, + self._throttler, + self._api_factory + ) + bids = [] + asks = [] + trading_pair_multipliers = await utils.get_trading_pair_multipliers( + await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + ) + + base_mult = trading_pair_multipliers.base_multiplier + + for order in snapshot: + order_details = [order['price'], order['size'] / base_mult] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + + snapshot_dict = { + "bids": bids, + "asks": asks, + "update_id": snapshot[-1]["id"] + } + + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = BitmexOrderBook.snapshot_message_from_exchange( + snapshot_dict, snapshot_timestamp, metadata={"trading_pair": trading_pair} + ) + order_book = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + return order_book + + async def _subscribe_to_order_book_streams(self) -> WSAssistant: + url = web_utils.wss_url("", self._domain) + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect(ws_url=url) + + stream_channels = [ + "trade", + "orderBookL2", + ] + for channel in stream_channels: + params = [] + for trading_pair in self._trading_pairs: + symbol = await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler) + params.append(f"{channel}:{symbol}") + payload = { + "op": "subscribe", + "args": params, + } + subscribe_request: WSJSONRequest = WSJSONRequest( + payload=payload, + is_auth_required=False + ) + await ws.send(subscribe_request) + + return ws + + async def listen_for_subscriptions(self): + ws = None + while True: + try: + ws: WSAssistant = await self._subscribe_to_order_book_streams() + + async for msg in ws.iter_messages(): + if 'table' in msg.data: + if "orderBookL2" in msg.data["table"]: + self._message_queue[CONSTANTS.DIFF_STREAM_ID].put_nowait(msg) + elif "trade" in msg.data["table"]: + self._message_queue[CONSTANTS.TRADE_STREAM_ID].put_nowait(msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error with Websocket connection. Retrying after 30 seconds...", exc_info=True + ) + await self._sleep(30.0) + finally: + ws and await ws.disconnect() + + async def order_id_to_price(self, trading_pair: str, order_id: int): + exchange_trading_pair = await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + id_tick = await utils.get_trading_pair_index_and_tick_size(exchange_trading_pair) + trading_pair_id = id_tick.index + instrument_tick_size = id_tick.tick_size + price = ((1e8 * trading_pair_id) - order_id) * instrument_tick_size + return price + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + msg = await self._message_queue[CONSTANTS.DIFF_STREAM_ID].get() + timestamp: float = time.time() + if msg.data["action"] in ["update", "insert", "delete"]: + msg.data["data_dict"] = {} + exchange_trading_pair = msg.data["data"][0]["symbol"] + trading_pair = await self.convert_from_exchange_trading_pair( + exchange_trading_pair=exchange_trading_pair, + domain=self._domain, + throttler=self._throttler) + + trading_pair_multipliers = await utils.get_trading_pair_multipliers(exchange_trading_pair) + base_mult = trading_pair_multipliers.base_multiplier + + msg.data["data_dict"]["symbol"] = trading_pair + asks = [] + bids = [] + for order in msg.data["data"]: + price = await self.order_id_to_price(trading_pair, order["id"]) + amount = 0.0 if msg.data['action'] == "delete" else order["size"] / base_mult + order_details = [price, amount] + asks.append(order_details) if order["side"] == "Sell" else bids.append(order_details) + + msg.data["data_dict"]["bids"] = bids + msg.data["data_dict"]["asks"] = asks + order_book_message: OrderBookMessage = BitmexOrderBook.diff_message_from_exchange( + msg.data, timestamp + ) + output.put_nowait(order_book_message) + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + msg = await self._message_queue[CONSTANTS.TRADE_STREAM_ID].get() + if len(msg.data["data"]) > 0: + msg.data["data_dict"] = {} + trading_pair = await self.convert_from_exchange_trading_pair( + exchange_trading_pair=msg.data["data"][0]["symbol"], + domain=self._domain, + throttler=self._throttler) + for trade in msg.data["data"]: + trade["symbol"] = trading_pair + trade_message: OrderBookMessage = BitmexOrderBook.trade_message_from_exchange(trade) + output.put_nowait(trade_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + for trading_pair in self._trading_pairs: + snapshot: Dict[str, Any] = await self.get_snapshot( + trading_pair, domain=self._domain, throttler=self._throttler, api_factory=self._api_factory + ) + + snapshot_timestamp: float = time.time() + bids = [] + asks = [] + trading_pair_multipliers = await utils.get_trading_pair_multipliers( + await self.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler + ) + ) + base_mult = trading_pair_multipliers.base_multiplier + + for order in snapshot: + order_details = [order['price'], order['size'] / base_mult] + asks.append(order_details) if order['side'] == "Sell" else bids.append(order_details) + + snapshot_dict = { + "bids": bids, + "asks": asks, + "update_id": snapshot[-1]["id"] + } + snapshot_msg: OrderBookMessage = BitmexOrderBook.snapshot_message_from_exchange( + snapshot_dict, snapshot_timestamp, metadata={"trading_pair": trading_pair} + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + delta = CONSTANTS.ONE_HOUR - time.time() % CONSTANTS.ONE_HOUR + await self._sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...", exc_info=True + ) + await self._sleep(5.0) diff --git a/hummingbot/connector/exchange/bitmex/bitmex_auth.py b/hummingbot/connector/exchange/bitmex/bitmex_auth.py new file mode 100644 index 0000000..0b2a78e --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_auth.py @@ -0,0 +1,57 @@ +import hashlib +import hmac +import json +import time +from urllib.parse import urlencode, urlparse + +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + +EXPIRATION = 25 # seconds + + +class BitmexAuth(AuthBase): + """ + Auth class required by Bitmex API + """ + + def __init__(self, api_key: str, api_secret: str): + self._api_key: str = api_key + self._api_secret: str = api_secret + + @property + def api_key(self): + return self._api_key + + def generate_signature_from_payload(self, payload: str) -> str: + secret = bytes(self._api_secret.encode("utf-8")) + signature = hmac.new(secret, payload.encode("utf-8"), hashlib.sha256).hexdigest() + return signature + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + verb = str(request.method) + expires = str(int(time.time()) + EXPIRATION) + data = json.dumps(request.data) if request.data is not None else '' + parsed_url = urlparse(request.url) + path = parsed_url.path + query = urlencode(request.params) if request.params is not None else '' + if not (query == ''): + query = '?' + query + payload = verb + path + query + expires + data + signature = self.generate_signature_from_payload(payload) + + request.headers = { + "api-expires": expires, + "api-key": self._api_key, + "api-signature": signature, + } + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + return request # pass-through + + async def generate_ws_signature(self, ts: str): + payload = 'GET/realtime' + ts + signature = self.generate_signature_from_payload(payload) + return signature diff --git a/hummingbot/connector/exchange/bitmex/bitmex_exchange.py b/hummingbot/connector/exchange/bitmex/bitmex_exchange.py new file mode 100644 index 0000000..0e69411 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_exchange.py @@ -0,0 +1,1056 @@ +import asyncio +import json +import logging +import time +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Tuple + +import hummingbot.connector.exchange.bitmex.bitmex_utils as utils +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.exchange.bitmex.bitmex_api_order_book_data_source import BitmexAPIOrderBookDataSource +from hummingbot.connector.exchange.bitmex.bitmex_auth import BitmexAuth +from hummingbot.connector.exchange.bitmex.bitmex_in_flight_order import BitmexInFlightOrder +from hummingbot.connector.exchange.bitmex.bitmex_order_book_tracker import BitmexOrderBookTracker +from hummingbot.connector.exchange.bitmex.bitmex_user_stream_tracker import BitmexUserStreamTracker +from hummingbot.connector.exchange_base import ExchangeBase, s_decimal_NaN +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TradeFeeBase +from hummingbot.core.data_type.transaction_tracker import TransactionTracker +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +bpm_logger = None + + +def now(): + return int(time.time()) * 1000 + + +BUY_ORDER_COMPLETED_EVENT = MarketEvent.BuyOrderCompleted +SELL_ORDER_COMPLETED_EVENT = MarketEvent.SellOrderCompleted +ORDER_CANCELLED_EVENT = MarketEvent.OrderCancelled +ORDER_EXPIRED_EVENT = MarketEvent.OrderExpired +ORDER_FILLED_EVENT = MarketEvent.OrderFilled +ORDER_FAILURE_EVENT = MarketEvent.OrderFailure +BUY_ORDER_CREATED_EVENT = MarketEvent.BuyOrderCreated +SELL_ORDER_CREATED_EVENT = MarketEvent.SellOrderCreated +API_CALL_TIMEOUT = 10.0 + +# ========================================================== +UNRECOGNIZED_ORDER_DEBOUCE = 60 # seconds + + +class LatchingEventResponder(EventListener): + def __init__(self, callback: any, num_expected: int): + super().__init__() + self._callback = callback + self._completed = asyncio.Event() + self._num_remaining = num_expected + + def __call__(self, arg: any): + if self._callback(arg): + self._reduce() + + def _reduce(self): + self._num_remaining -= 1 + if self._num_remaining <= 0: + self._completed.set() + + async def wait_for_completion(self, timeout: float): + try: + await asyncio.wait_for(self._completed.wait(), timeout=timeout) + except asyncio.TimeoutError: + pass + return self._completed.is_set() + + def cancel_one(self): + self._reduce() + + +class BitmexExchangeTransactionTracker(TransactionTracker): + def __init__(self, owner): + super().__init__() + self._owner = owner + + def did_timeout_tx(self, tx_id: str): + TransactionTracker.c_did_timeout_tx(self, tx_id) + self._owner.did_timeout_tx(tx_id) + + +class BitmexExchange(ExchangeBase): + API_CALL_TIMEOUT = 10.0 + SHORT_POLL_INTERVAL = 5.0 + LONG_POLL_INTERVAL = 12.0 + ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + HEARTBEAT_TIME_INTERVAL = 30.0 + UPDATE_ORDERS_INTERVAL = 10.0 + + @classmethod + def logger(cls) -> HummingbotLogger: + global bpm_logger + if bpm_logger is None: + bpm_logger = logging.getLogger(__name__) + return bpm_logger + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + bitmex_api_key: str = None, + bitmex_api_secret: str = None, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DOMAIN, + ): + + self._bitmex_time_synchronizer = TimeSynchronizer() + self._auth: BitmexAuth = BitmexAuth(api_key=bitmex_api_key, + api_secret=bitmex_api_secret) + + self._trading_pairs = trading_pairs + self._trading_required = trading_required + self._throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self._domain = domain + self._api_factory = web_utils.build_api_factory( + auth=self._auth) + self._rest_assistant: Optional[RESTAssistant] = None + self._ws_assistant: Optional[WSAssistant] = None + + ExchangeBase.__init__(self, client_config_map=client_config_map) + + self._user_stream_tracker = BitmexUserStreamTracker( + auth=self._auth, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory, + time_synchronizer=self._bitmex_time_synchronizer + ) + self._order_book_tracker = BitmexOrderBookTracker( + trading_pairs=trading_pairs, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory) + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + self._order_not_found_records = defaultdict(int) + self._last_timestamp = 0 + self._trading_rules = {} + self._in_flight_orders = {} + self._status_polling_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._user_stream_tracker_task = None + self._last_poll_timestamp = 0 + self._client_order_tracker: ClientOrderTracker = ClientOrderTracker(connector=self) + self._trading_pair_to_multipliers = {} + self._trading_pair_price_estimate_for_quantize = {} + self._token_multiplier = {} + + @property + def name(self) -> str: + # Note: domain here refers to the entire exchange name. i.e. bitmex or bitmex_testnet + return self._domain + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self._order_book_tracker.order_books + + @property + def ready(self): + return all(self.status_dict.values()) + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrder]: + return self._in_flight_orders + + @property + def status_dict(self): + sd = { + "symbols_mapping_initialized": BitmexAPIOrderBookDataSource.trading_pair_symbol_map_ready( + domain=self._domain), + "order_books_initialized": self._order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0, + "user_stream_initialized": self._user_stream_tracker.data_source.last_recv_time > 0, + } + return sd + + @property + def limit_orders(self) -> List[LimitOrder]: + return [order.to_limit_order() for order in self._client_order_tracker.all_orders.values()] + + @property + def tracking_states(self) -> Dict[str, any]: + """ + :return active in-flight orders in json format, is used to save in sqlite db. + """ + return { + client_order_id: in_flight_order.to_json() + for client_order_id, in_flight_order in self._client_order_tracker.active_orders.items() + if not in_flight_order.is_done + } + + def restore_tracking_states(self, saved_states: Dict[str, any]): + for order_id, in_flight_repr in saved_states.items(): + if isinstance(in_flight_repr, dict): + in_flight_json: Dict[str, Any] = in_flight_repr + else: + in_flight_json: Dict[str, Any] = json.loads(in_flight_repr) + order = BitmexInFlightOrder.from_json(in_flight_json) + if not order.is_done: + self._in_flight_orders[order_id] = order + + def supported_order_types(self) -> List[OrderType]: + """ + Returns list of OrderType supported by this connector. + """ + return [OrderType.LIMIT, OrderType.MARKET] + + async def start_network(self): + """ + This function is required by the NetworkIterator base class and is called automatically. + It starts tracking order books, polling trading rules, updating statuses, and tracking user data. + """ + self._order_book_tracker.start() + await self._update_trading_rules() + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + async def stop_network(self): + """ + This function is required by the NetworkIterator base class and is called automatically. + It performs the necessary shut down procedure. + """ + self._stop_network() + + async def check_network(self) -> NetworkStatus: + """ + This function is required by NetworkIterator base class and is called periodically to check + the network connection. Ping the network (or call any lightweight public API). + """ + try: + await self._api_request(path=CONSTANTS.PING_URL) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + def quantize_order_amount(self, trading_pair: str, amount: object, price: object = Decimal(0)): + trading_rule = self._trading_rules[trading_pair] + quantized_amount: Decimal = super().quantize_order_amount(trading_pair, amount) + + # Check against min_order_size and min_notional_size. If not passing either check, return 0. + if quantized_amount < trading_rule.min_order_size: + return Decimal('0') + + if price == Decimal('0'): + current_price: Decimal = self.get_price(trading_pair, False) + notional_size = current_price * quantized_amount + else: + notional_size = price * quantized_amount + + # Add 1% as a safety factor in case the prices changed while making the order. + if notional_size < trading_rule.min_notional_size * Decimal("1.01"): + return Decimal('0') + + return quantized_amount + + def get_order_price_quantum(self, trading_pair: str, price: object): + """ + Returns a price step, a minimum price increment for a given trading pair. + + Parameters + ---------- + trading_pair: + The pair to which the quantization will apply + price: + Price to be quantized + """ + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + def get_order_size_quantum(self, trading_pair: str, order_size: object): + """ + Returns an order amount step, a minimum amount increment for a given trading pair. + + Parameters + ---------- + trading_pair: + The pair to which the quantization will apply + order_size: + Size to be quantized + """ + trading_rule: TradingRule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + """ + Calculates the estimated fee an order would pay based on the connector configuration + :param base_currency: the order base currency + :param quote_currency: the order quote currency + :param order_type: the type of order (MARKET, LIMIT, LIMIT_MAKER) + :param order_side: if the order is for buying or selling + :param amount: the order amount + :param price: the order price + :return: the estimated fee for the order + """ + + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = order_type is OrderType.LIMIT + return DeductedFromReturnsTradeFee(percent=self.estimate_fee_pct(is_maker)) + + def start_tracking_order( + self, + order_side: TradeType, + client_order_id: str, + order_type: OrderType, + created_at: float, + hash: str, + trading_pair: str, + price: Decimal, + amount: Decimal, + ): + in_flight_order = BitmexInFlightOrder( + client_order_id, + None, + trading_pair, + order_type, + order_side, + price, + amount, + created_at, + ) + self._in_flight_orders[in_flight_order.client_order_id] = in_flight_order + + def stop_tracking_order(self, order_id: str): + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + def time_now_s(self) -> float: + return time.time() + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + now = time.time() + poll_interval = (self.SHORT_POLL_INTERVAL + if now - self._user_stream_tracker.last_recv_time > 60.0 + else self.LONG_POLL_INTERVAL) + last_tick = int(self._last_timestamp / poll_interval) + current_tick = int(timestamp / poll_interval) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + + self._last_timestamp = timestamp + + def get_order_book(self, trading_pair: str) -> OrderBook: + """ + They are used by the OrderBookCommand to display the order book in the terminal. + + Parameters + ---------- + trading_pair: + The pair for which the order book should be obtained + """ + order_books: dict = self._order_book_tracker.order_books + if trading_pair not in order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return order_books[trading_pair] + + def set_leverage(self, trading_pair: str, leverage: int = 1): + self._leverage[trading_pair] = leverage + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + def _stop_network(self): + # Reset timestamps and _poll_notifier for status_polling_loop + self._last_poll_timestamp = 0 + self._last_timestamp = 0 + self._poll_notifier = asyncio.Event() + + self._order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._status_polling_task = self._user_stream_tracker_task = \ + self._user_stream_event_listener_task = None + + async def _update_order_status(self): + last_tick = int(self._last_poll_timestamp / self.UPDATE_ORDERS_INTERVAL) + current_tick = int(self.current_timestamp / self.UPDATE_ORDERS_INTERVAL) + + if current_tick > last_tick and len(self._in_flight_orders) > 0: + in_flight_orders_copy = self._in_flight_orders.copy() + for trading_pair in self._trading_pairs: + exchange_trading_pair = await BitmexAPIOrderBookDataSource.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler, + ) + response = await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + method=RESTMethod.GET, + params={"symbol": exchange_trading_pair} + ) + orders = response + for order in orders: + client_order_id = order.get('clOrdID') + if client_order_id is not None: + tracked_order = self._in_flight_orders.get(client_order_id) + if tracked_order is not None: + del in_flight_orders_copy[client_order_id] + self._update_inflight_order(tracked_order, order) + for client_order_id, in_flight_order in in_flight_orders_copy.items(): + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # We'll just have to assume that this order doesn't exist + cancellation_event = OrderCancelledEvent(now(), client_order_id) + self.stop_tracking_order(client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from Bitmex. Check API key and network connection.", + ) + await self._sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Wait for new messages from _user_stream_tracker.user_stream queue and processes them according to their + message channels. The respective UserStreamDataSource queues these messages. + """ + async for event_message in self._iter_user_event_queue(): + try: + await self._process_user_stream_event(event_message) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Unexpected error in user stream listener loop: {e}", exc_info=True) + await self._sleep(5.0) + + async def _process_user_stream_event(self, event_message: Dict[str, Any]): + topic = event_message.get("table") + data = event_message.get("data") + if topic == "wallet": + for currency_info in data: + if "deltaAmount" in currency_info: + delta = currency_info.get("deltaAmount") + if delta > 0: + currency_info["pendingCredit"] = Decimal(str(delta)) + currency_info["pendingDebit"] = Decimal('0') + else: + currency_info["pendingDebit"] = Decimal(str(abs(delta))) + currency_info["pendingCredit"] = Decimal('0') + await self.set_balance(currency_info) + elif topic == "execution": + for order in data: + client_order_id = order.get("clOrdID") + if client_order_id is not None: + tracked_order = self._in_flight_orders.get(client_order_id) + if tracked_order is not None: + self._update_inflight_order(tracked_order, order) + + async def _update_trading_rules(self): + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + """ + last_tick = int(self._last_timestamp / 60.0) + current_tick = int(self.current_timestamp / 60.0) + if current_tick > last_tick or len(self._trading_rules) < 1: + exchange_info = await self._api_request(path=CONSTANTS.EXCHANGE_INFO_URL, + method=RESTMethod.GET, + params={"filter": json.dumps({"typ": "IFXXXP"})} + ) + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + await self._update_trading_pair_prices_for_quantize() + + async def _format_trading_rules(self, exchange_info_list: List[Dict[str, Any]]) -> List[TradingRule]: + """ + Queries the necessary API endpoint and initialize the TradingRule object for each trading pair being traded. + + Parameters + ---------- + exchange_info_dict: + Trading rules dictionary response from the exchange + """ + return_val: list = [] + for rule in exchange_info_list: + try: + trading_pair = combine_to_hb_trading_pair(rule["rootSymbol"], rule["quoteCurrency"]) + if trading_pair in self._trading_pairs: + trading_pair_multipliers = await utils.get_trading_pair_multipliers(rule['symbol']) + self._trading_pair_to_multipliers[trading_pair] = trading_pair_multipliers + max_order_size = Decimal(str(rule.get("maxOrderQty"))) + + min_order_size = Decimal(str(rule.get("lotSize"))) / trading_pair_multipliers.base_multiplier + + tick_size = Decimal(str(rule.get("tickSize"))) + return_val.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=Decimal(tick_size), + min_base_amount_increment=Decimal(min_order_size), + max_order_size=max_order_size, + ) + ) + + except Exception as e: + self.logger().error( + f"Error parsing the trading pair rule {rule}. Error: {e}. Skipping...", exc_info=True + ) + return return_val + + async def _update_trading_pair_prices_for_quantize(self): + for trading_pair in self._trading_pairs: + price = await BitmexAPIOrderBookDataSource.get_last_traded_price( + trading_pair, + self._domain + ) + self._trading_pair_price_estimate_for_quantize[trading_pair] = Decimal(price) + + async def _trading_rules_polling_loop(self): + """ + An asynchronous task that periodically updates trading rules. + """ + while True: + try: + await safe_gather(self._update_trading_rules()) + await self._sleep(CONSTANTS.ONE_HOUR) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading rules.", + exc_info=True, + app_warning_msg="Could not fetch new trading rules from Bitmex. " + "Check network connection.", + ) + await self._sleep(0.5) + + async def _status_polling_loop(self): + """ + Periodically update user balances and order status via REST API. This serves as a fallback measure for + socket API updates. Calling of both _update_balances() and _update_order_status() functions is + determined by the _poll_notifier variable. + """ + while True: + try: + await self._poll_notifier.wait() + # await self._update_time_synchronizer() + await safe_gather( + self._update_balances(), + ) + await self._update_order_status() + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error while fetching account updates.", exc_info=True, + app_warning_msg="Could not fetch account updates from Bitmex. " + "Check API key and network connection.") + await self._sleep(0.5) + finally: + self._poll_notifier = asyncio.Event() + + def _update_inflight_order(self, tracked_order: BitmexInFlightOrder, event: Dict[str, Any]): + trading_pair_multiplier = self._trading_pair_to_multipliers[tracked_order.trading_pair] + event["amount_remaining"] = Decimal(str(event["leavesQty"])) / trading_pair_multiplier.base_multiplier + + issuable_events: List[MarketEvent] = tracked_order.update(event) + + # Issue relevent events + for (market_event, new_amount, new_price, new_fee) in issuable_events: + base, quote = self.split_trading_pair(tracked_order.trading_pair) + if market_event == MarketEvent.OrderFilled: + self.trigger_event(ORDER_FILLED_EVENT, + OrderFilledEvent(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + new_price, + new_amount, + build_trade_fee( + self._domain, + True, + base, + quote, + tracked_order.order_type, + tracked_order.trade_type, + new_amount, + new_price + ), + tracked_order.client_order_id)) + elif market_event == MarketEvent.OrderCancelled: + self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id}") + self.stop_tracking_order(tracked_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, + OrderCancelledEvent(self.current_timestamp, + tracked_order.client_order_id)) + elif market_event == MarketEvent.BuyOrderCompleted: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.trigger_event(BUY_ORDER_COMPLETED_EVENT, + BuyOrderCompletedEvent(self.current_timestamp, + tracked_order.client_order_id, + base, + quote, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type, + tracked_order.exchange_order_id)) + elif market_event == MarketEvent.SellOrderCompleted: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.trigger_event(SELL_ORDER_COMPLETED_EVENT, + SellOrderCompletedEvent(self.current_timestamp, + tracked_order.client_order_id, + base, + quote, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type, + tracked_order.exchange_order_id)) + # Complete the order if relevent + if tracked_order.is_done: + self.stop_tracking_order(tracked_order.client_order_id) + + def adjust_quote_based_amounts( + self, + trading_pair: str, + price: Decimal, + amount: Decimal + ) -> Tuple[Decimal, Decimal]: + trading_pair_multipliers = self._trading_pair_to_multipliers[trading_pair] + trading_rule = self._trading_rules[trading_pair] + lot_size = trading_rule.min_order_size * trading_pair_multipliers.base_multiplier + quote_amount = amount * price + strp_amount = int(quote_amount) % int(lot_size) + quote_amount = int(quote_amount - strp_amount) + base_amount = Decimal(quote_amount) / price + return base_amount, quote_amount + + async def place_order( + self, + client_order_id: str, + trading_pair: str, + amount: Decimal, + is_buy: bool, + order_type: OrderType, + price: Decimal + ) -> Dict[str, Any]: + + symbol = await BitmexAPIOrderBookDataSource.convert_to_exchange_trading_pair( + hb_trading_pair=trading_pair, + domain=self._domain, + throttler=self._throttler, + ) + trading_pair_multipliers = self._trading_pair_to_multipliers[trading_pair] + amount *= trading_pair_multipliers.base_multiplier + + order_side = "Buy" if is_buy else "Sell" + bitmex_order_type = "Limit" if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "Market" + + params = { + "symbol": symbol, + "side": order_side, + "orderQty": str(float(amount)), + "clOrdID": client_order_id, + "ordType": bitmex_order_type, + "text": CONSTANTS.BROKER_ID + } + + if bitmex_order_type == "Limit": + params['price'] = str(float(price)) + + return await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + params=params, + method=RESTMethod.POST + ) + + async def execute_order( + self, order_side, client_order_id, trading_pair, amount, order_type, price + ): + """ + Completes the common tasks from execute_buy and execute_sell. Quantizes the order's amount and price, and + validates the order against the trading rules before placing this order. + """ + + # Quantize order + price = self.quantize_order_price(trading_pair, price) + amount = self.quantize_order_amount(trading_pair, amount, price) + + if amount == Decimal(0): + raise ValueError( + "Order amount or notional size is insufficient" + ) + # Check trading rules + trading_rule = self._trading_rules[trading_pair] + + if amount > trading_rule.max_order_size: + raise ValueError( + f"Order amount({str(amount)}) is greater than the maximum allowable amount({str(trading_rule.max_order_size)})" + ) + + try: + created_at = self.time_now_s() + + base_amount = amount + self.start_tracking_order( + order_side, + client_order_id, + order_type, + created_at, + None, + trading_pair, + price, + base_amount + ) + + try: + creation_response = await self.place_order( + client_order_id, + trading_pair, + amount, + order_side is TradeType.BUY, + order_type, + price + ) + except asyncio.TimeoutError: + return + + # Verify the response from the exchange + order = creation_response + + status = order["ordStatus"] + if status not in ["New", "PartiallyFilled", "Filled"]: + raise Exception(status) + + bitmex_order_id = order["orderID"] + + in_flight_order = self._in_flight_orders.get(client_order_id) + if in_flight_order is not None: + # Begin tracking order + in_flight_order.update_exchange_order_id(bitmex_order_id) + self.logger().info( + f"Created order {client_order_id} for {amount} {trading_pair}." + ) + else: + self.logger().info(f"Created order {client_order_id} for {amount} {trading_pair}.") + + except Exception as e: + self.logger().warning( + f"Error submitting {order_side.name} {order_type.name} order to bitmex for " + f"{amount} {trading_pair} at {price}." + ) + self.logger().info(e, exc_info=True) + + # Stop tracking this order + self.stop_tracking_order(client_order_id) + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), client_order_id, order_type)) + + async def execute_buy( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = Decimal("NaN"), + ): + try: + await self.execute_order(TradeType.BUY, order_id, trading_pair, amount, order_type, price) + tracked_order = self.in_flight_orders.get(order_id) + if tracked_order is not None: + self.trigger_event( + BUY_ORDER_CREATED_EVENT, + BuyOrderCreatedEvent( + now(), + order_type, + trading_pair, + Decimal(amount), + Decimal(price), + order_id, + tracked_order.creation_timestamp), + ) + + except ValueError as e: + # never tracked, so no need to stop tracking + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), order_id, order_type)) + self.logger().warning(f"Failed to place {order_id} on bitmex. {str(e)}") + + async def execute_sell( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = Decimal("NaN"), + ): + try: + await self.execute_order(TradeType.SELL, order_id, trading_pair, amount, order_type, price) + tracked_order = self.in_flight_orders.get(order_id) + if tracked_order is not None: + self.trigger_event( + SELL_ORDER_CREATED_EVENT, + SellOrderCreatedEvent( + now(), + order_type, + trading_pair, + Decimal(amount), + Decimal(price), + order_id, + tracked_order.creation_timestamp, + ), + ) + + except ValueError as e: + # never tracked, so no need to stop tracking + self.trigger_event(ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), order_id, order_type)) + self.logger().warning(f"Failed to place {order_id} on bitmex. {str(e)}") + + def buy( + self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, **kwargs + ) -> str: + client_order_id: str = get_new_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + safe_ensure_future( + self.execute_buy(client_order_id, trading_pair, amount, order_type, price) + ) + return client_order_id + + def sell( + self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, **kwargs + ) -> str: + client_order_id: str = get_new_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + safe_ensure_future( + self.execute_sell(client_order_id, trading_pair, amount, order_type, price) + ) + return client_order_id + + # ---------------------------------------- + # Cancellation + + async def cancel_order(self, client_order_id: str): + in_flight_order = self._in_flight_orders.get(client_order_id) + cancellation_event = OrderCancelledEvent(now(), client_order_id) + exchange_order_id = in_flight_order.exchange_order_id + + if in_flight_order is None: + self.logger().warning("Cancelled an untracked order {client_order_id}") + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + + try: + if exchange_order_id is None: + # Note, we have no way of canceling an order or querying for information about the order + # without an exchange_order_id + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # We'll just have to assume that this order doesn't exist + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + params = {"clOrdID": client_order_id} + await self._api_request( + path=CONSTANTS.ORDER_URL, + is_auth_required=True, + params=params, + method=RESTMethod.DELETE + ) + return True + + except Exception as e: + if "Not Found" in str(e): + if in_flight_order.creation_timestamp < (self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE): + # Order didn't exist on exchange, mark this as canceled + self.stop_tracking_order(in_flight_order.client_order_id) + self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event) + return False + else: + raise Exception( + f"order {client_order_id} does not yet exist on the exchange and could not be cancelled." + ) + except Exception as e: + self.logger().warning(f"Failed to cancel order {client_order_id}") + self.logger().info(e) + return False + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + cancellation_queue = self._in_flight_orders.copy() + if len(cancellation_queue) == 0: + return [] + + order_status = {o.client_order_id: o.is_done for o in cancellation_queue.values()} + + def set_cancellation_status(oce: OrderCancelledEvent): + if oce.order_id in order_status: + order_status[oce.order_id] = True + return True + return False + + cancel_verifier = LatchingEventResponder(set_cancellation_status, len(cancellation_queue)) + self.add_listener(ORDER_CANCELLED_EVENT, cancel_verifier) + + for order_id, in_flight in cancellation_queue.items(): + try: + if order_status[order_id]: + cancel_verifier.cancel_one() + elif not await self.cancel_order(order_id): + # this order did not exist on the exchange + cancel_verifier.cancel_one() + order_status[order_id] = True + except Exception: + cancel_verifier.cancel_one() + order_status[order_id] = True + + await cancel_verifier.wait_for_completion(timeout_seconds) + self.remove_listener(ORDER_CANCELLED_EVENT, cancel_verifier) + + return [CancellationResult(order_id=order_id, success=success) for order_id, success in order_status.items()] + + def cancel(self, trading_pair: str, client_order_id: str): + return safe_ensure_future(self.cancel_order(client_order_id)) + + async def _initialize_token_decimals(self): + token_info = await self._api_request( + path=CONSTANTS.TOKEN_INFO_URL + ) + for asset in token_info: + zeros = asset['scale'] + currency = asset['asset'] + self._token_multiplier[currency] = Decimal(f"1e{zeros}") + + async def _update_balances(self): + account_info = await self._api_request( + path=CONSTANTS.ACCOUNT_INFO_URL, + is_auth_required=True, + params={"currency": "all"} + ) + for currency_info in account_info: + await self.set_balance(currency_info) + + async def set_balance(self, data: Dict[str, Any]): + if not (len(self._token_multiplier) > 0): + await self._initialize_token_decimals() + asset_name = data['currency'].upper() + asset_name = "ETH" if asset_name == "GWEI" else asset_name + total_balance = Decimal(str(data['amount'])) + pending_credit = Decimal(str(data['pendingCredit'])) + pending_debit = Decimal(str(data['pendingDebit'])) + available_balance = total_balance - pending_credit + pending_debit + + multiplier: Decimal = self._token_multiplier[asset_name] + self._account_balances[asset_name] = Decimal(total_balance) / multiplier + self._account_available_balances[asset_name] = Decimal(available_balance) / multiplier + + async def _api_request(self, + path: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None): + + try: + return await web_utils.api_request( + path=path, + api_factory=self._api_factory, + throttler=self._throttler, + domain=self._domain, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + limit_id=limit_id) + except Exception as e: + self.logger().error(f"Error fetching {path}", exc_info=True) + self.logger().warning(f"{e}") + raise e + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) + + async def all_trading_pairs(self) -> List[str]: + return await BitmexAPIOrderBookDataSource.fetch_trading_pairs() diff --git a/hummingbot/connector/exchange/bitmex/bitmex_in_flight_order.py b/hummingbot/connector/exchange/bitmex/bitmex_in_flight_order.py new file mode 100644 index 0000000..684e386 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_in_flight_order.py @@ -0,0 +1,136 @@ +from decimal import Decimal +from typing import Any, Dict, List, Optional + +from hummingbot.connector.exchange.bitmex.bitmex_order_status import BitmexOrderStatus +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.event.events import MarketEvent, OrderType, TradeType + + +class BitmexInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + created_at: float, + initial_state: str = "New"): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + created_at, + initial_state + ) + self.created_at = created_at + self.state = BitmexOrderStatus.New + + def __repr__(self) -> str: + return f"super().__repr__()" \ + f"created_at='{str(self.created_at)}'')" + + def to_json(self) -> Dict[str, Any]: + response = super().to_json() + response["created_at"] = str(self.created_at) + return response + + @property + def is_done(self) -> bool: + return self.state in [BitmexOrderStatus.Canceled, BitmexOrderStatus.Filled, BitmexOrderStatus.FAILURE] + + @property + def is_failure(self) -> bool: + return self.state is BitmexOrderStatus.FAILURE or self.is_cancelled + + @property + def is_cancelled(self) -> bool: + return self.state is BitmexOrderStatus.Canceled and self.executed_amount_base < self.amount + + def set_status(self, status: str): + self.last_state = status + self.state = BitmexOrderStatus[status] + + @property + def order_type_description(self) -> str: + order_type = "market" if self.order_type is OrderType.MARKET else "limit" + side = "buy" if self.trade_type is TradeType.BUY else "sell" + return f"{order_type} {side}" + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase: + retval: BitmexInFlightOrder = BitmexInFlightOrder( + data["client_order_id"], + data["exchange_order_id"], + data["trading_pair"], + getattr(OrderType, data["order_type"]), + getattr(TradeType, data["trade_type"]), + Decimal(data["price"]), + Decimal(data["amount"]), + float(data["created_at"] if "created_at" in data else 0), + data["last_state"], + ) + retval.executed_amount_base = Decimal(data.get("executed_amount_base", '0')) + retval.executed_amount_quote = Decimal(data.get("executed_amount_quote", '0')) + last_state = int(data["last_state"]) + retval.state = BitmexOrderStatus(last_state) + return retval + + def update(self, data: Dict[str, Any]) -> List[Any]: + events: List[Any] = [] + + incoming_status = data.get("ordStatus") + if incoming_status is not None: + new_status: BitmexOrderStatus = BitmexOrderStatus[data["ordStatus"]] + else: + if self.status < BitmexOrderStatus.Cancelled: + # no status is sent over websocket fills + new_status: BitmexOrderStatus = BitmexOrderStatus.PartiallyFilled + else: + new_status = self.status + old_executed_base: Decimal = self.executed_amount_base + old_executed_quote: Decimal = self.executed_amount_quote + if new_status == BitmexOrderStatus.Canceled: + overall_executed_base = self.executed_amount_base + overall_remaining_size = self.amount - overall_executed_base + overall_executed_quote = self.executed_amount_quote + else: + if "amount_remaining" in data: + overall_remaining_size: Decimal = data["amount_remaining"] + overall_executed_base: Decimal = self.amount - overall_remaining_size + else: + overall_remaining_quote: Decimal = Decimal(str(data["quote_amount_remaining"])) + overall_remaining_size: Decimal = overall_remaining_quote / Decimal(str(data["price"])) + overall_executed_base: Decimal = self.amount - overall_remaining_size + + if data.get("avgPx") is not None: + overall_executed_quote: Decimal = overall_executed_base * Decimal(str(data["avgPx"])) + else: + overall_executed_quote: Decimal = Decimal("0") + + diff_base: Decimal = overall_executed_base - old_executed_base + diff_quote: Decimal = overall_executed_quote - old_executed_quote + + if diff_base > 0: + diff_price: Decimal = diff_quote / diff_base + events.append((MarketEvent.OrderFilled, diff_base, diff_price, None)) + self.executed_amount_base = overall_executed_base + self.executed_amount_quote = overall_executed_quote + + if not self.is_done and new_status in [BitmexOrderStatus.Canceled, BitmexOrderStatus.Filled]: + if overall_remaining_size > 0: + events.append((MarketEvent.OrderCancelled, None, None, None)) + elif self.trade_type is TradeType.BUY: + events.append((MarketEvent.BuyOrderCompleted, overall_executed_base, overall_executed_quote, None)) + else: + events.append((MarketEvent.SellOrderCompleted, overall_executed_base, overall_executed_quote, None)) + + self.state = new_status + self.last_state = new_status.name + + return events diff --git a/hummingbot/connector/exchange/bitmex/bitmex_order_book.py b/hummingbot/connector/exchange/bitmex/bitmex_order_book.py new file mode 100644 index 0000000..337f489 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_order_book.py @@ -0,0 +1,58 @@ +import logging +from datetime import datetime +from typing import Dict, Optional + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.event.events import TradeType +from hummingbot.logger import HummingbotLogger + + +class BitmexOrderBook(OrderBook): + _bpob_logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._baobds_logger is None: + cls._baobds_logger = logging.getLogger(__name__) + return cls._baobds_logger + + @classmethod + def snapshot_message_from_exchange(cls, msg: Dict[str, any], timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": timestamp, + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, msg: Dict[str, any], timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + data = msg["data_dict"] + if metadata: + data.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": data["symbol"], + "update_id": timestamp, + "bids": data["bids"], + "asks": data["asks"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + data = msg + if metadata: + data.update(metadata) + timestamp = datetime.timestamp(datetime.strptime(data["timestamp"], "%Y-%m-%dT%H:%M:%S.%fZ")) + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": data["symbol"], + "trade_type": float(TradeType.SELL.value) if data["side"] == "Sell" else float(TradeType.BUY.value), + "trade_id": timestamp, + "update_id": timestamp, + "price": data["price"], + "amount": data["size"] + }, timestamp=timestamp) diff --git a/hummingbot/connector/exchange/bitmex/bitmex_order_book_tracker.py b/hummingbot/connector/exchange/bitmex/bitmex_order_book_tracker.py new file mode 100644 index 0000000..a00c520 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_order_book_tracker.py @@ -0,0 +1,61 @@ +import asyncio +import logging +from collections import defaultdict, deque +from typing import Deque, Dict, List, Optional + +from hummingbot.connector.exchange.bitmex.bitmex_api_order_book_data_source import BitmexAPIOrderBookDataSource +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class BitmexOrderBookTracker(OrderBookTracker): + _bpobt_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpobt_logger is None: + cls._bpobt_logger = logging.getLogger(__name__) + return cls._bpobt_logger + + def __init__(self, + trading_pairs: Optional[List[str]] = None, + domain: str = "bitmex", + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None): + super().__init__(data_source=BitmexAPIOrderBookDataSource( + trading_pairs=trading_pairs, + domain=domain, + throttler=throttler, + api_factory=api_factory), + trading_pairs=trading_pairs, + domain=domain + ) + + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._saved_messages_queues: Dict[str, Deque[OrderBookMessage]] = defaultdict(lambda: deque(maxlen=1000)) + self._trading_pairs: Optional[List[str]] = trading_pairs + self._domain = domain + + self._order_book_stream_listener_task: Optional[asyncio.Task] = None + self._order_book_funding_info_listener_task: Optional[asyncio.Task] = None + + def is_funding_info_initialized(self) -> bool: + return self._data_source.is_funding_info_initialized() + + @property + def exchange_name(self) -> str: + return self._domain + + def start(self): + super().start() + + def stop(self): + self._order_book_stream_listener_task and self._order_book_stream_listener_task.cancel() + self._order_book_funding_info_listener_task and self._order_book_funding_info_listener_task.cancel() + super().stop() diff --git a/hummingbot/connector/exchange/bitmex/bitmex_order_status.py b/hummingbot/connector/exchange/bitmex/bitmex_order_status.py new file mode 100644 index 0000000..02be092 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_order_status.py @@ -0,0 +1,29 @@ +from enum import Enum + + +class BitmexOrderStatus(Enum): + New = 0 + PartiallyFilled = 101 + Canceled = 200 + Filled = 201 + FAILURE = 300 + + def __ge__(self, other): + if self.__class__ is other.__class__: + return self.value >= other.value + return NotImplemented + + def __gt__(self, other): + if self.__class__ is other.__class__: + return self.value > other.value + return NotImplemented + + def __le__(self, other): + if self.__class__ is other.__class__: + return self.value <= other.value + return NotImplemented + + def __lt__(self, other): + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented diff --git a/hummingbot/connector/exchange/bitmex/bitmex_user_stream_data_source.py b/hummingbot/connector/exchange/bitmex/bitmex_user_stream_data_source.py new file mode 100644 index 0000000..3620d11 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_user_stream_data_source.py @@ -0,0 +1,102 @@ +import asyncio +import logging +import time +from typing import Optional + +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +from hummingbot.connector.exchange.bitmex.bitmex_auth import BitmexAuth +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BitmexUserStreamDataSource(UserStreamTrackerDataSource): + + _bpusds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bpusds_logger is None: + cls._bpusds_logger = logging.getLogger(__name__) + return cls._bpusds_logger + + def __init__( + self, + auth: BitmexAuth, + domain: str = "bitmex", + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + ): + super().__init__() + self._time_synchronizer = time_synchronizer + self._domain = domain + self._throttler = throttler + self._api_factory: WebAssistantsFactory = api_factory or web_utils.build_api_factory( + auth=auth + ) + self._auth = auth + self._ws_assistant: Optional[WSAssistant] = None + + @property + def last_recv_time(self) -> float: + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + ws = None + while True: + try: + expires = int(time.time()) + 25 + url = web_utils.wss_url("", self._domain) + # # establish initial connection to websocket + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect(ws_url=url) + + # # send auth request + API_KEY = self._auth.api_key + signature = await self._auth.generate_ws_signature(str(expires)) + auth_payload = {"op": "authKeyExpires", "args": [API_KEY, expires, signature]} + + auth_request: WSJSONRequest = WSJSONRequest( + payload=auth_payload, + is_auth_required=False + ) + await ws.send(auth_request) + + # # send subscribe + # order - Live updates on your orders + # wallet - Bitcoin address balance data, including total deposits & withdrawals + subscribe_payload = {"op": "subscribe", "args": ["execution", "wallet"]} + subscribe_request: WSJSONRequest = WSJSONRequest( + payload=subscribe_payload, + is_auth_required=False + ) + await ws.send(subscribe_request) + + async for msg in ws.iter_messages(): + if len(msg.data) > 0: + output.put_nowait(msg.data) + + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error( + f"Unexpected error while listening to user stream. Retrying after 5 seconds... " + f"Error: {e}", + exc_info=True, + ) + finally: + # Make sure no background task is leaked. + ws and await ws.disconnect() + await self._sleep(5) diff --git a/hummingbot/connector/exchange/bitmex/bitmex_user_stream_tracker.py b/hummingbot/connector/exchange/bitmex/bitmex_user_stream_tracker.py new file mode 100644 index 0000000..589e21e --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_user_stream_tracker.py @@ -0,0 +1,67 @@ +import asyncio +import logging +from typing import Optional + +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.exchange.bitmex.bitmex_auth import BitmexAuth +from hummingbot.connector.exchange.bitmex.bitmex_user_stream_data_source import BitmexUserStreamDataSource +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class BitmexUserStreamTracker(UserStreamTracker): + + _bpust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bust_logger is None: + cls._bust_logger = logging.getLogger(__name__) + return cls._bust_logger + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + def __init__(self, + auth: BitmexAuth, + domain: str = CONSTANTS.DOMAIN, + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + time_synchronizer: Optional[TimeSynchronizer] = None): + self._data_source: Optional[UserStreamTrackerDataSource] = None + self._auth: BitmexAuth = auth + self._domain = domain + self._throttler = throttler + self._api_factory = api_factory + self._time_synchronizer = time_synchronizer + + super().__init__(self._data_source) + self._ev_loop: asyncio.events.AbstractEventLoop = asyncio.get_event_loop() + self._user_stream_tracking_task: Optional[asyncio.Task] = None + + @property + def exchange_name(self) -> str: + return self._domain + + @property + def data_source(self) -> UserStreamTrackerDataSource: + if self._data_source is None: + self._data_source = BitmexUserStreamDataSource( + auth=self._auth, + domain=self._domain, + throttler=self._throttler, + api_factory=self._api_factory, + time_synchronizer=self._time_synchronizer) + return self._data_source + + async def start(self): + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._ev_loop, self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/bitmex/bitmex_utils.py b/hummingbot/connector/exchange/bitmex/bitmex_utils.py new file mode 100644 index 0000000..3bb08d9 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_utils.py @@ -0,0 +1,130 @@ +from collections import namedtuple + +from pydantic import Field, SecretStr + +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData + +CENTRALIZED = True + + +EXAMPLE_PAIR = "ETH-XBT" + + +DEFAULT_FEES = [0.1, 0.1] + + +class BitmexConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitmex", const=True, client_data=None) + bitmex_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmex_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitmex" + + +KEYS = BitmexConfigMap.construct() + +OTHER_DOMAINS = ["bitmex_testnet"] +OTHER_DOMAINS_PARAMETER = {"bitmex_testnet": "bitmex_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"bitmex_testnet": "ETH-XBT"} +OTHER_DOMAINS_DEFAULT_FEES = {"bitmex_testnet": [0.02, 0.04]} + + +class BitmexTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bitmex_testnet", const=True, client_data=None) + bitmex_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + bitmex_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bitmex Testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bitmex_testnet" + + +OTHER_DOMAINS_KEYS = {"bitmex_testnet": BitmexTestnetConfigMap.construct()} + + +TRADING_PAIR_INDICES: dict = {} +TRADING_PAIR_INDEX = namedtuple('TradingPairIndex', 'index tick_size') +TRADING_PAIR_MULTIPLIERS: dict = {} +TRADING_PAIR_MULTIPLIERS_TUPLE = namedtuple('TradingPairMultipliersTuple', 'base_multiplier quote_multiplier') + + +async def get_trading_pair_multipliers(exchange_trading_pair): + if exchange_trading_pair in TRADING_PAIR_MULTIPLIERS: + return TRADING_PAIR_MULTIPLIERS[exchange_trading_pair] + else: + instrument = await web_utils.api_request( + path = CONSTANTS.EXCHANGE_INFO_URL, + domain = "bitmex_testnet", + params = {"symbol": exchange_trading_pair} + ) + trading_pair_info = instrument[0] + base_multiplier = trading_pair_info.get("underlyingToPositionMultiplier") + quote_multiplier = trading_pair_info.get("quoteToSettleMultiplier") + TRADING_PAIR_MULTIPLIERS[exchange_trading_pair] = TRADING_PAIR_MULTIPLIERS_TUPLE(base_multiplier, quote_multiplier) + + return TRADING_PAIR_MULTIPLIERS[exchange_trading_pair] + + +async def get_trading_pair_index_and_tick_size(exchange_trading_pair): + if exchange_trading_pair in TRADING_PAIR_INDICES: + return TRADING_PAIR_INDICES[exchange_trading_pair] + else: + index = 0 + multiplier = 0 + while True: + offset = 500 * multiplier + instruments = await web_utils.api_request( + path = CONSTANTS.EXCHANGE_INFO_URL, + domain = "bitmex", + params = { + "count": 500, + "start": offset + } + ) + for instrument in instruments: + if instrument['symbol'] == exchange_trading_pair: + TRADING_PAIR_INDICES[exchange_trading_pair] = TRADING_PAIR_INDEX( + index, + instrument['tickSize'] + ) + return TRADING_PAIR_INDICES[exchange_trading_pair] + else: + index += 1 + if len(instruments) < 500: + return False + else: + multiplier += 1 diff --git a/hummingbot/connector/exchange/bitmex/bitmex_web_utils.py b/hummingbot/connector/exchange/bitmex/bitmex_web_utils.py new file mode 100644 index 0000000..0d44e75 --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/bitmex_web_utils.py @@ -0,0 +1,88 @@ +from typing import Any, Dict, Optional + +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitmexRESTPreProcessor(RESTPreProcessorBase): + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + if request.headers is None: + request.headers = {} + request.headers["Content-Type"] = ( + "application/json" if request.method == RESTMethod.POST else "application/x-www-form-urlencoded" + ) + return request + + +def rest_url(path_url: str, domain: str = "bitmex"): + base_url = CONSTANTS.BASE_URL if domain == "bitmex" else CONSTANTS.TESTNET_BASE_URL + return base_url + path_url + + +def wss_url(endpoint: str, domain: str = "bitmex"): + base_ws_url = CONSTANTS.WS_URL if domain == "bitmex" else CONSTANTS.TESTNET_WS_URL + return base_ws_url + endpoint + + +def build_api_factory( + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + throttler = create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + BitmexRESTPreProcessor(), + ]) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def api_request(path: str, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DOMAIN, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + api_version: str = "v1", + limit_id: Optional[str] = None, + timeout: Optional[float] = None): + + throttler = throttler or create_throttler() + + api_factory = api_factory or build_api_factory() + rest_assistant = await api_factory.get_rest_assistant() + + async with throttler.execute_task(limit_id=limit_id if limit_id else path): + url = rest_url(path, domain) + + request = RESTRequest( + method=method, + url=url, + params=params, + data=data, + is_auth_required=is_auth_required, + throttler_limit_id=limit_id if limit_id else path + ) + response = await rest_assistant.call(request=request, timeout=timeout) + + if response.status != 200: + if return_err: + error_response = await response.json() + return error_response + else: + error_response = await response.text() + raise IOError(f"Error executing request {method.name} {path}. " + f"HTTP status is {response.status}. " + f"Error: {error_response}") + return await response.json() diff --git a/hummingbot/connector/exchange/bitmex/constants.py b/hummingbot/connector/exchange/bitmex/constants.py new file mode 100644 index 0000000..55b394a --- /dev/null +++ b/hummingbot/connector/exchange/bitmex/constants.py @@ -0,0 +1,77 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +EXCHANGE_NAME = "bitmex" +BROKER_ID = "hummingbot" +MAX_ORDER_ID_LEN = 36 + +DOMAIN = EXCHANGE_NAME +TESTNET_DOMAIN = "bitmex_testnet" + +BASE_URL = "https://www.bitmex.com/api/v1" +TESTNET_BASE_URL = "https://testnet.bitmex.com/api/v1" + +WS_URL = "wss://ws.bitmex.com/realtime" +TESTNET_WS_URL = "wss://ws.testnet.bitmex.com/realtime" + +# Public API v1 Endpoints +SNAPSHOT_REST_URL = "/orderBook/L2" +TICKER_PRICE_URL = "/instrument" +TICKER_PRICE_CHANGE_URL = "/instrument" +EXCHANGE_INFO_URL = "/instrument" +RECENT_TRADES_URL = "/trades" +PING_URL = "" +SERVER_TIME_PATH_URL = "" + +# Private API v1 Endpoints +ORDER_URL = "/order" +ACCOUNT_INFO_URL = "/user/wallet" +TOKEN_INFO_URL = "/wallet/assets" + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +ORDERS_1MIN = "ORDERS_1MIN" +ORDERS_1SEC = "ORDERS_1SEC" + +HEARTBEAT_TIME_INTERVAL = 30.0 + +# Rate Limit time intervals +ONE_HOUR = 3600 +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 2400 + +DIFF_STREAM_ID = 1 +TRADE_STREAM_ID = 2 +FUNDING_INFO_STREAM_ID = 3 + +RATE_LIMITS = [ + # Pool Limits + RateLimit(limit_id=REQUEST_WEIGHT, limit=2400, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1MIN, limit=1200, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS_1SEC, limit=300, time_interval=10), + # Weight Limits for individual endpoints + RateLimit(limit_id=SNAPSHOT_REST_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=20)]), + RateLimit(limit_id=TICKER_PRICE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=2)]), + RateLimit(limit_id=TICKER_PRICE_CHANGE_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=EXCHANGE_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=40)]), + RateLimit(limit_id=TOKEN_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=40)]), + RateLimit(limit_id=RECENT_TRADES_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=PING_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1)]), + RateLimit(limit_id=ORDER_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=1), + LinkedLimitWeightPair(ORDERS_1MIN, weight=1), + LinkedLimitWeightPair(ORDERS_1SEC, weight=1)]), + RateLimit(limit_id=ACCOUNT_INFO_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, weight=5)]), +] diff --git a/hummingbot/connector/exchange/bitmex/dummy.pxd b/hummingbot/connector/exchange/bitmex/dummy.pxd new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/bitmex/dummy.pyx b/hummingbot/connector/exchange/bitmex/dummy.pyx new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/btc_markets/__init__.py b/hummingbot/connector/exchange/btc_markets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_api_order_book_data_source.py b/hummingbot/connector/exchange/btc_markets/btc_markets_api_order_book_data_source.py new file mode 100644 index 0000000..9959248 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_api_order_book_data_source.py @@ -0,0 +1,214 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from dateutil.parser import parse as dateparse + +import hummingbot.connector.exchange.btc_markets.btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets import btc_markets_web_utils as web_utils +from hummingbot.connector.exchange.btc_markets.btc_markets_order_book import BtcMarketsOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.btc_markets.btc_markets_exchange import BtcMarketsExchange + + +class BtcMarketsAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: 'BtcMarketsExchange', + api_factory: WebAssistantsFactory + ): + super().__init__(trading_pairs) + self._connector: BtcMarketsExchange = connector + self._domain = CONSTANTS.DEFAULT_DOMAIN + self._api_factory = api_factory + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + + :return: an instance of WSAssistant connected to the exchange + """ + websocket_assistant: WSAssistant = await self._api_factory.get_ws_assistant() + + await websocket_assistant.connect( + ws_url=CONSTANTS.WSS_V1_PUBLIC_URL[self._domain], + ping_timeout=CONSTANTS.WS_PING_TIMEOUT) + + return websocket_assistant + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + self.logger().info("Subscribing ...") + try: + marketIds = [] + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + marketIds.append(symbol) + + subscription_payload = { + "messageType": "subscribe", + "marketIds": marketIds, + "channels": [CONSTANTS.DIFF_EVENT_TYPE, CONSTANTS.SNAPSHOT_EVENT_TYPE, CONSTANTS.TRADE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + + subscription_request: WSJSONRequest = WSJSONRequest(payload=subscription_payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await websocket_assistant.send(subscription_request) + + self.logger().info("Subscribed to public order book and trade channels for all trading pairs ...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + """ + Identifies the channel for a particular event message. Used to find the correct queue to add the message in + + :param event_message: the event received through the websocket connection + + :return: the message channel + """ + + event_type = event_message["messageType"] + if event_type == CONSTANTS.DIFF_EVENT_TYPE: + return self._diff_messages_queue_key + elif event_type == CONSTANTS.SNAPSHOT_EVENT_TYPE: + return self._snapshot_messages_queue_key + elif event_type == CONSTANTS.TRADE_EVENT_TYPE: + return self._trade_messages_queue_key + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + + channel: str = self._channel_originating_message(event_message=data) + if channel in [self._diff_messages_queue_key, self._trade_messages_queue_key, self._snapshot_messages_queue_key]: + self._message_queue[channel].put_nowait(data) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + """ + Create an instance of OrderBookMessage of type OrderBookMessageType.TRADE + + :param raw_message: the JSON dictionary of the public trade event + :param message_queue: queue where the parsed messages should be stored in + """ + try: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(raw_message["marketId"]) + timestamp: float = float(dateparse(raw_message["timestamp"]).timestamp()) + + trade_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.trade_message_from_exchange( + raw_message, timestamp, {"marketId": trading_pair}) + + message_queue.put_nowait(trade_message) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public trade updates from exchange") + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + """ + Create an instance of OrderBookMessage of type OrderBookMessageType.DIFF + + :param raw_message: the JSON dictionary of the public trade event + :param message_queue: queue where the parsed messages should be stored in + """ + try: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(raw_message["marketId"]) + timestamp: float = float(dateparse(raw_message["timestamp"]).timestamp()) + + diff_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.diff_message_from_exchange( + raw_message, timestamp, {"marketId": trading_pair}) + + message_queue.put_nowait(diff_message) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public order book updates from exchange") + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + try: + marketId = raw_message["marketId"] + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(marketId) + timestamp: float = float(dateparse(raw_message["timestamp"]).timestamp()) + + snapshot_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.snapshot_message_from_exchange_rest( + raw_message, timestamp, {"marketId": trading_pair}) + + message_queue.put_nowait(snapshot_message) + + except asyncio.CancelledError: + raise + except Exception: + marketId = raw_message["marketId"] + self.logger().error(f"Unexpected error fetching order book snapshot for {marketId}.", exc_info=True) + await self._sleep(5.0) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + try: + snapshot: Dict[str, Any] = await self.get_snapshot(trading_pair=trading_pair) + snapshot_timestamp: float = float(snapshot["snapshotId"]) + + return BtcMarketsOrderBook.snapshot_message_from_exchange_rest( + snapshot, + snapshot_timestamp, + metadata={"marketId": trading_pair} + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error(f"Unexpected error fetching order book snapshot for {trading_pair}.", exc_info=True) + await self._sleep(5.0) + + async def get_snapshot( + self, + trading_pair: str, + limit: int = 1000, + ) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + :param trading_pair: the trading pair for which the order book will be retrieved + :param limit: the depth of the order book to retrieve + :return: the response from the exchange (JSON dictionary) + """ + params = {} + if limit != 0: + params["limit"] = str(limit) + + ex_trading_pair = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{ex_trading_pair}/orderbook"), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.MARKETS_URL, + ) + + return data diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_api_user_stream_data_source.py b/hummingbot/connector/exchange/btc_markets/btc_markets_api_user_stream_data_source.py new file mode 100644 index 0000000..f7e25be --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_api_user_stream_data_source.py @@ -0,0 +1,95 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import hummingbot.connector.exchange.btc_markets.btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets.btc_markets_auth import BtcMarketsAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.btc_markets.btc_markets_exchange import BtcMarketsExchange + + +class BtcMarketsAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: BtcMarketsAuth, + trading_pairs: List[str], + connector: 'BtcMarketsExchange', + api_factory: WebAssistantsFactory + ): + super().__init__() + self._auth: BtcMarketsAuth = auth + self._domain = CONSTANTS.DEFAULT_DOMAIN + self._trading_pairs = trading_pairs + self._connector = connector + self._api_factory = api_factory + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + + :return: an instance of WSAssistant connected to the exchange + """ + print("Connecting ...") + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + + await self._ws_assistant.connect( + ws_url=CONSTANTS.WSS_PRIVATE_URL[self._domain], + ping_timeout=CONSTANTS.WS_PING_TIMEOUT) + + return self._ws_assistant + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + marketIds = [] + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + marketIds.append(symbol) + + payload = self._auth.generate_ws_authentication_message() + payload["channels"] = [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.FUND_CHANGE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + payload["marketIds"] = marketIds + + subscribe_request: WSJSONRequest = WSJSONRequest(payload) + + async with self._api_factory.throttler.execute_task(limit_id = CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await websocket_assistant.send(subscribe_request) + + self.logger().info("Subscribed to private account and orders channels...") + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to private account and orders channels ...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + + messageType = data.get("messageType") + if messageType == "error": + code = data.get("code") + msg = data.get("message") + raise ValueError(f"Error message ({code}: {msg}) received in the user stream data source: {data}") + + if messageType in [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.FUND_CHANGE_EVENT_TYPE]: + queue.put_nowait(data) + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_auth.py b/hummingbot/connector/exchange/btc_markets/btc_markets_auth.py new file mode 100644 index 0000000..84e6267 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_auth.py @@ -0,0 +1,134 @@ +import base64 +import hashlib +import hmac +import time +from typing import Any, Dict + +import hummingbot.connector.exchange.btc_markets.btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets import btc_markets_web_utils as web_utils +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class BtcMarketsAuth(AuthBase): + """ + Auth class required by btc_markets API + Learn more at https://api.btcmarkets.net/doc/v3#section/Authentication/Authentication-process + """ + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + now = self._timestamp_in_milliseconds() + sig = self.get_signature( + request.method.name, + web_utils.get_path_from_url(request.url), + now, + request.data if request.method.name == "POST" else {} + ) + + headers = self._generate_auth_headers(now, sig) + if request.headers is not None: + headers.update(request.headers) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. BtcMarkets does not use this + functionality + """ + return request # pass-through + + def get_referral_code_headers(self): + """ + Generates authentication headers required by BtcMarkets + :return: a dictionary of auth headers + """ + return { + "referer": CONSTANTS.HBOT_BROKER_ID + } + + def get_signature( + self, + method: str, + path_url: str, + nonce: int, + data: Dict[str, Any] = None + ): + """ + Generates authentication signature and return it in a dictionary along with other inputs + :return: a dictionary of request info including the request signature + """ + data = data or {} + + if data is None or data == {}: + payload = f"{method}/{path_url}{nonce}{''}" + else: + bjson = str(data) + payload = f"{method}/{path_url}{nonce}{bjson}" + + return self._generate_signature(payload) + + def _generate_auth_headers(self, nonce: int, sig: str): + """ + Generates HTTP headers + """ + headers = { + "Accept": "application/json", + "Accept-Charset": "UTF-8", + "Content-Type": "application/json", + "BM-AUTH-APIKEY": self.api_key, + "BM-AUTH-TIMESTAMP": str(nonce), + "BM-AUTH-SIGNATURE": sig + } + + return headers + + def _generate_signature(self, payload: str) -> str: + """ + Generates a presigned signature + :return: a signature of auth params + """ + digest = base64.b64encode(hmac.new( + base64.b64decode(self.secret_key), payload.encode("utf8"), digestmod=hashlib.sha512).digest()) + return digest.decode('utf8') + + def _generate_auth_dict_ws(self, nonce: int) -> str: + """ + Generates an authentication params for websockets login + :return: a signature of auth params + """ + + payload = "/users/self/subscribe" + "\n" + str(nonce) + return self._generate_signature(payload) + + def generate_ws_authentication_message(self) -> str: + """ + Generates the authentication message to start receiving messages from + the 3 private ws channels + """ + now = self._timestamp_in_milliseconds() + signature = self._generate_auth_dict_ws(now) + auth_message = { + "signature": signature, + "key": self.api_key, + "timestamp": str(now), + "messageType": CONSTANTS.SUBSCRIBE, + } + return auth_message + + def _timestamp_in_milliseconds(self) -> int: + return int(self._time() * 1e3) + + def _time(self): + return time.time() diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_constants.py b/hummingbot/connector/exchange/btc_markets/btc_markets_constants.py new file mode 100644 index 0000000..4234ce7 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_constants.py @@ -0,0 +1,92 @@ +# A single source of truth for constant variables related to the exchange +# https://api.btcmarkets.net/doc/v3#section/General-notes + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "btc_markets" +DEFAULT_DOMAIN = "btc_markets" +# Base URL +REST_URLS = {"btc_markets": "https://api.btcmarkets.net/"} + +WSS_V1_PUBLIC_URL = {"btc_markets": "wss://socket.btcmarkets.net/v2"} + +WSS_PRIVATE_URL = {"btc_markets": "wss://socket.btcmarkets.net/v2"} + +REST_API_VERSION = "v3" +WS_PING_TIMEOUT = 10 + +HBOT_ORDER_ID_PREFIX = "BTCM-" +MAX_ORDER_ID_LEN = 32 +HBOT_BROKER_ID = "Hummingbot" + +SIDE_BUY = "BUY" +SIDE_SELL = "SELL" + +TIME_IN_FORCE_GTC = "GTC" + +# REST API Public Endpoints +ACCOUNTS_URL = f"{REST_API_VERSION}/accounts" +BALANCE_URL = f"{ACCOUNTS_URL}/me/balances" +FEES_URL = f"{ACCOUNTS_URL}/me/trading-fees" +MARKETS_URL = f"{REST_API_VERSION}/markets" +ORDERS_URL = f"{REST_API_VERSION}/orders" +BATCH_ORDERS_URL = f"{REST_API_VERSION}/batchorders" +TRADES_URL = f"{REST_API_VERSION}/trades" +SERVER_TIME_PATH_URL = f"{REST_API_VERSION}/time" + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_REQUEST_LIMIT_ID = "WSRequest" +WS_SUBSCRIPTION_LIMIT_ID = "WSSubscription" +WS_LOGIN_LIMIT_ID = "WSLogin" + +# Websocket event types +TICK = "tick" +DIFF_EVENT_TYPE = "orderbookUpdate" +SNAPSHOT_EVENT_TYPE = "orderbook" +ORDER_CHANGE_EVENT_TYPE = "orderChange" +TRADE_EVENT_TYPE = "trade" +FUND_CHANGE_EVENT_TYPE = "fundChange" +HEARTBEAT = "heartbeat" +ERROR = "error" +SUBSCRIBE = "subscribe" + +# Order States +ORDER_STATE = { + "Accepted": OrderState.APPROVED, + "Placed": OrderState.OPEN, + "Partially Matched": OrderState.PARTIALLY_FILLED, + "Fully Matched": OrderState.FILLED, + "Partially Cancelled": OrderState.PENDING_CANCEL, + "Cancelled": OrderState.CANCELED, + "Failed": OrderState.FAILED, +} + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=3, time_interval=10), + RateLimit(WS_REQUEST_LIMIT_ID, limit=3, time_interval=10), + RateLimit(WS_SUBSCRIPTION_LIMIT_ID, limit=3, time_interval=10), + RateLimit(WS_LOGIN_LIMIT_ID, limit=1, time_interval=15), + RateLimit(limit_id=ACCOUNTS_URL, limit=50, time_interval=10), + RateLimit(limit_id=BALANCE_URL, limit=50, time_interval=10), + RateLimit(limit_id=FEES_URL, limit=50, time_interval=10), + RateLimit(limit_id=MARKETS_URL, limit=150, time_interval=10), + RateLimit(limit_id=ORDERS_URL, limit=50, time_interval=10), + RateLimit(limit_id=BATCH_ORDERS_URL, limit=50, time_interval=10), + RateLimit(limit_id=TRADES_URL, limit=50, time_interval=10), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=50, time_interval=10) +] +""" +Rate Limits - https://api.btcmarkets.net/doc/v3#section/General-Notes +Rate Limits ws - https://docs.btcmarkets.net/v3/#tag/WS_Overview +""" + +# Error codes +INVALID_TIME_WINDOW = "InvalidTimeWindow" +INVALID_TIMESTAMP = "InvalidTimestamp" +INVALID_AUTH_TIMESTAMP = "InvalidAuthTimestamp" +INVALID_AUTH_SIGNATURE = "InvalidAuthSignature" +ORDER_NOT_FOUND = "OrderNotFound" +INVALID_ORDERID = "InvalidOrderId" diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_exchange.py b/hummingbot/connector/exchange/btc_markets/btc_markets_exchange.py new file mode 100644 index 0000000..1fc9c81 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_exchange.py @@ -0,0 +1,584 @@ +import asyncio +import math +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Tuple + +from bidict import bidict +from dateutil.parser import parse as dateparse + +import hummingbot.connector.exchange.btc_markets.btc_markets_constants as CONSTANTS +import hummingbot.connector.exchange.btc_markets.btc_markets_utils as utils +import hummingbot.connector.exchange.btc_markets.btc_markets_web_utils as web_utils +from hummingbot.connector.exchange.btc_markets.btc_markets_api_order_book_data_source import ( + BtcMarketsAPIOrderBookDataSource, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_api_user_stream_data_source import ( + BtcMarketsAPIUserStreamDataSource, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_auth import BtcMarketsAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal(0) +s_decimal_NaN = Decimal("nan") + + +class BtcMarketsExchange(ExchangePyBase): + """ + BtcMarketsExchange connects with BtcMarkets exchange and provides order book pricing, user account tracking and + trading functionality. + """ + + SHORT_POLL_INTERVAL = 5.0 + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + LONG_POLL_INTERVAL = 120.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + btc_markets_api_key: str, + btc_markets_api_secret: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + """ + :param btc_markets_api_key: The API key to connect to private BTCMarkets APIs. + :param btc_markets_api_secret: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + self._api_key: str = btc_markets_api_key + self._secret_key: str = btc_markets_api_secret + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + super().__init__(client_config_map) + self.real_time_balance_update = False + + @property + def authenticator(self): + return BtcMarketsAuth( + api_key=self._api_key, + secret_key=self._secret_key, + time_provider=self._time_synchronizer) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self.authenticator) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BtcMarketsAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BtcMarketsAPIUserStreamDataSource( + auth=self.authenticator, + trading_pairs=self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory) + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return CONSTANTS.DEFAULT_DOMAIN + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.MARKETS_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.MARKETS_URL + + @property + def check_network_request_path(self): + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def name_cap(self) -> str: + return self.name.capitalize() + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @staticmethod + def btc_markets_order_type(order_type: OrderType) -> str: + return order_type.name # .upper() + + @staticmethod + def to_hb_order_type(btc_markets_type: str) -> OrderType: + return OrderType[btc_markets_type] + + # https://docs.btcmarkets.net/v3/#tag/OrderTypes + def supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + # https://docs.btcmarkets.net/v3/#tag/ErrorCodes + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_code = str(request_exception) + is_time_synchronizer_related = CONSTANTS.INVALID_TIME_WINDOW in error_code or CONSTANTS.INVALID_TIMESTAMP in error_code or CONSTANTS.INVALID_AUTH_TIMESTAMP in error_code or CONSTANTS.INVALID_AUTH_SIGNATURE in error_code + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_FOUND) in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_FOUND) in str(cancelation_exception) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + response = await self._api_delete( + path_url=f"{CONSTANTS.ORDERS_URL}/{tracked_order.exchange_order_id}", + is_auth_required=True, + limit_id=f"{CONSTANTS.ORDERS_URL}" + ) + cancelled = True if response["clientOrderId"] == order_id else False + + return cancelled + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs + ) -> Tuple[str, float]: + order_result = None + amount_str = f"{amount:f}" + price_str = f"{price:f}" + type_str = 'Bid' if trade_type is TradeType.BUY else 'Ask' + + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + post_data = { + "marketId": symbol, + "side": type_str, + "amount": amount_str, + "selfTrade": "P", # prevents self trading + "clientOrderId": order_id, + "timeInForce": CONSTANTS.TIME_IN_FORCE_GTC + } + + if order_type == OrderType.MARKET: + post_data["type"] = "Market" + elif order_type == OrderType.LIMIT: + post_data["type"] = "Limit" + post_data["price"] = price_str + elif order_type == OrderType.LIMIT_MAKER: + post_data["type"] = "Limit" + post_data["price"] = price_str + post_data["postOnly"] = "true" + + order_result = await self._api_post( + path_url = CONSTANTS.ORDERS_URL, + data = post_data, + is_auth_required = True + ) + exchange_order_id = str(order_result["orderId"]) + + return exchange_order_id, self.current_timestamp + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None + ) -> AddedToCostTradeFee: + """ + Calculates the estimated fee an order would pay based on the connector configuration + :param base_currency: the order base currency + :param quote_currency: the order quote currency + :param order_type: the type of order (MARKET, LIMIT, LIMIT_MAKER) + :param order_side: if the order is for buying or selling + :param amount: the order amount + :param price: the order price + :return: the estimated fee for the order + """ + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data["makerFeeRate"]) if is_maker else Decimal(fees_data["takerFeeRate"]) + fee = AddedToCostTradeFee(percent=fee_value) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + resp = await self._api_get( + path_url=CONSTANTS.FEES_URL, + is_auth_required=True, + limit_id=CONSTANTS.FEES_URL + ) + fees_json = resp["feeByMarkets"] + for fee_json in fees_json: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=fee_json["marketId"]) + self._trading_fees[trading_pair] = fee_json + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + try: + if order.exchange_order_id is not None: + all_fills_response = await self._request_order_fills(order=order) + updates = self._create_order_fill_updates(order=order, fill_update=all_fills_response) + trade_updates.extend(updates) + except asyncio.CancelledError: + raise + except Exception as ex: + is_error_caused_by_unexistent_order = '"code":"OrderNotFound"' in str(ex) + if not is_error_caused_by_unexistent_order: + raise + + return trade_updates + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + orderId = await order.get_exchange_order_id() + return await self._api_get( + path_url=CONSTANTS.TRADES_URL, + params={ + "orderId": orderId + }, + is_auth_required=True, + limit_id=CONSTANTS.TRADES_URL + ) + + async def _request_order_update(self, order: InFlightOrder) -> Dict[str, Any]: + return await self._get_order_update(order.exchange_order_id) + + async def _get_order_update(self, orderId: int) -> Dict[str, Any]: + return await self._api_get( + path_url=f"{CONSTANTS.ORDERS_URL}/{orderId}", + is_auth_required=True, + limit_id=CONSTANTS.ORDERS_URL + ) + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._request_order_update(order=tracked_order) + + order_update = self._create_order_update(order=tracked_order, order_update=updated_order_data) + return order_update + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Example: + [ + { + "marketId": "BTC-AUD", + "baseAssetName": "BTC", + "quoteAssetName": "AUD", + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + }, + { + "marketId": "LTC-AUD", + "baseAssetName": "LTC", + "quoteAssetName": "AUD", + "minOrderAmount": "0.001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Post Only" + } + ] + """ + + trading_pair_rules = exchange_info_dict + retval = [] + for rule in trading_pair_rules: + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["marketId"]) + + min_order_size = Decimal(str(rule["minOrderAmount"])) + # E.g. a price decimal of 2 means 0.01 incremental. + price_decimal = Decimal(str(rule["priceDecimals"])) + price_step = Decimal("1") / Decimal(str(math.pow(10, price_decimal))) + amount_decimal = Decimal(str(rule["amountDecimals"])) + amount_step = Decimal("1") / Decimal(str(math.pow(10, amount_decimal))) + + retval.append( + TradingRule( + trading_pair, + min_order_size=Decimal(min_order_size), + max_order_size=Decimal(str(rule["maxOrderAmount"])), + min_price_increment=Decimal(price_step), + min_base_amount_increment=Decimal(amount_step), + ) + ) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule}. Skipping.") + return retval + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("messageType") + + if event_type == CONSTANTS.HEARTBEAT: + continue + elif event_type == CONSTANTS.ORDER_CHANGE_EVENT_TYPE: + exchange_order_id: Optional[str] = event_message.get("orderId") + client_order_id: Optional[str] = event_message.get("clientOrderId") + if client_order_id is None: + infligthOrder = await self._get_order_update(exchange_order_id) + client_order_id: Optional[str] = infligthOrder.get("clientOrderId") + + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + new_state = CONSTANTS.ORDER_STATE[event_message["status"]] + event_timestamp = int(dateparse(event_message["timestamp"]).timestamp()) + + if fillable_order is not None: + is_fill_candidate_by_state = new_state in [OrderState.PARTIALLY_FILLED, OrderState.FILLED] + # is_fill_candidate_by_amount = fillable_order.executed_amount_base < Decimal(event_message["filled_size"]) + if is_fill_candidate_by_state: # and is_fill_candidate_by_amount: + try: + for trade in event_message["trades"]: + fee = TradeFeeBase.new_spot_fee( + fee_schema = self.trade_fee_schema(), + trade_type = fillable_order.trade_type, + percent_token = fillable_order.quote_asset, + flat_fees = [TokenAmount(amount=Decimal(trade["fee"]), token = fillable_order.quote_asset)] + ) + + try: + trade_update = TradeUpdate( + trade_id=str(trade["tradeId"]), + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=fillable_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["volume"]), + fill_quote_amount=Decimal(trade["valueInQuoteAsset"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=event_timestamp + ) + + self._order_tracker.process_trade_update(trade_update) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + f"Unexpected error requesting order fills for {fillable_order.client_order_id}") + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error requesting order fills for {fillable_order.client_order_id}") + + if updatable_order is not None: + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=event_timestamp, + new_state=new_state, + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif event_type == CONSTANTS.FUND_CHANGE_EVENT_TYPE: + asset_name = event_message.get("currency") + type = event_message.get("type") + status = event_message.get("status") + amount = Decimal(event_message.get("amount")) + if status == "Complete": + if type == "Deposit": + self._account_available_balances[asset_name] = self._account_available_balances[asset_name] + amount + self._account_balances[asset_name] = self._account_balances[asset_name] + amount + elif type == "Withdrawal": + self._account_balances[asset_name] = self._account_balances[asset_name] - amount + if status == "Pending Authorization" and type == "Withdrawal": + self._account_available_balances[asset_name] = self._account_available_balances[asset_name] - amount + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Error while reading user events queue. Retrying after 1 second.") + await asyncio.sleep(1.0) + + def _create_order_fill_updates( + self, + order: InFlightOrder, + fill_update: Dict[str, Any] + ) -> List[TradeUpdate]: + updates = [] + fills_data = fill_update + + for fill_data in fills_data: + fee = TradeFeeBase.new_spot_fee( + fee_schema = self.trade_fee_schema(), + trade_type = order.trade_type, + percent_token = order.quote_asset, + flat_fees = [TokenAmount(amount=Decimal(fill_data.get("fee")), token = order.quote_asset)] + ) + + trade_update = TradeUpdate( + trade_id = str(fill_data.get("id")), + client_order_id = fill_data.get("clientOrderId"), + exchange_order_id = fill_data.get("orderId"), + trading_pair = order.trading_pair, + fee = fee, + fill_base_amount = Decimal(fill_data.get("amount")), + fill_price = Decimal(fill_data.get("price")), + fill_quote_amount=Decimal(fill_data.get("amount")) * Decimal(fill_data["price"]), + fill_timestamp = int(dateparse(fill_data.get("timestamp")).timestamp()) + ) + updates.append(trade_update) + + return updates + + def _create_order_update(self, order: InFlightOrder, order_update: Dict[str, Any]) -> OrderUpdate: + new_state = CONSTANTS.ORDER_STATE[order_update["status"]] + return OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp = int(dateparse(order_update["creationTime"]).timestamp()), + new_state = new_state, + client_order_id = order.client_order_id, + exchange_order_id = str(order_update["orderId"]) + ) + + async def _get_balances(self): + return await self._api_get( + method=RESTMethod.GET, + path_url=CONSTANTS.BALANCE_URL, + is_auth_required=True, + limit_id=CONSTANTS.BALANCE_URL + ) + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + account_info = await self._get_balances() + + for balance_entry in account_info: + asset_name = balance_entry["assetName"] + free_balance = Decimal(balance_entry["available"]) + total_balance = Decimal(balance_entry["balance"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: List[Dict[str, Any]]): + mapping = bidict() + for symbol_data in filter(utils.is_exchange_information_valid, exchange_info): + instrument_id = symbol_data["marketId"] + trading_pair = combine_to_hb_trading_pair( + base = symbol_data["baseAssetName"], + quote = symbol_data["quoteAssetName"] + ) + if instrument_id in mapping: + self.logger().error( + f"Instrument ID {instrument_id} (trading pair {trading_pair}) already present in the map " + f"(with trading pair {mapping[instrument_id]})." + ) + continue + elif trading_pair in mapping.inverse: + self.logger().error( + f"Trading pair {trading_pair} (instrument ID {instrument_id}) already present in the map " + f"(with ID {mapping.inverse[trading_pair]})." + ) + continue + mapping[instrument_id] = trading_pair + + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair) + data = await self._api_request( + method=RESTMethod.GET, + path_url=f"{CONSTANTS.MARKETS_URL}/{trading_pair}/ticker", + limit_id=CONSTANTS.MARKETS_URL + ) + + return float(data["lastPrice"]) diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_order_book.py b/hummingbot/connector/exchange/btc_markets/btc_markets_order_book.py new file mode 100644 index 0000000..c4c36f7 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_order_book.py @@ -0,0 +1,99 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BtcMarketsOrderBook(OrderBook): + @classmethod + def snapshot_message_from_exchange_websocket(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["marketId"], + "snapshotId": msg["snapshotId"], + "update_id": msg["snapshotId"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def snapshot_message_from_exchange_rest(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["marketId"], + "snapshotId": msg["snapshotId"], + "update_id": msg["snapshotId"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["marketId"], + "snapshotId": msg["snapshotId"], + "update_id": msg["snapshotId"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["marketId"], + "trade_type": float(TradeType.SELL.value) if msg["side"] == "Ask" else float(TradeType.BUY.value), + "trade_id": msg["tradeId"], + "price": msg["price"], + "amount": msg["volume"] + }, timestamp=timestamp) diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_utils.py b/hummingbot/connector/exchange/btc_markets/btc_markets_utils.py new file mode 100644 index 0000000..b8d0179 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_utils.py @@ -0,0 +1,53 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC-AUD" + +# https://www.btcmarkets.net/fees +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0085"), + taker_percent_fee_decimal=Decimal("0.0085"), +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("status", None) in ("Online", "Post Only", "Limit Only", "Offline") + + +class BtcMarketsConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="btc_markets", const=True, client_data=None) + btc_markets_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BtcMarkets API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + btc_markets_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your BtcMarkets API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "btc_markets" + + +KEYS = BtcMarketsConfigMap.construct() diff --git a/hummingbot/connector/exchange/btc_markets/btc_markets_web_utils.py b/hummingbot/connector/exchange/btc_markets/btc_markets_web_utils.py new file mode 100644 index 0000000..9540ca2 --- /dev/null +++ b/hummingbot/connector/exchange/btc_markets/btc_markets_web_utils.py @@ -0,0 +1,70 @@ +from typing import Callable, Optional + +from dateutil.parser import parse as dateparse + +import hummingbot.connector.exchange.btc_markets.btc_markets_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the BTCMarkets domain to connect to. The default value is "btc_markets" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URLS[domain] + path_url + + +def private_rest_url(path_url: str, **kwargs) -> str: + return public_rest_url(path_url) + + +def get_path_from_url(url: str) -> str: + return url.replace(CONSTANTS.REST_URLS[CONSTANTS.DEFAULT_DOMAIN], '') + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = float(dateparse(response["timestamp"]).timestamp()) + return server_time * 1e3 diff --git a/hummingbot/connector/exchange/bybit/__init__.py b/hummingbot/connector/exchange/bybit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/bybit/bybit_api_order_book_data_source.py b/hummingbot/connector/exchange/bybit/bybit_api_order_book_data_source.py new file mode 100644 index 0000000..1f24e16 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_api_order_book_data_source.py @@ -0,0 +1,247 @@ +import asyncio +import time +from collections import defaultdict +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional + +import hummingbot.connector.exchange.bybit.bybit_constants as CONSTANTS +from hummingbot.connector.exchange.bybit import bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_order_book import BybitOrderBook +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.bybit.bybit_exchange import BybitExchange + + +class BybitAPIOrderBookDataSource(OrderBookTrackerDataSource): + HEARTBEAT_TIME_INTERVAL = 30.0 + TRADE_STREAM_ID = 1 + DIFF_STREAM_ID = 2 + ONE_HOUR = 60 * 60 + + _logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, Mapping[str, str]] = {} + _mapping_initialization_lock = asyncio.Lock() + + def __init__(self, + trading_pairs: List[str], + connector: 'BybitExchange', + api_factory: Optional[WebAssistantsFactory] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None): + super().__init__(trading_pairs) + self._connector = connector + self._diff_messages_queue_key = CONSTANTS.DIFF_EVENT_TYPE + self._domain = domain + self._time_synchronizer = time_synchronizer + self._throttler = throttler + self._api_factory = api_factory or web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + ) + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + self._last_ws_message_sent_timestamp = 0 + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "limit": "1000" + } + data = await self._connector._api_request(path_url=CONSTANTS.SNAPSHOT_PATH_URL, + method=RESTMethod.GET, + params=params) + return data['result'] + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = float(snapshot["time"]) * 1e-3 + snapshot_msg: OrderBookMessage = BybitOrderBook.snapshot_message_from_exchange_rest( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["symbol"]) + for trades in raw_message["data"]: + trade_message: OrderBookMessage = BybitOrderBook.trade_message_from_exchange( + trades, {"trading_pair": trading_pair}) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["symbol"]) + for diff_message in raw_message["data"]: + order_book_message: OrderBookMessage = BybitOrderBook.diff_message_from_exchange( + diff_message, diff_message["t"], {"trading_pair": trading_pair}) + message_queue.put_nowait(order_book_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + This method runs continuously and request the full order book content from the exchange every hour. + The method uses the REST API from the exchange because it does not provide an endpoint to get the full order + book through websocket. With the information creates a snapshot messages that is added to the output queue + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created snapshot messages + """ + while True: + try: + await asyncio.wait_for(self._process_ob_snapshot(snapshot_queue=output), timeout=self.ONE_HOUR) + except asyncio.TimeoutError: + await self._take_full_order_book_snapshot(trading_pairs=self._trading_pairs, snapshot_queue=output) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await self._take_full_order_book_snapshot(trading_pairs=self._trading_pairs, snapshot_queue=output) + await self._sleep(5.0) + + async def listen_for_subscriptions(self): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + ws = None + while True: + try: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WSS_V1_PUBLIC_URL[self._domain]) + await self._subscribe_channels(ws) + self._last_ws_message_sent_timestamp = self._time() + + while True: + try: + seconds_until_next_ping = (CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL - ( + self._time() - self._last_ws_message_sent_timestamp)) + await asyncio.wait_for(self._process_ws_messages(ws=ws), timeout=seconds_until_next_ping) + except asyncio.TimeoutError: + ping_time = self._time() + payload = { + "ping": int(ping_time * 1e3) + } + ping_request = WSJSONRequest(payload=payload) + await ws.send(request=ping_request) + self._last_ws_message_sent_timestamp = ping_time + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + exc_info=True, + ) + await self._sleep(5.0) + finally: + ws and await ws.disconnect() + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + trade_payload = { + "topic": "trade", + "event": "sub", + "symbol": symbol, + "params": { + "binary": False + } + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trade_payload) + + depth_payload = { + "topic": "diffDepth", + "event": "sub", + "symbol": symbol, + "params": { + "binary": False + } + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=depth_payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self.logger().info(f"Subscribed to public order book and trade channels of {trading_pair}...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + raise + + async def _process_ws_messages(self, ws: WSAssistant): + async for ws_response in ws.iter_messages(): + data = ws_response.data + if data.get("msg") == "Success": + continue + event_type = data.get("topic") + if event_type == CONSTANTS.DIFF_EVENT_TYPE: + if data.get("f"): + self._message_queue[CONSTANTS.SNAPSHOT_EVENT_TYPE].put_nowait(data) + else: + self._message_queue[CONSTANTS.DIFF_EVENT_TYPE].put_nowait(data) + elif event_type == CONSTANTS.TRADE_EVENT_TYPE: + self._message_queue[CONSTANTS.TRADE_EVENT_TYPE].put_nowait(data) + + async def _process_ob_snapshot(self, snapshot_queue: asyncio.Queue): + message_queue = self._message_queue[CONSTANTS.SNAPSHOT_EVENT_TYPE] + while True: + try: + json_msg = await message_queue.get() + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=json_msg["symbol"]) + order_book_message: OrderBookMessage = BybitOrderBook.snapshot_message_from_exchange_websocket( + json_msg["data"][0], json_msg["data"][0], {"trading_pair": trading_pair}) + snapshot_queue.put_nowait(order_book_message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error when processing public order book updates from exchange") + raise + + async def _take_full_order_book_snapshot(self, trading_pairs: List[str], snapshot_queue: asyncio.Queue): + for trading_pair in trading_pairs: + try: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair=trading_pair) + snapshot_timestamp: float = float(snapshot["time"]) * 1e-3 + snapshot_msg: OrderBookMessage = BybitOrderBook.snapshot_message_from_exchange_rest( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + snapshot_queue.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error(f"Unexpected error fetching order book snapshot for {trading_pair}.", + exc_info=True) + await self._sleep(5.0) + + def _time(self): + return time.time() diff --git a/hummingbot/connector/exchange/bybit/bybit_api_user_stream_data_source.py b/hummingbot/connector/exchange/bybit/bybit_api_user_stream_data_source.py new file mode 100644 index 0000000..58e893f --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_api_user_stream_data_source.py @@ -0,0 +1,121 @@ +import asyncio +import logging +import time +from typing import Optional + +import hummingbot.connector.exchange.bybit.bybit_constants as CONSTANTS +import hummingbot.connector.exchange.bybit.bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_auth import BybitAuth +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class BybitAPIUserStreamDataSource(UserStreamTrackerDataSource): + + HEARTBEAT_TIME_INTERVAL = 30.0 + + _bausds_logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: BybitAuth, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None): + super().__init__() + self._auth: BybitAuth = auth + self._time_synchronizer = time_synchronizer + self._last_recv_time: float = 0 + self._domain = domain + self._throttler = throttler + self._api_factory = api_factory or web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + self._ws_assistant: Optional[WSAssistant] = None + self._last_ws_message_sent_timestamp = 0 + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bausds_logger is None: + cls._bausds_logger = logging.getLogger(__name__) + return cls._bausds_logger + + @property + def last_recv_time(self) -> float: + """ + Returns the time of the last received message + :return: the timestamp of the last received message in seconds + """ + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + Connects to the user private channel in the exchange using a websocket connection. With the established + connection listens to all balance events and order updates provided by the exchange, and stores them in the + output queue + :param output: the queue to use to store the received messages + """ + ws = None + while True: + try: + ws: WSAssistant = await self._get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WSS_PRIVATE_URL[self._domain]) + await self._authenticate_connection(ws) + self._last_ws_message_sent_timestamp = self._time() + while True: + try: + seconds_until_next_ping = (CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL - + (self._time() - self._last_ws_message_sent_timestamp)) + await asyncio.wait_for( + self._process_ws_messages(ws=ws, output=output), timeout=seconds_until_next_ping) + except asyncio.TimeoutError: + ping_time = self._time() + payload = { + "ping": int(ping_time * 1e3) + } + ping_request = WSJSONRequest(payload=payload) + await ws.send(request=ping_request) + self._last_ws_message_sent_timestamp = ping_time + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while listening to user stream. Retrying after 5 seconds...") + finally: + # Make sure no background task is leaked. + ws and await ws.disconnect() + await self._sleep(5) + + async def _authenticate_connection(self, ws: WSAssistant): + """ + Sends the authentication message. + :param ws: the websocket assistant used to connect to the exchange + """ + auth_message: WSJSONRequest = WSJSONRequest(payload=self._auth.generate_ws_authentication_message()) + await ws.send(auth_message) + + async def _process_ws_messages(self, ws: WSAssistant, output: asyncio.Queue): + async for ws_response in ws.iter_messages(): + data = ws_response.data + if isinstance(data, list): + for message in data: + if message["e"] in ["executionReport", "outboundAccountInfo"]: + output.put_nowait(message) + elif data.get("auth") == "fail": + raise IOError("Private channel authentication failed.") + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + def _time(self): + return time.time() diff --git a/hummingbot/connector/exchange/bybit/bybit_auth.py b/hummingbot/connector/exchange/bybit/bybit_auth.py new file mode 100644 index 0000000..6c4b821 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_auth.py @@ -0,0 +1,87 @@ +import hashlib +import hmac +import time +from collections import OrderedDict +from typing import Any, Dict, Optional +from urllib.parse import urlencode + +import hummingbot.connector.exchange.bybit.bybit_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class BybitAuth(AuthBase): + + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + @staticmethod + def keysort(dictionary: Dict[str, str]) -> Dict[str, str]: + return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + request.params = self.add_auth_to_params(params=request.params) + headers = {} + if request.headers is not None: + headers.update(request.headers) + request.headers = headers + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. Bybit does not use this + functionality + """ + return request # pass-through + + def get_referral_code_headers(self): + """ + Generates authentication headers required by ByBit + :return: a dictionary of auth headers + """ + headers = { + "referer": CONSTANTS.HBOT_BROKER_ID + } + return headers + + def add_auth_to_params(self, + params: Optional[Dict[str, Any]]): + timestamp = int(self.time_provider.time() * 1e3) + request_params = params or {} + request_params["timestamp"] = timestamp + request_params["api_key"] = self.api_key + request_params = self.keysort(request_params) + signature = self._generate_signature(params=request_params) + request_params["sign"] = signature + return request_params + + def _generate_signature(self, params: Dict[str, Any]) -> str: + encoded_params_str = urlencode(params) + digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() + return digest + + def generate_ws_authentication_message(self): + """ + Generates the authentication message to start receiving messages from + the 3 private ws channels + """ + expires = int((self.time_provider.time() + 10) * 1e3) + _val = f'GET/realtime{expires}' + signature = hmac.new(self.secret_key.encode("utf8"), + _val.encode("utf8"), hashlib.sha256).hexdigest() + auth_message = { + "op": "auth", + "args": [self.api_key, expires, signature] + } + return auth_message + + def _time(self): + return time.time() diff --git a/hummingbot/connector/exchange/bybit/bybit_constants.py b/hummingbot/connector/exchange/bybit/bybit_constants.py new file mode 100644 index 0000000..8a6a62e --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_constants.py @@ -0,0 +1,107 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +DEFAULT_DOMAIN = "bybit_main" + +HBOT_ORDER_ID_PREFIX = "BYBIT-" +MAX_ORDER_ID_LEN = 32 +HBOT_BROKER_ID = "Hummingbot" + +SIDE_BUY = "BUY" +SIDE_SELL = "SELL" + +TIME_IN_FORCE_GTC = "GTC" +# Base URL +REST_URLS = {"bybit_main": "https://api.bybit.com", + "bybit_testnet": "https://api-testnet.bybit.com"} + +WSS_V1_PUBLIC_URL = {"bybit_main": "wss://stream.bybit.com/spot/quote/ws/v1", + "bybit_testnet": "wss://stream-testnet.bybit.com/spot/quote/ws/v1"} + +WSS_PRIVATE_URL = {"bybit_main": "wss://stream.bybit.com/spot/ws", + "bybit_testnet": "wss://stream-testnet.bybit.com/spot/ws"} + +# Websocket event types +DIFF_EVENT_TYPE = "diffDepth" +TRADE_EVENT_TYPE = "trade" +SNAPSHOT_EVENT_TYPE = "depth" + +# Public API endpoints +LAST_TRADED_PRICE_PATH = "/spot/quote/v1/ticker/price" +EXCHANGE_INFO_PATH_URL = "/spot/v1/symbols" +SNAPSHOT_PATH_URL = "/spot/quote/v1/depth" +SERVER_TIME_PATH_URL = "/spot/v1/time" + +# Private API endpoints +ACCOUNTS_PATH_URL = "/spot/v1/account" +MY_TRADES_PATH_URL = "/spot/v1/myTrades" +ORDER_PATH_URL = "/spot/v1/order" + +# Order States +ORDER_STATE = { + "PENDING": OrderState.PENDING_CREATE, + "NEW": OrderState.OPEN, + "PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED, + "FILLED": OrderState.FILLED, + "PENDING_CANCEL": OrderState.PENDING_CANCEL, + "CANCELED": OrderState.CANCELED, + "REJECTED": OrderState.FAILED, +} + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +# Rate Limit Type +REQUEST_GET = "GET" +REQUEST_GET_BURST = "GET_BURST" +REQUEST_GET_MIXED = "GET_MIXED" +REQUEST_POST = "POST" +REQUEST_POST_BURST = "POST_BURST" +REQUEST_POST_MIXED = "POST_MIXED" + +# Rate Limit Max request + +MAX_REQUEST_GET = 6000 +MAX_REQUEST_GET_BURST = 70 +MAX_REQUEST_GET_MIXED = 400 +MAX_REQUEST_POST = 2400 +MAX_REQUEST_POST_BURST = 50 +MAX_REQUEST_POST_MIXED = 270 + +# Rate Limit time intervals +TWO_MINUTES = 120 +ONE_SECOND = 1 +SIX_SECONDS = 6 +ONE_DAY = 86400 + +RATE_LIMITS = { + # General + RateLimit(limit_id=REQUEST_GET, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES), + RateLimit(limit_id=REQUEST_GET_BURST, limit=MAX_REQUEST_GET_BURST, time_interval=ONE_SECOND), + RateLimit(limit_id=REQUEST_GET_MIXED, limit=MAX_REQUEST_GET_MIXED, time_interval=SIX_SECONDS), + RateLimit(limit_id=REQUEST_POST, limit=MAX_REQUEST_POST, time_interval=TWO_MINUTES), + RateLimit(limit_id=REQUEST_POST_BURST, limit=MAX_REQUEST_POST_BURST, time_interval=ONE_SECOND), + RateLimit(limit_id=REQUEST_POST_MIXED, limit=MAX_REQUEST_POST_MIXED, time_interval=SIX_SECONDS), + # Linked limits + RateLimit(limit_id=LAST_TRADED_PRICE_PATH, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_GET, 1), LinkedLimitWeightPair(REQUEST_GET_BURST, 1), + LinkedLimitWeightPair(REQUEST_GET_MIXED, 1)]), + RateLimit(limit_id=EXCHANGE_INFO_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_GET, 1), LinkedLimitWeightPair(REQUEST_GET_BURST, 1), + LinkedLimitWeightPair(REQUEST_GET_MIXED, 1)]), + RateLimit(limit_id=SNAPSHOT_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_GET, 1), LinkedLimitWeightPair(REQUEST_GET_BURST, 1), + LinkedLimitWeightPair(REQUEST_GET_MIXED, 1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_GET, 1), LinkedLimitWeightPair(REQUEST_GET_BURST, 1), + LinkedLimitWeightPair(REQUEST_GET_MIXED, 1)]), + RateLimit(limit_id=ORDER_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_POST, 1), LinkedLimitWeightPair(REQUEST_POST_BURST, 1), + LinkedLimitWeightPair(REQUEST_POST_MIXED, 1)]), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_POST, 1), LinkedLimitWeightPair(REQUEST_POST_BURST, 1), + LinkedLimitWeightPair(REQUEST_POST_MIXED, 1)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=MAX_REQUEST_GET, time_interval=TWO_MINUTES, + linked_limits=[LinkedLimitWeightPair(REQUEST_POST, 1), LinkedLimitWeightPair(REQUEST_POST_BURST, 1), + LinkedLimitWeightPair(REQUEST_POST_MIXED, 1)]), + +} diff --git a/hummingbot/connector/exchange/bybit/bybit_exchange.py b/hummingbot/connector/exchange/bybit/bybit_exchange.py new file mode 100644 index 0000000..4812f68 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_exchange.py @@ -0,0 +1,493 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +import hummingbot.connector.exchange.bybit.bybit_constants as CONSTANTS +import hummingbot.connector.exchange.bybit.bybit_utils as bybit_utils +import hummingbot.connector.exchange.bybit.bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_api_order_book_data_source import BybitAPIOrderBookDataSource +from hummingbot.connector.exchange.bybit.bybit_api_user_stream_data_source import BybitAPIUserStreamDataSource +from hummingbot.connector.exchange.bybit.bybit_auth import BybitAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_NaN = Decimal("nan") + + +class BybitExchange(ExchangePyBase): + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + bybit_api_key: str, + bybit_api_secret: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.api_key = bybit_api_key + self.secret_key = bybit_api_secret + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._last_trades_poll_bybit_timestamp = 1.0 + super().__init__(client_config_map) + + @staticmethod + def bybit_order_type(order_type: OrderType) -> str: + return order_type.name.upper() + + @staticmethod + def to_hb_order_type(bybit_type: str) -> OrderType: + return OrderType[bybit_type] + + @property + def authenticator(self): + return BybitAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + if self._domain == "bybit_main": + return "bybit" + else: + return f"bybit_{self._domain}" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = ("-1021" in error_description + and "Timestamp for the request" in error_description) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return BybitAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory, + throttler=self._throttler, + time_synchronizer=self._time_synchronizer) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return BybitAPIUserStreamDataSource( + auth=self._auth, + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = order_type is OrderType.LIMIT_MAKER + trade_base_fee = build_trade_fee( + exchange=self.name, + is_maker=is_maker, + order_side=order_side, + order_type=order_type, + amount=amount, + price=price, + base_currency=base_currency, + quote_currency=quote_currency + ) + return trade_base_fee + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + amount_str = f"{amount:f}" + type_str = self.bybit_order_type(order_type) + + side_str = CONSTANTS.SIDE_BUY if trade_type is TradeType.BUY else CONSTANTS.SIDE_SELL + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"symbol": symbol, + "side": side_str, + "qty": amount_str, + "type": type_str, + "orderLinkId": order_id} + if order_type != OrderType.MARKET: + api_params["price"] = f"{price:f}" + if order_type == OrderType.LIMIT: + api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTC + + order_result = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + params=api_params, + is_auth_required=True, + trading_pair=trading_pair, + headers={"referer": CONSTANTS.HBOT_BROKER_ID}, + ) + + o_id = str(order_result["result"]["orderId"]) + transact_time = int(order_result["result"]["transactTime"]) * 1e-3 + return (o_id, transact_time) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + api_params = {} + if tracked_order.exchange_order_id: + api_params["orderId"] = tracked_order.exchange_order_id + else: + api_params["orderLinkId"] = tracked_order.client_order_id + cancel_result = await self._api_delete( + path_url=CONSTANTS.ORDER_PATH_URL, + params=api_params, + is_auth_required=True) + + if isinstance(cancel_result, dict) and "orderLinkId" in cancel_result["result"]: + return True + return False + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Example: + { + "ret_code": 0, + "ret_msg": "", + "ext_code": null, + "ext_info": null, + "result": [ + { + "name": "BTCUSDT", + "alias": "BTCUSDT", + "baseCurrency": "BTC", + "quoteCurrency": "USDT", + "basePrecision": "0.000001", + "quotePrecision": "0.01", + "minTradeQuantity": "0.0001", + "minTradeAmount": "10", + "minPricePrecision": "0.01", + "maxTradeQuantity": "2", + "maxTradeAmount": "200", + "category": 1 + }, + { + "name": "ETHUSDT", + "alias": "ETHUSDT", + "baseCurrency": "ETH", + "quoteCurrency": "USDT", + "basePrecision": "0.0001", + "quotePrecision": "0.01", + "minTradeQuantity": "0.0001", + "minTradeAmount": "10", + "minPricePrecision": "0.01", + "maxTradeQuantity": "2", + "maxTradeAmount": "200", + "category": 1 + } + ] + } + """ + trading_pair_rules = exchange_info_dict.get("result", []) + retval = [] + for rule in trading_pair_rules: + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule.get("name")) + + min_order_size = rule.get("minTradeQuantity") + min_price_increment = rule.get("minPricePrecision") + min_base_amount_increment = rule.get("basePrecision") + min_notional_size = rule.get("minTradeAmount") + + retval.append( + TradingRule(trading_pair, + min_order_size=Decimal(min_order_size), + min_price_increment=Decimal(min_price_increment), + min_base_amount_increment=Decimal(min_base_amount_increment), + min_notional_size=Decimal(min_notional_size))) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule.get('name')}. Skipping.") + return retval + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("e") + if event_type == "executionReport": + execution_type = event_message.get("X") + client_order_id = event_message.get("c") + tracked_order = self._order_tracker.fetch_order(client_order_id=client_order_id) + if tracked_order is not None: + if execution_type in ["PARTIALLY_FILLED", "FILLED"]: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + flat_fees=[TokenAmount(amount=Decimal(event_message["n"]), token=event_message["N"])] + ) + trade_update = TradeUpdate( + trade_id=str(event_message["t"]), + client_order_id=client_order_id, + exchange_order_id=str(event_message["i"]), + trading_pair=tracked_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(event_message["l"]), + fill_quote_amount=Decimal(event_message["l"]) * Decimal(event_message["L"]), + fill_price=Decimal(event_message["L"]), + fill_timestamp=int(event_message["E"]) * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=int(event_message["E"]) * 1e-3, + new_state=CONSTANTS.ORDER_STATE[event_message["X"]], + client_order_id=client_order_id, + exchange_order_id=str(event_message["i"]), + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif event_type == "outboundAccountInfo": + balances = event_message["B"] + for balance_entry in balances: + asset_name = balance_entry["a"] + free_balance = Decimal(balance_entry["f"]) + total_balance = Decimal(balance_entry["f"]) + Decimal(balance_entry["l"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = int(order.exchange_order_id) + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "symbol": trading_pair, + "orderId": exchange_order_id + }, + is_auth_required=True, + limit_id=CONSTANTS.MY_TRADES_PATH_URL) + fills_data = all_fills_response.get("result", []) + if fills_data is not None: + for trade in fills_data: + exchange_order_id = str(trade["orderId"]) + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["ticketId"]), + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["price"]) * Decimal(trade["qty"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=int(trade["executionTime"]) * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_PATH_URL, + params={ + "orderLinkId": tracked_order.client_order_id}, + is_auth_required=True) + + new_state = CONSTANTS.ORDER_STATE[updated_order_data["result"]["status"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(updated_order_data["result"]["orderId"]), + trading_pair=tracked_order.trading_pair, + update_timestamp=int(updated_order_data["result"]["updateTime"]) * 1e-3, + new_state=new_state, + ) + + return order_update + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + is_auth_required=True) + balances = account_info["result"]["balances"] + for balance_entry in balances: + asset_name = balance_entry["coin"] + free_balance = Decimal(balance_entry["free"]) + total_balance = Decimal(balance_entry["total"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(bybit_utils.is_exchange_information_valid, exchange_info["result"]): + mapping[symbol_data["name"]] = combine_to_hb_trading_pair(base=symbol_data["baseCurrency"], + quote=symbol_data["quoteCurrency"]) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + } + resp_json = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.LAST_TRADED_PRICE_PATH, + params=params, + ) + + return float(resp_json["result"]["price"]) + + async def _api_request(self, + path_url, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + trading_pair: Optional[str] = None, + **kwargs) -> Dict[str, Any]: + last_exception = None + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + url = web_utils.rest_url(path_url, domain=self.domain) + local_headers = { + "Content-Type": "application/x-www-form-urlencoded"} + for _ in range(2): + try: + request_result = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + headers=local_headers, + throttler_limit_id=limit_id if limit_id else path_url, + ) + return request_result + except IOError as request_exception: + last_exception = request_exception + if self._is_request_exception_related_to_time_synchronizer(request_exception=request_exception): + self._time_synchronizer.clear_time_offset_ms_samples() + await self._update_time_synchronizer() + else: + raise + + # Failed even after the last retry + raise last_exception diff --git a/hummingbot/connector/exchange/bybit/bybit_order_book.py b/hummingbot/connector/exchange/bybit/bybit_order_book.py new file mode 100644 index 0000000..3a7bd44 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_order_book.py @@ -0,0 +1,93 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BybitOrderBook(OrderBook): + @classmethod + def snapshot_message_from_exchange_websocket(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["t"] + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": ts, + "bids": msg["b"], + "asks": msg["a"] + }, timestamp=timestamp) + + @classmethod + def snapshot_message_from_exchange_rest(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["time"] + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": ts, + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["t"] + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["trading_pair"], + "update_id": ts, + "bids": msg["b"], + "asks": msg["a"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["t"] + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["trading_pair"], + "trade_type": float(TradeType.BUY.value) if msg["m"] else float(TradeType.SELL.value), + "trade_id": ts, + "update_id": ts, + "price": msg["p"], + "amount": msg["q"] + }, timestamp=ts * 1e-3) diff --git a/hummingbot/connector/exchange/bybit/bybit_utils.py b/hummingbot/connector/exchange/bybit/bybit_utils.py new file mode 100644 index 0000000..6e5a381 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_utils.py @@ -0,0 +1,84 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC-USDT" +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("showStatus") is True + + +class BybitConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bybit", const=True, client_data=None) + bybit_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + bybit_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "bybit" + + +KEYS = BybitConfigMap.construct() + +OTHER_DOMAINS = ["bybit_testnet"] +OTHER_DOMAINS_PARAMETER = {"bybit_testnet": "bybit_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"bybit_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"bybit_testnet": DEFAULT_FEES} + + +class BybitTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="bybit_testnet", const=True, client_data=None) + bybit_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Testnet API Key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + bybit_testnet_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Bybit Testnet API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "bybit_testnet" + + +OTHER_DOMAINS_KEYS = {"bybit_testnet": BybitTestnetConfigMap.construct()} diff --git a/hummingbot/connector/exchange/bybit/bybit_web_utils.py b/hummingbot/connector/exchange/bybit/bybit_web_utils.py new file mode 100644 index 0000000..4be0875 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/bybit_web_utils.py @@ -0,0 +1,124 @@ +from typing import Any, Callable, Dict, Optional + +import hummingbot.connector.exchange.bybit.bybit_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Bybit domain to connect to ("mainnet" or "testnet"). The default value is "mainnet" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URLS[domain] + path_url + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def api_request(path: str, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + timeout: Optional[float] = None, + headers: Dict[str, Any] = {}): + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + + # If api_factory is not provided a default one is created + # The default instance has no authentication capabilities and all authenticated requests will fail + api_factory = api_factory or build_api_factory( + throttler=throttler, + time_synchronizer=time_synchronizer, + domain=domain, + ) + rest_assistant = await api_factory.get_rest_assistant() + + local_headers = { + "Content-Type": "application/x-www-form-urlencoded"} + local_headers.update(headers) + url = rest_url(path, domain=domain) + + request = RESTRequest( + method=method, + url=url, + params=params, + data=data, + headers=local_headers, + is_auth_required=is_auth_required, + throttler_limit_id=limit_id if limit_id else path + ) + + async with throttler.execute_task(limit_id=limit_id if limit_id else path): + response = await rest_assistant.call(request=request, timeout=timeout) + if response.status != 200: + if return_err: + error_response = await response.json() + return error_response + else: + error_response = await response.text() + if error_response is not None and "ret_code" in error_response and "ret_msg" in error_response: + raise IOError(f"The request to Bybit failed. Error: {error_response}. Request: {request}") + else: + raise IOError(f"Error executing request {method.name} {path}. " + f"HTTP status is {response.status}. " + f"Error: {error_response}") + + return await response.json() + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + response = await api_request( + path=CONSTANTS.SERVER_TIME_PATH_URL, + api_factory=api_factory, + throttler=throttler, + domain=domain, + method=RESTMethod.GET) + server_time = response["result"]["serverTime"] + + return server_time diff --git a/hummingbot/connector/exchange/bybit/dummy.pxd b/hummingbot/connector/exchange/bybit/dummy.pxd new file mode 100644 index 0000000..cbb5138 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass \ No newline at end of file diff --git a/hummingbot/connector/exchange/bybit/dummy.pyx b/hummingbot/connector/exchange/bybit/dummy.pyx new file mode 100644 index 0000000..cbb5138 --- /dev/null +++ b/hummingbot/connector/exchange/bybit/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass \ No newline at end of file diff --git a/hummingbot/connector/exchange/coinbase_pro/__init__.py b/hummingbot/connector/exchange/coinbase_pro/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pxd b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pxd new file mode 100644 index 0000000..1b150b9 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pxd @@ -0,0 +1,11 @@ +# distutils: language=c++ +cimport numpy as np + + +cdef class CoinbaseProActiveOrderTracker: + cdef dict _active_bids + cdef dict _active_asks + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message) + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message) + cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pyx b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pyx new file mode 100644 index 0000000..5ec63af --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_active_order_tracker.pyx @@ -0,0 +1,299 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp + +import logging +from decimal import Decimal +from typing import Dict + +import numpy as np + +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.logger import HummingbotLogger + +_cbpaot_logger = None +s_empty_diff = np.ndarray(shape=(0, 4), dtype="float64") + +CoinbaseProOrderBookTrackingDictionary = Dict[Decimal, Dict[str, Dict[str, any]]] + +TYPE_OPEN = "open" +TYPE_CHANGE = "change" +TYPE_MATCH = "match" +TYPE_DONE = "done" +SIDE_BUY = "buy" +SIDE_SELL = "sell" + +cdef class CoinbaseProActiveOrderTracker: + def __init__(self, + active_asks: CoinbaseProOrderBookTrackingDictionary = None, + active_bids: CoinbaseProOrderBookTrackingDictionary = None): + super().__init__() + self._active_asks = active_asks or {} + self._active_bids = active_bids or {} + + @classmethod + def logger(cls) -> HummingbotLogger: + global _cbpaot_logger + if _cbpaot_logger is None: + _cbpaot_logger = logging.getLogger(__name__) + return _cbpaot_logger + + @property + def active_asks(self) -> CoinbaseProOrderBookTrackingDictionary: + """ + Get all asks on the order book in dictionary format + :returns: Dict[price, Dict[order_id, order_book_message]] + """ + return self._active_asks + + @property + def active_bids(self) -> CoinbaseProOrderBookTrackingDictionary: + """ + Get all bids on the order book in dictionary format + :returns: Dict[price, Dict[order_id, order_book_message]] + """ + return self._active_bids + + def volume_for_ask_price(self, price) -> float: + """ + For a certain price, get the volume sum of all ask order book rows with that price + :returns: volume sum + """ + return sum([float(msg["remaining_size"]) for msg in self._active_asks[price].values()]) + + def volume_for_bid_price(self, price) -> float: + """ + For a certain price, get the volume sum of all bid order book rows with that price + :returns: volume sum + """ + return sum([float(msg["remaining_size"]) for msg in self._active_bids[price].values()]) + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message): + """ + Interpret an incoming diff message and apply changes to the order book accordingly + :returns: new order book rows: Tuple(np.array (bids), np.array (asks)) + """ + + cdef: + dict content = message.content + str msg_type = content["type"] + str order_id + str order_side + str price_raw + object price + dict order_dict + str remaining_size + double timestamp = message.timestamp + double quantity = 0 + + order_id = content.get("order_id") or content.get("maker_order_id") + order_side = content.get("side") + price_raw = content.get("price") + if order_id is None: + raise ValueError(f"Unknown order id for message - '{message}'. Aborting.") + if order_side not in [SIDE_BUY, SIDE_SELL]: + raise ValueError(f"Unknown order side for message - '{message}'. Aborting.") + if price_raw is None: + raise ValueError(f"Unknown order price for message - '{message}'. Aborting.") + elif price_raw == "null": # 'change' messages have 'null' as price for market orders + return s_empty_diff, s_empty_diff + price = Decimal(price_raw) + + if msg_type == TYPE_OPEN: + order_dict = { + "order_id": order_id, + "remaining_size": content["remaining_size"] + } + if order_side == SIDE_BUY: + if price in self._active_bids: + self._active_bids[price][order_id] = order_dict + else: + self._active_bids[price] = {order_id: order_dict} + quantity = self.volume_for_bid_price(price) + return np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64"), s_empty_diff + else: + if price in self._active_asks: + self._active_asks[price][order_id] = order_dict + else: + self._active_asks[price] = {order_id: order_dict} + quantity = self.volume_for_ask_price(price) + return s_empty_diff, np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64") + + elif msg_type == TYPE_CHANGE: + if content.get("new_size") is not None: + remaining_size = content["new_size"] + elif content.get("new_funds") is not None: + remaining_size = str(Decimal(content["new_funds"]) / price) + else: + raise ValueError(f"Invalid change message - '{message}'. Aborting.") + if order_side == SIDE_BUY: + if price in self._active_bids and order_id in self._active_bids[price]: + self._active_bids[price][order_id]["remaining_size"] = remaining_size + quantity = self.volume_for_bid_price(price) + return ( + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64"), + s_empty_diff + ) + else: + return s_empty_diff, s_empty_diff + else: + if price in self._active_asks and order_id in self._active_asks[price]: + self._active_asks[price][order_id]["remaining_size"] = remaining_size + quantity = self.volume_for_ask_price(price) + return ( + s_empty_diff, + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64") + ) + else: + return s_empty_diff, s_empty_diff + + elif msg_type == TYPE_MATCH: + if order_side == SIDE_BUY: + if price in self._active_bids and order_id in self._active_bids[price]: + remaining_size = self._active_bids[price][order_id]["remaining_size"] + self._active_bids[price][order_id]["remaining_size"] = str(float(remaining_size) - float(content["size"])) + quantity = self.volume_for_bid_price(price) + return ( + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64"), + s_empty_diff + ) + else: + return s_empty_diff, s_empty_diff + else: + if price in self._active_asks and order_id in self._active_asks[price]: + remaining_size = self._active_asks[price][order_id]["remaining_size"] + self._active_asks[price][order_id]["remaining_size"] = str(float(remaining_size) - float(content["size"])) + quantity = self.volume_for_ask_price(price) + return ( + s_empty_diff, + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64") + ) + else: + return s_empty_diff, s_empty_diff + + elif msg_type == TYPE_DONE: + if order_side == SIDE_BUY: + if price in self._active_bids and order_id in self._active_bids[price]: + del self._active_bids[price][order_id] + if len(self._active_bids[price]) < 1: + del self._active_bids[price] + return ( + np.array([[timestamp, float(price), 0.0, message.update_id]], dtype="float64"), + s_empty_diff + ) + else: + quantity = self.volume_for_bid_price(price) + return ( + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64"), + s_empty_diff + ) + return s_empty_diff, s_empty_diff + else: + if price in self._active_asks and order_id in self._active_asks[price]: + del self._active_asks[price][order_id] + if len(self._active_asks[price]) < 1: + del self._active_asks[price] + return ( + s_empty_diff, + np.array([[timestamp, float(price), 0.0, message.update_id]], dtype="float64") + ) + else: + quantity = self.volume_for_ask_price(price) + return ( + s_empty_diff, + np.array([[timestamp, float(price), quantity, message.update_id]], dtype="float64") + ) + return s_empty_diff, s_empty_diff + + else: + raise ValueError(f"Unknown message type '{msg_type}' - {message}. Aborting.") + + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message): + """ + Interpret an incoming snapshot message and apply changes to the order book accordingly + :returns: new order book rows: Tuple(np.array (bids), np.array (asks)) + """ + cdef: + object price + str order_id + str amount + dict order_dict + + # Refresh all order tracking. + self._active_bids.clear() + self._active_asks.clear() + for snapshot_orders, active_orders in [(message.content["bids"], self._active_bids), + (message.content["asks"], self._active_asks)]: + for order in snapshot_orders: + price = Decimal(order[0]) + order_id = order[2] + amount = order[1] + order_dict = { + "order_id": order_id, + "remaining_size": amount + } + + if price in active_orders: + active_orders[price][order_id] = order_dict + else: + active_orders[price] = { + order_id: order_dict + } + + # Return the sorted snapshot tables. + cdef: + np.ndarray[np.float64_t, ndim=2] bids = np.array( + [[message.timestamp, + float(price), + sum([float(order_dict["remaining_size"]) + for order_dict in self._active_bids[price].values()]), + message.update_id] + for price in sorted(self._active_bids.keys(), reverse=True)], dtype="float64", ndmin=2) + np.ndarray[np.float64_t, ndim=2] asks = np.array( + [[message.timestamp, + float(price), + sum([float(order_dict["remaining_size"]) + for order_dict in self._active_asks[price].values()]), + message.update_id] + for price in sorted(self._active_asks.keys(), reverse=True)], dtype="float64", ndmin=2) + + # If there're no rows, the shape would become (1, 0) and not (0, 4). + # Reshape to fix that. + if bids.shape[1] != 4: + bids = bids.reshape((0, 4)) + if asks.shape[1] != 4: + asks = asks.reshape((0, 4)) + + return bids, asks + + cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message): + """ + Interpret an incoming trade message and apply changes to the order book accordingly + :returns: new order book rows: Tuple[np.array (bids), np.array (asks)] + """ + cdef: + double trade_type_value = 1.0 if message.content["side"] == SIDE_SELL else 2.0 + + return np.array( + [message.timestamp, trade_type_value, float(message.content["price"]), float(message.content["size"])], + dtype="float64" + ) + + def convert_diff_message_to_order_book_row(self, message): + """ + Convert an incoming diff message to Tuple of np.arrays, and then convert to OrderBookRow + :returns: Tuple(List[bids_row], List[asks_row]) + """ + np_bids, np_asks = self.c_convert_diff_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row + + def convert_snapshot_message_to_order_book_row(self, message): + """ + Convert an incoming snapshot message to Tuple of np.arrays, and then convert to OrderBookRow + :returns: Tuple(List[bids_row], List[asks_row]) + """ + np_bids, np_asks = self.c_convert_snapshot_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py new file mode 100755 index 0000000..47eed95 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_order_book_data_source.py @@ -0,0 +1,287 @@ +import asyncio +import logging +import time +from decimal import Decimal +from typing import AsyncIterable, Dict, List, Optional + +import pandas as pd + +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_active_order_tracker import CoinbaseProActiveOrderTracker +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book import CoinbaseProOrderBook +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_tracker_entry import ( + CoinbaseProOrderBookTrackerEntry, +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_utils import ( + CoinbaseProRESTRequest, + build_coinbase_pro_web_assistant_factory, +) +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +MAX_RETRIES = 20 +NaN = float("nan") + + +class CoinbaseProAPIOrderBookDataSource(OrderBookTrackerDataSource): + + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + + _cbpaobds_logger: Optional[HummingbotLogger] = None + _shared_web_assistants_factory: Optional[WebAssistantsFactory] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._cbpaobds_logger is None: + cls._cbpaobds_logger = logging.getLogger(__name__) + return cls._cbpaobds_logger + + def __init__( + self, + trading_pairs: Optional[List[str]] = None, + web_assistants_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__(trading_pairs) + self._web_assistants_factory = web_assistants_factory or build_coinbase_pro_web_assistant_factory() + self._rest_assistant = None + + @classmethod + async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Decimal]: + tasks = [cls.get_last_traded_price(t_pair) for t_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + @classmethod + async def get_last_traded_price(cls, trading_pair: str) -> Decimal: + factory = build_coinbase_pro_web_assistant_factory() + rest_assistant = await factory.get_rest_assistant() + endpoint = f"{CONSTANTS.PRODUCTS_PATH_URL}/{trading_pair}/ticker" + request = CoinbaseProRESTRequest(RESTMethod.GET, endpoint=endpoint) + response = await rest_assistant.call(request) + resp_json = await response.json() + return Decimal(resp_json["price"]) + + @staticmethod + async def fetch_trading_pairs() -> List[str]: + trading_pair_list = [] + try: + factory = build_coinbase_pro_web_assistant_factory() + rest_assistant = await factory.get_rest_assistant() + request = CoinbaseProRESTRequest(RESTMethod.GET, endpoint=CONSTANTS.PRODUCTS_PATH_URL) + response = await rest_assistant.call(request) + if response.status == 200: + markets = await response.json() + raw_trading_pairs: List[str] = list(map(lambda details: details.get('id'), markets)) + trading_pair_list: List[str] = [] + for raw_trading_pair in raw_trading_pairs: + trading_pair_list.append(raw_trading_pair) + except Exception: + # Do nothing if the request fails -- there will be no autocomplete for coinbase trading pairs + pass + return trading_pair_list + + @staticmethod + async def get_snapshot(rest_assistant: RESTAssistant, trading_pair: str) -> Dict[str, any]: + """ + Fetches order book snapshot for a particular trading pair from the rest API + :returns: Response from the rest API + """ + endpoint = f"{CONSTANTS.PRODUCTS_PATH_URL}/{trading_pair}/book?level=3" + request = CoinbaseProRESTRequest(RESTMethod.GET, endpoint=endpoint) + response = await rest_assistant.call(request) + if response.status != 200: + raise IOError(f"Error fetching Coinbase Pro market snapshot for {trading_pair}. " + f"HTTP status is {response.status}.") + response_data = await response.json() + return response_data + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + rest_assistant = await self._get_rest_assistant() + snapshot: Dict[str, any] = await self.get_snapshot(rest_assistant, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = CoinbaseProOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + active_order_tracker: CoinbaseProActiveOrderTracker = CoinbaseProActiveOrderTracker() + bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg) + order_book = self.order_book_create_function() + order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) + return order_book + + async def get_tracking_pairs(self) -> Dict[str, OrderBookTrackerEntry]: + """ + *required + Initializes order books and order book trackers for the list of trading pairs + returned by `self.get_trading_pairs` + :returns: A dictionary of order book trackers for each trading pair + """ + # Get the currently active markets + trading_pairs: List[str] = self._trading_pairs + retval: Dict[str, OrderBookTrackerEntry] = {} + rest_assistant = await self._get_rest_assistant() + + number_of_pairs: int = len(trading_pairs) + for index, trading_pair in enumerate(trading_pairs): + try: + snapshot: Dict[str, any] = await self.get_snapshot(rest_assistant, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = CoinbaseProOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + order_book: OrderBook = self.order_book_create_function() + active_order_tracker: CoinbaseProActiveOrderTracker = CoinbaseProActiveOrderTracker() + bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg) + order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) + + retval[trading_pair] = CoinbaseProOrderBookTrackerEntry( + trading_pair, + snapshot_timestamp, + order_book, + active_order_tracker + ) + self.logger().info(f"Initialized order book for {trading_pair}. " + f"{index+1}/{number_of_pairs} completed.") + await self._sleep(0.6) + except IOError: + self.logger().network( + f"Error getting snapshot for {trading_pair}.", + exc_info=True, + app_warning_msg=f"Error getting snapshot for {trading_pair}. Check network connection." + ) + except Exception: + self.logger().error(f"Error initializing order book for {trading_pair}. ", exc_info=True) + return retval + + async def _iter_messages(self, ws: WSAssistant) -> AsyncIterable[Dict]: + """ + Generator function that returns messages from the web socket stream + :param ws: current web socket connection + :returns: message in AsyncIterable format + """ + # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. + try: + async for response in ws.iter_messages(): + msg = response.data + yield msg + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + finally: + await ws.disconnect() + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + # Trade messages are received from the order book web socket + pass + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + *required + Subscribe to diff channel via web socket, and keep the connection open for incoming messages + :param ev_loop: ev_loop to execute this function in + :param output: an async queue where the incoming messages are stored + """ + while True: + try: + trading_pairs: List[str] = self._trading_pairs + ws_assistant = await self._web_assistants_factory.get_ws_assistant() + await ws_assistant.connect(CONSTANTS.WS_URL, message_timeout=CONSTANTS.WS_MESSAGE_TIMEOUT) + subscribe_payload = { + "type": "subscribe", + "product_ids": trading_pairs, + "channels": [CONSTANTS.FULL_CHANNEL_NAME] + } + subscribe_request = WSJSONRequest(payload=subscribe_payload) + await ws_assistant.subscribe(subscribe_request) + async for msg in self._iter_messages(ws_assistant): + msg_type: str = msg.get("type", None) + if msg_type is None: + raise ValueError(f"Coinbase Pro Websocket message does not contain a type - {msg}") + elif msg_type == "error": + raise ValueError(f"Coinbase Pro Websocket received error message - {msg['message']}") + elif msg_type in ["open", "match", "change", "done"]: + if msg_type == "done" and "price" not in msg: + # done messages with no price are completed market orders which can be ignored + continue + order_book_message: OrderBookMessage = CoinbaseProOrderBook.diff_message_from_exchange(msg) + output.put_nowait(order_book_message) + elif msg_type in ["received", "activate", "subscriptions"]: + # these messages are not needed to track the order book + continue + else: + raise ValueError(f"Unrecognized Coinbase Pro Websocket message received - {msg}") + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg=f"Unexpected error with WebSocket connection." + f" Retrying in {CONSTANTS.REST_API_LIMIT_COOLDOWN} seconds." + f" Check network connection." + ) + await self._sleep(CONSTANTS.WS_RECONNECT_COOLDOWN) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + *required + Fetches order book snapshots for each trading pair, and use them to update the local order book + :param ev_loop: ev_loop to execute this function in + :param output: an async queue where the incoming messages are stored + """ + while True: + try: + trading_pairs: List[str] = self._trading_pairs + rest_assistant = await self._get_rest_assistant() + for trading_pair in trading_pairs: + try: + snapshot: Dict[str, any] = await self.get_snapshot(rest_assistant, trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = CoinbaseProOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"product_id": trading_pair} + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + # Be careful not to go above API rate limits. + await self._sleep(CONSTANTS.REST_API_LIMIT_COOLDOWN) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg=f"Unexpected error with WebSocket connection." + f" Retrying in {CONSTANTS.REST_API_LIMIT_COOLDOWN} seconds." + f" Check network connection." + ) + await self._sleep(CONSTANTS.REST_API_LIMIT_COOLDOWN) + this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0) + next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) + delta: float = next_hour.timestamp() - time.time() + await self._sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await self._sleep(CONSTANTS.REST_API_LIMIT_COOLDOWN) + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._rest_assistant = await self._web_assistants_factory.get_rest_assistant() + return self._rest_assistant diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py new file mode 100755 index 0000000..d17798a --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_api_user_stream_data_source.py @@ -0,0 +1,110 @@ +import asyncio +import logging +from typing import AsyncIterable, Dict, List, Optional + +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book import CoinbaseProOrderBook +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class CoinbaseProAPIUserStreamDataSource(UserStreamTrackerDataSource): + _cbpausds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._cbpausds_logger is None: + cls._cbpausds_logger = logging.getLogger(__name__) + return cls._cbpausds_logger + + def __init__( + self, + web_assistants_factory: WebAssistantsFactory, + trading_pairs: Optional[List[str]] = None, + ): + self._trading_pairs = trading_pairs + self._web_assistants_factory = web_assistants_factory + self._ws_assistant: Optional[WSAssistant] = None + self._current_listen_key = None + self._listen_for_user_stream_task = None + super().__init__() + + @property + def order_book_class(self): + """ + *required + Get relevant order book class to access class specific methods + :returns: OrderBook class + """ + return CoinbaseProOrderBook + + @property + def last_recv_time(self) -> float: + return self._ws_assistant.last_recv_time if self._ws_assistant is not None else 0 + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + *required + Subscribe to user stream via web socket, and keep the connection open for incoming messages + + :param output: an async queue where the incoming messages are stored + """ + while True: + try: + self._ws_assistant = await self._web_assistants_factory.get_ws_assistant() + await self._ws_assistant.connect(CONSTANTS.WS_URL, message_timeout=CONSTANTS.WS_MESSAGE_TIMEOUT) + subscribe_payload: Dict[str, any] = { + "type": "subscribe", + "product_ids": self._trading_pairs, + "channels": [CONSTANTS.USER_CHANNEL_NAME] + } + subscribe_request = WSJSONRequest(payload=subscribe_payload, is_auth_required=True) + await self._ws_assistant.subscribe(subscribe_request) + async for msg in self._iter_messages(self._ws_assistant): + msg_type: str = msg.get("type", None) + if msg_type is None: + raise ValueError(f"Coinbase Pro Websocket message does not contain a type - {msg}") + elif msg_type == "error": + raise ValueError(f"Coinbase Pro Websocket received error message - {msg['message']}") + elif msg_type in ["open", "match", "change", "done"]: + output.put_nowait(msg) + elif msg_type in ["received", "activate", "subscriptions"]: + # these messages are not needed to track the order book + pass + else: + raise ValueError(f"Unrecognized Coinbase Pro Websocket message received - {msg}") + except asyncio.CancelledError: + self._ws_assistant = None + raise + except Exception: + self._ws_assistant = None + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg=f"Unexpected error with WebSocket connection." + f" Retrying in {CONSTANTS.REST_API_LIMIT_COOLDOWN} seconds." + f" Check network connection." + ) + await self._sleep(CONSTANTS.REST_API_LIMIT_COOLDOWN) + + async def _iter_messages(self, ws: WSAssistant) -> AsyncIterable[Dict]: + """ + Generator function that returns messages from the web socket stream + :param ws: current web socket connection + :returns: message in AsyncIterable format + """ + # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. + try: + async for response in ws.iter_messages(): + msg = response.data + yield msg + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + finally: + await ws.disconnect() + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_auth.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_auth.py new file mode 100755 index 0000000..1bda651 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_auth.py @@ -0,0 +1,67 @@ +import base64 +import hashlib +import hmac +import time +from typing import Dict + +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_utils import CoinbaseProRESTRequest +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class CoinbaseProAuth(AuthBase): + """ + Auth class required by Coinbase Pro API + Learn more at https://docs.pro.coinbase.com/?python#signing-a-message + """ + def __init__(self, api_key: str, secret_key: str, passphrase: str): + self.api_key = api_key + self.secret_key = secret_key + self.passphrase = passphrase + + async def rest_authenticate(self, request: CoinbaseProRESTRequest) -> RESTRequest: + request.headers = self._get_headers( + method_str=request.method.value, path_url=request.endpoint, body=request.data + ) + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + auth_dict = self._generate_auth_dict("GET", CONSTANTS.VERIFY_PATH_URL, "") + request.payload.update(auth_dict) + return request + + def _get_headers(self, method_str: str, path_url: str, body: str = "") -> Dict[str, any]: + """ + Generates authentication headers required by coinbase + :param method_str: GET / POST / etc. + :param path_url: e.g. "/accounts" + :param body: request payload + :return: a dictionary of auth headers + """ + header_dict = self._generate_auth_dict(method_str, path_url, body) + return { + "CB-ACCESS-SIGN": header_dict["signature"], + "CB-ACCESS-TIMESTAMP": header_dict["timestamp"], + "CB-ACCESS-KEY": header_dict["key"], + "CB-ACCESS-PASSPHRASE": header_dict["passphrase"], + "Content-Type": 'application/json', + } + + def _generate_auth_dict(self, method_str: str, path_url: str, body: str = "") -> Dict[str, any]: + """ + Generates authentication signature and return it in a dictionary along with other inputs + :return: a dictionary of request info including the request signature + """ + timestamp = str(time.time()) + message = timestamp + method_str + path_url + body + hmac_key = base64.b64decode(self.secret_key) + signature = hmac.new(hmac_key, message.encode('utf8'), hashlib.sha256) + signature_b64 = base64.b64encode(bytes(signature.digest())).decode('utf8') + + return { + "signature": signature_b64, + "timestamp": timestamp, + "key": self.api_key, + "passphrase": self.passphrase, + } diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_constants.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_constants.py new file mode 100644 index 0000000..f4ff144 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_constants.py @@ -0,0 +1,18 @@ +WS_MESSAGE_TIMEOUT = 30.0 # seconds +WS_RECONNECT_COOLDOWN = 30.0 # seconds +REST_API_LIMIT_COOLDOWN = 5.0 # seconds + +REST_URL = "https://api.pro.coinbase.com" +WS_URL = "wss://ws-feed.pro.coinbase.com" + +ACCOUNTS_PATH_URL = "/accounts" +FEES_PATH_URL = "/fees" +ORDERS_PATH_URL = "/orders" +PRODUCTS_PATH_URL = "/products" +TIME_PATH_URL = "/time" +TRANSFERS_PATH_URL = "/transfers" +VERIFY_PATH_URL = "/users/self/verify" + +# WebSocket Channels +FULL_CHANNEL_NAME = "full" +USER_CHANNEL_NAME = "user" diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pxd b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pxd new file mode 100755 index 0000000..4e0afb9 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pxd @@ -0,0 +1,35 @@ +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.data_type.transaction_tracker cimport TransactionTracker + + +cdef class CoinbaseProExchange(ExchangeBase): + cdef: + object _user_stream_tracker + object _ev_loop + object _poll_notifier + double _last_timestamp + double _last_order_update_timestamp + double _last_fee_percentage_update_timestamp + object _maker_fee_percentage + object _taker_fee_percentage + double _poll_interval + dict _in_flight_orders + TransactionTracker _tx_tracker + dict _trading_rules + object _coro_queue + object _status_polling_task + object _coro_scheduler_task + object _user_stream_tracker_task + object _user_stream_event_listener_task + object _trading_rules_polling_task + object _web_assistants_factory + object _rest_assistant + + cdef c_start_tracking_order(self, + str order_id, + str trading_pair, + object trade_type, + object order_type, + object price, + object amount) + cdef c_did_timeout_tx(self, str tracking_id) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pyx b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pyx new file mode 100755 index 0000000..b0e1eab --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_exchange.pyx @@ -0,0 +1,1084 @@ +import asyncio +import copy +import logging +from decimal import Decimal +from typing import Any, AsyncIterable, Dict, List, Optional, TYPE_CHECKING + +from async_timeout import timeout +from libc.stdint cimport int64_t + +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_order_book_data_source import \ + CoinbaseProAPIOrderBookDataSource +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_auth import CoinbaseProAuth +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_in_flight_order cimport CoinbaseProInFlightOrder +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_in_flight_order import CoinbaseProInFlightOrder +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_tracker import CoinbaseProOrderBookTracker +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_user_stream_tracker import CoinbaseProUserStreamTracker +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_utils import ( + build_coinbase_pro_web_assistant_factory, + CoinbaseProRESTRequest, +) +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.trading_rule cimport TradingRule +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.transaction_tracker import TransactionTracker +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + MarketTransactionFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.estimate_fee import estimate_fee +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal("0.0") +s_decimal_nan = Decimal("nan") + +cdef class CoinbaseProExchangeTransactionTracker(TransactionTracker): + cdef: + CoinbaseProExchange _owner + + def __init__(self, owner: CoinbaseProExchange): + super().__init__() + self._owner = owner + + cdef c_did_timeout_tx(self, str tx_id): + TransactionTracker.c_did_timeout_tx(self, tx_id) + self._owner.c_did_timeout_tx(tx_id) + + +cdef class CoinbaseProExchange(ExchangeBase): + MARKET_BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value + MARKET_SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value + MARKET_ORDER_CANCELED_EVENT_TAG = MarketEvent.OrderCancelled.value + MARKET_TRANSACTION_FAILURE_EVENT_TAG = MarketEvent.TransactionFailure.value + MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value + MARKET_ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value + MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value + MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value + + API_CALL_TIMEOUT = 10.0 + UPDATE_ORDERS_INTERVAL = 10.0 + UPDATE_FEE_PERCENTAGE_INTERVAL = 60.0 + MAKER_FEE_PERCENTAGE_DEFAULT = 0.005 + TAKER_FEE_PERCENTAGE_DEFAULT = 0.005 + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def __init__(self, + client_config_map: "ClientConfigAdapter", + coinbase_pro_api_key: str, + coinbase_pro_secret_key: str, + coinbase_pro_passphrase: str, + poll_interval: float = 5.0, # interval which the class periodically pulls status from the rest API + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True): + super().__init__(client_config_map) + self._trading_required = trading_required + auth = CoinbaseProAuth(coinbase_pro_api_key, coinbase_pro_secret_key, coinbase_pro_passphrase) + self._web_assistants_factory = build_coinbase_pro_web_assistant_factory(auth) + self._set_order_book_tracker(CoinbaseProOrderBookTracker(trading_pairs, self._web_assistants_factory)) + self._user_stream_tracker = CoinbaseProUserStreamTracker( + trading_pairs=trading_pairs, + web_assistants_factory=self._web_assistants_factory, + ) + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + self._last_timestamp = 0 + self._last_order_update_timestamp = 0 + self._last_fee_percentage_update_timestamp = 0 + self._poll_interval = poll_interval + self._in_flight_orders = {} + self._tx_tracker = CoinbaseProExchangeTransactionTracker(self) + self._trading_rules = {} + self._status_polling_task = None + self._user_stream_tracker_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._rest_assistant = None + self._maker_fee_percentage = Decimal(self.MAKER_FEE_PERCENTAGE_DEFAULT) + self._taker_fee_percentage = Decimal(self.TAKER_FEE_PERCENTAGE_DEFAULT) + self._real_time_balance_update = False + + @property + def name(self) -> str: + """ + *required + :return: A lowercase name / id for the market. Must stay consistent with market name in global settings. + """ + return "coinbase_pro" + + @property + def order_books(self) -> Dict[str, OrderBook]: + """ + *required + Get mapping of all the order books that are being tracked. + :return: Dict[trading_pair : OrderBook] + """ + return self.order_book_tracker.order_books + + @property + def status_dict(self) -> Dict[str, bool]: + """ + *required + :return: a dictionary of relevant status checks. + This is used by `ready` method below to determine if a market is ready for trading. + """ + return { + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0 if self._trading_required else True + } + + @property + def ready(self) -> bool: + """ + *required + :return: a boolean value that indicates if the market is ready for trading + """ + return all(self.status_dict.values()) + + @property + def limit_orders(self) -> List[LimitOrder]: + """ + *required + :return: list of active limit orders + """ + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + @property + def tracking_states(self) -> Dict[str, any]: + """ + *required + :return: Dict[client_order_id: InFlightOrder] + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + return { + key: value.to_json() + for key, value in self._in_flight_orders.items() + } + + @property + def in_flight_orders(self) -> Dict[str, CoinbaseProInFlightOrder]: + return self._in_flight_orders + + @property + def user_stream_tracker(self) -> CoinbaseProUserStreamTracker: + return self._user_stream_tracker + + @property + def maker_fee_percentage(self) -> Decimal: + return self._maker_fee_percentage + + @property + def taker_fee_percentage(self) -> Decimal: + return self._taker_fee_percentage + + @property + def trading_rules(self): + return self._trading_rules + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + *required + Updates inflight order statuses from API results + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + self._in_flight_orders.update({ + key: CoinbaseProInFlightOrder.from_json(value) + for key, value in saved_states.items() + }) + + cdef c_start(self, Clock clock, double timestamp): + """ + *required + c_start function used by top level Clock to orchestrate components of the bot + """ + self._tx_tracker.c_start(clock, timestamp) + ExchangeBase.c_start(self, clock, timestamp) + + async def start_network(self): + """ + *required + Async function used by NetworkBase class to handle when a single market goes online + """ + self._stop_network() + self.order_book_tracker.start() + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + def _stop_network(self): + """ + Synchronous function that handles when a single market goes offline + """ + self.order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + self._status_polling_task = self._user_stream_tracker_task = \ + self._user_stream_event_listener_task = None + + async def stop_network(self): + """ + *required + Async wrapper for `self._stop_network`. Used by NetworkBase class to handle when a single market goes offline. + """ + self._stop_network() + + async def check_network(self) -> NetworkStatus: + """ + *required + Async function used by NetworkBase class to check if the market is online / offline. + """ + try: + await self._api_request(RESTMethod.GET, endpoint=CONSTANTS.TIME_PATH_URL) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + cdef c_tick(self, double timestamp): + """ + *required + Used by top level Clock to orchestrate components of the bot. + This function is called frequently with every clock tick + """ + cdef: + int64_t last_tick = (self._last_timestamp / self._poll_interval) + int64_t current_tick = (timestamp / self._poll_interval) + + ExchangeBase.c_tick(self, timestamp) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + self._last_timestamp = timestamp + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._rest_assistant = await self._web_assistants_factory.get_rest_assistant() + return self._rest_assistant + + async def _api_request( + self, + method: RESTMethod, + url: Optional[str] = None, + endpoint: Optional[str] = None, + data: Any = None, + ) -> Dict[str, Any]: + """ + A wrapper for submitting API requests to Coinbase Pro + :returns: json data from the endpoints + """ + client = await self._get_rest_assistant() + request = CoinbaseProRESTRequest(method, url, data=data, endpoint=endpoint, is_auth_required=True) + request.data = "" if request.data is None else request.data + response = await client.call(request, timeout=self.API_CALL_TIMEOUT) + resp_data = await response.json() + if response.status != 200: + raise IOError(f"Error fetching data from {response.url}. HTTP status is {response.status}. {resp_data}") + response_data = await response.json() + return response_data + + cdef object c_get_fee(self, + str base_currency, + str quote_currency, + object order_type, + object order_side, + object amount, + object price, + object is_maker = None): + """ + *required + function to calculate fees for a particular order + :returns: TradeFee class that includes fee percentage and flat fees + """ + # There is no API for checking user's fee tier + # Fee info from https://pro.coinbase.com/fees + is_maker = order_type is OrderType.LIMIT_MAKER + return estimate_fee("coinbase_pro", is_maker) + + async def _update_fee_percentage(self): + """ + Pulls the API for updated balances + """ + cdef: + double current_timestamp = self._current_timestamp + + if current_timestamp - self._last_fee_percentage_update_timestamp <= self.UPDATE_FEE_PERCENTAGE_INTERVAL: + return + + fee_info = await self._api_request(RESTMethod.GET, endpoint=CONSTANTS.FEES_PATH_URL) + self._maker_fee_percentage = Decimal(fee_info["maker_fee_rate"]) + self._taker_fee_percentage = Decimal(fee_info["taker_fee_rate"]) + self._last_fee_percentage_update_timestamp = current_timestamp + + async def _update_balances(self): + """ + Pulls the API for updated balances + """ + cdef: + dict account_info + list balances + str asset_name + set local_asset_names = set(self._account_balances.keys()) + set remote_asset_names = set() + set asset_names_to_remove + + account_balances = await self._api_request(RESTMethod.GET, endpoint=CONSTANTS.ACCOUNTS_PATH_URL) + + for balance_entry in account_balances: + asset_name = balance_entry["currency"] + available_balance = Decimal(balance_entry["available"]) + total_balance = Decimal(balance_entry["balance"]) + self._account_available_balances[asset_name] = available_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._in_flight_orders.items()} + self._in_flight_orders_snapshot_timestamp = self._current_timestamp + + async def _update_trading_rules(self): + """ + Pulls the API for trading rules (min / max order size, etc) + """ + cdef: + # The poll interval for withdraw rules is 60 seconds. + int64_t last_tick = (self._last_timestamp / 60.0) + int64_t current_tick = (self._current_timestamp / 60.0) + if current_tick > last_tick or len(self._trading_rules) == 0: + product_info = await self._api_request(RESTMethod.GET, endpoint=CONSTANTS.PRODUCTS_PATH_URL) + trading_rules_list = self._format_trading_rules(product_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + + def _format_trading_rules(self, raw_trading_rules: List[Any]) -> List[TradingRule]: + """ + Turns json data from API into TradingRule instances + :returns: List of TradingRule + """ + cdef: + list retval = [] + for rule in raw_trading_rules: + try: + trading_pair = rule.get("id") + retval.append(TradingRule(trading_pair, + min_price_increment=Decimal(str(rule.get("quote_increment"))), + min_base_amount_increment=Decimal(str(rule.get("base_increment"))), + min_notional_size=Decimal(str(rule.get("min_market_funds"))), + supports_market_orders=(not rule.get("limit_only")))) + except Exception: + self.logger().error(f"Error parsing the trading_pair rule {rule}. Skipping.", exc_info=True) + return retval + + async def _update_order_status(self): + """ + Pulls the rest API for for latest order statuses and update local order statuses. + """ + cdef: + double current_timestamp = self._current_timestamp + + if current_timestamp - self._last_order_update_timestamp <= self.UPDATE_ORDERS_INTERVAL: + return + + tracked_orders = list(self._in_flight_orders.values()) + results = await self.list_orders() + order_dict = dict((result["id"], result) for result in results) + + for tracked_order in tracked_orders: + exchange_order_id = await tracked_order.get_exchange_order_id() + order_update = order_dict.get(exchange_order_id) + client_order_id = tracked_order.client_order_id + if order_update is None: + try: + order = await self.get_order(client_order_id) + except IOError as e: + if "order not found" in str(e): + # The order does not exist. So we should not be tracking it. + self.logger().info( + f"The tracked order {client_order_id} does not exist on Coinbase Pro." + f"Order removed from tracking." + ) + self.c_stop_tracking_order(client_order_id) + self.c_trigger_event( + self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, client_order_id) + ) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error fetching status update for the order {client_order_id}: ", + exc_info=True, + app_warning_msg=f"Could not fetch updates for the order {client_order_id}. " + f"Check API key and network connection.{e}" + ) + continue + + done_reason = order_update.get("done_reason") + # Calculate the newly executed amount for this update. + new_confirmed_amount = Decimal(order_update["filled_size"]) + execute_amount_diff = new_confirmed_amount - tracked_order.executed_amount_base + execute_price = s_decimal_0 if new_confirmed_amount == s_decimal_0 \ + else Decimal(order_update["executed_value"]) / new_confirmed_amount + + order_type_description = tracked_order.order_type_description + order_type = tracked_order.order_type + # Emit event if executed amount is greater than 0. + if execute_amount_diff > s_decimal_0: + order_filled_event = OrderFilledEvent( + self._current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + order_type, + execute_price, + execute_amount_diff, + self.c_get_fee( + tracked_order.base_asset, + tracked_order.quote_asset, + order_type, + tracked_order.trade_type, + execute_price, + execute_amount_diff, + ), + # Coinbase Pro's websocket stream tags events with order_id rather than trade_id + # Using order_id here for easier data validation + exchange_trade_id=str(int(self._time() * 1e6)), + ) + self.logger().info(f"Filled {execute_amount_diff} out of {tracked_order.amount} of the " + f"{order_type_description} order {client_order_id}.") + self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event) + + # Update the tracked order + tracked_order.last_state = done_reason if done_reason in {"filled", "canceled"} else order_update["status"] + tracked_order.executed_amount_base = new_confirmed_amount + tracked_order.executed_amount_quote = Decimal(order_update["executed_value"]) + tracked_order.fee_paid = Decimal(order_update["fill_fees"]) + if tracked_order.is_done: + if not tracked_order.is_failure: + if tracked_order.trade_type == TradeType.BUY: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + order_type)) + else: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + order_type)) + else: + self.logger().info(f"The market order {tracked_order.client_order_id} has failed/been canceled " + f"according to order status API.") + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent( + self._current_timestamp, + tracked_order.client_order_id + )) + self.c_stop_tracking_order(tracked_order.client_order_id) + self._last_order_update_timestamp = current_timestamp + + async def _iter_user_event_queue(self) -> AsyncIterable[OrderBookMessage]: + """ + Iterator for incoming messages from the user stream. + """ + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unknown error. Retrying after 1 seconds.", exc_info=True) + await asyncio.sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Update order statuses from incoming messages from the user stream + """ + async for event_message in self._iter_user_event_queue(): + try: + content = event_message + event_type = content.get("type") + exchange_order_ids = [content.get("order_id"), + content.get("maker_order_id"), + content.get("taker_order_id")] + + tracked_order = None + for order in list(self._in_flight_orders.values()): + await order.get_exchange_order_id() + if order.exchange_order_id in exchange_order_ids: + tracked_order = order + break + + if tracked_order is None: + continue + + order_type_description = tracked_order.order_type_description + execute_price = Decimal(content.get("price", 0.0)) + execute_amount_diff = s_decimal_0 + + if event_type == "match": + updated = tracked_order.update_with_trade_update(content) + if updated: + execute_amount_diff = Decimal(content.get("size", 0.0)) + self.logger().info(f"Filled {execute_amount_diff} out of {tracked_order.amount} of the " + f"{order_type_description} order {tracked_order.client_order_id}") + exchange_order_id = tracked_order.exchange_order_id + + self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, + OrderFilledEvent( + self._current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + execute_price, + execute_amount_diff, + AddedToCostTradeFee( + percent=tracked_order.fee_rate_from_trade_update(content) + ), + exchange_trade_id=content["trade_id"] + )) + + if event_type == "change": + if content.get("new_size") is not None: + tracked_order.amount = Decimal(content.get("new_size", 0.0)) + elif content.get("new_funds") is not None: + if tracked_order.price is not s_decimal_0: + tracked_order.amount = Decimal(content.get("new_funds")) / tracked_order.price + else: + self.logger().error(f"Invalid change message - '{content}'. Aborting.") + + if event_type in ["open", "done"]: + remaining_size = Decimal(content.get("remaining_size", tracked_order.amount)) + new_confirmed_amount = tracked_order.amount - remaining_size + execute_amount_diff = new_confirmed_amount - tracked_order.executed_amount_base + tracked_order.executed_amount_base = new_confirmed_amount + tracked_order.executed_amount_quote += execute_amount_diff * execute_price + + if content.get("reason") == "filled": # Only handles orders with "done" status + if tracked_order.trade_type == TradeType.BUY: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to Coinbase Pro user stream.") + self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type)) + else: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to Coinbase Pro user stream.") + self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type)) + tracked_order.last_state = "filled" + self.c_stop_tracking_order(tracked_order.client_order_id) + + elif content.get("reason") == "canceled": # reason == "canceled": + execute_amount_diff = 0 + tracked_order.last_state = "canceled" + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, tracked_order.client_order_id)) + execute_amount_diff = 0 + self.c_stop_tracking_order(tracked_order.client_order_id) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await asyncio.sleep(5.0) + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def place_order(self, order_id: str, trading_pair: str, amount: Decimal, is_buy: bool, order_type: OrderType, + price: Decimal): + """ + Async wrapper for placing orders through the rest API. + :returns: json response from the API + """ + data = { + "size": f"{amount:f}", + "product_id": trading_pair, + "side": "buy" if is_buy else "sell", + "type": "limit", + } + if order_type is OrderType.LIMIT: + data["price"] = f"{price:f}" + elif order_type is OrderType.LIMIT_MAKER: + data["price"] = f"{price:f}" + data["post_only"] = True + order_result = await self._api_request(RESTMethod.POST, endpoint=CONSTANTS.ORDERS_PATH_URL, data=data) + return order_result + + async def execute_buy(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = s_decimal_0): + """ + Function that takes strategy inputs, auto corrects itself with trading rule, + and submit an API request to place a buy order + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + decimal_amount = self.quantize_order_amount(trading_pair, amount) + decimal_price = self.quantize_order_price(trading_pair, price) + if decimal_amount < trading_rule.min_order_size: + raise ValueError(f"Buy order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + self.c_start_tracking_order(order_id, trading_pair, order_type, TradeType.BUY, decimal_price, decimal_amount) + order_result = await self.place_order(order_id, trading_pair, decimal_amount, True, order_type, decimal_price) + + exchange_order_id = order_result["id"] + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type} buy order {order_id} for {decimal_amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + + self.c_trigger_event(self.MARKET_BUY_ORDER_CREATED_EVENT_TAG, + BuyOrderCreatedEvent(self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp)) + except asyncio.CancelledError: + raise + except Exception: + self.c_stop_tracking_order(order_id) + order_type_str = order_type.name.lower() + self.logger().network( + f"Error submitting buy {order_type_str} order to Coinbase Pro for " + f"{decimal_amount} {trading_pair} {price}.", + exc_info=True, + app_warning_msg="Failed to submit buy order to Coinbase Pro. " + "Check API key and network connection." + ) + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, order_type)) + + cdef str c_buy(self, str trading_pair, object amount, object order_type=OrderType.LIMIT, object price=s_decimal_0, + dict kwargs={}): + """ + *required + Synchronous wrapper that generates a client-side order ID and schedules the buy order. + """ + cdef: + int64_t tracking_nonce = get_tracking_nonce() + str order_id = str(f"buy-{trading_pair}-{tracking_nonce}") + + safe_ensure_future(self.execute_buy(order_id, trading_pair, amount, order_type, price)) + return order_id + + async def execute_sell(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = s_decimal_0): + """ + Function that takes strategy inputs, auto corrects itself with trading rule, + and submit an API request to place a sell order + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + decimal_amount = self.quantize_order_amount(trading_pair, amount) + decimal_price = self.quantize_order_price(trading_pair, price) + if decimal_amount < trading_rule.min_order_size: + raise ValueError(f"Sell order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + self.c_start_tracking_order(order_id, trading_pair, order_type, TradeType.SELL, decimal_price, decimal_amount) + order_result = await self.place_order(order_id, trading_pair, decimal_amount, False, order_type, decimal_price) + + exchange_order_id = order_result["id"] + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type} sell order {order_id} for {decimal_amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + + self.c_trigger_event(self.MARKET_SELL_ORDER_CREATED_EVENT_TAG, + SellOrderCreatedEvent(self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp)) + except asyncio.CancelledError: + raise + except Exception: + self.c_stop_tracking_order(order_id) + order_type_str = order_type.name.lower() + self.logger().network( + f"Error submitting sell {order_type_str} order to Coinbase Pro for " + f"{decimal_amount} {trading_pair} {price}.", + exc_info=True, + app_warning_msg="Failed to submit sell order to Coinbase Pro. " + "Check API key and network connection." + ) + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, order_type)) + + cdef str c_sell(self, + str trading_pair, + object amount, + object order_type=OrderType.LIMIT, + object price=s_decimal_0, + dict kwargs={}): + """ + *required + Synchronous wrapper that generates a client-side order ID and schedules the sell order. + """ + cdef: + int64_t tracking_nonce = get_tracking_nonce() + str order_id = str(f"sell-{trading_pair}-{tracking_nonce}") + safe_ensure_future(self.execute_sell(order_id, trading_pair, amount, order_type, price)) + return order_id + + async def execute_cancel(self, trading_pair: str, order_id: str): + """ + Function that makes API request to cancel an active order + """ + try: + exchange_order_id = await self._in_flight_orders.get(order_id).get_exchange_order_id() + endpoint = f"{CONSTANTS.ORDERS_PATH_URL}/{exchange_order_id}" + cancelled_id = await self._api_request(RESTMethod.DELETE, endpoint=endpoint) + if cancelled_id == exchange_order_id: + self.logger().info(f"Successfully canceled order {order_id}.") + self.c_stop_tracking_order(order_id) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, order_id)) + return order_id + except IOError as e: + if "order not found" in str(e): + # The order was never there to begin with. So cancelling it is a no-op but semantically successful. + self.logger().info(f"The order {order_id} does not exist on Coinbase Pro. No cancelation needed.") + self.c_stop_tracking_order(order_id) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, order_id)) + return order_id + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Failed to cancel order {order_id}: ", + exc_info=True, + app_warning_msg=f"Failed to cancel the order {order_id} on Coinbase Pro. " + f"Check API key and network connection.{e}" + ) + return None + + cdef c_cancel(self, str trading_pair, str order_id): + """ + *required + Synchronous wrapper that schedules cancelling an order. + """ + safe_ensure_future(self.execute_cancel(trading_pair, order_id)) + return order_id + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + *required + Async function that cancels all active orders. + Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) + :returns: List of CancellationResult which indicates whether each order is successfully cancelled. + """ + incomplete_orders = [o for o in self._in_flight_orders.values() if not o.is_done] + tasks = [self.execute_cancel(o.trading_pair, o.client_order_id) for o in incomplete_orders] + order_id_set = set([o.client_order_id for o in incomplete_orders]) + successful_cancellations = [] + + try: + async with timeout(timeout_seconds): + results = await safe_gather(*tasks, return_exceptions=True) + for client_order_id in results: + if type(client_order_id) is str: + order_id_set.remove(client_order_id) + successful_cancellations.append(CancellationResult(client_order_id, True)) + else: + self.logger().warning( + f"failed to cancel order with error: " + f"{repr(client_order_id)}" + ) + except Exception as e: + self.logger().network( + f"Unexpected error canceling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order on Coinbase Pro. Check API key and network connection." + ) + + failed_cancellations = [CancellationResult(oid, False) for oid in order_id_set] + return successful_cancellations + failed_cancellations + + async def _status_polling_loop(self): + """ + Background process that periodically pulls for changes from the rest API + """ + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + + await safe_gather( + self._update_balances(), + self._update_order_status(), + self._update_fee_percentage(), + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching account updates.", + exc_info=True, + app_warning_msg=f"Could not fetch account updates on Coinbase Pro. " + f"Check API key and network connection." + ) + + async def _trading_rules_polling_loop(self): + """ + Separate background process that periodically pulls for trading rule changes + (Since trading rules don't get updated often, it is pulled less often.) + """ + while True: + try: + await safe_gather(self._update_trading_rules()) + await asyncio.sleep(60) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading rules.", + exc_info=True, + app_warning_msg=f"Could not fetch trading rule updates on Coinbase Pro. " + f"Check network connection." + ) + await asyncio.sleep(0.5) + + async def get_order(self, client_order_id: str) -> Dict[str, Any]: + """ + Gets status update for a particular order via rest API + :returns: json response + """ + order = self._in_flight_orders.get(client_order_id) + if order is None: + return None + exchange_order_id = await order.get_exchange_order_id() + endpoint = f"{CONSTANTS.ORDERS_PATH_URL}/{exchange_order_id}" + result = await self._api_request(RESTMethod.GET, endpoint=endpoint) + return result + + async def list_orders(self) -> List[Any]: + """ + Gets a list of the user's active orders via rest API + :returns: json response + """ + endpoint = f"{CONSTANTS.ORDERS_PATH_URL}?status=all" + result = await self._api_request(RESTMethod.GET, endpoint=endpoint) + return result + + cdef OrderBook c_get_order_book(self, str trading_pair): + """ + :returns: OrderBook for a specific trading pair + """ + cdef: + dict order_books = self._order_book_tracker.order_books + + if trading_pair not in order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return order_books[trading_pair] + + def start_tracking_order(self, + order_id: str, + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal): + self.c_start_tracking_order( + order_id, + trading_pair, + order_type, + trade_type, + price, + amount) + + cdef c_start_tracking_order(self, + str client_order_id, + str trading_pair, + object order_type, + object trade_type, + object price, + object amount): + """ + Add new order to self._in_flight_orders mapping + """ + self._in_flight_orders[client_order_id] = CoinbaseProInFlightOrder( + client_order_id, + None, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp=self.current_timestamp + ) + + cdef c_stop_tracking_order(self, str order_id): + """ + Delete an order from self._in_flight_orders mapping + """ + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + cdef c_did_timeout_tx(self, str tracking_id): + """ + Triggers MarketEvent.TransactionFailure when an Ethereum transaction has timed out + """ + self.c_trigger_event(self.MARKET_TRANSACTION_FAILURE_EVENT_TAG, + MarketTransactionFailureEvent(self._current_timestamp, tracking_id)) + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + """ + *required + Get the minimum increment interval for price + :return: Min order price increment in Decimal format + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size): + """ + *required + Get the minimum increment interval for order size (e.g. 0.01 USD) + :return: Min order size increment in Decimal format + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_base_amount_increment + + cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=s_decimal_0): + """ + *required + Check current order amount against trading rule, and correct any rule violations + :return: Valid order amount in Decimal format + """ + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + global s_decimal_0 + quantized_amount = ExchangeBase.c_quantize_order_amount(self, trading_pair, amount) + + # Check against min_order_size. If not passing either check, return 0. + if quantized_amount < trading_rule.min_order_size: + return s_decimal_0 + + # Check against max_order_size. If not passing either check, return 0. + if quantized_amount > trading_rule.max_order_size: + return s_decimal_0 + + return quantized_amount + + def get_price(self, trading_pair: str, is_buy: bool) -> Decimal: + return self.c_get_price(trading_pair, is_buy) + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_nan, **kwargs) -> str: + return self.c_buy(trading_pair, amount, order_type, price, kwargs) + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_nan, **kwargs) -> str: + return self.c_sell(trading_pair, amount, order_type, price, kwargs) + + def cancel(self, trading_pair: str, client_order_id: str): + return self.c_cancel(trading_pair, client_order_id) + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_nan, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + return self.c_get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + def get_order_book(self, trading_pair: str) -> OrderBook: + return self.c_get_order_book(trading_pair) + + async def all_trading_pairs(self) -> List[str]: + # This method should be removed and instead we should implement _initialize_trading_pair_symbol_map + return await CoinbaseProAPIOrderBookDataSource.fetch_trading_pairs() + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + # This method should be removed and instead we should implement _get_last_traded_price + return await CoinbaseProAPIOrderBookDataSource.get_last_traded_prices(trading_pairs=trading_pairs) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pxd b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pxd new file mode 100644 index 0000000..fef8bd2 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pxd @@ -0,0 +1,6 @@ +from hummingbot.connector.in_flight_order_base cimport InFlightOrderBase + + +cdef class CoinbaseProInFlightOrder(InFlightOrderBase): + cdef: + object trade_id_set diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pyx b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pyx new file mode 100644 index 0000000..85bf8b6 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_in_flight_order.pyx @@ -0,0 +1,81 @@ +from decimal import Decimal +from typing import Any, Dict, Optional + +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import OrderType, TradeType + + +cdef class CoinbaseProInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + initial_state: str = "open"): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp, + initial_state, + ) + + self.trade_id_set = set() + self.fee_asset = self.quote_asset + + @property + def is_done(self) -> bool: + return self.last_state in {"filled", "canceled", "done"} + + @property + def is_failure(self) -> bool: + # This is the only known canceled state + return self.last_state == "canceled" + + @property + def is_cancelled(self) -> bool: + return self.last_state == "canceled" + + @property + def order_type_description(self) -> str: + """ + :return: Order description string . One of ["limit buy" / "limit sell" / "market buy" / "market sell"] + """ + order_type = "limit_maker" if self.order_type is OrderType.LIMIT_MAKER else "limit" + side = "buy" if self.trade_type == TradeType.BUY else "sell" + return f"{order_type} {side}" + + def fee_rate_from_trade_update(self, trade_update: Dict[str, Any]) -> Decimal: + maker_fee_rate = Decimal(str(trade_update.get("maker_fee_rate", "0"))) + taker_fee_rate = Decimal(str(trade_update.get("taker_fee_rate", "0"))) + fee_rate = max(maker_fee_rate, taker_fee_rate) + return fee_rate + + def update_with_trade_update(self, trade_update: Dict[str, Any]) -> bool: + """ + Updates the in flight order with trade update (from GET /trade_history end point) + return: True if the order gets updated otherwise False + """ + trade_id = trade_update["trade_id"] + if (self.exchange_order_id not in [trade_update["maker_order_id"], trade_update["taker_order_id"]] + or trade_id in self.trade_id_set): + return False + self.trade_id_set.add(trade_id) + trade_amount = Decimal(str(trade_update["size"])) + trade_price = Decimal(str(trade_update["price"])) + quote_amount = trade_amount * trade_price + + self.executed_amount_base += trade_amount + self.executed_amount_quote += quote_amount + fee_rate = self.fee_rate_from_trade_update(trade_update) + self.fee_paid += quote_amount * fee_rate + + return True diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pxd b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pxd new file mode 100644 index 0000000..a4d1ee1 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pxd @@ -0,0 +1,5 @@ +from hummingbot.core.data_type.order_book cimport OrderBook + + +cdef class CoinbaseProOrderBook(OrderBook): + pass diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pyx b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pyx new file mode 100644 index 0000000..6837e0f --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book.pyx @@ -0,0 +1,69 @@ +import logging +from typing import Dict, List, Optional + +import pandas as pd + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_message import CoinbaseProOrderBookMessage +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.logger import HummingbotLogger + +_cbpob_logger = None + + +cdef class CoinbaseProOrderBook(OrderBook): + @classmethod + def logger(cls) -> HummingbotLogger: + global _cbpob_logger + if _cbpob_logger is None: + _cbpob_logger = logging.getLogger(__name__) + return _cbpob_logger + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + *required + Convert json snapshot data into standard OrderBookMessage format + :param msg: json snapshot data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: CoinbaseProOrderBookMessage + """ + if metadata: + msg.update(metadata) + return CoinbaseProOrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=msg, + timestamp=timestamp + ) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + *required + Convert json diff data into standard OrderBookMessage format + :param msg: json diff data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: CoinbaseProOrderBookMessage + """ + if metadata: + msg.update(metadata) + if "time" in msg: + msg_time = pd.Timestamp(msg["time"]).timestamp() + return CoinbaseProOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp or msg_time) + + @classmethod + def from_snapshot(cls, snapshot: OrderBookMessage): + raise NotImplementedError("Coinbase Pro order book needs to retain individual order data.") + + @classmethod + def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): + raise NotImplementedError("Coinbase Pro order book needs to retain individual order data.") diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_message.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_message.py new file mode 100644 index 0000000..3050d5b --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_message.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +from typing import Dict, List, Optional + +import pandas as pd + +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_row import OrderBookRow + + +class CoinbaseProOrderBookMessage(OrderBookMessage): + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + if timestamp is None: + if message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError("timestamp must not be None when initializing snapshot messages.") + timestamp = pd.Timestamp(content["time"], tz="UTC").timestamp() + return super(CoinbaseProOrderBookMessage, cls).__new__( + cls, message_type, content, timestamp=timestamp, *args, **kwargs + ) + + @property + def update_id(self) -> int: + if self.type in [OrderBookMessageType.DIFF, OrderBookMessageType.SNAPSHOT]: + return int(self.content["sequence"]) + else: + return -1 + + @property + def trade_id(self) -> int: + if self.type is OrderBookMessageType.TRADE: + return int(self.content["sequence"]) + return -1 + + @property + def trading_pair(self) -> str: + if "product_id" in self.content: + return self.content["product_id"] + elif "symbol" in self.content: + return self.content["symbol"] + + @property + def asks(self) -> List[OrderBookRow]: + raise NotImplementedError("Coinbase Pro order book messages have different semantics.") + + @property + def bids(self) -> List[OrderBookRow]: + raise NotImplementedError("Coinbase Pro order book messages have different semantics.") diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py new file mode 100644 index 0000000..81b7fcf --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python + +import asyncio +import bisect +import logging +import time +from collections import defaultdict, deque +from typing import Deque, Dict, List, Optional + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_active_order_tracker import CoinbaseProActiveOrderTracker +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_order_book_data_source import ( + CoinbaseProAPIOrderBookDataSource +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book import CoinbaseProOrderBook +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_message import CoinbaseProOrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class CoinbaseProOrderBookTracker(OrderBookTracker): + _cbpobt_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._cbpobt_logger is None: + cls._cbpobt_logger = logging.getLogger(__name__) + return cls._cbpobt_logger + + def __init__( + self, + trading_pairs: Optional[List[str]] = None, + web_assistants_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__( + data_source=CoinbaseProAPIOrderBookDataSource(trading_pairs, web_assistants_factory), + trading_pairs=trading_pairs, + ) + self._ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._process_msg_deque_task: Optional[asyncio.Task] = None + self._past_diffs_windows: Dict[str, Deque] = {} + self._order_books: Dict[str, CoinbaseProOrderBook] = {} + self._saved_message_queues: Dict[str, Deque[CoinbaseProOrderBookMessage]] = defaultdict(lambda: deque(maxlen=1000)) + self._active_order_trackers: Dict[str, CoinbaseProActiveOrderTracker] = defaultdict(CoinbaseProActiveOrderTracker) + + @property + def exchange_name(self) -> str: + """ + *required + Name of the current exchange + """ + return "coinbase_pro" + + async def _order_book_diff_router(self): + """ + Route the real-time order book diff messages to the correct order book. + """ + last_message_timestamp: float = time.time() + messages_queued: int = 0 + messages_accepted: int = 0 + messages_rejected: int = 0 + while True: + try: + ob_message: CoinbaseProOrderBookMessage = await self._order_book_diff_stream.get() + trading_pair: str = ob_message.trading_pair + if trading_pair not in self._tracking_message_queues: + messages_queued += 1 + # Save diff messages received before snapshots are ready + self._saved_message_queues[trading_pair].append(ob_message) + continue + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + # Check the order book's initial update ID. If it's larger, don't bother. + order_book: CoinbaseProOrderBook = self._order_books[trading_pair] + + if order_book.snapshot_uid > ob_message.update_id: + messages_rejected += 1 + continue + await message_queue.put(ob_message) + messages_accepted += 1 + if ob_message.content["type"] == "match": # put match messages to trade queue + trade_type = float(TradeType.SELL.value) if ob_message.content["side"].upper() == "SELL" \ + else float(TradeType.BUY.value) + self._order_book_trade_stream.put_nowait(OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": ob_message.trading_pair, + "trade_type": trade_type, + "trade_id": ob_message.update_id, + "update_id": ob_message.timestamp, + "price": ob_message.content["price"], + "amount": ob_message.content["size"] + }, timestamp=ob_message.timestamp)) + + # Log some statistics. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Diff messages processed: {messages_accepted}, " + f"rejected: {messages_rejected}, queued: {messages_queued}") + messages_accepted = 0 + messages_rejected = 0 + messages_queued = 0 + + last_message_timestamp = now + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f'{"Unexpected error routing order book messages."}', + exc_info=True, + app_warning_msg=f'{"Unexpected error routing order book messages. Retrying after 5 seconds."}' + ) + await asyncio.sleep(5.0) + + async def _track_single_book(self, trading_pair: str): + """ + Update an order book with changes from the latest batch of received messages + """ + past_diffs_window: Deque[CoinbaseProOrderBookMessage] = deque() + self._past_diffs_windows[trading_pair] = past_diffs_window + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: CoinbaseProOrderBook = self._order_books[trading_pair] + active_order_tracker: CoinbaseProActiveOrderTracker = self._active_order_trackers[trading_pair] + + last_message_timestamp: float = time.time() + diff_messages_accepted: int = 0 + + while True: + try: + message: CoinbaseProOrderBookMessage = None + saved_messages: Deque[CoinbaseProOrderBookMessage] = self._saved_message_queues[trading_pair] + # Process saved messages first if there are any + if len(saved_messages) > 0: + message = saved_messages.popleft() + else: + message = await message_queue.get() + + if message.type is OrderBookMessageType.DIFF: + bids, asks = active_order_tracker.convert_diff_message_to_order_book_row(message) + order_book.apply_diffs(bids, asks, message.update_id) + past_diffs_window.append(message) + while len(past_diffs_window) > self.PAST_DIFF_WINDOW_SIZE: + past_diffs_window.popleft() + diff_messages_accepted += 1 + + # Output some statistics periodically. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Processed {diff_messages_accepted} order book diffs for {trading_pair}.") + diff_messages_accepted = 0 + last_message_timestamp = now + elif message.type is OrderBookMessageType.SNAPSHOT: + past_diffs: List[CoinbaseProOrderBookMessage] = list(past_diffs_window) + # only replay diffs later than snapshot, first update active order with snapshot then replay diffs + replay_position = bisect.bisect_right(past_diffs, message) + replay_diffs = past_diffs[replay_position:] + s_bids, s_asks = active_order_tracker.convert_snapshot_message_to_order_book_row(message) + order_book.apply_snapshot(s_bids, s_asks, message.update_id) + for diff_message in replay_diffs: + d_bids, d_asks = active_order_tracker.convert_diff_message_to_order_book_row(diff_message) + order_book.apply_diffs(d_bids, d_asks, diff_message.update_id) + + self.logger().debug(f"Processed order book snapshot for {trading_pair}.") + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Unexpected error processing order book messages for {trading_pair}.", + exc_info=True, + app_warning_msg=f'{"Unexpected error processing order book messages. Retrying after 5 seconds."}' + ) + await asyncio.sleep(5.0) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker_entry.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker_entry.py new file mode 100644 index 0000000..3a4baf3 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_order_book_tracker_entry.py @@ -0,0 +1,23 @@ +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_active_order_tracker import CoinbaseProActiveOrderTracker +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry + + +class CoinbaseProOrderBookTrackerEntry(OrderBookTrackerEntry): + def __init__(self, + trading_pair: str, + timestamp: float, + order_book: OrderBook, + active_order_tracker: CoinbaseProActiveOrderTracker): + self._active_order_tracker = active_order_tracker + super(CoinbaseProOrderBookTrackerEntry, self).__init__(trading_pair, timestamp, order_book) + + def __repr__(self) -> str: + return ( + f"CoinbaseProOrderBookTrackerEntry(trading_pair='{self._trading_pair}', timestamp='{self._timestamp}', " + f"order_book='{self._order_book}')" + ) + + @property + def active_order_tracker(self) -> CoinbaseProActiveOrderTracker: + return self._active_order_tracker diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_user_stream_tracker.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_user_stream_tracker.py new file mode 100644 index 0000000..f2d0d91 --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_user_stream_tracker.py @@ -0,0 +1,65 @@ +import logging +from typing import List, Optional + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_user_stream_data_source import ( + CoinbaseProAPIUserStreamDataSource +) +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class CoinbaseProUserStreamTracker(UserStreamTracker): + _cbpust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bust_logger is None: + cls._bust_logger = logging.getLogger(__name__) + return cls._bust_logger + + def __init__( + self, + trading_pairs: Optional[List[str]] = None, + web_assistants_factory: Optional[WebAssistantsFactory] = None, + ): + self._trading_pairs: List[str] = trading_pairs or [] + self._web_assistants_factory = web_assistants_factory + super().__init__(data_source=CoinbaseProAPIUserStreamDataSource( + trading_pairs=self._trading_pairs, + web_assistants_factory=self._web_assistants_factory, + )) + + @property + def data_source(self) -> UserStreamTrackerDataSource: + """ + *required + Initializes a user stream data source (user specific order diffs from live socket stream) + :return: OrderBookTrackerDataSource + """ + if not self._data_source: + self._data_source = CoinbaseProAPIUserStreamDataSource( + trading_pairs=self._trading_pairs, + web_assistants_factory=self._web_assistants_factory, + ) + return self._data_source + + @property + def exchange_name(self) -> str: + """ + *required + Name of the current exchange + """ + return "coinbase_pro" + + async def start(self): + """ + *required + Start all listeners and tasks + """ + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py new file mode 100644 index 0000000..decccee --- /dev/null +++ b/hummingbot/connector/exchange/coinbase_pro/coinbase_pro_utils.py @@ -0,0 +1,79 @@ +import typing +from dataclasses import dataclass +from typing import Optional + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.connections.data_types import EndpointRESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if typing.TYPE_CHECKING: + from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_auth import CoinbaseProAuth + +CENTRALIZED = True + +EXAMPLE_PAIR = "ETH-USDC" + +DEFAULT_FEES = [0.5, 0.5] + + +class CoinbaseProConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="coinbase_pro", client_data=None) + coinbase_pro_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Coinbase API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + coinbase_pro_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Coinbase secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + coinbase_pro_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Coinbase passphrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "coinbase_pro" + + +KEYS = CoinbaseProConfigMap.construct() + + +@dataclass +class CoinbaseProRESTRequest(EndpointRESTRequest): + def __post_init__(self): + super().__post_init__() + self._ensure_endpoint_for_auth() + + @property + def base_url(self) -> str: + return CONSTANTS.REST_URL + + def _ensure_endpoint_for_auth(self): + if self.is_auth_required and self.endpoint is None: + raise ValueError("The endpoint must be specified if authentication is required.") + + +def build_coinbase_pro_web_assistant_factory(auth: Optional['CoinbaseProAuth'] = None) -> WebAssistantsFactory: + """The web-assistant's composition root.""" + throttler = AsyncThrottler(rate_limits=[]) + api_factory = WebAssistantsFactory(throttler=throttler, auth=auth) + return api_factory diff --git a/hummingbot/connector/exchange/foxbit/__init__.py b/hummingbot/connector/exchange/foxbit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/foxbit/foxbit_api_order_book_data_source.py b/hummingbot/connector/exchange/foxbit/foxbit_api_order_book_data_source.py new file mode 100644 index 0000000..4684c16 --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_api_order_book_data_source.py @@ -0,0 +1,210 @@ +import asyncio +import time +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils as utils, + foxbit_web_utils as web_utils, +) +from hummingbot.connector.exchange.foxbit.foxbit_order_book import ( + FoxbitOrderBook, + FoxbitOrderBookFields, + FoxbitTradeFields, +) +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.foxbit.foxbit_exchange import FoxbitExchange + + +class FoxbitAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + _trading_pair_exc_id = {} + _trading_pair_hb_dict = {} + _ORDER_BOOK_INTERVAL = 1.0 + + def __init__(self, + trading_pairs: List[str], + connector: 'FoxbitExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs) + self._connector = connector + self._trade_messages_queue_key = "trade" + self._diff_messages_queue_key = "order_book_diff" + self._domain = domain + self._api_factory = api_factory + self._first_update_id = {} + for trading_pair in self._trading_pairs: + self._first_update_id[trading_pair] = 0 + + self._live_stream_connected = {} + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + """ + Creates a local instance of the exchange order book for a particular trading pair + + :param trading_pair: the trading pair for which the order book has to be retrieved + + :return: a local copy of the current order book in the exchange + """ + await self._load_exchange_instrument_id() + instrument_id = await self._get_instrument_id_from_trading_pair(trading_pair) + self._live_stream_connected[instrument_id] = False + + snapshot_msg: OrderBookMessage = await self._order_book_snapshot(trading_pair=trading_pair) + order_book: OrderBook = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + return order_book + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + + instrument_id = await self._get_instrument_id_from_trading_pair(trading_pair) + wait_count = 0 + + while (not (instrument_id in self._live_stream_connected) or self._live_stream_connected[instrument_id] is False) and wait_count < 30: + self.logger().info("Waiting for real time stream before getting a snapshot") + await asyncio.sleep(self._ORDER_BOOK_INTERVAL) + wait_count += 1 + + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL.format(symbol[0]), domain=self._domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SNAPSHOT_PATH_URL, + ) + + return data + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + # Subscribe OrderBook + header = utils.get_ws_message_frame(endpoint=CONSTANTS.WS_SUBSCRIBE_ORDER_BOOK, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Subscribe"], + payload={"OMSId": 1, "InstrumentId": await self._get_instrument_id_from_trading_pair(trading_pair), "Depth": CONSTANTS.ORDER_BOOK_DEPTH},) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(header)) + await ws.send(subscribe_request) + + header = utils.get_ws_message_frame(endpoint=CONSTANTS.WS_SUBSCRIBE_TRADES, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Subscribe"], + payload={"InstrumentId": await self._get_instrument_id_from_trading_pair(trading_pair)},) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(header)) + await ws.send(subscribe_request) + + self.logger().info("Subscribed to public order book channel...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + raise + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=web_utils.websocket_url(), ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = FoxbitOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + self._first_update_id[trading_pair] = snapshot['sequence_id'] + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if CONSTANTS.WS_SUBSCRIBE_TRADES or CONSTANTS.WS_TRADE_RESPONSE in raw_message['n']: + full_msg = eval(raw_message['o'].replace(",false,", ",False,")) + for msg in full_msg: + instrument_id = int(msg[FoxbitTradeFields.INSTRUMENTID.value]) + trading_pair = "" + + if instrument_id not in self._trading_pair_hb_dict: + trading_pair = await self._get_trading_pair_from_instrument_id(instrument_id) + else: + trading_pair = self._trading_pair_hb_dict[instrument_id] + + trade_message = FoxbitOrderBook.trade_message_from_exchange( + msg=msg, + metadata={"trading_pair": trading_pair}, + ) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if CONSTANTS.WS_ORDER_BOOK_RESPONSE or CONSTANTS.WS_ORDER_STATE in raw_message['n']: + full_msg = eval(raw_message['o']) + for msg in full_msg: + instrument_id = int(msg[FoxbitOrderBookFields.PRODUCTPAIRCODE.value]) + + trading_pair = "" + + if instrument_id not in self._trading_pair_hb_dict: + trading_pair = await self._get_trading_pair_from_instrument_id(instrument_id) + else: + trading_pair = self._trading_pair_hb_dict[instrument_id] + + order_book_message: OrderBookMessage = FoxbitOrderBook.diff_message_from_exchange( + msg=msg, + metadata={"trading_pair": trading_pair}, + ) + message_queue.put_nowait(order_book_message) + self._live_stream_connected[instrument_id] = True + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "o" in event_message: + event_type = event_message.get("n") + if event_type == CONSTANTS.WS_SUBSCRIBE_TRADES: + return self._trade_messages_queue_key + elif event_type == CONSTANTS.WS_ORDER_BOOK_RESPONSE: + return self._diff_messages_queue_key + return channel + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _load_exchange_instrument_id(self): + for trading_pair in self._trading_pairs: + instrument_id = int(await self._connector.exchange_instrument_id_associated_to_pair(trading_pair=trading_pair)) + self._trading_pair_exc_id[trading_pair] = instrument_id + self._trading_pair_hb_dict[instrument_id] = trading_pair + + async def _get_trading_pair_from_instrument_id(self, instrument_id: int) -> str: + if instrument_id not in self._trading_pair_hb_dict: + await self._load_exchange_instrument_id() + return self._trading_pair_hb_dict[instrument_id] + + async def _get_instrument_id_from_trading_pair(self, traiding_pair: str) -> int: + if traiding_pair not in self._trading_pair_exc_id: + await self._load_exchange_instrument_id() + return self._trading_pair_exc_id[traiding_pair] diff --git a/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py b/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py new file mode 100644 index 0000000..422749f --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py @@ -0,0 +1,123 @@ +import asyncio +from typing import TYPE_CHECKING, List, Optional + +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils as utils, + foxbit_web_utils as web_utils, +) +from hummingbot.connector.exchange.foxbit.foxbit_auth import FoxbitAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.foxbit.foxbit_exchange import FoxbitExchange + + +class FoxbitAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: FoxbitAuth, + trading_pairs: List[str], + connector: 'FoxbitExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__() + self._auth: FoxbitAuth = auth + self._trading_pairs = trading_pairs + self._connector = connector + self._domain = domain + self._api_factory = api_factory + self._user_stream_data_source_initialized = False + + @property + def ready(self) -> bool: + return self._user_stream_data_source_initialized + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + try: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=web_utils.websocket_url(), ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + + header = utils.get_ws_message_frame( + endpoint=CONSTANTS.WS_AUTHENTICATE_USER, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Request"], + payload=self._auth.get_ws_authenticate_payload(), + ) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(header), is_auth_required=True) + + await ws.send(subscribe_request) + + ret_value = await ws.receive() + is_authenticated = False + if ret_value.data.get('o'): + is_authenticated = utils.ws_data_to_dict(ret_value.data.get('o'))["Authenticated"] + + await ws.ping() # to update + + if is_authenticated: + return ws + else: + self.logger().info("Some issue happens when try to subscribe at Foxbit User Stream Data, check your credentials.") + raise + + except Exception as ex: + self.logger().error( + f"Unexpected error occurred subscribing to account events stream...{ex}", + exc_info=True + ) + raise + + async def _subscribe_channels(self, + websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + All received messages from exchange are listened on FoxbitAPIOrderBookDataSource.listen_for_subscriptions() + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + # Subscribe Account, Orders and Trade Events + header = utils.get_ws_message_frame( + endpoint=CONSTANTS.WS_SUBSCRIBE_ACCOUNT, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Subscribe"], + payload={"OMSId": 1, "AccountId": self._connector.user_id}, + ) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(header)) + await websocket_assistant.send(subscribe_request) + + ws_response = await websocket_assistant.receive() + data = ws_response.data + + if data.get("n") == CONSTANTS.WS_SUBSCRIBE_ACCOUNT: + is_subscrebed = utils.ws_data_to_dict(data.get('o'))["Subscribed"] + + if is_subscrebed: + self._user_stream_data_source_initialized = is_subscrebed + self.logger().info("Subscribed to a private account events, like Position, Orders and Trades events...") + else: + self.logger().info("Some issue happens when try to subscribe at Foxbit User Stream Data, check your credentials.") + raise + + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error( + f"Unexpected error occurred subscribing to account events stream...{ex}", + exc_info=True + ) + raise + + async def _on_user_stream_interruption(self, + websocket_assistant: Optional[WSAssistant]): + await super()._on_user_stream_interruption(websocket_assistant=websocket_assistant) + await self._sleep(5) diff --git a/hummingbot/connector/exchange/foxbit/foxbit_auth.py b/hummingbot/connector/exchange/foxbit/foxbit_auth.py new file mode 100644 index 0000000..b543274 --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_auth.py @@ -0,0 +1,108 @@ +import hashlib +import hmac +from datetime import datetime, timezone +from typing import Dict + +from hummingbot.connector.exchange.foxbit import foxbit_web_utils as web_utils +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class FoxbitAuth(AuthBase): + + def __init__(self, api_key: str, secret_key: str, user_id: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.user_id = user_id + self.time_provider = time_provider + + async def rest_authenticate(self, + request: RESTRequest, + ) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + timestamp = str(int(datetime.now(timezone.utc).timestamp() * 1e3)) + + endpoint_url = web_utils.rest_endpoint_url(request.url) + + params = request.params if request.params is not None else "" + if request.method == RESTMethod.GET and request.params is not None: + params = '' + i = 0 + for p in request.params: + k = p + v = request.params[p] + if i == 0: + params = params + f"{k}={v}" + else: + params = params + f"&{k}={v}" + i += 1 + + data = request.data if request.data is not None else "" + + to_payload = params if len(params) > 0 else data + + payload = '{}{}{}{}'.format(timestamp, + request.method, + endpoint_url, + to_payload + ) + + signature = hmac.new(self.secret_key.encode("utf8"), + payload.encode("utf8"), + hashlib.sha256).digest().hex() + + foxbit_header = { + "X-FB-ACCESS-KEY": self.api_key, + "X-FB-ACCESS-SIGNATURE": signature, + "X-FB-ACCESS-TIMESTAMP": timestamp, + } + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(foxbit_header) + request.headers = headers + + return request + + async def ws_authenticate(self, + request: WSRequest, + ) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. + It should be used with empty requests to send an initial login payload. + :param request: the request to be configured for authenticated interaction + """ + + request.payload = self.get_ws_authenticate_payload(request) + return request + + def get_ws_authenticate_payload(self, + request: WSRequest = None, + ) -> Dict[str, any]: + timestamp = int(datetime.now(timezone.utc).timestamp() * 1e3) + + msg = '{}{}{}'.format(timestamp, + self.user_id, + self.api_key) + + signature = hmac.new(self.secret_key.encode("utf8"), + msg.encode("utf8"), + hashlib.sha256).digest().hex() + + payload = { + "APIKey": self.api_key, + "Signature": signature, + "UserId": self.user_id, + "Nonce": timestamp + } + + if hasattr(request, 'payload'): + payload.update(request.payload) + + return payload diff --git a/hummingbot/connector/exchange/foxbit/foxbit_connector.pxd b/hummingbot/connector/exchange/foxbit/foxbit_connector.pxd new file mode 100644 index 0000000..a50ffca --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_connector.pxd @@ -0,0 +1,2 @@ +cdef class foxbit_exchange_connector(): + pass diff --git a/hummingbot/connector/exchange/foxbit/foxbit_connector.pyx b/hummingbot/connector/exchange/foxbit/foxbit_connector.pyx new file mode 100644 index 0000000..a50ffca --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_connector.pyx @@ -0,0 +1,2 @@ +cdef class foxbit_exchange_connector(): + pass diff --git a/hummingbot/connector/exchange/foxbit/foxbit_constants.py b/hummingbot/connector/exchange/foxbit/foxbit_constants.py new file mode 100644 index 0000000..f637de6 --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_constants.py @@ -0,0 +1,156 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +DEFAULT_DOMAIN = "com.br" + +HBOT_ORDER_ID_PREFIX = "55" +USER_AGENT = "HBOT" +MAX_ORDER_ID_LEN = 20 + +# Base URL +REST_URL = "api.foxbit.com.br" +WSS_URL = "api.foxbit.com.br" + +PUBLIC_API_VERSION = "v3" +PRIVATE_API_VERSION = "v3" + +# Public API endpoints or FoxbitClient function +TICKER_PRICE_CHANGE_PATH_URL = "SubscribeLevel1" +EXCHANGE_INFO_PATH_URL = "markets" +PING_PATH_URL = "system/time" +SNAPSHOT_PATH_URL = "markets/{}/orderbook" +SERVER_TIME_PATH_URL = "system/time" + +# Private API endpoints or FoxbitClient function +ACCOUNTS_PATH_URL = "accounts" +MY_TRADES_PATH_URL = "trades" +ORDER_PATH_URL = "orders" +CANCEL_ORDER_PATH_URL = "orders/cancel" +GET_ORDER_BY_CLIENT_ID = "orders/by-client-order-id/{}" +GET_ORDER_BY_ID = "orders/by-order-id/{}" + +WS_HEADER = { + "Content-Type": "application/json", + "User-Agent": USER_AGENT, +} + +WS_MESSAGE_FRAME_TYPE = { + "Request": 0, + "Reply": 1, + "Subscribe": 2, + "Event": 3, + "Unsubscribe": 4, +} + +WS_MESSAGE_FRAME = { + "m": 0, # WS_MESSAGE_FRAME_TYPE + "i": 0, # Sequence Number + "n": "", # Endpoint + "o": "", # Message Payload +} + +WS_CHANNELS = { + "USER_STREAM": [ + "balance:all", + "position:all", + "order:all", + ] +} + +WS_HEARTBEAT_TIME_INTERVAL = 20 + +# Binance params + +SIDE_BUY = 'BUY' +SIDE_SELL = 'SELL' + +TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled +TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel +TIME_IN_FORCE_FOK = 'FOK' # Fill or kill + +# Rate Limit Type +REQUEST_WEIGHT = "REQUEST_WEIGHT" +ORDERS = "ORDERS" +ORDERS_24HR = "ORDERS_24HR" + +# Rate Limit time intervals +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 100 + +# Order States +ORDER_STATE = { + "PENDING": OrderState.PENDING_CREATE, + "ACTIVE": OrderState.OPEN, + "NEW": OrderState.OPEN, + "FILLED": OrderState.FILLED, + "PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED, + "PENDING_CANCEL": OrderState.OPEN, + "CANCELED": OrderState.CANCELED, + "PARTIALLY_CANCELED": OrderState.PARTIALLY_FILLED, + "REJECTED": OrderState.FAILED, + "EXPIRED": OrderState.FAILED, + "Unknown": OrderState.PENDING_CREATE, + "Working": OrderState.OPEN, + "Rejected": OrderState.FAILED, + "Canceled": OrderState.CANCELED, + "Expired": OrderState.FAILED, + "FullyExecuted": OrderState.FILLED, +} + +# Websocket subscribe endpoint +WS_AUTHENTICATE_USER = "AuthenticateUser" +WS_SUBSCRIBE_ACCOUNT = "SubscribeAccountEvents" +WS_SUBSCRIBE_ORDER_BOOK = "SubscribeLevel2" +WS_SUBSCRIBE_TOB = "SubscribeLevel1" +WS_SUBSCRIBE_TRADES = "SubscribeTrades" + +# Websocket response event types from Foxbit +# Market data events +WS_ORDER_BOOK_RESPONSE = "Level2UpdateEvent" +# Private order events +WS_ACCOUNT_POSITION = "AccountPositionEvent" +WS_ORDER_STATE = "OrderStateEvent" +WS_ORDER_TRADE = "OrderTradeEvent" +WS_TRADE_RESPONSE = "TradeDataUpdateEvent" + +ORDER_BOOK_DEPTH = 10 + +RATE_LIMITS = [ + # Pools + RateLimit(limit_id=REQUEST_WEIGHT, limit=1200, time_interval=ONE_MINUTE), + RateLimit(limit_id=ORDERS, limit=100, time_interval=ONE_SECOND), + RateLimit(limit_id=ORDERS_24HR, limit=100000, time_interval=ONE_DAY), + # Weighted Limits + RateLimit(limit_id=TICKER_PRICE_CHANGE_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 40)]), + RateLimit(limit_id=EXCHANGE_INFO_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[(LinkedLimitWeightPair(REQUEST_WEIGHT, 10))]), + RateLimit(limit_id=SNAPSHOT_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 50)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=PING_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=GET_ORDER_BY_ID, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=CANCEL_ORDER_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=ORDER_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(REQUEST_WEIGHT, 1), + LinkedLimitWeightPair(ORDERS, 1), + LinkedLimitWeightPair(ORDERS_24HR, 1)]), +] + +# Error codes +ORDER_NOT_EXIST_ERROR_CODE = -2013 +ORDER_NOT_EXIST_MESSAGE = "Order does not exist" + +UNKNOWN_ORDER_ERROR_CODE = -2011 +UNKNOWN_ORDER_MESSAGE = "Unknown order sent" diff --git a/hummingbot/connector/exchange/foxbit/foxbit_exchange.py b/hummingbot/connector/exchange/foxbit/foxbit_exchange.py new file mode 100644 index 0000000..4b9236c --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_exchange.py @@ -0,0 +1,926 @@ +import asyncio +import json +from datetime import datetime, timedelta, timezone +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils, + foxbit_web_utils as web_utils, +) +from hummingbot.connector.exchange.foxbit.foxbit_api_order_book_data_source import FoxbitAPIOrderBookDataSource +from hummingbot.connector.exchange.foxbit.foxbit_api_user_stream_data_source import FoxbitAPIUserStreamDataSource +from hummingbot.connector.exchange.foxbit.foxbit_auth import FoxbitAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import TradeFillOrderDetails, combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal(0) +s_float_NaN = float("nan") + + +class FoxbitExchange(ExchangePyBase): + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + foxbit_api_key: str, + foxbit_api_secret: str, + foxbit_user_id: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.api_key = foxbit_api_key + self.secret_key = foxbit_api_secret + self.user_id = foxbit_user_id + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._trading_pair_instrument_id_map: Optional[Mapping[str, str]] = None + self._mapping_initialization_instrument_id_lock = asyncio.Lock() + + super().__init__(client_config_map) + self._userstream_ds = self._create_user_stream_data_source() + + @property + def authenticator(self): + return FoxbitAuth( + api_key=self.api_key, + secret_key=self.secret_key, + user_id=self.user_id, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "foxbit" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.PING_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "symbols_mapping_initialized": self.trading_pair_symbol_map_ready(), + "instruments_mapping_initialized": self.trading_pair_instrument_id_map_ready(), + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": not self.is_trading_required or len(self._account_balances) > 0, + "trading_rule_initialized": len(self._trading_rules) > 0 if self.is_trading_required else True, + } + + @staticmethod + def convert_from_exchange_instrument_id(exchange_instrument_id: str) -> Optional[str]: + return exchange_instrument_id + + @staticmethod + def convert_to_exchange_instrument_id(hb_trading_pair: str) -> str: + return hb_trading_pair + + @staticmethod + def foxbit_order_type(order_type: OrderType) -> str: + if order_type == OrderType.LIMIT or order_type == OrderType.MARKET: + return order_type.name.upper() + else: + raise Exception("Order type not supported by Foxbit.") + + @staticmethod + def to_hb_order_type(foxbit_type: str) -> OrderType: + return OrderType[foxbit_type] + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + def trading_pair_instrument_id_map_ready(self): + """ + Checks if the mapping from exchange symbols to client trading pairs has been initialized + + :return: True if the mapping has been initialized, False otherwise + """ + return self._trading_pair_instrument_id_map is not None and len(self._trading_pair_instrument_id_map) > 0 + + async def trading_pair_instrument_id_map(self): + if not self.trading_pair_instrument_id_map_ready(): + async with self._mapping_initialization_instrument_id_lock: + if not self.trading_pair_instrument_id_map_ready(): + await self._initialize_trading_pair_instrument_id_map() + current_map = self._trading_pair_instrument_id_map or bidict() + return current_map.copy() + + async def exchange_instrument_id_associated_to_pair(self, trading_pair: str) -> str: + """ + Used to translate a trading pair from the client notation to the exchange notation + :param trading_pair: trading pair in client notation + :return: Instrument_Id in exchange notation + """ + symbol_map = await self.trading_pair_instrument_id_map() + return symbol_map.inverse[trading_pair] + + async def trading_pair_associated_to_exchange_instrument_id(self, instrument_id: str,) -> str: + """ + Used to translate a trading pair from the exchange notation to the client notation + :param instrument_id: Instrument_Id in exchange notation + :return: trading pair in client notation + """ + symbol_map = await self.trading_pair_instrument_id_map() + return symbol_map[instrument_id] + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return FoxbitAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return FoxbitAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + """ + Calculates the estimated fee an order would pay based on the connector configuration + :param base_currency: the order base currency + :param quote_currency: the order quote currency + :param order_type: the type of order (MARKET, LIMIT, LIMIT_MAKER) + :param order_side: if the order is for buying or selling + :param amount: the order amount + :param price: the order price + :return: the estimated fee for the order + """ + return DeductedFromReturnsTradeFee(percent=self.estimate_fee_pct(False)) + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = foxbit_utils.get_client_order_id(True) + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price)) + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = foxbit_utils.get_client_order_id(False) + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price)) + return order_id + + async def _create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None): + """ + Creates a an order in the exchange using the parameters to configure it + + :param trade_type: the side of the order (BUY of SELL) + :param order_id: the id that should be assigned to the order (the client id) + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + """ + exchange_order_id = "" + trading_rule = self._trading_rules[trading_pair] + + if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]: + order_type = OrderType.LIMIT + price = self.quantize_order_price(trading_pair, price) + quantized_amount = self.quantize_order_amount(trading_pair=trading_pair, amount=amount) + + self.start_tracking_order( + order_id=order_id, + exchange_order_id=None, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=quantized_amount + ) + if not price or price.is_nan() or price == s_decimal_0: + current_price: Decimal = self.get_price(trading_pair, False) + notional_size = current_price * quantized_amount + else: + notional_size = price * quantized_amount + + if order_type not in self.supported_order_types(): + self.logger().error(f"{order_type} is not in the list of supported order types") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + + if quantized_amount < trading_rule.min_order_size: + self.logger().warning(f"{trade_type.name.title()} order amount {amount} is lower than the minimum order " + f"size {trading_rule.min_order_size}. The order will not be created, increase the " + f"amount to be higher than the minimum order size.") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + + if notional_size < trading_rule.min_notional_size: + self.logger().warning(f"{trade_type.name.title()} order notional {notional_size} is lower than the " + f"minimum notional size {trading_rule.min_notional_size}. The order will not be " + f"created. Increase the amount or the price to be higher than the minimum notional.") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + + try: + exchange_order_id, update_timestamp = await self._place_order( + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + trade_type=trade_type, + order_type=order_type, + price=price) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + update_timestamp=update_timestamp, + new_state=OrderState.OPEN, + ) + self._order_tracker.process_order_update(order_update) + + return order_id, exchange_order_id + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Error submitting {trade_type.name.lower()} {order_type.name.upper()} order to {self.name_cap} for " + f"{amount.normalize()} {trading_pair} {price.normalize()}.", + exc_info=True, + app_warning_msg=f"Failed to submit {trade_type.name.lower()} order to {self.name_cap}. Check API key and network connection." + ) + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + ) -> Tuple[str, float]: + order_result = None + amount_str = '%.10f' % amount + price_str = '%.10f' % price + type_str = FoxbitExchange.foxbit_order_type(order_type) + side_str = CONSTANTS.SIDE_BUY if trade_type is TradeType.BUY else CONSTANTS.SIDE_SELL + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"market_symbol": symbol, + "side": side_str, + "quantity": amount_str, + "type": type_str, + "client_order_id": order_id, + } + if order_type == OrderType.LIMIT: + api_params["price"] = price_str + + self.logger().info(f'New order sent with these fields: {api_params}') + + order_result = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + data=api_params, + is_auth_required=True) + o_id = str(order_result.get("id")) + transact_time = int(datetime.now(timezone.utc).timestamp() * 1e3) + return (o_id, transact_time) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + params = { + "type": "CLIENT_ORDER_ID", + "client_order_id": order_id, + } + + try: + cancel_result = await self._api_put( + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, + data=params, + is_auth_required=True) + except OSError as e: + if "HTTP status is 404" in str(e): + return True + raise e + + if len(cancel_result.get("data")) > 0: + if cancel_result.get("data")[0].get('id') == tracked_order.exchange_order_id: + return True + + return False + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Example: + { + "data": [ + { + "symbol": "btcbrl", + "quantity_min": "0.00002", + "quantity_increment": "0.00001", + "price_min": "1.0", + "price_increment": "0.0001", + "base": { + "symbol": "btc", + "name": "Bitcoin", + "type": "CRYPTO" + }, + "quote": { + "symbol": "btc", + "name": "Bitcoin", + "type": "CRYPTO" + } + } + ] + } + """ + trading_pair_rules = exchange_info_dict.get("data", []) + retval = [] + for rule in filter(foxbit_utils.is_exchange_information_valid, trading_pair_rules): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule.get("symbol")) + + min_order_size = foxbit_utils.decimal_val_or_none(rule.get("quantity_min")) + tick_size = foxbit_utils.decimal_val_or_none(rule.get("price_increment")) + step_size = foxbit_utils.decimal_val_or_none(rule.get("quantity_increment")) + min_notional = foxbit_utils.decimal_val_or_none(rule.get("price_min")) + + retval.append( + TradingRule(trading_pair, + min_order_size=min_order_size, + min_price_increment=foxbit_utils.decimal_val_or_none(tick_size), + min_base_amount_increment=foxbit_utils.decimal_val_or_none(step_size), + min_notional_size=foxbit_utils.decimal_val_or_none(min_notional))) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule.get('symbol')}. Skipping.") + return retval + + async def _status_polling_loop_fetch_updates(self): + await self._update_order_fills_from_trades() + await super()._status_polling_loop_fetch_updates() + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + # Getting basic data + event_type = event_message.get("n") + order_data = foxbit_utils.ws_data_to_dict(event_message.get('o')) + + if event_type == CONSTANTS.WS_ACCOUNT_POSITION: + # It is an Account Position Event + self._process_balance_message(order_data) + continue + + field_name = "" + if CONSTANTS.WS_ORDER_STATE == event_type: + field_name = "Instrument" + elif CONSTANTS.WS_ORDER_TRADE == event_type: + field_name = "InstrumentId" + + # Check if this monitor has to tracking this event message + ixm_id = foxbit_utils.int_val_or_none(order_data.get(field_name), on_error_return_none=False) + if ixm_id == 0: + self.logger().error(f"Received a message type {event_type} with no instrument. raw message {event_message}.") + # When it occours, this instance receibed a message from other instance... Nothing to do... + continue + + rec_symbol = await self.trading_pair_associated_to_exchange_instrument_id(instrument_id=ixm_id) + if rec_symbol not in self.trading_pairs: + # When it occours, this instance receibed a message from other instance... Nothing to do... + continue + + if CONSTANTS.WS_ORDER_STATE or CONSTANTS.WS_ORDER_TRADE in event_type: + # Locating tracked order by ClientOrderId + client_order_id = order_data.get("ClientOrderId") is None and '' or str(order_data.get("ClientOrderId")) + tracked_order = self.in_flight_orders.get(client_order_id) + + if tracked_order: + # Found tracked order by client_order_id, check if it has an exchange_order_id + try: + await tracked_order.get_exchange_order_id() + except asyncio.TimeoutError: + self.logger().error(f"Failed to get exchange order id for order: {tracked_order.client_order_id}, raw message {event_message}.") + raise + + order_state = "" + if event_type == CONSTANTS.WS_ORDER_TRADE: + order_state = tracked_order.current_state + # It is a Trade Update Event (there is no OrderState) + await self._update_order_fills_from_event_or_create(client_order_id, tracked_order, order_data) + else: + # Translate exchange OrderState to HB Client + order_state = foxbit_utils.get_order_state(order_data.get("OrderState"), on_error_return_failed=False) + + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=foxbit_utils.int_val_or_none(order_data.get("LastUpdatedTime"), on_error_return_none=False) * 1e-3, + new_state=order_state, + client_order_id=client_order_id, + exchange_order_id=str(order_data.get("OrderId")), + ) + self._order_tracker.process_order_update(order_update=order_update) + + else: + # An unknown order was received, if it was in canceled order state, nothing to do, otherwise, log it as an unexpected error + if foxbit_utils.get_order_state(order_data.get('OrderState')) != OrderState.CANCELED: + self.logger().warning(f"Received unknown message type {event_type} with ClientOrderId: {client_order_id} raw message: {event_message}.") + + else: + # An unexpected event type was received + self.logger().warning(f"Received unknown message type {event_type} raw message: {event_message}.") + + except asyncio.CancelledError: + self.logger().error(f"An Asyncio.CancelledError occurs when process message: {event_message}.", exc_info=True) + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await asyncio.sleep(5.0) + + async def _update_order_fills_from_trades(self): + """ + This is intended to be a backup measure to get filled events with trade ID for orders, + NOTE: It is not required to copy this functionality in other connectors. + This is separated from _update_order_status which only updates the order status without producing filled + events, since Foxbit's get order endpoint does not return trade IDs. + The minimum poll interval for order status is 10 seconds. + """ + small_interval_last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + small_interval_current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + long_interval_last_tick = self._last_poll_timestamp / self.LONG_POLL_INTERVAL + long_interval_current_tick = self.current_timestamp / self.LONG_POLL_INTERVAL + + if (long_interval_current_tick > long_interval_last_tick + or (self.in_flight_orders and small_interval_current_tick > small_interval_last_tick)): + order_by_exchange_id_map = {} + for order in self._order_tracker.all_orders.values(): + order_by_exchange_id_map[order.exchange_order_id] = order + + tasks = [] + trading_pairs = self.trading_pairs + for trading_pair in trading_pairs: + params = { + "market_symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + if self._last_poll_timestamp > 0: + params["start_time"] = (datetime.utcnow() - timedelta(minutes=self.SHORT_POLL_INTERVAL)).isoformat()[:23] + "Z" + tasks.append(self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params=params, + is_auth_required=True)) + + self.logger().debug(f"Polling for order fills of {len(tasks)} trading pairs.") + results = await safe_gather(*tasks, return_exceptions=True) + + for trades, trading_pair in zip(results, trading_pairs): + + if isinstance(trades, Exception): + self.logger().network( + f"Error fetching trades update for the order {trading_pair}: {trades}.", + app_warning_msg=f"Failed to fetch trade update for {trading_pair}." + ) + continue + + for trade in trades.get('data'): + exchange_order_id = str(trade.get("order_id")) + if exchange_order_id in order_by_exchange_id_map: + # This is a fill for a tracked order + tracked_order = order_by_exchange_id_map[exchange_order_id] + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + flat_fees=[TokenAmount(amount=foxbit_utils.decimal_val_or_none(trade.get("fee")), token=trade.get("fee_currency_symbol").upper())] + ) + + trade_id = str(foxbit_utils.int_val_or_none(trade.get("id"), on_error_return_none=True)) + if trade_id is None: + trade_id = "0" + self.logger().warning(f'W001: Received trade message with no trade_id :{trade}') + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=foxbit_utils.datetime_val_or_now(trade.get("created_at"), on_error_return_now=True).timestamp(), + fill_price=foxbit_utils.decimal_val_or_none(trade.get("price")), + fill_base_amount=foxbit_utils.decimal_val_or_none(trade.get("quantity")), + fill_quote_amount=foxbit_utils.decimal_val_or_none(trade.get("quantity")), + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update) + elif self.is_confirmed_new_order_filled_event(str(trade.get("id")), exchange_order_id, trading_pair): + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=TradeType.BUY if trade.get("side") == "BUY" else TradeType.SELL, + flat_fees=[TokenAmount(amount=foxbit_utils.decimal_val_or_none(trade.get("fee")), token=trade.get("fee_currency_symbol").upper())] + ) + # This is a fill of an order registered in the DB but not tracked any more + self._current_trade_fills.add(TradeFillOrderDetails( + market=self.display_name, + exchange_trade_id=str(trade.get("id")), + symbol=trading_pair)) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + timestamp=foxbit_utils.datetime_val_or_now(trade.get('created_at'), on_error_return_now=True).timestamp(), + order_id=self._exchange_order_ids.get(str(trade.get("order_id")), None), + trading_pair=trading_pair, + trade_type=TradeType.BUY if trade.get("side") == "BUY" else TradeType.SELL, + order_type=OrderType.LIMIT, + price=foxbit_utils.decimal_val_or_none(trade.get("price")), + amount=foxbit_utils.decimal_val_or_none(trade.get("quantity")), + trade_fee=fee, + exchange_trade_id=str(foxbit_utils.int_val_or_none(trade.get("id"), on_error_return_none=False)), + ), + ) + self.logger().info(f"Recreating missing trade in TradeFill: {trade}") + + async def _update_order_fills_from_event_or_create(self, client_order_id, tracked_order, order_data): + """ + Used to update fills from user stream events or order creation. + """ + exec_amt_base = foxbit_utils.decimal_val_or_none(order_data.get("Quantity")) + if not exec_amt_base: + return + + fill_price = foxbit_utils.decimal_val_or_none(order_data.get("Price")) + exec_amt_quote = exec_amt_base * fill_price if exec_amt_base and fill_price else None + + base_asset, quote_asset = foxbit_utils.get_base_quote_from_trading_pair(tracked_order.trading_pair) + fee_paid = foxbit_utils.decimal_val_or_none(order_data.get("Fee")) + if fee_paid: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + flat_fees=[TokenAmount(amount=fee_paid, token=quote_asset)] + ) + else: + fee = self.get_fee(base_currency=base_asset, + quote_currency=quote_asset, + order_type=tracked_order.order_type, + order_side=tracked_order.trade_type, + amount=tracked_order.amount, + price=tracked_order.price, + is_maker=True) + + trade_id = str(foxbit_utils.int_val_or_none(order_data.get("TradeId"), on_error_return_none=True)) + if trade_id is None: + trade_id = "0" + self.logger().warning(f'W002: Received trade message with no trade_id :{order_data}') + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=client_order_id, + exchange_order_id=str(order_data.get("OrderId")), + trading_pair=tracked_order.trading_pair, + fill_timestamp=foxbit_utils.int_val_or_none(order_data.get("TradeTime"), on_error_return_none=False) * 1e-3, + fill_price=fill_price, + fill_base_amount=exec_amt_base, + fill_quote_amount=exec_amt_quote, + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update=trade_update) + + async def _update_order_status(self): + # This is intended to be a backup measure to close straggler orders, in case Foxbit's user stream events + # are not working. + # The minimum poll interval for order status is 10 seconds. + last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + + tracked_orders: List[InFlightOrder] = list(self.in_flight_orders.values()) + if current_tick > last_tick and len(tracked_orders) > 0: + + tasks = [self._api_get(path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ID.format(o.client_order_id), + is_auth_required=True, + limit_id=CONSTANTS.GET_ORDER_BY_ID) for o in tracked_orders] + + self.logger().debug(f"Polling for order status updates of {len(tasks)} orders.") + results = await safe_gather(*tasks, return_exceptions=True) + for order_update, tracked_order in zip(results, tracked_orders): + client_order_id = tracked_order.client_order_id + + # If the order has already been canceled or has failed do nothing + if client_order_id not in self.in_flight_orders: + continue + + if isinstance(order_update, Exception): + self.logger().network( + f"Error fetching status update for the order {client_order_id}: {order_update}.", + app_warning_msg=f"Failed to fetch status update for the order {client_order_id}." + ) + # Wait until the order not found error have repeated a few times before actually treating + # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601 + await self._order_tracker.process_order_not_found(client_order_id) + + else: + # Update order execution status + new_state = CONSTANTS.ORDER_STATE[order_update.get("state")] + + update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=(datetime.now(timezone.utc).timestamp() * 1e3), + new_state=new_state, + client_order_id=client_order_id, + exchange_order_id=str(order_update.get("id")), + ) + self._order_tracker.process_order_update(update) + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get( + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + is_auth_required=True) + + balances = account_info.get("data") + + for balance_entry in balances: + asset_name = balance_entry.get("currency_symbol").upper() + free_balance = foxbit_utils.decimal_val_or_none(balance_entry.get("balance_available")) + total_balance = foxbit_utils.decimal_val_or_none(balance_entry.get("balance")) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = int(order.exchange_order_id) + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "market_symbol": trading_pair, + "order_id": exchange_order_id + }, + is_auth_required=True + ) + + for trade in all_fills_response: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + flat_fees=[TokenAmount(amount=foxbit_utils.decimal_val_or_none(trade.get("fee")), token=trade.get("fee_currency_symbol").upper())] + ) + + trade_id = str(foxbit_utils.int_val_or_none(trade.get("id"), on_error_return_none=True)) + if trade_id is None: + trade_id = "0" + self.logger().warning(f'W003: Received trade message with no trade_id :{trade}') + + exchange_order_id = str(trade.get("id")) + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=foxbit_utils.decimal_val_or_none(trade.get("quantity")), + fill_quote_amount=foxbit_utils.decimal_val_or_none(trade.get("quantity")), + fill_price=foxbit_utils.decimal_val_or_none(trade.get("price")), + fill_timestamp=foxbit_utils.datetime_val_or_now(trade.get("created_at"), on_error_return_now=True).timestamp(), + ) + trade_updates.append(trade_update) + + return trade_updates + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_EXIST_ERROR_CODE) in str( + status_update_exception + ) and CONSTANTS.ORDER_NOT_EXIST_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.UNKNOWN_ORDER_ERROR_CODE) in str( + cancelation_exception + ) and CONSTANTS.UNKNOWN_ORDER_MESSAGE in str(cancelation_exception) + + def _process_balance_message(self, account_info: Dict[str, Any]): + asset_name = account_info.get("ProductSymbol") + hold_balance = foxbit_utils.decimal_val_or_none(account_info.get("Hold"), False) + total_balance = foxbit_utils.decimal_val_or_none(account_info.get("Amount"), False) + free_balance = total_balance - hold_balance + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._api_get( + path_url=CONSTANTS.GET_ORDER_BY_ID.format(tracked_order.exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.GET_ORDER_BY_ID + ) + + new_state = foxbit_utils.get_order_state(CONSTANTS.ORDER_STATE[updated_order_data.get("state")]) + + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=(datetime.now(timezone.utc).timestamp() * 1e3), + new_state=new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(updated_order_data.get("id")), + ) + + return order_update + + async def _get_last_traded_price(self, trading_pair: str) -> float: + + ixm_id = await self.exchange_instrument_id_associated_to_pair(trading_pair=trading_pair) + + ws: WSAssistant = await self._create_web_assistants_factory().get_ws_assistant() + await ws.connect(ws_url=web_utils.websocket_url(), ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + + auth_header = foxbit_utils.get_ws_message_frame(endpoint=CONSTANTS.WS_SUBSCRIBE_TOB, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Request"], + payload={"OMSId": 1, "InstrumentId": ixm_id}, + ) + + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(auth_header)) + + await ws.send(subscribe_request) + retValue: WSResponse = await ws.receive() + if isinstance(type(retValue), type(WSResponse)): + dec = json.JSONDecoder() + data = dec.decode(retValue.data['o']) + + if not (len(data) and "LastTradedPx" in data): + raise IOError(f"Error fetching last traded prices for {trading_pair}. Response: {data}.") + + return float(data.get("LastTradedPx")) + + return 0.0 + + async def _initialize_trading_pair_instrument_id_map(self): + try: + ws: WSAssistant = await self._create_web_assistants_factory().get_ws_assistant() + await ws.connect(ws_url=web_utils.websocket_url(), ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + + auth_header = foxbit_utils.get_ws_message_frame(endpoint="GetInstruments", + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Request"], + payload={"OMSId": 1},) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(auth_header)) + + await ws.send(subscribe_request) + retValue: WSResponse = await ws.receive() + if isinstance(type(retValue), type(WSResponse)): + dec = json.JSONDecoder() + exchange_info = dec.decode(retValue.data['o']) + + self._initialize_trading_pair_instrument_id_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _set_trading_pair_instrument_id_map(self, trading_pair_and_instrument_id_map: Optional[Mapping[str, str]]): + """ + Method added to allow the pure Python subclasses to set the value of the map + """ + self._trading_pair_instrument_id_map = trading_pair_and_instrument_id_map + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(foxbit_utils.is_exchange_information_valid, exchange_info["data"]): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data['base']['symbol'].upper(), + quote=symbol_data['quote']['symbol'].upper()) + self._set_trading_pair_symbol_map(mapping) + + def _initialize_trading_pair_instrument_id_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(foxbit_utils.is_exchange_information_valid, exchange_info): + mapping[symbol_data["InstrumentId"]] = combine_to_hb_trading_pair(symbol_data['Product1Symbol'].upper(), + symbol_data['Product2Symbol'].upper()) + self._set_trading_pair_instrument_id_map(mapping) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + error_description = str(request_exception) + is_time_synchronizer_related = ("-1021" in error_description + and "Timestamp for this request" in error_description) + return is_time_synchronizer_related diff --git a/hummingbot/connector/exchange/foxbit/foxbit_order_book.py b/hummingbot/connector/exchange/foxbit/foxbit_order_book.py new file mode 100644 index 0000000..dad1213 --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_order_book.py @@ -0,0 +1,178 @@ +from enum import Enum +from typing import Dict, Optional + +from hummingbot.connector.exchange.foxbit import foxbit_constants as CONSTANTS +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class FoxbitTradeFields(Enum): + ID = 0 + INSTRUMENTID = 1 + QUANTITY = 2 + PRICE = 3 + ORDERMAKERID = 4 + ORDERTAKERID = 5 + CREATEDAT = 6 + TREND = 7 + SIDE = 8 + FIXED_BOOL = 9 + FIXED_INT = 10 + + +class FoxbitOrderBookFields(Enum): + MDUPDATEID = 0 + ACCOUNTS = 1 + ACTIONDATETIME = 2 + ACTIONTYPE = 3 + LASTTRADEPRICE = 4 + ORDERS = 5 + PRICE = 6 + PRODUCTPAIRCODE = 7 + QUANTITY = 8 + SIDE = 9 + + +class FoxbitOrderBookAction(Enum): + NEW = 0 + UPDATE = 1 + DELETION = 2 + + +class FoxbitOrderBookSide(Enum): + BID = 0 + ASK = 1 + + +class FoxbitOrderBookItem(Enum): + PRICE = 0 + QUANTITY = 1 + + +class FoxbitOrderBook(OrderBook): + _bids = {} + _asks = {} + + @classmethod + def trade_message_from_exchange(cls, + msg: Dict[str, any], + metadata: Optional[Dict] = None, + ): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + ts = int(msg[FoxbitTradeFields.CREATEDAT.value]) + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": metadata["trading_pair"], + "trade_type": float(TradeType.SELL.value) if msg[FoxbitTradeFields.SIDE.value] == 1 else float(TradeType.BUY.value), + "trade_id": msg[FoxbitTradeFields.ID.value], + "update_id": ts, + "price": '%.10f' % float(msg[FoxbitTradeFields.PRICE.value]), + "amount": '%.10f' % float(msg[FoxbitTradeFields.QUANTITY.value]) + }, timestamp=ts * 1e-3) + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None, + ) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + + sample of msg {'sequence_id': 5972127, 'asks': [['140999.9798', '0.00007093'], ['140999.9899', '0.10646516'], ['140999.99', '0.01166287'], ['141000.0', '0.00024751'], ['141049.9999', '0.3688'], ['141050.0', '0.00184094'], ['141099.0', '0.00007087'], ['141252.9994', '0.02374105'], ['141253.0', '0.5786'], ['141275.0', '0.00707839'], ['141299.0', '0.00007077'], ['141317.9492', '0.814357'], ['141323.9741', '0.0039086'], ['141339.358', '0.64833964']], 'bids': [[['140791.4571', '0.0000569'], ['140791.4471', '0.00000028'], ['140791.4371', '0.0000289'], ['140791.4271', '0.00018672'], ['140512.4635', '0.06396371'], ['140512.4632', '0.3688'], ['140506.0', '0.5786'], ['140499.5014', '0.1'], ['140377.2678', '0.00976774'], ['140300.0', '0.005866'], ['140054.3859', '0.14746'], ['140054.1159', '3.45282018'], ['140032.8321', '1.2267452'], ['140025.553', '1.12483605']]} + """ + cls.logger().info(f'Refreshing order book to {metadata["trading_pair"]}.') + + cls._bids = {} + cls._asks = {} + + for item in msg["bids"]: + cls.update_order_book('%.10f' % float(item[FoxbitOrderBookItem.QUANTITY.value]), + '%.10f' % float(item[FoxbitOrderBookItem.PRICE.value]), + FoxbitOrderBookSide.BID) + + for item in msg["asks"]: + cls.update_order_book('%.10f' % float(item[FoxbitOrderBookItem.QUANTITY.value]), + '%.10f' % float(item[FoxbitOrderBookItem.PRICE.value]), + FoxbitOrderBookSide.ASK) + + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": metadata["trading_pair"], + "update_id": int(msg["sequence_id"]), + "bids": [[price, quantity] for price, quantity in cls._bids.items()], + "asks": [[price, quantity] for price, quantity in cls._asks.items()] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None, + ) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + + sample of msg = [5971940, 0, 1683735920192, 2, 140999.9798, 0, 140688.6227, 1, 0, 0] + """ + trading_pair = metadata["trading_pair"] + order_book_id = int(msg[FoxbitOrderBookFields.MDUPDATEID.value]) + prc = '%.10f' % float(msg[FoxbitOrderBookFields.PRICE.value]) + qty = '%.10f' % float(msg[FoxbitOrderBookFields.QUANTITY.value]) + + if msg[FoxbitOrderBookFields.ACTIONTYPE.value] == FoxbitOrderBookAction.DELETION.value: + qty = '0' + + if msg[FoxbitOrderBookFields.SIDE.value] == FoxbitOrderBookSide.BID.value: + + return OrderBookMessage( + OrderBookMessageType.DIFF, { + "trading_pair": trading_pair, + "update_id": order_book_id, + "bids": [[prc, qty]], + "asks": [], + }, timestamp=int(msg[FoxbitOrderBookFields.ACTIONDATETIME.value])) + + if msg[FoxbitOrderBookFields.SIDE.value] == FoxbitOrderBookSide.ASK.value: + return OrderBookMessage( + OrderBookMessageType.DIFF, { + "trading_pair": trading_pair, + "update_id": order_book_id, + "bids": [], + "asks": [[prc, qty]], + }, timestamp=int(msg[FoxbitOrderBookFields.ACTIONDATETIME.value])) + + @classmethod + def update_order_book(cls, quantity: str, price: str, side: FoxbitOrderBookSide): + q = float(quantity) + p = float(price) + + if side == FoxbitOrderBookSide.BID: + cls._bids[p] = q + if len(cls._bids) > CONSTANTS.ORDER_BOOK_DEPTH: + min_bid = min(cls._bids.keys()) + del cls._bids[min_bid] + + cls._bids = dict(sorted(cls._bids.items(), reverse=True)) + return + + if side == FoxbitOrderBookSide.ASK: + cls._asks[p] = q + if len(cls._asks) > CONSTANTS.ORDER_BOOK_DEPTH: + max_ask = max(cls._asks.keys()) + del cls._asks[max_ask] + + cls._asks = dict(sorted(cls._asks.items())) + return diff --git a/hummingbot/connector/exchange/foxbit/foxbit_utils.py b/hummingbot/connector/exchange/foxbit/foxbit_utils.py new file mode 100644 index 0000000..1296b05 --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_utils.py @@ -0,0 +1,171 @@ +import json +from datetime import datetime +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.foxbit import foxbit_constants as CONSTANTS +from hummingbot.core.data_type.in_flight_order import OrderState +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC-BRL" +_seq_nr: int = 0 + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), + buy_percent_fee_deducted_from_returns=True +) + + +def get_client_order_id(is_buy: bool) -> str: + """ + Creates a client order id for a new order + :param is_buy: True if the order is a buy order, False if the order is a sell order + :return: an identifier for the new order to be used in the client + """ + newId = str(get_tracking_nonce())[4:] + side = "00" if is_buy else "01" + return f"{CONSTANTS.HBOT_ORDER_ID_PREFIX}{side}{newId}" + + +def get_ws_message_frame(endpoint: str, + msg_type: str = "0", + payload: str = "", + ) -> Dict[str, Any]: + retValue = CONSTANTS.WS_MESSAGE_FRAME.copy() + retValue["m"] = msg_type + retValue["i"] = _get_next_message_frame_sequence_number() + retValue["n"] = endpoint + retValue["o"] = json.dumps(payload) + return retValue + + +def _get_next_message_frame_sequence_number() -> int: + """ + Returns next sequence number to be used into message frame for WS requests + """ + global _seq_nr + _seq_nr += 1 + return _seq_nr + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair. Dictionary with status and permissions + :return: True if the trading pair is enabled, False otherwise + + Nowadays all available pairs are valid. + It is here for future implamentation. + """ + return True + + +def ws_data_to_dict(data: str) -> Dict[str, Any]: + return eval(data.replace(":null", ":None").replace(":false", ":False").replace(":true", ":True")) + + +def datetime_val_or_now(string_value: str, + string_format: str = '%Y-%m-%dT%H:%M:%S.%fZ', + on_error_return_now: bool = True, + ) -> datetime: + try: + return datetime.strptime(string_value, string_format) + except Exception: + if on_error_return_now: + return datetime.now() + else: + return None + + +def decimal_val_or_none(string_value: str, + on_error_return_none: bool = True, + ) -> Decimal: + try: + return Decimal(string_value) + except Exception: + if on_error_return_none: + return None + else: + return Decimal('0') + + +def int_val_or_none(string_value: str, + on_error_return_none: bool = True, + ) -> int: + try: + return int(string_value) + except Exception: + if on_error_return_none: + return None + else: + return int('0') + + +def get_order_state(state: str, + on_error_return_failed: bool = False, + ) -> OrderState: + try: + return CONSTANTS.ORDER_STATE[state] + except Exception: + if on_error_return_failed: + return OrderState.FAILED + else: + return None + + +def get_base_quote_from_trading_pair(trading_pair: str): + if len(trading_pair) == 0: + return "", "" + if trading_pair.find("-") == -1: + return "", "" + pair = trading_pair.split("-") + return pair[0].upper(), pair[1].upper() + + +class FoxbitConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="foxbit", client_data=None) + foxbit_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Foxbit API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + foxbit_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Foxbit API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + foxbit_user_id: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Foxbit User ID", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "foxbit" + + +KEYS = FoxbitConfigMap.construct() + +OTHER_DOMAINS = [] +OTHER_DOMAINS_PARAMETER = {} +OTHER_DOMAINS_EXAMPLE_PAIR = {} +OTHER_DOMAINS_DEFAULT_FEES = {} +OTHER_DOMAINS_KEYS = {} diff --git a/hummingbot/connector/exchange/foxbit/foxbit_web_utils.py b/hummingbot/connector/exchange/foxbit/foxbit_web_utils.py new file mode 100644 index 0000000..702bcbe --- /dev/null +++ b/hummingbot/connector/exchange/foxbit/foxbit_web_utils.py @@ -0,0 +1,104 @@ +from typing import Any, Callable, Dict, Optional + +import hummingbot.connector.exchange.foxbit.foxbit_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: The default value is "com.br". Not in use at this time. + :return: the full URL to the endpoint + """ + return f"https://{CONSTANTS.REST_URL}/rest/{CONSTANTS.PUBLIC_API_VERSION}/{path_url}" + + +def private_rest_url(path_url: str, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: The default value is "com.br". Not in use at this time. + :return: the full URL to the endpoint + """ + return f"https://{CONSTANTS.REST_URL}/rest/{CONSTANTS.PRIVATE_API_VERSION}/{path_url}" + + +def rest_endpoint_url(full_url: str, + ) -> str: + """ + Creates a REST endpoint + :param full_url: a full url + :return: the URL endpoint + """ + url_size = len(f"https://{CONSTANTS.REST_URL}") + return full_url[url_size:] + + +def websocket_url() -> str: + """ + Creates a full URL for provided WebSocket endpoint + :return: the full URL to the endpoint + """ + return f"wss://{CONSTANTS.WSS_URL}/" + + +def format_ws_header(header: Dict[str, Any]) -> Dict[str, Any]: + retValue = {} + retValue.update(CONSTANTS.WS_HEADER.copy()) + retValue.update(header) + return retValue + + +def build_api_factory(throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, + ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time(throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request(url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, + domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["timestamp"] + return server_time diff --git a/hummingbot/connector/exchange/gate_io/__init__.py b/hummingbot/connector/exchange/gate_io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py b/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py new file mode 100644 index 0000000..950bc18 --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_api_order_book_data_source.py @@ -0,0 +1,170 @@ +import asyncio +import json +from collections import defaultdict +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS, gate_io_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange + + +class GateIoAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'GateIoExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._trading_pairs: List[str] = trading_pairs + + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = self._time() + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + { + "trading_pair": trading_pair, + "update_id": snapshot_response["id"], + "bids": snapshot_response["bids"], + "asks": snapshot_response["asks"], + }, + timestamp=snapshot_timestamp) + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "currency_pair": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "with_id": json.dumps(True) + } + + rest_assistant = await self._api_factory.get_rest_assistant() + return await rest_assistant.execute_request( + url=web_utils.public_rest_url(endpoint=CONSTANTS.ORDER_BOOK_PATH_URL), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.ORDER_BOOK_PATH_URL, + ) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_data: Dict[str, Any] = raw_message["result"] + trade_timestamp: int = trade_data["create_time"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=trade_data["currency_pair"]) + message_content = { + "trading_pair": trading_pair, + "trade_type": (float(TradeType.SELL.value) + if trade_data["side"] == "sell" + else float(TradeType.BUY.value)), + "trade_id": trade_data["id"], + "update_id": trade_timestamp, + "price": trade_data["price"], + "amount": trade_data["amount"], + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=trade_timestamp) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_data: [str, Any] = raw_message["result"] + timestamp: float = (diff_data["t"]) * 1e-3 + update_id: int = diff_data["u"] + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=diff_data["s"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "first_update_id": diff_data["U"], + "bids": diff_data["b"], + "asks": diff_data["a"], + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + order_book_message_content, + timestamp) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param ws: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + trades_payload = { + "time": int(self._time()), + "channel": CONSTANTS.TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": [symbol] + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + order_book_payload = { + "time": int(self._time()), + "channel": CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME, + "event": "subscribe", + "payload": [symbol, "100ms"] + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error occurred subscribing to order book data streams.") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if event_message.get("error") is not None: + err_msg = event_message.get("error", {}).get("message", event_message.get("error")) + raise IOError(f"Error event received from the server ({err_msg})") + elif event_message.get("event") == "update": + if event_message.get("channel") == CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME: + channel = self._diff_messages_queue_key + elif event_message.get("channel") == CONSTANTS.TRADES_ENDPOINT_NAME: + channel = self._trade_messages_queue_key + + return channel + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=CONSTANTS.PING_TIMEOUT) + return ws diff --git a/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py b/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py new file mode 100644 index 0000000..fcd5161 --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_api_user_stream_data_source.py @@ -0,0 +1,99 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange + + +class GateIoAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: GateIoAuth, + trading_pairs: List[str], + connector: 'GateIoExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._api_factory = api_factory + self._auth: GateIoAuth = auth + self._trading_pairs: List[str] = trading_pairs + self._connector = connector + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=CONSTANTS.PING_TIMEOUT) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events and balance events. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + symbols = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + + orders_change_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "payload": symbols + } + subscribe_order_change_request: WSJSONRequest = WSJSONRequest( + payload=orders_change_payload, + is_auth_required=True) + + trades_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": symbols + } + subscribe_trades_request: WSJSONRequest = WSJSONRequest( + payload=trades_payload, + is_auth_required=True) + + balance_payload = { + "time": int(self._time()), + "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + "event": "subscribe", # "unsubscribe" for unsubscription + } + subscribe_balance_request: WSJSONRequest = WSJSONRequest( + payload=balance_payload, + is_auth_required=True) + + await websocket_assistant.send(subscribe_order_change_request) + await websocket_assistant.send(subscribe_trades_request) + await websocket_assistant.send(subscribe_balance_request) + + self.logger().info("Subscribed to private order changes and balance updates channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if event_message.get("error") is not None: + err_msg = event_message.get("error", {}).get("message", event_message.get("error")) + raise IOError({ + "label": "WSS_ERROR", + "message": f"Error received via websocket - {err_msg}." + }) + elif event_message.get("event") == "update" and event_message.get("channel") in [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + ]: + queue.put_nowait(event_message) diff --git a/hummingbot/connector/exchange/gate_io/gate_io_auth.py b/hummingbot/connector/exchange/gate_io/gate_io_auth.py new file mode 100644 index 0000000..938877b --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_auth.py @@ -0,0 +1,98 @@ +import hashlib +import hmac +import json +import time +from typing import Any, Dict +from urllib.parse import urlparse + +import six + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class GateIoAuth(AuthBase): + """ + Auth Gate.io API + https://www.gate.io/docs/apiv4/en/#authentication + """ + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self._get_auth_headers(request)) + request.headers = headers + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + request.payload["auth"] = self._get_auth_headers_ws(payload=request.payload) + return request + + def _get_auth_headers_ws(self, payload: Dict[str, Any] = None) -> Dict[str, Any]: + """ + Generates authn for Gate.io websockets + + :return: a dictionary with headers + """ + sig = self._sign_payload_ws(payload['channel'], payload['event'], payload['time']) + headers = { + "method": "api_key", + "KEY": f"{self.api_key}", + "SIGN": f"{sig}", + } + return headers + + def _get_auth_headers(self, request: RESTRequest) -> Dict[str, Any]: + """ + Generates authentication headers for Gate.io REST API + + :return: a dictionary with headers + """ + sign, ts = self._sign_payload(request) + headers = { + "KEY": f"{self.api_key}", + "SIGN": f"{sign}", + "Timestamp": f"{ts}", + "Content-Type": "application/json", + "X-Gate-Channel-Id": CONSTANTS.HBOT_BROKER_ID, + } + return headers + + def _sign_payload_ws(self, channel, event, time) -> str: + return self._sign(f"channel={channel}&event={event}&time={time}") + + def _sign_payload(self, r: RESTRequest) -> (str, int): + query_string = "" + body = r.data + + ts = time.time() + m = hashlib.sha512() + path = urlparse(r.url).path + + if body is not None: + if not isinstance(r.data, six.string_types): + body = json.dumps(r.data) + m.update(body.encode('utf-8')) + body_hash = m.hexdigest() + + if r.params: + qs = [] + for k, v in r.params.items(): + qs.append(f"{k}={v}") + query_string = "&".join(qs) + + s = f'{r.method}\n{path}\n{query_string}\n{body_hash}\n{ts}' + return self._sign(s), ts + + def _sign(self, payload) -> str: + return hmac.new( + self.secret_key.encode('utf-8'), + payload.encode('utf-8'), + hashlib.sha512).hexdigest() diff --git a/hummingbot/connector/exchange/gate_io/gate_io_constants.py b/hummingbot/connector/exchange/gate_io/gate_io_constants.py new file mode 100644 index 0000000..10d0479 --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_constants.py @@ -0,0 +1,67 @@ +# A single source of truth for constant variables related to the exchange +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +EXCHANGE_NAME = "gate_io" +DEFAULT_DOMAIN = "" +HBOT_BROKER_ID = "hummingbot" +HBOT_ORDER_ID = "t-HBOT" +MAX_ID_LEN = 30 + +REST_URL = "https://api.gateio.ws/api/v4" +REST_URL_AUTH = "/api/v4" +WS_URL = "wss://api.gateio.ws/ws/v4/" +NETWORK_CHECK_PATH_URL = "spot/currencies/BTC" +SYMBOL_PATH_URL = "spot/currency_pairs" +ORDER_CREATE_PATH_URL = "spot/orders" +ORDER_DELETE_PATH_URL = "spot/orders/{order_id}" +USER_BALANCES_PATH_URL = "spot/accounts" +ORDER_STATUS_PATH_URL = "spot/orders/{order_id}" +USER_ORDERS_PATH_URL = "spot/open_orders" +TICKER_PATH_URL = "spot/tickers" +ORDER_BOOK_PATH_URL = "spot/order_book" +MY_TRADES_PATH_URL = "spot/my_trades" + +TRADES_ENDPOINT_NAME = "spot.trades" +ORDER_SNAPSHOT_ENDPOINT_NAME = "spot.order_book" +ORDERS_UPDATE_ENDPOINT_NAME = "spot.order_book_update" +USER_TRADES_ENDPOINT_NAME = "spot.usertrades" +USER_ORDERS_ENDPOINT_NAME = "spot.orders" +USER_BALANCE_ENDPOINT_NAME = "spot.balances" +PONG_CHANNEL_NAME = "spot.pong" + +# Timeouts +MESSAGE_TIMEOUT = 30.0 +PING_TIMEOUT = 10.0 +API_CALL_TIMEOUT = 10.0 +API_MAX_RETRIES = 4 + +# Intervals +# Only used when nothing is received from WS +SHORT_POLL_INTERVAL = 5.0 +# 45 seconds should be fine since we get trades, orders and balances via WS +LONG_POLL_INTERVAL = 45.0 +# One minute should be fine since we get trades, orders and balances via WS +UPDATE_ORDER_STATUS_INTERVAL = 60.0 +# 10 minute interval to update trading rules, these would likely never change whilst running. +INTERVAL_TRADING_RULES = 600 + +PUBLIC_URL_POINTS_LIMIT_ID = "PublicPoints" +PRIVATE_URL_POINTS_LIMIT_ID = "PrivatePoints" # includes place-orders +CANCEL_ORDERS_LIMITS_ID = "CancelOrders" +ORDER_DELETE_LIMIT_ID = "OrderDelete" +ORDER_STATUS_LIMIT_ID = "OrderStatus" +RATE_LIMITS = [ + RateLimit(limit_id=PUBLIC_URL_POINTS_LIMIT_ID, limit=900, time_interval=1), + RateLimit(limit_id=PRIVATE_URL_POINTS_LIMIT_ID, limit=900, time_interval=1), + RateLimit(limit_id=CANCEL_ORDERS_LIMITS_ID, limit=5_000, time_interval=1), + RateLimit(limit_id=NETWORK_CHECK_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=SYMBOL_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_CREATE_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_DELETE_LIMIT_ID, limit=5_000, time_interval=1, linked_limits=[LinkedLimitWeightPair(CANCEL_ORDERS_LIMITS_ID)]), + RateLimit(limit_id=USER_BALANCES_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_STATUS_LIMIT_ID, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=USER_ORDERS_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=TICKER_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=ORDER_BOOK_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=900, time_interval=1, linked_limits=[LinkedLimitWeightPair(PRIVATE_URL_POINTS_LIMIT_ID)]), +] diff --git a/hummingbot/connector/exchange/gate_io/gate_io_exchange.py b/hummingbot/connector/exchange/gate_io/gate_io_exchange.py new file mode 100644 index 0000000..688c19e --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_exchange.py @@ -0,0 +1,534 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS, gate_io_web_utils as web_utils +from hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source import GateIoAPIOrderBookDataSource +from hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source import GateIoAPIUserStreamDataSource +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GateIoExchange(ExchangePyBase): + DEFAULT_DOMAIN = "" + + # Using 120 seconds here as Gate.io websocket is quiet + TICK_INTERVAL_LIMIT = 120.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + gate_io_api_key: str, + gate_io_secret_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = DEFAULT_DOMAIN): + """ + :param gate_io_api_key: The API key to connect to private Gate.io APIs. + :param gate_io_secret_key: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + self._gate_io_api_key = gate_io_api_key + self._gate_io_secret_key = gate_io_secret_key + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + + super().__init__(client_config_map) + + @property + def authenticator(self): + return GateIoAuth( + api_key=self._gate_io_api_key, + secret_key=self._gate_io_secret_key, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "gate_io" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID + + @property + def trading_rules_request_path(self): + return CONSTANTS.SYMBOL_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.SYMBOL_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.NETWORK_CHECK_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # API documentation does not clarify the error message for timestamp related problems + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return GateIoAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return GateIoAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + async def _format_trading_rules(self, raw_trading_pair_info: Dict[str, Any]) -> List[TradingRule]: + """ + Converts json API response into a dictionary of trading rules. + + :param raw_trading_pair_info: The json API response + :return A dictionary of trading rules. + + Example raw_trading_pair_info: + https://www.gate.io/docs/apiv4/en/#list-all-currency-pairs-supported + """ + result = [] + for rule in raw_trading_pair_info: + try: + if not web_utils.is_exchange_information_valid(rule): + continue + + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule["id"]) + + min_amount_inc = Decimal(f"1e-{rule['amount_precision']}") + min_price_inc = Decimal(f"1e-{rule['precision']}") + min_amount = Decimal(str(rule.get("min_base_amount", min_amount_inc))) + min_notional = Decimal(str(rule.get("min_quote_amount", min_price_inc))) + result.append( + TradingRule( + trading_pair, + min_order_size=min_amount, + min_price_increment=min_price_inc, + min_base_amount_increment=min_amount_inc, + min_notional_size=min_notional, + min_order_value=min_notional, + ) + ) + except Exception: + self.logger().error( + f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) + return result + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + order_type_str = order_type.name.lower().split("_")[0] + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + # When type is market, it refers to different currency according to side + # side : buy means quote currency, BTC_USDT means USDT + # side : sell means base currency,BTC_USDT means BTC + data = { + "text": order_id, + "currency_pair": symbol, + "side": trade_type.name.lower(), + "type": order_type_str, + "amount": f"{amount:f}", + } + if order_type.is_limit_type(): + data.update({ + "price": f"{price:f}", + "time_in_force": "gtc" + }) + if order_type is OrderType.LIMIT_MAKER: + data.update({"time_in_force": "poc"}) + else: + data.update({ + "time_in_force": "ioc", + }) + if trade_type.name.lower() == 'buy': + if price.is_nan(): + price = self.get_price_for_volume( + trading_pair, + True, + amount + ).result_price + data.update({ + "amount": f"{price * amount:f}", + }) + + # RESTRequest does not support json, and if we pass a dict + # the underlying aiohttp will encode it to params + data = data + endpoint = CONSTANTS.ORDER_CREATE_PATH_URL + order_result = await self._api_post( + path_url=endpoint, + data=data, + is_auth_required=True, + limit_id=endpoint, + ) + if order_result.get("status") in {"cancelled"}: + raise IOError({"label": "ORDER_REJECTED", "message": "Order rejected."}) + exchange_order_id = str(order_result["id"]) + return exchange_order_id, self.current_timestamp + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation-specific method is called by _cancel + returns True if successful + """ + canceled = False + exchange_order_id = await tracked_order.get_exchange_order_id() + params = { + 'currency_pair': await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + } + resp = await self._api_delete( + path_url=CONSTANTS.ORDER_DELETE_PATH_URL.format(order_id=exchange_order_id), + params=params, + is_auth_required=True, + limit_id=CONSTANTS.ORDER_DELETE_LIMIT_ID, + ) + canceled = resp.get("status") == "cancelled" + return canceled + + async def _update_balances(self): + """ + Calls REST API to update total and available balances. + """ + account_info = "" + try: + account_info = await self._api_get( + path_url=CONSTANTS.USER_BALANCES_PATH_URL, + is_auth_required=True, + limit_id=CONSTANTS.USER_BALANCES_PATH_URL + ) + self._process_balance_message(account_info) + except Exception as e: + self.logger().network( + f"Unexpected error while fetching balance update - {str(e)}", exc_info=True, + app_warning_msg=(f"Could not fetch balance update from {self.name_cap}")) + raise e + return account_info + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + try: + exchange_order_id = await order.get_exchange_order_id() + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "currency_pair": trading_pair, + "order_id": exchange_order_id + }, + is_auth_required=True, + limit_id=CONSTANTS.MY_TRADES_PATH_URL) + + for trade_fill in all_fills_response: + trade_update = self._create_trade_update_with_order_fill_data( + order_fill=trade_fill, + order=order) + trade_updates.append(trade_update) + + except asyncio.TimeoutError: + raise IOError(f"Skipped order update with order fills for {order.client_order_id} " + "- waiting for exchange order id.") + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + try: + exchange_order_id = await tracked_order.get_exchange_order_id() + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=exchange_order_id), + params={ + "currency_pair": trading_pair + }, + is_auth_required=True, + limit_id=CONSTANTS.ORDER_STATUS_LIMIT_ID) + + order_update = self._create_order_update_with_order_status_data( + order_status=updated_order_data, + order=tracked_order) + except asyncio.TimeoutError: + raise IOError(f"Skipped order status update for {tracked_order.client_order_id}" + f" - waiting for exchange order id.") + + return order_update + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + is_maker = order_type is OrderType.LIMIT_MAKER + return AddedToCostTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + Listens to messages from _user_stream_tracker.user_stream queue. + Traders, Orders, and Balance updates from the WS. + """ + user_channels = [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + ] + async for event_message in self._iter_user_event_queue(): + channel: str = event_message.get("channel", None) + results: List[Dict[str, Any]] = event_message.get("result", None) + try: + if channel not in user_channels: + self.logger().error( + f"Unexpected message in user stream: {event_message}.", exc_info=True) + continue + + if channel == CONSTANTS.USER_TRADES_ENDPOINT_NAME: + for trade_msg in results: + self._process_trade_message(trade_msg) + elif channel == CONSTANTS.USER_ORDERS_ENDPOINT_NAME: + for order_msg in results: + self._process_order_message(order_msg) + elif channel == CONSTANTS.USER_BALANCE_ENDPOINT_NAME: + self._process_balance_message_ws(results) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + def _normalise_order_message_state(self, order_msg: Dict[str, Any], tracked_order): + state = None + # we do not handle: + # "failed" because it is handled by create order + # "put" as the exchange order id is returned in the create order response + # "open" for same reason + + # same field for both WS and REST + amount_left = Decimal(order_msg.get("left")) + filled_amount = Decimal(order_msg.get("filled_total")) + + # WS + if "event" in order_msg: + event_type = order_msg.get("event") + if event_type == "update": + state = OrderState.FILLED + if amount_left > 0: + state = OrderState.PARTIALLY_FILLED + if event_type == "finish": + finish_as = order_msg.get("finish_as") + if finish_as == "filled" or finish_as == "ioc": + state = OrderState.FILLED + elif finish_as == "cancelled": + state = OrderState.CANCELED + elif finish_as == "open" and filled_amount > 0: + state = OrderState.PARTIALLY_FILLED + else: + status = order_msg.get("status") + if status == "closed": + finish_as = order_msg.get("finish_as") + if finish_as == "filled" or finish_as == "ioc": + state = OrderState.FILLED + elif finish_as == "cancelled": + state = OrderState.CANCELED + elif finish_as == "open" and filled_amount > 0: + state = OrderState.PARTIALLY_FILLED + if status == "cancelled": + state = OrderState.CANCELED + return state + + def _create_order_update_with_order_status_data(self, order_status: Dict[str, Any], order: InFlightOrder): + client_order_id = str(order_status.get("text", "")) + state = self._normalise_order_message_state(order_status, order) or order.current_state + + order_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=int(order_status["update_time"]), + new_state=state, + client_order_id=client_order_id, + exchange_order_id=str(order_status["id"]), + ) + return order_update + + def _process_order_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancelation or failure event if needed. + + :param order_msg: The order response from either REST or web socket API (they are of the same format) + + Example Order: + https://www.gate.io/docs/apiv4/en/#list-orders + """ + client_order_id = str(order_msg.get("text", "")) + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if not tracked_order: + self.logger().debug(f"Ignoring order message with id {client_order_id}: not in in_flight_orders.") + return + + order_update = self._create_order_update_with_order_status_data(order_status=order_msg, order=tracked_order) + self._order_tracker.process_order_update(order_update=order_update) + + def _create_trade_update_with_order_fill_data( + self, + order_fill: Dict[str, Any], + order: InFlightOrder): + + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=order_fill["fee_currency"], + flat_fees=[TokenAmount( + amount=Decimal(order_fill["fee"]), + token=order_fill["fee_currency"] + )] + ) + trade_update = TradeUpdate( + trade_id=str(order_fill["id"]), + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(order_fill["amount"]), + fill_quote_amount=Decimal(order_fill["amount"]) * Decimal(order_fill["price"]), + fill_price=Decimal(order_fill["price"]), + fill_timestamp=order_fill["create_time"], + ) + return trade_update + + def _process_trade_message(self, trade: Dict[str, Any], client_order_id: Optional[str] = None): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + Example Trade: + https://www.gate.io/docs/apiv4/en/#retrieve-market-trades + """ + client_order_id = client_order_id or str(trade["text"]) + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + if tracked_order is None: + self.logger().debug(f"Ignoring trade message with id {client_order_id}: not in in_flight_orders.") + else: + trade_update = self._create_trade_update_with_order_fill_data( + order_fill=trade, + order=tracked_order) + self._order_tracker.process_trade_update(trade_update) + + def _process_balance_message(self, balance_update): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + for account in balance_update: + asset_name = account["currency"] + self._account_available_balances[asset_name] = Decimal(str(account["available"])) + self._account_balances[asset_name] = Decimal(str(account["locked"])) + Decimal(str(account["available"])) + remote_asset_names.add(asset_name) + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _process_balance_message_ws(self, balance_update): + for account in balance_update: + asset_name = account["currency"] + self._account_available_balances[asset_name] = Decimal(str(account["available"])) + self._account_balances[asset_name] = Decimal(str(account["total"])) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(web_utils.is_exchange_information_valid, exchange_info): + mapping[symbol_data["id"]] = combine_to_hb_trading_pair(base=symbol_data["base"], + quote=symbol_data["quote"]) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "currency_pair": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.TICKER_PATH_URL, + params=params + ) + + return float(resp_json[0]["last"]) diff --git a/hummingbot/connector/exchange/gate_io/gate_io_utils.py b/hummingbot/connector/exchange/gate_io/gate_io_utils.py new file mode 100644 index 0000000..9922e4f --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_utils.py @@ -0,0 +1,42 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC-USDT" +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.002"), + taker_percent_fee_decimal=Decimal("0.002"), +) + + +class GateIOConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="gate_io", client_data=None) + gate_io_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {CONSTANTS.EXCHANGE_NAME} API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + gate_io_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {CONSTANTS.EXCHANGE_NAME} secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "gate_io" + + +KEYS = GateIOConfigMap.construct() diff --git a/hummingbot/connector/exchange/gate_io/gate_io_web_utils.py b/hummingbot/connector/exchange/gate_io/gate_io_web_utils.py new file mode 100644 index 0000000..f703a8b --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/gate_io_web_utils.py @@ -0,0 +1,55 @@ +import time +from typing import Any, Dict, Optional + +import hummingbot.connector.exchange.gate_io.gate_io_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + + :param endpoint: a public REST endpoint + :param domain: unused + + :return: the full URL to the endpoint + """ + if CONSTANTS.REST_URL[-1] != "/" and endpoint[0] != "/": + endpoint = "/" + endpoint + return CONSTANTS.REST_URL + endpoint + + +def private_rest_url(endpoint: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(endpoint, domain) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + auth: Optional[AuthBase] = None) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + return time.time() + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("trade_status", None) == "tradable" diff --git a/hummingbot/connector/exchange/gate_io/placeholder.pxd b/hummingbot/connector/exchange/gate_io/placeholder.pxd new file mode 100644 index 0000000..527585a --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/placeholder.pxd @@ -0,0 +1,2 @@ +cdef class placeholder(): + pass diff --git a/hummingbot/connector/exchange/gate_io/placeholder.pyx b/hummingbot/connector/exchange/gate_io/placeholder.pyx new file mode 100644 index 0000000..527585a --- /dev/null +++ b/hummingbot/connector/exchange/gate_io/placeholder.pyx @@ -0,0 +1,2 @@ +cdef class placeholder(): + pass diff --git a/hummingbot/connector/exchange/hitbtc/__init__.py b/hummingbot/connector/exchange/hitbtc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pxd b/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pxd new file mode 100644 index 0000000..5babac5 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pxd @@ -0,0 +1,11 @@ +# distutils: language=c++ +cimport numpy as np + +cdef class HitbtcActiveOrderTracker: + cdef dict _active_bids + cdef dict _active_asks + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message) + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message) + # This method doesn't seem to be used anywhere at all + # cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message) diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pyx b/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pyx new file mode 100644 index 0000000..5e248bb --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_active_order_tracker.pyx @@ -0,0 +1,155 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp +import logging +import numpy as np +from decimal import Decimal +from typing import Dict +from hummingbot.logger import HummingbotLogger +from hummingbot.core.data_type.order_book_row import OrderBookRow + +_logger = None +s_empty_diff = np.ndarray(shape=(0, 4), dtype="float64") +HitbtcOrderBookTrackingDictionary = Dict[Decimal, Dict[str, Dict[str, any]]] + +cdef class HitbtcActiveOrderTracker: + def __init__(self, + active_asks: HitbtcOrderBookTrackingDictionary = None, + active_bids: HitbtcOrderBookTrackingDictionary = None): + super().__init__() + self._active_asks = active_asks or {} + self._active_bids = active_bids or {} + + @classmethod + def logger(cls) -> HummingbotLogger: + global _logger + if _logger is None: + _logger = logging.getLogger(__name__) + return _logger + + @property + def active_asks(self) -> HitbtcOrderBookTrackingDictionary: + return self._active_asks + + @property + def active_bids(self) -> HitbtcOrderBookTrackingDictionary: + return self._active_bids + + # TODO: research this more + def volume_for_ask_price(self, price) -> float: + return NotImplementedError + + # TODO: research this more + def volume_for_bid_price(self, price) -> float: + return NotImplementedError + + def get_rates_and_quantities(self, entry) -> tuple: + # price, quantity + return float(entry["price"]), float(entry["size"]) + + cdef tuple c_convert_diff_message_to_np_arrays(self, object message): + cdef: + dict content = message.content + list content_keys = list(content.keys()) + list bid_entries = [] + list ask_entries = [] + str order_id + str order_side + str price_raw + object price + dict order_dict + double timestamp = message.timestamp + double amount = 0 + + if "bid" in content_keys: + bid_entries = content["bid"] + if "ask" in content_keys: + ask_entries = content["ask"] + + bids = s_empty_diff + asks = s_empty_diff + + if len(bid_entries) > 0: + bids = np.array( + [[timestamp, + price, + amount, + message.update_id] + for price, amount in [self.get_rates_and_quantities(entry) for entry in bid_entries]], + dtype="float64", + ndmin=2 + ) + + if len(ask_entries) > 0: + asks = np.array( + [[timestamp, + price, + amount, + message.update_id] + for price, amount in [self.get_rates_and_quantities(entry) for entry in ask_entries]], + dtype="float64", + ndmin=2 + ) + + return bids, asks + + cdef tuple c_convert_snapshot_message_to_np_arrays(self, object message): + cdef: + float price + float amount + str order_id + dict order_dict + + # Refresh all order tracking. + self._active_bids.clear() + self._active_asks.clear() + timestamp = message.timestamp + content = message.content + + for snapshot_orders, active_orders in [(content["bid"], self._active_bids), (content["ask"], self._active_asks)]: + for entry in snapshot_orders: + price, amount = self.get_rates_and_quantities(entry) + active_orders[price] = amount + + # Return the sorted snapshot tables. + cdef: + np.ndarray[np.float64_t, ndim=2] bids = np.array( + [[message.timestamp, + float(price), + float(self._active_bids[price]), + message.update_id] + for price in sorted(self._active_bids.keys())], dtype='float64', ndmin=2) + np.ndarray[np.float64_t, ndim=2] asks = np.array( + [[message.timestamp, + float(price), + float(self._active_asks[price]), + message.update_id] + for price in sorted(self._active_asks.keys(), reverse=True)], dtype='float64', ndmin=2) + + if bids.shape[1] != 4: + bids = bids.reshape((0, 4)) + if asks.shape[1] != 4: + asks = asks.reshape((0, 4)) + + return bids, asks + + # This method doesn't seem to be used anywhere at all + # cdef np.ndarray[np.float64_t, ndim=1] c_convert_trade_message_to_np_array(self, object message): + # cdef: + # double trade_type_value = 1.0 if message.content["side"] == "buy" else 2.0 + # list content = message.content + # return np.array( + # [message.timestamp, trade_type_value, float(content["price"]), float(content["quantity"])], + # dtype="float64" + # ) + + def convert_diff_message_to_order_book_row(self, message): + np_bids, np_asks = self.c_convert_diff_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row + + def convert_snapshot_message_to_order_book_row(self, message): + np_bids, np_asks = self.c_convert_snapshot_message_to_np_arrays(message) + bids_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_bids] + asks_row = [OrderBookRow(price, qty, update_id) for ts, price, qty, update_id in np_asks] + return bids_row, asks_row diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py b/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py new file mode 100644 index 0000000..764fa52 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_api_order_book_data_source.py @@ -0,0 +1,256 @@ +import asyncio +import logging +import time +from decimal import Decimal +from typing import Any, Dict, List, Optional + +import aiohttp +import pandas as pd + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.logger import HummingbotLogger + +from .hitbtc_active_order_tracker import HitbtcActiveOrderTracker +from .hitbtc_constants import Constants +from .hitbtc_order_book import HitbtcOrderBook +from .hitbtc_utils import HitbtcAPIError, api_call_with_retries, str_date_to_ts, translate_asset +from .hitbtc_websocket import HitbtcWebsocket + + +class HitbtcAPIOrderBookDataSource(OrderBookTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + _trading_pair_symbol_map: Dict[str, str] = {} + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pairs: List[str] = None): + super().__init__(trading_pairs) + self._trading_pairs: List[str] = trading_pairs + self._snapshot_msg: Dict[str, any] = {} + + @classmethod + async def init_trading_pair_symbols(cls, shared_session: Optional[aiohttp.ClientSession] = None): + """Initialize _trading_pair_symbol_map class variable + """ + + symbols: List[Dict[str, Any]] = await api_call_with_retries( + "GET", + Constants.ENDPOINT["SYMBOL"], + shared_client=shared_session) + cls._trading_pair_symbol_map = { + symbol_data["id"]: (f"{translate_asset(symbol_data['baseCurrency'])}-" + f"{translate_asset(symbol_data['quoteCurrency'])}") + for symbol_data in symbols + } + + @classmethod + async def trading_pair_symbol_map(cls) -> Dict[str, str]: + if not cls._trading_pair_symbol_map: + await cls.init_trading_pair_symbols() + + return cls._trading_pair_symbol_map + + @classmethod + async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, Decimal]: + results = {} + if len(trading_pairs) > 1: + tickers: List[Dict[Any]] = await api_call_with_retries("GET", Constants.ENDPOINT["TICKER"]) + for trading_pair in trading_pairs: + ex_pair: str = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair) + if len(trading_pairs) > 1: + ticker: Dict[Any] = list([tic for tic in tickers if tic['symbol'] == ex_pair])[0] + else: + url_endpoint = Constants.ENDPOINT["TICKER_SINGLE"].format(trading_pair=ex_pair) + ticker: Dict[Any] = await api_call_with_retries("GET", url_endpoint) + results[trading_pair]: Decimal = Decimal(str(ticker["last"])) + return results + + @staticmethod + async def exchange_symbol_associated_to_pair(trading_pair: str) -> str: + symbol_map = await HitbtcAPIOrderBookDataSource.trading_pair_symbol_map() + symbols = [symbol for symbol, pair in symbol_map.items() if pair == trading_pair] + + if symbols: + symbol = symbols[0] + else: + raise ValueError(f"There is no symbol mapping for trading pair {trading_pair}") + + return symbol + + @staticmethod + async def trading_pair_associated_to_exchange_symbol(symbol: str) -> str: + symbol_map = await HitbtcAPIOrderBookDataSource.trading_pair_symbol_map() + return symbol_map[symbol] + + @staticmethod + async def fetch_trading_pairs() -> List[str]: + symbols_map = await HitbtcAPIOrderBookDataSource.trading_pair_symbol_map() + return list(symbols_map.values()) + + @staticmethod + async def get_order_book_data(trading_pair: str) -> Dict[str, any]: + """ + Get whole orderbook + """ + try: + ex_pair = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair) + orderbook_response: Dict[Any] = await api_call_with_retries("GET", Constants.ENDPOINT["ORDER_BOOK"], + params={"limit": 150, "symbols": ex_pair}) + return orderbook_response[ex_pair] + except HitbtcAPIError as e: + err = e.error_payload.get('error', e.error_payload) + raise IOError( + f"Error fetching OrderBook for {trading_pair} at {Constants.EXCHANGE_NAME}. " + f"HTTP status is {e.error_payload['status']}. Error is {err.get('message', str(err))}.") + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + snapshot: Dict[str, Any] = await self.get_order_book_data(trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = HitbtcOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair}) + order_book = self.order_book_create_function() + active_order_tracker: HitbtcActiveOrderTracker = HitbtcActiveOrderTracker() + bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg) + order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) + return order_book + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + Listen for trades using websocket trade channel + """ + while True: + try: + ws = HitbtcWebsocket() + await ws.connect() + + for pair in self._trading_pairs: + symbol = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(pair) + await ws.subscribe(Constants.WS_SUB["TRADES"], symbol) + + async for response in ws.on_message(): + method: str = response.get("method", None) + trades_data: str = response.get("params", None) + + if trades_data is None or method != Constants.WS_METHODS['TRADES_UPDATE']: + continue + + pair: str = await self.trading_pair_associated_to_exchange_symbol(response["params"]["symbol"]) + + for trade in trades_data["data"]: + trade: Dict[Any] = trade + trade_timestamp: int = str_date_to_ts(trade["timestamp"]) + trade_msg: OrderBookMessage = HitbtcOrderBook.trade_message_from_exchange( + trade, + trade_timestamp, + metadata={"trading_pair": pair}) + output.put_nowait(trade_msg) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await asyncio.sleep(5.0) + finally: + await ws.disconnect() + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + Listen for orderbook diffs using websocket book channel + """ + while True: + try: + ws = HitbtcWebsocket() + await ws.connect() + + order_book_methods = [ + Constants.WS_METHODS['ORDERS_SNAPSHOT'], + Constants.WS_METHODS['ORDERS_UPDATE'], + ] + + for pair in self._trading_pairs: + symbol = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(pair) + await ws.subscribe(Constants.WS_SUB["ORDERS"], symbol) + + async for response in ws.on_message(): + method: str = response.get("method", None) + order_book_data: str = response.get("params", None) + + if order_book_data is None or method not in order_book_methods: + continue + + timestamp: int = str_date_to_ts(order_book_data["timestamp"]) + pair: str = await self.trading_pair_associated_to_exchange_symbol(order_book_data["symbol"]) + + order_book_msg_cls = (HitbtcOrderBook.diff_message_from_exchange + if method == Constants.WS_METHODS['ORDERS_UPDATE'] else + HitbtcOrderBook.snapshot_message_from_exchange) + + orderbook_msg: OrderBookMessage = order_book_msg_cls( + order_book_data, + timestamp, + metadata={"trading_pair": pair}) + output.put_nowait(orderbook_msg) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error with WebSocket connection.", exc_info=True, + app_warning_msg="Unexpected error with WebSocket connection. Retrying in 30 seconds. " + "Check network connection.") + await asyncio.sleep(30.0) + finally: + await ws.disconnect() + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + Listen for orderbook snapshots by fetching orderbook + """ + while True: + try: + for trading_pair in self._trading_pairs: + try: + snapshot: Dict[str, any] = await self.get_order_book_data(trading_pair) + snapshot_timestamp: int = str_date_to_ts(snapshot["timestamp"]) + snapshot_msg: OrderBookMessage = HitbtcOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + # Be careful not to go above API rate limits. + await asyncio.sleep(5.0) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error with WebSocket connection.", exc_info=True, + app_warning_msg="Unexpected error with WebSocket connection. Retrying in 5 seconds. " + "Check network connection.") + await asyncio.sleep(5.0) + this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0) + next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) + delta: float = next_hour.timestamp() - time.time() + await asyncio.sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await asyncio.sleep(5.0) + + async def listen_for_subscriptions(self): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + # This connector does not use this base class method and needs a refactoring + pass diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_api_user_stream_data_source.py b/hummingbot/connector/exchange/hitbtc/hitbtc_api_user_stream_data_source.py new file mode 100755 index 0000000..0bde1d6 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_api_user_stream_data_source.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +import time +import asyncio +import logging +from typing import ( + Any, + AsyncIterable, + List, + Optional, +) +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.logger import HummingbotLogger +from .hitbtc_constants import Constants +from .hitbtc_auth import HitbtcAuth +from .hitbtc_utils import HitbtcAPIError +from .hitbtc_websocket import HitbtcWebsocket + + +class HitbtcAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, hitbtc_auth: HitbtcAuth, trading_pairs: Optional[List[str]] = []): + self._hitbtc_auth: HitbtcAuth = hitbtc_auth + self._ws: HitbtcWebsocket = None + self._trading_pairs = trading_pairs + self._current_listen_key = None + self._listen_for_user_stream_task = None + self._last_recv_time: float = 0 + super().__init__() + + @property + def last_recv_time(self) -> float: + return self._last_recv_time + + async def _ws_request_balances(self): + return await self._ws.request(Constants.WS_METHODS["USER_BALANCE"]) + + async def _listen_to_orders_trades_balances(self) -> AsyncIterable[Any]: + """ + Subscribe to active orders via web socket + """ + + try: + self._ws = HitbtcWebsocket(self._hitbtc_auth) + + await self._ws.connect() + + await self._ws.subscribe(Constants.WS_SUB["USER_ORDERS_TRADES"], None, {}) + + event_methods = [ + Constants.WS_METHODS["USER_ORDERS"], + Constants.WS_METHODS["USER_TRADES"], + ] + + async for msg in self._ws.on_message(): + self._last_recv_time = time.time() + + if msg.get("params", msg.get("result", None)) is None: + continue + elif msg.get("method", None) in event_methods: + await self._ws_request_balances() + yield msg + except Exception as e: + raise e + finally: + await self._ws.disconnect() + await asyncio.sleep(5) + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + Subscribe to user stream via web socket, and keep the connection open for incoming messages + + :param output: an async queue where the incoming messages are stored + """ + + while True: + try: + async for msg in self._listen_to_orders_trades_balances(): + output.put_nowait(msg) + except asyncio.CancelledError: + raise + except HitbtcAPIError as e: + self.logger().error(e.error_payload.get('error'), exc_info=True) + raise + except Exception: + self.logger().error( + f"Unexpected error with {Constants.EXCHANGE_NAME} WebSocket connection. " + "Retrying after 30 seconds...", exc_info=True) + await asyncio.sleep(30.0) diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py b/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py new file mode 100755 index 0000000..be37f2e --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_auth.py @@ -0,0 +1,72 @@ +import hmac +import hashlib +import time +from base64 import b64encode +from typing import Dict, Any + + +class HitbtcAuth(): + """ + Auth class required by HitBTC API + Learn more at https://exchange-docs.crypto.com/#digital-signature + """ + def __init__(self, api_key: str, secret_key: str): + self.api_key = api_key + self.secret_key = secret_key + + def generate_payload( + self, + method: str, + url: str, + params: Dict[str, Any] = None, + ): + """ + Generates authentication payload and returns it. + :return: A base64 encoded payload for the authentication header. + """ + # Nonce is standard EPOCH timestamp only accurate to 1s + nonce = str(int(time.time())) + body = "" + # Need to build the full URL with query string for HS256 sig + if params is not None and len(params) > 0: + query_string = "&".join([f"{k}={v}" for k, v in params.items()]) + if method == "GET": + url = f"{url}?{query_string}" + else: + body = query_string + # Concat payload + payload = f"{method}{nonce}{url}{body}" + # Create HS256 sig + sig = hmac.new(self.secret_key.encode(), payload.encode(), hashlib.sha256).hexdigest() + # Base64 encode it with public key and nonce + return b64encode(f"{self.api_key}:{nonce}:{sig}".encode()).decode().strip() + + def generate_auth_dict_ws(self, + nonce: int): + """ + Generates an authentication params for HitBTC websockets login + :return: a dictionary of auth params + """ + return { + "algo": "HS256", + "pKey": str(self.api_key), + "nonce": str(nonce), + "signature": hmac.new(self.secret_key.encode('utf-8'), + str(nonce).encode('utf-8'), + hashlib.sha256).hexdigest() + } + + def get_headers(self, + method, + url, + params) -> Dict[str, Any]: + """ + Generates authentication headers required by HitBTC + :return: a dictionary of auth headers + """ + payload = self.generate_payload(method, url, params) + headers = { + "Authorization": f"HS256 {payload}", + "Content-Type": "application/x-www-form-urlencoded", + } + return headers diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_constants.py b/hummingbot/connector/exchange/hitbtc/hitbtc_constants.py new file mode 100644 index 0000000..de4d894 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_constants.py @@ -0,0 +1,57 @@ +# A single source of truth for constant variables related to the exchange +class Constants: + EXCHANGE_NAME = "hitbtc" + REST_URL = "https://api.hitbtc.com/api/2" + REST_URL_AUTH = "/api/2" + WS_PRIVATE_URL = "wss://api.hitbtc.com/api/2/ws/trading" + WS_PUBLIC_URL = "wss://api.hitbtc.com/api/2/ws/public" + + HBOT_BROKER_ID = "refzzz48" + + ENDPOINT = { + # Public Endpoints + "TICKER": "public/ticker", + "TICKER_SINGLE": "public/ticker/{trading_pair}", + "SYMBOL": "public/symbol", + "ORDER_BOOK": "public/orderbook", + "ORDER_CREATE": "order", + "ORDER_DELETE": "order/{id}", + "ORDER_STATUS": "order/{id}", + "USER_ORDERS": "order", + "USER_BALANCES": "trading/balance", + } + + WS_SUB = { + "TRADES": "Trades", + "ORDERS": "Orderbook", + "USER_ORDERS_TRADES": "Reports", + + } + + WS_METHODS = { + "ORDERS_SNAPSHOT": "snapshotOrderbook", + "ORDERS_UPDATE": "updateOrderbook", + "TRADES_SNAPSHOT": "snapshotTrades", + "TRADES_UPDATE": "updateTrades", + "USER_BALANCE": "getTradingBalance", + "USER_ORDERS": "activeOrders", + "USER_TRADES": "report", + } + + # Timeouts + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + API_CALL_TIMEOUT = 10.0 + API_MAX_RETRIES = 4 + + # Intervals + # Only used when nothing is received from WS + SHORT_POLL_INTERVAL = 5.0 + # One minute should be fine since we get trades, orders and balances via WS + LONG_POLL_INTERVAL = 60.0 + UPDATE_ORDER_STATUS_INTERVAL = 60.0 + # 10 minute interval to update trading rules, these would likely never change whilst running. + INTERVAL_TRADING_RULES = 600 + + # Trading pair splitter regex + TRADING_PAIR_SPLITTER = r"^(\w+)(BTC|BCH|DAI|DDRST|EOSDT|EOS|ETH|EURS|HIT|IDRT|PAX|BUSD|GUSD|TUSD|USDC|USDT|USD)$" diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py b/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py new file mode 100644 index 0000000..3e937cb --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_exchange.py @@ -0,0 +1,895 @@ +import asyncio +import logging +import math +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional + +import aiohttp +from async_timeout import timeout + +from hummingbot.connector.exchange.hitbtc.hitbtc_api_order_book_data_source import HitbtcAPIOrderBookDataSource +from hummingbot.connector.exchange.hitbtc.hitbtc_auth import HitbtcAuth +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants +from hummingbot.connector.exchange.hitbtc.hitbtc_in_flight_order import HitbtcInFlightOrder +from hummingbot.connector.exchange.hitbtc.hitbtc_order_book_tracker import HitbtcOrderBookTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_user_stream_tracker import HitbtcUserStreamTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_utils import ( + HitbtcAPIError, + aiohttp_response_with_errors, + get_new_client_order_id, + retry_sleep_time, + str_date_to_ts, + translate_asset, +) +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OpenOrder, OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +ctce_logger = None +s_decimal_NaN = Decimal("nan") + + +class HitbtcExchange(ExchangeBase): + """ + HitbtcExchange connects with HitBTC exchange and provides order book pricing, user account tracking and + trading functionality. + """ + ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + ORDER_NOT_EXIST_CANCEL_COUNT = 2 + + @classmethod + def logger(cls) -> HummingbotLogger: + global ctce_logger + if ctce_logger is None: + ctce_logger = logging.getLogger(__name__) + return ctce_logger + + def __init__(self, + client_config_map: "ClientConfigAdapter", + hitbtc_api_key: str, + hitbtc_secret_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True + ): + """ + :param hitbtc_api_key: The API key to connect to private HitBTC APIs. + :param hitbtc_secret_key: The API secret. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + super().__init__(client_config_map) + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._hitbtc_auth = HitbtcAuth(hitbtc_api_key, hitbtc_secret_key) + self._set_order_book_tracker(HitbtcOrderBookTracker(trading_pairs=trading_pairs)) + self._user_stream_tracker = HitbtcUserStreamTracker(self._hitbtc_auth, trading_pairs) + self._ev_loop = asyncio.get_event_loop() + self._shared_client = None + self._poll_notifier = asyncio.Event() + self._last_timestamp = 0 + self._in_flight_orders = {} # Dict[client_order_id:str, HitbtcInFlightOrder] + self._order_not_found_records = {} # Dict[client_order_id:str, count:int] + self._trading_rules = {} # Dict[trading_pair:str, TradingRule] + self._status_polling_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._last_poll_timestamp = 0 + + @property + def name(self) -> str: + return "hitbtc" + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self.order_book_tracker.order_books + + @property + def trading_rules(self) -> Dict[str, TradingRule]: + return self._trading_rules + + @property + def in_flight_orders(self) -> Dict[str, HitbtcInFlightOrder]: + return self._in_flight_orders + + @property + def status_dict(self) -> Dict[str, bool]: + """ + A dictionary of statuses of various connector's components. + """ + return { + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0, + "user_stream_initialized": + self._user_stream_tracker.data_source.last_recv_time > 0 if self._trading_required else True, + } + + @property + def ready(self) -> bool: + """ + :return True when all statuses pass, this might take 5-10 seconds for all the connector's components and + services to be ready. + """ + return all(self.status_dict.values()) + + @property + def limit_orders(self) -> List[LimitOrder]: + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + @property + def tracking_states(self) -> Dict[str, any]: + """ + :return active in-flight orders in json format, is used to save in sqlite db. + """ + return { + key: value.to_json() + for key, value in self._in_flight_orders.items() + if not value.is_done + } + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + Restore in-flight orders from saved tracking states, this is st the connector can pick up on where it left off + when it disconnects. + :param saved_states: The saved tracking_states. + """ + self._in_flight_orders.update({ + key: HitbtcInFlightOrder.from_json(value) + for key, value in saved_states.items() + }) + + def supported_order_types(self) -> List[OrderType]: + """ + :return a list of OrderType supported by this connector. + Note that Market order type is no longer required and will not be used. + """ + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def start(self, clock: Clock, timestamp: float): + """ + This function is called automatically by the clock. + """ + super().start(clock, timestamp) + + def stop(self, clock: Clock): + """ + This function is called automatically by the clock. + """ + super().stop(clock) + + async def start_network(self): + """ + This function is required by NetworkIterator base class and is called automatically. + It starts tracking order book, polling trading rules, + updating statuses and tracking user data. + """ + self.order_book_tracker.start() + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + async def stop_network(self): + """ + This function is required by NetworkIterator base class and is called automatically. + """ + self.order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._trading_rules_polling_task = None + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + self._user_stream_tracker_task = None + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + self._user_stream_event_listener_task = None + + async def check_network(self) -> NetworkStatus: + """ + This function is required by NetworkIterator base class and is called periodically to check + the network connection. Simply ping the network (or call any light weight public API). + """ + try: + # since there is no ping endpoint, the lowest rate call is to get BTC-USD symbol + await self._api_request("GET", + Constants.ENDPOINT['SYMBOL'], + params={'symbols': 'BTCUSD'}) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + async def _http_client(self) -> aiohttp.ClientSession: + """ + :returns Shared client session instance + """ + if self._shared_client is None: + self._shared_client = aiohttp.ClientSession() + return self._shared_client + + async def _trading_rules_polling_loop(self): + """ + Periodically update trading rule. + """ + while True: + try: + await self._update_trading_rules() + await asyncio.sleep(Constants.INTERVAL_TRADING_RULES) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network(f"Unexpected error while fetching trading rules. Error: {str(e)}", + exc_info=True, + app_warning_msg=("Could not fetch new trading rules from " + f"{Constants.EXCHANGE_NAME}. Check network connection.")) + await asyncio.sleep(0.5) + + async def _update_trading_rules(self): + symbols_info = await self._api_request("GET", endpoint=Constants.ENDPOINT['SYMBOL']) + self._trading_rules.clear() + self._trading_rules = await self._format_trading_rules(symbols_info) + + async def _format_trading_rules(self, symbols_info: Dict[str, Any]) -> Dict[str, TradingRule]: + """ + Converts json API response into a dictionary of trading rules. + :param symbols_info: The json API response + :return A dictionary of trading rules. + Response Example: + [ + { + id: "BTCUSD", + baseCurrency: "BTC", + quoteCurrency: "USD", + quantityIncrement: "0.00001", + tickSize: "0.01", + takeLiquidityRate: "0.0025", + provideLiquidityRate: "0.001", + feeCurrency: "USD", + marginTrading: true, + maxInitialLeverage: "12.00" + } + ] + """ + result = {} + for rule in symbols_info: + try: + trading_pair = await HitbtcAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol(rule["id"]) + price_step = Decimal(str(rule["tickSize"])) + size_step = Decimal(str(rule["quantityIncrement"])) + result[trading_pair] = TradingRule(trading_pair, + min_order_size=size_step, + min_base_amount_increment=size_step, + min_price_increment=price_step) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) + return result + + async def _api_request(self, + method: str, + endpoint: str, + params: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + try_count: int = 0) -> Dict[str, Any]: + """ + Sends an aiohttp request and waits for a response. + :param method: The HTTP method, e.g. get or post + :param endpoint: The path url or the API end point + :param params: Additional get/post parameters + :param is_auth_required: Whether an authentication is required, when True the function will add encrypted + signature to the request. + :returns A response in json format. + """ + url = f"{Constants.REST_URL}/{endpoint}" + shared_client = await self._http_client() + # Turn `params` into either GET params or POST body data + qs_params: dict = params if method.upper() == "GET" else None + req_form = aiohttp.FormData(params) if method.upper() == "POST" and params is not None else None + # Generate auth headers if needed. + headers: dict = {"Content-Type": "application/x-www-form-urlencoded"} + if is_auth_required: + headers: dict = self._hitbtc_auth.get_headers(method, f"{Constants.REST_URL_AUTH}/{endpoint}", + params) + # Build request coro + response_coro = shared_client.request(method=method.upper(), url=url, headers=headers, + params=qs_params, data=req_form, + timeout=Constants.API_CALL_TIMEOUT) + http_status, parsed_response, request_errors = await aiohttp_response_with_errors(response_coro) + if request_errors or parsed_response is None: + if try_count < Constants.API_MAX_RETRIES: + try_count += 1 + time_sleep = retry_sleep_time(try_count) + self.logger().info(f"Error fetching data from {url}. HTTP status is {http_status}. " + f"Retrying in {time_sleep:.0f}s.") + await asyncio.sleep(time_sleep) + return await self._api_request(method=method, endpoint=endpoint, params=params, + is_auth_required=is_auth_required, try_count=try_count) + else: + raise HitbtcAPIError({"error": parsed_response, "status": http_status}) + if "error" in parsed_response: + raise HitbtcAPIError(parsed_response) + return parsed_response + + def get_order_price_quantum(self, trading_pair: str, price: Decimal): + """ + Returns a price step, a minimum price increment for a given trading pair. + """ + trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal): + """ + Returns an order amount step, a minimum amount increment for a given trading pair. + """ + trading_rule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + def get_order_book(self, trading_pair: str) -> OrderBook: + if trading_pair not in self.order_book_tracker.order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return self.order_book_tracker.order_books[trading_pair] + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + """ + Buys an amount of base asset (of the given trading pair). This function returns immediately. + To see an actual order, you'll have to wait for BuyOrderCreatedEvent. + :param trading_pair: The market (e.g. BTC-USDT) to buy from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price (note: this is no longer optional) + :returns A new internal order id + """ + order_id: str = get_new_client_order_id(True, trading_pair) + safe_ensure_future(self._create_order(TradeType.BUY, order_id, trading_pair, amount, order_type, price)) + return order_id + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + """ + Sells an amount of base asset (of the given trading pair). This function returns immediately. + To see an actual order, you'll have to wait for SellOrderCreatedEvent. + :param trading_pair: The market (e.g. BTC-USDT) to sell from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price (note: this is no longer optional) + :returns A new internal order id + """ + order_id: str = get_new_client_order_id(False, trading_pair) + safe_ensure_future(self._create_order(TradeType.SELL, order_id, trading_pair, amount, order_type, price)) + return order_id + + def cancel(self, trading_pair: str, order_id: str): + """ + Cancel an order. This function returns immediately. + To get the cancellation result, you'll have to wait for OrderCancelledEvent. + :param trading_pair: The market (e.g. BTC-USDT) of the order. + :param order_id: The internal order id (also called client_order_id) + """ + safe_ensure_future(self._execute_cancel(trading_pair, order_id)) + return order_id + + async def _create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Decimal): + """ + Calls create-order API end point to place an order, starts tracking the order and triggers order created event. + :param trade_type: BUY or SELL + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param amount: The order amount (in base token value) + :param order_type: The order type + :param price: The order price + """ + if not order_type.is_limit_type(): + raise Exception(f"Unsupported order type: {order_type}") + trading_rule = self._trading_rules[trading_pair] + + amount = self.quantize_order_amount(trading_pair, amount) + price = self.quantize_order_price(trading_pair, price) + if amount < trading_rule.min_order_size: + raise ValueError(f"Buy order amount {amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + order_type_str = order_type.name.lower().split("_")[0] + symbol = await HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair) + api_params = {"symbol": symbol, + "side": trade_type.name.lower(), + "type": order_type_str, + "price": f"{price:f}", + "quantity": f"{amount:f}", + "clientOrderId": order_id, + # Without strict validate, HitBTC might adjust order prices/sizes. + "strictValidate": "true", + } + if order_type is OrderType.LIMIT_MAKER: + api_params["postOnly"] = "true" + self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, order_type) + try: + order_result = await self._api_request("POST", Constants.ENDPOINT["ORDER_CREATE"], api_params, True) + exchange_order_id = str(order_result["id"]) + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type.name} {trade_type.name} order {order_id} for " + f"{amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + if trade_type is TradeType.BUY: + event_tag = MarketEvent.BuyOrderCreated + event_cls = BuyOrderCreatedEvent + else: + event_tag = MarketEvent.SellOrderCreated + event_cls = SellOrderCreatedEvent + self.trigger_event(event_tag, + event_cls( + self.current_timestamp, + order_type, + trading_pair, + amount, + price, + order_id, + tracked_order.creation_timestamp)) + except asyncio.CancelledError: + raise + except HitbtcAPIError as e: + error_reason = e.error_payload.get('error', {}).get('message') + self.stop_tracking_order(order_id) + self.logger().network( + f"Error submitting {trade_type.name} {order_type.name} order to {Constants.EXCHANGE_NAME} for " + f"{amount} {trading_pair} {price} - {error_reason}.", + exc_info=True, + app_warning_msg=(f"Error submitting order to {Constants.EXCHANGE_NAME} - {error_reason}.") + ) + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent(self.current_timestamp, order_id, order_type)) + + def start_tracking_order(self, + order_id: str, + exchange_order_id: str, + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType): + """ + Starts tracking an order by simply adding it into _in_flight_orders dictionary. + """ + self._in_flight_orders[order_id] = HitbtcInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=amount, + creation_timestamp=self.current_timestamp + ) + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an order by simply removing it from _in_flight_orders dictionary. + """ + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + if order_id in self._order_not_found_records: + del self._order_not_found_records[order_id] + + async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: + """ + Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether + the cancellation is successful, it simply states it receives the request. + :param trading_pair: The market trading pair (Unused during cancel on HitBTC) + :param order_id: The internal order id + order.last_state to change to CANCELED + """ + order_was_cancelled = False + try: + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is None: + raise ValueError(f"Failed to cancel order - {order_id}. Order not found.") + if tracked_order.exchange_order_id is None: + await tracked_order.get_exchange_order_id() + # ex_order_id = tracked_order.exchange_order_id + await self._api_request("DELETE", + Constants.ENDPOINT["ORDER_DELETE"].format(id=order_id), + is_auth_required=True) + order_was_cancelled = True + except asyncio.CancelledError: + raise + except HitbtcAPIError as e: + err = e.error_payload.get('error', e.error_payload) + self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1 + if err.get('code') == 20002 and \ + self._order_not_found_records[order_id] >= self.ORDER_NOT_EXIST_CANCEL_COUNT: + order_was_cancelled = True + if order_was_cancelled: + self.logger().info(f"Successfully canceled order {order_id} on {Constants.EXCHANGE_NAME}.") + self.stop_tracking_order(order_id) + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent(self.current_timestamp, order_id)) + tracked_order.cancelled_event.set() + return CancellationResult(order_id, True) + else: + self.logger().network( + f"Failed to cancel order {order_id}: {err.get('message', str(err))}", + exc_info=True, + app_warning_msg=f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. " + f"Check API key and network connection." + ) + return CancellationResult(order_id, False) + + async def _status_polling_loop(self): + """ + Periodically update user balances and order status via REST API. This serves as a fallback measure for web + socket API updates. + """ + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self._update_balances(), + self._update_order_status(), + ) + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(str(e), exc_info=True) + warn_msg = (f"Could not fetch account updates from {Constants.EXCHANGE_NAME}. " + "Check API key and network connection.") + self.logger().network("Unexpected error while fetching account updates.", exc_info=True, + app_warning_msg=warn_msg) + await asyncio.sleep(0.5) + + async def _update_balances(self): + """ + Calls REST API to update total and available balances. + """ + account_info = await self._api_request("GET", Constants.ENDPOINT["USER_BALANCES"], is_auth_required=True) + self._process_balance_message(account_info) + + async def _update_order_status(self): + """ + Calls REST API to get status update for each in-flight order. + """ + last_tick = int(self._last_poll_timestamp / Constants.UPDATE_ORDER_STATUS_INTERVAL) + current_tick = int(self.current_timestamp / Constants.UPDATE_ORDER_STATUS_INTERVAL) + + if current_tick > last_tick and len(self._in_flight_orders) > 0: + tracked_orders = list(self._in_flight_orders.values()) + tasks = [] + for tracked_order in tracked_orders: + # exchange_order_id = await tracked_order.get_exchange_order_id() + order_id = tracked_order.client_order_id + tasks.append(self._api_request("GET", + Constants.ENDPOINT["ORDER_STATUS"].format(id=order_id), + is_auth_required=True)) + self.logger().debug(f"Polling for order status updates of {len(tasks)} orders.") + responses = await safe_gather(*tasks, return_exceptions=True) + for response, tracked_order in zip(responses, tracked_orders): + client_order_id = tracked_order.client_order_id + if isinstance(response, HitbtcAPIError): + err = response.error_payload.get('error', response.error_payload) + if err.get('code') == 20002: + self._order_not_found_records[client_order_id] = \ + self._order_not_found_records.get(client_order_id, 0) + 1 + if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT: + # Wait until the order not found error have repeated a few times before actually treating + # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601 + continue + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, client_order_id, tracked_order.order_type)) + self.stop_tracking_order(client_order_id) + else: + continue + elif "clientOrderId" not in response: + self.logger().info(f"_update_order_status clientOrderId not in resp: {response}") + continue + else: + self._process_order_message(response) + + def _process_order_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancellation or failure event if needed. + :param order_msg: The order response from either REST or web socket API (they are of the same format) + Example Order: + { + "id": "4345613661", + "clientOrderId": "57d5525562c945448e3cbd559bd068c3", + "symbol": "BCCBTC", + "side": "sell", + "status": "new", + "type": "limit", + "timeInForce": "GTC", + "quantity": "0.013", + "price": "0.100000", + "cumQuantity": "0.000", + "postOnly": false, + "createdAt": "2017-10-20T12:17:12.245Z", + "updatedAt": "2017-10-20T12:17:12.245Z", + "reportType": "status" + } + """ + client_order_id = order_msg["clientOrderId"] + if client_order_id not in self._in_flight_orders: + return + tracked_order = self._in_flight_orders[client_order_id] + # Update order execution status + tracked_order.last_state = order_msg["status"] + # update order + tracked_order.executed_amount_base = Decimal(order_msg["cumQuantity"]) + tracked_order.executed_amount_quote = Decimal(order_msg["price"]) * Decimal(order_msg["cumQuantity"]) + + if tracked_order.is_cancelled: + self.logger().info(f"Successfully canceled order {client_order_id}.") + self.stop_tracking_order(client_order_id) + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent(self.current_timestamp, client_order_id)) + tracked_order.cancelled_event.set() + elif tracked_order.is_failure: + self.logger().info(f"The market order {client_order_id} has failed according to order status API. ") + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, client_order_id, tracked_order.order_type)) + self.stop_tracking_order(client_order_id) + + async def _process_trade_message(self, trade_msg: Dict[str, Any]): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + Example Trade: + { + "id": "4345697765", + "clientOrderId": "53b7cf917963464a811a4af426102c19", + "symbol": "ETHBTC", + "side": "sell", + "status": "filled", + "type": "limit", + "timeInForce": "GTC", + "quantity": "0.001", + "price": "0.053868", + "cumQuantity": "0.001", + "postOnly": false, + "createdAt": "2017-10-20T12:20:05.952Z", + "updatedAt": "2017-10-20T12:20:38.708Z", + "reportType": "trade", + "tradeQuantity": "0.001", + "tradePrice": "0.053868", + "tradeId": 55051694, + "tradeFee": "-0.000000005" + } + """ + tracked_orders = list(self._in_flight_orders.values()) + for order in tracked_orders: + await order.get_exchange_order_id() + track_order = [o for o in tracked_orders if trade_msg["id"] == o.exchange_order_id] + if not track_order: + return + tracked_order = track_order[0] + updated = tracked_order.update_with_trade_update(trade_msg) + if not updated: + return + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + Decimal(str(trade_msg.get("tradePrice", "0"))), + Decimal(str(trade_msg.get("tradeQuantity", "0"))), + AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.quote_asset, Decimal(str(trade_msg.get("tradeFee", "0"))))] + ), + exchange_trade_id=trade_msg["id"] + ) + ) + if math.isclose(tracked_order.executed_amount_base, tracked_order.amount) or \ + tracked_order.executed_amount_base >= tracked_order.amount or \ + tracked_order.is_done: + tracked_order.last_state = "FILLED" + self.logger().info(f"The {tracked_order.trade_type.name} order " + f"{tracked_order.client_order_id} has completed " + f"according to order status API.") + event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \ + else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \ + else SellOrderCompletedEvent + await asyncio.sleep(0.1) + self.trigger_event(event_tag, + event_class(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type)) + self.stop_tracking_order(tracked_order.client_order_id) + + def _process_balance_message(self, balance_update): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + for account in balance_update: + asset_name = translate_asset(account["currency"]) + self._account_available_balances[asset_name] = Decimal(str(account["available"])) + self._account_balances[asset_name] = Decimal(str(account["reserved"])) + Decimal(str(account["available"])) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all in-flight orders and waits for cancellation results. + Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) + :param timeout_seconds: The timeout at which the operation will be canceled. + :returns List of CancellationResult which indicates whether each order is successfully cancelled. + """ + if self._trading_pairs is None: + raise Exception("cancel_all can only be used when trading_pairs are specified.") + open_orders = [o for o in self._in_flight_orders.values() if not o.is_done] + if len(open_orders) == 0: + return [] + tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in open_orders] + cancellation_results = [] + try: + async with timeout(timeout_seconds): + cancellation_results = await safe_gather(*tasks, return_exceptions=False) + except Exception: + self.logger().network( + "Unexpected error canceling orders.", exc_info=True, + app_warning_msg=(f"Failed to cancel all orders on {Constants.EXCHANGE_NAME}. " + "Check API key and network connection.") + ) + return cancellation_results + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + now = time.time() + poll_interval = (Constants.SHORT_POLL_INTERVAL + if now - self._user_stream_tracker.last_recv_time > 60.0 + else Constants.LONG_POLL_INTERVAL) + last_tick = int(self._last_timestamp / poll_interval) + current_tick = int(timestamp / poll_interval) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + self._last_timestamp = timestamp + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = order_type is OrderType.LIMIT_MAKER + return AddedToCostTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", exc_info=True, + app_warning_msg=(f"Could not fetch user events from {Constants.EXCHANGE_NAME}. " + "Check API key and network connection.")) + await asyncio.sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Listens to message in _user_stream_tracker.user_stream queue. The messages are put in by + HitbtcAPIUserStreamDataSource. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_methods = [ + Constants.WS_METHODS["USER_ORDERS"], + Constants.WS_METHODS["USER_TRADES"], + ] + method: str = event_message.get("method", None) + params: str = event_message.get("params", None) + account_balances: list = event_message.get("result", None) + + if method not in event_methods and account_balances is None: + self.logger().error(f"Unexpected message in user stream: {event_message}.", exc_info=True) + continue + if method == Constants.WS_METHODS["USER_TRADES"]: + await self._process_trade_message(params) + elif method == Constants.WS_METHODS["USER_ORDERS"]: + for order_msg in params: + self._process_order_message(order_msg) + elif isinstance(account_balances, list) and "currency" in account_balances[0]: + self._process_balance_message(account_balances) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await asyncio.sleep(5.0) + + # This is currently unused, but looks like a future addition. + async def get_open_orders(self) -> List[OpenOrder]: + result = await self._api_request("GET", Constants.ENDPOINT["USER_ORDERS"], is_auth_required=True) + ret_val = [] + for order in result: + if Constants.HBOT_BROKER_ID not in order["clientOrderId"]: + continue + if order["type"] != OrderType.LIMIT.name.lower(): + self.logger().info(f"Unsupported order type found: {order['type']}") + continue + trading_pair = await HitbtcAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol( + order["symbol"]) + ret_val.append( + OpenOrder( + client_order_id=order["clientOrderId"], + trading_pair=trading_pair, + price=Decimal(str(order["price"])), + amount=Decimal(str(order["quantity"])), + executed_amount=Decimal(str(order["cumQuantity"])), + status=order["status"], + order_type=OrderType.LIMIT, + is_buy=True if order["side"].lower() == TradeType.BUY.name.lower() else False, + time=str_date_to_ts(order["createdAt"]), + exchange_order_id=order["id"] + ) + ) + return ret_val + + async def all_trading_pairs(self) -> List[str]: + # This method should be removed and instead we should implement _initialize_trading_pair_symbol_map + return await HitbtcAPIOrderBookDataSource.fetch_trading_pairs() + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + # This method should be removed and instead we should implement _get_last_traded_price + return await HitbtcAPIOrderBookDataSource.get_last_traded_prices(trading_pairs=trading_pairs) diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_in_flight_order.py b/hummingbot/connector/exchange/hitbtc/hitbtc_in_flight_order.py new file mode 100644 index 0000000..8d48911 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_in_flight_order.py @@ -0,0 +1,95 @@ +import asyncio +from decimal import Decimal +from typing import ( + Any, + Dict, + Optional, +) + +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import OrderType, TradeType + +s_decimal_0 = Decimal(0) + + +class HitbtcInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + initial_state: str = "new"): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp, + initial_state, + ) + self.trade_id_set = set() + self.cancelled_event = asyncio.Event() + + @property + def is_done(self) -> bool: + return self.last_state in {"filled", "canceled", "expired"} + + @property + def is_failure(self) -> bool: + return self.last_state in {"suspended"} + + @property + def is_cancelled(self) -> bool: + return self.last_state in {"canceled", "expired"} + + def update_with_trade_update(self, trade_update: Dict[str, Any]) -> bool: + """ + Updates the in flight order with trade update (from private/get-order-detail end point) + return: True if the order gets updated otherwise False + Example Trade: + { + "id": "4345697765", + "clientOrderId": "53b7cf917963464a811a4af426102c19", + "symbol": "ETHBTC", + "side": "sell", + "status": "filled", + "type": "limit", + "timeInForce": "GTC", + "quantity": "0.001", + "price": "0.053868", + "cumQuantity": "0.001", + "postOnly": false, + "createdAt": "2017-10-20T12:20:05.952Z", + "updatedAt": "2017-10-20T12:20:38.708Z", + "reportType": "trade", + } + ... Trade variables are only included after fills. + { + "tradeQuantity": "0.001", + "tradePrice": "0.053868", + "tradeId": 55051694, + "tradeFee": "-0.000000005" + } + """ + self.executed_amount_base = Decimal(str(trade_update["cumQuantity"])) + if self.executed_amount_base <= s_decimal_0: + # No trades executed yet. + return False + trade_id = trade_update["updatedAt"] + if trade_id in self.trade_id_set: + # trade already recorded + return False + self.trade_id_set.add(trade_id) + self.fee_paid += Decimal(str(trade_update.get("tradeFee", "0"))) + self.executed_amount_quote += (Decimal(str(trade_update.get("tradePrice", "0"))) * + Decimal(str(trade_update.get("tradeQuantity", "0")))) + if not self.fee_asset: + self.fee_asset = self.quote_asset + return True diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book.py b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book.py new file mode 100644 index 0000000..3c6b1cc --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book.py @@ -0,0 +1,104 @@ +import logging + +from typing import ( + Any, + Dict, + List, + Optional, +) +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants +from hummingbot.connector.exchange.hitbtc.hitbtc_order_book_message import HitbtcOrderBookMessage +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.logger import HummingbotLogger + +_logger = None + + +class HitbtcOrderBook(OrderBook): + @classmethod + def logger(cls) -> HummingbotLogger: + global _logger + if _logger is None: + _logger = logging.getLogger(__name__) + return _logger + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None): + """ + Convert json snapshot data into standard OrderBookMessage format + :param msg: json snapshot data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: HitbtcOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + return HitbtcOrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=msg, + timestamp=timestamp + ) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Convert json diff data into standard OrderBookMessage format + :param msg: json diff data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: HitbtcOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + return HitbtcOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp + ) + + @classmethod + def trade_message_from_exchange(cls, + msg: Dict[str, Any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Convert a trade data into standard OrderBookMessage format + :param record: a trade data from the database + :return: HitbtcOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + msg.update({ + "exchange_order_id": msg.get("id"), + "trade_type": msg.get("side"), + "price": msg.get("price"), + "amount": msg.get("quantity"), + }) + + return HitbtcOrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=msg, + timestamp=timestamp + ) + + @classmethod + def from_snapshot(cls, snapshot: OrderBookMessage): + raise NotImplementedError(Constants.EXCHANGE_NAME + " order book needs to retain individual order data.") + + @classmethod + def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): + raise NotImplementedError(Constants.EXCHANGE_NAME + " order book needs to retain individual order data.") diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py new file mode 100644 index 0000000..1e0c38f --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_message.py @@ -0,0 +1,73 @@ +from typing import ( + Dict, + List, + Optional, +) + +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.core.data_type.order_book_row import OrderBookRow +from .hitbtc_constants import Constants + + +class HitbtcOrderBookMessage(OrderBookMessage): + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + if timestamp is None: + if message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError("timestamp must not be None when initializing snapshot messages.") + timestamp = content["timestamp"] + + return super(HitbtcOrderBookMessage, cls).__new__( + cls, message_type, content, timestamp=timestamp, *args, **kwargs + ) + + @property + def update_id(self) -> int: + if self.type in [OrderBookMessageType.DIFF, OrderBookMessageType.SNAPSHOT]: + return int(self.timestamp * 1e3) + else: + return -1 + + @property + def trade_id(self) -> int: + if self.type is OrderBookMessageType.TRADE: + return int(self.timestamp * 1e3) + return -1 + + @property + def trading_pair(self) -> str: + return self.content["trading_pair"] + + # The `asks` and `bids` properties are only used in the methods below. + # They are all replaced or unused in this connector: + # OrderBook.restore_from_snapshot_and_diffs + # OrderBookTracker._track_single_book + # MockAPIOrderBookDataSource.get_tracking_pairs + @property + def asks(self) -> List[OrderBookRow]: + raise NotImplementedError(Constants.EXCHANGE_NAME + " order book uses active_order_tracker.") + + @property + def bids(self) -> List[OrderBookRow]: + raise NotImplementedError(Constants.EXCHANGE_NAME + " order book uses active_order_tracker.") + + def __eq__(self, other) -> bool: + return self.type == other.type and self.timestamp == other.timestamp + + def __lt__(self, other) -> bool: + if self.timestamp != other.timestamp: + return self.timestamp < other.timestamp + else: + """ + If timestamp is the same, the ordering is snapshot < diff < trade + """ + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker.py b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker.py new file mode 100644 index 0000000..d3161de --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +import asyncio +import bisect +import logging +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants +import time + +from collections import defaultdict, deque +from typing import Optional, Dict, List, Deque +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.logger import HummingbotLogger +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_order_book_message import HitbtcOrderBookMessage +from hummingbot.connector.exchange.hitbtc.hitbtc_active_order_tracker import HitbtcActiveOrderTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_api_order_book_data_source import HitbtcAPIOrderBookDataSource +from hummingbot.connector.exchange.hitbtc.hitbtc_order_book import HitbtcOrderBook + + +class HitbtcOrderBookTracker(OrderBookTracker): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pairs: Optional[List[str]] = None,): + super().__init__(HitbtcAPIOrderBookDataSource(trading_pairs), trading_pairs) + + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_trade_stream: asyncio.Queue = asyncio.Queue() + self._process_msg_deque_task: Optional[asyncio.Task] = None + self._past_diffs_windows: Dict[str, Deque] = {} + self._order_books: Dict[str, HitbtcOrderBook] = {} + self._saved_message_queues: Dict[str, Deque[HitbtcOrderBookMessage]] = \ + defaultdict(lambda: deque(maxlen=1000)) + self._active_order_trackers: Dict[str, HitbtcActiveOrderTracker] = defaultdict(HitbtcActiveOrderTracker) + self._order_book_stream_listener_task: Optional[asyncio.Task] = None + self._order_book_trade_listener_task: Optional[asyncio.Task] = None + + @property + def exchange_name(self) -> str: + """ + Name of the current exchange + """ + return Constants.EXCHANGE_NAME + + async def _track_single_book(self, trading_pair: str): + """ + Update an order book with changes from the latest batch of received messages + """ + past_diffs_window: Deque[HitbtcOrderBookMessage] = deque() + self._past_diffs_windows[trading_pair] = past_diffs_window + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: HitbtcOrderBook = self._order_books[trading_pair] + active_order_tracker: HitbtcActiveOrderTracker = self._active_order_trackers[trading_pair] + + last_message_timestamp: float = time.time() + diff_messages_accepted: int = 0 + + while True: + try: + message: HitbtcOrderBookMessage = None + saved_messages: Deque[HitbtcOrderBookMessage] = self._saved_message_queues[trading_pair] + # Process saved messages first if there are any + if len(saved_messages) > 0: + message = saved_messages.popleft() + else: + message = await message_queue.get() + + if message.type is OrderBookMessageType.DIFF: + bids, asks = active_order_tracker.convert_diff_message_to_order_book_row(message) + order_book.apply_diffs(bids, asks, message.update_id) + past_diffs_window.append(message) + while len(past_diffs_window) > self.PAST_DIFF_WINDOW_SIZE: + past_diffs_window.popleft() + diff_messages_accepted += 1 + + # Output some statistics periodically. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Processed {diff_messages_accepted} order book diffs for {trading_pair}.") + diff_messages_accepted = 0 + last_message_timestamp = now + elif message.type is OrderBookMessageType.SNAPSHOT: + past_diffs: List[HitbtcOrderBookMessage] = list(past_diffs_window) + # only replay diffs later than snapshot, first update active order with snapshot then replay diffs + replay_position = bisect.bisect_right(past_diffs, message) + replay_diffs = past_diffs[replay_position:] + s_bids, s_asks = active_order_tracker.convert_snapshot_message_to_order_book_row(message) + order_book.apply_snapshot(s_bids, s_asks, message.update_id) + for diff_message in replay_diffs: + d_bids, d_asks = active_order_tracker.convert_diff_message_to_order_book_row(diff_message) + order_book.apply_diffs(d_bids, d_asks, diff_message.update_id) + + self.logger().debug(f"Processed order book snapshot for {trading_pair}.") + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Unexpected error processing order book messages for {trading_pair}.", + exc_info=True, + app_warning_msg="Unexpected error processing order book messages. Retrying after 5 seconds." + ) + await asyncio.sleep(5.0) diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker_entry.py b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker_entry.py new file mode 100644 index 0000000..5edfbad --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_order_book_tracker_entry.py @@ -0,0 +1,21 @@ +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry +from hummingbot.connector.exchange.hitbtc.hitbtc_active_order_tracker import HitbtcActiveOrderTracker + + +class HitbtcOrderBookTrackerEntry(OrderBookTrackerEntry): + def __init__( + self, trading_pair: str, timestamp: float, order_book: OrderBook, active_order_tracker: HitbtcActiveOrderTracker + ): + self._active_order_tracker = active_order_tracker + super(HitbtcOrderBookTrackerEntry, self).__init__(trading_pair, timestamp, order_book) + + def __repr__(self) -> str: + return ( + f"HitbtcOrderBookTrackerEntry(trading_pair='{self._trading_pair}', timestamp='{self._timestamp}', " + f"order_book='{self._order_book}')" + ) + + @property + def active_order_tracker(self) -> HitbtcActiveOrderTracker: + return self._active_order_tracker diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_user_stream_tracker.py b/hummingbot/connector/exchange/hitbtc/hitbtc_user_stream_tracker.py new file mode 100644 index 0000000..ed3ecbc --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_user_stream_tracker.py @@ -0,0 +1,71 @@ +import logging +from typing import ( + List, + Optional, +) + +from hummingbot.connector.exchange.hitbtc.hitbtc_api_user_stream_data_source import \ + HitbtcAPIUserStreamDataSource +from hummingbot.connector.exchange.hitbtc.hitbtc_auth import HitbtcAuth +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants +from hummingbot.core.data_type.user_stream_tracker import ( + UserStreamTracker +) +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger import HummingbotLogger + + +class HitbtcUserStreamTracker(UserStreamTracker): + _cbpust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._bust_logger is None: + cls._bust_logger = logging.getLogger(__name__) + return cls._bust_logger + + def __init__(self, + hitbtc_auth: Optional[HitbtcAuth] = None, + trading_pairs: Optional[List[str]] = None): + self._hitbtc_auth: HitbtcAuth = hitbtc_auth + self._trading_pairs: List[str] = trading_pairs or [] + super().__init__(data_source=HitbtcAPIUserStreamDataSource( + hitbtc_auth=self._hitbtc_auth, + trading_pairs=self._trading_pairs + )) + + @property + def data_source(self) -> UserStreamTrackerDataSource: + """ + *required + Initializes a user stream data source (user specific order diffs from live socket stream) + :return: OrderBookTrackerDataSource + """ + if not self._data_source: + self._data_source = HitbtcAPIUserStreamDataSource( + hitbtc_auth=self._hitbtc_auth, + trading_pairs=self._trading_pairs + ) + return self._data_source + + @property + def exchange_name(self) -> str: + """ + *required + Name of the current exchange + """ + return Constants.EXCHANGE_NAME + + async def start(self): + """ + *required + Start all listeners and tasks + """ + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_utils.py b/hummingbot/connector/exchange/hitbtc/hitbtc_utils.py new file mode 100644 index 0000000..0e00fc4 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_utils.py @@ -0,0 +1,165 @@ +import asyncio +import random +import re +from typing import Any, Dict, Optional, Tuple + +import aiohttp +from dateutil.parser import parse as dateparse +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce + +from .hitbtc_constants import Constants + +TRADING_PAIR_SPLITTER = re.compile(Constants.TRADING_PAIR_SPLITTER) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USD" + +DEFAULT_FEES = [0.1, 0.25] + + +class HitbtcAPIError(IOError): + def __init__(self, error_payload: Dict[str, Any]): + super().__init__(str(error_payload)) + self.error_payload = error_payload + + +# convert date string to timestamp +def str_date_to_ts(date: str) -> int: + return int(dateparse(date).timestamp()) + + +# Request ID class +class RequestId: + """ + Generate request ids + """ + _request_id: int = 0 + + @classmethod + def generate_request_id(cls) -> int: + return get_tracking_nonce() + + +def split_trading_pair(trading_pair: str) -> Optional[Tuple[str, str]]: + try: + if trading_pair == 'USDTUSD': + return 'USD', 'TUSD' + elif trading_pair == 'USDTGUSD': + return 'USD', 'GUSD' + m = TRADING_PAIR_SPLITTER.match(trading_pair) + return m.group(1), m.group(2) + # Exceptions are now logged as warnings in trading pair fetcher + except Exception: + return None + + +def translate_asset(asset_name: str) -> str: + asset_replacements = [ + ("USD", "USDT"), + ] + for asset_replacement in asset_replacements: + for inv in [0, 1]: + if asset_name == asset_replacement[inv]: + return asset_replacement[(0 if inv else 1)] + return asset_name + + +def get_new_client_order_id(is_buy: bool, trading_pair: str) -> str: + side = "B" if is_buy else "S" + symbols = trading_pair.split("-") + base = symbols[0].upper() + quote = symbols[1].upper() + base_str = f"{base[0]}{base[-1]}" + quote_str = f"{quote[0]}{quote[-1]}" + return f"{Constants.HBOT_BROKER_ID}-{side}-{base_str}{quote_str}-{get_tracking_nonce()}" + + +def retry_sleep_time(try_count: int) -> float: + random.seed() + randSleep = 1 + float(random.randint(1, 10) / 100) + return float(2 + float(randSleep * (1 + (try_count ** try_count)))) + + +async def aiohttp_response_with_errors(request_coroutine): + http_status, parsed_response, request_errors = None, None, False + try: + async with request_coroutine as response: + http_status = response.status + try: + parsed_response = await response.json() + except Exception: + request_errors = True + try: + parsed_response = str(await response.read()) + if len(parsed_response) > 100: + parsed_response = f"{parsed_response[:100]} ... (truncated)" + except Exception: + pass + TempFailure = (parsed_response is None or + (response.status not in [200, 201] and "error" not in parsed_response)) + if TempFailure: + parsed_response = response.reason if parsed_response is None else parsed_response + request_errors = True + except Exception: + request_errors = True + return http_status, parsed_response, request_errors + + +async def api_call_with_retries(method, + endpoint, + params: Optional[Dict[str, Any]] = None, + shared_client=None, + try_count: int = 0) -> Dict[str, Any]: + url = f"{Constants.REST_URL}/{endpoint}" + headers = {"Content-Type": "application/json"} + http_client = shared_client if shared_client is not None else aiohttp.ClientSession() + # Build request coro + response_coro = http_client.request(method=method.upper(), url=url, headers=headers, + params=params, timeout=Constants.API_CALL_TIMEOUT) + http_status, parsed_response, request_errors = await aiohttp_response_with_errors(response_coro) + if shared_client is None: + await http_client.close() + if request_errors or parsed_response is None: + if try_count < Constants.API_MAX_RETRIES: + try_count += 1 + time_sleep = retry_sleep_time(try_count) + print(f"Error fetching data from {url}. HTTP status is {http_status}. " + f"Retrying in {time_sleep:.0f}s.") + await asyncio.sleep(time_sleep) + return await api_call_with_retries(method=method, endpoint=endpoint, params=params, + shared_client=shared_client, try_count=try_count) + else: + raise HitbtcAPIError({"error": parsed_response, "status": http_status}) + return parsed_response + + +class HitbtcConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="hitbtc", client_data=None) + hitbtc_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {Constants.EXCHANGE_NAME} API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + hitbtc_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: f"Enter your {Constants.EXCHANGE_NAME} secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "hitbtc" + + +KEYS = HitbtcConfigMap.construct() diff --git a/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py b/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py new file mode 100644 index 0000000..da65b86 --- /dev/null +++ b/hummingbot/connector/exchange/hitbtc/hitbtc_websocket.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +import asyncio +import copy +import logging +import websockets +import json +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants + + +from typing import ( + Any, + AsyncIterable, + Dict, + Optional, +) +from websockets.exceptions import ConnectionClosed +from hummingbot.logger import HummingbotLogger +from hummingbot.connector.exchange.hitbtc.hitbtc_auth import HitbtcAuth +from hummingbot.connector.exchange.hitbtc.hitbtc_utils import ( + RequestId, + HitbtcAPIError, +) + +# reusable websocket class +# ToDo: We should eventually remove this class, and instantiate web socket connection normally (see Binance for example) + + +class HitbtcWebsocket(RequestId): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, + auth: Optional[HitbtcAuth] = None): + self._auth: Optional[HitbtcAuth] = auth + self._isPrivate = True if self._auth is not None else False + self._WS_URL = Constants.WS_PRIVATE_URL if self._isPrivate else Constants.WS_PUBLIC_URL + self._client: Optional[websockets.WebSocketClientProtocol] = None + + # connect to exchange + async def connect(self): + self._client = await websockets.connect(self._WS_URL) + + # if auth class was passed into websocket class + # we need to emit authenticated requests + if self._isPrivate: + auth_params = self._auth.generate_auth_dict_ws(self.generate_request_id()) + await self._emit("login", auth_params, no_id=True) + raw_msg_str: str = await asyncio.wait_for(self._client.recv(), timeout=Constants.MESSAGE_TIMEOUT) + json_msg = json.loads(raw_msg_str) + if json_msg.get("result") is not True: + err_msg = json_msg.get('error', {}).get('message') + raise HitbtcAPIError({"error": f"Failed to authenticate to websocket - {err_msg}."}) + + return self._client + + # disconnect from exchange + async def disconnect(self): + if self._client is None: + return + + await self._client.close() + + # receive & parse messages + async def _messages(self) -> AsyncIterable[Any]: + try: + while True: + try: + raw_msg_str: str = await asyncio.wait_for(self._client.recv(), timeout=Constants.MESSAGE_TIMEOUT) + try: + msg = json.loads(raw_msg_str) + # HitBTC doesn't support ping or heartbeat messages. + # Can handle them here if that changes - use `safe_ensure_future`. + yield msg + except ValueError: + continue + except asyncio.TimeoutError: + await asyncio.wait_for(self._client.ping(), timeout=Constants.PING_TIMEOUT) + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + return + except ConnectionClosed: + return + finally: + await self.disconnect() + + # emit messages + async def _emit(self, method: str, data: Optional[Dict[str, Any]] = {}, no_id: bool = False) -> int: + id = self.generate_request_id() + + payload = { + "id": id, + "method": method, + "params": copy.deepcopy(data), + } + + await self._client.send(json.dumps(payload)) + + return id + + # request via websocket + async def request(self, method: str, data: Optional[Dict[str, Any]] = {}) -> int: + return await self._emit(method, data) + + # subscribe to a method + async def subscribe(self, + channel: str, + trading_pair: Optional[str] = None, + params: Optional[Dict[str, Any]] = {}) -> int: + if trading_pair is not None: + params['symbol'] = trading_pair + return await self.request(f"subscribe{channel}", params) + + # unsubscribe to a method + async def unsubscribe(self, + channel: str, + trading_pair: Optional[str] = None, + params: Optional[Dict[str, Any]] = {}) -> int: + if trading_pair is not None: + params['symbol'] = trading_pair + return await self.request(f"unsubscribe{channel}", params) + + # listen to messages by method + async def on_message(self) -> AsyncIterable[Any]: + async for msg in self._messages(): + yield msg diff --git a/hummingbot/connector/exchange/huobi/__init__.py b/hummingbot/connector/exchange/huobi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py b/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py new file mode 100644 index 0000000..a063230 --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_api_order_book_data_source.py @@ -0,0 +1,179 @@ +import asyncio +import uuid +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +import hummingbot.connector.exchange.huobi.huobi_constants as CONSTANTS +from hummingbot.connector.exchange.huobi.huobi_web_utils import public_rest_url +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.huobi.huobi_exchange import HuobiExchange + + +class HuobiAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'HuobiExchange', + api_factory: WebAssistantsFactory, + ): + super().__init__(trading_pairs) + self._connector = connector + self._diff_messages_queue_key = CONSTANTS.ORDERBOOK_CHANNEL_SUFFIX + self._trade_messages_queue_key = CONSTANTS.TRADE_CHANNEL_SUFFIX + self._api_factory = api_factory + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_PUBLIC_URL, ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + + return ws + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Suppressing call to this function as the orderbook snapshots are handled by + listen_for_order_book_diffs() for Huobi + """ + pass + + def snapshot_message_from_exchange(self, + msg: Dict[str, Any], + metadata: Optional[Dict] = None) -> OrderBookMessage: + + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + msg_ts = msg["tick"]["ts"] * 1e-3 + content = { + "trading_pair": msg["trading_pair"], + "update_id": msg["tick"]["ts"], + "bids": msg["tick"].get("bids", []), + "asks": msg["tick"].get("asks", []) + } + + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, content, timestamp=msg_ts) + + def trade_message_from_exchange(self, + msg: Dict[str, Any], + metadata: Dict[str, Any] = None) -> OrderBookMessage: + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + + msg_ts = int(round(msg["ts"] / 1e3)) + content = { + "trading_pair": msg["trading_pair"], + "trade_type": float(TradeType.BUY.value) if msg["direction"] == "buy" else float(TradeType.SELL.value), + "trade_id": msg["id"], + "update_id": msg["ts"], + "amount": msg["amount"], + "price": msg["price"] + } + return OrderBookMessage(OrderBookMessageType.TRADE, content, timestamp=msg_ts) + + async def _request_new_orderbook_snapshot(self, trading_pair: str) -> Dict[str, Any]: + rest_assistant = await self._api_factory.get_rest_assistant() + url = public_rest_url(CONSTANTS.DEPTH_URL) + exchange_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + # when type is set to "step0", the default value of "depth" is 150 + params: Dict = {"symbol": exchange_symbol, "type": "step0"} + snapshot_data = await rest_assistant.execute_request( + url=url, + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.DEPTH_URL, + ) + return snapshot_data + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_new_orderbook_snapshot(trading_pair) + snapshot_msg: OrderBookMessage = self.snapshot_message_from_exchange( + msg=snapshot, + metadata={"trading_pair": trading_pair}, + ) + + return snapshot_msg + + async def _subscribe_channels(self, ws: WSAssistant): + try: + for trading_pair in self._trading_pairs: + exchange_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest({ + "sub": f"market.{exchange_symbol}.depth.step0", + "id": str(uuid.uuid4()) + }) + subscribe_trade_request: WSJSONRequest = WSJSONRequest({ + "sub": f"market.{exchange_symbol}.trade.detail", + "id": str(uuid.uuid4()) + }) + await ws.send(subscribe_orderbook_request) + await ws.send(subscribe_trade_request) + self.logger().info("Subscribed to public orderbook and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", exc_info=True + ) + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = event_message.get("ch", "") + retval = "" + if channel.endswith(self._trade_messages_queue_key): + retval = self._trade_messages_queue_key + if channel.endswith(self._diff_messages_queue_key): + retval = self._diff_messages_queue_key + + return retval + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + + ex_symbol = raw_message["ch"].split(".")[1] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=ex_symbol) + for data in raw_message["tick"]["data"]: + trade_message: OrderBookMessage = self.trade_message_from_exchange( + msg=data, + metadata={"trading_pair": trading_pair} + ) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + msg_channel = raw_message["ch"] + order_book_symbol = msg_channel.split(".")[1] + snapshot_msg: OrderBookMessage = self.snapshot_message_from_exchange( + msg=raw_message, + metadata={ + "trading_pair": await self._connector.trading_pair_associated_to_exchange_symbol(order_book_symbol) + } + ) + message_queue.put_nowait(snapshot_msg) + + async def _process_message_for_unknown_channel( + self, event_message: Dict[str, Any], websocket_assistant: WSAssistant + ): + if "ping" in event_message: + pong_request = WSJSONRequest(payload={"pong": event_message["ping"]}) + await websocket_assistant.send(request=pong_request) diff --git a/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py b/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py new file mode 100644 index 0000000..9b42a3e --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_api_user_stream_data_source.py @@ -0,0 +1,110 @@ +import asyncio +from typing import TYPE_CHECKING, List, Optional + +import hummingbot.connector.exchange.huobi.huobi_constants as CONSTANTS +from hummingbot.connector.exchange.huobi.huobi_auth import HuobiAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.huobi.huobi_exchange import HuobiExchange + + +class HuobiAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, huobi_auth: HuobiAuth, + trading_pairs: List[str], + connector: 'HuobiExchange', + api_factory: Optional[WebAssistantsFactory]): + self._auth: HuobiAuth = huobi_auth + self._connector = connector + self._api_factory = api_factory + self._trading_pairs = trading_pairs + super().__init__() + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_PRIVATE_URL, ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _authenticate_client(self, ws: WSAssistant): + """ + Sends an Authentication request to Huobi's WebSocket API Server + """ + try: + ws_request: WSJSONRequest = WSJSONRequest( + { + "action": "req", + "ch": "auth", + "params": {}, + } + ) + auth_params = self._auth.generate_auth_params_for_WS(ws_request) + ws_request.payload['params'] = auth_params + await ws.send(ws_request) + resp: WSResponse = await ws.receive() + auth_response = resp.data + if auth_response.get("code", 0) != 200: + raise ValueError(f"User Stream Authentication Fail! {auth_response}") + self.logger().info("Successfully authenticated to user stream...") + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Error occurred authenticating websocket connection... Error: {str(e)}", exc_info=True) + raise + + async def _subscribe_topic(self, topic: str, websocket_assistant: WSAssistant): + """ + Specifies which event channel to subscribe to + + :param topic: the event type to subscribe to + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + subscribe_request: WSJSONRequest = WSJSONRequest({"action": "sub", "ch": topic}) + await websocket_assistant.send(subscribe_request) + self.logger().info(f"Subscribed to {topic}") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error(f"Cannot subscribe to user stream topic: {topic}") + raise + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events, balance events and account events + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + await self._authenticate_client(websocket_assistant) + await self._subscribe_topic(CONSTANTS.HUOBI_ACCOUNT_UPDATE_TOPIC, websocket_assistant) + for trading_pair in self._trading_pairs: + exchange_symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + await self._subscribe_topic(CONSTANTS.HUOBI_TRADE_DETAILS_TOPIC.format(exchange_symbol), + websocket_assistant) + await self._subscribe_topic(CONSTANTS.HUOBI_ORDER_UPDATE_TOPIC.format(exchange_symbol), + websocket_assistant) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error occurred subscribing to private user streams...", exc_info=True) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + if data["action"] == "ping": + pong_request = WSJSONRequest(payload={"action": "pong", "data": data["data"]}) + await websocket_assistant.send(request=pong_request) + elif data["action"] == "sub": + if data.get("code") != 200: + raise ValueError(f"Error subscribing to topic: {data.get('ch')} ({data})") + else: + queue.put_nowait(data) diff --git a/hummingbot/connector/exchange/huobi/huobi_auth.py b/hummingbot/connector/exchange/huobi/huobi_auth.py new file mode 100644 index 0000000..73d769c --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_auth.py @@ -0,0 +1,86 @@ +import base64 +import hashlib +import hmac +from collections import OrderedDict +from datetime import datetime +from typing import Any, Dict +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSJSONRequest + +HUOBI_HOST_NAME = "api.huobi.pro" + + +class HuobiAuth(AuthBase): + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key: str = api_key + self.hostname: str = HUOBI_HOST_NAME + self.secret_key: str = secret_key + self.time_provider = time_provider + + @staticmethod + def keysort(dictionary: Dict[str, str]) -> Dict[str, str]: + return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + + auth_params = self.generate_auth_params_for_REST(request=request) + request.params = auth_params + + return request + + async def ws_authenticate(self, request: WSJSONRequest) -> WSJSONRequest: + return request # pass-through + + def generate_auth_params_for_REST(self, request: RESTRequest) -> Dict[str, Any]: + timestamp = datetime.utcfromtimestamp(self.time_provider.time()).strftime("%Y-%m-%dT%H:%M:%S") + path_url = f"/v1{request.url.split('v1')[-1]}" + params = request.params or {} + params.update({ + "AccessKeyId": self.api_key, + "SignatureMethod": "HmacSHA256", + "SignatureVersion": "2", + "Timestamp": timestamp + }) + sorted_params = self.keysort(params) + signature = self.generate_signature(method=request.method.value.upper(), + path_url=path_url, + params=sorted_params, + ) + sorted_params["Signature"] = signature + return sorted_params + + def generate_auth_params_for_WS(self, request: WSJSONRequest) -> Dict[str, Any]: + timestamp = datetime.utcfromtimestamp(self.time_provider.time()).strftime("%Y-%m-%dT%H:%M:%S") + path_url = "/ws/v2" + params = request.payload.get("params") or {} + params.update({ + "accessKey": self.api_key, + "signatureMethod": "HmacSHA256", + "signatureVersion": "2.1", + "timestamp": timestamp + }) + sorted_params = self.keysort(params) + signature = self.generate_signature(method="get", + path_url=path_url, + params=sorted_params, + ) + sorted_params["signature"] = signature + sorted_params["authType"] = "api" + return sorted_params + + def generate_signature(self, + method: str, + path_url: str, + params: Dict[str, Any], + ) -> str: + + query_endpoint = path_url + encoded_params_str = urlencode(params) + payload = "\n".join([method.upper(), self.hostname, query_endpoint, encoded_params_str]) + digest = hmac.new(self.secret_key.encode("utf8"), payload.encode("utf8"), hashlib.sha256).digest() + signature_b64 = base64.b64encode(digest).decode() + + return signature_b64 diff --git a/hummingbot/connector/exchange/huobi/huobi_constants.py b/hummingbot/connector/exchange/huobi/huobi_constants.py new file mode 100644 index 0000000..9373963 --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_constants.py @@ -0,0 +1,78 @@ +# A single source of truth for constant variables related to the exchange + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "huobi" +BROKER_ID = "AAc484720a" +DOMAIN = "" +MAX_CLIENT_ORDER_ID_LENGTH = 64 + + +REST_URL = "https://api.huobi.pro" +WS_PUBLIC_URL = "wss://api.huobi.pro/ws" +WS_PRIVATE_URL = "wss://api.huobi.pro/ws/v2" + +WS_HEARTBEAT_TIME_INTERVAL = 5 # seconds + +# Websocket event types +TRADE_CHANNEL_SUFFIX = "trade.detail" +ORDERBOOK_CHANNEL_SUFFIX = "depth.step0" + +TRADE_INFO_URL = "/v1/settings/common/market-symbols" +MOST_RECENT_TRADE_URL = "/market/tickers" +DEPTH_URL = "/market/depth" +LAST_TRADE_URL = "/market/trade" + +SERVER_TIME_URL = "/v1/common/timestamp" +ACCOUNT_ID_URL = "/v1/account/accounts" +ACCOUNT_BALANCE_URL = "/v1/account/accounts/{}/balance" +OPEN_ORDERS_URL = "/v1/order/openOrders" +ORDER_DETAIL_URL = "/v1/order/orders/{}" +ORDER_MATCHES_URL = "/v1/order/orders/{}/matchresults" +PLACE_ORDER_URL = "/v1/order/orders/place" +CANCEL_ORDER_URL = "/v1/order/orders/{}/submitcancel" +BATCH_CANCEL_URL = "/v1/order/orders/batchcancel" + +HUOBI_ACCOUNT_UPDATE_TOPIC = "accounts.update#2" +HUOBI_ORDER_UPDATE_TOPIC = "orders#{}" +HUOBI_TRADE_DETAILS_TOPIC = "trade.clearing#{}#0" + +HUOBI_SUBSCRIBE_TOPICS = {HUOBI_ORDER_UPDATE_TOPIC, HUOBI_ACCOUNT_UPDATE_TOPIC, HUOBI_TRADE_DETAILS_TOPIC} + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_REQUEST_LIMIT_ID = "WSRequest" +CANCEL_URL_LIMIT_ID = "cancelRequest" +ACCOUNT_BALANCE_LIMIT_ID = "accountBalance" +ORDER_DETAIL_LIMIT_ID = "orderDetail" +ORDER_MATCHES_LIMIT_ID = "orderMatch" + +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=50, time_interval=1), + RateLimit(WS_REQUEST_LIMIT_ID, limit=10, time_interval=1), + RateLimit(limit_id=TRADE_INFO_URL, limit=10, time_interval=1), + RateLimit(limit_id=MOST_RECENT_TRADE_URL, limit=10, time_interval=1), + RateLimit(limit_id=DEPTH_URL, limit=10, time_interval=1), + RateLimit(limit_id=LAST_TRADE_URL, limit=10, time_interval=1), + RateLimit(limit_id=SERVER_TIME_URL, limit=10, time_interval=1), + RateLimit(limit_id=ACCOUNT_ID_URL, limit=100, time_interval=2), + RateLimit(limit_id=ACCOUNT_BALANCE_LIMIT_ID, limit=100, time_interval=2), + RateLimit(limit_id=ORDER_DETAIL_LIMIT_ID, limit=50, time_interval=2), + RateLimit(limit_id=ORDER_MATCHES_LIMIT_ID, limit=50, time_interval=2), + RateLimit(limit_id=PLACE_ORDER_URL, limit=100, time_interval=2), + RateLimit(limit_id=CANCEL_URL_LIMIT_ID, limit=100, time_interval=2), + RateLimit(limit_id=BATCH_CANCEL_URL, limit=50, time_interval=2), + +] + +# Order States +ORDER_STATE = { + "rejected": OrderState.FAILED, + "canceled": OrderState.CANCELED, + "submitted": OrderState.OPEN, + "partial-filled": OrderState.PARTIALLY_FILLED, + "filled": OrderState.FILLED, + "partial-canceled": OrderState.CANCELED, + "created": OrderState.PENDING_CREATE, + "canceling": OrderState.PENDING_CANCEL +} diff --git a/hummingbot/connector/exchange/huobi/huobi_exchange.py b/hummingbot/connector/exchange/huobi/huobi_exchange.py new file mode 100644 index 0000000..e8019ee --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_exchange.py @@ -0,0 +1,453 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional + +from bidict import bidict + +import hummingbot.connector.exchange.huobi.huobi_constants as CONSTANTS +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.exchange.huobi import huobi_web_utils as web_utils +from hummingbot.connector.exchange.huobi.huobi_api_order_book_data_source import HuobiAPIOrderBookDataSource +from hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source import HuobiAPIUserStreamDataSource +from hummingbot.connector.exchange.huobi.huobi_auth import HuobiAuth +from hummingbot.connector.exchange.huobi.huobi_utils import is_exchange_information_valid +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class HuobiExchange(ExchangePyBase): + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + huobi_api_key: str, + huobi_secret_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ): + self.huobi_api_key = huobi_api_key + self.huobi_secret_key = huobi_secret_key + self._trading_pairs = trading_pairs + self._trading_required = trading_required + self._account_id = "" + super().__init__(client_config_map=client_config_map) + + @property + def name(self) -> str: + return "huobi" + + @property + def authenticator(self): + return HuobiAuth( + api_key=self.huobi_api_key, secret_key=self.huobi_secret_key, time_provider=self._time_synchronizer + ) + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return CONSTANTS.DOMAIN + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_CLIENT_ORDER_ID_LENGTH + + @property + def client_order_id_prefix(self): + return CONSTANTS.BROKER_ID + + @property + def trading_rules_request_path(self): + return CONSTANTS.TRADE_INFO_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.TRADE_INFO_URL + + @property + def check_network_request_path(self): + return CONSTANTS.SERVER_TIME_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ): + return build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # API documentation does not clarify the error message for timestamp related problems + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, time_synchronizer=self._time_synchronizer, auth=self._auth + ) + + def _create_order_book_data_source(self): + return HuobiAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, connector=self, api_factory=self._web_assistants_factory + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return HuobiAPIUserStreamDataSource( + huobi_auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + ) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_account_id(self) -> str: + accounts = await self._api_get(path_url=CONSTANTS.ACCOUNT_ID_URL, is_auth_required=True) + try: + for account in accounts["data"]: + if account["state"] == "working" and account["type"] == "spot": + self._account_id = str(account["id"]) + except Exception: + raise ValueError(f"Unable to retrieve account id.\n{accounts['err-msg']}") + + async def _update_balances(self): + + new_available_balances = {} + new_balances = {} + if not self._account_id: + await self._update_account_id() + data = await self._api_get( + path_url=CONSTANTS.ACCOUNT_BALANCE_URL.format(self._account_id), + is_auth_required=True, + limit_id=CONSTANTS.ACCOUNT_BALANCE_LIMIT_ID, + ) + balances = data.get("data", {}).get("list", []) + if len(balances) > 0: + for balance_entry in balances: + asset_name = balance_entry["currency"].upper() + balance = Decimal(balance_entry["balance"]) + if balance == s_decimal_0: + continue + if asset_name not in new_available_balances: + new_available_balances[asset_name] = s_decimal_0 + if asset_name not in new_balances: + new_balances[asset_name] = s_decimal_0 + + new_balances[asset_name] += balance + if balance_entry["type"] == "trade": + new_available_balances[asset_name] = balance + + self._account_available_balances = new_available_balances + self._account_balances = new_balances + + async def _format_trading_rules(self, raw_trading_pair_info: List[Dict[str, Any]]) -> List[TradingRule]: + trading_rules = [] + supported_symbols = await self.trading_pair_symbol_map() + for info in raw_trading_pair_info["data"]: + try: + if info["symbol"] not in supported_symbols: + continue + base_asset = info["bc"] + quote_asset = info["qc"] + price_precision = info["pp"] + amount_precision = info["ap"] + value_precision = info["vp"] + trading_rules.append( + TradingRule( + trading_pair=f"{base_asset}-{quote_asset}".upper(), + min_order_size=Decimal(info["minoa"]), + max_order_size=Decimal(info["maxoa"]), + min_price_increment=Decimal(str(10**-price_precision)), + min_base_amount_increment=Decimal(str(10**-amount_precision)), + min_quote_amount_increment=Decimal(str(10**-value_precision)), + min_notional_size=Decimal(info["minov"]), + ) + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {info}. Skipping.", exc_info=True) + return trading_rules + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = int(order.exchange_order_id) + all_fills_response = await self._api_get( + path_url=CONSTANTS.ORDER_MATCHES_URL.format(exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.ORDER_MATCHES_LIMIT_ID, + ) + + for trade in all_fills_response.get("data", []): + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["fee-currency"].upper(), + flat_fees=[TokenAmount(amount=Decimal(trade["filled-fees"]), token=trade["fee-currency"].upper())], + ) + trade_update = TradeUpdate( + trade_id=str(trade["trade-id"]), + client_order_id=order.client_order_id, + exchange_order_id=str(trade["order-id"]), + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["filled-amount"]), + fill_quote_amount=Decimal(trade["filled-amount"]) * Decimal(trade["price"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["created-at"] * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + exchange_order_id = await tracked_order.get_exchange_order_id() + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_DETAIL_URL.format(exchange_order_id), + is_auth_required=True, + limit_id=CONSTANTS.ORDER_DETAIL_LIMIT_ID, + ) + + if updated_order_data["status"] == "ok": + new_state = CONSTANTS.ORDER_STATE[updated_order_data["data"]["state"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + ) + + return order_update + else: + raise ValueError(f"Erroneous order status response {updated_order_data}") + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, Any]]: + """ + Called by _user_stream_event_listener. + """ + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Unknown error. Retrying after 1 second. {e}", exc_info=True) + await asyncio.sleep(1.0) + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for stream_message in self._iter_user_event_queue(): + try: + channel = stream_message["ch"] + data = stream_message["data"] + if channel.startswith("accounts"): + asset_name = data["currency"].upper() + balance = data["balance"] + available_balance = data["available"] + + self._account_balances.update({asset_name: Decimal(balance)}) + self._account_available_balances.update({asset_name: Decimal(available_balance)}) + elif channel.startswith("orders"): + safe_ensure_future(self._process_order_update(data)) + elif channel.startswith("trade.clearing"): + safe_ensure_future(self._process_trade_event(data)) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + async def _process_order_update(self, msg: Dict[str, Any]): + client_order_id = msg["clientOrderId"] + order_status = msg["orderStatus"] + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if tracked_order is not None: + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=CONSTANTS.ORDER_STATE[order_status], + client_order_id=client_order_id, + ) + self._order_tracker.process_order_update(order_update=order_update) + + async def _process_trade_event(self, trade_event: Dict[str, Any]): + client_order_id = trade_event["clientOrderId"] + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + + if tracked_order: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=trade_event["feeCurrency"].upper(), + flat_fees=[ + TokenAmount(amount=Decimal(trade_event["transactFee"]), token=trade_event["feeCurrency"].upper()) + ], + ) + trade_update = TradeUpdate( + trade_id=str(trade_event["tradeId"]), + client_order_id=client_order_id, + exchange_order_id=str(trade_event["orderId"]), + trading_pair=tracked_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(trade_event["tradeVolume"]), + fill_quote_amount=Decimal(trade_event["tradeVolume"]) * Decimal(trade_event["tradePrice"]), + fill_price=Decimal(trade_event["tradePrice"]), + fill_timestamp=trade_event["tradeTime"] * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + + async def _update_trading_fees(self): + pass + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ): + path_url = CONSTANTS.PLACE_ORDER_URL + side = trade_type.name.lower() + order_type_str = "limit" if order_type is OrderType.LIMIT else "limit-maker" + if not self._account_id: + await self._update_account_id() + exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + params = { + "account-id": self._account_id, + "amount": f"{amount}", + "client-order-id": order_id, + "symbol": exchange_symbol, + "type": f"{side}-{order_type_str}", + } + if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: + params["price"] = f"{price}" + creation_response = await self._api_post(path_url=path_url, params=params, data=params, is_auth_required=True) + + if ( + creation_response["status"] == "ok" + and creation_response["data"] is not None + and str(creation_response["data"]).isdecimal() + ): + exchange_order_id = str(creation_response["data"]) + return exchange_order_id, self.current_timestamp + else: + raise ValueError(f"Huobi rejected the order {order_id} ({creation_response})") + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + if tracked_order is None: + raise ValueError(f"Failed to cancel order - {order_id}. Order not found.") + path_url = CONSTANTS.CANCEL_ORDER_URL.format(tracked_order.exchange_order_id) + params = {"order-id": str(tracked_order.exchange_order_id)} + response = await self._api_post( + path_url=path_url, params=params, data=params, limit_id=CONSTANTS.CANCEL_URL_LIMIT_ID, is_auth_required=True + ) + if response.get("status") == "ok": + return True + return False + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(is_exchange_information_valid, exchange_info.get("data", [])): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair( + base=symbol_data["bc"].upper(), quote=symbol_data["qc"].upper() + ) + + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + path_url = CONSTANTS.MOST_RECENT_TRADE_URL + params = {"symbol": await self.exchange_symbol_associated_to_pair(trading_pair)} + resp_json = await self._api_get( + path_url=path_url, + params=params, + ) + resp_record = resp_json["tick"]["data"][0] + return float(resp_record["price"]) diff --git a/hummingbot/connector/exchange/huobi/huobi_utils.py b/hummingbot/connector/exchange/huobi/huobi_utils.py new file mode 100644 index 0000000..42d7afd --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_utils.py @@ -0,0 +1,56 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "ETH-USDT" + + +DEFAULT_FEES = TradeFeeSchema( + buy_percent_fee_deducted_from_returns=True, + maker_percent_fee_decimal=Decimal("0.002"), + taker_percent_fee_decimal=Decimal("0.002"), +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + if exchange_info.get("state") == "online": + return True + return False + + +class HuobiConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="huobi", client_data=None) + huobi_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Huobi API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + huobi_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Huobi secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "huobi" + + +KEYS = HuobiConfigMap.construct() diff --git a/hummingbot/connector/exchange/huobi/huobi_web_utils.py b/hummingbot/connector/exchange/huobi/huobi_web_utils.py new file mode 100644 index 0000000..897a906 --- /dev/null +++ b/hummingbot/connector/exchange/huobi/huobi_web_utils.py @@ -0,0 +1,59 @@ +from typing import Callable, Optional + +import hummingbot.connector.exchange.huobi.huobi_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import GZipCompressionWSPostProcessor, TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = None) -> str: + return CONSTANTS.REST_URL + path_url + + +def private_rest_url(path_url: str, domain: str = None) -> str: + return public_rest_url(path_url=path_url, domain=domain) + + +def build_api_factory(throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or AsyncThrottler(CONSTANTS.RATE_LIMITS) + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + ws_post_processors=[GZipCompressionWSPostProcessor()], + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler,) + return api_factory + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = None, +) -> float: + throttler = throttler or AsyncThrottler(CONSTANTS.RATE_LIMITS) + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_URL, + ) + server_time = response["data"] + return server_time diff --git a/hummingbot/connector/exchange/injective_v2/README.md b/hummingbot/connector/exchange/injective_v2/README.md new file mode 100644 index 0000000..a562719 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/README.md @@ -0,0 +1,36 @@ +## Injective v2 + +This is a spot connector created by **[Injective Labs](https://injectivelabs.org/)**. +The difference with `injective` connector is that v2 is a pure Python connector. That means that the user does not need to configure and run a Gateway instance to use the connector. +The connector supports two different account modes: +- Trading with delegate accounts +- Trading through off-chain vault contracts + +There is a third account type called `read_only_account`. This mode only allows to request public information from the nodes, but since it does not require credentials it does not allow to perform trading operations. + +### Delegate account mode +When configuring the connector with this mode, the account used to send the transactions to the chain for trading is not the account holding the funds. +The user will need to have one portfolio account and at least one trading account. And permissions should be granted with the portfolio account to the trading account for it to operate using the portfolio account's funds. + +#### Trading permissions grant +To grant permissions from a portfolio account to a trading account to operate using the portfolio account funds please refer to the script `account_delegation_script.py` + +#### Mode parameters +When configuring a new instance of the connector in Hummingbot the following parameters are required: + +- **private_key**: the private key of the trading account (grantee account) +- **subaccount_index**: the index (decimal number) of the subaccount from the trading account that the connector will be operating with +- **granter_address**: the public key (injective format address) of the portfolio account +- **granter_subaccount_index**: the index (decimal number) of the subaccount from the portfolio account (the subaccount holding the funds) + + +### Off-chain vault mode +When configuring the connector with this mode, all the operations are sent to be executed by a vault contract in the chain. +The user will need to have a vault contract deployed on chain, and use the vault's admin account to configure this mode's parameters. + +#### Mode parameters +When configuring a new instance of the connector in Hummingbot the following parameters are required: + +- **private_key**: the vault's admin account private key +- **subaccount_index**: the index (decimal number) of the subaccount from the vault's admin account +- **vault_contract_address**: the address in the chain for the vault contract diff --git a/hummingbot/connector/exchange/injective_v2/__init__.py b/hummingbot/connector/exchange/injective_v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/injective_v2/account_delegation_script.py b/hummingbot/connector/exchange/injective_v2/account_delegation_script.py new file mode 100644 index 0000000..e56e6ac --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/account_delegation_script.py @@ -0,0 +1,106 @@ +import asyncio + +from pyinjective.async_client import AsyncClient +from pyinjective.core.network import Network +from pyinjective.transaction import Transaction +from pyinjective.wallet import PrivateKey + +# Values to be configured by the user +NETWORK = Network.testnet() # Select the correct network: mainnet, testnet, devnet, local or custom +GRANT_EXPIRATION_IN_DAYS = 365 +GRANTER_ACCOUNT_PRIVATE_KEY = "" +GRANTER_SUBACCOUNT_INDEX = 0 +GRANTEE_PUBLIC_INJECTIVE_ADDRESS = "" +SPOT_MARKET_IDS = [] +DERIVATIVE_MARKET_IDS = [] +# List of the ids of all the markets the grant will include, for example: +# SPOT_MARKET_IDS = ["0x0511ddc4e6586f3bfe1acb2dd905f8b8a82c97e1edaef654b12ca7e6031ca0fa"] # noqa: mock +# Mainnet spot markets: https://lcd.injective.network/injective/exchange/v1beta1/spot/markets +# Testnet spot markets: https://k8s.testnet.lcd.injective.network/injective/exchange/v1beta1/spot/markets +# Mainnet derivative markets: https://lcd.injective.network/injective/exchange/v1beta1/derivative/markets +# Testnet derivative markets: https://k8s.testnet.lcd.injective.network/injective/exchange/v1beta1/derivative/markets + +# Fixed values, do not change +SECONDS_PER_DAY = 60 * 60 * 24 + + +async def main() -> None: + # initialize grpc client + client = AsyncClient(NETWORK, insecure=False) + composer = await client.composer() + await client.sync_timeout_height() + + # load account + granter_private_key = PrivateKey.from_hex(GRANTER_ACCOUNT_PRIVATE_KEY) + granter_public_key = granter_private_key.to_public_key() + granter_address = granter_public_key.to_address() + account = await client.get_account(granter_address.to_acc_bech32()) # noqa: F841 + granter_subaccount_id = granter_address.get_subaccount_id(index=GRANTER_SUBACCOUNT_INDEX) + + msg_spot_market = composer.MsgGrantTyped( + granter=granter_address.to_acc_bech32(), + grantee=GRANTEE_PUBLIC_INJECTIVE_ADDRESS, + msg_type="CreateSpotMarketOrderAuthz", + expire_in=GRANT_EXPIRATION_IN_DAYS * SECONDS_PER_DAY, + subaccount_id=granter_subaccount_id, + market_ids=SPOT_MARKET_IDS, + ) + + msg_derivative_market = composer.MsgGrantTyped( + granter=granter_address.to_acc_bech32(), + grantee=GRANTEE_PUBLIC_INJECTIVE_ADDRESS, + msg_type="CreateDerivativeMarketOrderAuthz", + expire_in=GRANT_EXPIRATION_IN_DAYS * SECONDS_PER_DAY, + subaccount_id=granter_subaccount_id, + market_ids=DERIVATIVE_MARKET_IDS, + ) + + msg_batch_update = composer.MsgGrantTyped( + granter = granter_address.to_acc_bech32(), + grantee = GRANTEE_PUBLIC_INJECTIVE_ADDRESS, + msg_type = "BatchUpdateOrdersAuthz", + expire_in=GRANT_EXPIRATION_IN_DAYS * SECONDS_PER_DAY, + subaccount_id=granter_subaccount_id, + spot_markets=SPOT_MARKET_IDS, + derivative_markets=DERIVATIVE_MARKET_IDS, + ) + + tx = ( + Transaction() + .with_messages(msg_spot_market, msg_derivative_market, msg_batch_update) + .with_sequence(client.get_sequence()) + .with_account_num(client.get_number()) + .with_chain_id(NETWORK.chain_id) + ) + sim_sign_doc = tx.get_sign_doc(granter_public_key) + sim_sig = granter_private_key.sign(sim_sign_doc.SerializeToString()) + sim_tx_raw_bytes = tx.get_tx_data(sim_sig, granter_public_key) + + # simulate tx + (sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes) + if not success: + print(sim_res) + return + + # build tx + gas_price = 500000000 + gas_limit = sim_res.gas_info.gas_used + 20000 + gas_fee = "{:.18f}".format((gas_price * gas_limit) / pow(10, 18)).rstrip("0") + fee = [composer.Coin( + amount=gas_price * gas_limit, + denom=NETWORK.fee_denom, + )] + + tx = tx.with_gas(gas_limit).with_fee(fee).with_memo("").with_timeout_height(client.timeout_height) + sign_doc = tx.get_sign_doc(granter_public_key) + sig = granter_private_key.sign(sign_doc.SerializeToString()) + tx_raw_bytes = tx.get_tx_data(sig, granter_public_key) + + res = await client.send_tx_sync_mode(tx_raw_bytes) + print(res) + print("gas wanted: {}".format(gas_limit)) + print("gas fee: {} INJ".format(gas_fee)) + + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) diff --git a/hummingbot/connector/exchange/injective_v2/data_sources/__init__.py b/hummingbot/connector/exchange/injective_v2/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/injective_v2/data_sources/injective_data_source.py b/hummingbot/connector/exchange/injective_v2/data_sources/injective_data_source.py new file mode 100644 index 0000000..dfa7e76 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/data_sources/injective_data_source.py @@ -0,0 +1,1368 @@ +import asyncio +import logging +import time +from abc import ABC, abstractmethod +from decimal import Decimal +from enum import Enum +from functools import partial +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from google.protobuf import any_pb2 +from pyinjective import Transaction +from pyinjective.composer import Composer, injective_exchange_tx_pb + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.injective_events import InjectiveEvent +from hummingbot.connector.exchange.injective_v2.injective_market import ( + InjectiveDerivativeMarket, + InjectiveSpotMarket, + InjectiveToken, +) +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder, GatewayPerpetualInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import ( + AccountEvent, + BalanceUpdateEvent, + MarketEvent, + OrderBookDataSourceEvent, + PositionUpdateEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.logger import HummingbotLogger + + +class InjectiveDataSource(ABC): + _logger: Optional[HummingbotLogger] = None + + TRANSACTIONS_LOOKUP_TIMEOUT = CONSTANTS.EXPECTED_BLOCK_TIME * 3 + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + @property + @abstractmethod + def publisher(self): + raise NotImplementedError + + @property + @abstractmethod + def query_executor(self): + raise NotImplementedError + + @property + @abstractmethod + def order_creation_lock(self) -> asyncio.Lock: + raise NotImplementedError + + @property + @abstractmethod + def throttler(self): + raise NotImplementedError + + @property + @abstractmethod + def portfolio_account_injective_address(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def portfolio_account_subaccount_id(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def trading_account_injective_address(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def injective_chain_id(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def fee_denom(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def portfolio_account_subaccount_index(self) -> int: + raise NotImplementedError + + @property + @abstractmethod + def network_name(self) -> str: + raise NotImplementedError + + @abstractmethod + async def composer(self) -> Composer: + raise NotImplementedError + + @abstractmethod + async def timeout_height(self) -> int: + raise NotImplementedError + + @abstractmethod + async def spot_market_and_trading_pair_map(self): + raise NotImplementedError + + @abstractmethod + async def spot_market_info_for_id(self, market_id: str): + raise NotImplementedError + + @abstractmethod + async def derivative_market_and_trading_pair_map(self): + raise NotImplementedError + + @abstractmethod + async def derivative_market_info_for_id(self, market_id: str): + raise NotImplementedError + + @abstractmethod + async def trading_pair_for_market(self, market_id: str): + raise NotImplementedError + + @abstractmethod + async def market_id_for_spot_trading_pair(self, trading_pair: str) -> str: + raise NotImplementedError + + @abstractmethod + async def market_id_for_derivative_trading_pair(self, trading_pair: str) -> str: + raise NotImplementedError + + @abstractmethod + async def spot_markets(self): + raise NotImplementedError + + @abstractmethod + async def derivative_markets(self): + raise NotImplementedError + + @abstractmethod + async def token(self, denom: str) -> InjectiveToken: + raise NotImplementedError + + @abstractmethod + def events_listening_tasks(self) -> List[asyncio.Task]: + raise NotImplementedError + + @abstractmethod + def add_listening_task(self, task: asyncio.Task): + raise NotImplementedError + + @abstractmethod + def configure_throttler(self, throttler: AsyncThrottlerBase): + raise NotImplementedError + + @abstractmethod + async def trading_account_sequence(self) -> int: + raise NotImplementedError + + @abstractmethod + async def trading_account_number(self) -> int: + raise NotImplementedError + + @abstractmethod + async def initialize_trading_account(self): + raise NotImplementedError + + @abstractmethod + async def update_markets(self): + raise NotImplementedError + + @abstractmethod + def real_tokens_spot_trading_pair(self, unique_trading_pair: str) -> str: + raise NotImplementedError + + @abstractmethod + def real_tokens_perpetual_trading_pair(self, unique_trading_pair: str) -> str: + raise NotImplementedError + + @abstractmethod + async def order_updates_for_transaction( + self, + transaction_hash: str, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None, + ) -> List[OrderUpdate]: + raise NotImplementedError + + @abstractmethod + def supported_order_types(self) -> List[OrderType]: + raise NotImplementedError + + def is_started(self): + return len(self.events_listening_tasks()) > 0 + + async def check_network(self) -> NetworkStatus: + try: + await self.query_executor.ping() + status = NetworkStatus.CONNECTED + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + async def start(self, market_ids: List[str]): + if not self.is_started(): + await self.initialize_trading_account() + if not self.is_started(): + spot_markets = [] + derivative_markets = [] + for market_id in market_ids: + if market_id in await self.spot_market_and_trading_pair_map(): + spot_markets.append(market_id) + else: + derivative_markets.append(market_id) + + if len(spot_markets) > 0: + self.add_listening_task(asyncio.create_task(self._listen_to_public_spot_trades(market_ids=spot_markets))) + self.add_listening_task(asyncio.create_task(self._listen_to_spot_order_book_updates(market_ids=spot_markets))) + for market_id in spot_markets: + self.add_listening_task(asyncio.create_task( + self._listen_to_subaccount_spot_order_updates(market_id=market_id)) + ) + self.add_listening_task(asyncio.create_task( + self._listen_to_subaccount_spot_order_updates(market_id=market_id)) + ) + if len(derivative_markets) > 0: + self.add_listening_task( + asyncio.create_task(self._listen_to_public_derivative_trades(market_ids=derivative_markets))) + self.add_listening_task( + asyncio.create_task(self._listen_to_derivative_order_book_updates(market_ids=derivative_markets))) + self.add_listening_task( + asyncio.create_task(self._listen_to_positions_updates()) + ) + for market_id in derivative_markets: + self.add_listening_task(asyncio.create_task( + self._listen_to_subaccount_derivative_order_updates(market_id=market_id)) + ) + self.add_listening_task( + asyncio.create_task(self._listen_to_funding_info_updates(market_id=market_id)) + ) + self.add_listening_task(asyncio.create_task(self._listen_to_account_balance_updates())) + self.add_listening_task(asyncio.create_task(self._listen_to_chain_transactions())) + + await self._initialize_timeout_height() + + async def stop(self): + for task in self.events_listening_tasks(): + task.cancel() + + def add_listener(self, event_tag: Enum, listener: EventListener): + self.publisher.add_listener(event_tag=event_tag, listener=listener) + + def remove_listener(self, event_tag: Enum, listener: EventListener): + self.publisher.remove_listener(event_tag=event_tag, listener=listener) + + async def spot_trading_rules(self) -> List[TradingRule]: + markets = await self.spot_markets() + trading_rules = self._create_trading_rules(markets=markets) + + return trading_rules + + async def derivative_trading_rules(self) -> List[TradingRule]: + markets = await self.derivative_markets() + trading_rules = self._create_trading_rules(markets=markets) + + return trading_rules + + async def spot_order_book_snapshot(self, market_id: str, trading_pair: str) -> OrderBookMessage: + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_ORDERBOOK_LIMIT_ID): + snapshot_data = await self.query_executor.get_spot_orderbook(market_id=market_id) + + market = await self.spot_market_info_for_id(market_id=market_id) + bids = [(market.price_from_chain_format(chain_price=Decimal(price)), + market.quantity_from_chain_format(chain_quantity=Decimal(quantity))) + for price, quantity, _ in snapshot_data["buys"]] + asks = [(market.price_from_chain_format(chain_price=Decimal(price)), + market.quantity_from_chain_format(chain_quantity=Decimal(quantity))) + for price, quantity, _ in snapshot_data["sells"]] + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": snapshot_data["sequence"], + "bids": bids, + "asks": asks, + }, + timestamp=snapshot_data["timestamp"] * 1e-3, + ) + return snapshot_msg + + async def perpetual_order_book_snapshot(self, market_id: str, trading_pair: str) -> OrderBookMessage: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_ORDERBOOK_LIMIT_ID): + snapshot_data = await self.query_executor.get_derivative_orderbook(market_id=market_id) + + market = await self.derivative_market_info_for_id(market_id=market_id) + bids = [(market.price_from_chain_format(chain_price=Decimal(price)), + market.quantity_from_chain_format(chain_quantity=Decimal(quantity))) + for price, quantity, _ in snapshot_data["buys"]] + asks = [(market.price_from_chain_format(chain_price=Decimal(price)), + market.quantity_from_chain_format(chain_quantity=Decimal(quantity))) + for price, quantity, _ in snapshot_data["sells"]] + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": snapshot_data["sequence"], + "bids": bids, + "asks": asks, + }, + timestamp=snapshot_data["timestamp"] * 1e-3, + ) + return snapshot_msg + + async def last_traded_price(self, market_id: str) -> Decimal: + price = await self._last_traded_price(market_id=market_id) + return price + + async def all_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + account_address = self.portfolio_account_injective_address + + async with self.throttler.execute_task(limit_id=CONSTANTS.PORTFOLIO_BALANCES_LIMIT_ID): + portfolio_response = await self.query_executor.account_portfolio(account_address=account_address) + + bank_balances = portfolio_response["bankBalances"] + sub_account_balances = portfolio_response.get("subaccounts", []) + + balances_dict: Dict[str, Dict[str, Decimal]] = {} + + if self._uses_default_portfolio_subaccount(): + for bank_entry in bank_balances: + token = await self.token(denom=bank_entry["denom"]) + if token is not None: + asset_name: str = token.unique_symbol + + available_balance = token.value_from_chain_format(chain_value=Decimal(bank_entry["amount"])) + total_balance = available_balance + balances_dict[asset_name] = { + "total_balance": total_balance, + "available_balance": available_balance, + } + + for entry in sub_account_balances: + if entry["subaccountId"] == self.portfolio_account_subaccount_id: + token = await self.token(denom=entry["denom"]) + if token is not None: + asset_name: str = token.unique_symbol + + total_balance = token.value_from_chain_format(chain_value=Decimal(entry["deposit"]["totalBalance"])) + available_balance = token.value_from_chain_format( + chain_value=Decimal(entry["deposit"]["availableBalance"])) + + balance_element = balances_dict.get( + asset_name, {"total_balance": Decimal("0"), "available_balance": Decimal("0")} + ) + balance_element["total_balance"] += total_balance + balance_element["available_balance"] += available_balance + balances_dict[asset_name] = balance_element + + return balances_dict + + async def account_positions(self) -> List[Position]: + done = False + skip = 0 + position_entries = [] + + while not done: + async with self.throttler.execute_task(limit_id=CONSTANTS.POSITIONS_LIMIT_ID): + positions_response = await self.query_executor.get_derivative_positions( + subaccount_id=self.portfolio_account_subaccount_id, + skip=skip, + ) + if "positions" in positions_response: + total = int(positions_response["paging"]["total"]) + entries = positions_response["positions"] + + position_entries.extend(entries) + done = len(position_entries) >= total + skip += len(entries) + else: + done = True + + positions = [] + for position_entry in position_entries: + position_update = await self._parse_position_update_event(event=position_entry) + + position = Position( + trading_pair=position_update.trading_pair, + position_side=position_update.position_side, + unrealized_pnl=position_update.unrealized_pnl, + entry_price=position_update.entry_price, + amount=position_update.amount, + leverage=position_update.leverage, + ) + + positions.append(position) + + return positions + + async def create_orders( + self, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None, + ) -> List[PlaceOrderResult]: + spot_orders = spot_orders or [] + perpetual_orders = perpetual_orders or [] + results = [] + if self.order_creation_lock.locked(): + raise RuntimeError("It is not possible to create new orders because the hash manager is not synchronized") + + if len(spot_orders) > 0 or len(perpetual_orders) > 0: + async with self.order_creation_lock: + + order_creation_messages, spot_order_hashes, derivative_order_hashes = await self._order_creation_messages( + spot_orders_to_create=spot_orders, + derivative_orders_to_create=perpetual_orders, + ) + + try: + result = await self._send_in_transaction(messages=order_creation_messages) + if result["rawLog"] != "[]" or result["txhash"] in [None, ""]: + raise ValueError(f"Error sending the order creation transaction ({result['rawLog']})") + else: + transaction_hash = result["txhash"] + results = self._place_order_results( + orders_to_create=spot_orders + perpetual_orders, + order_hashes=spot_order_hashes + derivative_order_hashes, + misc_updates={ + "creation_transaction_hash": transaction_hash, + }, + ) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().debug( + f"Error broadcasting transaction to create orders (message: {order_creation_messages})") + results = self._place_order_results( + orders_to_create=spot_orders + perpetual_orders, + order_hashes=spot_order_hashes + derivative_order_hashes, + misc_updates={}, + exception=ex, + ) + + return results + + async def cancel_orders( + self, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None, + ) -> List[CancelOrderResult]: + spot_orders = spot_orders or [] + perpetual_orders = perpetual_orders or [] + + orders_with_hash = [] + spot_orders_data = [] + derivative_orders_data = [] + results = [] + + if len(spot_orders) > 0 or len(perpetual_orders) > 0: + for order in spot_orders: + if order.exchange_order_id is None: + results.append(CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + not_found=True, + )) + else: + market_id = await self.market_id_for_spot_trading_pair(trading_pair=order.trading_pair) + order_data = await self._generate_injective_order_data(order=order, market_id=market_id) + spot_orders_data.append(order_data) + orders_with_hash.append(order) + + for order in perpetual_orders: + if order.exchange_order_id is None: + results.append(CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + not_found=True, + )) + else: + market_id = await self.market_id_for_derivative_trading_pair(trading_pair=order.trading_pair) + order_data = await self._generate_injective_order_data(order=order, market_id=market_id) + derivative_orders_data.append(order_data) + orders_with_hash.append(order) + + if len(orders_with_hash) > 0: + delegated_message = await self._order_cancel_message( + spot_orders_to_cancel=spot_orders_data, + derivative_orders_to_cancel=derivative_orders_data, + ) + + try: + result = await self._send_in_transaction(messages=[delegated_message]) + if result["rawLog"] != "[]": + raise ValueError(f"Error sending the order cancel transaction ({result['rawLog']})") + else: + cancel_transaction_hash = result.get("txhash", "") + results.extend([ + CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + misc_updates={"cancelation_transaction_hash": cancel_transaction_hash}, + ) for order in orders_with_hash + ]) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().debug(f"Error broadcasting transaction to cancel orders (message: {delegated_message})") + results.extend([ + CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + exception=ex, + ) for order in orders_with_hash + ]) + + return results + + async def cancel_all_subaccount_orders( + self, + spot_markets_ids: Optional[List[str]] = None, + perpetual_markets_ids: Optional[List[str]] = None, + ): + spot_markets_ids = spot_markets_ids or [] + perpetual_markets_ids = perpetual_markets_ids or [] + + delegated_message = await self._all_subaccount_orders_cancel_message( + spot_markets_ids=spot_markets_ids, + derivative_markets_ids=perpetual_markets_ids, + ) + + result = await self._send_in_transaction(messages=[delegated_message]) + if result["rawLog"] != "[]": + raise ValueError(f"Error sending the order cancel transaction ({result['rawLog']})") + + async def spot_trade_updates(self, market_ids: List[str], start_time: float) -> List[TradeUpdate]: + done = False + skip = 0 + trade_entries = [] + + while not done: + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_TRADES_LIMIT_ID): + trades_response = await self.query_executor.get_spot_trades( + market_ids=market_ids, + subaccount_id=self.portfolio_account_subaccount_id, + start_time=int(start_time * 1e3), + skip=skip, + ) + if "trades" in trades_response: + total = int(trades_response["paging"]["total"]) + entries = trades_response["trades"] + + trade_entries.extend(entries) + done = len(trade_entries) >= total + skip += len(entries) + else: + done = True + + trade_updates = [await self._parse_spot_trade_entry(trade_info=trade_info) for trade_info in trade_entries] + + return trade_updates + + async def perpetual_trade_updates(self, market_ids: List[str], start_time: float) -> List[TradeUpdate]: + done = False + skip = 0 + trade_entries = [] + + while not done: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_TRADES_LIMIT_ID): + trades_response = await self.query_executor.get_derivative_trades( + market_ids=market_ids, + subaccount_id=self.portfolio_account_subaccount_id, + start_time=int(start_time * 1e3), + skip=skip, + ) + if "trades" in trades_response: + total = int(trades_response["paging"]["total"]) + entries = trades_response["trades"] + + trade_entries.extend(entries) + done = len(trade_entries) >= total + skip += len(entries) + else: + done = True + + trade_updates = [await self._parse_derivative_trade_entry(trade_info=trade_info) for trade_info in trade_entries] + + return trade_updates + + async def spot_order_updates(self, market_ids: List[str], start_time: float) -> List[OrderUpdate]: + done = False + skip = 0 + order_entries = [] + + while not done: + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_ORDERS_HISTORY_LIMIT_ID): + orders_response = await self.query_executor.get_historical_spot_orders( + market_ids=market_ids, + subaccount_id=self.portfolio_account_subaccount_id, + start_time=int(start_time * 1e3), + skip=skip, + ) + if "orders" in orders_response: + total = int(orders_response["paging"]["total"]) + entries = orders_response["orders"] + + order_entries.extend(entries) + done = len(order_entries) >= total + skip += len(entries) + else: + done = True + + order_updates = [await self._parse_order_entry(order_info=order_info) for order_info in order_entries] + + return order_updates + + async def perpetual_order_updates(self, market_ids: List[str], start_time: float) -> List[OrderUpdate]: + done = False + skip = 0 + order_entries = [] + + while not done: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_ORDERS_HISTORY_LIMIT_ID): + orders_response = await self.query_executor.get_historical_derivative_orders( + market_ids=market_ids, + subaccount_id=self.portfolio_account_subaccount_id, + start_time=int(start_time * 1e3), + skip=skip, + ) + if "orders" in orders_response: + total = int(orders_response["paging"]["total"]) + entries = orders_response["orders"] + + order_entries.extend(entries) + done = len(order_entries) >= total + skip += len(entries) + else: + done = True + + order_updates = [await self._parse_order_entry(order_info=order_info) for order_info in order_entries] + + return order_updates + + async def reset_order_hash_generator(self, active_orders: List[GatewayInFlightOrder]): + if not self.order_creation_lock.locked: + raise RuntimeError("The order creation lock should be acquired before resetting the order hash manager") + transactions_to_wait_before_reset = set() + for order in active_orders: + if order.creation_transaction_hash is not None and order.current_state == OrderState.PENDING_CREATE: + transactions_to_wait_before_reset.add(order.creation_transaction_hash) + transaction_wait_tasks = [ + asyncio.wait_for( + self._transaction_from_chain(tx_hash=transaction_hash, retries=2), + timeout=self.TRANSACTIONS_LOOKUP_TIMEOUT + ) + for transaction_hash in transactions_to_wait_before_reset + ] + await safe_gather(*transaction_wait_tasks, return_exceptions=True) + self._reset_order_hash_manager() + + async def get_spot_trading_fees(self) -> Dict[str, TradeFeeSchema]: + markets = await self.spot_markets() + fees = await self._create_trading_fees(markets=markets) + + return fees + + async def get_derivative_trading_fees(self) -> Dict[str, TradeFeeSchema]: + markets = await self.derivative_markets() + fees = await self._create_trading_fees(markets=markets) + + return fees + + async def funding_info(self, market_id: str) -> FundingInfo: + funding_rate = await self.last_funding_rate(market_id=market_id) + oracle_price = await self._oracle_price(market_id=market_id) + last_traded_price = await self.last_traded_price(market_id=market_id) + updated_market_info = await self._updated_derivative_market_info_for_id(market_id=market_id) + + funding_info = FundingInfo( + trading_pair=await self.trading_pair_for_market(market_id=market_id), + index_price=last_traded_price, # Use the last traded price as the index_price + mark_price=oracle_price, + next_funding_utc_timestamp=updated_market_info.next_funding_timestamp(), + rate=funding_rate, + ) + return funding_info + + async def last_funding_rate(self, market_id: str) -> Decimal: + async with self.throttler.execute_task(limit_id=CONSTANTS.FUNDING_RATES_LIMIT_ID): + response = await self.query_executor.get_funding_rates(market_id=market_id, limit=1) + rate = Decimal(response["fundingRates"][0]["rate"]) + + return rate + + async def last_funding_payment(self, market_id: str) -> Tuple[Decimal, float]: + async with self.throttler.execute_task(limit_id=CONSTANTS.FUNDING_PAYMENTS_LIMIT_ID): + response = await self.query_executor.get_funding_payments( + subaccount_id=self.portfolio_account_subaccount_id, + market_id=market_id, + limit=1 + ) + + last_payment = Decimal(-1) + last_timestamp = 0 + payments = response.get("payments", []) + + if len(payments) > 0: + last_payment = Decimal(payments[0]["amount"]) + last_timestamp = int(payments[0]["timestamp"]) * 1e-3 + + return last_payment, last_timestamp + + @abstractmethod + async def _initialize_timeout_height(self): + raise NotImplementedError + + @abstractmethod + def _sign_and_encode(self, transaction: Transaction) -> bytes: + raise NotImplementedError + + @abstractmethod + def _uses_default_portfolio_subaccount(self) -> bool: + raise NotImplementedError + + @abstractmethod + async def _calculate_order_hashes( + self, + spot_orders: List[GatewayInFlightOrder], + derivative_orders: [GatewayPerpetualInFlightOrder] + ) -> Tuple[List[str], List[str]]: + raise NotImplementedError + + @abstractmethod + def _reset_order_hash_manager(self): + raise NotImplementedError + + @abstractmethod + async def _order_creation_messages( + self, + spot_orders_to_create: List[GatewayInFlightOrder], + derivative_orders_to_create: List[GatewayPerpetualInFlightOrder], + ) -> Tuple[List[any_pb2.Any], List[str], List[str]]: + raise NotImplementedError + + @abstractmethod + async def _order_cancel_message( + self, + spot_orders_to_cancel: List[injective_exchange_tx_pb.OrderData], + derivative_orders_to_cancel: List[injective_exchange_tx_pb.OrderData] + ) -> any_pb2.Any: + raise NotImplementedError + + @abstractmethod + async def _all_subaccount_orders_cancel_message( + self, + spot_markets_ids: List[str], + derivative_markets_ids: List[str] + ) -> any_pb2.Any: + raise NotImplementedError + + @abstractmethod + async def _generate_injective_order_data(self, order: GatewayInFlightOrder, market_id: str) -> injective_exchange_tx_pb.OrderData: + raise NotImplementedError + + @abstractmethod + async def _updated_derivative_market_info_for_id(self, market_id: str) -> InjectiveDerivativeMarket: + raise NotImplementedError + + @abstractmethod + def _place_order_results( + self, + orders_to_create: List[GatewayInFlightOrder], + order_hashes: List[str], + misc_updates: Dict[str, Any], + exception: Optional[Exception] = None, + ) -> List[PlaceOrderResult]: + raise NotImplementedError + + async def _last_traded_price(self, market_id: str) -> Decimal: + price = Decimal("nan") + if market_id in await self.spot_market_and_trading_pair_map(): + market = await self.spot_market_info_for_id(market_id=market_id) + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_TRADES_LIMIT_ID): + trades_response = await self.query_executor.get_spot_trades( + market_ids=[market_id], + limit=1, + ) + trades = trades_response.get("trades", []) + if len(trades) > 0: + price = market.price_from_chain_format( + chain_price=Decimal(trades[0]["price"]["price"])) + + else: + market = await self.derivative_market_info_for_id(market_id=market_id) + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_TRADES_LIMIT_ID): + trades_response = await self.query_executor.get_derivative_trades( + market_ids=[market_id], + limit=1, + ) + trades = trades_response.get("trades", []) + if len(trades) > 0: + price = market.price_from_chain_format( + chain_price=Decimal(trades_response["trades"][0]["positionDelta"]["executionPrice"])) + + return price + + async def _transaction_from_chain(self, tx_hash: str, retries: int) -> int: + executed_tries = 0 + found = False + block_height = None + + while executed_tries < retries and not found: + executed_tries += 1 + try: + async with self.throttler.execute_task(limit_id=CONSTANTS.GET_TRANSACTION_CHAIN_LIMIT_ID): + block_height = await self.query_executor.get_tx_block_height(tx_hash=tx_hash) + found = True + except ValueError: + # No block found containing the transaction, continue the search + raise NotImplementedError + if executed_tries < retries and not found: + await self._sleep(CONSTANTS.EXPECTED_BLOCK_TIME) + + if not found: + raise ValueError(f"The transaction {tx_hash} is not included in any mined block") + + return block_height + + async def _oracle_price(self, market_id: str) -> Decimal: + market = await self.derivative_market_info_for_id(market_id=market_id) + async with self.throttler.execute_task(limit_id=CONSTANTS.ORACLE_PRICES_LIMIT_ID): + response = await self.query_executor.get_oracle_prices( + base_symbol=market.oracle_base(), + quote_symbol=market.oracle_quote(), + oracle_type=market.oracle_type(), + oracle_scale_factor=0, + ) + price = Decimal(response["price"]) + + return price + + def _spot_order_book_updates_stream(self, market_ids: List[str]): + stream = self.query_executor.spot_order_book_updates_stream(market_ids=market_ids) + return stream + + def _public_spot_trades_stream(self, market_ids: List[str]): + stream = self.query_executor.public_spot_trades_stream(market_ids=market_ids) + return stream + + def _derivative_order_book_updates_stream(self, market_ids: List[str]): + stream = self.query_executor.derivative_order_book_updates_stream(market_ids=market_ids) + return stream + + def _public_derivative_trades_stream(self, market_ids: List[str]): + stream = self.query_executor.public_derivative_trades_stream(market_ids=market_ids) + return stream + + def _oracle_prices_stream(self, oracle_base: str, oracle_quote: str, oracle_type: str): + stream = self.query_executor.oracle_prices_stream( + oracle_base=oracle_base, oracle_quote=oracle_quote, oracle_type=oracle_type + ) + return stream + + def _subaccount_positions_stream(self): + stream = self.query_executor.subaccount_positions_stream(subaccount_id=self.portfolio_account_subaccount_id) + return stream + + def _subaccount_balance_stream(self): + stream = self.query_executor.subaccount_balance_stream(subaccount_id=self.portfolio_account_subaccount_id) + return stream + + def _subaccount_spot_orders_stream(self, market_id: str): + stream = self.query_executor.subaccount_historical_spot_orders_stream( + market_id=market_id, subaccount_id=self.portfolio_account_subaccount_id + ) + return stream + + def _subaccount_derivative_orders_stream(self, market_id: str): + stream = self.query_executor.subaccount_historical_derivative_orders_stream( + market_id=market_id, subaccount_id=self.portfolio_account_subaccount_id + ) + return stream + + def _transactions_stream(self): + stream = self.query_executor.transactions_stream() + return stream + + async def _parse_spot_trade_entry(self, trade_info: Dict[str, Any]) -> TradeUpdate: + exchange_order_id: str = trade_info["orderHash"] + market = await self.spot_market_info_for_id(market_id=trade_info["marketId"]) + trading_pair = await self.trading_pair_for_market(market_id=trade_info["marketId"]) + trade_id: str = trade_info["tradeId"] + + price = market.price_from_chain_format(chain_price=Decimal(trade_info["price"]["price"])) + size = market.quantity_from_chain_format(chain_quantity=Decimal(trade_info["price"]["quantity"])) + trade_type = TradeType.BUY if trade_info["tradeDirection"] == "buy" else TradeType.SELL + is_taker: bool = trade_info["executionSide"] == "taker" + trade_time = int(trade_info["executedAt"]) * 1e-3 + + fee_amount = market.quote_token.value_from_chain_format(chain_value=Decimal(trade_info["fee"])) + fee = TradeFeeBase.new_spot_fee( + fee_schema=TradeFeeSchema(), + trade_type=trade_type, + percent_token=market.quote_token.symbol, + flat_fees=[TokenAmount(amount=fee_amount, token=market.quote_token.symbol)] + ) + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=None, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=trade_time, + fill_price=price, + fill_base_amount=size, + fill_quote_amount=size * price, + fee=fee, + is_taker=is_taker, + ) + + return trade_update + + async def _parse_derivative_trade_entry(self, trade_info: Dict[str, Any]) -> TradeUpdate: + exchange_order_id: str = trade_info["orderHash"] + market = await self.derivative_market_info_for_id(market_id=trade_info["marketId"]) + trading_pair = await self.trading_pair_for_market(market_id=trade_info["marketId"]) + trade_id: str = trade_info["tradeId"] + + price = market.price_from_chain_format(chain_price=Decimal(trade_info["positionDelta"]["executionPrice"])) + size = market.quantity_from_chain_format(chain_quantity=Decimal(trade_info["positionDelta"]["executionQuantity"])) + is_taker: bool = trade_info["executionSide"] == "taker" + trade_time = int(trade_info["executedAt"]) * 1e-3 + + fee_amount = market.quote_token.value_from_chain_format(chain_value=Decimal(trade_info["fee"])) + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=TradeFeeSchema(), + position_action=PositionAction.OPEN, # will be changed by the exchange class + percent_token=market.quote_token.symbol, + flat_fees=[TokenAmount(amount=fee_amount, token=market.quote_token.symbol)] + ) + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=None, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=trade_time, + fill_price=price, + fill_base_amount=size, + fill_quote_amount=size * price, + fee=fee, + is_taker=is_taker, + ) + + return trade_update + + async def _parse_order_entry(self, order_info: Dict[str, Any]) -> OrderUpdate: + exchange_order_id: str = order_info["orderHash"] + trading_pair = await self.trading_pair_for_market(market_id=order_info["marketId"]) + + status_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=int(order_info["updatedAt"]) * 1e-3, + new_state=CONSTANTS.ORDER_STATE_MAP[order_info["state"]], + client_order_id=None, + exchange_order_id=exchange_order_id, + ) + + return status_update + + async def _parse_position_update_event(self, event: Dict[str, Any]) -> PositionUpdateEvent: + market = await self.derivative_market_info_for_id(market_id=event["marketId"]) + trading_pair = await self.trading_pair_for_market(market_id=event["marketId"]) + + if "direction" in event: + position_side = PositionSide[event["direction"].upper()] + amount_sign = Decimal(-1) if position_side == PositionSide.SHORT else Decimal(1) + chain_entry_price = Decimal(event["entryPrice"]) + chain_mark_price = Decimal(event["markPrice"]) + chain_amount = Decimal(event["quantity"]) + chain_margin = Decimal(event["margin"]) + entry_price = market.price_from_chain_format(chain_price=chain_entry_price) + mark_price = market.price_from_chain_format(chain_price=chain_mark_price) + amount = market.quantity_from_chain_format(chain_quantity=chain_amount) + leverage = (chain_amount * chain_entry_price) / chain_margin + unrealized_pnl = (mark_price - entry_price) * amount * amount_sign + else: + position_side = None + entry_price = unrealized_pnl = amount = Decimal("0") + leverage = amount_sign = Decimal("1") + + parsed_event = PositionUpdateEvent( + timestamp=int(event["updatedAt"]) * 1e-3, + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount * amount_sign, + leverage=leverage, + ) + + return parsed_event + + async def _send_in_transaction(self, messages: List[any_pb2.Any]) -> Dict[str, Any]: + transaction = Transaction() + transaction.with_messages(*messages) + transaction.with_sequence(await self.trading_account_sequence()) + transaction.with_account_num(await self.trading_account_number()) + transaction.with_chain_id(self.injective_chain_id) + + signed_transaction_data = self._sign_and_encode(transaction=transaction) + + async with self.throttler.execute_task(limit_id=CONSTANTS.SIMULATE_TRANSACTION_LIMIT_ID): + try: + simulation_result = await self.query_executor.simulate_tx(tx_byte=signed_transaction_data) + except RuntimeError as simulation_ex: + if CONSTANTS.ACCOUNT_SEQUENCE_MISMATCH_ERROR in str(simulation_ex): + await self.initialize_trading_account() + raise + + composer = await self.composer() + gas_limit = int(simulation_result["gasInfo"]["gasUsed"]) + CONSTANTS.EXTRA_TRANSACTION_GAS + fee = [composer.Coin( + amount=gas_limit * CONSTANTS.DEFAULT_GAS_PRICE, + denom=self.fee_denom, + )] + + transaction.with_gas(gas_limit) + transaction.with_fee(fee) + transaction.with_memo("") + transaction.with_timeout_height(await self.timeout_height()) + + signed_transaction_data = self._sign_and_encode(transaction=transaction) + + async with self.throttler.execute_task(limit_id=CONSTANTS.SEND_TRANSACTION): + result = await self.query_executor.send_tx_sync_mode(tx_byte=signed_transaction_data) + + if CONSTANTS.ACCOUNT_SEQUENCE_MISMATCH_ERROR in result.get("rawLog", ""): + await self.initialize_trading_account() + + return result + + async def _listen_to_spot_order_book_updates(self, market_ids: List[str]): + await self._listen_stream_events( + stream_provider=partial(self._spot_order_book_updates_stream, market_ids=market_ids), + event_processor=self._process_order_book_update, + event_name_for_errors="spot order book", + ) + + async def _listen_to_public_spot_trades(self, market_ids: List[str]): + await self._listen_stream_events( + stream_provider=partial(self._public_spot_trades_stream, market_ids=market_ids), + event_processor=self._process_public_spot_trade_update, + event_name_for_errors="public spot trade", + ) + + async def _listen_to_derivative_order_book_updates(self, market_ids: List[str]): + await self._listen_stream_events( + stream_provider=partial(self._derivative_order_book_updates_stream, market_ids=market_ids), + event_processor=self._process_order_book_update, + event_name_for_errors="derivative order book", + ) + + async def _listen_to_public_derivative_trades(self, market_ids: List[str]): + await self._listen_stream_events( + stream_provider=partial(self._public_derivative_trades_stream, market_ids=market_ids), + event_processor=self._process_public_derivative_trade_update, + event_name_for_errors="public derivative trade", + ) + + async def _listen_to_funding_info_updates(self, market_id: str): + market = await self.derivative_market_info_for_id(market_id=market_id) + await self._listen_stream_events( + stream_provider=partial( + self._oracle_prices_stream, + oracle_base=market.oracle_base(), + oracle_quote=market.oracle_quote(), + oracle_type=market.oracle_type() + ), + event_processor=self._process_oracle_price_update, + event_name_for_errors="funding info", + market_id=market_id, + ) + + async def _listen_to_positions_updates(self): + await self._listen_stream_events( + stream_provider=self._subaccount_positions_stream, + event_processor=self._process_position_update, + event_name_for_errors="position", + ) + + async def _listen_to_account_balance_updates(self): + await self._listen_stream_events( + stream_provider=self._subaccount_balance_stream, + event_processor=self._process_subaccount_balance_update, + event_name_for_errors="balance", + ) + + async def _listen_to_subaccount_spot_order_updates(self, market_id: str): + await self._listen_stream_events( + stream_provider=partial(self._subaccount_spot_orders_stream, market_id=market_id), + event_processor=self._process_subaccount_order_update, + event_name_for_errors="subaccount spot order", + ) + + async def _listen_to_subaccount_derivative_order_updates(self, market_id: str): + await self._listen_stream_events( + stream_provider=partial(self._subaccount_derivative_orders_stream, market_id=market_id), + event_processor=self._process_subaccount_order_update, + event_name_for_errors="subaccount derivative order", + ) + + async def _listen_to_chain_transactions(self): + await self._listen_stream_events( + stream_provider=self._transactions_stream, + event_processor=self._process_transaction_update, + event_name_for_errors="transaction", + ) + + async def _listen_stream_events( + self, + stream_provider: Callable, + event_processor: Callable, + event_name_for_errors: str, + **kwargs): + while True: + self.logger().debug(f"Starting stream for {event_name_for_errors}") + try: + stream = stream_provider() + async for event in stream: + try: + await event_processor(event, **kwargs) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().warning(f"Invalid {event_name_for_errors} event format ({ex})\n{event}") + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error(f"Error while listening to {event_name_for_errors} stream, reconnecting ... ({ex})") + self.logger().debug(f"Reconnecting stream for {event_name_for_errors}") + + async def _process_order_book_update(self, order_book_update: Dict[str, Any]): + market_id = order_book_update["marketId"] + if market_id in await self.spot_market_and_trading_pair_map(): + market_info = await self.spot_market_info_for_id(market_id=market_id) + else: + market_info = await self.derivative_market_info_for_id(market_id=market_id) + + trading_pair = await self.trading_pair_for_market(market_id=market_id) + bids = [(market_info.price_from_chain_format(chain_price=Decimal(bid["price"])), + market_info.quantity_from_chain_format(chain_quantity=Decimal(bid["quantity"]))) + for bid in order_book_update.get("buys", [])] + asks = [(market_info.price_from_chain_format(chain_price=Decimal(ask["price"])), + market_info.quantity_from_chain_format(chain_quantity=Decimal(ask["quantity"]))) + for ask in order_book_update.get("sells", [])] + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": int(order_book_update["sequence"]), + "bids": bids, + "asks": asks, + } + diff_message = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=order_book_message_content, + timestamp=int(order_book_update["updatedAt"]) * 1e-3, + ) + self.publisher.trigger_event( + event_tag=OrderBookDataSourceEvent.DIFF_EVENT, message=diff_message + ) + + async def _process_public_spot_trade_update(self, trade_update: Dict[str, Any]): + market_id = trade_update["marketId"] + market_info = await self.spot_market_info_for_id(market_id=market_id) + + trading_pair = await self.trading_pair_for_market(market_id=market_id) + timestamp = int(trade_update["executedAt"]) * 1e-3 + trade_type = float(TradeType.BUY.value) if trade_update["tradeDirection"] == "buy" else float( + TradeType.SELL.value) + message_content = { + "trade_id": trade_update["tradeId"], + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": market_info.quantity_from_chain_format( + chain_quantity=Decimal(str(trade_update["price"]["quantity"]))), + "price": market_info.price_from_chain_format(chain_price=Decimal(str(trade_update["price"]["price"]))), + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=timestamp, + ) + self.publisher.trigger_event( + event_tag=OrderBookDataSourceEvent.TRADE_EVENT, message=trade_message + ) + + update = await self._parse_spot_trade_entry(trade_info=trade_update) + self.publisher.trigger_event(event_tag=MarketEvent.TradeUpdate, message=update) + + async def _process_public_derivative_trade_update(self, trade_update: Dict[str, Any]): + market_id = trade_update["marketId"] + market_info = await self.derivative_market_info_for_id(market_id=market_id) + + trading_pair = await self.trading_pair_for_market(market_id=market_id) + timestamp = int(trade_update["executedAt"]) * 1e-3 + trade_type = (float(TradeType.BUY.value) + if trade_update["positionDelta"]["tradeDirection"] == "buy" + else float(TradeType.SELL.value)) + message_content = { + "trade_id": trade_update["tradeId"], + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": market_info.quantity_from_chain_format( + chain_quantity=Decimal(str(trade_update["positionDelta"]["executionQuantity"]))), + "price": market_info.price_from_chain_format( + chain_price=Decimal(str(trade_update["positionDelta"]["executionPrice"]))), + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=timestamp, + ) + self.publisher.trigger_event( + event_tag=OrderBookDataSourceEvent.TRADE_EVENT, message=trade_message + ) + + update = await self._parse_derivative_trade_entry(trade_info=trade_update) + self.publisher.trigger_event(event_tag=MarketEvent.TradeUpdate, message=update) + + async def _process_oracle_price_update(self, oracle_price_update: Dict[str, Any], market_id: str): + trading_pair = await self.trading_pair_for_market(market_id=market_id) + funding_info = await self.funding_info(market_id=market_id) + funding_info_update = FundingInfoUpdate( + trading_pair=trading_pair, + index_price=funding_info.index_price, + mark_price=funding_info.mark_price, + next_funding_utc_timestamp=funding_info.next_funding_utc_timestamp, + rate=funding_info.rate, + ) + self.publisher.trigger_event(event_tag=MarketEvent.FundingInfo, message=funding_info_update) + + async def _process_position_update(self, position_event: Dict[str, Any]): + parsed_event = await self._parse_position_update_event(event=position_event) + self.publisher.trigger_event(event_tag=AccountEvent.PositionUpdate, message=parsed_event) + + async def _process_subaccount_balance_update(self, balance_event: Dict[str, Any]): + updated_token = await self.token(denom=balance_event["balance"]["denom"]) + if updated_token is not None: + if self._uses_default_portfolio_subaccount(): + token_balances = await self.all_account_balances() + total_balance = token_balances[updated_token.unique_symbol]["total_balance"] + available_balance = token_balances[updated_token.unique_symbol]["available_balance"] + else: + updated_total = balance_event["balance"]["deposit"].get("totalBalance") + total_balance = (updated_token.value_from_chain_format(chain_value=Decimal(updated_total)) + if updated_total is not None + else None) + updated_available = balance_event["balance"]["deposit"].get("availableBalance") + available_balance = (updated_token.value_from_chain_format(chain_value=Decimal(updated_available)) + if updated_available is not None + else None) + + balance_msg = BalanceUpdateEvent( + timestamp=int(balance_event["timestamp"]) * 1e3, + asset_name=updated_token.unique_symbol, + total_balance=total_balance, + available_balance=available_balance, + ) + self.publisher.trigger_event(event_tag=AccountEvent.BalanceEvent, message=balance_msg) + + async def _process_subaccount_order_update(self, order_event: Dict[str, Any]): + order_update = await self._parse_order_entry(order_info=order_event) + self.publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=order_update) + + async def _process_transaction_update(self, transaction_event: Dict[str, Any]): + self.publisher.trigger_event(event_tag=InjectiveEvent.ChainTransactionEvent, message=transaction_event) + + async def _create_spot_order_definition(self, order: GatewayInFlightOrder): + composer = await self.composer() + market_id = await self.market_id_for_spot_trading_pair(order.trading_pair) + definition = composer.SpotOrder( + market_id=market_id, + subaccount_id=self.portfolio_account_subaccount_id, + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER + ) + return definition + + async def _create_derivative_order_definition(self, order: GatewayPerpetualInFlightOrder): + composer = await self.composer() + market_id = await self.market_id_for_derivative_trading_pair(order.trading_pair) + definition = composer.DerivativeOrder( + market_id=market_id, + subaccount_id=self.portfolio_account_subaccount_id, + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + leverage=order.leverage, + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER, + is_reduce_only = order.position == PositionAction.CLOSE, + ) + return definition + + def _create_trading_rules( + self, markets: List[Union[InjectiveSpotMarket, InjectiveDerivativeMarket]] + ) -> List[TradingRule]: + trading_rules = [] + for market in markets: + try: + min_price_tick_size = market.min_price_tick_size() + min_quantity_tick_size = market.min_quantity_tick_size() + trading_rule = TradingRule( + trading_pair=market.trading_pair(), + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + trading_rules.append(trading_rule) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception(f"Error parsing the trading pair rule: {market.market_info}. Skipping...") + + return trading_rules + + async def _create_trading_fees( + self, markets: List[Union[InjectiveSpotMarket, InjectiveDerivativeMarket]] + ) -> Dict[str, TradeFeeSchema]: + fees = {} + for market in markets: + trading_pair = await self.trading_pair_for_market(market_id=market.market_id) + fees[trading_pair] = TradeFeeSchema( + percent_fee_token=market.quote_token.unique_symbol, + maker_percent_fee_decimal=market.maker_fee_rate(), + taker_percent_fee_decimal=market.taker_fee_rate(), + ) + + return fees + + def _time(self): + return time.time() + + async def _sleep(self, delay: float): + """ + Method created to enable tests to prevent processes from sleeping + """ + await asyncio.sleep(delay) diff --git a/hummingbot/connector/exchange/injective_v2/data_sources/injective_grantee_data_source.py b/hummingbot/connector/exchange/injective_v2/data_sources/injective_grantee_data_source.py new file mode 100644 index 0000000..d6df605 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/data_sources/injective_grantee_data_source.py @@ -0,0 +1,648 @@ +import asyncio +import base64 +import json +import re +from decimal import Decimal +from typing import Any, Dict, List, Mapping, Optional, Tuple + +from bidict import bidict +from google.protobuf import any_pb2 +from pyinjective import Transaction +from pyinjective.async_client import AsyncClient +from pyinjective.composer import Composer, injective_exchange_tx_pb +from pyinjective.core.network import Network +from pyinjective.orderhash import OrderHashManager +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource +from hummingbot.connector.exchange.injective_v2.injective_market import ( + InjectiveDerivativeMarket, + InjectiveSpotMarket, + InjectiveToken, +) +from hummingbot.connector.exchange.injective_v2.injective_query_executor import PythonSDKInjectiveQueryExecutor +from hummingbot.connector.gateway.common_types import PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder, GatewayPerpetualInFlightOrder +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.pubsub import PubSub +from hummingbot.logger import HummingbotLogger + + +class InjectiveGranteeDataSource(InjectiveDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + private_key: str, + subaccount_index: int, + granter_address: str, + granter_subaccount_index: int, + network: Network, + rate_limits: List[RateLimit], + use_secure_connection: bool = True): + self._network = network + self._client = AsyncClient( + network=self._network, + insecure=not use_secure_connection, + ) + self._composer = None + self._query_executor = PythonSDKInjectiveQueryExecutor(sdk_client=self._client) + + self._private_key = None + self._public_key = None + self._grantee_address = "" + self._grantee_subaccount_index = subaccount_index + self._granter_subaccount_id = "" + if private_key: + self._private_key = PrivateKey.from_hex(private_key) + self._public_key = self._private_key.to_public_key() + self._grantee_address = self._public_key.to_address() + self._grantee_subaccount_id = self._grantee_address.get_subaccount_id(index=subaccount_index) + + self._granter_address = None + self._granter_subaccount_id = "" + self._granter_subaccount_index = granter_subaccount_index + if granter_address: + self._granter_address = Address.from_acc_bech32(granter_address) + self._granter_subaccount_id = self._granter_address.get_subaccount_id(index=granter_subaccount_index) + + self._order_hash_manager: Optional[OrderHashManager] = None + self._publisher = PubSub() + self._last_received_message_time = 0 + self._order_creation_lock = asyncio.Lock() + self._throttler = AsyncThrottler(rate_limits=rate_limits) + + self._is_timeout_height_initialized = False + self._is_trading_account_initialized = False + self._markets_initialization_lock = asyncio.Lock() + self._spot_market_info_map: Optional[Dict[str, InjectiveSpotMarket]] = None + self._derivative_market_info_map: Optional[Dict[str, InjectiveDerivativeMarket]] = None + self._spot_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._derivative_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._tokens_map: Optional[Dict[str, InjectiveToken]] = None + self._token_symbol_symbol_and_denom_map: Optional[Mapping[str, str]] = None + + self._events_listening_tasks: List[asyncio.Task] = [] + + @property + def publisher(self): + return self._publisher + + @property + def query_executor(self): + return self._query_executor + + @property + def order_creation_lock(self) -> asyncio.Lock: + return self._order_creation_lock + + @property + def throttler(self): + return self._throttler + + @property + def portfolio_account_injective_address(self) -> str: + return self._granter_address.to_acc_bech32() + + @property + def portfolio_account_subaccount_id(self) -> str: + return self._granter_subaccount_id + + @property + def trading_account_injective_address(self) -> str: + return self._grantee_address.to_acc_bech32() + + @property + def injective_chain_id(self) -> str: + return self._network.chain_id + + @property + def fee_denom(self) -> str: + return self._network.fee_denom + + @property + def portfolio_account_subaccount_index(self) -> int: + return self._granter_subaccount_index + + @property + def network_name(self) -> str: + return self._network.string() + + async def composer(self) -> Composer: + if self._composer is None: + self._composer = await self._client.composer() + return self._composer + + def events_listening_tasks(self) -> List[asyncio.Task]: + return self._events_listening_tasks.copy() + + def add_listening_task(self, task: asyncio.Task): + self._events_listening_tasks.append(task) + + async def timeout_height(self) -> int: + if not self._is_timeout_height_initialized: + await self._initialize_timeout_height() + return self._client.timeout_height + + async def spot_market_and_trading_pair_map(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + return self._spot_market_and_trading_pair_map.copy() + + async def spot_market_info_for_id(self, market_id: str): + if self._spot_market_info_map is None: + async with self._markets_initialization_lock: + if self._spot_market_info_map is None: + await self.update_markets() + + return self._spot_market_info_map[market_id] + + async def derivative_market_and_trading_pair_map(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + return self._derivative_market_and_trading_pair_map.copy() + + async def derivative_market_info_for_id(self, market_id: str): + if self._derivative_market_info_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_info_map is None: + await self.update_markets() + + return self._derivative_market_info_map[market_id] + + async def trading_pair_for_market(self, market_id: str): + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + trading_pair = self._spot_market_and_trading_pair_map.get(market_id) + + if trading_pair is None: + trading_pair = self._derivative_market_and_trading_pair_map[market_id] + return trading_pair + + async def market_id_for_spot_trading_pair(self, trading_pair: str) -> str: + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return self._spot_market_and_trading_pair_map.inverse[trading_pair] + + async def market_id_for_derivative_trading_pair(self, trading_pair: str) -> str: + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return self._derivative_market_and_trading_pair_map.inverse[trading_pair] + + async def spot_markets(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._spot_market_info_map.values()) + + async def derivative_markets(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._derivative_market_info_map.values()) + + async def token(self, denom: str) -> InjectiveToken: + if self._tokens_map is None: + async with self._markets_initialization_lock: + if self._tokens_map is None: + await self.update_markets() + + return self._tokens_map.get(denom) + + def configure_throttler(self, throttler: AsyncThrottlerBase): + self._throttler = throttler + + async def trading_account_sequence(self) -> int: + if not self._is_trading_account_initialized: + await self.initialize_trading_account() + return self._client.get_sequence() + + async def trading_account_number(self) -> int: + if not self._is_trading_account_initialized: + await self.initialize_trading_account() + return self._client.get_number() + + async def stop(self): + await super().stop() + self._events_listening_tasks = [] + + async def initialize_trading_account(self): + await self._client.get_account(address=self.trading_account_injective_address) + self._is_trading_account_initialized = True + + async def order_hash_manager(self) -> OrderHashManager: + if self._order_hash_manager is None: + async with self.throttler.execute_task(limit_id=CONSTANTS.GET_SUBACCOUNT_LIMIT_ID): + self._order_hash_manager = OrderHashManager( + address=self._granter_address, + network=self._network, + subaccount_indexes=[self._granter_subaccount_index] + ) + return self._order_hash_manager + + def supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + async def update_markets(self): + self._tokens_map = {} + self._token_symbol_symbol_and_denom_map = bidict() + spot_markets_map = {} + derivative_markets_map = {} + spot_market_id_to_trading_pair = bidict() + derivative_market_id_to_trading_pair = bidict() + + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_MARKETS_LIMIT_ID): + markets = await self._query_executor.spot_markets(status="active") + + for market_info in markets: + try: + if "/" in market_info["ticker"]: + ticker_base, ticker_quote = market_info["ticker"].split("/") + else: + ticker_base = market_info["ticker"] + ticker_quote = None + base_token = self._token_from_market_info( + denom=market_info["baseDenom"], + token_meta=market_info["baseTokenMeta"], + candidate_symbol=ticker_base, + ) + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveSpotMarket( + market_id=market_info["marketId"], + base_token=base_token, + quote_token=quote_token, + market_info=market_info + ) + spot_market_id_to_trading_pair[market.market_id] = market.trading_pair() + spot_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The spot market {market_info['marketId']} will be excluded because it could not " + f"be parsed ({market_info})") + continue + + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + markets = await self._query_executor.derivative_markets(status="active") + for market_info in markets: + try: + market = self._parse_derivative_market_info(market_info=market_info) + if market.trading_pair() in derivative_market_id_to_trading_pair.inverse: + self.logger().debug( + f"The derivative market {market_info['marketId']} will be excluded because there is other" + f" market with trading pair {market.trading_pair()} ({market_info})") + continue + derivative_market_id_to_trading_pair[market.market_id] = market.trading_pair() + derivative_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The derivative market {market_info['marketId']} will be excluded because it could" + f" not be parsed ({market_info})") + continue + + self._spot_market_info_map = spot_markets_map + self._spot_market_and_trading_pair_map = spot_market_id_to_trading_pair + self._derivative_market_info_map = derivative_markets_map + self._derivative_market_and_trading_pair_map = derivative_market_id_to_trading_pair + + async def order_updates_for_transaction( + self, + transaction_hash: str, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None, + ) -> List[OrderUpdate]: + spot_orders = spot_orders or [] + perpetual_orders = perpetual_orders or [] + transaction_orders = spot_orders + perpetual_orders + + order_updates = [] + transaction_market_orders = [] + transaction_spot_orders = [] + transaction_derivative_orders = [] + + async with self.throttler.execute_task(limit_id=CONSTANTS.GET_TRANSACTION_INDEXER_LIMIT_ID): + transaction_info = await self.query_executor.get_tx_by_hash(tx_hash=transaction_hash) + + transaction_messages = json.loads(base64.b64decode(transaction_info["data"]["messages"]).decode()) + for message_info in transaction_messages[0]["value"]["msgs"]: + if message_info.get("@type") in CONSTANTS.MARKET_ORDER_MESSAGE_TYPES: + transaction_market_orders.append(message_info["order"]) + elif message_info.get("@type") == CONSTANTS.BATCH_UPDATE_ORDERS_MESSAGE_TYPE: + transaction_spot_orders.extend(message_info.get("spot_orders_to_create", [])) + transaction_derivative_orders.extend(message_info.get("derivative_orders_to_create", [])) + transaction_data = str(base64.b64decode(transaction_info["data"]["data"])) + order_hashes = re.findall(r"(0[xX][0-9a-fA-F]{64})", transaction_data) + + for order_info, order_hash in zip( + transaction_market_orders + transaction_spot_orders + transaction_derivative_orders, order_hashes + ): + market_id = order_info["market_id"] + if market_id in await self.spot_market_and_trading_pair_map(): + market = await self.spot_market_info_for_id(market_id=market_id) + else: + market = await self.derivative_market_info_for_id(market_id=market_id) + price = market.price_from_chain_format(chain_price=Decimal(order_info["order_info"]["price"])) + amount = market.quantity_from_chain_format(chain_quantity=Decimal(order_info["order_info"]["quantity"])) + trade_type = TradeType.BUY if "BUY" in order_info["order_type"] else TradeType.SELL + for transaction_order in transaction_orders: + if transaction_order in spot_orders: + market_id = await self.market_id_for_spot_trading_pair(trading_pair=transaction_order.trading_pair) + else: + market_id = await self.market_id_for_derivative_trading_pair(trading_pair=transaction_order.trading_pair) + if (market_id == order_info["market_id"] + and transaction_order.amount == amount + and transaction_order.price == price + and transaction_order.trade_type == trade_type): + new_state = OrderState.OPEN if transaction_order.is_pending_create else transaction_order.current_state + order_update = OrderUpdate( + trading_pair=transaction_order.trading_pair, + update_timestamp=self._time(), + new_state=new_state, + client_order_id=transaction_order.client_order_id, + exchange_order_id=order_hash, + ) + transaction_orders.remove(transaction_order) + order_updates.append(order_update) + self.logger().debug( + f"Exchange order id found for order {transaction_order.client_order_id} ({order_update})" + ) + break + + return order_updates + + def real_tokens_spot_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._spot_market_and_trading_pair_map is not None + and self._spot_market_info_map is not None): + market_id = self._spot_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._spot_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token.symbol, + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + def real_tokens_perpetual_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._derivative_market_and_trading_pair_map is not None + and self._derivative_market_info_map is not None): + market_id = self._derivative_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._derivative_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token_symbol(), + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + async def _initialize_timeout_height(self): + await self._client.sync_timeout_height() + self._is_timeout_height_initialized = True + + def _reset_order_hash_manager(self): + self._order_hash_manager = None + + def _sign_and_encode(self, transaction: Transaction) -> bytes: + sign_doc = transaction.get_sign_doc(self._public_key) + sig = self._private_key.sign(sign_doc.SerializeToString()) + tx_raw_bytes = transaction.get_tx_data(sig, self._public_key) + return tx_raw_bytes + + def _uses_default_portfolio_subaccount(self) -> bool: + return self._granter_subaccount_index == CONSTANTS.DEFAULT_SUBACCOUNT_INDEX + + def _token_from_market_info( + self, denom: str, token_meta: Dict[str, Any], candidate_symbol: Optional[str] = None + ) -> InjectiveToken: + token = self._tokens_map.get(denom) + if token is None: + unique_symbol = token_meta["symbol"] + if unique_symbol in self._token_symbol_symbol_and_denom_map: + if candidate_symbol is not None and candidate_symbol not in self._token_symbol_symbol_and_denom_map: + unique_symbol = candidate_symbol + else: + unique_symbol = token_meta["name"] + token = InjectiveToken( + denom=denom, + symbol=token_meta["symbol"], + unique_symbol=unique_symbol, + name=token_meta["name"], + decimals=token_meta["decimals"] + ) + self._tokens_map[denom] = token + self._token_symbol_symbol_and_denom_map[unique_symbol] = denom + + return token + + def _parse_derivative_market_info(self, market_info: Dict[str, Any]) -> InjectiveDerivativeMarket: + ticker_quote = None + if "/" in market_info["ticker"]: + _, ticker_quote = market_info["ticker"].split("/") + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveDerivativeMarket( + market_id=market_info["marketId"], + quote_token=quote_token, + market_info=market_info + ) + return market + + async def _updated_derivative_market_info_for_id(self, market_id: str) -> InjectiveDerivativeMarket: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + market_info = await self._query_executor.derivative_market(market_id=market_id) + + market = self._parse_derivative_market_info(market_info=market_info) + return market + + async def _calculate_order_hashes( + self, + spot_orders: List[GatewayInFlightOrder], + derivative_orders: [GatewayPerpetualInFlightOrder] + ) -> Tuple[List[str], List[str]]: + spot_hashes = [] + derivative_hashes = [] + + if len(spot_orders) > 0 or len(derivative_orders) > 0: + hash_manager = await self.order_hash_manager() + hash_manager_result = hash_manager.compute_order_hashes( + spot_orders=spot_orders, + derivative_orders=derivative_orders, + subaccount_index=self._granter_subaccount_index, + ) + spot_hashes = hash_manager_result.spot + derivative_hashes = hash_manager_result.derivative + + return spot_hashes, derivative_hashes + + async def _order_creation_messages( + self, + spot_orders_to_create: List[GatewayInFlightOrder], + derivative_orders_to_create: List[GatewayPerpetualInFlightOrder], + ) -> Tuple[List[any_pb2.Any], List[str], List[str]]: + composer = await self.composer() + spot_market_order_definitions = [] + derivative_market_order_definitions = [] + spot_order_definitions = [] + derivative_order_definitions = [] + all_messages = [] + + for order in spot_orders_to_create: + if order.order_type == OrderType.MARKET: + market_id = await self.market_id_for_spot_trading_pair(order.trading_pair) + creation_message = composer.MsgCreateSpotMarketOrder( + sender=self.portfolio_account_injective_address, + market_id=market_id, + subaccount_id=self.portfolio_account_subaccount_id, + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + is_buy=order.trade_type == TradeType.BUY, + ) + spot_market_order_definitions.append(creation_message.order) + all_messages.append(creation_message) + else: + order_definition = await self._create_spot_order_definition(order=order) + spot_order_definitions.append(order_definition) + + for order in derivative_orders_to_create: + if order.order_type == OrderType.MARKET: + market_id = await self.market_id_for_derivative_trading_pair(order.trading_pair) + creation_message = composer.MsgCreateDerivativeMarketOrder( + sender=self.portfolio_account_injective_address, + market_id=market_id, + subaccount_id=self.portfolio_account_subaccount_id, + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + leverage=order.leverage, + is_buy=order.trade_type == TradeType.BUY, + is_reduce_only=order.position == PositionAction.CLOSE, + ) + derivative_market_order_definitions.append(creation_message.order) + all_messages.append(creation_message) + else: + order_definition = await self._create_derivative_order_definition(order=order) + derivative_order_definitions.append(order_definition) + + market_spot_hashes, market_derivative_hashes = await self._calculate_order_hashes( + spot_orders=spot_market_order_definitions, + derivative_orders=derivative_market_order_definitions, + ) + limit_spot_hashes, limit_derivative_hashes = await self._calculate_order_hashes( + spot_orders=spot_order_definitions, + derivative_orders=derivative_order_definitions, + ) + spot_order_hashes = market_spot_hashes + limit_spot_hashes + derivative_order_hashes = market_derivative_hashes + limit_derivative_hashes + + if len(limit_spot_hashes) > 0 or len(limit_derivative_hashes) > 0: + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + spot_orders_to_create=spot_order_definitions, + derivative_orders_to_create=derivative_order_definitions, + ) + all_messages.append(message) + + delegated_message = composer.MsgExec( + grantee=self.trading_account_injective_address, + msgs=all_messages + ) + + return [delegated_message], spot_order_hashes, derivative_order_hashes + + async def _order_cancel_message( + self, + spot_orders_to_cancel: List[injective_exchange_tx_pb.OrderData], + derivative_orders_to_cancel: List[injective_exchange_tx_pb.OrderData] + ) -> any_pb2.Any: + composer = await self.composer() + + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + spot_orders_to_cancel=spot_orders_to_cancel, + derivative_orders_to_cancel=derivative_orders_to_cancel, + ) + delegated_message = composer.MsgExec( + grantee=self.trading_account_injective_address, + msgs=[message] + ) + return delegated_message + + async def _all_subaccount_orders_cancel_message( + self, + spot_markets_ids: List[str], + derivative_markets_ids: List[str] + ) -> any_pb2.Any: + composer = await self.composer() + + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + subaccount_id=self.portfolio_account_subaccount_id, + spot_market_ids_to_cancel_all=spot_markets_ids, + derivative_market_ids_to_cancel_all=derivative_markets_ids, + ) + delegated_message = composer.MsgExec( + grantee=self.trading_account_injective_address, + msgs=[message] + ) + return delegated_message + + async def _generate_injective_order_data(self, order: GatewayInFlightOrder, market_id: str) -> injective_exchange_tx_pb.OrderData: + composer = await self.composer() + order_data = composer.OrderData( + market_id=market_id, + subaccount_id=self.portfolio_account_subaccount_id, + order_hash=order.exchange_order_id, + order_direction="buy" if order.trade_type == TradeType.BUY else "sell", + order_type="market" if order.order_type == OrderType.MARKET else "limit", + ) + + return order_data + + def _place_order_results( + self, + orders_to_create: List[GatewayInFlightOrder], + order_hashes: List[str], + misc_updates: Dict[str, Any], + exception: Optional[Exception] = None, + ) -> List[PlaceOrderResult]: + return [ + PlaceOrderResult( + update_timestamp=self._time(), + client_order_id=order.client_order_id, + exchange_order_id=order_hash, + trading_pair=order.trading_pair, + misc_updates=misc_updates, + exception=exception + ) for order, order_hash in zip(orders_to_create, order_hashes) + ] diff --git a/hummingbot/connector/exchange/injective_v2/data_sources/injective_read_only_data_source.py b/hummingbot/connector/exchange/injective_v2/data_sources/injective_read_only_data_source.py new file mode 100644 index 0000000..a1cb8f5 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/data_sources/injective_read_only_data_source.py @@ -0,0 +1,414 @@ +import asyncio +from typing import Any, Dict, List, Mapping, Optional, Tuple + +from bidict import bidict +from google.protobuf import any_pb2 +from pyinjective import Transaction +from pyinjective.async_client import AsyncClient +from pyinjective.composer import Composer, injective_exchange_tx_pb +from pyinjective.core.network import Network + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource +from hummingbot.connector.exchange.injective_v2.injective_market import ( + InjectiveDerivativeMarket, + InjectiveSpotMarket, + InjectiveToken, +) +from hummingbot.connector.exchange.injective_v2.injective_query_executor import PythonSDKInjectiveQueryExecutor +from hummingbot.connector.gateway.common_types import PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder, GatewayPerpetualInFlightOrder +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import OrderUpdate +from hummingbot.core.pubsub import PubSub +from hummingbot.logger import HummingbotLogger + + +class InjectiveReadOnlyDataSource(InjectiveDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + network: Network, + rate_limits: List[RateLimit], + use_secure_connection: bool = True): + self._network = network + self._client = AsyncClient( + network=self._network, + insecure=not use_secure_connection, + ) + self._composer = None + self._query_executor = PythonSDKInjectiveQueryExecutor(sdk_client=self._client) + + self._publisher = PubSub() + self._last_received_message_time = 0 + self._throttler = AsyncThrottler(rate_limits=rate_limits) + + self._markets_initialization_lock = asyncio.Lock() + self._spot_market_info_map: Optional[Dict[str, InjectiveSpotMarket]] = None + self._derivative_market_info_map: Optional[Dict[str, InjectiveDerivativeMarket]] = None + self._spot_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._derivative_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._tokens_map: Optional[Dict[str, InjectiveToken]] = None + self._token_symbol_symbol_and_denom_map: Optional[Mapping[str, str]] = None + + self._events_listening_tasks: List[asyncio.Task] = [] + + @property + def publisher(self): + return self._publisher + + @property + def query_executor(self): + return self._query_executor + + @property + def order_creation_lock(self) -> asyncio.Lock: + return None + + @property + def throttler(self): + return self._throttler + + @property + def portfolio_account_injective_address(self) -> str: + raise NotImplementedError + + @property + def portfolio_account_subaccount_id(self) -> str: + raise NotImplementedError + + @property + def trading_account_injective_address(self) -> str: + raise NotImplementedError + + @property + def injective_chain_id(self) -> str: + return self._network.chain_id + + @property + def fee_denom(self) -> str: + return self._network.fee_denom + + @property + def portfolio_account_subaccount_index(self) -> int: + raise NotImplementedError + + @property + def network_name(self) -> str: + return self._network.string() + + async def composer(self) -> Composer: + if self._composer is None: + self._composer = await self._client.composer() + return self._composer + + async def timeout_height(self) -> int: + raise NotImplementedError + + async def spot_market_and_trading_pair_map(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + return self._spot_market_and_trading_pair_map.copy() + + async def spot_market_info_for_id(self, market_id: str): + if self._spot_market_info_map is None: + async with self._markets_initialization_lock: + if self._spot_market_info_map is None: + await self.update_markets() + + return self._spot_market_info_map[market_id] + + async def derivative_market_and_trading_pair_map(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + return self._derivative_market_and_trading_pair_map.copy() + + async def derivative_market_info_for_id(self, market_id: str): + if self._derivative_market_info_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_info_map is None: + await self.update_markets() + + return self._derivative_market_info_map[market_id] + + async def trading_pair_for_market(self, market_id: str): + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + trading_pair = self._spot_market_and_trading_pair_map.get(market_id) + + if trading_pair is None: + trading_pair = self._derivative_market_and_trading_pair_map[market_id] + return trading_pair + + async def market_id_for_spot_trading_pair(self, trading_pair: str) -> str: + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return self._spot_market_and_trading_pair_map.inverse[trading_pair] + + async def market_id_for_derivative_trading_pair(self, trading_pair: str) -> str: + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return self._derivative_market_and_trading_pair_map.inverse[trading_pair] + + async def spot_markets(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._spot_market_info_map.values()) + + async def derivative_markets(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._derivative_market_info_map.values()) + + async def token(self, denom: str) -> InjectiveToken: + if self._tokens_map is None: + async with self._markets_initialization_lock: + if self._tokens_map is None: + await self.update_markets() + + return self._tokens_map.get(denom) + + def events_listening_tasks(self) -> List[asyncio.Task]: + return self._events_listening_tasks.copy() + + def add_listening_task(self, task: asyncio.Task): + self._events_listening_tasks.append(task) + + def configure_throttler(self, throttler: AsyncThrottlerBase): + self._throttler = throttler + + async def trading_account_sequence(self) -> int: + raise NotImplementedError + + async def trading_account_number(self) -> int: + raise NotImplementedError + + async def initialize_trading_account(self): + raise NotImplementedError + + async def update_markets(self): + self._tokens_map = {} + self._token_symbol_symbol_and_denom_map = bidict() + spot_markets_map = {} + derivative_markets_map = {} + spot_market_id_to_trading_pair = bidict() + derivative_market_id_to_trading_pair = bidict() + + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_MARKETS_LIMIT_ID): + markets = await self._query_executor.spot_markets(status="active") + + for market_info in markets: + try: + if "/" in market_info["ticker"]: + ticker_base, ticker_quote = market_info["ticker"].split("/") + else: + ticker_base = market_info["ticker"] + ticker_quote = None + base_token = self._token_from_market_info( + denom=market_info["baseDenom"], + token_meta=market_info["baseTokenMeta"], + candidate_symbol=ticker_base, + ) + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveSpotMarket( + market_id=market_info["marketId"], + base_token=base_token, + quote_token=quote_token, + market_info=market_info + ) + spot_market_id_to_trading_pair[market.market_id] = market.trading_pair() + spot_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The spot market {market_info['marketId']} will be excluded because it could not " + f"be parsed ({market_info})") + continue + + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + markets = await self._query_executor.derivative_markets(status="active") + for market_info in markets: + try: + market = self._parse_derivative_market_info(market_info=market_info) + if market.trading_pair() in derivative_market_id_to_trading_pair.inverse: + self.logger().debug( + f"The derivative market {market_info['marketId']} will be excluded because there is other" + f" market with trading pair {market.trading_pair()} ({market_info})") + continue + derivative_market_id_to_trading_pair[market.market_id] = market.trading_pair() + derivative_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The derivative market {market_info['marketId']} will be excluded because it could" + f" not be parsed ({market_info})") + continue + + self._spot_market_info_map = spot_markets_map + self._spot_market_and_trading_pair_map = spot_market_id_to_trading_pair + self._derivative_market_info_map = derivative_markets_map + self._derivative_market_and_trading_pair_map = derivative_market_id_to_trading_pair + + def real_tokens_spot_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._spot_market_and_trading_pair_map is not None + and self._spot_market_info_map is not None): + market_id = self._spot_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._spot_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token.symbol, + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + def real_tokens_perpetual_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._derivative_market_and_trading_pair_map is not None + and self._derivative_market_info_map is not None): + market_id = self._derivative_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._derivative_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token_symbol(), + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + async def order_updates_for_transaction( + self, + transaction_hash: str, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None + ) -> List[OrderUpdate]: + raise NotImplementedError + + def supported_order_types(self) -> List[OrderType]: + return [] + + async def _initialize_timeout_height(self): + raise NotImplementedError + + def _sign_and_encode(self, transaction: Transaction) -> bytes: + raise NotImplementedError + + def _uses_default_portfolio_subaccount(self) -> bool: + raise NotImplementedError + + async def _calculate_order_hashes( + self, + spot_orders: List[GatewayInFlightOrder], + derivative_orders: [GatewayPerpetualInFlightOrder]) -> Tuple[List[str], List[str]]: + raise NotImplementedError + + def _reset_order_hash_manager(self): + raise NotImplementedError + + async def _order_creation_messages( + self, + spot_orders_to_create: List[GatewayInFlightOrder], + derivative_orders_to_create: List[GatewayPerpetualInFlightOrder] + ) -> Tuple[List[any_pb2.Any], List[str], List[str]]: + raise NotImplementedError + + async def _order_cancel_message( + self, + spot_orders_to_cancel: List[injective_exchange_tx_pb.OrderData], + derivative_orders_to_cancel: List[injective_exchange_tx_pb.OrderData] + ) -> any_pb2.Any: + raise NotImplementedError + + async def _all_subaccount_orders_cancel_message( + self, + spot_orders_to_cancel: List[injective_exchange_tx_pb.OrderData], + derivative_orders_to_cancel: List[injective_exchange_tx_pb.OrderData] + ) -> any_pb2.Any: + raise NotImplementedError + + async def _generate_injective_order_data( + self, + order: GatewayInFlightOrder, + market_id: str, + ) -> injective_exchange_tx_pb.OrderData: + raise NotImplementedError + + async def _updated_derivative_market_info_for_id(self, market_id: str) -> InjectiveDerivativeMarket: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + market_info = await self._query_executor.derivative_market(market_id=market_id) + + market = self._parse_derivative_market_info(market_info=market_info) + return market + + def _place_order_results( + self, + orders_to_create: List[GatewayInFlightOrder], + order_hashes: List[str], + misc_updates: Dict[str, Any], + exception: Optional[Exception] = None + ) -> List[PlaceOrderResult]: + raise NotImplementedError + + def _token_from_market_info( + self, denom: str, token_meta: Dict[str, Any], candidate_symbol: Optional[str] = None + ) -> InjectiveToken: + token = self._tokens_map.get(denom) + if token is None: + unique_symbol = token_meta["symbol"] + if unique_symbol in self._token_symbol_symbol_and_denom_map: + if candidate_symbol is not None and candidate_symbol not in self._token_symbol_symbol_and_denom_map: + unique_symbol = candidate_symbol + else: + unique_symbol = token_meta["name"] + token = InjectiveToken( + denom=denom, + symbol=token_meta["symbol"], + unique_symbol=unique_symbol, + name=token_meta["name"], + decimals=token_meta["decimals"] + ) + self._tokens_map[denom] = token + self._token_symbol_symbol_and_denom_map[unique_symbol] = denom + + return token + + def _parse_derivative_market_info(self, market_info: Dict[str, Any]) -> InjectiveDerivativeMarket: + ticker_quote = None + if "/" in market_info["ticker"]: + _, ticker_quote = market_info["ticker"].split("/") + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveDerivativeMarket( + market_id=market_info["marketId"], + quote_token=quote_token, + market_info=market_info + ) + return market diff --git a/hummingbot/connector/exchange/injective_v2/data_sources/injective_vaults_data_source.py b/hummingbot/connector/exchange/injective_v2/data_sources/injective_vaults_data_source.py new file mode 100644 index 0000000..f427694 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/data_sources/injective_vaults_data_source.py @@ -0,0 +1,705 @@ +import asyncio +import base64 +import json +import re +from decimal import Decimal +from typing import Any, Dict, List, Mapping, Optional, Tuple, Union + +from bidict import bidict +from google.protobuf import any_pb2, json_format +from pyinjective import Transaction +from pyinjective.async_client import AsyncClient +from pyinjective.composer import Composer, injective_exchange_tx_pb +from pyinjective.core.network import Network +from pyinjective.orderhash import OrderHashManager +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource +from hummingbot.connector.exchange.injective_v2.injective_market import ( + InjectiveDerivativeMarket, + InjectiveSpotMarket, + InjectiveToken, +) +from hummingbot.connector.exchange.injective_v2.injective_query_executor import PythonSDKInjectiveQueryExecutor +from hummingbot.connector.gateway.common_types import PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder, GatewayPerpetualInFlightOrder +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.pubsub import PubSub +from hummingbot.logger import HummingbotLogger + + +class InjectiveVaultsDataSource(InjectiveDataSource): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + private_key: str, + subaccount_index: int, + vault_contract_address: str, + vault_subaccount_index: int, + network: Network, + rate_limits: List[RateLimit], + use_secure_connection: bool = True): + self._network = network + self._client = AsyncClient( + network=self._network, + insecure=not use_secure_connection, + ) + self._composer = None + self._query_executor = PythonSDKInjectiveQueryExecutor(sdk_client=self._client) + + self._private_key = None + self._public_key = None + self._vault_admin_address = "" + self._vault_admin_subaccount_index = subaccount_index + self._vault_admin_subaccount_id = "" + if private_key: + self._private_key = PrivateKey.from_hex(private_key) + self._public_key = self._private_key.to_public_key() + self._vault_admin_address = self._public_key.to_address() + self._vault_admin_subaccount_id = self._vault_admin_address.get_subaccount_id(index=subaccount_index) + + self._vault_contract_address = None + self._vault_subaccount_id = "" + self._vault_subaccount_index = vault_subaccount_index + if vault_contract_address: + self._vault_contract_address = Address.from_acc_bech32(vault_contract_address) + self._vault_subaccount_id = self._vault_contract_address.get_subaccount_id(index=vault_subaccount_index) + + self._order_hash_manager: Optional[OrderHashManager] = None + self._publisher = PubSub() + self._last_received_message_time = 0 + self._order_creation_lock = asyncio.Lock() + self._throttler = AsyncThrottler(rate_limits=rate_limits) + + self._is_timeout_height_initialized = False + self._is_trading_account_initialized = False + self._markets_initialization_lock = asyncio.Lock() + self._spot_market_info_map: Optional[Dict[str, InjectiveSpotMarket]] = None + self._derivative_market_info_map: Optional[Dict[str, InjectiveDerivativeMarket]] = None + self._spot_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._derivative_market_and_trading_pair_map: Optional[Mapping[str, str]] = None + self._tokens_map: Optional[Dict[str, InjectiveToken]] = None + self._token_symbol_symbol_and_denom_map: Optional[Mapping[str, str]] = None + + self._events_listening_tasks: List[asyncio.Task] = [] + + @property + def publisher(self): + return self._publisher + + @property + def query_executor(self): + return self._query_executor + + @property + def order_creation_lock(self) -> asyncio.Lock: + return self._order_creation_lock + + @property + def throttler(self): + return self._throttler + + @property + def portfolio_account_injective_address(self) -> str: + return self._vault_contract_address.to_acc_bech32() + + @property + def portfolio_account_subaccount_id(self) -> str: + return self._vault_subaccount_id + + @property + def trading_account_injective_address(self) -> str: + return self._vault_admin_address.to_acc_bech32() + + @property + def injective_chain_id(self) -> str: + return self._network.chain_id + + @property + def fee_denom(self) -> str: + return self._network.fee_denom + + @property + def portfolio_account_subaccount_index(self) -> int: + return self._vault_subaccount_index + + @property + def network_name(self) -> str: + return self._network.string() + + async def composer(self) -> Composer: + if self._composer is None: + self._composer = await self._client.composer() + return self._composer + + def events_listening_tasks(self) -> List[asyncio.Task]: + return self._events_listening_tasks.copy() + + def add_listening_task(self, task: asyncio.Task): + self._events_listening_tasks.append(task) + + async def timeout_height(self) -> int: + if not self._is_timeout_height_initialized: + await self._initialize_timeout_height() + return self._client.timeout_height + + async def spot_market_and_trading_pair_map(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + return self._spot_market_and_trading_pair_map.copy() + + async def spot_market_info_for_id(self, market_id: str): + if self._spot_market_info_map is None: + async with self._markets_initialization_lock: + if self._spot_market_info_map is None: + await self.update_markets() + + return self._spot_market_info_map[market_id] + + async def derivative_market_and_trading_pair_map(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + return self._derivative_market_and_trading_pair_map.copy() + + async def derivative_market_info_for_id(self, market_id: str): + if self._derivative_market_info_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_info_map is None: + await self.update_markets() + + return self._derivative_market_info_map[market_id] + + async def trading_pair_for_market(self, market_id: str): + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None or self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + trading_pair = self._spot_market_and_trading_pair_map.get(market_id) + + if trading_pair is None: + trading_pair = self._derivative_market_and_trading_pair_map[market_id] + return trading_pair + + async def market_id_for_spot_trading_pair(self, trading_pair: str) -> str: + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return self._spot_market_and_trading_pair_map.inverse[trading_pair] + + async def market_id_for_derivative_trading_pair(self, trading_pair: str) -> str: + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return self._derivative_market_and_trading_pair_map.inverse[trading_pair] + + async def spot_markets(self): + if self._spot_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._spot_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._spot_market_info_map.values()) + + async def derivative_markets(self): + if self._derivative_market_and_trading_pair_map is None: + async with self._markets_initialization_lock: + if self._derivative_market_and_trading_pair_map is None: + await self.update_markets() + + return list(self._derivative_market_info_map.values()) + + async def token(self, denom: str) -> InjectiveToken: + if self._tokens_map is None: + async with self._markets_initialization_lock: + if self._tokens_map is None: + await self.update_markets() + + return self._tokens_map.get(denom) + + def configure_throttler(self, throttler: AsyncThrottlerBase): + self._throttler = throttler + + async def trading_account_sequence(self) -> int: + if not self._is_trading_account_initialized: + await self.initialize_trading_account() + return self._client.get_sequence() + + async def trading_account_number(self) -> int: + if not self._is_trading_account_initialized: + await self.initialize_trading_account() + return self._client.get_number() + + async def stop(self): + await super().stop() + self._events_listening_tasks = [] + + async def initialize_trading_account(self): + await self._client.get_account(address=self.trading_account_injective_address) + self._is_trading_account_initialized = True + + def supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def update_markets(self): + self._tokens_map = {} + self._token_symbol_symbol_and_denom_map = bidict() + spot_markets_map = {} + derivative_markets_map = {} + spot_market_id_to_trading_pair = bidict() + derivative_market_id_to_trading_pair = bidict() + + async with self.throttler.execute_task(limit_id=CONSTANTS.SPOT_MARKETS_LIMIT_ID): + markets = await self._query_executor.spot_markets(status="active") + + for market_info in markets: + try: + if "/" in market_info["ticker"]: + ticker_base, ticker_quote = market_info["ticker"].split("/") + else: + ticker_base = market_info["ticker"] + ticker_quote = None + base_token = self._token_from_market_info( + denom=market_info["baseDenom"], + token_meta=market_info["baseTokenMeta"], + candidate_symbol=ticker_base, + ) + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveSpotMarket( + market_id=market_info["marketId"], + base_token=base_token, + quote_token=quote_token, + market_info=market_info + ) + spot_market_id_to_trading_pair[market.market_id] = market.trading_pair() + spot_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The spot market {market_info['marketId']} will be excluded because it could not " + f"be parsed ({market_info})") + continue + + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + markets = await self._query_executor.derivative_markets(status="active") + for market_info in markets: + try: + market = self._parse_derivative_market_info(market_info=market_info) + if market.trading_pair() in derivative_market_id_to_trading_pair.inverse: + self.logger().debug( + f"The derivative market {market_info['marketId']} will be excluded because there is other" + f" market with trading pair {market.trading_pair()} ({market_info})") + continue + derivative_market_id_to_trading_pair[market.market_id] = market.trading_pair() + derivative_markets_map[market.market_id] = market + except KeyError: + self.logger().debug(f"The derivative market {market_info['marketId']} will be excluded because it could" + f" not be parsed ({market_info})") + continue + + self._spot_market_info_map = spot_markets_map + self._spot_market_and_trading_pair_map = spot_market_id_to_trading_pair + self._derivative_market_info_map = derivative_markets_map + self._derivative_market_and_trading_pair_map = derivative_market_id_to_trading_pair + + async def order_updates_for_transaction( + self, + transaction_hash: str, + spot_orders: Optional[List[GatewayInFlightOrder]] = None, + perpetual_orders: Optional[List[GatewayPerpetualInFlightOrder]] = None, + ) -> List[OrderUpdate]: + spot_orders = spot_orders or [] + perpetual_orders = perpetual_orders or [] + + async with self.throttler.execute_task(limit_id=CONSTANTS.GET_TRANSACTION_INDEXER_LIMIT_ID): + transaction_info = await self.query_executor.get_tx_by_hash(tx_hash=transaction_hash) + + transaction_messages = json.loads(base64.b64decode(transaction_info["data"]["messages"]).decode()) + transaction_spot_orders = transaction_messages[0]["value"]["msg"]["admin_execute_message"]["injective_message"]["custom"]["msg_data"]["batch_update_orders"]["spot_orders_to_create"] + transaction_derivative_orders = transaction_messages[0]["value"]["msg"]["admin_execute_message"]["injective_message"]["custom"]["msg_data"]["batch_update_orders"]["derivative_orders_to_create"] + + spot_order_hashes = self._order_hashes_from_transaction( + transaction_info=transaction_info, + hashes_group_key="spot_order_hashes", + ) + derivative_order_hashes = self._order_hashes_from_transaction( + transaction_info=transaction_info, + hashes_group_key="derivative_order_hashes", + ) + + spot_order_updates = await self._transaction_order_updates( + orders=spot_orders, + transaction_orders_info=transaction_spot_orders, + order_hashes=spot_order_hashes + ) + + derivative_order_updates = await self._transaction_order_updates( + orders=perpetual_orders, + transaction_orders_info=transaction_derivative_orders, + order_hashes=derivative_order_hashes + ) + + return spot_order_updates + derivative_order_updates + + def real_tokens_spot_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._spot_market_and_trading_pair_map is not None + and self._spot_market_info_map is not None): + market_id = self._spot_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._spot_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token.symbol, + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + def real_tokens_perpetual_trading_pair(self, unique_trading_pair: str) -> str: + resulting_trading_pair = unique_trading_pair + if (self._derivative_market_and_trading_pair_map is not None + and self._derivative_market_info_map is not None): + market_id = self._derivative_market_and_trading_pair_map.inverse.get(unique_trading_pair) + market = self._derivative_market_info_map.get(market_id) + if market is not None: + resulting_trading_pair = combine_to_hb_trading_pair( + base=market.base_token_symbol(), + quote=market.quote_token.symbol, + ) + + return resulting_trading_pair + + async def _initialize_timeout_height(self): + await self._client.sync_timeout_height() + self._is_timeout_height_initialized = True + + def _reset_order_hash_manager(self): + # The vaults data source does not calculate locally the order hashes + pass + + def _sign_and_encode(self, transaction: Transaction) -> bytes: + sign_doc = transaction.get_sign_doc(self._public_key) + sig = self._private_key.sign(sign_doc.SerializeToString()) + tx_raw_bytes = transaction.get_tx_data(sig, self._public_key) + return tx_raw_bytes + + def _uses_default_portfolio_subaccount(self) -> bool: + return self._vault_subaccount_index == CONSTANTS.DEFAULT_SUBACCOUNT_INDEX + + def _token_from_market_info( + self, denom: str, token_meta: Dict[str, Any], candidate_symbol: Optional[str] = None + ) -> InjectiveToken: + token = self._tokens_map.get(denom) + if token is None: + unique_symbol = token_meta["symbol"] + if unique_symbol in self._token_symbol_symbol_and_denom_map: + if candidate_symbol is not None and candidate_symbol not in self._token_symbol_symbol_and_denom_map: + unique_symbol = candidate_symbol + else: + unique_symbol = token_meta["name"] + token = InjectiveToken( + denom=denom, + symbol=token_meta["symbol"], + unique_symbol=unique_symbol, + name=token_meta["name"], + decimals=token_meta["decimals"] + ) + self._tokens_map[denom] = token + self._token_symbol_symbol_and_denom_map[unique_symbol] = denom + + return token + + def _parse_derivative_market_info(self, market_info: Dict[str, Any]) -> InjectiveDerivativeMarket: + ticker_quote = None + if "/" in market_info["ticker"]: + _, ticker_quote = market_info["ticker"].split("/") + quote_token = self._token_from_market_info( + denom=market_info["quoteDenom"], + token_meta=market_info["quoteTokenMeta"], + candidate_symbol=ticker_quote, + ) + market = InjectiveDerivativeMarket( + market_id=market_info["marketId"], + quote_token=quote_token, + market_info=market_info + ) + return market + + async def _updated_derivative_market_info_for_id(self, market_id: str) -> InjectiveDerivativeMarket: + async with self.throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + market_info = await self._query_executor.derivative_market(market_id=market_id) + + market = self._parse_derivative_market_info(market_info=market_info) + return market + + async def _calculate_order_hashes( + self, + spot_orders: List[GatewayInFlightOrder], + derivative_orders: [GatewayPerpetualInFlightOrder] + ) -> Tuple[List[str], List[str]]: + raise NotImplementedError + + async def _order_creation_messages( + self, + spot_orders_to_create: List[GatewayInFlightOrder], + derivative_orders_to_create: List[GatewayPerpetualInFlightOrder], + ) -> Tuple[List[any_pb2.Any], List[str], List[str]]: + composer = await self.composer() + spot_order_definitions = [] + derivative_order_definitions = [] + + for order in spot_orders_to_create: + order_definition = await self._create_spot_order_definition(order=order) + spot_order_definitions.append(order_definition) + + for order in derivative_orders_to_create: + order_definition = await self._create_derivative_order_definition(order=order) + derivative_order_definitions.append(order_definition) + + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + spot_orders_to_create=spot_order_definitions, + derivative_orders_to_create=derivative_order_definitions, + ) + + message_as_dictionary = json_format.MessageToDict( + message=message, + including_default_value_fields=True, + preserving_proto_field_name=True, + use_integers_for_enums=True, + ) + del message_as_dictionary["subaccount_id"] + + execute_message_parameter = self._create_execute_contract_internal_message(batch_update_orders_params=message_as_dictionary) + + execute_contract_message = composer.MsgExecuteContract( + sender=self._vault_admin_address.to_acc_bech32(), + contract=self._vault_contract_address.to_acc_bech32(), + msg=json.dumps(execute_message_parameter), + ) + + return [execute_contract_message], [], [] + + async def _order_cancel_message( + self, + spot_orders_to_cancel: List[injective_exchange_tx_pb.OrderData], + derivative_orders_to_cancel: List[injective_exchange_tx_pb.OrderData] + ) -> any_pb2.Any: + composer = await self.composer() + + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + spot_orders_to_cancel=spot_orders_to_cancel, + derivative_orders_to_cancel=derivative_orders_to_cancel, + ) + + message_as_dictionary = json_format.MessageToDict( + message=message, + including_default_value_fields=True, + preserving_proto_field_name=True, + use_integers_for_enums=True, + ) + del message_as_dictionary["subaccount_id"] + + execute_message_parameter = self._create_execute_contract_internal_message(batch_update_orders_params=message_as_dictionary) + + execute_contract_message = composer.MsgExecuteContract( + sender=self._vault_admin_address.to_acc_bech32(), + contract=self._vault_contract_address.to_acc_bech32(), + msg=json.dumps(execute_message_parameter), + ) + + return execute_contract_message + + async def _all_subaccount_orders_cancel_message( + self, + spot_markets_ids: List[str], + derivative_markets_ids: List[str] + ) -> any_pb2.Any: + composer = await self.composer() + + message = composer.MsgBatchUpdateOrders( + sender=self.portfolio_account_injective_address, + subaccount_id=self.portfolio_account_subaccount_id, + spot_market_ids_to_cancel_all=spot_markets_ids, + derivative_market_ids_to_cancel_all=derivative_markets_ids, + ) + + message_as_dictionary = json_format.MessageToDict( + message=message, + including_default_value_fields=True, + preserving_proto_field_name=True, + use_integers_for_enums=True, + ) + + execute_message_parameter = self._create_execute_contract_internal_message( + batch_update_orders_params=message_as_dictionary) + + execute_contract_message = composer.MsgExecuteContract( + sender=self._vault_admin_address.to_acc_bech32(), + contract=self._vault_contract_address.to_acc_bech32(), + msg=json.dumps(execute_message_parameter), + ) + + return execute_contract_message + + async def _generate_injective_order_data(self, order: GatewayInFlightOrder, market_id: str) -> injective_exchange_tx_pb.OrderData: + composer = await self.composer() + order_data = composer.OrderData( + market_id=market_id, + subaccount_id=str(self.portfolio_account_subaccount_index), + order_hash=order.exchange_order_id, + order_direction="buy" if order.trade_type == TradeType.BUY else "sell", + order_type="market" if order.order_type == OrderType.MARKET else "limit", + ) + + return order_data + + async def _create_spot_order_definition(self, order: GatewayInFlightOrder): + # Both price and quantity have to be adjusted because the vaults expect to receive those values without + # the extra 18 zeros that the chain backend expects for direct trading messages + market_id = await self.market_id_for_spot_trading_pair(order.trading_pair) + composer = await self.composer() + definition = composer.SpotOrder( + market_id=market_id, + subaccount_id=str(self.portfolio_account_subaccount_index), + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER + ) + + definition.order_info.quantity = f"{(Decimal(definition.order_info.quantity) * Decimal('1e-18')).normalize():f}" + definition.order_info.price = f"{(Decimal(definition.order_info.price) * Decimal('1e-18')).normalize():f}" + return definition + + async def _create_derivative_order_definition(self, order: GatewayPerpetualInFlightOrder): + # Price, quantity and margin have to be adjusted because the vaults expect to receive those values without + # the extra 18 zeros that the chain backend expects for direct trading messages + market_id = await self.market_id_for_derivative_trading_pair(order.trading_pair) + composer = await self.composer() + definition = composer.DerivativeOrder( + market_id=market_id, + subaccount_id=str(self.portfolio_account_subaccount_index), + fee_recipient=self.portfolio_account_injective_address, + price=order.price, + quantity=order.amount, + leverage=order.leverage, + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER, + is_reduce_only = order.position == PositionAction.CLOSE, + ) + + definition.order_info.quantity = f"{(Decimal(definition.order_info.quantity) * Decimal('1e-18')).normalize():f}" + definition.order_info.price = f"{(Decimal(definition.order_info.price) * Decimal('1e-18')).normalize():f}" + definition.margin = f"{(Decimal(definition.margin) * Decimal('1e-18')).normalize():f}" + return definition + + def _place_order_results( + self, + orders_to_create: List[GatewayInFlightOrder], + order_hashes: List[str], + misc_updates: Dict[str, Any], + exception: Optional[Exception] = None, + ) -> List[PlaceOrderResult]: + return [ + PlaceOrderResult( + update_timestamp=self._time(), + client_order_id=order.client_order_id, + exchange_order_id=None, + trading_pair=order.trading_pair, + misc_updates=misc_updates, + exception=exception + ) for order in orders_to_create + ] + + def _create_execute_contract_internal_message(self, batch_update_orders_params: Dict) -> Dict[str, Any]: + return { + "admin_execute_message": { + "injective_message": { + "custom": { + "route": "exchange", + "msg_data": { + "batch_update_orders": batch_update_orders_params + } + } + } + } + } + + def _order_hashes_from_transaction(self, transaction_info: Dict[str, Any], hashes_group_key: str) -> List[str]: + transaction_logs = json.loads(base64.b64decode(transaction_info["data"]["logs"]).decode()) + batch_orders_message_event = next( + (event for event in transaction_logs[0].get("events", []) if event.get("type") == "wasm"), + {} + ) + response = next( + (attribute.get("value", "") + for attribute in batch_orders_message_event.get("attributes", []) + if attribute.get("key") == "batch_update_orders_response"), "") + order_hashes_match = re.search(f"{hashes_group_key}: (\\[.*?\\])", response) + if order_hashes_match is not None: + order_hashes_text = order_hashes_match.group(1) + else: + order_hashes_text = "" + order_hashes = re.findall(r"[\"'](0x\w+)[\"']", order_hashes_text) + + return order_hashes + + async def _transaction_order_updates( + self, + orders: List[Union[GatewayInFlightOrder, GatewayPerpetualInFlightOrder]], + transaction_orders_info: List[Dict[str, Any]], + order_hashes: List[str], + ) -> List[OrderUpdate]: + order_updates = [] + + for order_info, order_hash in zip(transaction_orders_info, order_hashes): + market_id = order_info["market_id"] + if market_id in await self.spot_market_and_trading_pair_map(): + market = await self.spot_market_info_for_id(market_id=market_id) + else: + market = await self.derivative_market_info_for_id(market_id=market_id) + market_trading_pair = await self.trading_pair_for_market(market_id=market_id) + price = market.price_from_chain_format(chain_price=Decimal(order_info["order_info"]["price"])) + amount = market.quantity_from_chain_format(chain_quantity=Decimal(order_info["order_info"]["quantity"])) + trade_type = TradeType.BUY if order_info["order_type"] in [1, 7, 9] else TradeType.SELL + for transaction_order in orders: + if (transaction_order.trading_pair == market_trading_pair + and transaction_order.amount == amount + and transaction_order.price == price + and transaction_order.trade_type == trade_type): + new_state = OrderState.OPEN if transaction_order.is_pending_create else transaction_order.current_state + order_update = OrderUpdate( + trading_pair=transaction_order.trading_pair, + update_timestamp=self._time(), + new_state=new_state, + client_order_id=transaction_order.client_order_id, + exchange_order_id=order_hash, + ) + orders.remove(transaction_order) + order_updates.append(order_update) + self.logger().debug( + f"Exchange order id found for order {transaction_order.client_order_id} ({order_update})" + ) + break + + return order_updates diff --git a/hummingbot/connector/exchange/injective_v2/injective_constants.py b/hummingbot/connector/exchange/injective_v2/injective_constants.py new file mode 100644 index 0000000..cfa4c6a --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_constants.py @@ -0,0 +1,165 @@ +import sys + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "injective_v2" + +DEFAULT_DOMAIN = "" +TESTNET_DOMAIN = "testnet" + +DEFAULT_SUBACCOUNT_INDEX = 0 +EXTRA_TRANSACTION_GAS = 20000 +DEFAULT_GAS_PRICE = 500000000 + +EXPECTED_BLOCK_TIME = 1.5 +TRANSACTIONS_CHECK_INTERVAL = 3 * EXPECTED_BLOCK_TIME + +# Public limit ids +SPOT_MARKETS_LIMIT_ID = "SpotMarkets" +DERIVATIVE_MARKETS_LIMIT_ID = "DerivativeMarkets" +SPOT_ORDERBOOK_LIMIT_ID = "SpotOrderBookSnapshot" +DERIVATIVE_ORDERBOOK_LIMIT_ID = "DerivativeOrderBookSnapshot" +GET_TRANSACTION_INDEXER_LIMIT_ID = "GetTransactionIndexer" +GET_TRANSACTION_CHAIN_LIMIT_ID = "GetTransactionChain" +FUNDING_RATES_LIMIT_ID = "FundingRates" +ORACLE_PRICES_LIMIT_ID = "OraclePrices" +FUNDING_PAYMENTS_LIMIT_ID = "FundingPayments" +GET_SUBACCOUNT_LIMIT_ID = "GetSubaccount" + +# Private limit ids +PORTFOLIO_BALANCES_LIMIT_ID = "AccountPortfolio" +POSITIONS_LIMIT_ID = "Positions" +SPOT_ORDERS_HISTORY_LIMIT_ID = "SpotOrdersHistory" +DERIVATIVE_ORDERS_HISTORY_LIMIT_ID = "DerivativeOrdersHistory" +SPOT_TRADES_LIMIT_ID = "SpotTrades" +DERIVATIVE_TRADES_LIMIT_ID = "DerivativeTrades" +SIMULATE_TRANSACTION_LIMIT_ID = "SimulateTransaction" +SEND_TRANSACTION = "SendTransaction" + +CHAIN_ENDPOINTS_GROUP_LIMIT_ID = "ChainGroupLimit" +INDEXER_ENDPOINTS_GROUP_LIMIT_ID = "IndexerGroupLimit" + +NO_LIMIT = sys.maxsize +ONE_SECOND = 1 + +ENDPOINTS_RATE_LIMITS = [ + RateLimit( + limit_id=GET_SUBACCOUNT_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(CHAIN_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=GET_TRANSACTION_CHAIN_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(CHAIN_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SIMULATE_TRANSACTION_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(CHAIN_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SEND_TRANSACTION, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(CHAIN_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SPOT_MARKETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=DERIVATIVE_MARKETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SPOT_ORDERBOOK_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=DERIVATIVE_ORDERBOOK_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=GET_TRANSACTION_INDEXER_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=PORTFOLIO_BALANCES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=POSITIONS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SPOT_ORDERS_HISTORY_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=DERIVATIVE_ORDERS_HISTORY_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=SPOT_TRADES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=DERIVATIVE_TRADES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=FUNDING_RATES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=ORACLE_PRICES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), + RateLimit( + limit_id=FUNDING_PAYMENTS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=ONE_SECOND, + linked_limits=[LinkedLimitWeightPair(INDEXER_ENDPOINTS_GROUP_LIMIT_ID)]), +] + +PUBLIC_NODE_RATE_LIMITS = [ + RateLimit(limit_id=CHAIN_ENDPOINTS_GROUP_LIMIT_ID, limit=20, time_interval=ONE_SECOND), + RateLimit(limit_id=INDEXER_ENDPOINTS_GROUP_LIMIT_ID, limit=50, time_interval=ONE_SECOND), +] +PUBLIC_NODE_RATE_LIMITS.extend(ENDPOINTS_RATE_LIMITS) + +CUSTOM_NODE_RATE_LIMITS = [ + RateLimit(limit_id=CHAIN_ENDPOINTS_GROUP_LIMIT_ID, limit=NO_LIMIT, time_interval=ONE_SECOND), + RateLimit(limit_id=INDEXER_ENDPOINTS_GROUP_LIMIT_ID, limit=NO_LIMIT, time_interval=ONE_SECOND), +] +CUSTOM_NODE_RATE_LIMITS.extend(ENDPOINTS_RATE_LIMITS) + +ORDER_STATE_MAP = { + "booked": OrderState.OPEN, + "partial_filled": OrderState.PARTIALLY_FILLED, + "filled": OrderState.FILLED, + "canceled": OrderState.CANCELED, +} + +ORDER_NOT_FOUND_ERROR_MESSAGE = "order not found" +ACCOUNT_SEQUENCE_MISMATCH_ERROR = "account sequence mismatch" + +BATCH_UPDATE_ORDERS_MESSAGE_TYPE = "/injective.exchange.v1beta1.MsgBatchUpdateOrders" +MARKET_ORDER_MESSAGE_TYPES = [ + "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder", + "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder", +] diff --git a/hummingbot/connector/exchange/injective_v2/injective_events.py b/hummingbot/connector/exchange/injective_v2/injective_events.py new file mode 100644 index 0000000..794a56b --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_events.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class InjectiveEvent(int, Enum): + ChainTransactionEvent = 9999 diff --git a/hummingbot/connector/exchange/injective_v2/injective_market.py b/hummingbot/connector/exchange/injective_v2/injective_market.py new file mode 100644 index 0000000..5b9efb6 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_market.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass +from decimal import Decimal +from typing import Any, Dict + +from hummingbot.connector.utils import combine_to_hb_trading_pair + + +@dataclass(frozen=True) +class InjectiveToken: + denom: str + symbol: str + unique_symbol: str + name: str + decimals: int + + def value_from_chain_format(self, chain_value: Decimal) -> Decimal: + scaler = Decimal(f"1e{-self.decimals}") + return chain_value * scaler + + +@dataclass(frozen=True) +class InjectiveSpotMarket: + market_id: str + base_token: InjectiveToken + quote_token: InjectiveToken + market_info: Dict[str, Any] + + def trading_pair(self): + return combine_to_hb_trading_pair(self.base_token.unique_symbol, self.quote_token.unique_symbol) + + def quantity_from_chain_format(self, chain_quantity: Decimal) -> Decimal: + return self.base_token.value_from_chain_format(chain_value=chain_quantity) + + def price_from_chain_format(self, chain_price: Decimal) -> Decimal: + scaler = Decimal(f"1e{self.base_token.decimals-self.quote_token.decimals}") + return chain_price * scaler + + def min_price_tick_size(self) -> Decimal: + min_price_tick_size = Decimal(self.market_info["minPriceTickSize"]) + return self.price_from_chain_format(chain_price=min_price_tick_size) + + def min_quantity_tick_size(self) -> Decimal: + min_quantity_tick_size = Decimal(self.market_info["minQuantityTickSize"]) + return self.quantity_from_chain_format(chain_quantity=min_quantity_tick_size) + + def maker_fee_rate(self) -> Decimal: + return Decimal(self.market_info["makerFeeRate"]) + + def taker_fee_rate(self) -> Decimal: + return Decimal(self.market_info["takerFeeRate"]) + + +@dataclass(frozen=True) +class InjectiveDerivativeMarket: + market_id: str + quote_token: InjectiveToken + market_info: Dict[str, Any] + + def base_token_symbol(self): + ticker_base, _ = self.market_info["ticker"].split("/") + return ticker_base + + def trading_pair(self): + ticker_base, _ = self.market_info["ticker"].split("/") + return combine_to_hb_trading_pair(ticker_base, self.quote_token.unique_symbol) + + def quantity_from_chain_format(self, chain_quantity: Decimal) -> Decimal: + return chain_quantity + + def price_from_chain_format(self, chain_price: Decimal) -> Decimal: + scaler = Decimal(f"1e{-self.quote_token.decimals}") + return chain_price * scaler + + def min_price_tick_size(self) -> Decimal: + min_price_tick_size = Decimal(self.market_info["minPriceTickSize"]) + return self.price_from_chain_format(chain_price=min_price_tick_size) + + def min_quantity_tick_size(self) -> Decimal: + min_quantity_tick_size = Decimal(self.market_info["minQuantityTickSize"]) + return self.quantity_from_chain_format(chain_quantity=min_quantity_tick_size) + + def maker_fee_rate(self) -> Decimal: + return Decimal(self.market_info["makerFeeRate"]) + + def taker_fee_rate(self) -> Decimal: + return Decimal(self.market_info["takerFeeRate"]) + + def oracle_base(self) -> str: + return self.market_info["oracleBase"] + + def oracle_quote(self) -> str: + return self.market_info["oracleQuote"] + + def oracle_type(self) -> str: + return self.market_info["oracleType"] + + def next_funding_timestamp(self) -> int: + return int(self.market_info["perpetualMarketInfo"]["nextFundingTimestamp"]) diff --git a/hummingbot/connector/exchange/injective_v2/injective_query_executor.py b/hummingbot/connector/exchange/injective_v2/injective_query_executor.py new file mode 100644 index 0000000..3549341 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_query_executor.py @@ -0,0 +1,428 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, List, Optional + +from google.protobuf import json_format +from grpc import RpcError +from pyinjective.async_client import AsyncClient + + +class BaseInjectiveQueryExecutor(ABC): + + @abstractmethod + async def ping(self): + raise NotImplementedError + + @abstractmethod + async def spot_markets(self, status: str) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def derivative_markets(self, status: str) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def derivative_market(self, market_id: str) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_spot_orderbook(self, market_id: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def get_derivative_orderbook(self, market_id: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def get_tx_by_hash(self, tx_hash: str) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_tx_block_height(self, tx_hash: str) -> int: + raise NotImplementedError + + @abstractmethod + async def account_portfolio(self, account_address: str) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def simulate_tx(self, tx_byte: bytes) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def send_tx_sync_mode(self, tx_byte: bytes) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_spot_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_derivative_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_historical_spot_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_historical_derivative_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_funding_rates(self, market_id: str, limit: int) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_oracle_prices( + self, + base_symbol: str, + quote_symbol: str, + oracle_type: str, + oracle_scale_factor: int, + ) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_funding_payments(self, subaccount_id: str, market_id: str, limit: int) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def get_derivative_positions(self, subaccount_id: str, skip: int) -> Dict[str, Any]: + raise NotImplementedError + + @abstractmethod + async def spot_order_book_updates_stream(self, market_ids: List[str]): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def public_spot_trades_stream(self, market_ids: List[str]): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def derivative_order_book_updates_stream(self, market_ids: List[str]): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def public_derivative_trades_stream(self, market_ids: List[str]): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def oracle_prices_stream(self, oracle_base: str, oracle_quote: str, oracle_type: str): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def subaccount_positions_stream(self, subaccount_id: str): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def subaccount_balance_stream(self, subaccount_id: str): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def subaccount_historical_spot_orders_stream( + self, market_id: str, subaccount_id: str + ): + raise NotImplementedError + + @abstractmethod + async def subaccount_historical_derivative_orders_stream( + self, market_id: str, subaccount_id: str + ): + raise NotImplementedError + + @abstractmethod + async def transactions_stream(self): # pragma: no cover + raise NotImplementedError + + +class PythonSDKInjectiveQueryExecutor(BaseInjectiveQueryExecutor): + + def __init__(self, sdk_client: AsyncClient): + super().__init__() + self._sdk_client = sdk_client + + async def ping(self): # pragma: no cover + await self._sdk_client.ping() + + async def spot_markets(self, status: str) -> List[Dict[str, Any]]: # pragma: no cover + response = await self._sdk_client.get_spot_markets(status=status) + markets = [] + + for market_info in response.markets: + markets.append(json_format.MessageToDict(market_info)) + + return markets + + async def derivative_markets(self, status: str) -> List[Dict[str, Any]]: # pragma: no cover + response = await self._sdk_client.get_derivative_markets(status=status) + markets = [] + + for market_info in response.markets: + markets.append(json_format.MessageToDict(market_info)) + + return markets + + async def derivative_market(self, market_id: str) -> List[Dict[str, Any]]: # pragma: no cover + response = await self._sdk_client.get_derivative_market(market_id=market_id) + market = json_format.MessageToDict(response.market) + + return market + + async def get_spot_orderbook(self, market_id: str) -> Dict[str, Any]: # pragma: no cover + order_book_response = await self._sdk_client.get_spot_orderbookV2(market_id=market_id) + order_book_data = order_book_response.orderbook + result = { + "buys": [(buy.price, buy.quantity, buy.timestamp) for buy in order_book_data.buys], + "sells": [(buy.price, buy.quantity, buy.timestamp) for buy in order_book_data.sells], + "sequence": order_book_data.sequence, + "timestamp": order_book_data.timestamp, + } + + return result + + async def get_derivative_orderbook(self, market_id: str) -> Dict[str, Any]: # pragma: no cover + order_book_response = await self._sdk_client.get_derivative_orderbooksV2(market_ids=[market_id]) + order_book_data = order_book_response.orderbooks[0].orderbook + result = { + "buys": [(buy.price, buy.quantity, buy.timestamp) for buy in order_book_data.buys], + "sells": [(buy.price, buy.quantity, buy.timestamp) for buy in order_book_data.sells], + "sequence": order_book_data.sequence, + "timestamp": order_book_data.timestamp, + } + + return result + + async def get_tx_by_hash(self, tx_hash: str) -> Dict[str, Any]: # pragma: no cover + try: + transaction_response = await self._sdk_client.get_tx_by_hash(tx_hash=tx_hash) + except RpcError as rpc_exception: + if "object not found" in str(rpc_exception): + raise ValueError(f"The transaction with hash {tx_hash} was not found") + else: + raise + + result = json_format.MessageToDict(transaction_response) + return result + + async def get_tx_block_height(self, tx_hash: str) -> int: # pragma: no cover + try: + transaction_response = await self._sdk_client.get_tx(tx_hash=tx_hash) + except RpcError as rpc_exception: + if "StatusCode.NOT_FOUND" in str(rpc_exception): + raise ValueError(f"The transaction with hash {tx_hash} was not found") + else: + raise + + result = transaction_response.tx_response.height + return result + + async def account_portfolio(self, account_address: str) -> Dict[str, Any]: # pragma: no cover + portfolio_response = await self._sdk_client.get_account_portfolio(account_address=account_address) + result = json_format.MessageToDict(portfolio_response.portfolio) + return result + + async def simulate_tx(self, tx_byte: bytes) -> Dict[str, Any]: # pragma: no cover + response, success = await self._sdk_client.simulate_tx(tx_byte=tx_byte) + if not success: + raise RuntimeError(f"Transaction simulation failure ({response})") + result = json_format.MessageToDict(response) + return result + + async def send_tx_sync_mode(self, tx_byte: bytes) -> Dict[str, Any]: # pragma: no cover + response = await self._sdk_client.send_tx_sync_mode(tx_byte=tx_byte) + result = json_format.MessageToDict(response) + return result + + async def get_spot_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: # pragma: no cover + response = await self._sdk_client.get_spot_trades( + market_ids=market_ids, + subaccount_id=subaccount_id, + start_time=start_time, + skip=skip, + limit=limit, + ) + result = json_format.MessageToDict(response) + return result + + async def get_derivative_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: # pragma: no cover + response = await self._sdk_client.get_derivative_trades( + market_ids=market_ids, + subaccount_id=subaccount_id, + start_time=start_time, + skip=skip, + limit=limit, + ) + result = json_format.MessageToDict(response) + return result + + async def get_historical_spot_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: # pragma: no cover + response = await self._sdk_client.get_historical_spot_orders( + market_ids=market_ids, + subaccount_id=subaccount_id, + start_time=start_time, + skip=skip, + ) + result = json_format.MessageToDict(response) + return result + + async def get_historical_derivative_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: # pragma: no cover + response = await self._sdk_client.get_historical_derivative_orders( + market_ids=market_ids, + subaccount_id=subaccount_id, + start_time=start_time, + skip=skip, + ) + result = json_format.MessageToDict(response) + return result + + async def get_funding_rates(self, market_id: str, limit: int) -> Dict[str, Any]: + response = await self._sdk_client.get_funding_rates(market_id=market_id, limit=limit) + result = json_format.MessageToDict(response) + return result + + async def get_funding_payments(self, subaccount_id: str, market_id: str, limit: int) -> Dict[str, Any]: + response = await self._sdk_client.get_funding_payments( + subaccount_id=subaccount_id, + market_id=market_id, + limit=limit + ) + result = json_format.MessageToDict(response) + return result + + async def get_derivative_positions(self, subaccount_id: str, skip: int) -> Dict[str, Any]: + response = await self._sdk_client.get_derivative_positions( + subaccount_id=subaccount_id, skip=skip + ) + result = json_format.MessageToDict(response) + return result + + async def get_oracle_prices( + self, + base_symbol: str, + quote_symbol: str, + oracle_type: str, + oracle_scale_factor: int, + ) -> Dict[str, Any]: + response = await self._sdk_client.get_oracle_prices( + base_symbol=base_symbol, + quote_symbol=quote_symbol, + oracle_type=oracle_type, + oracle_scale_factor=oracle_scale_factor + ) + result = json_format.MessageToDict(response) + return result + + async def spot_order_book_updates_stream(self, market_ids: List[str]): # pragma: no cover + stream = await self._sdk_client.stream_spot_orderbook_update(market_ids=market_ids) + async for update in stream: + order_book_update = update.orderbook_level_updates + yield json_format.MessageToDict(order_book_update) + + async def public_spot_trades_stream(self, market_ids: List[str]): # pragma: no cover + stream = await self._sdk_client.stream_spot_trades(market_ids=market_ids) + async for trade in stream: + trade_data = trade.trade + yield json_format.MessageToDict(trade_data) + + async def derivative_order_book_updates_stream(self, market_ids: List[str]): # pragma: no cover + stream = await self._sdk_client.stream_derivative_orderbook_update(market_ids=market_ids) + async for update in stream: + order_book_update = update.orderbook_level_updates + yield json_format.MessageToDict(order_book_update) + + async def public_derivative_trades_stream(self, market_ids: List[str]): # pragma: no cover + stream = await self._sdk_client.stream_derivative_trades(market_ids=market_ids) + async for trade in stream: + trade_data = trade.trade + yield json_format.MessageToDict(trade_data) + + async def oracle_prices_stream(self, oracle_base: str, oracle_quote: str, oracle_type: str): # pragma: no cover + stream = await self._sdk_client.stream_oracle_prices( + base_symbol=oracle_base, quote_symbol=oracle_quote, oracle_type=oracle_type + ) + async for update in stream: + yield json_format.MessageToDict(update) + + async def subaccount_positions_stream(self, subaccount_id: str): # pragma: no cover + stream = await self._sdk_client.stream_derivative_positions(subaccount_id=subaccount_id) + async for event in stream: + event_data = event.position + yield json_format.MessageToDict(event_data) + + async def subaccount_balance_stream(self, subaccount_id: str): # pragma: no cover + stream = await self._sdk_client.stream_subaccount_balance(subaccount_id=subaccount_id) + async for event in stream: + yield json_format.MessageToDict(event) + + async def subaccount_historical_spot_orders_stream( + self, market_id: str, subaccount_id: str + ): # pragma: no cover + stream = await self._sdk_client.stream_historical_spot_orders(market_id=market_id, subaccount_id=subaccount_id) + async for event in stream: + event_data = event.order + yield json_format.MessageToDict(event_data) + + async def subaccount_historical_derivative_orders_stream( + self, market_id: str, subaccount_id: str + ): # pragma: no cover + stream = await self._sdk_client.stream_historical_derivative_orders(market_id=market_id, subaccount_id=subaccount_id) + async for event in stream: + event_data = event.order + yield json_format.MessageToDict(event_data) + + async def transactions_stream(self): # pragma: no cover + stream = await self._sdk_client.stream_txs() + async for event in stream: + yield json_format.MessageToDict(event) diff --git a/hummingbot/connector/exchange/injective_v2/injective_v2_api_order_book_data_source.py b/hummingbot/connector/exchange/injective_v2/injective_v2_api_order_book_data_source.py new file mode 100644 index 0000000..1aea444 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_v2_api_order_book_data_source.py @@ -0,0 +1,72 @@ +import asyncio +from typing import TYPE_CHECKING, Dict, List, Optional + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import OrderBookDataSourceEvent + +if TYPE_CHECKING: + from hummingbot.connector.exchange.injective_v2.injective_v2_exchange import InjectiveV2Exchange + + +class InjectiveV2APIOrderBookDataSource(OrderBookTrackerDataSource): + + def __init__( + self, + trading_pairs: List[str], + connector: "InjectiveV2Exchange", + data_source: InjectiveDataSource, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs=trading_pairs) + self._ev_loop = asyncio.get_event_loop() + self._connector = connector + self._data_source = data_source + self._domain = domain + self._forwarders = [] + self._configure_event_forwarders() + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def listen_for_subscriptions(self): + # Subscriptions to streams is handled by the data_source + # Here we just make sure the data_source is listening to the streams + market_ids = [await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + await self._data_source.start(market_ids=market_ids) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + snapshot = await self._data_source.spot_order_book_snapshot(market_id=symbol, trading_pair=trading_pair) + return snapshot + + async def _parse_order_book_diff_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Injective 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + async def _parse_trade_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Injective 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_order_book_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener( + event_tag=OrderBookDataSourceEvent.DIFF_EVENT, listener=event_forwarder + ) + + event_forwarder = EventForwarder(to_function=self._process_public_trade_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=event_forwarder) + + def _process_order_book_event(self, order_book_diff: OrderBookMessage): + self._message_queue[self._diff_messages_queue_key].put_nowait(order_book_diff) + + def _process_public_trade_event(self, trade_update: OrderBookMessage): + self._message_queue[self._trade_messages_queue_key].put_nowait(trade_update) diff --git a/hummingbot/connector/exchange/injective_v2/injective_v2_exchange.py b/hummingbot/connector/exchange/injective_v2/injective_v2_exchange.py new file mode 100644 index 0000000..f03d075 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_v2_exchange.py @@ -0,0 +1,1019 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from enum import Enum +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union + +from async_timeout import timeout + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.injective_v2 import ( + injective_constants as CONSTANTS, + injective_v2_web_utils as web_utils, +) +from hummingbot.connector.exchange.injective_v2.injective_events import InjectiveEvent +from hummingbot.connector.exchange.injective_v2.injective_v2_api_order_book_data_source import ( + InjectiveV2APIOrderBookDataSource, +) +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import InjectiveConfigMap +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TradeFeeBase, TradeFeeSchema +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class InjectiveV2Exchange(ExchangePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + connector_configuration: InjectiveConfigMap, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + **kwargs, + ): + self._orders_processing_delta_time = 0.5 + + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._data_source = connector_configuration.create_data_source() + self._rate_limits = connector_configuration.network.rate_limits() + + super().__init__(client_config_map=client_config_map) + self._data_source.configure_throttler(throttler=self._throttler) + self._forwarders = [] + self._configure_event_forwarders() + self._latest_polled_order_fill_time: float = self._time() + self._orders_transactions_check_task: Optional[asyncio.Task] = None + self._last_received_message_timestamp = 0 + self._orders_queued_to_create: List[GatewayInFlightOrder] = [] + self._orders_queued_to_cancel: List[GatewayInFlightOrder] = [] + + self._orders_transactions_check_task = None + self._queued_orders_task = None + self._all_trading_events_queue = asyncio.Queue() + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> AuthBase: + return None + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return self._rate_limits + + @property + def domain(self) -> str: + return self._data_source.network_name + + @property + def client_order_id_max_length(self) -> int: + return None + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def trading_rules_request_path(self) -> str: + raise NotImplementedError + + @property + def trading_pairs_request_path(self) -> str: + raise NotImplementedError + + @property + def check_network_request_path(self) -> str: + raise NotImplementedError + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def status_dict(self) -> Dict[str, bool]: + status = super().status_dict + status["data_source_initialized"] = self._data_source.is_started() + return status + + async def start_network(self): + await super().start_network() + + market_ids = [ + await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs + ] + await self._data_source.start(market_ids=market_ids) + + if self.is_trading_required: + self._orders_transactions_check_task = safe_ensure_future(self._check_orders_transactions()) + self._queued_orders_task = safe_ensure_future(self._process_queued_orders()) + + async def stop_network(self): + """ + This function is executed when the connector is stopped. It performs a general cleanup and stops all background + tasks that require the connection with the exchange to work. + """ + await super().stop_network() + await self._data_source.stop() + self._forwarders = [] + if self._orders_transactions_check_task is not None: + self._orders_transactions_check_task.cancel() + self._orders_transactions_check_task = None + if self._queued_orders_task is not None: + self._queued_orders_task.cancel() + self._queued_orders_task = None + + def supported_order_types(self) -> List[OrderType]: + return self._data_source.supported_order_types() + + def start_tracking_order( + self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + **kwargs, + ): + self._order_tracker.start_tracking_order( + GatewayInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp, + ) + ) + + def batch_order_create(self, orders_to_create: List[Union[MarketOrder, LimitOrder]]) -> List[LimitOrder]: + """ + Issues a batch order creation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_create: A list of LimitOrder or MarketOrder objects representing the orders to create. The order IDs + can be blanc. + :returns: A tuple composed of LimitOrder or MarketOrder objects representing the created orders, complete with the generated + order IDs. + """ + orders_with_ids_to_create = [] + for order in orders_to_create: + client_order_id = get_new_client_order_id( + is_buy=order.is_buy, + trading_pair=order.trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + orders_with_ids_to_create.append(order.copy_with_id(client_order_id=client_order_id)) + safe_ensure_future(self._execute_batch_order_create(orders_to_create=orders_with_ids_to_create)) + return orders_with_ids_to_create + + def batch_order_cancel(self, orders_to_cancel: List[LimitOrder]): + """ + Issues a batch order cancelation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_cancel: A list of the orders to cancel. + """ + safe_ensure_future(coro=self._execute_batch_cancel(orders_to_cancel=orders_to_cancel)) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all currently active orders. The cancellations are performed in parallel tasks. + + :param timeout_seconds: the maximum time (in seconds) the cancel logic should run + + :return: a list of CancellationResult instances, one for each of the orders to be cancelled + """ + incomplete_orders = {} + limit_orders = [] + successful_cancellations = [] + + for order in self.in_flight_orders.values(): + if not order.is_done: + incomplete_orders[order.client_order_id] = order + limit_orders.append(order.to_limit_order()) + + if len(limit_orders) > 0: + try: + async with timeout(timeout_seconds): + cancellation_results = await self._execute_batch_cancel(orders_to_cancel=limit_orders) + for cr in cancellation_results: + if cr.success: + del incomplete_orders[cr.order_id] + successful_cancellations.append(CancellationResult(cr.order_id, True)) + except Exception: + self.logger().network( + "Unexpected error cancelling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order. Check API key and network connection." + ) + failed_cancellations = [CancellationResult(oid, False) for oid in incomplete_orders.keys()] + return successful_cancellations + failed_cancellations + + async def cancel_all_subaccount_orders(self): + markets_ids = [await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self.trading_pairs] + await self._data_source.cancel_all_subaccount_orders(spot_markets_ids=markets_ids) + + async def check_network(self) -> NetworkStatus: + """ + Checks connectivity with the exchange using the API + """ + try: + status = await self._data_source.check_network() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + def trigger_event(self, event_tag: Enum, message: any): + # Reimplemented because Injective connector has trading pairs with modified token names, because market tickers + # are not always unique. + # We need to change the original trading pair in all events to the real tokens trading pairs to not impact the + # bot events processing + trading_pair = getattr(message, "trading_pair", None) + if trading_pair is not None: + new_trading_pair = self._data_source.real_tokens_spot_trading_pair(unique_trading_pair=trading_pair) + if isinstance(message, tuple): + message = message._replace(trading_pair=new_trading_pair) + else: + setattr(message, "trading_pair", new_trading_pair) + + super().trigger_event(event_tag=event_tag, message=message) + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # For Injective the cancelation is done by sending a transaction to the chain. + # The cancel request is not validated until the transaction is included in a block, and so this does not apply + return False + + async def _place_cancel(self, order_id: str, tracked_order: GatewayInFlightOrder): + # Not required because of _execute_order_cancel redefinition + raise NotImplementedError + + async def _execute_order_cancel(self, order: GatewayInFlightOrder) -> str: + # Order cancelation requests for single orders are queued to be executed in batch if possible + self._orders_queued_to_cancel.append(order) + return None + + async def _place_order(self, order_id: str, trading_pair: str, amount: Decimal, trade_type: TradeType, + order_type: OrderType, price: Decimal, **kwargs) -> Tuple[str, float]: + # Not required because of _place_order_and_process_update redefinition + raise NotImplementedError + + async def _create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + **kwargs): + """ + Creates an order in the exchange using the parameters to configure it + + :param trade_type: the side of the order (BUY of SELL) + :param order_id: the id that should be assigned to the order (the client id) + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + """ + try: + if price is None or price.is_nan(): + calculated_price = self.get_price_for_volume( + trading_pair=trading_pair, + is_buy=trade_type == TradeType.BUY, + volume=amount, + ).result_price + else: + calculated_price = price + + calculated_price = self.quantize_order_price(trading_pair, calculated_price) + + await super()._create_order( + trade_type=trade_type, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=calculated_price, + ** kwargs + ) + + except asyncio.CancelledError: + raise + except Exception as ex: + self._on_order_failure( + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + trade_type=trade_type, + order_type=order_type, + price=price, + exception=ex, + **kwargs, + ) + + async def _place_order_and_process_update(self, order: GatewayInFlightOrder, **kwargs) -> str: + # Order creation requests for single orders are queued to be executed in batch if possible + self._orders_queued_to_create.append(order) + return None + + async def _execute_batch_order_create(self, orders_to_create: List[Union[MarketOrder, LimitOrder]]): + inflight_orders_to_create = [] + for order in orders_to_create: + valid_order = await self._start_tracking_and_validate_order( + trade_type=TradeType.BUY if order.is_buy else TradeType.SELL, + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.quantity, + order_type=order.order_type(), + price=order.price, + ) + if valid_order is not None: + inflight_orders_to_create.append(valid_order) + await self._execute_batch_inflight_order_create(inflight_orders_to_create=inflight_orders_to_create) + + async def _execute_batch_inflight_order_create(self, inflight_orders_to_create: List[GatewayInFlightOrder]): + try: + place_order_results = await self._data_source.create_orders( + spot_orders=inflight_orders_to_create + ) + for place_order_result, in_flight_order in ( + zip(place_order_results, inflight_orders_to_create) + ): + if place_order_result.exception: + self._on_order_creation_failure( + order_id=in_flight_order.client_order_id, + trading_pair=in_flight_order.trading_pair, + amount=in_flight_order.amount, + trade_type=in_flight_order.trade_type, + order_type=in_flight_order.order_type, + price=in_flight_order.price, + exception=place_order_result.exception, + ) + else: + self._update_order_after_creation_success( + exchange_order_id=place_order_result.exchange_order_id, + order=in_flight_order, + update_timestamp=self.current_timestamp, + misc_updates=place_order_result.misc_updates, + ) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().network("Batch order create failed.") + for order in inflight_orders_to_create: + self._on_order_creation_failure( + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.amount, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + exception=ex, + ) + + async def _start_tracking_and_validate_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + **kwargs + ) -> Optional[GatewayInFlightOrder]: + trading_rule = self._trading_rules[trading_pair] + + if price is None: + calculated_price = self.get_price_for_volume( + trading_pair=trading_pair, + is_buy=trade_type == TradeType.BUY, + volume=amount, + ).result_price + calculated_price = self.quantize_order_price(trading_pair, calculated_price) + else: + calculated_price = price + + price = self.quantize_order_price(trading_pair, calculated_price) + amount = self.quantize_order_amount(trading_pair=trading_pair, amount=amount) + + self.start_tracking_order( + order_id=order_id, + exchange_order_id=None, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=amount, + **kwargs, + ) + order = self._order_tracker.active_orders[order_id] + + if order_type not in self.supported_order_types(): + self.logger().error(f"{order_type} is not in the list of supported order types") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + elif amount < trading_rule.min_order_size: + self.logger().warning(f"{trade_type.name.title()} order amount {amount} is lower than the minimum order" + f" size {trading_rule.min_order_size}. The order will not be created.") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + elif price is not None and amount * price < trading_rule.min_notional_size: + self.logger().warning(f"{trade_type.name.title()} order notional {amount * price} is lower than the " + f"minimum notional size {trading_rule.min_notional_size}. " + "The order will not be created.") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + + return order + + def _update_order_after_creation_success( + self, + exchange_order_id: Optional[str], + order: GatewayInFlightOrder, + update_timestamp: float, + misc_updates: Optional[Dict[str, Any]] = None + ): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=order.current_state, + misc_updates=misc_updates, + ) + self.logger().debug(f"\nCreated order {order.client_order_id} ({exchange_order_id}) with TX {misc_updates}") + self._order_tracker.process_order_update(order_update) + + def _on_order_creation_failure( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Optional[Decimal], + exception: Exception, + ): + self.logger().network( + f"Error submitting {trade_type.name.lower()} {order_type.name.upper()} order to {self.name_cap} for " + f"{amount} {trading_pair} {price}.", + exc_info=exception, + app_warning_msg=f"Failed to submit buy order to {self.name_cap}. Check API key and network connection." + ) + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + + def _update_order_after_creation_failure(self, order_id: str, trading_pair: str): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + self._order_tracker.process_order_update(order_update) + + async def _execute_batch_cancel(self, orders_to_cancel: List[LimitOrder]) -> List[CancellationResult]: + results = [] + tracked_orders_to_cancel = [] + + for order in orders_to_cancel: + tracked_order = self._order_tracker.all_updatable_orders.get(order.client_order_id) + if tracked_order is not None: + tracked_orders_to_cancel.append(tracked_order) + else: + results.append(CancellationResult(order_id=order.client_order_id, success=False)) + + if len(tracked_orders_to_cancel) > 0: + results.extend(await self._execute_batch_order_cancel(orders_to_cancel=tracked_orders_to_cancel)) + + return results + + async def _execute_batch_order_cancel(self, orders_to_cancel: List[GatewayInFlightOrder]) -> List[CancellationResult]: + try: + cancel_order_results = await self._data_source.cancel_orders(spot_orders=orders_to_cancel) + cancelation_results = [] + for cancel_order_result in cancel_order_results: + success = True + if cancel_order_result.not_found: + self.logger().warning( + f"Failed to cancel the order {cancel_order_result.client_order_id} due to the order" + f" not being found." + ) + await self._order_tracker.process_order_not_found( + client_order_id=cancel_order_result.client_order_id + ) + success = False + elif cancel_order_result.exception is not None: + self.logger().error( + f"Failed to cancel order {cancel_order_result.client_order_id}", + exc_info=cancel_order_result.exception, + ) + success = False + else: + order_update: OrderUpdate = OrderUpdate( + client_order_id=cancel_order_result.client_order_id, + trading_pair=cancel_order_result.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + misc_updates=cancel_order_result.misc_updates, + ) + self._order_tracker.process_order_update(order_update) + cancelation_results.append( + CancellationResult(order_id=cancel_order_result.client_order_id, success=success) + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Failed to cancel orders {', '.join([o.client_order_id for o in orders_to_cancel])}", + exc_info=True, + ) + cancelation_results = [ + CancellationResult(order_id=order.client_order_id, success=False) + for order in orders_to_cancel + ] + + return cancelation_results + + def _update_order_after_cancelation_success(self, order: GatewayInFlightOrder): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + ) + self._order_tracker.process_order_update(order_update) + + def _get_fee(self, base_currency: str, quote_currency: str, order_type: OrderType, order_side: TradeType, + amount: Decimal, price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fee_schema: TradeFeeSchema = self._trading_fees[trading_pair] + fee_rate = fee_schema.maker_percent_fee_decimal if is_maker else fee_schema.taker_percent_fee_decimal + fee = TradeFeeBase.new_spot_fee( + fee_schema=fee_schema, + trade_type=order_side, + percent=fee_rate, + percent_token=fee_schema.percent_fee_token, + ) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + self._trading_fees = await self._data_source.get_spot_trading_fees() + + async def _user_stream_event_listener(self): + while True: + try: + event_message = await self._all_trading_events_queue.get() + channel = event_message["channel"] + event_data = event_message["data"] + + if channel == "transaction": + transaction_hash = event_data["hash"] + await self._check_created_orders_status_for_transaction(transaction_hash=transaction_hash) + elif channel == "trade": + trade_update = event_data + tracked_order = self._order_tracker.all_fillable_orders_by_exchange_order_id.get( + trade_update.exchange_order_id + ) + if tracked_order is not None: + new_trade_update = TradeUpdate( + trade_id=trade_update.trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_update.exchange_order_id, + trading_pair=trade_update.trading_pair, + fill_timestamp=trade_update.fill_timestamp, + fill_price=trade_update.fill_price, + fill_base_amount=trade_update.fill_base_amount, + fill_quote_amount=trade_update.fill_quote_amount, + fee=trade_update.fee, + is_taker=trade_update.is_taker, + ) + self._order_tracker.process_trade_update(new_trade_update) + elif channel == "order": + order_update = event_data + tracked_order = self._order_tracker.all_updatable_orders_by_exchange_order_id.get( + order_update.exchange_order_id) + if tracked_order is not None: + new_order_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=order_update.new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + self._order_tracker.process_order_update(order_update=new_order_update) + elif channel == "balance": + if event_data.total_balance is not None: + self._account_balances[event_data.asset_name] = event_data.total_balance + if event_data.available_balance is not None: + self._account_available_balances[event_data.asset_name] = event_data.available_balance + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop") + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + # Not used in Injective + raise NotImplementedError # pragma: no cover + + async def _update_trading_rules(self): + await self._data_source.update_markets() + await self._initialize_trading_pair_symbol_map() + trading_rules_list = await self._data_source.spot_trading_rules() + trading_rules = {} + for trading_rule in trading_rules_list: + trading_rules[trading_rule.trading_pair] = trading_rule + self._trading_rules.clear() + self._trading_rules.update(trading_rules) + + async def _update_balances(self): + all_balances = await self._data_source.all_account_balances() + + self._account_available_balances.clear() + self._account_balances.clear() + + for token, token_balance_info in all_balances.items(): + self._account_balances[token] = token_balance_info["total_balance"] + self._account_available_balances[token] = token_balance_info["available_balance"] + + async def _all_trade_updates_for_order(self, order: GatewayInFlightOrder) -> List[TradeUpdate]: + # Not required because of _update_orders_fills redefinition + raise NotImplementedError + + async def _update_orders_fills(self, orders: List[GatewayInFlightOrder]): + oldest_order_creation_time = self.current_timestamp + all_market_ids = set() + orders_by_hash = {} + + for order in orders: + oldest_order_creation_time = min(oldest_order_creation_time, order.creation_timestamp) + all_market_ids.add(await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair)) + if order.exchange_order_id is not None: + orders_by_hash[order.exchange_order_id] = order + + try: + start_time = min(oldest_order_creation_time, self._latest_polled_order_fill_time) + trade_updates = await self._data_source.spot_trade_updates(market_ids=all_market_ids, start_time=start_time) + for trade_update in trade_updates: + tracked_order = orders_by_hash.get(trade_update.exchange_order_id) + if tracked_order is not None: + new_trade_update = TradeUpdate( + trade_id=trade_update.trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_update.exchange_order_id, + trading_pair=trade_update.trading_pair, + fill_timestamp=trade_update.fill_timestamp, + fill_price=trade_update.fill_price, + fill_base_amount=trade_update.fill_base_amount, + fill_quote_amount=trade_update.fill_quote_amount, + fee=trade_update.fee, + is_taker=trade_update.is_taker, + ) + self._latest_polled_order_fill_time = max(self._latest_polled_order_fill_time, trade_update.fill_timestamp) + self._order_tracker.process_trade_update(new_trade_update) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().warning( + f"Failed to fetch trade updates. Error: {ex}", + exc_info=ex, + ) + + async def _request_order_status(self, tracked_order: GatewayInFlightOrder) -> OrderUpdate: + # Not required due to the redefinition of _update_orders_with_error_handler + raise NotImplementedError + + async def _update_orders_with_error_handler(self, orders: List[GatewayInFlightOrder], error_handler: Callable): + oldest_order_creation_time = self.current_timestamp + all_market_ids = set() + orders_by_hash = {} + + for order in orders: + oldest_order_creation_time = min(oldest_order_creation_time, order.creation_timestamp) + all_market_ids.add(await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair)) + if order.exchange_order_id is not None: + orders_by_hash[order.exchange_order_id] = order + + try: + order_updates = await self._data_source.spot_order_updates( + market_ids=all_market_ids, + start_time=oldest_order_creation_time - self.LONG_POLL_INTERVAL + ) + + for order_update in order_updates: + tracked_order = orders_by_hash.get(order_update.exchange_order_id) + if tracked_order is not None: + try: + new_order_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=order_update.new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + + if tracked_order.current_state == OrderState.PENDING_CREATE and new_order_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=order_update.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=OrderState.OPEN, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + misc_updates=order_update.misc_updates, + ) + self._order_tracker.process_order_update(open_update) + + del orders_by_hash[order_update.exchange_order_id] + self._order_tracker.process_order_update(new_order_update) + except asyncio.CancelledError: + raise + except Exception as ex: + await error_handler(tracked_order, ex) + + if len(orders_by_hash) > 0: + # await self._data_source.check_order_hashes_synchronization(orders=orders_by_hash.values()) + for order in orders_by_hash.values(): + not_found_error = RuntimeError( + f"There was a problem updating order {order.client_order_id} " + f"({CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE})" + ) + await error_handler(order, not_found_error) + except asyncio.CancelledError: + raise + except Exception as request_error: + for order in orders_by_hash.values(): + await error_handler(order, request_error) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return WebAssistantsFactory(throttler=self._throttler) + + def _create_order_tracker(self) -> ClientOrderTracker: + tracker = GatewayOrderTracker(connector=self) + return tracker + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return InjectiveV2APIOrderBookDataSource( + trading_pairs=self.trading_pairs, + connector=self, + data_source=self._data_source, + domain=self.domain + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + # Not used in Injective + raise NotImplementedError # pragma: no cover + + def _is_user_stream_initialized(self): + # Injective does not have private websocket endpoints + return self._data_source.is_started() + + def _create_user_stream_tracker(self): + # Injective does not use a tracker for the private streams + return None + + def _create_user_stream_tracker_task(self): + # Injective does not use a tracker for the private streams + return None + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + # Not used in Injective + raise NotImplementedError() # pragma: no cover + + async def _initialize_trading_pair_symbol_map(self): + exchange_info = None + try: + mapping = await self._data_source.spot_market_and_trading_pair_map() + self._set_trading_pair_symbol_map(mapping) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + return exchange_info + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_user_trade_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_user_order_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_balance_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_transaction_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=InjectiveEvent.ChainTransactionEvent, listener=event_forwarder) + + def _process_balance_event(self, event: BalanceUpdateEvent): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "balance", "data": event} + ) + + def _process_user_order_update(self, order_update: OrderUpdate): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "order", "data": order_update} + ) + + def _process_user_trade_update(self, trade_update: TradeUpdate): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "trade", "data": trade_update} + ) + + def _process_transaction_event(self, transaction_event: Dict[str, Any]): + self._last_received_message_timestamp = self._time() + self._all_trading_events_queue.put_nowait( + {"channel": "transaction", "data": transaction_event} + ) + + async def _check_orders_transactions(self): + while True: + try: + # Executing the process shielded from this async task to isolate it from network disconnections + # (network disconnections cancel this task) + task = asyncio.create_task(self._check_orders_creation_transactions()) + await asyncio.shield(task) + await self._sleep(CONSTANTS.TRANSACTIONS_CHECK_INTERVAL) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while running the transactions check process", exc_info=True) + await self._sleep(0.5) + + async def _check_orders_creation_transactions(self): + orders: List[GatewayInFlightOrder] = self._order_tracker.active_orders.values() + orders_by_creation_tx = defaultdict(list) + orders_with_inconsistent_hash = [] + + for order in orders: + if order.creation_transaction_hash is not None and order.is_pending_create: + orders_by_creation_tx[order.creation_transaction_hash].append(order) + + for transaction_hash, orders in orders_by_creation_tx.items(): + all_orders = orders.copy() + try: + order_updates = await self._data_source.order_updates_for_transaction( + transaction_hash=transaction_hash, spot_orders=orders + ) + + for order_update in order_updates: + tracked_order = self._order_tracker.active_orders.get(order_update.client_order_id) + if tracked_order is not None: + all_orders.remove(tracked_order) + if (tracked_order.exchange_order_id is not None + and tracked_order.exchange_order_id != order_update.exchange_order_id): + tracked_order.update_exchange_order_id(order_update.exchange_order_id) + orders_with_inconsistent_hash.append(tracked_order) + self._order_tracker.process_order_update(order_update=order_update) + + for not_found_order in all_orders: + self._update_order_after_failure( + order_id=not_found_order.client_order_id, + trading_pair=not_found_order.trading_pair + ) + + except ValueError: + self.logger().debug(f"Transaction not included in a block yet ({transaction_hash})") + + if len(orders_with_inconsistent_hash) > 0: + async with self._data_source.order_creation_lock: + active_orders = [ + order for order in self._order_tracker.active_orders.values() + if order not in orders_with_inconsistent_hash and order.current_state == OrderState.PENDING_CREATE + ] + await self._data_source.reset_order_hash_generator(active_orders=active_orders) + + async def _check_created_orders_status_for_transaction(self, transaction_hash: str): + transaction_orders = [] + order: GatewayInFlightOrder + for order in self.in_flight_orders.values(): + if order.creation_transaction_hash == transaction_hash and order.is_pending_create: + transaction_orders.append(order) + + if len(transaction_orders) > 0: + order_updates = await self._data_source.order_updates_for_transaction( + transaction_hash=transaction_hash, spot_orders=transaction_orders + ) + + for order_update in order_updates: + tracked_order = self._order_tracker.active_orders.get(order_update.client_order_id) + if (tracked_order is not None + and tracked_order.exchange_order_id is not None + and tracked_order.exchange_order_id != order_update.exchange_order_id): + tracked_order.update_exchange_order_id(order_update.exchange_order_id) + self._order_tracker.process_order_update(order_update=order_update) + + async def _process_queued_orders(self): + while True: + try: + # Executing the batch cancelation and creation process shielded from this async task to isolate the + # creation/cancelation process from network disconnections (network disconnections cancel this task) + task = asyncio.create_task(self._cancel_and_create_queued_orders()) + await asyncio.shield(task) + sleep_time = (self.clock.tick_size * 0.5 + if self.clock is not None + else self._orders_processing_delta_time) + await self._sleep(sleep_time) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while processing queued individual orders", exc_info=True) + await self._sleep(self.clock.tick_size * 0.5) + + async def _cancel_and_create_queued_orders(self): + if len(self._orders_queued_to_cancel) > 0: + orders = [order.to_limit_order() for order in self._orders_queued_to_cancel] + self._orders_queued_to_cancel = [] + await self._execute_batch_cancel(orders_to_cancel=orders) + if len(self._orders_queued_to_create) > 0: + orders = self._orders_queued_to_create + self._orders_queued_to_create = [] + await self._execute_batch_inflight_order_create(inflight_orders_to_create=orders) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + market_id = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + last_price = await self._data_source.last_traded_price(market_id=market_id) + return float(last_price) + + def _get_poll_interval(self, timestamp: float) -> float: + last_recv_diff = timestamp - self._last_received_message_timestamp + poll_interval = ( + self.SHORT_POLL_INTERVAL + if last_recv_diff > self.TICK_INTERVAL_LIMIT + else self.LONG_POLL_INTERVAL + ) + return poll_interval diff --git a/hummingbot/connector/exchange/injective_v2/injective_v2_utils.py b/hummingbot/connector/exchange/injective_v2/injective_v2_utils.py new file mode 100644 index 0000000..9fe48d1 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_v2_utils.py @@ -0,0 +1,360 @@ +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, List, Union + +from pydantic import Field, SecretStr +from pydantic.class_validators import validator +from pyinjective.core.network import Network + +from hummingbot.client.config.config_data_types import BaseClientModel, BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source import ( + InjectiveGranteeDataSource, +) +from hummingbot.connector.exchange.injective_v2.data_sources.injective_read_only_data_source import ( + InjectiveReadOnlyDataSource, +) +from hummingbot.connector.exchange.injective_v2.data_sources.injective_vaults_data_source import ( + InjectiveVaultsDataSource, +) +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +if TYPE_CHECKING: + from hummingbot.connector.exchange.injective_v2.data_sources.injective_data_source import InjectiveDataSource + +CENTRALIZED = False +EXAMPLE_PAIR = "INJ-USDT" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0"), + taker_percent_fee_decimal=Decimal("0"), +) + +TESTNET_NODES = ["lb", "sentry"] + + +class InjectiveNetworkMode(BaseClientModel, ABC): + @abstractmethod + def network(self) -> Network: + pass + + @abstractmethod + def use_secure_connection(self) -> bool: + pass + + +class InjectiveMainnetNetworkMode(InjectiveNetworkMode): + + class Config: + title = "mainnet_network" + + def network(self) -> Network: + return Network.mainnet() + + def use_secure_connection(self) -> bool: + return True + + def rate_limits(self) -> List[RateLimit]: + return CONSTANTS.PUBLIC_NODE_RATE_LIMITS + + +class InjectiveTestnetNetworkMode(InjectiveNetworkMode): + testnet_node: str = Field( + default="lb", + client_data=ClientFieldData( + prompt=lambda cm: (f"Enter the testnet node you want to connect to ({'/'.join(TESTNET_NODES)})"), + prompt_on_new=True + ), + ) + + class Config: + title = "testnet_network" + + @validator("testnet_node", pre=True) + def validate_node(cls, v: str): + if v not in TESTNET_NODES: + raise ValueError(f"{v} is not a valid node ({TESTNET_NODES})") + return v + + def network(self) -> Network: + return Network.testnet(node=self.testnet_node) + + def use_secure_connection(self) -> bool: + return True + + def rate_limits(self) -> List[RateLimit]: + return CONSTANTS.PUBLIC_NODE_RATE_LIMITS + + +class InjectiveCustomNetworkMode(InjectiveNetworkMode): + lcd_endpoint: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network lcd_endpoint"), + prompt_on_new=True + ), + ) + tm_websocket_endpoint: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network tm_websocket_endpoint"), + prompt_on_new=True + ), + ) + grpc_endpoint: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network grpc_endpoint"), + prompt_on_new=True + ), + ) + grpc_exchange_endpoint: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network grpc_exchange_endpoint"), + prompt_on_new=True + ), + ) + grpc_explorer_endpoint: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network grpc_explorer_endpoint"), + prompt_on_new=True + ), + ) + chain_id: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network chain_id"), + prompt_on_new=True + ), + ) + env: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Enter the network environment name"), + prompt_on_new=True + ), + ) + secure_connection: bool = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: ("Should this configuration use secure connections? (yes/no)"), + prompt_on_new=True + ), + ) + + class Config: + title = "custom_network" + + def network(self) -> Network: + return Network.custom( + lcd_endpoint=self.lcd_endpoint, + tm_websocket_endpoint=self.tm_websocket_endpoint, + grpc_endpoint=self.grpc_endpoint, + grpc_exchange_endpoint=self.grpc_exchange_endpoint, + grpc_explorer_endpoint=self.grpc_explorer_endpoint, + chain_id=self.chain_id, + env=self.env, + ) + + def use_secure_connection(self) -> bool: + return self.secure_connection + + def rate_limits(self) -> List[RateLimit]: + return CONSTANTS.CUSTOM_NODE_RATE_LIMITS + + +NETWORK_MODES = { + InjectiveMainnetNetworkMode.Config.title: InjectiveMainnetNetworkMode, + InjectiveTestnetNetworkMode.Config.title: InjectiveTestnetNetworkMode, + InjectiveCustomNetworkMode.Config.title: InjectiveCustomNetworkMode, +} + + +class InjectiveAccountMode(BaseClientModel, ABC): + + @abstractmethod + def create_data_source( + self, network: Network, use_secure_connection: bool, rate_limits: List[RateLimit], + ) -> "InjectiveDataSource": + pass + + +class InjectiveDelegatedAccountMode(InjectiveAccountMode): + private_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Injective trading account private key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + subaccount_index: int = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Injective trading account subaccount index", + prompt_on_new=True, + ), + ) + granter_address: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the Injective address of the granter account (portfolio account)", + prompt_on_new=True, + ), + ) + granter_subaccount_index: int = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the Injective granter subaccount index (portfolio subaccount index)", + prompt_on_new=True, + ), + ) + + class Config: + title = "delegate_account" + + def create_data_source( + self, network: Network, use_secure_connection: bool, rate_limits: List[RateLimit], + ) -> "InjectiveDataSource": + return InjectiveGranteeDataSource( + private_key=self.private_key.get_secret_value(), + subaccount_index=self.subaccount_index, + granter_address=self.granter_address, + granter_subaccount_index=self.granter_subaccount_index, + network=network, + use_secure_connection=use_secure_connection, + rate_limits=rate_limits, + ) + + +class InjectiveVaultAccountMode(InjectiveAccountMode): + private_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the vault admin private key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + subaccount_index: int = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the vault admin subaccount index", + prompt_on_new=True, + ), + ) + vault_contract_address: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the vault contract address", + prompt_on_new=True, + ), + ) + vault_subaccount_index: int = Field( + default=1, + const=True, + client_data=None + ) + + class Config: + title = "vault_account" + + def create_data_source( + self, network: Network, use_secure_connection: bool, rate_limits: List[RateLimit], + ) -> "InjectiveDataSource": + return InjectiveVaultsDataSource( + private_key=self.private_key.get_secret_value(), + subaccount_index=self.subaccount_index, + vault_contract_address=self.vault_contract_address, + vault_subaccount_index=self.vault_subaccount_index, + network=network, + use_secure_connection=use_secure_connection, + rate_limits=rate_limits, + ) + + +class InjectiveReadOnlyAccountMode(InjectiveAccountMode): + + class Config: + title = "read_only_account" + + def create_data_source( + self, network: Network, use_secure_connection: bool, rate_limits: List[RateLimit], + ) -> "InjectiveDataSource": + return InjectiveReadOnlyDataSource( + network=network, + use_secure_connection=use_secure_connection, + rate_limits=rate_limits, + ) + + +ACCOUNT_MODES = { + InjectiveDelegatedAccountMode.Config.title: InjectiveDelegatedAccountMode, + InjectiveVaultAccountMode.Config.title: InjectiveVaultAccountMode, + InjectiveReadOnlyAccountMode.Config.title: InjectiveReadOnlyAccountMode, +} + + +class InjectiveConfigMap(BaseConnectorConfigMap): + # Setting a default dummy configuration to allow the bot to create a dummy instance to fetch all trading pairs + connector: str = Field(default="injective_v2", const=True, client_data=None) + receive_connector_configuration: bool = Field( + default=True, const=True, + client_data=ClientFieldData(), + ) + network: Union[tuple(NETWORK_MODES.values())] = Field( + default=InjectiveMainnetNetworkMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the network ({'/'.join(list(NETWORK_MODES.keys()))})", + prompt_on_new=True, + ), + ) + account_type: Union[tuple(ACCOUNT_MODES.values())] = Field( + default=InjectiveReadOnlyAccountMode(), + client_data=ClientFieldData( + prompt=lambda cm: f"Select the type of account configuration ({'/'.join(list(ACCOUNT_MODES.keys()))})", + prompt_on_new=True, + ), + ) + + class Config: + title = "injective_v2" + + @validator("network", pre=True) + def validate_network(cls, v: Union[(str, Dict) + tuple(NETWORK_MODES.values())]): + if isinstance(v, tuple(NETWORK_MODES.values()) + (Dict,)): + sub_model = v + elif v not in NETWORK_MODES: + raise ValueError( + f"Invalid network, please choose a value from {list(NETWORK_MODES.keys())}." + ) + else: + sub_model = NETWORK_MODES[v].construct() + return sub_model + + @validator("account_type", pre=True) + def validate_account_type(cls, v: Union[(str, Dict) + tuple(ACCOUNT_MODES.values())]): + if isinstance(v, tuple(ACCOUNT_MODES.values()) + (Dict,)): + sub_model = v + elif v not in ACCOUNT_MODES: + raise ValueError( + f"Invalid account type, please choose a value from {list(ACCOUNT_MODES.keys())}." + ) + else: + sub_model = ACCOUNT_MODES[v].construct() + return sub_model + + def create_data_source(self): + return self.account_type.create_data_source( + network=self.network.network(), + use_secure_connection=self.network.use_secure_connection(), + rate_limits=self.network.rate_limits(), + ) + + +KEYS = InjectiveConfigMap.construct() diff --git a/hummingbot/connector/exchange/injective_v2/injective_v2_web_utils.py b/hummingbot/connector/exchange/injective_v2/injective_v2_web_utils.py new file mode 100644 index 0000000..082f232 --- /dev/null +++ b/hummingbot/connector/exchange/injective_v2/injective_v2_web_utils.py @@ -0,0 +1,15 @@ +import time +from typing import Optional + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + return _time() * 1e3 + + +def _time() -> float: + return time.time() diff --git a/hummingbot/connector/exchange/kraken/__init__.py b/hummingbot/connector/exchange/kraken/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py b/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py new file mode 100755 index 0000000..7c16566 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py @@ -0,0 +1,290 @@ +import asyncio +import logging +import time +from typing import Any, Dict, List, Optional + +import pandas as pd + +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_order_book import KrakenOrderBook +from hummingbot.connector.exchange.kraken.kraken_utils import ( + build_api_factory, + build_rate_limits_by_tier, + convert_from_exchange_trading_pair, + convert_to_exchange_trading_pair, +) +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class KrakenAPIOrderBookDataSource(OrderBookTrackerDataSource): + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + + _kraobds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._kraobds_logger is None: + cls._kraobds_logger = logging.getLogger(__name__) + return cls._kraobds_logger + + def __init__(self, + throttler: Optional[AsyncThrottler] = None, + trading_pairs: List[str] = None, + api_factory: Optional[WebAssistantsFactory] = None): + super().__init__(trading_pairs) + self._throttler = throttler or self._get_throttler_instance() + self._api_factory = api_factory or build_api_factory(throttler=throttler) + self._rest_assistant = None + self._ws_assistant = None + self._order_book_create_function = lambda: OrderBook() + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + throttler = AsyncThrottler(build_rate_limits_by_tier()) + return throttler + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._rest_assistant = await self._api_factory.get_rest_assistant() + return self._rest_assistant + + @classmethod + async def get_last_traded_prices( + cls, trading_pairs: List[str], throttler: Optional[AsyncThrottler] = None + ) -> Dict[str, float]: + throttler = throttler or cls._get_throttler_instance() + tasks = [cls._get_last_traded_price(t_pair, throttler) for t_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + @classmethod + async def _get_last_traded_price(cls, trading_pair: str, throttler: AsyncThrottler) -> float: + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.TICKER_PATH_URL}" + f"?pair={convert_to_exchange_trading_pair(trading_pair)}" + ) + + request = RESTRequest( + method=RESTMethod.GET, + url=url + ) + rest_assistant = await build_api_factory(throttler=throttler).get_rest_assistant() + + async with throttler.execute_task(CONSTANTS.TICKER_PATH_URL): + resp = await rest_assistant.call(request) + resp_json = await resp.json() + record = list(resp_json["result"].values())[0] + return float(record["c"][0]) + + @classmethod + async def get_snapshot( + cls, + rest_assistant: RESTAssistant, + trading_pair: str, + limit: int = 1000, + throttler: Optional[AsyncThrottler] = None, + ) -> Dict[str, Any]: + throttler = throttler or cls._get_throttler_instance() + original_trading_pair: str = trading_pair + if limit != 0: + params = { + "count": str(limit), + "pair": convert_to_exchange_trading_pair(trading_pair) + } + else: + params = {"pair": convert_to_exchange_trading_pair(trading_pair)} + async with throttler.execute_task(CONSTANTS.SNAPSHOT_PATH_URL): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SNAPSHOT_PATH_URL}" + + request = RESTRequest( + method=RESTMethod.GET, + url=url, + params=params + ) + + response = await rest_assistant.call(request) + + if response.status != 200: + raise IOError(f"Error fetching Kraken market snapshot for {original_trading_pair}. " + f"HTTP status is {response.status}.") + response_json = await response.json() + if len(response_json["error"]) > 0: + raise IOError(f"Error fetching Kraken market snapshot for {original_trading_pair}. " + f"Error is {response_json['error']}.") + data: Dict[str, Any] = next(iter(response_json["result"].values())) + data = {"trading_pair": trading_pair, **data} + data["latest_update"] = max([*map(lambda x: x[2], data["bids"] + data["asks"])], default=0.) + + return data + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + rest_assistant = await self._get_rest_assistant() + snapshot: Dict[str, Any] = await self.get_snapshot( + rest_assistant, trading_pair, limit=1000, throttler=self._throttler + ) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = KrakenOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + order_book: OrderBook = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + return order_book + + @classmethod + async def fetch_trading_pairs(cls, throttler: Optional[AsyncThrottler] = None) -> List[str]: + throttler = throttler or cls._get_throttler_instance() + try: + async with throttler.execute_task(CONSTANTS.ASSET_PAIRS_PATH_URL): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + request = RESTRequest( + method=RESTMethod.GET, + url=url + ) + rest_assistant = await build_api_factory(throttler=throttler).get_rest_assistant() + response = await rest_assistant.call(request, timeout=5) + + if response.status == 200: + data: Dict[str, Any] = await response.json() + raw_pairs = data.get("result", []) + converted_pairs: List[str] = [] + for pair, details in raw_pairs.items(): + if "." not in pair: + try: + wsname = details["wsname"] # pair in format BASE/QUOTE + converted_pairs.append(convert_from_exchange_trading_pair(wsname)) + except IOError: + pass + return [item for item in converted_pairs] + except Exception: + pass + # Do nothing if the request fails -- there will be no autocomplete for kraken trading pairs + return [] + + async def listen_for_trades(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + while True: + try: + ws_message: str = await self.get_ws_subscription_message("trade") + + async with self._throttler.execute_task(CONSTANTS.WS_CONNECTION_LIMIT_ID): + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=self.PING_TIMEOUT) + + await ws.send(ws_message) + async for ws_response in ws.iter_messages(): + msg = ws_response.data + if not (type(msg) is dict and "event" in msg.keys() and + msg["event"] in ["heartbeat", "systemStatus", "subscriptionStatus"]): + trades = [ + {"pair": convert_from_exchange_trading_pair(msg[-1]), "trade": trade} + for trade in msg[1] + ] + for trade in trades: + trade_msg: OrderBookMessage = KrakenOrderBook.trade_message_from_exchange(trade) + output.put_nowait(trade_msg) + ws.disconnect() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", + exc_info=True) + await asyncio.sleep(30.0) + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + ws_message: str = await self.get_ws_subscription_message("book") + async with self._throttler.execute_task(CONSTANTS.WS_CONNECTION_LIMIT_ID): + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_URL, ping_timeout=self.PING_TIMEOUT) + + await ws.send(ws_message) + async for ws_response in ws.iter_messages(): + msg = ws_response.data + if not (type(msg) is dict and "event" in msg.keys() and + msg["event"] in ["heartbeat", "systemStatus", "subscriptionStatus"]): + msg_dict = {"trading_pair": convert_from_exchange_trading_pair(msg[-1]), + "asks": msg[1].get("a", []) or msg[1].get("as", []) or [], + "bids": msg[1].get("b", []) or msg[1].get("bs", []) or []} + msg_dict["update_id"] = max( + [*map(lambda x: float(x[2]), msg_dict["bids"] + msg_dict["asks"])], default=0. + ) + if "as" in msg[1] and "bs" in msg[1]: + order_book_message: OrderBookMessage = ( + KrakenOrderBook.snapshot_ws_message_from_exchange(msg_dict, time.time()) + ) + else: + order_book_message: OrderBookMessage = KrakenOrderBook.diff_message_from_exchange( + msg_dict, time.time()) + output.put_nowait(order_book_message) + ws.disconnect() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", + exc_info=True) + await asyncio.sleep(30.0) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + rest_assistant = await self._get_rest_assistant() + while True: + try: + for trading_pair in self._trading_pairs: + try: + snapshot: Dict[str, Any] = await self.get_snapshot( + rest_assistant, trading_pair, throttler=self._throttler + ) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = KrakenOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + output.put_nowait(snapshot_msg) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + await asyncio.sleep(5.0) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error. ", exc_info=True) + await asyncio.sleep(5.0) + this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0) + next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) + delta: float = next_hour.timestamp() - time.time() + await asyncio.sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error. ", exc_info=True) + await asyncio.sleep(5.0) + + async def get_ws_subscription_message(self, subscription_type: str): + trading_pairs: List[str] = [] + for tp in self._trading_pairs: + trading_pairs.append(convert_to_exchange_trading_pair(tp, '/')) + + ws_message: WSJSONRequest = WSJSONRequest({ + "event": "subscribe", + "pair": trading_pairs, + "subscription": {"name": subscription_type, "depth": 1000}}) + + return ws_message + + async def listen_for_subscriptions(self): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + # This connector does not use this base class method and needs a refactoring + pass diff --git a/hummingbot/connector/exchange/kraken/kraken_api_user_stream_data_source.py b/hummingbot/connector/exchange/kraken/kraken_api_user_stream_data_source.py new file mode 100755 index 0000000..c3484c2 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_api_user_stream_data_source.py @@ -0,0 +1,126 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +from hummingbot.connector.exchange.kraken.kraken_order_book import KrakenOrderBook +from hummingbot.connector.exchange.kraken.kraken_utils import build_api_factory +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +MESSAGE_TIMEOUT = 3.0 +PING_TIMEOUT = 5.0 + + +class KrakenAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _krausds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._krausds_logger is None: + cls._krausds_logger = logging.getLogger(__name__) + return cls._krausds_logger + + def __init__(self, + throttler: AsyncThrottler, + kraken_auth: KrakenAuth, + api_factory: Optional[WebAssistantsFactory] = None): + self._throttler = throttler + self._api_factory = api_factory or build_api_factory(throttler=throttler) + self._rest_assistant = None + self._ws_assistant = None + self._kraken_auth: KrakenAuth = kraken_auth + self._current_auth_token: Optional[str] = None + super().__init__() + + @property + def order_book_class(self): + return KrakenOrderBook + + @property + def last_recv_time(self): + if self._ws_assistant is None: + return 0 + else: + return self._ws_assistant.last_recv_time + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._rest_assistant = await self._api_factory.get_rest_assistant() + return self._rest_assistant + + async def get_auth_token(self) -> str: + api_auth: Dict[str, Any] = self._kraken_auth.generate_auth_dict(uri=CONSTANTS.GET_TOKEN_PATH_URL) + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.GET_TOKEN_PATH_URL}" + + request = RESTRequest( + method=RESTMethod.POST, + url=url, + headers=api_auth["headers"], + data=api_auth["postDict"] + ) + rest_assistant = await self._get_rest_assistant() + + async with self._throttler.execute_task(CONSTANTS.GET_TOKEN_PATH_URL): + response = await rest_assistant.call(request=request, timeout=100) + if response.status != 200: + raise IOError(f"Error fetching Kraken user stream listen key. HTTP status is {response.status}.") + + try: + response_json: Dict[str, Any] = await response.json() + except Exception: + raise IOError(f"Error parsing data from {url}.") + + err = response_json["error"] + if "EAPI:Invalid nonce" in err: + self.logger().error(f"Invalid nonce error from {url}. " + + "Please ensure your Kraken API key nonce window is at least 10, " + + "and if needed reset your API key.") + raise IOError({"error": response_json}) + + return response_json["result"]["token"] + + async def listen_for_user_stream(self, output: asyncio.Queue): + ws = None + while True: + try: + async with self._throttler.execute_task(CONSTANTS.WS_CONNECTION_LIMIT_ID): + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WS_AUTH_URL, ping_timeout=PING_TIMEOUT) + + if self._current_auth_token is None: + self._current_auth_token = await self.get_auth_token() + + for subscription_type in ["openOrders", "ownTrades"]: + subscribe_request: WSJSONRequest = WSJSONRequest({ + "event": "subscribe", + "subscription": { + "name": subscription_type, + "token": self._current_auth_token + } + }) + await ws.send(subscribe_request) + + async for ws_response in ws.iter_messages(): + msg = ws_response.data + if not (type(msg) is dict and "event" in msg.keys() and + msg["event"] in ["heartbeat", "systemStatus", "subscriptionStatus"]): + output.put_nowait(msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error with Kraken WebSocket connection. " + "Retrying after 30 seconds...", exc_info=True) + self._current_auth_token = None + await asyncio.sleep(30.0) + finally: + if ws is not None: + await ws.disconnect() diff --git a/hummingbot/connector/exchange/kraken/kraken_auth.py b/hummingbot/connector/exchange/kraken/kraken_auth.py new file mode 100755 index 0000000..1359d6d --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_auth.py @@ -0,0 +1,49 @@ +from typing import ( + Optional, + Dict, + Any +) +import base64 +import hashlib +import hmac +from hummingbot.connector.exchange.kraken.kraken_tracking_nonce import get_tracking_nonce + + +class KrakenAuth: + def __init__(self, api_key: str, secret_key: str): + self.api_key = api_key + self.secret_key = secret_key + + def generate_auth_dict(self, uri: str, data: Optional[Dict[str, str]] = None) -> Dict[str, Any]: + """ + Generates authentication signature and returns it in a dictionary + :return: a dictionary of request info including the request signature and post data + """ + + # Decode API private key from base64 format displayed in account management + api_secret: bytes = base64.b64decode(self.secret_key) + + # Variables (API method, nonce, and POST data) + api_path: bytes = bytes(uri, 'utf-8') + api_nonce: str = get_tracking_nonce() + api_post: str = "nonce=" + api_nonce + + if data is not None: + for key, value in data.items(): + api_post += f"&{key}={value}" + + # Cryptographic hash algorithms + api_sha256: bytes = hashlib.sha256(bytes(api_nonce + api_post, 'utf-8')).digest() + api_hmac: hmac.HMAC = hmac.new(api_secret, api_path + api_sha256, hashlib.sha512) + + # Encode signature into base64 format used in API-Sign value + api_signature: bytes = base64.b64encode(api_hmac.digest()) + + return { + "headers": { + "API-Key": self.api_key, + "API-Sign": str(api_signature, 'utf-8') + }, + "post": api_post, + "postDict": {"nonce": api_nonce, **data} if data is not None else {"nonce": api_nonce} + } diff --git a/hummingbot/connector/exchange/kraken/kraken_constants.py b/hummingbot/connector/exchange/kraken/kraken_constants.py new file mode 100644 index 0000000..f30dca5 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_constants.py @@ -0,0 +1,100 @@ +from enum import Enum +from typing import ( + Dict, + Tuple, +) +from hummingbot.core.api_throttler.data_types import RateLimit, LinkedLimitWeightPair + + +class KrakenAPITier(Enum): + """ + Kraken's Private Endpoint Rate Limit Tiers, based on the Account Verification level. + """ + STARTER = "STARTER" + INTERMEDIATE = "INTERMEDIATE" + PRO = "PRO" + + +# Values are calculated by adding the Maxiumum Counter value and the expected count decay(in a minute) of a given tier. +# Reference: +# - API Rate Limits: https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits +# - Matching Engine Limits: https://support.kraken.com/hc/en-us/articles/360045239571 +STARTER_PRIVATE_ENDPOINT_LIMIT = 15 + 20 +STARTER_MATCHING_ENGINE_LIMIT = 60 + 60 +INTERMEDIATE_PRIVATE_ENDPOINT_LIMIT = 20 + 30 +INTERMEDIATE_MATCHING_ENGINE_LIMIT = 125 + 140 +PRO_PRIVATE_ENDPOINT_LIMIT = 20 + 60 +PRO_MATCHING_ENGINE_LIMIT = 180 + 225 + +KRAKEN_TIER_LIMITS: Dict[KrakenAPITier, Tuple[int, int]] = { + KrakenAPITier.STARTER: (STARTER_PRIVATE_ENDPOINT_LIMIT, STARTER_MATCHING_ENGINE_LIMIT), + KrakenAPITier.INTERMEDIATE: (INTERMEDIATE_PRIVATE_ENDPOINT_LIMIT, INTERMEDIATE_MATCHING_ENGINE_LIMIT), + KrakenAPITier.PRO: (PRO_PRIVATE_ENDPOINT_LIMIT, PRO_MATCHING_ENGINE_LIMIT), +} + +KRAKEN_TO_HB_MAP = { + "XBT": "BTC", + "XDG": "DOGE", +} + +BASE_URL = "https://api.kraken.com" +TICKER_PATH_URL = "/0/public/Ticker" +SNAPSHOT_PATH_URL = "/0/public/Depth" +ASSET_PAIRS_PATH_URL = "/0/public/AssetPairs" +TIME_PATH_URL = "/0/public/Time" +GET_TOKEN_PATH_URL = "/0/private/GetWebSocketsToken" +ADD_ORDER_PATH_URL = "/0/private/AddOrder" +CANCEL_ORDER_PATH_URL = "/0/private/CancelOrder" +BALANCE_PATH_URL = "/0/private/Balance" +OPEN_ORDERS_PATH_URL = "/0/private/OpenOrders" +QUERY_ORDERS_PATH_URL = "/0/private/QueryOrders" + +WS_URL = "wss://ws.kraken.com" +WS_AUTH_URL = "wss://ws-auth.kraken.com/" + +PUBLIC_ENDPOINT_LIMIT_ID = "PublicEndpointLimitID" +PUBLIC_ENDPOINT_LIMIT = 1 +PUBLIC_ENDPOINT_LIMIT_INTERVAL = 1 +PRIVATE_ENDPOINT_LIMIT_ID = "PrivateEndpointLimitID" +PRIVATE_ENDPOINT_LIMIT_INTERVAL = 60 +MATCHING_ENGINE_LIMIT_ID = "MatchingEngineLimitID" +MATCHING_ENGINE_LIMIT_INTERVAL = 60 +WS_CONNECTION_LIMIT_ID = "WSConnectionLimitID" + +PUBLIC_API_LIMITS = [ + # Public API Pool + RateLimit( + limit_id=PUBLIC_ENDPOINT_LIMIT_ID, + limit=PUBLIC_ENDPOINT_LIMIT, + time_interval=PUBLIC_ENDPOINT_LIMIT_INTERVAL, + ), + # Public Endpoints + RateLimit( + limit_id=SNAPSHOT_PATH_URL, + limit=PUBLIC_ENDPOINT_LIMIT, + time_interval=PUBLIC_ENDPOINT_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(PUBLIC_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=ASSET_PAIRS_PATH_URL, + limit=PUBLIC_ENDPOINT_LIMIT, + time_interval=PUBLIC_ENDPOINT_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(PUBLIC_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=TICKER_PATH_URL, + limit=PUBLIC_ENDPOINT_LIMIT, + time_interval=PUBLIC_ENDPOINT_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(PUBLIC_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=TIME_PATH_URL, + limit=PUBLIC_ENDPOINT_LIMIT, + time_interval=PUBLIC_ENDPOINT_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(PUBLIC_ENDPOINT_LIMIT_ID)], + ), + # WebSocket Connection Limit + RateLimit(limit_id=WS_CONNECTION_LIMIT_ID, + limit=150, + time_interval=60 * 10), +] diff --git a/hummingbot/connector/exchange/kraken/kraken_exchange.pxd b/hummingbot/connector/exchange/kraken/kraken_exchange.pxd new file mode 100755 index 0000000..accc4a9 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_exchange.pxd @@ -0,0 +1,43 @@ +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.data_type.transaction_tracker cimport TransactionTracker +from libc.stdint cimport int32_t + + +cdef class KrakenExchange(ExchangeBase): + cdef: + object _user_stream_tracker + object _ev_loop + object _poll_notifier + double _last_timestamp + double _poll_interval + double _last_pull_timestamp + dict _in_flight_orders + dict _order_not_found_records + TransactionTracker _tx_tracker + dict _trading_rules + dict _trade_fees + double _last_update_trade_fees_timestamp + public object _status_polling_task + public object _user_stream_event_listener_task + public object _user_stream_tracker_task + public object _trading_rules_polling_task + object _async_scheduler + object _set_server_time_offset_task + public object _kraken_auth + object _api_factory + object _rest_assistant + dict _asset_pairs + int32_t _last_userref + object _throttler + object _kraken_api_tier + + cdef c_did_timeout_tx(self, str tracking_id) + cdef c_start_tracking_order(self, + str order_id, + str exchange_order_id, + str trading_pair, + object trade_type, + object price, + object amount, + object order_type, + int userref) diff --git a/hummingbot/connector/exchange/kraken/kraken_exchange.pyx b/hummingbot/connector/exchange/kraken/kraken_exchange.pyx new file mode 100755 index 0000000..e0f96d7 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_exchange.pyx @@ -0,0 +1,1150 @@ +import asyncio +import copy +import logging +import re +from collections import defaultdict +from decimal import Decimal +from typing import ( + Any, + AsyncIterable, + Dict, + List, + Optional, + TYPE_CHECKING, +) + +from async_timeout import timeout +from libc.stdint cimport int32_t, int64_t + +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_api_order_book_data_source import KrakenAPIOrderBookDataSource +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +from hummingbot.connector.exchange.kraken.kraken_constants import KrakenAPITier +from hummingbot.connector.exchange.kraken.kraken_in_flight_order import ( + KrakenInFlightOrder, + KrakenInFlightOrderNotCreated, +) +from hummingbot.connector.exchange.kraken.kraken_order_book_tracker import KrakenOrderBookTracker +from hummingbot.connector.exchange.kraken.kraken_user_stream_tracker import KrakenUserStreamTracker +from hummingbot.connector.exchange.kraken.kraken_utils import ( + build_api_factory, + build_rate_limits_by_tier, + convert_from_exchange_symbol, + convert_from_exchange_trading_pair, + convert_to_exchange_trading_pair, + is_dark_pool, + split_to_base_quote, +) +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.trading_rule cimport TradingRule +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.data_type.transaction_tracker import TransactionTracker +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + MarketTransactionFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal(0) +s_decimal_NaN = Decimal("NaN") + + +cdef class KrakenExchangeTransactionTracker(TransactionTracker): + cdef: + KrakenExchange _owner + + def __init__(self, owner: KrakenExchange): + super().__init__() + self._owner = owner + + cdef c_did_timeout_tx(self, str tx_id): + TransactionTracker.c_did_timeout_tx(self, tx_id) + self._owner.c_did_timeout_tx(tx_id) + + +cdef class KrakenExchange(ExchangeBase): + MARKET_RECEIVED_ASSET_EVENT_TAG = MarketEvent.ReceivedAsset.value + MARKET_BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value + MARKET_SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value + MARKET_ORDER_CANCELED_EVENT_TAG = MarketEvent.OrderCancelled.value + MARKET_TRANSACTION_FAILURE_EVENT_TAG = MarketEvent.TransactionFailure.value + MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value + MARKET_ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value + MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value + MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value + + REQUEST_ATTEMPTS = 5 + + ORDER_NOT_EXIST_CONFIRMATION_COUNT = 3 + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def __init__(self, + client_config_map: "ClientConfigAdapter", + kraken_api_key: str, + kraken_secret_key: str, + poll_interval: float = 30.0, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + kraken_api_tier: str = "starter"): + + super().__init__(client_config_map) + self._trading_required = trading_required + self._kraken_api_tier = KrakenAPITier(kraken_api_tier.upper()) + self._throttler = self._build_async_throttler(api_tier=self._kraken_api_tier) + self._api_factory = build_api_factory(throttler=self._throttler) + self._rest_assistant = None + self._set_order_book_tracker(KrakenOrderBookTracker(trading_pairs=trading_pairs, throttler=self._throttler)) + self._kraken_auth = KrakenAuth(kraken_api_key, kraken_secret_key) + self._user_stream_tracker = KrakenUserStreamTracker(self._throttler, self._kraken_auth, self._api_factory) + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + self._last_timestamp = 0 + self._poll_interval = poll_interval + self._in_flight_orders = {} # Dict[client_order_id:str, KrakenInFlightOrder] + self._order_not_found_records = {} # Dict[client_order_id:str, count:int] + self._tx_tracker = KrakenExchangeTransactionTracker(self) + self._trading_rules = {} # Dict[trading_pair:str, TradingRule] + self._trade_fees = {} # Dict[trading_pair:str, (maker_fee_percent:Decimal, taken_fee_percent:Decimal)] + self._last_update_trade_fees_timestamp = 0 + self._status_polling_task = None + self._user_stream_tracker_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + self._async_scheduler = AsyncCallScheduler(call_interval=0.5) + self._last_pull_timestamp = 0 + self._asset_pairs = {} + self._last_userref = 0 + self._real_time_balance_update = False + + @property + def name(self) -> str: + return "kraken" + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self.order_book_tracker.order_books + + @property + def kraken_auth(self) -> KrakenAuth: + return self._kraken_auth + + @property + def trading_rules(self) -> Dict[str, TradingRule]: + return self._trading_rules + + @property + def in_flight_orders(self) -> Dict[str, KrakenInFlightOrder]: + return self._in_flight_orders + + @property + def limit_orders(self) -> List[LimitOrder]: + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + @property + def tracking_states(self) -> Dict[str, Any]: + return { + order_id: value.to_json() + for order_id, value in self._in_flight_orders.items() + } + + def restore_tracking_states(self, saved_states: Dict[str, Any]): + in_flight_orders: Dict[str, KrakenInFlightOrder] = {} + for key, value in saved_states.items(): + in_flight_orders[key] = KrakenInFlightOrder.from_json(value) + self._last_userref = max(int(value["userref"]), self._last_userref) + self._in_flight_orders.update(in_flight_orders) + + async def get_asset_pairs(self) -> Dict[str, Any]: + if not self._asset_pairs: + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + asset_pairs = await self._api_request(method="get", endpoint=CONSTANTS.ASSET_PAIRS_PATH_URL) + self._asset_pairs = {f"{details['base']}-{details['quote']}": details + for _, details in asset_pairs.items() if not is_dark_pool(details)} + return self._asset_pairs + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._rest_assistant = await self._api_factory.get_rest_assistant() + return self._rest_assistant + + async def _update_balances(self): + cdef: + dict open_orders + dict balances + str asset_name + str balance + str base + str quote + set local_asset_names = set(self._account_balances.keys()) + set remote_asset_names = set() + set asset_names_to_remove + + balances = await self._api_request_with_retry("POST", CONSTANTS.BALANCE_PATH_URL, is_auth_required=True) + open_orders = await self._api_request_with_retry("POST", CONSTANTS.OPEN_ORDERS_PATH_URL, is_auth_required=True) + + locked = defaultdict(Decimal) + + for order in open_orders.get("open").values(): + if order.get("status") == "open": + details = order.get("descr") + if details.get("ordertype") == "limit": + pair = convert_from_exchange_trading_pair( + details.get("pair"), tuple((await self.get_asset_pairs()).keys()) + ) + (base, quote) = self.split_trading_pair(pair) + vol_locked = Decimal(order.get("vol", 0)) - Decimal(order.get("vol_exec", 0)) + if details.get("type") == "sell": + locked[convert_from_exchange_symbol(base)] += vol_locked + elif details.get("type") == "buy": + locked[convert_from_exchange_symbol(quote)] += vol_locked * Decimal(details.get("price")) + + for asset_name, balance in balances.items(): + cleaned_name = convert_from_exchange_symbol(asset_name).upper() + total_balance = Decimal(balance) + free_balance = total_balance - Decimal(locked[cleaned_name]) + self._account_available_balances[cleaned_name] = free_balance + self._account_balances[cleaned_name] = total_balance + remote_asset_names.add(cleaned_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._in_flight_orders.items()} + self._in_flight_orders_snapshot_timestamp = self._current_timestamp + + cdef object c_get_fee(self, + str base_currency, + str quote_currency, + object order_type, + object order_side, + object amount, + object price, + object is_maker = None): + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = order_type is OrderType.LIMIT_MAKER + return AddedToCostTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _update_trading_rules(self): + cdef: + # The poll interval for withdraw rules is 60 seconds. + int64_t last_tick = (self._last_timestamp / 60.0) + int64_t current_tick = (self._current_timestamp / 60.0) + if current_tick > last_tick or len(self._trading_rules) < 1: + asset_pairs = await self.get_asset_pairs() + trading_rules_list = self._format_trading_rules(asset_pairs) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[convert_from_exchange_trading_pair(trading_rule.trading_pair)] = trading_rule + + def _format_trading_rules(self, asset_pairs_dict: Dict[str, Any]) -> List[TradingRule]: + """ + Example: + { + "XBTUSDT": { + "altname": "XBTUSDT", + "wsname": "XBT/USDT", + "aclass_base": "currency", + "base": "XXBT", + "aclass_quote": "currency", + "quote": "USDT", + "lot": "unit", + "pair_decimals": 1, + "lot_decimals": 8, + "lot_multiplier": 1, + "leverage_buy": [2, 3], + "leverage_sell": [2, 3], + "fees": [ + [0, 0.26], + [50000, 0.24], + [100000, 0.22], + [250000, 0.2], + [500000, 0.18], + [1000000, 0.16], + [2500000, 0.14], + [5000000, 0.12], + [10000000, 0.1] + ], + "fees_maker": [ + [0, 0.16], + [50000, 0.14], + [100000, 0.12], + [250000, 0.1], + [500000, 0.08], + [1000000, 0.06], + [2500000, 0.04], + [5000000, 0.02], + [10000000, 0] + ], + "fee_volume_currency": "ZUSD", + "margin_call": 80, + "margin_stop": 40, + "ordermin": "0.0002" + } + } + """ + cdef: + list retval = [] + for trading_pair, rule in asset_pairs_dict.items(): + try: + base, quote = split_to_base_quote(trading_pair) + base = convert_from_exchange_symbol(base) + min_order_size = Decimal(rule.get('ordermin', 0)) + min_price_increment = Decimal(f"1e-{rule.get('pair_decimals')}") + min_base_amount_increment = Decimal(f"1e-{rule.get('lot_decimals')}") + retval.append( + TradingRule( + trading_pair, + min_order_size=min_order_size, + min_price_increment=min_price_increment, + min_base_amount_increment=min_base_amount_increment, + ) + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True) + return retval + + async def _update_order_status(self): + cdef: + # This is intended to be a backup measure to close straggler orders, in case Kraken's user stream events + # are not working. + # The poll interval for order status is 10 seconds. + int64_t last_tick = (self._last_pull_timestamp / 10.0) + int64_t current_tick = (self._current_timestamp / 10.0) + + if len(self._in_flight_orders) > 0: + tracked_orders = list(self._in_flight_orders.values()) + tasks = [self._api_request_with_retry("POST", + CONSTANTS.QUERY_ORDERS_PATH_URL, + data={"txid": o.exchange_order_id}, + is_auth_required=True) + for o in tracked_orders] + results = await safe_gather(*tasks, return_exceptions=True) + + for order_update, tracked_order in zip(results, tracked_orders): + client_order_id = tracked_order.client_order_id + + # If the order has already been cancelled or has failed do nothing + if client_order_id not in self._in_flight_orders: + continue + + if isinstance(order_update, Exception): + self.logger().network( + f"Error fetching status update for the order {client_order_id}: {order_update}.", + app_warning_msg=f"Failed to fetch status update for the order {client_order_id}." + ) + continue + + if order_update.get("error") is not None and "EOrder:Invalid order" not in order_update["error"]: + self.logger().debug(f"Error in fetched status update for order {client_order_id}: " + f"{order_update['error']}") + self.c_cancel(tracked_order.trading_pair, tracked_order.client_order_id) + continue + + update = order_update.get(tracked_order.exchange_order_id) + + if not update: + self._order_not_found_records[client_order_id] = \ + self._order_not_found_records.get(client_order_id, 0) + 1 + if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT: + # Wait until the order not found error have repeated a few times before actually treating + # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601 + continue + self.c_trigger_event( + self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, client_order_id, tracked_order.order_type) + ) + self.c_stop_tracking_order(client_order_id) + continue + + # Update order execution status + tracked_order.last_state = update["status"] + executed_amount_base = Decimal(update["vol_exec"]) + executed_amount_quote = executed_amount_base * Decimal(update["price"]) + + if tracked_order.is_done: + if not tracked_order.is_failure: + if tracked_order.trade_type is TradeType.BUY: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + executed_amount_base, + executed_amount_quote, + tracked_order.order_type)) + else: + self.logger().info(f"The market sell order {client_order_id} has completed " + f"according to order status API.") + self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + executed_amount_base, + executed_amount_quote, + tracked_order.order_type)) + else: + # check if its a cancelled order + # if its a cancelled order, issue cancel and stop tracking order + if tracked_order.is_cancelled: + self.logger().info(f"Successfully canceled order {client_order_id}.") + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent( + self._current_timestamp, + client_order_id)) + else: + self.logger().info(f"The market order {client_order_id} has failed according to " + f"order status API.") + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent( + self._current_timestamp, + client_order_id, + tracked_order.order_type + )) + self.c_stop_tracking_order(client_order_id) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, Any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from Kraken. Check API key and network connection." + ) + await asyncio.sleep(1.0) + + async def _user_stream_event_listener(self): + async for event_message in self._iter_user_event_queue(): + try: + # Event type is second from last, there is newly added sequence number (last item). + # https://docs.kraken.com/websockets/#sequence-numbers + event_type: str = event_message[-2] + updates: List[Any] = event_message[0] + if event_type == "ownTrades": + for update in updates: + trade_id: str = next(iter(update)) + trade: Dict[str, str] = update[trade_id] + trade["trade_id"] = trade_id + exchange_order_id = trade.get("ordertxid") + try: + client_order_id = next(key for key, value in self._in_flight_orders.items() + if value.exchange_order_id == exchange_order_id) + except StopIteration: + continue + + tracked_order = self._in_flight_orders.get(client_order_id) + + if tracked_order is None: + # Hiding the messages for now. Root cause to be investigated in later sprints. + self.logger().debug(f"Unrecognized order ID from user stream: {client_order_id}.") + self.logger().debug(f"Event: {event_message}") + self.logger().debug(f"Order Event: {update}") + continue + + updated: bool = tracked_order.update_with_trade_update(trade) + if updated: + self.c_trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, + OrderFilledEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + Decimal(trade.get("price")), + Decimal(trade.get("vol")), + AddedToCostTradeFee( + flat_fees=[ + TokenAmount( + tracked_order.fee_asset, + Decimal((trade.get("fee"))), + ) + ] + ), + trade.get("trade_id"))) + + if tracked_order.is_done: + if not tracked_order.is_failure: + if tracked_order.trade_type is TradeType.BUY: + self.logger().info(f"The market buy order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.c_trigger_event(self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type)) + else: + self.logger().info(f"The market sell order {tracked_order.client_order_id} has completed " + f"according to user stream.") + self.c_trigger_event(self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type)) + else: + # check if its a cancelled order + # if its a cancelled order, check in flight orders + # if present in in flight orders issue cancel and stop tracking order + if tracked_order.is_cancelled: + if tracked_order.client_order_id in self._in_flight_orders: + self.logger().info(f"Successfully canceled order {tracked_order.client_order_id}.") + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, + tracked_order.client_order_id)) + else: + self.logger().info(f"The market order {tracked_order.client_order_id} has failed according to " + f"order status API.") + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, + tracked_order.client_order_id, + tracked_order.order_type)) + + self.c_stop_tracking_order(tracked_order.client_order_id) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await asyncio.sleep(5.0) + + async def _status_polling_loop(self): + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self._update_balances(), + self._update_order_status(), + ) + self._last_pull_timestamp = self._current_timestamp + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error while fetching account updates.", exc_info=True, + app_warning_msg="Could not fetch account updates from Kraken. " + "Check API key and network connection.") + await asyncio.sleep(0.5) + + async def _trading_rules_polling_loop(self): + while True: + try: + await safe_gather( + self._update_trading_rules(), + ) + await asyncio.sleep(60) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error while fetching trading rules.", exc_info=True, + app_warning_msg="Could not fetch new trading rules from Kraken. " + "Check network connection.") + await asyncio.sleep(0.5) + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0, + } + + @property + def ready(self) -> bool: + return all(self.status_dict.values()) + + cdef c_start(self, Clock clock, double timestamp): + self._tx_tracker.c_start(clock, timestamp) + ExchangeBase.c_start(self, clock, timestamp) + + cdef c_stop(self, Clock clock): + ExchangeBase.c_stop(self, clock) + self._async_scheduler.stop() + + async def start_network(self): + self._stop_network() + self.order_book_tracker.start() + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + def _stop_network(self): + self.order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._status_polling_task = self._user_stream_tracker_task = \ + self._user_stream_event_listener_task = None + + async def stop_network(self): + self._stop_network() + + async def check_network(self) -> NetworkStatus: + try: + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}" + request = RESTRequest( + method=RESTMethod.GET, + url=url + ) + rest_assistant = await self._get_rest_assistant() + async with self._throttler.execute_task(CONSTANTS.TIME_PATH_URL): + resp = await rest_assistant.call(request=request) + if resp.status != 200: + raise ConnectionError + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + cdef c_tick(self, double timestamp): + cdef: + int64_t last_tick = (self._last_timestamp / self._poll_interval) + int64_t current_tick = (timestamp / self._poll_interval) + ExchangeBase.c_tick(self, timestamp) + self._tx_tracker.c_tick(timestamp) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + self._last_timestamp = timestamp + + def generate_userref(self): + self._last_userref += 1 + return self._last_userref + + @staticmethod + def is_cloudflare_exception(exception: Exception): + """ + Error status 5xx or 10xx are related to Cloudflare. + https://support.kraken.com/hc/en-us/articles/360001491786-API-error-messages#6 + """ + return bool(re.search(r"HTTP status is (5|10)\d\d\.", str(exception))) + + async def get_open_orders_with_userref(self, userref: int): + data = {'userref': userref} + return await self._api_request_with_retry("POST", + CONSTANTS.OPEN_ORDERS_PATH_URL, + is_auth_required=True, + data=data) + + async def _api_request_with_retry(self, + method: str, + endpoint: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + retry_interval = 2.0) -> Dict[str, Any]: + result = None + for retry_attempt in range(self.REQUEST_ATTEMPTS): + try: + result= await self._api_request(method, endpoint, params, data, is_auth_required) + break + except IOError as e: + if self.is_cloudflare_exception(e): + if endpoint == CONSTANTS.ADD_ORDER_PATH_URL: + self.logger().info(f"Retrying {endpoint}") + # Order placement could have been successful despite the IOError, so check for the open order. + response = self.get_open_orders_with_userref(data.get('userref')) + if any(response.get("open").values()): + return response + self.logger().warning( + f"Cloudflare error. Attempt {retry_attempt+1}/{self.REQUEST_ATTEMPTS}" + f" API command {method}: {endpoint}" + ) + await asyncio.sleep(retry_interval ** retry_attempt) + continue + else: + raise e + if result is None: + raise IOError(f"Error fetching data from {endpoint}.") + return result + + async def _api_request(self, + method: str, + endpoint: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False) -> Dict[str, Any]: + async with self._throttler.execute_task(endpoint): + url = f"{CONSTANTS.BASE_URL}{endpoint}" + headers = {} + data_dict = data if data is not None else {} + + if is_auth_required: + auth_dict: Dict[str, Any] = self._kraken_auth.generate_auth_dict(endpoint, data=data) + headers.update(auth_dict["headers"]) + data_dict = auth_dict["postDict"] + + request = RESTRequest( + method=RESTMethod[method.upper()], + url=url, + headers=headers, + params=params, + data=data_dict + ) + rest_assistant = await self._get_rest_assistant() + response = await rest_assistant.call(request=request, timeout=100) + + if response.status != 200: + raise IOError(f"Error fetching data from {url}. HTTP status is {response.status}.") + try: + response_json = await response.json() + except Exception: + raise IOError(f"Error parsing data from {url}.") + + try: + err = response_json["error"] + if "EOrder:Unknown order" in err or "EOrder:Invalid order" in err: + return {"error": err} + elif "EAPI:Invalid nonce" in err: + self.logger().error(f"Invalid nonce error from {url}. " + + "Please ensure your Kraken API key nonce window is at least 10, " + + "and if needed reset your API key.") + raise IOError({"error": response_json}) + except IOError: + raise + except Exception: + pass + + data = response_json.get("result") + if data is None: + self.logger().error(f"Error received from {url}. Response is {response_json}.") + raise IOError({"error": response_json}) + return data + + async def get_order(self, client_order_id: str) -> Dict[str, Any]: + o = self._in_flight_orders.get(client_order_id) + result = await self._api_request_with_retry("POST", + CONSTANTS.QUERY_ORDERS_PATH_URL, + data={"txid": o.exchange_order_id}, + is_auth_required=True) + return result + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def place_order(self, + userref: int, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + is_buy: bool, + price: Optional[Decimal] = s_decimal_NaN): + + trading_pair = convert_to_exchange_trading_pair(trading_pair) + data = { + "pair": trading_pair, + "type": "buy" if is_buy else "sell", + "ordertype": "limit", + "volume": str(amount), + "userref": userref, + "price": str(price) + } + if order_type is OrderType.LIMIT_MAKER: + data["oflags"] = "post" + return await self._api_request_with_retry("post", + CONSTANTS.ADD_ORDER_PATH_URL, + data=data, + is_auth_required=True) + + async def execute_buy(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = s_decimal_NaN, + userref: int = 0): + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + str base_currency = self.split_trading_pair(trading_pair)[0] + str quote_currency = self.split_trading_pair(trading_pair)[1] + + decimal_amount = self.c_quantize_order_amount(trading_pair, amount) + decimal_price = self.c_quantize_order_price(trading_pair, price) + if decimal_amount < trading_rule.min_order_size: + raise ValueError(f"Buy order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + order_result = None + order_decimal_amount = f"{decimal_amount:f}" + if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: + order_decimal_price = f"{decimal_price:f}" + self.c_start_tracking_order( + order_id, + "", + trading_pair, + TradeType.BUY, + decimal_price, + decimal_amount, + order_type, + userref + ) + order_result = await self.place_order(userref=userref, + trading_pair=trading_pair, + amount=order_decimal_amount, + order_type=order_type, + is_buy=True, + price=order_decimal_price) + else: + raise ValueError(f"Invalid OrderType {order_type}. Aborting.") + + exchange_order_id = order_result["txid"][0] + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type} buy order {order_id} for " + f"{decimal_amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + self.c_trigger_event(self.MARKET_BUY_ORDER_CREATED_EVENT_TAG, + BuyOrderCreatedEvent( + self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp + )) + + except asyncio.CancelledError: + raise + + except Exception as e: + self.c_stop_tracking_order(order_id) + order_type_str = 'LIMIT' if order_type is OrderType.LIMIT else "LIMIT_MAKER" + self.logger().network( + f"Error submitting buy {order_type_str} order to Kraken for " + f"{decimal_amount} {trading_pair}" + f" {decimal_price}.", + exc_info=True, + app_warning_msg=f"Failed to submit buy order to Kraken. Check API key and network connection." + ) + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, order_type)) + + cdef str c_buy(self, str trading_pair, object amount, object order_type=OrderType.LIMIT, object price=s_decimal_NaN, + dict kwargs={}): + cdef: + int64_t tracking_nonce = get_tracking_nonce() + int32_t userref = self.generate_userref() + str order_id = str(f"buy-{trading_pair}-{tracking_nonce}") + safe_ensure_future(self.execute_buy(order_id, trading_pair, amount, order_type, price=price, userref=userref)) + return order_id + + async def execute_sell(self, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = Decimal("NaN"), + userref: int = 0): + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + + decimal_amount = self.quantize_order_amount(trading_pair, amount) + decimal_price = self.c_quantize_order_price(trading_pair, price) + + if decimal_amount < trading_rule.min_order_size: + raise ValueError(f"Sell order amount {decimal_amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + try: + order_result = None + order_decimal_amount = f"{decimal_amount:f}" + if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: + order_decimal_price = f"{decimal_price:f}" + self.c_start_tracking_order( + order_id, + "", + trading_pair, + TradeType.SELL, + decimal_price, + decimal_amount, + order_type, + userref + ) + order_result = await self.place_order(userref=userref, + trading_pair=trading_pair, + amount=order_decimal_amount, + order_type=order_type, + is_buy=False, + price=order_decimal_price) + else: + raise ValueError(f"Invalid OrderType {order_type}. Aborting.") + + exchange_order_id = order_result["txid"][0] + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type} sell order {order_id} for " + f"{decimal_amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + self.c_trigger_event(self.MARKET_SELL_ORDER_CREATED_EVENT_TAG, + SellOrderCreatedEvent( + self._current_timestamp, + order_type, + trading_pair, + decimal_amount, + decimal_price, + order_id, + tracked_order.creation_timestamp, + )) + except asyncio.CancelledError: + raise + except Exception: + self.c_stop_tracking_order(order_id) + order_type_str = 'LIMIT' if order_type is OrderType.LIMIT else "LIMIT_MAKER" + self.logger().network( + f"Error submitting sell {order_type_str} order to Kraken for " + f"{decimal_amount} {trading_pair} " + f"{decimal_price}.", + exc_info=True, + app_warning_msg=f"Failed to submit sell order to Kraken. Check API key and network connection." + ) + self.c_trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, order_type)) + + cdef str c_sell(self, + str trading_pair, + object amount, + object order_type=OrderType.LIMIT, + object price=s_decimal_NaN, + dict kwargs={}): + cdef: + int64_t tracking_nonce = get_tracking_nonce() + int32_t userref = self.generate_userref() + str order_id = str(f"sell-{trading_pair}-{tracking_nonce}") + safe_ensure_future(self.execute_sell(order_id, trading_pair, amount, order_type, price=price, userref=userref)) + return order_id + + async def execute_cancel(self, trading_pair: str, order_id: str): + try: + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is None: + raise ValueError(f"Failed to cancel order – {order_id}. Order not found.") + if tracked_order.is_local: + raise KrakenInFlightOrderNotCreated(f"Failed to cancel order - {order_id}. Order not yet created.") + + data: Dict[str, str] = {"txid": tracked_order.exchange_order_id} + cancel_result = await self._api_request_with_retry("POST", + CONSTANTS.CANCEL_ORDER_PATH_URL, + data=data, + is_auth_required=True) + + if isinstance(cancel_result, dict) and (cancel_result.get("count") == 1 or cancel_result.get("error") is not None): + self.logger().info(f"Successfully canceled order {order_id}.") + self.c_stop_tracking_order(order_id) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, order_id)) + return { + "origClientOrderId": order_id + } + except KrakenInFlightOrderNotCreated: + raise + except Exception as e: + self.logger().warning(f"Error canceling order on Kraken", + exc_info=True) + + cdef c_cancel(self, str trading_pair, str order_id): + safe_ensure_future(self.execute_cancel(trading_pair, order_id)) + return order_id + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + incomplete_orders = [(key, o) for (key, o) in self._in_flight_orders.items() if not o.is_done] + tasks = [self.execute_cancel(o.trading_pair, key) for (key, o) in incomplete_orders] + order_id_set = set([key for (key, o) in incomplete_orders]) + successful_cancellations = [] + + try: + async with timeout(timeout_seconds): + cancellation_results = await safe_gather(*tasks, return_exceptions=True) + for cr in cancellation_results: + if isinstance(cr, Exception): + continue + if isinstance(cr, dict) and "origClientOrderId" in cr: + client_order_id = cr.get("origClientOrderId") + order_id_set.remove(client_order_id) + successful_cancellations.append(CancellationResult(client_order_id, True)) + except Exception: + self.logger().network( + f"Unexpected error canceling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order with Kraken. Check API key and network connection." + ) + + failed_cancellations = [CancellationResult(oid, False) for oid in order_id_set] + return successful_cancellations + failed_cancellations + + cdef OrderBook c_get_order_book(self, str trading_pair): + cdef: + dict order_books = self.order_book_tracker.order_books + + if trading_pair not in order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return order_books[trading_pair] + + cdef c_did_timeout_tx(self, str tracking_id): + self.c_trigger_event(self.MARKET_TRANSACTION_FAILURE_EVENT_TAG, + MarketTransactionFailureEvent(self._current_timestamp, tracking_id)) + + def start_tracking_order(self, + order_id: str, + exchange_order_id: str, + trading_pair: str, + trade_type: TradeType, + price: float, + amount: float, + order_type: OrderType, + userref: int): + """Used for testing.""" + self.c_start_tracking_order( + order_id, exchange_order_id, trading_pair, trade_type, price, amount, order_type, userref + ) + + cdef c_start_tracking_order(self, + str order_id, + str exchange_order_id, + str trading_pair, + object trade_type, + object price, + object amount, + object order_type, + int userref): + self._in_flight_orders[order_id] = KrakenInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + trade_type=trade_type, + price=price, + amount=amount, + order_type=order_type, + creation_timestamp=self.current_timestamp, + userref=userref + ) + + cdef c_stop_tracking_order(self, str order_id): + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + if order_id in self._order_not_found_records: + del self._order_not_found_records[order_id] + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size): + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + cdef object c_quantize_order_amount(self, str trading_pair, object amount, object price=s_decimal_0): + cdef: + TradingRule trading_rule = self._trading_rules[trading_pair] + object quantized_amount = ExchangeBase.c_quantize_order_amount(self, trading_pair, amount) + + global s_decimal_0 + if quantized_amount < trading_rule.min_order_size: + return s_decimal_0 + + return quantized_amount + + def get_price(self, trading_pair: str, is_buy: bool) -> Decimal: + return self.c_get_price(trading_pair, is_buy) + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + return self.c_buy(trading_pair, amount, order_type, price, kwargs) + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + return self.c_sell(trading_pair, amount, order_type, price, kwargs) + + def cancel(self, trading_pair: str, client_order_id: str): + return self.c_cancel(trading_pair, client_order_id) + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + return self.c_get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + def get_order_book(self, trading_pair: str) -> OrderBook: + return self.c_get_order_book(trading_pair) + + def _build_async_throttler(self, api_tier: KrakenAPITier) -> AsyncThrottler: + limits_pct = self._client_config.rate_limits_share_pct + if limits_pct < Decimal("100"): + self.logger().warning( + f"The Kraken API does not allow enough bandwidth for a reduced rate-limit share percentage." + f" Current percentage: {limits_pct}." + ) + throttler = AsyncThrottler(build_rate_limits_by_tier(api_tier)) + return throttler + + async def all_trading_pairs(self) -> List[str]: + # This method should be removed and instead we should implement _initialize_trading_pair_symbol_map + return await KrakenAPIOrderBookDataSource.fetch_trading_pairs(throttler=self._throttler) + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + # This method should be removed and instead we should implement _get_last_traded_price + return await KrakenAPIOrderBookDataSource.get_last_traded_prices( + trading_pairs=trading_pairs, + throttler=self._throttler) diff --git a/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pxd b/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pxd new file mode 100644 index 0000000..7cf591a --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pxd @@ -0,0 +1,6 @@ +from hummingbot.connector.in_flight_order_base cimport InFlightOrderBase + +cdef class KrakenInFlightOrder(InFlightOrderBase): + cdef: + public object trade_id_set + public int userref diff --git a/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pyx b/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pyx new file mode 100644 index 0000000..111e9bd --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_in_flight_order.pyx @@ -0,0 +1,100 @@ +import math +from decimal import Decimal +from typing import ( + Any, + Dict, + List, +) + +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import OrderType, TradeType + +s_decimal_0 = Decimal(0) + + +class KrakenInFlightOrderNotCreated(Exception): + pass + + +cdef class KrakenInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: str, + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + userref: int, + initial_state: str = "local"): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp, + initial_state, + ) + self.trade_id_set = set() + self.userref = userref + + @property + def is_local(self) -> bool: + return self.last_state in {"local"} + + @property + def is_done(self) -> bool: + return self.last_state in {"closed", "canceled", "expired"} + + @property + def is_failure(self) -> bool: + return self.last_state in {"canceled", "expired"} + + @property + def is_cancelled(self) -> bool: + return self.last_state in {"canceled"} + + @classmethod + def _instance_creation_parameters_from_json(cls, data: Dict[str, Any]) -> List[Any]: + arguments: List[Any] = super()._instance_creation_parameters_from_json(data) + arguments.insert(-1, data["userref"]) + return arguments + + def to_json(self): + json = super().to_json() + json.update({"userref": self.userref}) + return json + + def update_exchange_order_id(self, exchange_id: str): + super().update_exchange_order_id(exchange_id) + self.last_state = "new" + + def _mark_as_filled(self): + """ + Updates the status of the InFlightOrder as filled. + Note: Should only be called when order is completely filled. + """ + self.last_state = "closed" + + def update_with_trade_update(self, trade_update: Dict[str, Any]) -> bool: + """ + Updartes the InFlightOrder with the trade update (from WebSocket API ownTrades stream) + :return: True if the order gets updated otherwise False + """ + trade_id = trade_update["trade_id"] + if str(trade_update["ordertxid"]) != self.exchange_order_id or trade_id in self.trade_id_set: + # trade already recorded + return False + self.trade_id_set.add(trade_id) + self.executed_amount_base += Decimal(trade_update["vol"]) + self.fee_paid += Decimal(trade_update["fee"]) + self.executed_amount_quote += Decimal(trade_update["vol"]) * Decimal(trade_update["price"]) + if not self.fee_asset: + self.fee_asset = self.quote_asset + if (math.isclose(self.executed_amount_base, self.amount) or self.executed_amount_base >= self.amount): + self._mark_as_filled() + return True diff --git a/hummingbot/connector/exchange/kraken/kraken_order_book.pxd b/hummingbot/connector/exchange/kraken/kraken_order_book.pxd new file mode 100644 index 0000000..69517f8 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_order_book.pxd @@ -0,0 +1,4 @@ +from hummingbot.core.data_type.order_book cimport OrderBook + +cdef class KrakenOrderBook(OrderBook): + pass diff --git a/hummingbot/connector/exchange/kraken/kraken_order_book.pyx b/hummingbot/connector/exchange/kraken/kraken_order_book.pyx new file mode 100644 index 0000000..08b0803 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_order_book.pyx @@ -0,0 +1,86 @@ +import logging +from typing import ( + Dict, + Optional +) + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType +) +from hummingbot.logger import HummingbotLogger + +_krob_logger = None + + +cdef class KrakenOrderBook(OrderBook): + @classmethod + def logger(cls) -> HummingbotLogger: + global _krob_logger + if _krob_logger is None: + _krob_logger = logging.getLogger(__name__) + return _krob_logger + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"].replace("/", ""), + "update_id": msg["latest_update"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp * 1e-3) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["trading_pair"].replace("/", ""), + "update_id": msg["update_id"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp * 1e-3) + + @classmethod + def snapshot_ws_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"].replace("/", ""), + "update_id": msg["update_id"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp * 1e-3) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + if metadata: + msg.update(metadata) + ts = float(msg["trade"][2]) + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["pair"].replace("/", ""), + "trade_type": float(TradeType.SELL.value) if msg["trade"][3] == "s" else float(TradeType.BUY.value), + "trade_id": ts, + "update_id": ts, + "price": msg["trade"][0], + "amount": msg["trade"][1] + }, timestamp=ts * 1e-3) + + @classmethod + def from_snapshot(cls, msg: OrderBookMessage) -> "OrderBook": + retval = KrakenOrderBook() + retval.apply_snapshot(msg.bids, msg.asks, msg.update_id) + return retval diff --git a/hummingbot/connector/exchange/kraken/kraken_order_book_tracker.py b/hummingbot/connector/exchange/kraken/kraken_order_book_tracker.py new file mode 100644 index 0000000..d41c213 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_order_book_tracker.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +import asyncio +from collections import deque, defaultdict +import logging +import time +from typing import ( + Deque, + Dict, + List, + Optional +) + +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.logger import HummingbotLogger +from hummingbot.core.data_type.order_book_tracker import ( + OrderBookTracker +) +from hummingbot.connector.exchange.kraken.kraken_api_order_book_data_source import KrakenAPIOrderBookDataSource +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.utils.async_utils import wait_til +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class KrakenOrderBookTracker(OrderBookTracker): + _krobt_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._krobt_logger is None: + cls._krobt_logger = logging.getLogger(__name__) + return cls._krobt_logger + + def __init__(self, + trading_pairs: List[str], + throttler: Optional[AsyncThrottler] = None, + api_factory: Optional[WebAssistantsFactory] = None, + ): + super().__init__(KrakenAPIOrderBookDataSource(throttler, trading_pairs), trading_pairs, api_factory) + self._api_factory = api_factory + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._saved_message_queues: Dict[str, Deque[OrderBookMessage]] = defaultdict(lambda: deque(maxlen=1000)) + + @property + def data_source(self) -> OrderBookTrackerDataSource: + if not self._data_source: + self._data_source = KrakenAPIOrderBookDataSource(trading_pairs=self._trading_pairs, + api_factory=self._api_factory) + return self._data_source + + @property + def exchange_name(self) -> str: + return "kraken" + + async def _order_book_diff_router(self): + """ + Route the real-time order book diff messages to the correct order book. + """ + last_message_timestamp: float = time.time() + messages_accepted: int = 0 + messages_rejected: int = 0 + + await wait_til(lambda: len(self.data_source._trading_pairs) == len(self._order_books.keys())) + while True: + try: + ob_message: OrderBookMessage = await self._order_book_diff_stream.get() + trading_pair: str = ob_message.trading_pair + + if trading_pair not in self._tracking_message_queues: + messages_rejected += 1 + continue + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + # Check the order book's initial update ID. If it's larger, don't bother. + order_book: OrderBook = self._order_books[trading_pair] + + if order_book.snapshot_uid > ob_message.update_id: + messages_rejected += 1 + continue + await message_queue.put(ob_message) + messages_accepted += 1 + + # Log some statistics. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Diff messages processed: {messages_accepted}, rejected: {messages_rejected}") + messages_accepted = 0 + messages_rejected = 0 + + last_message_timestamp = now + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unknown error. Retrying after 5 seconds.", exc_info=True) + await asyncio.sleep(5.0) diff --git a/hummingbot/connector/exchange/kraken/kraken_tracking_nonce.py b/hummingbot/connector/exchange/kraken/kraken_tracking_nonce.py new file mode 100644 index 0000000..78f0069 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_tracking_nonce.py @@ -0,0 +1,10 @@ +import time + +_last_tracking_nonce: int = 0 + + +def get_tracking_nonce() -> str: + global _last_tracking_nonce + nonce = int(time.time()) + _last_tracking_nonce = nonce if nonce > _last_tracking_nonce else _last_tracking_nonce + 1 + return str(_last_tracking_nonce) diff --git a/hummingbot/connector/exchange/kraken/kraken_user_stream_tracker.py b/hummingbot/connector/exchange/kraken/kraken_user_stream_tracker.py new file mode 100644 index 0000000..4660d7c --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_user_stream_tracker.py @@ -0,0 +1,52 @@ +import logging +from typing import Optional + +from hummingbot.connector.exchange.kraken.kraken_api_user_stream_data_source import KrakenAPIUserStreamDataSource +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + + +class KrakenUserStreamTracker(UserStreamTracker): + _krust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._krust_logger is None: + cls._krust_logger = logging.getLogger(__name__) + return cls._krust_logger + + def __init__(self, + throttler: AsyncThrottler, + kraken_auth: KrakenAuth, + api_factory: Optional[WebAssistantsFactory] = None): + self._throttler = throttler + self._api_factory = api_factory + self._kraken_auth: KrakenAuth = kraken_auth + super().__init__(data_source=KrakenAPIUserStreamDataSource( + self._throttler, + self._kraken_auth, + self._api_factory)) + + @property + def data_source(self) -> UserStreamTrackerDataSource: + if not self._data_source: + self._data_source = KrakenAPIUserStreamDataSource(self._throttler, self._kraken_auth, self._api_factory) + return self._data_source + + @property + def exchange_name(self) -> str: + return "kraken" + + async def start(self): + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/kraken/kraken_utils.py b/hummingbot/connector/exchange/kraken/kraken_utils.py new file mode 100644 index 0000000..d00c278 --- /dev/null +++ b/hummingbot/connector/exchange/kraken/kraken_utils.py @@ -0,0 +1,217 @@ +from typing import Any, Dict, List, Optional, Tuple + +from pydantic import Field, SecretStr + +import hummingbot.connector.exchange.kraken.kraken_constants as CONSTANTS +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.kraken.kraken_constants import KrakenAPITier +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +CENTRALIZED = True + +EXAMPLE_PAIR = "ETH-USDC" + +DEFAULT_FEES = [0.16, 0.26] + + +def split_trading_pair(trading_pair: str) -> Tuple[str, str]: + return tuple(convert_from_exchange_trading_pair(trading_pair).split("-")) + + +def convert_from_exchange_symbol(symbol: str) -> str: + # Assuming if starts with Z or X and has 4 letters then Z/X is removable + if (symbol[0] == "X" or symbol[0] == "Z") and len(symbol) == 4: + symbol = symbol[1:] + return CONSTANTS.KRAKEN_TO_HB_MAP.get(symbol, symbol) + + +def convert_to_exchange_symbol(symbol: str) -> str: + inverted_kraken_to_hb_map = {v: k for k, v in CONSTANTS.KRAKEN_TO_HB_MAP.items()} + return inverted_kraken_to_hb_map.get(symbol, symbol) + + +def split_to_base_quote(exchange_trading_pair: str) -> Tuple[Optional[str], Optional[str]]: + base, quote = exchange_trading_pair.split("-") + return base, quote + + +def convert_from_exchange_trading_pair(exchange_trading_pair: str, available_trading_pairs: Optional[Tuple] = None) -> Optional[str]: + base, quote = "", "" + if "-" in exchange_trading_pair: + base, quote = split_to_base_quote(exchange_trading_pair) + elif "/" in exchange_trading_pair: + base, quote = exchange_trading_pair.split("/") + elif len(available_trading_pairs) > 0: + # If trading pair has no spaces (i.e. ETHUSDT). Then it will have to match with the existing pairs + # Option 1: Using traditional naming convention + connector_trading_pair = {''.join(convert_from_exchange_trading_pair(tp).split('-')): tp for tp in available_trading_pairs}.get( + exchange_trading_pair) + if not connector_trading_pair: + # Option 2: Using kraken naming convention ( XXBT for Bitcoin, XXDG for Doge, ZUSD for USD, etc) + connector_trading_pair = {''.join(tp.split('-')): tp for tp in available_trading_pairs}.get( + exchange_trading_pair) + if not connector_trading_pair: + # Option 3: Kraken naming convention but without the initial X and Z + connector_trading_pair = {''.join(convert_to_exchange_symbol(convert_from_exchange_symbol(s)) + for s in tp.split('-')): tp + for tp in available_trading_pairs}.get(exchange_trading_pair) + return connector_trading_pair + + if not base or not quote: + return None + base = convert_from_exchange_symbol(base) + quote = convert_from_exchange_symbol(quote) + return f"{base}-{quote}" + + +def convert_to_exchange_trading_pair(hb_trading_pair: str, delimiter: str = "") -> str: + """ + Note: The result of this method can safely be used to submit/make queries. + Result shouldn't be used to parse responses as Kraken add special formating to most pairs. + """ + if "-" in hb_trading_pair: + base, quote = hb_trading_pair.split("-") + elif "/" in hb_trading_pair: + base, quote = hb_trading_pair.split("/") + else: + return hb_trading_pair + base = convert_to_exchange_symbol(base) + quote = convert_to_exchange_symbol(quote) + + exchange_trading_pair = f"{base}{delimiter}{quote}" + return exchange_trading_pair + + +def is_dark_pool(trading_pair_details: Dict[str, Any]): + ''' + Want to filter out dark pool trading pairs from the list of trading pairs + For more info, please check + https://support.kraken.com/hc/en-us/articles/360001391906-Introducing-the-Kraken-Dark-Pool + ''' + if trading_pair_details.get('altname'): + return trading_pair_details.get('altname').endswith('.d') + return False + + +def _build_private_rate_limits(tier: KrakenAPITier = KrakenAPITier.STARTER) -> List[RateLimit]: + private_rate_limits = [] + + PRIVATE_ENDPOINT_LIMIT, MATCHING_ENGINE_LIMIT = CONSTANTS.KRAKEN_TIER_LIMITS[tier] + + # Private REST endpoints + private_rate_limits.extend([ + # Private API Pool + RateLimit( + limit_id=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_ID, + limit=PRIVATE_ENDPOINT_LIMIT, + time_interval=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_INTERVAL, + ), + # Private endpoints + RateLimit( + limit_id=CONSTANTS.GET_TOKEN_PATH_URL, + limit=PRIVATE_ENDPOINT_LIMIT, + time_interval=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.PRIVATE_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=CONSTANTS.BALANCE_PATH_URL, + limit=PRIVATE_ENDPOINT_LIMIT, + time_interval=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_INTERVAL, + weight=2, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.PRIVATE_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=CONSTANTS.OPEN_ORDERS_PATH_URL, + limit=PRIVATE_ENDPOINT_LIMIT, + time_interval=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_INTERVAL, + weight=2, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.PRIVATE_ENDPOINT_LIMIT_ID)], + ), + RateLimit( + limit_id=CONSTANTS.QUERY_ORDERS_PATH_URL, + limit=PRIVATE_ENDPOINT_LIMIT, + time_interval=CONSTANTS.PRIVATE_ENDPOINT_LIMIT_INTERVAL, + weight=2, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.PRIVATE_ENDPOINT_LIMIT_ID)], + ), + ]) + + # Matching Engine Limits + private_rate_limits.extend([ + RateLimit( + limit_id=CONSTANTS.ADD_ORDER_PATH_URL, + limit=MATCHING_ENGINE_LIMIT, + time_interval=CONSTANTS.MATCHING_ENGINE_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.MATCHING_ENGINE_LIMIT_ID)], + ), + RateLimit( + limit_id=CONSTANTS.CANCEL_ORDER_PATH_URL, + limit=MATCHING_ENGINE_LIMIT, + time_interval=CONSTANTS.MATCHING_ENGINE_LIMIT_INTERVAL, + linked_limits=[LinkedLimitWeightPair(CONSTANTS.MATCHING_ENGINE_LIMIT_ID)], + ), + ]) + + return private_rate_limits + + +def build_rate_limits_by_tier(tier: KrakenAPITier = KrakenAPITier.STARTER) -> List[RateLimit]: + rate_limits = [] + + rate_limits.extend(CONSTANTS.PUBLIC_API_LIMITS) + rate_limits.extend(_build_private_rate_limits(tier=tier)) + + return rate_limits + + +def _api_tier_validator(value: str) -> Optional[str]: + """ + Determines if input value is a valid API tier + """ + try: + KrakenAPITier(value.upper()) + except ValueError: + return "No such Kraken API Tier." + + +class KrakenConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="kraken", client_data=None) + kraken_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Kraken API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kraken_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Kraken secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kraken_api_tier: str = Field( + default="Starter", + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Kraken API Tier (Starter/Intermediate/Pro)", + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "kraken" + + +KEYS = KrakenConfigMap.construct() + + +def build_api_factory(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory diff --git a/hummingbot/connector/exchange/kucoin/__init__.py b/hummingbot/connector/exchange/kucoin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/kucoin/dummy.pxd b/hummingbot/connector/exchange/kucoin/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/kucoin/dummy.pyx b/hummingbot/connector/exchange/kucoin/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/kucoin/kucoin_api_order_book_data_source.py b/hummingbot/connector/exchange/kucoin/kucoin_api_order_book_data_source.py new file mode 100644 index 0000000..f7a2b55 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_api_order_book_data_source.py @@ -0,0 +1,195 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS, kucoin_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange + + +class KucoinAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: 'KucoinExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs) + self._connector = connector + self._domain = domain + self._api_factory = api_factory + self._last_ws_message_sent_timestamp = 0 + self._ping_interval = 0 + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp = float(snapshot_response["data"]["time"]) * 1e-3 + update_id: int = int(snapshot_response["data"]["sequence"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": snapshot_response["data"]["bids"], + "asks": snapshot_response["data"]["asks"] + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + snapshot_timestamp) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL, + ) + + return data + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_data: Dict[str, Any] = raw_message["data"] + timestamp: float = int(trade_data["time"]) * 1e-9 + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=trade_data["symbol"]) + message_content = { + "trade_id": trade_data["tradeId"], + "update_id": trade_data["sequence"], + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) if trade_data["side"] == "buy" else float( + TradeType.SELL.value), + "amount": trade_data["size"], + "price": trade_data["price"] + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=timestamp) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_data: [str, Any] = raw_message["data"] + timestamp: float = self._time() + update_id: int = diff_data["sequenceEnd"] + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=diff_data["symbol"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "first_update_id": diff_data["sequenceStart"], + "bids": diff_data["changes"]["bids"], + "asks": diff_data["changes"]["asks"], + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + order_book_message_content, + timestamp) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + try: + symbols = ",".join([await self._connector.exchange_symbol_associated_to_pair(trading_pair=pair) + for pair in self._trading_pairs]) + + trades_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"/market/match:{symbols}", + "privateChannel": False, + "response": False, + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + order_book_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": f"/market/level2:{symbols}", + "privateChannel": False, + "response": False, + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self._last_ws_message_sent_timestamp = self._time() + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "data" in event_message and event_message.get("type") == "message": + event_channel = event_message.get("subject") + if event_channel == CONSTANTS.TRADE_EVENT_TYPE: + channel = self._trade_messages_queue_key + if event_channel == CONSTANTS.DIFF_EVENT_TYPE: + channel = self._diff_messages_queue_key + + return channel + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) + await asyncio.wait_for(super()._process_websocket_messages(websocket_assistant=websocket_assistant), + timeout=seconds_until_next_ping) + except asyncio.TimeoutError: + payload = { + "id": web_utils.next_message_id(), + "type": "ping", + } + ping_request = WSJSONRequest(payload=payload) + self._last_ws_message_sent_timestamp = self._time() + await websocket_assistant.send(request=ping_request) + + async def _connected_websocket_assistant(self) -> WSAssistant: + rest_assistant = await self._api_factory.get_rest_assistant() + connection_info = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, + ) + + ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] + self._ping_interval = int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3 + token = connection_info["data"]["token"] + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{ws_url}?token={token}", message_timeout=self._ping_interval) + return ws diff --git a/hummingbot/connector/exchange/kucoin/kucoin_api_user_stream_data_source.py b/hummingbot/connector/exchange/kucoin/kucoin_api_user_stream_data_source.py new file mode 100644 index 0000000..378a956 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_api_user_stream_data_source.py @@ -0,0 +1,106 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS, kucoin_web_utils as web_utils +from hummingbot.connector.exchange.kucoin.kucoin_auth import KucoinAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange + + +class KucoinAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: KucoinAuth, + trading_pairs: List[str], + connector: 'KucoinExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._domain = domain + self._api_factory = api_factory + self._last_ws_message_sent_timestamp = 0 + self._ping_interval = 0 + + async def _connected_websocket_assistant(self) -> WSAssistant: + rest_assistant = await self._api_factory.get_rest_assistant() + connection_info = await rest_assistant.execute_request( + url=web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.PRIVATE_WS_DATA_PATH_URL, + is_auth_required=True, + ) + + ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] + self._ping_interval = int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3 + token = connection_info["data"]["token"] + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{ws_url}?token={token}", message_timeout=self._ping_interval) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events and balance events. + + :param ws: the websocket assistant used to connect to the exchange + """ + try: + orders_change_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": "/spotMarket/tradeOrders", + "privateChannel": True, + "response": False, + } + subscribe_order_change_request: WSJSONRequest = WSJSONRequest(payload=orders_change_payload) + + balance_payload = { + "id": web_utils.next_message_id(), + "type": "subscribe", + "topic": "/account/balance", + "privateChannel": True, + "response": False, + } + subscribe_balance_request: WSJSONRequest = WSJSONRequest(payload=balance_payload) + + await websocket_assistant.send(subscribe_order_change_request) + await websocket_assistant.send(subscribe_balance_request) + + self._last_ws_message_sent_timestamp = self._time() + self.logger().info("Subscribed to private order changes and balance updates channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) + await asyncio.wait_for( + super()._process_websocket_messages( + websocket_assistant=websocket_assistant, queue=queue), + timeout=seconds_until_next_ping) + except asyncio.TimeoutError: + payload = { + "id": web_utils.next_message_id(), + "type": "ping", + } + ping_request = WSJSONRequest(payload=payload) + self._last_ws_message_sent_timestamp = self._time() + await websocket_assistant.send(request=ping_request) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if (len(event_message) > 0 + and event_message.get("type") == "message" + and event_message.get("subject") in [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.BALANCE_EVENT_TYPE]): + queue.put_nowait(event_message) diff --git a/hummingbot/connector/exchange/kucoin/kucoin_auth.py b/hummingbot/connector/exchange/kucoin/kucoin_auth.py new file mode 100644 index 0000000..dde5f80 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_auth.py @@ -0,0 +1,95 @@ +import base64 +import hashlib +import hmac +from collections import OrderedDict +from typing import Any, Dict +from urllib.parse import urlencode + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class KucoinAuth(AuthBase): + def __init__(self, api_key: str, passphrase: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key: str = api_key + self.passphrase: str = passphrase + self.secret_key: str = secret_key + self.time_provider = time_provider + + @staticmethod + def keysort(dictionary: Dict[str, str]) -> Dict[str, str]: + return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + + :param request: the request to be configured for authenticated interaction + """ + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.authentication_headers(request=request)) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. KuCoin does not use this + functionality + """ + return request # pass-through + + def partner_header(self, timestamp: str): + partner_payload = timestamp + CONSTANTS.HB_PARTNER_ID + self.api_key + partner_signature = base64.b64encode( + hmac.new( + CONSTANTS.HB_PARTNER_KEY.encode("utf-8"), + partner_payload.encode("utf-8"), + hashlib.sha256).digest()) + third_party = { + "KC-API-PARTNER": CONSTANTS.HB_PARTNER_ID, + "KC-API-PARTNER-SIGN": str(partner_signature, "utf-8") + } + return third_party + + def authentication_headers(self, request: RESTRequest) -> Dict[str, Any]: + timestamp = int(self.time_provider.time() * 1000) + header = { + "KC-API-KEY": self.api_key, + "KC-API-TIMESTAMP": str(timestamp), + "KC-API-KEY-VERSION": "2" + } + + path_url = f"/api{request.url.split('/api')[-1]}" + if request.params: + sorted_params = self.keysort(request.params) + query_string_components = urlencode(sorted_params, safe=',') + path_url = f"{path_url}?{query_string_components}" + + if request.data is not None: + body = request.data + else: + body = "" + payload = str(timestamp) + request.method.value.upper() + path_url + body + + signature = base64.b64encode( + hmac.new( + self.secret_key.encode("utf-8"), + payload.encode("utf-8"), + hashlib.sha256).digest()) + passphrase = base64.b64encode( + hmac.new( + self.secret_key.encode('utf-8'), + self.passphrase.encode('utf-8'), + hashlib.sha256).digest()) + header["KC-API-SIGN"] = str(signature, "utf-8") + header["KC-API-PASSPHRASE"] = str(passphrase, "utf-8") + partner_headers = self.partner_header(str(timestamp)) + header.update(partner_headers) + return header diff --git a/hummingbot/connector/exchange/kucoin/kucoin_constants.py b/hummingbot/connector/exchange/kucoin/kucoin_constants.py new file mode 100644 index 0000000..af2a62d --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_constants.py @@ -0,0 +1,65 @@ +import sys + +from hummingbot.core.api_throttler.data_types import RateLimit + +MAX_ORDER_ID_LEN = 40 +TRADING_FEES_SYMBOL_LIMIT = 10 + +DEFAULT_DOMAIN = "main" +HB_PARTNER_ID = "Hummingbot" +HB_PARTNER_KEY = "8fb50686-81a8-408a-901c-07c5ac5bd758" + +# REST endpoints +BASE_PATH_URL = { + "main": "https://api.kucoin.com", +} +PUBLIC_WS_DATA_PATH_URL = "/api/v1/bullet-public" +PRIVATE_WS_DATA_PATH_URL = "/api/v1/bullet-private" +TICKER_PRICE_CHANGE_PATH_URL = "/api/v1/market/orderbook/level1" +SNAPSHOT_NO_AUTH_PATH_URL = "/api/v1/market/orderbook/level2_100" +ACCOUNTS_PATH_URL = "/api/v1/accounts" +SERVER_TIME_PATH_URL = "/api/v1/timestamp" +SYMBOLS_PATH_URL = "/api/v2/symbols" +ORDERS_PATH_URL = "/api/v1/orders" +FEE_PATH_URL = "/api/v1/trade-fees" +ALL_TICKERS_PATH_URL = "/api/v1/market/allTickers" +FILLS_PATH_URL = "/api/v1/fills" +LIMIT_FILLS_PATH_URL = "/api/v1/limit/fills" +ORDER_CLIENT_ORDER_PATH_URL = "/api/v1/order/client-order" + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_CONNECTION_LIMIT = 30 +WS_CONNECTION_TIME_INTERVAL = 60 +WS_REQUEST_LIMIT_ID = "WSRequest" +GET_ORDER_LIMIT_ID = "GetOrders" +POST_ORDER_LIMIT_ID = "PostOrder" +DELETE_ORDER_LIMIT_ID = "DeleteOrder" +WS_PING_HEARTBEAT = 10 + +DIFF_EVENT_TYPE = "trade.l2update" +TRADE_EVENT_TYPE = "trade.l3match" +ORDER_CHANGE_EVENT_TYPE = "orderChange" +BALANCE_EVENT_TYPE = "account.balance" + +NO_LIMIT = sys.maxsize +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=WS_CONNECTION_LIMIT, time_interval=WS_CONNECTION_TIME_INTERVAL), + RateLimit(WS_REQUEST_LIMIT_ID, limit=100, time_interval=10), + + RateLimit(limit_id=PUBLIC_WS_DATA_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=PRIVATE_WS_DATA_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=TICKER_PRICE_CHANGE_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=SYMBOLS_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=SNAPSHOT_NO_AUTH_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=GET_ORDER_LIMIT_ID, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=FEE_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=ALL_TICKERS_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=LIMIT_FILLS_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=ORDER_CLIENT_ORDER_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(limit_id=POST_ORDER_LIMIT_ID, limit=45, time_interval=3), + RateLimit(limit_id=DELETE_ORDER_LIMIT_ID, limit=60, time_interval=3), + RateLimit(limit_id=ORDERS_PATH_URL, limit=45, time_interval=3), + RateLimit(limit_id=FILLS_PATH_URL, limit=9, time_interval=3), +] diff --git a/hummingbot/connector/exchange/kucoin/kucoin_exchange.py b/hummingbot/connector/exchange/kucoin/kucoin_exchange.py new file mode 100644 index 0000000..755d6fb --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_exchange.py @@ -0,0 +1,516 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.kucoin import ( + kucoin_constants as CONSTANTS, + kucoin_utils as utils, + kucoin_web_utils as web_utils, +) +from hummingbot.connector.exchange.kucoin.kucoin_api_order_book_data_source import KucoinAPIOrderBookDataSource +from hummingbot.connector.exchange.kucoin.kucoin_api_user_stream_data_source import KucoinAPIUserStreamDataSource +from hummingbot.connector.exchange.kucoin.kucoin_auth import KucoinAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class KucoinExchange(ExchangePyBase): + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + kucoin_api_key: str, + kucoin_passphrase: str, + kucoin_secret_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + self.kucoin_api_key = kucoin_api_key + self.kucoin_passphrase = kucoin_passphrase + self.kucoin_secret_key = kucoin_secret_key + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._last_order_fill_ts_s: float = 0 + super().__init__(client_config_map=client_config_map) + + @property + def authenticator(self): + return KucoinAuth( + api_key=self.kucoin_api_key, + passphrase=self.kucoin_passphrase, + secret_key=self.kucoin_secret_key, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "kucoin" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return "" + + @property + def trading_rules_request_path(self): + return CONSTANTS.SYMBOLS_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.SYMBOLS_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.SERVER_TIME_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def get_all_pairs_prices(self) -> List[Dict[str, str]]: + pairs_prices = await self._api_get(path_url=CONSTANTS.ALL_TICKERS_PATH_URL) + return pairs_prices + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # API documentation does not clarify the error message for timestamp related problems + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self.domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return KucoinAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return KucoinAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data["makerFeeRate"]) if is_maker else Decimal(fees_data["takerFeeRate"]) + fee = AddedToCostTradeFee(percent=fee_value) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(utils.is_pair_information_valid, exchange_info.get("data", [])): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["baseCurrency"], + quote=symbol_data["quoteCurrency"]) + self._set_trading_pair_symbol_map(mapping) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + path_url = CONSTANTS.ORDERS_PATH_URL + side = trade_type.name.lower() + order_type_str = "market" if order_type == OrderType.MARKET else "limit" + data = { + "size": str(amount), + "clientOid": order_id, + "side": side, + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "type": order_type_str, + } + if order_type is OrderType.LIMIT: + data["price"] = str(price) + elif order_type is OrderType.LIMIT_MAKER: + data["price"] = str(price) + data["postOnly"] = True + exchange_order_id = await self._api_post( + path_url=path_url, + data=data, + is_auth_required=True, + limit_id=CONSTANTS.POST_ORDER_LIMIT_ID, + ) + return str(exchange_order_id["data"]["orderId"]), self.current_timestamp + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation specific function is called by _cancel, and returns True if successful + """ + exchange_order_id = await tracked_order.get_exchange_order_id() + cancel_result = await self._api_delete( + f"{CONSTANTS.ORDERS_PATH_URL}/{exchange_order_id}", + is_auth_required=True, + limit_id=CONSTANTS.DELETE_ORDER_LIMIT_ID + ) + if tracked_order.exchange_order_id in cancel_result["data"].get("cancelledOrderIds", []): + return True + return False + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("type") + event_subject = event_message.get("subject") + execution_data = event_message.get("data") + + # Refer to https://docs.kucoin.com/#private-order-change-events + if event_type == "message" and event_subject == CONSTANTS.ORDER_CHANGE_EVENT_TYPE: + order_event_type = execution_data["type"] + client_order_id: Optional[str] = execution_data.get("clientOid") + + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + event_timestamp = execution_data["ts"] * 1e-9 + + if fillable_order is not None and order_event_type == "match": + execute_amount_diff = Decimal(execution_data["matchSize"]) + execute_price = Decimal(execution_data["matchPrice"]) + + fee = self.get_fee( + fillable_order.base_asset, + fillable_order.quote_asset, + fillable_order.order_type, + fillable_order.trade_type, + execute_price, + execute_amount_diff, + ) + + trade_update = TradeUpdate( + trade_id=execution_data["tradeId"], + client_order_id=client_order_id, + exchange_order_id=execution_data["orderId"], + trading_pair=updatable_order.trading_pair, + fee=fee, + fill_base_amount=execute_amount_diff, + fill_quote_amount=execute_amount_diff * execute_price, + fill_price=execute_price, + fill_timestamp=event_timestamp, + ) + self._order_tracker.process_trade_update(trade_update) + + if updatable_order is not None: + updated_status = updatable_order.current_state + if order_event_type == "open": + updated_status = OrderState.OPEN + elif order_event_type == "match": + updated_status = OrderState.PARTIALLY_FILLED + elif order_event_type == "filled": + updated_status = OrderState.FILLED + elif order_event_type == "canceled": + updated_status = OrderState.CANCELED + + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=event_timestamp, + new_state=updated_status, + client_order_id=client_order_id, + exchange_order_id=execution_data["orderId"], + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif event_type == "message" and event_subject == CONSTANTS.BALANCE_EVENT_TYPE: + currency = execution_data["currency"] + available_balance = Decimal(execution_data["available"]) + total_balance = Decimal(execution_data["total"]) + self._account_balances.update({currency: total_balance}) + self._account_available_balances.update({currency: available_balance}) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + response = await self._api_get( + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + params={"type": "trade"}, + is_auth_required=True) + + if response: + for balance_entry in response["data"]: + asset_name = balance_entry["currency"] + self._account_available_balances[asset_name] = Decimal(balance_entry["available"]) + self._account_balances[asset_name] = Decimal(balance_entry["balance"]) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _format_trading_rules(self, raw_trading_pair_info: Dict[str, Any]) -> List[TradingRule]: + trading_rules = [] + + for info in raw_trading_pair_info["data"]: + if utils.is_pair_information_valid(info): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=info.get("symbol")) + trading_rules.append( + TradingRule(trading_pair=trading_pair, + min_order_size=Decimal(info["baseMinSize"]), + max_order_size=Decimal(info["baseMaxSize"]), + min_price_increment=Decimal(info['priceIncrement']), + min_base_amount_increment=Decimal(info['baseIncrement']), + min_quote_amount_increment=Decimal(info['quoteIncrement']), + min_notional_size=Decimal(info["quoteMinSize"])) + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule {info}. Skipping.", exc_info=True) + return trading_rules + + async def _update_trading_fees(self): + trading_symbols = [await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs] + fees_json = [] + for idx in range(0, len(trading_symbols), CONSTANTS.TRADING_FEES_SYMBOL_LIMIT): + sub_trading_symbols = trading_symbols[idx:idx + CONSTANTS.TRADING_FEES_SYMBOL_LIMIT] + params = {"symbols": ",".join(sub_trading_symbols)} + resp = await self._api_get( + path_url=CONSTANTS.FEE_PATH_URL, + params=params, + is_auth_required=True, + ) + fees_json.extend(resp["data"]) + + for fee_json in fees_json: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=fee_json["symbol"]) + self._trading_fees[trading_pair] = fee_json + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + # This method in the base ExchangePyBase, makes an API call for each order. + # Given the rate limit of the API method and the breadth of info provided by the method + # the mitigation proposal is to collect all orders in one shot, then parse them + # Note that this is limited to 500 orders (pagination) + # An alternative for Kucoin would be to use the limit/fills that returns 24hr updates, which should + # be sufficient, the rate limit seems better suited + all_trades_updates: List[TradeUpdate] = [] + if len(orders) > 0: + try: + all_trades_updates: List[TradeUpdate] = await self._all_trades_updates(orders) + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Failed to fetch trade updates. Error: {request_error}") + + for trade_update in all_trades_updates: + self._order_tracker.process_trade_update(trade_update) + + async def _all_trades_updates(self, orders: List[InFlightOrder]) -> List[TradeUpdate]: + trade_updates: List[TradeUpdate] = [] + if len(orders) > 0: + exchange_to_client = {o.exchange_order_id: {"client_id": o.client_order_id, "trading_pair": o.trading_pair} for o in orders} + + # We request updates from either: + # - The earliest order creation_timestamp in the list (first couple requests) + # - The last time we got a fill + self._last_order_fill_ts_s = int(max(self._last_order_fill_ts_s, min([o.creation_timestamp for o in orders]))) + + # From Kucoin https://docs.kucoin.com/#list-fills: + # "If you only specified the start time, the system will automatically + # calculate the end time (end time = start time + 7 * 24 hours)" + all_fills_response = await self._api_get( + path_url=CONSTANTS.FILLS_PATH_URL, + params={ + "pageSize": 500, + "startAt": self._last_order_fill_ts_s * 1000, + }, + is_auth_required=True) + + for trade in all_fills_response.get("items", []): + if str(trade["orderId"]) in exchange_to_client: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=TradeType.BUY if trade["side"] == "buy" else "sell", + percent_token=trade["feeCurrency"], + flat_fees=[TokenAmount(amount=Decimal(trade["fee"]), token=trade["feeCurrency"])] + ) + + client_info = exchange_to_client[str(trade["orderId"])] + trade_update = TradeUpdate( + trade_id=str(trade["tradeId"]), + client_order_id=client_info["client_id"], + trading_pair=client_info["trading_pair"], + exchange_order_id=str(trade["orderId"]), + fee=fee, + fill_base_amount=Decimal(trade["size"]), + fill_quote_amount=Decimal(trade["funds"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["createdAt"] * 1e-3, + ) + trade_updates.append(trade_update) + # Update the last fill timestamp with the latest one + self._last_order_fill_ts_s = max(self._last_order_fill_ts_s, trade["createdAt"] * 1e-3) + + return trade_updates + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + raise Exception("Developer: This method should not be called, it is obsoleted for Kucoin") + + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = order.exchange_order_id + all_fills_response = await self._api_get( + path_url=CONSTANTS.FILLS_PATH_URL, + params={ + "orderId": exchange_order_id, + "pageSize": 500, + }, + is_auth_required=True) + + for trade in all_fills_response.get("items", []): + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["feeCurrency"], + flat_fees=[TokenAmount(amount=Decimal(trade["fee"]), token=trade["feeCurrency"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["tradeId"]), + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["size"]), + fill_quote_amount=Decimal(trade["funds"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["createdAt"] * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + exchange_order_id = await tracked_order.get_exchange_order_id() + updated_order_data = await self._api_get( + path_url=f"{CONSTANTS.ORDERS_PATH_URL}/{exchange_order_id}", + is_auth_required=True, + limit_id=CONSTANTS.GET_ORDER_LIMIT_ID) + + ordered_canceled = updated_order_data["data"]["cancelExist"] + is_active = updated_order_data["data"]["isActive"] + op_type = updated_order_data["data"]["opType"] + + new_state = tracked_order.current_state + if ordered_canceled or op_type == "CANCEL": + new_state = OrderState.CANCELED + elif not is_active: + new_state = OrderState.FILLED + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=updated_order_data["data"]["id"], + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=new_state, + ) + + return order_update + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_request( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, + method=RESTMethod.GET, + params=params + ) + + return float(resp_json["data"]["price"]) diff --git a/hummingbot/connector/exchange/kucoin/kucoin_order_book_message.py b/hummingbot/connector/exchange/kucoin/kucoin_order_book_message.py new file mode 100644 index 0000000..79afd5e --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_order_book_message.py @@ -0,0 +1,63 @@ +import time +from typing import Dict, List, Optional + +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_row import OrderBookRow + + +class KucoinOrderBookMessage(OrderBookMessage): + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + if timestamp is None: + if message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError("timestamp must not be None when initializing snapshot messages.") + timestamp = int(time.time()) + return super(KucoinOrderBookMessage, cls).__new__( + cls, message_type, content, timestamp=timestamp, *args, **kwargs + ) + + @property + def trading_pair(self) -> str: + return self.content["trading_pair"] + + @property + def asks(self) -> List[OrderBookRow]: + # raise NotImplementedError("Kucoin order book messages have different semantics.") + return [ + OrderBookRow(float(price), float(amount), self.update_id) + for price, amount, *trash in self.content.get("asks", []) + ] + + @property + def bids(self) -> List[OrderBookRow]: + # raise NotImplementedError("Kucoin order book messages have different semantics.") + return [ + OrderBookRow(float(price), float(amount), self.update_id) + for price, amount, *trash in self.content.get("bids", []) + ] + + @property + def has_update_id(self) -> bool: + return True + + @property + def has_trade_id(self) -> bool: + return True + + def __eq__(self, other) -> bool: + return self.type == other.type and self.timestamp == other.timestamp + + def __lt__(self, other) -> bool: + if self.timestamp != other.timestamp: + return self.timestamp < other.timestamp + else: + """ + If timestamp is the same, the ordering is snapshot < diff < trade + """ + return self.type.value < other.type.value diff --git a/hummingbot/connector/exchange/kucoin/kucoin_utils.py b/hummingbot/connector/exchange/kucoin/kucoin_utils.py new file mode 100644 index 0000000..ba96c43 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_utils.py @@ -0,0 +1,64 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True + +EXAMPLE_PAIR = "ETH-USDT" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), +) + + +def is_pair_information_valid(pair_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its market information + + :param pair_info: the market information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + return pair_info.get("enableTrading", False) + + +class KuCoinConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="kucoin", client_data=None) + kucoin_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your KuCoin API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kucoin_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your KuCoin secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + kucoin_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your KuCoin passphrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "kucoin" + + +KEYS = KuCoinConfigMap.construct() diff --git a/hummingbot/connector/exchange/kucoin/kucoin_web_utils.py b/hummingbot/connector/exchange/kucoin/kucoin_web_utils.py new file mode 100644 index 0000000..79ce283 --- /dev/null +++ b/hummingbot/connector/exchange/kucoin/kucoin_web_utils.py @@ -0,0 +1,84 @@ +from typing import Callable, Optional + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided REST endpoint + + :param path_url: a public REST endpoint + :param domain: the domain to connect to ("main" or "testnet"). The default value is "main" + + :return: the full URL to the endpoint + """ + return CONSTANTS.BASE_PATH_URL[domain] + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided REST endpoint + + :param path_url: a private REST endpoint + :param domain: the domain to connect to ("main" or "testnet"). The default value is "main" + + :return: the full URL to the endpoint + """ + return public_rest_url(path_url=path_url, domain=domain) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["data"] + return server_time + + +def next_message_id() -> str: + return str(get_tracking_nonce()) diff --git a/hummingbot/connector/exchange/mexc/__init__.py b/hummingbot/connector/exchange/mexc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/mexc/dummy.pxd b/hummingbot/connector/exchange/mexc/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/mexc/dummy.pyx b/hummingbot/connector/exchange/mexc/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py b/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py new file mode 100755 index 0000000..0846aa4 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_api_order_book_data_source.py @@ -0,0 +1,142 @@ +import asyncio +import time +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange + + +class MexcAPIOrderBookDataSource(OrderBookTrackerDataSource): + HEARTBEAT_TIME_INTERVAL = 30.0 + TRADE_STREAM_ID = 1 + DIFF_STREAM_ID = 2 + ONE_HOUR = 60 * 60 + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'MexcExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__(trading_pairs) + self._connector = connector + self._trade_messages_queue_key = CONSTANTS.TRADE_EVENT_TYPE + self._diff_messages_queue_key = CONSTANTS.DIFF_EVENT_TYPE + self._domain = domain + self._api_factory = api_factory + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "symbol": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "limit": "1000" + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self._domain), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SNAPSHOT_PATH_URL, + ) + + return data + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + trade_params = [] + depth_params = [] + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + trade_params.append(f"spot@public.deals.v3.api@{symbol}") + depth_params.append(f"spot@public.increase.depth.v3.api@{symbol}") + payload = { + "method": "SUBSCRIPTION", + "params": trade_params, + "id": 1 + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) + + payload = { + "method": "SUBSCRIPTION", + "params": depth_params, + "id": 2 + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_trade_request) + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + raise + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=CONSTANTS.WSS_URL.format(self._domain), + ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: float = time.time() + snapshot_msg: OrderBookMessage = MexcOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if "code" not in raw_message: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + for sinlge_msg in raw_message['d']['deals']: + trade_message = MexcOrderBook.trade_message_from_exchange( + sinlge_msg, timestamp=raw_message['t'], metadata={"trading_pair": trading_pair}) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + if "code" not in raw_message: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["s"]) + order_book_message: OrderBookMessage = MexcOrderBook.diff_message_from_exchange( + raw_message, raw_message['t'], {"trading_pair": trading_pair}) + message_queue.put_nowait(order_book_message) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "code" not in event_message: + event_type = event_message.get("c", "") + channel = (self._diff_messages_queue_key if CONSTANTS.DIFF_EVENT_TYPE in event_type + else self._trade_messages_queue_key) + return channel diff --git a/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py b/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py new file mode 100755 index 0000000..06fc462 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_api_user_stream_data_source.py @@ -0,0 +1,184 @@ +import asyncio +import time +from typing import TYPE_CHECKING, List, Optional + +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange + + +class MexcAPIUserStreamDataSource(UserStreamTrackerDataSource): + LISTEN_KEY_KEEP_ALIVE_INTERVAL = 1800 # Recommended to Ping/Update listen key to keep connection alive + HEARTBEAT_TIME_INTERVAL = 30.0 + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + auth: MexcAuth, + trading_pairs: List[str], + connector: 'MexcExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._auth: MexcAuth = auth + self._current_listen_key = None + self._domain = domain + self._api_factory = api_factory + + self._listen_key_initialized_event: asyncio.Event = asyncio.Event() + self._last_listen_key_ping_ts = 0 + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + self._manage_listen_key_task = safe_ensure_future(self._manage_listen_key_task_loop()) + await self._listen_key_initialized_event.wait() + + ws: WSAssistant = await self._get_ws_assistant() + url = f"{CONSTANTS.WSS_URL.format(self._domain)}?listenKey={self._current_listen_key}" + await ws.connect(ws_url=url, ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events and balance events. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + + orders_change_payload = { + "method": "SUBSCRIPTION", + "params": [CONSTANTS.USER_ORDERS_ENDPOINT_NAME], + "id": 1 + } + subscribe_order_change_request: WSJSONRequest = WSJSONRequest(payload=orders_change_payload) + + trades_payload = { + "method": "SUBSCRIPTION", + "params": [CONSTANTS.USER_TRADES_ENDPOINT_NAME], + "id": 2 + } + subscribe_trades_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + balance_payload = { + "method": "SUBSCRIPTION", + "params": [CONSTANTS.USER_BALANCE_ENDPOINT_NAME], + "id": 3 + } + subscribe_balance_request: WSJSONRequest = WSJSONRequest(payload=balance_payload) + + await websocket_assistant.send(subscribe_order_change_request) + await websocket_assistant.send(subscribe_trades_request) + await websocket_assistant.send(subscribe_balance_request) + + self.logger().info("Subscribed to private order changes and balance updates channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _get_listen_key(self): + rest_assistant = await self._api_factory.get_rest_assistant() + try: + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self._domain), + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.MEXC_USER_STREAM_PATH_URL, + headers=self._auth.header_for_authentication(), + is_auth_required=True + ) + except asyncio.CancelledError: + raise + except Exception as exception: + raise IOError(f"Error fetching user stream listen key. Error: {exception}") + + return data["listenKey"] + + async def _ping_listen_key(self) -> bool: + rest_assistant = await self._api_factory.get_rest_assistant() + try: + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self._domain), + params={"listenKey": self._current_listen_key}, + method=RESTMethod.PUT, + return_err=True, + throttler_limit_id=CONSTANTS.MEXC_USER_STREAM_PATH_URL, + headers=self._auth.header_for_authentication() + ) + + if "code" in data: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {data}") + return False + + except asyncio.CancelledError: + raise + except Exception as exception: + self.logger().warning(f"Failed to refresh the listen key {self._current_listen_key}: {exception}") + return False + + return True + + async def _manage_listen_key_task_loop(self): + try: + while True: + now = int(time.time()) + if self._current_listen_key is None: + self._current_listen_key = await self._get_listen_key() + self.logger().info(f"Successfully obtained listen key {self._current_listen_key}") + self._listen_key_initialized_event.set() + self._last_listen_key_ping_ts = int(time.time()) + + if now - self._last_listen_key_ping_ts >= self.LISTEN_KEY_KEEP_ALIVE_INTERVAL: + success: bool = await self._ping_listen_key() + if not success: + self.logger().error("Error occurred renewing listen key ...") + break + else: + self.logger().info(f"Refreshed listen key {self._current_listen_key}.") + self._last_listen_key_ping_ts = int(time.time()) + else: + await self._sleep(self.LISTEN_KEY_KEEP_ALIVE_INTERVAL) + finally: + self._current_listen_key = None + self._listen_key_initialized_event.clear() + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + async def _send_ping(self, websocket_assistant: WSAssistant): + payload = { + "method": "PING", + } + ping_request: WSJSONRequest = WSJSONRequest(payload=payload) + await websocket_assistant.send(ping_request) + + async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): + await super()._on_user_stream_interruption(websocket_assistant=websocket_assistant) + self._manage_listen_key_task and self._manage_listen_key_task.cancel() + self._current_listen_key = None + self._listen_key_initialized_event.clear() + await self._sleep(5) + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + await asyncio.wait_for( + super()._process_websocket_messages(websocket_assistant=websocket_assistant, queue=queue), + timeout=CONSTANTS.WS_CONNECTION_TIME_INTERVAL + ) + except asyncio.TimeoutError: + ping_request = WSJSONRequest(payload={"method": "PING"}) + await websocket_assistant.send(ping_request) diff --git a/hummingbot/connector/exchange/mexc/mexc_auth.py b/hummingbot/connector/exchange/mexc/mexc_auth.py new file mode 100644 index 0000000..f988b44 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_auth.py @@ -0,0 +1,64 @@ +import hashlib +import hmac +import json +from collections import OrderedDict +from typing import Any, Dict +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class MexcAuth(AuthBase): + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + if request.method == RESTMethod.POST: + request.data = self.add_auth_to_params(params=json.loads(request.data) if request.data is not None else {}) + else: + request.params = self.add_auth_to_params(params=request.params) + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.header_for_authentication()) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. Mexc does not use this + functionality + """ + return request # pass-through + + def add_auth_to_params(self, + params: Dict[str, Any]): + timestamp = int(self.time_provider.time() * 1e3) + + request_params = OrderedDict(params or {}) + request_params["timestamp"] = timestamp + + signature = self._generate_signature(params=request_params) + request_params["signature"] = signature + + return request_params + + def header_for_authentication(self) -> Dict[str, str]: + return {"X-MEXC-APIKEY": self.api_key} + + def _generate_signature(self, params: Dict[str, Any]) -> str: + + encoded_params_str = urlencode(params) + digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() + return digest diff --git a/hummingbot/connector/exchange/mexc/mexc_constants.py b/hummingbot/connector/exchange/mexc/mexc_constants.py new file mode 100644 index 0000000..69f82f8 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_constants.py @@ -0,0 +1,113 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +DEFAULT_DOMAIN = "com" + +HBOT_ORDER_ID_PREFIX = "x-XEKWYICX" +MAX_ORDER_ID_LEN = 32 + +# Base URL +REST_URL = "https://api.mexc.{}/api/" +WSS_URL = "wss://wbs.mexc.{}/ws" + +PUBLIC_API_VERSION = "v3" +PRIVATE_API_VERSION = "v3" + +# Public API endpoints or MexcClient function +TICKER_PRICE_CHANGE_PATH_URL = "/ticker/24hr" +TICKER_BOOK_PATH_URL = "/ticker/bookTicker" +EXCHANGE_INFO_PATH_URL = "/exchangeInfo" +SUPPORTED_SYMBOL_PATH_URL = "/defaultSymbols" +PING_PATH_URL = "/ping" +SNAPSHOT_PATH_URL = "/depth" +SERVER_TIME_PATH_URL = "/time" + +# Private API endpoints or MexcClient function +ACCOUNTS_PATH_URL = "/account" +MY_TRADES_PATH_URL = "/myTrades" +ORDER_PATH_URL = "/order" +MEXC_USER_STREAM_PATH_URL = "/userDataStream" + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +# Mexc params + +SIDE_BUY = "BUY" +SIDE_SELL = "SELL" + +TIME_IN_FORCE_GTC = "GTC" # Good till cancelled +TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel +TIME_IN_FORCE_FOK = "FOK" # Fill or kill + +# Rate Limit Type +IP_REQUEST_WEIGHT = "IP_REQUEST_WEIGHT" +UID_REQUEST_WEIGHT = "UID_REQUEST_WEIGHT" + +# Rate Limit time intervals +ONE_MINUTE = 60 +ONE_SECOND = 1 +ONE_DAY = 86400 + +MAX_REQUEST = 5000 + +# Order States +ORDER_STATE = { + "PENDING": OrderState.PENDING_CREATE, + "NEW": OrderState.OPEN, + "FILLED": OrderState.FILLED, + "PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED, + "PENDING_CANCEL": OrderState.OPEN, + "CANCELED": OrderState.CANCELED, + "REJECTED": OrderState.FAILED, + "EXPIRED": OrderState.FAILED, +} + +# WS Order States +WS_ORDER_STATE = { + 1: OrderState.OPEN, + 2: OrderState.FILLED, + 3: OrderState.PARTIALLY_FILLED, + 4: OrderState.CANCELED, + 5: OrderState.OPEN, +} + +# Websocket event types +DIFF_EVENT_TYPE = "increase.depth" +TRADE_EVENT_TYPE = "public.deals" + +USER_TRADES_ENDPOINT_NAME = "spot@private.deals.v3.api" +USER_ORDERS_ENDPOINT_NAME = "spot@private.orders.v3.api" +USER_BALANCE_ENDPOINT_NAME = "spot@private.account.v3.api" +WS_CONNECTION_TIME_INTERVAL = 20 +RATE_LIMITS = [ + RateLimit(limit_id=IP_REQUEST_WEIGHT, limit=20000, time_interval=ONE_MINUTE), + RateLimit(limit_id=UID_REQUEST_WEIGHT, limit=240000, time_interval=ONE_MINUTE), + # Weighted Limits + RateLimit(limit_id=TICKER_PRICE_CHANGE_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=TICKER_BOOK_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 2)]), + RateLimit(limit_id=EXCHANGE_INFO_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=SUPPORTED_SYMBOL_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=SNAPSHOT_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 50)]), + RateLimit(limit_id=MEXC_USER_STREAM_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(UID_REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=SERVER_TIME_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=PING_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(IP_REQUEST_WEIGHT, 1)]), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(UID_REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=MY_TRADES_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(UID_REQUEST_WEIGHT, 10)]), + RateLimit(limit_id=ORDER_PATH_URL, limit=MAX_REQUEST, time_interval=ONE_MINUTE, + linked_limits=[LinkedLimitWeightPair(UID_REQUEST_WEIGHT, 2)]) +] + +ORDER_NOT_EXIST_ERROR_CODE = -2013 +ORDER_NOT_EXIST_MESSAGE = "Order does not exist" +UNKNOWN_ORDER_ERROR_CODE = -2011 +UNKNOWN_ORDER_MESSAGE = "Unknown order sent" diff --git a/hummingbot/connector/exchange/mexc/mexc_exchange.py b/hummingbot/connector/exchange/mexc/mexc_exchange.py new file mode 100755 index 0000000..3b30946 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_exchange.py @@ -0,0 +1,562 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_utils, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource +from hummingbot.connector.exchange.mexc.mexc_api_user_stream_data_source import MexcAPIUserStreamDataSource +from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import TradeFillOrderDetails, combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class MexcExchange(ExchangePyBase): + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + mexc_api_key: str, + mexc_api_secret: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.api_key = mexc_api_key + self.secret_key = mexc_api_secret + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._last_trades_poll_mexc_timestamp = 1.0 + super().__init__(client_config_map) + + @staticmethod + def mexc_order_type(order_type: OrderType) -> str: + return order_type.name.upper() + + @staticmethod + def to_hb_order_type(mexc_type: str) -> OrderType: + return OrderType[mexc_type] + + @property + def authenticator(self): + return MexcAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + if self._domain == "com": + return "mexc" + else: + return f"mexc_{self._domain}" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.PING_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + async def get_all_pairs_prices(self) -> List[Dict[str, str]]: + pairs_prices = await self._api_get(path_url=CONSTANTS.TICKER_BOOK_PATH_URL) + return pairs_prices + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = ("-1021" in error_description + and "Timestamp for this request" in error_description) + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(CONSTANTS.ORDER_NOT_EXIST_ERROR_CODE) in str( + status_update_exception + ) and CONSTANTS.ORDER_NOT_EXIST_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return str(CONSTANTS.UNKNOWN_ORDER_ERROR_CODE) in str( + cancelation_exception + ) and CONSTANTS.UNKNOWN_ORDER_MESSAGE in str(cancelation_exception) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return MexcAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return MexcAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + is_maker = order_type is OrderType.LIMIT_MAKER + return DeductedFromReturnsTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + order_result = None + amount_str = f"{amount:f}" + type_str = MexcExchange.mexc_order_type(order_type) + side_str = CONSTANTS.SIDE_BUY if trade_type is TradeType.BUY else CONSTANTS.SIDE_SELL + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + api_params = {"symbol": symbol, + "side": side_str, + "quantity": amount_str, + # "quoteOrderQty": amount_str, + "type": type_str, + "newClientOrderId": order_id} + if order_type.is_limit_type(): + price_str = f"{price:f}" + api_params["price"] = price_str + else: + if trade_type.name.lower() == 'buy': + if price.is_nan(): + price = self.get_price_for_volume( + trading_pair, + True, + amount + ) + del api_params['quantity'] + api_params.update({ + "quoteOrderQty": f"{price * amount:f}", + }) + if order_type == OrderType.LIMIT: + api_params["timeInForce"] = CONSTANTS.TIME_IN_FORCE_GTC + + try: + order_result = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + data=api_params, + is_auth_required=True) + o_id = str(order_result["orderId"]) + transact_time = order_result["transactTime"] * 1e-3 + except IOError as e: + error_description = str(e) + is_server_overloaded = ("status is 503" in error_description + and "Unknown error, please check your request or try again later." in error_description) + if is_server_overloaded: + o_id = "UNKNOWN" + transact_time = self._time_synchronizer.time() + else: + raise + return o_id, transact_time + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + api_params = { + "symbol": symbol, + "origClientOrderId": order_id, + } + cancel_result = await self._api_delete( + path_url=CONSTANTS.ORDER_PATH_URL, + params=api_params, + is_auth_required=True) + if cancel_result.get("status") == "NEW": + return True + return False + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + trading_pair_rules = exchange_info_dict.get("symbols", []) + retval = [] + for rule in filter(mexc_utils.is_exchange_information_valid, trading_pair_rules): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=rule.get("symbol")) + min_order_size = Decimal(rule.get("baseSizePrecision")) + min_price_inc = Decimal(f"1e-{rule['quotePrecision']}") + min_amount_inc = Decimal(f"1e-{rule['baseAssetPrecision']}") + min_notional = Decimal(rule['quoteAmountPrecision']) + retval.append( + TradingRule(trading_pair, + min_order_size=min_order_size, + min_price_increment=min_price_inc, + min_base_amount_increment=min_amount_inc, + min_notional_size=min_notional)) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule}. Skipping.") + return retval + + async def _status_polling_loop_fetch_updates(self): + await self._update_order_fills_from_trades() + await super()._status_polling_loop_fetch_updates() + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + Listens to messages from _user_stream_tracker.user_stream queue. + Traders, Orders, and Balance updates from the WS. + """ + user_channels = [ + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + ] + async for event_message in self._iter_user_event_queue(): + try: + channel: str = event_message.get("c", None) + results: Dict[str, Any] = event_message.get("d", {}) + if "code" not in event_message and channel not in user_channels: + self.logger().error( + f"Unexpected message in user stream: {event_message}.", exc_info=True) + continue + if channel == CONSTANTS.USER_TRADES_ENDPOINT_NAME: + self._process_trade_message(results) + elif channel == CONSTANTS.USER_ORDERS_ENDPOINT_NAME: + self._process_order_message(event_message) + elif channel == CONSTANTS.USER_BALANCE_ENDPOINT_NAME: + self._process_balance_message_ws(results) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + def _process_balance_message_ws(self, account): + asset_name = account["a"] + self._account_available_balances[asset_name] = Decimal(str(account["f"])) + self._account_balances[asset_name] = Decimal(str(account["f"])) + Decimal(str(account["l"])) + + def _create_trade_update_with_order_fill_data( + self, + order_fill: Dict[str, Any], + order: InFlightOrder): + + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=order_fill["N"], + flat_fees=[TokenAmount( + amount=Decimal(order_fill["n"]), + token=order_fill["N"] + )] + ) + trade_update = TradeUpdate( + trade_id=str(order_fill["t"]), + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(order_fill["v"]), + fill_quote_amount=Decimal(order_fill["a"]), + fill_price=Decimal(order_fill["p"]), + fill_timestamp=order_fill["T"] * 1e-3, + ) + return trade_update + + def _process_trade_message(self, trade: Dict[str, Any], client_order_id: Optional[str] = None): + client_order_id = client_order_id or str(trade["c"]) + tracked_order = self._order_tracker.all_fillable_orders.get(client_order_id) + if tracked_order is None: + self.logger().debug(f"Ignoring trade message with id {client_order_id}: not in in_flight_orders.") + else: + trade_update = self._create_trade_update_with_order_fill_data( + order_fill=trade, + order=tracked_order) + self._order_tracker.process_trade_update(trade_update) + + def _create_order_update_with_order_status_data(self, order_status: Dict[str, Any], order: InFlightOrder): + client_order_id = str(order_status["d"].get("c", "")) + order_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=int(order_status["t"] * 1e-3), + new_state=CONSTANTS.WS_ORDER_STATE[order_status["d"]["s"]], + client_order_id=client_order_id, + exchange_order_id=str(order_status["d"]["i"]), + ) + return order_update + + def _process_order_message(self, raw_msg: Dict[str, Any]): + order_msg = raw_msg.get("d", {}) + client_order_id = str(order_msg.get("c", "")) + tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id) + if not tracked_order: + self.logger().debug(f"Ignoring order message with id {client_order_id}: not in in_flight_orders.") + return + + order_update = self._create_order_update_with_order_status_data(order_status=raw_msg, order=tracked_order) + self._order_tracker.process_order_update(order_update=order_update) + + async def _update_order_fills_from_trades(self): + """ + This is intended to be a backup measure to get filled events with trade ID for orders, + in case Mexc's user stream events are not working. + NOTE: It is not required to copy this functionality in other connectors. + This is separated from _update_order_status which only updates the order status without producing filled + events, since Mexc's get order endpoint does not return trade IDs. + The minimum poll interval for order status is 10 seconds. + """ + small_interval_last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + small_interval_current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL + long_interval_last_tick = self._last_poll_timestamp / self.LONG_POLL_INTERVAL + long_interval_current_tick = self.current_timestamp / self.LONG_POLL_INTERVAL + + if (long_interval_current_tick > long_interval_last_tick + or (self.in_flight_orders and small_interval_current_tick > small_interval_last_tick)): + query_time = int(self._last_trades_poll_mexc_timestamp * 1e3) + self._last_trades_poll_mexc_timestamp = self._time_synchronizer.time() + order_by_exchange_id_map = {} + for order in self._order_tracker.all_fillable_orders.values(): + order_by_exchange_id_map[order.exchange_order_id] = order + + tasks = [] + trading_pairs = self.trading_pairs + for trading_pair in trading_pairs: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + if self._last_poll_timestamp > 0: + params["startTime"] = query_time + tasks.append(self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params=params, + is_auth_required=True)) + + self.logger().debug(f"Polling for order fills of {len(tasks)} trading pairs.") + results = await safe_gather(*tasks, return_exceptions=True) + + for trades, trading_pair in zip(results, trading_pairs): + + if isinstance(trades, Exception): + self.logger().network( + f"Error fetching trades update for the order {trading_pair}: {trades}.", + app_warning_msg=f"Failed to fetch trade update for {trading_pair}." + ) + continue + for trade in trades: + exchange_order_id = str(trade["orderId"]) + if exchange_order_id in order_by_exchange_id_map: + # This is a fill for a tracked order + tracked_order = order_by_exchange_id_map[exchange_order_id] + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["time"] * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + elif self.is_confirmed_new_order_filled_event(str(trade["id"]), exchange_order_id, trading_pair): + # This is a fill of an order registered in the DB but not tracked any more + self._current_trade_fills.add(TradeFillOrderDetails( + market=self.display_name, + exchange_trade_id=str(trade["id"]), + symbol=trading_pair)) + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + timestamp=float(trade["time"]) * 1e-3, + order_id=self._exchange_order_ids.get(str(trade["orderId"]), None), + trading_pair=trading_pair, + trade_type=TradeType.BUY if trade["isBuyer"] else TradeType.SELL, + order_type=OrderType.LIMIT_MAKER if trade["isMaker"] else OrderType.LIMIT, + price=Decimal(trade["price"]), + amount=Decimal(trade["qty"]), + trade_fee=DeductedFromReturnsTradeFee( + flat_fees=[ + TokenAmount( + trade["commissionAsset"], + Decimal(trade["commission"]) + ) + ] + ), + exchange_trade_id=str(trade["id"]) + )) + self.logger().info(f"Recreating missing trade in TradeFill: {trade}") + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = order.exchange_order_id + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + all_fills_response = await self._api_get( + path_url=CONSTANTS.MY_TRADES_PATH_URL, + params={ + "symbol": trading_pair, + "orderId": exchange_order_id + }, + is_auth_required=True, + limit_id=CONSTANTS.MY_TRADES_PATH_URL) + + for trade in all_fills_response: + exchange_order_id = str(trade["orderId"]) + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["commissionAsset"], + flat_fees=[TokenAmount(amount=Decimal(trade["commission"]), token=trade["commissionAsset"])] + ) + trade_update = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=Decimal(trade["qty"]), + fill_quote_amount=Decimal(trade["quoteQty"]), + fill_price=Decimal(trade["price"]), + fill_timestamp=trade["time"] * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + trading_pair = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + updated_order_data = await self._api_get( + path_url=CONSTANTS.ORDER_PATH_URL, + params={ + "symbol": trading_pair, + "origClientOrderId": tracked_order.client_order_id}, + is_auth_required=True) + + new_state = CONSTANTS.ORDER_STATE[updated_order_data["status"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(updated_order_data["orderId"]), + trading_pair=tracked_order.trading_pair, + update_timestamp=updated_order_data["updateTime"] * 1e-3, + new_state=new_state, + ) + + return order_update + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get( + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + is_auth_required=True) + + balances = account_info["balances"] + for balance_entry in balances: + asset_name = balance_entry["asset"] + free_balance = Decimal(balance_entry["free"]) + total_balance = Decimal(balance_entry["free"]) + Decimal(balance_entry["locked"]) + self._account_available_balances[asset_name] = free_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(mexc_utils.is_exchange_information_valid, exchange_info["symbols"]): + mapping[symbol_data["symbol"]] = combine_to_hb_trading_pair(base=symbol_data["baseAsset"], + quote=symbol_data["quoteAsset"]) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + + resp_json = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, + params=params + ) + + return float(resp_json["lastPrice"]) diff --git a/hummingbot/connector/exchange/mexc/mexc_order_book.py b/hummingbot/connector/exchange/mexc/mexc_order_book.py new file mode 100644 index 0000000..abbab66 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_order_book.py @@ -0,0 +1,74 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class MexcOrderBook(OrderBook): + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["trading_pair"], + "update_id": msg["lastUpdateId"], + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["trading_pair"], + "update_id": int(msg['d']["r"]), + "bids": [[i['p'], i['v']] for i in msg['d'].get("bids", [])], + "asks": [[i['p'], i['v']] for i in msg['d'].get("asks", [])], + }, timestamp=timestamp * 1e-3) + + @classmethod + def trade_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + ts = timestamp + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["trading_pair"], + "trade_type": float(TradeType.SELL.value) if msg["S"] == 2 else float(TradeType.BUY.value), + "trade_id": msg["t"], + "update_id": ts, + "price": msg["p"], + "amount": msg["v"] + }, timestamp=ts * 1e-3) diff --git a/hummingbot/connector/exchange/mexc/mexc_utils.py b/hummingbot/connector/exchange/mexc/mexc_utils.py new file mode 100644 index 0000000..0acf552 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_utils.py @@ -0,0 +1,54 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "ZRX-ETH" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.000"), + taker_percent_fee_decimal=Decimal("0.000"), + buy_percent_fee_deducted_from_returns=True +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("status", None) == "ENABLED" and "SPOT" in exchange_info.get("permissions", list()) \ + and exchange_info.get("isSpotTradingAllowed", True) is True + + +class MexcConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="mexc", const=True, client_data=None) + mexc_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Mexc API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + mexc_api_secret: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Mexc API secret", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "mexc" + + +KEYS = MexcConfigMap.construct() diff --git a/hummingbot/connector/exchange/mexc/mexc_web_utils.py b/hummingbot/connector/exchange/mexc/mexc_web_utils.py new file mode 100644 index 0000000..88ec348 --- /dev/null +++ b/hummingbot/connector/exchange/mexc/mexc_web_utils.py @@ -0,0 +1,75 @@ +from typing import Callable, Optional + +import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Mexc domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PUBLIC_API_VERSION + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided private REST endpoint + :param path_url: a private REST endpoint + :param domain: the Mexc domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URL.format(domain) + CONSTANTS.PRIVATE_API_VERSION + path_url + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time( + throttler=throttler, + domain=domain, + )) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.SERVER_TIME_PATH_URL, + ) + server_time = response["serverTime"] + return server_time diff --git a/hummingbot/connector/exchange/ndax/__init__.py b/hummingbot/connector/exchange/ndax/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/ndax/dummy.pxd b/hummingbot/connector/exchange/ndax/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/ndax/dummy.pyx b/hummingbot/connector/exchange/ndax/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/ndax/ndax_api_order_book_data_source.py b/hummingbot/connector/exchange/ndax/ndax_api_order_book_data_source.py new file mode 100644 index 0000000..d920ed5 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_api_order_book_data_source.py @@ -0,0 +1,331 @@ +import asyncio +import logging +import time +from typing import Any, Dict, List, Optional + +import aiohttp + +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS, ndax_utils +from hummingbot.connector.exchange.ndax.ndax_order_book import NdaxOrderBook +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookEntry, NdaxOrderBookMessage +from hummingbot.connector.exchange.ndax.ndax_utils import convert_to_exchange_trading_pair +from hummingbot.connector.exchange.ndax.ndax_websocket_adaptor import NdaxWebSocketAdaptor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.logger.logger import HummingbotLogger + + +class NdaxAPIOrderBookDataSource(OrderBookTrackerDataSource): + _ORDER_BOOK_SNAPSHOT_DELAY = 60 * 60 # expressed in seconds + + _logger: Optional[HummingbotLogger] = None + _trading_pair_id_map: Dict[str, int] = {} + _last_traded_prices: Dict[str, float] = {} + + def __init__( + self, + throttler: Optional[AsyncThrottler] = None, + shared_client: Optional[aiohttp.ClientSession] = None, + trading_pairs: Optional[List[str]] = None, + domain: Optional[str] = None, + ): + super().__init__(trading_pairs) + self._shared_client = shared_client or self._get_session_instance() + self._throttler = throttler or self._get_throttler_instance() + self._domain: Optional[str] = domain + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @classmethod + def _get_session_instance(cls) -> aiohttp.ClientSession: + session = aiohttp.ClientSession() + return session + + @classmethod + def _get_throttler_instance(cls) -> AsyncThrottler: + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + return throttler + + @classmethod + async def init_trading_pair_ids(cls, domain: Optional[str] = None, throttler: Optional[AsyncThrottler] = None, shared_client: Optional[aiohttp.ClientSession] = None): + """Initialize _trading_pair_id_map class variable + """ + cls._trading_pair_id_map.clear() + + shared_client = shared_client or cls._get_session_instance() + + params = { + "OMSId": 1 + } + + throttler = throttler or cls._get_throttler_instance() + async with throttler.execute_task(CONSTANTS.MARKETS_URL): + async with shared_client.get( + f"{ndax_utils.rest_api_url(domain) + CONSTANTS.MARKETS_URL}", params=params + ) as response: + if response.status == 200: + resp_json: Dict[str, Any] = await response.json() + + results = { + f"{instrument['Product1Symbol']}-{instrument['Product2Symbol']}": int( + instrument["InstrumentId"]) + for instrument in resp_json + if instrument["SessionStatus"] == "Running" + } + + cls._trading_pair_id_map = results + + @classmethod + async def get_last_traded_prices( + cls, trading_pairs: List[str], domain: Optional[str] = None, throttler: Optional[AsyncThrottler] = None, shared_client: Optional[aiohttp.ClientSession] = None + ) -> Dict[str, float]: + """Fetches the Last Traded Price of the specified trading pairs. + + :params: List[str] trading_pairs: List of trading pairs(in Hummingbot base-quote format i.e. BTC-CAD) + :return: Dict[str, float]: Dictionary of the trading pairs mapped to its last traded price in float + """ + if not len(cls._trading_pair_id_map) > 0: + await cls.init_trading_pair_ids(domain) + + shared_client = shared_client or cls._get_session_instance() + + results = {} + + for trading_pair in trading_pairs: + if trading_pair in cls._last_traded_prices: + results[trading_pair] = cls._last_traded_prices[trading_pair] + else: + params = { + "OMSId": 1, + "InstrumentId": cls._trading_pair_id_map[trading_pair], + } + throttler = throttler or cls._get_throttler_instance() + async with throttler.execute_task(CONSTANTS.LAST_TRADE_PRICE_URL): + async with shared_client.get( + f"{ndax_utils.rest_api_url(domain) + CONSTANTS.LAST_TRADE_PRICE_URL}", params=params + ) as response: + if response.status == 200: + resp_json: Dict[str, Any] = await response.json() + + results.update({ + trading_pair: float(resp_json["LastTradedPx"]) + }) + + return results + + @staticmethod + async def fetch_trading_pairs(domain: str = None, throttler: Optional[AsyncThrottler] = None) -> List[str]: + """Fetches and formats all supported trading pairs. + + Returns: + List[str]: List of supported trading pairs in Hummingbot's format. (i.e. BASE-QUOTE) + """ + async with aiohttp.ClientSession() as client: + params = { + "OMSId": 1 + } + throttler = throttler or NdaxAPIOrderBookDataSource._get_throttler_instance() + async with throttler.execute_task(CONSTANTS.MARKETS_URL): + async with client.get( + f"{ndax_utils.rest_api_url(domain) + CONSTANTS.MARKETS_URL}", params=params + ) as response: + if response.status == 200: + resp_json: Dict[str, Any] = await response.json() + return [f"{instrument['Product1Symbol']}-{instrument['Product2Symbol']}" + for instrument in resp_json + if instrument["SessionStatus"] == "Running"] + return [] + + async def get_order_book_data( + self, trading_pair: str, domain: Optional[str] = None, throttler: Optional[AsyncThrottler] = None + ) -> Dict[str, any]: + """Retrieves entire orderbook snapshot of the specified trading pair via the REST API. + + Args: + trading_pair (str): Trading pair of the particular orderbook. + domain (str): The label of the variant of the connector that is being used. + throttler (AsyncThrottler): API-requests throttler to use. + + Returns: + Dict[str, any]: Parsed API Response. + """ + if not len(self._trading_pair_id_map) > 0: + await self.init_trading_pair_ids(domain) + params = { + "OMSId": 1, + "InstrumentId": self._trading_pair_id_map[trading_pair], + "Depth": 200, + } + + throttler = throttler or self._get_throttler_instance() + async with throttler.execute_task(CONSTANTS.ORDER_BOOK_URL): + async with self._shared_client.get( + f"{ndax_utils.rest_api_url(domain) + CONSTANTS.ORDER_BOOK_URL}", params=params + ) as response: + status = response.status + if status != 200: + raise IOError( + f"Error fetching OrderBook for {trading_pair} at {CONSTANTS.ORDER_BOOK_URL}. " + f"HTTP {status}. Response: {await response.json()}" + ) + + response_ls: List[Any] = await response.json() + orderbook_entries: List[NdaxOrderBookEntry] = [NdaxOrderBookEntry(*entry) for entry in response_ls] + return {"data": orderbook_entries, + "timestamp": int(time.time() * 1e3)} + + async def _sleep(self, delay): + """ + Function added only to facilitate patching the sleep in unit tests without affecting the asyncio module + """ + await asyncio.sleep(delay) + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + snapshot: Dict[str, Any] = await self.get_order_book_data(trading_pair, self._domain) + + snapshot_msg: NdaxOrderBookMessage = NdaxOrderBook.snapshot_message_from_exchange( + msg=snapshot, + timestamp=snapshot["timestamp"], + ) + order_book = self.order_book_create_function() + + bids, asks = snapshot_msg.bids, snapshot_msg.asks + order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) + + return order_book + + async def get_instrument_ids(self) -> Dict[str, int]: + if not len(self._trading_pair_id_map) > 0: + await self.init_trading_pair_ids(self._domain, self._throttler, self._shared_client) + return self._trading_pair_id_map + + async def _create_websocket_connection(self) -> NdaxWebSocketAdaptor: + """ + Initialize WebSocket client for UserStreamDataSource + """ + try: + ws = await self._shared_client.ws_connect(ndax_utils.wss_url(self._domain)) + return NdaxWebSocketAdaptor(throttler=self._throttler, websocket=ws) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().network(f"Unexpected error occurred during {CONSTANTS.EXCHANGE_NAME} WebSocket Connection " + f"({ex})") + raise + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + Periodically polls for orderbook snapshots using the REST API. + """ + if not len(self._trading_pair_id_map) > 0: + await self.init_trading_pair_ids(self._domain, self._throttler, self._shared_client) + while True: + await self._sleep(self._ORDER_BOOK_SNAPSHOT_DELAY) + try: + for trading_pair in self._trading_pairs: + snapshot: Dict[str: Any] = await self.get_order_book_data(trading_pair, domain=self._domain) + metadata = { + "trading_pair": trading_pair, + "instrument_id": self._trading_pair_id_map.get(trading_pair, None) + } + snapshot_message: NdaxOrderBookMessage = NdaxOrderBook.snapshot_message_from_exchange( + msg=snapshot, + timestamp=snapshot["timestamp"], + metadata=metadata + ) + output.put_nowait(snapshot_message) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error occured listening for orderbook snapshots. Retrying in 5 secs...", + exc_info=True) + await self._sleep(5.0) + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + """ + Listen for orderbook diffs using WebSocket API. + """ + if not len(self._trading_pair_id_map) > 0: + await self.init_trading_pair_ids(self._domain, self._throttler, self._shared_client) + + while True: + try: + ws_adaptor: NdaxWebSocketAdaptor = await self._create_websocket_connection() + for trading_pair in self._trading_pairs: + payload = { + "OMSId": 1, + "Symbol": convert_to_exchange_trading_pair(trading_pair), + "Depth": 200 + } + async with self._throttler.execute_task(CONSTANTS.WS_ORDER_BOOK_CHANNEL): + await ws_adaptor.send_request(endpoint_name=CONSTANTS.WS_ORDER_BOOK_CHANNEL, + payload=payload) + async for raw_msg in ws_adaptor.iter_messages(): + payload = NdaxWebSocketAdaptor.payload_from_raw_message(raw_msg) + msg_event: str = NdaxWebSocketAdaptor.endpoint_from_raw_message(raw_msg) + if msg_event in [CONSTANTS.WS_ORDER_BOOK_CHANNEL, CONSTANTS.WS_ORDER_BOOK_L2_UPDATE_EVENT]: + msg_data: List[NdaxOrderBookEntry] = [NdaxOrderBookEntry(*entry) + for entry in payload] + msg_timestamp: int = int(time.time() * 1e3) + msg_product_code: int = msg_data[0].productPairCode + + content = {"data": msg_data} + msg_trading_pair: Optional[str] = None + + for trading_pair, instrument_id in self._trading_pair_id_map.items(): + if msg_product_code == instrument_id: + msg_trading_pair = trading_pair + break + + if msg_trading_pair: + metadata = { + "trading_pair": msg_trading_pair, + "instrument_id": msg_product_code, + } + + order_book_message = None + if msg_event == CONSTANTS.WS_ORDER_BOOK_CHANNEL: + order_book_message: NdaxOrderBookMessage = NdaxOrderBook.snapshot_message_from_exchange( + msg=content, + timestamp=msg_timestamp, + metadata=metadata) + elif msg_event == CONSTANTS.WS_ORDER_BOOK_L2_UPDATE_EVENT: + order_book_message: NdaxOrderBookMessage = NdaxOrderBook.diff_message_from_exchange( + msg=content, + timestamp=msg_timestamp, + metadata=metadata) + self._last_traded_prices[ + order_book_message.trading_pair] = order_book_message.last_traded_price + await output.put(order_book_message) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error with WebSocket connection.", + exc_info=True, + app_warning_msg="Unexpected error with WebSocket connection. Retrying in 30 seconds. " + "Check network connection." + ) + if ws_adaptor: + await ws_adaptor.close() + await self._sleep(30.0) + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + # NDAX does not have a public orderbook trade channel, rather it can be inferred from the Level2UpdateEvent when + # subscribed to the SubscribeLevel2 channel + pass + + async def listen_for_subscriptions(self): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + # This connector does not use this base class method and needs a refactoring + pass diff --git a/hummingbot/connector/exchange/ndax/ndax_api_user_stream_data_source.py b/hummingbot/connector/exchange/ndax/ndax_api_user_stream_data_source.py new file mode 100644 index 0000000..f6beea7 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_api_user_stream_data_source.py @@ -0,0 +1,139 @@ +import aiohttp +import asyncio +import logging +import time +import ujson + +from typing import ( + Any, + Dict, + Optional, +) + +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS, ndax_utils +from hummingbot.connector.exchange.ndax.ndax_websocket_adaptor import NdaxWebSocketAdaptor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.logger import HummingbotLogger + + +class NdaxAPIUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, throttler: AsyncThrottler, auth_assistant: NdaxAuth, shared_client: Optional[aiohttp.ClientSession] = None, domain: Optional[str] = None): + super().__init__() + self._shared_client = shared_client or self._get_session_instance() + self._ws_adaptor = None + self._auth_assistant: NdaxAuth = auth_assistant + self._last_recv_time: float = 0 + self._account_id: Optional[int] = None + self._oms_id: Optional[int] = None + self._domain = domain + self._throttler = throttler + + @property + def last_recv_time(self) -> float: + return self._last_recv_time + + @classmethod + def _get_session_instance(cls) -> aiohttp.ClientSession: + session = aiohttp.ClientSession() + return session + + async def _init_websocket_connection(self) -> NdaxWebSocketAdaptor: + """ + Initialize WebSocket client for UserStreamDataSource + """ + try: + if self._ws_adaptor is None: + ws = await self._shared_client.ws_connect(ndax_utils.wss_url(self._domain)) + self._ws_adaptor = NdaxWebSocketAdaptor(throttler=self._throttler, websocket=ws) + return self._ws_adaptor + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().network(f"Unexpected error occurred during {CONSTANTS.EXCHANGE_NAME} WebSocket Connection " + f"({ex})") + raise + + async def _authenticate(self, ws: NdaxWebSocketAdaptor): + """ + Authenticates user to websocket + """ + try: + auth_payload: Dict[str, Any] = self._auth_assistant.get_ws_auth_payload() + async with self._throttler.execute_task(CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME): + await ws.send_request(CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, auth_payload) + auth_resp = await ws.receive() + auth_payload: Dict[str, Any] = ws.payload_from_raw_message(auth_resp.data) + + if not auth_payload["Authenticated"]: + self.logger().error(f"Response: {auth_payload}", + exc_info=True) + raise Exception("Could not authenticate websocket connection with NDAX") + + auth_user = auth_payload.get("User") + self._account_id = auth_user.get("AccountId") + self._oms_id = auth_user.get("OMSId") + + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error(f"Error occurred when authenticating to user stream ({ex})", + exc_info=True) + raise + + async def _subscribe_to_events(self, ws: NdaxWebSocketAdaptor): + """ + Subscribes to User Account Events + """ + payload = {"AccountId": self._account_id, + "OMSId": self._oms_id} + try: + async with self._throttler.execute_task(CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME): + await ws.send_request(CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME, payload) + + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error(f"Error occurred subscribing to {CONSTANTS.EXCHANGE_NAME} private channels ({ex})", + exc_info=True) + raise + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + *required + Subscribe to user stream via web socket, and keep the connection open for incoming messages + + :param output: an async queue where the incoming messages are stored + """ + while True: + try: + ws: NdaxWebSocketAdaptor = await self._init_websocket_connection() + self.logger().info("Authenticating to User Stream...") + await self._authenticate(ws) + self.logger().info("Successfully authenticated to User Stream.") + await self._subscribe_to_events(ws) + self.logger().info("Successfully subscribed to user events.") + + async for msg in ws.iter_messages(): + self._last_recv_time = int(time.time()) + output.put_nowait(ujson.loads(msg)) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error( + f"Unexpected error with NDAX WebSocket connection. Retrying in 30 seconds. ({ex})", + exc_info=True + ) + if self._ws_adaptor is not None: + await self._ws_adaptor.close() + self._ws_adaptor = None + await asyncio.sleep(30.0) diff --git a/hummingbot/connector/exchange/ndax/ndax_auth.py b/hummingbot/connector/exchange/ndax/ndax_auth.py new file mode 100644 index 0000000..128c5f7 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_auth.py @@ -0,0 +1,67 @@ +import hashlib +import hmac +from typing import Dict, Any + +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce_low_res + + +class NdaxAuth(): + """ + Auth class required by NDAX API + """ + + def __init__(self, uid: str, api_key: str, secret_key: str, account_name: str): + self._uid: str = uid + self._api_key: str = api_key + self._secret_key: str = secret_key + self._account_name: str = account_name + + @property + def uid(self) -> int: + return int(self._uid) + + @property + def account_name(self) -> str: + return self._account_name + + def generate_nonce(self): + return str(get_tracking_nonce_low_res()) + + def generate_auth_dict(self) -> Dict[str, Any]: + """ + Generates a dictionary with all required information for the authentication process + :return: a dictionary of authentication info including the request signature + """ + nonce = self.generate_nonce() + raw_signature = nonce + self._uid + self._api_key + + auth_info = {'Nonce': nonce, + 'APIKey': self._api_key, + 'Signature': hmac.new(self._secret_key.encode('utf-8'), + raw_signature.encode('utf-8'), + hashlib.sha256).hexdigest(), + 'UserId': self._uid} + + return auth_info + + def get_ws_auth_payload(self) -> Dict[str, Any]: + """ + Generates and returns a dictionary with the structure of the payload required for authentication + :return: a dictionary with the required parameters for authentication + """ + return self.generate_auth_dict() + + def get_headers(self) -> Dict[str, Any]: + """ + Generates authentication headers required by ProBit + :return: a dictionary of auth headers + """ + + return { + "Content-Type": 'application/json', + } + + def get_auth_headers(self): + headers = self.get_headers() + headers.update(self.generate_auth_dict()) + return headers diff --git a/hummingbot/connector/exchange/ndax/ndax_constants.py b/hummingbot/connector/exchange/ndax/ndax_constants.py new file mode 100644 index 0000000..4789456 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_constants.py @@ -0,0 +1,165 @@ +# A single source of truth for constant variables related to the exchange +from hummingbot.core.api_throttler.data_types import RateLimit, LinkedLimitWeightPair + +EXCHANGE_NAME = "ndax" + +REST_URLS = {"ndax_main": "https://api.ndax.io:8443/AP/", + "ndax_testnet": "https://ndaxmarginstaging.cdnhop.net:8443/AP/"} +WSS_URLS = {"ndax_main": "wss://api.ndax.io/WSGateway", + "ndax_testnet": "wss://ndaxmarginstaging.cdnhop.net/WSGateway"} + +REST_API_VERSION = "v3.3" + +# REST API Public Endpoints +MARKETS_URL = "GetInstruments" +ORDER_BOOK_URL = "GetL2Snapshot" +LAST_TRADE_PRICE_URL = "GetLevel1" + +# REST API Private Endpoints +ACCOUNT_POSITION_PATH_URL = "GetAccountPositions" +USER_ACCOUNT_INFOS_PATH_URL = "GetUserAccountInfos" +SEND_ORDER_PATH_URL = "SendOrder" +CANCEL_ORDER_PATH_URL = "CancelOrder" +GET_ORDER_STATUS_PATH_URL = "GetOrderStatus" +GET_TRADES_HISTORY_PATH_URL = "GetTradesHistory" +GET_OPEN_ORDERS_PATH_URL = "GetOpenOrders" +PING_PATH_URL = "Ping" +HTTP_PING_ID = "HTTPPing" + +# WebSocket Public Endpoints +ACCOUNT_POSITION_EVENT_ENDPOINT_NAME = "AccountPositionEvent" +AUTHENTICATE_USER_ENDPOINT_NAME = "AuthenticateUser" +ORDER_STATE_EVENT_ENDPOINT_NAME = "OrderStateEvent" +ORDER_TRADE_EVENT_ENDPOINT_NAME = "OrderTradeEvent" +SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME = "SubscribeAccountEvents" +WS_ORDER_BOOK_CHANNEL = "SubscribeLevel2" +WS_PING_REQUEST = "Ping" +WS_PING_ID = "WSPing" + +# WebSocket Message Events +WS_ORDER_BOOK_L2_UPDATE_EVENT = "Level2UpdateEvent" + +API_LIMIT_REACHED_ERROR_MESSAGE = "TOO MANY REQUESTS" + +MINUTE = 60 +HTTP_ENDPOINTS_LIMIT_ID = "AllHTTP" +HTTP_LIMIT = 600 +WS_AUTH_LIMIT_ID = "AllWsAuth" +WS_ENDPOINTS_LIMIT_ID = "AllWs" +WS_LIMIT = 500 +RATE_LIMITS = [ + RateLimit(limit_id=HTTP_ENDPOINTS_LIMIT_ID, limit=HTTP_LIMIT, time_interval=MINUTE), + # public http + RateLimit( + limit_id=MARKETS_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=ORDER_BOOK_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=LAST_TRADE_PRICE_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + # private http + RateLimit( + limit_id=ACCOUNT_POSITION_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=USER_ACCOUNT_INFOS_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=SEND_ORDER_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=CANCEL_ORDER_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=GET_ORDER_STATUS_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=GET_TRADES_HISTORY_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=GET_OPEN_ORDERS_PATH_URL, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=HTTP_PING_ID, + limit=HTTP_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(HTTP_ENDPOINTS_LIMIT_ID)], + ), + # ws public + RateLimit(limit_id=WS_AUTH_LIMIT_ID, limit=50, time_interval=MINUTE), + RateLimit(limit_id=WS_ENDPOINTS_LIMIT_ID, limit=WS_LIMIT, time_interval=MINUTE), + RateLimit( + limit_id=ACCOUNT_POSITION_EVENT_ENDPOINT_NAME, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=AUTHENTICATE_USER_ENDPOINT_NAME, + limit=50, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_AUTH_LIMIT_ID)], + ), + RateLimit( + limit_id=ORDER_STATE_EVENT_ENDPOINT_NAME, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=ORDER_TRADE_EVENT_ENDPOINT_NAME, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=WS_ORDER_BOOK_CHANNEL, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), + RateLimit( + limit_id=WS_PING_ID, + limit=WS_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(WS_ENDPOINTS_LIMIT_ID)], + ), +] diff --git a/hummingbot/connector/exchange/ndax/ndax_exchange.py b/hummingbot/connector/exchange/ndax/ndax_exchange.py new file mode 100644 index 0000000..8444081 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_exchange.py @@ -0,0 +1,1056 @@ +import asyncio +import logging +import math +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Union + +import aiohttp +import ujson + +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS, ndax_utils +from hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source import NdaxAPIOrderBookDataSource +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth +from hummingbot.connector.exchange.ndax.ndax_in_flight_order import NdaxInFlightOrder, NdaxInFlightOrderNotCreated +from hummingbot.connector.exchange.ndax.ndax_order_book_tracker import NdaxOrderBookTracker +from hummingbot.connector.exchange.ndax.ndax_user_stream_tracker import NdaxUserStreamTracker +from hummingbot.connector.exchange.ndax.ndax_websocket_adaptor import NdaxWebSocketAdaptor +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OpenOrder, OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + +RESOURCE_NOT_FOUND_ERR = "Resource Not Found" + + +class NdaxExchange(ExchangeBase): + """ + Class to onnect with NDAX exchange. Provides order book pricing, user account tracking and + trading functionality. + """ + SHORT_POLL_INTERVAL = 5.0 + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + UPDATE_TRADING_RULES_INTERVAL = 60.0 + LONG_POLL_INTERVAL = 120.0 + ORDER_EXCEED_NOT_FOUND_COUNT = 2 + + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, + client_config_map: "ClientConfigAdapter", + ndax_uid: str, + ndax_api_key: str, + ndax_secret_key: str, + ndax_account_name: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: Optional[str] = None + ): + """ + :param ndax_uid: User ID of the account + :param ndax_api_key: The API key to connect to private NDAX APIs. + :param ndax_secret_key: The API secret. + :param ndax_account_name: The name of the account associated to the user account. + :param trading_pairs: The market trading pairs which to track order book data. + :param trading_required: Whether actual trading is needed. + """ + super().__init__(client_config_map) + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._auth = NdaxAuth(uid=ndax_uid, + api_key=ndax_api_key, + secret_key=ndax_secret_key, + account_name=ndax_account_name) + self._throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self._shared_client = aiohttp.ClientSession() + self._set_order_book_tracker(NdaxOrderBookTracker( + throttler=self._throttler, shared_client=self._shared_client, trading_pairs=trading_pairs, domain=domain + )) + self._user_stream_tracker = NdaxUserStreamTracker( + throttler=self._throttler, shared_client=self._shared_client, auth_assistant=self._auth, domain=domain + ) + self._domain = domain + self._ev_loop = asyncio.get_event_loop() + self._poll_notifier = asyncio.Event() + self._last_timestamp = 0 + self._in_flight_orders = {} + self._order_not_found_records = {} # Dict[client_order_id:str, count:int] + self._trading_rules = {} # Dict[trading_pair:str, TradingRule] + self._last_poll_timestamp = 0 + + self._status_polling_task = None + self._user_stream_tracker_task = None + self._user_stream_event_listener_task = None + self._trading_rules_polling_task = None + + self._account_id = None + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def account_id(self) -> int: + return self._account_id + + @property + def trading_rules(self) -> Dict[str, TradingRule]: + return self._trading_rules + + @property + def in_flight_orders(self) -> Dict[str, NdaxInFlightOrder]: + return self._in_flight_orders + + @property + def status_dict(self) -> Dict[str, bool]: + """ + A dictionary of statuses of various exchange's components. Used to determine if the connector is ready + """ + return { + "account_id_initialized": self.account_id if self._trading_required else True, + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "trading_rule_initialized": len(self._trading_rules) > 0, + "user_stream_initialized": + self._user_stream_tracker.data_source.last_recv_time > 0 if self._trading_required else True, + } + + @property + def ready(self) -> bool: + """ + Determines if the connector is ready. + :return True when all statuses pass, this might take 5-10 seconds for all the connector's components and + services to be ready. + """ + return all(self.status_dict.values()) + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self.order_book_tracker.order_books + + @property + def limit_orders(self) -> List[LimitOrder]: + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + @property + def tracking_states(self) -> Dict[str, Any]: + """ + :return active in-flight order in JSON format. Used to save order entries into local sqlite databse. + """ + return { + client_oid: order.to_json() + for client_oid, order in self._in_flight_orders.items() + if not order.is_done + } + + async def initialized_account_id(self) -> int: + if not self._account_id: + self._account_id = await self._get_account_id() + return self._account_id + + def restore_tracking_states(self, saved_states: Dict[str, Any]): + """ + Restore in-flight orders from the saved tracking states(from local db). This is such that the connector can pick + up from where it left off before Hummingbot client was terminated. + :param saved_states: The saved tracking_states. + """ + self._in_flight_orders.update({ + client_oid: NdaxInFlightOrder.from_json(order_json) + for client_oid, order_json in saved_states.items() + }) + + def supported_order_types(self) -> List[OrderType]: + """ + :return: a list of OrderType supported by this connector. + Note that Market order type is no longer required and will not be used. + """ + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def _get_account_id(self) -> int: + """ + Calls REST API to retrieve Account ID + """ + params = { + "OMSId": 1, + "UserId": self._auth.uid, + "UserName": self._auth.account_name + } + + resp: List[int] = await self._api_request( + "GET", + path_url=CONSTANTS.USER_ACCOUNT_INFOS_PATH_URL, + params=params, + is_auth_required=True, + ) + + account_info = next((account_info for account_info in resp + if account_info.get("AccountName") == self._auth.account_name), + None) + if account_info is None: + self.logger().error(f"There is no account named {self._auth.account_name} " + f"associated with the current NDAX user") + acc_id = None + else: + acc_id = int(account_info.get("AccountId")) + + return acc_id + + def start(self, clock: Clock, timestamp: float): + """ + This function is called automatically by the clock. + """ + super().start(clock, timestamp) + + def stop(self, clock: Clock): + """ + This function is called automatically by the clock. + """ + super().stop(clock) + + async def start_network(self): + """ + This function is required by NetworkIterator base class and is called automatically. + It starts tracking order book, polling trading rules, + updating statuses and tracking user data. + """ + self.logger().warning("This exchange connector does not provide trades feed. " + "Strategies which depend on it will not work properly.") + self.order_book_tracker.start() + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._user_stream_tracker_task = safe_ensure_future(self._user_stream_tracker.start()) + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + + async def stop_network(self): + """ + This function is required by NetworkIterator base class and is called automatically. + """ + self.order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._trading_rules_polling_task = None + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + self._user_stream_tracker_task = None + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + self._user_stream_event_listener_task = None + + async def check_network(self) -> NetworkStatus: + """ + This function is required by NetworkIterator base class and is called periodically to check + the network connection. Simply ping the network (or call any light weight public API). + """ + try: + resp = await self._api_request( + method="GET", + path_url=CONSTANTS.PING_PATH_URL, + limit_id=CONSTANTS.HTTP_PING_ID, + ) + if "msg" not in resp or resp["msg"] != "PONG": + raise Exception() + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + async def _api_request(self, + method: str, + path_url: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + limit_id: Optional[str] = None) -> Union[Dict[str, Any], List[Any]]: + """ + Sends an aiohttp request and waits for a response. + :param method: The HTTP method, e.g. get or post + :param path_url: The path url or the API end point + :param params: The query parameters of the API request + :param params: The body parameters of the API request + :param is_auth_required: Whether an authentication is required, when True the function will add encrypted + signature to the request. + :param limit_id: The id used for the API throttler. If not supplied, the `path_url` is used instead. + :returns A response in json format. + """ + url = ndax_utils.rest_api_url(self._domain) + path_url + + try: + if is_auth_required: + headers = self._auth.get_auth_headers() + else: + headers = self._auth.get_headers() + + limit_id = limit_id or path_url + if method == "GET": + async with self._throttler.execute_task(limit_id): + response = await self._shared_client.get(url, headers=headers, params=params) + elif method == "POST": + async with self._throttler.execute_task(limit_id): + response = await self._shared_client.post(url, headers=headers, data=ujson.dumps(data)) + else: + raise NotImplementedError(f"{method} HTTP Method not implemented. ") + + data = await response.text() + if data == CONSTANTS.API_LIMIT_REACHED_ERROR_MESSAGE: + raise Exception(f"The exchange API request limit has been reached (original error '{data}')") + + parsed_response = await response.json() + + except ValueError as e: + self.logger().error(f"{str(e)}") + raise ValueError(f"Error authenticating request {method} {url}. Error: {str(e)}") + except Exception as e: + raise IOError(f"Error parsing data from {url}. Error: {str(e)}") + if response.status != 200 or (isinstance(parsed_response, dict) and not parsed_response.get("result", True)): + self.logger().error(f"Error fetching data from {url}. HTTP status is {response.status}. " + f"Message: {parsed_response} " + f"Params: {params} " + f"Data: {data}") + raise Exception(f"Error fetching data from {url}. HTTP status is {response.status}. " + f"Message: {parsed_response} " + f"Params: {params} " + f"Data: {data}") + + return parsed_response + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + """ + Used by quantize_order_price() in _create_order() + Returns a price step, a minimum price increment for a given trading pair. + """ + trading_rule = self._trading_rules[trading_pair] + return trading_rule.min_price_increment + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + """ + Used by quantize_order_price() in _create_order() + Returns an order amount step, a minimum amount increment for a given trading pair. + """ + trading_rule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + def get_order_book(self, trading_pair: str) -> OrderBook: + if trading_pair not in self.order_book_tracker.order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return self.order_book_tracker.order_books[trading_pair] + + async def _create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + price: Decimal = s_decimal_0, + order_type: OrderType = OrderType.MARKET): + """ + Calls create-order API end point to place an order, starts tracking the order and triggers order created event. + :param trade_type: BUY or SELL + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param amount: The order amount (in base token value) + :param price: The order price + :param order_type: The order type + """ + trading_rule: TradingRule = self._trading_rules[trading_pair] + + trading_pair_ids: Dict[str, int] = await self.order_book_tracker.data_source.get_instrument_ids() + + try: + amount: Decimal = self.quantize_order_amount(trading_pair, amount) + if amount < trading_rule.min_order_size: + raise ValueError(f"{trade_type.name} order amount {amount} is lower than the minimum order size " + f"{trading_rule.min_order_size}.") + + params = { + "InstrumentId": trading_pair_ids[trading_pair], + "OMSId": 1, + "AccountId": await self.initialized_account_id(), + "ClientOrderId": int(order_id), + "Side": 0 if trade_type == TradeType.BUY else 1, + "Quantity": amount, + "TimeInForce": 1, # GTC + } + + if order_type.is_limit_type(): + price: Decimal = self.quantize_order_price(trading_pair, price) + + params.update({ + "OrderType": 2, # Limit + "LimitPrice": price, + }) + else: + params.update({ + "OrderType": 1 # Market + }) + + self.start_tracking_order(order_id, + None, + trading_pair, + trade_type, + price, + amount, + order_type + ) + + send_order_results = await self._api_request( + method="POST", + path_url=CONSTANTS.SEND_ORDER_PATH_URL, + data=params, + is_auth_required=True + ) + + if send_order_results["status"] == "Rejected": + raise ValueError(f"Order is rejected by the API. " + f"Parameters: {params} Error Msg: {send_order_results['errormsg']}") + + exchange_order_id = str(send_order_results["OrderId"]) + tracked_order = self._in_flight_orders.get(order_id) + if tracked_order is not None: + self.logger().info(f"Created {order_type.name} {trade_type.name} order {order_id} for " + f"{amount} {trading_pair}.") + tracked_order.update_exchange_order_id(exchange_order_id) + + except asyncio.CancelledError: + raise + except Exception as e: + self.stop_tracking_order(order_id) + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent(self.current_timestamp, order_id, order_type)) + self.logger().network( + f"Error submitting {trade_type.name} {order_type.name} order to NDAX for " + f"{amount} {trading_pair} {price}. Error: {str(e)}", + exc_info=True, + app_warning_msg="Error submitting order to NDAX. " + ) + + def trigger_order_created_event(self, order: NdaxInFlightOrder): + event_tag = MarketEvent.BuyOrderCreated if order.trade_type is TradeType.BUY else MarketEvent.SellOrderCreated + event_class = BuyOrderCreatedEvent if order.trade_type is TradeType.BUY else SellOrderCreatedEvent + self.trigger_event(event_tag, + event_class( + self.current_timestamp, + order.order_type, + order.trading_pair, + order.amount, + order.price, + order.client_order_id, + order.creation_timestamp, + exchange_order_id=order.exchange_order_id + )) + + def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType = OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + """ + Buys an amount of base asset as specified in the trading pair. This function returns immediately. + To see an actual order, wait for a BuyOrderCreatedEvent. + :param trading_pair: The market (e.g. BTC-CAD) to buy from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price in which the order is to be placed at + :returns A new client order id + """ + order_id: str = ndax_utils.get_new_client_order_id(True, trading_pair) + safe_ensure_future(self._create_order(trade_type=TradeType.BUY, + trading_pair=trading_pair, + order_id=order_id, + amount=amount, + price=price, + order_type=order_type, + )) + return order_id + + def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType = OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + """ + Sells an amount of base asset as specified in the trading pair. This function returns immediately. + To see an actual order, wait for a BuyOrderCreatedEvent. + :param trading_pair: The market (e.g. BTC-CAD) to buy from + :param amount: The amount in base token value + :param order_type: The order type + :param price: The price in which the order is to be placed at + :returns A new client order id + """ + order_id: str = ndax_utils.get_new_client_order_id(False, trading_pair) + safe_ensure_future(self._create_order(trade_type=TradeType.SELL, + trading_pair=trading_pair, + order_id=order_id, + amount=amount, + price=price, + order_type=order_type, + )) + return order_id + + async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: + """ + To determine if an order is successfully canceled, we either call the + GetOrderStatus/GetOpenOrders endpoint or wait for a OrderStateEvent/OrderTradeEvent from the WS. + :param trading_pair: The market (e.g. BTC-CAD) the order is in. + :param order_id: The client_order_id of the order to be cancelled. + """ + try: + tracked_order: Optional[NdaxInFlightOrder] = self._in_flight_orders.get(order_id, None) + if tracked_order is None: + raise ValueError(f"Failed to cancel order - {order_id}. Order not being tracked.") + if tracked_order.is_locally_working: + raise NdaxInFlightOrderNotCreated( + f"Failed to cancel order - {order_id}. Order not yet created." + f" This is most likely due to rate-limiting." + ) + + body_params = { + "OMSId": 1, + "AccountId": await self.initialized_account_id(), + "OrderId": await tracked_order.get_exchange_order_id() + } + + # The API response simply verifies that the API request have been received by the API servers. + await self._api_request( + method="POST", + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, + data=body_params, + is_auth_required=True + ) + + return order_id + + except asyncio.CancelledError: + raise + except NdaxInFlightOrderNotCreated: + raise + except Exception as e: + self.logger().error(f"Failed to cancel order {order_id}: {str(e)}") + self.logger().network( + f"Failed to cancel order {order_id}: {str(e)}", + exc_info=True, + app_warning_msg=f"Failed to cancel order {order_id} on NDAX. " + f"Check API key and network connection." + ) + if RESOURCE_NOT_FOUND_ERR in str(e): + self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1 + if self._order_not_found_records[order_id] >= self.ORDER_EXCEED_NOT_FOUND_COUNT: + self.logger().warning(f"Order {order_id} does not seem to be active, will stop tracking order...") + self.stop_tracking_order(order_id) + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent(self.current_timestamp, order_id)) + + def cancel(self, trading_pair: str, order_id: str): + """ + Cancel an order. This function returns immediately. + An Order is only determined to be cancelled when a OrderCancelledEvent is received. + :param trading_pair: The market (e.g. BTC-CAD) of the order. + :param order_id: The client_order_id of the order to be cancelled. + """ + safe_ensure_future(self._execute_cancel(trading_pair, order_id)) + return order_id + + async def get_open_orders(self) -> List[OpenOrder]: + query_params = { + "OMSId": 1, + "AccountId": await self.initialized_account_id(), + } + open_orders: List[Dict[str, Any]] = await self._api_request(method="GET", + path_url=CONSTANTS.GET_OPEN_ORDERS_PATH_URL, + params=query_params, + is_auth_required=True) + + trading_pair_id_map: Dict[str, int] = await self.order_book_tracker.data_source.get_instrument_ids() + id_trading_pair_map: Dict[int, str] = {instrument_id: trading_pair + for trading_pair, instrument_id in trading_pair_id_map.items()} + + return [OpenOrder(client_order_id=order["ClientOrderId"], + trading_pair=id_trading_pair_map[order["Instrument"]], + price=Decimal(str(order["Price"])), + amount=Decimal(str(order["Quantity"])), + executed_amount=Decimal(str(order["QuantityExecuted"])), + status=order["OrderState"], + order_type=OrderType.LIMIT if order["OrderType"] == "Limit" else OrderType.MARKET, + is_buy=True if order["Side"] == "Buy" else False, + time=order["ReceiveTime"], + exchange_order_id=order["OrderId"], + ) + for order in open_orders] + + async def cancel_all(self, timeout_sec: float) -> List[CancellationResult]: + """ + Cancels all in-flight orders and waits for cancellation results. + Used by bot's top level stop and exit commands (cancelling outstanding orders on exit) + :param timeout_sec: The timeout at which the operation will be canceled. + :returns List of CancellationResult which indicates whether each order is successfully cancelled. + """ + + # Note: NDAX's CancelOrder endpoint simply indicates if the cancel requests has been succesfully received. + cancellation_results = [] + tracked_orders = self.in_flight_orders + try: + for order in tracked_orders.values(): + self.cancel(trading_pair=order.trading_pair, + order_id=order.client_order_id) + + open_orders = await self.get_open_orders() + + for client_oid, tracked_order in tracked_orders.items(): + matched_order = [o for o in open_orders if o.client_order_id == client_oid] + if not matched_order: + cancellation_results.append(CancellationResult(client_oid, True)) + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent(self.current_timestamp, client_oid)) + else: + cancellation_results.append(CancellationResult(client_oid, False)) + + except Exception as ex: + self.logger().network( + f"Failed to cancel all orders ({ex})", + exc_info=True, + app_warning_msg="Failed to cancel all orders on NDAX. Check API key and network connection." + ) + return cancellation_results + + def _format_trading_rules(self, instrument_info: List[Dict[str, Any]]) -> Dict[str, TradingRule]: + """ + Converts JSON API response into a local dictionary of trading rules. + :param instrument_info: The JSON API response. + :returns: A dictionary of trading pair to its respective TradingRule. + """ + result = {} + for instrument in instrument_info: + try: + trading_pair = f"{instrument['Product1Symbol']}-{instrument['Product2Symbol']}" + + result[trading_pair] = TradingRule(trading_pair=trading_pair, + min_order_size=Decimal(str(instrument["MinimumQuantity"])), + min_price_increment=Decimal(str(instrument["PriceIncrement"])), + min_base_amount_increment=Decimal(str(instrument["QuantityIncrement"])), + ) + except Exception: + self.logger().error(f"Error parsing the trading pair rule: {instrument}. Skipping...", + exc_info=True) + return result + + async def _update_trading_rules(self): + params = { + "OMSId": 1 + } + instrument_info: List[Dict[str, Any]] = await self._api_request( + method="GET", + path_url=CONSTANTS.MARKETS_URL, + params=params + ) + self._trading_rules.clear() + self._trading_rules = self._format_trading_rules(instrument_info) + + async def _trading_rules_polling_loop(self): + """ + Periodically update trading rules. + """ + while True: + try: + await self._update_trading_rules() + await asyncio.sleep(self.UPDATE_TRADING_RULES_INTERVAL) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network(f"Unexpected error while fetching trading rules. Error: {str(e)}", + exc_info=True, + app_warning_msg="Could not fetch new trading rules from NDAX. " + "Check network connection.") + await asyncio.sleep(0.5) + + async def _update_balances(self): + """ + Calls REST API to update total and available balances + """ + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + params = { + "OMSId": 1, + "AccountId": await self.initialized_account_id() + } + account_positions: List[Dict[str, Any]] = await self._api_request( + method="GET", + path_url=CONSTANTS.ACCOUNT_POSITION_PATH_URL, + params=params, + is_auth_required=True + ) + for position in account_positions: + asset_name = position["ProductSymbol"] + self._account_balances[asset_name] = Decimal(str(position["Amount"])) + self._account_available_balances[asset_name] = self._account_balances[asset_name] - Decimal( + str(position["Hold"])) + remote_asset_names.add(asset_name) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def start_tracking_order(self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType): + """ + Starts tracking an order by simply adding it into _in_flight_orders dictionary. + """ + self._in_flight_orders[order_id] = NdaxInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=amount, + creation_timestamp=self.current_timestamp + ) + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an order by simply removing it from _in_flight_orders dictionary. + """ + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + async def _update_order_status(self): + """ + Calls REST API to get order status + """ + # Waiting on buy and sell functionality. + active_orders: List[NdaxInFlightOrder] = [ + o for o in self._in_flight_orders.values() + if not o.is_locally_working + ] + if len(active_orders) == 0: + return + + tasks = [] + for active_order in active_orders: + ex_order_id: Optional[str] = None + try: + ex_order_id = await active_order.get_exchange_order_id() + except asyncio.TimeoutError: + # We assume that tracked orders without an exchange order id is an order that failed to be created. + self._order_not_found_records[active_order.client_order_id] = self._order_not_found_records.get(active_order.client_order_id, 0) + 1 + self.logger().debug(f"Tracker order {active_order.client_order_id} does not have an exchange id." + f"Attempting fetch in next polling interval") + if self._order_not_found_records[active_order.client_order_id] >= self.ORDER_EXCEED_NOT_FOUND_COUNT: + self.logger().info(f"Order {active_order.client_order_id} does not seem to be active, will stop tracking order...") + self.stop_tracking_order(active_order.client_order_id) + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent(self.current_timestamp, active_order.client_order_id)) + continue + + query_params = { + "OMSId": 1, + "AccountId": await self.initialized_account_id(), + "OrderId": int(ex_order_id), + } + + tasks.append( + asyncio.create_task(self._api_request(method="GET", + path_url=CONSTANTS.GET_ORDER_STATUS_PATH_URL, + params=query_params, + is_auth_required=True, + ))) + self.logger().debug(f"Polling for order status updates of {len(tasks)} orders. ") + + raw_responses: List[Dict[str, Any]] = await safe_gather(*tasks, return_exceptions=True) + + # Initial parsing of responses. Removes Exceptions. + parsed_status_responses: List[Dict[str, Any]] = [] + for resp in raw_responses: + if not isinstance(resp, Exception): + parsed_status_responses.append(resp) + else: + self.logger().error(f"Error fetching order status. Response: {resp}") + + if len(parsed_status_responses) == 0: + return + + min_ts: int = min([int(order_status["ReceiveTime"]) + for order_status in parsed_status_responses]) + + trade_history_tasks = [] + trading_pair_ids: Dict[str, int] = await self.order_book_tracker.data_source.get_instrument_ids() + + for trading_pair in self._trading_pairs: + body_params = { + "OMSId": 1, + "AccountId": await self.initialized_account_id(), + "UserId": self._auth.uid, + "InstrumentId": trading_pair_ids[trading_pair], + "StartTimestamp": min_ts, + } + trade_history_tasks.append( + asyncio.create_task(self._api_request(method="POST", + path_url=CONSTANTS.GET_TRADES_HISTORY_PATH_URL, + data=body_params, + is_auth_required=True))) + + raw_responses: List[Dict[str, Any]] = await safe_gather(*trade_history_tasks, return_exceptions=True) + + # Initial parsing of responses. Joining all the responses + parsed_history_resps: List[Dict[str, Any]] = [] + for resp in raw_responses: + if not isinstance(resp, Exception): + parsed_history_resps.extend(resp) + else: + self.logger().error(f"Error fetching trades history. Response: {resp}") + + # Trade updates must be handled before any order status updates. + for trade in parsed_history_resps: + self._process_trade_event_message(trade) + + for order_status in parsed_status_responses: + self._process_order_event_message(order_status) + + def _reset_poll_notifier(self): + self._poll_notifier = asyncio.Event() + + async def _status_polling_loop(self): + """ + Periodically update user balances and order status via REST API. This serves as a fallback measure for web + socket API updates. + """ + while True: + try: + self._reset_poll_notifier() + await self._poll_notifier.wait() + start_ts = self.current_timestamp + await safe_gather( + self._update_balances(), + self._update_order_status(), + ) + self._last_poll_timestamp = start_ts + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Unexpected error while in status polling loop. Error: {str(e)}", exc_info=True) + self.logger().network("Unexpected error while fetching account updates.", + exc_info=True, + app_warning_msg="Could not fetch account updates from NDAX. " + "Check API key and network connection.") + await asyncio.sleep(0.5) + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock tick(1 second by default). + It checks if a status polling task is due for execution. + """ + now = time.time() + poll_interval = (self.SHORT_POLL_INTERVAL + if now - self._user_stream_tracker.last_recv_time > 60.0 + else self.LONG_POLL_INTERVAL) + last_tick = int(self._last_timestamp / poll_interval) + current_tick = int(timestamp / poll_interval) + if current_tick > last_tick: + if not self._poll_notifier.is_set(): + self._poll_notifier.set() + self._last_timestamp = timestamp + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + """ + To get trading fee, this function is simplified by using fee override configuration. Most parameters to this + function are ignore except order_type. Use OrderType.LIMIT_MAKER to specify you want trading fee for + maker order. + """ + is_maker = order_type is OrderType.LIMIT_MAKER + return AddedToCostTradeFee(percent=self.estimate_fee_pct(is_maker)) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unknown error. Retrying after 1 seconds.", + exc_info=True, + app_warning_msg="Could not fetch user events from NDAX. Check API key and network connection." + ) + await asyncio.sleep(1.0) + + async def _user_stream_event_listener(self): + """ + Listens to message in _user_stream_tracker.user_stream queue. + """ + async for event_message in self._iter_user_event_queue(): + try: + endpoint = NdaxWebSocketAdaptor.endpoint_from_message(event_message) + payload = NdaxWebSocketAdaptor.payload_from_message(event_message) + + if endpoint == CONSTANTS.ACCOUNT_POSITION_EVENT_ENDPOINT_NAME: + self._process_account_position_event(payload) + elif endpoint == CONSTANTS.ORDER_STATE_EVENT_ENDPOINT_NAME: + self._process_order_event_message(payload) + elif endpoint == CONSTANTS.ORDER_TRADE_EVENT_ENDPOINT_NAME: + self._process_trade_event_message(payload) + else: + self.logger().debug(f"Unknown event received from the connector ({event_message})") + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().error(f"Unexpected error in user stream listener loop ({ex})", exc_info=True) + await asyncio.sleep(5.0) + + def _process_account_position_event(self, account_position_event: Dict[str, Any]): + token = account_position_event["ProductSymbol"] + amount = Decimal(str(account_position_event["Amount"])) + on_hold = Decimal(str(account_position_event["Hold"])) + self._account_balances[token] = amount + self._account_available_balances[token] = (amount - on_hold) + + def _process_order_event_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and triggers cancellation or failure event if needed. + :param order_msg: The order event message payload + """ + client_order_id = str(order_msg["ClientOrderId"]) + if client_order_id in self.in_flight_orders: + tracked_order = self.in_flight_orders[client_order_id] + was_locally_working = tracked_order.is_locally_working + + # Update order execution status + tracked_order.last_state = order_msg["OrderState"] + + if was_locally_working and tracked_order.is_working: + self.trigger_order_created_event(tracked_order) + elif tracked_order.is_cancelled: + self.logger().info(f"Successfully canceled order {client_order_id}") + self.trigger_event(MarketEvent.OrderCancelled, + OrderCancelledEvent( + self.current_timestamp, + client_order_id)) + self.stop_tracking_order(client_order_id) + elif tracked_order.is_failure: + self.logger().info(f"The market order {client_order_id} has failed according to order status event. " + f"Reason: {order_msg['ChangeReason']}") + self.trigger_event(MarketEvent.OrderFailure, + MarketOrderFailureEvent( + self.current_timestamp, + client_order_id, + tracked_order.order_type + )) + self.stop_tracking_order(client_order_id) + + def _process_trade_event_message(self, order_msg: Dict[str, Any]): + """ + Updates in-flight order and trigger order filled event for trade message received. Triggers order completed + event if the total executed amount equals to the specified order amount. + :param order_msg: The order event message payload + """ + + client_order_id = str(order_msg["ClientOrderId"]) + if client_order_id in self.in_flight_orders: + tracked_order = self.in_flight_orders[client_order_id] + updated = tracked_order.update_with_trade_update(order_msg) + + if updated: + trade_amount = Decimal(str(order_msg["Quantity"])) + trade_price = Decimal(str(order_msg["Price"])) + trade_fee = self.get_fee(base_currency=tracked_order.base_asset, + quote_currency=tracked_order.quote_asset, + order_type=tracked_order.order_type, + order_side=tracked_order.trade_type, + amount=trade_amount, + price=trade_price) + amount_for_fee = (trade_amount if tracked_order.trade_type is TradeType.BUY + else trade_amount * trade_price) + tracked_order.fee_paid += amount_for_fee * trade_fee.percent + + self.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.trading_pair, + tracked_order.trade_type, + tracked_order.order_type, + trade_price, + trade_amount, + trade_fee, + exchange_trade_id=str(order_msg["TradeId"]) + ) + ) + if (math.isclose(tracked_order.executed_amount_base, tracked_order.amount) or + tracked_order.executed_amount_base >= tracked_order.amount): + tracked_order.mark_as_filled() + self.logger().info(f"The {tracked_order.trade_type.name} order " + f"{tracked_order.client_order_id} has completed " + f"according to order status API") + event_tag = (MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY + else MarketEvent.SellOrderCompleted) + event_class = (BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY + else SellOrderCompletedEvent) + self.trigger_event(event_tag, + event_class(self.current_timestamp, + tracked_order.client_order_id, + tracked_order.base_asset, + tracked_order.quote_asset, + tracked_order.executed_amount_base, + tracked_order.executed_amount_quote, + tracked_order.order_type, + tracked_order.exchange_order_id)) + self.stop_tracking_order(tracked_order.client_order_id) + + async def all_trading_pairs(self) -> List[str]: + # This method should be removed and instead we should implement _initialize_trading_pair_symbol_map + return await NdaxAPIOrderBookDataSource.fetch_trading_pairs( + domain=self._domain, + throttler=self._throttler, + ) + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + # This method should be removed and instead we should implement _get_last_traded_price + return await NdaxAPIOrderBookDataSource.get_last_traded_prices( + trading_pairs=trading_pairs, + domain=self._domain, + throttler=self._throttler, + shared_client=self._shared_client) diff --git a/hummingbot/connector/exchange/ndax/ndax_in_flight_order.py b/hummingbot/connector/exchange/ndax/ndax_in_flight_order.py new file mode 100644 index 0000000..15ae975 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_in_flight_order.py @@ -0,0 +1,77 @@ +from decimal import Decimal +from typing import ( + Any, + Dict, + Optional, +) + +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import OrderType, TradeType + +WORKING_LOCAL_STATUS = "WorkingLocal" + + +class NdaxInFlightOrderNotCreated(Exception): + pass + + +class NdaxInFlightOrder(InFlightOrderBase): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + initial_state: str = WORKING_LOCAL_STATUS): + super().__init__( + client_order_id, + exchange_order_id, + trading_pair, + order_type, + trade_type, + price, + amount, + creation_timestamp, + initial_state, + ) + self.fee_asset = self.base_asset if self.trade_type is TradeType.BUY else self.quote_asset + self.trade_id_set = set() + + @property + def is_locally_working(self) -> bool: + return self.last_state in {WORKING_LOCAL_STATUS} + + @property + def is_working(self) -> bool: + return self.last_state in {"Working"} + + @property + def is_done(self) -> bool: + return self.last_state in {"FullyExecuted", "Canceled", "Rejected", "Expired"} + + @property + def is_failure(self) -> bool: + return self.last_state in {"Rejected"} + + @property + def is_cancelled(self) -> bool: + return self.last_state in {"Canceled", "Expired"} + + def mark_as_filled(self): + self.last_state = "FullyExecuted" + + def update_with_trade_update(self, trade_update: Dict[str, Any]) -> bool: + """ + Updates the in flight order with trade update (from GET /trade_history end point) + return: True if the order gets updated otherwise False + """ + trade_id = trade_update["TradeId"] + if str(trade_update["OrderId"]) != self.exchange_order_id or trade_id in self.trade_id_set: + return False + self.trade_id_set.add(trade_id) + self.executed_amount_base += Decimal(str(trade_update["Quantity"])) + self.executed_amount_quote += Decimal(str(trade_update["Value"])) + return True diff --git a/hummingbot/connector/exchange/ndax/ndax_order_book.py b/hummingbot/connector/exchange/ndax/ndax_order_book.py new file mode 100644 index 0000000..e72a2ff --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_order_book.py @@ -0,0 +1,104 @@ +import logging + +from typing import ( + Optional, + Dict, + List, Any) +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookMessage +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) +from hummingbot.logger import HummingbotLogger + +_logger = None + + +class NdaxOrderBook(OrderBook): + @classmethod + def logger(cls) -> HummingbotLogger: + global _logger + if _logger is None: + _logger = logging.getLogger(__name__) + return _logger + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None): + """ + Convert json snapshot data into standard OrderBookMessage format + :param msg: json snapshot data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: NdaxOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + return NdaxOrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=msg, + timestamp=timestamp + ) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Convert json diff data into standard OrderBookMessage format + :param msg: json diff data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: NdaxOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + return NdaxOrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=msg, + timestamp=timestamp + ) + + @classmethod + def trade_message_from_exchange(cls, + msg: Dict[str, Any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None): + """ + Convert a trade data into standard OrderBookMessage format + :param msg: json trade data from live web socket stream + :param timestamp: timestamp attached to incoming data + :return: NdaxOrderBookMessage + """ + + if metadata: + msg.update(metadata) + + # Data fields are obtained from OrderTradeEvents + msg.update({ + "exchange_order_id": msg.get("TradeId"), + "trade_type": msg.get("Side"), + "price": msg.get("Price"), + "amount": msg.get("Quantity"), + }) + + return NdaxOrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=msg, + timestamp=timestamp + ) + + @classmethod + def from_snapshot(cls, snapshot: OrderBookMessage): + raise NotImplementedError(CONSTANTS.EXCHANGE_NAME + " order book needs to retain individual order data.") + + @classmethod + def restore_from_snapshot_and_diffs(cls, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): + raise NotImplementedError(CONSTANTS.EXCHANGE_NAME + " order book needs to retain individual order data.") diff --git a/hummingbot/connector/exchange/ndax/ndax_order_book_message.py b/hummingbot/connector/exchange/ndax/ndax_order_book_message.py new file mode 100644 index 0000000..84d8e68 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_order_book_message.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +from collections import namedtuple +from typing import ( + Dict, + List, + Optional, +) + +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType, +) + +NdaxOrderBookEntry = namedtuple("NdaxOrderBookEntry", "mdUpdateId accountId actionDateTime actionType lastTradePrice orderId price productPairCode quantity side") +NdaxTradeEntry = namedtuple("NdaxTradeEntry", "tradeId productPairCode quantity price order1 order2 tradeTime direction takerSide blockTrade orderClientId") + + +class NdaxOrderBookMessage(OrderBookMessage): + + _DELETE_ACTION_TYPE = 2 + _BUY_SIDE = 0 + _SELL_SIDE = 1 + + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + if timestamp is None: + if message_type is OrderBookMessageType.SNAPSHOT: + raise ValueError("timestamp must not be None when initializing snapshot messages.") + timestamp = content["timestamp"] + + return super(NdaxOrderBookMessage, cls).__new__( + cls, message_type, content, timestamp=timestamp, *args, **kwargs + ) + + @property + def update_id(self) -> int: + if self.type == OrderBookMessageType.SNAPSHOT: + # Assumes Snapshot Update ID to be 0 + # Since uid of orderbook snapshots from REST API is not in sync with uid from websocket + return 0 + elif self.type in [OrderBookMessageType.DIFF, OrderBookMessageType.TRADE]: + last_entry: NdaxOrderBookEntry = self.content["data"][-1] + return last_entry.mdUpdateId + + @property + def trade_id(self) -> int: + entry: NdaxTradeEntry = self.content["data"][0] + return entry.tradeId + + @property + def trading_pair(self) -> str: + return self.content["trading_pair"] + + @property + def last_traded_price(self) -> float: + entries: List[NdaxOrderBookEntry] = self.content["data"] + return float(entries[-1].lastTradePrice) + + @property + def asks(self) -> List[OrderBookRow]: + entries: List[NdaxOrderBookEntry] = self.content["data"] + asks = [self._order_book_row_for_entry(entry) for entry in entries if entry.side == self._SELL_SIDE] + asks.sort(key=lambda row: (row.price, row.update_id)) + return asks + + @property + def bids(self) -> List[OrderBookRow]: + entries: List[NdaxOrderBookEntry] = self.content["data"] + bids = [self._order_book_row_for_entry(entry) for entry in entries if entry.side == self._BUY_SIDE] + bids.sort(key=lambda row: (row.price, row.update_id)) + return bids + + def _order_book_row_for_entry(self, entry: NdaxOrderBookEntry) -> OrderBookRow: + price = float(entry.price) + amount = float(entry.quantity) if entry.actionType != self._DELETE_ACTION_TYPE else 0.0 + update_id = entry.mdUpdateId + return OrderBookRow(price, amount, update_id) + + def __eq__(self, other) -> bool: + return type(self) == type(other) and self.type == other.type and self.timestamp == other.timestamp + + def __lt__(self, other) -> bool: + # If timestamp is the same, the ordering is snapshot < diff < trade + return (self.timestamp < other.timestamp or (self.timestamp == other.timestamp and self.type.value < other.type.value)) + + def __hash__(self) -> int: + return hash((self.type, self.timestamp)) diff --git a/hummingbot/connector/exchange/ndax/ndax_order_book_tracker.py b/hummingbot/connector/exchange/ndax/ndax_order_book_tracker.py new file mode 100644 index 0000000..9bfc0da --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_order_book_tracker.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +import asyncio +import aiohttp +import bisect +import logging +import time + +from collections import defaultdict, deque +from typing import Optional, Dict, List, Deque + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookMessage +from hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source import NdaxAPIOrderBookDataSource +from hummingbot.connector.exchange.ndax.ndax_order_book import NdaxOrderBook +from hummingbot.logger import HummingbotLogger + + +class NdaxOrderBookTracker(OrderBookTracker): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__( + self, + throttler: Optional[AsyncThrottler] = None, + shared_client: Optional[aiohttp.ClientSession] = None, + trading_pairs: Optional[List[str]] = None, + domain: Optional[str] = None, + ): + super().__init__(NdaxAPIOrderBookDataSource(throttler=throttler, shared_client=shared_client, trading_pairs=trading_pairs, domain=domain), trading_pairs, domain) + + self._domain = domain + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_trade_stream: asyncio.Queue = asyncio.Queue() + self._process_msg_deque_task: Optional[asyncio.Task] = None + self._past_diffs_windows: Dict[str, Deque] = {} + self._order_books: Dict[str, NdaxOrderBook] = {} + self._saved_message_queues: Dict[str, Deque[NdaxOrderBookMessage]] = \ + defaultdict(lambda: deque(maxlen=1000)) + self._order_book_stream_listener_task: Optional[asyncio.Task] = None + self._order_book_trade_listener_task: Optional[asyncio.Task] = None + + self._order_books_initialized_counter: int = 0 + + @property + def exchange_name(self) -> str: + """ + Name of the current exchange + """ + return CONSTANTS.EXCHANGE_NAME + + async def _track_single_book(self, trading_pair: str): + """ + Update an order book with changes from the latest batch of received messages + """ + past_diffs_window: Deque[NdaxOrderBookMessage] = deque() + self._past_diffs_windows[trading_pair] = past_diffs_window + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: NdaxOrderBook = self._order_books[trading_pair] + + last_message_timestamp: float = time.time() + diff_messages_accepted: int = 0 + + while True: + try: + message: NdaxOrderBookMessage = None + saved_messages: Deque[NdaxOrderBookMessage] = self._saved_message_queues[trading_pair] + # Process saved messages first if there are any + if len(saved_messages) > 0: + message = saved_messages.popleft() + else: + message = await message_queue.get() + + if message.type is OrderBookMessageType.DIFF: + bids, asks = message.bids, message.asks + order_book.apply_diffs(bids, asks, message.update_id) + past_diffs_window.append(message) + while len(past_diffs_window) > self.PAST_DIFF_WINDOW_SIZE: + past_diffs_window.popleft() + diff_messages_accepted += 1 + + # Output some statistics periodically. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Processed {diff_messages_accepted} order book diffs for {trading_pair}.") + diff_messages_accepted = 0 + last_message_timestamp = now + elif message.type is OrderBookMessageType.SNAPSHOT: + + s_bids, s_asks = message.bids, message.asks + order_book.apply_snapshot(s_bids, s_asks, message.update_id) + + if order_book.last_diff_uid == 0: + self._order_books_initialized_counter += 1 + self.logger().info(f"Initialized order book for {trading_pair}. " + f"{self._order_books_initialized_counter}/{len(self._trading_pairs)} completed.") + + past_diffs: List[NdaxOrderBookMessage] = list(past_diffs_window) + # only replay diffs later than snapshot, first update active order with snapshot then replay diffs + replay_position = bisect.bisect_right(past_diffs, message) + replay_diffs = past_diffs[replay_position:] + for diff_message in replay_diffs: + d_bids, d_asks = diff_message.bids, diff_message.asks + order_book.apply_diffs(d_bids, d_asks, diff_message.update_id) + + self.logger().debug(f"Processed order book snapshot for {trading_pair}.") + + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Unexpected error processing order book messages for {trading_pair}.", + exc_info=True, + app_warning_msg="Unexpected error processing order book messages. Retrying after 5 seconds." + ) + await asyncio.sleep(5.0) diff --git a/hummingbot/connector/exchange/ndax/ndax_user_stream_tracker.py b/hummingbot/connector/exchange/ndax/ndax_user_stream_tracker.py new file mode 100644 index 0000000..ad3c695 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_user_stream_tracker.py @@ -0,0 +1,65 @@ +import logging +from typing import Optional + +import aiohttp + +from hummingbot.connector.exchange.ndax.ndax_api_user_stream_data_source import NdaxAPIUserStreamDataSource +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger import HummingbotLogger + + +class NdaxUserStreamTracker(UserStreamTracker): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__( + self, + throttler: AsyncThrottler, + shared_client: Optional[aiohttp.ClientSession] = None, + auth_assistant: Optional[NdaxAuth] = None, + domain: Optional[str] = None + ): + self._auth_assistant: NdaxAuth = auth_assistant + self._shared_client = shared_client + self._domain = domain + self._throttler = throttler + super().__init__(data_source=NdaxAPIUserStreamDataSource( + throttler=self._throttler, + shared_client=self._shared_client, + auth_assistant=self._auth_assistant, + domain=self._domain + )) + + @property + def data_source(self) -> UserStreamTrackerDataSource: + """ + Initializes a user stream data source (user specific order diffs from live socket stream) + :return: UserStreamTrackerDataSource + """ + if not self._data_source: + self._data_source = NdaxAPIUserStreamDataSource( + throttler=self._throttler, shared_client=self._shared_client, auth_assistant=self._auth_assistant, + domain=self._domain + ) + return self._data_source + + async def start(self): + """ + Start all listeners and tasks + """ + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) diff --git a/hummingbot/connector/exchange/ndax/ndax_utils.py b/hummingbot/connector/exchange/ndax/ndax_utils.py new file mode 100644 index 0000000..8ea0762 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_utils.py @@ -0,0 +1,136 @@ +from typing import Optional + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce + +CENTRALIZED = True +EXAMPLE_PAIR = "BTC-CAD" +HUMMINGBOT_ID_PREFIX = 777 + +# NDAX fees: https://ndax.io/fees +# Fees have to be expressed as percent value +DEFAULT_FEES = [0.2, 0.2] + + +# USE_ETHEREUM_WALLET not required because default value is false +# FEE_TYPE not required because default value is Percentage +# FEE_TOKEN not required because the fee is not flat + + +def convert_to_exchange_trading_pair(hb_trading_pair: str) -> str: + return hb_trading_pair.replace("-", "") + + +def get_new_client_order_id(is_buy: bool, trading_pair: str) -> str: + ts_micro_sec: int = get_tracking_nonce() + return f"{HUMMINGBOT_ID_PREFIX}{ts_micro_sec}" + + +def rest_api_url(connector_variant_label: Optional[str]) -> str: + variant = connector_variant_label if connector_variant_label else "ndax_main" + return CONSTANTS.REST_URLS.get(variant) + + +def wss_url(connector_variant_label: Optional[str]) -> str: + variant = connector_variant_label if connector_variant_label else "ndax_main" + return CONSTANTS.WSS_URLS.get(variant) + + +class NdaxConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="ndax", client_data=None) + ndax_uid: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX user ID (uid)", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_account_name: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the name of the account you want to use", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "ndax" + + +KEYS = NdaxConfigMap.construct() + +OTHER_DOMAINS = ["ndax_testnet"] +OTHER_DOMAINS_PARAMETER = {"ndax_testnet": "ndax_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"ndax_testnet": "BTC-CAD"} +OTHER_DOMAINS_DEFAULT_FEES = {"ndax_testnet": [0.2, 0.2]} + + +class NdaxTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="ndax_testnet", client_data=None) + ndax_testnet_uid: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX Testnet user ID (uid)", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_testnet_account_name: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter the name of the account you want to use", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_testnet_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX Testnet API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + ndax_testnet_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your NDAX Testnet secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "ndax_testnet" + + +OTHER_DOMAINS_KEYS = {"ndax_testnet": NdaxTestnetConfigMap.construct()} diff --git a/hummingbot/connector/exchange/ndax/ndax_websocket_adaptor.py b/hummingbot/connector/exchange/ndax/ndax_websocket_adaptor.py new file mode 100644 index 0000000..ff8f4c0 --- /dev/null +++ b/hummingbot/connector/exchange/ndax/ndax_websocket_adaptor.py @@ -0,0 +1,109 @@ +import aiohttp +import asyncio +from enum import Enum +from typing import AsyncIterable, Dict, Any, Optional + +import ujson + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class NdaxMessageType(Enum): + REQUEST_TYPE = 0 + REPLY_TYPE = 1 + SUBSCRIBE_TO_EVENT_TYPE = 2 + EVENT = 3 + UNSUBSCRIBE_FROM_EVENT = 4 + ERROR = 5 + + +class NdaxWebSocketAdaptor: + + _message_type_field_name = "m" + _message_number_field_name = "i" + _endpoint_field_name = "n" + _payload_field_name = "o" + + """ + Auxiliary class that works as a wrapper of a low level web socket. It contains the logic to create messages + with the format expected by NDAX + :param websocket: The low level socket to be used to send and receive messages + :param previous_messages_number: number of messages already sent to NDAX. This parameter is useful when the + connection is reestablished after a communication error, and allows to keep a unique identifier for each message. + The default previous_messages_number is 0 + """ + MESSAGE_TIMEOUT = 20.0 + PING_TIMEOUT = 5.0 + + def __init__( + self, + throttler: AsyncThrottler, + websocket: aiohttp.ClientWebSocketResponse, + previous_messages_number: int = 0, + ): + self._websocket = websocket + self._messages_counter = previous_messages_number + self._lock = asyncio.Lock() + self._throttler = throttler + + @classmethod + def endpoint_from_raw_message(cls, raw_message: str) -> str: + message = ujson.loads(raw_message) + return cls.endpoint_from_message(message=message) + + @classmethod + def endpoint_from_message(cls, message: Dict[str, Any]) -> str: + return message.get(cls._endpoint_field_name) + + @classmethod + def payload_from_raw_message(cls, raw_message: str) -> Dict[str, Any]: + message = ujson.loads(raw_message) + return cls.payload_from_message(message=message) + + @classmethod + def payload_from_message(cls, message: Dict[str, Any]) -> Dict[str, Any]: + payload = ujson.loads(message.get(cls._payload_field_name)) + return payload + + async def next_message_number(self): + async with self._lock: + self._messages_counter += 1 + next_number = self._messages_counter + return next_number + + async def send_request(self, endpoint_name: str, payload: Dict[str, Any], limit_id: Optional[str] = None): + message_number = await self.next_message_number() + message = {self._message_type_field_name: NdaxMessageType.REQUEST_TYPE.value, + self._message_number_field_name: message_number, + self._endpoint_field_name: endpoint_name, + self._payload_field_name: ujson.dumps(payload)} + + limit_id = limit_id or endpoint_name + async with self._throttler.execute_task(limit_id): + await self._websocket.send_json(message) + + async def receive(self): + return await self._websocket.receive() + + async def iter_messages(self) -> AsyncIterable[str]: + try: + while True: + try: + raw_msg = await asyncio.wait_for(self.receive(), timeout=self.MESSAGE_TIMEOUT) + if raw_msg.type == aiohttp.WSMsgType.CLOSED: + raise ConnectionError + yield raw_msg.data + except asyncio.TimeoutError: + await asyncio.wait_for( + self.send_request(CONSTANTS.WS_PING_REQUEST, payload={}, limit_id=CONSTANTS.WS_PING_ID), + timeout=self.PING_TIMEOUT + ) + except ConnectionError: + return + finally: + await self.close() + + async def close(self): + if self._websocket is not None: + await self._websocket.close() diff --git a/hummingbot/connector/exchange/okx/__init__.py b/hummingbot/connector/exchange/okx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/okx/okx_api_order_book_data_source.py b/hummingbot/connector/exchange/okx/okx_api_order_book_data_source.py new file mode 100644 index 0000000..f8e1b9b --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_api_order_book_data_source.py @@ -0,0 +1,202 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.okx import okx_constants as CONSTANTS, okx_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest, WSPlainTextRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.okx.okx_exchange import OkxExchange + + +class OkxAPIOrderBookDataSource(OrderBookTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, + trading_pairs: List[str], + connector: 'OkxExchange', + api_factory: WebAssistantsFactory): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + + async def get_last_traded_prices(self, + trading_pairs: List[str], + domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_data: Dict[str, Any] = snapshot_response['data'][0] + snapshot_timestamp: float = int(snapshot_data["ts"]) * 1e-3 + update_id: int = int(snapshot_timestamp) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in snapshot_data["bids"]], + "asks": [(ask[0], ask[1]) for ask in snapshot_data["asks"]], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + snapshot_timestamp) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + params = { + "instId": await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "sz": "400" + } + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.OKX_ORDER_BOOK_PATH, + ) + + return data + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=raw_message["arg"]["instId"]) + snapshot_data = raw_message["data"][0] + snapshot_timestamp: float = int(snapshot_data["ts"]) * 1e-3 + update_id: int = int(snapshot_timestamp) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in snapshot_data["bids"]], + "asks": [(ask[0], ask[1]) for ask in snapshot_data["asks"]], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + order_book_message_content, + snapshot_timestamp) + + message_queue.put_nowait(snapshot_msg) + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trade_updates = raw_message["data"] + + for trade_data in trade_updates: + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=trade_data["instId"]) + message_content = { + "trade_id": trade_data["tradeId"], + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) if trade_data["side"] == "buy" else float( + TradeType.SELL.value), + "amount": trade_data["sz"], + "price": trade_data["px"] + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=(int(trade_data["ts"]) * 1e-3)) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + diff_updates: Dict[str, Any] = raw_message["data"] + + for diff_data in diff_updates: + timestamp: float = int(diff_data["ts"]) * 1e-3 + update_id: int = int(timestamp) + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=raw_message["arg"]["instId"]) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in diff_data["bids"]], + "asks": [(ask[0], ask[1]) for ask in diff_data["asks"]], + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + order_book_message_content, + timestamp) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + try: + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + payload = { + "op": "subscribe", + "args": [ + { + "channel": "trades", + "instId": symbol, + } + ] + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) + + payload = { + "op": "subscribe", + "args": [ + { + "channel": "books", + "instId": symbol, + }] + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await ws.send(subscribe_trade_request) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if "data" in event_message: + event_channel = event_message["arg"]["channel"] + if event_channel == CONSTANTS.OKX_WS_PUBLIC_TRADES_CHANNEL: + channel = self._trade_messages_queue_key + elif event_channel == CONSTANTS.OKX_WS_PUBLIC_BOOKS_CHANNEL and event_message["action"] == "update": + channel = self._diff_messages_queue_key + elif event_channel == CONSTANTS.OKX_WS_PUBLIC_BOOKS_CHANNEL and event_message["action"] == "snapshot": + channel = self._snapshot_messages_queue_key + + return channel + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + await super()._process_websocket_messages(websocket_assistant=websocket_assistant) + except asyncio.TimeoutError: + ping_request = WSPlainTextRequest(payload="ping") + await websocket_assistant.send(request=ping_request) + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_CONNECTION_LIMIT_ID): + await ws.connect( + ws_url=CONSTANTS.OKX_WS_URI_PUBLIC, + message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + return ws diff --git a/hummingbot/connector/exchange/okx/okx_api_user_stream_data_source.py b/hummingbot/connector/exchange/okx/okx_api_user_stream_data_source.py new file mode 100644 index 0000000..3f5e6be --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_api_user_stream_data_source.py @@ -0,0 +1,106 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, Optional + +from hummingbot.connector.exchange.okx import okx_constants as CONSTANTS +from hummingbot.connector.exchange.okx.okx_auth import OkxAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSPlainTextRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.okx.okx_exchange import OkxExchange + + +class OkxAPIUserStreamDataSource(UserStreamTrackerDataSource): + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: OkxAuth, + connector: 'OkxExchange', + api_factory: WebAssistantsFactory): + super().__init__() + self._auth: OkxAuth = auth + self._connector = connector + self._api_factory = api_factory + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + + ws: WSAssistant = await self._get_ws_assistant() + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_CONNECTION_LIMIT_ID): + await ws.connect( + ws_url=CONSTANTS.OKX_WS_URI_PRIVATE, + message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE) + + payload = { + "op": "login", + "args": [self._auth.websocket_login_parameters()] + } + + login_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_LOGIN_LIMIT_ID): + await ws.send(login_request) + + response: WSResponse = await ws.receive() + message = response.data + if message.get("event") != "login": + self.logger().error("Error authenticating the private websocket connection") + raise IOError("Private websocket connection authentication failed") + + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + try: + payload = { + "op": "subscribe", + "args": [{"channel": "account"}], + } + subscribe_account_request: WSJSONRequest = WSJSONRequest(payload=payload) + + payload = { + "op": "subscribe", + "args": [ + { + "channel": "orders", + "instType": "SPOT", + } + ] + } + subscribe_orders_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await websocket_assistant.send(subscribe_account_request) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_SUBSCRIPTION_LIMIT_ID): + await websocket_assistant.send(subscribe_orders_request) + self.logger().info("Subscribed to private account and orders channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + await super()._process_websocket_messages( + websocket_assistant=websocket_assistant, + queue=queue) + except asyncio.TimeoutError: + ping_request = WSPlainTextRequest(payload="ping") + await websocket_assistant.send(request=ping_request) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0 and "data" in event_message: + queue.put_nowait(event_message) + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant diff --git a/hummingbot/connector/exchange/okx/okx_auth.py b/hummingbot/connector/exchange/okx/okx_auth.py new file mode 100644 index 0000000..4109c9e --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_auth.py @@ -0,0 +1,88 @@ +import base64 +import hashlib +import hmac +from collections import OrderedDict +from datetime import datetime +from typing import Any, Dict, Optional +from urllib.parse import urlencode + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class OkxAuth(AuthBase): + + def __init__(self, api_key: str, secret_key: str, passphrase: str, time_provider: TimeSynchronizer): + self.api_key: str = api_key + self.secret_key: str = secret_key + self.passphrase: str = passphrase + self.time_provider: TimeSynchronizer = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + + :param request: the request to be configured for authenticated interaction + + :return: The RESTRequest with auth information included + """ + + headers = {} + if request.headers is not None: + headers.update(request.headers) + headers.update(self.authentication_headers(request=request)) + request.headers = headers + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. OKX does not use this + functionality + """ + return request # pass-through + + @staticmethod + def keysort(dictionary: Dict[str, str]) -> Dict[str, str]: + return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) + + def _generate_signature(self, timestamp: str, method: str, path_url: str, body: Optional[str] = None) -> str: + unsigned_signature = timestamp + method + path_url + if body is not None: + unsigned_signature += body + + signature = base64.b64encode( + hmac.new( + self.secret_key.encode("utf-8"), + unsigned_signature.encode("utf-8"), + hashlib.sha256).digest()).decode() + return signature + + def authentication_headers(self, request: RESTRequest) -> Dict[str, Any]: + timestamp = datetime.utcfromtimestamp(self.time_provider.time()).isoformat(timespec="milliseconds") + "Z" + + path_url = f"/api{request.url.split('/api')[-1]}" + if request.params: + query_string_components = urlencode(request.params) + path_url = f"{path_url}?{query_string_components}" + + header = { + "OK-ACCESS-KEY": self.api_key, + "OK-ACCESS-SIGN": self._generate_signature(timestamp, request.method.value.upper(), path_url, request.data), + "OK-ACCESS-TIMESTAMP": timestamp, + "OK-ACCESS-PASSPHRASE": self.passphrase, + } + + return header + + def websocket_login_parameters(self) -> Dict[str, Any]: + timestamp = str(int(self.time_provider.time())) + + return { + "apiKey": self.api_key, + "passphrase": self.passphrase, + "timestamp": timestamp, + "sign": self._generate_signature(timestamp, "GET", "/users/self/verify") + } diff --git a/hummingbot/connector/exchange/okx/okx_constants.py b/hummingbot/connector/exchange/okx/okx_constants.py new file mode 100644 index 0000000..d9307f4 --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_constants.py @@ -0,0 +1,74 @@ +import sys + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +CLIENT_ID_PREFIX = "93027a12dac34fBC" +MAX_ID_LEN = 32 +SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE = 30 * 0.8 + +DEFAULT_DOMAIN = "" + +# URLs + +OKX_BASE_URL = "https://www.okx.com/" + +# Doesn't include base URL as the tail is required to generate the signature + +OKX_SERVER_TIME_PATH = '/api/v5/public/time' +OKX_INSTRUMENTS_PATH = '/api/v5/public/instruments' +OKX_TICKER_PATH = '/api/v5/market/ticker' +OKX_ORDER_BOOK_PATH = '/api/v5/market/books' + +# Auth required +OKX_PLACE_ORDER_PATH = "/api/v5/trade/order" +OKX_ORDER_DETAILS_PATH = '/api/v5/trade/order' +OKX_ORDER_CANCEL_PATH = '/api/v5/trade/cancel-order' +OKX_BATCH_ORDER_CANCEL_PATH = '/api/v5/trade/cancel-batch-orders' +OKX_BALANCE_PATH = '/api/v5/account/balance' +OKX_TRADE_FILLS_PATH = "/api/v5/trade/fills" + +# WS +OKX_WS_URI_PUBLIC = "wss://ws.okx.com:8443/ws/v5/public" +OKX_WS_URI_PRIVATE = "wss://ws.okx.com:8443/ws/v5/private" + +OKX_WS_ACCOUNT_CHANNEL = "account" +OKX_WS_ORDERS_CHANNEL = "orders" +OKX_WS_PUBLIC_TRADES_CHANNEL = "trades" +OKX_WS_PUBLIC_BOOKS_CHANNEL = "books" + +OKX_WS_CHANNELS = { + OKX_WS_ACCOUNT_CHANNEL, + OKX_WS_ORDERS_CHANNEL +} + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_REQUEST_LIMIT_ID = "WSRequest" +WS_SUBSCRIPTION_LIMIT_ID = "WSSubscription" +WS_LOGIN_LIMIT_ID = "WSLogin" + +ORDER_STATE = { + "live": OrderState.OPEN, + "filled": OrderState.FILLED, + "partially_filled": OrderState.PARTIALLY_FILLED, + "canceled": OrderState.CANCELED, +} + +NO_LIMIT = sys.maxsize + +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=1, time_interval=1), + RateLimit(WS_REQUEST_LIMIT_ID, limit=100, time_interval=10), + RateLimit(WS_SUBSCRIPTION_LIMIT_ID, limit=240, time_interval=60 * 60), + RateLimit(WS_LOGIN_LIMIT_ID, limit=1, time_interval=15), + RateLimit(limit_id=OKX_SERVER_TIME_PATH, limit=10, time_interval=2), + RateLimit(limit_id=OKX_INSTRUMENTS_PATH, limit=20, time_interval=2), + RateLimit(limit_id=OKX_TICKER_PATH, limit=20, time_interval=2), + RateLimit(limit_id=OKX_ORDER_BOOK_PATH, limit=20, time_interval=2), + RateLimit(limit_id=OKX_PLACE_ORDER_PATH, limit=60, time_interval=2), + RateLimit(limit_id=OKX_ORDER_DETAILS_PATH, limit=60, time_interval=2), + RateLimit(limit_id=OKX_ORDER_CANCEL_PATH, limit=60, time_interval=2), + RateLimit(limit_id=OKX_BATCH_ORDER_CANCEL_PATH, limit=300, time_interval=2), + RateLimit(limit_id=OKX_BALANCE_PATH, limit=10, time_interval=2), + RateLimit(limit_id=OKX_TRADE_FILLS_PATH, limit=60, time_interval=2), +] diff --git a/hummingbot/connector/exchange/okx/okx_exchange.py b/hummingbot/connector/exchange/okx/okx_exchange.py new file mode 100644 index 0000000..afeb954 --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_exchange.py @@ -0,0 +1,427 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.exchange.okx import okx_constants as CONSTANTS, okx_utils, okx_web_utils as web_utils +from hummingbot.connector.exchange.okx.okx_api_order_book_data_source import OkxAPIOrderBookDataSource +from hummingbot.connector.exchange.okx.okx_api_user_stream_data_source import OkxAPIUserStreamDataSource +from hummingbot.connector.exchange.okx.okx_auth import OkxAuth +from hummingbot.connector.exchange_base import s_decimal_NaN +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class OkxExchange(ExchangePyBase): + + web_utils = web_utils + + def __init__(self, + client_config_map: "ClientConfigAdapter", + okx_api_key: str, + okx_secret_key: str, + okx_passphrase: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True): + + self.okx_api_key = okx_api_key + self.okx_secret_key = okx_secret_key + self.okx_passphrase = okx_passphrase + self._trading_required = trading_required + self._trading_pairs = trading_pairs + super().__init__(client_config_map) + + @property + def authenticator(self): + return OkxAuth( + api_key=self.okx_api_key, + secret_key=self.okx_secret_key, + passphrase=self.okx_passphrase, + time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "okx" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return "" + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.CLIENT_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.OKX_INSTRUMENTS_PATH + + @property + def trading_pairs_request_path(self): + return CONSTANTS.OKX_INSTRUMENTS_PATH + + @property + def check_network_request_path(self): + return CONSTANTS.OKX_SERVER_TIME_PATH + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + is_time_synchronizer_related = '"code":"50113"' in error_description + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return OkxAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return OkxAPIUserStreamDataSource( + auth=self._auth, + connector=self, + api_factory=self._web_assistants_factory) + + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> TradeFeeBase: + + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _initialize_trading_pair_symbol_map(self): + # This has to be reimplemented because the request requires an extra parameter + try: + exchange_info = await self._api_get( + path_url=self.trading_pairs_request_path, + params={"instType": "SPOT"}, + ) + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for symbol_data in filter(okx_utils.is_exchange_information_valid, exchange_info["data"]): + mapping[symbol_data["instId"]] = combine_to_hb_trading_pair(base=symbol_data["baseCcy"], + quote=symbol_data["quoteCcy"]) + self._set_trading_pair_symbol_map(mapping) + + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs) -> Tuple[str, float]: + data = { + "clOrdId": order_id, + "tdMode": "cash", + "ordType": "limit", + "side": trade_type.name.lower(), + "instId": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "sz": str(amount), + "px": str(price) + } + + exchange_order_id = await self._api_request( + path_url=CONSTANTS.OKX_PLACE_ORDER_PATH, + method=RESTMethod.POST, + data=data, + is_auth_required=True, + limit_id=CONSTANTS.OKX_PLACE_ORDER_PATH, + ) + data = exchange_order_id["data"][0] + if data["sCode"] != "0": + raise IOError(f"Error submitting order {order_id}: {data['sMsg']}") + return str(data["ordId"]), self.current_timestamp + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation specific function is called by _cancel, and returns True if successful + """ + params = { + "clOrdId": order_id, + "instId": tracked_order.trading_pair + } + cancel_result = await self._api_post( + path_url=CONSTANTS.OKX_ORDER_CANCEL_PATH, + data=params, + is_auth_required=True, + ) + if cancel_result["data"][0]["sCode"] == "0": + final_result = True + elif cancel_result["data"][0]["sCode"] == "51400": + # Cancelation failed because the order does not exist + final_result = True + elif cancel_result["data"][0]["sCode"] == "51401": + # Cancelation failed because order has been cancelled + final_result = True + else: + raise IOError(f"Error cancelling order {order_id}: {cancel_result}") + + return final_result + + async def _get_last_traded_price(self, trading_pair: str) -> float: + params = {"instId": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair)} + + resp_json = await self._api_request( + path_url=CONSTANTS.OKX_TICKER_PATH, + params=params, + ) + + ticker_data, *_ = resp_json["data"] + return float(ticker_data["last"]) + + async def _update_balances(self): + msg = await self._api_request( + path_url=CONSTANTS.OKX_BALANCE_PATH, + is_auth_required=True) + + if msg['code'] == '0': + balances = msg['data'][0]['details'] + else: + raise Exception(msg['msg']) + + self._account_available_balances.clear() + self._account_balances.clear() + + for balance in balances: + self._update_balance_from_details(balance_details=balance) + + def _update_balance_from_details(self, balance_details: Dict[str, Any]): + equity_text = balance_details["eq"] + available_equity_text = balance_details["availEq"] + + if equity_text and available_equity_text: + total = Decimal(equity_text) + available = Decimal(available_equity_text) + else: + available = Decimal(balance_details["availBal"]) + total = available + Decimal(balance_details["frozenBal"]) + self._account_balances[balance_details["ccy"]] = total + self._account_available_balances[balance_details["ccy"]] = available + + async def _update_trading_rules(self): + # This has to be reimplemented because the request requires an extra parameter + exchange_info = await self._api_get( + path_url=self.trading_rules_request_path, + params={"instType": "SPOT"}, + ) + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + + async def _format_trading_rules(self, raw_trading_pair_info: List[Dict[str, Any]]) -> List[TradingRule]: + trading_rules = [] + + for info in raw_trading_pair_info.get("data", []): + try: + if okx_utils.is_exchange_information_valid(exchange_info=info): + trading_rules.append( + TradingRule( + trading_pair=await self.trading_pair_associated_to_exchange_symbol(symbol=info["instId"]), + min_order_size=Decimal(info["minSz"]), + min_price_increment=Decimal(info["tickSz"]), + min_base_amount_increment=Decimal(info["lotSz"]), + ) + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {info}. Skipping.") + return trading_rules + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _request_order_update(self, order: InFlightOrder) -> Dict[str, Any]: + return await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH, + params={ + "instId": await self.exchange_symbol_associated_to_pair(order.trading_pair), + "clOrdId": order.client_order_id}, + is_auth_required=True) + + async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]: + return await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.OKX_TRADE_FILLS_PATH, + params={ + "instType": "SPOT", + "instId": await self.exchange_symbol_associated_to_pair(order.trading_pair), + "ordId": await order.get_exchange_order_id()}, + is_auth_required=True) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + all_fills_response = await self._request_order_fills(order=order) + fills_data = all_fills_response["data"] + + for fill_data in fills_data: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=fill_data["feeCcy"], + flat_fees=[TokenAmount(amount=Decimal(fill_data["fee"]), token=fill_data["feeCcy"])] + ) + trade_update = TradeUpdate( + trade_id=str(fill_data["tradeId"]), + client_order_id=order.client_order_id, + exchange_order_id=str(fill_data["ordId"]), + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(fill_data["fillSz"]), + fill_quote_amount=Decimal(fill_data["fillSz"]) * Decimal(fill_data["fillPx"]), + fill_price=Decimal(fill_data["fillPx"]), + fill_timestamp=int(fill_data["ts"]) * 1e-3, + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._request_order_update(order=tracked_order) + + order_data = updated_order_data["data"][0] + new_state = CONSTANTS.ORDER_STATE[order_data["state"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(order_data["ordId"]), + trading_pair=tracked_order.trading_pair, + update_timestamp=int(order_data["uTime"]) * 1e-3, + new_state=new_state, + ) + return order_update + + async def _user_stream_event_listener(self): + async for stream_message in self._iter_user_event_queue(): + try: + args = stream_message.get("arg", {}) + channel = args.get("channel", None) + + if channel == CONSTANTS.OKX_WS_ORDERS_CHANNEL: + for data in stream_message.get("data", []): + order_status = CONSTANTS.ORDER_STATE[data["state"]] + client_order_id = data["clOrdId"] + fillable_order = self._order_tracker.all_fillable_orders.get(client_order_id) + updatable_order = self._order_tracker.all_updatable_orders.get(client_order_id) + + if (fillable_order is not None + and order_status in [OrderState.PARTIALLY_FILLED, OrderState.FILLED]): + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=fillable_order.trade_type, + percent_token=data["fillFeeCcy"], + flat_fees=[TokenAmount(amount=Decimal(data["fillFee"]), token=data["fillFeeCcy"])] + ) + trade_update = TradeUpdate( + trade_id=str(data["tradeId"]), + client_order_id=fillable_order.client_order_id, + exchange_order_id=str(data["ordId"]), + trading_pair=fillable_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(data["fillSz"]), + fill_quote_amount=Decimal(data["fillSz"]) * Decimal(data["fillPx"]), + fill_price=Decimal(data["fillPx"]), + fill_timestamp=int(data["uTime"]) * 1e-3, + ) + self._order_tracker.process_trade_update(trade_update) + + if updatable_order is not None: + order_update = OrderUpdate( + trading_pair=updatable_order.trading_pair, + update_timestamp=int(data["uTime"]) * 1e-3, + new_state=order_status, + client_order_id=updatable_order.client_order_id, + exchange_order_id=str(data["ordId"]), + ) + self._order_tracker.process_order_update(order_update=order_update) + + elif channel == CONSTANTS.OKX_WS_ACCOUNT_CHANNEL: + for data in stream_message.get("data", []): + for details in data.get("details", []): + self._update_balance_from_details(balance_details=details) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) diff --git a/hummingbot/connector/exchange/okx/okx_utils.py b/hummingbot/connector/exchange/okx/okx_utils.py new file mode 100644 index 0000000..78e5467 --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_utils.py @@ -0,0 +1,61 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0008"), + taker_percent_fee_decimal=Decimal("0.0001"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + + +class OKXConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="okx", const=True, client_data=None) + okx_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your OKX API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + okx_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your OKX secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + okx_passphrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your OKX passphrase key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + +KEYS = OKXConfigMap.construct() + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("instType", None) == "SPOT" diff --git a/hummingbot/connector/exchange/okx/okx_web_utils.py b/hummingbot/connector/exchange/okx/okx_web_utils.py new file mode 100644 index 0000000..f6e20a8 --- /dev/null +++ b/hummingbot/connector/exchange/okx/okx_web_utils.py @@ -0,0 +1,68 @@ +from typing import Callable, Optional +from urllib.parse import urljoin + +import hummingbot.connector.exchange.okx.okx_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided REST endpoint + + :param path_url: a public REST endpoint + :param domain: not required for OKX. Added only for compatibility. + + :return: the full URL to the endpoint + """ + return urljoin(CONSTANTS.OKX_BASE_URL, path_url) + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(path_url, domain) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, ) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or (lambda: get_current_server_time(throttler=throttler)) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ]) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN) -> float: + throttler = throttler or create_throttler() + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.OKX_SERVER_TIME_PATH), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.OKX_SERVER_TIME_PATH, + ) + server_time = float(response["data"][0]["ts"]) + + return server_time diff --git a/hummingbot/connector/exchange/paper_trade/__init__.py b/hummingbot/connector/exchange/paper_trade/__init__.py new file mode 100644 index 0000000..63096ff --- /dev/null +++ b/hummingbot/connector/exchange/paper_trade/__init__.py @@ -0,0 +1,24 @@ +from typing import List + +from hummingbot.client.config.config_helpers import ClientConfigAdapter, get_connector_class +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import PaperTradeExchange +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker + + +def get_order_book_tracker(connector_name: str, trading_pairs: List[str]) -> OrderBookTracker: + conn_setting = AllConnectorSettings.get_connector_settings()[connector_name] + try: + connector_instance = conn_setting.non_trading_connector_instance_with_default_configuration( + trading_pairs=trading_pairs) + return connector_instance.order_book_tracker + except Exception as exception: + raise Exception(f"Connector {connector_name} OrderBookTracker class not found ({exception})") + + +def create_paper_trade_market(exchange_name: str, client_config_map: ClientConfigAdapter, trading_pairs: List[str]): + tracker = get_order_book_tracker(connector_name=exchange_name, trading_pairs=trading_pairs) + return PaperTradeExchange(client_config_map, + tracker, + get_connector_class(exchange_name), + exchange_name=exchange_name) diff --git a/hummingbot/connector/exchange/paper_trade/market_config.py b/hummingbot/connector/exchange/paper_trade/market_config.py new file mode 100644 index 0000000..9b65c3c --- /dev/null +++ b/hummingbot/connector/exchange/paper_trade/market_config.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +from collections import namedtuple +from decimal import Decimal +from enum import Enum + + +class AssetType(Enum): + BASE_CURRENCY = 1 + QUOTE_CURRENCY = 2 + + +class MarketConfig(namedtuple("_MarketConfig", "buy_fees_asset," + "buy_fees_amount," + "sell_fees_asset," + "sell_fees_amount,")): + buy_fees_asset: AssetType + buy_fees_amount: Decimal + sell_fees_asset: AssetType + sell_fees_amount: Decimal + + @classmethod + def default_config(cls) -> "MarketConfig": + return MarketConfig(AssetType.BASE_CURRENCY, Decimal(0.0), AssetType.QUOTE_CURRENCY, Decimal(0.0)) + + @classmethod + def create_config(cls, trading_fee: float): + return MarketConfig(AssetType.BASE_CURRENCY, trading_fee, AssetType.QUOTE_CURRENCY, trading_fee) diff --git a/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pxd b/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pxd new file mode 100644 index 0000000..d55715b --- /dev/null +++ b/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pxd @@ -0,0 +1,83 @@ +from libcpp.set cimport set as cpp_set +from libcpp.string cimport string +from libcpp.unordered_map cimport unordered_map +from libcpp.utility cimport pair + +from hummingbot.core.data_type.LimitOrder cimport LimitOrder as CPPLimitOrder +from hummingbot.core.data_type.OrderExpirationEntry cimport OrderExpirationEntry as CPPOrderExpirationEntry +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.connector.exchange_base cimport ExchangeBase + + +ctypedef cpp_set[CPPLimitOrder] SingleTradingPairLimitOrders +ctypedef unordered_map[string, SingleTradingPairLimitOrders].iterator LimitOrdersIterator +ctypedef pair[string, SingleTradingPairLimitOrders] LimitOrdersPair +ctypedef unordered_map[string, SingleTradingPairLimitOrders] LimitOrders +ctypedef cpp_set[CPPLimitOrder].iterator SingleTradingPairLimitOrdersIterator +ctypedef cpp_set[CPPLimitOrder].reverse_iterator SingleTradingPairLimitOrdersRIterator +ctypedef cpp_set[CPPOrderExpirationEntry] LimitOrderExpirationSet +ctypedef cpp_set[CPPOrderExpirationEntry].iterator LimitOrderExpirationSetIterator + +cdef class QuantizationParams: + cdef: + str trading_pair + int price_precision + int price_decimals + int order_size_precision + int order_size_decimals + + +cdef class PaperTradeExchange(ExchangeBase): + cdef: + LimitOrders _bid_limit_orders + LimitOrders _ask_limit_orders + bint _paper_trade_market_initialized + dict _trading_pairs + object _queued_orders + dict _quantization_params + object _order_book_trade_listener + object _market_order_filled_listener + LimitOrderExpirationSet _limit_order_expiration_set + object _target_market + str _exchange_name + + cdef c_execute_buy(self, str order_id, str trading_pair, object amount) + cdef c_execute_sell(self, str order_id, str trading_pair, object amount) + cdef c_process_market_orders(self) + cdef c_set_balance(self, str currency, object amount) + cdef object c_get_fee(self, + str base_asset, + str quote_asset, + object order_type, + object order_side, + object amount, + object price, + object is_maker=*) + cdef c_delete_limit_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + const SingleTradingPairLimitOrdersIterator orders_it) + cdef c_process_limit_order(self, + bint is_buy, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it) + cdef c_process_limit_bid_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it) + cdef c_process_limit_ask_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it) + cdef c_process_crossed_limit_orders_for_trading_pair(self, + bint is_buy, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr) + cdef c_process_crossed_limit_orders(self) + cdef c_match_trade_to_limit_orders(self, object order_book_trade_event) + cdef object c_cancel_order_from_orders_map(self, + LimitOrders *orders_map, + str trading_pair_str, + bint cancel_all=*, + str client_order_id=*) diff --git a/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pyx b/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pyx new file mode 100644 index 0000000..9a1c7ca --- /dev/null +++ b/hummingbot/connector/exchange/paper_trade/paper_trade_exchange.pyx @@ -0,0 +1,1138 @@ +# distutils: sources=['hummingbot/core/cpp/Utils.cpp', 'hummingbot/core/cpp/LimitOrder.cpp', 'hummingbot/core/cpp/OrderExpirationEntry.cpp'] + +import asyncio +import math +import random +from collections import defaultdict, deque +from decimal import Decimal, ROUND_DOWN +from typing import Callable, Dict, List, Optional, Tuple, TYPE_CHECKING + +from cpython cimport PyObject +from cython.operator cimport address, dereference as deref, postincrement as inc +from libcpp cimport bool as cppbool +from libcpp.vector cimport vector + +from hummingbot.connector.budget_checker import BudgetChecker +from hummingbot.connector.connector_metrics_collector import DummyMetricsCollector +from hummingbot.connector.exchange.paper_trade.trading_pair import TradingPair +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.clock cimport Clock +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.composite_order_book import CompositeOrderBook +from hummingbot.core.data_type.composite_order_book cimport CompositeOrderBook +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.limit_order cimport c_create_limit_order_from_cpp_limit_order +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.event_listener cimport EventListener +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderBookEvent, + OrderBookTradeEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.Utils cimport getIteratorFromReverseIterator, reverse_iterator +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +ptm_logger = None +s_decimal_0 = Decimal(0) + + +cdef class QuantizationParams: + def __init__(self, + str trading_pair, + int price_precision, + int price_decimals, + int order_size_precision, + int order_size_decimals): + self.trading_pair = trading_pair + self.price_precision = price_precision + self.price_decimals = price_decimals + self.order_size_precision = order_size_precision + self.order_size_decimals = order_size_decimals + + def __repr__(self) -> str: + return (f"QuantizationParams('{self.trading_pair}', {self.price_precision}, {self.price_decimals}, " + f"{self.order_size_precision}, {self.order_size_decimals})") + + +cdef class QueuedOrder: + cdef: + double create_timestamp + str _order_id + bint _is_buy + str _trading_pair + object _amount + + def __init__(self, create_timestamp: float, order_id: str, is_buy: bool, trading_pair: str, amount: Decimal): + self.create_timestamp = create_timestamp + self._order_id = order_id + self._is_buy = is_buy + self._trading_pair = trading_pair + self._amount = amount + + @property + def timestamp(self) -> double: + return self.create_timestamp + + @property + def order_id(self) -> str: + return self._order_id + + @property + def is_buy(self) -> bint: + return self._is_buy + + @property + def trading_pair(self) -> str: + return self._trading_pair + + @property + def amount(self) -> Decimal: + return self._amount + + def __repr__(self) -> str: + return (f"QueuedOrder({self.create_timestamp}, '{self.order_id}', {self.is_buy}, '{self.trading_pair}', " + f"{self.amount})") + + +cdef class OrderBookTradeListener(EventListener): + cdef: + ExchangeBase _market + + def __init__(self, market: ExchangeBase): + super().__init__() + self._market = market + + cdef c_call(self, object event_object): + try: + self._market.match_trade_to_limit_orders(event_object) + except Exception as e: + self.logger().error("Error call trade listener.", exc_info=True) + +cdef class OrderBookMarketOrderFillListener(EventListener): + cdef: + ExchangeBase _market + + def __init__(self, market: ExchangeBase): + super().__init__() + self._market = market + + cdef c_call(self, object event_object): + + if event_object.trading_pair not in self._market.order_books or event_object.order_type != OrderType.MARKET: + return + order_book = self._market.order_books[event_object.trading_pair] + order_book.record_filled_order(event_object) + + +cdef class PaperTradeExchange(ExchangeBase): + TRADE_EXECUTION_DELAY = 5.0 + ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value + SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value + BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value + MARKET_ORDER_CANCELED_EVENT_TAG = MarketEvent.OrderCancelled.value + MARKET_ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value + ORDER_BOOK_TRADE_EVENT_TAG = OrderBookEvent.TradeEvent.value + MARKET_SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value + MARKET_BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + order_book_tracker: OrderBookTracker, + target_market: Callable, + exchange_name: str, + ): + order_book_tracker.data_source.order_book_create_function = lambda: CompositeOrderBook() + self._set_order_book_tracker(order_book_tracker) + self._budget_checker = BudgetChecker(exchange=self) + super(ExchangeBase, self).__init__(client_config_map) + self._exchange_name = exchange_name + self._account_balances = {} + self._account_available_balances = {} + self._paper_trade_market_initialized = False + self._trading_pairs = {} + self._queued_orders = deque() + self._quantization_params = {} + self._order_book_trade_listener = OrderBookTradeListener(self) + self._target_market = target_market + self._market_order_filled_listener = OrderBookMarketOrderFillListener(self) + self.c_add_listener(self.ORDER_FILLED_EVENT_TAG, self._market_order_filled_listener) + + # Trade volume metrics should never be gather for paper trade connector + self._trade_volume_metric_collector = DummyMetricsCollector() + + @property + def budget_checker(self) -> BudgetChecker: + return self._budget_checker + + @classmethod + def random_order_id(cls, order_side: str, trading_pair: str) -> str: + vals = [random.choice(range(0, 256)) for i in range(0, 13)] + return f"{order_side}://" + trading_pair + "/" + "".join([f"{val:02x}" for val in vals]) + + def init_paper_trade_market(self): + for trading_pair_str, order_book in self.order_book_tracker.order_books.items(): + assert type(order_book) is CompositeOrderBook + base_asset, quote_asset = self.split_trading_pair(trading_pair_str) + self._trading_pairs[self._target_market.convert_from_exchange_trading_pair(trading_pair_str)] = TradingPair(trading_pair_str, base_asset, quote_asset) + (order_book).c_add_listener( + self.ORDER_BOOK_TRADE_EVENT_TAG, + self._order_book_trade_listener + ) + + def split_trading_pair(self, trading_pair: str) -> Tuple[str, str]: + return self._target_market.split_trading_pair(trading_pair) + + # + @property + def trading_pair(self) -> Dict[str, TradingPair]: + return self._trading_pairs + + @property + def trading_pairs(self) -> List[str]: + return [trading_pair for trading_pair in self._trading_pairs] + + @property + def name(self) -> str: + return self._exchange_name + + @property + def display_name(self) -> str: + return f"{self._exchange_name}_PaperTrade" + + @property + def order_books(self) -> Dict[str, CompositeOrderBook]: + return self.order_book_tracker.order_books + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "order_books_initialized": self.order_book_tracker and len(self.order_book_tracker.order_books) > 0 + } + + @property + def ready(self): + if not self.order_book_tracker.ready: + return False + if all(self.status_dict.values()): + if not self._paper_trade_market_initialized: + self.init_paper_trade_market() + self._paper_trade_market_initialized = True + return True + else: + return False + + @property + def queued_orders(self) -> List[QueuedOrder]: + return self._queued_orders + + @property + def limit_orders(self) -> List[LimitOrder]: + cdef: + LimitOrdersIterator map_it + SingleTradingPairLimitOrders *single_trading_pair_collection_ptr + SingleTradingPairLimitOrdersIterator collection_it + SingleTradingPairLimitOrdersRIterator collection_rit + const CPPLimitOrder *cpp_limit_order_ptr + list retval = [] + + map_it = self._bid_limit_orders.begin() + while map_it != self._bid_limit_orders.end(): + single_trading_pair_collection_ptr = address(deref(map_it).second) + collection_rit = single_trading_pair_collection_ptr.rbegin() + while collection_rit != single_trading_pair_collection_ptr.rend(): + cpp_limit_order_ptr = address(deref(collection_rit)) + retval.append(c_create_limit_order_from_cpp_limit_order(deref(cpp_limit_order_ptr))) + inc(collection_rit) + inc(map_it) + + map_it = self._ask_limit_orders.begin() + while map_it != self._ask_limit_orders.end(): + single_trading_pair_collection_ptr = address(deref(map_it).second) + collection_it = single_trading_pair_collection_ptr.begin() + while collection_it != single_trading_pair_collection_ptr.end(): + cpp_limit_order_ptr = address(deref(collection_it)) + retval.append(c_create_limit_order_from_cpp_limit_order(deref(cpp_limit_order_ptr))) + inc(collection_it) + inc(map_it) + + return retval + + @property + def on_hold_balances(self) -> Dict[str, Decimal]: + _on_hold_balances = defaultdict(Decimal) + for limit_order in self.limit_orders: + if limit_order.is_buy: + _on_hold_balances[limit_order.quote_currency] += limit_order.quantity * limit_order.price + else: + _on_hold_balances[limit_order.base_currency] += limit_order.quantity + return _on_hold_balances + + @property + def available_balances(self) -> Dict[str, Decimal]: + _available_balances = self._account_balances.copy() + for trading_pair_str, balance in _available_balances.items(): + _available_balances[trading_pair_str] -= self.on_hold_balances[trading_pair_str] + return _available_balances + + # + + cdef c_start(self, Clock clock, double timestamp): + ExchangeBase.c_start(self, clock, timestamp) + + async def start_network(self): + await self.stop_network() + self.order_book_tracker.start() + + async def stop_network(self): + self.order_book_tracker.stop() + + async def check_network(self) -> NetworkStatus: + return NetworkStatus.CONNECTED + + cdef c_set_balance(self, str currency, object balance): + self._account_balances[currency.upper()] = Decimal(balance) + + cdef object c_get_balance(self, str currency): + if currency.upper() not in self._account_balances: + self.logger().warning(f"Account balance does not have asset {currency.upper()}.") + return Decimal(0.0) + return self._account_balances[currency.upper()] + + cdef c_tick(self, double timestamp): + ExchangeBase.c_tick(self, timestamp) + self.c_process_market_orders() + self.c_process_crossed_limit_orders() + + cdef str c_buy(self, + str trading_pair_str, + object amount, + object order_type=OrderType.MARKET, + object price=s_decimal_0, + dict kwargs={}): + if trading_pair_str not in self._trading_pairs: + raise ValueError(f"Trading pair '{trading_pair_str}' does not existing in current data set.") + + cdef: + str order_id = self.random_order_id("buy", trading_pair_str) + str quote_asset = self._trading_pairs[trading_pair_str].quote_asset + string cpp_order_id = order_id.encode("utf8") + string cpp_trading_pair_str = trading_pair_str.encode("utf8") + string cpp_base_asset = self._trading_pairs[trading_pair_str].base_asset.encode("utf8") + string cpp_quote_asset = quote_asset.encode("utf8") + string cpp_position = "NIL".encode("utf8") + LimitOrdersIterator map_it + SingleTradingPairLimitOrders *limit_orders_collection_ptr = NULL + pair[LimitOrders.iterator, cppbool] insert_result + + quantized_price = (self.c_quantize_order_price(trading_pair_str, price) + if order_type is OrderType.LIMIT + else s_decimal_0) + quantized_amount = self.c_quantize_order_amount(trading_pair_str, amount) + if order_type is OrderType.MARKET: + self._queued_orders.append(QueuedOrder(self._current_timestamp, order_id, True, trading_pair_str, + quantized_amount)) + elif order_type is OrderType.LIMIT: + + map_it = self._bid_limit_orders.find(cpp_trading_pair_str) + + if map_it == self._bid_limit_orders.end(): + insert_result = self._bid_limit_orders.insert(LimitOrdersPair(cpp_trading_pair_str, + SingleTradingPairLimitOrders())) + map_it = insert_result.first + limit_orders_collection_ptr = address(deref(map_it).second) + limit_orders_collection_ptr.insert(CPPLimitOrder( + cpp_order_id, + cpp_trading_pair_str, + True, + cpp_base_asset, + cpp_quote_asset, + quantized_price, + quantized_amount, + None, + int(self._current_timestamp * 1e6), + 0, + cpp_position, + )) + safe_ensure_future(self.trigger_event_async( + self.MARKET_BUY_ORDER_CREATED_EVENT_TAG, + BuyOrderCreatedEvent(self._current_timestamp, + order_type, + trading_pair_str, + quantized_amount, + quantized_price, + order_id, + self._current_timestamp))) + return order_id + + cdef str c_sell(self, + str trading_pair_str, + object amount, + object order_type=OrderType.MARKET, + object price=s_decimal_0, + dict kwargs={}): + + if trading_pair_str not in self._trading_pairs: + raise ValueError(f"Trading pair '{trading_pair_str}' does not existing in current data set.") + cdef: + str order_id = self.random_order_id("sell", trading_pair_str) + str base_asset = self._trading_pairs[trading_pair_str].base_asset + string cpp_order_id = order_id.encode("utf8") + string cpp_trading_pair_str = trading_pair_str.encode("utf8") + string cpp_base_asset = base_asset.encode("utf8") + string cpp_quote_asset = self._trading_pairs[trading_pair_str].quote_asset.encode("utf8") + string cpp_position = "NIL".encode("utf8") + LimitOrdersIterator map_it + SingleTradingPairLimitOrders *limit_orders_collection_ptr = NULL + pair[LimitOrders.iterator, cppbool] insert_result + + quantized_price = (self.c_quantize_order_price(trading_pair_str, price) + if order_type is OrderType.LIMIT + else s_decimal_0) + quantized_amount = self.c_quantize_order_amount(trading_pair_str, amount) + if order_type is OrderType.MARKET: + self._queued_orders.append(QueuedOrder(self._current_timestamp, order_id, False, trading_pair_str, + quantized_amount)) + elif order_type is OrderType.LIMIT: + map_it = self._ask_limit_orders.find(cpp_trading_pair_str) + + if map_it == self._ask_limit_orders.end(): + insert_result = self._ask_limit_orders.insert(LimitOrdersPair(cpp_trading_pair_str, + SingleTradingPairLimitOrders())) + map_it = insert_result.first + limit_orders_collection_ptr = address(deref(map_it).second) + limit_orders_collection_ptr.insert(CPPLimitOrder( + cpp_order_id, + cpp_trading_pair_str, + False, + cpp_base_asset, + cpp_quote_asset, + quantized_price, + quantized_amount, + None, + int(self._current_timestamp * 1e6), + 0, + cpp_position, + )) + safe_ensure_future(self.trigger_event_async( + self.MARKET_SELL_ORDER_CREATED_EVENT_TAG, + SellOrderCreatedEvent(self._current_timestamp, + order_type, + trading_pair_str, + quantized_amount, + quantized_price, + order_id, + self._current_timestamp))) + return order_id + + cdef c_execute_buy(self, str order_id, str trading_pair_str, object amount): + cdef: + str quote_asset = self._trading_pairs[trading_pair_str].quote_asset + str base_asset = self._trading_pairs[trading_pair_str].base_asset + object quote_balance = self.c_get_balance(quote_asset) + object base_balance = self.c_get_balance(base_asset) + + order_book = self.order_books[trading_pair_str] + + buy_entries = order_book.simulate_buy(amount) + + # Get the weighted average price of the trade + avg_price = Decimal(0) + for entry in buy_entries: + avg_price += Decimal(entry.price) * Decimal(entry.amount) + avg_price = avg_price / amount + + order_candidate = OrderCandidate( + trading_pair=trading_pair_str, + # Market orders are not maker orders + is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, + amount=amount, + price=avg_price, + from_total_balances=True + ) + + adjusted_order_candidate = self._budget_checker.populate_collateral_entries(order_candidate) + + # Quote currency used, including fees. + paid_amount = adjusted_order_candidate.order_collateral.amount + # Base currency acquired, including fees. + acquired_amount = adjusted_order_candidate.potential_returns.amount + + # It's not possible to fulfill the order, the possible acquired amount is less than requested + if paid_amount > quote_balance: + self.logger().warning(f"Insufficient {quote_asset} balance available for buy order. " + f"{quote_balance} {quote_asset} available vs. " + f"{paid_amount} {quote_asset} required for the order.") + self.c_trigger_event( + self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, OrderType.MARKET) + ) + return + + # The order was successfully executed + self.c_set_balance(quote_asset, + quote_balance - paid_amount) + self.c_set_balance(base_asset, + base_balance + acquired_amount) + + # add fee + fees = build_trade_fee( + exchange=self.name, + is_maker=False, + base_currency="", + quote_currency="", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("0"), + price=Decimal("0"), + ) + + order_filled_events = OrderFilledEvent.order_filled_events_from_order_book_rows( + self._current_timestamp, order_id, trading_pair_str, TradeType.BUY, OrderType.MARKET, + fees, buy_entries + ) + + for order_filled_event in order_filled_events: + self.c_trigger_event(self.ORDER_FILLED_EVENT_TAG, order_filled_event) + + self.c_trigger_event( + self.BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent(self._current_timestamp, + order_id, + base_asset, + quote_asset, + acquired_amount, + paid_amount, + OrderType.MARKET)) + + cdef c_execute_sell(self, str order_id, str trading_pair_str, object amount): + cdef: + str quote_asset = self._trading_pairs[trading_pair_str].quote_asset + str base_asset = self._trading_pairs[trading_pair_str].base_asset + object quote_balance = self.c_get_balance(quote_asset) + object base_balance = self.c_get_balance(base_asset) + + order_book = self.order_books[trading_pair_str] + + sell_entries = order_book.simulate_sell(amount) + + # Get the weighted average price of the trade + avg_price = Decimal(0) + for entry in sell_entries: + avg_price += Decimal(entry.price) * Decimal(entry.amount) + avg_price = avg_price / amount + + order_candidate = OrderCandidate( + trading_pair=trading_pair_str, + # Market orders are not maker orders + is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.SELL, + amount=amount, + price=avg_price, + from_total_balances=True + ) + + adjusted_order_candidate = self._budget_checker.populate_collateral_entries(order_candidate) + + # Base currency used, including fees. + sold_amount = adjusted_order_candidate.order_collateral.amount + # Quote currency acquired, including fees. + acquired_amount = adjusted_order_candidate.potential_returns.amount + + # It's not possible to fulfill the order, the possible sold amount is less than requested + if sold_amount > base_balance: + self.logger().warning(f"Insufficient {base_asset} balance available for sell order. " + f"{base_balance} {base_asset} available vs. " + f"{amount} {base_asset} required for the order.") + self.c_trigger_event( + self.MARKET_ORDER_FAILURE_EVENT_TAG, + MarketOrderFailureEvent(self._current_timestamp, order_id, OrderType.MARKET) + ) + return + + # The order was successfully executed + self.c_set_balance(quote_asset, + quote_balance + acquired_amount) + self.c_set_balance(base_asset, + base_balance - sold_amount) + + # add fee + fees = build_trade_fee( + exchange=self.name, + is_maker=False, + base_currency="", + quote_currency="", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("0"), + price=Decimal("0"), + ) + + order_filled_events = OrderFilledEvent.order_filled_events_from_order_book_rows( + self._current_timestamp, order_id, trading_pair_str, TradeType.SELL, + OrderType.MARKET, fees, sell_entries + ) + + for order_filled_event in order_filled_events: + self.c_trigger_event(self.ORDER_FILLED_EVENT_TAG, order_filled_event) + + self.c_trigger_event( + self.SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent(self._current_timestamp, + order_id, + base_asset, + quote_asset, + sold_amount, + acquired_amount, + OrderType.MARKET)) + + cdef c_process_market_orders(self): + cdef: + QueuedOrder front_order = None + while len(self._queued_orders) > 0: + front_order = self._queued_orders[0] + if front_order.create_timestamp <= self._current_timestamp - self.TRADE_EXECUTION_DELAY: + self._queued_orders.popleft() + try: + if front_order.is_buy: + self.c_execute_buy(front_order.order_id, front_order.trading_pair, front_order.amount) + else: + self.c_execute_sell(front_order.order_id, front_order.trading_pair, front_order.amount) + except Exception as e: + self.logger().error("Error executing queued order.", exc_info=True) + else: + return + + cdef c_delete_limit_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + const SingleTradingPairLimitOrdersIterator orders_it): + cdef: + SingleTradingPairLimitOrders *orders_collection_ptr = address(deref(deref(map_it_ptr)).second) + try: + orders_collection_ptr.erase(orders_it) + if orders_collection_ptr.empty(): + map_it_ptr[0] = limit_orders_map_ptr.erase(deref(map_it_ptr)) + return True + except Exception as err: + self.logger().error("Error deleting limit order.", exc_info=True) + return False + + cdef c_process_limit_bid_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it): + cdef: + const CPPLimitOrder *cpp_limit_order_ptr = address(deref(orders_it)) + str trading_pair_str = cpp_limit_order_ptr.getTradingPair().decode("utf8") + str quote_asset = cpp_limit_order_ptr.getQuoteCurrency().decode("utf8") + str base_asset = cpp_limit_order_ptr.getBaseCurrency().decode("utf8") + str order_id = cpp_limit_order_ptr.getClientOrderID().decode("utf8") + object amount = cpp_limit_order_ptr.getQuantity() + object price = cpp_limit_order_ptr.getPrice() + object quote_balance = self.c_get_balance(quote_asset) + object base_balance = self.c_get_balance(base_asset) + + order_candidate = OrderCandidate( + trading_pair=trading_pair_str, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=amount, + price=price, + from_total_balances=True + ) + + adjusted_order_candidate = self._budget_checker.populate_collateral_entries(order_candidate) + + # Quote currency used, including fees. + paid_amount = adjusted_order_candidate.order_collateral.amount + # Base currency acquired, including fees. + acquired_amount = adjusted_order_candidate.potential_returns.amount + + # It's not possible to fulfill the order, the possible acquired amount is less than requested + if paid_amount > quote_balance: + self.logger().warning(f"Not enough {quote_asset} balance to fill limit buy order on {trading_pair_str}. " + f"{paid_amount:.8g} {quote_asset} needed vs. " + f"{quote_balance:.8g} {quote_asset} available.") + + self.c_delete_limit_order(limit_orders_map_ptr, map_it_ptr, orders_it) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, + order_id) + ) + return + + # The order was successfully executed + self.c_set_balance(quote_asset, + quote_balance - paid_amount) + self.c_set_balance(base_asset, + base_balance + acquired_amount) + + # add fee + fees = build_trade_fee( + exchange=self.name, + is_maker=True, + base_currency="", + quote_currency="", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("0"), + price=Decimal("0"), + ) + + # Emit the trade and order completed events. + self.c_trigger_event( + self.ORDER_FILLED_EVENT_TAG, + OrderFilledEvent( + self._current_timestamp, + order_id, + trading_pair_str, + TradeType.BUY, + OrderType.LIMIT, + cpp_limit_order_ptr.getPrice(), + cpp_limit_order_ptr.getQuantity(), + fees, + exchange_trade_id=str(int(self._time() * 1e6)) + )) + + self.c_trigger_event( + self.BUY_ORDER_COMPLETED_EVENT_TAG, + BuyOrderCompletedEvent( + self._current_timestamp, + order_id, + base_asset, + quote_asset, + acquired_amount, + paid_amount, + OrderType.LIMIT + )) + self.c_delete_limit_order(limit_orders_map_ptr, map_it_ptr, orders_it) + + cdef c_process_limit_ask_order(self, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it): + cdef: + const CPPLimitOrder *cpp_limit_order_ptr = address(deref(orders_it)) + str trading_pair_str = cpp_limit_order_ptr.getTradingPair().decode("utf8") + str quote_asset = cpp_limit_order_ptr.getQuoteCurrency().decode("utf8") + str base_asset = cpp_limit_order_ptr.getBaseCurrency().decode("utf8") + str order_id = cpp_limit_order_ptr.getClientOrderID().decode("utf8") + object amount = cpp_limit_order_ptr.getQuantity() + object price = cpp_limit_order_ptr.getPrice() + object quote_balance = self.c_get_balance(quote_asset) + object base_balance = self.c_get_balance(base_asset) + + order_candidate = OrderCandidate( + trading_pair=trading_pair_str, + # Market orders are not maker orders + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=amount, + price=price, + from_total_balances=True + ) + + adjusted_order_candidate = self._budget_checker.populate_collateral_entries(order_candidate) + + # Base currency used, including fees. + sold_amount = adjusted_order_candidate.order_collateral.amount + # Quote currency acquired, including fees. + acquired_amount = adjusted_order_candidate.potential_returns.amount + + # It's not possible to fulfill the order, the possible sold amount is less than requested + if sold_amount > base_balance: + self.logger().warning(f"Not enough {base_asset} balance to fill limit sell order on {trading_pair_str}. " + f"{sold_amount:.8g} {base_asset} needed vs. " + f"{base_balance:.8g} {base_asset} available.") + self.c_delete_limit_order(limit_orders_map_ptr, map_it_ptr, orders_it) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, + order_id) + ) + return + + # The order was successfully executed + self.c_set_balance(quote_asset, + quote_balance + acquired_amount) + self.c_set_balance(base_asset, + base_balance - sold_amount) + + # add fee + fees = build_trade_fee( + exchange=self.name, + is_maker=True, + base_currency="", + quote_currency="", + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("0"), + price=Decimal("0"), + ) + + # Emit the trade and order completed events. + self.c_trigger_event( + self.ORDER_FILLED_EVENT_TAG, + OrderFilledEvent( + self._current_timestamp, + order_id, + trading_pair_str, + TradeType.SELL, + OrderType.LIMIT, + cpp_limit_order_ptr.getPrice(), + cpp_limit_order_ptr.getQuantity(), + fees, + exchange_trade_id=str(int(self._time() * 1e6)) + )) + + self.c_trigger_event( + self.SELL_ORDER_COMPLETED_EVENT_TAG, + SellOrderCompletedEvent( + self._current_timestamp, + order_id, + base_asset, + quote_asset, + sold_amount, + acquired_amount, + OrderType.LIMIT + )) + self.c_delete_limit_order(limit_orders_map_ptr, map_it_ptr, orders_it) + + cdef c_process_limit_order(self, + bint is_buy, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr, + SingleTradingPairLimitOrdersIterator orders_it): + try: + if is_buy: + self.c_process_limit_bid_order(limit_orders_map_ptr, map_it_ptr, orders_it) + else: + self.c_process_limit_ask_order(limit_orders_map_ptr, map_it_ptr, orders_it) + except Exception as e: + self.logger().error(f"Error processing limit order.", exc_info=True) + + cdef c_process_crossed_limit_orders_for_trading_pair(self, + bint is_buy, + LimitOrders *limit_orders_map_ptr, + LimitOrdersIterator *map_it_ptr): + """ + Trigger limit orders when the opposite side of the order book has crossed the limit order's price. + This implies someone was ready to fill the limit order, if that limit order was on the market. + + :param is_buy: are the limit orders on the bid side? + :param limit_orders_map_ptr: pointer to the limit orders map + :param map_it_ptr: limit orders map iterator, which implies the trading pair being processed + """ + cdef: + str trading_pair = deref(deref(map_it_ptr)).first.decode("utf8") + object opposite_order_book_price = self.c_get_price(trading_pair, is_buy) + SingleTradingPairLimitOrders *orders_collection_ptr = address(deref(deref(map_it_ptr)).second) + SingleTradingPairLimitOrdersIterator orders_it = orders_collection_ptr.begin() + SingleTradingPairLimitOrdersRIterator orders_rit = orders_collection_ptr.rbegin() + vector[SingleTradingPairLimitOrdersIterator] process_order_its + const CPPLimitOrder *cpp_limit_order_ptr = NULL + + if is_buy: + while orders_rit != orders_collection_ptr.rend(): + cpp_limit_order_ptr = address(deref(orders_rit)) + if opposite_order_book_price > cpp_limit_order_ptr.getPrice(): + break + process_order_its.push_back(getIteratorFromReverseIterator( + orders_rit)) + inc(orders_rit) + else: + while orders_it != orders_collection_ptr.end(): + cpp_limit_order_ptr = address(deref(orders_it)) + if opposite_order_book_price < cpp_limit_order_ptr.getPrice(): + break + process_order_its.push_back(orders_it) + inc(orders_it) + + for orders_it in process_order_its: + self.c_process_limit_order(is_buy, limit_orders_map_ptr, map_it_ptr, orders_it) + + cdef c_process_crossed_limit_orders(self): + cdef: + LimitOrders *limit_orders_ptr = address(self._bid_limit_orders) + LimitOrdersIterator map_it = limit_orders_ptr.begin() + + while map_it != limit_orders_ptr.end(): + self.c_process_crossed_limit_orders_for_trading_pair(True, limit_orders_ptr, address(map_it)) + if map_it != limit_orders_ptr.end(): + inc(map_it) + + limit_orders_ptr = address(self._ask_limit_orders) + map_it = limit_orders_ptr.begin() + + while map_it != limit_orders_ptr.end(): + self.c_process_crossed_limit_orders_for_trading_pair(False, limit_orders_ptr, address(map_it)) + if map_it != limit_orders_ptr.end(): + inc(map_it) + + # + cdef c_match_trade_to_limit_orders(self, object order_book_trade_event): + """ + Trigger limit orders when incoming market orders have crossed the limit order's price. + + :param order_book_trade_event: trade event from order book + """ + cdef: + string cpp_trading_pair = order_book_trade_event.trading_pair.encode("utf8") + bint is_maker_buy = order_book_trade_event.type is TradeType.SELL + object trade_price = order_book_trade_event.price + object trade_quantity = order_book_trade_event.amount + LimitOrders *limit_orders_map_ptr = (address(self._bid_limit_orders) + if is_maker_buy + else address(self._ask_limit_orders)) + LimitOrdersIterator map_it = limit_orders_map_ptr.find(cpp_trading_pair) + SingleTradingPairLimitOrders *orders_collection_ptr = NULL + SingleTradingPairLimitOrdersIterator orders_it + SingleTradingPairLimitOrdersRIterator orders_rit + vector[SingleTradingPairLimitOrdersIterator] process_order_its + const CPPLimitOrder *cpp_limit_order_ptr = NULL + + if map_it == limit_orders_map_ptr.end(): + return + + orders_collection_ptr = address(deref(map_it).second) + if is_maker_buy: + orders_rit = orders_collection_ptr.rbegin() + while orders_rit != orders_collection_ptr.rend(): + cpp_limit_order_ptr = address(deref(orders_rit)) + if cpp_limit_order_ptr.getPrice() <= trade_price: + break + process_order_its.push_back(getIteratorFromReverseIterator( + orders_rit)) + inc(orders_rit) + else: + orders_it = orders_collection_ptr.begin() + while orders_it != orders_collection_ptr.end(): + cpp_limit_order_ptr = address(deref(orders_it)) + if cpp_limit_order_ptr.getPrice() >= trade_price: + break + process_order_its.push_back(orders_it) + inc(orders_it) + + for orders_it in process_order_its: + self.c_process_limit_order(is_maker_buy, limit_orders_map_ptr, address(map_it), orders_it) + + # + + cdef object c_get_available_balance(self, str currency): + return self.available_balances.get(currency.upper(), s_decimal_0) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + cdef: + LimitOrders *limit_orders_map_ptr + list cancellation_results = [] + limit_orders_map_ptr = address(self._bid_limit_orders) + for trading_pair_str in self._trading_pairs.keys(): + results = self.c_cancel_order_from_orders_map(limit_orders_map_ptr, trading_pair_str, cancel_all=True) + cancellation_results.extend(results) + + limit_orders_map_ptr = address(self._ask_limit_orders) + for trading_pair_str in self._trading_pairs.keys(): + results = self.c_cancel_order_from_orders_map(limit_orders_map_ptr, trading_pair_str, cancel_all=True) + cancellation_results.extend(results) + return cancellation_results + + cdef object c_cancel_order_from_orders_map(self, + LimitOrders *orders_map, + str trading_pair_str, + bint cancel_all=False, + str client_order_id=None): + cdef: + string cpp_trading_pair = trading_pair_str.encode("utf8") + LimitOrdersIterator map_it = orders_map.find(cpp_trading_pair) + SingleTradingPairLimitOrders *limit_orders_collection_ptr = NULL + SingleTradingPairLimitOrdersIterator orders_it + vector[SingleTradingPairLimitOrdersIterator] process_order_its + const CPPLimitOrder *limit_order_ptr = NULL + str limit_order_cid + list cancellation_results = [] + try: + if map_it == orders_map.end(): + return [] + + limit_orders_collection_ptr = address(deref(map_it).second) + orders_it = limit_orders_collection_ptr.begin() + while orders_it != limit_orders_collection_ptr.end(): + limit_order_ptr = address(deref(orders_it)) + limit_order_cid = limit_order_ptr.getClientOrderID().decode("utf8") + if (not cancel_all and limit_order_cid == client_order_id) or cancel_all: + process_order_its.push_back(orders_it) + inc(orders_it) + + for orders_it in process_order_its: + limit_order_ptr = address(deref(orders_it)) + limit_order_cid = limit_order_ptr.getClientOrderID().decode("utf8") + delete_success = self.c_delete_limit_order(orders_map, address(map_it), orders_it) + cancellation_results.append(CancellationResult(limit_order_cid, + delete_success)) + self.c_trigger_event(self.MARKET_ORDER_CANCELED_EVENT_TAG, + OrderCancelledEvent(self._current_timestamp, + limit_order_cid) + ) + return cancellation_results + except Exception as err: + self.logger().error(f"Error canceling order.", exc_info=True) + + cdef c_cancel(self, str trading_pair_str, str client_order_id): + cdef: + string cpp_trading_pair = trading_pair_str.encode("utf8") + string cpp_client_order_id = client_order_id.encode("utf8") + str trade_type = client_order_id.split("://")[0] + bint is_maker_buy = trade_type.upper() == "BUY" + LimitOrders *limit_orders_map_ptr = (address(self._bid_limit_orders) + if is_maker_buy + else address(self._ask_limit_orders)) + self.c_cancel_order_from_orders_map(limit_orders_map_ptr, trading_pair_str, False, client_order_id) + + cdef object c_get_fee(self, + str base_asset, + str quote_asset, + object order_type, + object order_side, + object amount, + object price, + object is_maker = None): + return build_trade_fee( + self.name, + is_maker=is_maker if is_maker is not None else order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER], + base_currency=base_asset, + quote_currency=quote_asset, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + + cdef OrderBook c_get_order_book(self, str trading_pair): + if trading_pair not in self._trading_pairs: + raise ValueError(f"No order book exists for '{trading_pair}'.") + trading_pair = self._target_market.convert_to_exchange_trading_pair(trading_pair) + return self._order_book_tracker.order_books[trading_pair] + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + cdef: + QuantizationParams q_params + if trading_pair in self._quantization_params: + q_params = self._quantization_params[trading_pair] + decimals_quantum = Decimal(f"1e-{q_params.price_decimals}") + if price.is_finite() and price > s_decimal_0: + precision_quantum = Decimal(f"1e{math.ceil(math.log10(price)) - q_params.price_precision}") + else: + precision_quantum = Decimal(0) + return max(precision_quantum, decimals_quantum) + else: + return Decimal(f"1e-10") + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return self.c_get_order_price_quantum(trading_pair, price) + + cdef object c_get_order_size_quantum(self, + str trading_pair, + object order_size): + cdef: + QuantizationParams q_params + if trading_pair in self._quantization_params: + q_params = self._quantization_params[trading_pair] + decimals_quantum = Decimal(f"1e-{q_params.order_size_decimals}") + if order_size.is_finite() and order_size > s_decimal_0: + precision_quantum = Decimal(f"1e{math.ceil(math.log10(order_size)) - q_params.order_size_precision}") + else: + precision_quantum = Decimal(0) + return max(precision_quantum, decimals_quantum) + else: + return Decimal(f"1e-7") + + cdef object c_quantize_order_price(self, + str trading_pair, + object price): + price = Decimal('%.7g' % price) # hard code to round to 8 significant digits + price_quantum = self.c_get_order_price_quantum(trading_pair, price) + return (price // price_quantum) * price_quantum + + cdef object c_quantize_order_amount(self, + str trading_pair, + object amount, + object price=s_decimal_0): + amount = amount.quantize(Decimal('1e-7'), rounding=ROUND_DOWN) + if amount <= 1e-7: + amount = Decimal("0") + order_size_quantum = self.c_get_order_size_quantum(trading_pair, amount) + return (amount // order_size_quantum) * order_size_quantum + + def get_available_balance(self, currency: str) -> Decimal: + return self.c_get_available_balance(currency) + + def get_all_balances(self) -> Dict[str, Decimal]: + return self._account_balances.copy() + + # + def match_trade_to_limit_orders(self, event_object: OrderBookTradeEvent): + self.c_match_trade_to_limit_orders(event_object) + + def set_balance(self, currency: str, balance: Decimal): + self.c_set_balance(currency, balance) + # + + def get_price(self, trading_pair: str, is_buy: bool) -> Decimal: + return self.c_get_price(trading_pair, is_buy) + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_0, **kwargs) -> str: + return self.c_buy(trading_pair, amount, order_type, price, kwargs) + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_0, **kwargs) -> str: + return self.c_sell(trading_pair, amount, order_type, price, kwargs) + + def cancel(self, trading_pair: str, client_order_id: str): + return self.c_cancel(trading_pair, client_order_id) + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_0, + is_maker: Optional[bool] = None): + return self.c_get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + def get_order_book(self, trading_pair: str) -> OrderBook: + return self.c_get_order_book(trading_pair) + + def get_maker_order_type(self): + return OrderType.LIMIT + + def get_taker_order_type(self): + return OrderType.LIMIT + + async def trigger_event_async(self, + event_tag, + event): + await asyncio.sleep(0.01) + self.c_trigger_event(event_tag, event) diff --git a/hummingbot/connector/exchange/paper_trade/trading_pair.py b/hummingbot/connector/exchange/paper_trade/trading_pair.py new file mode 100644 index 0000000..7560fb0 --- /dev/null +++ b/hummingbot/connector/exchange/paper_trade/trading_pair.py @@ -0,0 +1,7 @@ +from typing import NamedTuple + + +class TradingPair(NamedTuple): + trading_pair: str + base_asset: str + quote_asset: str diff --git a/hummingbot/connector/exchange/polkadex/__init__.py b/hummingbot/connector/exchange/polkadex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/polkadex/polkadex_api_order_book_data_source.py b/hummingbot/connector/exchange/polkadex/polkadex_api_order_book_data_source.py new file mode 100644 index 0000000..eb003cf --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_api_order_book_data_source.py @@ -0,0 +1,91 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.polkadex import polkadex_constants as CONSTANTS +from hummingbot.connector.exchange.polkadex.polkadex_data_source import PolkadexDataSource +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import OrderBookEvent +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.exchange.polkadex.polkadex_exchange import PolkadexExchange + + +class PolkadexAPIOrderBookDataSource(OrderBookTrackerDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: "PolkadexExchange", + data_source: PolkadexDataSource, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + super().__init__(trading_pairs=trading_pairs) + self._ev_loop = asyncio.get_event_loop() + self._connector = connector + self._data_source = data_source + self._domain = domain + self._forwarders = [] + self._configure_event_forwarders() + + # Initialize message queues that will be accessed by events from a different thread. The lazy initialization + # would break in that case because an async Queue can't be created in a thread that is not running + # the async loop + self._message_queue[self._diff_messages_queue_key] = asyncio.Queue() + self._message_queue[self._trade_messages_queue_key] = asyncio.Queue() + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def listen_for_subscriptions(self): + # The socket reconnection is managed by the data_source. This method should do nothing + pass + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + # Polkadex only sends diff order book messages + raise NotImplementedError + + async def _connected_websocket_assistant(self) -> WSAssistant: + # Polkadex uses GrapQL websockets to consume stream events + raise NotImplementedError + + async def _subscribe_channels(self, ws: WSAssistant): + # Polkadex uses GrapQL websockets to consume stream events + raise NotImplementedError + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + # Polkadex uses GrapQL websockets to consume stream events + return "" + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + snapshot = await self._data_source.order_book_snapshot(market_symbol=symbol, trading_pair=trading_pair) + return snapshot + + async def _parse_trade_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Polkadex 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + async def _parse_order_book_diff_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + # In Polkadex 'raw_message' is not a raw message, but the OrderBookMessage with type Trade created + # by the data source + message_queue.put_nowait(raw_message) + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_order_book_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener( + event_tag=OrderBookEvent.OrderBookDataSourceUpdateEvent, listener=event_forwarder + ) + + event_forwarder = EventForwarder(to_function=self._process_public_trade_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=OrderBookEvent.TradeEvent, listener=event_forwarder) + + def _process_order_book_event(self, order_book_diff: OrderBookMessage): + self._message_queue[self._diff_messages_queue_key].put_nowait(order_book_diff) + + def _process_public_trade_event(self, trade_update: OrderBookMessage): + self._message_queue[self._trade_messages_queue_key].put_nowait(trade_update) diff --git a/hummingbot/connector/exchange/polkadex/polkadex_constants.py b/hummingbot/connector/exchange/polkadex/polkadex_constants.py new file mode 100644 index 0000000..6f65c8f --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_constants.py @@ -0,0 +1,162 @@ +import sys + +from hummingbot.connector.constants import SECOND +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +EXCHANGE_NAME = "polkadex" + +MAX_ID_LEN = 32 +CLIENT_ID_PREFIX = "HBOT" + +DEFAULT_DOMAIN = "" + +GRAPHQL_ENDPOINTS = { + DEFAULT_DOMAIN: "https://yx375ldozvcvthjk2nczch3fhq.appsync-api.eu-central-1.amazonaws.com/graphql", +} +BLOCKCHAIN_URLS = { + DEFAULT_DOMAIN: "wss://polkadex.public.curie.radiumblock.co/ws", +} +POLKADEX_SS58_PREFIX = 88 + +ORDERBOOK_UPDATES_STREAM_NAME = "ob-inc" +RECENT_TRADES_STREAM_NAME = "recent-trades" + +# Rate limit IDs +ORDERBOOK_LIMIT_ID = "Orderbook" +ALL_ASSETS_LIMIT_ID = "AllAssets" +ALL_MARKETS_LIMIT_ID = "AllMarkets" +FIND_USER_LIMIT_ID = "FindUser" +PUBLIC_TRADES_LIMIT_ID = "RecentTrades" +ALL_BALANCES_LIMIT_ID = "AllBalances" +ALL_FILLS_LIMIT_ID = "AllFills" +PLACE_ORDER_LIMIT_ID = "PlaceOrder" +CANCEL_ORDER_LIMIT_ID = "CancelOrder" +BATCH_ORDER_UPDATES_LIMIT_ID = "BatchOrderUpdates" +ORDER_UPDATE_LIMIT_ID = "OrderUpdate" +LIST_OPEN_ORDERS_LIMIT_ID = "ListOpenOrders" + +NO_LIMIT = sys.maxsize + +RATE_LIMITS = [ + RateLimit( + limit_id=ALL_ASSETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=ALL_MARKETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=ORDERBOOK_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=FIND_USER_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=PUBLIC_TRADES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=ALL_BALANCES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=ALL_FILLS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=PLACE_ORDER_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=CANCEL_ORDER_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=BATCH_ORDER_UPDATES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=ORDER_UPDATE_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), + RateLimit( + limit_id=LIST_OPEN_ORDERS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), +] + + +ORDER_STATE = { + "OPEN": OrderState.OPEN, + "CANCELLED": OrderState.CANCELED, + "CLOSED": OrderState.FILLED, +} + + +CUSTOM_TYPES = { + "runtime_id": 1, + "versioning": [], + "types": { + "OrderPayload": { + "type": "struct", + "type_mapping": [ + ["client_order_id", "H256"], + ["user", "AccountId"], + ["main_account", "AccountId"], + ["pair", "String"], + ["side", "OrderSide"], + ["order_type", "OrderType"], + ["quote_order_quantity", "String"], + ["qty", "String"], + ["price", "String"], + ["timestamp", "i64"], + ], + }, + "order_id": "H256", + "OrderSide": { + "type": "enum", + "type_mapping": [ + ["Ask", "Null"], + ["Bid", "Null"], + ], + }, + "OrderType": { + "type": "enum", + "type_mapping": [ + ["LIMIT", "Null"], + ["MARKET", "Null"], + ], + }, + "EcdsaSignature": "[u8; 65]", + "Ed25519Signature": "H512", + "Sr25519Signature": "H512", + "AnySignature": "H512", + "MultiSignature": { + "type": "enum", + "type_mapping": [ + ["Ed25519", "Ed25519Signature"], + ["Sr25519", "Sr25519Signature"], + ["Ecdsa", "EcdsaSignature"], + ], + }, + }, +} + +ORDER_NOT_FOUND_ERROR_CODE = "-32000" +ORDER_NOT_FOUND_MESSAGE = "Order not found" diff --git a/hummingbot/connector/exchange/polkadex/polkadex_data_source.py b/hummingbot/connector/exchange/polkadex/polkadex_data_source.py new file mode 100644 index 0000000..ffbc87c --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_data_source.py @@ -0,0 +1,605 @@ +import asyncio +import json +import logging +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple +from urllib.parse import urlparse + +from bidict import bidict +from gql.transport.appsync_auth import AppSyncJWTAuthentication +from scalecodec import ScaleBytes +from substrateinterface import Keypair, KeypairType, SubstrateInterface + +from hummingbot.connector.exchange.polkadex import polkadex_constants as CONSTANTS, polkadex_utils +from hummingbot.connector.exchange.polkadex.polkadex_query_executor import GrapQLQueryExecutor +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent, OrderBookEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.pubsub import Enum, PubSub +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange_py_base import ExchangePyBase + + +class PolkadexDataSource: + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + def __init__( + self, + connector: "ExchangePyBase", + seed_phrase: str, + domain: Optional[str] = CONSTANTS.DEFAULT_DOMAIN, + trading_required: bool = True, + ): + self._connector = connector + self._domain = domain + self._trading_required = trading_required + graphql_host = CONSTANTS.GRAPHQL_ENDPOINTS[self._domain] + netloc_host = urlparse(graphql_host).netloc + self._keypair = None + self._user_main_address = None + if seed_phrase is not None and len(seed_phrase) > 0: + self._keypair = Keypair.create_from_mnemonic( + seed_phrase, CONSTANTS.POLKADEX_SS58_PREFIX, KeypairType.SR25519 + ) + self._user_proxy_address = self._keypair.ss58_address + self._auth = AppSyncJWTAuthentication(netloc_host, self._user_proxy_address) + else: + self._user_proxy_address = "READ_ONLY" + self._auth = AppSyncJWTAuthentication(netloc_host, "READ_ONLY") + + self._substrate_interface = self._build_substrate_interface() + self._query_executor = GrapQLQueryExecutor(auth=self._auth, domain=self._domain) + + self._publisher = PubSub() + self._last_received_message_time = 0 + # We create a throttler instance here just to have a fully valid instance from the first moment. + # The connector using this data source should replace the throttler with the one used by the connector. + self._throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self._events_listening_tasks = [] + self._assets_map: Dict[str, str] = {} + + self._polkadex_order_type = { + OrderType.MARKET: "MARKET", + OrderType.LIMIT: "LIMIT", + OrderType.LIMIT_MAKER: "LIMIT", + } + self._hummingbot_order_type = { + "LIMIT": OrderType.LIMIT, + "MARKET": OrderType.MARKET, + } + self._polkadex_trade_type = { + TradeType.BUY: "Bid", + TradeType.SELL: "Ask", + } + self._hummingbot_trade_type = { + "Bid": TradeType.BUY, + "Ask": TradeType.SELL, + } + + def is_started(self) -> bool: + return len(self._events_listening_tasks) > 0 + + async def start(self, market_symbols: List[str]): + if len(self._events_listening_tasks) > 0: + raise AssertionError("Polkadex datasource is already listening to events and can't be started again") + + for market_symbol in market_symbols: + self._events_listening_tasks.append( + asyncio.create_task( + self._query_executor.listen_to_orderbook_updates( + events_handler=self._process_order_book_event, market_symbol=market_symbol + ) + ) + ) + self._events_listening_tasks.append( + asyncio.create_task( + self._query_executor.listen_to_public_trades( + events_handler=self._process_recent_trades_event, market_symbol=market_symbol + ) + ) + ) + + if self._trading_required: + self._events_listening_tasks.append( + asyncio.create_task( + self._query_executor.listen_to_private_events( + events_handler=self._process_private_event, address=self._user_proxy_address + ) + ) + ) + main_address = await self.user_main_address() + self._events_listening_tasks.append( + asyncio.create_task( + self._query_executor.listen_to_private_events( + events_handler=self._process_private_event, address=main_address + ) + ) + ) + + async def stop(self): + for task in self._events_listening_tasks: + task.cancel() + self._events_listening_tasks = [] + + def configure_throttler(self, throttler: AsyncThrottlerBase): + self._throttler = throttler + + def last_received_message_time(self) -> float: + return self._last_received_message_time + + def add_listener(self, event_tag: Enum, listener: EventListener): + self._publisher.add_listener(event_tag=event_tag, listener=listener) + + def remove_listener(self, event_tag: Enum, listener: EventListener): + self._publisher.remove_listener(event_tag=event_tag, listener=listener) + + async def exchange_status(self): + all_assets = await self.assets_map() + + if len(all_assets) > 0: + result = NetworkStatus.CONNECTED + else: + result = NetworkStatus.NOT_CONNECTED + + return result + + async def assets_map(self) -> Dict[str, str]: + async with self._throttler.execute_task(limit_id=CONSTANTS.ALL_ASSETS_LIMIT_ID): + all_assets = await self._query_executor.all_assets() + self._assets_map = { + asset["asset_id"]: polkadex_utils.normalized_asset_name( + asset_id=asset["asset_id"], asset_name=asset["name"] + ) + for asset in all_assets["getAllAssets"]["items"] + } + + if len(self._assets_map) > 0: + self._assets_map["polkadex"] = "PDEX" # required due to inconsistent token name in private balance event + + return self._assets_map + + async def symbols_map(self) -> Mapping[str, str]: + symbols_map = bidict() + assets_map = await self.assets_map() + + async with self._throttler.execute_task(limit_id=CONSTANTS.ALL_MARKETS_LIMIT_ID): + markets = await self._query_executor.all_markets() + + for market_info in markets["getAllMarkets"]["items"]: + try: + base_asset, quote_asset = market_info["market"].split("-") + base = assets_map[base_asset] + quote = assets_map[quote_asset] + symbols_map[market_info["market"]] = combine_to_hb_trading_pair(base=base, quote=quote) + except KeyError: + continue + return symbols_map + + async def all_trading_rules(self) -> List[TradingRule]: + async with self._throttler.execute_task(limit_id=CONSTANTS.ALL_MARKETS_LIMIT_ID): + markets = await self._query_executor.all_markets() + + trading_rules = [] + for market_info in markets["getAllMarkets"]["items"]: + try: + exchange_trading_pair = market_info["market"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=exchange_trading_pair + ) + min_order_size = Decimal(market_info["min_order_qty"]) + max_order_size = Decimal(market_info["max_order_qty"]) + min_order_price = Decimal(market_info["min_order_price"]) + amount_increment = Decimal(market_info["qty_step_size"]) + price_increment = Decimal(market_info["price_tick_size"]) + trading_rules.append( + TradingRule( + trading_pair=trading_pair, + min_order_size=min_order_size, + max_order_size=max_order_size, + min_price_increment=price_increment, + min_base_amount_increment=amount_increment, + min_quote_amount_increment=price_increment, + min_notional_size=min_order_size * min_order_price, + min_order_value=min_order_size * min_order_price, + ) + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception(f"Error parsing the trading pair rule: {market_info}. Skipping...") + + return trading_rules + + async def order_book_snapshot(self, market_symbol: str, trading_pair: str) -> OrderBookMessage: + async with self._throttler.execute_task(limit_id=CONSTANTS.ORDERBOOK_LIMIT_ID): + snapshot_data = await self._query_executor.get_orderbook(market_symbol=market_symbol) + + orderbook_entries = snapshot_data["getOrderbook"]["items"] + + timestamp = self._time() + update_id = -1 + bids = [] + asks = [] + + for orderbook_entry in orderbook_entries: + price = Decimal(str(orderbook_entry["p"])) + amount = Decimal(str(orderbook_entry["q"])) + + if orderbook_entry["s"] == "Bid": + bids.append((price, amount)) + else: + asks.append((price, amount)) + + update_id = max(update_id, int(orderbook_entry["stid"])) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=order_book_message_content, + timestamp=timestamp, + ) + + return snapshot_msg + + async def user_main_address(self): + if self._user_main_address is None: + async with self._throttler.execute_task(limit_id=CONSTANTS.FIND_USER_LIMIT_ID): + self._user_main_address = await self._query_executor.main_account_from_proxy( + proxy_account=self._user_proxy_address, + ) + return self._user_main_address + + async def last_price(self, market_symbol: str) -> float: + async with self._throttler.execute_task(limit_id=CONSTANTS.PUBLIC_TRADES_LIMIT_ID): + response = await self._query_executor.recent_trades(market_symbol=market_symbol, limit=1) + last_price = response["getRecentTrades"]["items"][0]["p"] + + return float(last_price) + + async def all_balances(self) -> List[Dict[str, Any]]: + result = [] + assets_map = await self.assets_map() + main_account = await self.user_main_address() + async with self._throttler.execute_task(limit_id=CONSTANTS.ALL_BALANCES_LIMIT_ID): + balances = await self._query_executor.get_all_balances_by_main_account(main_account=main_account) + + for token_balance in balances["getAllBalancesByMainAccount"]["items"]: + try: + balance_info = {} + available_balance = Decimal(token_balance["f"]) + locked_balance = Decimal(token_balance["r"]) + balance_info["token_name"] = assets_map[token_balance["a"]] + balance_info["total_balance"] = available_balance + locked_balance + balance_info["available_balance"] = available_balance + result.append(balance_info) + except KeyError: + continue + return result + + async def place_order( + self, + market_symbol: str, + client_order_id: str, + price: Decimal, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + ) -> Tuple[str, float]: + main_account = await self.user_main_address() + price = self.normalize_fraction(price) + amount = self.normalize_fraction(amount) + timestamp = self._time() + order_parameters = { + "user": self._user_proxy_address, + "main_account": main_account, + "pair": market_symbol, + "qty": f"{amount}", + "price": f"{price}", + "quote_order_quantity": "0", # No need to be 8 decimal points + "timestamp": int(timestamp * 1e3), + "client_order_id": client_order_id, + "order_type": self._polkadex_order_type[order_type], + "side": self._polkadex_trade_type[trade_type], + } + + place_order_request = self._substrate_interface.create_scale_object("OrderPayload").encode(order_parameters) + signature = self._keypair.sign(place_order_request) + + async with self._throttler.execute_task(limit_id=CONSTANTS.PLACE_ORDER_LIMIT_ID): + response = await self._query_executor.place_order( + polkadex_order=order_parameters, + signature={"Sr25519": signature.hex()}, + ) + place_order_data = json.loads(response["place_order"]) + + exchange_order_id = None + if place_order_data["is_success"] is True: + exchange_order_id = place_order_data["body"] + + if exchange_order_id is None: + raise ValueError(f"Error in Polkadex creating order {client_order_id}") + + return exchange_order_id, timestamp + + async def cancel_order(self, order: InFlightOrder, market_symbol: str, timestamp: float) -> OrderState: + try: + cancel_result = await self._place_order_cancel(order=order, market_symbol=market_symbol) + except Exception as e: + if "Order is not active" in str(e): + new_order_state = OrderState.CANCELED + else: + raise + else: + new_order_state = OrderState.PENDING_CANCEL if cancel_result["cancel_order"] else order.current_state + + return new_order_state + + async def order_update(self, order: InFlightOrder, market_symbol: str) -> OrderUpdate: + async with self._throttler.execute_task(limit_id=CONSTANTS.ORDER_UPDATE_LIMIT_ID): + response = await self._query_executor.find_order_by_main_account( + main_account=self._user_proxy_address, + market_symbol=market_symbol, + order_id=order.exchange_order_id, + ) + + order_info = response["findOrderByMainAccount"] + + if order_info is None: + raise IOError(f"Order not found {order.client_order_id} ({order.exchange_order_id})") + + new_state = CONSTANTS.ORDER_STATE[order_info["st"]] + filled_amount = Decimal(order_info["fq"]) + if new_state == OrderState.OPEN and filled_amount > 0: + new_state = OrderState.PARTIALLY_FILLED + order_update = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=order_info["id"], + trading_pair=order.trading_pair, + update_timestamp=self._time(), + new_state=new_state, + ) + return order_update + + async def get_all_fills( + self, from_timestamp: float, to_timestamp: float, orders: List[InFlightOrder] + ) -> List[TradeUpdate]: + trade_updates = [] + + async with self._throttler.execute_task(limit_id=CONSTANTS.ALL_FILLS_LIMIT_ID): + fills = await self._query_executor.get_order_fills_by_main_account( + from_timestamp=from_timestamp, to_timestamp=to_timestamp, main_account=self._user_proxy_address + ) + + exchange_order_id_to_order = {order.exchange_order_id: order for order in orders} + for fill in fills["listTradesByMainAccount"]["items"]: + exchange_trading_pair = fill["m"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=exchange_trading_pair + ) + + price = Decimal(fill["p"]) + size = Decimal(fill["q"]) + order = exchange_order_id_to_order.get(fill["m_id"], None) + if order is None: + order = exchange_order_id_to_order.get(fill["t_id"], None) + if order is not None: + exchange_order_id = order.exchange_order_id + client_order_id = order.client_order_id + + fee = await self._build_fee_for_event(event=fill, trade_type=order.trade_type) + trade_updates.append( + TradeUpdate( + trade_id=fill["trade_id"], + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=int(fill["t"]) * 1e-3, + fill_price=price, + fill_base_amount=size, + fill_quote_amount=price * size, + fee=fee, + ) + ) + + return trade_updates + + async def _place_order_cancel(self, order: InFlightOrder, market_symbol: str) -> Dict[str, Any]: + cancel_request = self._build_substrate_request_with_retries( + type_string="H256", encode_value=order.exchange_order_id + ) + signature = self._keypair.sign(cancel_request) + + async with self._throttler.execute_task(limit_id=CONSTANTS.CANCEL_ORDER_LIMIT_ID): + cancel_result = await self._query_executor.cancel_order( + order_id=order.exchange_order_id, + market_symbol=market_symbol, + main_address=self._user_main_address, + proxy_address=self._user_proxy_address, + signature={"Sr25519": signature.hex()}, + ) + + return cancel_result + + def _build_substrate_request_with_retries( + self, type_string: str, encode_value: Any, retries_left: int = 1 + ) -> ScaleBytes: + try: + request = self._substrate_interface.create_scale_object(type_string=type_string).encode(value=encode_value) + except BrokenPipeError: + self.logger().exception("Rebuilding the substrate interface.") + if retries_left == 0: + raise + self._substrate_interface = self._build_substrate_interface() + request = self._build_substrate_request_with_retries( + type_string=type_string, encode_value=encode_value, retries_left=retries_left - 1 + ) + return request + + def _build_substrate_interface(self) -> SubstrateInterface: + substrate_interface = SubstrateInterface( + url=CONSTANTS.BLOCKCHAIN_URLS[self._domain], + ss58_format=CONSTANTS.POLKADEX_SS58_PREFIX, + type_registry=CONSTANTS.CUSTOM_TYPES, + auto_discover=False, + ) + return substrate_interface + + def _process_order_book_event(self, event: Dict[str, Any], market_symbol: str): + safe_ensure_future(self._process_order_book_event_async(event=event, market_symbol=market_symbol)) + + async def _process_order_book_event_async(self, event: Dict[str, Any], market_symbol: str): + diff_data = json.loads(event["websocket_streams"]["data"]) + timestamp = self._time() + update_id = diff_data["i"] + asks = [(Decimal(price), Decimal(amount)) for price, amount in diff_data["a"].items()] + bids = [(Decimal(price), Decimal(amount)) for price, amount in diff_data["b"].items()] + + order_book_message_content = { + "trading_pair": await self._connector.trading_pair_associated_to_exchange_symbol(symbol=market_symbol), + "update_id": update_id, + "bids": bids, + "asks": asks, + } + diff_message = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content=order_book_message_content, + timestamp=timestamp, + ) + self._publisher.trigger_event(event_tag=OrderBookEvent.OrderBookDataSourceUpdateEvent, message=diff_message) + + def _process_recent_trades_event(self, event: Dict[str, Any]): + safe_ensure_future(self._process_recent_trades_event_async(event=event)) + + async def _process_recent_trades_event_async(self, event: Dict[str, Any]): + trade_data = json.loads(event["websocket_streams"]["data"]) + + exchange_trading_pair = trade_data["m"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=exchange_trading_pair) + timestamp = int(trade_data["t"]) * 1e-3 + trade_type = float(self._hummingbot_trade_type[trade_data["m_side"]].value) + message_content = { + "trade_id": trade_data["trade_id"], + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": Decimal(str(trade_data["q"])), + "price": Decimal(str(trade_data["p"])), + } + trade_message = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content=message_content, + timestamp=timestamp, + ) + self._publisher.trigger_event(event_tag=OrderBookEvent.TradeEvent, message=trade_message) + + def _process_private_event(self, event: Dict[str, Any]): + event_data = json.loads(event["websocket_streams"]["data"]) + + if event_data["type"] == "SetBalance": + safe_ensure_future(self._process_balance_event(event=event_data)) + elif event_data["type"] == "Order": + safe_ensure_future(self._process_private_order_update_event(event=event_data)) + elif event_data["type"] == "TradeFormat": + safe_ensure_future(self._process_private_trade_event(event=event_data)) + + async def _process_balance_event(self, event: Dict[str, Any]): + self._last_received_message_time = self._time() + + assets_map = await self.assets_map() + + asset_name = assets_map[event["asset"]["asset"]] + available_balance = Decimal(event["free"]) + reserved_balance = Decimal(event["reserved"]) + balance_msg = BalanceUpdateEvent( + timestamp=self._time(), + asset_name=asset_name, + total_balance=available_balance + reserved_balance, + available_balance=available_balance, + ) + self._publisher.trigger_event(event_tag=AccountEvent.BalanceEvent, message=balance_msg) + + async def _process_private_order_update_event(self, event: Dict[str, Any]): + self._last_received_message_time = self._time() + + exchange_order_id = event["id"] + base = event["pair"]["base"]["asset"] + quote = event["pair"]["quote"]["asset"] + trading_pair = combine_to_hb_trading_pair(base=self._assets_map[base], quote=self._assets_map[quote]) + fill_amount = Decimal(event["filled_quantity"]) + order_state = CONSTANTS.ORDER_STATE[event["status"]] + + if order_state == OrderState.OPEN and fill_amount > 0: + order_state = OrderState.PARTIALLY_FILLED + order_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=event["stid"], + new_state=order_state, + client_order_id=event["client_order_id"], + exchange_order_id=exchange_order_id, + ) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=order_update) + + async def _process_private_trade_event(self, event: Dict[str, Any]): + exchange_trading_pair = event["m"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=exchange_trading_pair) + price = Decimal(event["p"]) + size = Decimal(event["q"]) + trade_type = self._hummingbot_trade_type[event["s"]] + fee = await self._build_fee_for_event(event=event, trade_type=trade_type) + trade_update = TradeUpdate( + trade_id=event["trade_id"], + client_order_id=event["cid"], + exchange_order_id=event["order_id"], + trading_pair=trading_pair, + fill_timestamp=self._time(), + fill_price=price, + fill_base_amount=size, + fill_quote_amount=price * size, + fee=fee, + ) + + self._publisher.trigger_event(event_tag=MarketEvent.TradeUpdate, message=trade_update) + + async def _build_fee_for_event(self, event: Dict[str, Any], trade_type: TradeType) -> TradeFeeBase: + """Builds a TradeFee object from the given event data.""" + exchange_trading_pair = event["m"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=exchange_trading_pair) + _, quote = split_hb_trading_pair(trading_pair=trading_pair) + fee = TradeFeeBase.new_spot_fee( + fee_schema=self._connector.trade_fee_schema(), + trade_type=trade_type, + percent_token=quote, + flat_fees=[TokenAmount(token=quote, amount=Decimal("0"))], # feels will be zero for the foreseeable future + ) + return fee + + def _time(self): + return time.time() + + @staticmethod + def normalize_fraction(decimal_value: Decimal) -> Decimal: + normalized = decimal_value.normalize() + sign, digit, exponent = normalized.as_tuple() + return normalized if exponent <= 0 else normalized.quantize(1) diff --git a/hummingbot/connector/exchange/polkadex/polkadex_exchange.py b/hummingbot/connector/exchange/polkadex/polkadex_exchange.py new file mode 100644 index 0000000..774ee9b --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_exchange.py @@ -0,0 +1,446 @@ +import asyncio +import math +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from _decimal import Decimal + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.polkadex import polkadex_constants as CONSTANTS, polkadex_web_utils as web_utils +from hummingbot.connector.exchange.polkadex.polkadex_api_order_book_data_source import PolkadexAPIOrderBookDataSource +from hummingbot.connector.exchange.polkadex.polkadex_data_source import PolkadexDataSource +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter # pragma: no cover + + +class PolkadexExchange(ExchangePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + polkadex_seed_phrase: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._domain = domain + self._data_source = PolkadexDataSource( + connector=self, seed_phrase=polkadex_seed_phrase, domain=self._domain, trading_required=trading_required + ) + super().__init__(client_config_map=client_config_map) + self._data_source.configure_throttler(throttler=self._throttler) + self._forwarders = [] + self._configure_event_forwarders() + + @property + def name(self) -> str: + return CONSTANTS.EXCHANGE_NAME + + @property + def authenticator(self) -> AuthBase: + return None + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def domain(self) -> str: + return self._domain + + @property + def client_order_id_max_length(self) -> Optional[int]: + return CONSTANTS.MAX_ID_LEN + + @property + def client_order_id_prefix(self) -> str: + return CONSTANTS.CLIENT_ID_PREFIX + + @property + def trading_rules_request_path(self) -> str: + raise NotImplementedError # pragma: no cover + + @property + def trading_pairs_request_path(self) -> str: + raise NotImplementedError # pragma: no cover + + @property + def check_network_request_path(self) -> str: + raise NotImplementedError # pragma: no cover + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + async def start_network(self): + await super().start_network() + + market_symbols = [ + await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + for trading_pair in self._trading_pairs + ] + await self._data_source.start(market_symbols=market_symbols) + + async def stop_network(self): + """ + This function is executed when the connector is stopped. It performs a general cleanup and stops all background + tasks that require the connection with the exchange to work. + """ + await super().stop_network() + await self._data_source.stop() + + def supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.MARKET] + + async def check_network(self) -> NetworkStatus: + """ + Checks connectivity with the exchange using the API + """ + try: + status = await self._data_source.exchange_status() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + # === Orders placing === + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = get_new_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length + ) + hex_order_id = f"0x{order_id.encode('utf-8').hex()}" + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=hex_order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return hex_order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = get_new_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length + ) + hex_order_id = f"0x{order_id.encode('utf-8').hex()}" + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=hex_order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return hex_order_id + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + # Polkadex does not use a time synchronizer + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return CONSTANTS.ORDER_NOT_FOUND_MESSAGE in str(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return CONSTANTS.ORDER_NOT_FOUND_MESSAGE in str(cancelation_exception) + + async def _execute_order_cancel_and_process_update(self, order: InFlightOrder) -> bool: + new_order_state = await self._place_cancel(order.client_order_id, order) + cancelled = new_order_state in [OrderState.CANCELED, OrderState.PENDING_CANCEL] + if cancelled: + update_timestamp = self.current_timestamp + if update_timestamp is None or math.isnan(update_timestamp): + update_timestamp = self._time() + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=new_order_state, + ) + self._order_tracker.process_order_update(order_update) + return cancelled + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder) -> OrderState: + await tracked_order.get_exchange_order_id() + market_symbol = await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair) + + new_order_state = await self._data_source.cancel_order( + order=tracked_order, market_symbol=market_symbol, timestamp=self.current_timestamp + ) + + return new_order_state + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair) + + result = await self._data_source.place_order( + market_symbol=symbol, + client_order_id=order_id, + price=price, + amount=amount, + trade_type=trade_type, + order_type=order_type, + ) + + return result + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _trading_fees_polling_loop(self): + pass + + async def _update_trading_fees(self): + raise NotImplementedError # pragma: no cover + + async def _user_stream_event_listener(self): + # Not required in Polkadex since all event are processed using the data source PubSub + pass # pragma: no cover + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + # Not used in Polkadex + raise NotImplementedError # pragma: no cover + + async def _update_balances(self): + all_balances = await self._data_source.all_balances() + + self._account_available_balances.clear() + self._account_balances.clear() + + for token_balance_info in all_balances: + self._account_balances[token_balance_info["token_name"]] = token_balance_info["total_balance"] + self._account_available_balances[token_balance_info["token_name"]] = token_balance_info["available_balance"] + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + try: + if len(orders) != 0: + minimum_creation_timestamp = min([order.creation_timestamp for order in orders]) + current_timestamp = self.current_timestamp + trade_updates = await self._data_source.get_all_fills( + from_timestamp=minimum_creation_timestamp, + to_timestamp=current_timestamp, + orders=orders, + ) + + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update=trade_update) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().warning("Error fetching trades updates.") + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + # not used + raise NotImplementedError + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + symbol = await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair) + await tracked_order.get_exchange_order_id() + order_update = await self._data_source.order_update(order=tracked_order, market_symbol=symbol) + return order_update + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return WebAssistantsFactory(throttler=self._throttler) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return PolkadexAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + data_source=self._data_source, + domain=self.domain, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + # Not used in Polkadex + raise NotImplementedError # pragma: no cover + + def _is_user_stream_initialized(self): + # Polkadex does not have private websocket endpoints + return self._data_source.is_started() + + def _create_user_stream_tracker(self): + # Polkadex does not use a tracker for the private streams + return None + + def _create_user_stream_tracker_task(self): + # Polkadex does not use a tracker for the private streams + return None + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + # Not used in Bluefin + raise NotImplementedError() # pragma: no cover + + async def _initialize_trading_pair_symbol_map(self): + exchange_info = None + try: + mapping = await self._data_source.symbols_map() + self._set_trading_pair_symbol_map(mapping) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + return exchange_info + + async def _update_trading_rules(self): + trading_rules_list = await self._data_source.all_trading_rules() + self._trading_rules = {trading_rule.trading_pair: trading_rule for trading_rule in trading_rules_list} + + async def _get_all_pairs_prices(self) -> Dict[str, Any]: + # Polkadex is configured to not be a price provider (check is_price_provider) + # This method should never be called + raise NotImplementedError # pragma: no cover + + async def _get_last_traded_price(self, trading_pair: str) -> float: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + last_price = await self._data_source.last_price(market_symbol=symbol) + return last_price + + def _configure_event_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_user_trade_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_user_order_update) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_balance_event) + self._forwarders.append(event_forwarder) + self._data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=event_forwarder) + + def _process_balance_event(self, event: BalanceUpdateEvent): + self._account_balances[event.asset_name] = event.total_balance + self._account_available_balances[event.asset_name] = event.available_balance + + def _process_user_order_update(self, order_update: OrderUpdate): + tracked_order = self._order_tracker.all_updatable_orders.get(order_update.client_order_id) + + if tracked_order is not None: + self.logger().debug(f"Processing order update {order_update}\nUpdatable order {tracked_order.to_json()}") + order_update_to_process = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=order_update.update_timestamp, + new_state=order_update.new_state, + client_order_id=tracked_order.client_order_id, + exchange_order_id=order_update.exchange_order_id, + ) + self._order_tracker.process_order_update(order_update=order_update_to_process) + + def _process_user_trade_update(self, trade_update: TradeUpdate): + tracked_order = self._order_tracker.all_fillable_orders_by_exchange_order_id.get(trade_update.exchange_order_id) + + if tracked_order is not None: + self.logger().debug(f"Processing trade update {trade_update}\nFillable order {tracked_order.to_json()}") + flat_fees = [ + TokenAmount(amount=flat_fee.amount, token=tracked_order.quote_asset) + for flat_fee in trade_update.fee.flat_fees + ] + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=tracked_order.quote_asset, + flat_fees=flat_fees, + ) + fill_amount = trade_update.fill_base_amount - tracked_order.executed_amount_base + trade_update: TradeUpdate = TradeUpdate( + trade_id=trade_update.trade_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=trade_update.exchange_order_id, + trading_pair=tracked_order.trading_pair, + fill_timestamp=trade_update.fill_timestamp, + fill_price=trade_update.fill_price, + fill_base_amount=fill_amount, + fill_quote_amount=fill_amount * trade_update.fill_price, + fee=fee, + ) + self._order_tracker.process_trade_update(trade_update) diff --git a/hummingbot/connector/exchange/polkadex/polkadex_query_executor.py b/hummingbot/connector/exchange/polkadex/polkadex_query_executor.py new file mode 100644 index 0000000..7b288fd --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_query_executor.py @@ -0,0 +1,507 @@ +import asyncio +import json +import logging +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Any, AsyncIterable, Callable, Dict, Optional + +from gql import Client, gql +from gql.transport.aiohttp import AIOHTTPTransport +from gql.transport.appsync_auth import AppSyncAuthentication +from gql.transport.appsync_websockets import AppSyncWebsocketsTransport +from graphql import DocumentNode + +from hummingbot.connector.exchange.polkadex import polkadex_constants as CONSTANTS +from hummingbot.logger import HummingbotLogger + + +class BaseQueryExecutor(ABC): + @abstractmethod + async def all_assets(self): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def all_markets(self): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def get_orderbook(self, market_symbol: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def main_account_from_proxy(self, proxy_account=str) -> str: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def recent_trades(self, market_symbol: str, limit: int) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def get_all_balances_by_main_account(self, main_account: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def get_order_fills_by_main_account( + self, from_timestamp: float, to_timestamp: float, main_account: str + ) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def place_order(self, polkadex_order: Dict[str, Any], signature: Dict[str, Any]) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def cancel_order( + self, + order_id: str, + market_symbol: str, + proxy_address: str, + main_address: str, + signature: Dict[str, Any], + ) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def list_order_history_by_account( + self, main_account: str, from_time: float, to_time: float + ) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def find_order_by_main_account(self, main_account: str, market_symbol: str, order_id: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def list_open_orders_by_main_account(self, main_account: str) -> Dict[str, Any]: + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def listen_to_orderbook_updates(self, events_handler: Callable, market_symbol: str): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def listen_to_public_trades(self, events_handler: Callable, market_symbol: str): + raise NotImplementedError # pragma: no cover + + @abstractmethod + async def listen_to_private_events(self, events_handler: Callable, address: str): + raise NotImplementedError # pragma: no cover + + +class GrapQLQueryExecutor(BaseQueryExecutor): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + def __init__(self, auth: AppSyncAuthentication, domain: Optional[str] = CONSTANTS.DEFAULT_DOMAIN): + super().__init__() + self._auth = auth + self._domain = domain + + async def all_assets(self): + query = gql( + """ + query GetAllAssets { + getAllAssets { + items { + asset_id + name + } + } + } + """ + ) + + parameters = {} + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def all_markets(self): + query = gql( + """ + query MyQuery { + getAllMarkets { + items { + market + max_order_price + min_order_price + min_order_qty + max_order_qty + price_tick_size + qty_step_size + base_asset_precision + quote_asset_precision + } + } + } + """ + ) + + parameters = {} + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def get_orderbook(self, market_symbol: str) -> Dict[str, Any]: + query = gql( + """ + query getOrderbook($market: String!, $limit: Int, $nextToken: String) { + getOrderbook(market: $market, limit: $limit, nextToken: $nextToken) { + nextToken + items { + p + q + s + stid + } + nextToken + } + } + """ + ) + + parameters = {"market": market_symbol} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def main_account_from_proxy(self, proxy_account=str) -> str: + query = gql( + """ + query findUserByProxyAccount($proxy_account: String!) { + findUserByProxyAccount(proxy_account: $proxy_account) { + items { + hash_key + range_key + stid + } + } + } + """ + ) + parameters = {"proxy_account": proxy_account} + + result = await self._execute_query(query=query, parameters=parameters) + main_account = result["findUserByProxyAccount"]["items"][0]["range_key"] + return main_account + + async def recent_trades(self, market_symbol: str, limit: int) -> Dict[str, Any]: + query = gql( + """ + query getRecentTrades($market: String!, $limit: Int, $nextToken: String) { + getRecentTrades(m: $market, limit: $limit, nextToken: $nextToken) { + items { + isReverted + m + p + q + t + } + } + } + """ + ) + + parameters = {"market": market_symbol, "limit": limit} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def get_all_balances_by_main_account(self, main_account: str) -> Dict[str, Any]: + query = gql( + """ + query GetAllBalancesByMainAccount($main: String!) { + getAllBalancesByMainAccount(main_account: $main) { + items { + a + f + r + } + } + } + """ + ) + + parameters = {"main": main_account} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def get_order_fills_by_main_account( + self, from_timestamp: float, to_timestamp: float, main_account: str + ) -> Dict[str, Any]: + query = gql( + """ + query listTradesByMainAccount( + $main_account:String! + $limit: Int + $from: AWSDateTime! + $to: AWSDateTime! + $nextToken: String + ) { + listTradesByMainAccount( + main_account: $main_account + from: $from + to: $to + limit: $limit + nextToken: $nextToken + ) { + items { + isReverted + m + m_id + p + q + stid + t + t_id + trade_id + } + } + } + """ + ) + + parameters = { + "main_account": main_account, + "from": self._timestamp_to_aws_datetime_string(timestamp=from_timestamp), + "to": self._timestamp_to_aws_datetime_string(timestamp=to_timestamp), + } + + result = await self._execute_query(query=query, parameters=parameters) + + return result + + async def place_order(self, polkadex_order: Dict[str, Any], signature: Dict[str, Any]) -> Dict[str, Any]: + query = gql( + """ + mutation PlaceOrder($payload: String!) { + place_order(input: {payload: $payload}) + } + """ + ) + + input_parameters = [ + polkadex_order, + signature, + ] + parameters = {"payload": json.dumps({"PlaceOrder": input_parameters})} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def cancel_order( + self, + order_id: str, + market_symbol: str, + main_address: str, + proxy_address: str, + signature: Dict[str, Any], + ) -> Dict[str, Any]: + query = gql( + """ + mutation CancelOrder($payload: String!) { + cancel_order(input: {payload: $payload}) + } + """ + ) + + input_parameters = [ + order_id, + main_address, + proxy_address, + market_symbol, + signature, + ] + parameters = {"payload": json.dumps({"CancelOrder": input_parameters})} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def list_order_history_by_account( + self, main_account: str, from_time: float, to_time: float + ) -> Dict[str, Any]: + query = gql( + """ + query ListOrderHistory( + $main_account:String! + $limit: Int + $from: AWSDateTime! + $to: AWSDateTime! + $nextToken: String + ) { + listOrderHistorybyMainAccount( + main_account: $main_account + from: $from + to: $to + limit: $limit + nextToken: $nextToken + ) { + items { + u + cid + id + t + m + s + ot + st + p + q + afp + fq + fee + isReverted + } + } + } + """ + ) + + parameters = { + "main_account": main_account, + "to": self._timestamp_to_aws_datetime_string(timestamp=to_time), + "from": self._timestamp_to_aws_datetime_string(timestamp=from_time), + } + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def find_order_by_main_account(self, main_account: str, market_symbol: str, order_id: str) -> Dict[str, Any]: + query = gql( + """ + query FindOrder($main_account: String!, $market: String!, $order_id: String!) { + findOrderByMainAccount(main_account: $main_account, market: $market, order_id: $order_id) { + afp + cid + fee + fq + id + isReverted + m + ot + p + q + s + st + stid + t + u + } + } + """ + ) + + parameters = { + "main_account": main_account, + "market": market_symbol, + "order_id": order_id, + } + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def list_open_orders_by_main_account(self, main_account: str) -> Dict[str, Any]: + query = gql( + """ + query ListOpenOrdersByMainAccount($main_account: String!, $limit: Int, $nextToken: String) { + listOpenOrdersByMainAccount(main_account: $main_account, limit: $limit, nextToken: $nextToken) { + items { + u + cid + id + t + m + s + ot + st + p + q + afp + fq + fee + stid + isReverted + } + } + } + """ + ) + + parameters = {"main_account": main_account} + + result = await self._execute_query(query=query, parameters=parameters) + return result + + async def listen_to_orderbook_updates(self, events_handler: Callable, market_symbol: str): + while True: + try: + stream_name = f"{market_symbol}-{CONSTANTS.ORDERBOOK_UPDATES_STREAM_NAME}" + async for event in self._subscribe_to_stream(stream_name=stream_name): + events_handler(event=event, market_symbol=market_symbol) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error listening to order book updates from Polkadex", exc_info=True) + + async def listen_to_public_trades(self, events_handler: Callable, market_symbol: str): + while True: + try: + stream_name = f"{market_symbol}-{CONSTANTS.RECENT_TRADES_STREAM_NAME}" + async for event in self._subscribe_to_stream(stream_name=stream_name): + events_handler(event=event) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error listening to public trades from Polkadex", exc_info=True) + + async def listen_to_private_events(self, events_handler: Callable, address: str): + while True: + try: + async for event in self._subscribe_to_stream(stream_name=address): + events_handler(event=event) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error listening to private updates from Polkadex", exc_info=True) + + async def _execute_query( + self, + query: DocumentNode, + parameters: Optional[Dict[str, Any]] = None, + ): + # Extract host from url + url = CONSTANTS.GRAPHQL_ENDPOINTS[self._domain] + + transport = AIOHTTPTransport(url=url, auth=self._auth) + async with Client(transport=transport, fetch_schema_from_transport=False) as session: + result = await session.execute(query, variable_values=parameters, parse_result=True) + + return result + + async def _subscribe_to_stream(self, stream_name: str) -> AsyncIterable: + query = gql( + """ + subscription WebsocketStreamsMessage($name: String!) { + websocket_streams(name: $name) { + data + } + } + """ + ) + variables = {"name": stream_name} + + url = CONSTANTS.GRAPHQL_ENDPOINTS[self._domain] + transport = AppSyncWebsocketsTransport(url=url, auth=self._auth) + + async with Client(transport=transport, fetch_schema_from_transport=False) as session: + async for result in session.subscribe(query, variable_values=variables, parse_result=True): + yield result + + @staticmethod + def _timestamp_to_aws_datetime_string(timestamp: float) -> str: + timestamp_string = datetime.utcfromtimestamp(timestamp).isoformat(timespec="milliseconds") + "Z" + return timestamp_string diff --git a/hummingbot/connector/exchange/polkadex/polkadex_utils.py b/hummingbot/connector/exchange/polkadex/polkadex_utils.py new file mode 100644 index 0000000..afe091f --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_utils.py @@ -0,0 +1,67 @@ +from decimal import Decimal + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +EXAMPLE_PAIR = "PDEX-1" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0"), + taker_percent_fee_decimal=Decimal("0"), +) + + +def normalized_asset_name(asset_id: str, asset_name: str) -> str: + name = asset_name if asset_id.isdigit() else asset_id + name = name.replace("CHAINBRIDGE-", "C") + name = name.replace("TEST DEX", "TDEX") + name = name.replace("TEST BRIDGE", "TBRI") + return name + + +class PolkadexConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="polkadex", const=True, client_data=None) + polkadex_seed_phrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Polkadex seed phrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "polkadex" + + +KEYS = PolkadexConfigMap.construct() + +# Disabling testnet because it breaks. We should enable it back when the issues in the server are solved +# OTHER_DOMAINS = ["polkadex_testnet"] +OTHER_DOMAINS = [] +OTHER_DOMAINS_PARAMETER = {"polkadex_testnet": "testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"polkadex_testnet": EXAMPLE_PAIR} +OTHER_DOMAINS_DEFAULT_FEES = {"polkadex_testnet": DEFAULT_FEES} + + +class PolkadexTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="polkadex_testnet", const=True, client_data=None) + polkadex_seed_phrase: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Polkadex testnet seed phrase", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "polkadex_testnet" + + +OTHER_DOMAINS_KEYS = {"polkadex_testnet": PolkadexTestnetConfigMap.construct()} diff --git a/hummingbot/connector/exchange/polkadex/polkadex_web_utils.py b/hummingbot/connector/exchange/polkadex/polkadex_web_utils.py new file mode 100644 index 0000000..e2fef22 --- /dev/null +++ b/hummingbot/connector/exchange/polkadex/polkadex_web_utils.py @@ -0,0 +1,15 @@ +import time +from typing import Optional + +from hummingbot.connector.exchange.polkadex import polkadex_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN +) -> float: + return _time() * 1e3 + + +def _time() -> float: + return time.time() diff --git a/hummingbot/connector/exchange/vertex/__init__.py b/hummingbot/connector/exchange/vertex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/vertex/dummy.pxd b/hummingbot/connector/exchange/vertex/dummy.pxd new file mode 100644 index 0000000..530a0f1 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/dummy.pxd @@ -0,0 +1,2 @@ +cdef class vertex_exchange_connector(): + pass diff --git a/hummingbot/connector/exchange/vertex/dummy.pyx b/hummingbot/connector/exchange/vertex/dummy.pyx new file mode 100644 index 0000000..530a0f1 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/dummy.pyx @@ -0,0 +1,2 @@ +cdef class vertex_exchange_connector(): + pass diff --git a/hummingbot/connector/exchange/vertex/vertex_api_order_book_data_source.py b/hummingbot/connector/exchange/vertex/vertex_api_order_book_data_source.py new file mode 100644 index 0000000..214abad --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_api_order_book_data_source.py @@ -0,0 +1,174 @@ +import asyncio +from collections import defaultdict +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.vertex import ( + vertex_constants as CONSTANTS, + vertex_utils as utils, + vertex_web_utils as web_utils, +) +from hummingbot.connector.exchange.vertex.vertex_order_book import VertexOrderBook +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.exchange.vertex.vertex_exchange import VertexExchange + + +class VertexAPIOrderBookDataSource(OrderBookTrackerDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: "VertexExchange", + api_factory: Optional[WebAssistantsFactory] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + throttler: Optional[AsyncThrottler] = None, + ): + super().__init__(trading_pairs) + self._connector = connector + self._domain = domain + self._throttler = throttler + self._api_factory = api_factory or web_utils.build_api_factory( + throttler=self._throttler, + ) + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + self._last_ws_message_sent_timestamp = 0 + self._ping_interval = 0 + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp = utils.convert_timestamp(snapshot["data"]["timestamp"]) + snapshot_msg: OrderBookMessage = VertexOrderBook.snapshot_message_from_exchange_rest( + snapshot, snapshot_timestamp, metadata={"trading_pair": trading_pair} + ) + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + product_id = utils.trading_pair_to_product_id(trading_pair, self._connector._exchange_market_info[self._domain]) + params = { + "type": CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE, + "product_id": product_id, + "depth": CONSTANTS.ORDER_BOOK_DEPTH, + } + rest_assistant = await self._api_factory.get_rest_assistant() + + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url(path_url=CONSTANTS.QUERY_PATH_URL, domain=self._domain), + params=params, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE, + ) + return data + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = utils.market_to_trading_pair( + self._connector._exchange_market_info[self._domain][raw_message["product_id"]]["market"] + ) + metadata = {"trading_pair": trading_pair} + trade_message: OrderBookMessage = VertexOrderBook.trade_message_from_exchange(raw_message, metadata=metadata) + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = utils.market_to_trading_pair( + self._connector._exchange_market_info[self._domain][raw_message["product_id"]]["market"] + ) + metadata = {"trading_pair": trading_pair} + order_book_message: OrderBookMessage = VertexOrderBook.diff_message_from_exchange( + raw_message, metadata=metadata + ) + message_queue.put_nowait(order_book_message) + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + + try: + for trading_pair in self._trading_pairs: + product_id = utils.trading_pair_to_product_id( + trading_pair, self._connector._exchange_market_info[self._domain] + ) + trade_payload = { + "method": CONSTANTS.WS_SUBSCRIBE_METHOD, + "stream": {"type": CONSTANTS.TRADE_EVENT_TYPE, "product_id": product_id}, + "id": product_id, + } + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trade_payload) + + order_book_payload = { + "method": CONSTANTS.WS_SUBSCRIBE_METHOD, + "stream": {"type": CONSTANTS.DIFF_EVENT_TYPE, "product_id": product_id}, + "id": product_id, + } + subscribe_order_book_dif_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) + + await websocket_assistant.send(subscribe_trade_request) + await websocket_assistant.send(subscribe_order_book_dif_request) + + self._last_ws_message_sent_timestamp = self._time() + + self.logger().info(f"Subscribed to public trade and order book diff channels of {trading_pair}...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to trading and order book stream...", exc_info=True + ) + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + + if "type" in event_message: + event_channel = event_message.get("type") + if event_channel == CONSTANTS.TRADE_EVENT_TYPE: + channel = self._trade_messages_queue_key + if event_channel == CONSTANTS.DIFF_EVENT_TYPE: + channel = self._diff_messages_queue_key + + return channel + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + while True: + try: + seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) + + await asyncio.wait_for( + super()._process_websocket_messages(websocket_assistant=websocket_assistant), + timeout=seconds_until_next_ping, + ) + except asyncio.TimeoutError: + ping_time = self._time() + await websocket_assistant.ping() + self._last_ws_message_sent_timestamp = ping_time + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws_url = f"{CONSTANTS.WSS_URLS[self._domain]}{CONSTANTS.WS_SUBSCRIBE_PATH_URL}" + + self._ping_interval = CONSTANTS.HEARTBEAT_TIME_INTERVAL + + websocket_assistant: WSAssistant = await self._api_factory.get_ws_assistant() + + await websocket_assistant.connect(ws_url=ws_url, message_timeout=self._ping_interval) + + return websocket_assistant diff --git a/hummingbot/connector/exchange/vertex/vertex_api_user_stream_data_source.py b/hummingbot/connector/exchange/vertex/vertex_api_user_stream_data_source.py new file mode 100644 index 0000000..9684fa6 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_api_user_stream_data_source.py @@ -0,0 +1,115 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.vertex import ( + vertex_constants as CONSTANTS, + vertex_utils as utils, + vertex_web_utils as web_utils, +) +from hummingbot.connector.exchange.vertex.vertex_auth import VertexAuth +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.exchange.vertex.vertex_exchange import VertexExchange + + +class VertexAPIUserStreamDataSource(UserStreamTrackerDataSource): + def __init__( + self, + auth: VertexAuth, + trading_pairs: List[str], + connector: "VertexExchange", + domain: str = CONSTANTS.DEFAULT_DOMAIN, + api_factory: Optional[WebAssistantsFactory] = None, + throttler: Optional[AsyncThrottler] = None, + ): + super().__init__() + self._connector = connector + self._auth: VertexAuth = auth + self._trading_pairs = trading_pairs + self._last_recv_time: float = 0 + self._domain = domain + self._throttler = throttler + self._api_factory = api_factory or web_utils.build_api_factory(throttler=self._throttler, auth=self._auth) + self._ping_interval = 0 + self._last_ws_message_sent_timestamp = 0 + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws_url = f"{CONSTANTS.WSS_URLS[self._domain]}{CONSTANTS.WS_SUBSCRIBE_PATH_URL}" + self._ping_interval = CONSTANTS.HEARTBEAT_TIME_INTERVAL + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=ws_url, message_timeout=self._ping_interval) + return ws + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + try: + for trading_pair in self._trading_pairs: + product_id = utils.trading_pair_to_product_id( + trading_pair, self._connector._exchange_market_info[self._domain] + ) + + fill_payload = { + "method": CONSTANTS.WS_SUBSCRIBE_METHOD, + "stream": { + "type": CONSTANTS.FILL_EVENT_TYPE, + "product_id": product_id, + "subaccount": self._auth.sender_address, + }, + "id": product_id, + } + position_change_payload = { + "method": CONSTANTS.WS_SUBSCRIBE_METHOD, + "stream": { + "type": CONSTANTS.POSITION_CHANGE_EVENT_TYPE, + "product_id": product_id, + "subaccount": self._auth.sender_address, + }, + "id": product_id, + } + + subscribe_fill_request: WSJSONRequest = WSJSONRequest(payload=fill_payload) + subscribe_position_change_request: WSJSONRequest = WSJSONRequest(payload=position_change_payload) + await websocket_assistant.send(subscribe_fill_request) + await websocket_assistant.send(subscribe_position_change_request) + + self._last_ws_message_sent_timestamp = self._time() + + self.logger().info(f"Subscribed to subaccount fill and position change channels of {trading_pair}...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to trading and order book stream...", exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) + await asyncio.wait_for( + super()._process_websocket_messages(websocket_assistant=websocket_assistant, queue=queue), + timeout=seconds_until_next_ping, + ) + except asyncio.TimeoutError: + ping_time = self._time() + await websocket_assistant.ping() + self._last_ws_message_sent_timestamp = ping_time + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if ( + len(event_message) > 0 + and "type" in event_message + and event_message.get("type") in [CONSTANTS.POSITION_CHANGE_EVENT_TYPE, CONSTANTS.FILL_EVENT_TYPE] + ): + queue.put_nowait(event_message) diff --git a/hummingbot/connector/exchange/vertex/vertex_auth.py b/hummingbot/connector/exchange/vertex/vertex_auth.py new file mode 100644 index 0000000..0bab31c --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_auth.py @@ -0,0 +1,89 @@ +import time +from typing import Any, Tuple + +import sha3 +from coincurve import PrivateKey +from eip712_structs import make_domain +from eth_utils import big_endian_to_int + +import hummingbot.connector.exchange.vertex.vertex_constants as CONSTANTS +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +def keccak_hash(x): + return sha3.keccak_256(x).digest() + + +class VertexAuth(AuthBase): + def __init__(self, vertex_arbitrum_address: str, vertex_arbitrum_private_key: str): + self.sender_address = vertex_arbitrum_address + self.private_key = vertex_arbitrum_private_key + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + This method is intended to configure a rest request to be authenticated. Vertex does not use this + functionality. + + :param request: the request to be configured for authenticated interaction + """ + return request # pass-through + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. Vertex does not use this + functionality. + + :param request: the request to be configured for authenticated interaction + """ + return request # pass-through + + def get_referral_code_headers(self): + """ + Generates referral headers when supported by Vertex + + :return: a dictionary of auth headers + """ + headers = {"referer": CONSTANTS.HBOT_BROKER_ID} + return headers + + def sign_payload(self, payload: Any, contract: str, chain_id: int) -> Tuple[str, str]: + """ + Signs the payload using the sender address (address with subaccount identifier) and private key + provided in the configuration. + + :param payload: the payload using EIP712 structure for signature (eg. order, cancel) + :param contract: the market or general contract signing in domain struct creation for Vertex + :param chain_id: the chain used for domain struct creation (NOTE: different for testnet vs mainnet) + + :return: a tuple for both a string hex of the signature of the EIP712 payload and a string hex of + the digest + """ + domain = make_domain(name="Vertex", version=CONSTANTS.VERSION, chainId=chain_id, verifyingContract=contract) + + signable_bytes = payload.signable_bytes(domain) + # Digest for order tracking in Hummingbot + digest = self.generate_digest(signable_bytes) + + pk = PrivateKey.from_hex(self.private_key) + signature = pk.sign_recoverable(signable_bytes, hasher=keccak_hash) + + v = signature[64] + 27 + r = big_endian_to_int(signature[0:32]) + s = big_endian_to_int(signature[32:64]) + + final_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big") + v.to_bytes(1, "big") + return f"0x{final_sig.hex()}", digest + + def generate_digest(self, signable_bytes: bytearray) -> str: + """ + Generates the digest of the payload for use across Vetext lookups + + :param signable_bytes: the bytes of the payload + + :return: a string hex of the keccak_256 of the signable_bytes of the payload + """ + return f"0x{keccak_hash(signable_bytes).hex()}" + + def _time(self): + return time.time() diff --git a/hummingbot/connector/exchange/vertex/vertex_constants.py b/hummingbot/connector/exchange/vertex/vertex_constants.py new file mode 100644 index 0000000..56ca536 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_constants.py @@ -0,0 +1,307 @@ +from typing import Any, Dict + +# A single source of truth for constant variables related to the exchange +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +# The max size of a digest is 66 characters (Vertex uses digests comprable to client order id). +MAX_ORDER_ID_LEN = 66 + +HEARTBEAT_TIME_INTERVAL = 30.0 + +ORDER_BOOK_DEPTH = 100 + +VERSION = "0.0.1" + +EXCHANGE_NAME = "vertex" + +DEFAULT_DOMAIN = "vertex" +TESTNET_DOMAIN = "vertex_testnet" + +QUOTE = "USDC" + +BASE_URLS = { + DEFAULT_DOMAIN: "https://prod.vertexprotocol-backend.com", + TESTNET_DOMAIN: "https://test.vertexprotocol-backend.com", +} + +WSS_URLS = { + DEFAULT_DOMAIN: "wss://prod.vertexprotocol-backend.com", + TESTNET_DOMAIN: "wss://test.vertexprotocol-backend.com", +} + +CONTRACTS = { + DEFAULT_DOMAIN: "0xbbee07b3e8121227afcfe1e2b82772246226128e", + TESTNET_DOMAIN: "0x5956d6f55011678b2cab217cd21626f7668ba6c5", +} + +CHAIN_IDS = { + DEFAULT_DOMAIN: 42161, + TESTNET_DOMAIN: 421613, +} + +HBOT_BROKER_ID = "" + +SIDE_BUY = "BUY" +SIDE_SELL = "SELL" + +TIME_IN_FORCE_GTC = "GTC" # Good till cancelled +TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel +TIME_IN_FORCE_FOK = "FOK" # Fill or kill +TIME_IN_FORCE_POSTONLY = "POSTONLY" # PostOnly + +# API PATHS +POST_PATH_URL = "/execute" +QUERY_PATH_URL = "/query" +INDEXER_PATH_URL = "/indexer" +SYMBOLS_PATH_URL = "/symbols" +WS_PATH_URL = "/ws" +WS_SUBSCRIBE_PATH_URL = "/subscribe" + +# POST METHODS +PLACE_ORDER_METHOD = "place_order" +PLACE_ORDER_METHOD_NO_LEVERAGE = "place_order_no_leverage" +CANCEL_ORDERS_METHOD = "cancel_orders" +CANCEL_ALL_METHOD = "cancel_product_orders" + +# REST QUERY API TYPES +STATUS_REQUEST_TYPE = "status" +ORDER_REQUEST_TYPE = "order" +SUBACCOUNT_INFO_REQUEST_TYPE = "subaccount_info" +MARKET_LIQUIDITY_REQUEST_TYPE = "market_liquidity" +ALL_PRODUCTS_REQUEST_TYPE = "all_products" +MARKET_PRICE_REQUEST_TYPE = "market_price" +FEE_RATES_REQUEST_TYPE = "fee_rates" +CONTRACTS_REQUEST_TYPE = "contracts" +SUBACCOUNT_ORDERS_REQUEST_TYPE = "subaccount_orders" +MAX_WITHDRAWABLE_REQUEST_TYPE = "max_withdrawable" + +# WS API ENDPOINTS +WS_SUBSCRIBE_METHOD = "subscribe" +TOB_TOPIC_EVENT_TYPE = "best_bid_offer" +POSITION_CHANGE_EVENT_TYPE = "position_change" +SNAPSHOT_EVENT_TYPE = "market_liquidity" +TRADE_EVENT_TYPE = "trade" +DIFF_EVENT_TYPE = "book_depth" +FILL_EVENT_TYPE = "fill" +POSITION_CHANGE_EVENT_TYPE = "position_change" + +# Products +# NOTE: Index 7+ is only on testnet +PRODUCTS = { + 0: { + "symbol": "USDC", + "market": None, + DEFAULT_DOMAIN: "0x0000000000000000000000000000000000000000", + TESTNET_DOMAIN: "0x0000000000000000000000000000000000000000", + }, + 1: { + "symbol": "wBTC", + "market": "wBTC/USDC", + DEFAULT_DOMAIN: "0x70e5911371472e406f1291c621d1c8f207764d73", + TESTNET_DOMAIN: "0x939b0915f9c3b657b9e9a095269a0078dd587491", + }, + 2: { + "symbol": "BTC-PERP", + "market": "wBTC/USDC", + DEFAULT_DOMAIN: "0xf03f457a30e598d5020164a339727ef40f2b8fbc", + TESTNET_DOMAIN: "0x291b578ff99bfef1706a2018d9dfdd98773e4f3e", + }, + 3: { + "symbol": "wETH", + "market": "wETH/USDC", + DEFAULT_DOMAIN: "0x1c6281a78aa0ed88949c319cba5f0f0de2ce8353", + TESTNET_DOMAIN: "0x4008c7b762d7000034207bdef628a798065c3dcc", + }, + 4: { + "symbol": "ETH-PERP", + "market": "wETH/USDC", + DEFAULT_DOMAIN: "0xfe653438a1a4a7f56e727509c341d60a7b54fa91", + TESTNET_DOMAIN: "0xe5106c497f8398ee8d1d6d246f08c125245d19ff", + }, + 5: { + "symbol": "ARB", + "market": "ARB/USDC", + DEFAULT_DOMAIN: "0xb6304e9a6ca241376a5fc9294daa8fca65ddcdcd", + TESTNET_DOMAIN: "0x49eff6d3de555be7a039d0b86471e3cb454b35de", + }, + 6: { + "symbol": "ARB-PERP", + "market": "ARB/USDC", + DEFAULT_DOMAIN: "0x01ec802ae0ab1b2cc4f028b9fe6eb954aef06ed1", + TESTNET_DOMAIN: "0xc5f223f12d091fba16141d4eeb5d39c5e0e2577c", + }, + # TESTNET + 7: { + "symbol": "ARB2", + "market": "ARB2/USDC", + DEFAULT_DOMAIN: None, + TESTNET_DOMAIN: "0xf9144ddc09bd6961cbed631f8be708d2d1e87f57", + }, + 8: { + "symbol": "ARB-PERP2", + "market": "ARB2/USDC", + DEFAULT_DOMAIN: None, + TESTNET_DOMAIN: "0xa0c85ffadceba288fbcba1dcb780956c01b25cdf", + }, + 9: { + "symbol": "ARB-PERP2", + "market": "ARB2/USDC", + DEFAULT_DOMAIN: None, + TESTNET_DOMAIN: "0xa0c85ffadceba288fbcba1dcb780956c01b25cdf", + }, + 10: { + "symbol": "ARB-PERP2", + "market": "ARB2/USDC", + DEFAULT_DOMAIN: None, + TESTNET_DOMAIN: "0xa0c85ffadceba288fbcba1dcb780956c01b25cdf", + }, +} + +# OrderStates +ORDER_STATE = { + "PendingNew": OrderState.PENDING_CREATE, + "New": OrderState.OPEN, + "Filled": OrderState.FILLED, + "PartiallyFilled": OrderState.PARTIALLY_FILLED, + "Canceled": OrderState.CANCELED, + "Rejected": OrderState.FAILED, +} + +# Any call increases call rate in ALL pool, so e.g. a query/execute call will contribute to both ALL and query/execute pools. +ALL_ENDPOINTS_LIMIT = "All" +RATE_LIMITS = [ + RateLimit(limit_id=ALL_ENDPOINTS_LIMIT, limit=600, time_interval=10), + RateLimit( + limit_id=INDEXER_PATH_URL, limit=60, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)] + ), + RateLimit( + limit_id=STATUS_REQUEST_TYPE, + limit=60, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=ORDER_REQUEST_TYPE, + limit=60, + time_interval=1, + # NOTE: No weight for weight of 1... + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=SUBACCOUNT_INFO_REQUEST_TYPE, + limit=60, + time_interval=10, + weight=10, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=MARKET_LIQUIDITY_REQUEST_TYPE, + limit=60, + time_interval=1, + # NOTE: No weight for weight of 1... + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=ALL_PRODUCTS_REQUEST_TYPE, + limit=12, + time_interval=1, + weight=5, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=MARKET_PRICE_REQUEST_TYPE, + limit=60, + time_interval=1, + # NOTE: No weight for weight of 1... + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=FEE_RATES_REQUEST_TYPE, + limit=30, + time_interval=1, + weight=2, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=CONTRACTS_REQUEST_TYPE, + limit=60, + time_interval=1, + weight=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=SUBACCOUNT_ORDERS_REQUEST_TYPE, + limit=30, + time_interval=1, + weight=2, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=MAX_WITHDRAWABLE_REQUEST_TYPE, + limit=120, + time_interval=10, + weight=5, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + # NOTE: For spot with no leverage, there are different limits. + # Review https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/place-order + RateLimit( + limit_id=PLACE_ORDER_METHOD, + limit=10, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + RateLimit( + limit_id=PLACE_ORDER_METHOD_NO_LEVERAGE, + limit=5, + time_interval=10, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + # NOTE: We're only providing one (1) digest at a time currently. + # https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/cancel-orders + RateLimit( + limit_id=CANCEL_ORDERS_METHOD, + limit=600, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), + # NOTE: This isn't currently in use. + # https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/cancel-product-orders + RateLimit( + limit_id=CANCEL_ALL_METHOD, + limit=2, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)], + ), +] + +""" +https://vertex-protocol.gitbook.io/docs/developer-resources/api/api-errors +""" +ERRORS: Dict[int, Any] = { + 1000: { + "code": 1000, + "error_value": "RateLimit", + "description": "Too Many Requests: You have exceeded the rate limit. Please reduce your request frequency and try again later.", + "message": "", + }, + 1001: { + "code": 1001, + "error_value": "BlacklistedAddress", + "description": "This address has been blacklisted from accessing the sequencer due to a violation of the Terms of Service. If you believe this is an error, please contact the Vertex team for assistance.", + "message": "", + }, + 1002: { + "code": 1002, + "error_value": "BlockedLocation", + "description": "Access from your current location ({location}) is blocked. Please check your location and try again.", + "message": "", + }, + 1003: { + "code": 1003, + "error_value": "BlockedSubdivision", + "description": "Access from your current location ({location} - {subdivision}) is blocked. Please check your location and try again.", + "message": "", + }, +} diff --git a/hummingbot/connector/exchange/vertex/vertex_eip712_structs.py b/hummingbot/connector/exchange/vertex/vertex_eip712_structs.py new file mode 100644 index 0000000..4d943af --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_eip712_structs.py @@ -0,0 +1,32 @@ +from eip712_structs import Address, Array, Bytes, EIP712Struct, Int, String, Uint + + +class EIP712Domain(EIP712Struct): + name = String() + version = String() + chainId = Uint(256) + verifyingContract = Address() + + +# https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/place-order +class Order(EIP712Struct): + sender = Bytes(32) + priceX18 = Int(128) + amount = Int(128) + expiration = Uint(64) + nonce = Uint(64) + + +# https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/cancel-orders +class Cancellation(EIP712Struct): + sender = Bytes(32) + productIds = Array(Uint(32)) + digests = Array(Bytes(32)) + nonce = Uint(64) + + +# https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/cancel-product-orders +class CancellationProducts(EIP712Struct): + sender = Bytes(32) + productIds = Array(Uint(32)) + nonce = Uint(64) diff --git a/hummingbot/connector/exchange/vertex/vertex_exchange.py b/hummingbot/connector/exchange/vertex/vertex_exchange.py new file mode 100644 index 0000000..4250eab --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_exchange.py @@ -0,0 +1,900 @@ +import asyncio +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.exchange.vertex import ( + vertex_constants as CONSTANTS, + vertex_eip712_structs as vertex_eip712_structs, + vertex_utils as utils, + vertex_web_utils as web_utils, +) +from hummingbot.connector.exchange.vertex.vertex_api_order_book_data_source import VertexAPIOrderBookDataSource +from hummingbot.connector.exchange.vertex.vertex_api_user_stream_data_source import VertexAPIUserStreamDataSource +from hummingbot.connector.exchange.vertex.vertex_auth import VertexAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class VertexExchange(ExchangePyBase): + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + vertex_arbitrum_address: str, + vertex_arbitrum_private_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.sender_address = utils.convert_address_to_sender(vertex_arbitrum_address) + self.private_key = vertex_arbitrum_private_key + self._use_spot_leverage = False + # NOTE: Vertex doesn't submit all balance updates, instead it only updates the product on position change (not cancel) + self.real_time_balance_update = False + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._exchange_market_info = {self._domain: {}} + self._symbols = {} + self._contracts = {} + self._chain_id = CONSTANTS.CHAIN_IDS[self.domain] + super().__init__(client_config_map) + + @staticmethod + def vertex_order_type(order_type: OrderType) -> str: + return order_type.name.upper() + + @staticmethod + def to_hb_order_type(vertex_type: str) -> OrderType: + return OrderType[vertex_type] + + @property + def authenticator(self): + return VertexAuth(vertex_arbitrum_address=self.sender_address, vertex_arbitrum_private_key=self.private_key) + + @property + def name(self) -> str: + return self._domain + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_BROKER_ID + + @property + def trading_rules_request_path(self): + return CONSTANTS.QUERY_PATH_URL + "?type=" + CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE + + @property + def trading_pairs_request_path(self): + return CONSTANTS.QUERY_PATH_URL + "?type=" + CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE + + @property + def check_network_request_path(self): + return CONSTANTS.QUERY_PATH_URL + "?type=" + CONSTANTS.STATUS_REQUEST_TYPE + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + async def start_network(self): + await self.build_exchange_market_info() + await super().start_network() + + def supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory(throttler=self._throttler, auth=self._auth) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return VertexAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return VertexAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + api_factory=self._web_assistants_factory, + connector=self, + domain=self.domain, + ) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + trading_pair = f"{base_currency}-{quote_currency}" + is_maker = is_maker or False + if trading_pair not in self._trading_fees: + fee = build_trade_fee( + exchange=self.name, + is_maker=is_maker, + order_side=order_side, + order_type=order_type, + amount=amount, + price=price, + base_currency=base_currency, + quote_currency=quote_currency, + ) + else: + fee_data = self._trading_fees[trading_pair] + if is_maker: + fee_value = fee_data["maker"] + else: + fee_value = fee_data["taker"] + fee = AddedToCostTradeFee(percent=fee_value) + return fee + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + # NOTE: A positive amount indicates a buy, and a negative amount indicates a sell. + if trade_type == TradeType.SELL: + amount = -amount + + trading_rules = self.trading_rules[trading_pair] + amount_str = utils.convert_to_x18(amount, trading_rules.min_base_amount_increment) + price_str = utils.convert_to_x18(price, trading_rules.min_price_increment) + + if order_type and order_type == OrderType.LIMIT_MAKER: + _order_type = CONSTANTS.TIME_IN_FORCE_POSTONLY + else: + _order_type = CONSTANTS.TIME_IN_FORCE_GTC + + expiration = utils.generate_expiration(time.time(), order_type=_order_type) + product_id = utils.trading_pair_to_product_id(trading_pair, self._exchange_market_info[self._domain]) + nonce = utils.generate_nonce(time.time()) + + contract = self._exchange_market_info[self._domain][product_id]["contract"] + + sender = utils.hex_to_bytes32(self.sender_address) + + order = vertex_eip712_structs.Order( + sender=sender, priceX18=int(price_str), amount=int(amount_str), expiration=int(expiration), nonce=nonce + ) + + signature, digest = self.authenticator.sign_payload(order, contract, self._chain_id) + + place_order = { + "place_order": { + "product_id": product_id, + "order": { + "sender": self.sender_address, + "priceX18": price_str, + "amount": amount_str, + "expiration": expiration, + "nonce": str(nonce), + }, + "signature": signature, + "spot_leverage": self._use_spot_leverage, + } + } + + try: + # NOTE: There are two differen't limits depending on the use of leverage + limit_id = CONSTANTS.PLACE_ORDER_METHOD_NO_LEVERAGE + if self._use_spot_leverage: + limit_id = CONSTANTS.PLACE_ORDER_METHOD + + order_result = await self._api_post(path_url=CONSTANTS.POST_PATH_URL, data=place_order, limit_id=limit_id) + if order_result.get("status") == "failure": + raise Exception(f"Failed to create order {order_result}") + + except IOError: + raise + + o_id = digest + transact_time = int(time.time()) + await self._update_balances() + return o_id, transact_time + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + sender = utils.hex_to_bytes32(self.sender_address) + product_id = utils.trading_pair_to_product_id( + tracked_order.trading_pair, self._exchange_market_info[self._domain] + ) + nonce = utils.generate_nonce(time.time()) + # NOTE: Dynamically adjust this + endpoint_contract = CONSTANTS.CONTRACTS[self.domain] + + if tracked_order.exchange_order_id: + order_id = tracked_order.exchange_order_id + else: + order_id = tracked_order.client_order_id + + order_id_bytes = utils.hex_to_bytes32(order_id) + + cancel = vertex_eip712_structs.Cancellation( + sender=sender, productIds=[int(product_id)], digests=[order_id_bytes], nonce=nonce + ) + signature, digest = self.authenticator.sign_payload(cancel, endpoint_contract, self._chain_id) + + cancel_orders = { + "cancel_orders": { + "tx": { + "sender": self.sender_address, + "productIds": [product_id], + "digests": [order_id], + "nonce": str(nonce), + }, + "signature": signature, + } + } + + cancel_result = await self._api_post( + path_url=CONSTANTS.POST_PATH_URL, data=cancel_orders, limit_id=CONSTANTS.CANCEL_ORDERS_METHOD + ) + await self._update_balances() + if cancel_result.get("status") == "failure": + if cancel_result.get("error_code") and cancel_result["error_code"] == 2020: + # NOTE: This is the most elegant handling outside of passing through restrictive lost order limit to 0 + self._order_tracker._trigger_cancelled_event(tracked_order) + self._order_tracker._trigger_order_completion(tracked_order) + self.logger().warning(f"Marked order canceled as the exchange holds no record: {order_id}") + return True + + if isinstance(cancel_result, dict) and cancel_result["status"] == "success": + return True + return False + + async def _format_trading_rules(self, exchange_info_dict: Dict[int, Any]) -> List[TradingRule]: + """ + Example: + "spot_products": [ + { + "product_id": 1, + "oracle_price_x18": "25741837349502615455138", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0" + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000" + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001477610660740732", + "cumulative_borrows_multiplier_x18": "1005360996332066877", + "total_deposits_normalized": "336131479261252096179100", + "total_borrows_normalized": "106663044719707335242158" + }, + "lp_state": { + "supply": "62623749006749305149587800", + "quote": { + "amount": "90948379767723832838627925", + "last_cumulative_multiplier_x18": "1000000008171891309" + }, + "base": { + "amount": "3549779755052134826620", + "last_cumulative_multiplier_x18": "1001477610660740732" + } + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "41050488980466524595135", + "lp_spread_x18": "3000000000000000" + } + }, + ] + """ + retval = [] + for rule in exchange_info_dict: + try: + if rule == 0: + # NOTE: USDC product doesn't have a market + continue + trading_pair = utils.market_to_trading_pair(self._exchange_market_info[self._domain][rule]["market"]) + rule_set: Dict[str, Any] = exchange_info_dict[rule]["book_info"] + min_order_size = utils.convert_from_x18(rule_set.get("min_size")) + min_price_increment = utils.convert_from_x18(rule_set.get("price_increment_x18")) + min_base_amount_increment = utils.convert_from_x18(rule_set.get("size_increment")) + retval.append( + TradingRule( + trading_pair, + min_order_size=Decimal(min_order_size), + min_price_increment=Decimal(min_price_increment), + min_base_amount_increment=Decimal(min_base_amount_increment), + min_notional_size=Decimal("0.01"), # NOTE: added to ensure proper functioning with strategies. + ) + ) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {rule.get('name')}. Skipping.") + return retval + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + """ + { + "status": "success", + "data": { + "taker_fee_rates_x18": [ + "0", + "300000000000000", + "200000000000000", + "300000000000000", + "200000000000000" + ], + "maker_fee_rates_x18": [ + "0", + "0", + "0", + "0", + "0" + ], + "liquidation_sequencer_fee": "250000000000000000", + "health_check_sequencer_fee": "100000000000000000", + "taker_sequencer_fee": "25000000000000000", + "withdraw_sequencer_fees": [ + "10000000000000000", + "40000000000000", + "0", + "600000000000000", + "0" + ] + } + } + """ + try: + fee_rates = await self._get_fee_rates() + taker_fees = {idx: fee_rate for idx, fee_rate in enumerate(fee_rates["taker_fee_rates_x18"])} + maker_fees = {idx: fee_rate for idx, fee_rate in enumerate(fee_rates["maker_fee_rates_x18"])} + # NOTE: This builds our fee rates based on indexed product_id + for trading_pair in self._trading_pairs: + product_id = utils.trading_pair_to_product_id( + trading_pair=trading_pair, exchange_market_info=self._exchange_market_info[self._domain] + ) + self._trading_fees[trading_pair] = { + "maker": Decimal(utils.convert_from_x18(maker_fees[product_id])), + "taker": Decimal(utils.convert_from_x18(taker_fees[product_id])), + } + except Exception: + # NOTE: If failure to fetch, build default fees + for trading_pair in self._trading_pairs: + self._trading_fees[trading_pair] = { + "maker": utils.DEFAULT_FEES.maker_percent_fee_decimal, + "taker": utils.DEFAULT_FEES.taker_percent_fee_decimal, + } + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are fill and position change events. + """ + + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("type") + + if event_type == CONSTANTS.FILL_EVENT_TYPE: + exchange_order_id = event_message.get("order_digest") + execution_type = ( + OrderState.PARTIALLY_FILLED + if Decimal(utils.convert_from_x18(event_message["remaining_qty"])) > Decimal("0.0") + else OrderState.FILLED + ) + tracked_order = self._order_tracker.fetch_order(exchange_order_id=exchange_order_id) + if tracked_order is not None: + if execution_type in [OrderState.PARTIALLY_FILLED, OrderState.FILLED]: + amount = abs(Decimal(utils.convert_from_x18(event_message["filled_qty"]))) + price = Decimal(utils.convert_from_x18(event_message["price"])) + fee_rate = self._trading_fees[tracked_order.trading_pair]["maker"] + if event_message["is_taker"]: + fee_rate = self._trading_fees[tracked_order.trading_pair]["taker"] + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent=fee_rate, + percent_token="USDC", # NOTE: All fees are denominated in USDC + ) + trade_update = TradeUpdate( + trade_id=str(event_message["timestamp"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(exchange_order_id), + trading_pair=tracked_order.trading_pair, + fee=fee, + fill_base_amount=amount, + fill_quote_amount=amount * price, + fill_price=price, + fill_timestamp=int(event_message["timestamp"]) * 1e-9, + ) + self._order_tracker.process_trade_update(trade_update) + + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=int(event_message["timestamp"]) * 1e-9, + new_state=execution_type, + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(exchange_order_id), + ) + + self._order_tracker.process_order_update(order_update=order_update) + + elif event_type == CONSTANTS.POSITION_CHANGE_EVENT_TYPE: + await self._update_balances() + + except asyncio.CancelledError: + self.logger().error( + f"An Asyncio.CancelledError occurs when process message: {event_message}.", exc_info=True + ) + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + if order.exchange_order_id is not None: + exchange_order_id = order.exchange_order_id + trading_pair = order.trading_pair + product_id = utils.trading_pair_to_product_id(order.trading_pair, self._exchange_market_info[self._domain]) + + matches_response = await self._api_post( + path_url=CONSTANTS.INDEXER_PATH_URL, + data={"matches": {"product_ids": [product_id], "subaccount": self.sender_address}}, + limit_id=CONSTANTS.INDEXER_PATH_URL, + ) + + matches_data = matches_response.get("matches", []) + if matches_data is not None: + for trade in matches_data: + # NOTE: Vertex returns all orders and matches. + if trade["digest"] != order.exchange_order_id: + continue + + exchange_order_id = str(trade["digest"]) + # NOTE: Matches can be composed of multiple trade transactions. + # https://vertex-protocol.gitbook.io/docs/developer-resources/api/indexer-api/matches + submission_idx = str(trade["submission_idx"]) + trade_fee = utils.convert_from_x18(trade["fee"]) + trade_amount = utils.convert_from_x18(trade["order"]["amount"]) + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=TradeType.SELL if Decimal(trade_amount) < s_decimal_0 else TradeType.BUY, + flat_fees=[TokenAmount(amount=Decimal(trade_fee), token="USDC")], + ) + fill_base_amount = utils.convert_from_x18(trade["base_filled"]) + converted_price = utils.convert_from_x18(trade["order"]["priceX18"]) + fill_quote_amount = utils.convert_from_x18(trade["base_filled"]) + # NOTE: Matches can be composed of multiple trade transactions.. + matches_transactions_data = matches_response.get("txs", []) + trade_timestamp = int(time.time()) + for transaction in matches_transactions_data: + if str(transaction["submission_idx"]) != submission_idx: + continue + trade_timestamp = transaction["timestamp"] + break + trade_update = TradeUpdate( + trade_id=submission_idx, + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fee=fee, + fill_base_amount=abs(Decimal(fill_base_amount)), + fill_quote_amount=Decimal(converted_price) * abs(Decimal(fill_quote_amount)), + fill_price=Decimal(converted_price), + fill_timestamp=int(trade_timestamp), + ) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + """ + This requests the order from the live squencer, then if it cannot locate it, it attempts to locate it with the indexer + """ + live_order = True + try: + order_request_response = await self._api_get( + path_url=CONSTANTS.QUERY_PATH_URL, + params={ + "type": CONSTANTS.ORDER_REQUEST_TYPE, + "product_id": utils.trading_pair_to_product_id( + tracked_order.trading_pair, self._exchange_market_info[self._domain] + ), + "digest": tracked_order.exchange_order_id, + }, + limit_id=CONSTANTS.ORDER_REQUEST_TYPE, + ) + if order_request_response.get("status") == "failure": + updated_order_data = { + "status": "failure", + "data": {"unfilled_amount": 100000000000, "amount": 1000000000000}, + } + else: + updated_order_data = order_request_response + except Exception as e: + self.logger().warning(f"Error requesting orders from Vertex sequencer: {e}") + + # NOTE: Try to fetch order details from indexer + if updated_order_data.get("status") == "failure": + live_order = False + try: + data = { + "orders": {"digests": [tracked_order.exchange_order_id]}, + } + indexed_order_data = await self._api_post( + path_url=CONSTANTS.INDEXER_PATH_URL, data=data, limit_id=CONSTANTS.INDEXER_PATH_URL + ) + orders = indexed_order_data.get("orders", []) + if len(orders) > 0: + updated_order_data["data"] = orders[0] + updated_order_data["data"]["unfilled_amount"] = float(updated_order_data["data"]["amount"]) - float( + updated_order_data["data"]["base_filled"] + ) + + except Exception as e: + self.logger().warning(f"Error requesting orders from Vertex indexer: {e}") + + unfilled_amount = Decimal(utils.convert_from_x18(updated_order_data["data"]["unfilled_amount"])) + order_amount = Decimal(utils.convert_from_x18(updated_order_data["data"]["amount"])) + filled_amount = abs(Decimal(order_amount - unfilled_amount)) + + if filled_amount == s_decimal_0: + new_state = OrderState.OPEN + if filled_amount > s_decimal_0: + new_state = OrderState.PARTIALLY_FILLED + # NOTE: Default to canceled if this is queried against indexer + if not live_order: + new_state = OrderState.CANCELED + if unfilled_amount == s_decimal_0: + if live_order: + new_state = OrderState.FILLED + else: + # Override default canceled with complete if complete + new_state = OrderState.COMPLETED + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(tracked_order.exchange_order_id), + trading_pair=tracked_order.trading_pair, + update_timestamp=int(time.time()), + new_state=new_state, + ) + + return order_update + + async def _update_balances(self): + if not self._exchange_market_info[self._domain]: + await self.build_exchange_market_info() + + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + account = await self._get_account() + available_balances = await self._get_account_max_withdrawable() + self._allocated_collateral_sum = s_decimal_0 + + # Loop for all the balances returned for account + for spot_balance in account["spot_balances"]: + try: + product_id = spot_balance["product_id"] + # If we don't have it in our exchange defined list, we don't care + if product_id not in self._exchange_market_info[self._domain] and product_id != 0: + continue + + asset_name = self._exchange_market_info[self._domain][product_id]["symbol"] + total_balance = Decimal(utils.convert_from_x18(spot_balance["balance"]["amount"])) + + available_balance = s_decimal_0 + if product_id in available_balances: + available_balance = available_balances[product_id] + + self._account_available_balances[asset_name] = available_balance + self._account_balances[asset_name] = total_balance + remote_asset_names.add(asset_name) + except Exception as e: + self.logger().warning(f"Balance Error: {spot_balance} {e}") + pass + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def build_exchange_market_info(self): + exchange_info = await self._api_get(path_url=self.trading_pairs_request_path) + symbol_map = await self._get_symbols() + contract_info = await self._get_contracts() + self._exchange_market_info[self._domain] = {} + + symbol_data = {} + for product in symbol_map: + symbol_data.update({product["product_id"]: product["symbol"]}) + + product_data = {} + for product in exchange_info["data"]["spot_products"]: + if product["product_id"] in symbol_data: + try: + product_id = int(product["product_id"]) + # NOTE: Hardcoded USDC + product.update({"symbol": f"{symbol_data[product_id]}"}) + product.update({"market": f"{symbol_data[product_id]}/USDC"}) + product.update({"contract": f"{contract_info[product_id]}"}) + product_data.update({product_id: product}) + except Exception: + pass + + self._exchange_market_info[self._domain] = product_data + return product_data + + async def _make_trading_rules_request(self) -> Any: + return self._exchange_market_info[self._domain] + + async def _initialize_trading_pair_symbol_map(self): + try: + exchange_info = await self.build_exchange_market_info() + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + for product_id in filter(utils.is_exchange_information_valid, exchange_info): + trading_pair = exchange_info[product_id]["market"] + # NOTE: USDC is an asset, however it doesn't have a "market" + if product_id == 0: + continue + base = trading_pair.split("/")[0] + quote = trading_pair.split("/")[1] + mapping[trading_pair] = combine_to_hb_trading_pair(base=base, quote=quote) + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + product_id = utils.trading_pair_to_product_id(trading_pair, self._exchange_market_info[self._domain]) + + try: + data = {"matches": {"product_ids": [product_id], "limit": 5}} + matches_response = await self._api_post( + path_url=CONSTANTS.INDEXER_PATH_URL, + data=data, + limit_id=CONSTANTS.INDEXER_PATH_URL, + ) + matches = matches_response.get("matches", []) + if matches and len(matches) > 0: + last_price = float(utils.convert_from_x18(matches[0]["order"]["priceX18"])) + return last_price + + except Exception as e: + self.logger().warning(f"Failed to get last traded price, using mid price instead, error: {e}") + + params = {"type": CONSTANTS.MARKET_PRICE_REQUEST_TYPE, "product_id": product_id} + resp_json = await self._api_get( + path_url=CONSTANTS.QUERY_PATH_URL, + params=params, + limit_id=CONSTANTS.MARKET_PRICE_REQUEST_TYPE, + ) + trading_rules = self.trading_rules[trading_pair] + mid_price = float( + str( + ( + ( + Decimal(utils.convert_from_x18(resp_json["data"]["bid_x18"])) + + Decimal(utils.convert_from_x18(resp_json["data"]["ask_x18"])) + ) + / Decimal("2.0") + ).quantize(trading_rules.min_price_increment) + ) + ) + return mid_price + + async def _get_account(self): + sender_address = self.sender_address + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.QUERY_PATH_URL, + params={"type": CONSTANTS.SUBACCOUNT_INFO_REQUEST_TYPE, "subaccount": sender_address}, + limit_id=CONSTANTS.SUBACCOUNT_INFO_REQUEST_TYPE, + ) + + if response is None or "failure" in response["status"] or "data" not in response: + if "error_code" in response and response["error_code"] in CONSTANTS.ERRORS: + raise IOError(f"IP address issue from Vertex {response}") + raise IOError(f"Unable to get account info for sender address {sender_address}") + + return response["data"] + + async def _get_symbols(self): + response = await self._api_get(path_url=CONSTANTS.SYMBOLS_PATH_URL) + + if response is None or "status" in response: + raise IOError("Unable to get Vertex symbols") + + self._symbols = response + + return response + + async def _get_account_max_withdrawable(self): + sender_address = self.sender_address + available_balances = {} + trading_pairs = self._trading_pairs + + params = { + "type": CONSTANTS.MAX_WITHDRAWABLE_REQUEST_TYPE, + "product_id": 0, + "sender": sender_address, + "spot_leverage": str(self._use_spot_leverage).lower(), + } + response = await self._api_get(path_url=CONSTANTS.QUERY_PATH_URL, params=params) + + if response is None or "failure" in response["status"] or "data" not in response: + raise IOError(f"Unable to get available balance of product {0} for {sender_address}") + + available_balances.update({0: Decimal(utils.convert_from_x18(response["data"]["max_withdrawable"]))}) + + if len(self._trading_pairs) == 0: + trading_pairs = [] + for product_id in self._exchange_market_info[self._domain]: + if product_id != 0: + trading_pairs.append(self._exchange_market_info[self._domain][product_id]["market"]) + for trading_pair in trading_pairs: + product_id = utils.trading_pair_to_product_id( + trading_pair=trading_pair, exchange_market_info=self._exchange_market_info[self._domain] + ) + params = { + "type": CONSTANTS.MAX_WITHDRAWABLE_REQUEST_TYPE, + "product_id": product_id, + "sender": sender_address, + "spot_leverage": str(self._use_spot_leverage).lower(), + } + response = await self._api_get(path_url=CONSTANTS.QUERY_PATH_URL, params=params) + + if response is None or "failure" in response["status"] or "data" not in response: + raise IOError(f"Unable to get available balance of product {product_id} for {sender_address}") + + available_balances.update( + {product_id: Decimal(utils.convert_from_x18(response["data"]["max_withdrawable"]))} + ) + + return available_balances + + async def _get_contracts(self): + response = await self._api_get( + path_url=CONSTANTS.QUERY_PATH_URL, params={"type": CONSTANTS.CONTRACTS_REQUEST_TYPE} + ) + + if response is None or "failure" in response["status"] or "data" not in response: + raise IOError("Unable to get Vertex contracts") + + # NOTE: List indexed to be matached according to product_id + contracts = response["data"]["book_addrs"] + + self._contracts = contracts + + return contracts + + async def _get_fee_rates(self): + sender_address = self.sender_address + response: Dict[str, Dict[str, Any]] = await self._api_get( + path_url=CONSTANTS.QUERY_PATH_URL, + params={ + "type": CONSTANTS.FEE_RATES_REQUEST_TYPE, + "sender": sender_address, + }, + is_auth_required=False, + limit_id=CONSTANTS.FEE_RATES_REQUEST_TYPE, + ) + + if response is None or "failure" in response["status"] or "data" not in response: + raise IOError(f"Unable to get trading fees sender address {sender_address}") + + return response["data"] + + async def _api_request( + self, + path_url, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + **kwargs, + ) -> Dict[str, Any]: + last_exception = None + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + url = web_utils.public_rest_url(path_url, domain=self.domain) + local_headers = {"Content-Type": "application/json"} + for _ in range(2): + try: + request_result = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + headers=local_headers, + throttler_limit_id=limit_id if limit_id else CONSTANTS.ALL_ENDPOINTS_LIMIT, + ) + return request_result + except IOError as request_exception: + last_exception = request_exception + raise + + # Failed even after the last retry + raise last_exception diff --git a/hummingbot/connector/exchange/vertex/vertex_order_book.py b/hummingbot/connector/exchange/vertex/vertex_order_book.py new file mode 100644 index 0000000..ff88a97 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_order_book.py @@ -0,0 +1,107 @@ +from typing import Any, Dict, Optional + +from hummingbot.connector.exchange.vertex.vertex_utils import convert_from_x18, convert_timestamp +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class VertexOrderBook(OrderBook): + @classmethod + def snapshot_message_from_exchange_websocket( + cls, msg: Dict[str, Any], timestamp: float, metadata: Optional[Dict] = None + ) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["data"]["timestamp"] + return OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + { + "trading_pair": msg["trading_pair"], + "update_id": int(ts), + "bids": convert_from_x18(msg["data"]["bids"]), + "asks": convert_from_x18(msg["data"]["asks"]), + }, + timestamp=timestamp, + ) + + @classmethod + def snapshot_message_from_exchange_rest( + cls, msg: Dict[str, Any], timestamp: float, metadata: Optional[Dict] = None + ) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + if metadata: + msg.update(metadata) + ts = msg["data"]["timestamp"] + return OrderBookMessage( + OrderBookMessageType.SNAPSHOT, + { + "trading_pair": msg["trading_pair"], + "update_id": int(ts), + "bids": convert_from_x18(msg["data"]["bids"]), + "asks": convert_from_x18(msg["data"]["asks"]), + }, + timestamp=timestamp, + ) + + @classmethod + def diff_message_from_exchange( + cls, msg: Dict[str, Any], timestamp: Optional[float] = None, metadata: Optional[Dict] = None + ) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + ts = convert_timestamp(msg["last_max_timestamp"]) + return OrderBookMessage( + OrderBookMessageType.DIFF, + { + "trading_pair": msg["trading_pair"], + "update_id": int(msg["last_max_timestamp"]), + "bids": convert_from_x18(msg["bids"]), + "asks": convert_from_x18(msg["asks"]), + }, + timestamp=ts, + ) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, Any], metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + ts = convert_timestamp(msg["timestamp"]) + return OrderBookMessage( + OrderBookMessageType.TRADE, + { + "trading_pair": msg["trading_pair"], + "trade_type": float(TradeType.BUY.value) if msg["is_taker_buyer"] else float(TradeType.SELL.value), + "trade_id": int(msg["timestamp"]), + "update_id": int(msg["timestamp"]), + "price": convert_from_x18(msg["price"]), + "amount": convert_from_x18(msg["taker_qty"]), + }, + timestamp=ts, + ) diff --git a/hummingbot/connector/exchange/vertex/vertex_utils.py b/hummingbot/connector/exchange/vertex/vertex_utils.py new file mode 100644 index 0000000..81d7fef --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_utils.py @@ -0,0 +1,225 @@ +import numbers +from decimal import Decimal +from random import randint +from typing import Any, Dict, Optional + +from pydantic import Field, SecretStr + +import hummingbot.connector.exchange.vertex.vertex_constants as CONSTANTS +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True +USE_ETHEREUM_WALLET = False +EXAMPLE_PAIR = "WBTC-USDC" +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0"), + taker_percent_fee_decimal=Decimal("0.0002"), +) + + +def hex_to_bytes32(hex_string: str) -> bytes: + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + data_bytes = bytes.fromhex(hex_string) + padded_data = data_bytes + b"\x00" * (32 - len(data_bytes)) + return padded_data + + +def convert_timestamp(timestamp: Any) -> float: + return float(timestamp) / 1e9 + + +def trading_pair_to_product_id(trading_pair: str, exchange_market_info: Dict, is_perp: Optional[bool] = False) -> int: + tp = trading_pair.replace("-", "/") + for product_id in exchange_market_info: + if is_perp and "perp" not in exchange_market_info[product_id]["symbol"].lower(): + continue + if exchange_market_info[product_id]["market"] == tp: + return product_id + return -1 + + +def market_to_trading_pair(market: str) -> str: + """Converts a market symbol from Vertex to a trading pair.""" + return market.replace("/", "-") + + +def convert_from_x18(data: Any, precision: Optional[Decimal] = None) -> Any: + """ + Converts numerical data encoded as x18 to a string representation of a + floating point number, resursively applies the conversion for other data types. + """ + if data is None: + return None + + # Check if data type is str or float + if isinstance(data, str) or isinstance(data, numbers.Number): + data = Decimal(data) / Decimal("1000000000000000000") # type: ignore + if precision: + data = data.quantize(precision) + return str(data) + + if isinstance(data, dict): + for k, v in data.items(): + data[k] = convert_from_x18(v, precision) + elif isinstance(data, list): + for i in range(0, len(data)): + data[i] = convert_from_x18(data[i], precision) + else: + raise TypeError("Data is of unsupported type for convert_from_x18 to process", data) + return data + + +def convert_to_x18(data: Any, precision: Optional[Decimal] = None) -> Any: + """ + Converts numerical data encoded to a string representation of x18, resursively + applies the conversion for other data types. + """ + if data is None: + return None + + # Check if data type is str or float + if isinstance(data, str) or isinstance(data, numbers.Number): + data = Decimal(str(data)) # type: ignore + if precision: + data = data.quantize(precision) + return str((data * Decimal("1000000000000000000")).quantize(Decimal("1"))) + + if isinstance(data, dict): + for k, v in data.items(): + data[k] = convert_to_x18(v, precision) + elif isinstance(data, list): + for i in range(0, len(data)): + data[i] = convert_to_x18(data[i], precision) + else: + raise TypeError("Data is of unsupported type for convert_to_x18 to process", data) + return data + + +def generate_expiration(timestamp: float = None, order_type: Optional[str] = None) -> str: + default_max_time = 8640000000000000 # NOTE: Forever + default_day_time = 86400 + # Default significant bit is 0 for GTC + # https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes/place-order + sig_bit = 0 + + if order_type == CONSTANTS.TIME_IN_FORCE_IOC: + sig_bit = 1 + elif order_type == CONSTANTS.TIME_IN_FORCE_FOK: + sig_bit = 2 + elif order_type == CONSTANTS.TIME_IN_FORCE_POSTONLY: + sig_bit = 3 + + # NOTE: We can setup maxtime + expiration = str(default_max_time | (sig_bit << 62)) + + if timestamp: + unix_epoch = int(timestamp) + expiration = str((unix_epoch + default_day_time) | (sig_bit << 62)) + + return expiration + + +def generate_nonce(timestamp: float, expiry_ms: int = 90) -> int: + unix_epoch_ms = int((timestamp * 1000) + (expiry_ms * 1000)) + nonce = (unix_epoch_ms << 20) + randint(1, 1001) + return nonce + + +def convert_address_to_sender(address: str) -> str: + # NOTE: the sender address includes the subaccount, which is "default" by default, you cannot interact with + # subaccounts outside of default on the UI currently. + # https://vertex-protocol.gitbook.io/docs/developer-resources/api/websocket-rest-api/executes#signing + if isinstance(address, str): + default_12bytes = "64656661756c740000000000" + return address + default_12bytes + raise TypeError("Address must be of type string") + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Default's to true, there isn't anything to check agaisnt. + """ + return True + + +class VertexConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="vertex", const=True, client_data=None) + vertex_arbitrum_private_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Arbitrum private key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + vertex_arbitrum_address: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Arbitrum wallet address", + is_secure=False, + is_connect_key=True, + prompt_on_new=True, + ), + ) + # NOTE: Vertex allows for spot leverage + # vertex_spot_leverage: bool = Field( + # default=False, + # client_data=ClientFieldData( + # prompt=lambda cm: "Enable spot leverage? This auto-borrows assets against your margin to trade with larger size. Set to True to enable borrowing (default: False).", + # is_secure=False, + # is_connect_key=False, + # prompt_on_new=True, + # ), + # ) + + class Config: + title = "vertex" + + +KEYS = VertexConfigMap.construct() + + +class VertexTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="vertex_testnet", client_data=None) + vertex_testnet_arbitrum_private_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Arbitrum TESTNET private key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + vertex_testnet_arbitrum_address: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Arbitrum TESTNET wallet address", + is_secure=False, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + # vertex_testnet_spot_leverage: bool = Field( + # default=False, + # client_data=ClientFieldData( + # prompt=lambda cm: "Enable spot leverage? This auto-borrows assets against your margin to trade with larger size. Set to True to enable borrowing (default: False).", + # is_secure=False, + # is_connect_key=False, + # prompt_on_new=True, + # ), + # ) + + class Config: + title = "vertex_testnet" + + +OTHER_DOMAINS = ["vertex_testnet"] +OTHER_DOMAINS_PARAMETER = {"vertex_testnet": "vertex_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"vertex_testnet": "WBTC-USDC"} +OTHER_DOMAINS_DEFAULT_FEES = {"vertex_testnet": DEFAULT_FEES} + +OTHER_DOMAINS_KEYS = {"vertex_testnet": VertexTestnetConfigMap.construct()} diff --git a/hummingbot/connector/exchange/vertex/vertex_web_utils.py b/hummingbot/connector/exchange/vertex/vertex_web_utils.py new file mode 100644 index 0000000..9e55023 --- /dev/null +++ b/hummingbot/connector/exchange/vertex/vertex_web_utils.py @@ -0,0 +1,48 @@ +import time +from typing import Optional + +import hummingbot.connector.exchange.vertex.vertex_constants as CONSTANTS +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Vertex domain to connect to ("mainnet" or "testnet"). The default value is "mainnet" + + :return: the full URL to the endpoint + """ + return CONSTANTS.BASE_URLS[domain] + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided REST endpoint + + :param path_url: a private REST endpoint + :param domain: the domain to connect to ("main" or "testnet"). The default value is "main" + + :return: the full URL to the endpoint + """ + return public_rest_url(path_url=path_url, domain=domain) + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + api_factory = WebAssistantsFactory(throttler=throttler, auth=auth) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time(throttler: AsyncThrottler, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> float: + server_time = float(time.time()) + return server_time diff --git a/hummingbot/connector/exchange/whitebit/__init__.py b/hummingbot/connector/exchange/whitebit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/whitebit/dummy.pxd b/hummingbot/connector/exchange/whitebit/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/whitebit/dummy.pyx b/hummingbot/connector/exchange/whitebit/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/whitebit/test_whitebit_api_order_book_data_source.py b/hummingbot/connector/exchange/whitebit/test_whitebit_api_order_book_data_source.py new file mode 100644 index 0000000..ee6d2f3 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/test_whitebit_api_order_book_data_source.py @@ -0,0 +1,470 @@ +import asyncio +import json +import re +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.connector.exchange.whitebit.whitebit_api_order_book_data_source import WhitebitAPIOrderBookDataSource +from hummingbot.connector.exchange.whitebit.whitebit_exchange import WhitebitExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook, OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class WhitebitAPIOrderBookDataSourceUnitTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = WhitebitExchange( + client_config_map=client_config_map, + whitebit_api_key="", + whitebit_secret_key="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.data_source = WhitebitAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_BOOK_PATH) + url = url + f"/{self.ex_trading_pair}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = { + "timestamp": 1594391413, + "asks": [ + ["9184.41", "0.773162"], + ], + "bids": [ + ["9181.19", "0.010873"], + ], + } + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = resp["timestamp"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(float(resp["bids"][0][0]), bids[0].price) + self.assertEqual(float(resp["bids"][0][1]), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(float(resp["asks"][0][0]), asks[0].price) + self.assertEqual(float(resp["asks"][0][1]), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_with_only_bids_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_BOOK_PATH) + url = url + f"/{self.ex_trading_pair}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = { + "timestamp": 1594391413, + "bids": [ + ["9181.19", "0.010873"], + ], + } + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = resp["timestamp"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(float(resp["bids"][0][0]), bids[0].price) + self.assertEqual(float(resp["bids"][0][1]), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(0, len(asks)) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_BOOK_PATH) + url = url + f"/{self.ex_trading_pair}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = {"id": 2, "result": {"status": "success"}, "error": None} + result_subscribe_diffs = {"id": 1, "result": {"status": "success"}, "error": None} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_diffs) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_diff_subscription = { + "id": 1, + "method": "depth_subscribe", + "params": [ + self.ex_trading_pair, + 100, + "0", + True, + ], + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[0]) + + expected_trade_subscription = { + "id": 2, + "method": "trades_subscribe", + "params": [ + self.ex_trading_pair, + ], + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged("INFO", "Subscribed to public order book and trade channels...")) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + ) + ) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = {"id": None, "method": CONSTANTS.WHITEBIT_WS_PUBLIC_TRADES_CHANNEL, "params": []} + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue(self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PUBLIC_TRADES_CHANNEL, + "params": [ + self.ex_trading_pair, + [ + { + "id": 41358530, + "time": 1580905394.70332, + "price": "0.020857", + "amount": "5.511", + "type": "buy", + }, + ], + ], + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_event["params"][1][0]["id"], msg.trade_id) + self.assertEqual(float(trade_event["params"][1][0]["time"]), msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = {"id": None, "method": CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL, "params": []} + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange") + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._time") + def test_listen_for_order_book_diffs_successful(self, time_mock): + time_mock.side_effect = [1655749683.225128, 1656749683, 1657749683] + mock_queue = AsyncMock() + diff_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL, + "params": [ + False, + {"asks": [["0.020861", "0"]], "bids": [["0.020844", "5.949"], ["0.020800", "4"]]}, + self.ex_trading_pair, + ], + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(1655749683.225128, msg.timestamp) + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_update_id = 1655749683.225128 * 1e6 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(float(diff_event["params"][1]["bids"][0][0]), bids[0].price) + self.assertEqual(float(diff_event["params"][1]["bids"][0][1]), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(float(diff_event["params"][1]["asks"][0][0]), asks[0].price) + self.assertEqual(float(diff_event["params"][1]["asks"][0][1]), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._time") + def test_listen_for_order_book_diffs_with_only_asks_successful(self, time_mock): + time_mock.side_effect = [1655749683.225128, 1656749683, 1657749683] + mock_queue = AsyncMock() + diff_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL, + "params": [ + False, + { + "asks": [["0.020861", "0"]], + }, + self.ex_trading_pair, + ], + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(1655749683.225128, msg.timestamp) + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_update_id = 1655749683.225128 * 1e6 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(0, len(bids)) + self.assertEqual(1, len(asks)) + self.assertEqual(float(diff_event["params"][1]["asks"][0][0]), asks[0].price) + self.assertEqual(float(diff_event["params"][1]["asks"][0][1]), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._time") + def test_listen_for_order_book_diffs_process_full_snapshot_correctly(self, time_mock): + time_mock.side_effect = [1655749683.225128, 1656749683, 1657749683] + mock_queue = AsyncMock() + + snapshot_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL, + "params": [ + True, + {"asks": [["0.020861", "20"]], "bids": [["0.020844", "5.950"], ["0.020800", "4.1"]]}, + self.ex_trading_pair, + ], + } + + diff_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL, + "params": [ + False, + {"asks": [["0.020861", "0"]], "bids": [["0.020844", "5.949"], ["0.020800", "4"]]}, + self.ex_trading_pair, + ], + } + mock_queue.get.side_effect = [snapshot_event, diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(1655749683.225128, msg.timestamp) + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_update_id = 1655749683.225128 * 1e6 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(float(snapshot_event["params"][1]["bids"][0][0]), bids[0].price) + self.assertEqual(float(snapshot_event["params"][1]["bids"][0][1]), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(float(snapshot_event["params"][1]["asks"][0][0]), asks[0].price) + self.assertEqual(float(snapshot_event["params"][1]["asks"][0][1]), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/test_whitebit_api_user_stream_data_source.py b/hummingbot/connector/exchange/whitebit/test_whitebit_api_user_stream_data_source.py new file mode 100644 index 0000000..e3e8a50 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/test_whitebit_api_user_stream_data_source.py @@ -0,0 +1,305 @@ +import asyncio +import json +from typing import Awaitable, Optional +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.connector.exchange.whitebit.whitebit_api_user_stream_data_source import WhitebitAPIUserStreamDataSource +from hummingbot.connector.exchange.whitebit.whitebit_auth import WhitebitAuth +from hummingbot.connector.exchange.whitebit.whitebit_exchange import WhitebitExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class WhitebitAPIUserStreamDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.trading_pair.replace("-", "_") + cls.api_key = "someKey" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = web_utils.create_throttler() + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = WhitebitAuth(self.api_key, self.api_secret_key, time_provider=self.mock_time_provider) + + self.api_factory = web_utils.build_api_factory() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = WhitebitExchange( + client_config_map=client_config_map, + whitebit_api_key="", + whitebit_secret_key="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.data_source = WhitebitAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_orders_trades_and_balances_events(self, mock_api, ws_connect_mock): + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH) + + resp = {"websocket_token": "test_token"} + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {"id": 0, "result": {"status": "success"}, "error": None} + result_subscribe_balances = {"id": 1, "result": {"status": "success"}, "error": None} + result_subscribe_trades = {"id": 2, "result": {"status": "success"}, "error": None} + result_subscribe_orders = {"id": 3, "result": {"status": "success"}, "error": None} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_auth) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_balances) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_orders) + ) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_auth_message, *sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + expected_auth_message = {"id": 0, "method": "authorize", "params": ["test_token", "public"]} + self.assertEqual(expected_auth_message, sent_auth_message) + + self.assertEqual(3, len(sent_subscription_messages)) + + expected_balances_subscription = { + "id": 1, + "method": "balanceSpot_subscribe", + "params": self.ex_trading_pair.split("_"), + } + self.assertEqual(expected_balances_subscription, sent_subscription_messages[0]) + + expected_trades_subscription = {"id": 2, "method": "deals_subscribe", "params": [[self.ex_trading_pair]]} + self.assertEqual(expected_trades_subscription, sent_subscription_messages[1]) + + expected_orders_subscription = {"id": 3, "method": "ordersPending_subscribe", "params": [self.ex_trading_pair]} + self.assertEqual(expected_orders_subscription, sent_subscription_messages[2]) + + self.assertTrue(self._is_logged("INFO", "Subscribed to private order changes and balance updates channels...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_error_when_auth_token_request_fails(self, mock_api, ws_connect_mock): + request_event = asyncio.Event() + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH) + + resp = {"error": "errorMessage"} + mock_api.post(url, body=json.dumps(resp), status=404, callback=lambda *args, **kwargs: request_event.set()) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + output_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.async_run_with_timeout(request_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_error_when_authentication_fails(self, mock_api, ws_connect_mock): + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH) + + resp = {"websocket_token": "test_token"} + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {"id": 0, "result": {"status": "error"}, "error": "Invalid token"} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_auth) + ) + + output_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(self._is_logged("ERROR", "Error authenticating the private websocket connection")) + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_accepts_only_balance_trades_and_orders_event_messages( + self, mock_api, ws_connect_mock + ): + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH) + + resp = {"websocket_token": "test_token"} + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {"id": 0, "result": {"status": "success"}, "error": None} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_auth) + ) + + invalid_event = { + "id": None, + "method": "invalid_method", + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(invalid_event) + ) + + balance_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PRIVATE_BALANCE_CHANNEL, + "params": [ + {"USDT": {"available": "100.1885", "freeze": "0"}}, + ], + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(balance_event) + ) + + trade_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PRIVATE_TRADES_CHANNEL, + "params": [ + 252104486, + 1602770801.015587, + "BTC_USDT", + 7425988844, + "11399.24", + "0.008256", + "0.094112125440", + "1234", + ], + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(trade_event) + ) + + order_event = { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PRIVATE_ORDERS_CHANNEL, + "params": [ + 2, + { + "id": 621879, + "market": "BTC_USDT", + "type": 1, + "side": 1, + "ctime": 1601475234.656275, + "mtime": 1601475266.733574, + "price": "10646.12", + "amount": "0.01", + "left": "0.008026", + "deal_stock": "0.001974", + "deal_money": "21.01544088", + "deal_fee": "2.101544088", + "client_order_id": "22", + }, + ], + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(order_event) + ) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(3, msg_queue.qsize()) + received_event = msg_queue.get_nowait() + self.assertEqual(balance_event, received_event) + received_event = msg_queue.get_nowait() + self.assertEqual(trade_event, received_event) + received_event = msg_queue.get_nowait() + self.assertEqual(order_event, received_event) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue(self._is_logged("ERROR", "Unexpected error occurred subscribing to user streams...")) \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/test_whitebit_auth.py b/hummingbot/connector/exchange/whitebit/test_whitebit_auth.py new file mode 100644 index 0000000..a3f06c7 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/test_whitebit_auth.py @@ -0,0 +1,114 @@ +import asyncio +import base64 +import hashlib +import hmac +import json +from typing import Any, Awaitable, Dict, Optional, Tuple +from unittest import TestCase +from unittest.mock import MagicMock +from urllib.parse import urlparse + +from hummingbot.connector.exchange.whitebit.whitebit_auth import WhitebitAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class WhitebitAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = WhitebitAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _create_payload_and_signature(self, url: str, request_data: Optional[Dict[str, Any]] = None) -> Tuple[str, str]: + all_params = request_data or {} + + parsed_url = urlparse(url) + path_url = parsed_url.path + + all_params.update( + { + "request": path_url, + "nonce": str(int(self.mock_time_provider.time() * 1e3)), + "nonceWindow": True, + } + ) + + data_json = json.dumps(all_params, separators=(",", ":")) # use separators param for deleting spaces + payload = base64.b64encode(data_json.encode("ascii")) + signature = hmac.new(self.secret_key.encode("ascii"), payload, hashlib.sha512).hexdigest() + + string_payload = payload.decode("ascii") + + return string_payload, signature + + def test_add_auth_headers_to_get_request_without_params(self): + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint", + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + expected_payload, expected_signature = self._create_payload_and_signature(url=request.url) + + self.assertEqual(self.api_key, request.headers["X-TXC-APIKEY"]) + self.assertEqual(expected_payload, request.headers["X-TXC-PAYLOAD"]) + self.assertEqual(expected_signature, request.headers["X-TXC-SIGNATURE"]) + + expected_request_data = { + "request": "/api/endpoint", + "nonce": str(int(self.mock_time_provider.time() * 1e3)), + "nonceWindow": True, + } + self.assertEqual(expected_request_data, json.loads(request.data)) + + def test_add_auth_headers_to_get_request_with_params(self): + request_data = {"ticker": "BTC"} + + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=json.dumps(request_data), + is_auth_required=True, + throttler_limit_id="/api/endpoint", + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + expected_payload, expected_signature = self._create_payload_and_signature( + url=request.url, request_data=request_data + ) + + self.assertEqual(self.api_key, request.headers["X-TXC-APIKEY"]) + self.assertEqual(expected_payload, request.headers["X-TXC-PAYLOAD"]) + self.assertEqual(expected_signature, request.headers["X-TXC-SIGNATURE"]) + + expected_request_data = { + "ticker": "BTC", + "request": "/api/endpoint", + "nonce": str(int(self.mock_time_provider.time() * 1e3)), + "nonceWindow": True, + } + self.assertEqual(expected_request_data, json.loads(request.data)) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/test_whitebit_exchange.py b/hummingbot/connector/exchange/whitebit/test_whitebit_exchange.py new file mode 100644 index 0000000..7b1c33c --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/test_whitebit_exchange.py @@ -0,0 +1,705 @@ +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.connector.exchange.whitebit.whitebit_exchange import WhitebitExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class WhitebitExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret_key = "someSecretKey" + + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_INSTRUMENTS_PATH) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_TICKER_PATH) + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_SERVER_STATUS_PATH) + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_INSTRUMENTS_PATH) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_CREATION_PATH) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_BALANCE_PATH) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "success": True, + "message": None, + "result": [ + { + "name": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "stock": self.base_asset, + "money": self.quote_asset, + "stockPrec": "3", + "moneyPrec": "2", + "feePrec": "4", + "makerFee": "0.001", + "takerFee": "0.001", + "minAmount": "0.001", + "minTotal": "0.001", + "tradesEnabled": True, + } + ], + } + + @property + def latest_prices_request_mock_response(self): + exchange_ticker_id = self.exchange_symbol_for_tokens( + base_token=self.base_asset, quote_token=self.quote_asset + ) + return { + exchange_ticker_id: { + "base_id": 1, + "quote_id": 825, + "last_price": str(self.expected_latest_price), + "quote_volume": "43341942.90416876", + "base_volume": "4723.286463", + "isFrozen": False, + "change": "0.57", + }, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "success": True, + "message": None, + "result": [ + { + "name": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "stock": self.base_asset, + "money": self.quote_asset, + "stockPrec": "3", + "moneyPrec": "2", + "feePrec": "4", + "makerFee": "0.001", + "takerFee": "0.001", + "minAmount": "0.001", + "minTotal": "0.001", + "tradesEnabled": True, + }, + { + "name": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "stock": "INVALID", + "money": "PAIR", + "stockPrec": "3", + "moneyPrec": "2", + "feePrec": "4", + "makerFee": "0.001", + "takerFee": "0.001", + "minAmount": "0.001", + "minTotal": "0.001", + "tradesEnabled": False, + }, + ], + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return ["pong"] + + @property + def trading_rules_request_mock_response(self): + response = { + "success": True, + "message": None, + "result": [ + { + "name": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "stock": self.base_asset, + "money": self.quote_asset, + "stockPrec": "3", + "moneyPrec": "2", + "feePrec": "4", + "makerFee": "0.001", + "takerFee": "0.001", + "minAmount": "0.001", + "minTotal": "0.001", + "tradesEnabled": True, + } + ], + } + return response + + @property + def trading_rules_request_erroneous_mock_response(self): + response = { + "success": True, + "message": None, + "result": [ + { + "name": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "stock": self.base_asset, + "money": self.quote_asset, + "tradesEnabled": True, + } + ], + } + return response + + @property + def order_creation_request_successful_mock_response(self): + return { + "orderId": self.expected_exchange_order_id, + "clientOrderId": "OID1", + "market": self.exchange_symbol_for_tokens(base_token=self.base_asset, quote_token=self.quote_asset), + "side": "buy", + "type": "limit", + "timestamp": 1595792396.165973, + "dealMoney": "0", + "dealStock": "0", + "amount": "1", + "takerFee": "0.001", + "makerFee": "0.001", + "left": "1", + "dealFee": "0", + "price": "10000", + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + self.base_asset: {"available": "10", "freeze": "5"}, + self.quote_asset: {"available": "2000", "freeze": "0"}, + } + + @property + def balance_request_mock_response_only_base(self): + return { + self.base_asset: {"available": "10", "freeze": "5"}, + } + + @property + def balance_event_websocket_update(self): + return { + "id": None, + "method": CONSTANTS.WHITEBIT_WS_PRIVATE_BALANCE_CHANNEL, + "params": [ + {self.base_asset: {"available": "10", "freeze": "5"}}, + ], + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + return TradingRule( + ticker_id=self.ticker_id, + min_order_size=Decimal(self.trading_rules_request_mock_response["result"][0]["minAmount"]), + min_order_value=Decimal(self.trading_rules_request_mock_response["result"][0]["minTotal"]), + max_price_significant_digits=Decimal(self.trading_rules_request_mock_response["result"][0]["moneyPrec"]), + min_base_amount_increment=( + Decimal(1) + / (Decimal(10) ** Decimal(str(self.trading_rules_request_mock_response["result"][0]["stockPrec"]))) + ), + min_quote_amount_increment=( + Decimal(1) + / (Decimal(10) ** Decimal(str(self.trading_rules_request_mock_response["result"][0]["moneyPrec"]))) + ), + min_price_increment=( + Decimal(1) + / (Decimal(10) ** Decimal(str(self.trading_rules_request_mock_response["result"][0]["moneyPrec"]))) + ), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["result"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 4180284841 + + @property + def is_cancel_request_executed_synchronously_by_server(self) -> bool: + return True + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "21" + + @property + def exchange_order_id(self) -> str: + return "4986126152" + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}_{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = WhitebitExchange( + client_config_map=client_config_map, + whitebit_api_key=self.api_key, + whitebit_secret_key=self.api_secret_key, + ticker_ids=[self.ticker_id], + ) + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn("X-TXC-APIKEY", request_headers) + self.assertEqual(self.api_key, request_headers["X-TXC-APIKEY"]) + self.assertIn("X-TXC-PAYLOAD", request_headers) + self.assertIn("X-TXC-SIGNATURE", request_headers) + + data = json.loads(request_call.kwargs["data"]) + self.assertIn("request", data) + self.assertTrue(data["request"].startswith("/api/")) + self.assertIn("nonce", data) + self.assertTrue(data["nonceWindow"]) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["market"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(order.amount, Decimal(request_data["amount"])) + self.assertEqual(order.price, Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["clientOrderId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["market"]) + self.assertEqual(int(order.exchange_order_id), request_data["orderId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + endpoint = request_data["request"] + + if endpoint == f"/{CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH}": + self.assertEqual(order.client_order_id, request_data["clientOrderId"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["market"]) + elif endpoint == f"/{CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH}": + self.assertEqual(str(order.exchange_order_id), request_data["orderId"]) + else: + self.fail() + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(int(order.exchange_order_id), request_data["orderId"]) + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_CANCEL_PATH) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_CANCEL_PATH) + response = {"code": 0, "message": "Validation failed", "errors": {"market": ["Market is not available"]}} + mock_api.post(url, body=json.dumps(response), status=422, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = [] + mock_api.post(regex_url, body=json.dumps(response)) + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # WhiteBit does not provide HTTP updates for canceled orders + pass + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = [] + mock_api.post(regex_url, body=json.dumps(response)) + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = self._order_status_request_open_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH) + regex_url = re.compile(url) + mock_api.post(regex_url, status=404) + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH) + regex_url = re.compile(url) + mock_api.post(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH) + regex_url = re.compile(url) + response = [] + mock_api.post(regex_url, body=json.dumps(response)) + return url + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_TRADES_PATH) + regex_url = re.compile(url) + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_TRADES_PATH) + regex_url = re.compile(url) + mock_api.post(regex_url, status=400, callback=callback) + return url + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_TRADES_PATH) + regex_url = re.compile(url) + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "id": None, + "method": "ordersPending_update", + "params": [ + 1, # 1 is for new orders + { + "id": int(order.exchange_order_id) or 621879, + "market": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "type": 1, + "side": 2, + "ctime": 1601475234.656275, + "mtime": 1601475266.733574, + "price": str(order.price), + "amount": str(order.amount), + "left": str(order.amount), + "deal_stock": "0", + "deal_money": "0", + "deal_fee": "0", + "client_order_id": order.client_order_id, + }, + ], + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "id": None, + "method": "ordersPending_update", + "params": [ + 3, # 3 is both for canceled and executed orders + { + "id": int(order.exchange_order_id) or 621879, + "market": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "type": 1, + "side": 2, + "ctime": 1601475234.656275, + "mtime": 1601475266.733574, + "price": str(order.price), + "amount": str(order.amount), + "left": str(order.amount), + "deal_stock": "0", + "deal_money": "0", + "deal_fee": "0", + "client_order_id": order.client_order_id, + }, + ], + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "id": None, + "method": "ordersPending_update", + "params": [ + 3, # 3 is both for canceled and executed orders + { + "id": int(order.exchange_order_id) or 621879, + "market": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "type": 1, + "side": 2, + "ctime": 1601475234.656275, + "mtime": 1601475266.733574, + "price": str(order.price), + "amount": str(order.amount), + "left": "0", + "deal_stock": str(order.amount), + "deal_money": str(order.amount * order.price), + "deal_fee": str(self.expected_fill_fee.flat_fees[0].amount), + "client_order_id": order.client_order_id, + }, + ], + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "id": None, + "method": "deals_update", + "params": [ + int(self.expected_fill_trade_id), + 1602770801.015587, + self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + int(order.exchange_order_id) or 7425988844, + str(order.price), + str(order.amount), + str(self.expected_fill_fee.flat_fees[0].amount), + order.client_order_id, + ], + } + + @aioresponses() + def test_update_order_status_when_canceled(self, mock_api): + # WhiteBit does not provide HTTP updates for canceled orders + pass + + @aioresponses() + def test_order_fills_error_response_when_no_fills_is_ignored(self, mock_api): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id=self.exchange_order_id, + ticker_id=self.ticker_id, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_TRADES_PATH) + regex_url = re.compile(url) + + response = { + "response": None, + "status": 422, + "errors": {"orderId": ["Finished order id 97773367680 not found on your account"]}, + "notification": None, + "warning": "Finished order id 97773367680 not found on your account", + "_token": None, + } + mock_api.post(regex_url, body=json.dumps(response)) + + order_fills = self.async_run_with_timeout(self.exchange._request_order_fills(order=order)) + + self.assertEquals(0, len(order_fills)) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "orderId": order.exchange_order_id or "dummyExchangeOrderId", + "clientOrderId": order.client_order_id, + "market": self.exchange_symbol_for_tokens(base_token=order.base_asset, quote_token=order.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "timestamp": 1595792396.165973, + "dealMoney": "0", + "dealStock": "0", + "amount": str(order.amount), + "takerFee": "0.001", + "makerFee": "0.001", + "left": str(order.amount), + "dealFee": "0", + "price": str(order.price), + "activation_price": "0", + } + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.balance_url + mock_api.post( + re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")), body=json.dumps(response), callback=callback + ) + return url + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder): + symbol = self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset) + return { + symbol: [ + { + "amount": str(order.amount), + "price": str(order.price), + "type": "limit", + "id": order.exchange_order_id or 4986126152, + "clientOrderId": order.client_order_id, + "side": order.trade_type.name.lower(), + "ctime": 1597486960.311311, + "takerFee": "0.001", + "ftime": 1597486960.311332, + "makerFee": "0.001", + "dealFee": str(self.expected_fill_fee.flat_fees[0].amount), + "dealStock": str(order.amount), + "dealMoney": str(order.price), + }, + ] + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "records": [ + { + "time": 1593342324.613711, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "price": str(order.price), + "amount": str(order.amount), + "id": int(order.exchange_order_id), + "dealOrderId": self.expected_fill_trade_id, + "clientOrderId": order.client_order_id, + "role": 2, + "deal": str(order.amount * order.price), + } + ], + "offset": 0, + "limit": 100, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "orderId": order.exchange_order_id or 3686033640, + "clientOrderId": order.client_order_id, + "market": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "timestamp": 1594605801.49815, + "dealMoney": "0", + "dealStock": "0", + "amount": str(order.amount), + "takerFee": "0.001", + "makerFee": "0.001", + "left": str(order.amount), + "dealFee": "0", + "price": str(order.price), + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "orderId": order.exchange_order_id or 3686033640, + "clientOrderId": order.client_order_id, + "market": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "timestamp": 1594605801.49815, + "dealMoney": str(self.expected_partial_fill_price), + "dealStock": str(self.expected_partial_fill_amount), + "amount": str(order.amount), + "takerFee": "0.001", + "makerFee": "0.001", + "left": str(order.amount - self.expected_partial_fill_amount), + "dealFee": str(self.expected_fill_fee.flat_fees[0].amount), + "price": str(order.price), + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "records": [ + { + "time": 1593342324.613711, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "price": str(self.expected_partial_fill_price), + "amount": str(self.expected_partial_fill_amount), + "id": int(order.exchange_order_id), + "dealOrderId": self.expected_fill_trade_id, + "clientOrderId": order.client_order_id, + "role": 2, + "deal": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + } + ], + "offset": 0, + "limit": 100, + } \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/whitebit_api_order_book_data_source.py b/hummingbot/connector/exchange/whitebit/whitebit_api_order_book_data_source.py new file mode 100644 index 0000000..66ce98e --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_api_order_book_data_source.py @@ -0,0 +1,172 @@ +import asyncio +import logging +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from .whitebit_exchange import WhitebitExchange + + +class WhitebitAPIOrderBookDataSource(OrderBookTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__( + self, + trading_pairs: List[str], + connector: "WhitebitExchange", + api_factory: WebAssistantsFactory, + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + WhiteBit connector sends a full order book snapshot as the first message of the depth channel. + It is not required to request more snapshots apart from that if the channel is not disconnected. + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created snapshot messages + """ + pass + + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_data: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + snapshot_timestamp: int = int(snapshot_data["timestamp"]) + update_id: int = snapshot_timestamp + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in snapshot_data.get("bids", [])], + "asks": [(ask[0], ask[1]) for ask in snapshot_data.get("asks", [])], + } + snapshot_msg: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.SNAPSHOT, order_book_message_content, snapshot_timestamp + ) + + return snapshot_msg + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + market = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + url = web_utils.public_rest_url(path_url=CONSTANTS.WHITEBIT_ORDER_BOOK_PATH) + url = url + f"/{market}" + + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=url, + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.WHITEBIT_ORDER_BOOK_PATH, + ) + + return data + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + symbol, trade_updates = raw_message["params"] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=symbol) + + for trade_data in trade_updates: + message_content = { + "trade_id": trade_data["id"], + "trading_pair": trading_pair, + "trade_type": float(TradeType.BUY.value) + if trade_data["type"] == "buy" + else float(TradeType.SELL.value), + "amount": trade_data["amount"], + "price": trade_data["price"], + } + trade_message: Optional[OrderBookMessage] = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, content=message_content, timestamp=float(trade_data["time"]) + ) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + is_full_snapshot, depth_update, symbol = raw_message["params"] + order_book_message_type = OrderBookMessageType.SNAPSHOT if is_full_snapshot else OrderBookMessageType.DIFF + + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=symbol) + timestamp: float = self._time() + update_id: int = int(timestamp * 1e6) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": [(bid[0], bid[1]) for bid in depth_update.get("bids", [])], + "asks": [(ask[0], ask[1]) for ask in depth_update.get("asks", [])], + } + diff_message: OrderBookMessage = OrderBookMessage( + order_book_message_type, order_book_message_content, timestamp + ) + + message_queue.put_nowait(diff_message) + + async def _subscribe_channels(self, ws: WSAssistant): + all_symbols = [] + try: + for trading_pair_enumeration_number, trading_pair in enumerate(self._trading_pairs): + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + all_symbols.append(symbol) + + payload = { + "id": trading_pair_enumeration_number + 1, + "method": "depth_subscribe", + "params": [symbol, 100, "0", True], + } + subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_REQUEST_LIMIT_ID): + await ws.send(subscribe_orderbook_request) + + payload = {"id": len(all_symbols) + 1, "method": "trades_subscribe", "params": all_symbols} + subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_REQUEST_LIMIT_ID): + await ws.send(subscribe_trade_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + event_channel = event_message.get("method") + if event_channel == CONSTANTS.WHITEBIT_WS_PUBLIC_TRADES_CHANNEL: + channel = self._trade_messages_queue_key + elif event_channel == CONSTANTS.WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL: + channel = self._diff_messages_queue_key + + return channel + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_CONNECTION_LIMIT_ID): + await ws.connect(ws_url=CONSTANTS.WHITEBIT_WS_URI) + return ws \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/whitebit_api_user_stream_data_source.py b/hummingbot/connector/exchange/whitebit/whitebit_api_user_stream_data_source.py new file mode 100644 index 0000000..048cb4d --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_api_user_stream_data_source.py @@ -0,0 +1,120 @@ +import asyncio +import logging +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.connector.exchange.whitebit.whitebit_auth import WhitebitAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse, RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from .whitebit_exchange import WhitebitExchange + +class WhitebitAPIUserStreamDataSource(UserStreamTrackerDataSource): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__( + self, + auth: WhitebitAuth, + trading_pairs: List[str], + connector: "WhitebitExchange", + api_factory: WebAssistantsFactory, + ): + super().__init__() + self._auth = auth + self._trading_pairs = trading_pairs + self._connector = connector + self._api_factory = api_factory + + async def _connected_websocket_assistant(self) -> WSAssistant: + self.logger().info("Starting _connected_websocket_assistant method") + self.logger().info("REST assistant obtained") + + try: + token_response = await self._auth.get_websocket_token() + self.logger().info(f"Token response received: {token_response}") + + if "websocket_token" not in token_response: + self.logger().error(f"Websocket token not in response: {token_response}") + raise IOError(f"Could not get an authentication token for private websocket ({token_response})") + + token = token_response["websocket_token"] + self.logger().info(f"Websocket token obtained: {token}") + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + self.logger().info("WS assistant obtained, connecting to WebSocket") + await ws.connect(ws_url=CONSTANTS.WHITEBIT_WS_URI) + self.logger().info("WebSocket connected") + + auth_payload = {"id": 0, "method": "authorize", "params": [token, "public"]} + login_request: WSJSONRequest = WSJSONRequest(payload=auth_payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_REQUEST_LIMIT_ID): + await ws.send(login_request) + self.logger().info("Authorization request sent") + + response: WSResponse = await ws.receive() + self.logger().info(f"Authorization response received: {response.data}") + message = response.data + if message.get("result", {}).get("status") != "success": + self.logger().error(f"Error authenticating the private websocket connection: {message}") + raise IOError(f"Private websocket connection authentication failed ({message})") + + self.logger().info("WebSocket connection authenticated successfully") + return ws + except Exception as e: + self.logger().exception(f"Error in _connected_websocket_assistant: {str(e)}") + raise + + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to order events and balance events. + + :param ws: the websocket assistant used to connect to the exchange + """ + tokens = set() + symbols = list() + + for trading_pair in self._trading_pairs: + tokens.update(trading_pair.split("-")) + symbols.append(await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair)) + + try: + balance_payload = {"id": 1, "method": "balanceSpot_subscribe", "params": sorted(list(tokens))} + subscribe_balance_request: WSJSONRequest = WSJSONRequest(payload=balance_payload) + + trades_payload = {"id": 2, "method": "deals_subscribe", "params": [symbols]} + subscribe_trades_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) + + orders_payload = {"id": 3, "method": "ordersPending_subscribe", "params": symbols} + subscribe_orders_request: WSJSONRequest = WSJSONRequest(payload=orders_payload) + + await websocket_assistant.send(subscribe_balance_request) + await websocket_assistant.send(subscribe_trades_request) + await websocket_assistant.send(subscribe_orders_request) + + self._last_ws_message_sent_timestamp = self._time() + self.logger().info("Subscribed to private order changes and balance updates channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to user streams...") + raise + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0 and event_message.get("method") in [ + CONSTANTS.WHITEBIT_WS_PRIVATE_BALANCE_CHANNEL, + CONSTANTS.WHITEBIT_WS_PRIVATE_TRADES_CHANNEL, + CONSTANTS.WHITEBIT_WS_PRIVATE_ORDERS_CHANNEL, + ]: + queue.put_nowait(event_message) \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/whitebit_auth.py b/hummingbot/connector/exchange/whitebit/whitebit_auth.py new file mode 100644 index 0000000..49a7acd --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_auth.py @@ -0,0 +1,130 @@ +import base64 +import hashlib +import hmac +import json +import time +import requests +from typing import Dict, Optional +import logging +from urllib.parse import urlparse + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest, RESTMethod +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS, whitebit_web_utils as web_utils +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.logger import HummingbotLogger + +class WhitebitAuth(AuthBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key: str = api_key + self.secret_key: str = secret_key + self.time_provider: TimeSynchronizer = time_provider + self._nonce_creator = NonceCreator.for_milliseconds() + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + + :param request: the request to be configured for authenticated interaction + """ + self._add_authentication_details(request=request) + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + Configures a WebSocket request to be authenticated using the retrieved token. + """ + # Assuming the WebSocket token is stored in the class after retrieval + token = self.websocket_token + + # Construct the payload for the "authorize" request + payload = { + "id": 0, # Unique ID for the request + "method": "authorize", + "params": [token, "public"] + } + + # Update the WebSocket request with the authorization payload + request.payload = payload + + return request + + + def _add_authentication_details(self, request: RESTRequest) -> RESTRequest: + timestamp = self.time_provider.time() + nonce = self._nonce_creator.get_tracking_nonce(timestamp=timestamp) + parsed_url = urlparse(request.url) + path_url = parsed_url.path + + authentication_params = { + "request": path_url, + "nonce": str(nonce), + "nonceWindow": True, + } + + params = json.loads(request.data) if request.data is not None else {} + params.update(authentication_params) + + data_json = json.dumps(params, separators=(",", ":")) + payload = base64.b64encode(data_json.encode("ascii")) + signature = hmac.new(self.secret_key.encode("ascii"), payload, hashlib.sha512).hexdigest() + + header = request.headers or {} + header.update( + { + "X-TXC-APIKEY": self.api_key, + "X-TXC-PAYLOAD": payload.decode("ascii"), + "X-TXC-SIGNATURE": signature, + } + ) + + request.headers = header + request.data = data_json + + return request + + async def get_websocket_token(self) -> Dict: + nonce = time.time_ns() // 1_000_000 # Unix timestamp in milliseconds + self.logger().info(f"Nonce generated: {nonce}") + + payload = { + "request": "/api/v4/profile/websocket_token", + "nonce": nonce + } + + data_json = json.dumps(payload, separators=(',', ':')) + base64_payload = base64.b64encode(data_json.encode('ascii')) + signature = hmac.new(self.secret_key.encode('ascii'), base64_payload, hashlib.sha512).hexdigest() + + headers = { + "Content-Type": "application/json", + "X-TXC-APIKEY": self.api_key, + "X-TXC-PAYLOAD": base64_payload.decode('ascii'), + "X-TXC-SIGNATURE": signature, + } + self.logger().info(f"Headers prepared: {headers}") + + complete_url = "https://whitebit.com/api/v4/profile/websocket_token" # Complete URL + + # Use requests.post to send the request + response = requests.post(complete_url, headers=headers, data=data_json) + self.logger().info(f"Response received: Status={response.status_code}, Body={response.text}") + + if response.status_code != 200: + self.logger().error(f"Failed to get WebSocket token. Status={response.status_code}, Response={response.text}") + raise IOError(f"Failed to get WebSocket token: {response.text}") + + return response.json() + + diff --git a/hummingbot/connector/exchange/whitebit/whitebit_constants.py b/hummingbot/connector/exchange/whitebit/whitebit_constants.py new file mode 100644 index 0000000..6d8aa01 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_constants.py @@ -0,0 +1,138 @@ +import sys + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +DEFAULT_DOMAIN = "com" + +# REST endpoints +WHITEBIT_BASE_URL = "https://whitebit.com/" + +# Public Endpoints (from market.py and API documentation) +WHITEBIT_SERVER_STATUS_PATH = "api/v4/public/ping" +WHITEBIT_SERVER_TIME_PATH = "api/v4/public/time" +WHITEBIT_INSTRUMENTS_PATH = "api/v4/public/markets" +WHITEBIT_TICKER_PATH = "api/v4/public/ticker" +WHITEBIT_ORDER_BOOK_PATH = "api/v4/public/orderbook" +WHITEBIT_TRADES_PATH = "api/v4/public/trades/" # Added from market.py +WHITEBIT_EXCHANGE_INFO_PATH = "api/v4/public/markets" + +# Private Endpoints (from order.py and API documentation) +WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH = "api/v4/profile/websocket_token" +WHITEBIT_BALANCE_PATH = "api/v4/trade-account/balance" +WHITEBIT_ORDER_CREATION_PATH = "api/v4/order/new" +WHITEBIT_ORDER_CANCEL_PATH = "api/v4/order/cancel" +WHITEBIT_ACTIVE_ORDER_STATUS_PATH = "api/v4/orders" +WHITEBIT_EXECUTED_ORDER_STATUS_PATH = "api/v4/trade-account/order/history" +WHITEBIT_ORDER_TRADES_PATH = "api/v4/trade-account/order" + +# Additional endpoints from SDK +WHITEBIT_LIMIT_ORDER_PATH = "api/v4/order/new" +WHITEBIT_MARKET_ORDER_PATH = "api/v4/order/market" + +ORDER_FILLS_REQUEST_INVALID_ORDER_ID_ERROR_CODE = 422 + +# WS endpoints +WHITEBIT_WS_URI = "wss://api.whitebit.com/ws" + +WS_CONNECTION_LIMIT_ID = "WSConnection" +WS_REQUEST_LIMIT_ID = "WSRequest" +WHITEBIT_WS_PUBLIC_BOOKS_CHANNEL = "depth_update" +WHITEBIT_WS_PUBLIC_TRADES_CHANNEL = "trades_update" +WHITEBIT_WS_PRIVATE_BALANCE_CHANNEL = "balanceSpot_update" +WHITEBIT_WS_PRIVATE_TRADES_CHANNEL = "deals_update" +WHITEBIT_WS_PRIVATE_ORDERS_CHANNEL = "ordersPending_update" + +MINUTE = 60 +NO_LIMIT = sys.maxsize +MAX_REQUESTS_LIMIT = 100 +WHITEBIT_GENERAL_RATE_LIMIT = "HTTPRequestGlobalLimit" + +RATE_LIMITS = [ + RateLimit(WS_CONNECTION_LIMIT_ID, limit=100, time_interval=MINUTE), + RateLimit(WS_REQUEST_LIMIT_ID, limit=NO_LIMIT, time_interval=1), + RateLimit(WHITEBIT_GENERAL_RATE_LIMIT, limit=MAX_REQUESTS_LIMIT, time_interval=1), + RateLimit( + WHITEBIT_SERVER_STATUS_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_SERVER_TIME_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_INSTRUMENTS_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_TICKER_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_ORDER_BOOK_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_BALANCE_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_ORDER_CREATION_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_ORDER_CANCEL_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_ACTIVE_ORDER_STATUS_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_EXECUTED_ORDER_STATUS_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_ORDER_TRADES_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_WS_AUTHENTICATION_TOKEN_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_LIMIT_ORDER_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), + RateLimit( + WHITEBIT_MARKET_ORDER_PATH, + limit=NO_LIMIT, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(WHITEBIT_GENERAL_RATE_LIMIT)], + ), +] \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/whitebit_exchange.py b/hummingbot/connector/exchange/whitebit/whitebit_exchange.py new file mode 100644 index 0000000..64384b5 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_exchange.py @@ -0,0 +1,532 @@ +import asyncio +import logging +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.exchange.whitebit import ( + whitebit_constants as CONSTANTS, + whitebit_utils as utils, + whitebit_web_utils as web_utils, +) +from hummingbot.connector.exchange.whitebit.whitebit_api_order_book_data_source import WhitebitAPIOrderBookDataSource +from hummingbot.connector.exchange.whitebit.whitebit_api_user_stream_data_source import WhitebitAPIUserStreamDataSource +from hummingbot.connector.exchange.whitebit.whitebit_auth import WhitebitAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class WhitebitExchange(ExchangePyBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + whitebit_api_key: str, + whitebit_secret_key: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ): + self._api_key = whitebit_api_key + self._secret_key = whitebit_secret_key + self._trading_required = trading_required + self._trading_pairs = trading_pairs + super().__init__(client_config_map) + + @property + def authenticator(self): + return WhitebitAuth(api_key=self._api_key, secret_key=self._secret_key, time_provider=self._time_synchronizer) + + @property + def name(self) -> str: + return "whitebit" + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return CONSTANTS.DEFAULT_DOMAIN + + @property + def client_order_id_max_length(self): + return None + + @property + def client_order_id_prefix(self): + return "HBOT-" + + @property + def trading_rules_request_path(self): + return CONSTANTS.WHITEBIT_INSTRUMENTS_PATH + + @property + def trading_pairs_request_path(self): + return CONSTANTS.WHITEBIT_INSTRUMENTS_PATH + + @property + def check_network_request_path(self): + return CONSTANTS.WHITEBIT_SERVER_STATUS_PATH + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + """ + Return a dictionary the trading_pair as key and the current price as value for each trading pair passed as + parameter + + :param trading_pairs: list of trading pairs to get the prices for + + :return: Dictionary of associations between token pair and its latest price + """ + last_prices = {} + response = await self._api_get(path_url=CONSTANTS.WHITEBIT_TICKER_PATH) + for market_symbol, ticker_info in response.items(): + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=market_symbol) + if trading_pair in trading_pairs: + last_prices[trading_pair] = float(ticker_info["last_price"]) + + return last_prices + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # Not required for this connectors + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + exchange_order_id = await tracked_order.get_exchange_order_id() + + params = { + "market": await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair), + "orderId": int(exchange_order_id), + } + + cancel_result = await self._api_post( + path_url=CONSTANTS.WHITEBIT_ORDER_CANCEL_PATH, + data=params, + is_auth_required=True, + ) + + if len(cancel_result.get("errors", {})) > 0: + raise IOError(f"Error canceling order {order_id} ({cancel_result})") + + return True + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + + market = await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + self.logger().debug(f"Placing order on Whitebit: Market={market}, Order ID={order_id}") + + # Determine the correct endpoint and parameters based on the order type + if order_type == OrderType.LIMIT: + endpoint = CONSTANTS.WHITEBIT_LIMIT_ORDER_PATH + data = { + "market": market, + "side": trade_type.name.lower(), + "amount": str(amount), + "price": str(price), + "clientOrderId": order_id + } + elif order_type == OrderType.MARKET: + endpoint = CONSTANTS.WHITEBIT_MARKET_ORDER_PATH + data = { + "market": market, + "side": trade_type.name.lower(), + "amount": str(amount), + "clientOrderId": order_id + } + else: + raise ValueError(f"Unsupported order type: {order_type}") + + self.logger().debug(f"Order data: {data}") + + # Send the order request + try: + response = await self._api_post(path_url=endpoint, data=data, is_auth_required=True) + # Check for success in response + if "orderId" in response: + # Order placement successful + self.logger().info(f"Order placed successfully: {response}") + return response["orderId"], response.get("timestamp", ...) + else: + # Handle specific API error responses + if "code" in response and "message" in response: + error_code = response["code"] + error_message = response["message"] + detailed_errors = response.get("errors", {}) + full_error_msg = f"API Error {error_code}: {error_message}. Details: {detailed_errors}" + self.logger().error(full_error_msg) + raise IOError(full_error_msg) + else: + raise IOError(f"Unexpected response from API: {response}") + except Exception as e: + # Handle any other exceptions + self.logger().exception(f"Failed to place order on Whitebit: {e}") + raise + + + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> AddedToCostTradeFee: + is_maker = is_maker or (order_type in [OrderType.LIMIT, OrderType.MARKET]) + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _update_trading_fees(self): + pass + + async def _user_stream_event_listener(self): + async for stream_message in self._iter_user_event_queue(): + try: + channel = stream_message.get("method") + + if channel == CONSTANTS.WHITEBIT_WS_PRIVATE_TRADES_CHANNEL: + event_message = { + "time": stream_message["params"][1], + "fee": stream_message["params"][6], + "price": stream_message["params"][4], + "amount": stream_message["params"][5], + "id": stream_message["params"][0], + "dealOrderId": stream_message["params"][3], + "clientOrderId": stream_message["params"][7], + } + event_message["deal"] = str( + Decimal(str(event_message["amount"])) * Decimal(str(event_message["price"])) + ) + + order = self._order_tracker.all_fillable_orders.get(event_message["clientOrderId"]) + if order is not None: + trade_update = self._create_trade_update(trade_msg=event_message, order=order) + self._order_tracker.process_trade_update(trade_update) + + elif channel == CONSTANTS.WHITEBIT_WS_PRIVATE_ORDERS_CHANNEL: + update_event_id, event_message = stream_message["params"] + executed_amount = Decimal(str(event_message["deal_stock"])) + + if update_event_id in [1, 2]: + order_state = OrderState.OPEN + elif update_event_id == 3 and executed_amount == s_decimal_0: + order_state = OrderState.CANCELED + else: + order_state = OrderState.FILLED + + event_message["order_state"] = order_state + client_order_id = str(event_message.get("clientOrderId", event_message.get("client_order_id"))) + order = self._order_tracker.all_updatable_orders.get(client_order_id) + if order is not None: + order_update = self._create_order_update(order_msg=event_message, order=order) + self._order_tracker.process_order_update(order_update) + + elif channel == CONSTANTS.WHITEBIT_WS_PRIVATE_BALANCE_CHANNEL: + for data in stream_message.get("params", []): + for token, balance_info in data.items(): + available = Decimal(str(balance_info["available"])) + frozen = Decimal(str(balance_info["freeze"])) + self._account_balances[token] = available + frozen + self._account_available_balances[token] = available + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _format_trading_rules(self, exchange_info: Dict[str, Any]) -> List[TradingRule]: + trading_rules = [] + exchange_info = await self._api_get(path_url=CONSTANTS.WHITEBIT_EXCHANGE_INFO_PATH) + + for info in exchange_info: + try: + symbol = info["name"] + symbol_map = await self.trading_pair_symbol_map() + if symbol not in symbol_map: + # self.logger().warning(f"Skipping trading pair not found in symbol map: {symbol}") + continue + + trading_pair = symbol_map[symbol] + min_order_size = Decimal(info["minAmount"]) + min_order_value = Decimal(info["minTotal"]) + price_precision = Decimal(info["moneyPrec"]) + quantity_precision = Decimal(info["stockPrec"]) + + # Create a TradingRule object with the extracted information + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=min_order_size, + min_order_value=min_order_value, + max_price_significant_digits=price_precision, + min_base_amount_increment=Decimal(1) / (Decimal(10) ** quantity_precision), + min_quote_amount_increment=Decimal(1) / (Decimal(10) ** price_precision), + min_price_increment=Decimal(1) / (Decimal(10) ** price_precision), + ) + trading_rules.append(trading_rule) + except Exception as e: + self.logger().exception(f"Error parsing trading pair information from Whitebit: {e}") + continue + + return trading_rules + + + async def _request_order_fills(self, order: InFlightOrder): + order_fills = [] + if order.exchange_order_id is not None: + pagination_limit = 100 + pagination_offset = 0 + has_more_pages = True + + while has_more_pages: + has_more_pages = False + + fills_result = await self._api_post( + path_url=CONSTANTS.WHITEBIT_ORDER_TRADES_PATH, + data={ + "orderId": int(order.exchange_order_id), + "limit": pagination_limit, + "offset": pagination_offset, + }, + is_auth_required=True, + ) + + is_invalid_order_id = ( + fills_result.get("status") != CONSTANTS.ORDER_FILLS_REQUEST_INVALID_ORDER_ID_ERROR_CODE + ) + has_warnings = f"Finished order id {order.exchange_order_id}" in fills_result.get("warning", "") + if is_invalid_order_id and not has_warnings: + order_fills.extend(fills_result["records"]) + + if len(fills_result["records"]) == pagination_limit: + has_more_pages = True + pagination_offset += pagination_limit + + return order_fills + + async def _request_order_update(self, order: InFlightOrder) -> List[Dict[str, Any]]: + result = [] + symbol = await self.exchange_symbol_associated_to_pair(order.trading_pair) + active_order_result = await self._api_post( + path_url=CONSTANTS.WHITEBIT_ACTIVE_ORDER_STATUS_PATH, + data={"market": symbol, "clientOrderId": order.client_order_id}, + is_auth_required=True, + ) + + if type(active_order_result) == dict: + active_order_result = [active_order_result] + + for active_order_status in active_order_result: + active_order_status["order_state"] = OrderState.OPEN + result.append(active_order_status) + + if order.exchange_order_id is not None: + executed_order_result = await self._api_post( + path_url=CONSTANTS.WHITEBIT_EXECUTED_ORDER_STATUS_PATH, + data={"orderId": str(order.exchange_order_id)}, + is_auth_required=True, + ) + if len(executed_order_result) > 0: + # If the order is not executed, the result is an empty list. Otherwise, it is a dictionary + for executed_order_status in executed_order_result[symbol]: + executed_order_status["order_state"] = OrderState.FILLED + result.append(executed_order_status) + + return result + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + try: + if order.exchange_order_id is not None: + all_fills_response = await self._request_order_fills(order=order) + for trade_fill in all_fills_response: + trade_update = self._create_trade_update(trade_msg=trade_fill, order=order) + trade_updates.append(trade_update) + except asyncio.CancelledError: + raise + except Exception as ex: + is_error_caused_by_unexistent_order = '"code":50005' in str(ex) + if not is_error_caused_by_unexistent_order: + raise + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._request_order_update(order=tracked_order) + if len(updated_order_data) > 0: + order_update = self._create_order_update(order_msg=updated_order_data[-1], order=tracked_order) + else: + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=tracked_order.current_state, + ) + return order_update + + def _create_trade_update(self, trade_msg: Dict[str, Any], order: InFlightOrder): + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=order.quote_asset, + flat_fees=[TokenAmount(amount=Decimal(trade_msg["fee"]), token=order.quote_asset)], + ) + trade_update = TradeUpdate( + trade_id=str(trade_msg["id"]), + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=Decimal(trade_msg["amount"]), + fill_quote_amount=Decimal(trade_msg["amount"]) * Decimal(trade_msg["price"]), + fill_price=Decimal(trade_msg["price"]), + fill_timestamp=float(trade_msg["time"]), + ) + return trade_update + + def _create_order_update(self, order_msg: Dict[str, Any], order: InFlightOrder): + client_order_id = str(order_msg.get("clientOrderId", order_msg.get("client_order_id"))) + state = order_msg["order_state"] # The state is added in the dict before calling this method + if state == OrderState.OPEN and order.executed_amount_base > s_decimal_0: + state = OrderState.PARTIALLY_FILLED + order_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=float( + order_msg.get("timestamp", order_msg.get("ftime", order_msg.get("mtime", self.current_timestamp))) + ), + new_state=state, + client_order_id=client_order_id, + exchange_order_id=str(order_msg.get("id", order_msg.get("orderId"))), + ) + return order_update + + async def _update_balances(self): + response = await self._api_post(path_url=CONSTANTS.WHITEBIT_BALANCE_PATH, is_auth_required=True) + + self._account_available_balances.clear() + self._account_balances.clear() + + for token, balance_details in response.items(): + self._account_balances[token] = Decimal(balance_details["available"]) + Decimal(balance_details["freeze"]) + self._account_available_balances[token] = Decimal(balance_details["available"]) + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, time_synchronizer=self._time_synchronizer, auth=self._auth + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return WhitebitAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, connector=self, api_factory=self._web_assistants_factory + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return WhitebitAPIUserStreamDataSource( + auth=self._auth, trading_pairs=self._trading_pairs, connector=self, api_factory=self._web_assistants_factory + ) + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: List[Any]): + # Define the pair you are interested in + target_pair = "KLC_USDT" + + # Initialize an empty mapping + mapping = bidict() + + for symbol_data in exchange_info: + # Check if the current symbol data is for the target pair + if symbol_data["name"] == target_pair: + try: + # Combine base and quote to create a Hummingbot trading pair + hb_trading_pair = combine_to_hb_trading_pair( + base=symbol_data["stock"], quote=symbol_data["money"] + ) + + # Add the target trading pair to the mapping + mapping[target_pair] = hb_trading_pair + self.logger().info(f"Added trading pair mapping: {target_pair} -> {hb_trading_pair}") + + # Since we found our target pair, we don't need to process further + break + + except Exception as e: + self.logger().exception(f"Error processing symbol_data {symbol_data}: {e}") + + # Set the trading pair symbol map if the target pair was added + if mapping: + self._set_trading_pair_symbol_map(mapping) + else: + self.logger().error(f"Target trading pair {target_pair} was not added to the symbol map.") + + + diff --git a/hummingbot/connector/exchange/whitebit/whitebit_order_book.py b/hummingbot/connector/exchange/whitebit/whitebit_order_book.py new file mode 100644 index 0000000..360775e --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_order_book.py @@ -0,0 +1,62 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import ( + OrderBookMessage, + OrderBookMessageType +) + + +class WhitebitOrderBook(OrderBook): + + @classmethod + def snapshot_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message. + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "trading_pair": msg["ticker_id"], + "update_id": msg.get("trade_timestamp", timestamp), + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange(cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book. + """ + if metadata: + msg.update(metadata) + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg["ticker_id"], + "update_id": msg.get("trade_timestamp", timestamp), + "bids": msg["bids"], + "asks": msg["asks"] + }, timestamp=timestamp) + + @classmethod + def trade_message_from_exchange(cls, msg: Dict[str, any], metadata: Optional[Dict] = None): + """ + Creates a trade message with the information from the trade event. + """ + if metadata: + msg.update(metadata) + trade_type = TradeType.SELL.value if msg["type"] == "sell" else TradeType.BUY.value + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg["ticker_id"], + "trade_type": float(trade_type), + "trade_id": msg["tradeID"], + "update_id": msg["trade_timestamp"], + "price": msg["price"], + "amount": msg["base_volume"] + }, timestamp=msg["trade_timestamp"] * 1e-3) diff --git a/hummingbot/connector/exchange/whitebit/whitebit_utils.py b/hummingbot/connector/exchange/whitebit/whitebit_utils.py new file mode 100644 index 0000000..5140706 --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_utils.py @@ -0,0 +1,56 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field +from pydantic.types import SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.001"), + taker_percent_fee_decimal=Decimal("0.001"), +) + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + return exchange_info.get("tradesEnabled", False) + + +class WhitebitConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="whitebit", const=True, client_data=None) + whitebit_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your WhiteBit API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + whitebit_secret_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your WhiteBit secret key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ), + ) + + class Config: + title = "whitebit" + + +KEYS = WhitebitConfigMap.construct() \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit/whitebit_web_utils.py b/hummingbot/connector/exchange/whitebit/whitebit_web_utils.py new file mode 100644 index 0000000..1e6447d --- /dev/null +++ b/hummingbot/connector/exchange/whitebit/whitebit_web_utils.py @@ -0,0 +1,74 @@ +import logging +from typing import Callable, Optional +from urllib.parse import urljoin + +from requests.auth import AuthBase + +from hummingbot.connector.exchange.whitebit import whitebit_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.utils import TimeSynchronizerRESTPreProcessor +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided REST endpoint + """ + full_url = urljoin(CONSTANTS.WHITEBIT_BASE_URL, path_url) + # Logging the constructed public REST URL + logging.debug(f"Constructed public REST URL: {full_url}") + return full_url + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(path_url, domain) + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None, +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + time_synchronizer = time_synchronizer or TimeSynchronizer() + time_provider = time_provider or ( + lambda: get_current_server_time( + throttler=throttler, + domain=domain, + ) + ) + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth, + rest_pre_processors=[ + TimeSynchronizerRESTPreProcessor(synchronizer=time_synchronizer, time_provider=time_provider), + ], + ) + return api_factory + + +def build_api_factory_without_time_synchronizer_pre_processor(throttler: AsyncThrottler) -> WebAssistantsFactory: + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + api_factory = build_api_factory_without_time_synchronizer_pre_processor(throttler=throttler) + rest_assistant = await api_factory.get_rest_assistant() + response = await rest_assistant.execute_request( + url=public_rest_url(path_url=CONSTANTS.WHITEBIT_SERVER_TIME_PATH, domain=domain), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.WHITEBIT_SERVER_TIME_PATH, + ) + server_time = response["time"] + logging.debug(f"Received server time: {server_time}") + return server_time * 1e3 \ No newline at end of file diff --git a/hummingbot/connector/exchange/whitebit_PlaceTrade_Working.zip b/hummingbot/connector/exchange/whitebit_PlaceTrade_Working.zip new file mode 100644 index 0000000..ce67ccf Binary files /dev/null and b/hummingbot/connector/exchange/whitebit_PlaceTrade_Working.zip differ diff --git a/hummingbot/connector/exchange/woo_x/__init__.py b/hummingbot/connector/exchange/woo_x/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/exchange/woo_x/dummy.pxd b/hummingbot/connector/exchange/woo_x/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/woo_x/dummy.pyx b/hummingbot/connector/exchange/woo_x/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/connector/exchange/woo_x/woo_x_api_order_book_data_source.py b/hummingbot/connector/exchange/woo_x/woo_x_api_order_book_data_source.py new file mode 100644 index 0000000..d930145 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_api_order_book_data_source.py @@ -0,0 +1,189 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_web_utils as web_utils +from hummingbot.connector.exchange.woo_x.woo_x_order_book import WooXOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.woo_x.woo_x_exchange import WooXExchange + + +class WooXAPIOrderBookDataSource(OrderBookTrackerDataSource): + HEARTBEAT_TIME_INTERVAL = 30.0 + TRADE_STREAM_ID = 1 + DIFF_STREAM_ID = 2 + ONE_HOUR = 60 * 60 + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector: 'WooXExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN + ): + super().__init__(trading_pairs) + self._connector = connector + self._trade_messages_queue_key = CONSTANTS.TRADE_EVENT_TYPE + self._diff_messages_queue_key = CONSTANTS.DIFF_EVENT_TYPE + self._domain = domain + self._api_factory = api_factory + + async def get_last_traded_prices( + self, + trading_pairs: List[str], + domain: Optional[str] = None + ) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _request_order_book_snapshot(self, trading_pair: str) -> Dict[str, Any]: + """ + Retrieves a copy of the full order book from the exchange, for a particular trading pair. + + :param trading_pair: the trading pair for which the order book will be retrieved + + :return: the response from the exchange (JSON dictionary) + """ + + rest_assistant = await self._api_factory.get_rest_assistant() + + data = await rest_assistant.execute_request( + url=web_utils.public_rest_url( + path_url=f"{CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL}/{await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair)}", + domain=self._domain + ), + method=RESTMethod.GET, + throttler_limit_id=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, + ) + + return data + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + channels = ['trade', 'orderbookupdate'] + + topics = [] + + for trading_pair in self._trading_pairs: + symbol = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + + for channel in channels: + topics.append(f"{symbol}@{channel}") + + payloads = [ + { + "id": str(i), + "topic": topic, + "event": "subscribe" + } + for i, topic in enumerate(topics) + ] + + await asyncio.gather(*[ + ws.send(WSJSONRequest(payload=payload)) for payload in payloads + ]) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to order book trading and delta streams...", + exc_info=True + ) + + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async def ping(): + await websocket_assistant.send(WSJSONRequest(payload={'event': 'ping'})) + + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + + if data.get('event') == 'ping': + asyncio.ensure_future(ping()) + + if data is not None: # data will be None when the websocket is disconnected + channel: str = self._channel_originating_message(event_message=data) + valid_channels = self._get_messages_queue_keys() + if channel in valid_channels: + self._message_queue[channel].put_nowait(data) + else: + await self._process_message_for_unknown_channel( + event_message=data, websocket_assistant=websocket_assistant + ) + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + + await ws.connect( + ws_url=web_utils.wss_public_url(self._domain).format(self._connector.application_id), + ping_timeout=CONSTANTS.WS_HEARTBEAT_TIME_INTERVAL + ) + + return ws + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot: Dict[str, Any] = await self._request_order_book_snapshot(trading_pair) + + snapshot_timestamp: int = snapshot['timestamp'] + + snapshot_msg: OrderBookMessage = WooXOrderBook.snapshot_message_from_exchange( + snapshot, + snapshot_timestamp, + metadata={"trading_pair": trading_pair} + ) + + return snapshot_msg + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=raw_message['topic'].split('@')[0] + ) + + trade_message = WooXOrderBook.trade_message_from_exchange( + raw_message, + {"trading_pair": trading_pair} + ) + + message_queue.put_nowait(trade_message) + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol( + symbol=raw_message['topic'].split('@')[0] + ) + + order_book_message: OrderBookMessage = WooXOrderBook.diff_message_from_exchange( + raw_message, + raw_message['ts'], + {"trading_pair": trading_pair} + ) + + message_queue.put_nowait(order_book_message) + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + + if "topic" in event_message: + channel = event_message.get("topic").split('@')[1] + + relations = { + CONSTANTS.DIFF_EVENT_TYPE: self._diff_messages_queue_key, + CONSTANTS.TRADE_EVENT_TYPE: self._trade_messages_queue_key + } + + channel = relations.get(channel, "") + + return channel diff --git a/hummingbot/connector/exchange/woo_x/woo_x_api_user_stream_data_source.py b/hummingbot/connector/exchange/woo_x/woo_x_api_user_stream_data_source.py new file mode 100644 index 0000000..a2c6f9b --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_api_user_stream_data_source.py @@ -0,0 +1,110 @@ +import asyncio +import json +import time +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_web_utils as web_utils +from hummingbot.connector.exchange.woo_x.woo_x_auth import WooXAuth +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.connector.exchange.woo_x.woo_x_exchange import WooXExchange + + +class WooXAPIUserStreamDataSource(UserStreamTrackerDataSource): + LISTEN_KEY_KEEP_ALIVE_INTERVAL = 1800 # Recommended to Ping/Update listen key to keep connection alive + + HEARTBEAT_TIME_INTERVAL = 30 + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + auth: WooXAuth, + trading_pairs: List[str], + connector: 'WooXExchange', + api_factory: WebAssistantsFactory, + domain: str = CONSTANTS.DEFAULT_DOMAIN + ): + super().__init__() + + self._auth: WooXAuth = auth + self._trading_pairs = trading_pairs + self._connector = connector + self._api_factory = api_factory + self._domain = domain + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + """ + websocket_assistant = await self._api_factory.get_ws_assistant() + + await websocket_assistant.connect( + ws_url=web_utils.wss_private_url(self._domain).format(self._connector.application_id), + message_timeout=CONSTANTS.SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE + ) + + timestamp = int(time.time() * 1e3) + + await websocket_assistant.send(WSJSONRequest(payload={ + 'id': 'auth', + 'event': 'auth', + 'params': { + 'apikey': self._connector.api_key, + 'sign': self._auth.signature(timestamp), + 'timestamp': timestamp + } + })) + + response = await websocket_assistant.receive() + + if not response.data['success']: + self.logger().error(f"Error authenticating the private websocket connection: {json.dumps(response.data)}") + + raise IOError("Private websocket connection authentication failed") + + return websocket_assistant + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + + channels = ['executionreport', 'balance'] + + for channel in channels: + await websocket_assistant.send(WSJSONRequest(payload={ + "id": channel, + "topic": channel, + "event": "subscribe" + })) + + response = await websocket_assistant.receive() + + if not response.data['success']: + raise IOError(f"Error subscribing to the {channel} channel: {json.dumps(response)}") + + self.logger().info("Subscribed to private account and orders channels...") + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async def ping(): + await websocket_assistant.send(WSJSONRequest(payload={'event': 'ping'})) + + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + + if data.get('event') == 'ping': + asyncio.ensure_future(ping()) + + await self._process_event_message(event_message=data, queue=queue) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0: + queue.put_nowait(event_message) diff --git a/hummingbot/connector/exchange/woo_x/woo_x_auth.py b/hummingbot/connector/exchange/woo_x/woo_x_auth.py new file mode 100644 index 0000000..30fcb4a --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_auth.py @@ -0,0 +1,58 @@ +import hashlib +import hmac +import json +from typing import Dict + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSRequest + + +class WooXAuth(AuthBase): + def __init__(self, api_key: str, secret_key: str, time_provider: TimeSynchronizer): + self.api_key = api_key + self.secret_key = secret_key + self.time_provider = time_provider + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + """ + Adds authentication headers to the request + Adds the server time and the signature to the request, required for authenticated interactions. It also adds + the required parameter in the request header. + :param request: the request to be configured for authenticated interaction + """ + timestamp = str(int(self.time_provider.time() * 1e3)) + + if request.method == RESTMethod.POST: + request.headers = self.headers(timestamp, **json.loads(request.data or json.dumps({}))) + + request.data = json.loads(request.data or json.dumps({})) # Allow aiohttp to send as application/x-www-form-urlencoded + else: + request.headers = self.headers(timestamp, **(request.params or {})) + + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + """ + This method is intended to configure a websocket request to be authenticated. + Woo X does not use this functionality + """ + return request # pass-through + + def signature(self, timestamp, **kwargs): + signable = '&'.join([f"{key}={value}" for key, value in sorted(kwargs.items())]) + f"|{timestamp}" + + return hmac.new( + bytes(self.secret_key, "utf-8"), + bytes(signable, "utf-8"), + hashlib.sha256 + ).hexdigest().upper() + + def headers(self, timestamp, **kwargs) -> Dict[str, str]: + return { + 'x-api-timestamp': timestamp, + 'x-api-key': self.api_key, + 'x-api-signature': self.signature(timestamp, **kwargs), + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + } diff --git a/hummingbot/connector/exchange/woo_x/woo_x_constants.py b/hummingbot/connector/exchange/woo_x/woo_x_constants.py new file mode 100644 index 0000000..d171635 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_constants.py @@ -0,0 +1,70 @@ +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.in_flight_order import OrderState + +DEFAULT_DOMAIN = "woo_x" + +MAX_ORDER_ID_LEN = 19 + +HBOT_ORDER_ID_PREFIX = "" + +REST_URLS = { + "woo_x": "https://api.woo.org", + "woo_x_testnet": "https://api.staging.woo.org", +} + +WSS_PUBLIC_URLS = { + "woo_x": "wss://wss.woo.org/ws/stream/{}", + "woo_x_testnet": "wss://wss.staging.woo.org/ws/stream/{}" +} + +WSS_PRIVATE_URLS = { + "woo_x": "wss://wss.woo.org/v2/ws/private/stream/{}", + "woo_x_testnet": "wss://wss.staging.woo.org/v2/ws/private/stream/{}" +} + +WS_HEARTBEAT_TIME_INTERVAL = 30 + +EXCHANGE_INFO_PATH_URL = '/v1/public/info' +MARKET_TRADES_PATH = '/v1/public/market_trades' +ORDERBOOK_SNAPSHOT_PATH_URL = '/v1/public/orderbook' +ORDER_PATH_URL = '/v1/order' +CANCEL_ORDER_PATH_URL = '/v1/client/order' +ACCOUNTS_PATH_URL = '/v2/client/holding' +GET_TRADES_BY_ORDER_ID_PATH = '/v1/order/{}/trades' +GET_ORDER_BY_CLIENT_ORDER_ID_PATH = '/v1/client/order/{}' + + +RATE_LIMITS = [ + RateLimit(limit_id=EXCHANGE_INFO_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=CANCEL_ORDER_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=GET_TRADES_BY_ORDER_ID_PATH, limit=10, time_interval=1), + RateLimit(limit_id=MARKET_TRADES_PATH, limit=10, time_interval=1), + RateLimit(limit_id=ORDERBOOK_SNAPSHOT_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=ORDER_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=ACCOUNTS_PATH_URL, limit=10, time_interval=1), + RateLimit(limit_id=GET_ORDER_BY_CLIENT_ORDER_ID_PATH, limit=10, time_interval=1) +] + +# Websocket event types +DIFF_EVENT_TYPE = "orderbookupdate" +TRADE_EVENT_TYPE = "trade" + +SECONDS_TO_WAIT_TO_RECEIVE_MESSAGE = 20 # According to the documentation this has to be less than 30 seconds + +ORDER_STATE = { + "NEW": OrderState.OPEN, + "CANCELLED": OrderState.CANCELED, + "PARTIAL_FILLED": OrderState.PARTIALLY_FILLED, + "FILLED": OrderState.FILLED, + "REJECTED": OrderState.FAILED, + "INCOMPLETE": OrderState.OPEN, + "COMPLETED": OrderState.COMPLETED, +} + +ORDER_NOT_EXIST_ERROR_CODE = -1006 + +UNKNOWN_ORDER_ERROR_CODE = -1004 + +TIME_IN_FORCE_GTC = "GTC" # Good till cancelled +TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel +TIME_IN_FORCE_FOK = "FOK" # Fill or kill diff --git a/hummingbot/connector/exchange/woo_x/woo_x_exchange.py b/hummingbot/connector/exchange/woo_x/woo_x_exchange.py new file mode 100644 index 0000000..0ade660 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_exchange.py @@ -0,0 +1,499 @@ +import asyncio +import secrets +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_utils, woo_x_web_utils as web_utils +from hummingbot.connector.exchange.woo_x.woo_x_api_order_book_data_source import WooXAPIOrderBookDataSource +from hummingbot.connector.exchange.woo_x.woo_x_api_user_stream_data_source import WooXAPIUserStreamDataSource +from hummingbot.connector.exchange.woo_x.woo_x_auth import WooXAuth +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class WooXExchange(ExchangePyBase): + UPDATE_ORDER_STATUS_MIN_INTERVAL = 10.0 + + web_utils = web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + public_api_key: str, + secret_api_key: str, + application_id: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + ): + self.api_key = public_api_key + self.secret_key = secret_api_key + self.application_id = application_id + self._domain = domain + self._trading_required = trading_required + self._trading_pairs = trading_pairs + self._last_trades_poll_woo_x_timestamp = 1.0 + super().__init__(client_config_map) + + @staticmethod + def woo_x_order_type(order_type: OrderType) -> str: + if order_type.name == 'LIMIT_MAKER': + return 'POST_ONLY' + else: + return order_type.name.upper() + + @staticmethod + def to_hb_order_type(woo_x_type: str) -> OrderType: + return OrderType[woo_x_type] + + @property + def authenticator(self) -> WooXAuth: + return WooXAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self._time_synchronizer + ) + + @property + def name(self) -> str: + return self._domain + + @property + def rate_limits_rules(self): + return CONSTANTS.RATE_LIMITS + + @property + def domain(self): + return self._domain + + @property + def client_order_id_max_length(self): + return CONSTANTS.MAX_ORDER_ID_LEN + + @property + def client_order_id_prefix(self): + return CONSTANTS.HBOT_ORDER_ID_PREFIX + + @property + def trading_rules_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def check_network_request_path(self): + return CONSTANTS.EXCHANGE_INFO_PATH_URL + + @property + def trading_pairs(self): + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + error_description = str(request_exception) + + is_time_synchronizer_related = ( + "-1021" in error_description and "Timestamp for this request" in error_description + ) + + return is_time_synchronizer_related + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + return web_utils.build_api_factory( + throttler=self._throttler, + time_synchronizer=self._time_synchronizer, + domain=self._domain, + auth=self._auth + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return WooXAPIOrderBookDataSource( + trading_pairs=self._trading_pairs, + connector=self, + domain=self.domain, + api_factory=self._web_assistants_factory + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return WooXAPIUserStreamDataSource( + auth=self._auth, + trading_pairs=self._trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + domain=self.domain, + ) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None + ) -> TradeFeeBase: + is_maker = order_type is OrderType.LIMIT_MAKER + + return DeductedFromReturnsTradeFee(percent=self.estimate_fee_pct(is_maker)) + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = str(secrets.randbelow(9223372036854775807)) + + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs) + ) + + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + + order_id = str(secrets.randbelow(9223372036854775807)) + + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs) + ) + + return order_id + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs + ) -> Tuple[str, float]: + data = { + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair), + "order_type": self.woo_x_order_type(order_type), + "side": trade_type.name.upper(), + "order_quantity": float(amount), + "client_order_id": order_id + } + + if order_type is OrderType.LIMIT or order_type is OrderType.LIMIT_MAKER: + data["order_price"] = float(price) + + response = await self._api_post( + path_url=CONSTANTS.ORDER_PATH_URL, + data=data, + is_auth_required=True + ) + + return str(response["order_id"]), int(float(response['timestamp']) * 1e3) + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + params = { + "client_order_id": order_id, + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=tracked_order.trading_pair), + } + + cancel_result = await self._api_delete( + path_url=CONSTANTS.CANCEL_ORDER_PATH_URL, + params=params, + is_auth_required=True + ) + + if cancel_result.get("status") != "CANCEL_SENT": + raise IOError() + + return True + + async def _format_trading_rules(self, exchange_info: Dict[str, Any]) -> List[TradingRule]: + result = [] + + for entry in filter(woo_x_utils.is_exchange_information_valid, exchange_info.get("rows", [])): + try: + trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=entry.get("symbol")) + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(str(entry['base_min'])), + min_price_increment=Decimal(str(entry['quote_tick'])), + min_base_amount_increment=Decimal(str(entry['base_tick'])), + min_notional_size=Decimal(str(entry['min_notional'])) + ) + + result.append(trading_rule) + + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {entry}. Skipping.") + return result + + async def _status_polling_loop_fetch_updates(self): + await super()._status_polling_loop_fetch_updates() + + async def _update_trading_fees(self): + """ + Update fees information from the exchange + """ + pass + + async def _user_stream_event_listener(self): + """ + This functions runs in background continuously processing the events received from the exchange by the user + stream data source. It keeps reading events from the queue until the task is interrupted. + The events received are balance updates, order updates and trade events. + """ + async for event_message in self._iter_user_event_queue(): + try: + event_type = event_message.get("topic") + + if event_type == "executionreport": + event_data = event_message.get("data") + + execution_type = event_data.get("status") + + client_order_id = event_data.get("clientOrderId") + + if execution_type in ["PARTIAL_FILLED", "FILLED"]: + tracked_order = self._order_tracker.all_fillable_orders.get(str(client_order_id)) + + if tracked_order is not None: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=tracked_order.trade_type, + percent_token=event_data["feeAsset"], + flat_fees=[ + TokenAmount( + amount=Decimal(event_data["fee"]), + token=event_data["feeAsset"] + ) + ] + ) + + trade_update = TradeUpdate( + trade_id=str(event_data["tradeId"]), + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(event_data["orderId"]), + trading_pair=tracked_order.trading_pair, + fee=fee, + fill_base_amount=Decimal(str(event_data["executedQuantity"])), + fill_quote_amount=Decimal(str(event_data["executedQuantity"])) * Decimal(str(event_data["executedPrice"])), + fill_price=Decimal(str(event_data["executedPrice"])), + fill_timestamp=event_data["timestamp"] * 1e-3, + ) + + self._order_tracker.process_trade_update(trade_update) + + tracked_order = self._order_tracker.all_updatable_orders.get(str(client_order_id)) + + if tracked_order is not None: + order_update = OrderUpdate( + trading_pair=tracked_order.trading_pair, + update_timestamp=event_data["timestamp"] * 1e-3, + new_state=CONSTANTS.ORDER_STATE[event_data["status"]], + client_order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + ) + + self._order_tracker.process_order_update(order_update=order_update) + elif event_type == "balance": + balances = event_message["data"]["balances"] + + for asset_name, balance_entry in balances.items(): + free, frozen = Decimal(str(balance_entry["holding"])), Decimal(str(balance_entry["frozen"])) + + total = free + frozen + + self._account_available_balances[asset_name] = free + + self._account_balances[asset_name] = total + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error in user stream listener loop.", exc_info=True) + await self._sleep(5.0) + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + symbol = await self.exchange_symbol_associated_to_pair(trading_pair=order.trading_pair) + + content = await self._api_get( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id), + limit_id=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH, + is_auth_required=True, + ) + + for trade in content['Transactions']: + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=order.trade_type, + percent_token=trade["fee_asset"], + flat_fees=[ + TokenAmount( + amount=Decimal(str(trade["fee"])), + token=trade["fee_asset"] + ) + ] + ) + + trade_update = TradeUpdate( + trade_id=str(trade["id"]), + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=symbol, + fee=fee, + fill_base_amount=Decimal(str(trade["executed_quantity"])), + fill_quote_amount=Decimal(str(trade["executed_price"])) * Decimal(str(trade["executed_quantity"])), + fill_price=Decimal(str(trade["executed_price"])), + fill_timestamp=float(trade["executed_timestamp"]) * 1e-3, + ) + + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + updated_order_data = await self._api_get( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(tracked_order.client_order_id), + is_auth_required=True, + limit_id=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH + ) + + new_state = CONSTANTS.ORDER_STATE[updated_order_data["status"]] + + order_update = OrderUpdate( + client_order_id=tracked_order.client_order_id, + exchange_order_id=str(updated_order_data["order_id"]), + trading_pair=tracked_order.trading_pair, + update_timestamp=float(updated_order_data["created_time"]), + new_state=new_state, + ) + + return order_update + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + account_info = await self._api_get( + path_url=CONSTANTS.ACCOUNTS_PATH_URL, + is_auth_required=True + ) + + balances = account_info.get('holding', []) + + for balance_info in balances: + asset = balance_info['token'] + holding = balance_info['holding'] + frozen = balance_info['frozen'] + + self._account_available_balances[asset] = Decimal(holding) + self._account_balances[asset] = Decimal(holding) + Decimal(frozen) + remote_asset_names.add(asset) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + mapping = bidict() + + for entry in filter(woo_x_utils.is_exchange_information_valid, exchange_info["rows"]): + base, quote = entry['symbol'].split('_')[1:] + + mapping[entry["symbol"]] = combine_to_hb_trading_pair( + base=base, + quote=quote + ) + + self._set_trading_pair_symbol_map(mapping) + + async def _get_last_traded_price(self, trading_pair: str) -> float: + content = await self._api_request( + method=RESTMethod.GET, + path_url=CONSTANTS.MARKET_TRADES_PATH, + params={ + "symbol": await self.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + } + ) + + return content['rows'][0]['executed_price'] diff --git a/hummingbot/connector/exchange/woo_x/woo_x_order_book.py b/hummingbot/connector/exchange/woo_x/woo_x_order_book.py new file mode 100644 index 0000000..548178a --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_order_book.py @@ -0,0 +1,81 @@ +from typing import Dict, Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class WooXOrderBook(OrderBook): + @classmethod + def snapshot_message_from_exchange( + cls, + msg: Dict[str, any], + timestamp: float, + metadata: Optional[Dict] = None + ) -> OrderBookMessage: + """ + Creates a snapshot message with the order book snapshot message + :param msg: the response from the exchange when requesting the order book snapshot + :param timestamp: the snapshot timestamp + :param metadata: a dictionary with extra information to add to the snapshot data + :return: a snapshot message with the snapshot information received from the exchange + """ + + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.SNAPSHOT, { + "update_id": timestamp, + "bids": [[entry['price'], entry['quantity']] for entry in msg["bids"]], + "asks": [[entry['price'], entry['quantity']] for entry in msg["asks"]], + }, timestamp=timestamp) + + @classmethod + def diff_message_from_exchange( + cls, + msg: Dict[str, any], + timestamp: Optional[float] = None, + metadata: Optional[Dict] = None + ) -> OrderBookMessage: + """ + Creates a diff message with the changes in the order book received from the exchange + :param msg: the changes in the order book + :param timestamp: the timestamp of the difference + :param metadata: a dictionary with extra information to add to the difference data + :return: a diff message with the changes in the order book notified by the exchange + """ + if metadata: + msg.update(metadata) + + return OrderBookMessage(OrderBookMessageType.DIFF, { + "trading_pair": msg['trading_pair'], + "update_id": msg['ts'], + "bids": msg['data']['bids'], + "asks": msg['data']['asks'] + }, timestamp=msg['ts']) + + @classmethod + def trade_message_from_exchange( + cls, + msg: Dict[str, any], + metadata: Optional[Dict] = None + ): + """ + Creates a trade message with the information from the trade event sent by the exchange + :param msg: the trade event details sent by the exchange + :param metadata: a dictionary with extra information to add to trade message + :return: a trade message with the details of the trade as provided by the exchange + """ + if metadata: + msg.update(metadata) + + timestamp = msg['ts'] + + return OrderBookMessage(OrderBookMessageType.TRADE, { + "trading_pair": msg['trading_pair'], + "trade_type": TradeType[msg['data']["side"]].value, + "trade_id": timestamp, + "update_id": timestamp, + "price": msg['data']['price'], + "amount": msg['data']['size'] + }, timestamp=timestamp * 1e-3) diff --git a/hummingbot/connector/exchange/woo_x/woo_x_utils.py b/hummingbot/connector/exchange/woo_x/woo_x_utils.py new file mode 100644 index 0000000..9cb3289 --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_utils.py @@ -0,0 +1,107 @@ +from decimal import Decimal +from typing import Any, Dict + +from pydantic import Field, SecretStr + +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap, ClientFieldData +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + +CENTRALIZED = True + +EXAMPLE_PAIR = "BTC-USDT" + +DEFAULT_FEES = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.0003"), + taker_percent_fee_decimal=Decimal("0.0003"), + buy_percent_fee_deducted_from_returns=True +) + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + :param exchange_info: the exchange information for a trading pair + :return: True if the trading pair is enabled, False otherwise + """ + category, *rest = exchange_info['symbol'].split('_') + + return category == 'SPOT' + + +class WooXConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="woo_x", const=True, client_data=None) + public_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X public API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + secret_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X secret API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + application_id: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X application ID", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "woo_x" + + +KEYS = WooXConfigMap.construct() + +OTHER_DOMAINS = ["woo_x_testnet"] +OTHER_DOMAINS_PARAMETER = {"woo_x_testnet": "woo_x_testnet"} +OTHER_DOMAINS_EXAMPLE_PAIR = {"woo_x_testnet": "BTC-USDT"} +OTHER_DOMAINS_DEFAULT_FEES = {"woo_x_testnet": DEFAULT_FEES} + + +class WooXTestnetConfigMap(BaseConnectorConfigMap): + connector: str = Field(default="woo_x_testnet", const=True, client_data=None) + public_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X public API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + secret_api_key: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X secret API key", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + application_id: SecretStr = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda cm: "Enter your Woo X application ID", + is_secure=True, + is_connect_key=True, + prompt_on_new=True, + ) + ) + + class Config: + title = "woo_x_testnet" + + +OTHER_DOMAINS_KEYS = {"woo_x_testnet": WooXTestnetConfigMap.construct()} diff --git a/hummingbot/connector/exchange/woo_x/woo_x_web_utils.py b/hummingbot/connector/exchange/woo_x/woo_x_web_utils.py new file mode 100644 index 0000000..5d36a2d --- /dev/null +++ b/hummingbot/connector/exchange/woo_x/woo_x_web_utils.py @@ -0,0 +1,58 @@ +import time +from typing import Callable, Optional + +import hummingbot.connector.exchange.woo_x.woo_x_constants as CONSTANTS +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def public_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + """ + Creates a full URL for provided public REST endpoint + :param path_url: a public REST endpoint + :param domain: the Woo X domain to connect to ("com" or "us"). The default value is "com" + :return: the full URL to the endpoint + """ + return CONSTANTS.REST_URLS[domain] + path_url + + +def private_rest_url(path_url: str, domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return public_rest_url(path_url, domain) + + +def wss_public_url(domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return CONSTANTS.WSS_PUBLIC_URLS[domain] + + +def wss_private_url(domain: str = CONSTANTS.DEFAULT_DOMAIN) -> str: + return CONSTANTS.WSS_PRIVATE_URLS[domain] + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + time_synchronizer: Optional[TimeSynchronizer] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, + time_provider: Optional[Callable] = None, + auth: Optional[AuthBase] = None +) -> WebAssistantsFactory: + throttler = throttler or create_throttler() + + api_factory = WebAssistantsFactory( + throttler=throttler, + auth=auth + ) + + return api_factory + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, + domain: str = CONSTANTS.DEFAULT_DOMAIN, +) -> float: + return time.time() * 1e3 + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) diff --git a/hummingbot/connector/exchange_base.pxd b/hummingbot/connector/exchange_base.pxd new file mode 100644 index 0000000..6b98e82 --- /dev/null +++ b/hummingbot/connector/exchange_base.pxd @@ -0,0 +1,41 @@ +from hummingbot.core.event.event_reporter cimport EventReporter +from hummingbot.core.event.event_logger cimport EventLogger +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.connector.connector_base cimport ConnectorBase +from hummingbot.core.data_type.order_book_query_result cimport( + ClientOrderBookQueryResult, + OrderBookQueryResult, +) + +cdef class ExchangeBase(ConnectorBase): + cdef: + object _order_book_tracker + object _budget_checker + object _trading_pair_symbol_map + object _mapping_initialization_lock + + cdef str c_buy(self, str trading_pair, object amount, object order_type= *, object price= *, dict kwargs= *) + cdef str c_sell(self, str trading_pair, object amount, object order_type= *, object price= *, dict kwargs= *) + cdef c_cancel(self, str trading_pair, str client_order_id) + cdef c_stop_tracking_order(self, str order_id) + cdef OrderBook c_get_order_book(self, str trading_pair) + cdef object c_get_price(self, str trading_pair, bint is_buy) + cdef ClientOrderBookQueryResult c_get_quote_volume_for_base_amount( + self, + str trading_pair, + bint is_buy, + object base_amount) + cdef ClientOrderBookQueryResult c_get_volume_for_price(self, str trading_pair, bint is_buy, object price) + cdef ClientOrderBookQueryResult c_get_quote_volume_for_price(self, str trading_pair, bint is_buy, object price) + cdef ClientOrderBookQueryResult c_get_vwap_for_volume(self, str trading_pair, bint is_buy, object volume) + cdef ClientOrderBookQueryResult c_get_price_for_quote_volume(self, str trading_pair, bint is_buy, double volume) + cdef ClientOrderBookQueryResult c_get_price_for_volume(self, str trading_pair, bint is_buy, object volume) + cdef object c_get_fee( + self, + str base_currency, + str quote_currency, + object order_type, + object order_side, + object amount, + object price, + object is_maker= *) diff --git a/hummingbot/connector/exchange_base.pyx b/hummingbot/connector/exchange_base.pyx new file mode 100644 index 0000000..6fd1740 --- /dev/null +++ b/hummingbot/connector/exchange_base.pyx @@ -0,0 +1,383 @@ +import asyncio +from decimal import Decimal +from typing import Dict, List, Iterator, Mapping, Optional, TYPE_CHECKING + +from bidict import bidict + +from hummingbot.connector.budget_checker import BudgetChecker +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_query_result import ClientOrderBookQueryResult, OrderBookQueryResult +from hummingbot.core.data_type.order_book_row import ClientOrderBookRow +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.utils.async_utils import safe_gather + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_float_NaN = float("nan") +s_decimal_NaN = Decimal("nan") +s_decimal_0 = Decimal(0) + + +cdef class ExchangeBase(ConnectorBase): + """ + ExchangeBase provides common exchange (for both centralized and decentralized) connector functionality and + interface. + """ + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + self._order_book_tracker = None + self._budget_checker = BudgetChecker(exchange=self) + self._trading_pair_symbol_map: Optional[Mapping[str, str]] = None + self._mapping_initialization_lock = asyncio.Lock() + + @staticmethod + def convert_from_exchange_trading_pair(exchange_trading_pair: str) -> Optional[str]: + return exchange_trading_pair + + @staticmethod + def convert_to_exchange_trading_pair(hb_trading_pair: str) -> str: + return hb_trading_pair + + @property + def order_books(self) -> Dict[str, OrderBook]: + raise NotImplementedError + + @property + def limit_orders(self) -> List[LimitOrder]: + raise NotImplementedError + + @property + def budget_checker(self) -> BudgetChecker: + return self._budget_checker + + @property + def order_book_tracker(self) -> Optional[OrderBookTracker]: + return self._order_book_tracker + + async def trading_pair_symbol_map(self): + if not self.trading_pair_symbol_map_ready(): + async with self._mapping_initialization_lock: + if not self.trading_pair_symbol_map_ready(): + await self._initialize_trading_pair_symbol_map() + current_map = self._trading_pair_symbol_map or bidict() + return current_map + + def trading_pair_symbol_map_ready(self): + """ + Checks if the mapping from exchange symbols to client trading pairs has been initialized + + :return: True if the mapping has been initialized, False otherwise + """ + return self._trading_pair_symbol_map is not None and len(self._trading_pair_symbol_map) > 0 + + async def all_trading_pairs(self) -> List[str]: + """ + List of all trading pairs supported by the connector + + :return: List of trading pair symbols in the Hummingbot format + """ + mapping = await self.trading_pair_symbol_map() + return list(mapping.values()) + + async def exchange_symbol_associated_to_pair(self, trading_pair: str) -> str: + """ + Used to translate a trading pair from the client notation to the exchange notation + + :param trading_pair: trading pair in client notation + + :return: trading pair in exchange notation + """ + symbol_map = await self.trading_pair_symbol_map() + return symbol_map.inverse[trading_pair] + + async def trading_pair_associated_to_exchange_symbol(self, symbol: str,) -> str: + """ + Used to translate a trading pair from the exchange notation to the client notation + + :param symbol: trading pair in exchange notation + + :return: trading pair in client notation + """ + symbol_map = await self.trading_pair_symbol_map() + return symbol_map[symbol] + + async def get_last_traded_prices(self, trading_pairs: List[str]) -> Dict[str, float]: + """ + Return a dictionary the trading_pair as key and the current price as value for each trading pair passed as + parameter + + :param trading_pairs: list of trading pairs to get the prices for + + :return: Dictionary of associations between token pair and its latest price + """ + tasks = [self._get_last_traded_price(trading_pair=trading_pair) for trading_pair in trading_pairs] + results = await safe_gather(*tasks) + return {t_pair: result for t_pair, result in zip(trading_pairs, results)} + + def get_mid_price(self, trading_pair: str) -> Decimal: + return (self.get_price(trading_pair, True) + self.get_price(trading_pair, False)) / Decimal("2") + + cdef str c_buy(self, str trading_pair, object amount, object order_type=OrderType.MARKET, + object price=s_decimal_NaN, dict kwargs={}): + return self.buy(trading_pair, amount, order_type, price, **kwargs) + + cdef str c_sell(self, str trading_pair, object amount, object order_type=OrderType.MARKET, + object price=s_decimal_NaN, dict kwargs={}): + return self.sell(trading_pair, amount, order_type, price, **kwargs) + + cdef c_cancel(self, str trading_pair, str client_order_id): + return self.cancel(trading_pair, client_order_id) + + cdef c_stop_tracking_order(self, str order_id): + raise NotImplementedError + + cdef object c_get_fee(self, + str base_currency, + str quote_currency, + object order_type, + object order_side, + object amount, + object price, + object is_maker = None): + return self.get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + cdef OrderBook c_get_order_book(self, str trading_pair): + return self.get_order_book(trading_pair) + + cdef object c_get_price(self, str trading_pair, bint is_buy): + """ + :returns: Top bid/ask price for a specific trading pair + """ + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + object top_price + try: + top_price = Decimal(str(order_book.c_get_price(is_buy))) + except EnvironmentError as e: + self.logger().warning(f"{'Ask' if is_buy else 'Bid'} orderbook for {trading_pair} is empty.") + return s_decimal_NaN + return self.c_quantize_order_price(trading_pair, top_price) + + cdef ClientOrderBookQueryResult c_get_vwap_for_volume(self, str trading_pair, bint is_buy, object volume): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_vwap_for_volume(is_buy, float(volume)) + object query_volume = Decimal(str(result.query_volume)) + object result_price = Decimal(str(result.result_price)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(s_decimal_NaN, + query_volume, + result_price, + result_volume) + + cdef ClientOrderBookQueryResult c_get_price_for_quote_volume(self, str trading_pair, bint is_buy, double volume): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_price_for_quote_volume(is_buy, float(volume)) + object query_volume = Decimal(str(result.query_volume)) + object result_price = Decimal(str(result.result_price)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(s_decimal_NaN, + query_volume, + result_price, + result_volume) + + cdef ClientOrderBookQueryResult c_get_price_for_volume(self, str trading_pair, bint is_buy, object volume): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_price_for_volume(is_buy, float(volume)) + object query_volume = Decimal(str(result.query_volume)) + object result_price = Decimal(str(result.result_price)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(s_decimal_NaN, + query_volume, + result_price, + result_volume) + + cdef ClientOrderBookQueryResult c_get_quote_volume_for_base_amount(self, str trading_pair, bint is_buy, + object base_amount): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_quote_volume_for_base_amount(is_buy, float(base_amount)) + object query_volume = Decimal(str(result.query_volume)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(s_decimal_NaN, + query_volume, + s_decimal_NaN, + result_volume) + + cdef ClientOrderBookQueryResult c_get_volume_for_price(self, str trading_pair, bint is_buy, object price): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_volume_for_price(is_buy, float(price)) + object query_price = Decimal(str(result.query_price)) + object result_price = Decimal(str(result.result_price)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(query_price, + s_decimal_NaN, + result_price, + result_volume) + + cdef ClientOrderBookQueryResult c_get_quote_volume_for_price(self, str trading_pair, bint is_buy, object price): + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + OrderBookQueryResult result = order_book.c_get_volume_for_price(is_buy, float(price)) + object query_price = Decimal(str(result.query_price)) + object result_price = Decimal(str(result.result_price)) + object result_volume = Decimal(str(result.result_volume)) + return ClientOrderBookQueryResult(query_price, + s_decimal_NaN, + result_price, + result_volume) + + def order_book_bid_entries(self, trading_pair) -> Iterator[ClientOrderBookRow]: + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + for entry in order_book.bid_entries(): + yield ClientOrderBookRow(Decimal(str(entry.price)), + Decimal(str(entry.amount)), + entry.update_id) + + def order_book_ask_entries(self, trading_pair) -> Iterator[ClientOrderBookRow]: + cdef: + OrderBook order_book = self.c_get_order_book(trading_pair) + for entry in order_book.ask_entries(): + yield ClientOrderBookRow(Decimal(str(entry.price)), + Decimal(str(entry.amount)), + entry.update_id) + + def get_vwap_for_volume(self, trading_pair: str, is_buy: bool, volume: Decimal): + return self.c_get_vwap_for_volume(trading_pair, is_buy, volume) + + def get_price_for_quote_volume(self, trading_pair: str, is_buy: bool, volume: Decimal): + return self.c_get_price_for_quote_volume(trading_pair, is_buy, volume) + + def get_price_for_volume(self, trading_pair: str, is_buy: bool, volume: Decimal): + return self.c_get_price_for_volume(trading_pair, is_buy, volume) + + def get_quote_volume_for_base_amount(self, trading_pair: str, is_buy: bool, + base_amount: Decimal) -> ClientOrderBookQueryResult: + return self.c_get_quote_volume_for_base_amount(trading_pair, is_buy, base_amount) + + def get_volume_for_price(self, trading_pair: str, is_buy: bool, price: Decimal) -> ClientOrderBookQueryResult: + return self.c_get_volume_for_price(trading_pair, is_buy, price) + + def get_quote_volume_for_price(self, trading_pair: str, is_buy: bool, price: Decimal) -> ClientOrderBookQueryResult: + return self.c_get_quote_volume_for_price(trading_pair, is_buy, price) + + def get_price(self, trading_pair: str, is_buy: bool) -> Decimal: + return self.c_get_price(trading_pair, is_buy) + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + raise NotImplementedError + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, + price: Decimal = s_decimal_NaN, **kwargs) -> str: + raise NotImplementedError + + def cancel(self, trading_pair: str, client_order_id: str): + raise NotImplementedError + + def get_order_book(self, trading_pair: str) -> OrderBook: + raise NotImplementedError + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + raise NotImplementedError + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return self.c_get_order_price_quantum(trading_pair, price) + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + return self.c_get_order_size_quantum(trading_pair, order_size) + + def quantize_order_price(self, trading_pair: str, price: Decimal) -> Decimal: + return self.c_quantize_order_price(trading_pair, price) + + def quantize_order_amount(self, trading_pair: str, amount: Decimal) -> Decimal: + return self.c_quantize_order_amount(trading_pair, amount) + + def supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + def get_maker_order_type(self): + """ + Return a maker order type depending what order types the connector supports. + """ + if OrderType.LIMIT_MAKER in self.supported_order_types(): + return OrderType.LIMIT_MAKER + elif OrderType.LIMIT in self.supported_order_types(): + return OrderType.LIMIT + else: + raise Exception("There is no maker order type supported by this exchange.") + + def get_taker_order_type(self): + """ + Return a taker order type depending what order types the connector supports. + """ + if OrderType.MARKET in self.supported_order_types(): + return OrderType.MARKET + elif OrderType.LIMIT in self.supported_order_types(): + return OrderType.LIMIT + else: + raise Exception("There is no taker order type supported by this exchange.") + + def get_price_by_type(self, trading_pair: str, price_type: PriceType) -> Decimal: + """ + Gets price by type (BestBid, BestAsk, MidPrice or LastTrade) + :param trading_pair: The market trading pair + :param price_type: The price type + :returns The price + """ + if price_type is PriceType.BestBid: + return self.c_get_price(trading_pair, False) + elif price_type is PriceType.BestAsk: + return self.c_get_price(trading_pair, True) + elif price_type is PriceType.MidPrice: + return (self.c_get_price(trading_pair, True) + self.c_get_price(trading_pair, False)) / Decimal("2") + elif price_type is PriceType.LastTrade: + return Decimal(self.c_get_order_book(trading_pair).last_trade_price) + + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + """ + For an exchange type connector, the quote price is volume weighted average price. + """ + return Decimal(str(self.get_vwap_for_volume(trading_pair, is_buy, amount).result_price)) + + async def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + """ + For an exchange type connector, the price required for order submission is the price of the order book for + required volume. + """ + return Decimal(str(self.get_price_for_volume(trading_pair, is_buy, amount).result_price)) + + async def _initialize_trading_pair_symbol_map(self): + raise NotImplementedError + + def _set_trading_pair_symbol_map(self, trading_pair_and_symbol_map: Optional[Mapping[str, str]]): + """ + Method added to allow the pure Python subclasses to set the value of the map + """ + self._trading_pair_symbol_map = trading_pair_and_symbol_map + + def _set_order_book_tracker(self, order_book_tracker: Optional[OrderBookTracker]): + """ + Method added to allow the pure Python subclasses to store the tracker in the instance variable + """ + self._order_book_tracker = order_book_tracker + + async def _get_last_traded_price(self, trading_pair: str) -> float: + raise NotImplementedError diff --git a/hummingbot/connector/exchange_py_base.py b/hummingbot/connector/exchange_py_base.py new file mode 100644 index 0000000..d07ac72 --- /dev/null +++ b/hummingbot/connector/exchange_py_base.py @@ -0,0 +1,1096 @@ +import asyncio +import copy +import logging +import math +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import TYPE_CHECKING, Any, AsyncIterable, Callable, Dict, List, Optional, Tuple + +from async_timeout import timeout + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.constants import MINUTE, TWELVE_HOURS, s_decimal_0, s_decimal_NaN +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.data_type.user_stream_tracker import UserStreamTracker +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class ExchangePyBase(ExchangeBase, ABC): + _logger = None + + SHORT_POLL_INTERVAL = 5.0 + LONG_POLL_INTERVAL = 120.0 + TRADING_RULES_INTERVAL = 30 * MINUTE + TRADING_FEES_INTERVAL = TWELVE_HOURS + TICK_INTERVAL_LIMIT = 60.0 + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + + self._last_poll_timestamp = 0 + self._last_timestamp = 0 + self._trading_rules = {} + self._trading_fees = {} + + self._status_polling_task: Optional[asyncio.Task] = None + self._user_stream_tracker_task: Optional[asyncio.Task] = None + self._user_stream_event_listener_task: Optional[asyncio.Task] = None + self._trading_rules_polling_task: Optional[asyncio.Task] = None + self._trading_fees_polling_task: Optional[asyncio.Task] = None + self._lost_orders_update_task: Optional[asyncio.Task] = None + + self._time_synchronizer = TimeSynchronizer() + self._throttler = AsyncThrottler( + rate_limits=self.rate_limits_rules, + limits_share_percentage=client_config_map.rate_limits_share_pct) + self._poll_notifier = asyncio.Event() + + # init Auth and Api factory + self._auth: AuthBase = self.authenticator + self._web_assistants_factory: WebAssistantsFactory = self._create_web_assistants_factory() + + # init OrderBook Data Source and Tracker + self._orderbook_ds: OrderBookTrackerDataSource = self._create_order_book_data_source() + self._set_order_book_tracker(OrderBookTracker( + data_source=self._orderbook_ds, + trading_pairs=self.trading_pairs, + domain=self.domain)) + + # init UserStream Data Source and Tracker + self._user_stream_tracker = self._create_user_stream_tracker() + + self._order_tracker: ClientOrderTracker = self._create_order_tracker() + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + @property + @abstractmethod + def name(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def authenticator(self) -> AuthBase: + raise NotImplementedError + + @property + @abstractmethod + def rate_limits_rules(self) -> List[RateLimit]: + raise NotImplementedError + + @property + @abstractmethod + def domain(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def client_order_id_max_length(self) -> int: + raise NotImplementedError + + @property + @abstractmethod + def client_order_id_prefix(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def trading_rules_request_path(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def trading_pairs_request_path(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def check_network_request_path(self) -> str: + raise NotImplementedError + + @property + @abstractmethod + def trading_pairs(self) -> List[str]: + raise NotImplementedError + + @property + @abstractmethod + def is_cancel_request_in_exchange_synchronous(self) -> bool: + raise NotImplementedError + + @property + @abstractmethod + def is_trading_required(self) -> bool: + raise NotImplementedError + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self.order_book_tracker.order_books + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrder]: + return self._order_tracker.active_orders + + @property + def trading_rules(self) -> Dict[str, TradingRule]: + return self._trading_rules + + @property + def limit_orders(self) -> List[LimitOrder]: + return [in_flight_order.to_limit_order() for in_flight_order in self.in_flight_orders.values()] + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "symbols_mapping_initialized": self.trading_pair_symbol_map_ready(), + "order_books_initialized": self.order_book_tracker.ready, + "account_balance": not self.is_trading_required or len(self._account_balances) > 0, + "trading_rule_initialized": len(self._trading_rules) > 0 if self.is_trading_required else True, + "user_stream_initialized": self._is_user_stream_initialized(), + } + + @property + def ready(self) -> bool: + """ + Returns True if the connector is ready to operate (all connections established with the exchange). If it is + not ready it returns False. + """ + return all(self.status_dict.values()) + + @property + def name_cap(self) -> str: + return self.name.capitalize() + + @property + def tracking_states(self) -> Dict[str, any]: + """ + Returns a dictionary associating current active orders client id to their JSON representation + """ + return {key: value.to_json() for key, value in self._order_tracker.all_updatable_orders.items()} + + @abstractmethod + def supported_order_types(self) -> List[OrderType]: + raise NotImplementedError + + @abstractmethod + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + raise NotImplementedError + + @abstractmethod + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + raise NotImplementedError + + @abstractmethod + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + raise NotImplementedError + + # === Price logic === + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + """ + Used by quantize_order_price() in _create_order() + Returns a price step, a minimum price increment for a given trading pair. + + :param trading_pair: the trading pair to check for market conditions + :param price: the starting point price + """ + trading_rule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_price_increment) + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + """ + Used by quantize_order_price() in _create_order() + Returns an order amount step, a minimum amount increment for a given trading pair. + + :param trading_pair: the trading pair to check for market conditions + :param order_size: the starting point order price + """ + trading_rule = self._trading_rules[trading_pair] + return Decimal(trading_rule.min_base_amount_increment) + + def get_order_book(self, trading_pair: str) -> OrderBook: + """ + Returns the current order book for a particular market + + :param trading_pair: the pair of tokens for which the order book should be retrieved + """ + if trading_pair not in self.order_book_tracker.order_books: + raise ValueError(f"No order book exists for '{trading_pair}'.") + return self.order_book_tracker.order_books[trading_pair] + + def tick(self, timestamp: float): + """ + Includes the logic that has to be processed every time a new tick happens in the bot. Particularly it enables + the execution of the status update polling loop using an event. + """ + poll_interval = self._get_poll_interval(timestamp=timestamp) + last_tick = int(self._last_timestamp / poll_interval) + current_tick = int(timestamp / poll_interval) + if current_tick > last_tick: + self._poll_notifier.set() + self._last_timestamp = timestamp + + # === Orders placing === + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = get_new_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = get_new_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + """ + Calculates the fee to pay based on the fee information provided by the exchange for + the account and the token pair. If exchange info is not available it calculates the estimated + fee an order would pay based on the connector configuration. + + :param base_currency: the order base currency + :param quote_currency: the order quote currency + :param order_type: the type of order (MARKET, LIMIT, LIMIT_MAKER) + :param order_side: if the order is for buying or selling + :param amount: the order amount + :param price: the order price + :param is_maker: True if the order is a maker order, False if it is a taker order + + :return: the calculated or estimated fee + """ + return self._get_fee(base_currency, quote_currency, order_type, order_side, amount, price, is_maker) + + def cancel(self, trading_pair: str, client_order_id: str): + """ + Creates a promise to cancel an order in the exchange + + :param trading_pair: the trading pair the order to cancel operates with + :param client_order_id: the client id of the order to cancel + + :return: the client id of the order to cancel + """ + safe_ensure_future(self._execute_cancel(trading_pair, client_order_id)) + return client_order_id + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + Cancels all currently active orders. The cancellations are performed in parallel tasks. + + :param timeout_seconds: the maximum time (in seconds) the cancel logic should run + + :return: a list of CancellationResult instances, one for each of the orders to be cancelled + """ + incomplete_orders = [o for o in self.in_flight_orders.values() if not o.is_done] + tasks = [self._execute_cancel(o.trading_pair, o.client_order_id) for o in incomplete_orders] + order_id_set = set([o.client_order_id for o in incomplete_orders]) + successful_cancellations = [] + + try: + async with timeout(timeout_seconds): + cancellation_results = await safe_gather(*tasks, return_exceptions=True) + for cr in cancellation_results: + if isinstance(cr, Exception): + continue + client_order_id = cr + if client_order_id is not None: + order_id_set.remove(client_order_id) + successful_cancellations.append(CancellationResult(client_order_id, True)) + except Exception: + self.logger().network( + "Unexpected error cancelling orders.", + exc_info=True, + app_warning_msg="Failed to cancel order. Check API key and network connection." + ) + failed_cancellations = [CancellationResult(oid, False) for oid in order_id_set] + return successful_cancellations + failed_cancellations + + async def _create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + **kwargs): + """ + Creates an order in the exchange using the parameters to configure it + + :param trade_type: the side of the order (BUY of SELL) + :param order_id: the id that should be assigned to the order (the client id) + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + """ + trading_rule = self._trading_rules[trading_pair] + + if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]: + price = self.quantize_order_price(trading_pair, price) + quantized_amount = self.quantize_order_amount(trading_pair=trading_pair, amount=amount) + + self.start_tracking_order( + order_id=order_id, + exchange_order_id=None, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=quantized_amount, + **kwargs, + ) + order = self._order_tracker.active_orders[order_id] + if not price or price.is_nan() or price == s_decimal_0: + current_price: Decimal = self.get_price(trading_pair, False) + notional_size = current_price * quantized_amount + else: + notional_size = price * quantized_amount + + if order_type not in self.supported_order_types(): + self.logger().error(f"{order_type} is not in the list of supported order types") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + + elif quantized_amount < trading_rule.min_order_size: + self.logger().warning(f"{trade_type.name.title()} order amount {amount} is lower than the minimum order " + f"size {trading_rule.min_order_size}. The order will not be created, increase the " + f"amount to be higher than the minimum order size.") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + + elif notional_size < trading_rule.min_notional_size: + self.logger().warning(f"{trade_type.name.title()} order notional {notional_size} is lower than the " + f"minimum notional size {trading_rule.min_notional_size}. The order will not be " + f"created. Increase the amount or the price to be higher than the minimum notional.") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + return + try: + await self._place_order_and_process_update(order=order, **kwargs,) + + except asyncio.CancelledError: + raise + except Exception as ex: + self._on_order_failure( + order_id=order_id, + trading_pair=trading_pair, + amount=quantized_amount, + trade_type=trade_type, + order_type=order_type, + price=price, + exception=ex, + **kwargs, + ) + + async def _place_order_and_process_update(self, order: InFlightOrder, **kwargs) -> str: + exchange_order_id, update_timestamp = await self._place_order( + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.amount, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + **kwargs, + ) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=str(exchange_order_id), + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=OrderState.OPEN, + ) + self._order_tracker.process_order_update(order_update) + + return exchange_order_id + + def _on_order_failure( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Optional[Decimal], + exception: Exception, + **kwargs, + ): + self.logger().network( + f"Error submitting {trade_type.name.lower()} {order_type.name.upper()} order to {self.name_cap} for " + f"{amount} {trading_pair} {price}.", + exc_info=True, + app_warning_msg=f"Failed to submit {trade_type.name.upper()} order to {self.name_cap}. Check API key and network connection." + ) + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + + def _update_order_after_failure(self, order_id: str, trading_pair: str): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + self._order_tracker.process_order_update(order_update) + + async def _execute_order_cancel(self, order: InFlightOrder) -> str: + try: + cancelled = await self._execute_order_cancel_and_process_update(order=order) + if cancelled: + return order.client_order_id + except asyncio.CancelledError: + raise + except asyncio.TimeoutError: + # some exchanges do not allow cancels with the client/user order id + # so log a warning and wait for the creation of the order to complete + self.logger().warning( + f"Failed to cancel the order {order.client_order_id} because it does not have an exchange order id yet" + ) + await self._order_tracker.process_order_not_found(order.client_order_id) + except Exception as ex: + if self._is_order_not_found_during_cancelation_error(cancelation_exception=ex): + self.logger().warning(f"Failed to cancel order {order.client_order_id} (order not found)") + await self._order_tracker.process_order_not_found(order.client_order_id) + else: + self.logger().error(f"Failed to cancel order {order.client_order_id}", exc_info=True) + + async def _execute_order_cancel_and_process_update(self, order: InFlightOrder) -> bool: + cancelled = await self._place_cancel(order.client_order_id, order) + if cancelled: + update_timestamp = self.current_timestamp + if update_timestamp is None or math.isnan(update_timestamp): + update_timestamp = self._time() + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + ) + self._order_tracker.process_order_update(order_update) + return cancelled + + async def _execute_cancel(self, trading_pair: str, order_id: str) -> str: + """ + Requests the exchange to cancel an active order + + :param trading_pair: the trading pair the order to cancel operates with + :param order_id: the client id of the order to cancel + """ + result = None + tracked_order = self._order_tracker.fetch_tracked_order(order_id) + if tracked_order is not None: + result = await self._execute_order_cancel(order=tracked_order) + + return result + + # === Order Tracking === + + def restore_tracking_states(self, saved_states: Dict[str, Any]): + """ + Restore in-flight orders from saved tracking states, this is st the connector can pick up on where it left off + when it disconnects. + + :param saved_states: The saved tracking_states. + """ + self._order_tracker.restore_tracking_states(tracking_states=saved_states) + + def start_tracking_order(self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + **kwargs): + """ + Starts tracking an order by adding it to the order tracker. + + :param order_id: the order identifier + :param exchange_order_id: the identifier for the order in the exchange + :param trading_pair: the token pair for the operation + :param trade_type: the type of order (buy or sell) + :param price: the price for the order + :param amount: the amount for the order + :param order_type: type of execution for the order (MARKET, LIMIT, LIMIT_MAKER) + """ + self._order_tracker.start_tracking_order( + InFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp + ) + ) + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an order + + :param order_id: The id of the order that will not be tracked any more + """ + self._order_tracker.stop_tracking_order(client_order_id=order_id) + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) + + # === Implementation-specific methods === + + @abstractmethod + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + raise NotImplementedError + + @abstractmethod + async def _place_order(self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + raise NotImplementedError + + @abstractmethod + def _get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None) -> AddedToCostTradeFee: + raise NotImplementedError + + # === Network-API-related code === + + # overridden in implementation of exchanges + # + web_utils = None + + async def start_network(self): + """ + Start all required tasks to update the status of the connector. Those tasks include: + - The order book tracker + - The polling loops to update the trading rules and trading fees + - The polling loop to update order status and balance status using REST API (backup for main update process) + - The background task to process the events received through the user stream tracker (websocket connection) + """ + self._stop_network() + self.order_book_tracker.start() + if self.is_trading_required: + self._trading_rules_polling_task = safe_ensure_future(self._trading_rules_polling_loop()) + self._trading_fees_polling_task = safe_ensure_future(self._trading_fees_polling_loop()) + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._user_stream_tracker_task = self._create_user_stream_tracker_task() + self._user_stream_event_listener_task = safe_ensure_future(self._user_stream_event_listener()) + self._lost_orders_update_task = safe_ensure_future(self._lost_orders_update_polling_loop()) + + async def stop_network(self): + """ + This function is executed when the connector is stopped. It perform a general cleanup and stops all background + tasks that require the connection with the exchange to work. + """ + self._stop_network() + + async def check_network(self) -> NetworkStatus: + """ + Checks connectivity with the exchange using the API + """ + try: + await self._make_network_check_request() + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + def _stop_network(self): + # Resets timestamps and events for status_polling_loop + self._last_poll_timestamp = 0 + self._last_timestamp = 0 + self._poll_notifier = asyncio.Event() + + self.order_book_tracker.stop() + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._trading_rules_polling_task is not None: + self._trading_rules_polling_task.cancel() + self._trading_rules_polling_task = None + if self._trading_fees_polling_task is not None: + self._trading_fees_polling_task.cancel() + self._trading_fees_polling_task = None + if self._user_stream_tracker_task is not None: + self._user_stream_tracker_task.cancel() + self._user_stream_tracker_task = None + if self._user_stream_event_listener_task is not None: + self._user_stream_event_listener_task.cancel() + self._user_stream_event_listener_task = None + if self._lost_orders_update_task is not None: + self._lost_orders_update_task.cancel() + self._lost_orders_update_task = None + + # === loops and sync related methods === + # + async def _trading_rules_polling_loop(self): + """ + Updates the trading rules by requesting the latest definitions from the exchange. + Executes regularly every 30 minutes + """ + while True: + try: + await safe_gather(self._update_trading_rules()) + await self._sleep(self.TRADING_RULES_INTERVAL) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading rules.", exc_info=True, + app_warning_msg=f"Could not fetch new trading rules from {self.name_cap}" + " Check network connection.") + await self._sleep(0.5) + + async def _trading_fees_polling_loop(self): + """ + Only some exchanges provide a fee endpoint. + If _update_trading_fees() is not defined, we just exit the loop + """ + while True: + try: + await safe_gather(self._update_trading_fees()) + await self._sleep(self.TRADING_FEES_INTERVAL) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching trading fees.", exc_info=True, + app_warning_msg=f"Could not fetch new trading fees from {self.name_cap}." + " Check network connection.") + await self._sleep(0.5) + + async def _status_polling_loop(self): + """ + Performs all required operation to keep the connector updated and synchronized with the exchange. + It contains the backup logic to update status using API requests in case the main update source + (the user stream data source websocket) fails. + It also updates the time synchronizer. This is necessary because the exchange requires + the time of the client to be the same as the time in the exchange. + Executes when the _poll_notifier event is enabled by the `tick` function. + """ + while True: + try: + await self._poll_notifier.wait() + await self._update_time_synchronizer() + + # the following method is implementation-specific + await self._status_polling_loop_fetch_updates() + + self._last_poll_timestamp = self.current_timestamp + self._poll_notifier = asyncio.Event() + except asyncio.CancelledError: + raise + except NotImplementedError: + raise + except Exception: + self.logger().network( + "Unexpected error while fetching account updates.", + exc_info=True, + app_warning_msg=f"Could not fetch account updates from {self.name_cap}. " + "Check API key and network connection.") + await self._sleep(0.5) + + async def _update_time_synchronizer(self, pass_on_non_cancelled_error: bool = False): + try: + await self._time_synchronizer.update_server_time_offset_with_time_provider( + time_provider=self.web_utils.get_current_server_time( + throttler=self._throttler, + domain=self.domain, + ) + ) + except asyncio.CancelledError: + raise + except Exception: + if not pass_on_non_cancelled_error: + self.logger().exception(f"Error requesting time from {self.name_cap} server") + raise + + async def _lost_orders_update_polling_loop(self): + """ + This loop regularly executes the update of lost orders, to keep receiving any new order fill or status change + until we are totally sure the order is no longer alive in the exchange + """ + while True: + try: + await self._cancel_lost_orders() + await self._update_lost_orders_status() + await self._sleep(self.SHORT_POLL_INTERVAL) + except NotImplementedError: + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while updating the time synchronizer") + await self._sleep(0.5) + + async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]: + """ + Called by _user_stream_event_listener. + """ + while True: + try: + yield await self._user_stream_tracker.user_stream.get() + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Error while reading user events queue. Retrying in 1s.") + await self._sleep(1.0) + + def _is_user_stream_initialized(self): + return self._user_stream_tracker.data_source.last_recv_time > 0 or not self.is_trading_required + + def _create_user_stream_tracker(self): + return UserStreamTracker(data_source=self._create_user_stream_data_source()) + + def _create_user_stream_tracker_task(self): + return safe_ensure_future(self._user_stream_tracker.start()) + + # === Exchange / Trading logic methods that call the API === + + async def _update_trading_rules(self): + exchange_info = await self._make_trading_rules_request() + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + + async def _api_get(self, *args, **kwargs): + kwargs["method"] = RESTMethod.GET + return await self._api_request(*args, **kwargs) + + async def _api_post(self, *args, **kwargs): + kwargs["method"] = RESTMethod.POST + return await self._api_request(*args, **kwargs) + + async def _api_put(self, *args, **kwargs): + kwargs["method"] = RESTMethod.PUT + return await self._api_request(*args, **kwargs) + + async def _api_delete(self, *args, **kwargs): + kwargs["method"] = RESTMethod.DELETE + return await self._api_request(*args, **kwargs) + + async def _api_request_url(self, path_url: str, is_auth_required: bool = False) -> str: + if is_auth_required: + url = self.web_utils.private_rest_url(path_url, domain=self.domain) + else: + url = self.web_utils.public_rest_url(path_url, domain=self.domain) + + return url + + async def _api_request( + self, + path_url, + overwrite_url: Optional[str] = None, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + **kwargs, + ) -> Dict[str, Any]: + + last_exception = None + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + + url = overwrite_url or await self._api_request_url(path_url=path_url, is_auth_required=is_auth_required) + + for _ in range(2): + try: + request_result = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + throttler_limit_id=limit_id if limit_id else path_url, + ) + + return request_result + except IOError as request_exception: + last_exception = request_exception + if self._is_request_exception_related_to_time_synchronizer(request_exception=request_exception): + self._time_synchronizer.clear_time_offset_ms_samples() + await self._update_time_synchronizer() + else: + raise + + # Failed even after the last retry + raise last_exception + + async def _status_polling_loop_fetch_updates(self): + """ + Called by _status_polling_loop, which executes after each tick() is executed + """ + await safe_gather( + self._update_all_balances(), + self._update_order_status(), + ) + + async def _update_all_balances(self): + try: + await self._update_balances() + if not self.real_time_balance_update: + # This is only required for exchanges that do not provide balance update notifications through websocket + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self.in_flight_orders.items()} + self._in_flight_orders_snapshot_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Failed to update balances. Error: {request_error}", + exc_info=request_error, + ) + + async def _update_orders_fills(self, orders: List[InFlightOrder]): + for order in orders: + try: + trade_updates = await self._all_trade_updates_for_order(order=order) + for trade_update in trade_updates: + self._order_tracker.process_trade_update(trade_update) + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Failed to fetch trade updates for order {order.client_order_id}. Error: {request_error}", + exc_info=request_error, + ) + + async def _handle_update_error_for_active_order(self, order: InFlightOrder, error: Exception): + try: + raise error + except asyncio.TimeoutError: + self.logger().debug( + f"Tracked order {order.client_order_id} does not have an exchange id. " + f"Attempting fetch in next polling interval." + ) + await self._order_tracker.process_order_not_found(order.client_order_id) + except asyncio.CancelledError: + raise + except Exception as request_error: + self.logger().warning( + f"Error fetching status update for the active order {order.client_order_id}: {request_error}.", + ) + self.logger().debug(f"Order {order.client_order_id} not found counter: {self._order_tracker._order_not_found_records.get(order.client_order_id, 0)}") + await self._order_tracker.process_order_not_found(order.client_order_id) + + async def _handle_update_error_for_lost_order(self, order: InFlightOrder, error: Exception): + is_not_found = self._is_order_not_found_during_status_update_error(status_update_exception=error) + self.logger().debug(f"Order update error for lost order {order.client_order_id}\n{order}\nIs order not found: {is_not_found} ({error})") + if is_not_found: + self._update_order_after_failure(order.client_order_id, order.trading_pair) + else: + self.logger().warning(f"Error fetching status update for the lost order {order.client_order_id}: {error}.") + + async def _update_orders_with_error_handler(self, orders: List[InFlightOrder], error_handler: Callable): + for order in orders: + try: + order_update = await self._request_order_status(tracked_order=order) + self._order_tracker.process_order_update(order_update) + except asyncio.CancelledError: + raise + except Exception as request_error: + await error_handler(order, request_error) + + async def _update_orders(self): + orders_to_update = self.in_flight_orders.copy() + await self._update_orders_with_error_handler( + orders=list(orders_to_update.values()), error_handler=self._handle_update_error_for_active_order + ) + + async def _update_lost_orders(self): + orders_to_update = self._order_tracker.lost_orders.copy() + await self._update_orders_with_error_handler( + orders=list(orders_to_update.values()), error_handler=self._handle_update_error_for_lost_order + ) + + async def _update_order_status(self): + await self._update_orders_fills(orders=list(self._order_tracker.all_fillable_orders.values())) + await self._update_orders() + + async def _update_lost_orders_status(self): + await self._update_orders_fills(orders=list(self._order_tracker.lost_orders.values())) + await self._update_lost_orders() + + async def _cancel_lost_orders(self): + for _, lost_order in self._order_tracker.lost_orders.items(): + await self._execute_order_cancel(order=lost_order) + + # Methods tied to specific API data formats + # + @abstractmethod + async def _update_trading_fees(self): + raise NotImplementedError + + @abstractmethod + async def _user_stream_event_listener(self): + raise NotImplementedError + + @abstractmethod + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + raise NotImplementedError + + @abstractmethod + async def _update_balances(self): + raise NotImplementedError + + @abstractmethod + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + raise NotImplementedError + + @abstractmethod + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + raise NotImplementedError + + @abstractmethod + def _create_web_assistants_factory(self) -> WebAssistantsFactory: + raise NotImplementedError + + @abstractmethod + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + raise NotImplementedError + + @abstractmethod + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + raise NotImplementedError + + @abstractmethod + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + raise NotImplementedError + + def _create_order_tracker(self) -> ClientOrderTracker: + return ClientOrderTracker(connector=self) + + async def _initialize_trading_pair_symbol_map(self): + try: + exchange_info = await self._make_trading_pairs_request() + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + async def _make_network_check_request(self): + await self._api_get(path_url=self.check_network_request_path) + + async def _make_trading_rules_request(self) -> Any: + exchange_info = await self._api_get(path_url=self.trading_rules_request_path) + return exchange_info + + async def _make_trading_pairs_request(self) -> Any: + exchange_info = await self._api_get(path_url=self.trading_pairs_request_path) + return exchange_info + + def _get_poll_interval(self, timestamp: float) -> float: + last_user_stream_message_time = ( + 0 if self._user_stream_tracker is None else self._user_stream_tracker.last_recv_time + ) + last_recv_diff = timestamp - last_user_stream_message_time + poll_interval = ( + self.SHORT_POLL_INTERVAL if last_recv_diff > self.TICK_INTERVAL_LIMIT else self.LONG_POLL_INTERVAL + ) + return poll_interval diff --git a/hummingbot/connector/gateway/__init__.py b/hummingbot/connector/gateway/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/amm/__init__.py b/hummingbot/connector/gateway/amm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/amm/gateway_algorand_amm.py b/hummingbot/connector/gateway/amm/gateway_algorand_amm.py new file mode 100644 index 0000000..2471c85 --- /dev/null +++ b/hummingbot/connector/gateway/amm/gateway_algorand_amm.py @@ -0,0 +1,260 @@ +import asyncio +import itertools as it +import logging +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union, cast + +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal("0") +s_decimal_NaN = Decimal("nan") + +_milliseconds_nonce_provider = NonceCreator.for_milliseconds() + + +class GatewayAlgorandAMM(GatewayEVMAMM): + """ + Defines basic functions common to connectors that interact with Gateway. + """ + + _connector_name: str + _name: str + _chain: str + _network: str + _trading_pairs: List[str] + _tokens: Set[str] + _wallet_address: str + _trading_required: bool + _ev_loop: asyncio.AbstractEventLoop + _last_poll_timestamp: float + _last_balance_poll_timestamp: float + _allowances: Dict[str, Decimal] + _chain_info: Dict[str, Any] + _status_polling_task: Optional[asyncio.Task] + _get_chain_info_task: Optional[asyncio.Task] + _poll_notifier: Optional[asyncio.Event] + _native_currency: str + _amount_quantum_dict: Dict[str, Decimal] + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], # not implemented + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + super().__init__(client_config_map=client_config_map, connector_name=connector_name, chain=chain, network=network, address=address, trading_pairs=trading_pairs, trading_required=trading_required) + self._native_currency = "ALGO" + self._default_fee = Decimal("0.001") + self._network_transaction_fee: Optional[TokenAmount] = TokenAmount(token=self._native_currency, amount=self._default_fee) # Algorand fees are fixed + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(cls.__name__) + return cast(HummingbotLogger, s_logger) + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + *required + Updates inflight order statuses from API results + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + for order in saved_states.values(): + self._order_tracker.start_tracking_order(GatewayInFlightOrder.from_json(order)) + + async def all_trading_pairs(self) -> List[str]: + """ + Calls the tokens endpoint on Gateway. + """ + try: + tokens = await GatewayHttpClient.get_instance().get_algorand_assets(network=self._network) + token_symbols = [t["symbol"] for t in tokens["assets"]] + trading_pairs = [] + for base, quote in it.permutations(token_symbols, 2): + trading_pairs.append(f"{base}-{quote}") + return trading_pairs + except Exception: + return [] + + @staticmethod + def create_market_order_id(side: TradeType, trading_pair: str) -> str: + return f"{side.name.lower()}-{trading_pair}-{_milliseconds_nonce_provider.get_tracking_nonce()}" + + async def load_token_data(self): + tokens = await GatewayHttpClient.get_instance().get_algorand_assets(network=self._network) + for t in tokens.get("assets", []): + self._amount_quantum_dict[t["symbol"]] = Decimal(str(10 ** -t["decimals"])) + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + } + + async def start_network(self): + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._get_chain_info_task = safe_ensure_future(self.get_chain_info()) + + async def stop_network(self): + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._get_chain_info_task is not None: + self._get_chain_info_task.cancel() + self._get_chain_info_task = None + + async def _update_nonce(self, new_nonce: Optional[int] = None): + pass + + async def _status_polling_loop(self): + await self.update_balances(on_interval=False) + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self.update_balances(on_interval=True), + self.update_order_status(self.amm_orders) + ) + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(str(e), exc_info=True) + + async def update_order_status(self, tracked_orders: List[GatewayInFlightOrder]): + """ + Calls REST API to get status update for each in-flight amm orders. + { + "currentBlock": 28534865, + "txBlock": 28512623, + "txHash": "0xSCOCBDNHJVMA4I3VKETUHGFIM6HLTLAIALRPTL2LR6K66WPYEWUQ", + "fee": 1000 + } + """ + if len(tracked_orders) < 1: + return + + # split canceled and non-canceled orders + tx_hash_list: List[str] = await safe_gather( + *[tracked_order.get_exchange_order_id() for tracked_order in tracked_orders] + ) + self.logger().debug( + "Polling for order status updates of %d orders.", + len(tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_order, tx_details in zip(tracked_orders, update_results): + if isinstance(tx_details, Exception): + self.logger().error(f"An error occurred fetching transaction status of {tracked_order.client_order_id}") + continue + if "txHash" not in tx_details: + self.logger().error(f"No txHash field for transaction status of {tracked_order.client_order_id}: " + f"{tx_details}.") + continue + tx_block: int = tx_details["txBlock"] + if tx_block > 0: + self.processs_trade_fill_update(tracked_order=tracked_order, fee=self._default_fee) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FILLED, + ) + self._order_tracker.process_order_update(order_update) + else: + self.logger().network( + f"Error fetching transaction status for the order {tracked_order.client_order_id}: {tx_details}.", + app_warning_msg=f"Failed to fetch transaction status for the order {tracked_order.client_order_id}." + ) + await self._order_tracker.process_order_not_found(tracked_order.client_order_id) + + @async_ttl_cache(ttl=5, maxsize=10) + async def get_quote_price( + self, + trading_pair: str, + is_buy: bool, + amount: Decimal, + ignore_shim: bool = False + ) -> Optional[Decimal]: + """ + Retrieves a quote price. + + :param trading_pair: The market trading pair + :param is_buy: True for an intention to buy, False for an intention to sell + :param amount: The amount required (in base token unit) + :param ignore_shim: Ignore the price shim, and return the real price on the network + :return: The quote price. + """ + + base, quote = trading_pair.split("-") + side: TradeType = TradeType.BUY if is_buy else TradeType.SELL + + # Pull the price from gateway. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + return self.parse_price_response(base, quote, amount, side, price_response=resp, process_exception=False) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting quote price for {trading_pair} {side} order for {amount} amount.", + exc_info=True, + app_warning_msg=str(e) + ) + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is not supported for algorand blockchains. + """ + return [] + + async def _execute_cancel(self, order_id: str, cancel_age: int) -> Optional[str]: + """ + This is intentionally left blank, because cancellation is not supported for algorand blockchains. + """ + pass + + async def cancel_outdated_orders(self, cancel_age: int) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is not supported for algorand blockchains. + """ + return [] diff --git a/hummingbot/connector/gateway/amm/gateway_evm_amm.py b/hummingbot/connector/gateway/amm/gateway_evm_amm.py new file mode 100644 index 0000000..2085c17 --- /dev/null +++ b/hummingbot/connector/gateway/amm/gateway_evm_amm.py @@ -0,0 +1,1076 @@ +import asyncio +import copy +import itertools as it +import logging +import re +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union, cast + +from async_timeout import timeout + +from hummingbot.client.settings import GatewayConnectionSetting +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_price_shim import GatewayPriceShim +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + OrderType, + TokenApprovalCancelledEvent, + TokenApprovalEvent, + TokenApprovalFailureEvent, + TokenApprovalSuccessEvent, + TradeType, +) +from hummingbot.core.gateway import check_transaction_exceptions +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal("0") +s_decimal_NaN = Decimal("nan") + + +class GatewayEVMAMM(ConnectorBase): + """ + Defines basic functions common to connectors that interact with Gateway. + """ + + API_CALL_TIMEOUT = 10.0 + POLL_INTERVAL = 1.0 + UPDATE_BALANCE_INTERVAL = 30.0 + APPROVAL_ORDER_ID_PATTERN = re.compile(r"approve-(\w+)-(\w+)") + + _connector_name: str + _name: str + _chain: str + _network: str + _trading_pairs: List[str] + _tokens: Set[str] + _wallet_address: str + _trading_required: bool + _ev_loop: asyncio.AbstractEventLoop + _last_poll_timestamp: float + _last_balance_poll_timestamp: float + _last_est_gas_cost_reported: float + _allowances: Dict[str, Decimal] + _chain_info: Dict[str, Any] + _status_polling_task: Optional[asyncio.Task] + _get_chain_info_task: Optional[asyncio.Task] + _update_allowances: Optional[asyncio.Task] + _poll_notifier: Optional[asyncio.Event] + _native_currency: str + _amount_quantum_dict: Dict[str, Decimal] + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], # not implemented + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + self._connector_name = connector_name + self._name = "_".join([connector_name, chain, network]) + super().__init__(client_config_map) + self._chain = chain + self._network = network + self._trading_pairs = trading_pairs + self._tokens = set() + [self._tokens.update(set(trading_pair.split("-"))) for trading_pair in trading_pairs] + self._wallet_address = address + self._trading_required = trading_required + self._ev_loop = asyncio.get_event_loop() + self._last_poll_timestamp = 0.0 + self._last_balance_poll_timestamp = time.time() + self._last_est_gas_cost_reported = 0 + self._allowances = {} + self._chain_info = {} + self._status_polling_task = None + self._get_chain_info_task = None + self._get_gas_estimate_task = None + self._poll_notifier = None + self._native_currency = None + self._network_transaction_fee: Optional[TokenAmount] = None + self._order_tracker: ClientOrderTracker = ClientOrderTracker(connector=self, lost_order_count_limit=10) + self._amount_quantum_dict = {} + safe_ensure_future(self.load_token_data()) + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(cls.__name__) + return cast(HummingbotLogger, s_logger) + + @property + def connector_name(self): + """ + This returns the name of connector/protocol to be connected to on Gateway. + """ + return self._connector_name + + @property + def chain(self): + return self._chain + + @property + def network(self): + return self._network + + @property + def name(self): + return self._name + + @property + def address(self): + return self._wallet_address + + async def all_trading_pairs(self) -> List[str]: + """ + Calls the tokens endpoint on Gateway. + """ + try: + tokens = await GatewayHttpClient.get_instance().get_tokens(self._chain, self._network) + token_symbols = [t["symbol"] for t in tokens["tokens"]] + trading_pairs = [] + for base, quote in it.permutations(token_symbols, 2): + trading_pairs.append(f"{base}-{quote}") + return trading_pairs + except Exception: + return [] + + @property + def approval_orders(self) -> List[GatewayInFlightOrder]: + return [ + approval_order + for approval_order in self._order_tracker.active_orders.values() + if approval_order.is_approval_request + ] + + @property + def amm_orders(self) -> List[GatewayInFlightOrder]: + return [ + in_flight_order + for in_flight_order in self._order_tracker.active_orders.values() + if in_flight_order.is_open + ] + + @property + def canceling_orders(self) -> List[GatewayInFlightOrder]: + return [ + cancel_order + for cancel_order in self.amm_orders + if cancel_order.is_pending_cancel_confirmation + ] + + @property + def limit_orders(self) -> List[LimitOrder]: + return [ + in_flight_order.to_limit_order() + for in_flight_order in self.amm_orders + ] + + @property + def network_transaction_fee(self) -> TokenAmount: + """ + The most recently known transaction fee (i.e. gas fees) required for making trades. + """ + return self._network_transaction_fee + + @network_transaction_fee.setter + def network_transaction_fee(self, new_fee: TokenAmount): + self._network_transaction_fee = new_fee + + @property + def in_flight_orders(self) -> Dict[str, GatewayInFlightOrder]: + return self._order_tracker.active_orders + + @property + def tracking_states(self) -> Dict[str, Any]: + """ + Returns a dictionary associating current active orders client id to their JSON representation + """ + return { + key: value.to_json() + for key, value in self.in_flight_orders.items() + } + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + *required + Updates inflight order statuses from API results + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + self._order_tracker._in_flight_orders.update({ + key: GatewayInFlightOrder.from_json(value) + for key, value in saved_states.items() + }) + + def create_approval_order_id(self, token_symbol: str) -> str: + return f"approve-{self.connector_name}-{token_symbol}" + + def get_token_symbol_from_approval_order_id(self, approval_order_id: str) -> Optional[str]: + match = self.APPROVAL_ORDER_ID_PATTERN.search(approval_order_id) + if match: + return match.group(2) + return None + + @staticmethod + def create_market_order_id(side: TradeType, trading_pair: str) -> str: + return f"{side.name.lower()}-{trading_pair}-{get_tracking_nonce()}" + + def is_pending_approval(self, token: str) -> bool: + for order in self.approval_orders: + if token in order.client_order_id: + return order.is_pending_approval + return False + + async def load_token_data(self): + tokens = await GatewayHttpClient.get_instance().get_tokens(self.chain, self.network) + for t in tokens.get("tokens", []): + self._amount_quantum_dict[t["symbol"]] = Decimal(str(10 ** -t["decimals"])) + + async def get_chain_info(self): + """ + Calls the base endpoint of the connector on Gateway to know basic info about chain being used. + """ + try: + self._chain_info = await self._get_gateway_instance().get_network_status( + chain=self.chain, network=self.network + ) + if type(self._chain_info) != list: + self._native_currency = self._chain_info.get("nativeCurrency", "ETH") + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + "Error fetching chain info", + exc_info=True, + app_warning_msg=str(e) + ) + + async def get_gas_estimate(self): + """ + Gets the gas estimates for the connector. + """ + try: + response: Dict[Any] = await self._get_gateway_instance().amm_estimate_gas( + chain=self.chain, network=self.network, connector=self.connector_name + ) + self.network_transaction_fee = TokenAmount( + response.get("gasPriceToken"), Decimal(response.get("gasCost")) + ) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting gas price estimates for {self.connector_name} on {self.network}.", + exc_info=True, + app_warning_msg=str(e) + ) + + async def approve_token(self, token_symbol: str, **request_args) -> Optional[GatewayInFlightOrder]: + """ + Approves contract as a spender for a token. + :param token_symbol: token to approve. + """ + approval_id: str = self.create_approval_order_id(token_symbol) + + self.logger().info(f"Initiating approval for {token_symbol}.") + + self.start_tracking_order(order_id=approval_id, + trading_pair=token_symbol, + is_approval=True) + try: + resp: Dict[str, Any] = await self._get_gateway_instance().approve_token( + self.chain, + self.network, + self.address, + token_symbol, + self.connector_name, + **request_args + ) + + transaction_hash: Optional[str] = resp.get("approval", {}).get("hash") + nonce: Optional[int] = resp.get("nonce") + if transaction_hash is not None and nonce is not None: + tracked_order = self._order_tracker.fetch_order(client_order_id=approval_id) + tracked_order.update_exchange_order_id(transaction_hash) + tracked_order.nonce = nonce + self.logger().info( + f"Maximum {token_symbol} approval for {self.connector_name} contract sent, hash: {transaction_hash}." + ) + return tracked_order + else: + self.stop_tracking_order(approval_id) + self.logger().info(f"Approval for {token_symbol} on {self.connector_name} failed.") + return None + except Exception: + self.stop_tracking_order(approval_id) + self.logger().error( + f"Error submitting approval order for {token_symbol} on {self.connector_name}-{self.network}.", + exc_info=True + ) + return None + + async def update_allowances(self): + """ + Allowances updated continously. + """ + while True: + self._allowances = await self.get_allowances() + await asyncio.sleep(120) # sleep for 2 mins + + async def get_allowances(self) -> Dict[str, Decimal]: + """ + Retrieves allowances for token in trading_pairs + :return: A dictionary of token and its allowance. + """ + ret_val = {} + resp: Dict[str, Any] = await self._get_gateway_instance().get_allowances( + self.chain, self.network, self.address, list(self._tokens), self.connector_name + ) + for token, amount in resp["approvals"].items(): + ret_val[token] = Decimal(str(amount)) + return ret_val + + def parse_price_response( + self, + base: str, + quote: str, + amount: Decimal, + side: TradeType, + price_response: Dict[str, Any], + process_exception: bool = True + ) -> Optional[Decimal]: + """ + Parses price response + :param base: The base asset + :param quote: The quote asset + :param amount: amount + :param side: trade side + :param price_response: Price response from Gateway. + :param process_exception: Flag to trigger error on exception + """ + required_items = ["price", "gasLimit", "gasPrice", "gasCost", "gasPriceToken"] + if any(item not in price_response.keys() for item in required_items): + if "info" in price_response.keys(): + self.logger().info(f"Unable to get price. {price_response['info']}") + else: + self.logger().info(f"Missing data from price result. Incomplete return result for ({price_response.keys()})") + else: + gas_price_token: str = price_response["gasPriceToken"] + gas_cost: Decimal = Decimal(price_response["gasCost"]) + price: Decimal = Decimal(price_response["price"]) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + if process_exception is True: + gas_limit: int = int(price_response["gasLimit"]) + exceptions: List[str] = check_transaction_exceptions( + allowances=self._allowances, + balances=self._account_balances, + base_asset=base, + quote_asset=quote, + amount=amount, + side=side, + gas_limit=gas_limit, + gas_cost=gas_cost, + gas_asset=gas_price_token, + swaps_count=len(price_response.get("swaps", [])) + ) + for index in range(len(exceptions)): + self.logger().warning( + f"Warning! [{index + 1}/{len(exceptions)}] {side} order - {exceptions[index]}" + ) + if len(exceptions) > 0: + return None + return Decimal(str(price)) + return None + + @async_ttl_cache(ttl=5, maxsize=10) + async def get_quote_price( + self, + trading_pair: str, + is_buy: bool, + amount: Decimal, + ignore_shim: bool = False + ) -> Optional[Decimal]: + """ + Retrieves a quote price. + + :param trading_pair: The market trading pair + :param is_buy: True for an intention to buy, False for an intention to sell + :param amount: The amount required (in base token unit) + :param ignore_shim: Ignore the price shim, and return the real price on the network + :return: The quote price. + """ + + base, quote = trading_pair.split("-") + side: TradeType = TradeType.BUY if is_buy else TradeType.SELL + + # Get the price from gateway price shim for integration tests. + if not ignore_shim: + test_price: Optional[Decimal] = await GatewayPriceShim.get_instance().get_connector_price( + self.connector_name, + self.chain, + self.network, + trading_pair, + is_buy, + amount + ) + if test_price is not None: + # Grab the gas price for test net. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + gas_price_token: str = resp["gasPriceToken"] + gas_cost: Decimal = Decimal(resp["gasCost"]) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + except asyncio.CancelledError: + raise + except Exception: + pass + return test_price + + # Pull the price from gateway. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + return self.parse_price_response(base, quote, amount, side, price_response=resp) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting quote price for {trading_pair} {side} order for {amount} amount.", + exc_info=True, + app_warning_msg=str(e) + ) + + async def get_order_price( + self, + trading_pair: str, + is_buy: bool, + amount: Decimal, + ignore_shim: bool = False + ) -> Decimal: + + """ + This is simply the quote price + """ + return await self.get_quote_price(trading_pair, is_buy, amount, ignore_shim=ignore_shim) + + def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs) -> str: + """ + Buys an amount of base token for a given price (or cheaper). + :param trading_pair: The market trading pair + :param amount: The order amount (in base token unit) + :param order_type: Any order type is fine, not needed for this. + :param price: The maximum price for the order. + :return: A newly created order id (internal). + """ + return self.place_order(True, trading_pair, amount, price) + + def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs) -> str: + """ + Sells an amount of base token for a given price (or at a higher price). + :param trading_pair: The market trading pair + :param amount: The order amount (in base token unit) + :param order_type: Any order type is fine, not needed for this. + :param price: The minimum price for the order. + :return: A newly created order id (internal). + """ + return self.place_order(False, trading_pair, amount, price) + + def place_order(self, is_buy: bool, trading_pair: str, amount: Decimal, price: Decimal, **request_args) -> str: + """ + Places an order. + :param is_buy: True for buy order + :param trading_pair: The market trading pair + :param amount: The order amount (in base token unit) + :param price: The minimum price for the order. + :return: A newly created order id (internal). + """ + side: TradeType = TradeType.BUY if is_buy else TradeType.SELL + order_id: str = self.create_market_order_id(side, trading_pair) + safe_ensure_future(self._create_order(side, order_id, trading_pair, amount, price, **request_args)) + return order_id + + async def _create_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + price: Decimal, + **request_args + ): + """ + Calls buy or sell API end point to place an order, starts tracking the order and triggers relevant order events. + :param trade_type: BUY or SELL + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param amount: The order amount (in base token value) + :param price: The order price + """ + + amount = self.quantize_order_amount(trading_pair, amount) + price = self.quantize_order_price(trading_pair, price) + base, quote = trading_pair.split("-") + self.start_tracking_order(order_id=order_id, + trading_pair=trading_pair, + trade_type=trade_type, + price=price, + amount=amount) + try: + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_trade( + self.chain, + self.network, + self.connector_name, + self.address, + base, + quote, + trade_type, + amount, + price, + **request_args + ) + transaction_hash: Optional[str] = order_result.get("txHash") + if transaction_hash is not None and transaction_hash != "": + gas_cost: Decimal = Decimal(order_result.get("gasCost")) + gas_price_token: str = order_result.get("gasPriceToken") + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + exchange_order_id=transaction_hash, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.OPEN, # Assume that the transaction has been successfully mined. + misc_updates={ + "nonce": order_result.get("nonce"), + "gas_price": Decimal(order_result.get("gasPrice")), + "gas_limit": int(order_result.get("gasLimit")), + "gas_cost": Decimal(order_result.get("gasCost")), + "gas_price_token": order_result.get("gasPriceToken"), + "fee_asset": self._native_currency + } + ) + self._order_tracker.process_order_update(order_update) + else: + raise ValueError + + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Error submitting {trade_type.name} swap order to {self.connector_name} on {self.network} for " + f"{amount} {trading_pair} " + f"{price}.", + exc_info=True + ) + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED + ) + self._order_tracker.process_order_update(order_update) + + def start_tracking_order(self, + order_id: str, + exchange_order_id: Optional[str] = None, + trading_pair: str = "", + trade_type: TradeType = TradeType.BUY, + price: Decimal = s_decimal_0, + amount: Decimal = s_decimal_0, + gas_price: Decimal = s_decimal_0, + is_approval: bool = False): + """ + Starts tracking an order by simply adding it into _in_flight_orders dictionary in ClientOrderTracker. + """ + self._order_tracker.start_tracking_order( + GatewayInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=trade_type, + price=price, + amount=amount, + gas_price=gas_price, + creation_timestamp=self.current_timestamp, + initial_state=OrderState.PENDING_APPROVAL if is_approval else OrderState.PENDING_CREATE + ) + ) + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an order by simply removing it from _in_flight_orders dictionary in ClientOrderTracker. + """ + self._order_tracker.stop_tracking_order(client_order_id=order_id) + + async def update_token_approval_status(self, tracked_approvals: List[GatewayInFlightOrder]): + """ + Calls REST API to get status update for each in-flight token approval transaction. + """ + if len(tracked_approvals) < 1: + return + tx_hash_list: List[str] = await safe_gather(*[ + tracked_approval.get_exchange_order_id() for tracked_approval in tracked_approvals + ]) + transaction_states: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_approval, transaction_status in zip(tracked_approvals, transaction_states): + token_symbol: str = self.get_token_symbol_from_approval_order_id(tracked_approval.client_order_id) + if isinstance(transaction_status, Exception): + self.logger().error(f"Error while trying to approve token {token_symbol} for {self.connector_name}: " + f"{transaction_status}") + continue + if "txHash" not in transaction_status: + self.logger().error(f"Error while trying to approve token {token_symbol} for {self.connector_name}: " + "txHash key not found in transaction status.") + continue + if transaction_status["txStatus"] == 1: + if transaction_status["txReceipt"]["status"] == 1: + self.logger().info(f"Token approval for {tracked_approval.client_order_id} on {self.connector_name} " + f"successful.") + tracked_approval.current_state = OrderState.APPROVED + self.trigger_event( + TokenApprovalEvent.ApprovalSuccessful, + TokenApprovalSuccessEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + else: + self.logger().warning( + f"Token approval for {tracked_approval.client_order_id} on {self.connector_name} failed." + ) + tracked_approval.current_state = OrderState.FAILED + self.trigger_event( + TokenApprovalEvent.ApprovalFailed, + TokenApprovalFailureEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + self.stop_tracking_order(tracked_approval.client_order_id) + + async def update_canceling_transactions(self, canceled_tracked_orders: List[GatewayInFlightOrder]): + """ + Update tracked orders that have a cancel_tx_hash. + :param canceled_tracked_orders: Canceled tracked_orders (cancel_tx_has is not None). + """ + if len(canceled_tracked_orders) < 1: + return + + self.logger().debug( + "Polling for order status updates of %d canceled orders.", + len(canceled_tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in [t.cancel_tx_hash for t in canceled_tracked_orders] + ], return_exceptions=True) + for tracked_order, update_result in zip(canceled_tracked_orders, update_results): + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().error(f"No txHash field for transaction status of {tracked_order.client_order_id}: " + f"{update_result}.") + continue + if update_result["txStatus"] == 1: + if update_result["txReceipt"]["status"] == 1: + if tracked_order.current_state == OrderState.PENDING_CANCEL: + if not tracked_order.is_approval_request: + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + client_order_id=tracked_order.client_order_id, + update_timestamp=self.current_timestamp, + new_state=OrderState.CANCELED + ) + self._order_tracker.process_order_update(order_update) + + elif tracked_order.is_approval_request: + order_update: OrderUpdate = OrderUpdate( + trading_pair=tracked_order.trading_pair, + client_order_id=tracked_order.client_order_id, + update_timestamp=self.current_timestamp, + new_state=OrderState.CANCELED + ) + token_symbol: str = self.get_token_symbol_from_approval_order_id( + tracked_order.client_order_id + ) + self.trigger_event( + TokenApprovalEvent.ApprovalCancelled, + TokenApprovalCancelledEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + self.logger().info(f"Token approval for {tracked_order.client_order_id} on " + f"{self.connector_name} has been canceled.") + self.stop_tracking_order(tracked_order.client_order_id) + + def processs_trade_fill_update(self, tracked_order: GatewayInFlightOrder, fee: Decimal): + trade_fee: TradeFeeBase = AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, fee)] + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id=tracked_order.exchange_order_id, + client_order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + trading_pair=tracked_order.trading_pair, + fill_timestamp=self.current_timestamp, + fill_price=tracked_order.price, + fill_base_amount=tracked_order.amount, + fill_quote_amount=tracked_order.amount * tracked_order.price, + fee=trade_fee + ) + + self._order_tracker.process_trade_update(trade_update) + + async def update_order_status(self, tracked_orders: List[GatewayInFlightOrder]): + """ + Calls REST API to get status update for each in-flight amm orders. + """ + if len(tracked_orders) < 1: + return + + # split canceled and non-canceled orders + tx_hash_list: List[str] = await safe_gather( + *[tracked_order.get_exchange_order_id() for tracked_order in tracked_orders] + ) + self.logger().debug( + "Polling for order status updates of %d orders.", + len(tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_order, tx_details in zip(tracked_orders, update_results): + if isinstance(tx_details, Exception): + self.logger().error(f"An error occurred fetching transaction status of {tracked_order.client_order_id}") + continue + if "txHash" not in tx_details: + self.logger().error(f"No txHash field for transaction status of {tracked_order.client_order_id}: " + f"{tx_details}.") + continue + tx_status: int = tx_details["txStatus"] + tx_receipt: Optional[Dict[str, Any]] = tx_details["txReceipt"] + if tx_status == 1 and (tx_receipt is not None and tx_receipt.get("status") == 1): + gas_used: int = tx_receipt["gasUsed"] + gas_price: Decimal = tracked_order.gas_price + fee: Decimal = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) + + self.processs_trade_fill_update(tracked_order=tracked_order, fee=fee) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FILLED, + ) + self._order_tracker.process_order_update(order_update) + elif tx_status in [0, 2, 3]: + # 0: in the mempool but we dont have data to guess its status + # 2: in the mempool and likely to succeed + # 3: in the mempool and likely to fail + pass + + elif tx_status == -1 or (tx_receipt is not None and tx_receipt.get("status") == 0): + self.logger().network( + f"Error fetching transaction status for the order {tracked_order.client_order_id}: {tx_details}.", + app_warning_msg=f"Failed to fetch transaction status for the order {tracked_order.client_order_id}." + ) + await self._order_tracker.process_order_not_found(tracked_order.client_order_id) + + def get_taker_order_type(self): + return OrderType.LIMIT + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("1e-15") + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + base, quote = trading_pair.split("-") + return max(self._amount_quantum_dict[base], self._amount_quantum_dict[quote]) + + @property + def ready(self): + return all(self.status_dict.values()) + + def has_allowances(self) -> bool: + """ + Checks if all tokens have allowance (an amount approved) + """ + allowances_available = all(amount > s_decimal_0 for amount in self._allowances.values()) + return ((len(self._allowances.values()) == len(self._tokens)) and + (allowances_available)) + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "allowances: use trading interface to do manual approval.": self.has_allowances() if self._trading_required else True, + "native_currency": self._native_currency is not None, + "network_transaction_fee": self.network_transaction_fee is not None if self._trading_required else True, + } + + async def start_network(self): + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._update_allowances = safe_ensure_future(self.update_allowances()) + self._get_gas_estimate_task = safe_ensure_future(self.get_gas_estimate()) + self._get_chain_info_task = safe_ensure_future(self.get_chain_info()) + + async def stop_network(self): + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._update_allowances is not None: + self._update_allowances.cancel() + self._update_allowances = None + if self._get_chain_info_task is not None: + self._get_chain_info_task.cancel() + self._get_chain_info_task = None + if self._get_gas_estimate_task is not None: + self._get_gas_estimate_task.cancel() + self._get_chain_info_task = None + + async def check_network(self) -> NetworkStatus: + try: + if await self._get_gateway_instance().ping_gateway(): + return NetworkStatus.CONNECTED + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.NOT_CONNECTED + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: + if self._poll_notifier is not None and not self._poll_notifier.is_set(): + self._poll_notifier.set() + + async def _update_nonce(self, new_nonce: Optional[int] = None): + """ + Call the gateway API to get the current nonce for self.address + """ + if not new_nonce: + resp_json: Dict[str, Any] = await self._get_gateway_instance().get_evm_nonce(self.chain, self.network, self.address) + new_nonce: int = resp_json.get("nonce") + + self._nonce = new_nonce + + async def _status_polling_loop(self): + await self.update_balances(on_interval=False) + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self.update_balances(on_interval=True), + self.update_canceling_transactions(self.canceling_orders), + self.update_token_approval_status(self.approval_orders), + self.update_order_status(self.amm_orders) + ) + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(str(e), exc_info=True) + + async def update_balances(self, on_interval: bool = False): + """ + Calls Eth API to update total and available balances. + """ + if self._native_currency is None: + await self.get_chain_info() + connector_tokens = GatewayConnectionSetting.get_connector_spec_from_market_name(self._name).get("tokens", "").split(",") + last_tick = self._last_balance_poll_timestamp + current_tick = self.current_timestamp + if not on_interval or (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL: + self._last_balance_poll_timestamp = current_tick + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + token_list = list(self._tokens) + [self._native_currency] + connector_tokens + resp_json: Dict[str, Any] = await self._get_gateway_instance().get_balances( + chain=self.chain, + network=self.network, + address=self.address, + token_symbols=token_list + ) + for token, bal in resp_json["balances"].items(): + self._account_available_balances[token] = Decimal(str(bal)) + self._account_balances[token] = Decimal(str(bal)) + remote_asset_names.add(token) + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._order_tracker.all_orders.items()} + self._in_flight_orders_snapshot_timestamp = self.current_timestamp + + async def _update_balances(self): + """ + This is called by UserBalances. + """ + await self.update_balances() + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is expensive on blockchains. It's not worth it for + Hummingbot to force cancel all orders whenever Hummingbot quits. + """ + return [] + + async def _execute_cancel(self, order_id: str, cancel_age: int) -> Optional[str]: + """ + Cancel an existing order if the age of the order is greater than its cancel_age, + and if the order is not done or already in the cancelling state. + """ + try: + tracked_order: GatewayInFlightOrder = self._order_tracker.fetch_order(client_order_id=order_id) + if tracked_order is None: + self.logger().error(f"The order {order_id} is not being tracked.") + raise ValueError(f"The order {order_id} is not being tracked.") + + if (self.current_timestamp - tracked_order.creation_timestamp) < cancel_age: + return None + + if tracked_order.is_done: + return None + + if tracked_order.is_pending_cancel_confirmation: + return order_id + + self.logger().info(f"The blockchain transaction for {order_id} with nonce {tracked_order.nonce} has " + f"expired. Canceling the order...") + resp: Dict[str, Any] = await self._get_gateway_instance().cancel_evm_transaction( + self.chain, + self.network, + self.address, + tracked_order.nonce + ) + + tx_hash: Optional[str] = resp.get("txHash") + if tx_hash is not None: + tracked_order.cancel_tx_hash = tx_hash + else: + raise EnvironmentError(f"Missing txHash from cancel_evm_transaction() response: {resp}.") + + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.PENDING_CANCEL + ) + self._order_tracker.process_order_update(order_update) + + return order_id + except asyncio.CancelledError: + raise + except Exception as err: + self.logger().error( + f"Failed to cancel order {order_id}: {str(err)}.", + exc_info=True + ) + + async def cancel_outdated_orders(self, cancel_age: int) -> List[CancellationResult]: + """ + Iterate through all known orders and cancel them if their age is greater than cancel_age. + """ + incomplete_orders: List[GatewayInFlightOrder] = [] + + # Incomplete Approval Requests + incomplete_orders.extend([ + o for o in self.approval_orders + if o.is_pending_approval + ]) + # Incomplete Active Orders + incomplete_orders.extend([ + o for o in self.amm_orders + if not o.is_done + ]) + + if len(incomplete_orders) < 1: + return [] + + timeout_seconds: float = 30.0 + canceling_id_set: Set[str] = set([o.client_order_id for o in incomplete_orders]) + sent_cancellations: List[CancellationResult] = [] + + try: + async with timeout(timeout_seconds): + for incomplete_order in incomplete_orders: + try: + canceling_order_id: Optional[str] = await self._execute_cancel( + incomplete_order.client_order_id, + cancel_age + ) + except Exception: + continue + if canceling_order_id is not None: + canceling_id_set.remove(canceling_order_id) + sent_cancellations.append(CancellationResult(canceling_order_id, True)) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error cancelling outdated orders.", + exc_info=True, + app_warning_msg=f"Failed to cancel orders on {self.chain}-{self.network}." + ) + + skipped_cancellations: List[CancellationResult] = [CancellationResult(oid, False) for oid in canceling_id_set] + return sent_cancellations + skipped_cancellations + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._client_config) + return gateway_instance diff --git a/hummingbot/connector/gateway/amm/gateway_near_amm.py b/hummingbot/connector/gateway/amm/gateway_near_amm.py new file mode 100644 index 0000000..82ba027 --- /dev/null +++ b/hummingbot/connector/gateway/amm/gateway_near_amm.py @@ -0,0 +1,225 @@ +import asyncio +import logging +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast + +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_price_shim import GatewayPriceShim +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None + + +class GatewayNearAMM(GatewayEVMAMM): + """ + Defines basic functions common to connectors that interact with Gateway. + """ + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], # not implemented + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + super().__init__(client_config_map=client_config_map, + connector_name=connector_name, + chain=chain, + network=network, + address=address, + trading_pairs=trading_pairs, + additional_spenders=additional_spenders, + trading_required=trading_required) + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(cls.__name__) + return cast(HummingbotLogger, s_logger) + + async def update_order_status(self, tracked_orders: List[GatewayInFlightOrder]): + """ + Calls REST API to get status update for each in-flight amm orders. + """ + if len(tracked_orders) < 1: + return + + # split canceled and non-canceled orders + tx_hash_list: List[str] = await safe_gather( + *[tracked_order.get_exchange_order_id() for tracked_order in tracked_orders] + ) + self.logger().debug( + "Polling for order status updates of %d orders.", + len(tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + chain=self.chain, + network=self.network, + transaction_hash=tx_hash, + address=self.address, + fail_silently=True + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_order, tx_details in zip(tracked_orders, update_results): + if "txHash" not in tx_details: + continue + tx_status: int = tx_details.get("txStatus", -1) + tx_receipt: Optional[Dict[str, Any]] = tx_details.get("txReceipt", None) + if tx_receipt is not None: + if tx_status == 1: + gas_used: int = tx_receipt["transaction_outcome"]["outcome"]["gas_burnt"] + gas_price: Decimal = tracked_order.gas_price + fee: Decimal = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e24)) + + self.processs_trade_fill_update(tracked_order=tracked_order, fee=fee) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FILLED, + ) + + else: # transaction failed + order_update: OrderUpdate = OrderUpdate( + client_order_id=tracked_order.client_order_id, + trading_pair=tracked_order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + + self._order_tracker.process_order_update(order_update) + else: + await self._order_tracker.process_order_not_found(tracked_order.client_order_id) + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + return Decimal("1e-15") + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "native_currency": self._native_currency is not None, + "network_transaction_fee": self.network_transaction_fee is not None if self._trading_required else True, + } + + async def start_network(self): + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._get_gas_estimate_task = safe_ensure_future(self.get_gas_estimate()) + self._get_chain_info_task = safe_ensure_future(self.get_chain_info()) + + async def stop_network(self): + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._get_chain_info_task is not None: + self._get_chain_info_task.cancel() + self._get_chain_info_task = None + if self._get_gas_estimate_task is not None: + self._get_gas_estimate_task.cancel() + self._get_chain_info_task = None + + async def cancel_outdated_orders(self, cancel_age: int) -> List[CancellationResult]: + """ + We do not cancel transactions on Near network. + """ + return [] + + async def update_canceling_transactions(self, canceled_tracked_orders: List[GatewayInFlightOrder]): + """ + Update tracked orders that have a cancel_tx_hash. + :param canceled_tracked_orders: Canceled tracked_orders (cancel_tx_has is not None). + """ + pass + + async def update_token_approval_status(self, tracked_approvals: List[GatewayInFlightOrder]): + """ + Calls REST API to get status update for each in-flight token approval transaction. + :param tracked_approvals: tracked approval orders. + """ + pass + + @async_ttl_cache(ttl=5, maxsize=10) + async def get_quote_price( + self, + trading_pair: str, + is_buy: bool, + amount: Decimal, + ignore_shim: bool = False + ) -> Optional[Decimal]: + """ + Retrieves a quote price. + + :param trading_pair: The market trading pair + :param is_buy: True for an intention to buy, False for an intention to sell + :param amount: The amount required (in base token unit) + :param ignore_shim: Ignore the price shim, and return the real price on the network + :return: The quote price. + """ + + base, quote = trading_pair.split("-") + side: TradeType = TradeType.BUY if is_buy else TradeType.SELL + + # Get the price from gateway price shim for integration tests. + if not ignore_shim: + test_price: Optional[Decimal] = await GatewayPriceShim.get_instance().get_connector_price( + self.connector_name, + self.chain, + self.network, + trading_pair, + is_buy, + amount + ) + if test_price is not None: + # Grab the gas price for test net. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + gas_price_token: str = resp["gasPriceToken"] + gas_cost: Decimal = Decimal(resp["gasCost"]) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + except asyncio.CancelledError: + raise + except Exception: + pass + return test_price + + # Pull the price from gateway. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + return self.parse_price_response(base, quote, amount, side, price_response=resp, process_exception=False) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting quote price for {trading_pair} {side} order for {amount} amount.", + exc_info=True, + app_warning_msg=str(e) + ) diff --git a/hummingbot/connector/gateway/amm/gateway_tezos_amm.py b/hummingbot/connector/gateway/amm/gateway_tezos_amm.py new file mode 100644 index 0000000..a21b438 --- /dev/null +++ b/hummingbot/connector/gateway/amm/gateway_tezos_amm.py @@ -0,0 +1,138 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway import check_transaction_exceptions + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GatewayTezosAMM(GatewayEVMAMM): + """ + Defines basic functions common to connectors that interact with Gateway. + """ + + API_CALL_TIMEOUT = 60.0 + POLL_INTERVAL = 15.0 + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], # not implemented + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + super().__init__(client_config_map=client_config_map, + connector_name=connector_name, + chain=chain, + network=network, + address=address, + trading_pairs=trading_pairs, + additional_spenders=additional_spenders, + trading_required=trading_required) + + async def get_chain_info(self): + """ + Calls the base endpoint of the connector on Gateway to know basic info about chain being used. + """ + try: + self._chain_info = await self._get_gateway_instance().get_network_status( + chain=self.chain, network=self.network + ) + if not isinstance(self._chain_info, list): + self._native_currency = self._chain_info.get("nativeCurrency", "XTZ") + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + "Error fetching chain info", + exc_info=True, + app_warning_msg=str(e) + ) + + def parse_price_response( + self, + base: str, + quote: str, + amount: Decimal, + side: TradeType, + price_response: Dict[str, Any], + process_exception: bool = True + ) -> Optional[Decimal]: + """ + Parses price response + :param base: The base asset + :param quote: The quote asset + :param amount: amount + :param side: trade side + :param price_response: Price response from Gateway. + :param process_exception: Flag to trigger error on exception + """ + required_items = ["price", "gasLimit", "gasPrice", "gasCost", "gasPriceToken"] + if any(item not in price_response.keys() for item in required_items): + if "info" in price_response.keys(): + self.logger().info(f"Unable to get price. {price_response['info']}") + else: + self.logger().info(f"Missing data from price result. Incomplete return result for ({price_response.keys()})") + else: + gas_price_token: str = price_response["gasPriceToken"] + gas_cost: Decimal = Decimal(price_response["gasCost"]) + price: Decimal = Decimal(price_response["price"]) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + if process_exception is True: + gas_limit: int = int(price_response["gasLimit"]) + exceptions: List[str] = check_transaction_exceptions( + allowances=self._allowances, + balances=self._account_balances, + base_asset=base, + quote_asset=quote, + amount=amount, + side=side, + gas_limit=gas_limit, + gas_cost=gas_cost, + gas_asset=gas_price_token, + swaps_count=len(price_response.get("swaps", [])), + chain=self.chain + ) + for index in range(len(exceptions)): + self.logger().warning( + f"Warning! [{index + 1}/{len(exceptions)}] {side} order - {exceptions[index]}" + ) + if len(exceptions) > 0: + return None + return Decimal(str(price)) + return None + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is not supported for tezos blockchain. + """ + return [] + + async def _execute_cancel(self, order_id: str, cancel_age: int) -> Optional[str]: + """ + This is intentionally left blank, because cancellation is not supported for tezos blockchain. + """ + pass + + async def cancel_outdated_orders(self, cancel_age: int) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is not supported for tezos blockchain. + """ + return [] diff --git a/hummingbot/connector/gateway/amm_lp/__init__.py b/hummingbot/connector/gateway/amm_lp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/amm_lp/gateway_evm_amm_lp.py b/hummingbot/connector/gateway/amm_lp/gateway_evm_amm_lp.py new file mode 100644 index 0000000..fde42c9 --- /dev/null +++ b/hummingbot/connector/gateway/amm_lp/gateway_evm_amm_lp.py @@ -0,0 +1,1258 @@ +import asyncio +import copy +import itertools as it +import logging +import re +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Type, Union, cast + +from async_timeout import timeout + +from hummingbot.client.settings import GatewayConnectionSetting +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.gateway.amm_lp.gateway_in_flight_lp_order import GatewayInFlightLPOrder +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.in_flight_order import OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.event.events import ( + LPType, + MarketEvent, + OrderCancelledEvent, + RangePositionClosedEvent, + RangePositionFeeCollectedEvent, + RangePositionLiquidityAddedEvent, + RangePositionLiquidityRemovedEvent, + RangePositionUpdateEvent, + RangePositionUpdateFailureEvent, + TokenApprovalCancelledEvent, + TokenApprovalEvent, + TokenApprovalFailureEvent, + TokenApprovalSuccessEvent, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_logger = None +s_decimal_0 = Decimal("0") +s_decimal_NaN = Decimal("nan") + + +class GatewayEVMAMMLP(ConnectorBase): + """ + Defines basic funtions common to LPing connectors that interract with Gateway. + """ + + API_CALL_TIMEOUT = 10.0 + POLL_INTERVAL = 1.0 + UPDATE_BALANCE_INTERVAL = 30.0 + APPROVAL_ORDER_ID_PATTERN = re.compile(r"approve-(\w+)-(\w+)") + + _connector_name: str + _name: str + _chain: str + _network: str + _trading_pairs: List[str] + _all_spenders: List[str] + _tokens: Set[str] + _wallet_address: str + _trading_required: bool + _ev_loop: asyncio.AbstractEventLoop + _last_poll_timestamp: float + _last_balance_poll_timestamp: float + _last_est_gas_cost_reported: float + _in_flight_orders: Dict[str, GatewayInFlightLPOrder] + _allowances: Dict[str, Decimal] + _chain_info: Dict[str, Any] + _status_polling_task: Optional[asyncio.Task] + _get_chain_info_task: Optional[asyncio.Task] + _auto_approve_task: Optional[asyncio.Task] + _poll_notifier: Optional[asyncio.Event] + _nonce: Optional[int] + _native_currency: str + _amount_quantum_dict: Dict[str, Decimal] + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + self._connector_name = connector_name + self._name = "_".join([connector_name, chain, network]) + super().__init__(client_config_map) + self._chain = chain + self._network = network + self._trading_pairs = trading_pairs + self._all_spenders = additional_spenders + self._all_spenders.append(self._connector_name) + self._tokens = set() + [self._tokens.update(set(trading_pair.split("-"))) for trading_pair in trading_pairs] + self._wallet_address = address + self._trading_required = trading_required + self._ev_loop = asyncio.get_event_loop() + self._last_poll_timestamp = 0.0 + self._last_balance_poll_timestamp = time.time() + self._in_flight_orders = {} + self._allowances = {} + self._chain_info = {} + self._status_polling_task = None + self._get_chain_info_task = None + self._auto_approve_task = None + self._get_gas_estimate_task = None + self._poll_notifier = None + self._nonce: Optional[int] = None + self._native_currency = None + self._network_transaction_fee: Optional[TokenAmount] = None + self._amount_quantum_dict = {} + safe_ensure_future(self.load_token_data()) + + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(cls.__name__) + return cast(HummingbotLogger, s_logger) + + @property + def connector_name(self): + """ + This returns the name of connector/protocol to be connected to on Gateway. + """ + return self._connector_name + + @property + def chain(self): + return self._chain + + @property + def network(self): + return self._network + + @property + def name(self): + return self._name + + @property + def address(self): + return self._wallet_address + + @property + def limit_orders(self) -> List[LimitOrder]: + return [] + + async def all_trading_pairs(self) -> List[str]: + """ + Calls the tokens endpoint on Gateway. + """ + try: + tokens = await GatewayHttpClient.get_instance().get_tokens(self._chain, self._network) + token_symbols = [t["symbol"] for t in tokens["tokens"]] + trading_pairs = [] + for base, quote in it.permutations(token_symbols, 2): + trading_pairs.append(f"{base}-{quote}") + return trading_pairs + except Exception: + return [] + + @staticmethod + def is_approval_order(in_flight_order: GatewayInFlightLPOrder) -> bool: + return in_flight_order.client_order_id.split("-")[0] == "approve" + + @property + def approval_orders(self) -> List[GatewayInFlightLPOrder]: + return [ + approval_order + for approval_order in self._in_flight_orders.values() + if self.is_approval_order(approval_order) + and not approval_order.is_pending_cancel_confirmation + ] + + @property + def amm_lp_orders(self) -> List[GatewayInFlightLPOrder]: + return [ + in_flight_order + for in_flight_order in self._in_flight_orders.values() + if not self.is_approval_order(in_flight_order) + and not in_flight_order.is_pending_cancel_confirmation + ] + + @property + def canceling_orders(self) -> List[GatewayInFlightLPOrder]: + return [ + cancel_order + for cancel_order in self._in_flight_orders.values() + if cancel_order.is_pending_cancel_confirmation + ] + + @property + def network_transaction_fee(self) -> TokenAmount: + """ + The most recently known transaction fee (i.e. gas fees) required for making transactions. + """ + return self._network_transaction_fee + + @network_transaction_fee.setter + def network_transaction_fee(self, new_fee: TokenAmount): + self._network_transaction_fee = new_fee + + def create_approval_order_id(self, spender: str, token_symbol: str) -> str: + return f"approve-{spender}-{token_symbol}" + + def get_token_symbol_from_approval_order_id(self, approval_order_id: str) -> Optional[str]: + match = self.APPROVAL_ORDER_ID_PATTERN.search(approval_order_id) + if match: + return f"{match.group(1)}_{match.group(2)}" + return None + + @staticmethod + def create_lp_order_id(action: LPType, trading_pair: str) -> str: + return f"{action.name.lower()}-{trading_pair}-{get_tracking_nonce()}" + + def is_pending_approval(self, spender_token: str) -> bool: + pending_approval_tokens: List[Optional[str]] = [ + self.get_token_symbol_from_approval_order_id(order_id) + for order_id in self._in_flight_orders.keys() + ] + return True if spender_token in pending_approval_tokens else False + + async def load_token_data(self): + tokens = await GatewayHttpClient.get_instance().get_tokens(self.chain, self.network) + for t in tokens.get("tokens", []): + self._amount_quantum_dict[t["symbol"]] = Decimal(str(10 ** -t["decimals"])) + + async def get_chain_info(self): + """ + Calls the base endpoint of the connector on Gateway to know basic info about chain being used. + """ + try: + self._chain_info = await self._get_gateway_instance().get_network_status( + chain=self.chain, network=self.network + ) + if not isinstance(self._chain_info, list): + self._native_currency = self._chain_info.get("nativeCurrency", "ETH") + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + "Error fetching chain info", + exc_info=True, + app_warning_msg=str(e) + ) + + async def get_gas_estimate(self): + """ + Gets the gas estimates for the connector. + """ + try: + response: Dict[Any] = await self._get_gateway_instance().amm_estimate_gas( + chain=self.chain, network=self.network, connector=self.connector_name + ) + self.network_transaction_fee = TokenAmount( + response.get("gasPriceToken"), Decimal(response.get("gasCost")) + ) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting gas price estimates for {self.connector_name} on {self.network}.", + exc_info=True, + app_warning_msg=str(e) + ) + + async def auto_approve(self): + """ + Automatically approves trading pair tokens for contract(s). + It first checks if there are any already approved amount (allowance) + """ + await self.update_allowances() + for spender_token, amount in self._allowances.items(): + if amount <= s_decimal_0 and not self.is_pending_approval(spender_token): + spender, token = spender_token.split("_") + await self.approve_token(spender, token) + + async def approve_token(self, spender: str, token_symbol: str, **request_args) -> Optional[GatewayInFlightLPOrder]: + """ + Approves contract as a spender for a token. + :param spender: contract to approve for. + :param token_symbol: token to approve. + """ + order_id: str = self.create_approval_order_id(spender, token_symbol) + resp: Dict[str, Any] = await self._get_gateway_instance().approve_token( + self.chain, + self.network, + self.address, + token_symbol, + spender, + **request_args + ) + self.start_tracking_order(order_id, None, token_symbol) + + if "hash" in resp.get("approval", {}).keys(): + nonce: int = resp.get("nonce") + await self._update_nonce(nonce) + hash = resp["approval"]["hash"] + tracked_order = self._in_flight_orders.get(order_id) + tracked_order.update_exchange_order_id(hash) + tracked_order.nonce = nonce + self.logger().info( + f"Maximum {token_symbol} approval for {spender} contract sent, hash: {hash}." + ) + return tracked_order + else: + self.logger().info(f"Missing data from approval result. Incomplete return result for ({resp.keys()})") + self.logger().info(f"Approval for {token_symbol} on {spender} failed.") + return None + + async def update_allowances(self): + self._allowances = await self.get_allowances() + + async def get_allowances(self) -> Dict[str, Decimal]: + """ + Retrieves allowances for token in trading_pairs + :return: A dictionary of token and its allowance. + """ + ret_val = {} + approval_lists: List[str] = await safe_gather(*[ + self._get_gateway_instance().get_allowances( + self.chain, self.network, self.address, list(self._tokens), spender + ) for spender in self._all_spenders + ]) + + for spender, approval_list in zip(self._all_spenders, approval_lists): + for token, amount in approval_list["approvals"].items(): + ret_val[f"{spender}_{token}"] = Decimal(str(amount)) + return ret_val + + @async_ttl_cache(ttl=5, maxsize=10) + async def get_price( + self, + trading_pair: str, + fee: str, + period: Optional[int] = 1, + interval: Optional[int] = 1, + ) -> Optional[List[Decimal]]: + """ + Retrieves a quote price. + + :param trading_pair: The market trading pair + :param fee: The fee tier to get price data from + :param period: The period of time to lookup price data (in seconds) + :param interval: The interval within the specified period of time to lookup price data (in seconds) + :return: A list of prices. + """ + + token_0, token_1 = trading_pair.split("-") + + # Pull the current/historical price(s) from gateway. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().amm_lp_price( + self.chain, self.network, self.connector_name, token_0, token_1, fee.upper(), period, interval + ) + if "info" in resp.keys(): + self.logger().info(f"Unable to get price data. {resp['info']}") + return ["0"] + return [Decimal(price) for price in resp.get("prices", [])] + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting price data for {trading_pair}.", + exc_info=True, + app_warning_msg=str(e) + ) + + def add_liquidity(self, trading_pair: str, amount_0: Decimal, amount_1: Decimal, lower_price: Decimal, upper_price: Decimal, fee: str, **request_args) -> str: + """ + Add/increases liquidity. + :param trading_pair: The market trading pair + :param amount_0: The base amount + :param amount_1: The quote amount + :param lower_price: The lower-bound price for the range position order. + :param upper_price: The upper-bound price for the range position order. + :param fee: The fee tier to provide liquidity on. + :return: A newly created order id (internal). + """ + order_id: str = self.create_lp_order_id(LPType.ADD, trading_pair) + safe_ensure_future(self._add_liquidity(order_id, trading_pair, amount_0, amount_1, lower_price, upper_price, fee, **request_args)) + return order_id + + async def _add_liquidity( + self, + order_id: str, + trading_pair: str, + amount_0: Decimal, + amount_1: Decimal, + lower_price: Decimal, + upper_price: Decimal, + fee: str, + **request_args + ): + """ + Calls /liquidity/add API end point to add/increase liquidity, starts tracking the order and triggers relevant order events. + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param amount_0: The base amount + :param amount_1: The quote amount + :param lower_price: The lower-bound to add liquidity + :param upper_price: The upper-bound to add liquidity + :param fee: the market tier to add liquidity on + """ + + lp_type = LPType.ADD + amount_0 = self.quantize_order_amount(trading_pair, amount_0) + amount_1 = self.quantize_order_amount(trading_pair, amount_1) + lower_price = self.quantize_order_price(trading_pair, lower_price) + upper_price = self.quantize_order_price(trading_pair, upper_price) + token_0, token_1 = trading_pair.split("-") + self.start_tracking_order(order_id=order_id, + trading_pair=trading_pair, + lp_type=lp_type, + lower_price=lower_price, + upper_price=upper_price, + amount_0=amount_0, + amount_1=amount_1) + try: + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_lp_add( + self.chain, + self.network, + self.connector_name, + self.address, + token_0, + token_1, + amount_0, + amount_1, + fee, + lower_price, + upper_price, + **request_args + ) + transaction_hash: str = order_result.get("txHash") + nonce: int = order_result.get("nonce") + await self._update_nonce(nonce) + gas_price: Decimal = Decimal(order_result.get("gasPrice")) + gas_limit: int = int(order_result.get("gasLimit")) + gas_cost: Decimal = Decimal(order_result.get("gasCost")) + gas_price_token: str = order_result.get("gasPriceToken") + tracked_order: GatewayInFlightLPOrder = self._in_flight_orders.get(order_id) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + + if tracked_order is not None: + self.logger().info(f"Created {lp_type.name} liquidity order {order_id} txHash: {transaction_hash} " + f"on {self.network}. Estimated Gas Cost: {gas_cost} " + f" (gas limit: {gas_limit}, gas price: {gas_price})") + tracked_order.update_exchange_order_id(transaction_hash) + tracked_order.gas_price = gas_price + tracked_order.current_state = OrderState.OPEN + if transaction_hash is not None: + tracked_order.nonce = nonce + tracked_order.fee_tier = fee + tracked_order.fee_asset = self._native_currency + event_tag: MarketEvent = MarketEvent.RangePositionUpdate + event_class: Type[RangePositionUpdateEvent] = RangePositionUpdateEvent + self.trigger_event(event_tag, event_class( + timestamp=self.current_timestamp, + order_id=order_id, + exchange_order_id=transaction_hash, + order_action=lp_type, + trading_pair=trading_pair, + fee_tier=fee, + lower_price=lower_price, + upper_price=upper_price, + amount=amount_0, + creation_timestamp=tracked_order.creation_timestamp, + )) + else: + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Error submitting {lp_type.name} liquidity order to {self.connector_name} on {self.network} for " + f"{trading_pair} ", + exc_info=True + ) + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + + def remove_liquidity(self, trading_pair: str, token_id: int, reduce_percent: Optional[int] = 100, **request_args) -> str: + """ + Remove/reduce liquidity. + :param trading_pair: The market trading pair + :param token_id: The market trading pair + :param reduce_percent: Percentage of liquidity to remove(as integer). + :return: A newly created order id (internal). + """ + order_id: str = self.create_lp_order_id(LPType.REMOVE, trading_pair) + safe_ensure_future(self._remove_liquidity(order_id, trading_pair, token_id, reduce_percent, **request_args)) + return order_id + + async def _remove_liquidity( + self, + order_id: str, + trading_pair: str, + token_id: int, + reduce_percent: int, + **request_args + ): + """ + Calls /liquidity/remove API end point to remove/decrease liquidity, starts tracking the order and triggers relevant order events. + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param token_id: The market trading pair + :param reduce_percent: Percentage of liquidity to remove(as integer). + """ + + lp_type = LPType.REMOVE + self.start_tracking_order(order_id=order_id, + trading_pair=trading_pair, + lp_type=lp_type, + token_id=token_id) + try: + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_lp_remove( + self.chain, + self.network, + self.connector_name, + self.address, + token_id, + reduce_percent, + **request_args + ) + transaction_hash: str = order_result.get("txHash") + nonce: int = order_result.get("nonce") + await self._update_nonce(nonce) + gas_price: Decimal = Decimal(order_result.get("gasPrice")) + gas_limit: int = int(order_result.get("gasLimit")) + gas_cost: Decimal = Decimal(order_result.get("gasCost")) + gas_price_token: str = order_result.get("gasPriceToken") + tracked_order: GatewayInFlightLPOrder = self._in_flight_orders.get(order_id) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + + if tracked_order is not None: + self.logger().info(f"Created {lp_type.name} liquidity order {order_id} txHash: {transaction_hash} " + f"on {self.network}. Estimated Gas Cost: {gas_cost} " + f" (gas limit: {gas_limit}, gas price: {gas_price})") + tracked_order.update_exchange_order_id(transaction_hash) + tracked_order.gas_price = gas_price + tracked_order.current_state = OrderState.OPEN + if transaction_hash is not None: + tracked_order.nonce = nonce + tracked_order.fee_asset = self._native_currency + event_tag: MarketEvent = MarketEvent.RangePositionUpdate + event_class: Type[RangePositionUpdateEvent] = RangePositionUpdateEvent + self.trigger_event(event_tag, event_class( + timestamp=self.current_timestamp, + order_id=order_id, + exchange_order_id=transaction_hash, + order_action=lp_type, + trading_pair=trading_pair, + creation_timestamp=tracked_order.creation_timestamp, + token_id=token_id, + )) + else: + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Error submitting {lp_type.name} liquidity order to {self.connector_name} on {self.network} for " + f"{trading_pair} ", + exc_info=True + ) + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + + def collect_fees(self, trading_pair: str, token_id: int, **request_args) -> str: + """ + Collect earned fees. + :param trading_pair: The market trading pair + :param token_id: The market trading pair + :return: A newly created order id (internal). + """ + order_id: str = self.create_lp_order_id(LPType.COLLECT, trading_pair) + safe_ensure_future(self._collect_fees(order_id, trading_pair, token_id, **request_args)) + return order_id + + async def _collect_fees( + self, + order_id: str, + trading_pair: str, + token_id: int, + **request_args + ): + """ + Calls /liquidity/collect_fees API end point to collect earned fees, starts tracking the order and triggers relevant order events. + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param token_id: The market trading pair + """ + + lp_type = LPType.COLLECT + self.start_tracking_order(order_id=order_id, + trading_pair=trading_pair, + lp_type=lp_type, + token_id=token_id) + try: + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_lp_collect_fees( + self.chain, + self.network, + self.connector_name, + self.address, + token_id, + **request_args + ) + transaction_hash: str = order_result.get("txHash") + nonce: int = order_result.get("nonce") + await self._update_nonce(nonce) + gas_price: Decimal = Decimal(order_result.get("gasPrice")) + gas_limit: int = int(order_result.get("gasLimit")) + gas_cost: Decimal = Decimal(order_result.get("gasCost")) + gas_price_token: str = order_result.get("gasPriceToken") + tracked_order: GatewayInFlightLPOrder = self._in_flight_orders.get(order_id) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + + if tracked_order is not None: + self.logger().info(f"Submitted {lp_type.name} request {order_id} txHash: {transaction_hash} " + f"on {self.network}. Estimated Gas Cost: {gas_cost} " + f" (gas limit: {gas_limit}, gas price: {gas_price})") + tracked_order.update_exchange_order_id(transaction_hash) + tracked_order.gas_price = gas_price + tracked_order.current_state = OrderState.OPEN + if transaction_hash is not None: + tracked_order.nonce = nonce + tracked_order.fee_asset = self._native_currency + event_tag: MarketEvent = MarketEvent.RangePositionUpdate + event_class: Type[RangePositionUpdateEvent] = RangePositionUpdateEvent + self.trigger_event(event_tag, event_class( + timestamp=self.current_timestamp, + order_id=order_id, + exchange_order_id=transaction_hash, + order_action=lp_type, + trading_pair=trading_pair, + creation_timestamp=tracked_order.creation_timestamp, + token_id=token_id, + )) + else: + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Error submitting {lp_type.name} request to {self.connector_name} on {self.network} for " + f"{trading_pair} ", + exc_info=True + ) + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent(self.current_timestamp, order_id, lp_type)) + self.stop_tracking_order(order_id) + + def start_tracking_order(self, + order_id: str, + exchange_order_id: Optional[str] = None, + trading_pair: str = "", + lp_type: Optional[LPType] = LPType.ADD, + lower_price: Optional[Decimal] = s_decimal_0, + upper_price: Optional[Decimal] = s_decimal_0, + amount_0: Optional[Decimal] = s_decimal_0, + amount_1: Optional[Decimal] = s_decimal_0, + token_id: Optional[int] = 0, + gas_price: Decimal = s_decimal_0): + """ + Starts tracking an order by simply adding it into _in_flight_orders dictionary. + """ + self._in_flight_orders[order_id] = GatewayInFlightLPOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + lp_type=lp_type, + lower_price=lower_price, + upper_price=upper_price, + amount_0=amount_0, + amount_1=amount_1, + token_id=token_id, + gas_price=gas_price, + creation_timestamp=self.current_timestamp + ) + + def stop_tracking_order(self, order_id: str): + """ + Stops tracking an order by simply removing it from _in_flight_orders dictionary. + """ + if order_id in self._in_flight_orders: + del self._in_flight_orders[order_id] + + async def update_token_approval_status(self, tracked_approvals: List[GatewayInFlightLPOrder]): + """ + Calls REST API to get status update for each in-flight token approval transaction. + """ + if len(tracked_approvals) < 1: + return + tx_hash_list: List[str] = await safe_gather(*[ + tracked_approval.get_exchange_order_id() for tracked_approval in tracked_approvals + ]) + transaction_states: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_approval, transaction_status in zip(tracked_approvals, transaction_states): + token_symbol: str = self.get_token_symbol_from_approval_order_id(tracked_approval.client_order_id) + if isinstance(transaction_status, Exception): + self.logger().error(f"Error while trying to approve token {token_symbol} for {self.connector_name}: " + f"{transaction_status}") + continue + if "txHash" not in transaction_status: + self.logger().error(f"Error while trying to approve token {token_symbol} for {self.connector_name}: " + "txHash key not found in transaction status.") + continue + if transaction_status["txStatus"] == 1: + if transaction_status["txReceipt"]["status"] == 1: + self.logger().info(f"Token approval for {tracked_approval.client_order_id} on {self.connector_name} " + f"successful.") + self.trigger_event( + TokenApprovalEvent.ApprovalSuccessful, + TokenApprovalSuccessEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + safe_ensure_future(self.update_allowances()) + else: + self.logger().warning( + f"Token approval for {tracked_approval.client_order_id} on {self.connector_name} failed." + ) + self.trigger_event( + TokenApprovalEvent.ApprovalFailed, + TokenApprovalFailureEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + self.stop_tracking_order(tracked_approval.client_order_id) + + async def update_canceling_transactions(self, canceled_tracked_orders: List[GatewayInFlightLPOrder]): + """ + Update tracked orders that have a cancel_tx_hash. + :param canceled_tracked_orders: Canceled tracked_orders (cancel_tx_has is not None). + """ + if len(canceled_tracked_orders) < 1: + return + + self.logger().debug( + "Polling for order status updates of %d canceled orders.", + len(canceled_tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash + ) + for tx_hash in [t.cancel_tx_hash for t in canceled_tracked_orders] + ], return_exceptions=True) + for tracked_order, update_result in zip(canceled_tracked_orders, update_results): + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().error(f"No txHash field for transaction status of {tracked_order.client_order_id}: " + f"{update_result}.") + continue + if update_result["txStatus"] == 1: + if update_result["txReceipt"]["status"] == 1: + if tracked_order.current_state == OrderState.PENDING_CANCEL: + if not self.is_approval_order(tracked_order): + self.trigger_event( + MarketEvent.OrderCancelled, + OrderCancelledEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.exchange_order_id, + ) + ) + self.logger().info(f"The {tracked_order.lp_type.name} order " + f"{tracked_order.client_order_id} has been canceled " + f"according to the order status API.") + elif self.is_approval_order(tracked_order): + token_symbol: str = self.get_token_symbol_from_approval_order_id( + tracked_order.client_order_id + ) + self.trigger_event( + TokenApprovalEvent.ApprovalCancelled, + TokenApprovalCancelledEvent( + self.current_timestamp, + self.connector_name, + token_symbol + ) + ) + self.logger().info(f"Token approval for {tracked_order.client_order_id} on " + f"{self.connector_name} has been canceled.") + tracked_order.current_state = OrderState.CANCELED + self.stop_tracking_order(tracked_order.client_order_id) + + async def update_order_status(self, tracked_orders: List[GatewayInFlightLPOrder]): + """ + Calls REST API to get status update for each in-flight amm orders. + """ + if len(tracked_orders) < 1: + return + + # filter non nft orders + pending_nft_orders = [new_order for new_order in tracked_orders + if not new_order.is_nft or (new_order.is_nft and new_order.current_state == OrderState.OPEN)] + tx_hash_list: List[str] = await safe_gather( + *[tracked_order.get_exchange_order_id() for tracked_order in pending_nft_orders] + ) + self.logger().debug( + "Polling for order status updates of %d orders.", + len(tracked_orders) + ) + update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().get_transaction_status( + self.chain, + self.network, + tx_hash, + connector=self.connector_name + ) + for tx_hash in tx_hash_list + ], return_exceptions=True) + for tracked_order, update_result in zip(pending_nft_orders, update_results): + if isinstance(update_result, Exception): + raise update_result + if "txHash" not in update_result: + self.logger().error(f"No txHash field for transaction status of {tracked_order.client_order_id}: " + f"{update_result}.") + continue + if update_result["txStatus"] == 1: + if update_result["txReceipt"]["status"] == 1: + gas_used: int = update_result["txReceipt"]["gasUsed"] + gas_price: Decimal = tracked_order.gas_price + fee: Decimal = Decimal(str(gas_used)) * Decimal(str(gas_price)) / Decimal(str(1e9)) + tracked_order.fee_paid = fee + if tracked_order.lp_type == LPType.ADD: + token_id = int(list(filter(lambda evt: evt["name"] == "tokenId", list(filter(lambda log: log["name"] == "IncreaseLiquidity", update_result["txReceipt"]["logs"]))[0]["events"]))[0]["value"]) + self.logger().info(f"Liquidity added for position with ID {token_id}.") + self.trigger_event( + MarketEvent.RangePositionLiquidityAdded, + RangePositionLiquidityAddedEvent( + timestamp=self.current_timestamp, + order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + trading_pair=tracked_order.trading_pair, + lower_price=Decimal(str(tracked_order.lower_price)), + upper_price=Decimal(str(tracked_order.upper_price)), + amount=Decimal(str(tracked_order.amount)), + fee_tier=tracked_order.fee_tier, + creation_timestamp=tracked_order.creation_timestamp, + trade_fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] + ), + token_id=token_id, + ) + ) + + if not self.token_id_exists(token_id): + tracked_order.current_state = OrderState.CREATED + tracked_order.token_id = token_id + continue + + tracked_order.current_state = OrderState.COMPLETED + else: + reduce_evt = list(filter(lambda evt: evt["name"] == "tokenId", list(filter(lambda log: log["name"] == "DecreaseLiquidity", update_result["txReceipt"]["logs"]))[0]["events"])) if tracked_order.lp_type == LPType.REMOVE else [] + collect_evt = list(filter(lambda evt: evt["name"] == "tokenId", list(filter(lambda log: log["name"] == "Collect", update_result["txReceipt"]["logs"]))[0]["events"])) + if len(reduce_evt) > 0: + token_id = int(reduce_evt[0]["value"]) + tracked_order.token_id = token_id + self.logger().info(f"Liquidity removed for position with ID {token_id}.") + self.trigger_event( + MarketEvent.RangePositionLiquidityRemoved, + RangePositionLiquidityRemovedEvent( + timestamp=self.current_timestamp, + order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + trading_pair=tracked_order.trading_pair, + creation_timestamp=tracked_order.creation_timestamp, + token_id=token_id, + trade_fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] + ), + ) + ) + if len(collect_evt) > 0: + token_id = int(collect_evt[0]["value"]) + tracked_order.token_id = token_id + self.logger().info(f"Unclaimed fees collected for position with ID {token_id}.") + self.trigger_event( + MarketEvent.RangePositionFeeCollected, + RangePositionFeeCollectedEvent( + timestamp=self.current_timestamp, + order_id=tracked_order.client_order_id, + exchange_order_id=tracked_order.exchange_order_id, + trading_pair=tracked_order.trading_pair, + token_id=token_id, + creation_timestamp=tracked_order.creation_timestamp, + trade_fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(tracked_order.fee_asset, Decimal(str(fee)))] + ), + ) + ) + tracked_order.current_state = OrderState.COMPLETED + else: + self.logger().info( + f"The LP update order {tracked_order.client_order_id} has failed according to order status API. ") + self.trigger_event(MarketEvent.RangePositionUpdateFailure, + RangePositionUpdateFailureEvent( + self.current_timestamp, + tracked_order.client_order_id, + tracked_order.lp_type + )) + self.stop_tracking_order(tracked_order.client_order_id) + + async def update_nft(self, tracked_orders: List[GatewayInFlightLPOrder]): + """ + Calls REST API to get status update for each created in-flight tokens. + """ + nft_orders: List[GatewayInFlightLPOrder] = [tracked_order for tracked_order in tracked_orders + if tracked_order.is_nft and tracked_order.token_id > 0] + token_id_list: List[int] = [nft_order.token_id for nft_order in nft_orders] + if len(nft_orders) < 1: + return + + self.logger().debug( + "Polling for nft updates for %d tokens.", + len(nft_orders) + ) + + nft_update_results: List[Union[Dict[str, Any], Exception]] = await safe_gather(*[ + self._get_gateway_instance().amm_lp_position( + self.chain, + self.network, + self.connector_name, + token_id, + ) + for token_id in token_id_list + ], return_exceptions=True) + + for nft_order, nft_update_result in zip(nft_orders, nft_update_results): + if isinstance(nft_update_result, Exception): + raise nft_update_result + lower_price = Decimal(nft_update_result["lowerPrice"]) + upper_price = Decimal(nft_update_result["upperPrice"]) + amount_0 = Decimal(nft_update_result["amount0"]) + amount_1 = Decimal(nft_update_result["amount1"]) + unclaimed_fee_0 = Decimal(nft_update_result["unclaimedToken0"]) + unclaimed_fee_1 = Decimal(nft_update_result["unclaimedToken1"]) + fee_tier = nft_update_result["fee"] + if amount_0 + amount_1 + unclaimed_fee_0 + unclaimed_fee_1 == s_decimal_0: # position closed, stop tracking + self.logger().info(f"Position with ID {nft_order.token_id} closed. About to stop tracking...") + nft_order.current_state = OrderState.COMPLETED + self.stop_tracking_order(nft_order.client_order_id) + self.trigger_event( + MarketEvent.RangePositionClosed, + RangePositionClosedEvent( + timestamp=self.current_timestamp, + token_id=nft_order.token_id, + token_0=nft_update_result["token0"], + token_1=nft_update_result["token1"], + claimed_fee_0=unclaimed_fee_0, + claimed_fee_1=unclaimed_fee_1, + ) + ) + else: + nft_order.adjusted_lower_price = lower_price + nft_order.adjusted_upper_price = upper_price + if nft_order.trading_pair.split("-")[0] != nft_update_result["token0"]: + nft_order.adjusted_lower_price = Decimal("1") / upper_price + nft_order.adjusted_upper_price = Decimal("1") / lower_price + unclaimed_fee_0, unclaimed_fee_1 = unclaimed_fee_1, unclaimed_fee_0 + amount_0, amount_1 = amount_1, amount_0 + nft_order.amount_0 = amount_0 + nft_order.amount_1 = amount_1 + nft_order.unclaimed_fee_0 = unclaimed_fee_0 + nft_order.unclaimed_fee_1 = unclaimed_fee_1 + nft_order.fee_tier = fee_tier + + def token_id_exists(self, token_id: int) -> bool: + """ + Checks if there are existing tracked inflight orders with same token id created earlier. + """ + token_id_list = [order for order in self.amm_lp_orders if order.token_id == token_id and order.is_nft] + return len(token_id_list) > 0 + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("1e-15") + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + base, quote = trading_pair.split("-") + return max(self._amount_quantum_dict[base], self._amount_quantum_dict[quote]) + + @property + def ready(self): + return all(self.status_dict.values()) + + def has_allowances(self) -> bool: + """ + Checks if all tokens have allowance (an amount approved) + """ + return ((len(self._allowances.values()) == len(self._tokens) * len(self._all_spenders)) and + (all(amount > s_decimal_0 for amount in self._allowances.values()))) + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "allowances": self.has_allowances() if self._trading_required else True, + "native_currency": self._native_currency is not None, + "network_transaction_fee": self.network_transaction_fee is not None if self._trading_required else True, + } + + async def start_network(self): + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._auto_approve_task = safe_ensure_future(self.auto_approve()) + self._get_gas_estimate_task = safe_ensure_future(self.get_gas_estimate()) + self._get_chain_info_task = safe_ensure_future(self.get_chain_info()) + + async def stop_network(self): + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._auto_approve_task is not None: + self._auto_approve_task.cancel() + self._auto_approve_task = None + if self._get_chain_info_task is not None: + self._get_chain_info_task.cancel() + self._get_chain_info_task = None + if self._get_gas_estimate_task is not None: + self._get_gas_estimate_task.cancel() + self._get_chain_info_task = None + + async def check_network(self) -> NetworkStatus: + try: + if await self._get_gateway_instance().ping_gateway(): + return NetworkStatus.CONNECTED + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.NOT_CONNECTED + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: + if self._poll_notifier is not None and not self._poll_notifier.is_set(): + self._poll_notifier.set() + + async def _update_nonce(self, new_nonce: Optional[int] = None): + """ + Call the gateway API to get the current nonce for self.address + """ + if not new_nonce: + resp_json: Dict[str, Any] = await self._get_gateway_instance().get_evm_nonce(self.chain, self.network, self.address) + new_nonce: int = resp_json.get("nonce") + + self._nonce = new_nonce + + async def _status_polling_loop(self): + await self.update_balances(on_interval=False) + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self.update_balances(on_interval=True), + self.update_canceling_transactions(self.canceling_orders), + self.update_token_approval_status(self.approval_orders), + self.update_order_status(self.amm_lp_orders), + self.update_nft(self.amm_lp_orders) + ) + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(str(e), exc_info=True) + + async def update_balances(self, on_interval=False): + """ + Calls Eth API to update total and available balances. + """ + if self._native_currency is None: + await self.get_chain_info() + connector_tokens = GatewayConnectionSetting.get_connector_spec_from_market_name(self._name).get("tokens", "").split(",") + last_tick = self._last_balance_poll_timestamp + current_tick = self.current_timestamp + if not on_interval or (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL: + self._last_balance_poll_timestamp = current_tick + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + resp_json: Dict[str, Any] = await self._get_gateway_instance().get_balances( + self.chain, self.network, self.address, list(self._tokens) + [self._native_currency] + connector_tokens + ) + for token, bal in resp_json["balances"].items(): + self._account_available_balances[token] = Decimal(str(bal)) + self._account_balances[token] = Decimal(str(bal)) + remote_asset_names.add(token) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._in_flight_orders.items()} + self._in_flight_orders_snapshot_timestamp = self.current_timestamp + + async def _update_balances(self): + """ + This is called by UserBalances. + """ + await self.update_balances() + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + """ + This is intentionally left blank, because cancellation is expensive on blockchains. It's not worth it for + Hummingbot to force cancel all orders whenever Hummingbot quits. + """ + return [] + + async def _execute_cancel(self, order_id: str, cancel_age: int) -> Optional[str]: + """ + Cancel an existing order if the age of the order is greater than its cancel_age, + and if the order is not done or already in the cancelling state. + """ + try: + tracked_order: GatewayInFlightLPOrder = self._in_flight_orders.get(order_id) + if tracked_order is None: + self.logger().error(f"The order {order_id} is not being tracked.") + raise ValueError(f"The order {order_id} is not being tracked.") + + if (self.current_timestamp - tracked_order.creation_timestamp) < cancel_age: + return None + + if tracked_order.is_done: + return None + + if tracked_order.is_pending_cancel_confirmation: + return order_id + + self.logger().info(f"The blockchain transaction for {order_id} with nonce {tracked_order.nonce} has " + "expired. Canceling the order...") + resp: Dict[str, Any] = await self._get_gateway_instance().cancel_evm_transaction( + self.chain, + self.network, + self.address, + tracked_order.nonce + ) + + tx_hash: Optional[str] = resp.get("txHash") + if tx_hash is not None: + tracked_order.cancel_tx_hash = tx_hash + else: + raise EnvironmentError(f"Missing txHash from cancel_evm_transaction() response: {resp}.") + + tracked_order.current_state = OrderState.PENDING_CANCEL + return order_id + except asyncio.CancelledError: + raise + except Exception as err: + self.logger().error( + f"Failed to cancel order {order_id}: {str(err)}.", + exc_info=True + ) + + async def cancel_outdated_orders(self, cancel_age: int) -> List[CancellationResult]: + """ + Iterate through all known orders and cancel them if their age is greater than cancel_age. + """ + incomplete_orders: List[GatewayInFlightLPOrder] = [ + o for o in self._in_flight_orders.values() + if not (o.is_done or o.is_pending_cancel_confirmation) + ] + if len(incomplete_orders) < 1: + return [] + + timeout_seconds: float = 30.0 + canceling_id_set: Set[str] = set([o.client_order_id for o in incomplete_orders]) + sent_cancellations: List[CancellationResult] = [] + + try: + async with timeout(timeout_seconds): + for incomplete_order in incomplete_orders: + try: + canceling_order_id: Optional[str] = await self._execute_cancel( + incomplete_order.client_order_id, + cancel_age + ) + except Exception: + continue + if canceling_order_id is not None: + canceling_id_set.remove(canceling_order_id) + sent_cancellations.append(CancellationResult(canceling_order_id, True)) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error cancelling outdated orders.", + exc_info=True, + app_warning_msg=f"Failed to cancel orders on {self.chain}-{self.network}." + ) + + skipped_cancellations: List[CancellationResult] = [CancellationResult(oid, False) for oid in canceling_id_set] + return sent_cancellations + skipped_cancellations + + @property + def in_flight_orders(self) -> Dict[str, GatewayInFlightLPOrder]: + return self._in_flight_orders + + def restore_tracking_states(self, saved_states: Dict[str, any]): + """ + *required + Updates inflight order statuses from API results + This is used by the MarketsRecorder class to orchestrate market classes at a higher level. + """ + self._in_flight_orders.update({ + key: GatewayInFlightLPOrder.from_json(value) + for key, value in saved_states.items() + }) + + @property + def tracking_states(self) -> Dict[str, any]: + return {key: value.to_json() for key, value in self._in_flight_orders.items()} + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._client_config) + return gateway_instance diff --git a/hummingbot/connector/gateway/amm_lp/gateway_in_flight_lp_order.py b/hummingbot/connector/gateway/amm_lp/gateway_in_flight_lp_order.py new file mode 100644 index 0000000..4e75250 --- /dev/null +++ b/hummingbot/connector/gateway/amm_lp/gateway_in_flight_lp_order.py @@ -0,0 +1,116 @@ +from decimal import Decimal +from typing import Any, Dict, Optional + +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.data_type.common import LPType, OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState + +s_decimal_0 = Decimal("0") + + +class GatewayInFlightLPOrder(GatewayInFlightOrder): + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + lp_type: LPType, + lower_price: Decimal, + upper_price: Decimal, + amount_0: Decimal, + amount_1: Decimal, + token_id: Optional[int], + creation_timestamp: float, + gas_price: Decimal, + initial_state: OrderState = OrderState.PENDING_CREATE): + super().__init__( + client_order_id=client_order_id, + trading_pair=trading_pair, + order_type=OrderType.LIMIT_MAKER, + trade_type=TradeType.RANGE, + creation_timestamp=creation_timestamp, + price=s_decimal_0, + amount=s_decimal_0, + exchange_order_id=exchange_order_id, + initial_state=initial_state, + ) + self.lp_type = lp_type + self.lower_price = lower_price + self.upper_price = upper_price + self.token_id = token_id + self.amount_0 = amount_0 + self.amount_1 = amount_1 + self.adjusted_lower_price = s_decimal_0 + self.adjusted_upper_price = s_decimal_0 + self.unclaimed_fee_0 = s_decimal_0 + self.unclaimed_fee_1 = s_decimal_0 + self.fee_tier = "" + self.fee_paid = s_decimal_0 + self.trade_id_set = set() + self._gas_price = gas_price + self.nonce = 0 + self._cancel_tx_hash: Optional[str] = None + + @property + def is_done(self) -> bool: + return self.current_state in {OrderState.CANCELED, OrderState.COMPLETED, OrderState.FAILED, OrderState.REJECTED, OrderState.EXPIRED} + + @property + def is_nft(self) -> bool: + return self.current_state in {OrderState.CREATED, OrderState.OPEN} and self.lp_type == LPType.ADD + + @property + def last_state(self) -> OrderState: + return self.current_state + + def to_json(self) -> Dict[str, Any]: + return { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "lp_type": self.lp_type.name, + "lower_price": str(self.lower_price), + "upper_price": str(self.upper_price), + "amount_0": str(self.amount_0), + "amount_1": str(self.amount_1), + "token_id": self.token_id, + "adjusted_lower_price": str(self.adjusted_lower_price), + "adjusted_upper_price": str(self.adjusted_upper_price), + "unclaimed_fee_0": str(self.unclaimed_fee_0), + "unclaimed_fee_1": str(self.unclaimed_fee_1), + "fee_tier": self.fee_tier, + "fee_asset": self.fee_asset, + "fee_paid": str(self.fee_paid), + "creation_timestamp": self.creation_timestamp, + "last_state": str(self.last_state.value), + } + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> "GatewayInFlightLPOrder": + retval = GatewayInFlightLPOrder( + client_order_id=data["client_order_id"], + exchange_order_id=data["exchange_order_id"], + trading_pair=data["trading_pair"], + lp_type=getattr(LPType, data["lp_type"]), + lower_price=Decimal(data["lower_price"]), + upper_price=Decimal(data["upper_price"]), + amount_0=Decimal(data["amount_0"]), + amount_1=Decimal(data["amount_1"]), + token_id=data["token_id"], + creation_timestamp=data["creation_timestamp"], + gas_price=s_decimal_0, + initial_state=OrderState(int(data["last_state"])) + ) + retval.adjusted_lower_price = Decimal(data["adjusted_lower_price"]) + retval.adjusted_upper_price = Decimal(data["adjusted_upper_price"]) + retval.unclaimed_fee_0 = Decimal(data["unclaimed_fee_0"]) + retval.unclaimed_fee_1 = Decimal(data["unclaimed_fee_1"]) + retval.fee_tier = data["fee_tier"] + retval.fee_asset = data["fee_asset"] + retval.fee_paid = Decimal(data["fee_paid"]) + return retval + + def _creation_timestamp_from_order_id(self) -> int: + timestamp = -1 + if len(self.client_order_id.split("-")) > 3: + timestamp = float(self.client_order_id[-1]) + return timestamp diff --git a/hummingbot/connector/gateway/amm_perpetual/__init__.py b/hummingbot/connector/gateway/amm_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/amm_perpetual/gateway_evm_amm_perpetual.py b/hummingbot/connector/gateway/amm_perpetual/gateway_evm_amm_perpetual.py new file mode 100644 index 0000000..9ad40da --- /dev/null +++ b/hummingbot/connector/gateway/amm_perpetual/gateway_evm_amm_perpetual.py @@ -0,0 +1,629 @@ +import asyncio +import copy +import logging +import re +import time +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast + +from hummingbot.connector.derivative.perpetual_budget_checker import PerpetualBudgetChecker +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.events import AccountEvent, PositionModeChangeEvent, TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + +from ..gateway_price_shim import GatewayPriceShim + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +gp_logger = None +s_decimal_0 = Decimal("0") +s_decimal_NaN = Decimal("nan") + +TRADING_PAIR_SPLITTER = re.compile(r"^(\w+)(USD)$") + + +class GatewayEVMAMMPerpetual(GatewayEVMAMM, PerpetualTrading): + """ + Defines basic funtions common to connectors that interract with perpetual contracts on Gateway. + """ + + _collateral_currency: str + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: List[str] = [], + additional_spenders: List[str] = [], # not implemented + trading_required: bool = True + ): + """ + :param connector_name: name of connector on gateway + :param chain: refers to a block chain, e.g. ethereum or avalanche + :param network: refers to a network of a particular blockchain e.g. mainnet or kovan + :param address: the address of the eth wallet which has been added on gateway + :param trading_pairs: a list of trading pairs + :param trading_required: Whether actual trading is needed. Useful for some functionalities or commands like the balance command + """ + GatewayEVMAMM.__init__( + self, + client_config_map = client_config_map, + connector_name = connector_name, + chain = chain, + network = network, + address= address, + trading_pairs = trading_pairs, + trading_required = trading_required + ) + PerpetualTrading.__init__(self, trading_pairs=trading_pairs) + self._budget_checker = PerpetualBudgetChecker(self) + + # This values may not be applicable to all gateway perps, but applies to perp curie + self._collateral_currency = "USD" + + @classmethod + def logger(cls) -> HummingbotLogger: + global gp_logger + if gp_logger is None: + gp_logger = logging.getLogger(cls.__name__) + return cast(HummingbotLogger, gp_logger) + + async def all_trading_pairs(self) -> List[str]: + """ + Calls the get_perp_markets endpoint on Gateway. + """ + try: + response = await GatewayHttpClient.get_instance().get_perp_markets( + self._chain, self._network, self._connector_name + ) + trading_pairs = [] + for pair in response.get("pairs", []): + split = TRADING_PAIR_SPLITTER.search(pair) + trading_pairs.append(f"{split.group(1)}-{split.group(2)}") + return trading_pairs + except Exception: + return [] + + @property + def budget_checker(self) -> PerpetualBudgetChecker: + return self._budget_checker + + async def get_gas_estimate(self): + """ + Gets the gas estimates for the connector. + """ + try: + response: Dict[Any] = await self._get_gateway_instance().amm_perp_estimate_gas( + chain=self.chain, network=self.network, connector=self.connector_name + ) + self.network_transaction_fee = TokenAmount( + response.get("gasPriceToken"), Decimal(response.get("gasCost")) + ) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting gas price estimates for {self.connector_name} on {self.network}.", + exc_info=True, + app_warning_msg=str(e) + ) + + def get_order_size_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("1e-18") + + @async_ttl_cache(ttl=5, maxsize=10) + async def get_quote_price( + self, + trading_pair: str, + is_buy: bool, + amount: Decimal, + ignore_shim: bool = False + ) -> Optional[Decimal]: + """ + Retrieves a quote price. + + :param trading_pair: The market trading pair + :param is_buy: True for an intention to buy, False for an intention to sell + :param amount: The amount required (in base token unit) + :param ignore_shim: Ignore the price shim, and return the real price on the network + :return: The quote price. + """ + + base, quote = trading_pair.split("-") + side: PositionSide = PositionSide.LONG if is_buy else PositionSide.SHORT + + # Get the price from gateway price shim for integration tests. + if not ignore_shim: + test_price: Optional[Decimal] = await GatewayPriceShim.get_instance().get_connector_price( + self.connector_name, + self.chain, + self.network, + trading_pair, + is_buy, + amount + ) + if test_price is not None: + # Grab the gas price for test net. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_price( + self.chain, self.network, self.connector_name, base, quote, amount, side + ) + gas_price_token: str = resp["gasPriceToken"] + gas_cost: Decimal = Decimal(resp["gasCost"]) + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + except asyncio.CancelledError: + raise + except Exception: + pass + return test_price + + # Pull the price from gateway. + try: + resp: Dict[str, Any] = await self._get_gateway_instance().get_perp_market_price( + chain=self.chain, + network=self.network, + connector=self.connector_name, + base_asset=base, + quote_asset=quote, + amount=amount, + side=side, + ) + return Decimal(resp["markPrice"]) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error getting prices for {trading_pair} {side} order for {amount} amount.", + exc_info=True, + app_warning_msg=str(e) + ) + + def buy( + self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.MARKET, + price: Decimal = s_decimal_NaN, + **kwargs, + ) -> str: + """ + The function that takes the strategy inputs generates a client order ID + (used by Hummingbot for local order tracking) and places a buy order by + calling the _create_order() function. + + Parameters + ---------- + trading_pair: + The pair that is being traded + amount: + The amount to trade + order_type: + LIMIT + position_action: + OPEN or CLOSE + price: + Price for a limit order + """ + order_id: str = self.create_market_order_id(TradeType.BUY, trading_pair) + safe_ensure_future( + self._create_order(TradeType.BUY, + order_id, + trading_pair, + amount, + order_type, + kwargs["position_action"], + price) + ) + return order_id + + def sell( + self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.MARKET, + price: Decimal = s_decimal_NaN, + **kwargs, + ) -> str: + """ + The function that takes the strategy inputs generates a client order ID + (used by Hummingbot for local order tracking) and places a buy order by + calling the _create_order() function. + + Parameters + ---------- + trading_pair: + The pair that is being traded + amount: + The amount to trade + order_type: + LIMIT + position_action: + OPEN or CLOSE + price: + Price for a limit order + """ + order_id: str = self.create_market_order_id(TradeType.SELL, trading_pair) + safe_ensure_future( + self._create_order(TradeType.SELL, + order_id, + trading_pair, + amount, + order_type, + kwargs["position_action"], + price) + ) + return order_id + + async def _create_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction, + price: Decimal, + **request_args + ): + """ + This function is responsible for executing the API request to place the order on the exchange. + :param trade_type: BUY or SELL + :param order_id: Internal order id (also called client_order_id) + :param trading_pair: The market to place order + :param amount: The order amount (in base token value) + order_type: LIMIT + position_action: OPEN or CLOSE + :param price: The order price + """ + + if position_action not in [PositionAction.OPEN, PositionAction.CLOSE]: + raise ValueError("Specify either OPEN_POSITION or CLOSE_POSITION position_action.") + + amount = self.quantize_order_amount(trading_pair, amount) + price = self.quantize_order_price(trading_pair, price) + side = PositionSide.LONG if TradeType.BUY else PositionSide.SHORT + base, quote = trading_pair.split("-") + self.start_tracking_order( + order_id=order_id, + trading_pair=trading_pair, + trading_type=trade_type, + price=price, + amount=amount, + leverage=self._leverage[trading_pair], + position=position_action, + ) + try: + if position_action == PositionAction.OPEN: + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_perp_open( + chain=self.chain, + network=self.network, + connector=self.connector_name, + address=self.address, + base_asset=base, + quote_asset=quote, + side=side, + amount=amount * Decimal(str(self.get_leverage(trading_pair))), + price= price, + ) + else: + # Note: Partial close request isn't supported yet. + order_result: Dict[str, Any] = await self._get_gateway_instance().amm_perp_close( + chain=self.chain, + network=self.network, + connector=self.connector_name, + address=self.address, + base_asset=base, + quote_asset=quote, + ) + transaction_hash: Optional[str] = order_result.get("txHash") + if transaction_hash is not None: + gas_cost: Decimal = Decimal(order_result.get("gasCost")) + gas_price_token: str = order_result.get("gasPriceToken") + self.network_transaction_fee = TokenAmount(gas_price_token, gas_cost) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + exchange_order_id=transaction_hash, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.OPEN, # Assume that the transaction has been successfully mined. + misc_updates={ + "nonce": order_result.get("nonce"), + "gas_price": Decimal(order_result.get("gasPrice")), + "gas_limit": int(order_result.get("gasLimit")), + "gas_cost": Decimal(order_result.get("gasCost")), + "gas_price_token": order_result.get("gasPriceToken"), + "fee_asset": self._native_currency, + "leverage": self.get_leverage(trading_pair), + "position": position_action, + + } + ) + self._order_tracker.process_order_update(order_update) + else: + raise ValueError + + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().network( + f"Error submitting order to {self.connector_name} for {amount} {trading_pair} " + f"{'' if order_type is OrderType.MARKET else price}.", + exc_info=True, + app_warning_msg=str(e), + ) + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED + ) + self._order_tracker.process_order_update(order_update) + + def start_tracking_order( + self, + order_id: str, + trading_pair: str, + trading_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + gas_price: Decimal = s_decimal_0, + exchange_order_id: Optional[str] = None, + leverage: int = 1, + position: PositionAction = PositionAction.NIL, + ): + """ + Starts tracking an order by simply adding it into _in_flight_orders dictionary in ClientOrderTracker. + """ + self._order_tracker.start_tracking_order( + GatewayInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trading_type, + price=price, + amount=amount, + gas_price=gas_price, + creation_timestamp=self.current_timestamp, + initial_state=OrderState.PENDING_CREATE, + leverage=leverage, + position=position + ) + ) + + @property + def status_dict(self) -> Dict[str, bool]: + return { + "account_balance": len(self._account_balances) > 0 if self._trading_required else True, + "native_currency": self._native_currency is not None, + "network_transaction_fee": self.network_transaction_fee is not None if self._trading_required else True, + } + + async def start_network(self): + if self._trading_required: + self._status_polling_task = safe_ensure_future(self._status_polling_loop()) + self._get_gas_estimate_task = safe_ensure_future(self.get_gas_estimate()) + self._get_chain_info_task = safe_ensure_future(self.get_chain_info()) + + async def stop_network(self): + if self._status_polling_task is not None: + self._status_polling_task.cancel() + self._status_polling_task = None + if self._get_chain_info_task is not None: + self._get_chain_info_task.cancel() + self._get_chain_info_task = None + if self._get_gas_estimate_task is not None: + self._get_gas_estimate_task.cancel() + self._get_chain_info_task = None + + async def check_network(self) -> NetworkStatus: + try: + if await self._get_gateway_instance().ping_gateway(): + return NetworkStatus.CONNECTED + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.NOT_CONNECTED + + def tick(self, timestamp: float): + """ + Is called automatically by the clock for each clock's tick (1 second by default). + It checks if status polling task is due for execution. + """ + if time.time() - self._last_poll_timestamp > self.POLL_INTERVAL: + if self._poll_notifier is not None and not self._poll_notifier.is_set(): + self._poll_notifier.set() + + async def _status_polling_loop(self): + await self.update_balances(on_interval=False) + while True: + try: + self._poll_notifier = asyncio.Event() + await self._poll_notifier.wait() + await safe_gather( + self.update_balances(on_interval=True), + self.update_positions(), + self.update_canceling_transactions(self.canceling_orders), + self.update_order_status(self.amm_orders) + ) + self._last_poll_timestamp = self.current_timestamp + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(str(e), exc_info=True) + + async def update_balances(self, on_interval=False): + """ + Calls Eth API to update total and available balances. + """ + if self._native_currency is None: + await self.get_chain_info() + last_tick = self._last_balance_poll_timestamp + current_tick = self.current_timestamp + if not on_interval or (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL: + self._last_balance_poll_timestamp = current_tick + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + native_bal_resp_json: Dict[str, Any] = await self._get_gateway_instance().get_balances( + self.chain, self.network, self.address, [self._native_currency] + ) + col_bal_resp_json: Dict[str, Any] = await self._get_gateway_instance().amm_perp_balance( + chain = self.chain, + network = self.network, + connector = self.connector_name, + address = self.address, + ) + for token, bal in native_bal_resp_json["balances"].items(): + self._account_available_balances[token] = Decimal(str(bal)) + self._account_balances[token] = Decimal(str(bal)) + remote_asset_names.add(token) + + col_balance = col_bal_resp_json.get("balance", "0") + self._account_available_balances[self._collateral_currency] = Decimal(str(col_balance)) + self._account_balances[self._collateral_currency] = Decimal(str(col_balance)) + remote_asset_names.add(self._collateral_currency) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._order_tracker.all_orders.items()} + self._in_flight_orders_snapshot_timestamp = self.current_timestamp + + def get_next_funding_timestamp(self): + # We're returing a value of -1 because of the nature of block based funding payment + return -1 + + def set_leverage(self, trading_pair: str, leverage: int = 1): + self._leverage[trading_pair] = leverage + self.logger().info(f"Leverage Successfully set to {leverage} for {trading_pair}.") + return leverage + + def set_position_mode(self, position_mode: PositionMode): + for trading_pair in self._trading_pairs: + self.trigger_event(AccountEvent.PositionModeChangeSucceeded, + PositionModeChangeEvent( + self.current_timestamp, + trading_pair, + position_mode) + ) + self._position_mode = position_mode + self.logger().info(f"Using {position_mode.name} position mode for pair {trading_pair}.") + return position_mode + + def supported_position_modes(self): + """ + This method needs to be overridden to provide the accurate information depending on the exchange. + """ + return [PositionMode.ONEWAY] + + def get_buy_collateral_token(self, trading_pair: str) -> str: + return self._collateral_currency + + def get_sell_collateral_token(self, trading_pair: str) -> str: + return self._collateral_currency + + async def update_positions(self): + position_requests = [] + for trading_pair in self._trading_pairs: + base, quote = trading_pair.split("-") + position_requests.append(self._get_gateway_instance().get_perp_position( + chain = self.chain, + network = self.network, + connector = self.connector_name, + address = self.address, + base_asset = base, + quote_asset = quote, + )) + positions: Dict[str, Any] = await safe_gather(*position_requests, return_exceptions=True) + + for position in positions: + trading_pair = f"{position.get('base')}-{position.get('quote')}" + position_side = PositionSide.LONG if position.get("positionSide") == "LONG" else PositionSide.SHORT + unrealized_pnl = Decimal(position.get("unrealizedProfit", "0")) + entry_price = Decimal(position.get("entryPrice", "0")) + amount = Decimal(position.get("positionAmt", "0")) + leverage = Decimal(position.get("leverage", "0")) + existing_position = self.get_position(trading_pair, position_side) + pos_key = self.position_key(trading_pair, position_side) + if amount != s_decimal_0: + if existing_position is None: + self._account_positions[pos_key] = Position( + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage + ) + else: + existing_position.update_position( + position_side = position_side, + unrealized_pnl = unrealized_pnl, + entry_price = entry_price, + amount = amount) + else: + if pos_key in self._account_positions: + del self._account_positions[pos_key] + + async def _fetch_funding_payment(self, trading_pair: str) -> bool: + """ + This ought to fetch the funding settlement details of all the active trading pairs and trigger FundingPaymentCompletedEvent. + But retrieving funding payments is a little tricky due to nature of block-based funding payment. + """ + pass + + async def _get_funding_info(self, trading_pair: str) -> Optional[FundingInfo]: + base, quote = trading_pair.split("-") + prices: Dict[str, Any] = await self._get_gateway_instance().get_perp_market_price( + chain=self.chain, + network=self.network, + connector=self.connector_name, + base_asset=base, + quote_asset=quote, + amount=s_decimal_0, + side=PositionSide.LONG, + ) + pos: Dict[str, Any] = await self._get_gateway_instance().get_perp_position( + chain = self.chain, + network = self.network, + connector = self.connector_name, + address = self.address, + base_asset = base, + quote_asset = quote, + ) + rate = s_decimal_0 if pos["pendingFundingPayment"] == "0" and pos["positionAmt"] == "0" \ + else Decimal(pos["pendingFundingPayment"]) / Decimal(pos["positionAmt"]) + self._funding_info[trading_pair] = FundingInfo( + trading_pair=trading_pair, + index_price=Decimal(prices["indexPrice"]), + mark_price=Decimal(prices["markPrice"]), + next_funding_utc_timestamp=0, # technically, it's next block + rate=rate, + ) + + def get_funding_info(self, trading_pair: str) -> Optional[FundingInfo]: + """ + Retrieves the Funding Info for the specified trading pair. + :param: trading_pair: The specified trading pair. + """ + safe_ensure_future(self._get_funding_info()) + return self._funding_info[trading_pair] diff --git a/hummingbot/connector/gateway/clob_perp/__init__.py b/hummingbot/connector/gateway/clob_perp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/__init__.py b/hummingbot/connector/gateway/clob_perp/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/clob_perp_api_data_source_base.py b/hummingbot/connector/gateway/clob_perp/data_sources/clob_perp_api_data_source_base.py new file mode 100644 index 0000000..c248256 --- /dev/null +++ b/hummingbot/connector/gateway/clob_perp/data_sources/clob_perp_api_data_source_base.py @@ -0,0 +1,43 @@ +from abc import abstractmethod +from decimal import Decimal +from typing import List, Tuple + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.gateway.clob_spot.data_sources.clob_api_data_source_base import CLOBAPIDataSourceBase +from hummingbot.core.data_type.common import PositionMode +from hummingbot.core.data_type.funding_info import FundingInfo + + +class CLOBPerpAPIDataSourceBase(CLOBAPIDataSourceBase): + @property + @abstractmethod + def supported_position_modes(self) -> List[PositionMode]: + ... + + @abstractmethod + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + ... + + @abstractmethod + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + ... + + @abstractmethod + async def fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + ... + + @abstractmethod + async def set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + ... + + @abstractmethod + async def trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + ... + + @abstractmethod + async def fetch_positions(self) -> List[Position]: + ... + + @abstractmethod + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + ... diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/__init__.py b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py new file mode 100644 index 0000000..e6ab188 --- /dev/null +++ b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_api_data_source.py @@ -0,0 +1,1345 @@ +import asyncio +import json +import time +from collections import defaultdict +from decimal import Decimal +from enum import Enum +from typing import Any, Dict, List, Optional, Tuple + +from grpc.aio import UnaryStreamCall +from pyinjective.async_client import AsyncClient +from pyinjective.orderhash import OrderHashResponse +from pyinjective.proto.exchange.injective_accounts_rpc_pb2 import StreamSubaccountBalanceResponse +from pyinjective.proto.exchange.injective_derivative_exchange_rpc_pb2 import ( + DerivativeLimitOrderbookV2, + DerivativeMarketInfo, + DerivativeOrderHistory, + DerivativePosition, + DerivativeTrade, + FundingPayment, + FundingPaymentsResponse, + FundingRate, + FundingRatesResponse, + MarketsResponse, + OrderbooksV2Response, + OrdersHistoryResponse, + PositionsResponse, + StreamOrderbookV2Response, + StreamOrdersHistoryResponse, + StreamPositionsResponse, + StreamTradesResponse, + TokenMeta, + TradesResponse, +) +from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import GetTxByTxHashResponse, StreamTxsResponse, TxDetailData +from pyinjective.proto.exchange.injective_oracle_rpc_pb2 import StreamPricesResponse +from pyinjective.proto.exchange.injective_portfolio_rpc_pb2 import ( + AccountPortfolioResponse, + Coin, + Portfolio, + StreamAccountPortfolioResponse, + SubaccountBalanceV2, +) +from pyinjective.proto.injective.exchange.v1beta1.exchange_pb2 import DerivativeOrder + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.gateway.clob_perp.data_sources.clob_perp_api_data_source_base import CLOBPerpAPIDataSourceBase +from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual import ( + injective_perpetual_constants as CONSTANTS, +) +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_utils import Composer, OrderHashManager +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates, TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.events import ( + AccountEvent, + BalanceUpdateEvent, + MarketEvent, + OrderBookDataSourceEvent, + PositionUpdateEvent, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather + + +class InjectivePerpetualAPIDataSource(CLOBPerpAPIDataSourceBase): + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + super().__init__( + trading_pairs=trading_pairs, connector_spec=connector_spec, client_config_map=client_config_map + ) + self._connector_name = CONSTANTS.CONNECTOR_NAME + self._chain = connector_spec["chain"] + self._network = connector_spec["network"] + self._account_id = connector_spec["wallet_address"] + self._is_default_subaccount = self._account_id[-24:] == "000000000000000000000000" + + self._network_obj = CONSTANTS.NETWORK_CONFIG[self._network] + self._client = AsyncClient(network=self._network_obj) + self._account_address: Optional[str] = None + self._throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + + self._composer = Composer(network=self._network_obj.string()) + self._order_hash_manager: Optional[OrderHashManager] = None + + # Market Info Attributes + self._market_id_to_active_perp_markets: Dict[str, DerivativeMarketInfo] = {} + + self._denom_to_token_meta: Dict[str, TokenMeta] = {} + + # Listener(s) and Loop Task(s) + self._update_market_info_loop_task: Optional[asyncio.Task] = None + self._trades_stream_listener: Optional[asyncio.Task] = None + self._order_listeners: Dict[str, asyncio.Task] = {} + self._funding_info_listeners: Dict[str, asyncio.Task] = {} + self._order_books_stream_listener: Optional[asyncio.Task] = None + self._bank_balances_stream_listener: Optional[asyncio.Task] = None + self._subaccount_balances_stream_listener: Optional[asyncio.Task] = None + self._positions_stream_listener: Optional[asyncio.Task] = None + self._transactions_stream_listener: Optional[asyncio.Task] = None + + self._order_placement_lock = asyncio.Lock() + + # Local Balance + self._account_balances: defaultdict[str, Decimal] = defaultdict(lambda: Decimal("0")) + self._account_available_balances: defaultdict[str, Decimal] = defaultdict(lambda: Decimal("0")) + + @property + def real_time_balance_update(self) -> bool: + return True + + @property + def events_are_streamed(self) -> bool: + return True + + @staticmethod + def supported_stream_events() -> List[Enum]: + return [ + MarketEvent.TradeUpdate, + MarketEvent.OrderUpdate, + AccountEvent.BalanceEvent, + AccountEvent.PositionUpdate, + MarketEvent.FundingInfo, + OrderBookDataSourceEvent.TRADE_EVENT, + OrderBookDataSourceEvent.SNAPSHOT_EVENT, + ] + + def get_supported_order_types(self) -> List[OrderType]: + return CONSTANTS.SUPPORTED_ORDER_TYPES + + def supported_position_modes(self) -> List[PositionMode]: + return CONSTANTS.SUPPORTED_POSITION_MODES + + async def start(self): + """ + Starts the event streaming. + """ + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + + # Fetches and maintains dictionary of active markets + self._update_market_info_loop_task = self._update_market_info_loop_task or safe_ensure_future( + coro=self._update_market_info_loop() + ) + + # Ensures all market info has been initialized before starting streaming tasks. + await self._update_markets() + await self._start_streams() + self._gateway_order_tracker.lost_order_count_limit = CONSTANTS.LOST_ORDER_COUNT_LIMIT + self.logger().debug(f"account: {self._account_id}") + self.logger().debug(f"balances: {await self.get_account_balances()}") + + async def stop(self): + """ + Stops the event streaming. + """ + await self._stop_streams() + self._update_market_info_loop_task and self._update_market_info_loop_task.cancel() + self._update_market_info_loop_task = None + + async def check_network_status(self) -> NetworkStatus: + status = NetworkStatus.CONNECTED + try: + async with self._throttler.execute_task(limit_id=CONSTANTS.PING_LIMIT_ID): + await self._client.ping() + await self._get_gateway_instance().ping_gateway() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + async def set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + """ + Leverage is set on a per order basis. See place_order() + """ + return True, "" + + async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + market_info = self._markets_info[trading_pair] + price_scaler: Decimal = Decimal(f"1e-{market_info.quote_token_meta.decimals}") + async with self._throttler.execute_task(limit_id=CONSTANTS.ORDER_BOOK_LIMIT_ID): + response: OrderbooksV2Response = await self._client.get_derivative_orderbooksV2( + market_ids=[market_info.market_id] + ) + + snapshot_ob: DerivativeLimitOrderbookV2 = response.orderbooks[0].orderbook + snapshot_timestamp_ms: float = max( + [entry.timestamp for entry in list(snapshot_ob.buys) + list(snapshot_ob.sells)] + [0] + ) + snapshot_content: Dict[str, Any] = { + "trading_pair": combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote), + "update_id": snapshot_timestamp_ms, + "bids": [(Decimal(entry.price) * price_scaler, entry.quantity) for entry in snapshot_ob.buys], + "asks": [(Decimal(entry.price) * price_scaler, entry.quantity) for entry in snapshot_ob.sells], + } + + snapshot_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content=snapshot_content, + timestamp=snapshot_timestamp_ms * 1e-3, + ) + return snapshot_msg + + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(status_update_exception).startswith("No update found for order") + + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return False + + async def get_order_status_update(self, in_flight_order: GatewayInFlightOrder) -> OrderUpdate: + self.logger().debug( + f"Fetching order status update for {in_flight_order.client_order_id}" + f" with order hash {in_flight_order.exchange_order_id}" + ) + status_update: Optional[OrderUpdate] = None + misc_updates = { + "creation_transaction_hash": in_flight_order.creation_transaction_hash, + "cancelation_transaction_hash": in_flight_order.cancel_tx_hash, + } + + # Fetch by Order History + order_history: Optional[DerivativeOrderHistory] = await self._fetch_order_history(order=in_flight_order) + if order_history is not None: + status_update: OrderUpdate = self._parse_order_update_from_order_history( + order=in_flight_order, order_history=order_history, order_misc_updates=misc_updates + ) + + # Determine if order has failed from transaction hash + if status_update is None and in_flight_order.creation_transaction_hash is not None: + try: + tx_response: GetTxByTxHashResponse = await self._fetch_transaction_by_hash( + transaction_hash=in_flight_order.creation_transaction_hash + ) + except Exception: + self.logger().debug( + f"Failed to fetch transaction {in_flight_order.creation_transaction_hash} for order" + f" {in_flight_order.exchange_order_id}.", + exc_info=True, + ) + tx_response = None + if tx_response is None: + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + elif await self._check_if_order_failed_based_on_transaction(transaction=tx_response, order=in_flight_order): + status_update: OrderUpdate = self._parse_failed_order_update_from_transaction_hash_response( + order=in_flight_order, response=tx_response, order_misc_updates=misc_updates + ) + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + + if status_update is None: + raise ValueError(f"No update found for order {in_flight_order.client_order_id}") + + if in_flight_order.current_state == OrderState.PENDING_CREATE and status_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=status_update.update_timestamp, + new_state=OrderState.OPEN, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=status_update.exchange_order_id, + misc_updates=misc_updates, + ) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=open_update) + + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=status_update) + + return status_update + + async def place_order( + self, order: GatewayInFlightOrder, **kwargs + ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + perp_order_to_create = [self._compose_derivative_order_for_local_hash_computation(order=order)] + async with self._order_placement_lock: + self.logger().debug(f"Creating order {order.client_order_id}") + order_hashes: OrderHashResponse = self._order_hash_manager.compute_order_hashes( + spot_orders=[], derivative_orders=perp_order_to_create + ) + order_hash: str = order_hashes.derivative[0] + + try: + async with self._throttler.execute_task(limit_id=CONSTANTS.TRANSACTION_POST_LIMIT_ID): + order_result: Dict[str, Any] = await self._get_gateway_instance().clob_perp_place_order( + connector=self._connector_name, + chain=self._chain, + network=self._network, + trading_pair=order.trading_pair, + address=self._account_id, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + size=order.amount, + leverage=order.leverage, + ) + + transaction_hash: Optional[str] = order_result.get("txHash") + except Exception: + await self._update_account_address_and_create_order_hash_manager() + self.logger().debug(f"Failed to create order {order.client_order_id}", exc_info=True) + raise + + self.logger().debug( + f"Placed order {order.client_order_id} with order hash {order_hash}," + f" nonce {self._order_hash_manager.current_nonce - 1}," + f" and tx hash {transaction_hash}." + ) + + if transaction_hash in [None, ""]: + await self._update_account_address_and_create_order_hash_manager() + raise ValueError( + f"The creation transaction for {order.client_order_id} failed. Please ensure there is sufficient" + f" INJ in the bank to cover transaction fees." + ) + + transaction_hash = f"0x{transaction_hash.lower()}" + + misc_updates = { + "creation_transaction_hash": transaction_hash, + } + + return order_hash, misc_updates + + async def batch_order_create(self, orders_to_create: List[GatewayInFlightOrder]) -> List[PlaceOrderResult]: + derivative_orders_to_create = [ + self._compose_derivative_order_for_local_hash_computation(order=order) for order in orders_to_create + ] + + async with self._order_placement_lock: + order_hashes = self._order_hash_manager.compute_order_hashes( + spot_orders=[], derivative_orders=derivative_orders_to_create + ) + try: + async with self._throttler.execute_task(limit_id=CONSTANTS.TRANSACTION_POST_LIMIT_ID): + update_result = await self._get_gateway_instance().clob_perp_batch_order_modify( + connector=self._connector_name, + chain=self._chain, + network=self._network, + address=self._account_id, + orders_to_create=orders_to_create, + orders_to_cancel=[], + ) + except Exception: + await self._update_account_address_and_create_order_hash_manager() + raise + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash is None: + await self._update_account_address_and_create_order_hash_manager() + self.logger().error("The batch order update transaction failed.") + exception = RuntimeError("The creation transaction has failed on the Injective chain.") + + transaction_hash = f"0x{transaction_hash.lower()}" + + place_order_results = [ + PlaceOrderResult( + update_timestamp=self._time(), + client_order_id=order.client_order_id, + exchange_order_id=order_hash, + trading_pair=order.trading_pair, + misc_updates={ + "creation_transaction_hash": transaction_hash, + }, + exception=exception, + ) + for order, order_hash in zip(orders_to_create, order_hashes.derivative) + ] + + return place_order_results + + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Optional[Dict[str, Any]]]: + await order.get_exchange_order_id() + + async with self._throttler.execute_task(limit_id=CONSTANTS.TRANSACTION_POST_LIMIT_ID): + cancelation_result = await self._get_gateway_instance().clob_perp_cancel_order( + chain=self._chain, + network=self._network, + connector=self._connector_name, + address=self._account_id, + trading_pair=order.trading_pair, + exchange_order_id=order.exchange_order_id, + ) + transaction_hash: Optional[str] = cancelation_result.get("txHash") + + if transaction_hash in [None, ""]: + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + raise ValueError( + f"The cancelation transaction for {order.client_order_id} failed. Please ensure there is sufficient" + f" INJ in the bank to cover transaction fees." + ) + + self.logger().debug( + f"Canceling order {order.client_order_id}" + f" with order hash {order.exchange_order_id} and tx hash {transaction_hash}." + ) + + transaction_hash = f"0x{transaction_hash.lower()}" + + misc_updates = {"cancelation_transaction_hash": transaction_hash} + + return True, misc_updates + + async def batch_order_cancel(self, orders_to_cancel: List[InFlightOrder]) -> List[CancelOrderResult]: + in_flight_orders_to_cancel = [ + self._gateway_order_tracker.fetch_tracked_order(client_order_id=order.client_order_id) + for order in orders_to_cancel + ] + exchange_order_ids_to_cancel = await safe_gather( + *[order.get_exchange_order_id() for order in in_flight_orders_to_cancel], + return_exceptions=True, + ) + found_orders_to_cancel = [ + order + for order, result in zip(orders_to_cancel, exchange_order_ids_to_cancel) + if not isinstance(result, asyncio.TimeoutError) + ] + + async with self._throttler.execute_task(limit_id=CONSTANTS.TRANSACTION_POST_LIMIT_ID): + update_result = await self._get_gateway_instance().clob_perp_batch_order_modify( + connector=self._connector_name, + chain=self._chain, + network=self._network, + address=self._account_id, + orders_to_create=[], + orders_to_cancel=found_orders_to_cancel, + ) + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash is None: + await self._update_account_address_and_create_order_hash_manager() + self.logger().error("The batch order update transaction failed.") + exception = RuntimeError("The cancelation transaction has failed on the Injective chain.") + + transaction_hash = f"0x{transaction_hash.lower()}" + + cancel_order_results = [ + CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + misc_updates={"cancelation_transaction_hash": transaction_hash}, + exception=exception, + ) + for order in orders_to_cancel + ] + + return cancel_order_results + + async def fetch_positions(self) -> List[Position]: + market_ids = self._get_market_ids() + async with self._throttler.execute_task(limit_id=CONSTANTS.POSITIONS_LIMIT_ID): + backend_positions: PositionsResponse = await self._client.get_derivative_positions( + market_ids=market_ids, subaccount_id=self._account_id + ) + + positions = [ + self._parse_backed_position_to_position(backend_position=backed_position) + for backed_position in backend_positions.positions + ] + return positions + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + return await self._request_funding_info(trading_pair=trading_pair) + + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + return await self._request_last_trade_price(trading_pair=trading_pair) + + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + if self._account_address is None: + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + self._check_markets_initialized() or await self._update_markets() + + async with self._throttler.execute_task(limit_id=CONSTANTS.ACCOUNT_PORTFOLIO_LIMIT_ID): + portfolio_response: AccountPortfolioResponse = await self._client.get_account_portfolio( + account_address=self._account_address + ) + + portfolio: Portfolio = portfolio_response.portfolio + bank_balances: List[Coin] = portfolio.bank_balances + sub_account_balances: List[SubaccountBalanceV2] = portfolio.subaccounts + + balances_dict: Dict[str, Dict[str, Decimal]] = {} + + if self._is_default_subaccount: + for bank_entry in bank_balances: + denom_meta = self._denom_to_token_meta.get(bank_entry.denom) + if denom_meta is not None: + asset_name: str = denom_meta.symbol + denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + + available_balance: Decimal = Decimal(bank_entry.amount) * denom_scaler + total_balance: Decimal = available_balance + balances_dict[asset_name] = { + "total_balance": total_balance, + "available_balance": available_balance, + } + + for entry in sub_account_balances: + if entry.subaccount_id.casefold() != self._account_id.casefold(): + continue + + denom_meta = self._denom_to_token_meta.get(entry.denom) + if denom_meta is not None: + asset_name: str = denom_meta.symbol + denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + + total_balance: Decimal = Decimal(entry.deposit.total_balance) * denom_scaler + available_balance: Decimal = Decimal(entry.deposit.available_balance) * denom_scaler + + balance_element = balances_dict.get( + asset_name, {"total_balance": Decimal("0"), "available_balance": Decimal("0")} + ) + balance_element["total_balance"] += total_balance + balance_element["available_balance"] += available_balance + balances_dict[asset_name] = balance_element + + self._update_local_balances(balances=balances_dict) + return balances_dict + + async def get_all_order_fills(self, in_flight_order: InFlightOrder) -> List[TradeUpdate]: + self.logger().debug( + f"Getting all roder fills for {in_flight_order.client_order_id}" + f" with order hash {in_flight_order.exchange_order_id}" + ) + exchange_order_id = await in_flight_order.get_exchange_order_id() + trades: List[DerivativeTrade] = await self._fetch_order_fills(order=in_flight_order) + + trade_updates: List[TradeUpdate] = [] + client_order_id: str = in_flight_order.client_order_id + for trade in trades: + if trade.order_hash == exchange_order_id: + _, trade_update = self._parse_backend_trade(client_order_id=client_order_id, backend_trade=trade) + trade_updates.append(trade_update) + + return trade_updates + + async def fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + timestamp, funding_rate, payment = 0, Decimal("-1"), Decimal("-1") + + if trading_pair not in self._markets_info: + return timestamp, funding_rate, payment + + async with self._throttler.execute_task(limit_id=CONSTANTS.FUNDING_PAYMENT_LIMIT_ID): + response: FundingPaymentsResponse = await self._client.get_funding_payments( + subaccount_id=self._account_id, market_id=self._markets_info[trading_pair].market_id, limit=1 + ) + + if len(response.payments) != 0: + latest_funding_payment: FundingPayment = response.payments[0] # List of payments sorted by latest + + timestamp: float = latest_funding_payment.timestamp * 1e-3 + + # FundingPayment does not include price, hence we have to fetch latest funding rate + funding_rate: Decimal = await self._request_last_funding_rate(trading_pair=trading_pair) + amount_scaler: Decimal = Decimal(f"1e-{self._markets_info[trading_pair].quote_token_meta.decimals}") + payment: Decimal = Decimal(latest_funding_payment.amount) * amount_scaler + + return timestamp, funding_rate, payment + + async def _update_account_address_and_create_order_hash_manager(self): + if not self._order_placement_lock.locked(): + raise RuntimeError("The order-placement lock must be acquired before creating the order hash manager.") + async with self._throttler.execute_task(limit_id=CONSTANTS.BALANCES_LIMIT_ID): + response: Dict[str, Any] = await self._get_gateway_instance().clob_injective_balances( + chain=self._chain, network=self._network, address=self._account_id + ) + self._account_address: str = response["injectiveAddress"] + + async with self._throttler.execute_task(limit_id=CONSTANTS.ACCOUNT_LIMIT_ID): + await self._client.get_account(address=self._account_address) + async with self._throttler.execute_task(limit_id=CONSTANTS.SYNC_TIMEOUT_HEIGHT_LIMIT_ID): + await self._client.sync_timeout_height() + tasks_to_await_submitted_orders_to_be_processed_by_chain = [ + asyncio.wait_for(order.wait_until_processed_by_exchange(), timeout=CONSTANTS.ORDER_CHAIN_PROCESSING_TIMEOUT) + for order in self._gateway_order_tracker.active_orders.values() + if order.creation_transaction_hash is not None + ] # orders that have been sent to the chain but not yet added to a block will affect the order nonce + await safe_gather( + *tasks_to_await_submitted_orders_to_be_processed_by_chain, return_exceptions=True # await their processing + ) + self._order_hash_manager = OrderHashManager(network=self._network_obj, sub_account_id=self._account_id) + async with self._throttler.execute_task(limit_id=CONSTANTS.NONCE_LIMIT_ID): + await self._order_hash_manager.start() + + def _check_markets_initialized(self) -> bool: + return ( + len(self._markets_info) != 0 + and len(self._market_id_to_active_perp_markets) != 0 + and len(self._denom_to_token_meta) != 0 + ) + + async def trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + """ + Leverage is set on a per order basis. See place_order() + """ + if mode in self.supported_position_modes() and trading_pair in self._markets_info: + return True, "" + return False, "Please check that Position Mode is supported and trading pair is active." + + async def _update_market_info_loop(self): + while True: + await self._sleep(delay=CONSTANTS.MARKETS_UPDATE_INTERVAL) + await self._update_markets() + + async def _update_markets(self): + """Fetches and updates trading pair maps of active perpetual markets.""" + perpetual_markets: MarketsResponse = await self._fetch_derivative_markets() + self._update_market_map_attributes(markets=perpetual_markets) + spot_markets: MarketsResponse = await self._fetch_spot_markets() + self._update_denom_to_token_meta(markets=spot_markets) + + async def _fetch_derivative_markets(self) -> MarketsResponse: + market_status: str = "active" + async with self._throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_MARKETS_LIMIT_ID): + derivative_markets = await self._client.get_derivative_markets(market_status=market_status) + return derivative_markets + + async def _fetch_spot_markets(self) -> MarketsResponse: + market_status: str = "active" + async with self._throttler.execute_task(limit_id=CONSTANTS.SPOT_MARKETS_LIMIT_ID): + spot_markets = await self._client.get_spot_markets(market_status=market_status) + return spot_markets + + def _update_market_map_attributes(self, markets: MarketsResponse): + """Parses MarketsResponse and re-populate the market map attributes""" + active_perp_markets: Dict[str, DerivativeMarketInfo] = {} + market_id_to_market_map: Dict[str, DerivativeMarketInfo] = {} + for market in markets.markets: + trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + market_id: str = market.market_id + + active_perp_markets[trading_pair] = market + market_id_to_market_map[market_id] = market + + self._markets_info.clear() + self._market_id_to_active_perp_markets.clear() + + self._markets_info.update(active_perp_markets) + self._market_id_to_active_perp_markets.update(market_id_to_market_map) + + def _update_denom_to_token_meta(self, markets: MarketsResponse): + self._denom_to_token_meta.clear() + for market in markets.markets: + if market.base_token_meta.symbol != "": # the meta is defined + self._denom_to_token_meta[market.base_denom] = market.base_token_meta + if market.quote_token_meta.symbol != "": # the meta is defined + self._denom_to_token_meta[market.quote_denom] = market.quote_token_meta + + def _parse_trading_rule(self, trading_pair: str, market_info: Any) -> TradingRule: + min_price_tick_size = ( + Decimal(market_info.min_price_tick_size) * Decimal(f"1e-{market_info.quote_token_meta.decimals}") + ) + min_quantity_tick_size = Decimal(market_info.min_quantity_tick_size) + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + return trading_rule + + def _compose_derivative_order_for_local_hash_computation(self, order: GatewayInFlightOrder) -> DerivativeOrder: + market = self._markets_info[order.trading_pair] + return self._composer.DerivativeOrder( + market_id=market.market_id, + subaccount_id=self._account_id.lower(), + fee_recipient=self._account_address.lower(), + price=float(order.price), + quantity=float(order.amount), + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER, + leverage=float(order.leverage), + ) + + async def _fetch_order_history(self, order: GatewayInFlightOrder) -> Optional[DerivativeOrderHistory]: + # NOTE: Can be replaced by calling GatewayHttpClient.clob_perp_get_orders + trading_pair: str = order.trading_pair + order_hash: str = await order.get_exchange_order_id() + + market: DerivativeMarketInfo = self._markets_info[trading_pair] + direction: str = "buy" if order.trade_type == TradeType.BUY else "sell" + trade_type: TradeType = order.trade_type + order_type: OrderType = order.order_type + + order_history: Optional[DerivativeOrderHistory] = None + skip = 0 + search_completed = False + while not search_completed: + async with self._throttler.execute_task(limit_id=CONSTANTS.HISTORICAL_DERIVATIVE_ORDERS_LIMIT_ID): + response: OrdersHistoryResponse = await self._client.get_historical_derivative_orders( + market_id=market.market_id, + subaccount_id=self._account_id, + direction=direction, + start_time=int(order.creation_timestamp * 1e3), + limit=CONSTANTS.FETCH_ORDER_HISTORY_LIMIT, + skip=skip, + order_types=[CONSTANTS.CLIENT_TO_BACKEND_ORDER_TYPES_MAP[(trade_type, order_type)]], + ) + if len(response.orders) == 0: + search_completed = True + else: + skip += CONSTANTS.FETCH_ORDER_HISTORY_LIMIT + for response_order in response.orders: + if response_order.order_hash == order_hash: + order_history = response_order + search_completed = True + break + + return order_history + + async def _fetch_order_fills(self, order: InFlightOrder) -> List[DerivativeTrade]: + # NOTE: This can be replaced by calling `GatewayHttpClient.clob_get_order_trades(...)` + skip = 0 + all_trades: List[DerivativeTrade] = [] + search_completed = False + + market_info: DerivativeMarketInfo = self._markets_info[order.trading_pair] + + market_id: str = market_info.market_id + direction: str = "buy" if order.trade_type == TradeType.BUY else "sell" + + while not search_completed: + async with self._throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_TRADES_LIMIT_ID): + trades = await self._client.get_derivative_trades( + market_id=market_id, + subaccount_id=self._account_id, + direction=direction, + skip=skip, + start_time=int(order.creation_timestamp * 1e3), + ) + if len(trades.trades) == 0: + search_completed = True + else: + all_trades.extend(trades.trades) + skip += len(trades.trades) + + return all_trades + + async def _fetch_transaction_by_hash(self, transaction_hash: str) -> GetTxByTxHashResponse: + async with self._throttler.execute_task(limit_id=CONSTANTS.TRANSACTION_BY_HASH_LIMIT_ID): + transaction = await self._client.get_tx_by_hash(tx_hash=transaction_hash) + return transaction + + def _update_local_balances(self, balances: Dict[str, Dict[str, Decimal]]): + # We need to keep local copy of total and available balance so we can trigger BalanceUpdateEvent with correct + # details. This is specifically for Injective during the processing of balance streams, where the messages does not + # detail the total_balance and available_balance across bank and subaccounts. + for asset_name, balance_entry in balances.items(): + if "total_balance" in balance_entry: + self._account_balances[asset_name] = balance_entry["total_balance"] + if "available_balance" in balance_entry: + self._account_available_balances[asset_name] = balance_entry["available_balance"] + + async def _request_last_funding_rate(self, trading_pair: str) -> Decimal: + # NOTE: Can be removed when GatewayHttpClient.clob_perp_funding_info is used. + market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + async with self._throttler.execute_task(limit_id=CONSTANTS.FUNDING_RATES_LIMIT_ID): + response: FundingRatesResponse = await self._client.get_funding_rates( + market_id=market_info.market_id, limit=1 + ) + funding_rate: FundingRate = response.funding_rates[0] # We only want the latest funding rate. + return Decimal(funding_rate.rate) + + async def _request_oracle_price(self, market_info: DerivativeMarketInfo) -> Decimal: + # NOTE: Can be removed when GatewayHttpClient.clob_perp_funding_info is used. + """ + According to Injective, Oracle Price refers to mark price. + """ + async with self._throttler.execute_task(limit_id=CONSTANTS.ORACLE_PRICES_LIMIT_ID): + response = await self._client.get_oracle_prices( + base_symbol=market_info.oracle_base, + quote_symbol=market_info.oracle_quote, + oracle_type=market_info.oracle_type, + oracle_scale_factor=0, + ) + return Decimal(response.price) + + async def _request_last_trade_price(self, trading_pair: str) -> Decimal: + # NOTE: Can be replaced by calling GatewayHTTPClient.clob_perp_last_trade_price + market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + async with self._throttler.execute_task(limit_id=CONSTANTS.DERIVATIVE_TRADES_LIMIT_ID): + response: TradesResponse = await self._client.get_derivative_trades(market_id=market_info.market_id) + last_trade: DerivativeTrade = response.trades[0] + price_scaler: Decimal = Decimal(f"1e-{market_info.quote_token_meta.decimals}") + last_trade_price: Decimal = Decimal(last_trade.position_delta.execution_price) * price_scaler + return last_trade_price + + def _parse_derivative_ob_message(self, message: StreamOrderbookV2Response) -> OrderBookMessage: + """ + Order Update Example: + orderbook { + buys { + price: "23452500000" + quantity: "0.3207" + timestamp: 1677748154571 + } + + sells { + price: "23454100000" + quantity: "0.3207" + timestamp: 1677748184974 + } + } + operation_type: "update" + timestamp: 1677748187000 + market_id: "0x4ca0f92fc28be0c9761326016b5a1a2177dd6375558365116b5bdda9abc229ce" # noqa: documentation + """ + update_ts_ms: int = message.timestamp + market_id: str = message.market_id + market: DerivativeMarketInfo = self._market_id_to_active_perp_markets[market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + price_scaler: Decimal = Decimal(f"1e-{market.quote_token_meta.decimals}") + bids = [(Decimal(bid.price) * price_scaler, Decimal(bid.quantity)) for bid in message.orderbook.buys] + asks = [(Decimal(ask.price) * price_scaler, Decimal(ask.quantity)) for ask in message.orderbook.sells] + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": update_ts_ms, + "bids": bids, + "asks": asks, + }, + timestamp=update_ts_ms * 1e-3, + ) + return snapshot_msg + + def _process_order_book_stream_event(self, message: StreamOrderbookV2Response): + snapshot_msg: OrderBookMessage = self._parse_derivative_ob_message(message=message) + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, message=snapshot_msg) + + async def _listen_to_order_books_stream(self): + while True: + market_ids = self._get_market_ids() + stream: UnaryStreamCall = await self._client.stream_derivative_orderbook_snapshot(market_ids=market_ids) + try: + async for ob_msg in stream: + self._process_order_book_stream_event(message=ob_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in orderbook listener loop.") + self.logger().info("Restarting order books stream.") + stream.cancel() + + def _parse_backend_trade( + self, client_order_id: str, backend_trade: DerivativeTrade + ) -> Tuple[OrderBookMessage, TradeUpdate]: + exchange_order_id: str = backend_trade.order_hash + market_id: str = backend_trade.market_id + market: DerivativeMarketInfo = self._market_id_to_active_perp_markets[market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + trade_id: str = backend_trade.trade_id + + price_scaler: Decimal = Decimal(f"1e-{market.quote_token_meta.decimals}") + price: Decimal = Decimal(backend_trade.position_delta.execution_price) * price_scaler + size: Decimal = Decimal(backend_trade.position_delta.execution_quantity) + is_taker: bool = backend_trade.execution_side == "taker" + + fee_amount: Decimal = Decimal(backend_trade.fee) * price_scaler + _, quote = split_hb_trading_pair(trading_pair=trading_pair) + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=TradeFeeSchema(), + position_action=PositionAction.OPEN, + flat_fees=[TokenAmount(amount=fee_amount, token=quote)], + ) + + trade_msg_content = { + "trade_id": trade_id, + "trading_pair": trading_pair, + "trade_type": TradeType.BUY if backend_trade.position_delta.trade_direction == "buy" else TradeType.SELL, + "amount": size, + "price": price, + "is_taker": is_taker, + } + trade_ob_msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + timestamp=backend_trade.executed_at * 1e-3, + content=trade_msg_content, + ) + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=backend_trade.executed_at * 1e-3, + fill_price=price, + fill_base_amount=size, + fill_quote_amount=price * size, + fee=fee, + ) + return trade_ob_msg, trade_update + + def _process_trade_stream_event(self, message: StreamTradesResponse): + trade_message: DerivativeTrade = message.trade + exchange_order_id = trade_message.order_hash + tracked_order = self._gateway_order_tracker.all_fillable_orders_by_exchange_order_id.get(exchange_order_id) + client_order_id = "" if tracked_order is None else tracked_order.client_order_id + trade_ob_msg, trade_update = self._parse_backend_trade( + client_order_id=client_order_id, backend_trade=trade_message + ) + + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, message=trade_ob_msg) + self._publisher.trigger_event(event_tag=MarketEvent.TradeUpdate, message=trade_update) + + async def _listen_to_trades_stream(self): + while True: + market_ids: List[str] = self._get_market_ids() + stream: UnaryStreamCall = await self._client.stream_derivative_trades(market_ids=market_ids) + try: + async for trade_msg in stream: + self._process_trade_stream_event(message=trade_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in public trade listener loop.") + self.logger().info("Restarting public trades stream.") + stream.cancel() + + async def _listen_to_bank_balances_streams(self): + while True: + stream: UnaryStreamCall = await self._client.stream_account_portfolio( + account_address=self._account_address, type="bank" + ) + try: + async for bank_balance in stream: + self._process_bank_balance_stream_event(message=bank_balance) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in account balance listener loop.") + self.logger().info("Restarting account balances stream.") + stream.cancel() + + def _process_bank_balance_stream_event(self, message: StreamAccountPortfolioResponse): + denom_meta = self._denom_to_token_meta[message.denom] + symbol = denom_meta.symbol + safe_ensure_future(self._issue_balance_update(token=symbol)) + + async def _listen_to_subaccount_balances_stream(self): + while True: + # Uses InjectiveAccountsRPC since it provides both total_balance and available_balance in a single stream. + stream: UnaryStreamCall = await self._client.stream_subaccount_balance(subaccount_id=self._account_id) + try: + async for balance_msg in stream: + self._process_subaccount_balance_stream_event(message=balance_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in account balance listener loop.") + self.logger().info("Restarting account balances stream.") + stream.cancel() + + def _process_subaccount_balance_stream_event(self, message: StreamSubaccountBalanceResponse): + denom_meta = self._denom_to_token_meta[message.balance.denom] + symbol = denom_meta.symbol + safe_ensure_future(self._issue_balance_update(token=symbol)) + + async def _issue_balance_update(self, token: str): + account_balances = await self.get_account_balances() + token_balances = account_balances.get(token, {}) + total_balance = token_balances.get("total_balance", Decimal("0")) + available_balance = token_balances.get("available_balance", Decimal("0")) + balance_msg = BalanceUpdateEvent( + timestamp=self._time(), + asset_name=token, + total_balance=total_balance, + available_balance=available_balance, + ) + self._publisher.trigger_event(event_tag=AccountEvent.BalanceEvent, message=balance_msg) + + @staticmethod + def _parse_order_update_from_order_history( + order: GatewayInFlightOrder, order_history: DerivativeOrderHistory, order_misc_updates: Dict[str, Any] + ) -> OrderUpdate: + order_update: OrderUpdate = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=order_history.updated_at * 1e-3, + new_state=CONSTANTS.INJ_DERIVATIVE_ORDER_STATES[order_history.state], + client_order_id=order.client_order_id, + exchange_order_id=order_history.order_hash, + misc_updates=order_misc_updates, + ) + return order_update + + @staticmethod + def _parse_failed_order_update_from_transaction_hash_response( + order: GatewayInFlightOrder, response: GetTxByTxHashResponse, order_misc_updates: Dict[str, Any] + ) -> Optional[OrderUpdate]: + tx_detail: TxDetailData = response.data + + status_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=tx_detail.block_unix_timestamp * 1e-3, + new_state=OrderState.FAILED, + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + misc_updates=order_misc_updates, + ) + return status_update + + @staticmethod + async def _check_if_order_failed_based_on_transaction( + transaction: GetTxByTxHashResponse, order: GatewayInFlightOrder + ) -> bool: + order_hash = await order.get_exchange_order_id() + return order_hash.lower() not in transaction.data.data.decode().lower() + + async def _process_transaction_event(self, transaction: StreamTxsResponse): + order: GatewayInFlightOrder = self._gateway_order_tracker.get_fillable_order_by_hash( + transaction_hash=transaction.hash + ) + if order is not None: + messages = json.loads(s=transaction.messages) + for message in messages: + if message["type"] in CONSTANTS.INJ_DERIVATIVE_TX_EVENT_TYPES: + self.logger().debug( + f"received transaction event of type {message['type']} for order {order.exchange_order_id}" + ) + self.logger().debug(f"message: {message}") + safe_ensure_future(coro=self.get_order_status_update(in_flight_order=order)) + + async def _listen_to_transactions_stream(self): + while True: + stream: UnaryStreamCall = await self._client.stream_txs() + try: + async for transaction in stream: + await self._process_transaction_event(transaction=transaction) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in transaction listener loop.") + self.logger().info("Restarting transactions stream.") + stream.cancel() + + async def _process_order_update_event(self, message: StreamOrdersHistoryResponse): + """ + Order History Stream example: + order { + order_hash: "0xfb526d72b85e9ffb4426c37bf332403fb6fb48709fb5d7ca3be7b8232cd10292" # noqa: documentation + market_id: "0x90e662193fa29a3a7e6c07be4407c94833e762d9ee82136a2cc712d6b87d7de3" # noqa: documentation + is_active: true + subaccount_id: "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell_po" + price: "274310000" + trigger_price: "0" + quantity: "144" + filled_quantity: "0" + state: "booked" + created_at: 1665487076373 + updated_at: 1665487076373 + direction: "sell" + margin: "3950170000" + } + operation_type: "insert" + timestamp: 1665487078000 + """ + order_update_msg: DerivativeOrderHistory = message.order + order_hash: str = order_update_msg.order_hash + + in_flight_order = self._gateway_order_tracker.all_fillable_orders_by_exchange_order_id.get(order_hash) + if in_flight_order is not None: + market_id = order_update_msg.market_id + trading_pair = self._get_trading_pair_from_market_id(market_id=market_id) + order_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=order_update_msg.updated_at * 1e-3, + new_state=CONSTANTS.INJ_DERIVATIVE_ORDER_STATES[order_update_msg.state], + client_order_id=in_flight_order.client_order_id, + exchange_order_id=order_update_msg.order_hash, + ) + if in_flight_order.current_state == OrderState.PENDING_CREATE and order_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=order_update_msg.updated_at * 1e-3, + new_state=OrderState.OPEN, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=order_update_msg.order_hash, + ) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=open_update) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=order_update) + + def _get_trading_pair_from_market_id(self, market_id: str) -> str: + market = self._market_id_to_active_perp_markets[market_id] + trading_pair = combine_to_hb_trading_pair(base=market.oracle_base, quote=market.oracle_quote) + return trading_pair + + async def _listen_order_updates_stream(self, market_id: str): + while True: + stream: UnaryStreamCall = await self._client.stream_historical_derivative_orders(market_id=market_id) + try: + async for order in stream: + await self._process_order_update_event(message=order) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user order listener loop.") + self.logger().info("Restarting user orders stream.") + stream.cancel() + + async def _process_position_event(self, message: StreamPositionsResponse): + """ + Position Stream example: + position { + ticker: "BTC/USDT PERP" + market_id: "0x90e662193fa29a3a7e6c07be4407c94833e762d9ee82136a2cc712d6b87d7de3" # noqa: documentation + subaccount_id: "0xea98e3aa091a6676194df40ac089e40ab4604bf9000000000000000000000000" # noqa: documentation + direction: "short" + quantity: "0.01" + entry_price: "18000000000" + margin: "186042357.839476" + liquidation_price: "34861176937.092952" + mark_price: "16835930000" + aggregate_reduce_only_quantity: "0" + updated_at: 1676412001911 + created_at: -62135596800000 + } + timestamp: 1652793296000 + """ + backend_position: DerivativePosition = message.position + position_update = self._parse_backend_position_to_position_event(backend_position=backend_position) + + self._publisher.trigger_event(event_tag=AccountEvent.PositionUpdate, message=position_update) + + def _parse_backed_position_to_position(self, backend_position: DerivativePosition) -> Position: + position_event = self._parse_backend_position_to_position_event(backend_position=backend_position) + position = Position( + trading_pair=position_event.trading_pair, + position_side=position_event.position_side, + unrealized_pnl=position_event.unrealized_pnl, + entry_price=position_event.entry_price, + amount=position_event.amount, + leverage=position_event.leverage, + ) + return position + + def _parse_backend_position_to_position_event(self, backend_position: DerivativePosition) -> PositionUpdateEvent: + market_info: DerivativeMarketInfo = self._market_id_to_active_perp_markets[backend_position.market_id] + trading_pair: str = combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote) + amount: Decimal = Decimal(backend_position.quantity) + if backend_position.direction != "": + position_side = PositionSide[backend_position.direction.upper()] + entry_price: Decimal = ( + Decimal(backend_position.entry_price) * Decimal(f"1e-{market_info.quote_token_meta.decimals}") + ) + mark_price: Decimal = ( + Decimal(backend_position.mark_price) * Decimal(f"1e-{market_info.oracle_scale_factor}") + ) + leverage = Decimal( + round( + Decimal(backend_position.entry_price) / ( + Decimal(backend_position.margin) / Decimal(backend_position.quantity) + ) + ) + ) + if backend_position.direction == "short": + amount = -amount # client expects short positions to be negative in size + unrealized_pnl: Decimal = amount * ((1 / entry_price) - (1 / mark_price)) + else: + position_side = None + entry_price = Decimal("0") + unrealized_pnl = Decimal("0") + leverage = Decimal("0") + + position = PositionUpdateEvent( + timestamp=backend_position.updated_at * 1e-3, + trading_pair=trading_pair, + position_side=position_side, + unrealized_pnl=unrealized_pnl, + entry_price=entry_price, + amount=amount, + leverage=leverage, + ) + + return position + + async def _listen_to_positions_stream(self): + while True: + market_ids = self._get_market_ids() + stream: UnaryStreamCall = await self._client.stream_derivative_positions( + market_ids=market_ids, subaccount_id=self._account_id + ) + try: + async for message in stream: + await self._process_position_event(message=message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in position listener loop.") + self.logger().info("Restarting position stream.") + stream.cancel() + + async def _process_funding_info_event(self, market_info: DerivativeMarketInfo, message: StreamPricesResponse): + trading_pair: str = combine_to_hb_trading_pair(base=market_info.oracle_base, quote=market_info.oracle_quote) + funding_info = await self._request_funding_info(trading_pair=trading_pair) + funding_info_event = FundingInfoUpdate( + trading_pair=trading_pair, + index_price=funding_info.index_price, + mark_price=funding_info.mark_price, + next_funding_utc_timestamp=funding_info.next_funding_utc_timestamp, + rate=funding_info.rate, + ) + self._publisher.trigger_event(event_tag=MarketEvent.FundingInfo, message=funding_info_event) + + async def _request_funding_info(self, trading_pair: str) -> FundingInfo: + # NOTE: Can be replaced with GatewayHttpClient.clob_perp_funding_info() + self._check_markets_initialized() or await self._update_markets() + market_info: DerivativeMarketInfo = self._markets_info[trading_pair] + last_funding_rate: Decimal = await self._request_last_funding_rate(trading_pair=trading_pair) + oracle_price: Decimal = await self._request_oracle_price(market_info=market_info) + last_trade_price: Decimal = await self._request_last_trade_price(trading_pair=trading_pair) + async with self._throttler.execute_task(limit_id=CONSTANTS.SINGLE_DERIVATIVE_MARKET_LIMIT_ID): + updated_market_info = await self._client.get_derivative_market(market_id=market_info.market_id) + funding_info = FundingInfo( + trading_pair=trading_pair, + index_price=last_trade_price, # Default to using last trade price + mark_price=oracle_price, + next_funding_utc_timestamp=( + updated_market_info.market.perpetual_market_info.next_funding_timestamp * 1e-3 + ), + rate=last_funding_rate, + ) + return funding_info + + async def _listen_to_funding_info_stream(self, market_id: str): + self._check_markets_initialized() or await self._update_markets() + while True: + market_info = self._market_id_to_active_perp_markets[market_id] + stream: UnaryStreamCall = await self._client.stream_oracle_prices( + base_symbol=market_info.oracle_base, + quote_symbol=market_info.oracle_quote, + oracle_type=market_info.oracle_type, + ) + try: + async for message in stream: + await self._process_funding_info_event(market_info=market_info, message=message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in position listener loop.") + self.logger().info("Restarting position stream.") + stream.cancel() + + async def _start_streams(self): + self._trades_stream_listener = self._trades_stream_listener or safe_ensure_future( + coro=self._listen_to_trades_stream() + ) + self._order_books_stream_listener = self._order_books_stream_listener or safe_ensure_future( + coro=self._listen_to_order_books_stream() + ) + if self._is_default_subaccount: + self._bank_balances_stream_listener = self._bank_balances_stream_listener or safe_ensure_future( + coro=self._listen_to_bank_balances_streams() + ) + self._subaccount_balances_stream_listener = self._subaccount_balances_stream_listener or safe_ensure_future( + coro=self._listen_to_subaccount_balances_stream() + ) + self._transactions_stream_listener = self._transactions_stream_listener or safe_ensure_future( + coro=self._listen_to_transactions_stream() + ) + self._positions_stream_listener = self._positions_stream_listener or safe_ensure_future( + coro=self._listen_to_positions_stream() + ) + for market_id in [self._markets_info[tp].market_id for tp in self._trading_pairs]: + if market_id not in self._order_listeners: + self._order_listeners[market_id] = safe_ensure_future( + coro=self._listen_order_updates_stream(market_id=market_id) + ) + if market_id not in self._funding_info_listeners: + self._funding_info_listeners[market_id] = safe_ensure_future( + coro=self._listen_to_funding_info_stream(market_id=market_id) + ) + + async def _stop_streams(self): + self._trades_stream_listener and self._trades_stream_listener.cancel() + self._trades_stream_listener = None + for listener in self._order_listeners.values(): + listener.cancel() + self._order_listeners = {} + for listener in self._funding_info_listeners.values(): + listener.cancel() + self._funding_info_listeners = {} + self._order_books_stream_listener and self._order_books_stream_listener.cancel() + self._order_books_stream_listener = None + self._subaccount_balances_stream_listener and self._subaccount_balances_stream_listener.cancel() + self._subaccount_balances_stream_listener = None + self._bank_balances_stream_listener and self._bank_balances_stream_listener.cancel() + self._bank_balances_stream_listener = None + self._transactions_stream_listener and self._transactions_stream_listener.cancel() + self._transactions_stream_listener = None + self._positions_stream_listener and self._positions_stream_listener.cancel() + self._positions_stream_listener = None + + def _get_exchange_trading_pair_from_market_info(self, market_info: Any) -> str: + return market_info.market_id + + def _get_maker_taker_exchange_fee_rates_from_market_info( + self, market_info: Any + ) -> MakerTakerExchangeFeeRates: + # Since we are using the API, we are the service provider. + # Reference: https://api.injective.exchange/#overview-trading-fees-and-gas + fee_scaler = Decimal("1") - Decimal(market_info.service_provider_fee) + maker_fee = Decimal(market_info.maker_fee_rate) * fee_scaler + taker_fee = Decimal(market_info.taker_fee_rate) * fee_scaler + maker_taker_exchange_fee_rates = MakerTakerExchangeFeeRates( + maker=maker_fee, taker=taker_fee, maker_flat_fees=[], taker_flat_fees=[] + ) + return maker_taker_exchange_fee_rates + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._client_config) + return gateway_instance + + def _get_market_ids(self) -> List[str]: + market_ids = [ + self._markets_info[trading_pair].market_id + for trading_pair in self._trading_pairs + ] + return market_ids + + @staticmethod + async def _sleep(delay: float): + await asyncio.sleep(delay) + + @staticmethod + def _time() -> float: + return time.time() diff --git a/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py new file mode 100644 index 0000000..33d7381 --- /dev/null +++ b/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_constants.py @@ -0,0 +1,252 @@ +import sys +from configparser import ConfigParser +from decimal import Decimal +from typing import Dict, Tuple + +from pyinjective.constant import ( + devnet_config as DEVNET_TOKEN_META_CONFIG, + mainnet_config as MAINNET_TOKEN_META_CONFIG, + testnet_config as TESTNET_TOKEN_META_CONFIG, +) +from pyinjective.core.network import Network + +from hummingbot.connector.constants import MINUTE, SECOND +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState + +CONNECTOR_NAME = "injective_perpetual" +LOST_ORDER_COUNT_LIMIT = 3 +ORDER_CHAIN_PROCESSING_TIMEOUT = 5 + +DEFAULT_SUB_ACCOUNT_SUFFIX = "000000000000000000000000" + +NETWORK_CONFIG = { + "mainnet": Network.mainnet(), + "testnet": Network.testnet(), + "devnet": Network.devnet() +} + +MARKETS_UPDATE_INTERVAL = 8 * 60 * 60 + +SUPPORTED_ORDER_TYPES = [OrderType.LIMIT, OrderType.LIMIT_MAKER] +SUPPORTED_POSITION_MODES = [PositionMode.ONEWAY] + +MSG_CREATE_DERIVATIVE_LIMIT_ORDER = "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder" +MSG_CANCEL_DERIVATIVE_ORDER = "/injective.exchange.v1beta1.MsgCancelDerivativeOrder" +MSG_BATCH_UPDATE_ORDERS = "/injective.exchange.v1beta1.MsgBatchUpdateOrders" + +INJ_DERIVATIVE_TX_EVENT_TYPES = [ + MSG_CREATE_DERIVATIVE_LIMIT_ORDER, + MSG_CANCEL_DERIVATIVE_ORDER, + MSG_BATCH_UPDATE_ORDERS, +] + +INJ_DERIVATIVE_ORDER_STATES = { + "booked": OrderState.OPEN, + "partial_filled": OrderState.PARTIALLY_FILLED, + "filled": OrderState.FILLED, + "canceled": OrderState.CANCELED, +} + +CLIENT_TO_BACKEND_ORDER_TYPES_MAP: Dict[Tuple[TradeType, OrderType], str] = { + (TradeType.BUY, OrderType.LIMIT): "buy", + (TradeType.BUY, OrderType.LIMIT_MAKER): "buy_po", + (TradeType.BUY, OrderType.MARKET): "take_buy", + (TradeType.SELL, OrderType.LIMIT): "sell", + (TradeType.SELL, OrderType.LIMIT_MAKER): "sell_po", + (TradeType.SELL, OrderType.MARKET): "take_sell", +} + +FETCH_ORDER_HISTORY_LIMIT = 100 + +BASE_GAS = Decimal("100e3") +GAS_BUFFER = Decimal("20e3") +DERIVATIVE_SUBMIT_ORDER_GAS = Decimal("45e3") +DERIVATIVE_CANCEL_ORDER_GAS = Decimal("25e3") + + +def _parse_network_config_to_denom_meta(config: ConfigParser): + """ + Parses token's denom configuration from Injective SDK. + i.e. + { + "inj": { + "symbol": "INJ", + "decimal": 18 + }, + "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7": { + "symbol": "USDT", + "decimal": 6 + }, + } + """ + return { + entry["peggy_denom"]: {"symbol": entry.name, "decimal": entry["decimals"]} + for entry in config.values() if "peggy_denom" in entry + } + + +NETWORK_DENOM_TOKEN_META = { + "mainnet": _parse_network_config_to_denom_meta(config=MAINNET_TOKEN_META_CONFIG), + "testnet": _parse_network_config_to_denom_meta(config=TESTNET_TOKEN_META_CONFIG), + "devnet": _parse_network_config_to_denom_meta(config=DEVNET_TOKEN_META_CONFIG) +} + +NO_LIMIT = sys.maxsize +CHAIN_RPC_LIMIT_ID = "ChainRPCLimitID" +CHAIN_RPC_LIMIT = 120 +INDEXER_RPC_LIMIT_ID = "IndexerRPCLimitID" +REST_LIMIT_ID = "RESTLimitID" +REST_LIMIT = 120 +TRANSACTION_POST_LIMIT_ID = "TransactionPostLimitID" +TRANSACTION_POST_LIMIT = REST_LIMIT +BALANCES_LIMIT_ID = "BalancesLimitID" +BALANCES_LIMIT = REST_LIMIT +NONCE_LIMIT_ID = "NonceLimitID" +NONCE_LIMIT = REST_LIMIT +PING_LIMIT_ID = "PingLimitID" +ORDER_BOOK_LIMIT_ID = "OrderBookLimitID" +POSITIONS_LIMIT_ID = "PositionsLimitID" +ACCOUNT_PORTFOLIO_LIMIT_ID = "AccountPortfolioLimitID" +FUNDING_PAYMENT_LIMIT_ID = "GetFundingPaymentLimitID" +ACCOUNT_LIMIT_ID = "AccountLimitID" +SYNC_TIMEOUT_HEIGHT_LIMIT_ID = "SyncTimeoutHeightLimitID" +SYNC_TIMEOUT_HEIGHT_LIMIT = CHAIN_RPC_LIMIT +DERIVATIVE_MARKETS_LIMIT_ID = "DerivativeMarketsLimitID" +SINGLE_DERIVATIVE_MARKET_LIMIT_ID = "SingleDerivativeMarketLimitID" +SPOT_MARKETS_LIMIT_ID = "SpotMarketsLimitID" +HISTORICAL_DERIVATIVE_ORDERS_LIMIT_ID = "HistoricalDerivativeOrdersLimitID" +DERIVATIVE_TRADES_LIMIT_ID = "DerivativeTradesLimitID" +TRANSACTION_BY_HASH_LIMIT_ID = "TransactionByHashLimitID" +FUNDING_RATES_LIMIT_ID = "FundingRatesLimitID" +ORACLE_PRICES_LIMIT_ID = "OraclePricesLimitID" + +RATE_LIMITS = [ + RateLimit(limit_id=CHAIN_RPC_LIMIT_ID, limit=CHAIN_RPC_LIMIT, time_interval=MINUTE), + RateLimit(limit_id=INDEXER_RPC_LIMIT_ID, limit=NO_LIMIT, time_interval=SECOND), + RateLimit(limit_id=REST_LIMIT_ID, limit=REST_LIMIT, time_interval=MINUTE), + RateLimit( + limit_id=TRANSACTION_POST_LIMIT_ID, + limit=TRANSACTION_POST_LIMIT, + time_interval=MINUTE, + linked_limits=[ + LinkedLimitWeightPair( + limit_id=REST_LIMIT_ID, # Gateway uses httpClient to post transactions + weight=1, + ), + ], + ), + RateLimit( + limit_id=BALANCES_LIMIT_ID, + limit=BALANCES_LIMIT, + time_interval=MINUTE, + linked_limits=[ + LinkedLimitWeightPair( + limit_id=REST_LIMIT_ID, # Gateway uses httpClient to post transactions + weight=1, + ), + ], + ), + RateLimit( + limit_id=NONCE_LIMIT_ID, + limit=NONCE_LIMIT, + time_interval=MINUTE, + linked_limits=[ + LinkedLimitWeightPair( + limit_id=REST_LIMIT_ID, # the OrderHashManager issues a REST call to get the account nonce + weight=1, + ), + ], + ), + RateLimit( + limit_id=PING_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=ORDER_BOOK_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=POSITIONS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=ACCOUNT_PORTFOLIO_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=FUNDING_PAYMENT_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=ACCOUNT_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=SYNC_TIMEOUT_HEIGHT_LIMIT_ID, + limit=SYNC_TIMEOUT_HEIGHT_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(limit_id=CHAIN_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=DERIVATIVE_MARKETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=SINGLE_DERIVATIVE_MARKET_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=SPOT_MARKETS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=HISTORICAL_DERIVATIVE_ORDERS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=DERIVATIVE_TRADES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=TRANSACTION_BY_HASH_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=FUNDING_RATES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=ORACLE_PRICES_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + linked_limits=[LinkedLimitWeightPair(limit_id=INDEXER_RPC_LIMIT_ID, weight=1)], + ), +] diff --git a/hummingbot/connector/gateway/clob_perp/gateway_clob_perp.py b/hummingbot/connector/gateway/clob_perp/gateway_clob_perp.py new file mode 100644 index 0000000..1d90f21 --- /dev/null +++ b/hummingbot/connector/gateway/clob_perp/gateway_clob_perp.py @@ -0,0 +1,539 @@ +from copy import deepcopy +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.constants import FUNDING_FEE_POLL_INTERVAL, s_decimal_NaN +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.gateway.clob_perp.data_sources.clob_perp_api_data_source_base import CLOBPerpAPIDataSourceBase +from hummingbot.connector.gateway.clob_perp.gateway_clob_perp_api_order_book_data_source import ( + GatewayCLOBPerpAPIOrderBookDataSource, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayPerpetualInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.funding_info import FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.data_type.trade_fee import ( + AddedToCostTradeFee, + DeductedFromReturnsTradeFee, + MakerTakerExchangeFeeRates, + TradeFeeBase, +) +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent, PositionUpdateEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.web_assistant.auth import AuthBase + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GatewayCLOBPerp(PerpetualDerivativePyBase): + def __init__( + self, + client_config_map: "ClientConfigAdapter", + api_data_source: CLOBPerpAPIDataSourceBase, + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ) -> None: + + self._name = "_".join([connector_name, chain, network]) + self._connector_name = connector_name + self._chain = chain + self._network = network + self._address = address + self._trading_pairs = trading_pairs or [] + self._trading_required = trading_required + self._api_data_source = api_data_source + + self._last_received_message_timestamp = 0 + self._forwarders: List[EventForwarder] = [] + + self._add_forwarders() + + super().__init__(client_config_map) + + # region >>> Objective Attributes >>> + + @property + def connector_name(self): + """ + This returns the name of connector/protocol to be connected to on Gateway. + """ + return self._connector_name + + @property + def chain(self): + return self._chain + + @property + def network(self): + return self._network + + @property + def name(self) -> str: + return self._name + + @property + def domain(self) -> str: + return "" + + @property + def authenticator(self) -> Optional[AuthBase]: + return None + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return [] + + @property + def address(self): + return self._address + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def client_order_id_max_length(self) -> Optional[int]: + return None + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def check_network_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_pairs_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_rules_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_fees(self) -> Mapping[str, MakerTakerExchangeFeeRates]: + return deepcopy(self._trading_fees) + + @property + def status_dict(self) -> Dict[str, bool]: + sd = super().status_dict + sd["api_data_source_initialized"] = self._api_data_source.ready + return sd + + @property + def funding_fee_poll_interval(self) -> int: + return FUNDING_FEE_POLL_INTERVAL + + @property + def supported_position_modes(self) -> List[PositionMode]: + return self._api_data_source.supported_position_modes + + # endregion + + def _add_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_trade_update) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_order_update) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_balance_event) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_funding_info_event) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=MarketEvent.FundingInfo, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_position_update_event) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=AccountEvent.PositionUpdate, listener=event_forwarder) + + def _create_order_book_data_source(self) -> PerpetualAPIOrderBookDataSource: + data_source = GatewayCLOBPerpAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, api_data_source=self._api_data_source + ) + return data_source + + def _create_order_tracker(self) -> ClientOrderTracker: + tracker = GatewayOrderTracker(connector=self, lost_order_count_limit=10) + self._api_data_source.gateway_order_tracker = tracker + return tracker + + def _create_user_stream_tracker_task(self): + return None + + def _create_user_stream_tracker(self): + return None + + def _create_user_stream_data_source(self): + return None + + async def _get_last_traded_price(self, trading_pair: str) -> float: + price = await self._api_data_source.get_last_traded_price(trading_pair=trading_pair) + return float(price) + + def _create_web_assistants_factory(self): + return None + + def _is_user_stream_initialized(self): + return self.trading_pair_symbol_map_ready() # if ready, then self._api_data_source is initialized + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + """ + Not used. + """ + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return self._api_data_source.is_order_not_found_during_status_update_error(status_update_exception) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return self._api_data_source.is_order_not_found_during_cancelation_error(cancelation_exception) + + async def start_network(self): + await self._api_data_source.start() + await super().start_network() + + async def stop_network(self): + await super().stop_network() + await self._api_data_source.stop() + + def supported_order_types(self) -> List[OrderType]: + return self._api_data_source.get_supported_order_types() + + def start_tracking_order( + self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ): + leverage = self.get_leverage(trading_pair=trading_pair) + self._order_tracker.start_tracking_order( + GatewayPerpetualInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp, + leverage=leverage, + position=position_action, + ) + ) + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = self._api_data_source.get_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = self._api_data_source.get_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def get_buy_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.buy_order_collateral_token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + trading_rule: TradingRule = self._trading_rules[trading_pair] + return trading_rule.sell_order_collateral_token + + async def _update_time_synchronizer(self, pass_on_non_cancelled_error: bool = False): + pass + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + self._set_trading_pair_symbol_map(exchange_info) + + async def _make_network_check_request(self): + network_status = await self._api_data_source.check_network_status() + if network_status != NetworkStatus.CONNECTED: + raise IOError("The API data source has lost connection.") + + async def _make_trading_rules_request(self) -> Mapping[str, str]: + return await self._make_trading_pairs_request() + + async def _make_trading_pairs_request(self) -> Mapping[str, str]: + symbol_map = await self._api_data_source.get_symbol_map() + return symbol_map + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + trading_rules = await self._api_data_source.get_trading_rules() + return list(trading_rules.values()) + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + """ + Not used. + """ + raise NotImplementedError + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + Not used. + """ + raise NotImplementedError + + async def _user_stream_event_listener(self): + """ + Not used. + """ + pass + + async def _update_positions(self): + positions: List[Position] = await self._api_data_source.fetch_positions() + for position in positions: + position_key = self._perpetual_trading.position_key(position.trading_pair, position.position_side) + if position.amount != Decimal("0"): + position._leverage = self._perpetual_trading.get_leverage(trading_pair=position.trading_pair) + self._perpetual_trading.set_position(pos_key=position_key, position=position) + else: + self._perpetual_trading.remove_position(post_key=position_key) + + async def _update_balances(self): + balances = await self._api_data_source.get_account_balances() + self._account_balances.clear() + self._account_available_balances.clear() + for asset, balance in balances.items(): + self._account_balances[asset] = Decimal(balance["total_balance"]) + self._account_available_balances[asset] = Decimal(balance["available_balance"]) + + async def _update_trading_fees(self): + self._trading_fees = await self._api_data_source.get_trading_fees() + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + status_update = await self._api_data_source.get_order_status_update(in_flight_order=tracked_order) + return status_update + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + return await self._api_data_source.get_all_order_fills(in_flight_order=order) + + async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]: + return await self._api_data_source.trading_pair_position_mode_set(mode=mode, trading_pair=trading_pair) + + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + return await self._api_data_source.set_trading_pair_leverage(trading_pair=trading_pair, leverage=leverage) + + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + return await self._api_data_source.fetch_last_fee_payment(trading_pair=trading_pair) + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = ..., + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data.maker) if is_maker else Decimal(fees_data.taker) + flat_fees = fees_data.maker_flat_fees if is_maker else fees_data.taker_flat_fees + if order_side == TradeType.BUY: + fee = AddedToCostTradeFee(percent=fee_value, percent_token=quote_currency, flat_fees=flat_fees) + else: + fee = DeductedFromReturnsTradeFee(percent=fee_value, percent_token=quote_currency, flat_fees=flat_fees) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + + return fee + + async def _place_order_and_process_update(self, order: GatewayPerpetualInFlightOrder, **kwargs) -> str: + order.leverage = self._perpetual_trading.get_leverage(trading_pair=order.trading_pair) + exchange_order_id, misc_order_updates = await self._api_data_source.place_order(order, **kwargs) + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.PENDING_CREATE, + misc_updates=misc_order_updates, + ) + self._order_tracker.process_order_update(order_update) + + return exchange_order_id + + async def _execute_order_cancel_and_process_update(self, order: GatewayPerpetualInFlightOrder) -> bool: + cancelled, misc_order_updates = await self._api_data_source.cancel_order(order=order) + + if cancelled: + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=( + OrderState.CANCELED if self.is_cancel_request_in_exchange_synchronous else OrderState.PENDING_CANCEL + ), + misc_updates=misc_order_updates, + ) + self._order_tracker.process_order_update(order_update) + + return cancelled + + def _process_trade_update(self, trade_update: TradeUpdate): + self._last_received_message_timestamp = self._time() + self._order_tracker.process_trade_update(trade_update) + + def _process_order_update(self, order_update: OrderUpdate): + self._last_received_message_timestamp = self._time() + self._order_tracker.process_order_update(order_update=order_update) + + def _process_balance_event(self, balance_event: BalanceUpdateEvent): + self._last_received_message_timestamp = self._time() + if balance_event.total_balance is not None: + self._account_balances[balance_event.asset_name] = balance_event.total_balance + if balance_event.available_balance is not None: + self._account_available_balances[balance_event.asset_name] = balance_event.available_balance + + def _process_position_update_event(self, position_update_event: PositionUpdateEvent): + self._last_received_message_timestamp = self._time() + position_key = self._perpetual_trading.position_key( + position_update_event.trading_pair, position_update_event.position_side + ) + if position_update_event.amount != Decimal("0"): + position: Position = self._perpetual_trading.get_position( + trading_pair=position_update_event.trading_pair, side=position_update_event.position_side + ) + if position is not None: + leverage: Decimal = ( + # If event leverage is set to -1, the initial leverage is used. This is for specific cases when the + # DataSource does not provide any leverage information in the position stream response + position.leverage if position_update_event.leverage == Decimal("-1") else position_update_event.leverage + ) + position.update_position( + position_side=position_update_event.position_side, + unrealized_pnl=position_update_event.unrealized_pnl, + entry_price=position_update_event.entry_price, + amount=position_update_event.amount, + leverage=leverage, + ) + else: + safe_ensure_future(coro=self._update_positions()) + else: + self._perpetual_trading.remove_position(post_key=position_key) + + def _process_funding_info_event(self, funding_info_event: FundingInfoUpdate): + self._last_received_message_timestamp = self._time() + self._perpetual_trading.funding_info_stream.put_nowait(funding_info_event) + + def _get_poll_interval(self, timestamp: float) -> float: + last_recv_diff = timestamp - self._last_received_message_timestamp + poll_interval = ( + self.SHORT_POLL_INTERVAL + if last_recv_diff > self.TICK_INTERVAL_LIMIT or not self._api_data_source.events_are_streamed + else self.LONG_POLL_INTERVAL + ) + return poll_interval diff --git a/hummingbot/connector/gateway/clob_perp/gateway_clob_perp_api_order_book_data_source.py b/hummingbot/connector/gateway/clob_perp/gateway_clob_perp_api_order_book_data_source.py new file mode 100644 index 0000000..3d3290e --- /dev/null +++ b/hummingbot/connector/gateway/clob_perp/gateway_clob_perp_api_order_book_data_source.py @@ -0,0 +1,89 @@ +import asyncio +from typing import Dict, List, Optional + +from hummingbot.connector.gateway.clob_perp.data_sources.clob_perp_api_data_source_base import CLOBPerpAPIDataSourceBase +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import MarketEvent, OrderBookDataSourceEvent + + +class GatewayCLOBPerpAPIOrderBookDataSource(PerpetualAPIOrderBookDataSource): + _logger = None + + def __init__(self, trading_pairs: List[str], api_data_source: CLOBPerpAPIDataSourceBase) -> None: + super().__init__(trading_pairs=trading_pairs) + self._api_data_source = api_data_source + + self._forwarders: List[EventForwarder] = [] + + self._add_forwarders() + + async def get_last_traded_prices( + self, + trading_pairs: List[str], + domain: Optional[str] = None, + ) -> Dict[str, float]: + last_traded_prices = { + trading_pair: float(await self._api_data_source.get_last_traded_price(trading_pair=trading_pair)) + for trading_pair in trading_pairs + } + return last_traded_prices + + async def listen_for_subscriptions(self): + """Not used.""" + pass + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """Not used.""" + pass + + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + return await self._api_data_source.get_funding_info(trading_pair=trading_pair) + + def _add_forwarders(self): + event_forwarder = EventForwarder(to_function=self._message_queue[self._snapshot_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._message_queue[self._diff_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.DIFF_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._message_queue[self._trade_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder( + to_function=self._message_queue[self._funding_info_messages_queue_key].put_nowait + ) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener( + event_tag=MarketEvent.FundingInfo, listener=event_forwarder + ) + + async def _parse_trade_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + """Injective fires two trade updates per transaction. + + Generally, CEXes publish trade updates from the perspective of the taker, so we ignore makers. + """ + if raw_message.content["is_taker"]: + message_queue.put_nowait(raw_message) + + async def _parse_order_book_diff_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + message_queue.put_nowait(raw_message) + + async def _parse_order_book_snapshot_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + message_queue.put_nowait(raw_message) + + async def _parse_funding_info_message(self, raw_message: FundingInfoUpdate, message_queue: asyncio.Queue): + message_queue.put_nowait(raw_message) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + """ + Fetches order book snapshot for specified trading pair. + Used by APIOrderBookDataSource + """ + snapshot = await self._api_data_source.get_order_book_snapshot(trading_pair=trading_pair) + return snapshot diff --git a/hummingbot/connector/gateway/clob_spot/__init__.py b/hummingbot/connector/gateway/clob_spot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/__init__.py b/hummingbot/connector/gateway/clob_spot/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/clob_api_data_source_base.py b/hummingbot/connector/gateway/clob_spot/data_sources/clob_api_data_source_base.py new file mode 100644 index 0000000..6106908 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/clob_api_data_source_base.py @@ -0,0 +1,230 @@ +import logging +from abc import ABC, abstractmethod +from decimal import Decimal +from enum import Enum +from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple + +from bidict import bidict + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.pubsub import HummingbotLogger, PubSub + + +class CLOBAPIDataSourceBase(ABC): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + self._trading_pairs = trading_pairs + self._connector_spec = connector_spec + self._client_config = client_config_map + self._publisher = PubSub() + self._forwarders_map: Dict[Tuple[Enum, Callable], EventForwarder] = {} + self._gateway_order_tracker: Optional[GatewayOrderTracker] = None + self._markets_info: Dict[str, Any] = {} + self.cancel_all_orders_timeout = None + + @property + @abstractmethod + def real_time_balance_update(self) -> bool: + ... + + @property + @abstractmethod + def events_are_streamed(self) -> bool: + """Set this to False if the exchange does not offer event streams.""" + ... + + @staticmethod + @abstractmethod + def supported_stream_events() -> List[Enum]: + """This method serves as a guide to what events a client of this class expects an implementation to + provide. + """ + ... + + @abstractmethod + def get_supported_order_types(self) -> List[OrderType]: + ... + + @abstractmethod + async def start(self): + ... + + @abstractmethod + async def stop(self): + ... + + @abstractmethod + async def place_order( + self, order: GatewayInFlightOrder, **kwargs + ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + """ + :return: A tuple of the exchange order ID and any misc order updates. + """ + ... + + @abstractmethod + async def batch_order_create(self, orders_to_create: List[InFlightOrder]) -> List[PlaceOrderResult]: + """ + :param orders_to_create: The collection of orders to create. + :return: The result of the batch order create attempt. + """ + ... + + @abstractmethod + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Optional[Dict[str, Any]]]: + """ + :return: A tuple of the boolean indicating the cancelation success and any misc order updates. + """ + ... + + @abstractmethod + async def batch_order_cancel(self, orders_to_cancel: List[InFlightOrder]) -> List[CancelOrderResult]: + """ + :param orders_to_cancel: The collection of orders to cancel. + :return: The result of the batch order cancel attempt. + """ + ... + + @abstractmethod + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + ... + + @abstractmethod + async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + ... + + @abstractmethod + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + """Returns a dictionary like + + { + asset_name: { + "total_balance": Decimal, + "available_balance": Decimal, + } + } + """ + ... + + @abstractmethod + async def get_order_status_update(self, in_flight_order: InFlightOrder) -> OrderUpdate: + ... + + @abstractmethod + async def get_all_order_fills(self, in_flight_order: InFlightOrder) -> List[TradeUpdate]: + ... + + @abstractmethod + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + ... + + @abstractmethod + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + ... + + @abstractmethod + async def check_network_status(self) -> NetworkStatus: + ... + + @abstractmethod + def _check_markets_initialized(self) -> bool: + ... + + @abstractmethod + async def _update_markets(self): + ... + + @abstractmethod + def _parse_trading_rule(self, trading_pair: str, market_info: Any) -> TradingRule: + ... + + @abstractmethod + def _get_exchange_trading_pair_from_market_info(self, market_info: Any) -> str: + ... + + @abstractmethod + def _get_maker_taker_exchange_fee_rates_from_market_info(self, market_info: Any) -> MakerTakerExchangeFeeRates: + ... + + @property + def gateway_order_tracker(self): + return self._gateway_order_tracker + + @property + def ready(self) -> bool: + return self._check_markets_initialized() + + @gateway_order_tracker.setter + def gateway_order_tracker(self, tracker: GatewayOrderTracker): + if self._gateway_order_tracker is not None: + raise RuntimeError("Attempted to re-assign the order tracker.") + self._gateway_order_tracker = tracker + + @staticmethod + def get_client_order_id( + is_buy: bool, trading_pair: str, hbot_order_id_prefix: str, max_id_len: Optional[int] + ) -> str: + return get_new_client_order_id(is_buy, trading_pair, hbot_order_id_prefix, max_id_len) + + async def get_trading_rules(self) -> Dict[str, TradingRule]: + self._check_markets_initialized() or await self._update_markets() + + trading_rules = { + trading_pair: self._parse_trading_rule(trading_pair=trading_pair, market_info=market) + for trading_pair, market in self._markets_info.items() + } + return trading_rules + + async def get_symbol_map(self) -> bidict[str, str]: + self._check_markets_initialized() or await self._update_markets() + + mapping = bidict() + for trading_pair, market_info in self._markets_info.items(): + exchange_symbol = self._get_exchange_trading_pair_from_market_info(market_info=market_info) + mapping[exchange_symbol] = trading_pair + + return mapping + + async def get_trading_fees(self) -> Mapping[str, MakerTakerExchangeFeeRates]: + self._check_markets_initialized() or await self._update_markets() + + trading_fees = {} + for trading_pair, market_inf in self._markets_info.items(): + trading_fees[trading_pair] = self._get_maker_taker_exchange_fee_rates_from_market_info( + market_info=market_inf + ) + return trading_fees + + def add_listener(self, event_tag: Enum, listener: EventListener): + self._publisher.add_listener(event_tag=event_tag, listener=listener) + + def remove_listener(self, event_tag: Enum, listener: EventListener): + self._publisher.remove_listener(event_tag=event_tag, listener=listener) + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return False diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/__init__.py b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_api_data_source.py b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_api_data_source.py new file mode 100644 index 0000000..82926bb --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_api_data_source.py @@ -0,0 +1,680 @@ +import asyncio +from collections import defaultdict +from decimal import Decimal +from itertools import chain +from typing import Any, Dict, List, Optional, Tuple, Union + +import pandas as pd + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot import dexalot_constants as CONSTANTS +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_auth import DexalotAuth, WalletSigner +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_constants import ( + CONNECTOR_NAME, + DEXALOT_TO_HB_NUMERIC_STATUS_MAP, + DEXALOT_TO_HB_STATUS_MAP, + ORDER_SIDE_MAP, +) +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_web_utils import build_api_factory +from hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base import ( + GatewayCLOBAPIDataSourceBase, +) +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_numeric_client_order_id +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates, TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.events import MarketEvent, OrderBookDataSourceEvent +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class DexalotAPIDataSource(GatewayCLOBAPIDataSourceBase): + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + super().__init__( + trading_pairs=trading_pairs, connector_spec=connector_spec, client_config_map=client_config_map + ) + self._api_key = connector_spec["additional_prompt_values"]["api_key"] + self._api_factory: Optional[WebAssistantsFactory] = None + self._stream_listener: Optional[asyncio.Task] = None + self._client_order_id_nonce_provider = NonceCreator.for_microseconds() + self._last_traded_price_map = defaultdict(lambda: Decimal("0")) + self._snapshots_id_nonce_provider = NonceCreator.for_milliseconds() + + @property + def connector_name(self) -> str: + return CONNECTOR_NAME + + @property + def events_are_streamed(self) -> bool: + return False + + async def start(self): + signer = WalletSigner( + chain=self._chain, + network=self._network, + address=self._account_id, + gateway_instance=self._get_gateway_instance(), + ) + auth = DexalotAuth(signer=signer, address=self._account_id) + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self._api_factory = build_api_factory(throttler=throttler, api_key=self._api_key, auth=auth) + self._stream_listener = safe_ensure_future(self._listen_to_streams()) + self._gateway_order_tracker.lost_order_count_limit = CONSTANTS.LOST_ORDER_COUNT_LIMIT + await super().start() + + async def stop(self): + await super().stop() + self._stream_listener is not None and self._stream_listener.cancel() + + def get_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + async def place_order( + self, order: GatewayInFlightOrder, **kwargs + ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + place_order_results = await super().batch_order_create(orders_to_create=[order]) + result = place_order_results[0] + if result.exception is not None: + raise result.exception + self.logger().debug( + f"Order creation transaction hash for {order.client_order_id}:" + f" {result.misc_updates['creation_transaction_hash']}" + ) + return result.exchange_order_id, result.misc_updates + + async def batch_order_create(self, orders_to_create: List[GatewayInFlightOrder]) -> List[PlaceOrderResult]: + super_batch_order_create = super().batch_order_create # https://stackoverflow.com/a/31895448/6793798 + tasks = [ + super_batch_order_create( + orders_to_create=orders_to_create[i: i + CONSTANTS.MAX_ORDER_CREATIONS_PER_BATCH] + ) + for i in range(0, len(orders_to_create), CONSTANTS.MAX_ORDER_CREATIONS_PER_BATCH) + ] + results = await safe_gather(*tasks) + flattened_results = list(chain(*results)) + self.logger().debug( + f"Order creation transaction hashes for {', '.join([o.client_order_id for o in orders_to_create])}" + ) + for result in flattened_results: + self.logger().debug(f"Transaction hash: {result.misc_updates['creation_transaction_hash']}") + return flattened_results + + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Optional[Dict[str, Any]]]: + cancel_order_results = await super().batch_order_cancel(orders_to_cancel=[order]) + self.logger().debug( + f"cancel order transaction hash for {order.client_order_id}:" + f" {cancel_order_results[0].misc_updates['cancelation_transaction_hash']}" + ) + misc_updates = {} + canceled = False + if len(cancel_order_results) != 0: + result = cancel_order_results[0] + if result.exception is not None: + raise result.exception + misc_updates = result.misc_updates + canceled = True + return canceled, misc_updates + + async def batch_order_cancel(self, orders_to_cancel: List[GatewayInFlightOrder]) -> List[CancelOrderResult]: + super_batch_order_cancel = super().batch_order_cancel # https://stackoverflow.com/a/31895448/6793798 + tasks = [ + super_batch_order_cancel( + orders_to_cancel=orders_to_cancel[i: i + CONSTANTS.MAX_ORDER_CANCELATIONS_PER_BATCH] + ) + for i in range(0, len(orders_to_cancel), CONSTANTS.MAX_ORDER_CANCELATIONS_PER_BATCH) + ] + results = await safe_gather(*tasks) + flattened_results = list(chain(*results)) + self.logger().debug( + f"Order cancelation transaction hashes for {', '.join([o.client_order_id for o in orders_to_cancel])}" + ) + for result in flattened_results: + self.logger().debug(f"Transaction hash: {result.misc_updates['cancelation_transaction_hash']}") + return flattened_results + + def get_client_order_id( + self, is_buy: bool, trading_pair: str, hbot_order_id_prefix: str, max_id_len: Optional[int] + ) -> str: + decimal_id = get_new_numeric_client_order_id( + nonce_creator=self._client_order_id_nonce_provider, + max_id_bit_count=CONSTANTS.MAX_ID_BIT_COUNT, + ) + return "{0:#0{1}x}".format( # https://stackoverflow.com/a/12638477/6793798 + decimal_id, CONSTANTS.MAX_ID_HEX_DIGITS + 2 + ) + + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + self._check_markets_initialized() or await self._update_markets() + + result = await self._get_gateway_instance().get_balances( + chain=self.chain, + network=self._network, + address=self._account_id, + token_symbols=list(self._hb_to_exchange_tokens_map.values()), + connector=self.connector_name, + ) + balances = defaultdict(dict) + for exchange_token, total_balance in result["balances"]["total"].items(): + token = self._hb_to_exchange_tokens_map.inverse[exchange_token] + balance_value = Decimal(total_balance) + if balance_value != 0: + balances[token]["total_balance"] = balance_value + for exchange_token, available_balance in result["balances"]["available"].items(): + token = self._hb_to_exchange_tokens_map.inverse[exchange_token] + if token in balances: + balances[token]["available_balance"] = Decimal(available_balance) + return balances + + async def get_order_status_update(self, in_flight_order: GatewayInFlightOrder) -> OrderUpdate: + await in_flight_order.get_creation_transaction_hash() + status_update = None + + if in_flight_order.exchange_order_id is None: + status_update = await self._get_order_status_update_from_transaction_status(in_flight_order=in_flight_order) + if status_update is not None: + in_flight_order.exchange_order_id = status_update.exchange_order_id + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=status_update) + + if ( + in_flight_order.exchange_order_id is not None + or ( + status_update is not None + and status_update.new_state not in [OrderState.FAILED, OrderState.PENDING_CREATE] + ) + ): + status_update = await self._get_order_status_update_with_order_id(in_flight_order=in_flight_order) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=status_update) + + if status_update is None: + raise ValueError(f"No update found for order {in_flight_order.client_order_id}.") + + return status_update + + async def get_all_order_fills(self, in_flight_order: GatewayInFlightOrder) -> List[TradeUpdate]: + self._check_markets_initialized() or await self._update_markets() + + if in_flight_order.exchange_order_id is None: # we still haven't received an order status update + await self.get_order_status_update(in_flight_order=in_flight_order) + + request_params = { + "orderid": await in_flight_order.get_exchange_order_id(), + } + + resp = await self._api_get( + path_url=CONSTANTS.EXECUTIONS_PATH, + throttler_limit_id=CONSTANTS.EXECUTIONS_RATE_LIMIT_ID, + params=request_params, + is_auth_required=True, + ) + + trade_updates = [] + for fill_data in resp: + fill_price = Decimal(fill_data["price"]) + fill_size = Decimal(fill_data["quantity"]) + fee_token = self._hb_to_exchange_tokens_map.inverse[fill_data["feeunit"]] + fee = TradeFeeBase.new_spot_fee( + fee_schema=TradeFeeSchema(), + trade_type=ORDER_SIDE_MAP[fill_data["side"]], + flat_fees=[TokenAmount(token=fee_token, amount=Decimal(fill_data["fee"]))] + ) + trade_update = TradeUpdate( + trade_id=fill_data["execid"], + client_order_id=in_flight_order.client_order_id, + exchange_order_id=fill_data["orderid"], + trading_pair=in_flight_order.trading_pair, + fill_timestamp=self._dexalot_timestamp_to_timestamp(period_str=fill_data["ts"]), + fill_price=fill_price, + fill_base_amount=fill_size, + fill_quote_amount=fill_price * fill_size, + fee=fee, + is_taker=fill_data["type"] == "T", + ) + trade_updates.append(trade_update) + + return trade_updates + + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + return self._last_traded_price_map[trading_pair] + + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return ( + str(status_update_exception).endswith("not found") # transaction not found + or str(status_update_exception).startswith("No update found for order") # status update not found + ) + + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return False + + async def _update_snapshots_loop(self): + pass # Dexalot streams the snapshots via websocket + + async def _get_order_status_update_from_transaction_status( + self, in_flight_order: GatewayInFlightOrder + ) -> Optional[OrderUpdate]: + """ + Transaction not yet listed + { + "network": "dexalot", + "currentBlock": 1005813, + "timestamp": 1678431621633, + "txHash": "0xadaef9c4540192e45c991ffe6f12cc86be9c07b80b43487e5778d95c964405c7", # noqa: documentation + "txBlock": -1, + "txStatus": -1, + "txData": null, + "txReceipt": null + } + + ====================== + + Transaction failed (abbreviated) + { + "timestamp": 1678431839032, + "txHash": "0x34494d6c88e36415bbf3d1f44e648ed11bd952380743395f950c242cded147d1", # noqa: documentation + "txStatus": 1, + "txData": { + ... + }, + "txReceipt": { + "status": 0, + } + } + + ====================== + + Transaction success (abbreviated) + { + "txStatus": 1, + "timestamp": 1678440295386, + "txHash": "0x6aca797aa30636c3285c5ecaee76fe85a9ee2ac60d74af64b8d6660a6f4f0f27", # noqa: documentation + "txData": { + ... + }, + "txReceipt": { + "transactionHash": "...", + "logs": [ + { # SUCCESS + "name": "OrderStatusChanged", + "events": [ + { ... }, + { ... }, + { ... }, + { + "name": "orderId", + "type": "bytes32", + "value": "0x0000000000000000000000000000000000000000000000000000000063d84143" # noqa: documentation + }, + { + "name": "clientOrderId", + "type": "bytes32", + "value": "0xb71309b1a16b7903ffc817f256d1f4d42602d402bd81001feb29fd067e69013b" # noqa: documentation + }, + { + "name": "price", + "type": "uint256", + "value": "15000000" + }, + { ... }, + { + "name": "quantity", + "type": "uint256", + "value": "500000000000000000" + }, + { ... }, + { ... }, + { ... }, + { + "name": "status", + "type": "uint8", + "value": "0" + }, + { ... }, + { ... }, + { ... }, + ], + "address": "0x09383137C1eEe3E1A8bc781228E4199f6b4A9bbf" + }, + { # FAILED + "name": "OrderStatusChanged", + "events": [ + { ... }, + { ... }, + { ... }, + { + "name": "orderId", + "type": "bytes32", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: documentation + }, + { + "name": "clientOrderId", + "type": "bytes32", + "value": "0x936f0db5ba15ceaf9d6ee8fb6c1aeca01e67dfb3d061680a4ac3ce8ccb153072" # noqa: documentation + }, + { + "name": "price", + "type": "uint256", + "value": "15000000" + }, + { ... }, + { + "name": "quantity", + "type": "uint256", + "value": "0" + }, + { ... }, + { ... }, + { ... }, + { + "name": "status", + "type": "uint8", + "value": "1" + }, + { ... }, + { ... }, + { ... }, + ], + }, + ], + "status": 1, + }, + } + """ + transaction_data = await self._get_gateway_instance().get_transaction_status( + chain=self._chain, + network=self._network, + transaction_hash=in_flight_order.creation_transaction_hash, + connector="dexalot", + ) + if ( + transaction_data is not None + and transaction_data["txStatus"] == 1 + and transaction_data.get("txReceipt", {}).get("status") == 1 + ): + order_data = self._find_order_data_from_transaction_data( + transaction_data=transaction_data, in_flight_order=in_flight_order + ) + new_dexalot_numeric_state_event = next(filter(lambda event: event["name"] == "status", order_data)) + new_dexalot_numeric_state = new_dexalot_numeric_state_event["value"] + exchange_order_id_event = next(filter(lambda event: event["name"] == "orderId", order_data)) + exchange_order_id = exchange_order_id_event["value"] + timestamp = transaction_data["timestamp"] * 1e-3 + order_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=timestamp, + new_state=DEXALOT_TO_HB_NUMERIC_STATUS_MAP[int(new_dexalot_numeric_state)], + client_order_id=in_flight_order.client_order_id, + exchange_order_id=exchange_order_id, + ) + elif ( + transaction_data is not None + and ( + transaction_data["txStatus"] == -1 + or transaction_data.get("txReceipt", {}).get("status") == 0 + ) + ): + order_update = None # transaction data not found + else: # transaction is still being processed + order_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=self._time(), + new_state=OrderState.PENDING_CREATE, + client_order_id=in_flight_order.client_order_id, + ) + + return order_update + + @staticmethod + def _find_order_data_from_transaction_data( + transaction_data: Dict[str, Any], in_flight_order: GatewayInFlightOrder + ) -> List[Dict[str, str]]: + log_items = transaction_data["txReceipt"]["logs"] + + def log_items_filter(log_event_: Dict[str, Any]) -> bool: + target_hit = False + if log_event_["name"] == "OrderStatusChanged": + client_order_id_event = next( + filter( + lambda event: event["name"] == "clientOrderId", + log_event_["events"], + ), + ) + client_order_id = client_order_id_event["value"] + if client_order_id == in_flight_order.client_order_id: + target_hit = True + return target_hit + + log_item = next(filter(log_items_filter, log_items)) + log_event = log_item["events"] + + return log_event + + async def _get_order_status_update_with_order_id(self, in_flight_order: InFlightOrder) -> Optional[OrderUpdate]: + url = f"{CONSTANTS.ORDERS_PATH}/{in_flight_order.exchange_order_id}" + + try: + resp = await self._api_get( + path_url=url, throttler_limit_id=CONSTANTS.ORDERS_RATE_LIMIT_ID, is_auth_required=True + ) + except OSError as e: + if "HTTP status is 404" in str(e): + raise ValueError(f"No update found for order {in_flight_order.exchange_order_id}.") + raise e + + if resp.get("message") == "": + raise ValueError(f"No update found for order {in_flight_order.exchange_order_id}.") + else: + status_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=pd.Timestamp(resp["updateTs"]).timestamp(), + new_state=DEXALOT_TO_HB_STATUS_MAP[resp["status"]], + client_order_id=in_flight_order.client_order_id, + exchange_order_id=resp["id"], + ) + + return status_update + + def _parse_trading_rule(self, trading_pair: str, market_info: Dict[str, Any]) -> TradingRule: + base = market_info["baseSymbol"].upper() + quote = market_info["quoteSymbol"].upper() + quote_scaler = Decimal(f"1e-{market_info['quoteDecimals']}") + return TradingRule( + trading_pair=combine_to_hb_trading_pair(base=base, quote=quote), + min_order_size=Decimal(f"1e-{market_info['baseDisplayDecimals']}"), + min_price_increment=Decimal(f"1e-{market_info['quoteDisplayDecimals']}"), + min_quote_amount_increment=Decimal(f"1e-{market_info['quoteDisplayDecimals']}"), + min_base_amount_increment=Decimal(f"1e-{market_info['baseDisplayDecimals']}"), + min_notional_size=Decimal(market_info["minTradeAmount"]) * quote_scaler, + min_order_value=Decimal(market_info["minTradeAmount"]) * quote_scaler, + ) + + def _get_trading_pair_from_market_info(self, market_info: Dict[str, Any]) -> str: + base = market_info["baseSymbol"].upper() + quote = market_info["quoteSymbol"].upper() + trading_pair = combine_to_hb_trading_pair(base=base, quote=quote) + return trading_pair + + def _get_exchange_base_quote_tokens_from_market_info(self, market_info: Dict[str, Any]) -> Tuple[str, str]: + base = market_info["baseSymbol"] + quote = market_info["quoteSymbol"] + return base, quote + + def _get_exchange_trading_pair_from_market_info(self, market_info: Dict[str, Any]) -> str: + exchange_trading_pair = f"{market_info['baseSymbol']}/{market_info['quoteSymbol']}" + return exchange_trading_pair + + def _get_last_trade_price_from_ticker_data(self, ticker_data: List[Dict[str, Any]]) -> Decimal: + raise NotImplementedError + + def _get_maker_taker_exchange_fee_rates_from_market_info( + self, market_info: Dict[str, Any] + ) -> MakerTakerExchangeFeeRates: + maker_taker_exchange_fee_rates = MakerTakerExchangeFeeRates( + maker=Decimal(str(market_info["makerRate"])), + taker=Decimal(str(market_info["takerRate"])), + maker_flat_fees=[], + taker_flat_fees=[], + ) + return maker_taker_exchange_fee_rates + + @staticmethod + def _timestamp_to_dexalot_timestamp(timestamp: float) -> str: + dt_str = str(pd.Timestamp.utcfromtimestamp(ts=timestamp).tz_localize("UTC")) + return dt_str + + @staticmethod + def _dexalot_timestamp_to_timestamp(period_str: str) -> float: + ts = pd.Timestamp(period_str).timestamp() + return ts + + # REST methods + + async def _api_get(self, *args, **kwargs) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + kwargs["method"] = RESTMethod.GET + return await self._api_request(*args, **kwargs) + + async def _api_post(self, *args, **kwargs) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + kwargs["method"] = RESTMethod.POST + return await self._api_request(*args, **kwargs) + + async def _api_put(self, *args, **kwargs) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + kwargs["method"] = RESTMethod.PUT + return await self._api_request(*args, **kwargs) + + async def _api_delete(self, *args, **kwargs) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + kwargs["method"] = RESTMethod.DELETE + return await self._api_request(*args, **kwargs) + + async def _api_request( + self, + path_url, + throttler_limit_id: str, + is_auth_required: bool = False, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + ) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + rest_assistant = await self._api_factory.get_rest_assistant() + url = CONSTANTS.BASE_PATH_URL[self._network] + path_url + + request_result = await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + throttler_limit_id=throttler_limit_id, + ) + + return request_result + + async def _listen_to_streams(self): + while True: + try: + ws_assistant = await self._connected_websocket_assistant() + await self._subscribe_to_pairs(ws_assistant) + await self._process_websocket_messages(ws_assistant) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error while listening to user stream. Reconnecting...") + await self._sleep(1.0) + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws_url = CONSTANTS.WS_PATH_URL[self._network] + if self._api_key is not None and len(self._api_key) > 0: + auth_response = await self._api_get( + path_url=CONSTANTS.WS_AUTH_PATH, + throttler_limit_id=CONSTANTS.WS_AUTH_RATE_LIMIT_ID, + is_auth_required=True, + ) + token = auth_response["token"] + ws_url = f"{CONSTANTS.WS_PATH_URL[self._network]}?wstoken={token}" + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=ws_url, ping_timeout=CONSTANTS.HEARTBEAT_TIME_INTERVAL) + return ws + + async def _subscribe_to_pairs(self, ws_assistant: WSAssistant): + trading_pairs_map = await self.get_symbol_map() + for trading_pair in self._trading_pairs: + exchange_trading_pair = trading_pairs_map.inverse[trading_pair] + market_info = self._markets_info[trading_pair] + pair_sub_request = { + "data": exchange_trading_pair, + "pair": exchange_trading_pair, + "type": "subscribe", + "decimal": market_info["quoteDisplayDecimals"], + } + request = WSJSONRequest(payload=pair_sub_request, throttler_limit_id=CONSTANTS.WS_SUB_RATE_LIMIT_ID) + await ws_assistant.send(request) + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + await self._process_event_message(event_message=data) + + async def _process_event_message(self, event_message: Dict[str, Any]): + if event_message["type"] == "lastTrade": + await self._process_last_traded_price(price_message=event_message) + elif event_message["type"] == "orderBooks": + await self._process_snapshot_message(snapshot_message=event_message) + + async def _process_last_traded_price(self, price_message: Dict[str, Any]): + data = price_message["data"] + last_price_message = data[-1] + price = Decimal(last_price_message["price"]) + trading_pairs_map = await self.get_symbol_map() + exchange_pair = price_message["pair"] + trading_pair = trading_pairs_map[exchange_pair] + self._last_traded_price_map[trading_pair] = price + + async def _process_snapshot_message(self, snapshot_message: Dict[str, Any]): + data = snapshot_message["data"] + trading_pairs_map = await self.get_symbol_map() + exchange_trading_pair = snapshot_message["pair"] + trading_pair = trading_pairs_map[exchange_trading_pair] + market_info = self._markets_info[trading_pair] + price_scaler = Decimal(f"1e-{market_info['quoteDecimals']}") + size_scaler = Decimal(f"1e-{market_info['baseDecimals']}") + + bid_price_strings = data["buyBook"][0]["prices"].split(",") + bid_size_strings = data["buyBook"][0]["quantities"].split(",") + bids = [ + (Decimal(price) * price_scaler, Decimal(size) * size_scaler) + for price, size in zip(bid_price_strings, bid_size_strings) + ] + + ask_price_strings = data["sellBook"][0]["prices"].split(",") + ask_size_strings = data["sellBook"][0]["quantities"].split(",") + asks = [ + (Decimal(price) * price_scaler, Decimal(size) * size_scaler) + for price, size in zip(ask_price_strings, ask_size_strings) + ] + + timestamp = self._time() + update_id = self._snapshots_id_nonce_provider.get_tracking_nonce(timestamp=timestamp) + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + }, + timestamp=timestamp, + ) + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, message=snapshot_msg) diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_auth.py b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_auth.py new file mode 100644 index 0000000..2de27e7 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_auth.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class SignerBase(ABC): + @abstractmethod + async def sign_message(self, message: str) -> str: + ... + + +class WalletSigner(SignerBase): + def __init__( + self, + chain: str, + network: str, + address: str, + gateway_instance: GatewayHttpClient, + ): + self._chain = chain + self._network = network + self._address = address + self._gateway_instance = gateway_instance + + async def sign_message(self, message: str) -> str: + resp = await self._gateway_instance.wallet_sign( + chain=self._chain, + network=self._network, + address=self._address, + message=message, + ) + return resp["signature"] + + +class DexalotAuth(AuthBase): + def __init__(self, signer: SignerBase, address: str): + self._signer = signer + self._signature: Optional[str] = None + self._address = address + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + signature = await self._get_signature() + request.headers = request.headers or {} + request.headers["x-signature"] = f"{self._address}:{signature}" + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + raise NotImplementedError # currently unused + + async def _get_signature(self) -> str: + if self._signature is None: + self._signature = await self._signer.sign_message("dexalot") + return self._signature diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_constants.py b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_constants.py new file mode 100644 index 0000000..19b6337 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_constants.py @@ -0,0 +1,121 @@ +import sys + +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState + +CONNECTOR_NAME = "dexalot" + +MAX_ORDER_CREATIONS_PER_BATCH = 10 +MAX_ORDER_CANCELATIONS_PER_BATCH = 15 +MAX_ID_HEX_DIGITS = 64 +MAX_ID_BIT_COUNT = MAX_ID_HEX_DIGITS * 4 +LOST_ORDER_COUNT_LIMIT = 10 + +DEFAULT_DOMAIN = "mainnet" + +ORDERS_PATH = "/signed/orders" +BATCH_OPEN_ORDERS_PATH = "/trading/openorders/params" +EXECUTIONS_PATH = "/signed/executions" +WS_AUTH_PATH = "/auth/getwstoken" + +BASE_PATH_URL = { + "dexalot": "https://api.dexalot.com/privapi", + "testnet": "api.dexalot-test.com/privapi" +} +WS_PATH_URL = { + "dexalot": "wss://api.dexalot.com", + "testnet": "wss://api.dexalot-test.com", +} + +HEARTBEAT_TIME_INTERVAL = 30.0 + +GLOBAL_RATE_LIMIT_ID = "dexalotGlobalRateLimitID" +ORDERS_RATE_LIMIT_ID = "dexalotOrdersRateLimitID" +BATCH_OPEN_ORDERS_RATE_LIMIT_ID = "dexalotBatchOpenOrdersRateLimitID" +EXECUTIONS_RATE_LIMIT_ID = "dexalotExecutionsRateLimitID" +WS_AUTH_RATE_LIMIT_ID = "dexalotWSAuthRateLimitID" + +WS_SUB_RATE_LIMIT_ID = "dexalotWSSubRateLimitID" + +RATE_LIMITS = [ + RateLimit(limit_id=GLOBAL_RATE_LIMIT_ID, limit=10, time_interval=6), + RateLimit( + limit_id=ORDERS_RATE_LIMIT_ID, + limit=sys.maxsize, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(limit_id=GLOBAL_RATE_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=BATCH_OPEN_ORDERS_RATE_LIMIT_ID, + limit=sys.maxsize, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(limit_id=GLOBAL_RATE_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=EXECUTIONS_RATE_LIMIT_ID, + limit=sys.maxsize, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(limit_id=GLOBAL_RATE_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=WS_AUTH_RATE_LIMIT_ID, + limit=sys.maxsize, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(limit_id=GLOBAL_RATE_LIMIT_ID, weight=1)], + ), + RateLimit( + limit_id=WS_SUB_RATE_LIMIT_ID, + limit=sys.maxsize, + time_interval=1, + linked_limits=[LinkedLimitWeightPair(limit_id=GLOBAL_RATE_LIMIT_ID, weight=1)], + ), +] +ORDER_SIDE_MAP = bidict( + { + 0: TradeType.BUY, + 1: TradeType.SELL + } +) +ORDER_TYPE_MAP = bidict( + { + 0: OrderType.MARKET, + 1: OrderType.LIMIT, + 2: OrderType.LIMIT_MAKER, + } +) + +HB_TO_DEXALOT_NUMERIC_STATUS_MAP = { + OrderState.OPEN: 0, + OrderState.FAILED: 1, + OrderState.PARTIALLY_FILLED: 2, + OrderState.FILLED: 3, + OrderState.CANCELED: 4, +} +HB_TO_DEXALOT_STATUS_MAP = { + OrderState.OPEN: "NEW", + OrderState.FAILED: "REJECTED", + OrderState.PARTIALLY_FILLED: "PARTIAL", + OrderState.FILLED: "FILLED", + OrderState.CANCELED: "CANCELED", +} +DEXALOT_TO_HB_NUMERIC_STATUS_MAP = { + 0: OrderState.OPEN, + 1: OrderState.FAILED, + 2: OrderState.PARTIALLY_FILLED, + 3: OrderState.FILLED, + 4: OrderState.CANCELED, + 6: OrderState.CANCELED, + 7: OrderState.FILLED, +} +DEXALOT_TO_HB_STATUS_MAP = { + "NEW": OrderState.OPEN, + "REJECTED": OrderState.FAILED, + "PARTIAL": OrderState.PARTIALLY_FILLED, + "FILLED": OrderState.FILLED, + "CANCELED": OrderState.CANCELED, + "KILLED": OrderState.FAILED, + "CANCEL_REJECT": OrderState.FILLED, +} diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_web_utils.py b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_web_utils.py new file mode 100644 index 0000000..51bcff5 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/dexalot_web_utils.py @@ -0,0 +1,30 @@ +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +def build_api_factory( + throttler: AsyncThrottler, api_key: str, auth: AuthBase +) -> WebAssistantsFactory: + """The API KEY(if available) is used for "public" endpoints as well. "Signed" endpoints + require the additional signing of the message with the secret wallet key.""" + rest_pre_processors = [ + APIKeyStitcher(api_key=api_key), + ] + api_factory = WebAssistantsFactory( + throttler=throttler, rest_pre_processors=rest_pre_processors, auth=auth + ) + return api_factory + + +class APIKeyStitcher(RESTPreProcessorBase): + def __init__(self, api_key: str): + self._api_key = api_key + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + request.headers = request.headers if request.headers is not None else {} + if self._api_key is not None and len(self._api_key) > 0: + request.headers["x-apikey"] = self._api_key + return request diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/gateway_clob_api_data_source_base.py b/hummingbot/connector/gateway/clob_spot/data_sources/gateway_clob_api_data_source_base.py new file mode 100644 index 0000000..86fa9a9 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/gateway_clob_api_data_source_base.py @@ -0,0 +1,459 @@ +import asyncio +import time +from abc import ABC, abstractmethod +from decimal import Decimal +from enum import Enum +from typing import Any, Dict, List, Optional, Tuple + +from bidict import bidict + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.clob_spot.data_sources.clob_api_data_source_base import CLOBAPIDataSourceBase +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule, split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates +from hummingbot.core.event.events import MarketEvent, OrderBookDataSourceEvent +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + + +class GatewayCLOBAPIDataSourceBase(CLOBAPIDataSourceBase, ABC): + """This class defines the pure-Gateway CLOB implementation. + + Technical note on the lack of user-data streaming (i.e. balance updates, etc.): + Given that there are no user-stream data events being emitted due to the lack of ws streams from Gateway, + GatewayClobSpot won't update the _last_received_message_timestamp attribute, and this will result in always + using the short-polling interval in the polling loop. + """ + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + super().__init__( + trading_pairs=trading_pairs, connector_spec=connector_spec, client_config_map=client_config_map + ) + self._trading_pairs = trading_pairs + self._chain = connector_spec["chain"] + self._network = connector_spec["network"] + self._account_id = connector_spec["wallet_address"] + self._client_config = client_config_map + self._markets_info_lock = asyncio.Lock() + self._hb_to_exchange_tokens_map: bidict[str, str] = bidict() + self._snapshots_min_update_interval = 1 + self._snapshots_max_update_interval = 3 + + self._markets_update_task: Optional[asyncio.Task] = None + self._snapshots_update_task: Optional[asyncio.Task] = None + + @property + @abstractmethod + def connector_name(self) -> str: + ... + + @abstractmethod + def get_supported_order_types(self) -> List[OrderType]: + ... + + @abstractmethod + async def get_order_status_update(self, in_flight_order: InFlightOrder) -> OrderUpdate: + """This method should issue an OrderUpdate event before returning the result.""" + ... + + @abstractmethod + async def get_all_order_fills(self, in_flight_order: InFlightOrder) -> List[TradeUpdate]: + ... + + @abstractmethod + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + ... + + @abstractmethod + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + ... + + @abstractmethod + def _parse_trading_rule(self, trading_pair: str, market_info: Dict[str, Any]) -> TradingRule: + ... + + @abstractmethod + def _get_trading_pair_from_market_info(self, market_info: Dict[str, Any]) -> str: + ... + + @abstractmethod + def _get_exchange_base_quote_tokens_from_market_info(self, market_info: Dict[str, Any]) -> Tuple[str, str]: + ... + + @abstractmethod + def _get_exchange_trading_pair_from_market_info(self, market_info: Dict[str, Any]) -> str: + ... + + @abstractmethod + def _get_last_trade_price_from_ticker_data(self, ticker_data: List[Dict[str, Any]]) -> Decimal: + ... + + @abstractmethod + def _get_maker_taker_exchange_fee_rates_from_market_info( + self, market_info: Dict[str, Any] + ) -> MakerTakerExchangeFeeRates: + ... + + @property + def chain(self) -> str: + return self._chain + + @property + def network(self) -> str: + return self._network + + @property + def real_time_balance_update(self) -> bool: + return False + + @property + def markets_update_interval(self) -> int: + return 8 * 60 * 60 + + @property + def min_snapshots_update_interval(self) -> float: + """In seconds.""" + return self._snapshots_min_update_interval + + @min_snapshots_update_interval.setter + def min_snapshots_update_interval(self, value): + """For unit-tests.""" + self._snapshots_min_update_interval = value + + @property + def max_snapshots_update_interval(self) -> float: + """In seconds.""" + return self._snapshots_max_update_interval + + @max_snapshots_update_interval.setter + def max_snapshots_update_interval(self, value): + """For unit-tests.""" + self._snapshots_max_update_interval = value + + @staticmethod + def supported_stream_events() -> List[Enum]: + return [ + OrderBookDataSourceEvent.SNAPSHOT_EVENT, + MarketEvent.OrderUpdate, + ] + + async def start(self): + self._markets_update_task = self._markets_update_task or safe_ensure_future( + coro=self._update_markets_loop() + ) + self._snapshots_update_task = self._snapshots_update_task or safe_ensure_future( + coro=self._update_snapshots_loop() + ) + + async def stop(self): + self._markets_update_task and self._markets_update_task.cancel() + self._markets_update_task = None + self._snapshots_update_task and self._snapshots_update_task.cancel() + self._snapshots_update_task = None + + async def place_order( + self, order: GatewayInFlightOrder, **kwargs + ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + order_result = await self._get_gateway_instance().clob_place_order( + connector=self.connector_name, + chain=self._chain, + network=self._network, + trading_pair=order.trading_pair, + address=self._account_id, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + size=order.amount, + client_order_id=order.client_order_id, + ) + + transaction_hash: Optional[str] = order_result.get("txHash") + + if transaction_hash is None: + await self._on_create_order_transaction_failure(order=order, order_result=order_result) + + transaction_hash = transaction_hash.lower() + + misc_updates = { + "creation_transaction_hash": transaction_hash, + } + + return None, misc_updates + + async def batch_order_create(self, orders_to_create: List[GatewayInFlightOrder]) -> List[PlaceOrderResult]: + update_result = await self._get_gateway_instance().clob_batch_order_modify( + connector=self.connector_name, + chain=self._chain, + network=self._network, + address=self._account_id, + orders_to_create=orders_to_create, + orders_to_cancel=[], + ) + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash is None: + self.logger().error("The batch order update transaction failed.") + exception = ValueError(f"The creation transaction has failed on the {self._chain} chain.") + + transaction_hash = "" if transaction_hash is None else transaction_hash.lower() + + place_order_results = [] + for order in orders_to_create: + place_order_results.append( + PlaceOrderResult( + update_timestamp=self._time(), + client_order_id=order.client_order_id, + exchange_order_id=None, + trading_pair=order.trading_pair, + misc_updates={ + "creation_transaction_hash": transaction_hash, + }, + exception=exception, + ) + ) + + return place_order_results + + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Optional[Dict[str, Any]]]: + if order.exchange_order_id is None: # we still haven't receive an order status update + await self.get_order_status_update(in_flight_order=order) + + await order.get_exchange_order_id() + + cancelation_result = await self._get_gateway_instance().clob_cancel_order( + connector=self.connector_name, + chain=self._chain, + network=self._network, + trading_pair=order.trading_pair, + address=self._account_id, + exchange_order_id=order.exchange_order_id, + ) + transaction_hash: Optional[str] = cancelation_result.get("txHash") + + if transaction_hash is None: + await self._on_cancel_order_transaction_failure(order=order, cancelation_result=cancelation_result) + + transaction_hash = transaction_hash.lower() + + misc_updates = { + "cancelation_transaction_hash": transaction_hash + } + + return True, misc_updates + + async def batch_order_cancel(self, orders_to_cancel: List[GatewayInFlightOrder]) -> List[CancelOrderResult]: + in_flight_orders_to_cancel = [ + self._gateway_order_tracker.fetch_tracked_order(client_order_id=order.client_order_id) + for order in orders_to_cancel + ] + cancel_order_results = [] + if len(in_flight_orders_to_cancel) != 0: + exchange_order_ids_to_cancel = await safe_gather( + *[order.get_exchange_order_id() for order in in_flight_orders_to_cancel], + return_exceptions=True, + ) + found_orders_to_cancel = [ + order + for order, result in zip(orders_to_cancel, exchange_order_ids_to_cancel) + if not isinstance(result, asyncio.TimeoutError) + ] + + update_result = await self._get_gateway_instance().clob_batch_order_modify( + connector=self.connector_name, + chain=self._chain, + network=self._network, + address=self._account_id, + orders_to_create=[], + orders_to_cancel=found_orders_to_cancel, + ) + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash is None: + self.logger().error("The batch order update transaction failed.") + exception = ValueError(f"The cancelation transaction has failed on the {self._chain} chain.") + + transaction_hash = "" if transaction_hash is None else transaction_hash.lower() + + for order in found_orders_to_cancel: + cancel_order_results.append( + CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + misc_updates={ + "cancelation_transaction_hash": transaction_hash + }, + exception=exception, + ) + ) + + return cancel_order_results + + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + ticker_data = await self._get_ticker_data(trading_pair=trading_pair) + last_traded_price = self._get_last_trade_price_from_ticker_data(ticker_data=ticker_data) + return last_traded_price + + async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + data = await self._get_gateway_instance().get_clob_orderbook_snapshot( + trading_pair=trading_pair, connector=self.connector_name, chain=self._chain, network=self._network + ) + bids = [ + (Decimal(bid["price"]), Decimal(bid["quantity"])) + for bid in data["buys"] + if Decimal(bid["quantity"]) != 0 + ] + asks = [ + (Decimal(ask["price"]), Decimal(ask["quantity"])) + for ask in data["sells"] + if Decimal(ask["quantity"]) != 0 + ] + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": self._time() * 1e3, + "bids": bids, + "asks": asks, + }, + timestamp=data["timestamp"], + ) + return snapshot_msg + + async def check_network_status(self) -> NetworkStatus: + status = NetworkStatus.CONNECTED + try: + await self._get_gateway_instance().ping_gateway() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + """Returns a dictionary like + + { + asset_name: { + "total_balance": Decimal, + "available_balance": Decimal, + } + } + """ + balances = await self._get_gateway_instance().get_balances( + chain=self._chain, + network=self._network, + address=self._account_id, + ) + return balances + + def _check_markets_initialized(self) -> bool: + return len(self._markets_info) != 0 + + async def _update_markets_loop(self): + while True: + await self._sleep(delay=self.markets_update_interval) + await self._update_markets() + + async def _update_markets(self): + async with self._markets_info_lock: + for market_info in await self._get_markets_info(): + trading_pair = self._get_trading_pair_from_market_info(market_info=market_info) + self._markets_info[trading_pair] = market_info + base, quote = split_hb_trading_pair(trading_pair=trading_pair) + base_exchange, quote_exchange = self._get_exchange_base_quote_tokens_from_market_info( + market_info=market_info + ) + self._hb_to_exchange_tokens_map[base] = base_exchange + self._hb_to_exchange_tokens_map[quote] = quote_exchange + + async def _get_markets_info(self) -> List[Dict[str, Any]]: + resp = await self._get_gateway_instance().get_clob_markets( + connector=self.connector_name, chain=self._chain, network=self._network + ) + return resp["markets"] + + async def _update_snapshots_loop(self): + while True: + update_task = safe_ensure_future(coro=self._emit_snapshots_updates()) + min_delay_sleep = self._sleep(delay=self.min_snapshots_update_interval) + max_delay_sleep = self._sleep(delay=self.max_snapshots_update_interval) + + or_group = next( + asyncio.as_completed( + [update_task, max_delay_sleep] + ) # returns first done, lets the other run - i.e. run for at most max_delay_sleep + ) + and_group = safe_gather(min_delay_sleep, or_group) # run for at least min_delay_sleep + + start_ts = self._time() + await and_group + + if self._time() - start_ts >= self.max_snapshots_update_interval: + self.logger().warning(f"Snapshot update took longer than {self.max_snapshots_update_interval}.") + + async def _emit_snapshots_updates(self): + tasks = [ + self._emit_trading_pair_snapshot_update(trading_pair=trading_pair) + for trading_pair in self._trading_pairs + ] + await safe_gather(*tasks) + + async def _emit_trading_pair_snapshot_update(self, trading_pair: str): + try: + snapshot_msg = await self.get_order_book_snapshot(trading_pair=trading_pair) + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, message=snapshot_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error(f"Failed to update snapshot message for {trading_pair}.") + + async def _get_ticker_data(self, trading_pair: str) -> List[Dict[str, Any]]: + ticker_data = await self._get_gateway_instance().get_clob_ticker( + connector=self.connector_name, + chain=self._chain, + network=self._network, + trading_pair=trading_pair, + ) + return ticker_data["markets"] + + async def _on_create_order_transaction_failure(self, order: GatewayInFlightOrder, order_result: Dict[str, Any]): + raise ValueError( + f"The creation transaction for {order.client_order_id} failed. Please ensure you have sufficient" + f" funds to cover the transaction gas costs." + ) + + async def _on_cancel_order_transaction_failure(self, order: GatewayInFlightOrder, cancelation_result: Dict[str, Any]): + raise ValueError( + f"The cancelation transaction for {order.client_order_id} failed. Please ensure you have sufficient" + f" funds to cover the transaction gas costs." + ) + + @staticmethod + async def _sleep(delay: float): + await asyncio.sleep(delay) + + @staticmethod + def _time() -> float: + return time.time() + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._client_config) + return gateway_instance diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/injective/__init__.py b/hummingbot/connector/gateway/clob_spot/data_sources/injective/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_api_data_source.py b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_api_data_source.py new file mode 100644 index 0000000..412504f --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_api_data_source.py @@ -0,0 +1,1102 @@ +import asyncio +import json +import time +from asyncio import Lock +from collections import defaultdict +from decimal import Decimal +from enum import Enum +from math import floor +from typing import Any, Dict, List, Mapping, Optional, Tuple + +from grpc.aio import UnaryStreamCall +from pyinjective.async_client import AsyncClient +from pyinjective.composer import Composer as ProtoMsgComposer +from pyinjective.core.network import Network +from pyinjective.proto.exchange.injective_accounts_rpc_pb2 import StreamSubaccountBalanceResponse +from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import GetTxByTxHashResponse, StreamTxsResponse +from pyinjective.proto.exchange.injective_portfolio_rpc_pb2 import ( + AccountPortfolioResponse, + Coin, + Portfolio, + StreamAccountPortfolioResponse, + SubaccountBalanceV2, +) +from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2 import ( + MarketsResponse, + SpotMarketInfo, + SpotOrderHistory, + SpotTrade, + StreamOrderbookV2Response, + StreamOrdersResponse, + StreamTradesResponse, + TokenMeta, +) +from pyinjective.proto.injective.exchange.v1beta1.exchange_pb2 import SpotOrder +from pyinjective.wallet import Address + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.clob_spot.data_sources.clob_api_data_source_base import CLOBAPIDataSourceBase +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_constants import ( + BACKEND_TO_CLIENT_ORDER_STATE_MAP, + CLIENT_TO_BACKEND_ORDER_TYPES_MAP, + CONNECTOR_NAME, + DEFAULT_SUB_ACCOUNT_SUFFIX, + LOST_ORDER_COUNT_LIMIT, + MARKETS_UPDATE_INTERVAL, + MSG_BATCH_UPDATE_ORDERS, + MSG_CANCEL_SPOT_ORDER, + MSG_CREATE_SPOT_LIMIT_ORDER, + ORDER_CHAIN_PROCESSING_TIMEOUT, + REQUESTS_SKIP_STEP, +) +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_utils import OrderHashManager +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book import OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates, TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent, OrderBookDataSourceEvent +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + + +class InjectiveAPIDataSource(CLOBAPIDataSourceBase): + """An interface class to the Injective blockchain. + + Note — The same wallet address should not be used with different instances of the client as this will cause + issues with the account sequence management and may result in failed transactions, or worse, wrong locally computed + order hashes (exchange order IDs), which will in turn result in orphaned orders on the exchange. + """ + + _logger: Optional[HummingbotLogger] = None + + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + super().__init__( + trading_pairs=trading_pairs, connector_spec=connector_spec, client_config_map=client_config_map + ) + self._connector_name = CONNECTOR_NAME + self._chain = connector_spec["chain"] + self._network = connector_spec["network"] + self._sub_account_id = connector_spec["wallet_address"] + self._account_address: str = Address(bytes.fromhex(self._sub_account_id[2:-24])).to_acc_bech32() + if self._network == "mainnet": + self._network_obj = Network.mainnet() + elif self._network == "testnet": + self._network_obj = Network.testnet() + else: + raise ValueError(f"Invalid network: {self._network}") + self._client = AsyncClient(network=self._network_obj) + self._composer = ProtoMsgComposer(network=self._network_obj.string()) + self._order_hash_manager: Optional[OrderHashManager] = None + + self._markets_info: Dict[str, SpotMarketInfo] = {} + self._market_id_to_active_spot_markets: Dict[str, SpotMarketInfo] = {} + self._denom_to_token_meta: Dict[str, TokenMeta] = {} + self._markets_update_task: Optional[asyncio.Task] = None + + self._trades_stream_listener: Optional[asyncio.Task] = None + self._order_listeners: Dict[str, asyncio.Task] = {} + self._order_books_stream_listener: Optional[asyncio.Task] = None + self._bank_balances_stream_listener: Optional[asyncio.Task] = None + self._subaccount_balances_stream_listener: Optional[asyncio.Task] = None + self._transactions_stream_listener: Optional[asyncio.Task] = None + + self._order_placement_lock = Lock() + + # Local Balance + self._account_balances: defaultdict[str, Decimal] = defaultdict(lambda: Decimal("0")) + self._account_available_balances: defaultdict[str, Decimal] = defaultdict(lambda: Decimal("0")) + + @property + def real_time_balance_update(self) -> bool: + return True + + @property + def events_are_streamed(self) -> bool: + return True + + @staticmethod + def supported_stream_events() -> List[Enum]: + return [ + MarketEvent.TradeUpdate, + MarketEvent.OrderUpdate, + AccountEvent.BalanceEvent, + OrderBookDataSourceEvent.TRADE_EVENT, + OrderBookDataSourceEvent.DIFF_EVENT, + OrderBookDataSourceEvent.SNAPSHOT_EVENT, + ] + + def get_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def _is_default_subaccount(self): + return self._sub_account_id[-24:] == DEFAULT_SUB_ACCOUNT_SUFFIX + + async def start(self): + """Starts the event streaming.""" + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + self._markets_update_task = self._markets_update_task or safe_ensure_future( + coro=self._update_markets_loop() + ) + await self._update_markets() # required for the streams + await self._start_streams() + self._gateway_order_tracker.lost_order_count_limit = LOST_ORDER_COUNT_LIMIT + + async def stop(self): + """Stops the event streaming.""" + await self._stop_streams() + self._markets_update_task and self._markets_update_task.cancel() + self._markets_update_task = None + + async def check_network_status(self) -> NetworkStatus: + status = NetworkStatus.CONNECTED + try: + await self._client.ping() + await self._get_gateway_instance().ping_gateway() + except asyncio.CancelledError: + raise + except Exception: + status = NetworkStatus.NOT_CONNECTED + return status + + async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + market = self._markets_info[trading_pair] + order_book_response = await self._client.get_spot_orderbooksV2(market_ids=[market.market_id]) + price_scale = self._get_backend_price_scaler(market=market) + size_scale = self._get_backend_denom_scaler(denom_meta=market.base_token_meta) + last_update_timestamp_ms = 0 + bids = [] + orderbook = order_book_response.orderbooks[0].orderbook + for bid in orderbook.buys: + bids.append((Decimal(bid.price) * price_scale, Decimal(bid.quantity) * size_scale)) + last_update_timestamp_ms = max(last_update_timestamp_ms, bid.timestamp) + asks = [] + for ask in orderbook.sells: + asks.append((Decimal(ask.price) * price_scale, Decimal(ask.quantity) * size_scale)) + last_update_timestamp_ms = max(last_update_timestamp_ms, ask.timestamp) + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": last_update_timestamp_ms, + "bids": bids, + "asks": asks, + }, + timestamp=last_update_timestamp_ms * 1e-3, + ) + return snapshot_msg + + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return str(status_update_exception).startswith("No update found for order") + + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return False + + async def get_order_status_update(self, in_flight_order: GatewayInFlightOrder) -> OrderUpdate: + status_update: Optional[OrderUpdate] = None + trading_pair = in_flight_order.trading_pair + order_hash = await in_flight_order.get_exchange_order_id() + misc_updates = { + "creation_transaction_hash": in_flight_order.creation_transaction_hash, + "cancelation_transaction_hash": in_flight_order.cancel_tx_hash, + } + + market = self._markets_info[trading_pair] + direction = "buy" if in_flight_order.trade_type == TradeType.BUY else "sell" + status_update = await self._get_booked_order_status_update( + trading_pair=trading_pair, + client_order_id=in_flight_order.client_order_id, + order_hash=order_hash, + market_id=market.market_id, + direction=direction, + creation_timestamp=in_flight_order.creation_timestamp, + order_type=in_flight_order.order_type, + trade_type=in_flight_order.trade_type, + order_mist_updates=misc_updates, + ) + if status_update is None and in_flight_order.creation_transaction_hash is not None: + try: + tx_response = await self._get_transaction_by_hash( + transaction_hash=in_flight_order.creation_transaction_hash + ) + except Exception: + self.logger().debug( + f"Failed to fetch transaction {in_flight_order.creation_transaction_hash} for order" + f" {in_flight_order.exchange_order_id}.", + exc_info=True, + ) + tx_response = None + if tx_response is None: + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + elif await self._check_if_order_failed_based_on_transaction( + transaction=tx_response, order=in_flight_order + ): + status_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=tx_response.data.block_unix_timestamp * 1e-3, + new_state=OrderState.FAILED, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=in_flight_order.exchange_order_id, + misc_updates=misc_updates, + ) + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + if status_update is None: + raise IOError(f"No update found for order {in_flight_order.client_order_id}") + + if in_flight_order.current_state == OrderState.PENDING_CREATE and status_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=status_update.update_timestamp, + new_state=OrderState.OPEN, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=status_update.exchange_order_id, + misc_updates=misc_updates, + ) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=open_update) + + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=status_update) + + return status_update + + async def place_order( + self, order: GatewayInFlightOrder, **kwargs + ) -> Tuple[Optional[str], Dict[str, Any]]: + spot_order_to_create = [self._compose_spot_order_for_local_hash_computation(order=order)] + async with self._order_placement_lock: + order_hashes = self._order_hash_manager.compute_order_hashes( + spot_orders=spot_order_to_create, derivative_orders=[] + ) + order_hash = order_hashes.spot[0] + + try: + order_result: Dict[str, Any] = await self._get_gateway_instance().clob_place_order( + connector=self._connector_name, + chain=self._chain, + network=self._network, + trading_pair=order.trading_pair, + address=self._sub_account_id, + trade_type=order.trade_type, + order_type=order.order_type, + price=order.price, + size=order.amount, + ) + transaction_hash: Optional[str] = order_result.get("txHash") + except Exception: + await self._update_account_address_and_create_order_hash_manager() + raise + + self.logger().debug( + f"Placed order {order_hash} with nonce {self._order_hash_manager.current_nonce - 1}" + f" and tx hash {transaction_hash}." + ) + + if transaction_hash in (None, ""): + await self._update_account_address_and_create_order_hash_manager() + raise ValueError( + f"The creation transaction for {order.client_order_id} failed. Please ensure there is sufficient" + f" INJ in the bank to cover transaction fees." + ) + + transaction_hash = f"0x{transaction_hash.lower()}" + + misc_updates = { + "creation_transaction_hash": transaction_hash, + } + + return order_hash, misc_updates + + async def batch_order_create(self, orders_to_create: List[GatewayInFlightOrder]) -> List[PlaceOrderResult]: + spot_orders_to_create = [ + self._compose_spot_order_for_local_hash_computation(order=order) + for order in orders_to_create + ] + + async with self._order_placement_lock: + order_hashes = self._order_hash_manager.compute_order_hashes( + spot_orders=spot_orders_to_create, derivative_orders=[] + ) + try: + update_result = await self._get_gateway_instance().clob_batch_order_modify( + connector=self._connector_name, + chain=self._chain, + network=self._network, + address=self._sub_account_id, + orders_to_create=orders_to_create, + orders_to_cancel=[], + ) + except Exception: + await self._update_account_address_and_create_order_hash_manager() + raise + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash in (None, ""): + await self._update_account_address_and_create_order_hash_manager() + self.logger().error("The batch order update transaction failed.") + exception = RuntimeError("The creation transaction has failed on the Injective chain.") + + transaction_hash = f"0x{transaction_hash.lower()}" + + place_order_results = [ + PlaceOrderResult( + update_timestamp=self._time(), + client_order_id=order.client_order_id, + exchange_order_id=order_hash, + trading_pair=order.trading_pair, + misc_updates={ + "creation_transaction_hash": transaction_hash, + }, + exception=exception, + ) for order, order_hash in zip(orders_to_create, order_hashes.spot) + ] + + return place_order_results + + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Dict[str, Any]]: + await order.get_exchange_order_id() + + cancelation_result = await self._get_gateway_instance().clob_cancel_order( + connector=self._connector_name, + chain=self._chain, + network=self._network, + trading_pair=order.trading_pair, + address=self._sub_account_id, + exchange_order_id=order.exchange_order_id, + ) + transaction_hash: Optional[str] = cancelation_result.get("txHash") + + if transaction_hash in (None, ""): + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + raise ValueError( + f"The cancelation transaction for {order.client_order_id} failed. Please ensure there is sufficient" + f" INJ in the bank to cover transaction fees." + ) + + transaction_hash = f"0x{transaction_hash.lower()}" + + misc_updates = { + "cancelation_transaction_hash": transaction_hash + } + + return True, misc_updates + + async def batch_order_cancel(self, orders_to_cancel: List[InFlightOrder]) -> List[CancelOrderResult]: + in_flight_orders_to_cancel = [ + self._gateway_order_tracker.fetch_tracked_order(client_order_id=order.client_order_id) + for order in orders_to_cancel + ] + exchange_order_ids_to_cancel = await safe_gather( + *[order.get_exchange_order_id() for order in in_flight_orders_to_cancel], + return_exceptions=True, + ) + found_orders_to_cancel = [ + order + for order, result in zip(orders_to_cancel, exchange_order_ids_to_cancel) + if not isinstance(result, asyncio.TimeoutError) + ] + + update_result = await self._get_gateway_instance().clob_batch_order_modify( + connector=self._connector_name, + chain=self._chain, + network=self._network, + address=self._sub_account_id, + orders_to_create=[], + orders_to_cancel=found_orders_to_cancel, + ) + + transaction_hash: Optional[str] = update_result.get("txHash") + exception = None + + if transaction_hash is None: + await self._update_account_address_and_create_order_hash_manager() + self.logger().error("The batch order update transaction failed.") + exception = RuntimeError("The cancelation transaction has failed on the Injective chain.") + + transaction_hash = f"0x{transaction_hash.lower()}" + + cancel_order_results = [ + CancelOrderResult( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + misc_updates={ + "cancelation_transaction_hash": transaction_hash + }, + exception=exception, + ) for order in orders_to_cancel + ] + + return cancel_order_results + + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + market = self._markets_info[trading_pair] + trades = await self._client.get_spot_trades(market_id=market.market_id) + if len(trades.trades) != 0: + price = self._convert_price_from_backend(price=trades.trades[0].price.price, market=market) + else: + price = Decimal("NaN") + return price + + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + if self._account_address is None: + async with self._order_placement_lock: + await self._update_account_address_and_create_order_hash_manager() + self._check_markets_initialized() or await self._update_markets() + + portfolio_response: AccountPortfolioResponse = await self._client.get_account_portfolio( + account_address=self._account_address + ) + + portfolio: Portfolio = portfolio_response.portfolio + bank_balances: List[Coin] = portfolio.bank_balances + sub_account_balances: List[SubaccountBalanceV2] = portfolio.subaccounts + + balances_dict: Dict[str, Dict[str, Decimal]] = {} + + if self._is_default_subaccount: + for bank_entry in bank_balances: + denom_meta = self._denom_to_token_meta.get(bank_entry.denom) + if denom_meta is not None: + asset_name: str = denom_meta.symbol + denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + + available_balance: Decimal = Decimal(bank_entry.amount) * denom_scaler + total_balance: Decimal = available_balance + balances_dict[asset_name] = { + "total_balance": total_balance, + "available_balance": available_balance, + } + + for entry in sub_account_balances: + if entry.subaccount_id.casefold() != self._sub_account_id.casefold(): + continue + + denom_meta = self._denom_to_token_meta.get(entry.denom) + if denom_meta is not None: + asset_name: str = denom_meta.symbol + denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + + total_balance: Decimal = Decimal(entry.deposit.total_balance) * denom_scaler + available_balance: Decimal = Decimal(entry.deposit.available_balance) * denom_scaler + + balance_element = balances_dict.get( + asset_name, {"total_balance": Decimal("0"), "available_balance": Decimal("0")} + ) + balance_element["total_balance"] += total_balance + balance_element["available_balance"] += available_balance + balances_dict[asset_name] = balance_element + + self._update_local_balances(balances=balances_dict) + return balances_dict + + async def get_all_order_fills(self, in_flight_order: GatewayInFlightOrder) -> List[TradeUpdate]: + trading_pair = in_flight_order.trading_pair + market = self._markets_info[trading_pair] + exchange_order_id = await in_flight_order.get_exchange_order_id() + direction = "buy" if in_flight_order.trade_type == TradeType.BUY else "sell" + trades = await self._get_all_trades( + market_id=market.market_id, + direction=direction, + created_at=int(in_flight_order.creation_timestamp * 1e3), + updated_at=int(in_flight_order.last_update_timestamp * 1e3) + ) + + client_order_id: str = in_flight_order.client_order_id + trade_updates = [] + + for trade in trades: + if trade.order_hash == exchange_order_id: + _, trade_update = self._parse_backend_trade(client_order_id=client_order_id, backend_trade=trade) + trade_updates.append(trade_update) + + return trade_updates + + async def _update_account_address_and_create_order_hash_manager(self): + if not self._order_placement_lock.locked(): + raise RuntimeError("The order-placement lock must be acquired before creating the order hash manager.") + response: Dict[str, Any] = await self._get_gateway_instance().clob_injective_balances( + chain=self._chain, network=self._network, address=self._sub_account_id + ) + self._account_address: str = response["injectiveAddress"] + + await self._client.get_account(self._account_address) + await self._client.sync_timeout_height() + tasks_to_await_submitted_orders_to_be_processed_by_chain = [ + asyncio.wait_for(order.wait_until_processed_by_exchange(), timeout=ORDER_CHAIN_PROCESSING_TIMEOUT) + for order in self._gateway_order_tracker.active_orders.values() + if order.creation_transaction_hash is not None + ] # orders that have been sent to the chain but not yet added to a block will affect the order nonce + await safe_gather(*tasks_to_await_submitted_orders_to_be_processed_by_chain, return_exceptions=True) # await their processing + self._order_hash_manager = OrderHashManager(network=self._network_obj, sub_account_id=self._sub_account_id) + await self._order_hash_manager.start() + + def _check_markets_initialized(self) -> bool: + return ( + len(self._markets_info) != 0 + and len(self._market_id_to_active_spot_markets) != 0 + and len(self._denom_to_token_meta) != 0 + ) + + async def _update_markets_loop(self): + while True: + await self._sleep(delay=MARKETS_UPDATE_INTERVAL) + await self._update_markets() + + async def _update_markets(self): + markets = await self._get_spot_markets() + self._update_trading_pair_to_active_spot_markets(markets=markets) + self._update_market_id_to_active_spot_markets(markets=markets) + self._update_denom_to_token_meta(markets=markets) + + async def _get_spot_markets(self) -> MarketsResponse: + market_status = "active" + markets = await self._client.get_spot_markets(market_status=market_status) + return markets + + def _update_local_balances(self, balances: Dict[str, Dict[str, Decimal]]): + # We need to keep local copy of total and available balance so we can trigger BalanceUpdateEvent with correct + # details. This is specifically for Injective during the processing of balance streams, where the messages does not + # detail the total_balance and available_balance across bank and subaccounts. + for asset_name, balance_entry in balances.items(): + if "total_balance" in balance_entry: + self._account_balances[asset_name] = balance_entry["total_balance"] + if "available_balance" in balance_entry: + self._account_available_balances[asset_name] = balance_entry["available_balance"] + + def _update_market_id_to_active_spot_markets(self, markets: MarketsResponse): + markets_dict = {market.market_id: market for market in markets.markets} + self._market_id_to_active_spot_markets.clear() + self._market_id_to_active_spot_markets.update(markets_dict) + + def _parse_trading_rule(self, trading_pair: str, market_info: SpotMarketInfo) -> TradingRule: + min_price_tick_size = self._convert_price_from_backend( + price=market_info.min_price_tick_size, market=market_info + ) + min_quantity_tick_size = self._convert_size_from_backend( + size=market_info.min_quantity_tick_size, market=market_info + ) + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + return trading_rule + + def _compose_spot_order_for_local_hash_computation(self, order: GatewayInFlightOrder) -> SpotOrder: + market = self._markets_info[order.trading_pair] + return self._composer.SpotOrder( + market_id=market.market_id, + subaccount_id=self._sub_account_id.lower(), + fee_recipient=self._account_address, + price=float(order.price), + quantity=float(order.amount), + is_buy=order.trade_type == TradeType.BUY, + is_po=order.order_type == OrderType.LIMIT_MAKER, + ) + + async def get_trading_fees(self) -> Mapping[str, MakerTakerExchangeFeeRates]: + self._check_markets_initialized() or await self._update_markets() + + trading_fees = {} + for trading_pair, market in self._markets_info.items(): + fee_scaler = Decimal("1") - Decimal(market.service_provider_fee) + maker_fee = Decimal(market.maker_fee_rate) * fee_scaler + taker_fee = Decimal(market.taker_fee_rate) * fee_scaler + trading_fees[trading_pair] = MakerTakerExchangeFeeRates( + maker=maker_fee, taker=taker_fee, maker_flat_fees=[], taker_flat_fees=[] + ) + return trading_fees + + async def _get_booked_order_status_update( + self, + trading_pair: str, + client_order_id: str, + order_hash: str, + market_id: str, + direction: str, + creation_timestamp: float, + order_type: OrderType, + trade_type: TradeType, + order_mist_updates: Dict[str, str], + ) -> Optional[OrderUpdate]: + order_status = await self._get_backend_order_status( + market_id=market_id, + order_type=order_type, + trade_type=trade_type, + order_hash=order_hash, + direction=direction, + start_time=int(creation_timestamp * 1e3), + ) + + if order_status is not None: + status_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=order_status.updated_at * 1e-3, + new_state=BACKEND_TO_CLIENT_ORDER_STATE_MAP[order_status.state], + client_order_id=client_order_id, + exchange_order_id=order_status.order_hash, + misc_updates=order_mist_updates, + ) + else: + status_update = None + + return status_update + + def _update_trading_pair_to_active_spot_markets(self, markets: MarketsResponse): + markets_dict = {} + for market in markets.markets: + trading_pair = combine_to_hb_trading_pair( + base=market.base_token_meta.symbol, quote=market.quote_token_meta.symbol + ) + markets_dict[trading_pair] = market + self._markets_info.clear() + self._markets_info.update(markets_dict) + + def _update_denom_to_token_meta(self, markets: MarketsResponse): + self._denom_to_token_meta.clear() + for market in markets.markets: + if market.base_token_meta.symbol != "": # the meta is defined + self._denom_to_token_meta[market.base_denom] = market.base_token_meta + if market.quote_token_meta.symbol != "": # the meta is defined + self._denom_to_token_meta[market.quote_denom] = market.quote_token_meta + + async def _start_streams(self): + self._trades_stream_listener = ( + self._trades_stream_listener or safe_ensure_future(coro=self._listen_to_trades_stream()) + ) + market_ids = self._get_market_ids() + for market_id in market_ids: + if market_id not in self._order_listeners: + self._order_listeners[market_id] = safe_ensure_future( + coro=self._listen_to_orders_stream(market_id=market_id) + ) + self._order_books_stream_listener = ( + self._order_books_stream_listener or safe_ensure_future(coro=self._listen_to_order_books_stream()) + ) + if self._is_default_subaccount: + self._bank_balances_stream_listener = ( + self._bank_balances_stream_listener or safe_ensure_future(coro=self._listen_to_bank_balances_streams()) + ) + self._subaccount_balances_stream_listener = self._subaccount_balances_stream_listener or safe_ensure_future( + coro=self._listen_to_subaccount_balances_stream() + ) + self._transactions_stream_listener = self._transactions_stream_listener or safe_ensure_future( + coro=self._listen_to_transactions_stream() + ) + + async def _stop_streams(self): + self._trades_stream_listener and self._trades_stream_listener.cancel() + self._trades_stream_listener = None + for listener in self._order_listeners.values(): + listener.cancel() + self._order_listeners = {} + self._order_books_stream_listener and self._order_books_stream_listener.cancel() + self._order_books_stream_listener = None + self._subaccount_balances_stream_listener and self._subaccount_balances_stream_listener.cancel() + self._subaccount_balances_stream_listener = None + self._bank_balances_stream_listener and self._bank_balances_stream_listener.cancel() + self._bank_balances_stream_listener = None + self._transactions_stream_listener and self._transactions_stream_listener.cancel() + self._transactions_stream_listener = None + + async def _listen_to_trades_stream(self): + while True: + market_ids: List[str] = self._get_market_ids() + stream: UnaryStreamCall = await self._client.stream_spot_trades(market_ids=market_ids) + try: + async for trade_msg in stream: + self._process_trade_stream_event(message=trade_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in public trade listener loop.") + self.logger().info("Restarting public trades stream.") + stream.cancel() + + def _process_trade_stream_event(self, message: StreamTradesResponse): + trade_message: SpotTrade = message.trade + exchange_order_id = trade_message.order_hash + tracked_order = self._gateway_order_tracker.all_fillable_orders_by_exchange_order_id.get(exchange_order_id) + client_order_id = "" if tracked_order is None else tracked_order.client_order_id + trade_ob_msg, trade_update = self._parse_backend_trade( + client_order_id=client_order_id, backend_trade=trade_message + ) + + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, message=trade_ob_msg) + self._publisher.trigger_event(event_tag=MarketEvent.TradeUpdate, message=trade_update) + + async def _listen_to_orders_stream(self, market_id: str): + while True: + stream: UnaryStreamCall = await self._client.stream_historical_spot_orders(market_id=market_id) + try: + async for order in stream: + self._parse_order_stream_update(order=order) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + self.logger().info("Restarting orders stream.") + stream.cancel() + + def _parse_order_stream_update(self, order: StreamOrdersResponse): + order_hash = order.order.order_hash + in_flight_order = self._gateway_order_tracker.all_fillable_orders_by_exchange_order_id.get(order_hash) + if in_flight_order is not None: + market_id = order.order.market_id + trading_pair = self._get_trading_pair_from_market_id(market_id=market_id) + order_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=order.order.updated_at * 1e-3, + new_state=BACKEND_TO_CLIENT_ORDER_STATE_MAP[order.order.state], + client_order_id=in_flight_order.client_order_id, + exchange_order_id=order.order.order_hash, + ) + if in_flight_order.current_state == OrderState.PENDING_CREATE and order_update.new_state != OrderState.OPEN: + open_update = OrderUpdate( + trading_pair=trading_pair, + update_timestamp=order.order.updated_at * 1e-3, + new_state=OrderState.OPEN, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=order.order.order_hash, + ) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=open_update) + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=order_update) + + async def _listen_to_order_books_stream(self): + while True: + market_ids = self._get_market_ids() + stream: UnaryStreamCall = await self._client.stream_spot_orderbook_snapshot(market_ids=market_ids) + try: + async for order_book_update in stream: + self._parse_order_book_event(order_book_update=order_book_update) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + self.logger().info("Restarting order books stream.") + stream.cancel() + + def _parse_order_book_event(self, order_book_update: StreamOrderbookV2Response): + udpate_timestamp_ms = order_book_update.timestamp + market_id = order_book_update.market_id + trading_pair = self._get_trading_pair_from_market_id(market_id=market_id) + market = self._market_id_to_active_spot_markets[market_id] + price_scale = self._get_backend_price_scaler(market=market) + size_scale = self._get_backend_denom_scaler(denom_meta=market.base_token_meta) + bids = [ + (Decimal(bid.price) * price_scale, Decimal(bid.quantity) * size_scale) + for bid in order_book_update.orderbook.buys + ] + asks = [ + (Decimal(ask.price) * price_scale, Decimal(ask.quantity) * size_scale) + for ask in order_book_update.orderbook.sells + ] + snapshot_msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": udpate_timestamp_ms, + "bids": bids, + "asks": asks, + }, + timestamp=udpate_timestamp_ms * 1e-3, + ) + self._publisher.trigger_event(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, message=snapshot_msg) + + def _parse_bank_balance_message(self, message: StreamAccountPortfolioResponse) -> BalanceUpdateEvent: + denom_meta: TokenMeta = self._denom_to_token_meta[message.denom] + denom_scaler: Decimal = Decimal(f"1e-{denom_meta.decimals}") + + available_balance: Decimal = Decimal(message.amount) * denom_scaler + total_balance: Decimal = available_balance + + balance_msg = BalanceUpdateEvent( + timestamp=self._time(), + asset_name=denom_meta.symbol, + total_balance=total_balance, + available_balance=available_balance, + ) + self._update_local_balances( + balances={denom_meta.symbol: {"total_balance": total_balance, "available_balance": available_balance}} + ) + return balance_msg + + async def _listen_to_bank_balances_streams(self): + while True: + stream: UnaryStreamCall = await self._client.stream_account_portfolio( + account_address=self._account_address, type="bank" + ) + try: + async for bank_balance in stream: + self._process_bank_balance_stream_event(message=bank_balance) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in account balance listener loop.") + self.logger().info("Restarting account balances stream.") + stream.cancel() + + def _process_bank_balance_stream_event(self, message: StreamAccountPortfolioResponse): + denom_meta = self._denom_to_token_meta[message.denom] + symbol = denom_meta.symbol + safe_ensure_future(self._issue_balance_update(token=symbol)) + + async def _listen_to_subaccount_balances_stream(self): + while True: + # Uses InjectiveAccountsRPC since it provides both total_balance and available_balance in a single stream. + stream: UnaryStreamCall = await self._client.stream_subaccount_balance(subaccount_id=self._sub_account_id) + try: + async for balance_msg in stream: + self._process_subaccount_balance_stream_event(message=balance_msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in account balance listener loop.") + self.logger().info("Restarting account balances stream.") + stream.cancel() + + def _process_subaccount_balance_stream_event(self, message: StreamSubaccountBalanceResponse): + denom_meta = self._denom_to_token_meta[message.balance.denom] + symbol = denom_meta.symbol + safe_ensure_future(self._issue_balance_update(token=symbol)) + + async def _issue_balance_update(self, token: str): + account_balances = await self.get_account_balances() + token_balances = account_balances.get(token, {}) + total_balance = token_balances.get("total_balance", Decimal("0")) + available_balance = token_balances.get("available_balance", Decimal("0")) + balance_msg = BalanceUpdateEvent( + timestamp=self._time(), + asset_name=token, + total_balance=total_balance, + available_balance=available_balance, + ) + self._publisher.trigger_event(event_tag=AccountEvent.BalanceEvent, message=balance_msg) + + async def _get_backend_order_status( + self, + market_id: str, + order_type: OrderType, + trade_type: TradeType, + order_hash: Optional[str] = None, + direction: Optional[str] = None, + start_time: Optional[int] = None, + ) -> Optional[SpotOrderHistory]: + skip = 0 + order_status = None + search_completed = False + + while not search_completed: + response = await self._client.get_historical_spot_orders( + market_id=market_id, + subaccount_id=self._sub_account_id, + direction=direction, + start_time=start_time, + skip=skip, + order_types=[CLIENT_TO_BACKEND_ORDER_TYPES_MAP[(trade_type, order_type)]] + ) + if len(response.orders) == 0: + search_completed = True + else: + skip += REQUESTS_SKIP_STEP + for response_order in response.orders: + if response_order.order_hash == order_hash: + order_status = response_order + search_completed = True + break + + return order_status + + async def _get_all_trades( + self, + market_id: str, + direction: str, + created_at: int, + updated_at: int, + ) -> List[SpotTrade]: + skip = 0 + all_trades = [] + search_completed = False + + while not search_completed: + trades = await self._client.get_spot_trades( + market_id=market_id, + subaccount_id=self._sub_account_id, + direction=direction, + skip=skip, + start_time=created_at, + ) + if len(trades.trades) == 0: + search_completed = True + else: + all_trades.extend(trades.trades) + skip += len(trades.trades) + + return all_trades + + def _parse_backend_trade( + self, client_order_id: str, backend_trade: SpotTrade + ) -> Tuple[OrderBookMessage, TradeUpdate]: + exchange_order_id: str = backend_trade.order_hash + market = self._market_id_to_active_spot_markets[backend_trade.market_id] + trading_pair = self._get_trading_pair_from_market_id(market_id=backend_trade.market_id) + trade_id: str = backend_trade.trade_id + + price = self._convert_price_from_backend(price=backend_trade.price.price, market=market) + size = self._convert_size_from_backend(size=backend_trade.price.quantity, market=market) + trade_type = TradeType.BUY if backend_trade.trade_direction == "buy" else TradeType.SELL + is_taker: bool = backend_trade.execution_side == "taker" + + fee_amount = self._convert_quote_from_backend(quote_amount=backend_trade.fee, market=market) + _, quote = split_hb_trading_pair(trading_pair=trading_pair) + fee = TradeFeeBase.new_spot_fee( + fee_schema=TradeFeeSchema(), + trade_type=trade_type, + flat_fees=[TokenAmount(amount=fee_amount, token=quote)] + ) + + trade_msg_content = { + "trade_id": trade_id, + "trading_pair": trading_pair, + "trade_type": trade_type, + "amount": size, + "price": price, + "is_taker": is_taker, + } + trade_ob_msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + timestamp=backend_trade.executed_at * 1e-3, + content=trade_msg_content, + ) + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + fill_timestamp=backend_trade.executed_at * 1e-3, + fill_price=price, + fill_base_amount=size, + fill_quote_amount=price * size, + fee=fee, + ) + return trade_ob_msg, trade_update + + async def _listen_to_transactions_stream(self): + while True: + stream: UnaryStreamCall = await self._client.stream_txs() + try: + async for transaction in stream: + await self._parse_transaction_event(transaction=transaction) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + self.logger().info("Restarting transactions stream.") + stream.cancel() + + async def _parse_transaction_event(self, transaction: StreamTxsResponse): + order = self._gateway_order_tracker.get_fillable_order_by_hash(transaction_hash=transaction.hash) + if order is not None: + messages = json.loads(s=transaction.messages) + for message in messages: + if message["type"] in [MSG_CREATE_SPOT_LIMIT_ORDER, MSG_CANCEL_SPOT_ORDER, MSG_BATCH_UPDATE_ORDERS]: + safe_ensure_future(coro=self.get_order_status_update(in_flight_order=order)) + + def _get_trading_pair_from_market_id(self, market_id: str) -> str: + market = self._market_id_to_active_spot_markets[market_id] + trading_pair = combine_to_hb_trading_pair( + base=market.base_token_meta.symbol, quote=market.quote_token_meta.symbol + ) + return trading_pair + + def _get_exchange_trading_pair_from_market_info(self, market_info: Any) -> str: + return market_info.market_id + + def _get_maker_taker_exchange_fee_rates_from_market_info(self, market_info: Any) -> MakerTakerExchangeFeeRates: + fee_scaler = Decimal("1") - Decimal(market_info.service_provider_fee) + maker_fee = Decimal(market_info.maker_fee_rate) * fee_scaler + taker_fee = Decimal(market_info.taker_fee_rate) * fee_scaler + return MakerTakerExchangeFeeRates( + maker=maker_fee, taker=taker_fee, maker_flat_fees=[], taker_flat_fees=[] + ) + + def _convert_price_from_backend(self, price: str, market: SpotMarketInfo) -> Decimal: + scale = self._get_backend_price_scaler(market=market) + scaled_price = Decimal(price) * scale + return scaled_price + + async def _get_transaction_by_hash(self, transaction_hash: str) -> GetTxByTxHashResponse: + return await self._client.get_tx_by_hash(tx_hash=transaction_hash) + + def _get_market_ids(self) -> List[str]: + market_ids = [ + self._markets_info[trading_pair].market_id + for trading_pair in self._trading_pairs + ] + return market_ids + + async def _check_if_order_failed_based_on_transaction(self, transaction: GetTxByTxHashResponse, + order: GatewayInFlightOrder) -> bool: + order_hash = await order.get_exchange_order_id() + return order_hash.lower() not in transaction.data.data.decode().lower() + + @staticmethod + def _get_backend_price_scaler(market: SpotMarketInfo) -> Decimal: + scale = Decimal(f"1e{market.base_token_meta.decimals - market.quote_token_meta.decimals}") + return scale + + def _convert_quote_from_backend(self, quote_amount: str, market: SpotMarketInfo) -> Decimal: + scale = self._get_backend_denom_scaler(denom_meta=market.quote_token_meta) + scaled_quote_amount = Decimal(quote_amount) * scale + return scaled_quote_amount + + def _convert_size_from_backend(self, size: str, market: SpotMarketInfo) -> Decimal: + scale = self._get_backend_denom_scaler(denom_meta=market.base_token_meta) + size_tick_size = Decimal(market.min_quantity_tick_size) * scale + scaled_size = Decimal(size) * scale + return self._floor_to(scaled_size, size_tick_size) + + @staticmethod + def _get_backend_denom_scaler(denom_meta: TokenMeta): + scale = Decimal(f"1e{-denom_meta.decimals}") + return scale + + @staticmethod + def _floor_to(value: Decimal, target: Decimal) -> Decimal: + result = int(floor(value / target)) * target + return result + + @staticmethod + def _get_backend_order_type(in_flight_order: InFlightOrder) -> str: + return CLIENT_TO_BACKEND_ORDER_TYPES_MAP[(in_flight_order.trade_type, in_flight_order.order_type)] + + @staticmethod + async def _sleep(delay: float): + await asyncio.sleep(delay) + + @staticmethod + def _time() -> float: + return time.time() + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._client_config) + return gateway_instance diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_constants.py b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_constants.py new file mode 100644 index 0000000..dcb31e1 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_constants.py @@ -0,0 +1,46 @@ +from decimal import Decimal +from typing import Dict, Tuple + +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState + +NONCE_PATH = "injective/exchange/v1beta1/exchange" + +CONNECTOR_NAME = "injective" +REQUESTS_SKIP_STEP = 100 +LOST_ORDER_COUNT_LIMIT = 10 +ORDER_CHAIN_PROCESSING_TIMEOUT = 5 +MARKETS_UPDATE_INTERVAL = 8 * 60 * 60 +DEFAULT_SUB_ACCOUNT_SUFFIX = "000000000000000000000000" +CLIENT_TO_BACKEND_ORDER_TYPES_MAP: Dict[Tuple[TradeType, OrderType], str] = { + (TradeType.BUY, OrderType.LIMIT): "buy", + (TradeType.BUY, OrderType.LIMIT_MAKER): "buy_po", + (TradeType.BUY, OrderType.MARKET): "take_buy", + (TradeType.SELL, OrderType.LIMIT): "sell", + (TradeType.SELL, OrderType.LIMIT_MAKER): "sell_po", + (TradeType.SELL, OrderType.MARKET): "take_sell", +} + +BACKEND_TO_CLIENT_ORDER_STATE_MAP = { + "booked": OrderState.OPEN, + "partial_filled": OrderState.PARTIALLY_FILLED, + "filled": OrderState.FILLED, + "canceled": OrderState.CANCELED, +} + +INJ_TOKEN_DENOM = "inj" +MIN_GAS_PRICE_IN_INJ = ( + 5 * Decimal("1e8") # https://api.injective.exchange/#faq-3-how-can-i-calculate-the-gas-fees-in-inj +) +BASE_GAS = Decimal("100e3") +GAS_BUFFER = Decimal("20e3") +SPOT_SUBMIT_ORDER_GAS = Decimal("45e3") +SPOT_CANCEL_ORDER_GAS = Decimal("25e3") + +MSG_CREATE_SPOT_LIMIT_ORDER = "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder" +MSG_CANCEL_SPOT_ORDER = "/injective.exchange.v1beta1.MsgCancelSpotOrder" +MSG_BATCH_UPDATE_ORDERS = "/injective.exchange.v1beta1.MsgBatchUpdateOrders" + +ACC_NONCE_PATH_RATE_LIMIT_ID = "acc_nonce" +RATE_LIMITS = [RateLimit(limit_id=ACC_NONCE_PATH_RATE_LIMIT_ID, limit=100, time_interval=1)] diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_utils.py b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_utils.py new file mode 100644 index 0000000..1378694 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_utils.py @@ -0,0 +1,163 @@ +import logging +from decimal import Decimal +from math import floor +from typing import List, Union + +from pyinjective.composer import Composer as InjectiveComposer +from pyinjective.constant import Denom +from pyinjective.core.network import Network +from pyinjective.orderhash import OrderHashResponse, build_eip712_msg, hash_order +from pyinjective.proto.injective.exchange.v1beta1 import ( + exchange_pb2 as injective_dot_exchange_dot_v1beta1_dot_exchange__pb2, +) +from pyinjective.proto.injective.exchange.v1beta1.exchange_pb2 import DerivativeOrder, SpotOrder + +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_constants import ( + ACC_NONCE_PATH_RATE_LIMIT_ID, + NONCE_PATH, + RATE_LIMITS, +) +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class OrderHashManager: + def __init__(self, network: Network, sub_account_id: str): + self._sub_account_id = sub_account_id + self._network = network + self._sub_account_nonce = 0 + self._web_assistants_factory = WebAssistantsFactory(throttler=AsyncThrottler(rate_limits=RATE_LIMITS)) + + @property + def current_nonce(self) -> int: + return self._sub_account_nonce + + async def start(self): + url = f"{self._network.lcd_endpoint}/{NONCE_PATH}/{self._sub_account_id}" + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + res = await rest_assistant.execute_request(url=url, throttler_limit_id=ACC_NONCE_PATH_RATE_LIMIT_ID) + nonce = res["nonce"] + self._sub_account_nonce = nonce + 1 + + def compute_order_hashes( + self, spot_orders: List[SpotOrder], derivative_orders: List[DerivativeOrder] + ) -> OrderHashResponse: + order_hashes = OrderHashResponse(spot=[], derivative=[]) + + for o in spot_orders: + order_hash = hash_order(build_eip712_msg(o, self._sub_account_nonce)) + order_hashes.spot.append(order_hash) + self._sub_account_nonce += 1 + + for o in derivative_orders: + order_hash = hash_order(build_eip712_msg(o, self._sub_account_nonce)) + order_hashes.derivative.append(order_hash) + self._sub_account_nonce += 1 + + return order_hashes + + +class Composer(InjectiveComposer): + def DerivativeOrder( + self, + market_id: str, + subaccount_id: str, + fee_recipient: str, + price: float, + quantity: float, + trigger_price: float = 0, + **kwargs, + ): + """Changes the way the margin is computed to be synchronous with the approach used by Gateway.""" + # load denom metadata + denom = Denom.load_market(self.network, market_id) + logging.info("Loaded market metadata for:{}".format(denom.description)) + + if kwargs.get("is_reduce_only") is None: + margin = derivative_margin_to_backend_using_gateway_approach( + price, quantity, kwargs.get("leverage"), denom + ) + elif kwargs.get("is_reduce_only", True): + margin = 0 + else: + margin = derivative_margin_to_backend_using_gateway_approach( + price, quantity, kwargs.get("leverage"), denom + ) + + # prepare values + price = derivative_price_to_backend(price, denom) + trigger_price = derivative_price_to_backend(trigger_price, denom) + quantity = derivative_quantity_to_backend(quantity, denom) + + if kwargs.get("is_buy") and not kwargs.get("is_po"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.BUY + + elif not kwargs.get("is_buy") and not kwargs.get("is_po"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.SELL + + elif kwargs.get("is_buy") and kwargs.get("is_po"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.BUY_PO + + elif not kwargs.get("is_buy") and kwargs.get("is_po"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.SELL_PO + + elif kwargs.get("stop_buy"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.STOP_BUY + + elif kwargs.get("stop_sell"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.STOP_SEll + + elif kwargs.get("take_buy"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.TAKE_BUY + + elif kwargs.get("take_sell"): + order_type = injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderType.TAKE_SELL + + return injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.DerivativeOrder( + market_id=market_id, + order_info=injective_dot_exchange_dot_v1beta1_dot_exchange__pb2.OrderInfo( + subaccount_id=subaccount_id, + fee_recipient=fee_recipient, + price=str(price), + quantity=str(quantity), + ), + margin=str(margin), + order_type=order_type, + trigger_price=str(trigger_price), + ) + + +def derivative_margin_to_backend_using_gateway_approach( + price: float, quantity: float, leverage: float, denom: Denom +) -> int: + decimals = Decimal(18 + denom.quote) + price_big = Decimal(str(price)) * 10 ** decimals + quantity_big = Decimal(str(quantity)) * 10 ** decimals + leverage_big = Decimal(str(leverage)) * 10 ** decimals + decimals_big = 10 ** decimals + + numerator = price_big * quantity_big * decimals_big + denominator = leverage_big * decimals_big + res = int(numerator / denominator) + + return res + + +def floor_to(value: Union[float, Decimal], target: Union[float, Decimal]) -> Decimal: + value_tmp = Decimal(str(value)) + target_tmp = Decimal(str(target)) + result = int(floor(value_tmp / target_tmp)) * target_tmp + return result + + +def derivative_quantity_to_backend(quantity, denom) -> int: + quantity_tick_size = float(denom.min_quantity_tick_size) / pow(10, denom.base) + scale_quantity = Decimal(18 + denom.base) + exchange_quantity = floor_to(quantity, quantity_tick_size) * pow(Decimal(10), scale_quantity) + return int(exchange_quantity) + + +def derivative_price_to_backend(price, denom) -> int: + price_tick_size = Decimal(denom.min_price_tick_size) / pow(10, denom.quote) + exchange_price = floor_to(price, float(price_tick_size)) * pow(10, 18 + denom.quote) + return int(exchange_price) diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/kujira/__init__.py b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_api_data_source.py b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_api_data_source.py new file mode 100644 index 0000000..9eb1990 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_api_data_source.py @@ -0,0 +1,1023 @@ +import asyncio +import copy +from asyncio import Task +from enum import Enum +from time import time +from typing import Any, Dict, List, Optional, Tuple + +import jsonpickle +from _decimal import Decimal +from dotmap import DotMap + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base import ( + GatewayCLOBAPIDataSourceBase, +) +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_constants import ( + CONNECTOR, + DELAY_BETWEEN_RETRIES, + KUJIRA_NATIVE_TOKEN, + MARKETS_UPDATE_INTERVAL, + NUMBER_OF_RETRIES, + TIMEOUT, + UPDATE_ORDER_STATUS_INTERVAL, +) +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_helpers import ( + AsyncLock, + automatic_retry_with_timeout, + convert_market_name_to_hb_trading_pair, + generate_hash, +) +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_types import OrderStatus as KujiraOrderStatus +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type import in_flight_order +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import MakerTakerExchangeFeeRates, TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.events import AccountEvent, MarketEvent, OrderBookDataSourceEvent, OrderCancelledEvent +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather + + +class KujiraAPIDataSource(GatewayCLOBAPIDataSourceBase): + + def __init__( + self, + trading_pairs: List[str], + connector_spec: Dict[str, Any], + client_config_map: ClientConfigAdapter, + ): + super().__init__( + trading_pairs=trading_pairs, + connector_spec=connector_spec, + client_config_map=client_config_map + ) + + self._chain = connector_spec["chain"] + self._network = connector_spec["network"] + self._connector = CONNECTOR + self._owner_address = connector_spec["wallet_address"] + self._payer_address = self._owner_address + + self._markets = None + + self._user_balances = None + + self._tasks = DotMap({ + "update_order_status_loop": None + }, _dynamic=False) + + self._locks = DotMap({ + "place_order": AsyncLock(), + "place_orders": AsyncLock(), + "cancel_order": AsyncLock(), + "cancel_orders": AsyncLock(), + "settle_market_funds": AsyncLock(), + "settle_markets_funds": AsyncLock(), + "settle_all_markets_funds": AsyncLock(), + "all_active_orders": AsyncLock(), + }, _dynamic=False) + + self._gateway = GatewayHttpClient.get_instance(self._client_config) + + self._all_active_orders = None + + self._snapshots_min_update_interval = 30 + self._snapshots_max_update_interval = 60 + self.cancel_all_orders_timeout = TIMEOUT + + @property + def connector_name(self) -> str: + return CONNECTOR + + @property + def real_time_balance_update(self) -> bool: + return False + + @property + def events_are_streamed(self) -> bool: + return False + + @staticmethod + def supported_stream_events() -> List[Enum]: + return [ + MarketEvent.TradeUpdate, + MarketEvent.OrderUpdate, + MarketEvent.OrderFilled, + AccountEvent.BalanceEvent, + OrderBookDataSourceEvent.TRADE_EVENT, + OrderBookDataSourceEvent.DIFF_EVENT, + OrderBookDataSourceEvent.SNAPSHOT_EVENT, + ] + + def get_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.MARKET] + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def start(self): + self.logger().setLevel("INFO") + self.logger().debug("start: start") + + await super().start() + + self._tasks.update_order_status_loop = self._tasks.update_order_status_loop \ + or safe_ensure_future( + coro=self._update_all_active_orders() + ) + + self.logger().debug("start: end") + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def stop(self): + self.logger().debug("stop: start") + + await super().stop() + + self._tasks.update_order_status_loop and self._tasks.update_order_status_loop.cancel() + self._tasks.update_order_status_loop = None + + self.logger().debug("stop: end") + + async def place_order(self, order: GatewayInFlightOrder, **kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: + self.logger().debug("place_order: start") + + self._check_markets_initialized() or await self._update_markets() + + async with self._locks.place_order: + try: + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "trading_pair": order.trading_pair, + "address": self._owner_address, + "trade_type": order.trade_type, + "order_type": order.order_type, + "price": order.price, + "size": order.amount, + "client_order_id": order.client_order_id, + } + + self.logger().debug(f"""clob_place_order request:\n "{self._dump(request)}".""") + + response = await self._gateway_clob_place_order(request) + + self.logger().debug(f"""clob_place_order response:\n "{self._dump(response)}".""") + + transaction_hash = response["txHash"] + + order.exchange_order_id = response["id"] + + order.current_state = OrderState.CREATED + + self.logger().info( + f"""Order "{order.client_order_id}" / "{order.exchange_order_id}" successfully placed. Transaction hash: "{transaction_hash}".""" + ) + except Exception as exception: + self.logger().info( + f"""Placement of order "{order.client_order_id}" failed.""" + ) + + raise exception + + if transaction_hash in (None, ""): + raise Exception( + f"""Placement of order "{order.client_order_id}" failed. Invalid transaction hash: "{transaction_hash}".""" + ) + + misc_updates = DotMap({ + "creation_transaction_hash": transaction_hash, + }, _dynamic=False) + + self.logger().debug("place_order: end") + + await self._update_order_status() + + return order.exchange_order_id, misc_updates + + async def batch_order_create(self, orders_to_create: List[GatewayInFlightOrder]) -> List[PlaceOrderResult]: + self.logger().debug("batch_order_create: start") + + self._check_markets_initialized() or await self._update_markets() + + candidate_orders = [in_flight_order] + client_ids = [] + for order_to_create in orders_to_create: + if not order_to_create.client_order_id: + order_to_create.client_order_id = generate_hash(order_to_create) + client_ids.append(order_to_create.client_order_id) + + candidate_order = in_flight_order.InFlightOrder( + amount=order_to_create.amount, + client_order_id=order_to_create.client_order_id, + creation_timestamp=0, + order_type=order_to_create.order_type, + trade_type=order_to_create.trade_type, + trading_pair=order_to_create.trading_pair, + ) + candidate_orders.append(candidate_order) + + async with self._locks.place_orders: + try: + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "address": self._owner_address, + "orders_to_create": candidate_orders, + "orders_to_cancel": [], + } + + self.logger().debug(f"""clob_batch_order_modify request:\n "{self._dump(request)}".""") + + response = await self._gateway_clob_batch_order_modify(request) + + self.logger().debug(f"""clob_batch_order_modify response:\n "{self._dump(response)}".""") + + transaction_hash = response["txHash"] + + self.logger().info( + f"""Orders "{client_ids}" successfully placed. Transaction hash: {transaction_hash}.""" + ) + except Exception as exception: + self.logger().info( + f"""Placement of orders "{client_ids}" failed.""" + ) + + raise exception + + if transaction_hash in (None, ""): + raise RuntimeError( + f"""Placement of orders "{client_ids}" failed. Invalid transaction hash: "{transaction_hash}".""" + ) + + place_order_results = [] + for order_to_create, exchange_order_id in zip(orders_to_create, response["ids"]): + order_to_create.exchange_order_id = None + + place_order_results.append(PlaceOrderResult( + update_timestamp=time(), + client_order_id=order_to_create.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order_to_create.trading_pair, + misc_updates={ + "creation_transaction_hash": transaction_hash, + }, + exception=None, + )) + + self.logger().debug("batch_order_create: end") + + return place_order_results + + async def cancel_order(self, order: GatewayInFlightOrder) -> Tuple[bool, Optional[Dict[str, Any]]]: + active_order = self._gateway_order_tracker.active_orders.get(order.client_order_id) + + if active_order.exchange_order_id is None: + await self._update_order_status() + active_order = self._gateway_order_tracker.active_orders.get(order.client_order_id) + + fillable = self._gateway_order_tracker.all_fillable_orders_by_exchange_order_id.get( + active_order.exchange_order_id + ) + + if fillable and ( + active_order + ) and ( + active_order.current_state != OrderState.CANCELED + ) and ( + active_order.current_state != OrderState.FILLED + ) and ( + active_order.exchange_order_id + ): + self.logger().debug("cancel_order: start") + + self._check_markets_initialized() or await self._update_markets() + + await order.get_exchange_order_id() + + transaction_hash = None + + async with self._locks.cancel_order: + try: + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "trading_pair": order.trading_pair, + "address": self._owner_address, + "exchange_order_id": order.exchange_order_id, + } + + self.logger().debug(f"""clob_cancel_order request:\n "{self._dump(request)}".""") + + response = await self._gateway_clob_cancel_order(request) + + self.logger().debug(f"""clob_cancel_order response:\n "{self._dump(response)}".""") + + transaction_hash = response["txHash"] + + if transaction_hash in ("", None): + return False, DotMap({}, _dynamic=False) + + self.logger().info( + f"""Order "{order.client_order_id}" / "{order.exchange_order_id}" successfully cancelled. Transaction hash: "{transaction_hash}".""" + ) + except Exception as exception: + # await self.gateway_order_tracker.process_order_not_found(order.client_order_id) + if f"""Order "{order.exchange_order_id}" not found on markets""" in str(exception.args): + # order_update = self.get_order_status_update(order) + # self.gateway_order_tracker.process_order_update(order_update) + + self.logger().info( + f"""Order "{order.exchange_order_id}" not found on markets""" + ) + + return True, DotMap({}, _dynamic=False) + + elif 'No orders with the specified information exist' in str(exception.args): + self.logger().info( + f"""Order "{order.client_order_id}" / "{order.exchange_order_id}" already cancelled.""" + ) + + transaction_hash = "0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + else: + self.logger().info( + f"""Cancellation of order "{order.client_order_id}" / "{order.exchange_order_id}" failed.""" + ) + + raise exception + + misc_updates = DotMap({ + "cancelation_transaction_hash": transaction_hash, + }, _dynamic=False) + + self.logger().debug("cancel_order: end") + + order.cancel_tx_hash = transaction_hash + + await self._update_order_status() + + return True, misc_updates + + return False, DotMap({}, _dynamic=False) + + async def batch_order_cancel(self, orders_to_cancel: List[GatewayInFlightOrder]) -> List[CancelOrderResult]: + self.logger().debug("batch_order_cancel: start") + + self._check_markets_initialized() or await self._update_markets() + + client_ids = [order.client_order_id for order in orders_to_cancel] + + in_flight_orders_to_cancel = [ + self._gateway_order_tracker.fetch_tracked_order(client_order_id=order.client_order_id) + for order in orders_to_cancel + ] + exchange_order_ids_to_cancel = await safe_gather( + *[order.get_exchange_order_id() for order in in_flight_orders_to_cancel], + return_exceptions=True, + ) + found_orders_to_cancel = [ + order + for order, result in zip(orders_to_cancel, exchange_order_ids_to_cancel) + if not isinstance(result, asyncio.TimeoutError) + ] + + ids = [order.exchange_order_id for order in found_orders_to_cancel] + + async with self._locks.cancel_orders: + try: + + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "address": self._owner_address, + "orders_to_create": [], + "orders_to_cancel": found_orders_to_cancel, + } + + self.logger().debug(f"""clob_batch_order_moodify request:\n "{self._dump(request)}".""") + + response = await self._gateway_clob_batch_order_modify(request) + + self.logger().debug(f"""clob_batch_order_modify response:\n "{self._dump(response)}".""") + + transaction_hash = response["txHash"] + + self.logger().info( + f"""Orders "{client_ids}" / "{ids}" successfully cancelled. Transaction hash(es): "{transaction_hash}".""" + ) + except Exception as exception: + self.logger().info( + f"""Cancellation of orders "{client_ids}" / "{ids}" failed.""" + ) + + raise exception + + if transaction_hash in (None, ""): + raise RuntimeError( + f"""Cancellation of orders "{client_ids}" / "{ids}" failed. Invalid transaction hash: "{transaction_hash}".""" + ) + + cancel_order_results = [] + for order_to_cancel in orders_to_cancel: + cancel_order_results.append(CancelOrderResult( + client_order_id=order_to_cancel.client_order_id, + trading_pair=order_to_cancel.trading_pair, + misc_updates={ + "cancelation_transaction_hash": transaction_hash + }, + exception=None, + )) + + self.logger().debug("batch_order_cancel: end") + + return cancel_order_results + + async def get_last_traded_price(self, trading_pair: str) -> Decimal: + self.logger().debug("get_last_traded_price: start") + + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "trading_pair": trading_pair, + } + + self.logger().debug(f"""get_clob_ticker request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_ticker(request) + + self.logger().debug(f"""get_clob_ticker response:\n "{self._dump(response)}".""") + + ticker = DotMap(response, _dynamic=False).markets[trading_pair] + + ticker_price = Decimal(ticker.price) + + self.logger().debug("get_last_traded_price: end") + + return ticker_price + + async def get_order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + self.logger().debug("get_order_book_snapshot: start") + + request = { + "trading_pair": trading_pair, + "connector": self._connector, + "chain": self._chain, + "network": self._network, + } + + self.logger().debug(f"""get_clob_orderbook_snapshot request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_orderbook_snapshot(request) + + self.logger().debug(f"""get_clob_orderbook_snapshot response:\n "{self._dump(response)}".""") + + order_book = DotMap(response, _dynamic=False) + + price_scale = 1 + size_scale = 1 + + timestamp = time() + + bids = [] + asks = [] + for bid in order_book.buys: + bids.append((Decimal(bid.price) * price_scale, Decimal(bid.quantity) * size_scale)) + + for ask in order_book.sells: + asks.append((Decimal(ask.price) * price_scale, Decimal(ask.quantity) * size_scale)) + + snapshot = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "trading_pair": trading_pair, + "update_id": timestamp, + "bids": bids, + "asks": asks, + }, + timestamp=timestamp + ) + + self.logger().debug("get_order_book_snapshot: end") + + return snapshot + + async def get_account_balances(self) -> Dict[str, Dict[str, Decimal]]: + self.logger().debug("get_account_balances: start") + + if self._trading_pairs: + token_symbols = [] + + for trading_pair in self._trading_pairs: + symbols = trading_pair.split("-")[0], trading_pair.split("-")[1] + for symbol in symbols: + token_symbols.append(symbol) + + token_symbols.append(KUJIRA_NATIVE_TOKEN.symbol) + + request = { + "chain": self._chain, + "network": self._network, + "address": self._owner_address, + "connector": self._connector, + "token_symbols": list(set(token_symbols)) + } + else: + request = { + "chain": self._chain, + "network": self._network, + "address": self._owner_address, + "connector": self._connector, + "token_symbols": [] + } + + # self.logger().debug(f"""get_balances request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_balances(request) + + self.logger().debug(f"""get_balances response:\n "{self._dump(response)}".""") + + balances = DotMap(response, _dynamic=False).balances + + hb_balances = {} + for token, balance in balances.items(): + balance = Decimal(balance) + hb_balances[token] = DotMap({}, _dynamic=False) + hb_balances[token]["total_balance"] = balance + hb_balances[token]["available_balance"] = balance + + # self.logger().debug("get_account_balances: end") + + return hb_balances + + async def get_order_status_update(self, in_flight_order: GatewayInFlightOrder) -> OrderUpdate: + active_order = self.gateway_order_tracker.active_orders.get(in_flight_order.client_order_id) + + if active_order: + self.logger().debug("get_order_status_update: start") + + if active_order.current_state != OrderState.CANCELED: + await in_flight_order.get_exchange_order_id() + + request = { + "trading_pair": in_flight_order.trading_pair, + "chain": self._chain, + "network": self._network, + "connector": self._connector, + "address": self._owner_address, + "exchange_order_id": in_flight_order.exchange_order_id, + } + + self.logger().debug(f"""get_clob_order_status_updates request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_order_status_updates(request) + + self.logger().debug(f"""get_clob_order_status_updates response:\n "{self._dump(response)}".""") + + order_response = DotMap(response, _dynamic=False)["orders"] + order_update: OrderUpdate + if order_response: + order = order_response[0] + if order: + order_status = KujiraOrderStatus.to_hummingbot(KujiraOrderStatus.from_name(order.state)) + else: + order_status = in_flight_order.current_state + + open_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=time(), + new_state=order_status, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=in_flight_order.exchange_order_id, + misc_updates={ + "creation_transaction_hash": in_flight_order.creation_transaction_hash, + "cancelation_transaction_hash": in_flight_order.cancel_tx_hash, + }, + ) + + order_update = open_update + else: + canceled_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=time(), + new_state=OrderState.CANCELED, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=in_flight_order.exchange_order_id, + misc_updates={ + "creation_transaction_hash": in_flight_order.creation_transaction_hash, + "cancelation_transaction_hash": in_flight_order.cancel_tx_hash, + }, + ) + + order_update = canceled_update + + self.logger().debug("get_order_status_update: end") + return order_update + + no_update = OrderUpdate( + trading_pair=in_flight_order.trading_pair, + update_timestamp=time(), + new_state=in_flight_order.current_state, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=in_flight_order.exchange_order_id, + misc_updates={ + "creation_transaction_hash": in_flight_order.creation_transaction_hash, + "cancelation_transaction_hash": in_flight_order.cancel_tx_hash, + }, + ) + self.logger().debug("get_order_status_update: end") + return no_update + + async def get_all_order_fills(self, in_flight_order: GatewayInFlightOrder) -> List[TradeUpdate]: + if in_flight_order.exchange_order_id: + active_order = self.gateway_order_tracker.active_orders.get(in_flight_order.client_order_id) + + if active_order: + if active_order.current_state != OrderState.CANCELED: + self.logger().debug("get_all_order_fills: start") + + trade_update = None + + request = { + "trading_pair": in_flight_order.trading_pair, + "chain": self._chain, + "network": self._network, + "connector": self._connector, + "address": self._owner_address, + "exchange_order_id": in_flight_order.exchange_order_id, + } + + self.logger().debug(f"""get_clob_order_status_updates request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_order_status_updates(request) + + self.logger().debug(f"""get_clob_order_status_updates response:\n "{self._dump(response)}".""") + + orders = DotMap(response, _dynamic=False)["orders"] + + order = None + if len(orders): + order = orders[0] + + if order is not None: + order_status = KujiraOrderStatus.to_hummingbot(KujiraOrderStatus.from_name(order.state)) + else: + order_status = in_flight_order.current_state + + if order and order_status == OrderState.FILLED: + timestamp = time() + trade_id = str(timestamp) + + market = self._markets_info[in_flight_order.trading_pair] + + trade_update = TradeUpdate( + trade_id=trade_id, + client_order_id=in_flight_order.client_order_id, + exchange_order_id=in_flight_order.exchange_order_id, + trading_pair=in_flight_order.trading_pair, + fill_timestamp=timestamp, + fill_price=in_flight_order.price, + fill_base_amount=in_flight_order.amount, + fill_quote_amount=in_flight_order.price * in_flight_order.amount, + fee=TradeFeeBase.new_spot_fee( + fee_schema=TradeFeeSchema(), + trade_type=in_flight_order.trade_type, + flat_fees=[TokenAmount( + amount=Decimal(market.fees.taker), + token=market.quoteToken.symbol + )] + ), + ) + + self.logger().debug("get_all_order_fills: end") + + if trade_update: + return [trade_update] + + return [] + + def _get_trading_pair_from_market_info(self, market_info: Dict[str, Any]) -> str: + return market_info["hb_trading_pair"] + + def _get_exchange_base_quote_tokens_from_market_info(self, market_info: Dict[str, Any]) -> Tuple[str, str]: + base = market_info["baseToken"]["symbol"] + quote = market_info["quoteToken"]["symbol"] + + return base, quote + + def _get_last_trade_price_from_ticker_data(self, ticker_data: List[Dict[str, Any]]) -> Decimal: + raise NotImplementedError + + def is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + self.logger().debug("is_order_not_found_during_status_update_error: start") + + output = str(status_update_exception).startswith("No update found for order") + + self.logger().debug("is_order_not_found_during_status_update_error: end") + + return output + + def is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + self.logger().debug("is_order_not_found_during_cancelation_error: start") + + output = False + + self.logger().debug("is_order_not_found_during_cancelation_error: end") + + return output + + async def check_network_status(self) -> NetworkStatus: + # self.logger().debug("check_network_status: start") + + try: + status = await self._gateway_ping_gateway() + + if status: + return NetworkStatus.CONNECTED + else: + return NetworkStatus.NOT_CONNECTED + except asyncio.CancelledError: + raise + except Exception as exception: + self.logger().error(exception) + + return NetworkStatus.NOT_CONNECTED + + # self.logger().debug("check_network_status: end") + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + self.logger().debug("is_cancel_request_in_exchange_synchronous: start") + + output = True + + self.logger().debug("is_cancel_request_in_exchange_synchronous: end") + + return output + + def _check_markets_initialized(self) -> bool: + # self.logger().debug("_check_markets_initialized: start") + + output = self._markets is not None and bool(self._markets) + + # self.logger().debug("_check_markets_initialized: end") + + return output + + async def _update_markets(self): + self.logger().debug("_update_markets: start") + + if self._markets_info: + self._markets_info.clear() + + all_markets_map = DotMap() + + if self._trading_pairs: + for trading_pair in self._trading_pairs: + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + "trading_pair": trading_pair + } + + self.logger().debug(f"""get_clob_markets request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_markets(request) + + self.logger().debug(f"""get_clob_markets response:\n "{self._dump(response)}".""") + + market = DotMap(response, _dynamic=False).markets[trading_pair] + market["hb_trading_pair"] = convert_market_name_to_hb_trading_pair(market.name) + all_markets_map[trading_pair] = market + self._markets_info[market["hb_trading_pair"]] = market + else: + request = { + "connector": self._connector, + "chain": self._chain, + "network": self._network, + } + + self.logger().debug(f"""get_clob_markets request:\n "{self._dump(request)}".""") + + response = await self._gateway_get_clob_markets(request) + + self.logger().debug(f"""get_clob_markets response:\n "{self._dump(response)}".""") + + self._markets = DotMap(response, _dynamic=False).markets + + for market in self._markets.values(): + market["hb_trading_pair"] = convert_market_name_to_hb_trading_pair(market.name) + all_markets_map[market.name] = market + self._markets_info[market["hb_trading_pair"]] = market + + self._markets = all_markets_map + + self.logger().debug("_update_markets: end") + + return self._markets + + def _parse_trading_rule(self, trading_pair: str, market_info: Any) -> TradingRule: + self.logger().debug("_parse_trading_rule: start") + + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(market_info.minimumOrderSize), + min_price_increment=Decimal(market_info.minimumPriceIncrement), + min_base_amount_increment=Decimal(market_info.minimumBaseAmountIncrement), + min_quote_amount_increment=Decimal(market_info.minimumQuoteAmountIncrement), + ) + + self.logger().debug("_parse_trading_rule: end") + + return trading_rule + + def _get_exchange_trading_pair_from_market_info(self, market_info: Any) -> str: + self.logger().debug("_get_exchange_trading_pair_from_market_info: start") + + output = market_info.id + + self.logger().debug("_get_exchange_trading_pair_from_market_info: end") + + return output + + def _get_maker_taker_exchange_fee_rates_from_market_info(self, market_info: Any) -> MakerTakerExchangeFeeRates: + self.logger().debug("_get_maker_taker_exchange_fee_rates_from_market_info: start") + + fee_scaler = Decimal("1") - Decimal(market_info.fees.serviceProvider) + maker_fee = Decimal(market_info.fees.maker) * fee_scaler + taker_fee = Decimal(market_info.fees.taker) * fee_scaler + + output = MakerTakerExchangeFeeRates( + maker=maker_fee, + taker=taker_fee, + maker_flat_fees=[], + taker_flat_fees=[] + ) + + self.logger().debug("_get_maker_taker_exchange_fee_rates_from_market_info: end") + + return output + + async def _update_markets_loop(self): + self.logger().debug("_update_markets_loop: start") + + while True: + self.logger().debug("_update_markets_loop: start loop") + + await self._update_markets() + await asyncio.sleep(MARKETS_UPDATE_INTERVAL) + + self.logger().debug("_update_markets_loop: end loop") + + async def _check_if_order_failed_based_on_transaction( + self, + transaction: Any, + order: GatewayInFlightOrder + ) -> bool: + order_id = await order.get_exchange_order_id() + + return order_id.lower() not in transaction.data.lower() + + @staticmethod + def _dump(target: Any): + try: + return jsonpickle.encode(target, unpicklable=True, indent=2) + except (Exception,): + return target + + @staticmethod + def _create_task(target: Any) -> Task: + return asyncio.ensure_future(target) + + @staticmethod + def _create_event_loop(): + return asyncio.get_event_loop() + + def _create_and_run_task(self, target: Any): + event_loop = self._create_event_loop() + task = self._create_task(target) + if not event_loop.is_running(): + event_loop.run_until_complete(task) + + async def _update_order_status(self): + async with self._locks.all_active_orders: + self._all_active_orders = ( + self._gateway_order_tracker.active_orders if self._gateway_order_tracker else {} + ) + + orders = copy.copy(self._all_active_orders).values() + + for order in orders: + if order.exchange_order_id is None: + continue + + request = { + "trading_pair": order.trading_pair, + "chain": self._chain, + "network": self._network, + "connector": self._connector, + "address": self._owner_address, + "exchange_order_id": order.exchange_order_id, + } + + response = await self._gateway_get_clob_order_status_updates(request) + + try: + if response["orders"] is not None and len(response['orders']) and response["orders"][0] is not None and response["orders"][0]["state"] != order.current_state: + updated_order = response["orders"][0] + + message = { + "trading_pair": order.trading_pair, + "update_timestamp": + updated_order["updatedAt"] if len(updated_order["updatedAt"]) else time(), + "new_state": updated_order["state"], + } + + if updated_order["state"] in { + OrderState.PENDING_CREATE, + OrderState.OPEN, + OrderState.PARTIALLY_FILLED, + OrderState.PENDING_CANCEL, + }: + + self._publisher.trigger_event(event_tag=MarketEvent.OrderUpdate, message=message) + + elif updated_order["state"] == OrderState.FILLED.name: + message = { + "timestamp": + updated_order["updatedAt"] if len(updated_order["updatedAt"]) else time(), + "order_id": order.client_order_id, + "trading_pair": order.trading_pair, + "trade_type": order.trade_type, + "order_type": order.order_type, + "price": order.price, + "amount": order.amount, + "trade_fee": '', + "exchange_trade_id": "", + "exchange_order_id": order.exchange_order_id, + } + + self._publisher.trigger_event(event_tag=MarketEvent.OrderFilled, message=message) + + elif updated_order["state"] == OrderState.CANCELED.name: + + message = { + "timestamp": + updated_order["updatedAt"] if len(updated_order["updatedAt"]) else time(), + "order_id": order.client_order_id, + "exchange_order_id": order.exchange_order_id, + } + + self._publisher.trigger_event(event_tag=OrderCancelledEvent, message=message) + + except Exception: + raise self.logger().exception(Exception) + + async def _update_all_active_orders(self): + while True: + await self._update_order_status() + await asyncio.sleep(UPDATE_ORDER_STATUS_INTERVAL) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_ping_gateway(self, _request=None): + return await self._gateway.ping_gateway() + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_get_clob_markets(self, request): + return await self._gateway.get_clob_markets(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_get_clob_orderbook_snapshot(self, request): + return await self._gateway.get_clob_orderbook_snapshot(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_get_clob_ticker(self, request): + return await self._gateway.get_clob_ticker(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_get_balances(self, request): + return await self._gateway.get_balances(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_clob_place_order(self, request): + return await self._gateway.clob_place_order(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_clob_cancel_order(self, request): + return await self._gateway.clob_cancel_order(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_clob_batch_order_modify(self, request): + return await self._gateway.clob_batch_order_modify(**request) + + @automatic_retry_with_timeout(retries=NUMBER_OF_RETRIES, delay=DELAY_BETWEEN_RETRIES, timeout=TIMEOUT) + async def _gateway_get_clob_order_status_updates(self, request): + return await self._gateway.get_clob_order_status_updates(**request) diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_constants.py b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_constants.py new file mode 100644 index 0000000..d7372da --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_constants.py @@ -0,0 +1,17 @@ +from dotmap import DotMap + +KUJIRA_NATIVE_TOKEN = DotMap({ + "id": "ukuji", + "name": "Kuji", + "symbol": "KUJI", + "decimals": "6", +}, _dynamic=False) + +CONNECTOR = "kujira" + +MARKETS_UPDATE_INTERVAL = 8 * 60 * 60 +UPDATE_ORDER_STATUS_INTERVAL = 1 + +NUMBER_OF_RETRIES = 3 +DELAY_BETWEEN_RETRIES = 3 +TIMEOUT = 60 diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_helpers.py b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_helpers.py new file mode 100644 index 0000000..933764b --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_helpers.py @@ -0,0 +1,75 @@ +import asyncio +import hashlib +import traceback +from datetime import datetime +from typing import Any, List + +import jsonpickle + + +def generate_hash(input: Any) -> str: + return generate_hashes([input])[0] + + +def generate_hashes(inputs: List[Any]) -> List[str]: + hashes = [] + salt = datetime.now() + + for input in inputs: + serialized = jsonpickle.encode(input, unpicklable=True) + hasher = hashlib.md5() + target = f"{salt}{serialized}".encode("utf-8") + hasher.update(target) + hash = hasher.hexdigest() + + hashes.append(hash) + + return hashes + + +def convert_hb_trading_pair_to_market_name(trading_pair: str) -> str: + return trading_pair.replace("-", "/") + + +def convert_market_name_to_hb_trading_pair(market_name: str) -> str: + return market_name.replace("/", "-") + + +def automatic_retry_with_timeout(retries=0, delay=0, timeout=None): + def decorator(function): + async def wrapper(*args, **kwargs): + errors = [] + + for i in range(retries + 1): + try: + result = await asyncio.wait_for(function(*args, **kwargs), timeout=timeout) + + return result + except Exception as e: + tb_str = traceback.format_exception(type(e), value=e, tb=e.__traceback__) + errors.append(''.join(tb_str)) + + if i < retries: + await asyncio.sleep(delay) + + error_message = f"Function failed after {retries} attempts. Here are the errors:\n" + "\n".join(errors) + + raise Exception(error_message) + + wrapper.original = function + + return wrapper + + return decorator + + +class AsyncLock: + def __init__(self): + self._lock = asyncio.Lock() + + async def __aenter__(self): + await self._lock.acquire() + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + self._lock.release() diff --git a/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_types.py b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_types.py new file mode 100644 index 0000000..60c584c --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/data_sources/kujira/kujira_types.py @@ -0,0 +1,136 @@ +from enum import Enum + +from hummingbot.core.data_type.common import OrderType as HummingBotOrderType, TradeType as HummingBotOrderSide +from hummingbot.core.data_type.in_flight_order import OrderState as HummingBotOrderStatus + + +class OrderStatus(Enum): + OPEN = "OPEN", + CANCELLED = "CANCELLED", + PARTIALLY_FILLED = "PARTIALLY_FILLED", + FILLED = "FILLED", + CREATION_PENDING = "CREATION_PENDING", + CANCELLATION_PENDING = "CANCELLATION_PENDING", + UNKNOWN = "UNKNOWN" + + @staticmethod + def from_name(name: str): + if name == "OPEN": + return OrderStatus.OPEN + elif name == "CANCELLED": + return OrderStatus.CANCELLED + elif name == "PARTIALLY_FILLED": + return OrderStatus.PARTIALLY_FILLED + elif name == "FILLED": + return OrderStatus.FILLED + elif name == "CREATION_PENDING": + return OrderStatus.CREATION_PENDING + elif name == "CANCELLATION_PENDING": + return OrderStatus.CANCELLATION_PENDING + else: + raise ValueError(f"Unknown order status: {name}") + + @staticmethod + def from_hummingbot(target: HummingBotOrderStatus): + if target == HummingBotOrderStatus.PENDING_CREATE: + return OrderStatus.CREATION_PENDING + elif target == HummingBotOrderStatus.OPEN: + return OrderStatus.OPEN + elif target == HummingBotOrderStatus.PENDING_CANCEL: + return OrderStatus.CANCELLATION_PENDING + elif target == HummingBotOrderStatus.CANCELED: + return OrderStatus.CANCELLED + elif target == HummingBotOrderStatus.PARTIALLY_FILLED: + return OrderStatus.PARTIALLY_FILLED + elif target == HummingBotOrderStatus.FILLED: + return OrderStatus.FILLED + else: + raise ValueError(f"Unknown order status: {target}") + + @staticmethod + def to_hummingbot(self): + if self == OrderStatus.OPEN: + return HummingBotOrderStatus.OPEN + elif self == OrderStatus.CANCELLED: + return HummingBotOrderStatus.CANCELED + elif self == OrderStatus.PARTIALLY_FILLED: + return HummingBotOrderStatus.PARTIALLY_FILLED + elif self == OrderStatus.FILLED: + return HummingBotOrderStatus.FILLED + elif self == OrderStatus.CREATION_PENDING: + return HummingBotOrderStatus.PENDING_CREATE + elif self == OrderStatus.CANCELLATION_PENDING: + return HummingBotOrderStatus.PENDING_CANCEL + else: + raise ValueError(f"Unknown order status: {self}") + + +class OrderType(Enum): + MARKET = 'MARKET', + LIMIT = 'LIMIT', + + @staticmethod + def from_name(name: str): + if name == "MARKET": + return OrderType.MARKET + elif name == "LIMIT": + return OrderType.LIMIT + else: + raise ValueError(f"Unknown order type: {name}") + + @staticmethod + def from_hummingbot(target: HummingBotOrderType): + if target == HummingBotOrderType.LIMIT: + return OrderType.LIMIT + if target == HummingBotOrderType.MARKET: + return OrderType.MARKET + else: + raise ValueError(f'Unrecognized order type "{target}".') + + @staticmethod + def to_hummingbot(self): + if self == OrderType.LIMIT: + return HummingBotOrderType.LIMIT + if self == OrderType.MARKET: + return HummingBotOrderType.MARKET + else: + raise ValueError(f'Unrecognized order type "{self}".') + + +class OrderSide(Enum): + BUY = 'BUY', + SELL = 'SELL', + + @staticmethod + def from_name(name: str): + if name == "BUY": + return OrderSide.BUY + elif name == "SELL": + return OrderSide.SELL + else: + raise ValueError(f"Unknown order side: {name}") + + @staticmethod + def from_hummingbot(target: HummingBotOrderSide): + if target == HummingBotOrderSide.BUY: + return OrderSide.BUY + elif target == HummingBotOrderSide.SELL: + return OrderSide.SELL + else: + raise ValueError(f'Unrecognized order side "{target}".') + + def to_hummingbot(self): + if self == OrderSide.BUY: + return HummingBotOrderSide.BUY + elif self == OrderSide.SELL: + return HummingBotOrderSide.SELL + else: + raise ValueError(f'Unrecognized order side "{self}".') + + +class TickerSource(Enum): + ORDER_BOOK_SAP = "orderBookSimpleAveragePrice", + ORDER_BOOK_WAP = "orderBookWeightedAveragePrice", + ORDER_BOOK_VWAP = "orderBookVolumeWeightedAveragePrice", + LAST_FILLED_ORDER = "lastFilledOrder", + NOMICS = "nomics", diff --git a/hummingbot/connector/gateway/clob_spot/gateway_clob_api_order_book_data_source.py b/hummingbot/connector/gateway/clob_spot/gateway_clob_api_order_book_data_source.py new file mode 100644 index 0000000..ce571a3 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/gateway_clob_api_order_book_data_source.py @@ -0,0 +1,69 @@ +import asyncio +from typing import Dict, List, Optional + +from hummingbot.connector.gateway.clob_spot.data_sources.clob_api_data_source_base import CLOBAPIDataSourceBase +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import OrderBookDataSourceEvent + + +class GatewayCLOBSPOTAPIOrderBookDataSource(OrderBookTrackerDataSource): + _logger = None + + def __init__(self, trading_pairs: List[str], api_data_source: CLOBAPIDataSourceBase): + super().__init__(trading_pairs=trading_pairs) + self._api_data_source = api_data_source + self._forwarders: List[EventForwarder] = [] + + self._add_forwarders() + + async def get_last_traded_prices( + self, + trading_pairs: List[str], + domain: Optional[str] = None, + ) -> Dict[str, float]: + last_traded_prices = { + trading_pair: float(await self._api_data_source.get_last_traded_price(trading_pair=trading_pair)) + for trading_pair in trading_pairs + } + return last_traded_prices + + async def listen_for_subscriptions(self): + """Not used.""" + pass + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """Not used.""" + pass + + def _add_forwarders(self): + event_forwarder = EventForwarder(to_function=self._message_queue[self._snapshot_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._message_queue[self._diff_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.DIFF_EVENT, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._message_queue[self._trade_messages_queue_key].put_nowait) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=event_forwarder) + + async def _parse_trade_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + """Injective fires two trade updates per transaction. + + Generally, CEXes publish trade updates from the perspective of the taker, so we ignore makers. + """ + if raw_message.content["is_taker"]: + message_queue.put_nowait(raw_message) + + async def _parse_order_book_diff_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + message_queue.put_nowait(raw_message) + + async def _parse_order_book_snapshot_message(self, raw_message: OrderBookMessage, message_queue: asyncio.Queue): + message_queue.put_nowait(raw_message) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot = await self._api_data_source.get_order_book_snapshot(trading_pair=trading_pair) + return snapshot diff --git a/hummingbot/connector/gateway/clob_spot/gateway_clob_spot.py b/hummingbot/connector/gateway/clob_spot/gateway_clob_spot.py new file mode 100644 index 0000000..06510d9 --- /dev/null +++ b/hummingbot/connector/gateway/clob_spot/gateway_clob_spot.py @@ -0,0 +1,746 @@ +import asyncio +import math +from copy import deepcopy +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.exchange_base import TradeType +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base import CLOBAPIDataSourceBase +from hummingbot.connector.gateway.clob_spot.gateway_clob_api_order_book_data_source import ( + GatewayCLOBSPOTAPIOrderBookDataSource, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import ( + AddedToCostTradeFee, + DeductedFromReturnsTradeFee, + MakerTakerExchangeFeeRates, + TradeFeeBase, +) +from hummingbot.core.event.event_forwarder import EventForwarder +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GatewayCLOBSPOT(ExchangePyBase): + def __init__( + self, + client_config_map: "ClientConfigAdapter", + api_data_source: CLOBAPIDataSourceBase, + connector_name: str, + chain: str, + network: str, + address: str, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + ): + self._name = "_".join([connector_name, chain, network]) + self._connector_name = connector_name + + self._chain = chain + self._network = network + self._address = address + self._trading_pairs = trading_pairs or [] + self._trading_required = trading_required + self._api_data_source = api_data_source + self._real_time_balance_update = self._api_data_source.real_time_balance_update + self._trading_fees: Dict[str, MakerTakerExchangeFeeRates] = {} + self._last_received_message_timestamp = 0 + self._forwarders: List[EventForwarder] = [] + self._nonce_provider: Optional[NonceCreator] = NonceCreator.for_milliseconds() + + self._add_forwarders() + + self.has_started = False + + super().__init__(client_config_map) + + @property + def connector_name(self): + """ + This returns the name of connector/protocol to be connected to on Gateway. + """ + return self._connector_name + + @property + def chain(self): + return self._chain + + @property + def network(self): + return self._network + + @property + def name(self) -> str: + return self._name + + @property + def domain(self) -> str: + return "" + + @property + def authenticator(self) -> Optional[AuthBase]: + return None + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return [] + + @property + def address(self): + return self._address + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def client_order_id_max_length(self) -> Optional[int]: + return None + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return self._api_data_source.is_cancel_request_in_exchange_synchronous + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + @property + def check_network_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_pairs_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_rules_request_path(self): + """Not used.""" + raise NotImplementedError + + @property + def trading_fees(self) -> Mapping[str, MakerTakerExchangeFeeRates]: + return deepcopy(self._trading_fees) + + @property + def status_dict(self) -> Dict[str, bool]: + sd = super().status_dict + sd["api_data_source_initialized"] = self._api_data_source.ready + return sd + + def start(self, *args, **kwargs): + super().start(**kwargs) + safe_ensure_future(self.start_network()) + safe_ensure_future(self._api_data_source.start()) + + def stop(self, *args, **kwargs): + super().stop(**kwargs) + safe_ensure_future(self._api_data_source.stop()) + + async def start_network(self): + if not self.has_started: + await self._api_data_source.start() + await super().start_network() + self.has_started = True + + async def stop_network(self): + await super().stop_network() + await self._api_data_source.stop() + self.has_started = False + + @property + def ready(self) -> bool: + return super().ready + + def supported_order_types(self) -> List[OrderType]: + return self._api_data_source.get_supported_order_types() + + def start_tracking_order( + self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + **kwargs, + ): + self._order_tracker.start_tracking_order( + GatewayInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp, + ) + ) + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = self._api_data_source.get_client_order_id( + is_buy=True, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = self._api_data_source.get_client_order_id( + is_buy=False, + trading_pair=trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price, + **kwargs)) + return order_id + + def batch_order_create(self, orders_to_create: List[LimitOrder]) -> List[LimitOrder]: + """ + Issues a batch order creation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_create: A list of LimitOrder objects representing the orders to create. The order IDs + can be blanc. + :returns: A tuple composed of LimitOrder objects representing the created orders, complete with the generated + order IDs. + """ + orders_with_ids_to_create = [] + for order in orders_to_create: + client_order_id = self._api_data_source.get_client_order_id( + is_buy=order.is_buy, + trading_pair=order.trading_pair, + hbot_order_id_prefix=self.client_order_id_prefix, + max_id_len=self.client_order_id_max_length, + ) + orders_with_ids_to_create.append( + LimitOrder( + client_order_id=client_order_id, + trading_pair=order.trading_pair, + is_buy=order.is_buy, + base_currency=order.base_currency, + quote_currency=order.quote_currency, + price=order.price, + quantity=order.quantity, + filled_quantity=order.filled_quantity, + creation_timestamp=order.creation_timestamp, + status=order.status, + ) + ) + safe_ensure_future(self._execute_batch_order_create(orders_to_create=orders_with_ids_to_create)) + return orders_with_ids_to_create + + def batch_order_cancel(self, orders_to_cancel: List[LimitOrder]): + """ + Issues a batch order cancelation as a single API request for exchanges that implement this feature. The default + implementation of this method is to send the requests discretely (one by one). + :param orders_to_cancel: A list of the orders to cancel. + """ + safe_ensure_future(coro=self._execute_batch_cancel(orders_to_cancel=orders_to_cancel)) + + async def _execute_batch_order_create(self, orders_to_create: List[LimitOrder]): + in_flight_orders_to_create = [] + for order in orders_to_create: + valid_order = await self._start_tracking_and_validate_order( + trade_type=TradeType.BUY if order.is_buy else TradeType.SELL, + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.quantity, + order_type=OrderType.LIMIT, + price=order.price, + ) + if valid_order is not None: + in_flight_orders_to_create.append(valid_order) + try: + place_order_results = await self._api_data_source.batch_order_create( + orders_to_create=in_flight_orders_to_create + ) + for place_order_result, in_flight_order in ( + zip(place_order_results, in_flight_orders_to_create) + ): + if place_order_result.exception: + self._on_order_creation_failure( + order_id=in_flight_order.client_order_id, + trading_pair=in_flight_order.trading_pair, + amount=in_flight_order.amount, + trade_type=in_flight_order.trade_type, + order_type=in_flight_order.order_type, + price=in_flight_order.price, + exception=place_order_result.exception, + ) + else: + self._update_order_after_creation_success( + exchange_order_id=place_order_result.exchange_order_id, + order=in_flight_order, + update_timestamp=self.current_timestamp, + ) + except asyncio.CancelledError: + raise + except Exception as ex: + self.logger().network("Batch order create failed.") + for order in orders_to_create: + self._on_order_creation_failure( + order_id=order.client_order_id, + trading_pair=order.trading_pair, + amount=order.quantity, + trade_type=TradeType.BUY if order.is_buy else TradeType.SELL, + order_type=OrderType.LIMIT, + price=order.price, + exception=ex, + ) + + async def _start_tracking_and_validate_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + **kwargs + ) -> Optional[InFlightOrder]: + trading_rule = self._trading_rules[trading_pair] + + if order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER]: + price = self.quantize_order_price(trading_pair, price) + quantized_amount = self.quantize_order_amount(trading_pair=trading_pair, amount=amount) + + self.start_tracking_order( + order_id=order_id, + exchange_order_id=None, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=quantized_amount, + **kwargs, + ) + order = self._order_tracker.active_orders[order_id] + + if not price or price.is_nan() or price == s_decimal_0: + current_price: Decimal = self.get_price(trading_pair, False) + notional_size = current_price * quantized_amount + else: + notional_size = price * quantized_amount + + if order_type not in self.supported_order_types(): + self.logger().error(f"{order_type} is not in the list of supported order types") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + + elif quantized_amount < trading_rule.min_order_size: + self.logger().warning(f"{trade_type.name.title()} order amount {amount} is lower than the minimum order " + f"size {trading_rule.min_order_size}. The order will not be created, increase the " + f"amount to be higher than the minimum order size.") + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + order = None + elif notional_size < trading_rule.min_notional_size: + self.logger().warning(f"{trade_type.name.title()} order notional {notional_size} is lower than the " + f"minimum notional size {trading_rule.min_notional_size}. The order will not be " + f"created. Increase the amount or the price to be higher than the minimum notional.") + self._update_order_after_failure(order_id=order_id, trading_pair=trading_pair) + order = None + + return order + + def _update_order_after_creation_success( + self, exchange_order_id: str, order: InFlightOrder, update_timestamp: float + ): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=str(exchange_order_id), + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=OrderState.OPEN, + ) + self._order_tracker.process_order_update(order_update) + + def _on_order_creation_failure( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Optional[Decimal], + exception: Exception, + ): + self.logger().network( + f"Error submitting {trade_type.name.lower()} {order_type.name.upper()} order to {self.name_cap} for " + f"{amount} {trading_pair} {price}.", + exc_info=exception, + app_warning_msg=f"Failed to submit buy order to {self.name_cap}. Check API key and network connection." + ) + self._update_order_after_creation_failure(order_id=order_id, trading_pair=trading_pair) + + def _update_order_after_creation_failure(self, order_id: str, trading_pair: str): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order_id, + trading_pair=trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.FAILED, + ) + self._order_tracker.process_order_update(order_update) + + async def _execute_batch_cancel(self, orders_to_cancel: List[LimitOrder]) -> List[CancellationResult]: + results = [] + tracked_orders_to_cancel = [] + + for order in orders_to_cancel: + tracked_order = self._order_tracker.fetch_tracked_order(client_order_id=order.client_order_id) + if tracked_order is not None: + tracked_orders_to_cancel.append(tracked_order) + else: + results.append(CancellationResult(order_id=order.client_order_id, success=False)) + + results.extend(await self._execute_batch_order_cancel(orders_to_cancel=tracked_orders_to_cancel)) + + return results + + async def _execute_batch_order_cancel(self, orders_to_cancel: List[InFlightOrder]) -> List[CancellationResult]: + try: + cancel_order_results = await self._api_data_source.batch_order_cancel(orders_to_cancel=orders_to_cancel) + cancelation_results = [] + for cancel_order_result in cancel_order_results: + success = True + if cancel_order_result.not_found: + self.logger().warning( + f"Failed to cancel the order {cancel_order_result.client_order_id} due to the order" + f" not being found." + ) + await self._order_tracker.process_order_not_found( + client_order_id=cancel_order_result.client_order_id + ) + success = False + elif cancel_order_result.exception is not None: + self.logger().error( + f"Failed to cancel order {cancel_order_result.client_order_id}", + exc_info=cancel_order_result.exception, + ) + success = False + else: + order_update: OrderUpdate = OrderUpdate( + client_order_id=cancel_order_result.client_order_id, + trading_pair=cancel_order_result.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + ) + self._order_tracker.process_order_update(order_update) + cancelation_results.append( + CancellationResult(order_id=cancel_order_result.client_order_id, success=success) + ) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + f"Failed to cancel orders {', '.join([o.client_order_id for o in orders_to_cancel])}", + exc_info=True, + ) + cancelation_results = [ + CancellationResult(order_id=order.client_order_id, success=False) + for order in orders_to_cancel + ] + + return cancelation_results + + def _update_order_after_cancelation_success(self, order: InFlightOrder): + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + ) + self._order_tracker.process_order_update(order_update) + + def _add_forwarders(self): + event_forwarder = EventForwarder(to_function=self._process_trade_update) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_order_update) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=event_forwarder) + + event_forwarder = EventForwarder(to_function=self._process_balance_event) + self._forwarders.append(event_forwarder) + self._api_data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=event_forwarder) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + data_source = GatewayCLOBSPOTAPIOrderBookDataSource( + trading_pairs=self.trading_pairs, api_data_source=self._api_data_source + ) + return data_source + + def _create_order_tracker(self) -> ClientOrderTracker: + tracker = GatewayOrderTracker(connector=self, lost_order_count_limit=10) + self._api_data_source.gateway_order_tracker = tracker + return tracker + + def _is_user_stream_initialized(self): + return self.trading_pair_symbol_map_ready() # if ready, then self._api_data_source is initialized + + def _create_user_stream_tracker_task(self): + return None + + def _create_user_stream_tracker(self): + return None + + def _create_user_stream_data_source(self): + return None + + async def _get_last_traded_price(self, trading_pair: str) -> float: + price = await self._api_data_source.get_last_traded_price(trading_pair=trading_pair) + return float(price) + + async def _update_balances(self): + balances = await self._api_data_source.get_account_balances() + self._account_balances.clear() + self._account_available_balances.clear() + for asset, balance in balances.items(): + self._account_balances[asset] = Decimal(balance["total_balance"]) + self._account_available_balances[asset] = Decimal(balance["available_balance"]) + + async def _make_network_check_request(self): + network_status = await self._api_data_source.check_network_status() + if network_status != NetworkStatus.CONNECTED: + raise IOError("The API data source has lost connection.") + + async def _make_trading_rules_request(self) -> Mapping[str, str]: + return await self._make_trading_pairs_request() + + async def _make_trading_pairs_request(self) -> Mapping[str, str]: + symbol_map = await self._api_data_source.get_symbol_map() + return symbol_map + + async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]: + trading_rules = await self._api_data_source.get_trading_rules() + return list(trading_rules.values()) + + async def _request_order_status(self, tracked_order: GatewayInFlightOrder) -> OrderUpdate: + order_update = await self._api_data_source.get_order_status_update(in_flight_order=tracked_order) + return order_update + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = await self._api_data_source.get_all_order_fills(in_flight_order=order) + return trade_updates + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]): + self._set_trading_pair_symbol_map(exchange_info) + + async def _update_trading_fees(self): + self._trading_fees = await self._api_data_source.get_trading_fees() + + async def _update_time_synchronizer(self, pass_on_non_cancelled_error: bool = False): + pass + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = is_maker or (order_type is OrderType.LIMIT_MAKER) + trading_pair = combine_to_hb_trading_pair(base=base_currency, quote=quote_currency) + if trading_pair in self._trading_fees: + fees_data = self._trading_fees[trading_pair] + fee_value = Decimal(fees_data.maker) if is_maker else Decimal(fees_data.taker) + flat_fees = fees_data.maker_flat_fees if is_maker else fees_data.taker_flat_fees + if order_side == TradeType.BUY: + fee = AddedToCostTradeFee(percent=fee_value, percent_token=quote_currency, flat_fees=flat_fees) + else: + fee = DeductedFromReturnsTradeFee(percent=fee_value, percent_token=quote_currency, flat_fees=flat_fees) + else: + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + + return fee + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + **kwargs, + ) -> Tuple[str, float]: + """Not used.""" + raise NotImplementedError + + async def _place_order_and_process_update(self, order: GatewayInFlightOrder, **kwargs) -> str: + exchange_order_id, misc_order_updates = await self._api_data_source.place_order(order=order, **kwargs) + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=order.trading_pair, + update_timestamp=self.current_timestamp, + new_state=OrderState.PENDING_CREATE, + misc_updates=misc_order_updates, + ) + self._order_tracker.process_order_update(order_update) + + return exchange_order_id + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """Not used.""" + raise NotImplementedError + + async def _execute_order_cancel_and_process_update(self, order: GatewayInFlightOrder) -> bool: + cancelled, misc_order_updates = await self._api_data_source.cancel_order(order=order) + + if cancelled: + update_timestamp = self.current_timestamp + if update_timestamp is None or math.isnan(update_timestamp): + update_timestamp = self._time() + order_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + update_timestamp=update_timestamp, + new_state=(OrderState.CANCELED + if self.is_cancel_request_in_exchange_synchronous + else OrderState.PENDING_CANCEL), + misc_updates=misc_order_updates, + ) + self._order_tracker.process_order_update(order_update) + + return cancelled + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool: + """Not used.""" + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + return self._api_data_source.is_order_not_found_during_status_update_error( + status_update_exception=status_update_exception + ) + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + return self._api_data_source.is_order_not_found_during_cancelation_error( + cancelation_exception=cancelation_exception + ) + + async def _user_stream_event_listener(self): + """Not used.""" + pass + + def _process_trade_update(self, trade_update: TradeUpdate): + self._last_received_message_timestamp = self._time() + self._order_tracker.process_trade_update(trade_update) + + def _process_order_update(self, order_update: OrderUpdate): + self._last_received_message_timestamp = self._time() + self._order_tracker.process_order_update(order_update=order_update) + + def _process_balance_event(self, balance_event: BalanceUpdateEvent): + self._last_received_message_timestamp = self._time() + if balance_event.total_balance is not None: + self._account_balances[balance_event.asset_name] = balance_event.total_balance + if balance_event.available_balance is not None: + self._account_available_balances[balance_event.asset_name] = balance_event.available_balance + + def _create_web_assistants_factory(self) -> Optional[WebAssistantsFactory]: + return None + + def _get_poll_interval(self, timestamp: float) -> float: + last_recv_diff = timestamp - self._last_received_message_timestamp + poll_interval = ( + self.SHORT_POLL_INTERVAL + if last_recv_diff > self.TICK_INTERVAL_LIMIT or not self._api_data_source.events_are_streamed + else self.LONG_POLL_INTERVAL + ) + return poll_interval + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + timeout = self._api_data_source.cancel_all_orders_timeout \ + if self._api_data_source.cancel_all_orders_timeout is not None \ + else timeout_seconds + + return await super().cancel_all(timeout) diff --git a/hummingbot/connector/gateway/common_types.py b/hummingbot/connector/gateway/common_types.py new file mode 100644 index 0000000..ff70776 --- /dev/null +++ b/hummingbot/connector/gateway/common_types.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Any, Dict, Optional + + +class Chain(Enum): + ETHEREUM = ('ethereum', 'ETH') + TEZOS = ('tezos', 'XTZ') + + def __init__(self, chain: str, native_currency: str): + self.chain = chain + self.native_currency = native_currency + + +class Connector(Enum): + def __int__(self, chain: Chain, connector: str): + self.chain = chain + self.connector = connector + + +@dataclass +class PlaceOrderResult: + update_timestamp: float + client_order_id: str + exchange_order_id: Optional[str] + trading_pair: str + misc_updates: Dict[str, Any] = field(default_factory=lambda: {}) + exception: Optional[Exception] = None + + +@dataclass +class CancelOrderResult: + client_order_id: str + trading_pair: str + misc_updates: Dict[str, Any] = field(default_factory=lambda: {}) + not_found: bool = False + exception: Optional[Exception] = None diff --git a/hummingbot/connector/gateway/gateway_in_flight_order.py b/hummingbot/connector/gateway/gateway_in_flight_order.py new file mode 100644 index 0000000..07198ac --- /dev/null +++ b/hummingbot/connector/gateway/gateway_in_flight_order.py @@ -0,0 +1,265 @@ +import asyncio +import copy +from decimal import Decimal +from typing import Any, Dict, Optional, Tuple + +from async_timeout import timeout + +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate + +GET_GATEWAY_EX_ORDER_ID_TIMEOUT = 30 # seconds +GET_GATEWAY_TX_HASH = 1 # seconds + +s_decimal_0 = Decimal("0") + + +class GatewayInFlightOrder(InFlightOrder): + def __init__( + self, + client_order_id: str, + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + creation_timestamp: float, + price: Decimal = s_decimal_0, + amount: Decimal = s_decimal_0, + exchange_order_id: Optional[str] = None, + creation_transaction_hash: Optional[str] = None, + gas_price: Optional[Decimal] = s_decimal_0, + initial_state: OrderState = OrderState.PENDING_CREATE, + leverage: int = 1, + position: PositionAction = PositionAction.NIL, + ): + super().__init__( + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + price=price, + amount=amount, + creation_timestamp=creation_timestamp, + initial_state=initial_state, + leverage=leverage, + position=position, + ) + self._fee_asset = trading_pair.split("-")[0] # defaults to base asset + self._gas_price = gas_price + self._nonce: int = -1 + self._creation_transaction_hash: Optional[str] = creation_transaction_hash + self._creation_transaction_hash_update_event = asyncio.Event() + if self.creation_transaction_hash is not None: + self._creation_transaction_hash_update_event.set() + self._cancel_tx_hash: Optional[str] = None + + @property + def gas_price(self) -> Decimal: + return self._gas_price + + @gas_price.setter + def gas_price(self, gas_price: Decimal): + self._gas_price = gas_price + + @property + def fee_asset(self) -> str: + return self._fee_asset + + @fee_asset.setter + def fee_asset(self, fee_asset: str): + self._fee_asset = fee_asset + + @property + def nonce(self) -> int: + return self._nonce + + @nonce.setter + def nonce(self, nonce): + self._nonce = nonce + + @property + def creation_transaction_hash(self) -> Optional[str]: + return self._creation_transaction_hash + + @creation_transaction_hash.setter + def creation_transaction_hash(self, creation_transaction_hash: str): + self._creation_transaction_hash = creation_transaction_hash + + @property + def cancel_tx_hash(self) -> Optional[str]: + return self._cancel_tx_hash + + @cancel_tx_hash.setter + def cancel_tx_hash(self, cancel_tx_hash): + self._cancel_tx_hash = cancel_tx_hash + + async def get_exchange_order_id(self) -> Optional[str]: + """ + Overridden from parent class because blockchain orders take more time than ones from CEX. + """ + if self.exchange_order_id is None: + async with timeout(GET_GATEWAY_EX_ORDER_ID_TIMEOUT): + await self.exchange_order_id_update_event.wait() + return self.exchange_order_id + + @property + def attributes(self) -> Tuple[Any]: + return copy.deepcopy( + ( + self.client_order_id, + self.trading_pair, + self.order_type, + self.trade_type, + self.price, + self.amount, + self.exchange_order_id, + self.current_state, + self.leverage, + self.position, + self.executed_amount_base, + self.executed_amount_quote, + self.creation_timestamp, + self.last_update_timestamp, + self.nonce, + self.gas_price, + self._creation_transaction_hash, + self.cancel_tx_hash, + ) + ) + + @property + def is_done(self) -> bool: + if self.is_approval_request: + return not self.is_pending_approval + return super().is_done + + @property + def is_pending_approval(self) -> bool: + return self.current_state in {OrderState.PENDING_APPROVAL} + + @property + def is_approval_request(self) -> bool: + """ + A property attribute that returns `True` if this `GatewayInFlightOrder` is in fact a token approval request. + + :return: True if this `GatewayInFlightOrder` is in fact a token approval request, otherwise it returns False + :rtype: bool + """ + return "approve" in self.client_order_id or ( + self.current_state in {OrderState.PENDING_APPROVAL, OrderState.APPROVED} + ) + + def update_creation_transaction_hash(self, creation_transaction_hash: str): + self.creation_transaction_hash = creation_transaction_hash + self._creation_transaction_hash_update_event.set() + + async def get_creation_transaction_hash(self) -> str: + if self.creation_transaction_hash is None: + async with timeout(GET_GATEWAY_TX_HASH): + await self._creation_transaction_hash_update_event.wait() + return self.creation_transaction_hash + + def update_with_order_update(self, order_update: OrderUpdate) -> bool: + """ + Updates the in flight order with an order update + return: True if the order gets updated otherwise False + """ + if ( + order_update.client_order_id != self.client_order_id + and order_update.exchange_order_id != self.exchange_order_id + ): + return False + + prev_data = self.attributes + + if self.exchange_order_id is None and order_update.exchange_order_id is not None: + self.update_exchange_order_id(order_update.exchange_order_id) + + self.current_state = order_update.new_state + self.check_processed_by_exchange_condition() + misc_updates = order_update.misc_updates or {} + creation_transaction_hash = misc_updates.get("creation_transaction_hash", self.creation_transaction_hash) + if creation_transaction_hash is not None: + self.update_creation_transaction_hash(creation_transaction_hash=creation_transaction_hash) + self._cancel_tx_hash = misc_updates.get("cancelation_transaction_hash", self._cancel_tx_hash) + if self.current_state not in {OrderState.PENDING_CANCEL, OrderState.CANCELED}: + self.nonce = misc_updates.get("nonce", None) + self.fee_asset = misc_updates.get("fee_asset", None) + self.gas_price = misc_updates.get("gas_price", None) + + updated: bool = prev_data != self.attributes + + if updated: + self.last_update_timestamp = order_update.update_timestamp + + return updated + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> "GatewayInFlightOrder": + """ + Initialize an InFlightOrder using a JSON object + :param data: JSON data + :return: Formatted InFlightOrder + """ + order = GatewayInFlightOrder( + client_order_id=data["client_order_id"], + trading_pair=data["trading_pair"], + order_type=getattr(OrderType, data["order_type"]), + trade_type=getattr(TradeType, data["trade_type"]), + amount=Decimal(data["amount"]), + price=Decimal(data["price"]), + exchange_order_id=data["exchange_order_id"], + initial_state=OrderState(int(data["last_state"])), + leverage=int(data["leverage"]), + position=PositionAction(data["position"]), + creation_timestamp=data.get("creation_timestamp", -1), + ) + order.executed_amount_base = Decimal(data["executed_amount_base"]) + order.executed_amount_quote = Decimal(data["executed_amount_quote"]) + order.order_fills.update( + {key: TradeUpdate.from_json(value) for key, value in data.get("order_fills", {}).items()} + ) + order._nonce = data["nonce"] + order._cancel_tx_hash = data["cancel_tx_hash"] + order._gas_price = Decimal(data["gas_price"]) if data["gas_price"] != "None" else None + order._creation_transaction_hash = data["creation_transaction_hash"] + order.last_update_timestamp = data.get("last_update_timestamp", order.creation_timestamp) + + order.check_filled_condition() + + return order + + def to_json(self) -> Dict[str, Any]: + """ + Returns this InFlightOrder as a JSON object. + :return: JSON object + """ + return { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": self.order_type.name, + "trade_type": self.trade_type.name, + "price": str(self.price), + "amount": str(self.amount), + "executed_amount_base": str(self.executed_amount_base), + "executed_amount_quote": str(self.executed_amount_quote), + "last_state": str(self.current_state.value), + "leverage": str(self.leverage), + "position": self.position.value, + "creation_timestamp": self.creation_timestamp, + "last_update_timestamp": self.last_update_timestamp, + "order_fills": {key: fill.to_json() for key, fill in self.order_fills.items()}, + "nonce": self._nonce, + "cancel_tx_hash": self._cancel_tx_hash, + "creation_transaction_hash": self._creation_transaction_hash, + "gas_price": str(self._gas_price), + } + + +class GatewayPerpetualInFlightOrder(GatewayInFlightOrder): + def build_order_created_message(self) -> str: + return ( + f"Created {self.order_type.name.upper()} {self.trade_type.name.upper()} order " + f"{self.client_order_id} for {self.amount} to {self.position.name.upper()} a {self.trading_pair} position." + ) diff --git a/hummingbot/connector/gateway/gateway_order_tracker.py b/hummingbot/connector/gateway/gateway_order_tracker.py new file mode 100644 index 0000000..a88059b --- /dev/null +++ b/hummingbot/connector/gateway/gateway_order_tracker.py @@ -0,0 +1,48 @@ +from collections import OrderedDict +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder + +if TYPE_CHECKING: + from hummingbot.connector.connector_base import ConnectorBase + + +class GatewayOrderTracker(ClientOrderTracker): + + def __init__(self, connector: "ConnectorBase", lost_order_count_limit: int = 3) -> None: + """ + Provides utilities for connectors to update in-flight orders and also handle order errors. + Also it maintains cached orders to allow for additional updates to occur after the original order + is determined to no longer be active. + An error constitutes, but is not limited to, the following: + (1) Order not found on exchange. + (2) Cannot retrieve exchange_order_id of an order + (3) Error thrown by exchange when fetching order status + """ + super().__init__(connector=connector, lost_order_count_limit=lost_order_count_limit) + # For some DEXes it is important to process orders in the same order they were created + self._lost_orders: Dict[str, GatewayInFlightOrder] = OrderedDict() + + @property + def all_fillable_orders_by_hash(self) -> Dict[str, GatewayInFlightOrder]: + """ + :return: A dictionary of hashes (both creation and cancelation) to in-flight order. + """ + orders_by_hashes = {} + order: GatewayInFlightOrder + for order in self.all_fillable_orders.values(): + if order.creation_transaction_hash is not None: + orders_by_hashes[order.creation_transaction_hash] = order + if order.cancel_tx_hash is not None: + orders_by_hashes[order.cancel_tx_hash] = order + return orders_by_hashes + + def get_fillable_order_by_hash(self, transaction_hash: str) -> Optional[GatewayInFlightOrder]: + order = self.all_fillable_orders_by_hash.get(transaction_hash) + return order + + @staticmethod + def _restore_order_from_json(serialized_order: Dict) -> GatewayInFlightOrder: + order = GatewayInFlightOrder.from_json(serialized_order) + return order diff --git a/hummingbot/connector/gateway/gateway_price_shim.py b/hummingbot/connector/gateway/gateway_price_shim.py new file mode 100644 index 0000000..478f7c3 --- /dev/null +++ b/hummingbot/connector/gateway/gateway_price_shim.py @@ -0,0 +1,188 @@ +import logging +import time +from dataclasses import dataclass +from decimal import Decimal +from typing import Dict, NamedTuple, Optional, cast + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.logger.logger import HummingbotLogger + + +class GatewayPriceShimKey(NamedTuple): + connector_name: str + chain: str + network: str + trading_pair: str + + +@dataclass +class GatewayPriceShimEntry: + from_exchange: str + from_trading_pair: str + to_connector_name: str + to_chain: str + to_network: str + to_trading_pair: str + + +@dataclass +class GatewayPriceDeltaEntry: + connector_name: str + chain: str + network: str + trading_pair: str + delta: Decimal + end_timestamp: float + + +class GatewayPriceShim: + """ + Developer / QA tool for modifying the apparent prices on DEX connectors (e.g. uniswap) during integration tests. + + When the gateway price shim is enabled for a particular DEX trading pair (e.g. "WETH-DAI" on uniswap), the apparent + price on the DEX will follow the prices on a different trading pair on another exchange (e.g. "ETH-USDT" on + Binance). The price shim then exposes a function (`apply_price_delta()`) which allows the developer to modify the + apparent prices on the DEX trading pair during live trading. This means the developer can manually control and + inject arbitrage opportunities in an amm_arb trading session, for carrying out integration tests. + + How to use: + + 1. Set up an amm_arb strategy config that trades between a testnet trading pair (e.g. "WETH-DAI" on Uniswap Kovan), + and a corresponding trading pair on a paper trading exchange (e.g. "ETH-USDT" on Binance paper trade). + 2. Set the `debug_price_shim` parameter of the strategy config to true. + 3. Start the strategy. + 4. Observe that the apparent AMM prices will follow the paper trading exchange prices. + 5. Inject price deltas to the AMM prices by going to the debug console, and issuing the following: + + ``` + from hummingbot.connector.gateway_price_shim import GatewayPriceShim + from decimal import Decimal + GatewayPriceShim.get_instance().apply_price_delta("uniswap", "ethereum", "kovan", "WETH-DAI", Decimal(40)) + ``` + + 6. Observe that the apparent AMM prices is increased by the delta amount, and the amm_arb strategy will start + issuing arbitrage trades. + 7. Price delta values can be negative if you want to generate buy orders on the AMM side. + + """ + _gps_logger: Optional[HummingbotLogger] = None + _shared_instance: Optional["GatewayPriceShim"] = None + _shim_entries: Dict[GatewayPriceShimKey, GatewayPriceShimEntry] + _delta_entries: Dict[GatewayPriceShimKey, GatewayPriceDeltaEntry] + + def __init__(self): + self._shim_entries = {} + self._delta_entries = {} + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._gps_logger is None: + cls._gps_logger = cast(HummingbotLogger, logging.getLogger(__name__)) + return cls._gps_logger + + @classmethod + def get_instance(cls) -> "GatewayPriceShim": + if cls._shared_instance is None: + cls._shared_instance = GatewayPriceShim() + return cls._shared_instance + + def patch_prices( + self, + from_exchange: str, + from_trading_pair: str, + to_connector_name: str, + to_chain: str, + to_network: str, + to_trading_pair: str + ): + key: GatewayPriceShimKey = GatewayPriceShimKey( + connector_name=to_connector_name, + chain=to_chain, + network=to_network, + trading_pair=to_trading_pair, + ) + self._shim_entries[key] = GatewayPriceShimEntry( + from_exchange=from_exchange, + from_trading_pair=from_trading_pair, + to_connector_name=to_connector_name, + to_chain=to_chain, + to_network=to_network, + to_trading_pair=to_trading_pair + ) + + def apply_price_delta( + self, + connector_name: str, + chain: str, + network: str, + trading_pair: str, + delta: Decimal, + duration_seconds: float = 60): + key: GatewayPriceShimKey = GatewayPriceShimKey( + connector_name=connector_name, + chain=chain, + network=network, + trading_pair=trading_pair, + ) + if key not in self._shim_entries: + raise ValueError(f"The trading pair {trading_pair} on {chain}/{network}/{connector_name} has not had " + f"price shim installed yet.") + self._delta_entries[key] = GatewayPriceDeltaEntry( + connector_name=connector_name, + chain=chain, + network=network, + trading_pair=trading_pair, + delta=delta, + end_timestamp=time.time() + duration_seconds + ) + + async def get_connector_price( + self, + connector_name: str, + chain: str, + network: str, + trading_pair: str, + is_buy: bool, + amount: Decimal) -> Optional[Decimal]: + key: GatewayPriceShimKey = GatewayPriceShimKey( + connector_name=connector_name, + chain=chain, + network=network, + trading_pair=trading_pair + ) + if key not in self._shim_entries: + return None + + shim_entry: GatewayPriceShimEntry = self._shim_entries[key] + exchange_market: Optional[ExchangeBase] = HummingbotApplication.main_application().markets.get( + shim_entry.from_exchange + ) + if exchange_market is None: + self.logger().warning(f"Gateway price shim failure: " + f"reference exchange market '{shim_entry.from_exchange}' not found. " + f"Going to use on-chain prices instead.") + return None + + exchange_price: Decimal = await exchange_market.get_quote_price( + shim_entry.from_trading_pair, + is_buy, + amount + ) + if key in self._delta_entries: + delta_entry: GatewayPriceDeltaEntry = self._delta_entries[key] + now: float = time.time() + if now <= delta_entry.end_timestamp: + exchange_price += delta_entry.delta + else: + del self._delta_entries[key] + return exchange_price + + def has_price_shim(self, connector_name: str, chain: str, network: str, trading_pair: str) -> bool: + key: GatewayPriceShimKey = GatewayPriceShimKey( + connector_name=connector_name, + chain=chain, + network=network, + trading_pair=trading_pair + ) + return key in self._shim_entries diff --git a/hummingbot/connector/in_flight_order_base.pxd b/hummingbot/connector/in_flight_order_base.pxd new file mode 100644 index 0000000..aceb435 --- /dev/null +++ b/hummingbot/connector/in_flight_order_base.pxd @@ -0,0 +1,17 @@ +cdef class InFlightOrderBase: + cdef: + public str client_order_id + public str exchange_order_id + public str trading_pair + public object order_type + public object trade_type + public object price + public object amount + public object executed_amount_base + public object executed_amount_quote + public str fee_asset + public object fee_paid + public str last_state + public object exchange_order_id_update_event + public object completely_filled_event + public double _creation_timestamp diff --git a/hummingbot/connector/in_flight_order_base.pyx b/hummingbot/connector/in_flight_order_base.pyx new file mode 100644 index 0000000..f1588cd --- /dev/null +++ b/hummingbot/connector/in_flight_order_base.pyx @@ -0,0 +1,184 @@ +import asyncio +from decimal import Decimal +from typing import ( + Any, + Dict, + List, + Optional, +) + +from async_timeout import timeout + +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder + +s_decimal_0 = Decimal(0) + +GET_EX_ORDER_ID_TIMEOUT = 10 # seconds + +cdef class InFlightOrderBase: + def __init__(self, + client_order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + creation_timestamp: float, + initial_state: str): + + self.client_order_id = client_order_id + self.exchange_order_id = exchange_order_id + self.trading_pair = trading_pair + self.order_type = order_type + self.trade_type = trade_type + self.price = price + self.amount = amount + self.executed_amount_base = s_decimal_0 + self.executed_amount_quote = s_decimal_0 + self.fee_asset = None + self.fee_paid = s_decimal_0 + self.last_state = initial_state + self.exchange_order_id_update_event = asyncio.Event() + if self.exchange_order_id is not None: + self.exchange_order_id_update_event.set() + self.completely_filled_event = asyncio.Event() + self._creation_timestamp = creation_timestamp + + def __repr__(self) -> str: + return (f"InFlightOrder(" + f"client_order_id='{self.client_order_id}', " + f"exchange_order_id='{self.exchange_order_id}', " + f"creation_timestamp={self._creation_timestamp}, " + f"trading_pair='{self.trading_pair}', " + f"order_type={self.order_type}, " + f"trade_type={self.trade_type}, " + f"price={self.price}, " + f"amount={self.amount}, " + f"executed_amount_base={self.executed_amount_base}, " + f"executed_amount_quote={self.executed_amount_quote}, " + f"fee_asset='{self.fee_asset}', " + f"fee_paid={self.fee_paid}, " + f"last_state='{self.last_state}')") + + @property + def is_done(self) -> bool: + raise NotImplementedError + + @property + def is_cancelled(self) -> bool: + raise NotImplementedError + + @property + def is_failure(self) -> bool: + raise NotImplementedError + + @property + def base_asset(self) -> str: + return self.trading_pair.split("-")[0] + + @property + def quote_asset(self) -> str: + return self.trading_pair.split("-")[1] + + @property + def creation_timestamp(self) -> float: + """ + Returns the creation timestamp in seconds + :return: The creation timestamp + """ + if self._creation_timestamp > 0: + timestamp = self._creation_timestamp + else: + timestamp = self._creation_timestamp_from_order_id() + return timestamp + + def update_exchange_order_id(self, exchange_id: str): + self.exchange_order_id = exchange_id + self.exchange_order_id_update_event.set() + + async def get_exchange_order_id(self): + if self.exchange_order_id is None: + async with timeout(GET_EX_ORDER_ID_TIMEOUT): + await self.exchange_order_id_update_event.wait() + return self.exchange_order_id + + def to_limit_order(self) -> LimitOrder: + return LimitOrder( + self.client_order_id, + self.trading_pair, + self.trade_type is TradeType.BUY, + self.base_asset, + self.quote_asset, + self.price, + self.amount, + creation_timestamp=int(self.creation_timestamp * 1e6) + ) + + def to_json(self) -> Dict[str, Any]: + return { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": self.order_type.name, + "trade_type": self.trade_type.name, + "price": str(self.price), + "amount": str(self.amount), + "executed_amount_base": str(self.executed_amount_base), + "executed_amount_quote": str(self.executed_amount_quote), + "fee_asset": self.fee_asset, + "fee_paid": str(self.fee_paid), + "creation_timestamp": self.creation_timestamp, + "last_state": self.last_state, + } + + @classmethod + def _instance_creation_parameters_from_json(cls, data: Dict[str, Any]) -> List[Any]: + return [ + data["client_order_id"], + data["exchange_order_id"], + data["trading_pair"], + getattr(OrderType, data["order_type"]), + getattr(TradeType, data["trade_type"]), + Decimal(data["price"]), + Decimal(data["amount"]), + data.get("creation_timestamp", -1), + data["last_state"]] + + @classmethod + def _basic_from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase: + """ + :param data: json data from API + :return: formatted InFlightOrder + """ + arguments = cls._instance_creation_parameters_from_json(data) + order = cls(*arguments) + order.executed_amount_base = Decimal(data["executed_amount_base"]) + order.executed_amount_quote = Decimal(data["executed_amount_quote"]) + order.fee_asset = data["fee_asset"] + order.fee_paid = Decimal(data["fee_paid"]) + return order + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase: + """ + :param data: json data from API + :return: formatted InFlightOrder + """ + return cls._basic_from_json(data) + + def check_filled_condition(self): + if (abs(self.amount) - self.executed_amount_base).quantize(Decimal('1e-8')) <= 0: + self.completely_filled_event.set() + + async def wait_until_completely_filled(self): + await self.completely_filled_event.wait() + + def _creation_timestamp_from_order_id(self) -> int: + timestamp = -1 + if len(self.client_order_id) > 16: + nonce_component = self.client_order_id[-16:] + timestamp_string = f"{nonce_component[:10]}.{nonce_component[-6:]}" + timestamp = float(timestamp_string) if nonce_component.isnumeric() else -1 + return timestamp diff --git a/hummingbot/connector/markets_recorder.py b/hummingbot/connector/markets_recorder.py new file mode 100644 index 0000000..5b16663 --- /dev/null +++ b/hummingbot/connector/markets_recorder.py @@ -0,0 +1,521 @@ +import asyncio +import logging +import os.path +import threading +import time +from decimal import Decimal +from shutil import move +from typing import Dict, List, Optional, Tuple, Union + +import pandas as pd +from sqlalchemy.orm import Query, Session + +from hummingbot import data_path +from hummingbot.client.config.client_config_map import MarketDataCollectionConfigMap +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.utils import TradeFillOrderDetails +from hummingbot.core.data_type.common import PriceType +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + FundingPaymentCompletedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderFilledEvent, + PositionAction, + RangePositionClosedEvent, + RangePositionFeeCollectedEvent, + RangePositionLiquidityAddedEvent, + RangePositionLiquidityRemovedEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.logger import HummingbotLogger +from hummingbot.model.funding_payment import FundingPayment +from hummingbot.model.market_data import MarketData +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.order_status import OrderStatus +from hummingbot.model.position_executors import PositionExecutors +from hummingbot.model.range_position_collected_fees import RangePositionCollectedFees +from hummingbot.model.range_position_update import RangePositionUpdate +from hummingbot.model.sql_connection_manager import SQLConnectionManager +from hummingbot.model.trade_fill import TradeFill + + +class MarketsRecorder: + _logger = None + _shared_instance: "MarketsRecorder" = None + market_event_tag_map: Dict[int, MarketEvent] = { + event_obj.value: event_obj + for event_obj in MarketEvent.__members__.values() + } + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @classmethod + def get_instance(cls, *args, **kwargs) -> "MarketsRecorder": + if cls._shared_instance is None: + cls._shared_instance = MarketsRecorder(*args, **kwargs) + return cls._shared_instance + + def __init__(self, + sql: SQLConnectionManager, + markets: List[ConnectorBase], + config_file_path: str, + strategy_name: str, + market_data_collection: MarketDataCollectionConfigMap): + if threading.current_thread() != threading.main_thread(): + raise EnvironmentError("MarketsRecorded can only be initialized from the main thread.") + + self._ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self._sql_manager: SQLConnectionManager = sql + self._markets: List[ConnectorBase] = markets + self._config_file_path: str = config_file_path + self._strategy_name: str = strategy_name + self._market_data_collection_config: MarketDataCollectionConfigMap = market_data_collection + self._market_data_collection_task: Optional[asyncio.Task] = None + # Internal collection of trade fills in connector will be used for remote/local history reconciliation + for market in self._markets: + trade_fills = self.get_trades_for_config(self._config_file_path, 2000) + market.add_trade_fills_from_market_recorder({TradeFillOrderDetails(tf.market, + tf.exchange_trade_id, + tf.symbol) for tf in trade_fills}) + + exchange_order_ids = self.get_orders_for_config_and_market(self._config_file_path, market, True, 2000) + market.add_exchange_order_ids_from_market_recorder({o.exchange_order_id: o.id for o in exchange_order_ids}) + + self._create_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_create_order) + self._fill_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_fill_order) + self._cancel_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_cancel_order) + self._fail_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_fail_order) + self._complete_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_complete_order) + self._expire_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_expire_order) + self._funding_payment_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder( + self._did_complete_funding_payment) + self._update_range_position_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder( + self._did_update_range_position) + self._close_range_position_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder( + self._did_close_position) + + self._event_pairs: List[Tuple[MarketEvent, SourceInfoEventForwarder]] = [ + (MarketEvent.BuyOrderCreated, self._create_order_forwarder), + (MarketEvent.SellOrderCreated, self._create_order_forwarder), + (MarketEvent.OrderFilled, self._fill_order_forwarder), + (MarketEvent.OrderCancelled, self._cancel_order_forwarder), + (MarketEvent.OrderFailure, self._fail_order_forwarder), + (MarketEvent.BuyOrderCompleted, self._complete_order_forwarder), + (MarketEvent.SellOrderCompleted, self._complete_order_forwarder), + (MarketEvent.OrderExpired, self._expire_order_forwarder), + (MarketEvent.FundingPaymentCompleted, self._funding_payment_forwarder), + (MarketEvent.RangePositionLiquidityAdded, self._update_range_position_forwarder), + (MarketEvent.RangePositionLiquidityRemoved, self._update_range_position_forwarder), + (MarketEvent.RangePositionFeeCollected, self._update_range_position_forwarder), + (MarketEvent.RangePositionClosed, self._close_range_position_forwarder), + ] + MarketsRecorder._shared_instance = self + + def _start_market_data_recording(self): + self._market_data_collection_task = self._ev_loop.create_task(self._record_market_data()) + + async def _record_market_data(self): + while True: + try: + if all(ex.ready for ex in self._markets): + with self._sql_manager.get_new_session() as session: + with session.begin(): + for market in self._markets: + exchange = market.display_name + for trading_pair in market.trading_pairs: + mid_price = market.get_price_by_type(trading_pair, PriceType.MidPrice) + best_bid = market.get_price_by_type(trading_pair, PriceType.BestBid) + best_ask = market.get_price_by_type(trading_pair, PriceType.BestAsk) + order_book = market.get_order_book(trading_pair) + depth = self._market_data_collection_config.market_data_collection_depth + 1 + market_data = MarketData( + timestamp=self.db_timestamp, + exchange=exchange, + trading_pair=trading_pair, + mid_price=mid_price, + best_bid=best_bid, + best_ask=best_ask, + order_book={ + "bid": list(order_book.bid_entries())[:depth], + "ask": list(order_book.ask_entries())[:depth]} + ) + session.add(market_data) + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error("Unexpected error while recording market data.", e) + finally: + await self._sleep(self._market_data_collection_config.market_data_collection_interval) + + @property + def sql_manager(self) -> SQLConnectionManager: + return self._sql_manager + + @property + def config_file_path(self) -> str: + return self._config_file_path + + @property + def strategy_name(self) -> str: + return self._strategy_name + + @property + def db_timestamp(self) -> int: + return int(time.time() * 1e3) + + def start(self): + for market in self._markets: + for event_pair in self._event_pairs: + market.add_listener(event_pair[0], event_pair[1]) + if self._market_data_collection_config.market_data_collection_enabled: + self._start_market_data_recording() + + def stop(self): + for market in self._markets: + for event_pair in self._event_pairs: + market.remove_listener(event_pair[0], event_pair[1]) + if self._market_data_collection_task is not None: + self._market_data_collection_task.cancel() + + def store_executor(self, executor: Dict): + with self._sql_manager.get_new_session() as session: + with session.begin(): + session.add(PositionExecutors(**executor)) + + def get_position_executors(self, + controller_name: str = None, + exchange: str = None, + trading_pair: str = None + ): + with self._sql_manager.get_new_session() as session: + position_executors = PositionExecutors.get_position_executors(sql_session=session, + controller_name=controller_name, + exchange=exchange, + trading_pair=trading_pair) + return position_executors + + def get_orders_for_config_and_market(self, config_file_path: str, market: ConnectorBase, + with_exchange_order_id_present: Optional[bool] = False, + number_of_rows: Optional[int] = None) -> List[Order]: + with self._sql_manager.get_new_session() as session: + filters = [Order.config_file_path == config_file_path, + Order.market == market.display_name] + if with_exchange_order_id_present: + filters.append(Order.exchange_order_id.isnot(None)) + query: Query = (session + .query(Order) + .filter(*filters) + .order_by(Order.creation_timestamp)) + if number_of_rows is None: + return query.all() + else: + return query.limit(number_of_rows).all() + + def get_trades_for_config(self, config_file_path: str, number_of_rows: Optional[int] = None) -> List[TradeFill]: + with self._sql_manager.get_new_session() as session: + query: Query = (session + .query(TradeFill) + .filter(TradeFill.config_file_path == config_file_path) + .order_by(TradeFill.timestamp.desc())) + if number_of_rows is None: + return query.all() + else: + return query.limit(number_of_rows).all() + + def save_market_states(self, config_file_path: str, market: ConnectorBase, session: Session): + market_states: Optional[MarketState] = self.get_market_states(config_file_path, market, session=session) + timestamp: int = self.db_timestamp + + if market_states is not None: + market_states.saved_state = market.tracking_states + market_states.timestamp = timestamp + else: + market_states = MarketState(config_file_path=config_file_path, + market=market.display_name, + timestamp=timestamp, + saved_state=market.tracking_states) + session.add(market_states) + + def restore_market_states(self, config_file_path: str, market: ConnectorBase): + with self._sql_manager.get_new_session() as session: + market_states: Optional[MarketState] = self.get_market_states(config_file_path, market, session=session) + + if market_states is not None: + market.restore_tracking_states(market_states.saved_state) + + def get_market_states(self, + config_file_path: str, + market: ConnectorBase, + session: Session) -> Optional[MarketState]: + query: Query = (session + .query(MarketState) + .filter(MarketState.config_file_path == config_file_path, + MarketState.market == market.display_name)) + market_states: Optional[MarketState] = query.one_or_none() + return market_states + + def _did_create_order(self, + event_tag: int, + market: ConnectorBase, + evt: Union[BuyOrderCreatedEvent, SellOrderCreatedEvent]): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._did_create_order, event_tag, market, evt) + return + + base_asset, quote_asset = evt.trading_pair.split("-") + timestamp = int(evt.creation_timestamp * 1e3) + event_type: MarketEvent = self.market_event_tag_map[event_tag] + + with self._sql_manager.get_new_session() as session: + with session.begin(): + order_record: Order = Order(id=evt.order_id, + config_file_path=self._config_file_path, + strategy=self._strategy_name, + market=market.display_name, + symbol=evt.trading_pair, + base_asset=base_asset, + quote_asset=quote_asset, + creation_timestamp=timestamp, + order_type=evt.type.name, + amount=Decimal(evt.amount), + leverage=evt.leverage if evt.leverage else 1, + price=Decimal(evt.price) if evt.price == evt.price else Decimal(0), + position=evt.position if evt.position else PositionAction.NIL.value, + last_status=event_type.name, + last_update_timestamp=timestamp, + exchange_order_id=evt.exchange_order_id) + order_status: OrderStatus = OrderStatus(order=order_record, + timestamp=timestamp, + status=event_type.name) + session.add(order_record) + session.add(order_status) + market.add_exchange_order_ids_from_market_recorder({evt.exchange_order_id: evt.order_id}) + self.save_market_states(self._config_file_path, market, session=session) + + def _did_fill_order(self, + event_tag: int, + market: ConnectorBase, + evt: OrderFilledEvent): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._did_fill_order, event_tag, market, evt) + return + + base_asset, quote_asset = evt.trading_pair.split("-") + timestamp: int = int(evt.timestamp * 1e3) if evt.timestamp is not None else self.db_timestamp + event_type: MarketEvent = self.market_event_tag_map[event_tag] + order_id: str = evt.order_id + + with self._sql_manager.get_new_session() as session: + with session.begin(): + # Try to find the order record, and update it if necessary. + order_record: Optional[Order] = session.query(Order).filter(Order.id == order_id).one_or_none() + if order_record is not None: + order_record.last_status = event_type.name + order_record.last_update_timestamp = timestamp + + # Order status and trade fill record should be added even if the order record is not found, because it's + # possible for fill event to come in before the order created event for market orders. + order_status: OrderStatus = OrderStatus(order_id=order_id, + timestamp=timestamp, + status=event_type.name) + try: + fee_in_quote = evt.trade_fee.fee_amount_in_token( + trading_pair=evt.trading_pair, + price=evt.price, + order_amount=evt.amount, + token=quote_asset, + exchange=market + ) + except Exception as e: + self.logger().error(f"Error calculating fee in quote: {e}, will be stored in the DB as 0.") + fee_in_quote = 0 + trade_fill_record: TradeFill = TradeFill( + config_file_path=self.config_file_path, + strategy=self.strategy_name, + market=market.display_name, + symbol=evt.trading_pair, + base_asset=base_asset, + quote_asset=quote_asset, + timestamp=timestamp, + order_id=order_id, + trade_type=evt.trade_type.name, + order_type=evt.order_type.name, + price=evt.price, + amount=evt.amount, + leverage=evt.leverage if evt.leverage else 1, + trade_fee=evt.trade_fee.to_json(), + trade_fee_in_quote=fee_in_quote, + exchange_trade_id=evt.exchange_trade_id, + position=evt.position if evt.position else PositionAction.NIL.value, + ) + session.add(order_status) + session.add(trade_fill_record) + self.save_market_states(self._config_file_path, market, session=session) + + market.add_trade_fills_from_market_recorder({TradeFillOrderDetails(trade_fill_record.market, + trade_fill_record.exchange_trade_id, + trade_fill_record.symbol)}) + self.append_to_csv(trade_fill_record) + + def _did_complete_funding_payment(self, + event_tag: int, + market: ConnectorBase, + evt: FundingPaymentCompletedEvent): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._did_complete_funding_payment, event_tag, market, evt) + return + + timestamp: float = evt.timestamp + + with self._sql_manager.get_new_session() as session: + with session.begin(): + # Try to find the funding payment has been recorded already. + payment_record: Optional[FundingPayment] = session.query(FundingPayment).filter( + FundingPayment.timestamp == timestamp).one_or_none() + if payment_record is None: + funding_payment_record: FundingPayment = FundingPayment(timestamp=timestamp, + config_file_path=self.config_file_path, + market=market.display_name, + rate=evt.funding_rate, + symbol=evt.trading_pair, + amount=float(evt.amount)) + session.add(funding_payment_record) + + @staticmethod + def _csv_matches_header(file_path: str, header: tuple) -> bool: + df = pd.read_csv(file_path, header=None) + return tuple(df.iloc[0].values) == header + + def append_to_csv(self, trade: TradeFill): + csv_filename = "trades_" + trade.config_file_path[:-4] + ".csv" + csv_path = os.path.join(data_path(), csv_filename) + + field_names = tuple(trade.attribute_names_for_file_export()) + field_data = tuple(getattr(trade, attr) for attr in field_names) + + # adding extra field "age" + # // indicates order is a paper order so 'n/a'. For real orders, calculate age. + age = pd.Timestamp(int((trade.timestamp * 1e-3) - (trade.order.creation_timestamp * 1e-3)), unit='s').strftime( + '%H:%M:%S') if (trade.order is not None and "//" not in trade.order_id) else "n/a" + field_names += ("age",) + field_data += (age,) + + if (os.path.exists(csv_path) and (not self._csv_matches_header(csv_path, field_names))): + move(csv_path, csv_path[:-4] + '_old_' + pd.Timestamp.utcnow().strftime("%Y%m%d-%H%M%S") + ".csv") + + if not os.path.exists(csv_path): + df_header = pd.DataFrame([field_names]) + df_header.to_csv(csv_path, mode='a', header=False, index=False) + df = pd.DataFrame([field_data]) + df.to_csv(csv_path, mode='a', header=False, index=False) + + def _update_order_status(self, + event_tag: int, + market: ConnectorBase, + evt: Union[OrderCancelledEvent, + MarketOrderFailureEvent, + BuyOrderCompletedEvent, + SellOrderCompletedEvent, + OrderExpiredEvent]): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._update_order_status, event_tag, market, evt) + return + + timestamp: int = self.db_timestamp + event_type: MarketEvent = self.market_event_tag_map[event_tag] + order_id: str = evt.order_id + + with self._sql_manager.get_new_session() as session: + with session.begin(): + order_record: Optional[Order] = session.query(Order).filter(Order.id == order_id).one_or_none() + + if order_record is not None: + order_record.last_status = event_type.name + order_record.last_update_timestamp = timestamp + order_status: OrderStatus = OrderStatus(order_id=order_id, + timestamp=timestamp, + status=event_type.name) + session.add(order_status) + self.save_market_states(self._config_file_path, market, session=session) + + def _did_cancel_order(self, + event_tag: int, + market: ConnectorBase, + evt: OrderCancelledEvent): + self._update_order_status(event_tag, market, evt) + + def _did_fail_order(self, + event_tag: int, + market: ConnectorBase, + evt: MarketOrderFailureEvent): + self._update_order_status(event_tag, market, evt) + + def _did_complete_order(self, + event_tag: int, + market: ConnectorBase, + evt: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent]): + self._update_order_status(event_tag, market, evt) + + def _did_expire_order(self, + event_tag: int, + market: ConnectorBase, + evt: OrderExpiredEvent): + self._update_order_status(event_tag, market, evt) + + def _did_update_range_position(self, + event_tag: int, + connector: ConnectorBase, + evt: Union[RangePositionLiquidityAddedEvent, RangePositionLiquidityRemovedEvent, RangePositionFeeCollectedEvent]): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._did_update_range_position, event_tag, connector, evt) + return + + timestamp: int = self.db_timestamp + + with self._sql_manager.get_new_session() as session: + with session.begin(): + rp_update: RangePositionUpdate = RangePositionUpdate(hb_id=evt.order_id, + timestamp=timestamp, + tx_hash=evt.exchange_order_id, + token_id=evt.token_id, + trade_fee=evt.trade_fee.to_json()) + session.add(rp_update) + self.save_market_states(self._config_file_path, connector, session=session) + + def _did_close_position(self, + event_tag: int, + connector: ConnectorBase, + evt: RangePositionClosedEvent): + if threading.current_thread() != threading.main_thread(): + self._ev_loop.call_soon_threadsafe(self._did_close_position, event_tag, connector, evt) + return + + with self._sql_manager.get_new_session() as session: + with session.begin(): + rp_fees: RangePositionCollectedFees = RangePositionCollectedFees(config_file_path=self._config_file_path, + strategy=self._strategy_name, + token_id=evt.token_id, + token_0=evt.token_0, + token_1=evt.token_1, + claimed_fee_0=Decimal(evt.claimed_fee_0), + claimed_fee_1=Decimal(evt.claimed_fee_1)) + session.add(rp_fees) + self.save_market_states(self._config_file_path, connector, session=session) + + @staticmethod + async def _sleep(delay): + """ + A wrapper function that facilitates patching the sleep in unit tests without affecting the asyncio module + """ + await asyncio.sleep(delay) diff --git a/hummingbot/connector/other/__init__.py b/hummingbot/connector/other/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/parrot.py b/hummingbot/connector/parrot.py new file mode 100644 index 0000000..3115a1f --- /dev/null +++ b/hummingbot/connector/parrot.py @@ -0,0 +1,143 @@ +import asyncio +import logging +from dataclasses import dataclass +from decimal import Decimal +from typing import Dict, List + +import aiohttp + +from hummingbot.core.utils.async_utils import safe_gather + +PARROT_MINER_BASE_URL = "https://api.hummingbot.io/bounty/" + +s_decimal_0 = Decimal("0") + + +@dataclass +class CampaignSummary: + market_id: int = 0 + trading_pair: str = "" + exchange_name: str = "" + spread_max: Decimal = s_decimal_0 + payout_asset: str = "" + liquidity: Decimal = s_decimal_0 + liquidity_usd: Decimal = s_decimal_0 + active_bots: int = 0 + reward_per_wk: Decimal = s_decimal_0 + apy: Decimal = s_decimal_0 + + +def logger(): + return logging.getLogger(__name__) + + +async def get_campaign_summary(exchange: str, trading_pairs: List[str] = []) -> Dict[str, CampaignSummary]: + results = {} + try: + campaigns = await get_active_campaigns(exchange, trading_pairs) + tasks = [get_market_last_snapshot(m_id) for m_id in campaigns] + snapshots = await safe_gather(*tasks, return_exceptions=True) + for snapshot in snapshots: + if isinstance(snapshot, Exception): + raise snapshot + if 'status' in snapshot and snapshot.get('status') != "success": + logger().warning( + f"Snapshot info for {trading_pairs} is not available, please verify that this is a valid campaign pair for this exchange") + continue + if "market_snapshot" in snapshot: + snapshot = snapshot.get("market_snapshot") + market_id = int(snapshot.get("market_id")) + campaign = campaigns[market_id] + campaign.apy = Decimal(snapshot.get("annualized_return")) + oov = snapshot.get("summary_stats").get("open_volume") + campaign.liquidity = Decimal(oov.get("oov_eligible_ask")) + Decimal(oov.get("oov_eligible_bid")) + campaign.liquidity_usd = campaign.liquidity * Decimal(oov.get("base_asset_usd_rate")) + campaign.active_bots = int(oov.get("bots")) + results = {c.trading_pair: c for c in campaigns.values()} + except asyncio.CancelledError: + raise + except Exception: + logger().error("Unexpected error while requesting data from Hummingbot API.", exc_info=True) + return results + + +async def get_market_snapshots(market_id: int): + async with aiohttp.ClientSession() as client: + url = f"{PARROT_MINER_BASE_URL}charts/market_band?market_id={market_id}&chart_interval=1" + resp = await client.get(url) + resp_json = await resp.json() + + if not resp_json or "status" not in resp_json or resp_json.get("status") == "error": + logger().warning("Could not get market snapshots from Hummingbot API" + f" (returned response '{resp_json}').") + return None + return resp_json + + +async def get_market_last_snapshot(market_id: int): + data = await get_market_snapshots(market_id) + data = sorted(list(set([d.get("timestamp") for d in data.get('data')]))) + + await asyncio.sleep(0.5) + + async with aiohttp.ClientSession() as client: + url = f"{PARROT_MINER_BASE_URL}user/single_snapshot?market_id={market_id}×tamp={data[-1]}&aggregate_period=1m" + resp = await client.get(url) + resp_json = await resp.json() + return resp_json + + +async def get_active_campaigns(exchange: str, trading_pairs: List[str] = []) -> Dict[int, CampaignSummary]: + campaigns = {} + async with aiohttp.ClientSession() as client: + campaigns_url = f"{PARROT_MINER_BASE_URL}campaigns" + resp = await client.get(campaigns_url) + resp_json = await resp.json() + + if not resp_json or "status" not in resp_json or resp_json.get("status") == "error": + logger().warning("Could not get active campaigns from Hummingbot API" + f" (returned response '{resp_json}').") + else: + for campaign_retval in resp_json["campaigns"]: + for market in campaign_retval["markets"]: + if not are_same_entity(exchange, market["exchange_name"]): + continue + t_pair = f"{market['base_asset']}-{market['quote_asset']}" + if trading_pairs and t_pair not in trading_pairs: + continue + campaign = CampaignSummary() + campaign.market_id = int(market["market_id"]) + campaign.trading_pair = t_pair + campaign.exchange_name = exchange + campaigns[campaign.market_id] = campaign + + campaigns = await get_active_markets(campaigns) + + return campaigns + + +async def get_active_markets(campaigns: Dict[int, CampaignSummary]) -> Dict[int, CampaignSummary]: + async with aiohttp.ClientSession() as client: + markets_url = f"{PARROT_MINER_BASE_URL}markets" + resp = await client.get(markets_url) + resp_json = await resp.json() + + if not resp_json or "status" not in resp_json or resp_json.get("status") == "error": + logger().warning("Could not get active markets from Hummingbot API" + f" (returned response '{resp_json}').") + else: + for markets_retval in resp_json["markets"]: + market_id = int(markets_retval["market_id"]) + if market_id in campaigns: + campaigns[market_id].active_bots = markets_retval["bots"] + for bounty_period in markets_retval["active_bounty_periods"]: + campaigns[market_id].reward_per_wk = Decimal(str(bounty_period["budget"]["bid"])) + Decimal( + str(bounty_period["budget"]["ask"])) + campaigns[market_id].spread_max = Decimal(str(bounty_period["spread_max"])) / Decimal("100") + campaigns[market_id].payout_asset = bounty_period["payout_asset"] + + return campaigns + + +def are_same_entity(hb_exchange_name: str, parrot_exchange_name: str) -> bool: + return hb_exchange_name.replace("_", "") == parrot_exchange_name.replace("_", "") diff --git a/hummingbot/connector/perpetual_derivative_py_base.py b/hummingbot/connector/perpetual_derivative_py_base.py new file mode 100644 index 0000000..799f851 --- /dev/null +++ b/hummingbot/connector/perpetual_derivative_py_base.py @@ -0,0 +1,449 @@ +import asyncio +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple + +from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN +from hummingbot.connector.derivative.perpetual_budget_checker import PerpetualBudgetChecker +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import PerpetualDerivativeInFlightOrder +from hummingbot.core.data_type.perpetual_api_order_book_data_source import PerpetualAPIOrderBookDataSource +from hummingbot.core.data_type.trade_fee import TradeFeeBase +from hummingbot.core.event.events import ( + AccountEvent, + FundingPaymentCompletedEvent, + MarketEvent, + PositionModeChangeEvent, +) +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class PerpetualDerivativePyBase(ExchangePyBase, ABC): + VALID_POSITION_ACTIONS = [PositionAction.OPEN, PositionAction.CLOSE] + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + self._last_funding_fee_payment_ts: Dict[str, float] = {} + + self._perpetual_trading = PerpetualTrading(self.trading_pairs) + self._funding_info_listener_task: Optional[asyncio.Task] = None + self._funding_fee_polling_task: Optional[asyncio.Task] = None + self._funding_fee_poll_notifier = asyncio.Event() + self._orderbook_ds: PerpetualAPIOrderBookDataSource = self._orderbook_ds # for type-hinting + + self._budget_checker = PerpetualBudgetChecker(self) + + @property + @abstractmethod + def funding_fee_poll_interval(self) -> int: + raise NotImplementedError + + @property + def status_dict(self) -> Dict[str, bool]: + """ + A dictionary of statuses of various exchange's components. Used to determine if the connector is ready + """ + status_d = super().status_dict + status_d["funding_info"] = self._perpetual_trading.is_funding_info_initialized() + return status_d + + @property + def position_mode(self) -> PositionMode: + """Returns the current position mode.""" + return self._perpetual_trading.position_mode + + @property + def budget_checker(self) -> PerpetualBudgetChecker: + """Returns the exchange's associated budget checker.""" + return self._budget_checker + + @property + def account_positions(self) -> Dict[str, Position]: + """Returns a dictionary of current active open positions.""" + return self._perpetual_trading.account_positions + + @abstractmethod + def supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError + + @abstractmethod + def get_buy_collateral_token(self, trading_pair: str) -> str: + raise NotImplementedError + + @abstractmethod + def get_sell_collateral_token(self, trading_pair: str) -> str: + raise NotImplementedError + + def tick(self, timestamp: float): + """ + Includes the logic that has to be processed every time a new tick happens in the bot. Particularly it enables + the execution of the status update polling loop using an event. + """ + last_tick = int(self._last_timestamp / self.funding_fee_poll_interval) + current_tick = int(timestamp / self.funding_fee_poll_interval) + super().tick(timestamp) + if current_tick > last_tick: + self._funding_fee_poll_notifier.set() + + async def start_network(self): + await super().start_network() + self._perpetual_trading.start() + self._funding_info_listener_task = safe_ensure_future(self._listen_for_funding_info()) + if self.is_trading_required: + self._funding_fee_polling_task = safe_ensure_future(self._funding_payment_polling_loop()) + + def set_position_mode(self, mode: PositionMode): + """ + Sets position mode for perpetual trading, a child class might need to override this to set position mode on + the exchange + :param mode: the position mode + """ + if mode in self.supported_position_modes(): + safe_ensure_future(self._execute_set_position_mode(mode)) + else: + self.logger().error(f"Position mode {mode} is not supported. Mode not set.") + + def get_leverage(self, trading_pair: str) -> int: + return self._perpetual_trading.get_leverage(trading_pair) + + def set_leverage(self, trading_pair: str, leverage: int = 1): + safe_ensure_future(self._execute_set_leverage(trading_pair, leverage)) + + def get_funding_info(self, trading_pair: str) -> FundingInfo: + return self._perpetual_trading.get_funding_info(trading_pair) + + def start_tracking_order( + self, + order_id: str, + exchange_order_id: Optional[str], + trading_pair: str, + trade_type: TradeType, + price: Decimal, + amount: Decimal, + order_type: OrderType, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ): + """ + Starts tracking an order by adding it to the order tracker. + + :param order_id: the order identifier + :param exchange_order_id: the identifier for the order in the exchange + :param trading_pair: the token pair for the operation + :param trade_type: the type of order (buy or sell) + :param price: the price for the order + :param amount: the amount for the order + :param order_type: type of execution for the order (MARKET, LIMIT, LIMIT_MAKER) + :param position_action: is the order opening or closing a position + """ + leverage = self.get_leverage(trading_pair=trading_pair) + self._order_tracker.start_tracking_order( + PerpetualDerivativeInFlightOrder( + client_order_id=order_id, + exchange_order_id=exchange_order_id, + trading_pair=trading_pair, + order_type=order_type, + trade_type=trade_type, + amount=amount, + price=price, + creation_timestamp=self.current_timestamp, + leverage=leverage, + position=position_action, + ) + ) + + @abstractmethod + def _create_order_book_data_source(self) -> PerpetualAPIOrderBookDataSource: + raise NotImplementedError + + @abstractmethod + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ) -> Tuple[str, float]: + raise NotImplementedError + + @abstractmethod + async def _update_positions(self): + raise NotImplementedError + + @abstractmethod + async def _trading_pair_position_mode_set( + self, mode: PositionMode, trading_pair: str + ) -> Tuple[bool, str]: + """ + :return: A tuple of boolean (true if success) and error message if the exchange returns one on failure. + """ + raise NotImplementedError + + @abstractmethod + async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]: + raise NotImplementedError + + @abstractmethod + async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[float, Decimal, Decimal]: + """ + Returns a tuple of the latest funding payment timestamp, funding rate, and payment amount. + If no payment exists, return (0, -1, -1) + """ + raise NotImplementedError + + def _stop_network(self): + self._funding_fee_poll_notifier = asyncio.Event() + self._perpetual_trading.stop() + if self._funding_info_listener_task is not None: + self._funding_info_listener_task.cancel() + self._funding_info_listener_task = None + self._last_funding_fee_payment_ts.clear() + super()._stop_network() + + async def _create_order( + self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price: Optional[Decimal] = None, + position_action: PositionAction = PositionAction.NIL, + **kwargs, + ): + """ + Creates an order in the exchange using the parameters to configure it + + :param trade_type: the side of the order (BUY of SELL) + :param order_id: the id that should be assigned to the order (the client id) + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :param position_action: is the order opening or closing a position + """ + + if position_action not in self.VALID_POSITION_ACTIONS: + raise ValueError( + f"Invalid position action {position_action}. Must be one of {self.VALID_POSITION_ACTIONS}" + ) + + await super()._create_order( + trade_type, + order_id, + trading_pair, + amount, + order_type, + price, + position_action=position_action, + **kwargs, + ) + + def get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + """ + Calculates the fee to pay based on the fee information provided by the exchange for + the account and the token pair. If exchange info is not available it calculates the estimated + fee an order would pay based on the connector configuration. + + :param base_currency: the order base currency + :param quote_currency: the order quote currency + :param order_type: the type of order (MARKET, LIMIT, LIMIT_MAKER) + :param order_side: if the order is for buying or selling + :param position_action: the position action associated with the order + :param amount: the order amount + :param price: the order price + :param is_maker: True if the order is a maker order, False if it is a taker order + + :return: the calculated or estimated fee + """ + return self._get_fee( + base_currency, quote_currency, order_type, order_side, position_action, amount, price, is_maker + ) + + @abstractmethod + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + position_action: PositionAction, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + raise NotImplementedError + + async def _status_polling_loop_fetch_updates(self): + await safe_gather( + self._update_positions(), + self._update_balances(), + self._update_order_status(), + ) + + async def _execute_set_position_mode(self, mode: PositionMode): + success, successful_pairs, msg = await self._execute_set_position_mode_for_pairs( + mode=mode, trading_pairs=self.trading_pairs + ) + + if not success: + await self._execute_set_position_mode_for_pairs( + mode=self._perpetual_trading.position_mode, trading_pairs=successful_pairs + ) + for trading_pair in self.trading_pairs: + self.trigger_event( + AccountEvent.PositionModeChangeFailed, + PositionModeChangeEvent( + self.current_timestamp, + trading_pair, + mode, + msg, + ), + ) + else: + self._perpetual_trading.set_position_mode(mode) + for trading_pair in self.trading_pairs: + self.trigger_event( + AccountEvent.PositionModeChangeSucceeded, + PositionModeChangeEvent( + self.current_timestamp, + trading_pair, + mode, + ) + ) + self.logger().debug(f"Position mode switched to {mode}.") + + async def _execute_set_position_mode_for_pairs( + self, mode: PositionMode, trading_pairs: List[str] + ) -> Tuple[bool, List[str], str]: + successful_pairs = [] + success = True + msg = "" + + for trading_pair in trading_pairs: + if mode != self._perpetual_trading.position_mode: + success, msg = await self._trading_pair_position_mode_set(mode, trading_pair) + if success: + successful_pairs.append(trading_pair) + else: + self.logger().network(f"Error switching {trading_pair} mode to {mode}: {msg}") + break + + return success, successful_pairs, msg + + async def _execute_set_leverage(self, trading_pair: str, leverage: int): + success, msg = await self._set_trading_pair_leverage(trading_pair, leverage) + if success: + self._perpetual_trading.set_leverage(trading_pair, leverage) + self.logger().info(f"Leverage for {trading_pair} successfully set to {leverage}.") + else: + self.logger().network(f"Error setting leverage {leverage} for {trading_pair}: {msg}") + + async def _listen_for_funding_info(self): + await self._init_funding_info() + await self._orderbook_ds.listen_for_funding_info( + output=self._perpetual_trading.funding_info_stream + ) + + async def _init_funding_info(self): + for trading_pair in self.trading_pairs: + funding_info = await self._orderbook_ds.get_funding_info(trading_pair) + self._perpetual_trading.initialize_funding_info(funding_info) + + async def _funding_payment_polling_loop(self): + """ + Periodically calls _update_funding_payment(), responsible for handling all funding payments. + """ + await self._update_all_funding_payments(fire_event_on_new=False) # initialization of the timestamps + while True: + await self._funding_fee_poll_notifier.wait() + success = await self._update_all_funding_payments(fire_event_on_new=True) + if success: + # Only when all tasks are successful would the event notifier be reset + self._funding_fee_poll_notifier = asyncio.Event() + + async def _update_all_funding_payments(self, fire_event_on_new: bool) -> bool: + success = False + try: + tasks = [] + for trading_pair in self.trading_pairs: + tasks.append( + asyncio.create_task( + self._update_funding_payment(trading_pair=trading_pair, fire_event_on_new=fire_event_on_new) + ) + ) + responses: List[bool] = await safe_gather(*tasks) + success = all(responses) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error while retrieving funding payments.", + exc_info=True, + app_warning_msg=( + f"Could not fetch funding fee updates for {self.name}. Check API key and network connection." + ) + ) + return success + + async def _update_funding_payment(self, trading_pair: str, fire_event_on_new: bool) -> bool: + fetch_success = True + timestamp = funding_rate = payment_amount = 0 + try: + timestamp, funding_rate, payment_amount = await self._fetch_last_fee_payment(trading_pair=trading_pair) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Unexpected error while fetching last fee payment for {trading_pair}.", + exc_info=True, + app_warning_msg=f"Could not fetch last fee payment for {trading_pair}. Check network connection." + ) + fetch_success = False + if fetch_success: + self._emit_funding_payment_event(trading_pair, timestamp, funding_rate, payment_amount, fire_event_on_new) + return fetch_success + + def _emit_funding_payment_event( + self, trading_pair: str, timestamp: int, funding_rate: Decimal, payment_amount: Decimal, fire_event_on_new: bool + ): + prev_timestamp = self._last_funding_fee_payment_ts.get(trading_pair, 0) + + if timestamp > prev_timestamp and fire_event_on_new: + action: str = "paid" if payment_amount < s_decimal_0 else "received" + self.logger().info(f"Funding payment of {abs(payment_amount)} {action} on {trading_pair} market.") + self.trigger_event( + MarketEvent.FundingPaymentCompleted, + FundingPaymentCompletedEvent( + timestamp=timestamp, + market=self.name, + funding_rate=funding_rate, + trading_pair=trading_pair, + amount=payment_amount, + ), + ) + self._last_funding_fee_payment_ts[trading_pair] = timestamp + + if trading_pair not in self._last_funding_fee_payment_ts: + self._last_funding_fee_payment_ts[trading_pair] = timestamp diff --git a/hummingbot/connector/perpetual_trading.py b/hummingbot/connector/perpetual_trading.py new file mode 100644 index 0000000..8a0127b --- /dev/null +++ b/hummingbot/connector/perpetual_trading.py @@ -0,0 +1,200 @@ +import asyncio +import copy +import logging +import warnings +from collections import defaultdict +from typing import Dict, List, Optional + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import PositionMode, PositionSide +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class PerpetualTrading: + """Keeps perpetual trading state.""" + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, trading_pairs: List[str]): + self._account_positions: Dict[str, Position] = {} + self._position_mode: PositionMode = PositionMode.ONEWAY + self._leverage: Dict[str, int] = defaultdict(lambda: 1) + self._trading_pairs = trading_pairs + + self._funding_info: Dict[str, FundingInfo] = {} + self._funding_payment_span: List[int] = [0, 0] + self._funding_info_stream = asyncio.Queue() + + self._funding_info_updater_task: Optional[asyncio.Task] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + @property + def account_positions(self) -> Dict[str, Position]: + """ + Returns a dictionary of current active open positions + """ + return self._account_positions + + @property + def funding_info(self) -> Dict[str, FundingInfo]: + """ + The funding information per trading pair. + """ + return copy.deepcopy(self._funding_info) + + @property + def funding_info_stream(self) -> asyncio.Queue: + """ + The stream to which to supply funding info updates to be processed by the class. + """ + return self._funding_info_stream + + def set_position(self, pos_key: str, position: Position): + self.logger().debug(f"Setting position {pos_key} to {Position}") + self._account_positions[pos_key] = position + + def remove_position(self, post_key: str) -> Optional[Position]: + return self._account_positions.pop(post_key, None) + + def initialize_funding_info(self, funding_info: FundingInfo): + """ + Initializes a single trading pair funding information. + """ + self._funding_info[funding_info.trading_pair] = funding_info + + def is_funding_info_initialized(self) -> bool: + """ + Checks if there is funding information for all trading pairs. + """ + return all( + trading_pair in self._funding_info + for trading_pair in self._trading_pairs + ) + + def start(self): + """ + Starts the async task that updates the funding information from the updates stream queue. + """ + self.stop() + self._funding_info_updater_task = safe_ensure_future( + self._funding_info_updater() + ) + + def stop(self): + """ + Stops the funding info updating async task. + """ + if self._funding_info_updater_task is not None: + self._funding_info_updater_task.cancel() + self._funding_info_updater_task = None + self._funding_info.clear() + + def position_key(self, trading_pair: str, side: PositionSide = None, mode: PositionMode = None) -> str: + """ + Returns a key to a position in account_positions. On OneWay position mode this is the trading pair. + On Hedge position mode this is a combination of trading pair and position side + :param trading_pair: The market trading pair + :param side: The position side (long or short) + :return: A key to the position in account_positions dictionary + """ + pos_key = "" + if mode is not None: + pos_key = f"{trading_pair}{side.name}" if mode == PositionMode.HEDGE else trading_pair + else: + pos_key = f"{trading_pair}{side.name}" if self._position_mode == PositionMode.HEDGE else trading_pair + return pos_key + + def get_position(self, trading_pair: str, side: PositionSide = None) -> Optional[Position]: + """ + Returns an active position if exists, otherwise returns None + :param trading_pair: The market trading pair + :param side: The position side (long or short) + :return: A position from account_positions or None + """ + return self.account_positions.get(self.position_key(trading_pair, side), None) + + @property + def funding_payment_span(self) -> List[int]: + """ + Time span(in seconds) before and after funding period when exchanges consider active positions eligible for + funding payment. + :return: a list of seconds (before and after) + """ + return self._funding_payment_span + + @property + def position_mode(self) -> PositionMode: + return self._position_mode + + def set_position_mode(self, value: PositionMode): + """ + Sets position mode for perpetual trading, a child class might need to override this to set position mode on + the exchange + :param value: the position mode + """ + self._position_mode = value + + def get_leverage(self, trading_pair: str) -> int: + """ + Gets leverage level of a particular market + :param trading_pair: the market trading pair + :return: leverage level + """ + return self._leverage[trading_pair] + + def set_leverage(self, trading_pair: str, leverage: int = 1): + """ + Sets leverage level, e.g. 2x, 10x, etc.. + A child class may need to override this to set leverage level on the exchange + :param trading_pair: the market trading pair + :param leverage: leverage to be used + """ + self._leverage[trading_pair] = leverage + + def get_funding_info(self, trading_pair: str) -> FundingInfo: + """ + Returns funding information + :param trading_pair: the market trading pair + :return: funding info + """ + return self._funding_info[trading_pair] + + async def _funding_info_updater(self): + while True: + try: + funding_info_message: FundingInfoUpdate = await self._funding_info_stream.get() + trading_pair = funding_info_message.trading_pair + funding_info = self._funding_info[trading_pair] + funding_info.update(funding_info_message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error updating funding info.", exc_info=True) + + def get_buy_collateral_token(self, trading_pair: str) -> str: + warnings.warn( + "This method is replaced by PerpetualDerivativePyBase.get_buy_collateral_token, and will be removed" + " once all perpetual connectors are updated to the latest standards.", + DeprecationWarning, + stacklevel=2, + ) + _, quote = split_hb_trading_pair(trading_pair) + return quote + + def get_sell_collateral_token(self, trading_pair: str) -> str: + warnings.warn( + "This method is replaced by PerpetualDerivativePyBase.get_sell_collateral_token, and will be removed" + " once all perpetual connectors are updated to the latest standards.", + DeprecationWarning, + stacklevel=2, + ) + _, quote = split_hb_trading_pair(trading_pair) + return quote diff --git a/hummingbot/connector/test_support/__init__.py b/hummingbot/connector/test_support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/test_support/exchange_connector_test.py b/hummingbot/connector/test_support/exchange_connector_test.py new file mode 100644 index 0000000..d3fd1f0 --- /dev/null +++ b/hummingbot/connector/test_support/exchange_connector_test.py @@ -0,0 +1,1946 @@ +import asyncio +import json +import re +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict + +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import TradeFeeBase +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class AbstractExchangeConnectorTests: + """ + We need to create the abstract TestCase class inside another class not inheriting from TestCase to prevent test + frameworks from discovering and tyring to run the abstract class + """ + + class ExchangeConnectorTests(ABC, TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + client_order_id_prefix = "1" + exchange_order_id_prefix = "2" + + @property + def exchange_trading_pair(self) -> str: + return self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + + @property + @abstractmethod + def all_symbols_url(self): + raise NotImplementedError + + @property + @abstractmethod + def latest_prices_url(self): + raise NotImplementedError + + @property + @abstractmethod + def network_status_url(self): + raise NotImplementedError + + @property + @abstractmethod + def trading_rules_url(self): + raise NotImplementedError + + @property + @abstractmethod + def order_creation_url(self): + raise NotImplementedError + + @property + @abstractmethod + def balance_url(self): + raise NotImplementedError + + @property + @abstractmethod + def all_symbols_request_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def latest_prices_request_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + raise NotImplementedError + + @property + @abstractmethod + def network_status_request_successful_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def trading_rules_request_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def trading_rules_request_erroneous_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def order_creation_request_successful_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def balance_request_mock_response_for_base_and_quote(self): + raise NotImplementedError + + @property + @abstractmethod + def balance_request_mock_response_only_base(self): + raise NotImplementedError + + @property + @abstractmethod + def balance_event_websocket_update(self): + raise NotImplementedError + + @property + @abstractmethod + def expected_latest_price(self): + raise NotImplementedError + + @property + @abstractmethod + def expected_supported_order_types(self): + raise NotImplementedError + + @property + @abstractmethod + def expected_trading_rule(self): + raise NotImplementedError + + @property + @abstractmethod + def expected_logged_error_for_erroneous_trading_rule(self): + raise NotImplementedError + + @property + @abstractmethod + def expected_exchange_order_id(self): + raise NotImplementedError + + @property + @abstractmethod + def is_order_fill_http_update_included_in_status_update(self) -> bool: + raise NotImplementedError + + @property + @abstractmethod + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + raise NotImplementedError + + @property + @abstractmethod + def expected_partial_fill_price(self) -> Decimal: + raise NotImplementedError + + @property + @abstractmethod + def expected_partial_fill_amount(self) -> Decimal: + raise NotImplementedError + + @property + @abstractmethod + def expected_fill_fee(self) -> TradeFeeBase: + raise NotImplementedError + + @property + @abstractmethod + def expected_fill_trade_id(self) -> str: + raise NotImplementedError + + @abstractmethod + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + raise NotImplementedError + + @abstractmethod + def create_exchange_instance(self): + raise NotImplementedError + + @abstractmethod + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + @abstractmethod + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + @abstractmethod + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + @abstractmethod + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + @abstractmethod + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + @abstractmethod + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured for the cancelation + """ + raise NotImplementedError + + @abstractmethod + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + raise NotImplementedError + + @abstractmethod + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + raise NotImplementedError + + @abstractmethod + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + + @abstractmethod + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> Union[str, List[str]]: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_order_not_found_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = None) -> str: + """ + :return: the URL configured + """ + raise NotImplementedError + + @abstractmethod + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + raise NotImplementedError + + @abstractmethod + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + raise NotImplementedError + + @abstractmethod + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + raise NotImplementedError + + @abstractmethod + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + raise NotImplementedError + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + self.exchange = self.create_exchange_instance() + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + if hasattr(self.exchange, "_time_synchronizer"): + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + + self._initialize_event_loggers() + + self.exchange._set_trading_pair_symbol_map( + bidict({self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset): self.trading_pair})) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def configure_all_symbols_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + url = self.all_symbols_url + response = self.all_symbols_request_mock_response + mock_api.get(url, body=json.dumps(response), callback=callback) + return [url] + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + url = self.trading_rules_url + response = self.trading_rules_request_mock_response + mock_api.get(url, body=json.dumps(response), callback=callback) + return [url] + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + url = self.trading_rules_url + response = self.trading_rules_request_erroneous_mock_response + mock_api.get(url, body=json.dumps(response), callback=callback) + return [url] + + def place_buy_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=order_type, + price=price, + ) + return order_id + + def place_sell_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT): + order_id = self.exchange.sell( + trading_pair=self.trading_pair, + amount=amount, + order_type=order_type, + price=price, + ) + return order_id + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertEqual(self.expected_supported_order_types, supported_types) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id=self.client_order_id_prefix + "2", + exchange_order_id=self.exchange_order_id_prefix + "2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(InFlightOrder( + client_order_id=self.client_order_id_prefix + "3", + exchange_order_id=self.exchange_order_id_prefix + "3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(InFlightOrder( + client_order_id=self.client_order_id_prefix + "4", + exchange_order_id=self.exchange_order_id_prefix + "4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "3", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "4", self.exchange.in_flight_orders) + + @aioresponses() + def test_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + self.configure_all_symbols_response(mock_api=mock_api) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + expected_valid_trading_pairs = self._expected_valid_trading_pairs() + + self.assertEqual(len(expected_valid_trading_pairs), len(all_trading_pairs)) + for trading_pair in expected_valid_trading_pairs: + self.assertIn(trading_pair, all_trading_pairs) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + url = self.all_symbols_url + + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + mock_api.get(url, body=json.dumps(response)) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + url = self.all_symbols_url + mock_api.get(url, exception=Exception) + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs()) + + self.assertEqual(0, len(result)) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + url = self.latest_prices_url + + response = self.latest_prices_request_mock_response + + mock_api.get(url, body=json.dumps(response)) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + @aioresponses() + def test_check_network_success(self, mock_api): + url = self.network_status_url + response = self.network_status_request_successful_mock_response + mock_api.get(url, body=json.dumps(response)) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + url = self.network_status_url + mock_api.get(url, status=500) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + url = self.network_status_url + + mock_api.get(url, exception=asyncio.CancelledError) + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + + status_dict = self.exchange.status_dict + + self.assertEqual(self._expected_initial_status_dict(), status_dict) + self.assertFalse(self.exchange.ready) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + self.configure_trading_rules_response(mock_api=mock_api) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + trading_rule: TradingRule = self.exchange.trading_rules[self.trading_pair] + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(trading_rule)) + + trading_rule_with_default_values = TradingRule(trading_pair=self.trading_pair) + + # The following element can't be left with the default value because that breaks quantization in Cython + self.assertNotEqual(trading_rule_with_default_values.min_base_amount_increment, + trading_rule.min_base_amount_increment) + self.assertNotEqual(trading_rule_with_default_values.min_price_increment, + trading_rule.min_price_increment) + + @aioresponses() + def test_update_trading_rules_ignores_rule_with_error(self, mock_api): + self.exchange._set_current_timestamp(1000) + + self.configure_erroneous_trading_rules_response(mock_api=mock_api) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange._trading_rules)) + self.assertTrue( + self.is_logged("ERROR", self.expected_logged_error_for_erroneous_trading_rule) + ) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = self.order_creation_url + mock_api.post(url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + order_to_validate_request = InFlightOrder( + client_order_id=order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("100"), + creation_timestamp=self.exchange.current_timestamp, + price=Decimal("10000") + ) + self.validate_order_creation_request( + order=order_to_validate_request, + request_call=order_request) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + mock_api.post(url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait(), timeout=3) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + url = self.configure_successful_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.async_run_with_timeout(request_sent_event.wait()) + + if url != "": + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + else: + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + url = self.configure_erroneous_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id=self.client_order_id_prefix + "1") + self.async_run_with_timeout(request_sent_event.wait()) + + if url != "": + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue( + any( + log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_order_not_found_error_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id=self.client_order_id_prefix + "1") + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertFalse(order.is_done) + self.assertFalse(order.is_failure) + self.assertFalse(order.is_cancelled) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_updatable_orders) + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("12", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["12"] + + urls = self.configure_one_successful_one_erroneous_cancel_all_response( + successful_order=order1, + erroneous_order=order2, + mock_api=mock_api) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + for url in urls: + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order1.client_order_id}." + ) + ) + + @aioresponses() + def test_update_balances(self, mock_api): + response = self.balance_request_mock_response_for_base_and_quote + self._configure_balance_response(response=response, mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("15"), total_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + response = self.balance_request_mock_response_only_base + + self._configure_balance_response(response=response, mock_api=mock_api) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), total_balances[self.base_asset]) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + urls = self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + if self.is_order_fill_http_update_included_in_status_update: + trade_url = self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + else: + # If the fill events will not be requested with the order status, we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + for url in (urls if isinstance(urls, list) else [urls]): + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request( + order=order, + request_call=order_status_request) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + + if self.is_order_fill_http_update_included_in_status_update: + self.assertTrue(order.is_filled) + if trade_url: + trades_request = self._all_executed_requests(mock_api, trade_url)[0] + self.validate_auth_credentials_present(trades_request) + self.validate_trades_request( + order=order, + request_call=trades_request) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual( + order.amount if self.is_order_fill_http_update_included_in_status_update else Decimal(0), + buy_event.base_asset_amount) + self.assertEqual( + order.amount * order.price + if self.is_order_fill_http_update_included_in_status_update + else Decimal(0), + buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_update_order_status_when_canceled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + urls = self.configure_canceled_order_status_response( + order=order, + mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + for url in (urls if isinstance(urls, list) else [urls]): + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request(order=order, request_call=order_status_request) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + urls = self.configure_open_order_status_response( + order=order, + mock_api=mock_api) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + for url in (urls if isinstance(urls, list) else [urls]): + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request(order=order, request_call=order_status_request) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + url = self.configure_http_error_order_status_response( + order=order, + mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + if url: + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request( + order=order, + request_call=order_status_request) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_url = self.configure_partially_filled_order_status_response( + order=order, + mock_api=mock_api) + + if self.is_order_fill_http_update_included_in_status_update: + trade_url = self.configure_partial_fill_trade_response( + order=order, + mock_api=mock_api) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + if order_url: + order_status_request = self._all_executed_requests(mock_api, order_url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request( + order=order, + request_call=order_status_request) + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + if self.is_order_fill_http_update_included_in_status_update: + if trade_url: + trades_request = self._all_executed_requests(mock_api, trade_url)[0] + self.validate_auth_credentials_present(trades_request) + self.validate_trades_request( + order=order, + request_call=trades_request) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(self.expected_partial_fill_price, fill_event.price) + self.assertEqual(self.expected_partial_fill_amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + @aioresponses() + def test_update_order_status_when_filled_correctly_processed_even_when_trade_fill_update_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + urls = self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api) + + if self.is_order_fill_http_update_included_in_status_update: + trade_url = self.configure_erroneous_http_fill_trade_response( + order=order, + mock_api=mock_api) + + # Since the trade fill update will fail we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + for url in (urls if isinstance(urls, list) else [urls]): + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request(order=order, request_call=order_status_request) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + if self.is_order_fill_http_update_included_in_status_update: + if trade_url: + trades_request = self._all_executed_requests(mock_api, trade_url)[0] + self.validate_auth_credentials_present(trades_request) + self.validate_trades_request( + order=order, + request_call=trades_request) + + self.assertEqual(0, len(self.order_filled_logger.event_log)) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_balance_update(self): + if self.exchange.real_time_balance_update: + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_raises_cancel_exception(self): + self.exchange._set_current_timestamp(1640780000) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._user_stream_tracker._user_stream = mock_queue + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, + self.exchange._user_stream_event_listener()) + + def test_user_stream_logs_errors(self): + self.exchange._set_current_timestamp(1640780000) + + incomplete_event = "Invalid message" + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + with patch(f"{type(self.exchange).__module__}.{type(self.exchange).__qualname__}._sleep"): + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error in user stream listener loop." + ) + ) + + @aioresponses() + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + if self.is_order_fill_http_update_included_in_status_update: + trade_url = self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + else: + # If the fill events will not be requested with the order status, we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + request_sent_event.set() + + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + if self.is_order_fill_http_update_included_in_status_update: + if trade_url: + trades_request = self._all_executed_requests(mock_api, trade_url)[0] + self.validate_auth_credentials_present(trades_request) + self.validate_trades_request( + order=order, + request_call=trades_request) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + request_sent_event.clear() + + # Configure again the response to the order fills request since it is required by lost orders update logic + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_cancel_lost_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + url = self.configure_successful_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + self.async_run_with_timeout(request_sent_event.wait()) + + if url: + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + else: + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_cancel_lost_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + url = self.configure_erroneous_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + self.async_run_with_timeout(request_sent_event.wait()) + + if url: + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue( + any( + log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records + ) + ) + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + if self.is_order_fill_http_update_included_in_status_update: + # This is done for completeness reasons (to have a response available for the trades request) + self.configure_erroneous_http_fill_trade_response(order=order, mock_api=mock_api) + + self.configure_order_not_found_error_order_status_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + + self.assertFalse( + self.is_logged("INFO", f"BUY order {order.client_order_id} completely filled.") + ) + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def _expected_valid_trading_pairs(self): + return [self.trading_pair] + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def _all_executed_requests(self, api_mock: aioresponses, url: Union[str, re.Pattern]) -> List[RequestCall]: + request_calls = [] + for key, value in api_mock.requests.items(): + req_url = key[1].human_repr() + its_a_match = ( + url.search(req_url) + if isinstance(url, re.Pattern) + else req_url.startswith(url) + ) + if its_a_match: + request_calls.extend(value) + return request_calls + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + + url = self.balance_url + mock_api.get( + re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")), + body=json.dumps(response), + callback=callback) + return url + + def _expected_initial_status_dict(self) -> Dict[str, bool]: + return { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } diff --git a/hummingbot/connector/test_support/gateway_clob_api_data_source_test.py b/hummingbot/connector/test_support/gateway_clob_api_data_source_test.py new file mode 100644 index 0000000..c9a594f --- /dev/null +++ b/hummingbot/connector/test_support/gateway_clob_api_data_source_test.py @@ -0,0 +1,1015 @@ +import asyncio +import time +from abc import ABC, abstractmethod +from decimal import Decimal +from queue import Queue +from typing import Any, Awaitable, Dict, List, Tuple, Type, Union +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base import ( + GatewayCLOBAPIDataSourceBase, +) +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import ( + AddedToCostTradeFee, + MakerTakerExchangeFeeRates, + TokenAmount, + TradeFeeBase, +) +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookDataSourceEvent +from hummingbot.core.network_iterator import NetworkStatus + + +class MockExchange(ExchangeBase): + pass + + +class AbstractGatewayCLOBAPIDataSourceTests: + """ + We need to create the abstract TestCase class inside another class not inheriting from TestCase to prevent test + frameworks from discovering and tyring to run the abstract class + """ + + class GatewayCLOBAPIDataSourceTests(ABC, TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + base: str + quote: str + trading_pair: str + account_id: str + + @property + @abstractmethod + def expected_buy_exchange_order_id(self) -> str: + ... + + @property + @abstractmethod + def expected_sell_exchange_order_id(self) -> str: + ... + + @property + @abstractmethod + def exchange_base(self) -> str: + ... + + @property + @abstractmethod + def exchange_quote(self) -> str: + ... + + @abstractmethod + def build_api_data_source(self) -> GatewayCLOBAPIDataSourceBase: + ... + + @abstractmethod + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + ... + + @abstractmethod + def get_trading_pairs_info_response(self) -> List[Dict[str, Any]]: + ... + + @abstractmethod + def get_order_status_response( + self, + timestamp: float, + trading_pair: str, + exchange_order_id: str, + client_order_id: str, + status: OrderState, + ) -> List[Dict[str, Any]]: + ... + + @abstractmethod + def get_clob_ticker_response(self, trading_pair: str, last_traded_price: Decimal) -> List[Dict[str, Any]]: + ... + + @abstractmethod + def configure_account_balances_response( + self, + base_total_balance: Decimal, + base_available_balance: Decimal, + quote_total_balance: Decimal, + quote_available_balance: Decimal, + ): + ... + + @abstractmethod + def configure_empty_order_fills_response(self): + ... + + @abstractmethod + def configure_trade_fill_response( + self, + timestamp: float, + exchange_order_id: str, + price: Decimal, + size: Decimal, + fee: TradeFeeBase, + trade_id: Union[str, int], + is_taker: bool, + ): + ... + + @property + def expected_min_price_increment(self): + return Decimal("0.00001") + + @property + def exchange_trading_pair(self) -> str: + return self.exchange_symbol_for_tokens(self.base, self.quote) + + @property + def expected_buy_client_order_id(self) -> str: + return "someBuyClientOrderID" + + @property + def expected_sell_client_order_id(self) -> str: + return "someSellClientOrderID" + + @property + def expected_buy_order_price(self) -> Decimal: + return Decimal("10") + + @property + def expected_sell_order_price(self) -> Decimal: + return Decimal("11") + + @property + def expected_buy_order_size(self) -> Decimal: + return Decimal("2") + + @property + def expected_sell_order_size(self) -> Decimal: + return Decimal("3") + + @property + def expected_base_total_balance(self) -> Decimal: + return Decimal("10") + + @property + def expected_base_available_balance(self) -> Decimal: + return Decimal("9") + + @property + def expected_quote_total_balance(self) -> Decimal: + return Decimal("200") + + @property + def expected_quote_available_balance(self) -> Decimal: + return Decimal("150") + + @property + def expected_last_traded_price(self) -> Decimal: + return Decimal("9") + + @property + def expected_fill_price(self) -> Decimal: + return Decimal("3") + + @property + def expected_fill_size(self) -> Decimal: + return Decimal("4") + + @property + def expected_fill_fee_token(self) -> str: + return self.quote + + @property + def expected_fill_fee_amount(self) -> Decimal: + return Decimal("0.02") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.expected_fill_fee_token, amount=self.expected_fill_fee_amount)] + ) + + @property + def expected_maker_taker_fee_rates(self) -> MakerTakerExchangeFeeRates: + return MakerTakerExchangeFeeRates( + maker=Decimal("0.001"), + taker=Decimal("0.002"), + maker_flat_fees=[], + taker_flat_fees=[], + ) + + @property + def expected_fill_trade_id(self) -> Union[str, int]: + return "someTradeID" + + @property + def expected_transaction_hash(self) -> str: + return "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + + @property + def expected_total_balance(self) -> Decimal: + return Decimal("20") + + @property + def expected_available_balance(self) -> Decimal: + return Decimal("19") + + @property + def expected_event_counts_per_new_order(self) -> int: + return 1 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base = "COIN" + cls.quote = "ALPHA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.account_id = "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18" # noqa: mock + + def setUp(self) -> None: + super().setUp() + + self.ev_loop = asyncio.get_event_loop() + self.log_records = [] + self.initial_timestamp = 1669100347 + + self.gateway_instance_mock_patch = patch( + target=( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayHttpClient" + ), + autospec=True, + ) + self.gateway_instance_mock = self.gateway_instance_mock_patch.start() + self.gateway_instance_mock.get_instance.return_value = self.gateway_instance_mock + self.order_status_response_queue = Queue() + + self.client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) + self.connector = MockExchange(client_config_map=self.client_config_map) + self.data_source = self.build_api_data_source() + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + self.tracker = self.build_order_tracker(connector=self.connector) + self.data_source.gateway_order_tracker = self.tracker + + self.order_updates_logger = EventLogger() + self.snapshots_logger = EventLogger() + + self.data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=self.order_updates_logger) + self.data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=self.snapshots_logger + ) + + self.configure_trading_pairs_info_response() + self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[10, 2], [9, 3]], asks=[[12, 4]] + ) + + self.async_run_with_timeout(coro=self.data_source.start()) + + self.additional_data_sources_to_stop_on_tear_down = [] + self.async_tasks = [] + + def tearDown(self) -> None: + self.async_run_with_timeout(coro=self.data_source.stop()) + self.gateway_instance_mock_patch.stop() + for data_source in self.additional_data_sources_to_stop_on_tear_down: + self.async_run_with_timeout(coro=data_source.stop()) + for task in self.async_tasks: + task.cancel() + super().tearDown() + + @staticmethod + def async_run_with_timeout(coro: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coro, timeout)) + return ret + + @staticmethod + def build_order_tracker(connector: ExchangeBase) -> GatewayOrderTracker: + return GatewayOrderTracker(connector=connector) + + def is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def handle(self, record): + self.log_records.append(record) + + def configure_trading_pairs_info_response(self): + markets = self.get_trading_pairs_info_response() + mock_response = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "markets": markets, + } + self.gateway_instance_mock.get_clob_markets.return_value = mock_response + + def enqueue_order_status_response( + self, + timestamp: float, + trading_pair: str, + exchange_order_id: str, + client_order_id: str, + status: OrderState, + ) -> asyncio.Event: + orders = self.get_order_status_response( + timestamp=timestamp, + trading_pair=trading_pair, + exchange_order_id=exchange_order_id, + client_order_id=client_order_id, + status=status, + ) + mock_response = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "orders": orders, + } + update_delivered_event = asyncio.Event() + + self.order_status_response_queue.put(item=(mock_response, update_delivered_event)) + + async def deliver_event(*_, **__): + mock_response_, update_delivered_event_ = self.order_status_response_queue.get() + update_delivered_event_.set() + return mock_response_ + + self.gateway_instance_mock.get_clob_order_status_updates.side_effect = deliver_event + + return update_delivered_event + + def enqueue_order_status_responses_for_batch_order_create( + self, timestamp: float, orders: List[GatewayInFlightOrder], statuses: List[OrderState] + ) -> asyncio.Event: + assert len(orders) == len(statuses) != 0 + update_delivered_event = None + for order, status in zip(orders, statuses): + update_delivered_event = self.enqueue_order_status_response( + timestamp=timestamp, + trading_pair=order.trading_pair, + exchange_order_id=order.exchange_order_id, + client_order_id=order.client_order_id, + status=status, + ) + return update_delivered_event # returning the last event + + def enqueue_order_status_responses_for_batch_order_cancel( + self, timestamp: float, orders: List[GatewayInFlightOrder], statuses: List[OrderState] + ) -> asyncio.Event: + return self.enqueue_order_status_responses_for_batch_order_create( + timestamp=timestamp, orders=orders, statuses=statuses + ) + + def configure_check_network_success(self): + self.gateway_instance_mock.ping_gateway.side_effect = None + + def configure_check_network_failure(self, exc: Type[Exception] = RuntimeError): + self.gateway_instance_mock.ping_gateway.side_effect = exc + + def configure_orderbook_snapshot( + self, timestamp: int, bids: List[List[float]], asks: List[List[float]], latency: float = 0 + ) -> Tuple[asyncio.Event, asyncio.Event]: + snapshot_requested = asyncio.Event() + snapshot_delivered = asyncio.Event() + + async def deliver_snapshot(*_, **__): + snapshot_requested.set() + if latency: + await asyncio.sleep(latency) + snapshot_resp = self.get_orderbook_snapshot_response( + timestamp=timestamp, bids=bids, asks=asks + ) + snapshot_delivered.set() + return snapshot_resp + + self.gateway_instance_mock.get_clob_orderbook_snapshot.side_effect = deliver_snapshot + + return snapshot_requested, snapshot_delivered + + def configure_place_order_response( + self, + timestamp: float, + transaction_hash: str, + exchange_order_id: str, + trade_type: TradeType, + price: Decimal, + size: Decimal, + ): + response = { + "network": self.data_source.network, + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash, + } + self.gateway_instance_mock.clob_place_order.return_value = response + + def configure_place_order_failure_response(self): + self.gateway_instance_mock.clob_place_order.return_value = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "txHash": None, + } + + def configure_batch_order_create_response( + self, + timestamp: float, + transaction_hash: str, + created_orders: List[GatewayInFlightOrder], + ): + response = { + "network": self.data_source.network, + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash, + } + self.gateway_instance_mock.clob_batch_order_modify.return_value = response + + def configure_cancel_order_response(self, timestamp: float, transaction_hash: str): + response = { + "network": self.data_source.network, + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash, + } + self.gateway_instance_mock.clob_cancel_order.return_value = response + + def configure_cancel_order_failure_response(self): + self.gateway_instance_mock.clob_cancel_order.return_value = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "txHash": None, + } + + def configure_batch_order_cancel_response( + self, + timestamp: float, + transaction_hash: str, + canceled_orders: List[GatewayInFlightOrder], + ): + response = { + "network": self.data_source.network, + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash, + } + self.gateway_instance_mock.clob_batch_order_modify.return_value = response + + def configure_last_traded_price(self, trading_pair: str, last_traded_price: Decimal): + ticker_response = self.get_clob_ticker_response(trading_pair=trading_pair, + last_traded_price=last_traded_price) + response = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "markets": ticker_response, + } + self.gateway_instance_mock.get_clob_ticker.return_value = response + + def get_orderbook_snapshot_response( + self, timestamp: int, bids: List[List[float]], asks: List[List[float]] + ): + return { + "timestamp": timestamp, + "network": self.data_source.network, + "latency": 2, + "buys": [ + {"price": bid[0], "quantity": bid[1], "timestamp": timestamp * 1e3} + for bid in bids + ], + "sells": [ + {"price": ask[0], "quantity": ask[1], "timestamp": timestamp * 1e3} + for ask in asks + ], + } + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_place_order(self, sleep_mock: AsyncMock): + def sleep_mock_side_effect(delay): + raise Exception + + sleep_mock.side_effect = sleep_mock_side_effect + + self.configure_place_order_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + trade_type=TradeType.BUY, + price=self.expected_buy_order_price, + size=self.expected_buy_order_size, + ) + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + ) + exchange_order_id, misc_updates = self.async_run_with_timeout( + coro=self.data_source.place_order(order=order) + ) + + self.assertEqual({"creation_transaction_hash": self.expected_transaction_hash}, misc_updates) + + def test_place_order_transaction_fails(self): + self.configure_place_order_failure_response() + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + ) + + with self.assertRaises(ValueError): + self.async_run_with_timeout( + coro=self.data_source.place_order(order=order) + ) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_batch_order_create(self, sleep_mock: AsyncMock): + def sleep_mock_side_effect(delay): + raise Exception + + sleep_mock.side_effect = sleep_mock_side_effect + + buy_order_to_create = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + sell_order_to_create = GatewayInFlightOrder( + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + exchange_order_id=self.expected_sell_exchange_order_id, + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + self.configure_batch_order_create_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + created_orders=orders_to_create, + ) + + for order in orders_to_create: + order.exchange_order_id = None # the orders are new + + result: List[PlaceOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_create(orders_to_create=orders_to_create) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(self.expected_buy_client_order_id, result[0].client_order_id) + self.assertEqual({"creation_transaction_hash": self.expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(self.expected_sell_client_order_id, result[1].client_order_id) + self.assertEqual({"creation_transaction_hash": self.expected_transaction_hash}, result[1].misc_updates) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_cancel_order(self, sleep_mock: AsyncMock): + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_timestamp=self.initial_timestamp, + exchange_order_id=self.expected_buy_exchange_order_id, + creation_transaction_hash="someCreationHash", + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=order) + self.configure_cancel_order_response( + timestamp=self.initial_timestamp, transaction_hash=self.expected_transaction_hash + ) + cancelation_success, misc_updates = self.async_run_with_timeout( + coro=self.data_source.cancel_order(order=order) + ) + + self.assertTrue(cancelation_success) + self.assertEqual({"cancelation_transaction_hash": self.expected_transaction_hash}, misc_updates) + + def test_cancel_order_transaction_fails(self): + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_timestamp=self.initial_timestamp, + exchange_order_id=self.expected_buy_exchange_order_id, + creation_transaction_hash="someCreationHash", + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=order) + self.configure_cancel_order_failure_response() + + with self.assertRaises(ValueError): + self.async_run_with_timeout(coro=self.data_source.cancel_order(order=order)) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_batch_order_cancel(self, sleep_mock: AsyncMock): + buy_order_to_cancel = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_timestamp=self.initial_timestamp, + exchange_order_id=self.expected_buy_exchange_order_id, + creation_transaction_hash=self.expected_transaction_hash, + ) + sell_order_to_cancel = GatewayInFlightOrder( + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + creation_timestamp=self.initial_timestamp, + exchange_order_id=self.expected_sell_exchange_order_id, + creation_transaction_hash=self.expected_transaction_hash, + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=buy_order_to_cancel) + self.data_source.gateway_order_tracker.start_tracking_order(order=sell_order_to_cancel) + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + self.configure_batch_order_cancel_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + canceled_orders=orders_to_cancel, + ) + + result: List[CancelOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_cancel(orders_to_cancel=orders_to_cancel) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(buy_order_to_cancel.client_order_id, result[0].client_order_id) + self.assertIsNone(result[0].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": self.expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(sell_order_to_cancel.client_order_id, result[1].client_order_id) + self.assertIsNone(result[1].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": self.expected_transaction_hash}, result[1].misc_updates) + + def test_get_trading_rules(self): + trading_rules = self.async_run_with_timeout(coro=self.data_source.get_trading_rules()) + + self.assertEqual(2, len(trading_rules)) + self.assertIn(self.trading_pair, trading_rules) + + trading_rule: TradingRule = trading_rules[self.trading_pair] + + self.assertEqual(self.trading_pair, trading_rule.trading_pair) + self.assertEqual(self.expected_min_price_increment, trading_rule.min_price_increment) + + def test_get_symbol_map(self): + symbol_map = self.async_run_with_timeout(coro=self.data_source.get_symbol_map()) + + self.assertIsInstance(symbol_map, bidict) + self.assertEqual(2, len(symbol_map)) + self.assertIn(self.exchange_trading_pair, symbol_map) + self.assertIn(self.trading_pair, symbol_map.inverse) + + def test_get_last_traded_price(self): + self.configure_last_traded_price( + trading_pair=self.trading_pair, last_traded_price=self.expected_last_traded_price + ) + last_trade_price = self.async_run_with_timeout( + coro=self.data_source.get_last_traded_price(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.expected_last_traded_price, last_trade_price) + + def test_get_order_book_snapshot(self): + self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[9, 1], [8, 2]], asks=[[11, 3]] + ) + order_book_snapshot: OrderBookMessage = self.async_run_with_timeout( + coro=self.data_source.get_order_book_snapshot(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.initial_timestamp, order_book_snapshot.timestamp) + self.assertEqual(2, len(order_book_snapshot.bids)) + self.assertEqual(9, order_book_snapshot.bids[0].price) + self.assertEqual(1, order_book_snapshot.bids[0].amount) + self.assertEqual(1, len(order_book_snapshot.asks)) + self.assertEqual(11, order_book_snapshot.asks[0].price) + self.assertEqual(3, order_book_snapshot.asks[0].amount) + + def test_delivers_order_book_snapshot_events(self): + self.async_run_with_timeout(self.data_source.stop()) + + data_source = self.build_api_data_source() + self.additional_data_sources_to_stop_on_tear_down.append(data_source) + data_source.min_snapshots_update_interval = 0 + data_source.max_snapshots_update_interval = 0 + + snapshots_logger = EventLogger() + + data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=snapshots_logger + ) + + _, snapshot_delivered = self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[9, 1], [8, 2]], asks=[[11, 3]] + ) + + self.async_run_with_timeout(coro=data_source.start()) + self.async_run_with_timeout(coro=snapshot_delivered.wait()) + + self.assertEqual(1, len(snapshots_logger.event_log)) + + snapshot_event: OrderBookMessage = snapshots_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, snapshot_event.timestamp) + self.assertEqual(2, len(snapshot_event.bids)) + self.assertEqual(9, snapshot_event.bids[0].price) + self.assertEqual(1, snapshot_event.bids[0].amount) + self.assertEqual(1, len(snapshot_event.asks)) + self.assertEqual(11, snapshot_event.asks[0].price) + self.assertEqual(3, snapshot_event.asks[0].amount) + + def test_minimum_delay_between_requests_for_snapshot_events(self): + self.async_run_with_timeout(self.data_source.stop()) + + minimum_delay = 0.5 + + data_source = self.build_api_data_source() + self.additional_data_sources_to_stop_on_tear_down.append(data_source) + data_source.min_snapshots_update_interval = minimum_delay + data_source.max_snapshots_update_interval = minimum_delay + 1 + + snapshots_logger = EventLogger() + + data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=snapshots_logger + ) + + # startup + self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[10, 2]], asks=[] + ) + self.async_run_with_timeout(coro=data_source.start()) + + # first snapshot + snapshot_requested, _ = self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[11, 3]], asks=[] + ) + + self.async_run_with_timeout(coro=snapshot_requested.wait()) + first_request_ts = time.time() + + # second snapshot + snapshot_requested, snapshot_delivered = self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[12, 4]], asks=[] + ) + self.async_run_with_timeout(coro=snapshot_requested.wait()) + second_request_ts = time.time() + + self.assertGreater(second_request_ts - first_request_ts, minimum_delay) + + self.async_run_with_timeout(coro=snapshot_delivered.wait()) + + snapshot_event: OrderBookMessage = snapshots_logger.event_log[-1] + + self.assertEqual(12, snapshot_event.bids[0].price) + self.assertEqual(4, snapshot_event.bids[0].amount) + self.assertFalse( + self.is_logged( + log_level="WARNING", + message=f"Snapshot update took longer than {self.data_source.max_snapshots_update_interval}.", + ) + ) + + def test_maximum_delay_between_requests_for_snapshot_events(self): + self.async_run_with_timeout(self.data_source.stop()) + + maximum_delay = 0.1 + + data_source = self.build_api_data_source() + self.additional_data_sources_to_stop_on_tear_down.append(data_source) + data_source.min_snapshots_update_interval = 0 + data_source.max_snapshots_update_interval = maximum_delay + + snapshots_logger = EventLogger() + + data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=snapshots_logger + ) + + # startup + self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[10, 2]], asks=[] + ) + self.async_run_with_timeout(coro=data_source.start()) + + # first snapshot + snapshot_requested, slow_snapshot_delivered = self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp + 1, bids=[[11, 3]], asks=[], latency=maximum_delay * 2 + ) + + first_request_ts = time.time() + self.async_run_with_timeout(coro=snapshot_requested.wait()) + + # second snapshot + snapshot_requested, quick_snapshot_delivered = self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp + 2, bids=[[12, 4]], asks=[] + ) + self.async_run_with_timeout(coro=snapshot_requested.wait(), timeout=maximum_delay * 2) + second_request_ts = time.time() + + self.assertLess(second_request_ts - first_request_ts, maximum_delay * 1.1) + self.assertGreater(second_request_ts - first_request_ts, maximum_delay * 0.9) + + self.async_run_with_timeout(coro=quick_snapshot_delivered.wait()) + + snapshot_event: OrderBookMessage = snapshots_logger.event_log[-1] + + self.assertEqual(12, snapshot_event.bids[0].price) + self.assertEqual(4, snapshot_event.bids[0].amount) + + self.async_run_with_timeout(coro=slow_snapshot_delivered.wait()) + + self.assertTrue( + self.is_logged( + log_level="WARNING", + message=f"Snapshot update took longer than {data_source.max_snapshots_update_interval}.", + ) + ) + + def test_get_account_balances(self): + self.configure_account_balances_response( + base_total_balance=self.expected_base_total_balance, + base_available_balance=self.expected_base_available_balance, + quote_total_balance=self.expected_quote_total_balance, + quote_available_balance=self.expected_quote_available_balance, + ) + + sub_account_balances = self.async_run_with_timeout(coro=self.data_source.get_account_balances()) + + self.assertEqual(self.expected_base_total_balance, sub_account_balances[self.base]["total_balance"]) + self.assertEqual(self.expected_base_available_balance, sub_account_balances[self.base]["available_balance"]) + self.assertEqual(self.expected_quote_total_balance, sub_account_balances[self.quote]["total_balance"]) + self.assertEqual( + self.expected_quote_available_balance, sub_account_balances[self.quote]["available_balance"] + ) + + def test_get_order_status_update(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.PARTIALLY_FILLED, + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.PARTIALLY_FILLED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(self.expected_buy_exchange_order_id, status_update.exchange_order_id) + + def test_get_all_order_fills_no_fills(self): + expected_order_id = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + self.configure_empty_order_fills_response() + in_flight_order = GatewayInFlightOrder( + client_order_id="someOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + exchange_order_id=expected_order_id, + ) + + trade_updates = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(0, len(trade_updates)) + + def test_get_all_order_fills(self): + expected_fill_ts = self.initial_timestamp + 10 + self.configure_trade_fill_response( + timestamp=expected_fill_ts, + exchange_order_id=self.expected_sell_exchange_order_id, + price=self.expected_fill_price, + size=self.expected_fill_size, + fee=self.expected_fill_fee, + trade_id=self.expected_fill_trade_id, + is_taker=True, + ) + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + exchange_order_id=self.expected_sell_exchange_order_id, + ) + + trade_updates: List[TradeUpdate] = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(1, len(trade_updates)) + + trade_update = trade_updates[0] + + self.assertEqual(self.expected_fill_trade_id, trade_update.trade_id) + self.assertEqual(self.expected_sell_client_order_id, trade_update.client_order_id) + self.assertEqual(self.expected_sell_exchange_order_id, trade_update.exchange_order_id) + self.assertEqual(self.trading_pair, trade_update.trading_pair) + self.assertEqual(expected_fill_ts, trade_update.fill_timestamp) + self.assertEqual(self.expected_fill_price, trade_update.fill_price) + self.assertEqual(self.expected_fill_size, trade_update.fill_base_amount) + self.assertEqual(self.expected_fill_size * self.expected_fill_price, trade_update.fill_quote_amount) + self.assertEqual(self.expected_fill_fee, trade_update.fee) + self.assertTrue(trade_update.is_taker) + + def test_check_network_status(self): + self.configure_check_network_failure() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, status) + + self.configure_check_network_success() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.CONNECTED, status) + + def test_get_trading_fees(self): + all_trading_fees = self.async_run_with_timeout(coro=self.data_source.get_trading_fees()) + + self.assertIn(self.trading_pair, all_trading_fees) + + pair_trading_fees: MakerTakerExchangeFeeRates = all_trading_fees[self.trading_pair] + + self.assertEqual(self.expected_maker_taker_fee_rates, pair_trading_fees) + + def test_delivers_balance_events(self): + if self.data_source.real_time_balance_update: + raise NotImplementedError diff --git a/hummingbot/connector/test_support/mock_order_tracker.py b/hummingbot/connector/test_support/mock_order_tracker.py new file mode 100644 index 0000000..8ddedf3 --- /dev/null +++ b/hummingbot/connector/test_support/mock_order_tracker.py @@ -0,0 +1,47 @@ +import asyncio +from typing import Dict, List + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker, OrderBookTrackerDataSource + + +class MockOrderBookTrackerDataSource(OrderBookTrackerDataSource): + @staticmethod + async def fetch_trading_pairs() -> List[str]: + pass + + @classmethod + async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: + pass + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + pass + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + pass + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + pass + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + pass + + +class MockOrderTracker(OrderBookTracker): + def __init__(self): + self._data_source: MockOrderBookTrackerDataSource = MockOrderBookTrackerDataSource([]) + # self._trading_pairs: List[str] = trading_pairs + self._order_books: Dict[str, OrderBook] = {} + + # def exchange_name(self): + # return "MockPaperExchange" # self.__class__.__name__ + + @property + def ready(self) -> bool: + return True + + def start(self): + pass + + def stop(self): + pass diff --git a/hummingbot/connector/test_support/mock_paper_exchange.pxd b/hummingbot/connector/test_support/mock_paper_exchange.pxd new file mode 100644 index 0000000..85b6818 --- /dev/null +++ b/hummingbot/connector/test_support/mock_paper_exchange.pxd @@ -0,0 +1,10 @@ +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange cimport PaperTradeExchange + +cdef class MockPaperExchange(PaperTradeExchange): + cdef c_set_balanced_order_book(self, + str trading_pair, + double mid_price, + double min_price, + double max_price, + double price_step_size, + double volume_step_size) diff --git a/hummingbot/connector/test_support/mock_paper_exchange.pyx b/hummingbot/connector/test_support/mock_paper_exchange.pyx new file mode 100644 index 0000000..eebe2ec --- /dev/null +++ b/hummingbot/connector/test_support/mock_paper_exchange.pyx @@ -0,0 +1,174 @@ +from decimal import Decimal +from typing import List, Optional, Tuple, TYPE_CHECKING + +import numpy as np + +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map, fee_overrides_dict +from hummingbot.client.settings import AllConnectorSettings, ConnectorSetting, ConnectorType +from hummingbot.connector.connector_base cimport ConnectorBase +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange cimport PaperTradeExchange, QuantizationParams +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.exchange.paper_trade.trading_pair import TradingPair +from hummingbot.connector.test_support.mock_order_tracker import MockOrderTracker +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.composite_order_book cimport CompositeOrderBook +from hummingbot.core.data_type.order_book import OrderBookRow +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.network_iterator import NetworkStatus + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +s_decimal_0 = Decimal("0") + +cdef class MockPaperExchange(PaperTradeExchange): + + def __init__(self, client_config_map: "ClientConfigAdapter", trade_fee_schema: Optional[TradeFeeSchema] = None): + PaperTradeExchange.__init__( + self, + client_config_map, + MockOrderTracker(), + MockPaperExchange, + exchange_name="mock", + ) + + trade_fee_schema = trade_fee_schema or TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0"), taker_percent_fee_decimal=Decimal("0") + ) + AllConnectorSettings.get_connector_settings()[self.name] = ConnectorSetting( + self.name, + ConnectorType.Exchange, + example_pair="", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=trade_fee_schema, + config_keys={}, + is_sub_domain=False, + parent_name="", + domain_parameter="", + use_eth_gas_lookup=False, + ) + fee_overrides_config_map.update(fee_overrides_dict()) + + @property + def name(self) -> str: + return "mock_paper_exchange" + + @property + def display_name(self) -> str: + return self.name + + @property + def ready(self): + return True + + def split_trading_pair(self, trading_pair: str) -> Tuple[str, str]: + return trading_pair.split("-") + + def new_empty_order_book(self, trading_pair: str): + order_book = CompositeOrderBook() + order_book.c_add_listener(self.ORDER_BOOK_TRADE_EVENT_TAG, self._order_book_trade_listener) + base_asset, quote_asset = self.split_trading_pair(trading_pair) + self._trading_pairs[trading_pair] = TradingPair(trading_pair, base_asset, quote_asset) + self.order_book_tracker._order_books[trading_pair] = order_book + + def set_balanced_order_book(self, + str trading_pair, + double mid_price, + double min_price, + double max_price, + double price_step_size, + double volume_step_size): + self.c_set_balanced_order_book(trading_pair, mid_price, min_price, max_price, price_step_size, volume_step_size) + + cdef c_set_balanced_order_book(self, + str trading_pair, + double mid_price, + double min_price, + double max_price, + double price_step_size, + double volume_step_size): + cdef: + list bids = [] + list asks = [] + double current_price + double current_size + CompositeOrderBook order_book + order_book = CompositeOrderBook() + current_price = mid_price - price_step_size / 2 + current_size = volume_step_size + while current_price >= min_price: + bids.append(OrderBookRow(current_price, current_size, 1)) + current_price -= price_step_size + current_size += volume_step_size + + current_price = mid_price + price_step_size / 2 + current_size = volume_step_size + while current_price <= max_price: + asks.append(OrderBookRow(current_price, current_size, 1)) + current_price += price_step_size + current_size += volume_step_size + + order_book.apply_snapshot(bids, asks, 1) + order_book.c_add_listener(self.ORDER_BOOK_TRADE_EVENT_TAG, self._order_book_trade_listener) + base_asset, quote_asset = self.split_trading_pair(trading_pair) + self._trading_pairs[trading_pair] = TradingPair(trading_pair, base_asset, quote_asset) + self.order_book_tracker._order_books[trading_pair] = order_book + + cdef object c_get_order_price_quantum(self, str trading_pair, object price): + cdef: + QuantizationParams q_params + if trading_pair in self._quantization_params: + q_params = self._quantization_params[trading_pair] + decimals_quantum = Decimal(f"1e-{q_params.price_decimals}") + if price > s_decimal_0: + precision_quantum = Decimal(f"1e{np.ceil(np.log10(price)) - q_params.price_precision}") + else: + precision_quantum = s_decimal_0 + return max(precision_quantum, decimals_quantum) + else: + return Decimal(f"1e-15") + + cdef object c_get_order_size_quantum(self, str trading_pair, object order_size): + cdef: + QuantizationParams q_params + if trading_pair in self._quantization_params: + q_params = self._quantization_params[trading_pair] + decimals_quantum = Decimal(f"1e-{q_params.order_size_decimals}") + if order_size > s_decimal_0: + precision_quantum = Decimal(f"1e{np.ceil(np.log10(order_size)) - q_params.order_size_precision}") + else: + precision_quantum = s_decimal_0 + return max(precision_quantum, decimals_quantum) + else: + return Decimal(f"1e-15") + + cdef object c_quantize_order_price(self, + str trading_pair, + object price): + return ConnectorBase.c_quantize_order_price(self, trading_pair, price) + + cdef object c_quantize_order_amount(self, + str trading_pair, + object amount, + object price=s_decimal_0): + return ConnectorBase.c_quantize_order_amount(self, trading_pair, amount, price) + + def set_quantization_param(self, QuantizationParams p): + self._quantization_params[p.trading_pair] = p + + def supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.MARKET] + + def get_taker_order_type(self): + return OrderType.MARKET + + cdef c_start(self, Clock clock, double timestamp): + PaperTradeExchange.c_start(self, clock, timestamp) + self._network_status = NetworkStatus.CONNECTED + + async def _check_network_loop(self): + # Override the check network loop to exit immediately. + self._network_status = NetworkStatus.CONNECTED diff --git a/hummingbot/connector/test_support/mock_pure_python_paper_exchange.py b/hummingbot/connector/test_support/mock_pure_python_paper_exchange.py new file mode 100644 index 0000000..d99e389 --- /dev/null +++ b/hummingbot/connector/test_support/mock_pure_python_paper_exchange.py @@ -0,0 +1,8 @@ +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange + + +class MockPurePythonPaperExchange(MockPaperExchange): + + @property + def name(self) -> str: + return "MockPurePythonPaperExchange" diff --git a/hummingbot/connector/test_support/network_mocking_assistant.py b/hummingbot/connector/test_support/network_mocking_assistant.py new file mode 100644 index 0000000..07f2aa2 --- /dev/null +++ b/hummingbot/connector/test_support/network_mocking_assistant.py @@ -0,0 +1,178 @@ +import asyncio +import functools +from collections import defaultdict, deque +from typing import Any, Dict, Optional, Tuple, Union +from unittest.mock import AsyncMock, PropertyMock + +import aiohttp + +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class MockWebsocketClientSession: + # Created this class instead of using a generic mock to be sure that no other methods from the client session + # are required when working with websockets + def __init__(self, mock_websocket: AsyncMock): + self._mock_websocket = mock_websocket + self._connection_args: Optional[Tuple[Any]] = None + self._connection_kwargs: Optional[Dict[str, Any]] = None + + @property + def connection_args(self) -> Tuple[Any]: + return self._connection_args or () + + @property + def connection_kwargs(self) -> Dict[str, Any]: + return self._connection_kwargs or {} + + async def ws_connect(self, *args, **kwargs): + self._connection_args = args + self._connection_kwargs = kwargs + return self._mock_websocket + + +class NetworkMockingAssistant: + def __init__(self, event_loop=None): + super().__init__() + + self._response_text_queues = defaultdict(asyncio.Queue) + self._response_json_queues = defaultdict(asyncio.Queue) + self._response_status_queues = defaultdict(deque) + self._sent_http_requests = defaultdict(asyncio.Queue) + + self._incoming_websocket_json_queues = defaultdict(asyncio.Queue) + self._all_incoming_websocket_json_delivered_event = defaultdict(asyncio.Event) + self._incoming_websocket_text_queues = defaultdict(asyncio.Queue) + self._all_incoming_websocket_text_delivered_event = defaultdict(asyncio.Event) + self._incoming_websocket_aiohttp_queues = defaultdict(asyncio.Queue) + self._all_incoming_websocket_aiohttp_delivered_event = defaultdict(asyncio.Event) + self._sent_websocket_json_messages = defaultdict(list) + self._sent_websocket_text_messages = defaultdict(list) + + self._ev_loop = asyncio.get_event_loop() + + @staticmethod + def async_partial(function, *args, **kwargs): + async def partial_func(*args2, **kwargs2): + result = function(*args, *args2, **kwargs, **kwargs2) + if asyncio.iscoroutinefunction(function): + result = await result + return result + + return partial_func + + def _get_next_api_response_status(self, http_mock): + return self._response_status_queues[http_mock].popleft() + + async def _get_next_api_response_json(self, http_mock): + ret = await self._response_json_queues[http_mock].get() + return ret + + async def _get_next_api_response_text(self, http_mock): + return await self._response_text_queues[http_mock].get() + + def _handle_http_request(self, http_mock, url, headers=None, params=None, data=None, *args, **kwargs): + response = AsyncMock() + type(response).status = PropertyMock(side_effect=functools.partial( + self._get_next_api_response_status, http_mock)) + response.json.side_effect = self.async_partial(self._get_next_api_response_json, http_mock) + response.text.side_effect = self.async_partial(self._get_next_api_response_text, http_mock) + response.__aenter__.return_value = response + + components = params if params else data + self._sent_http_requests[http_mock].put_nowait((url, headers, components)) + + return response + + def configure_web_assistants_factory(self, web_assistants_factory: WebAssistantsFactory) -> AsyncMock: + websocket_mock = self.create_websocket_mock() + client_session_mock = MockWebsocketClientSession(mock_websocket=websocket_mock) + + web_assistants_factory._connections_factory._ws_independent_session = client_session_mock + + return websocket_mock + + def configure_http_request_mock(self, http_request_mock): + http_request_mock.side_effect = functools.partial(self._handle_http_request, http_request_mock) + + def add_http_response(self, http_request_mock, response_status, response_json=None, response_text=None): + self._response_status_queues[http_request_mock].append(response_status) + if response_json is not None: + self._response_json_queues[http_request_mock].put_nowait(response_json) + if response_text is not None: + self._response_text_queues[http_request_mock].put_nowait(response_text) + + async def next_sent_request_data(self, http_request_mock): + return await self._sent_http_requests[http_request_mock].get() + + async def _get_next_websocket_json_message(self, websocket_mock, *args, **kwargs): + queue = self._incoming_websocket_json_queues[websocket_mock] + message = await queue.get() + if queue.empty(): + self._all_incoming_websocket_json_delivered_event[websocket_mock].set() + return message + + async def _get_next_websocket_aiohttp_message(self, websocket_mock, *args, **kwargs): + queue = self._incoming_websocket_aiohttp_queues[websocket_mock] + message = await queue.get() + if queue.empty(): + self._all_incoming_websocket_aiohttp_delivered_event[websocket_mock].set() + if isinstance(message, (BaseException, Exception)): + raise message + return message + + async def _get_next_websocket_text_message(self, websocket_mock, *args, **kwargs): + queue = self._incoming_websocket_text_queues[websocket_mock] + message = await queue.get() + if queue.empty(): + self._all_incoming_websocket_text_delivered_event[websocket_mock].set() + return message + + def create_websocket_mock(self): + ws = AsyncMock() + ws.__aenter__.return_value = ws + ws.send_json.side_effect = lambda sent_message: self._sent_websocket_json_messages[ws].append(sent_message) + ws.send.side_effect = lambda sent_message: self._sent_websocket_text_messages[ws].append(sent_message) + ws.send_str.side_effect = lambda sent_message: self._sent_websocket_text_messages[ws].append(sent_message) + ws.receive_json.side_effect = self.async_partial(self._get_next_websocket_json_message, ws) + ws.receive_str.side_effect = self.async_partial(self._get_next_websocket_text_message, ws) + ws.receive.side_effect = self.async_partial(self._get_next_websocket_aiohttp_message, ws) + ws.recv.side_effect = self.async_partial(self._get_next_websocket_text_message, ws) + return ws + + def add_websocket_json_message(self, websocket_mock, message): + self._incoming_websocket_json_queues[websocket_mock].put_nowait(message) + self._all_incoming_websocket_json_delivered_event[websocket_mock].clear() + + def add_websocket_text_message(self, websocket_mock, message): + self._incoming_websocket_text_queues[websocket_mock].put_nowait(message) + self._all_incoming_websocket_text_delivered_event[websocket_mock].clear() + + def add_websocket_aiohttp_message( + self, websocket_mock, message, message_type: aiohttp.WSMsgType = aiohttp.WSMsgType.TEXT + ): + msg = aiohttp.WSMessage(message_type, message, extra=None) + self._incoming_websocket_aiohttp_queues[websocket_mock].put_nowait(msg) + self._all_incoming_websocket_aiohttp_delivered_event[websocket_mock].clear() + + def add_websocket_aiohttp_exception(self, websocket_mock, exception: Union[Exception, BaseException]): + self._incoming_websocket_aiohttp_queues[websocket_mock].put_nowait(exception) + self._all_incoming_websocket_aiohttp_delivered_event[websocket_mock].clear() + + def json_messages_sent_through_websocket(self, websocket_mock): + return self._sent_websocket_json_messages[websocket_mock] + + def text_messages_sent_through_websocket(self, websocket_mock): + return self._sent_websocket_text_messages[websocket_mock] + + def run_until_all_text_messages_delivered(self, websocket_mock, timeout: int = 1): + all_delivered = self._all_incoming_websocket_text_delivered_event[websocket_mock] + self._ev_loop.run_until_complete(asyncio.wait_for(all_delivered.wait(), timeout)) + + def run_until_all_json_messages_delivered(self, websocket_mock, timeout: int = 1): + all_delivered = self._all_incoming_websocket_json_delivered_event[websocket_mock] + self._ev_loop.run_until_complete(asyncio.wait_for(all_delivered.wait(), timeout)) + + def run_until_all_aiohttp_messages_delivered(self, websocket_mock, timeout: int = 1): + all_delivered = self._all_incoming_websocket_aiohttp_delivered_event[websocket_mock] + self._ev_loop.run_until_complete(asyncio.wait_for(all_delivered.wait(), timeout)) diff --git a/hummingbot/connector/test_support/oms_exchange_connector_test.py b/hummingbot/connector/test_support/oms_exchange_connector_test.py new file mode 100644 index 0000000..0374a37 --- /dev/null +++ b/hummingbot/connector/test_support/oms_exchange_connector_test.py @@ -0,0 +1,1000 @@ +import hashlib +import hmac +import json +import re +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Pattern, Tuple, Union + +from aioresponses.core import RequestCall, aioresponses + +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class OMSExchangeTests: + class ExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests, ABC): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someApiKey" + cls.secret = "someSecret" + cls.user_id = 20 + cls.time_mock = 1655283229.419752 + cls.nonce = str(int(cls.time_mock * 1e3)) + auth_concat = f"{cls.nonce}{cls.user_id}{cls.api_key}" + cls.signature = hmac.new( + key=cls.secret.encode("utf-8"), + msg=auth_concat.encode("utf-8"), + digestmod=hashlib.sha256, + ).hexdigest() + cls.user_name = "someUserName" + cls.oms_id = 1 + cls.account_id = 3 + cls.pair_id = 1 + cls.base_asset = "COINALPHA" + cls.base_id = 26 + cls.quote_asset = "HBOT" + cls.quote_id = 2 + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.exchange_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.exchange._token_id_map[self.quote_id] = self.quote_asset + self.exchange._token_id_map[self.base_id] = self.base_asset + + @property + @abstractmethod + def url_creator(self): + raise NotImplementedError + + @property + def all_symbols_url(self): + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_PRODUCTS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return regex_url + + @property + def latest_prices_url(self) -> Pattern[str]: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_GET_L1_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def network_status_url(self) -> Pattern[str]: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_PING_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def trading_rules_url(self) -> Pattern[str]: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_PRODUCTS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def order_creation_url(self) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_CREATION_ENDPOINT) + return url + + @property + def balance_url(self) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ACC_POSITIONS_ENDPOINT) + return url + + @property + def all_symbols_request_mock_response(self) -> List[Dict[str, Any]]: + return self.get_products_resp() + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, List[Dict[str, Any]]]: + resp = self.get_products_resp() + resp[0]["IsDisable"] = True + return self.trading_pair, resp + + @property + def latest_prices_request_mock_response(self) -> Dict[str, Union[int, float]]: + return { + "AskOrderCt": 0, + "AskQty": 1, + "BestBid": 0.382882, + "BestOffer": 0.38345, + "BidOrderCt": 0, + "BidQty": 0, + "CurrentDayNotional": 0, + "CurrentDayNumTrades": 0, + "CurrentDayPxChange": 0, + "CurrentDayVolume": 0, + "InstrumentId": self.pair_id, + "LastTradeTime": 0, + "LastTradedPx": float(self.expected_latest_price), + "LastTradedQty": 0, + "OMSId": self.oms_id, + "Rolling24HrNotional": 0, + "Rolling24HrPxChange": 0, + "Rolling24HrPxChangePercent": 0, + "Rolling24HrVolume": 0, + "Rolling24NumTrades": 0, + "SessionClose": 0, + "SessionHigh": 0, + "SessionLow": 0, + "SessionOpen": 0, + "TimeStamp": "0", + "Volume": 0, + } + + @property + def network_status_request_successful_mock_response(self) -> Dict[str, str]: + return {"msg": "PONG"} + + @property + def trading_rules_request_mock_response(self) -> List[Dict[str, Any]]: + return self.get_products_resp() + + @property + def trading_rules_request_erroneous_mock_response(self): + resp = self.get_products_resp() + resp[0].pop("MinimumQuantity") + return resp + + def get_auth_success_response(self) -> Dict[str, Any]: + auth_resp = { + "Authenticated": True, + "SessionToken": "0e8bbcbc-6ada-482a-a9b4-5d9218ada3f9", + "User": { + "UserId": self.user_id, + "UserName": self.user_name, + "Email": "", + "EmailVerified": True, + "AccountId": self.account_id, + "OMSId": self.oms_id, + "Use2FA": False, + }, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None, + } + return auth_resp + + @staticmethod + def get_auth_failure_response() -> Dict[str, Any]: + auth_resp = { + "Authenticated": False, + "EnforceEnable2FA": False, + "Locked": False, + "Requires2FA": False, + "SessionToken": None, + "TwoFAToken": None, + "TwoFAType": None, + "User": { + "AccountId": 0, + "Email": None, + "EmailVerified": False, + "OMSId": 0, + "Use2FA": False, + "UserId": 0, + "UserName": None, + }, + "errormsg": "User api key not found", + } + return auth_resp + + def get_products_resp(self) -> List[Dict[str, Any]]: + return [ + { + "AllowOnlyMarketMakerCounterParty": False, + "CreateWithMarketRunning": True, + "InstrumentId": self.pair_id, + "InstrumentType": "Standard", + "IsDisable": False, + "MasterDataId": 0, + "MinimumPrice": 0.9, + "MinimumQuantity": 0.01, + "OMSId": self.oms_id, + "OtcConvertSizeEnabled": False, + "OtcConvertSizeThreshold": 0.0, + "OtcTradesPublic": True, + "PreviousSessionStatus": "Stopped", + "PriceCeilingLimit": 0.0, + "PriceCeilingLimitEnabled": False, + "PriceCollarConvertToOtcAccountId": 0, + "PriceCollarConvertToOtcClientUserId": 0, + "PriceCollarConvertToOtcEnabled": False, + "PriceCollarConvertToOtcThreshold": 0.0, + "PriceCollarEnabled": False, + "PriceCollarIndexDifference": 0.0, + "PriceCollarPercent": 0.0, + "PriceCollarThreshold": 0.0, + "PriceFloorLimit": 0.0, + "PriceFloorLimitEnabled": False, + "PriceIncrement": 1e-05, + "PriceTier": 0, + "Product1": self.base_id, + "Product1Symbol": self.base_asset, + "Product2": self.quote_id, + "Product2Symbol": self.quote_asset, + "QuantityIncrement": 0.0001, + "SelfTradePrevention": True, + "SessionStatus": "Running", + "SessionStatusDateTime": "2022-05-23T17:11:16.422Z", + "SortIndex": 0, + "Symbol": self.exchange_trading_pair, + "VenueId": 1, + "VenueInstrumentId": 11, + "VenueSymbol": self.exchange_trading_pair, + } + ] + + @property + def order_creation_request_successful_mock_response(self) -> Dict[str, Union[str, int]]: + return { + "status": "Accepted", + "errormsg": "", + "OrderId": self.expected_exchange_order_id, + } + + @property + def balance_request_mock_response_for_base_and_quote(self) -> List[Dict[str, Union[str, int]]]: + return [ + self.get_mock_balance_base(), + self.get_mock_balance_quote(), + ] + + @property + def balance_request_mock_response_only_base(self) -> List[Dict[str, Union[str, int]]]: + return [self.get_mock_balance_base()] + + def get_mock_balance_base(self) -> Dict[str, Union[str, int]]: + return { + "AccountId": self.account_id, + "Amount": 15, + "Hold": 5, + "NotionalHoldAmount": 0, + "NotionalProductId": 0, + "NotionalProductSymbol": self.base_asset, + "NotionalRate": 1, + "NotionalValue": 0, + "OMSId": self.oms_id, + "PendingDeposits": 0, + "PendingWithdraws": 0, + "ProductId": self.pair_id + 1, + "ProductSymbol": self.base_asset, + "TotalDayDepositNotional": 0, + "TotalDayDeposits": 0, + "TotalDayTransferNotional": 0, + "TotalDayWithdrawNotional": 0, + "TotalDayWithdraws": 0, + "TotalMonthDepositNotional": 0, + "TotalMonthDeposits": 0, + "TotalMonthWithdrawNotional": 0, + "TotalMonthWithdraws": 0, + "TotalYearDepositNotional": 0, + "TotalYearDeposits": 0, + "TotalYearWithdrawNotional": 0, + "TotalYearWithdraws": 0 + } + + def get_mock_balance_quote(self) -> Dict[str, Union[str, int]]: + return { + "AccountId": self.account_id, + "Amount": 2000, + "Hold": 0, + "NotionalHoldAmount": 0, + "NotionalProductId": 0, + "NotionalProductSymbol": self.quote_asset, + "NotionalRate": 1, + "NotionalValue": 0, + "OMSId": self.oms_id, + "PendingDeposits": 0, + "PendingWithdraws": 0, + "ProductId": self.pair_id + 2, + "ProductSymbol": self.quote_asset, + "TotalDayDepositNotional": 0, + "TotalDayDeposits": 0, + "TotalDayTransferNotional": 0, + "TotalDayWithdrawNotional": 0, + "TotalDayWithdraws": 0, + "TotalMonthDepositNotional": 0, + "TotalMonthDeposits": 0, + "TotalMonthWithdrawNotional": 0, + "TotalMonthWithdraws": 0, + "TotalYearDepositNotional": 0, + "TotalYearDeposits": 0, + "TotalYearWithdrawNotional": 0, + "TotalYearWithdraws": 0, + } + + @property + def balance_event_websocket_update(self) -> Dict[str, Union[str, int, float]]: + return { + "i": 10, + "m": 3, + "n": CONSTANTS.WS_ACC_POS_EVENT, + "o": self.get_mock_balance_base(), + } + + @property + def expected_latest_price(self) -> float: + return 0.390718 + + @property + def expected_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT] + + @property + def expected_trading_rule(self): + trading_rule_resp = self.trading_rules_request_mock_response[0] + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(trading_rule_resp["MinimumQuantity"])), + min_price_increment=Decimal(str(trading_rule_resp["PriceIncrement"])), + min_base_amount_increment=Decimal(str(trading_rule_resp["QuantityIncrement"])), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "312269865356374016" + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("0.390234") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.075"))] + ) + + @property + def expected_fill_trade_id(self) -> int: + return 123 + + @property + def is_cancel_request_executed_synchronously_by_server(self) -> bool: + return True + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return str(self.pair_id) + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertEqual(request_headers[CONSTANTS.API_KEY_FIELD], self.api_key) + self.assertEqual(request_headers[CONSTANTS.SIGNATURE_FIELD], self.signature) + self.assertEqual(request_headers[CONSTANTS.USER_ID_FIELD], str(self.user_id)) + self.assertEqual(request_headers[CONSTANTS.NONCE_FIELD], self.nonce) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(request_data[CONSTANTS.INSTRUMENT_ID_FIELD], self.pair_id) + self.assertEqual(request_data[CONSTANTS.OMS_ID_FIELD], self.oms_id) + self.assertEqual(request_data[CONSTANTS.ACCOUNT_ID_FIELD], self.account_id) + self.assertEqual(request_data[CONSTANTS.TIME_IN_FORCE_FIELD], CONSTANTS.GTC_TIF) + self.assertEqual(request_data[CONSTANTS.CLIENT_ORDER_ID_FIELD], int(order.client_order_id)) + self.assertEqual(request_data[CONSTANTS.QUANTITY_FIELD], Decimal("100")) + self.assertEqual(request_data[CONSTANTS.LIMIT_PRICE_FIELD], Decimal("10000")) + self.assertEqual(request_data[CONSTANTS.ORDER_TYPE_FIELD], CONSTANTS.LIMIT_ORDER_TYPE) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = request_call.kwargs["params"] + self.assertEqual(request_data[CONSTANTS.OMS_ID_FIELD], self.oms_id) + self.assertEqual(request_data[CONSTANTS.ACCOUNT_ID_FIELD], self.account_id) + self.assertEqual(request_data[CONSTANTS.ORDER_ID_FIELD], int(order.exchange_order_id)) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(request_data[CONSTANTS.OMS_ID_FIELD], self.oms_id) + self.assertEqual(request_data[CONSTANTS.ACCOUNT_ID_FIELD], self.account_id) + self.assertEqual(request_data[CONSTANTS.CL_ORDER_ID_FIELD], int(order.client_order_id)) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = request_call.kwargs["params"] + self.assertEqual(request_data[CONSTANTS.OMS_ID_FIELD], self.oms_id) + self.assertEqual(request_data[CONSTANTS.ACCOUNT_ID_FIELD], self.account_id) + self.assertEqual(request_data[CONSTANTS.USER_ID_FIELD], self.user_id) + self.assertEqual(request_data[CONSTANTS.ORDER_ID_FIELD], int(order.exchange_order_id)) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_CANCELATION_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"detail": None, "errorcode": 0, "errormsg": None, "result": True} + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_CANCELATION_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"detail": None, "errorcode": 102, "errormsg": "Server Error", "result": False} + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_completely_filled_mock_response(order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"detail": None, "errorcode": 104, "errormsg": "Resource Not Found", "result": False} + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_TRADE_HISTORY_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_TRADE_HISTORY_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_TRADE_HISTORY_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"detail": None, "errorcode": 20, "errormsg": "Not Authorized", "result": False} + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder) -> Dict: + return { + "i": 2, + "m": 3, + "n": CONSTANTS.WS_ORDER_STATE_EVENT, + "o": { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": 0, + "CancelReason": None, + "ChangeReason": "NewInputAccepted", + "ClientOrderId": int(order.client_order_id), + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": float(order.amount), + "EnteredBy": 169072, + "ExecutableValue": 0, + "GrossValueExecuted": 0, + "InsideAsk": 1455.51, + "InsideAskSize": 0.0455, + "InsideBid": 1446.28, + "InsideBidSize": 1.3325, + "Instrument": self.pair_id, + "IpAddress": None, + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1457.34, + "LastUpdatedTime": 1655381197195, + "LastUpdatedTimeTicks": 637909779971950125, + "OMSId": self.oms_id, + "OrderFlag": "0", + "OrderId": int(order.exchange_order_id), + "OrderState": CONSTANTS.ACTIVE_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 3, + "OrigOrderId": 18846132298, + "OrigQuantity": float(order.amount), + "PegLimitOffset": 0, + "PegOffset": 0, + "PegPriceType": "Unknown", + "Price": float(order.price), + "Quantity": float(order.amount), + "QuantityExecuted": 0, + "ReceiveTime": 1655381197195, + "ReceiveTimeTicks": 637909779971946403, + "RejectReason": None, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0, + "UseMargin": False, + "UserName": self.user_name, + }, + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder) -> Dict: + return { + "i": 6, + "m": 3, + "n": CONSTANTS.WS_ORDER_STATE_EVENT, + "o": { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": 0, + "CancelReason": None, + "ChangeReason": "UserModified", + "ClientOrderId": int(order.client_order_id), + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": 0, + "EnteredBy": 169072, + "ExecutableValue": 0, + "GrossValueExecuted": 0, + "InsideAsk": 1455.51, + "InsideAskSize": 0.0455, + "InsideBid": 1446.28, + "InsideBidSize": 1.3325, + "Instrument": self.pair_id, + "IpAddress": None, + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1457.34, + "LastUpdatedTime": 1655381197195, + "LastUpdatedTimeTicks": 637909779971950125, + "OMSId": self.oms_id, + "OrderFlag": "0", + "OrderId": int(order.exchange_order_id), + "OrderState": CONSTANTS.CANCELED_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 3, + "OrigOrderId": 18846132298, + "OrigQuantity": float(order.amount), + "PegLimitOffset": 0, + "PegOffset": 0, + "PegPriceType": "Unknown", + "Price": float(order.price), + "Quantity": 0, + "QuantityExecuted": 0, + "ReceiveTime": 1655381197195, + "ReceiveTimeTicks": 637909779971946403, + "RejectReason": None, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0, + "UseMargin": False, + "UserName": self.user_name, + }, + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder) -> Dict: + return { + "i": 4, + "m": 3, + "n": CONSTANTS.WS_ORDER_TRADE_EVENT, + "o": self._order_fills_request_full_fill_mock_response(order)[0], + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder) -> Dict: + return { + "i": 6, + "m": 3, + "n": CONSTANTS.WS_ORDER_STATE_EVENT, + "o": self._order_status_request_completely_filled_mock_response(order), + } + + def _initialize_auth(self, auth: OMSConnectorAuth): + auth_resp = self.get_auth_success_response() + auth.update_with_rest_response(auth_resp) + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": float(order.price), + "CancelReason": "UserModified", + "ChangeReason": "UserModified", + "ClientOrderId": int(order.client_order_id), + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": 0, + "EnteredBy": 169072, + "ExecutableValue": 0.0, + "GrossValueExecuted": 0.0, + "InsideAsk": 1452.6, + "InsideAskSize": 0.0268, + "InsideBid": 1444.71, + "InsideBidSize": 0.1185, + "Instrument": self.pair_id, + "IpAddress": "85.54.187.233", + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1444.47, + "LastUpdatedTime": 1655295880854, + "LastUpdatedTimeTicks": 637908926808535532, + "OMSId": self.oms_id, + "OrderFlag": "AddedToBook, RemovedFromBook", + "OrderId": order.exchange_order_id, + "OrderState": CONSTANTS.FULLY_EXECUTED_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 1, + "OrigOrderId": 18830582877, + "OrigQuantity": float(order.amount), + "PegLimitOffset": 0.0, + "PegOffset": 0.0, + "PegPriceType": "Unknown", + "Price": float(order.price), + "Quantity": 0, + "QuantityExecuted": float(order.amount), + "ReceiveTime": 1655295879486, + "ReceiveTimeTicks": 637908926794855285, + "RejectReason": None, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0.0, + "UseMargin": False, + "UserName": self.user_name, + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": 0.0, + "CancelReason": "UserModified", + "ChangeReason": "UserModified", + "ClientOrderId": order.client_order_id, + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": 0, + "EnteredBy": 169072, + "ExecutableValue": 0.0, + "GrossValueExecuted": 0.0, + "InsideAsk": 1452.6, + "InsideAskSize": 0.0268, + "InsideBid": 1444.71, + "InsideBidSize": 0.1185, + "Instrument": self.pair_id, + "IpAddress": "85.54.187.233", + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1444.47, + "LastUpdatedTime": 1655295880854, + "LastUpdatedTimeTicks": 637908926808535532, + "OMSId": self.oms_id, + "OrderFlag": "AddedToBook, RemovedFromBook", + "OrderId": order.exchange_order_id, + "OrderState": CONSTANTS.CANCELED_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 1, + "OrigOrderId": 18830582877, + "OrigQuantity": 0, + "PegLimitOffset": 0.0, + "PegOffset": 0.0, + "PegPriceType": "Unknown", + "Price": float(order.price), + "Quantity": float(order.amount), + "QuantityExecuted": 0.0, + "ReceiveTime": 1655295879486, + "ReceiveTimeTicks": 637908926794855285, + "RejectReason": None, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0.0, + "UseMargin": False, + "UserName": self.user_name, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": 0.0, + "CancelReason": "UserModified", + "ChangeReason": "UserModified", + "ClientOrderId": order.client_order_id, + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": float(order.amount), + "EnteredBy": 169072, + "ExecutableValue": 0.0, + "GrossValueExecuted": 0.0, + "InsideAsk": 1452.6, + "InsideAskSize": 0.0268, + "InsideBid": 1444.71, + "InsideBidSize": 0.1185, + "Instrument": self.pair_id, + "IpAddress": "85.54.187.233", + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1444.47, + "LastUpdatedTime": 1655295880854, + "LastUpdatedTimeTicks": 637908926808535532, + "OMSId": self.oms_id, + "OrderFlag": "AddedToBook, RemovedFromBook", + "OrderId": order.exchange_order_id, + "OrderState": CONSTANTS.ACTIVE_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 1, + "OrigOrderId": 18830582877, + "OrigQuantity": float(order.amount), + "PegLimitOffset": 0.0, + "PegOffset": 0.0, + "PegPriceType": "Unknown", + "Price": float(order.price), + "Quantity": float(order.amount), + "QuantityExecuted": 0.0, + "ReceiveTime": 1655295879486, + "ReceiveTimeTicks": 637908926794855285, + "RejectReason": None, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0.0, + "UseMargin": False, + "UserName": self.user_name, + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "Account": self.account_id, + "AccountName": self.user_name, + "AvgPrice": float(self.expected_partial_fill_price), + "CancelReason": "UserModified", + "ChangeReason": "UserModified", + "ClientOrderId": order.client_order_id, + "ClientOrderIdUuid": None, + "CounterPartyId": 0, + "DisplayQuantity": float(order.amount), + "EnteredBy": 169072, + "ExecutableValue": 0.0, + "GrossValueExecuted": 0.0, + "InsideAsk": 1452.6, + "InsideAskSize": 0.0268, + "InsideBid": 1444.71, + "InsideBidSize": 0.1185, + "Instrument": self.pair_id, + "IpAddress": "85.54.187.233", + "IsLockedIn": False, + "IsQuote": False, + "LastTradePrice": 1444.47, + "LastUpdatedTime": 1655295880854, + "LastUpdatedTimeTicks": 637908926808535532, + "OMSId": self.oms_id, + "OrderFlag": "AddedToBook, RemovedFromBook", + "OrderId": order.exchange_order_id, + "OrderState": CONSTANTS.ACTIVE_ORDER_STATE, + "OrderType": "Limit", + "OrigClOrdId": 1, + "OrigOrderId": 18830582877, + "OrigQuantity": float(order.amount), + "PegLimitOffset": 0.0, + "PegOffset": 0.0, + "PegPriceType": "Last", + "Price": float(order.price), + "Quantity": float(order.amount), + "QuantityExecuted": float(self.expected_partial_fill_amount), + "ReceiveTime": 1655295879486, + "ReceiveTimeTicks": 637908926794855285, + "RejectReason": "", + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "StopPrice": 0.0, + "UseMargin": False, + "UserName": self.user_name, + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return [ + { + "AccountId": self.account_id, + "AccountName": self.user_name, + "AdapterTradeId": 0, + "ClientOrderId": 0, + "CounterParty": "61125", + "CounterPartyClientUserId": 1, + "Direction": "NoChange", + "ExecutionId": 4713579, + "Fee": float(self.expected_fill_fee.flat_fees[0].amount), + "FeeProductId": self.quote_id, + "InsideAsk": 3195.72, + "InsideAskSize": 0.0, + "InsideBid": 3172.31, + "InsideBidSize": 0.0119, + "InstrumentId": self.pair_id, + "IsBlockTrade": False, + "IsQuote": False, + "MakerTaker": "Taker", + "NotionalHoldAmount": 0, + "NotionalProductId": 6, + "NotionalRate": 0.8016636123283036, + "NotionalValue": 21.34669866907807, + "OMSId": self.oms_id, + "OrderId": int(order.exchange_order_id), + "OrderOriginator": 169072, + "OrderTradeRevision": 1, + "OrderType": "Market", + "Price": float(self.expected_partial_fill_price), + "Quantity": float(self.expected_partial_fill_amount), + "RemainingQuantity": float(order.amount - self.expected_partial_fill_amount), + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "SubAccountId": 0, + "TradeId": int(self.expected_fill_trade_id), + "TradeTime": 637634811411180462, + "TradeTimeMS": 1627884341118, + "UserName": self.user_name, + "Value": 26.628, + }, + ] + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return [ + { + "AccountId": self.user_id, + "AccountName": self.user_name, + "AdapterTradeId": 0, + "ClientOrderId": int(order.client_order_id), + "CounterParty": "61125", + "CounterPartyClientUserId": 1, + "Direction": "NoChange", + "ExecutionId": 4713579, + "Fee": float(self.expected_fill_fee.flat_fees[0].amount), + "FeeProductId": self.quote_id, + "InsideAsk": 3195.72, + "InsideAskSize": 0.0, + "InsideBid": 3172.31, + "InsideBidSize": 0.0119, + "InstrumentId": self.pair_id, + "IsBlockTrade": False, + "IsQuote": False, + "MakerTaker": "Taker", + "NotionalHoldAmount": 0, + "NotionalProductId": 6, + "NotionalRate": 0.8016636123283036, + "NotionalValue": 21.34669866907807, + "OMSId": self.oms_id, + "OrderId": int(order.exchange_order_id), + "OrderOriginator": 169072, + "OrderTradeRevision": 1, + "OrderType": "Market", + "Price": float(order.price), + "Quantity": float(order.amount), + "RemainingQuantity": 0.0, + "Side": "Buy" if order.trade_type == TradeType.BUY else "Sell", + "SubAccountId": 0, + "TradeId": int(self.expected_fill_trade_id), + "TradeTime": 637634811411180462, + "TradeTimeMS": 1627884341118, + "UserName": self.user_name, + "Value": 26.628, + }, + ] + + @aioresponses() + def test_exchange_authenticates(self, mock_api): + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_AUTH_ENDPOINT) + url_regex = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + auth_resp = self.get_auth_success_response() + mock_api.get(url_regex, body=json.dumps(auth_resp)) + + self.async_run_with_timeout(self.exchange.start_network()) + + self.assertTrue(self.exchange.authenticator.initialized) + + @aioresponses() + def test_failure_to_authenticate(self, mock_api): + url = self.url_creator.get_rest_url(path_url=CONSTANTS.REST_AUTH_ENDPOINT) + url_regex = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + auth_resp = self.get_auth_failure_response() + mock_api.get(url_regex, body=json.dumps(auth_resp)) + exchange = self.create_exchange_instance(authenticated=False) + + with self.assertRaises(IOError): + self.async_run_with_timeout(exchange.start_network()) + + self.assertFalse(exchange.authenticator.initialized) diff --git a/hummingbot/connector/test_support/perpetual_derivative_test.py b/hummingbot/connector/test_support/perpetual_derivative_test.py new file mode 100644 index 0000000..fedc4fd --- /dev/null +++ b/hummingbot/connector/test_support/perpetual_derivative_test.py @@ -0,0 +1,744 @@ +import asyncio +import json +from abc import abstractmethod +from decimal import Decimal +from typing import Callable, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + FundingPaymentCompletedEvent, + MarketEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) + + +class AbstractPerpetualDerivativeTests: + """ + We need to create the abstract TestCase class inside another class not inheriting from TestCase to prevent test + frameworks from discovering and tyring to run the abstract class + """ + + class PerpetualDerivativeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @property + @abstractmethod + def expected_supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError + + @property + @abstractmethod + def funding_info_url(self): + raise NotImplementedError + + @property + @abstractmethod + def funding_payment_url(self): + raise NotImplementedError + + @property + @abstractmethod + def funding_info_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def empty_funding_payment_mock_response(self): + raise NotImplementedError + + @property + @abstractmethod + def funding_payment_mock_response(self): + raise NotImplementedError + + @property + def target_funding_info_index_price(self): + return 1 + + @property + def target_funding_info_mark_price(self): + return 2 + + @property + def target_funding_info_next_funding_utc_timestamp(self): + return 1657099053 + + @property + def target_funding_info_rate(self): + return 3 + + @property + def target_funding_info_index_price_ws_updated(self): + return 10 + + @property + def target_funding_info_mark_price_ws_updated(self): + return 20 + + @property + def target_funding_info_next_funding_utc_timestamp_ws_updated(self): + return 1657100053 + + @property + def target_funding_info_rate_ws_updated(self): + return 30 + + @property + def target_funding_payment_timestamp(self): + return 1657110053 + + @property + def target_funding_payment_funding_rate(self): + return 100 + + @property + def target_funding_payment_payment_amount(self): + return 200 + + @abstractmethod + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + raise NotImplementedError + + @abstractmethod + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + raise NotImplementedError + + @abstractmethod + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + """ + :return: A tuple of the URL and an error message if the exchange returns one on failure. + """ + raise NotImplementedError + + @abstractmethod + def configure_failed_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + """ + :return: A tuple of the URL and an error message if the exchange returns one on failure. + """ + raise NotImplementedError + + @abstractmethod + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + raise NotImplementedError + + @abstractmethod + def funding_info_event_for_websocket_update(self): + raise NotImplementedError + + def place_buy_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT, + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=order_type, + price=price, + position_action=position_action, + ) + return order_id + + def place_sell_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT, + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.sell( + trading_pair=self.trading_pair, + amount=amount, + order_type=order_type, + price=price, + position_action=position_action, + ) + return order_id + + def _initialize_event_loggers(self): + super()._initialize_event_loggers() + self.funding_payment_logger = EventLogger() + self.exchange.add_listener(MarketEvent.FundingPaymentCompleted, self.funding_payment_logger) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._perpetual_trading._funding_info = {} + + status_dict = self.exchange.status_dict + + expected_initial_dict = self._expected_initial_status_dict() + expected_initial_dict["funding_info"] = False + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + """Open long position""" + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, + create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), + create_event.exchange_order_id) + self.assertEqual(leverage, create_event.leverage) + self.assertEqual(PositionAction.OPEN.value, create_event.position) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} to {PositionAction.OPEN.name} a {self.trading_pair} position." + ) + ) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + """Open short position""" + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + leverage = 3 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + self.assertEqual(leverage, create_event.leverage) + self.assertEqual(PositionAction.OPEN.value, create_event.position) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000000')} to {PositionAction.OPEN.name} a {self.trading_pair} position." + ) + ) + + @aioresponses() + def test_create_order_to_close_short_position(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + leverage = 4 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, + create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), + create_event.exchange_order_id) + self.assertEqual(leverage, create_event.leverage) + self.assertEqual(PositionAction.CLOSE.value, create_event.position) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} to {PositionAction.CLOSE.name} a {self.trading_pair} position." + ) + ) + + @aioresponses() + def test_create_order_to_close_long_position(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + leverage = 5 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_sell_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + self.assertEqual(leverage, create_event.leverage) + self.assertEqual(PositionAction.CLOSE.value, create_event.position) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000000')} to {PositionAction.CLOSE.name} a {self.trading_pair} position." + ) + ) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + urls = self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + if self.is_order_fill_http_update_included_in_status_update: + trade_url = self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + else: + # If the fill events will not be requested with the order status, we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + for url in (urls if isinstance(urls, list) else [urls]): + order_status_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_status_request) + self.validate_order_status_request(order=order, request_call=order_status_request) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + + if self.is_order_fill_http_update_included_in_status_update: + self.assertTrue(order.is_filled) + + if trade_url: + trades_request = self._all_executed_requests(mock_api, trade_url)[0] + self.validate_auth_credentials_present(trades_request) + self.validate_trades_request( + order=order, + request_call=trades_request) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + self.assertEqual(leverage, fill_event.leverage) + self.assertEqual(PositionAction.OPEN.value, fill_event.position) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual( + order.amount if self.is_order_fill_http_update_included_in_status_update else Decimal(0), + buy_event.base_asset_amount) + self.assertEqual( + order.amount * order.price + if self.is_order_fill_http_update_included_in_status_update + else Decimal(0), + buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + expected_unrealized_pnl = 12 + position_event = self.position_event_for_full_fill_websocket_update( + order=order, unrealized_pnl=expected_unrealized_pnl + ) + + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + if position_event: + event_messages.append(position_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + self.assertEqual(leverage, fill_event.leverage) + self.assertEqual(PositionAction.OPEN.value, fill_event.position) + + sell_event: SellOrderCompletedEvent = self.sell_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, sell_event.timestamp) + self.assertEqual(order.client_order_id, sell_event.order_id) + self.assertEqual(order.base_asset, sell_event.base_asset) + self.assertEqual(order.quote_asset, sell_event.quote_asset) + self.assertEqual(order.amount, sell_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, sell_event.quote_asset_amount) + self.assertEqual(order.order_type, sell_event.order_type) + self.assertEqual(order.exchange_order_id, sell_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"SELL order {order.client_order_id} completely filled." + ) + ) + + self.assertEqual(1, len(self.exchange.account_positions)) + + position: Position = self.exchange.account_positions[self.trading_pair] + self.assertEqual(self.trading_pair, position.trading_pair) + self.assertEqual(PositionSide.SHORT, position.position_side) + self.assertEqual(expected_unrealized_pnl, position.unrealized_pnl) + self.assertEqual(fill_event.price, position.entry_price) + self.assertEqual(-fill_event.amount, position.amount) + self.assertEqual(leverage, position.leverage) + + def test_supported_position_modes(self): + supported_modes = self.exchange.supported_position_modes() + self.assertEqual(self.expected_supported_position_modes, supported_modes) + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + request_sent_event = asyncio.Event() + _, error_msg = self.configure_failed_set_position_mode( + position_mode=PositionMode.HEDGE, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set(), + ) + self.exchange.set_position_mode(PositionMode.HEDGE) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue( + self.is_logged( + log_level="NETWORK", + message=f"Error switching {self.trading_pair} mode to {PositionMode.HEDGE}: {error_msg}" + ) + ) + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + request_sent_event = asyncio.Event() + self.configure_successful_set_position_mode( + position_mode=PositionMode.HEDGE, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set(), + ) + self.exchange.set_position_mode(PositionMode.HEDGE) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue( + self.is_logged( + log_level="DEBUG", + message=f"Position mode switched to {PositionMode.HEDGE}.", + ) + ) + + request_sent_event.clear() + self.configure_successful_set_position_mode( + position_mode=PositionMode.ONEWAY, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set(), + ) + self.exchange.set_position_mode(PositionMode.ONEWAY) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue( + self.is_logged( + log_level="DEBUG", + message=f"Position mode switched to {PositionMode.ONEWAY}.", + ) + ) + + @aioresponses() + def test_set_leverage_failure(self, mock_api): + request_sent_event = asyncio.Event() + target_leverage = 2 + _, message = self.configure_failed_set_leverage( + leverage=target_leverage, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set(), + ) + self.exchange.set_leverage(trading_pair=self.trading_pair, leverage=target_leverage) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue( + self.is_logged( + log_level="NETWORK", + message=f"Error setting leverage {target_leverage} for {self.trading_pair}: {message}", + ) + ) + + @aioresponses() + def test_set_leverage_success(self, mock_api): + request_sent_event = asyncio.Event() + target_leverage = 2 + self.configure_successful_set_leverage( + leverage=target_leverage, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set(), + ) + self.exchange.set_leverage(trading_pair=self.trading_pair, leverage=target_leverage) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue( + self.is_logged( + log_level="INFO", + message=f"Leverage for {self.trading_pair} successfully set to {target_leverage}.", + ) + ) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp, funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_updates_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + funding_info_event = self.funding_info_event_for_websocket_update() + + event_messages = [funding_info_event, asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + @aioresponses() + def test_funding_payment_polling_loop_sends_update_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + url = self.funding_payment_url + + self.async_tasks.append(asyncio.get_event_loop().create_task(self.exchange._funding_payment_polling_loop())) + + response = self.empty_funding_payment_mock_response + mock_api.get(url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + request_sent_event.clear() + response = self.funding_payment_mock_response + mock_api.get(url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + request_sent_event.clear() + response = self.funding_payment_mock_response + mock_api.get(url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.funding_payment_logger.event_log)) + funding_event: FundingPaymentCompletedEvent = self.funding_payment_logger.event_log[0] + self.assertEqual(self.target_funding_payment_timestamp, funding_event.timestamp) + self.assertEqual(self.exchange.name, funding_event.market) + self.assertEqual(self.trading_pair, funding_event.trading_pair) + self.assertEqual(self.target_funding_payment_payment_amount, funding_event.amount) + self.assertEqual(self.target_funding_payment_funding_rate, funding_event.funding_rate) + + @abstractmethod + def test_get_buy_and_sell_collateral_tokens(self): + raise NotImplementedError diff --git a/hummingbot/connector/time_synchronizer.py b/hummingbot/connector/time_synchronizer.py new file mode 100644 index 0000000..de3f115 --- /dev/null +++ b/hummingbot/connector/time_synchronizer.py @@ -0,0 +1,93 @@ +import asyncio +import logging +import time +from collections import deque +from typing import Awaitable, Deque + +import numpy + +from hummingbot.logger import HummingbotLogger + + +class TimeSynchronizer: + """ + Used to synchronize the local time with the server's time. + This class is useful when timestamp-based signatures are required by the exchange for authentication. + Upon receiving a timestamped message from the server, use `update_server_time_offset_with_time_provider` + to synchronize local time with the server's time. + """ + + NaN = float("nan") + _logger = None + + def __init__(self): + self._time_offset_ms: Deque[float] = deque(maxlen=5) + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @property + def time_offset_ms(self) -> float: + if not self._time_offset_ms: + offset = (self._time() - self._current_seconds_counter()) * 1e3 + else: + median = numpy.median(self._time_offset_ms) + weighted_average = numpy.average(self._time_offset_ms, weights=range(1, len(self._time_offset_ms) * 2 + 1, 2)) + offset = numpy.mean([median, weighted_average]) + + return offset + + def add_time_offset_ms_sample(self, offset: float): + self._time_offset_ms.append(offset) + + def clear_time_offset_ms_samples(self): + self._time_offset_ms.clear() + + def time(self) -> float: + """ + Returns the current time in seconds calculated base on the deviation samples. + :return: Calculated current time considering the registered deviations + """ + return self._current_seconds_counter() + self.time_offset_ms * 1e-3 + + async def update_server_time_offset_with_time_provider(self, time_provider: Awaitable): + """ + Executes the time_provider passed as parameter to obtain the current time, and adds a new sample in the + internal list. + + :param time_provider: Awaitable object that returns the current time + """ + try: + local_before_ms: float = self._current_seconds_counter() * 1e3 + server_time_ms: float = await time_provider + local_after_ms: float = self._current_seconds_counter() * 1e3 + local_server_time_pre_image_ms: float = (local_before_ms + local_after_ms) / 2.0 + time_offset_ms: float = server_time_ms - local_server_time_pre_image_ms + self.add_time_offset_ms_sample(time_offset_ms) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Error getting server time.", exc_info=True, + app_warning_msg="Could not refresh server time. Check network connection.") + + async def update_server_time_if_not_initialized(self, time_provider: Awaitable): + """ + Executes the time_provider passed as parameter to obtain the current time, and adds a new sample in the + internal list, ONLY if the current instance has not been updated yet. + + :param time_provider: Awaitable object that returns the current time + """ + if not self._time_offset_ms: + await self.update_server_time_offset_with_time_provider(time_provider) + else: + # This is done to avoid the warning message from asyncio framework saying a coroutine was not awaited + time_provider.close() + + def _current_seconds_counter(self): + return time.perf_counter() + + def _time(self): + return time.time() diff --git a/hummingbot/connector/trading_rule.pxd b/hummingbot/connector/trading_rule.pxd new file mode 100644 index 0000000..a363794 --- /dev/null +++ b/hummingbot/connector/trading_rule.pxd @@ -0,0 +1,15 @@ +cdef class TradingRule: + cdef: + public str trading_pair + public object min_order_size # Calculated min base asset size based on last trade price + public object max_order_size # Calculated max base asset size + public object min_price_increment # Min tick size difference accepted (e.g. 0.1) + public object min_base_amount_increment # Min step size of base asset amount (e.g. 0.01) + public object min_quote_amount_increment # Min step size of quote asset amount (e.g. 0.01) + public object max_price_significant_digits # Max # of significant digits in a price + public object min_notional_size # Notional value = price * quantity, min accepted (e.g. 3.001) + public object min_order_value # Calculated min base asset value based on the minimum accepted trade value (e.g. 0.078LTC is ~50,000 Satoshis) + public bint supports_limit_orders # if limit order is allowed for this trading pair + public bint supports_market_orders # if market order is allowed for this trading pair + public object buy_order_collateral_token # Indicates the collateral token used for buy orders + public object sell_order_collateral_token # Indicates the collateral token used for sell orders diff --git a/hummingbot/connector/trading_rule.pyx b/hummingbot/connector/trading_rule.pyx new file mode 100644 index 0000000..4a33fd2 --- /dev/null +++ b/hummingbot/connector/trading_rule.pyx @@ -0,0 +1,55 @@ +from decimal import Decimal +from typing import Optional + +from hummingbot.connector.utils import split_hb_trading_pair + +s_decimal_0 = Decimal(0) +s_decimal_max = Decimal("1e56") +s_decimal_min = Decimal(1) / s_decimal_max + + +cdef class TradingRule: + def __init__(self, + trading_pair: str, + min_order_size: Decimal = s_decimal_0, + max_order_size: Decimal = s_decimal_max, + min_price_increment: Decimal = s_decimal_min, + min_base_amount_increment: Decimal = s_decimal_min, + min_quote_amount_increment: Decimal = s_decimal_min, + min_notional_size: Decimal = s_decimal_0, + min_order_value: Decimal = s_decimal_0, + max_price_significant_digits: Decimal = s_decimal_max, + supports_limit_orders: bool = True, + supports_market_orders: bool = True, + buy_order_collateral_token: Optional[str] = None, + sell_order_collateral_token: Optional[str] = None): + self.trading_pair = trading_pair + self.min_order_size = min_order_size + self.max_order_size = max_order_size + self.min_price_increment = min_price_increment + self.min_base_amount_increment = min_base_amount_increment + self.min_quote_amount_increment = min_quote_amount_increment + self.min_notional_size = min_notional_size + self.min_order_value = min_order_value + self.max_price_significant_digits = max_price_significant_digits + self.supports_limit_orders = supports_limit_orders + self.supports_market_orders = supports_market_orders + quote_token = split_hb_trading_pair(self.trading_pair)[1] + self.buy_order_collateral_token = buy_order_collateral_token or quote_token + self.sell_order_collateral_token = sell_order_collateral_token or quote_token + + def __repr__(self) -> str: + return f"TradingRule(trading_pair='{self.trading_pair}', " \ + f"min_order_size={self.min_order_size}, " \ + f"max_order_size={self.max_order_size}, " \ + f"min_price_increment={self.min_price_increment}, " \ + f"min_base_amount_increment={self.min_base_amount_increment}, " \ + f"min_quote_amount_increment={self.min_quote_amount_increment}, " \ + f"min_notional_size={self.min_notional_size}, " \ + f"min_order_value={self.min_order_value}, " \ + f"max_price_significant_digits={self.max_price_significant_digits}, " \ + f"supports_limit_orders={self.supports_limit_orders}, " \ + f"supports_market_orders={self.supports_market_orders}, " \ + f"buy_order_collateral_token={self.buy_order_collateral_token}, " \ + f"sell_order_collateral_token={self.sell_order_collateral_token}," \ + f")" diff --git a/hummingbot/connector/utilities/__init__.py b/hummingbot/connector/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/utilities/oms_connector/__init__.py b/hummingbot/connector/utilities/oms_connector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_api_order_book_data_source.py b/hummingbot/connector/utilities/oms_connector/oms_connector_api_order_book_data_source.py new file mode 100644 index 0000000..113fa93 --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_api_order_book_data_source.py @@ -0,0 +1,191 @@ +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import ( + OMSConnectorURLCreatorBase, + OMSConnectorWebAssistantsFactory, +) +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + +if TYPE_CHECKING: + from hummingbot.connector.utilities.oms_connector.oms_connector_exchange import OMSExchange + + +class OMSConnectorAPIOrderBookDataSource(OrderBookTrackerDataSource): + def __init__( + self, + trading_pairs: List[str], + connector: 'OMSExchange', + api_factory: OMSConnectorWebAssistantsFactory, + url_provider: OMSConnectorURLCreatorBase, + oms_id: int, + ): + super().__init__(trading_pairs) + self._connector = connector + self._api_factory = api_factory + self._rest_assistant: Optional[RESTAssistant] = None + self._ws_assistant: Optional[WSAssistant] = None + self._auth: OMSConnectorAuth = api_factory.auth + self._url_provider = url_provider + self._oms_id = oms_id + self._nonce_provider = NonceCreator.for_milliseconds() + + async def get_last_traded_prices( + self, trading_pairs: List[str], domain: Optional[str] = None + ) -> Dict[str, float]: + return await self._connector.get_last_traded_prices(trading_pairs=trading_pairs) + + async def _parse_trade_message(self, raw_message: List[Dict[int, Union[int, float]]], message_queue: asyncio.Queue): + raise NotImplementedError # OMS connectors do not provide a public trades endpoint + + async def _parse_order_book_diff_message( + self, raw_message: List[List[Union[int, float]]], message_queue: asyncio.Queue + ): + msg_data = raw_message[CONSTANTS.MSG_DATA_FIELD] + first_row = msg_data[0] + ts_ms = first_row[CONSTANTS.DIFF_UPDATE_TS_FIELD] + update_id = self._nonce_provider.get_tracking_nonce(timestamp=ts_ms * 1e-3) + instrument_id = first_row[CONSTANTS.DIFF_UPDATE_INSTRUMENT_ID_FIELD] + trading_pair = await self._connector.trading_pair_associated_to_exchange_symbol(symbol=str(instrument_id)) + bids, asks = self._get_bids_and_asks_from_snapshot(msg_data) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + diff_message: OrderBookMessage = OrderBookMessage( + OrderBookMessageType.DIFF, + order_book_message_content, + ts_ms * 1e-3, + ) + + message_queue.put_nowait(diff_message) + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + snapshot_response = await self._request_order_book_snapshot(trading_pair) + first_row = snapshot_response[0] + ts_ms = first_row[CONSTANTS.DIFF_UPDATE_TS_FIELD] + update_id = self._nonce_provider.get_tracking_nonce(timestamp=ts_ms * 1e-3) + bids, asks = self._get_bids_and_asks_from_snapshot(snapshot_response) + + order_book_message_content = { + "trading_pair": trading_pair, + "update_id": update_id, + "bids": bids, + "asks": asks, + } + snapshot_msg = OrderBookMessage(OrderBookMessageType.SNAPSHOT, order_book_message_content, ts_ms) + return snapshot_msg + + @staticmethod + def _get_bids_and_asks_from_snapshot( + snapshot: List[List[Union[int, float]]] + ) -> Tuple[List[Tuple[float, float]], List[Tuple[float, float]]]: + """OMS connectors do not guarantee that the data is sorted in any way.""" + asks = [] + bids = [] + for row in snapshot: + update = (row[CONSTANTS.DIFF_UPDATE_PRICE_FIELD], row[CONSTANTS.DIFF_UPDATE_AMOUNT_FIELD]) + if row[CONSTANTS.DIFF_UPDATE_SIDE_FIELD] == CONSTANTS.BUY_ACTION: + bids.append(update) + else: + asks.append(update) + return bids, asks + + async def _request_order_book_snapshot(self, trading_pair: str) -> List[List[Union[int, float]]]: + instrument_id = await self._connector.exchange_symbol_associated_to_pair(trading_pair=trading_pair) + params = { + CONSTANTS.OMS_ID_FIELD: self._oms_id, + CONSTANTS.INSTRUMENT_ID_FIELD: int(instrument_id), + CONSTANTS.DEPTH_FIELD: CONSTANTS.MAX_L2_SNAPSHOT_DEPTH, + } + rest_assistant = await self._get_rest_assistant() + url = self._url_provider.get_rest_url(path_url=CONSTANTS.REST_GET_L2_SNAPSHOT_ENDPOINT) + resp = await rest_assistant.execute_request( + url=url, + throttler_limit_id=CONSTANTS.REST_REQ_LIMIT_ID, + params=params, + method=RESTMethod.GET, + ) + return resp + + async def _get_rest_assistant(self) -> RESTAssistant: + if self._rest_assistant is None: + self._ensure_authenticated() + self._rest_assistant = await self._api_factory.get_rest_assistant() + return self._rest_assistant + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._get_ws_assistant() + url = self._url_provider.get_ws_url() + await ws.connect(ws_url=url, message_timeout=CONSTANTS.WS_MESSAGE_TIMEOUT) + return ws + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ensure_authenticated() + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + def _ensure_authenticated(self): + if not self._auth.initialized: + raise RuntimeError("The authenticator is not initialized.") + + async def _subscribe_channels(self, ws: WSAssistant): + try: + for trading_pair in self._trading_pairs: + instrument_id = await self._connector.exchange_symbol_associated_to_pair(trading_pair) + req_params = { + CONSTANTS.OMS_ID_FIELD: self._oms_id, + CONSTANTS.INSTRUMENT_ID_FIELD: int(instrument_id), + CONSTANTS.DEPTH_FIELD: CONSTANTS.MAX_L2_SNAPSHOT_DEPTH, + } + payload = { + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_L2_SUB_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: req_params, + } + subscribe_orderbook_request = WSJSONRequest(payload=payload) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_REQ_LIMIT_ID): + await ws.send(subscribe_orderbook_request) + + self.logger().info("Subscribed to public order book and trade channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + channel = "" + if event_message[CONSTANTS.MSG_TYPE_FIELD] != CONSTANTS.ERROR_MSG_TYPE: + event_channel = event_message[CONSTANTS.MSG_ENDPOINT_FIELD] + if event_channel == CONSTANTS.WS_L2_EVENT: + channel = self._diff_messages_queue_key + return channel + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + channel: str = self._channel_originating_message(event_message=data) + if channel in [self._diff_messages_queue_key, self._trade_messages_queue_key]: + self._message_queue[channel].put_nowait(data) + except asyncio.TimeoutError: + ping_payload = { + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_PING_REQUEST, + CONSTANTS.MSG_DATA_FIELD: {}, + } + ping_request = WSJSONRequest(payload=ping_payload) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_PING_REQUEST): + await websocket_assistant.send(request=ping_request) diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_api_user_stream_data_source.py b/hummingbot/connector/utilities/oms_connector/oms_connector_api_user_stream_data_source.py new file mode 100644 index 0000000..509beed --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_api_user_stream_data_source.py @@ -0,0 +1,86 @@ +import asyncio + +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import ( + OMSConnectorURLCreatorBase, + OMSConnectorWebAssistantsFactory, +) +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + + +class OMSConnectorAPIUserStreamDataSource(UserStreamTrackerDataSource): + def __init__( + self, + api_factory: OMSConnectorWebAssistantsFactory, + url_provider: OMSConnectorURLCreatorBase, + oms_id: int, + ): + super().__init__() + self._api_factory = api_factory + self._auth: OMSConnectorAuth = api_factory.auth + self._url_provider = url_provider + self._oms_id = oms_id + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._get_ws_assistant() + url = self._url_provider.get_ws_url() + await ws.connect(ws_url=url, message_timeout=CONSTANTS.WS_MESSAGE_TIMEOUT) + auth_payload = { + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_AUTH_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: {}, + } + auth_request = WSJSONRequest( + payload=auth_payload, throttler_limit_id=CONSTANTS.WS_AUTH_ENDPOINT, is_auth_required=True + ) + await ws.send(auth_request) + return ws + + async def _get_ws_assistant(self) -> WSAssistant: + if self._ws_assistant is None: + self._ensure_authenticated() + self._ws_assistant = await self._api_factory.get_ws_assistant() + return self._ws_assistant + + def _ensure_authenticated(self): + if not self._auth.initialized: + raise RuntimeError("The authenticator is not initialized.") + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + try: + payload = { + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_ACC_EVENTS_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: { + CONSTANTS.ACCOUNT_ID_FIELD: self._api_factory.auth.account_id, + CONSTANTS.OMS_ID_FIELD: self._oms_id, + }, + } + subscribe_account_request = WSJSONRequest(payload=payload, is_auth_required=True) + + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_REQ_LIMIT_ID): + await websocket_assistant.send(subscribe_account_request) + + self.logger().info("Subscribed to private account and orders channels...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + while True: + try: + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + if data[CONSTANTS.MSG_TYPE_FIELD] == CONSTANTS.EVENT_MSG_TYPE: + await self._process_event_message(event_message=data, queue=queue) + except asyncio.TimeoutError: + ping_payload = { + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_PING_REQUEST, + CONSTANTS.MSG_DATA_FIELD: {}, + } + ping_request = WSJSONRequest(payload=ping_payload) + async with self._api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_PING_REQUEST): + await websocket_assistant.send(request=ping_request) diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_auth.py b/hummingbot/connector/utilities/oms_connector/oms_connector_auth.py new file mode 100644 index 0000000..1551a94 --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_auth.py @@ -0,0 +1,77 @@ +import hashlib +import hmac +import json +from typing import Dict + +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSJSONRequest + + +class OMSConnectorAuth(AuthBase): + def __init__(self, api_key: str, secret_key: str, user_id: int): + self.api_key = api_key + self.secret_key = secret_key + self.user_id = user_id + self.user_name = None + self.account_id = None + self._auth_dict = None + self._initialized = False + self._build_auth_dict() + + @property + def initialized(self) -> bool: + return self._initialized + + def get_rest_auth_headers(self) -> Dict[str, str]: + return self._auth_dict + + def validate_rest_auth(self, auth_resp_data: Dict[str, str]) -> bool: + return self._validate_auth(auth_resp_data) + + def update_with_rest_response(self, auth_resp_data: Dict[str, str]): + self._update_with_auth_response(auth_resp_data) + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + headers = request.headers or {} + headers.update(self._auth_dict) + request.headers = headers + return request + + async def ws_authenticate(self, request: WSJSONRequest) -> WSJSONRequest: + data = request.payload[CONSTANTS.MSG_DATA_FIELD] + data_dict = json.loads(data) + data_dict.update(self._auth_dict) + request.payload[CONSTANTS.MSG_DATA_FIELD] = json.dumps(data_dict) + return request + + def _build_auth_dict(self): + nonce_creator = NonceCreator.for_milliseconds() + nonce = str(nonce_creator.get_tracking_nonce()) + signature = self._generate_signature(nonce) + self._auth_dict = { + CONSTANTS.API_KEY_FIELD: self.api_key, + CONSTANTS.SIGNATURE_FIELD: signature, + CONSTANTS.USER_ID_FIELD: str(self.user_id), + CONSTANTS.NONCE_FIELD: nonce, + } + + def _generate_signature(self, nonce: str) -> str: + auth_concat = f"{nonce}{self.user_id}{self.api_key}" + signature = hmac.new( + key=self.secret_key.encode("utf-8"), + msg=auth_concat.encode("utf-8"), + digestmod=hashlib.sha256, + ).hexdigest() + return signature + + @staticmethod + def _validate_auth(data: Dict[str, str]) -> bool: + return data.get(CONSTANTS.AUTHENTICATED_FIELD) or False + + def _update_with_auth_response(self, data: Dict[str, str]): + user_data = data[CONSTANTS.USER_FIELD] + self.user_name = user_data[CONSTANTS.USER_NAME_FIELD] + self.account_id = user_data[CONSTANTS.ACCOUNT_ID_FIELD] + self._initialized = True diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_constants.py b/hummingbot/connector/utilities/oms_connector/oms_connector_constants.py new file mode 100644 index 0000000..219df0f --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_constants.py @@ -0,0 +1,193 @@ +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState + +MAX_ID_BIT_COUNT = 63 # experimentally, 64 bit ints sometimes result in OMS assigning order IDs of zero +MAX_ORDER_NOT_FOUND_ON_CANCEL = 2 + +# rest endpoints +REST_AUTH_ENDPOINT = "Authenticate" +REST_PRODUCTS_ENDPOINT = "GetInstruments" +REST_GET_L1_ENDPOINT = "GetLevel1" +REST_GET_L2_SNAPSHOT_ENDPOINT = "GetL2Snapshot" +REST_PING_ENDPOINT = "Ping" +REST_ORDER_CREATION_ENDPOINT = "SendOrder" +REST_ORDER_STATUS_ENDPOINT = "GetOrderStatus" +REST_ORDER_CANCELATION_ENDPOINT = "CancelOrder" +REST_ACC_POSITIONS_ENDPOINT = "GetAccountPositions" +REST_TRADE_HISTORY_ENDPOINT = "GetTradesHistory" +_ALL_REST_ENDPOINTS = [ + REST_AUTH_ENDPOINT, + REST_PRODUCTS_ENDPOINT, + REST_GET_L1_ENDPOINT, + REST_GET_L2_SNAPSHOT_ENDPOINT, + REST_PING_ENDPOINT, + REST_ORDER_CREATION_ENDPOINT, + REST_ORDER_STATUS_ENDPOINT, + REST_ORDER_CANCELATION_ENDPOINT, + REST_ACC_POSITIONS_ENDPOINT, + REST_TRADE_HISTORY_ENDPOINT, +] + +# ws endpoints +WS_AUTH_ENDPOINT = "AuthenticateUser" +WS_ACC_EVENTS_ENDPOINT = "SubscribeAccountEvents" +WS_TRADES_SUB_ENDPOINT = "SubscribeTrades" +WS_L2_SUB_ENDPOINT = "SubscribeLevel2" +WS_PING_REQUEST = "Ping" +_ALL_WS_ENDPOINTS = [ + WS_AUTH_ENDPOINT, + WS_ACC_EVENTS_ENDPOINT, + WS_TRADES_SUB_ENDPOINT, + WS_L2_SUB_ENDPOINT, + WS_PING_REQUEST, +] + +# ws events +WS_L2_EVENT = "Level2UpdateEvent" +WS_ACC_POS_EVENT = "AccountPositionEvent" +WS_ORDER_STATE_EVENT = "OrderStateEvent" +WS_ORDER_TRADE_EVENT = "OrderTradeEvent" +WS_CANCEL_ORDER_REJECTED_EVENT = "CancelOrderRejectEvent" + +# limits +REST_REQ_LIMIT_ID = "WSReqLimitID" +REST_REQ_LIMIT = 5_000 +WS_REQ_LIMIT_ID = "WSReqLimitID" +WS_REQ_LIMIT = 500_000 + +RATE_LIMITS = [ + RateLimit(REST_AUTH_ENDPOINT, limit=5_000, time_interval=60), + RateLimit(REST_REQ_LIMIT_ID, limit=REST_REQ_LIMIT, time_interval=60), + RateLimit(WS_AUTH_ENDPOINT, limit=50_000, time_interval=60), + RateLimit(WS_REQ_LIMIT_ID, limit=WS_REQ_LIMIT, time_interval=60), +] +for e in _ALL_REST_ENDPOINTS: + RATE_LIMITS.append( # each limit defined separately so that children can be more granular + RateLimit( + limit_id=e, + limit=REST_REQ_LIMIT, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(limit_id=REST_REQ_LIMIT_ID)], + ) + ) +for e in _ALL_WS_ENDPOINTS: # noqa: F821 + RATE_LIMITS.append( # each limit defined separately so that children can be more granular + RateLimit( + limit_id=e, + limit=WS_REQ_LIMIT, + time_interval=60, + linked_limits=[LinkedLimitWeightPair(limit_id=WS_REQ_LIMIT_ID)] + ) + ) + +# endpoint constant settings +MAX_L2_SNAPSHOT_DEPTH = 400 +INCLUDE_LAST_COUNT = 0 + +# msg types +REQ_MSG_TYPE = 0 +RESP_MSG_TYPE = 1 +EVENT_MSG_TYPE = 3 +ERROR_MSG_TYPE = 5 + +# time in force types +GTC_TIF = 1 + +# order types +LIMIT_ORDER_TYPE = 2 +ORDER_TYPES = { + OrderType.LIMIT: LIMIT_ORDER_TYPE +} + +# order actions +BUY_ACTION = 0 +SELL_ACTION = 1 +ORDER_SIDE_MAP = { + "Buy": TradeType.BUY, + "Sell": TradeType.SELL, +} + +# order state +ACTIVE_ORDER_STATE = "Working" # can be either OPEN or PARTIALLY_FILLED +CANCELED_ORDER_STATE = "Canceled" +REJECTED_ORDER_STATE = "Rejected" +EXPIRED_ORDER_STATE = "Expired" +FULLY_EXECUTED_ORDER_STATE = "FullyExecuted" +ORDER_STATE_MAP = { + CANCELED_ORDER_STATE: OrderState.CANCELED, + REJECTED_ORDER_STATE: OrderState.FAILED, + EXPIRED_ORDER_STATE: OrderState.FAILED, + FULLY_EXECUTED_ORDER_STATE: OrderState.FILLED, +} + +# fields +OMS_ID_FIELD = "OMSId" +USER_FIELD = "User" +USER_ID_FIELD = "UserId" +USER_NAME_FIELD = "UserName" +ACCOUNT_ID_FIELD = "AccountId" +INSTRUMENT_ID_FIELD = "InstrumentId" +BASE_FIELD = "Product1Symbol" +BASE_ID_FIELD = "Product1" +QUOTE_FIELD = "Product2Symbol" +QUOTE_ID_FIELD = "Product2" +FEE_PRODUCT_ID_FIELD = "FeeProductId" +FEE_AMOUNT_FIELD = "Fee" +START_TIME_FIELD = "StartTime" +TRADE_ID_FIELD = "TradeId" +AUTHENTICATED_FIELD = "Authenticated" +SESSION_TOKEN_FIELD = "SessionToken" +API_KEY_FIELD = "APIKey" +SIGNATURE_FIELD = "Signature" +NONCE_FIELD = "Nonce" +DEPTH_FIELD = "Depth" +INCLUDE_LAST_COUNT_FIELD = "IncludeLastCount" +TIME_IN_FORCE_FIELD = "TimeInForce" +CLIENT_ORDER_ID_FIELD = "ClientOrderId" +CL_ORDER_ID_FIELD = "ClOrderId" # yes, this and the above are not typos... +ORDER_ID_FIELD = "OrderId" +SIDE_FIELD = "Side" +QUANTITY_FIELD = "quantity" +ORDER_TYPE_FIELD = "OrderType" +LIMIT_PRICE_FIELD = "LimitPrice" +PRICE_FIELD = "Price" +TRADE_TIME_MS_FIELD = "TradeTimeMS" +RESULT_FIELD = "result" +ERROR_CODE_FIELD = "errorcode" +ERROR_MSG_FIELD = "errormsg" +PRODUCT_SYMBOL_FIELD = "ProductSymbol" +SYMBOL_FIELD = "Symbol" +AMOUNT_FIELD = "Amount" +ORIGINAL_QUANTITY_FIELD = "OrigQuantity" +QUANTITY_EXECUTED_FIELD = "QuantityExecuted" +ORDER_STATE_FIELD = "OrderState" +AMOUNT_ON_HOLD_FIELD = "Hold" +ORDER_UPDATE_TS_FIELD = "LastUpdatedTime" +IS_DISABLED_FIELD = "IsDisable" +SESSION_STATUS_FIELD = "SessionStatus" +MIN_QUANT_FIELD = "MinimumQuantity" +MIN_PRICE_INCR_FIELD = "PriceIncrement" +MIN_QUANT_INCR_FIELD = "QuantityIncrement" +RECEIVE_TIME_FIELD = "ReceiveTime" +LAST_TRADED_PRICE_FIELD = "LastTradedPx" +MSG_TYPE_FIELD = "m" +MSG_SEQUENCE_FIELD = "i" +MSG_ENDPOINT_FIELD = "n" +MSG_DATA_FIELD = "o" + +TRADE_UPDATE_INSTRUMENT_ID_FIELD = 1 +TRADE_UPDATE_AMOUNT_FIELD = 2 +TRADE_UPDATE_PRICE_FIELD = 3 +TRADE_UPDATE_TS_FIELD = 6 +TRADE_UPDATE_SIDE_FIELD = 8 + +DIFF_UPDATE_TS_FIELD = 2 +DIFF_UPDATE_PRICE_FIELD = 6 +DIFF_UPDATE_INSTRUMENT_ID_FIELD = 7 +DIFF_UPDATE_AMOUNT_FIELD = 8 +DIFF_UPDATE_SIDE_FIELD = 9 + +# other +RESOURCE_NOT_FOUND_ERR_CODE = 104 +WS_MESSAGE_TIMEOUT = 20 diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_exchange.py b/hummingbot/connector/utilities/oms_connector/oms_connector_exchange.py new file mode 100644 index 0000000..c1be0ee --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_exchange.py @@ -0,0 +1,598 @@ +import asyncio +from abc import abstractmethod +from collections import defaultdict +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +from bidict import bidict + +from hummingbot.connector.constants import s_decimal_NaN +from hummingbot.connector.exchange_py_base import ExchangePyBase +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utilities.oms_connector import ( + oms_connector_constants as CONSTANTS, + oms_connector_web_utils as ap_web_utils, +) +from hummingbot.connector.utilities.oms_connector.oms_connector_api_order_book_data_source import ( + OMSConnectorAPIOrderBookDataSource, +) +from hummingbot.connector.utilities.oms_connector.oms_connector_api_user_stream_data_source import ( + OMSConnectorAPIUserStreamDataSource, +) +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_utils import is_exchange_information_valid +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import ( + OMSConnectorURLCreatorBase, + OMSConnectorWebAssistantsFactory, +) +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_numeric_client_order_id +from hummingbot.core.api_throttler.data_types import RateLimit +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.core.utils.tracking_nonce import NonceCreator +from hummingbot.core.web_assistant.connections.data_types import RESTMethod + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class OMSExchange(ExchangePyBase): + + web_utils = ap_web_utils + + def __init__( + self, + client_config_map: "ClientConfigAdapter", + api_key: str, + secret_key: str, + user_id: int, + trading_pairs: Optional[List[str]] = None, + trading_required: bool = True, + url_creator: Optional[OMSConnectorURLCreatorBase] = None, + ): + self._api_key = api_key + self._secret_key = secret_key + self._user_id = user_id + self._auth: Optional[OMSConnectorAuth] = None + self._url_creator = url_creator + self._nonce_creator = NonceCreator.for_seconds() + self._trading_pairs = trading_pairs + self._trading_required = trading_required + self._web_assistants_factory: OMSConnectorWebAssistantsFactory + self._token_id_map: Dict[int, str] = {} + self._order_not_found_on_cancel_record: Dict[str, int] = defaultdict(lambda: 0) + super().__init__(client_config_map) + + @property + @abstractmethod + def oms_id(self) -> int: + raise NotImplementedError + + @property + def authenticator(self) -> OMSConnectorAuth: + if self._auth is None: + self._auth = OMSConnectorAuth(self._api_key, self._secret_key, self._user_id) + return self._auth + + @property + def rate_limits_rules(self) -> List[RateLimit]: + return CONSTANTS.RATE_LIMITS + + @property + def client_order_id_max_length(self) -> int: + return -1 + + @property + def client_order_id_prefix(self) -> str: + return "" + + @property + def trading_rules_request_path(self) -> str: + return CONSTANTS.REST_PRODUCTS_ENDPOINT + + @property + def trading_pairs_request_path(self) -> str: + return CONSTANTS.REST_PRODUCTS_ENDPOINT + + @property + def check_network_request_path(self) -> str: + return CONSTANTS.REST_PING_ENDPOINT + + @property + def trading_pairs(self) -> List[str]: + return self._trading_pairs + + @property + def is_cancel_request_in_exchange_synchronous(self) -> bool: + return True + + @property + def is_trading_required(self) -> bool: + return self._trading_required + + def supported_order_types(self): + return [OrderType.LIMIT] + + async def start_network(self): + await self._authenticate() + await super().start_network() + + def buy(self, + trading_pair: str, + amount: Decimal, + order_type=OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a buy order using the parameters + + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + + :return: the id assigned by the connector to the order (the client id) + """ + order_id = str( + get_new_numeric_client_order_id( + nonce_creator=self._nonce_creator, max_id_bit_count=CONSTANTS.MAX_ID_BIT_COUNT + ) + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price)) + return order_id + + def sell(self, + trading_pair: str, + amount: Decimal, + order_type: OrderType = OrderType.LIMIT, + price: Decimal = s_decimal_NaN, + **kwargs) -> str: + """ + Creates a promise to create a sell order using the parameters. + :param trading_pair: the token pair to operate with + :param amount: the order amount + :param order_type: the type of order to create (MARKET, LIMIT, LIMIT_MAKER) + :param price: the order price + :return: the id assigned by the connector to the order (the client id) + """ + order_id = str( + get_new_numeric_client_order_id( + nonce_creator=self._nonce_creator, max_id_bit_count=CONSTANTS.MAX_ID_BIT_COUNT + ) + ) + safe_ensure_future(self._create_order( + trade_type=TradeType.SELL, + order_id=order_id, + trading_pair=trading_pair, + amount=amount, + order_type=order_type, + price=price)) + return order_id + + def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception): + # Not required for OMS connectors + return False + + def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update + # when replacing the dummy implementation + return False + + def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool: + # TODO: implement this method correctly for the connector + # The default implementation was added when the functionality to detect not found orders was introduced in the + # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the + # dummy implementation + return False + + async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder): + """ + This implementation specific function is called by _cancel, and returns True if successful + """ + start_ts = self.current_timestamp + self.logger().debug(f"Starting cancelation of {tracked_order.client_order_id} at {start_ts}") + await self._ensure_authenticated() + params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.ACCOUNT_ID_FIELD: self._auth.account_id, + CONSTANTS.CL_ORDER_ID_FIELD: int(tracked_order.client_order_id), + } + cancel_result = await self._api_post( + path_url=CONSTANTS.REST_ORDER_CANCELATION_ENDPOINT, + data=params, + is_auth_required=True, + ) + self.logger().debug(f"Cancelation result of {tracked_order.client_order_id} at {start_ts}: {cancel_result}") + cancel_success = False + if cancel_result.get(CONSTANTS.ERROR_CODE_FIELD): + if cancel_result[CONSTANTS.ERROR_CODE_FIELD] == CONSTANTS.RESOURCE_NOT_FOUND_ERR_CODE: + await self._order_tracker.process_order_not_found(order_id) + else: + raise IOError(cancel_result[CONSTANTS.ERROR_MSG_FIELD]) + cancel_success = cancel_success or cancel_result[CONSTANTS.RESULT_FIELD] + + if not cancel_success: + self.logger().debug( + f"Failure to cancel {tracked_order.client_order_id}, attempted at {start_ts}: {cancel_result}" + ) + elif order_id in self._order_not_found_on_cancel_record: + del self._order_not_found_on_cancel_record[order_id] + + self.logger().debug( + f"Cancelation of {tracked_order.client_order_id} at {start_ts} success" + ) + + return cancel_success + + def _get_fee( + self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = s_decimal_NaN, + is_maker: Optional[bool] = None, + ) -> TradeFeeBase: + is_maker = False + fee = build_trade_fee( + self.name, + is_maker, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) + return fee + + async def _place_order( + self, + order_id: str, + trading_pair: str, + amount: Decimal, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + ) -> Tuple[str, float]: + instrument_id = await self.exchange_symbol_associated_to_pair(trading_pair) + data = { + CONSTANTS.INSTRUMENT_ID_FIELD: int(instrument_id), + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.ACCOUNT_ID_FIELD: self._auth.account_id, + CONSTANTS.TIME_IN_FORCE_FIELD: CONSTANTS.GTC_TIF, + CONSTANTS.CLIENT_ORDER_ID_FIELD: int(order_id), + CONSTANTS.SIDE_FIELD: CONSTANTS.BUY_ACTION if trade_type == TradeType.BUY else CONSTANTS.SELL_ACTION, + CONSTANTS.QUANTITY_FIELD: float(amount), + CONSTANTS.ORDER_TYPE_FIELD: CONSTANTS.ORDER_TYPES[order_type], + CONSTANTS.LIMIT_PRICE_FIELD: float(price), + } + + send_order_resp = await self._api_request( + path_url=CONSTANTS.REST_ORDER_CREATION_ENDPOINT, + method=RESTMethod.POST, + data=data, + is_auth_required=True, + limit_id=CONSTANTS.REST_ORDER_CREATION_ENDPOINT, + ) + if send_order_resp.get(CONSTANTS.ERROR_CODE_FIELD): + raise IOError(f"Error submitting order {order_id}: {send_order_resp[CONSTANTS.ERROR_MSG_FIELD]}") + return str(send_order_resp[CONSTANTS.ORDER_ID_FIELD]), self.current_timestamp + + async def _update_trading_fees(self): + pass + + async def _user_stream_event_listener(self): + async for event_message in self._iter_user_event_queue(): + try: + endpoint = event_message[CONSTANTS.MSG_ENDPOINT_FIELD] + payload = event_message[CONSTANTS.MSG_DATA_FIELD] + + if endpoint == CONSTANTS.WS_ACC_POS_EVENT: + self._process_account_position_event(payload) + elif endpoint == CONSTANTS.WS_ORDER_STATE_EVENT: + order = self._order_tracker.all_updatable_orders.get(str(payload[CONSTANTS.CLIENT_ORDER_ID_FIELD])) + if order is not None: + order_update = self._create_order_update(order_msg=payload, order=order) + self._order_tracker.process_order_update(order_update) + elif endpoint == CONSTANTS.WS_ORDER_TRADE_EVENT: + order = self._order_tracker.all_fillable_orders.get(str(payload[CONSTANTS.CLIENT_ORDER_ID_FIELD])) + if order is not None: + trade_update = self._create_trade_update(trade_event=payload, order=order) + self._order_tracker.process_trade_update(trade_update) + elif endpoint == CONSTANTS.WS_CANCEL_ORDER_REJECTED_EVENT: + pass + else: + self.logger().debug(f"Unknown event received from the connector ({event_message})") + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error in user stream listener loop.") + await self._sleep(5.0) + + async def _update_trading_rules(self): + # This has to be reimplemented because the request requires an extra parameter + exchange_info = await self._api_get( + path_url=self.trading_rules_request_path, + params={CONSTANTS.OMS_ID_FIELD: self.oms_id}, + ) + trading_rules_list = await self._format_trading_rules(exchange_info) + self._trading_rules.clear() + for trading_rule in trading_rules_list: + self._trading_rules[trading_rule.trading_pair] = trading_rule + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + + async def _format_trading_rules(self, raw_trading_pair_info: List[Dict[str, Any]]): + trading_rules = [] + + for info in raw_trading_pair_info: + try: + if is_exchange_information_valid(exchange_info=info): + instrument_id = info[CONSTANTS.INSTRUMENT_ID_FIELD] + trading_pair = await self.trading_pair_associated_to_exchange_symbol(str(instrument_id)) + trading_rules.append( + TradingRule( + trading_pair=trading_pair, + min_order_size=Decimal(str(info[CONSTANTS.MIN_QUANT_FIELD])), + min_price_increment=Decimal(str(info[CONSTANTS.MIN_PRICE_INCR_FIELD])), + min_base_amount_increment=Decimal(str(info[CONSTANTS.MIN_QUANT_INCR_FIELD])), + ) + ) + except Exception: + self.logger().exception(f"Error parsing the trading pair rule {info}. Skipping.") + + return trading_rules + + async def _get_last_traded_price(self, trading_pair: str) -> float: + instrument_id = await self.exchange_symbol_associated_to_pair(trading_pair) + params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.INSTRUMENT_ID_FIELD: instrument_id, + } + response = await self._api_request( + path_url=CONSTANTS.REST_GET_L1_ENDPOINT, params=params + ) + return response[CONSTANTS.LAST_TRADED_PRICE_FIELD] + + async def _update_balances(self): + local_asset_names = set(self._account_balances.keys()) + remote_asset_names = set() + + await self._ensure_authenticated() + + params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.ACCOUNT_ID_FIELD: self._auth.account_id, + } + account_positions: List[Dict[str, Any]] = await self._api_request( + path_url=CONSTANTS.REST_ACC_POSITIONS_ENDPOINT, + params=params, + is_auth_required=True, + ) + for position in account_positions: + self._process_account_position_event(position) + token = position[CONSTANTS.PRODUCT_SYMBOL_FIELD] + remote_asset_names.add(token) + + asset_names_to_remove = local_asset_names.difference(remote_asset_names) + for asset_name in asset_names_to_remove: + del self._account_available_balances[asset_name] + del self._account_balances[asset_name] + + async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]: + trade_updates = [] + + if order.exchange_order_id is not None: + exchange_order_id = int(order.exchange_order_id) + params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.ACCOUNT_ID_FIELD: self._auth.account_id, + CONSTANTS.USER_ID_FIELD: self._auth.user_id, + CONSTANTS.ORDER_ID_FIELD: exchange_order_id + } + + all_fills_response = await self._api_request( + path_url=CONSTANTS.REST_TRADE_HISTORY_ENDPOINT, + params=params, + is_auth_required=True, + ) + + for trade in all_fills_response: # trades must be handled before order status updates + trade_update = self._create_trade_update(trade_event=trade, order=order) + trade_updates.append(trade_update) + + return trade_updates + + async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate: + exchange_order_id = await tracked_order.get_exchange_order_id() + params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.ACCOUNT_ID_FIELD: self._auth.account_id, + CONSTANTS.ORDER_ID_FIELD: int(exchange_order_id), + } + updated_order_data = await self._api_request( + path_url=CONSTANTS.REST_ORDER_STATUS_ENDPOINT, + params=params, + is_auth_required=True, + ) + + order_update = self._create_order_update(order_msg=updated_order_data, order=tracked_order) + + return order_update + + async def _validate_status_responses( + self, status_responses: List[Dict[str, Any]], associated_orders: List[InFlightOrder] + ) -> List[Dict[str, Any]]: + validated_responses: List[Dict[str, Any]] = [] + for resp, order in zip(status_responses, associated_orders): + if resp.get(CONSTANTS.ERROR_CODE_FIELD): + self.logger().error(f"Error fetching order status. Response: {resp}") + await self._order_tracker.process_order_not_found(order.client_order_id) + else: + validated_responses.append(resp) + return validated_responses + + def _process_account_position_event(self, account_position_event: Dict[str, Any]): + token = account_position_event[CONSTANTS.PRODUCT_SYMBOL_FIELD] + amount = Decimal(str(account_position_event[CONSTANTS.AMOUNT_FIELD])) + on_hold = Decimal(str(account_position_event[CONSTANTS.AMOUNT_ON_HOLD_FIELD])) + self._account_balances[token] = amount + self._account_available_balances[token] = (amount - on_hold) + + def _create_order_update(self, order_msg: Dict[str, Any], order: InFlightOrder): + status_from_update = order_msg[CONSTANTS.ORDER_STATE_FIELD] + if status_from_update == CONSTANTS.ACTIVE_ORDER_STATE: + filled_amount = order_msg[CONSTANTS.QUANTITY_EXECUTED_FIELD] + if filled_amount != 0: + order_status = OrderState.PARTIALLY_FILLED + else: + order_status = OrderState.OPEN + else: + order_status = CONSTANTS.ORDER_STATE_MAP[status_from_update] + order_update = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=order_msg[CONSTANTS.ORDER_UPDATE_TS_FIELD] * 1e-3, + new_state=order_status, + client_order_id=order.client_order_id, + exchange_order_id=str(order_msg[CONSTANTS.ORDER_ID_FIELD]), + ) + return order_update + + def _create_trade_update(self, trade_event: Dict[str, Any], order: InFlightOrder): + order_action = trade_event[CONSTANTS.SIDE_FIELD] + trade_type = CONSTANTS.ORDER_SIDE_MAP[order_action] + token_asset_id = trade_event[CONSTANTS.FEE_PRODUCT_ID_FIELD] + fee_amount = Decimal(str(trade_event[CONSTANTS.FEE_AMOUNT_FIELD])) + fee_token = self._token_id_map[token_asset_id] if fee_amount else order.quote_asset + fee = TradeFeeBase.new_spot_fee( + fee_schema=self.trade_fee_schema(), + trade_type=trade_type, + percent_token=fee_token, + flat_fees=[TokenAmount(amount=fee_amount, token=fee_token)], + ) + fill_amount = Decimal(str(trade_event[CONSTANTS.QUANTITY_FIELD.capitalize()])) + fill_price = Decimal(str(trade_event[CONSTANTS.PRICE_FIELD])) + trade_time = trade_event[CONSTANTS.TRADE_TIME_MS_FIELD] + trade_update = TradeUpdate( + trade_id=str(trade_event[CONSTANTS.TRADE_ID_FIELD]), + client_order_id=order.client_order_id, + exchange_order_id=str(trade_event[CONSTANTS.ORDER_ID_FIELD]), + trading_pair=order.trading_pair, + fee=fee, + fill_base_amount=fill_amount, + fill_quote_amount=fill_amount * fill_price, + fill_price=fill_price, + fill_timestamp=int(trade_time * 1e-3), + ) + return trade_update + + def _create_web_assistants_factory(self) -> OMSConnectorWebAssistantsFactory: + """We create a new authenticator to store the new session token.""" + return ap_web_utils.build_api_factory( + throttler=self._throttler, auth=self.authenticator + ) + + async def _api_request( + self, + path_url, + method: RESTMethod = RESTMethod.GET, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + is_auth_required: bool = False, + return_err: bool = False, + limit_id: Optional[str] = None, + ) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + url = self._url_creator.get_rest_url(path_url) + return await rest_assistant.execute_request( + url=url, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + throttler_limit_id=limit_id if limit_id else path_url, + ) + + def _create_order_book_data_source(self) -> OrderBookTrackerDataSource: + return OMSConnectorAPIOrderBookDataSource( + self.trading_pairs, + connector=self, + api_factory=self._web_assistants_factory, + url_provider=self._url_creator, + oms_id=self.oms_id, + ) + + def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: + return OMSConnectorAPIUserStreamDataSource(self._web_assistants_factory, self._url_creator, self.oms_id) + + async def _initialize_trading_pair_symbol_map(self): + try: + params = {CONSTANTS.OMS_ID_FIELD: self.oms_id} + exchange_info = await self._api_get(path_url=self.trading_pairs_request_path, params=params) + self._initialize_trading_pair_symbols_from_exchange_info(exchange_info=exchange_info) + except Exception: + self.logger().exception("There was an error requesting exchange info.") + + def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: List[Dict[str, Any]]): + mapping = bidict() + for symbol_data in filter(is_exchange_information_valid, exchange_info): + instrument_id = str(symbol_data[CONSTANTS.INSTRUMENT_ID_FIELD]) + trading_pair = combine_to_hb_trading_pair( + base=symbol_data[CONSTANTS.BASE_FIELD], quote=symbol_data[CONSTANTS.QUOTE_FIELD] + ) + if instrument_id in mapping: + self.logger().error( + f"Instrument ID {instrument_id} (trading pair {trading_pair}) already present in the map " + f"(with trading pair {mapping[instrument_id]})." + ) + continue + elif trading_pair in mapping.inverse: + self.logger().error( + f"Trading pair {trading_pair} (instrument ID {instrument_id}) already present in the map " + f"(with ID {mapping.inverse[trading_pair]})." + ) + continue + mapping[instrument_id] = trading_pair + base_id = symbol_data[CONSTANTS.BASE_ID_FIELD] + base_token = symbol_data[CONSTANTS.BASE_FIELD] + self._token_id_map[base_id] = base_token + quote_id = symbol_data[CONSTANTS.QUOTE_ID_FIELD] + quote_token = symbol_data[CONSTANTS.QUOTE_FIELD] + self._token_id_map[quote_id] = quote_token + self._set_trading_pair_symbol_map(mapping) + + async def _ensure_authenticated(self): + if not self._auth.initialized: + await self._authenticate() + + async def _authenticate(self): + auth_headers = self._auth.get_rest_auth_headers() + url = self._url_creator.get_rest_url(CONSTANTS.REST_AUTH_ENDPOINT) + rest_assistant = await self._web_assistants_factory.get_rest_assistant() + + auth_response = await rest_assistant.execute_request( + url, + throttler_limit_id=CONSTANTS.REST_AUTH_ENDPOINT, + headers=auth_headers + ) + + auth_success = self._auth.validate_rest_auth(auth_response) + if auth_success: + self._auth.update_with_rest_response(auth_response) + else: + raise IOError("Failed to authenticate.") diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_utils.py b/hummingbot/connector/utilities/oms_connector/oms_connector_utils.py new file mode 100644 index 0000000..6a8cc3b --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_utils.py @@ -0,0 +1,18 @@ +from typing import Any, Dict + +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS + + +def is_exchange_information_valid(exchange_info: Dict[str, Any]) -> bool: + """ + Verifies if a trading pair is enabled to operate with based on its exchange information + + :param exchange_info: the exchange information for a trading pair + + :return: True if the trading pair is enabled, False otherwise + """ + return ( + not exchange_info[CONSTANTS.IS_DISABLED_FIELD] + and exchange_info[CONSTANTS.SESSION_STATUS_FIELD] + and "ERR" not in exchange_info[CONSTANTS.SYMBOL_FIELD] + ) diff --git a/hummingbot/connector/utilities/oms_connector/oms_connector_web_utils.py b/hummingbot/connector/utilities/oms_connector/oms_connector_web_utils.py new file mode 100644 index 0000000..b628e71 --- /dev/null +++ b/hummingbot/connector/utilities/oms_connector/oms_connector_web_utils.py @@ -0,0 +1,80 @@ +import json +import time +from abc import ABC, abstractmethod +from typing import Optional + +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.connections.data_types import WSRequest, WSResponse +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_post_processors import WSPostProcessorBase +from hummingbot.core.web_assistant.ws_pre_processors import WSPreProcessorBase + + +class OMSConnectorURLCreatorBase(ABC): + @abstractmethod + def get_rest_url(self, path_url: str) -> str: + raise NotImplementedError + + @abstractmethod + def get_ws_url(self) -> str: + raise NotImplementedError + + +class OMSConnectorWSPreProcessor(WSPreProcessorBase): + def __init__(self): + self._msg_sequence_num = 0 + + async def pre_process(self, request: WSRequest) -> WSRequest: + request.payload[CONSTANTS.MSG_TYPE_FIELD] = CONSTANTS.REQ_MSG_TYPE + sequence_num = self._generate_sequence_number() + request.payload[CONSTANTS.MSG_SEQUENCE_FIELD] = sequence_num + request.payload[CONSTANTS.MSG_DATA_FIELD] = json.dumps(request.payload[CONSTANTS.MSG_DATA_FIELD]) + return request + + def _generate_sequence_number(self) -> int: + self._msg_sequence_num += 2 + return self._msg_sequence_num + + +class OMSConnectorWSPostProcessor(WSPostProcessorBase): + async def post_process(self, response: WSResponse) -> WSResponse: + if CONSTANTS.MSG_DATA_FIELD in response.data: + response.data[CONSTANTS.MSG_DATA_FIELD] = json.loads(response.data[CONSTANTS.MSG_DATA_FIELD]) + return response + + +class OMSConnectorWebAssistantsFactory(WebAssistantsFactory): + @property + def auth(self) -> Optional[OMSConnectorAuth]: + return self._auth + + +def build_api_factory( + throttler: Optional[AsyncThrottler] = None, + auth: Optional[OMSConnectorAuth] = None, +): + throttler = throttler or create_throttler() + api_factory = OMSConnectorWebAssistantsFactory( + throttler=throttler, + auth=auth, + ws_pre_processors=[OMSConnectorWSPreProcessor()], + ws_post_processors=[OMSConnectorWSPostProcessor()], + ) + return api_factory + + +def create_throttler() -> AsyncThrottler: + return AsyncThrottler(CONSTANTS.RATE_LIMITS) + + +async def get_current_server_time( + throttler: Optional[AsyncThrottler] = None, domain: str = "" +) -> float: + return _time() * 1e3 + + +def _time() -> float: + """Can be mocked in unit-tests without directly affecting `time.time()`""" + return time.time() diff --git a/hummingbot/connector/utils.py b/hummingbot/connector/utils.py new file mode 100644 index 0000000..8ba4269 --- /dev/null +++ b/hummingbot/connector/utils.py @@ -0,0 +1,122 @@ +import gzip +import json +import os +import platform +from collections import namedtuple +from hashlib import md5 +from typing import Any, Callable, Dict, Optional, Tuple + +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.utils.tracking_nonce import NonceCreator, get_tracking_nonce +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSResponse +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_post_processors import WSPostProcessorBase + +TradeFillOrderDetails = namedtuple("TradeFillOrderDetails", "market exchange_trade_id symbol") + + +def build_api_factory(throttler: AsyncThrottlerBase) -> WebAssistantsFactory: + throttler = throttler or AsyncThrottler(rate_limits=[]) + api_factory = WebAssistantsFactory(throttler=throttler) + return api_factory + + +def split_hb_trading_pair(trading_pair: str) -> Tuple[str, str]: + base, quote = trading_pair.split("-") + return base, quote + + +def combine_to_hb_trading_pair(base: str, quote: str) -> str: + trading_pair = f"{base}-{quote}" + return trading_pair + + +def validate_trading_pair(trading_pair: str) -> bool: + valid = False + if "-" in trading_pair and len(trading_pair.split("-")) == 2: + valid = True + return valid + + +def _bot_instance_id() -> str: + return md5(f"{platform.uname()}_pid:{os.getpid()}_ppid:{os.getppid()}".encode("utf-8")).hexdigest() + + +def get_new_client_order_id( + is_buy: bool, trading_pair: str, hbot_order_id_prefix: str = "", max_id_len: Optional[int] = None +) -> str: + """ + Creates a client order id for a new order + + Note: If the need for much shorter IDs arises, an option is to concatenate the host name, the PID, + and the nonce, and hash the result. + + :param is_buy: True if the order is a buy order, False otherwise + :param trading_pair: the trading pair the order will be operating with + :param hbot_order_id_prefix: The hummingbot-specific identifier for the given exchange + :param max_id_len: The maximum length of the ID string. + :return: an identifier for the new order to be used in the client + """ + side = "B" if is_buy else "S" + symbols = split_hb_trading_pair(trading_pair) + base = symbols[0].upper() + quote = symbols[1].upper() + base_str = f"{base[0]}{base[-1]}" + quote_str = f"{quote[0]}{quote[-1]}" + client_instance_id = _bot_instance_id() + ts_hex = hex(get_tracking_nonce())[2:] + client_order_id = f"{hbot_order_id_prefix}{side}{base_str}{quote_str}{ts_hex}{client_instance_id}" + + if max_id_len is not None: + id_prefix = f"{hbot_order_id_prefix}{side}{base_str}{quote_str}" + suffix_max_length = max_id_len - len(id_prefix) + if suffix_max_length < len(ts_hex): + id_suffix = md5(f"{ts_hex}{client_instance_id}".encode()).hexdigest() + client_order_id = f"{id_prefix}{id_suffix[:suffix_max_length]}" + else: + client_order_id = client_order_id[:max_id_len] + return client_order_id + + +def get_new_numeric_client_order_id(nonce_creator: NonceCreator, max_id_bit_count: Optional[int] = None) -> int: + hexa_hash = _bot_instance_id() + host_part = int(hexa_hash, 16) + client_order_id = int(f"{host_part}{nonce_creator.get_tracking_nonce()}") + if max_id_bit_count: + max_int = 2 ** max_id_bit_count - 1 + client_order_id &= max_int + return client_order_id + + +class TimeSynchronizerRESTPreProcessor(RESTPreProcessorBase): + """ + This pre processor is intended to be used in those connectors that require synchronization with the server time + to accept API requests. It ensures the synchronizer has at least one server time sample before being used. + """ + + def __init__(self, synchronizer: TimeSynchronizer, time_provider: Callable): + super().__init__() + self._synchronizer = synchronizer + self._time_provider = time_provider + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + await self._synchronizer.update_server_time_if_not_initialized(time_provider=self._time_provider()) + return request + + +class GZipCompressionWSPostProcessor(WSPostProcessorBase): + """ + Performs the necessary response processing from both public and private websocket streams. + """ + + async def post_process(self, response: WSResponse) -> WSResponse: + if not isinstance(response.data, bytes): + # Unlike Market WebSocket, the return data of Account and Order Websocket are not compressed by GZIP. + return response + encoded_msg: bytes = gzip.decompress(response.data) + msg: Dict[str, Any] = json.loads(encoded_msg.decode("utf-8")) + + return WSResponse(data=msg) diff --git a/hummingbot/core/PyRef.pxd b/hummingbot/core/PyRef.pxd new file mode 100644 index 0000000..2a178bb --- /dev/null +++ b/hummingbot/core/PyRef.pxd @@ -0,0 +1,12 @@ +# distutils: language=c++ + +cdef extern from "cpp/PyRef.h": + ctypedef struct PyObject + + cdef cppclass PyRef: + PyRef() + PyRef(PyObject *obj) + PyRef(const PyRef &other) + PyRef &operator=(const PyRef &other) + bint operator==(const PyRef &other) const + PyObject *get() const diff --git a/hummingbot/core/Utils.pxd b/hummingbot/core/Utils.pxd new file mode 100644 index 0000000..9e04927 --- /dev/null +++ b/hummingbot/core/Utils.pxd @@ -0,0 +1,8 @@ +# distutils: language=c++ + +cdef extern from "" namespace "std" nogil: + cdef cppclass reverse_iterator[T]: + pass + +cdef extern from "cpp/Utils.h": + cdef const T getIteratorFromReverseIterator[T](const reverse_iterator[T] rit) diff --git a/hummingbot/core/__init__.py b/hummingbot/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/api_throttler/__init__.py b/hummingbot/core/api_throttler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/api_throttler/async_request_context_base.py b/hummingbot/core/api_throttler/async_request_context_base.py new file mode 100644 index 0000000..0cb2039 --- /dev/null +++ b/hummingbot/core/api_throttler/async_request_context_base.py @@ -0,0 +1,94 @@ +import asyncio +import logging +import time +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import List, Tuple + +from hummingbot.core.api_throttler.data_types import RateLimit, TaskLog +from hummingbot.logger.logger import HummingbotLogger + +arc_logger = None +MAX_CAPACITY_REACHED_WARNING_INTERVAL = 30.0 + + +class AsyncRequestContextBase(ABC): + """ + An async context class ('async with' syntax) that checks for rate limit and waits for the capacity to be freed. + It uses an async lock to prevent multiple instances of this class from accessing the `acquire()` function. + """ + + _last_max_cap_warning_ts: float = 0.0 + + @classmethod + def logger(cls) -> HummingbotLogger: + global arc_logger + if arc_logger is None: + arc_logger = logging.getLogger(__name__) + return arc_logger + + def __init__(self, + task_logs: List[TaskLog], + rate_limit: RateLimit, + related_limits: List[Tuple[RateLimit, int]], + lock: asyncio.Lock, + safety_margin_pct: float, + retry_interval: float = 0.1, + ): + """ + Asynchronous context associated with each API request. + :param task_logs: Shared task logs associated with this API request + :param rate_limit: The RateLimit associated with this API Request + :param related_limits: List of linked rate limits with its corresponding weight associated with this API Request + :param lock: A shared asyncio.Lock used between all instances of APIRequestContextBase + :param retry_interval: Time between each limit check + """ + self._task_logs: List[TaskLog] = task_logs + self._rate_limit: RateLimit = rate_limit + self._related_limits: List[Tuple[RateLimit, int]] = related_limits + self._lock: asyncio.Lock = lock + self._safety_margin_pct: float = safety_margin_pct + self._retry_interval: float = retry_interval + + def flush(self): + """ + Remove task logs that have passed rate limit periods + :return: + """ + now: Decimal = Decimal(str(time.time())) + for task in self._task_logs: + task_limit: RateLimit = task.rate_limit + elapsed: Decimal = now - Decimal(str(task.timestamp)) + if elapsed > Decimal(str(task_limit.time_interval * (1 + self._safety_margin_pct))): + self._task_logs.remove(task) + + @abstractmethod + def within_capacity(self) -> bool: + raise NotImplementedError + + async def acquire(self): + while True: + async with self._lock: + self.flush() + + if self.within_capacity(): + break + await asyncio.sleep(self._retry_interval) + async with self._lock: + now = time.time() + # Each related limit is represented as it own individual TaskLog + + # Log the acquired rate limit into the tasks log + self._task_logs.append(TaskLog(timestamp=now, + rate_limit=self._rate_limit, + weight=self._rate_limit.weight)) + + # Log its related limits into the tasks log as individual tasks + for limit, weight in self._related_limits: + self._task_logs.append(TaskLog(timestamp=now, rate_limit=limit, weight=weight)) + + async def __aenter__(self): + await self.acquire() + + async def __aexit__(self, exc_type, exc, tb): + pass diff --git a/hummingbot/core/api_throttler/async_throttler.py b/hummingbot/core/api_throttler/async_throttler.py new file mode 100644 index 0000000..f1f8911 --- /dev/null +++ b/hummingbot/core/api_throttler/async_throttler.py @@ -0,0 +1,77 @@ +import time +from decimal import Decimal +from typing import List, Tuple + +from hummingbot.core.api_throttler.async_request_context_base import ( + MAX_CAPACITY_REACHED_WARNING_INTERVAL, + AsyncRequestContextBase, +) +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.api_throttler.data_types import RateLimit + + +class AsyncRequestContext(AsyncRequestContextBase): + """ + An async context class ('async with' syntax) that checks for rate limit and wait for the capacity if needed. + It uses async lock to prevent other instances of this class from running acquire fn before it finishes with it. + """ + + def within_capacity(self) -> bool: + """ + Checks if an additional task within the defined RateLimit(s). Logs a warning message if the limit is about to be reached. + Note: A task can be associated to one or more RateLimit. + :return: True if it is within capacity to add a new task + """ + if self._rate_limit is not None: + list_of_limits: List[Tuple[RateLimit, int]] = [(self._rate_limit, + self._rate_limit.weight)] + self._related_limits + now: float = self._time() + for rate_limit, weight in list_of_limits: + capacity_used: int = sum([task.weight + for task in self._task_logs + if rate_limit.limit_id == task.rate_limit.limit_id and + Decimal(str(now)) - Decimal(str(task.timestamp)) - Decimal(str(task.rate_limit.time_interval * self._safety_margin_pct)) <= task.rate_limit.time_interval]) + + if capacity_used + weight > rate_limit.limit: + if self._last_max_cap_warning_ts < now - MAX_CAPACITY_REACHED_WARNING_INTERVAL: + msg = f"API rate limit on {rate_limit.limit_id} ({rate_limit.limit} calls per " \ + f"{rate_limit.time_interval}s) has almost reached. Limits used " \ + f"is {capacity_used} in the last " \ + f"{rate_limit.time_interval} seconds" + self.logger().notify(msg) + AsyncRequestContextBase._last_max_cap_warning_ts = now + return False + return True + + def _time(self): + return time.time() + + +class AsyncThrottler(AsyncThrottlerBase): + """ + Handles call rate limits by providing async context (async with), it delays as needed to make sure calls stay + within defined limits. + A task can have multiple call rates (weight), though tasks are still ordered in sequence as they come (FIFO). + (i.e) + Pool 0 - rate limit is 100 calls per second + Pool 1 - rate limit is 10 calls per second + Task A which consumes capacity from both Pool 0 and Pool 1 can be called at 10 calls per second, any calls after + this (whether it belongs to Pool 0 or Pool 1) will have to wait for new capacity (some of the Task A flushed out). + """ + + def execute_task(self, limit_id: str) -> AsyncRequestContext: + """ + Creates an async context where code within the context (a task) can be run only when all rate + limits have capacity for the new task. + :param limit_id: the limit_id associated with the APi request + :return: An async context (used with async with syntax) + """ + rate_limit, related_rate_limits = self.get_related_limits(limit_id=limit_id) + return AsyncRequestContext( + task_logs=self._task_logs, + rate_limit=rate_limit, + related_limits=related_rate_limits, + lock=self._lock, + safety_margin_pct=self._safety_margin_pct, + retry_interval=self._retry_interval, + ) diff --git a/hummingbot/core/api_throttler/async_throttler_base.py b/hummingbot/core/api_throttler/async_throttler_base.py new file mode 100644 index 0000000..d293fcd --- /dev/null +++ b/hummingbot/core/api_throttler/async_throttler_base.py @@ -0,0 +1,90 @@ +import asyncio +import copy +import logging +import math +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Dict, List, Optional, Tuple + +from hummingbot.core.api_throttler.async_request_context_base import AsyncRequestContextBase +from hummingbot.core.api_throttler.data_types import RateLimit, TaskLog +from hummingbot.logger.logger import HummingbotLogger + + +class AsyncThrottlerBase(ABC): + """ + The APIThrottlerBase is an abstract class meant to describe the functions necessary to handle the + throttling of API requests through the usage of asynchronous context managers. + """ + + _default_config_map = {} + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, + rate_limits: List[RateLimit], + retry_interval: float = 0.1, + safety_margin_pct: Optional[float] = 0.05, # An extra safety margin, in percentage. + limits_share_percentage: Optional[Decimal] = None + ): + """ + :param rate_limits: List of RateLimit(s). + :param retry_interval: Time between every capacity check. + :param safety_margin_pct: Percentage of limit to be added as a safety margin when calculating capacity to ensure + calls are within the limit. + :param limits_share_percentage: Percentage of the limits to be used by this instance (important when multiple + bots operate with the same account) + """ + # If configured, users can define the percentage of rate limits to allocate to the throttler. + share_percentage = limits_share_percentage or Decimal("100") + self.limits_pct: Decimal = share_percentage / 100 + + self.set_rate_limits(rate_limits) + + # List of TaskLog used to determine the API requests within a set time window. + self._task_logs: List[TaskLog] = [] + + # Throttler Parameters + self._retry_interval: float = retry_interval + self._safety_margin_pct: float = safety_margin_pct + + # Shared asyncio.Lock instance to prevent multiple async ContextManager from accessing the _task_logs variable + self._lock = asyncio.Lock() + + def set_rate_limits(self, rate_limits: List[RateLimit]): + # Rate Limit Definitions + self._rate_limits: List[RateLimit] = copy.deepcopy(rate_limits) + + for rate_limit in self._rate_limits: + rate_limit.limit = max(Decimal("1"), math.floor(Decimal(str(rate_limit.limit)) * self.limits_pct)) + + # Dictionary of path_url to RateLimit + self._id_to_limit_map: Dict[str, RateLimit] = {limit.limit_id: limit for limit in self._rate_limits} + + def _client_config_map(self): + from hummingbot.client.hummingbot_application import HummingbotApplication # avoids circular import + + return HummingbotApplication.main_application().client_config_map + + def get_related_limits(self, limit_id: str) -> Tuple[RateLimit, List[Tuple[RateLimit, int]]]: + rate_limit: Optional[RateLimit] = self._id_to_limit_map.get(limit_id, None) + linked_limits: List[RateLimit] = [] if rate_limit is None else rate_limit.linked_limits + + related_limits = [(self._id_to_limit_map[limit_weight_pair.limit_id], limit_weight_pair.weight) + for limit_weight_pair in linked_limits + if limit_weight_pair.limit_id in self._id_to_limit_map] + + # Append self as part of the related_limits + # if rate_limit is not None: + # related_limits.append((rate_limit, rate_limit.weight)) +# + return rate_limit, related_limits + + @abstractmethod + def execute_task(self, limit_id: str) -> AsyncRequestContextBase: + raise NotImplementedError diff --git a/hummingbot/core/api_throttler/data_types.py b/hummingbot/core/api_throttler/data_types.py new file mode 100644 index 0000000..2bbf211 --- /dev/null +++ b/hummingbot/core/api_throttler/data_types.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass +from typing import ( + List, + Optional, +) + +DEFAULT_PATH = "" +DEFAULT_WEIGHT = 1 + +Limit = int # Integer representing the no. of requests be time interval +RequestPath = str # String representing the request path url +RequestWeight = int # Integer representing the request weight of the path url +Seconds = float + + +@dataclass +class LinkedLimitWeightPair: + limit_id: str + weight: int = DEFAULT_WEIGHT + + +class RateLimit: + """ + Defines call rate limits typical for API endpoints. + """ + + def __init__(self, + limit_id: str, + limit: int, + time_interval: float, + weight: int = DEFAULT_WEIGHT, + linked_limits: Optional[List[LinkedLimitWeightPair]] = None, + ): + """ + :param limit_id: A unique identifier for this RateLimit object, this is usually an API request path url + :param limit: A total number of calls * weight permitted within time_interval period + :param time_interval: The time interval in seconds + :param weight: The weight (in integer) of each call. Defaults to 1 + :param linked_limits: Optional list of LinkedLimitWeightPairs. Used to associate a weight to the linked rate limit. + """ + self.limit_id = limit_id + self.limit = limit + self.time_interval = time_interval + self.weight = weight + self.linked_limits = linked_limits or [] + + def __repr__(self): + return f"limit_id: {self.limit_id}, limit: {self.limit}, time interval: {self.time_interval}, " \ + f"weight: {self.weight}, linked_limits: {self.linked_limits}" + + +@dataclass +class TaskLog: + timestamp: float + rate_limit: RateLimit + weight: int diff --git a/hummingbot/core/clock.pxd b/hummingbot/core/clock.pxd new file mode 100644 index 0000000..afbf70d --- /dev/null +++ b/hummingbot/core/clock.pxd @@ -0,0 +1,12 @@ +# distutils: language=c++ + +cdef class Clock: + cdef: + object _clock_mode + double _tick_size + double _start_time + double _end_time + list _child_iterators + list _current_context + double _current_tick + bint _started diff --git a/hummingbot/core/clock.pyx b/hummingbot/core/clock.pyx new file mode 100644 index 0000000..071b8f1 --- /dev/null +++ b/hummingbot/core/clock.pyx @@ -0,0 +1,158 @@ +# distutils: language=c++ + +import asyncio +import logging +import time +from typing import List + +from hummingbot.core.time_iterator import TimeIterator +from hummingbot.core.time_iterator cimport TimeIterator +from hummingbot.core.clock_mode import ClockMode +from hummingbot.logger import HummingbotLogger + +s_logger = None + + +cdef class Clock: + @classmethod + def logger(cls) -> HummingbotLogger: + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def __init__(self, clock_mode: ClockMode, tick_size: float = 1.0, start_time: float = 0.0, end_time: float = 0.0): + """ + :param clock_mode: either real time mode or back testing mode + :param tick_size: time interval of each tick + :param start_time: (back testing mode only) start of simulation in UNIX timestamp + :param end_time: (back testing mode only) end of simulation in UNIX timestamp. NaN to simulate to end of data. + """ + self._clock_mode = clock_mode + self._tick_size = tick_size + self._start_time = start_time + self._end_time = end_time + self._current_tick = start_time if clock_mode is ClockMode.BACKTEST else (time.time() // tick_size) * tick_size + self._child_iterators = [] + self._current_context = None + self._started = False + + @property + def clock_mode(self) -> ClockMode: + return self._clock_mode + + @property + def start_time(self) -> float: + return self._start_time + + @property + def tick_size(self) -> float: + return self._tick_size + + @property + def child_iterators(self) -> List[TimeIterator]: + return self._child_iterators + + @property + def current_timestamp(self) -> float: + return self._current_tick + + def __enter__(self) -> Clock: + if self._current_context is not None: + raise EnvironmentError("Clock context is not re-entrant.") + self._current_context = self._child_iterators.copy() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._current_context is not None: + for iterator in self._current_context: + (iterator).c_stop(self) + self._current_context = None + + def add_iterator(self, iterator: TimeIterator): + if self._current_context is not None: + self._current_context.append(iterator) + if self._started: + (iterator).c_start(self, self._current_tick) + self._child_iterators.append(iterator) + + def remove_iterator(self, iterator: TimeIterator): + if self._current_context is not None and iterator in self._current_context: + (iterator).c_stop(self) + self._current_context.remove(iterator) + self._child_iterators.remove(iterator) + + async def run(self): + await self.run_til(float("nan")) + + async def run_til(self, timestamp: float): + cdef: + TimeIterator child_iterator + double now = time.time() + double next_tick_time + + if self._current_context is None: + raise EnvironmentError("run() and run_til() can only be used within the context of a `with...` statement.") + + self._current_tick = (now // self._tick_size) * self._tick_size + if not self._started: + for ci in self._current_context: + child_iterator = ci + child_iterator.c_start(self, self._current_tick) + self._started = True + + try: + while True: + now = time.time() + if now >= timestamp: + return + + # Sleep until the next tick + next_tick_time = ((now // self._tick_size) + 1) * self._tick_size + await asyncio.sleep(next_tick_time - now) + self._current_tick = next_tick_time + + # Run through all the child iterators. + for ci in self._current_context: + child_iterator = ci + try: + child_iterator.c_tick(self._current_tick) + except StopIteration: + self.logger().error("Stop iteration triggered in real time mode. This is not expected.") + return + except Exception: + self.logger().error("Unexpected error running clock tick.", exc_info=True) + finally: + for ci in self._current_context: + child_iterator = ci + child_iterator._clock = None + + def backtest_til(self, timestamp: float): + cdef TimeIterator child_iterator + + if not self._started: + for ci in self._child_iterators: + child_iterator = ci + child_iterator.c_start(self, self._start_time) + self._started = True + + try: + while not (self._current_tick >= timestamp): + self._current_tick += self._tick_size + for ci in self._child_iterators: + child_iterator = ci + try: + child_iterator.c_tick(self._current_tick) + except StopIteration: + raise + except Exception: + self.logger().error("Unexpected error running clock tick.", exc_info=True) + except StopIteration: + return + finally: + for ci in self._child_iterators: + child_iterator = ci + child_iterator._clock = None + + def backtest(self): + self.backtest_til(self._end_time) diff --git a/hummingbot/core/clock_mode.py b/hummingbot/core/clock_mode.py new file mode 100644 index 0000000..e18ae03 --- /dev/null +++ b/hummingbot/core/clock_mode.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from enum import Enum + + +class ClockMode(Enum): + REALTIME = 1 + BACKTEST = 2 diff --git a/hummingbot/core/cpp/.gitignore b/hummingbot/core/cpp/.gitignore new file mode 100644 index 0000000..3625f94 --- /dev/null +++ b/hummingbot/core/cpp/.gitignore @@ -0,0 +1,2 @@ +/*.o +/TestOrderBookEntry diff --git a/hummingbot/core/cpp/LimitOrder.cpp b/hummingbot/core/cpp/LimitOrder.cpp new file mode 100644 index 0000000..3fde75b --- /dev/null +++ b/hummingbot/core/cpp/LimitOrder.cpp @@ -0,0 +1,164 @@ +#include "LimitOrder.h" + +LimitOrder::LimitOrder() { + this->clientOrderID = ""; + this->tradingPair = ""; + this->isBuy = false; + this->baseCurrency = ""; + this->quoteCurrency = ""; + this->price = NULL; + this->quantity = NULL; + this->filledQuantity = NULL; + this->creationTimestamp = 0.0; + this->status = 0; + this->position = "NIL"; +} + +LimitOrder::LimitOrder(std::string clientOrderID, + std::string tradingPair, + bool isBuy, + std::string baseCurrency, + std::string quoteCurrency, + PyObject *price, + PyObject *quantity + ) { + this->clientOrderID = clientOrderID; + this->tradingPair = tradingPair; + this->isBuy = isBuy; + this->baseCurrency = baseCurrency; + this->quoteCurrency = quoteCurrency; + this->price = price; + this->quantity = quantity; + this->filledQuantity = NULL; + this->creationTimestamp = 0.0; + this->status = 0; + this->position = "NIL"; + Py_XINCREF(price); + Py_XINCREF(quantity); +} + +LimitOrder::LimitOrder(std::string clientOrderID, + std::string tradingPair, + bool isBuy, + std::string baseCurrency, + std::string quoteCurrency, + PyObject *price, + PyObject *quantity, + PyObject *filledQuantity, + long creationTimestamp, + short int status, + std::string position + ) { + this->clientOrderID = clientOrderID; + this->tradingPair = tradingPair; + this->isBuy = isBuy; + this->baseCurrency = baseCurrency; + this->quoteCurrency = quoteCurrency; + this->price = price; + this->quantity = quantity; + this->filledQuantity = filledQuantity; + this->creationTimestamp = creationTimestamp; + this->status = status; + this->position = position; + Py_XINCREF(price); + Py_XINCREF(quantity); + Py_XINCREF(filledQuantity); +} + +LimitOrder::LimitOrder(const LimitOrder &other) { + this->clientOrderID = other.clientOrderID; + this->tradingPair = other.tradingPair; + this->isBuy = other.isBuy; + this->baseCurrency = other.baseCurrency; + this->quoteCurrency = other.quoteCurrency; + this->price = other.price; + this->quantity = other.quantity; + this->filledQuantity = other.filledQuantity; + this->creationTimestamp = other.creationTimestamp; + this->status = other.status; + this->position = other.position; + Py_XINCREF(this->price); + Py_XINCREF(this->quantity); + Py_XINCREF(this->filledQuantity); +} + +LimitOrder::~LimitOrder() { + Py_XDECREF(this->price); + Py_XDECREF(this->quantity); + Py_XDECREF(this->filledQuantity); + this->price = NULL; + this->quantity = NULL; + this->filledQuantity = NULL; +} + +LimitOrder &LimitOrder::operator=(const LimitOrder &other) { + this->clientOrderID = other.clientOrderID; + this->tradingPair = other.tradingPair; + this->isBuy = other.isBuy; + this->baseCurrency = other.baseCurrency; + this->quoteCurrency = other.quoteCurrency; + this->price = other.price; + this->quantity = other.quantity; + this->filledQuantity = other.filledQuantity; + this->creationTimestamp = other.creationTimestamp; + this->status = other.status; + this->position = other.position; + Py_XINCREF(this->price); + Py_XINCREF(this->quantity); + Py_XINCREF(this->filledQuantity); + + return *this; +} + +bool operator<(LimitOrder const &a, LimitOrder const &b) { + if ((bool)(PyObject_RichCompareBool(a.price, b.price, Py_EQ))) { + // return (bool)(PyObject_RichCompareBool(a.quantity, b.quantity, Py_LT)); + return (bool)(a.clientOrderID < b.clientOrderID); + } else { + return (bool)(PyObject_RichCompareBool(a.price, b.price, Py_LT)); + } +} + +std::string LimitOrder::getClientOrderID() const { + return this->clientOrderID; +} + +std::string LimitOrder::getTradingPair() const { + return this->tradingPair; +} + +bool LimitOrder::getIsBuy() const { + return this->isBuy; +} + +std::string LimitOrder::getBaseCurrency() const { + return this->baseCurrency; +} + +std::string LimitOrder::getQuoteCurrency() const { + return this->quoteCurrency; +} + +PyObject *LimitOrder::getPrice() const { + return this->price; +} + +PyObject *LimitOrder::getQuantity() const { + return this->quantity; +} + +PyObject *LimitOrder::getFilledQuantity() const { + return this->filledQuantity; +} + +long LimitOrder::getCreationTimestamp() const{ + return this->creationTimestamp; +} + +short int LimitOrder::getStatus() const{ + return this->status; +} + +std::string LimitOrder::getPosition() const{ + return this->position; +} diff --git a/hummingbot/core/cpp/LimitOrder.h b/hummingbot/core/cpp/LimitOrder.h new file mode 100644 index 0000000..5e2c17f --- /dev/null +++ b/hummingbot/core/cpp/LimitOrder.h @@ -0,0 +1,58 @@ +#ifndef _LIMIT_ORDER_H +#define _LIMIT_ORDER_H + +#include +#include + +class LimitOrder { + std::string clientOrderID; + std::string tradingPair; + bool isBuy; + std::string baseCurrency; + std::string quoteCurrency; + PyObject *price; + PyObject *quantity; + PyObject *filledQuantity; + long creationTimestamp; + short int status; + std::string position; + + public: + LimitOrder(); + LimitOrder(std::string clientOrderID, + std::string tradingPair, + bool isBuy, + std::string baseCurrency, + std::string quoteCurrency, + PyObject *price, + PyObject *quantity); + LimitOrder(std::string clientOrderID, + std::string tradingPair, + bool isBuy, + std::string baseCurrency, + std::string quoteCurrency, + PyObject *price, + PyObject *quantity, + PyObject *filledQuantity, + long creationTimestamp, + short int status, + std::string position); + ~LimitOrder(); + LimitOrder(const LimitOrder &other); + LimitOrder &operator=(const LimitOrder &other); + friend bool operator<(LimitOrder const &a, LimitOrder const &b); + + std::string getClientOrderID() const; + std::string getTradingPair() const; + bool getIsBuy() const; + std::string getBaseCurrency() const; + std::string getQuoteCurrency() const; + PyObject *getPrice() const; + PyObject *getQuantity() const; + PyObject *getFilledQuantity() const; + long getCreationTimestamp() const; + short int getStatus() const; + std::string getPosition() const; +}; + +#endif diff --git a/hummingbot/core/cpp/OrderBookEntry.cpp b/hummingbot/core/cpp/OrderBookEntry.cpp new file mode 100644 index 0000000..855f3b2 --- /dev/null +++ b/hummingbot/core/cpp/OrderBookEntry.cpp @@ -0,0 +1,92 @@ +#include "OrderBookEntry.h" +#include + +OrderBookEntry::OrderBookEntry() { + this->price = this->amount = 0; + this->updateId = 0; +} + +OrderBookEntry::OrderBookEntry(double price, double amount, int64_t updateId) { + this->price = price; + this->amount = amount; + this->updateId = updateId; +} + +OrderBookEntry::OrderBookEntry(const OrderBookEntry &other) { + this->price = other.price; + this->amount = other.amount; + this->updateId = other.updateId; +} + +OrderBookEntry &OrderBookEntry::operator=(const OrderBookEntry &other) { + this->price = other.price; + this->amount = other.amount; + this->updateId = other.updateId; + return *this; +} + +bool operator<(OrderBookEntry const &a, OrderBookEntry const &b) { + return a.price < b.price; +} + +void truncateOverlapEntries(std::set &bidBook, std::set &askBook, const int &dex) { + if (dex != 0) { + truncateOverlapEntriesDex(bidBook, askBook); + } else { + truncateOverlapEntriesCentralised(bidBook, askBook); + } +} + +void truncateOverlapEntriesDex(std::set &bidBook, std::set &askBook) { + std::set::reverse_iterator bidIterator = bidBook.rbegin(); + std::set::iterator askIterator = askBook.begin(); + while (bidIterator != bidBook.rend() && askIterator != askBook.end()) { + const OrderBookEntry& topBid = *bidIterator; + const OrderBookEntry& topAsk = *askIterator; + if (topBid.price >= topAsk.price) { + if (topBid.amount*topBid.price > topAsk.amount*topAsk.price) { + askBook.erase(askIterator++); + } else { + // There is no need to move the bid iterator, since its base + // always points to the end() iterator. + std::set::iterator eraseIterator = (std::next(bidIterator)).base(); + bidBook.erase(eraseIterator); + } + } else { + break; + } + } +} + +void truncateOverlapEntriesCentralised(std::set &bidBook, std::set &askBook) { + std::set::reverse_iterator bidIterator = bidBook.rbegin(); + std::set::iterator askIterator = askBook.begin(); + while (bidIterator != bidBook.rend() && askIterator != askBook.end()) { + const OrderBookEntry& topBid = *bidIterator; + const OrderBookEntry& topAsk = *askIterator; + if (topBid.price >= topAsk.price) { + if (topBid.updateId > topAsk.updateId) { + askBook.erase(askIterator++); + } else { + // There is no need to move the bid iterator, since its base + // always points to the end() iterator. + std::set::iterator eraseIterator = (std::next(bidIterator)).base(); + bidBook.erase(eraseIterator); + } + } else { + break; + } + } +} + +double OrderBookEntry::getPrice() const { + return this->price; +} + +double OrderBookEntry::getAmount() const { + return this->amount; +} + +int64_t OrderBookEntry::getUpdateId() const { + return this->updateId; +} diff --git a/hummingbot/core/cpp/OrderBookEntry.h b/hummingbot/core/cpp/OrderBookEntry.h new file mode 100644 index 0000000..258e074 --- /dev/null +++ b/hummingbot/core/cpp/OrderBookEntry.h @@ -0,0 +1,28 @@ +#ifndef _ORDER_BOOK_ENTRY_H +#define _ORDER_BOOK_ENTRY_H + +#include +#include +#include + +class OrderBookEntry { + double price; + double amount; + int64_t updateId; + + public: + OrderBookEntry(); + OrderBookEntry(double price, double amount, int64_t updateId); + OrderBookEntry(const OrderBookEntry &other); + OrderBookEntry &operator=(const OrderBookEntry &other); + friend bool operator<(OrderBookEntry const &a, OrderBookEntry const &b); + friend void truncateOverlapEntries(std::set &bidBook, std::set &askBook, const int &dex); + friend void truncateOverlapEntriesDex(std::set &bidBook, std::set &askBook); + friend void truncateOverlapEntriesCentralised(std::set &bidBook, std::set &askBook); + + double getPrice() const; + double getAmount() const; + int64_t getUpdateId() const; +}; + +#endif diff --git a/hummingbot/core/cpp/OrderExpirationEntry.cpp b/hummingbot/core/cpp/OrderExpirationEntry.cpp new file mode 100644 index 0000000..4853790 --- /dev/null +++ b/hummingbot/core/cpp/OrderExpirationEntry.cpp @@ -0,0 +1,59 @@ +#include "OrderExpirationEntry.h" +#include + +OrderExpirationEntry::OrderExpirationEntry() { + this->tradingPair = ""; + this->orderId = ""; + this->timestamp = 0; + this->expiration_timestamp = 0; +} + +OrderExpirationEntry::OrderExpirationEntry(std::string tradingPair, + std::string orderId, + double timestamp, + double expiration_timestamp) { + this->tradingPair = tradingPair; + this->orderId = orderId; + this->timestamp = timestamp; + this->expiration_timestamp = expiration_timestamp; +} + +OrderExpirationEntry::OrderExpirationEntry(const OrderExpirationEntry &other) { + this->tradingPair = other.tradingPair; + this->orderId = other.orderId; + this->timestamp = other.timestamp; + this->expiration_timestamp = other.expiration_timestamp; +} + +OrderExpirationEntry &OrderExpirationEntry::operator=(const OrderExpirationEntry &other) { + this->tradingPair = other.tradingPair; + this->orderId = other.orderId; + this->timestamp = other.timestamp; + this->expiration_timestamp = other.expiration_timestamp; + return *this; +} + +bool operator<(OrderExpirationEntry const &a, OrderExpirationEntry const &b) { + if(a.expiration_timestamp == b.expiration_timestamp){ + return a.orderId < b.orderId; + } + else{ + return a.expiration_timestamp < b.expiration_timestamp; + } +} + +std::string OrderExpirationEntry::getTradingPair() const { + return this->tradingPair; +} + +std::string OrderExpirationEntry::getClientOrderID() const { + return this->orderId; +} + +double OrderExpirationEntry::getTimestamp() const { + return this->timestamp; +} + +double OrderExpirationEntry::getExpirationTimestamp() const { + return this->expiration_timestamp; +} diff --git a/hummingbot/core/cpp/OrderExpirationEntry.h b/hummingbot/core/cpp/OrderExpirationEntry.h new file mode 100644 index 0000000..00426e5 --- /dev/null +++ b/hummingbot/core/cpp/OrderExpirationEntry.h @@ -0,0 +1,27 @@ +#ifndef _ORDER_EXPIRATION_ENTRY_H +#define _ORDER_EXPIRATION_ENTRY_H + +#include +#include +#include +#include + +class OrderExpirationEntry { + std::string tradingPair; + std::string orderId; + double timestamp; + double expiration_timestamp; + + public: + OrderExpirationEntry(); + OrderExpirationEntry(std::string tradingPair, std::string orderId, double timestamp, double expiration_timestamp); + OrderExpirationEntry(const OrderExpirationEntry &other); + OrderExpirationEntry &operator=(const OrderExpirationEntry &other); + friend bool operator<(OrderExpirationEntry const &a, OrderExpirationEntry const &b); + std::string getTradingPair() const; + std::string getClientOrderID() const; + double getTimestamp() const; + double getExpirationTimestamp() const; +}; + +#endif diff --git a/hummingbot/core/cpp/PyRef.cpp b/hummingbot/core/cpp/PyRef.cpp new file mode 100644 index 0000000..94c27aa --- /dev/null +++ b/hummingbot/core/cpp/PyRef.cpp @@ -0,0 +1,40 @@ +#include "PyRef.h" +#include + +PyRef::PyRef() { + this->obj = NULL; +} + +PyRef::PyRef(PyObject *obj) { + this->obj = obj; + Py_XINCREF(obj); +} + +PyRef::PyRef(const PyRef &other) { + this->obj = other.obj; + Py_XINCREF(this->obj); +} + +PyRef::~PyRef() { + Py_XDECREF(this->obj); +} + +PyRef &PyRef::operator=(const PyRef &other) { + this->obj = other.obj; + Py_XINCREF(this->obj); + return *this; +} + +bool PyRef::operator==(const PyRef &other) const { + return this->obj == other.obj; +} + +PyObject *PyRef::get() const { + return this->obj; +} + +namespace std { + size_t hash::operator()(const PyRef &x) const { + return PyObject_Hash(x.get()); + } +} \ No newline at end of file diff --git a/hummingbot/core/cpp/PyRef.h b/hummingbot/core/cpp/PyRef.h new file mode 100644 index 0000000..093df10 --- /dev/null +++ b/hummingbot/core/cpp/PyRef.h @@ -0,0 +1,30 @@ +#ifndef _PYREF_H +#define _PYREF_H + +#include +#include +#include + +class PyRef { + PyObject *obj; + + public: + PyRef(); + PyRef(PyObject *obj); + PyRef(const PyRef &other); + PyRef &operator=(const PyRef &other); + bool operator==(const PyRef &other) const; + ~PyRef(); + PyObject *get() const; +}; + + +namespace std { + template <> + struct hash + { + size_t operator()(const PyRef &x) const; + }; +} + +#endif \ No newline at end of file diff --git a/hummingbot/core/cpp/TestOrderBookEntry.cpp b/hummingbot/core/cpp/TestOrderBookEntry.cpp new file mode 100644 index 0000000..cf693f4 --- /dev/null +++ b/hummingbot/core/cpp/TestOrderBookEntry.cpp @@ -0,0 +1,80 @@ +// +// Created by Martin Kou on 2/14/20. +// + +#include +#include +#include +#include "OrderBookEntry.h" + +typedef std::set OrderBookSide; + +void testOverlappingOrderBooks(); + +int main(const int argc, const char **argv) { + testOverlappingOrderBooks(); + return 0; +} + +void printTopPrices(const OrderBookSide &bidsBook, const OrderBookSide &asksBook) { + double topBid = nan(""); + double topAsk = nan(""); + OrderBookSide::iterator asksIterator = asksBook.begin(); + OrderBookSide::reverse_iterator bidsIterator = bidsBook.rbegin(); + + if (asksIterator != asksBook.end()) { + topAsk = (*asksIterator).getPrice(); + } + if (bidsIterator != bidsBook.rend()) { + topBid = (*bidsIterator).getPrice(); + } + + printf("current top bid: %.2f, top ask: %.2f\n", topBid, topAsk); +} + +void testOverlappingOrderBooks() { + OrderBookSide bidsBook; + OrderBookSide asksBook; + OrderBookSide::iterator asksIterator = asksBook.begin(); + OrderBookSide::reverse_iterator bidsIterator = bidsBook.rbegin(); + + printf("*** testOverlappingOrderBooks(): Stage 1 ***\n"); + printf("Asks side iterator empty? %d\n", asksIterator == asksBook.end()); + printf("Bids side iterator empty? %d\n", bidsIterator == bidsBook.rend()); + printTopPrices(bidsBook, asksBook); + + printf("\n*** testOverlappingOrderBooks(): Stage 2 ***\n"); + bidsBook.insert(OrderBookEntry(100.0, 1.0, 1)); + bidsBook.insert(OrderBookEntry(99.9, 2.0, 1)); + bidsBook.insert(OrderBookEntry(99.8, 4.0, 1)); + truncateOverlapEntriesCentralised(bidsBook, asksBook); + printf("Asks side iterator empty? %d\n", asksIterator == asksBook.end()); + printf("Bids side iterator empty? %d\n", bidsIterator == bidsBook.rend()); + printTopPrices(bidsBook, asksBook); + + printf("\n*** testOverlappingOrderBooks(): Stage 3 ***\n"); + bidsBook.insert(OrderBookEntry(100.0, 4.0, 2)); + bidsBook.insert(OrderBookEntry(100.1, 2.0, 2)); + bidsBook.insert(OrderBookEntry(100.9, 1.5, 2)); + bidsBook.insert(OrderBookEntry(101.0, 0.1, 2)); + asksBook.insert(OrderBookEntry(105.0, 100, 2)); + asksBook.insert(OrderBookEntry(104.0, 50, 2)); + asksBook.insert(OrderBookEntry(103.0, 20, 3)); + asksBook.insert(OrderBookEntry(102.0, 10, 3)); + asksBook.insert(OrderBookEntry(100.91, 1, 3)); + truncateOverlapEntriesCentralised(bidsBook, asksBook); + asksIterator = asksBook.begin(); + bidsIterator = bidsBook.rbegin(); + printf("Asks side iterator empty? %d\n", asksIterator == asksBook.end()); + printf("Bids side iterator empty? %d\n", bidsIterator == bidsBook.rend()); + printTopPrices(bidsBook, asksBook); + + printf("\n*** testOverlappingOrderBooks(): Stage 4 ***\n"); + bidsBook.insert(OrderBookEntry(100.91, 3.0, 3)); + asksBook.insert(OrderBookEntry(100.89, 1.0, 4)); + asksBook.insert(OrderBookEntry(100.88, 0.8, 4)); + asksBook.insert(OrderBookEntry(100.86, 0.7, 4)); + bidsBook.insert(OrderBookEntry(100.87, 1.1, 5)); + truncateOverlapEntriesCentralised(bidsBook, asksBook); + printTopPrices(bidsBook, asksBook); +} diff --git a/hummingbot/core/cpp/Utils.cpp b/hummingbot/core/cpp/Utils.cpp new file mode 100644 index 0000000..5ac8949 --- /dev/null +++ b/hummingbot/core/cpp/Utils.cpp @@ -0,0 +1 @@ +#include "Utils.h" diff --git a/hummingbot/core/cpp/Utils.h b/hummingbot/core/cpp/Utils.h new file mode 100644 index 0000000..3038c53 --- /dev/null +++ b/hummingbot/core/cpp/Utils.h @@ -0,0 +1,13 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include + +template +const T getIteratorFromReverseIterator(const std::reverse_iterator rit) { + T iterator_base = rit.base(); + iterator_base--; + return iterator_base; +} + +#endif diff --git a/hummingbot/core/cpp/compile.sh b/hummingbot/core/cpp/compile.sh new file mode 100755 index 0000000..0c387cf --- /dev/null +++ b/hummingbot/core/cpp/compile.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +g++ -c -g TestOrderBookEntry.cpp +g++ -c -g OrderBookEntry.cpp +g++ TestOrderBookEntry.o OrderBookEntry.o -o TestOrderBookEntry diff --git a/hummingbot/core/data_type/LimitOrder.pxd b/hummingbot/core/data_type/LimitOrder.pxd new file mode 100644 index 0000000..07c4988 --- /dev/null +++ b/hummingbot/core/data_type/LimitOrder.pxd @@ -0,0 +1,41 @@ +# distutils: language=c++ + +from libcpp cimport bool as cppbool +from libcpp.string cimport string + +cdef extern from "../cpp/LimitOrder.h": + ctypedef struct PyObject + + cdef cppclass LimitOrder: + LimitOrder() + LimitOrder(string clientOrderID, + string tradingPair, + cppbool isBuy, + string baseCurrency, + string quoteCurrency, + PyObject *price, + PyObject *quantity) + LimitOrder(string clientOrderID, + string tradingPair, + cppbool isBuy, + string baseCurrency, + string quoteCurrency, + PyObject *price, + PyObject *quantity, + PyObject *filledQuantity, + long long creationTimestamp, + short int status, + string position) + LimitOrder(const LimitOrder &other) + LimitOrder &operator=(const LimitOrder &other) + string getClientOrderID() + string getTradingPair() + cppbool getIsBuy() + string getBaseCurrency() + string getQuoteCurrency() + PyObject *getPrice() + PyObject *getQuantity() + PyObject *getFilledQuantity() + long long getCreationTimestamp() + short int getStatus() + string getPosition() diff --git a/hummingbot/core/data_type/OrderBookEntry.pxd b/hummingbot/core/data_type/OrderBookEntry.pxd new file mode 100644 index 0000000..de4969b --- /dev/null +++ b/hummingbot/core/data_type/OrderBookEntry.pxd @@ -0,0 +1,16 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from libcpp.set cimport set + +cdef extern from "../cpp/OrderBookEntry.h": + cdef cppclass OrderBookEntry: + OrderBookEntry() + OrderBookEntry(double price, double amount, int64_t updateId) + OrderBookEntry(const OrderBookEntry &other) + OrderBookEntry &operator=(const OrderBookEntry &other) + double getPrice() const + double getAmount() const + int64_t getUpdateId() const + + void truncateOverlapEntries(set[OrderBookEntry] &bid_book, set[OrderBookEntry] &ask_book, const bint &dex) diff --git a/hummingbot/core/data_type/OrderExpirationEntry.pxd b/hummingbot/core/data_type/OrderExpirationEntry.pxd new file mode 100644 index 0000000..ba01487 --- /dev/null +++ b/hummingbot/core/data_type/OrderExpirationEntry.pxd @@ -0,0 +1,18 @@ +# distutils: language=c++ + +from libcpp.string cimport string + +cdef extern from "../cpp/OrderExpirationEntry.h": + cdef cppclass OrderExpirationEntry: + OrderExpirationEntry() + OrderExpirationEntry(string trading_pair, + string order_id, + double timestamp, + double expiration) + OrderExpirationEntry(const OrderExpirationEntry &other) + OrderExpirationEntry &operator=(const OrderExpirationEntry &other) + string getClientOrderID() + string getTradingPair() + double getTimestamp() + double getExpiration() + double getExpirationTimestamp() diff --git a/hummingbot/core/data_type/__init__.py b/hummingbot/core/data_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/data_type/cancellation_result.py b/hummingbot/core/data_type/cancellation_result.py new file mode 100644 index 0000000..ef1366a --- /dev/null +++ b/hummingbot/core/data_type/cancellation_result.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from typing import NamedTuple + + +class CancellationResult(NamedTuple): + order_id: str + success: bool diff --git a/hummingbot/core/data_type/common.py b/hummingbot/core/data_type/common.py new file mode 100644 index 0000000..6a73d6e --- /dev/null +++ b/hummingbot/core/data_type/common.py @@ -0,0 +1,66 @@ +from decimal import Decimal +from enum import Enum +from typing import NamedTuple + + +class OrderType(Enum): + MARKET = 1 + LIMIT = 2 + LIMIT_MAKER = 3 + + def is_limit_type(self): + return self in (OrderType.LIMIT, OrderType.LIMIT_MAKER) + + +class OpenOrder(NamedTuple): + client_order_id: str + trading_pair: str + price: Decimal + amount: Decimal + executed_amount: Decimal + status: str + order_type: OrderType + is_buy: bool + time: int + exchange_order_id: str + + +class PositionAction(Enum): + OPEN = "OPEN" + CLOSE = "CLOSE" + NIL = "NIL" + + +# For Derivatives Exchanges +class PositionSide(Enum): + LONG = "LONG" + SHORT = "SHORT" + BOTH = "BOTH" + + +# For Derivatives Exchanges +class PositionMode(Enum): + HEDGE = True + ONEWAY = False + + +class PriceType(Enum): + MidPrice = 1 + BestBid = 2 + BestAsk = 3 + LastTrade = 4 + LastOwnTrade = 5 + InventoryCost = 6 + Custom = 7 + + +class TradeType(Enum): + BUY = 1 + SELL = 2 + RANGE = 3 + + +class LPType(Enum): + ADD = 1 + REMOVE = 2 + COLLECT = 3 diff --git a/hummingbot/core/data_type/composite_order_book.pxd b/hummingbot/core/data_type/composite_order_book.pxd new file mode 100644 index 0000000..6f68e35 --- /dev/null +++ b/hummingbot/core/data_type/composite_order_book.pxd @@ -0,0 +1,8 @@ +# distutils: language=c++ +from hummingbot.core.data_type.order_book cimport OrderBook + +cdef class CompositeOrderBook(OrderBook): + cdef: + OrderBook _traded_order_book + + cdef double c_get_price(self, bint is_buy) except? -1 diff --git a/hummingbot/core/data_type/composite_order_book.pyx b/hummingbot/core/data_type/composite_order_book.pyx new file mode 100644 index 0000000..8dded1e --- /dev/null +++ b/hummingbot/core/data_type/composite_order_book.pyx @@ -0,0 +1,194 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp + +from typing import Iterator + +from cython.operator cimport address as ref, dereference as deref, postincrement as inc +from hummingbot.core.data_type.OrderBookEntry cimport OrderBookEntry +from libcpp.set cimport set +from libcpp.vector cimport vector + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_row import OrderBookRow + +cdef class CompositeOrderBook(OrderBook): + """ + Record orders that are bought during back testing and used to simulate order book consumption without modifying + the actual order book. + Override the order book bid_entries, ask_entries methods to return the composite order book entries + """ + def __init__(self, order_book: OrderBook = None): + super().__init__() + self._traded_order_book = OrderBook() + + @property + def traded_order_book(self) -> OrderBook: + return self._traded_order_book + + def clear_traded_order_book(self): + self._traded_order_book._bid_book.clear() + self._traded_order_book._ask_book.clear() + + def record_filled_order(self, order_fill_event): + cdef: + vector[OrderBookEntry] cpp_bids + vector[OrderBookEntry] cpp_asks + set[OrderBookEntry].reverse_iterator bid_order_it = self._traded_order_book._bid_book.rbegin() + set[OrderBookEntry].iterator ask_order_it = self._traded_order_book._ask_book.begin() + OrderBookEntry entry + + price = order_fill_event.price + amount = float(order_fill_event.amount) + timestamp = order_fill_event.timestamp + + if order_fill_event.trade_type is TradeType.BUY: + while ask_order_it != self._traded_order_book._ask_book.end(): + entry = deref(ask_order_it) + # price is in the order book, sum the amount + if entry.getPrice() == price: + amount += entry.getAmount() + break + # price is outside of the ask price range, break and insert the new filled ask order into the ask book + elif entry.getPrice() > price: + break + # price is further up the ask price range, continue searching + elif entry.getPrice() < price: + inc(ask_order_it) + cpp_asks.push_back(OrderBookEntry(price, amount, timestamp)) + + elif order_fill_event.trade_type is TradeType.SELL: + while bid_order_it != self._traded_order_book._bid_book.rend(): + entry = deref(bid_order_it) + if entry.getPrice() == price: + amount += entry.getAmount() + break + # price is further down the bid price range, continue searching + elif entry.getPrice() > price: + inc(bid_order_it) + # price is outside of the bid price range, break and insert the new filled order into the bid book + elif entry.getPrice() < price: + break + cpp_bids.push_back(OrderBookEntry(price, amount, timestamp)) + + self._traded_order_book.c_apply_diffs(cpp_bids, cpp_asks, timestamp) + + def original_bid_entries(self) -> Iterator[OrderBookRow]: + return super().bid_entries() + + def original_ask_entries(self) -> Iterator[OrderBookRow]: + return super().ask_entries() + + def bid_entries(self) -> Iterator[OrderBookRow]: + cdef: + set[OrderBookEntry].reverse_iterator order_it = self._bid_book.rbegin() + set[OrderBookEntry].reverse_iterator traded_order_it = self._traded_order_book._bid_book.rbegin() + OrderBookEntry traded_order_entry + OrderBookEntry original_order_entry + vector[OrderBookEntry] cpp_asks_changes + vector[OrderBookEntry] cpp_bids_changes + + while order_it != self._bid_book.rend(): + original_order_entry = deref(order_it) + original_order_price = original_order_entry.getPrice() + original_order_amount = original_order_entry.getAmount() + original_order_update_id = original_order_entry.getUpdateId() + + while traded_order_it != self._traded_order_book._bid_book.rend(): + traded_order_entry = deref(traded_order_it) + traded_order_price = traded_order_entry.getPrice() + traded_order_amount = traded_order_entry.getAmount() + traded_order_update_id = traded_order_entry.getUpdateId() + + # Found matching price for the recorded filled order, return composite order book row + if traded_order_price == original_order_price: + composite_amount = original_order_amount - traded_order_amount + if composite_amount > 0: + yield OrderBookRow(original_order_price, composite_amount, original_order_update_id) + else: + cpp_bids_changes.push_back(OrderBookEntry(original_order_price, + min(original_order_amount, traded_order_amount), + traded_order_update_id)) + inc(traded_order_it) + # continue to next original order book row + break + # Recorded filled order price is outside of the bid price range + elif traded_order_price > original_order_price: + # Remove the recorded entry and increment the pointer + cpp_bids_changes.push_back(OrderBookEntry(traded_order_price, 0, traded_order_update_id)) + inc(traded_order_it) + # Recorded filled order price is within lower end of the bid price range, yield original bid entry + elif traded_order_price < original_order_price: + yield OrderBookRow(original_order_price, original_order_amount, original_order_update_id) + break + else: + yield OrderBookRow(original_order_price, original_order_amount, original_order_update_id) + + inc(order_it) + + self._traded_order_book.c_apply_diffs(cpp_bids_changes, cpp_asks_changes, self._last_diff_uid) + + def ask_entries(self) -> Iterator[OrderBookRow]: + cdef: + set[OrderBookEntry].iterator order_it = self._ask_book.begin() + set[OrderBookEntry].iterator traded_order_it = self._traded_order_book._ask_book.begin() + OrderBookEntry original_order_entry + OrderBookEntry traded_order_entry + vector[OrderBookEntry] cpp_asks_changes + vector[OrderBookEntry] cpp_bids_changes + + while order_it != self._ask_book.end(): + original_order_entry = deref(order_it) + original_order_price = original_order_entry.getPrice() + original_order_amount = original_order_entry.getAmount() + original_order_update_id = original_order_entry.getUpdateId() + + while traded_order_it != self._traded_order_book._ask_book.end(): + traded_order_entry = deref(traded_order_it) + traded_order_price = traded_order_entry.getPrice() + traded_order_amount = traded_order_entry.getAmount() + traded_order_update_id = traded_order_entry.getUpdateId() + + if traded_order_price == original_order_price: + composite_amount = original_order_amount - traded_order_amount + if composite_amount > 0: + yield OrderBookRow(original_order_price, composite_amount, original_order_update_id) + else: + cpp_asks_changes.push_back(OrderBookEntry(original_order_price, + min(original_order_amount, traded_order_amount), + traded_order_update_id)) + inc(traded_order_it) + # continue to next original order book row + break + # Recorded filled order price is within upper end of the ask price range, yield original ask entry + elif traded_order_price > original_order_price: + yield OrderBookRow(original_order_price, original_order_amount, original_order_update_id) + break + # Recorded filled order price is outside of the ask price range, remove the recorded ask order + elif traded_order_price < original_order_price: + cpp_asks_changes.push_back(OrderBookEntry(traded_order_price, 0, traded_order_update_id)) + inc(traded_order_it) + + else: + yield OrderBookRow(original_order_price, original_order_amount, original_order_update_id) + + inc(order_it) + + self._traded_order_book.c_apply_diffs(cpp_bids_changes, cpp_asks_changes, self._last_diff_uid) + + cdef double c_get_price(self, bint is_buy) except? -1: + cdef: + set[OrderBookEntry] *book = ref(self._ask_book) if is_buy else ref(self._bid_book) + if deref(book).size() < 1: + raise EnvironmentError("Order book is empty - no price quote is possible.") + + ask_it = self.ask_entries() + bid_it = self.bid_entries() + try: + if is_buy: + best_ask = next(ask_it) + return best_ask.price + else: + best_bid = next(bid_it) + return best_bid.price + except Exception: + raise diff --git a/hummingbot/core/data_type/funding_info.py b/hummingbot/core/data_type/funding_info.py new file mode 100644 index 0000000..128caf1 --- /dev/null +++ b/hummingbot/core/data_type/funding_info.py @@ -0,0 +1,74 @@ +from dataclasses import asdict, dataclass +from decimal import Decimal +from typing import Optional + + +class FundingInfo: + """ + Data object that details the funding information of a perpetual market. + """ + + def __init__(self, + trading_pair: str, + index_price: Decimal, + mark_price: Decimal, + next_funding_utc_timestamp: int, + rate: Decimal, + ): + self._trading_pair = trading_pair + self._index_price = index_price + self._mark_price = mark_price + self._next_funding_utc_timestamp = next_funding_utc_timestamp + self._rate = rate + + @property + def trading_pair(self) -> str: + return self._trading_pair + + @property + def index_price(self) -> Decimal: + return self._index_price + + @index_price.setter + def index_price(self, index_price): + self._index_price = index_price + + @property + def mark_price(self) -> Decimal: + return self._mark_price + + @mark_price.setter + def mark_price(self, mark_price): + self._mark_price = mark_price + + @property + def next_funding_utc_timestamp(self) -> int: + return self._next_funding_utc_timestamp + + @next_funding_utc_timestamp.setter + def next_funding_utc_timestamp(self, next_funding_utc_timestamp): + self._next_funding_utc_timestamp = next_funding_utc_timestamp + + @property + def rate(self) -> Decimal: + return self._rate + + @rate.setter + def rate(self, rate): + self._rate = rate + + def update(self, info_update: "FundingInfoUpdate"): + update_dict = asdict(info_update) + update_dict.pop("trading_pair") + for key, value in update_dict.items(): + if value is not None: + setattr(self, key, value) + + +@dataclass +class FundingInfoUpdate: + trading_pair: str + index_price: Optional[Decimal] = None + mark_price: Optional[Decimal] = None + next_funding_utc_timestamp: Optional[int] = None + rate: Optional[Decimal] = None diff --git a/hummingbot/core/data_type/in_flight_order.py b/hummingbot/core/data_type/in_flight_order.py new file mode 100644 index 0000000..7362e17 --- /dev/null +++ b/hummingbot/core/data_type/in_flight_order.py @@ -0,0 +1,388 @@ +import asyncio +import copy +import math +import typing +from decimal import Decimal +from enum import Enum +from typing import Any, Dict, NamedTuple, Optional, Tuple + +from async_timeout import timeout + +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import TradeFeeBase + +if typing.TYPE_CHECKING: # avoid circular import problems + from hummingbot.connector.exchange_base import ExchangeBase + +s_decimal_0 = Decimal("0") + +GET_EX_ORDER_ID_TIMEOUT = 10 # seconds + + +class OrderState(Enum): + PENDING_CREATE = 0 + OPEN = 1 + PENDING_CANCEL = 2 + CANCELED = 3 + PARTIALLY_FILLED = 4 + FILLED = 5 + FAILED = 6 + PENDING_APPROVAL = 7 + APPROVED = 8 + CREATED = 9 + COMPLETED = 10 + + +class OrderUpdate(NamedTuple): + trading_pair: str + update_timestamp: float # seconds + new_state: OrderState + client_order_id: Optional[str] = None + exchange_order_id: Optional[str] = None + misc_updates: Optional[Dict[str, Any]] = None + + +class TradeUpdate(NamedTuple): + trade_id: str + client_order_id: str + exchange_order_id: str + trading_pair: str + fill_timestamp: float # seconds + fill_price: Decimal + fill_base_amount: Decimal + fill_quote_amount: Decimal + fee: TradeFeeBase + is_taker: bool = True # CEXs deliver trade events from the taker's perspective + + @property + def fee_asset(self): + return self.fee.fee_asset + + @classmethod + def from_json(cls, data: Dict[str, Any]): + instance = TradeUpdate( + trade_id=data["trade_id"], + client_order_id=data["client_order_id"], + exchange_order_id=data["exchange_order_id"], + trading_pair=data["trading_pair"], + fill_timestamp=data["fill_timestamp"], + fill_price=Decimal(data["fill_price"]), + fill_base_amount=Decimal(data["fill_base_amount"]), + fill_quote_amount=Decimal(data["fill_quote_amount"]), + fee=TradeFeeBase.from_json(data["fee"]), + ) + + return instance + + def to_json(self) -> Dict[str, Any]: + json_dict = self._asdict() + json_dict.update({ + "fill_price": str(self.fill_price), + "fill_base_amount": str(self.fill_base_amount), + "fill_quote_amount": str(self.fill_quote_amount), + "fee": self.fee.to_json(), + }) + return json_dict + + +class InFlightOrder: + def __init__( + self, + client_order_id: str, + trading_pair: str, + order_type: OrderType, + trade_type: TradeType, + amount: Decimal, + creation_timestamp: float, + price: Optional[Decimal] = None, + exchange_order_id: Optional[str] = None, + initial_state: OrderState = OrderState.PENDING_CREATE, + leverage: int = 1, + position: PositionAction = PositionAction.NIL, + ) -> None: + self.client_order_id = client_order_id + self.creation_timestamp = creation_timestamp + self.trading_pair = trading_pair + self.order_type = order_type + self.trade_type = trade_type + self.price = price + self.amount = amount + self.exchange_order_id = exchange_order_id + self.current_state = initial_state + self.leverage = leverage + self.position = position + + self.executed_amount_base = s_decimal_0 + self.executed_amount_quote = s_decimal_0 + + self.last_update_timestamp: float = creation_timestamp + + self.order_fills: Dict[str, TradeUpdate] = {} # Dict[trade_id, TradeUpdate] + + self.exchange_order_id_update_event = asyncio.Event() + if self.exchange_order_id: + self.exchange_order_id_update_event.set() + self.completely_filled_event = asyncio.Event() + self.processed_by_exchange_event = asyncio.Event() + self.check_processed_by_exchange_condition() + + @property + def attributes(self) -> Tuple[Any]: + return copy.deepcopy( + ( + self.client_order_id, + self.trading_pair, + self.order_type, + self.trade_type, + self.price, + self.amount, + self.exchange_order_id, + self.current_state, + self.leverage, + self.position, + self.executed_amount_base, + self.executed_amount_quote, + self.creation_timestamp, + self.last_update_timestamp, + ) + ) + + def __eq__(self, other: object) -> bool: + return type(self) is type(other) and self.attributes == other.attributes + + @property + def base_asset(self): + return self.trading_pair.split("-")[0] + + @property + def quote_asset(self): + return self.trading_pair.split("-")[1] + + @property + def is_pending_create(self) -> bool: + return self.current_state == OrderState.PENDING_CREATE + + @property + def is_pending_cancel_confirmation(self) -> bool: + return self.current_state == OrderState.PENDING_CANCEL + + @property + def is_open(self) -> bool: + return self.current_state in { + OrderState.PENDING_CREATE, + OrderState.OPEN, + OrderState.PARTIALLY_FILLED, + OrderState.PENDING_CANCEL} + + @property + def is_done(self) -> bool: + return ( + self.current_state in {OrderState.CANCELED, OrderState.FILLED, OrderState.FAILED} + or math.isclose(self.executed_amount_base, self.amount) + or self.executed_amount_base >= self.amount + ) + + @property + def is_filled(self) -> bool: + return ( + self.current_state == OrderState.FILLED + or (self.amount != s_decimal_0 + and (math.isclose(self.executed_amount_base, self.amount) + or self.executed_amount_base >= self.amount) + ) + ) + + @property + def is_failure(self) -> bool: + return self.current_state == OrderState.FAILED + + @property + def is_cancelled(self) -> bool: + return self.current_state == OrderState.CANCELED + + @property + def average_executed_price(self) -> Optional[Decimal]: + executed_value: Decimal = s_decimal_0 + total_base_amount: Decimal = s_decimal_0 + for order_fill in self.order_fills.values(): + executed_value += order_fill.fill_price * order_fill.fill_base_amount + total_base_amount += order_fill.fill_base_amount + if executed_value == s_decimal_0 or total_base_amount == s_decimal_0: + return None + return executed_value / total_base_amount + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> "InFlightOrder": + """ + Initialize an InFlightOrder using a JSON object + :param data: JSON data + :return: Formatted InFlightOrder + """ + order = InFlightOrder( + client_order_id=data["client_order_id"], + trading_pair=data["trading_pair"], + order_type=getattr(OrderType, data["order_type"]), + trade_type=getattr(TradeType, data["trade_type"]), + amount=Decimal(data["amount"]), + price=Decimal(data["price"]), + exchange_order_id=data["exchange_order_id"], + initial_state=OrderState(int(data["last_state"])), + leverage=int(data["leverage"]), + position=PositionAction(data["position"]), + creation_timestamp=data.get("creation_timestamp", -1) + ) + order.executed_amount_base = Decimal(data["executed_amount_base"]) + order.executed_amount_quote = Decimal(data["executed_amount_quote"]) + order.order_fills.update({key: TradeUpdate.from_json(value) + for key, value + in data.get("order_fills", {}).items()}) + order.last_update_timestamp = data.get("last_update_timestamp", order.creation_timestamp) + + order.check_filled_condition() + order.check_processed_by_exchange_condition() + + return order + + def to_json(self) -> Dict[str, Any]: + """ + Returns this InFlightOrder as a JSON object. + :return: JSON object + """ + return { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": self.order_type.name, + "trade_type": self.trade_type.name, + "price": str(self.price), + "amount": str(self.amount), + "executed_amount_base": str(self.executed_amount_base), + "executed_amount_quote": str(self.executed_amount_quote), + "last_state": str(self.current_state.value), + "leverage": str(self.leverage), + "position": self.position.value, + "creation_timestamp": self.creation_timestamp, + "last_update_timestamp": self.last_update_timestamp, + "order_fills": {key: fill.to_json() for key, fill in self.order_fills.items()} + } + + def to_limit_order(self) -> LimitOrder: + """ + Returns this InFlightOrder as a LimitOrder object. + :return: LimitOrder object. + """ + return LimitOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + is_buy=self.trade_type is TradeType.BUY, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=self.price, + quantity=self.amount, + filled_quantity=self.executed_amount_base, + creation_timestamp=int(self.creation_timestamp * 1e6) + ) + + def update_exchange_order_id(self, exchange_order_id: str): + self.exchange_order_id = exchange_order_id + self.exchange_order_id_update_event.set() + + async def get_exchange_order_id(self): + if self.exchange_order_id is None: + async with timeout(GET_EX_ORDER_ID_TIMEOUT): + await self.exchange_order_id_update_event.wait() + return self.exchange_order_id + + def cumulative_fee_paid(self, token: str, exchange: Optional['ExchangeBase'] = None) -> Decimal: + """ + Returns the total amount of fee paid for each traid update, expressed in the specified token + :param token: The token all partial fills' fees should be transformed to before summing them + :param exchange: The exchange being used. If specified the logic will try to use the order book to get the rate + :return: the cumulative fee paid for all partial fills in the specified token + """ + total_fee_in_token = Decimal("0") + for trade_update in self.order_fills.values(): + total_fee_in_token += trade_update.fee.fee_amount_in_token( + trading_pair=trade_update.trading_pair, + price=trade_update.fill_price, + order_amount=trade_update.fill_base_amount, + token=token, + exchange=exchange + ) + + return total_fee_in_token + + def update_with_order_update(self, order_update: OrderUpdate) -> bool: + """ + Updates the in flight order with an order update (from REST API or WS API) + return: True if the order gets updated otherwise False + """ + if (order_update.client_order_id != self.client_order_id + and order_update.exchange_order_id != self.exchange_order_id): + return False + + prev_data = (self.exchange_order_id, self.current_state) + + if self.exchange_order_id is None and order_update.exchange_order_id is not None: + self.update_exchange_order_id(order_update.exchange_order_id) + + self.current_state = order_update.new_state + self.check_processed_by_exchange_condition() + + updated: bool = prev_data != (self.exchange_order_id, self.current_state) + + if updated: + self.last_update_timestamp = order_update.update_timestamp + + return updated + + def update_with_trade_update(self, trade_update: TradeUpdate) -> bool: + """ + Updates the in flight order with a trade update (from REST API or WS API) + :return: True if the order gets updated otherwise False + """ + trade_id: str = trade_update.trade_id + + if (trade_id in self.order_fills + or (self.client_order_id != trade_update.client_order_id + and self.exchange_order_id != trade_update.exchange_order_id)): + return False + + self.order_fills[trade_id] = trade_update + + self.executed_amount_base += trade_update.fill_base_amount + self.executed_amount_quote += trade_update.fill_quote_amount + + self.last_update_timestamp = trade_update.fill_timestamp + self.check_filled_condition() + + return True + + def check_filled_condition(self): + if (abs(self.amount) - self.executed_amount_base).quantize(Decimal('1e-8')) <= 0: + self.completely_filled_event.set() + + async def wait_until_completely_filled(self): + await self.completely_filled_event.wait() + + def check_processed_by_exchange_condition(self): + if self.current_state.value > OrderState.PENDING_CREATE.value: + self.processed_by_exchange_event.set() + + async def wait_until_processed_by_exchange(self): + await self.processed_by_exchange_event.wait() + + def build_order_created_message(self) -> str: + return ( + f"Created {self.order_type.name.upper()} {self.trade_type.name.upper()} order " + f"{self.client_order_id} for {self.amount} {self.trading_pair}." + ) + + +class PerpetualDerivativeInFlightOrder(InFlightOrder): + def build_order_created_message(self) -> str: + return ( + f"Created {self.order_type.name.upper()} {self.trade_type.name.upper()} order " + f"{self.client_order_id} for {self.amount} to {self.position.name.upper()} a {self.trading_pair} position." + ) diff --git a/hummingbot/core/data_type/limit_order.pxd b/hummingbot/core/data_type/limit_order.pxd new file mode 100644 index 0000000..cfb9ed1 --- /dev/null +++ b/hummingbot/core/data_type/limit_order.pxd @@ -0,0 +1,13 @@ +# distutils: language=c++ + +from .LimitOrder cimport LimitOrder as CPPLimitOrder + + +cdef class LimitOrder: + cdef: + CPPLimitOrder _cpp_limit_order + cdef long long c_age(self) + cdef long long c_age_til(self, long long start_timestamp) + + +cdef LimitOrder c_create_limit_order_from_cpp_limit_order(const CPPLimitOrder cpp_limit_order) diff --git a/hummingbot/core/data_type/limit_order.pyx b/hummingbot/core/data_type/limit_order.pyx new file mode 100644 index 0000000..ecda240 --- /dev/null +++ b/hummingbot/core/data_type/limit_order.pyx @@ -0,0 +1,203 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/LimitOrder.cpp +import time +from decimal import Decimal +from typing import List + +import pandas as pd +from cpython cimport PyObject +from libcpp.string cimport string + +from hummingbot.core.data_type.common import PositionAction, OrderType +from hummingbot.core.event.events import LimitOrderStatus + +cdef class LimitOrder: + """ + A Python wrapper class on C++ LimitOrder. This data class is used to store order information and it is passed around + between connectors and strategies. It is also used in HummingSim for back testing as well. + """ + @classmethod + def to_pandas(cls, limit_orders: List[LimitOrder], mid_price: float = 0.0, hanging_ids: List[str] = None, + end_time_order_age: int = 0) \ + -> pd.DataFrame: + """ + Creates a dataframe for displaying current active orders + :param limit_orders: A list of current active LimitOrder from a single market + :param mid_price: The mid price (between best bid and best ask) of the market + :param hanging_ids: A list of hanging order ids if applicable + :param end_time_order_age: The end time for order age calculation, if unspecified the current time is used. + :return: A pandas data frame object + """ + cdef: + list buys = [o for o in limit_orders if o.is_buy] + list sells = [o for o in limit_orders if not o.is_buy] + buys.sort(key=lambda x: x.price, reverse=True) + sells.sort(key=lambda x: x.price, reverse=True) + cdef: + list orders = [] + list columns = ["Order ID", "Type", "Price", "Spread", "Amount", "Age", "Hang"] + list data = [] + str order_id_txt, type_txt, spread_txt, age_txt, hang_txt + double price, quantity + long long age_seconds + long long now_timestamp = int(time.time() * 1e6) if end_time_order_age == 0 else end_time_order_age + sells.extend(buys) + for order in sells: + order_id_txt = order.client_order_id if len(order.client_order_id) <= 7 else f"...{order.client_order_id[-4:]}" + type_txt = "buy" if order.is_buy else "sell" + price = float(order.price) + spread_txt = f"{(0 if mid_price == 0 else abs(float(order.price) - mid_price) / mid_price):.2%}" + quantity = float(order.quantity) + age_txt = "n/a" + age_seconds = order.age_til(now_timestamp) + if age_seconds >= 0: + age_txt = pd.Timestamp(age_seconds, unit='s', tz='UTC').strftime('%H:%M:%S') + hang_txt = "n/a" if hanging_ids is None else ("yes" if order.client_order_id in hanging_ids else "no") + data.append([order_id_txt, type_txt, price, spread_txt, quantity, age_txt, hang_txt]) + return pd.DataFrame(data=data, columns=columns) + + def __init__(self, + client_order_id: str, + trading_pair: str, + is_buy: bool, + base_currency: str, + quote_currency: str, + price: Decimal, + quantity: Decimal, + filled_quantity: Decimal = Decimal("NaN"), + creation_timestamp: int = 0, + status: LimitOrderStatus = LimitOrderStatus.UNKNOWN, + position: PositionAction = PositionAction.NIL): + cdef: + string cpp_client_order_id = client_order_id.encode("utf8") + string cpp_trading_pair = trading_pair.encode("utf8") + string cpp_base_currency = base_currency.encode("utf8") + string cpp_quote_currency = quote_currency.encode("utf8") + string cpp_position = position.value.encode("utf8") + self._cpp_limit_order = CPPLimitOrder(cpp_client_order_id, + cpp_trading_pair, + is_buy, + cpp_base_currency, + cpp_quote_currency, + price, + quantity, + filled_quantity, + creation_timestamp, + status.value, + cpp_position) + + @property + def client_order_id(self) -> str: + cdef: + string cpp_client_order_id = self._cpp_limit_order.getClientOrderID() + str retval = cpp_client_order_id.decode("utf8") + return retval + + @property + def trading_pair(self) -> str: + cdef: + string cpp_trading_pair = self._cpp_limit_order.getTradingPair() + str retval = cpp_trading_pair.decode("utf8") + return retval + + @property + def is_buy(self) -> bool: + return self._cpp_limit_order.getIsBuy() + + @property + def base_currency(self) -> str: + cdef: + string cpp_base_currency = self._cpp_limit_order.getBaseCurrency() + str retval = cpp_base_currency.decode("utf8") + return retval + + @property + def quote_currency(self) -> str: + cdef: + string cpp_quote_currency = self._cpp_limit_order.getQuoteCurrency() + str retval = cpp_quote_currency.decode("utf8") + return retval + + @property + def price(self) -> Decimal: + return (self._cpp_limit_order.getPrice()) + + @property + def quantity(self) -> Decimal: + return (self._cpp_limit_order.getQuantity()) + + @property + def filled_quantity(self) -> Decimal: + return (self._cpp_limit_order.getFilledQuantity()) + + @property + def creation_timestamp(self) -> int: + return self._cpp_limit_order.getCreationTimestamp() + + @property + def status(self) -> LimitOrderStatus: + return LimitOrderStatus(self._cpp_limit_order.getStatus()) + + @property + def position(self) -> PositionAction: + cdef: + string cpp_position = self._cpp_limit_order.getPosition() + str retval = cpp_position.decode("utf8") + return PositionAction(retval) + + cdef long long c_age_til(self, long long end_timestamp): + """ + Calculates and returns age of the order since it was created til end_timestamp in seconds + :param end_timestamp: The end timestamp + :return: The age of the order in seconds + """ + cdef long long start_timestamp = 0 + if self.creation_timestamp > 0: + start_timestamp = self.creation_timestamp + elif len(self.client_order_id) > 16 and self.client_order_id[-16:].isnumeric(): + start_timestamp = int(self.client_order_id[-16:]) + if 0 < start_timestamp < end_timestamp: + return int(end_timestamp - start_timestamp) / 1e6 + else: + return -1 + + cdef long long c_age(self): + """ + Calculates and returns age of the order since it was created til now. + """ + return self.c_age_til(int(time.time() * 1e6)) + + def age(self) -> int: + return self.c_age() + + def age_til(self, start_timestamp: int) -> int: + return self.c_age_til(start_timestamp) + + def order_type(self) -> OrderType: + return OrderType.LIMIT + + def copy_with_id(self, client_order_id: str): + return LimitOrder( + client_order_id=client_order_id, + trading_pair=self.trading_pair, + is_buy=self.is_buy, + base_currency=self.base_currency, + quote_currency=self.quote_currency, + price=self.price, + quantity=self.quantity, + filled_quantity=self.filled_quantity, + creation_timestamp=self.creation_timestamp, + status=self.status, + position=self.position, + ) + + def __repr__(self) -> str: + return (f"LimitOrder('{self.client_order_id}', '{self.trading_pair}', {self.is_buy}, '{self.base_currency}', " + f"'{self.quote_currency}', {self.price}, {self.quantity}, {self.filled_quantity}, " + f"{self.creation_timestamp})") + + +cdef LimitOrder c_create_limit_order_from_cpp_limit_order(const CPPLimitOrder cpp_limit_order): + cdef LimitOrder retval = LimitOrder.__new__(LimitOrder) + retval._cpp_limit_order = cpp_limit_order + return retval diff --git a/hummingbot/core/data_type/market_order.py b/hummingbot/core/data_type/market_order.py new file mode 100644 index 0000000..e191d85 --- /dev/null +++ b/hummingbot/core/data_type/market_order.py @@ -0,0 +1,60 @@ +from typing import List, NamedTuple + +import pandas as pd + +from hummingbot.core.data_type.common import OrderType, PositionAction + + +class MarketOrder(NamedTuple): + order_id: str + trading_pair: str + is_buy: bool + base_asset: str + quote_asset: str + amount: float + timestamp: float + position: PositionAction = PositionAction.NIL + + @classmethod + def to_pandas(cls, market_orders: List["MarketOrder"]) -> pd.DataFrame: + columns = ["order_id", "trading_pair", "is_buy", "base_asset", "quote_asset", "quantity", "timestamp"] + data = [[ + market_order.order_id, + market_order.trading_pair, + market_order.is_buy, + market_order.base_asset, + market_order.quote_asset, + market_order.amount, + pd.Timestamp(market_order.timestamp, unit='s', tz='UTC').strftime('%Y-%m-%d %H:%M:%S') + ] for market_order in market_orders] + return pd.DataFrame(data=data, columns=columns) + + @property + def client_order_id(self): + # Added to make this class polymorphic with LimitOrder + return self.order_id + + @property + def quantity(self): + # Added to make this class polymorphic with LimitOrder + return self.amount + + @property + def price(self): + # Added to make this class polymorphic with LimitOrder + return None + + def order_type(self) -> OrderType: + return OrderType.MARKET + + def copy_with_id(self, client_order_id: str): + return MarketOrder( + order_id=client_order_id, + trading_pair=self.trading_pair, + is_buy=self.is_buy, + base_asset=self.base_asset, + quote_asset=self.quote_asset, + amount=self.amount, + timestamp=self.timestamp, + position=self.position, + ) diff --git a/hummingbot/core/data_type/order_book.pxd b/hummingbot/core/data_type/order_book.pxd new file mode 100644 index 0000000..9af97b0 --- /dev/null +++ b/hummingbot/core/data_type/order_book.pxd @@ -0,0 +1,39 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from libcpp.set cimport set +from libcpp.vector cimport vector +from hummingbot.core.data_type.OrderBookEntry cimport OrderBookEntry +from hummingbot.core.pubsub cimport PubSub +from .order_book_query_result cimport OrderBookQueryResult +cimport numpy as np + + +cdef class OrderBook(PubSub): + cdef set[OrderBookEntry] _bid_book + cdef set[OrderBookEntry] _ask_book + cdef int64_t _snapshot_uid + cdef int64_t _last_diff_uid + cdef double _best_bid + cdef double _best_ask + cdef double _last_trade_price + cdef double _last_applied_trade + cdef double _last_trade_price_rest_updated + cdef bint _dex + + cdef c_apply_diffs(self, vector[OrderBookEntry] bids, vector[OrderBookEntry] asks, int64_t update_id) + cdef c_apply_snapshot(self, vector[OrderBookEntry] bids, vector[OrderBookEntry] asks, int64_t update_id) + cdef c_apply_trade(self, object trade_event) + cdef c_apply_numpy_diffs(self, + np.ndarray[np.float64_t, ndim=2] bids_array, + np.ndarray[np.float64_t, ndim=2] asks_array) + cdef c_apply_numpy_snapshot(self, + np.ndarray[np.float64_t, ndim=2] bids_array, + np.ndarray[np.float64_t, ndim=2] asks_array) + cdef double c_get_price(self, bint is_buy) except? -1 + cdef OrderBookQueryResult c_get_price_for_volume(self, bint is_buy, double volume) + cdef OrderBookQueryResult c_get_price_for_quote_volume(self, bint is_buy, double quote_volume) + cdef OrderBookQueryResult c_get_volume_for_price(self, bint is_buy, double price) + cdef OrderBookQueryResult c_get_quote_volume_for_price(self, bint is_buy, double price) + cdef OrderBookQueryResult c_get_vwap_for_volume(self, bint is_buy, double volume) + cdef OrderBookQueryResult c_get_quote_volume_for_base_amount(self, bint is_buy, double base_amount) diff --git a/hummingbot/core/data_type/order_book.pyx b/hummingbot/core/data_type/order_book.pyx new file mode 100644 index 0000000..8142e43 --- /dev/null +++ b/hummingbot/core/data_type/order_book.pyx @@ -0,0 +1,483 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp +import bisect +import logging +import time +from typing import ( + Dict, + Iterator, + List, + Optional, + Tuple, +) + +import numpy as np +import pandas as pd + +from cython.operator cimport( + address as ref, + dereference as deref, + postincrement as inc, +) + +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_query_result import OrderBookQueryResult +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.OrderBookEntry cimport truncateOverlapEntries +from hummingbot.logger import HummingbotLogger +from hummingbot.core.event.events import ( + OrderBookEvent, + OrderBookTradeEvent +) + +cimport numpy as np + +ob_logger = None +NaN = float("nan") + + +cdef class OrderBook(PubSub): + ORDER_BOOK_TRADE_EVENT_TAG = OrderBookEvent.TradeEvent.value + + @classmethod + def logger(cls) -> HummingbotLogger: + global ob_logger + if ob_logger is None: + ob_logger = logging.getLogger(__name__) + return ob_logger + + def __init__(self, dex=False): + super().__init__() + self._snapshot_uid = 0 + self._last_diff_uid = 0 + self._best_bid = self._best_ask = float("NaN") + self._last_trade_price = float("NaN") + self._last_applied_trade = -1000.0 + self._last_trade_price_rest_updated = -1000 + self._dex = dex + + cdef c_apply_diffs(self, vector[OrderBookEntry] bids, vector[OrderBookEntry] asks, int64_t update_id): + cdef: + set[OrderBookEntry].iterator bid_book_end = self._bid_book.end() + set[OrderBookEntry].iterator ask_book_end = self._ask_book.end() + set[OrderBookEntry].reverse_iterator bid_iterator + set[OrderBookEntry].iterator ask_iterator + set[OrderBookEntry].iterator result + OrderBookEntry top_bid + OrderBookEntry top_ask + + # Apply the diffs. Diffs with 0 amounts mean deletion. + for bid in bids: + result = self._bid_book.find(bid) + if result != bid_book_end: + self._bid_book.erase(result) + if bid.getAmount() > 0: + self._bid_book.insert(bid) + for ask in asks: + result = self._ask_book.find(ask) + if result != ask_book_end: + self._ask_book.erase(result) + if ask.getAmount() > 0: + self._ask_book.insert(ask) + + # If any overlapping entries between the bid and ask books, centralised: newer entries win, dex: see OrderBookEntry.cpp + truncateOverlapEntries(self._bid_book, self._ask_book, self._dex) + + # Record the current best prices, for faster c_get_price() calls. + bid_iterator = self._bid_book.rbegin() + ask_iterator = self._ask_book.begin() + if bid_iterator != self._bid_book.rend(): + top_bid = deref(bid_iterator) + self._best_bid = top_bid.getPrice() + if ask_iterator != self._ask_book.end(): + top_ask = deref(ask_iterator) + self._best_ask = top_ask.getPrice() + + # Remember the last diff update ID. + self._last_diff_uid = update_id + + cdef c_apply_snapshot(self, vector[OrderBookEntry] bids, vector[OrderBookEntry] asks, int64_t update_id): + cdef: + double best_bid_price = float("NaN") + double best_ask_price = float("NaN") + set[OrderBookEntry].reverse_iterator bid_iterator + set[OrderBookEntry].iterator ask_iterator + OrderBookEntry top_bid + OrderBookEntry top_ask + + # Start with an empty order book, and then insert all entries. + self._bid_book.clear() + self._ask_book.clear() + for bid in bids: + self._bid_book.insert(bid) + if not (bid.getPrice() <= best_bid_price): + best_bid_price = bid.getPrice() + for ask in asks: + self._ask_book.insert(ask) + if not (ask.getPrice() >= best_ask_price): + best_ask_price = ask.getPrice() + + if self._dex: + truncateOverlapEntries(self._bid_book, self._ask_book, self._dex) + # Record the current best prices, for faster c_get_price() calls. + bid_iterator = self._bid_book.rbegin() + ask_iterator = self._ask_book.begin() + if bid_iterator != self._bid_book.rend(): + top_bid = deref(bid_iterator) + best_bid_price = top_bid.getPrice() + if ask_iterator != self._ask_book.end(): + top_ask = deref(ask_iterator) + best_ask_price = top_ask.getPrice() + + # Record the current best prices, for faster c_get_price() calls. + self._best_bid = best_bid_price + self._best_ask = best_ask_price + + # Remember the last snapshot update ID. + self._snapshot_uid = update_id + + cdef c_apply_trade(self, object trade_event): + self._last_trade_price = trade_event.price + self._last_applied_trade = time.perf_counter() + self.c_trigger_event(self.ORDER_BOOK_TRADE_EVENT_TAG, trade_event) + + @property + def last_trade_price(self) -> float: + return self._last_trade_price + + @last_trade_price.setter + def last_trade_price(self, value: float): + self._last_trade_price = value + + @property + def last_applied_trade(self) -> float: + return self._last_applied_trade + + @property + def last_trade_price_rest_updated(self) -> float: + return self._last_trade_price_rest_updated + + @last_trade_price_rest_updated.setter + def last_trade_price_rest_updated(self, value: float): + self._last_trade_price_rest_updated = value + + @property + def snapshot_uid(self) -> int: + return self._snapshot_uid + + @property + def last_diff_uid(self) -> int: + return self._last_diff_uid + + @property + def snapshot(self) -> Tuple[pd.DataFrame, pd.DataFrame]: + bids_rows = list(self.bid_entries()) + asks_rows = list(self.ask_entries()) + bids_df = pd.DataFrame(data=bids_rows, columns=OrderBookRow._fields, dtype="float64") + asks_df = pd.DataFrame(data=asks_rows, columns=OrderBookRow._fields, dtype="float64") + return bids_df, asks_df + + def apply_diffs(self, bids: List[OrderBookRow], asks: List[OrderBookRow], update_id: int): + cdef: + vector[OrderBookEntry] cpp_bids + vector[OrderBookEntry] cpp_asks + for row in bids: + cpp_bids.push_back(OrderBookEntry(row.price, row.amount, row.update_id)) + for row in asks: + cpp_asks.push_back(OrderBookEntry(row.price, row.amount, row.update_id)) + self.c_apply_diffs(cpp_bids, cpp_asks, update_id) + + def apply_snapshot(self, bids: List[OrderBookRow], asks: List[OrderBookRow], update_id: int): + cdef: + vector[OrderBookEntry] cpp_bids + vector[OrderBookEntry] cpp_asks + for row in bids: + cpp_bids.push_back(OrderBookEntry(row.price, row.amount, row.update_id)) + for row in asks: + cpp_asks.push_back(OrderBookEntry(row.price, row.amount, row.update_id)) + self.c_apply_snapshot(cpp_bids, cpp_asks, update_id) + + def apply_trade(self, trade: OrderBookTradeEvent): + self.c_apply_trade(trade) + + def apply_pandas_diffs(self, bids_df: pd.DataFrame, asks_df: pd.DataFrame): + """ + The diffs data frame must have 3 columns, [price, amount, update_id], and a UNIX timestamp index. + + All columns are of double type. + """ + self.apply_numpy_diffs(bids_df.values, asks_df.values) + + def apply_numpy_diffs(self, bids_array: np.ndarray, asks_array: np.ndarray): + """ + The diffs data frame must have 3 columns, [price, amount, update_id]. + All columns are of double type. + """ + self.c_apply_numpy_diffs(bids_array, asks_array) + + cdef c_apply_numpy_diffs(self, + np.ndarray[np.float64_t, ndim=2] bids_array, + np.ndarray[np.float64_t, ndim=2] asks_array): + """ + The diffs data frame must have 3 columns, [price, amount, update_id]. + All columns are of double type. + """ + cdef: + vector[OrderBookEntry] cpp_bids + vector[OrderBookEntry] cpp_asks + int64_t last_update_id = 0 + + for row in bids_array: + cpp_bids.push_back(OrderBookEntry(row[0], row[1], (row[2]))) + last_update_id = max(last_update_id, row[2]) + for row in asks_array: + cpp_asks.push_back(OrderBookEntry(row[0], row[1], (row[2]))) + last_update_id = max(last_update_id, row[2]) + self.c_apply_diffs(cpp_bids, cpp_asks, last_update_id) + + def apply_numpy_snapshot(self, bids_array: np.ndarray, asks_array: np.ndarray): + """ + The diffs data frame must have 3 columns, [price, amount, update_id]. + All columns are of double type. + """ + self.c_apply_numpy_snapshot(bids_array, asks_array) + + cdef c_apply_numpy_snapshot(self, + np.ndarray[np.float64_t, ndim=2] bids_array, + np.ndarray[np.float64_t, ndim=2] asks_array): + """ + The diffs data frame must have 3 columns, [price, amount, update_id]. + All columns are of double type. + """ + cdef: + vector[OrderBookEntry] cpp_bids + vector[OrderBookEntry] cpp_asks + int64_t last_update_id = 0 + + for row in bids_array: + cpp_bids.push_back(OrderBookEntry(row[0], row[1], (row[2]))) + last_update_id = max(last_update_id, row[2]) + for row in asks_array: + cpp_asks.push_back(OrderBookEntry(row[0], row[1], (row[2]))) + last_update_id = max(last_update_id, row[2]) + self.c_apply_snapshot(cpp_bids, cpp_asks, last_update_id) + + def bid_entries(self) -> Iterator[OrderBookRow]: + cdef: + set[OrderBookEntry].reverse_iterator it = self._bid_book.rbegin() + OrderBookEntry entry + while it != self._bid_book.rend(): + entry = deref(it) + yield OrderBookRow(entry.getPrice(), entry.getAmount(), entry.getUpdateId()) + inc(it) + + def ask_entries(self) -> Iterator[OrderBookRow]: + cdef: + set[OrderBookEntry].iterator it = self._ask_book.begin() + OrderBookEntry entry + while it != self._ask_book.end(): + entry = deref(it) + yield OrderBookRow(entry.getPrice(), entry.getAmount(), entry.getUpdateId()) + inc(it) + + def simulate_buy(self, amount: float) -> List[OrderBookRow]: + amount_left = amount + retval = [] + for ask_entry in self.ask_entries(): + ask_entry = ask_entry + if ask_entry.amount < amount_left: + retval.append(ask_entry) + amount_left -= ask_entry.amount + else: + retval.append(OrderBookRow(ask_entry.price, amount_left, ask_entry.update_id)) + amount_left = 0.0 + break + return retval + + def simulate_sell(self, amount: float) -> List[OrderBookRow]: + amount_left = amount + retval = [] + for bid_entry in self.bid_entries(): + bid_entry = bid_entry + if bid_entry.amount < amount_left: + retval.append(bid_entry) + amount_left -= bid_entry.amount + else: + retval.append(OrderBookRow(bid_entry.price, amount_left, bid_entry.update_id)) + amount_left = 0.0 + break + return retval + + cdef double c_get_price(self, bint is_buy) except? -1: + cdef: + set[OrderBookEntry] *book = ref(self._ask_book) if is_buy else ref(self._bid_book) + if deref(book).size() < 1: + raise EnvironmentError("Order book is empty - no price quote is possible.") + return self._best_ask if is_buy else self._best_bid + + def get_price(self, is_buy: bool) -> float: + return self.c_get_price(is_buy) + + cdef OrderBookQueryResult c_get_price_for_volume(self, bint is_buy, double volume): + cdef: + double cumulative_volume = 0 + double result_price = NaN + + if is_buy: + for order_book_row in self.ask_entries(): + cumulative_volume += order_book_row.amount + if cumulative_volume >= volume: + result_price = order_book_row.price + break + else: + for order_book_row in self.bid_entries(): + cumulative_volume += order_book_row.amount + if cumulative_volume >= volume: + result_price = order_book_row.price + break + + return OrderBookQueryResult(NaN, volume, result_price, min(cumulative_volume, volume)) + + cdef OrderBookQueryResult c_get_vwap_for_volume(self, bint is_buy, double volume): + cdef: + double total_cost = 0 + double total_volume = 0 + double result_vwap = NaN + if is_buy: + for order_book_row in self.ask_entries(): + total_cost += order_book_row.amount * order_book_row.price + total_volume += order_book_row.amount + if total_volume >= volume: + total_cost -= order_book_row.amount * order_book_row.price + total_volume -= order_book_row.amount + incremental_amount = volume - total_volume + total_cost += incremental_amount * order_book_row.price + total_volume += incremental_amount + result_vwap = total_cost / total_volume + break + else: + for order_book_row in self.bid_entries(): + total_cost += order_book_row.amount * order_book_row.price + total_volume += order_book_row.amount + if total_volume >= volume: + total_cost -= order_book_row.amount * order_book_row.price + total_volume -= order_book_row.amount + incremental_amount = volume - total_volume + total_cost += incremental_amount * order_book_row.price + total_volume += incremental_amount + result_vwap = total_cost / total_volume + break + + return OrderBookQueryResult(NaN, volume, result_vwap, min(total_volume, volume)) + + cdef OrderBookQueryResult c_get_price_for_quote_volume(self, bint is_buy, double quote_volume): + cdef: + double cumulative_volume = 0 + double result_price = NaN + + if is_buy: + for order_book_row in self.ask_entries(): + cumulative_volume += order_book_row.amount * order_book_row.price + if cumulative_volume >= quote_volume: + result_price = order_book_row.price + break + else: + for order_book_row in self.bid_entries(): + cumulative_volume += order_book_row.amount * order_book_row.price + if cumulative_volume >= quote_volume: + result_price = order_book_row.price + break + + return OrderBookQueryResult(NaN, quote_volume, result_price, min(cumulative_volume, quote_volume)) + + cdef OrderBookQueryResult c_get_quote_volume_for_base_amount(self, bint is_buy, double base_amount): + cdef: + double cumulative_volume = 0 + double cumulative_base_amount = 0 + double row_amount = 0 + + if is_buy: + for order_book_row in self.ask_entries(): + row_amount = order_book_row.amount + if row_amount + cumulative_base_amount >= base_amount: + row_amount = base_amount - cumulative_base_amount + cumulative_base_amount += row_amount + cumulative_volume += row_amount * order_book_row.price + if cumulative_base_amount >= base_amount: + break + else: + for order_book_row in self.bid_entries(): + row_amount = order_book_row.amount + if row_amount + cumulative_base_amount >= base_amount: + row_amount = base_amount - cumulative_base_amount + cumulative_base_amount += row_amount + cumulative_volume += row_amount * order_book_row.price + if cumulative_base_amount >= base_amount: + break + + return OrderBookQueryResult(NaN, base_amount, NaN, cumulative_volume) + + cdef OrderBookQueryResult c_get_volume_for_price(self, bint is_buy, double price): + cdef: + double cumulative_volume = 0 + double result_price = NaN + + if is_buy: + for order_book_row in self.ask_entries(): + if order_book_row.price > price: + break + cumulative_volume += order_book_row.amount + result_price = order_book_row.price + else: + for order_book_row in self.bid_entries(): + if order_book_row.price < price: + break + cumulative_volume += order_book_row.amount + result_price = order_book_row.price + + return OrderBookQueryResult(price, NaN, result_price, cumulative_volume) + + cdef OrderBookQueryResult c_get_quote_volume_for_price(self, bint is_buy, double price): + cdef: + double cumulative_volume = 0 + double result_price = NaN + + if is_buy: + for order_book_row in self.ask_entries(): + if order_book_row.price > price: + break + cumulative_volume += order_book_row.amount * order_book_row.price + result_price = order_book_row.price + else: + for order_book_row in self.bid_entries(): + if order_book_row.price < price: + break + cumulative_volume += order_book_row.amount * order_book_row.price + result_price = order_book_row.price + + return OrderBookQueryResult(price, NaN, result_price, cumulative_volume) + + def get_price_for_volume(self, is_buy: bool, volume: float) -> OrderBookQueryResult: + return self.c_get_price_for_volume(is_buy, volume) + + def get_vwap_for_volume(self, is_buy: bool, volume: float) -> OrderBookQueryResult: + return self.c_get_vwap_for_volume(is_buy, volume) + + def get_price_for_quote_volume(self, is_buy: bool, quote_volume: float) -> OrderBookQueryResult: + return self.c_get_price_for_quote_volume(is_buy, quote_volume) + + def get_quote_volume_for_base_amount(self, is_buy: bool, base_amount: float) -> OrderBookQueryResult: + return self.c_get_quote_volume_for_base_amount(is_buy, base_amount) + + def get_volume_for_price(self, bint is_buy, double price) -> OrderBookQueryResult: + return self.c_get_volume_for_price(is_buy, price) + + def get_quote_volume_for_price(self, is_buy: bool, price: float) -> OrderBookQueryResult: + return self.c_get_quote_volume_for_price(is_buy, price) + + def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): + replay_position = bisect.bisect_right(diffs, snapshot) + replay_diffs = diffs[replay_position:] + self.apply_snapshot(snapshot.bids, snapshot.asks, snapshot.update_id) + for diff in replay_diffs: + self.apply_diffs(diff.bids, diff.asks, diff.update_id) diff --git a/hummingbot/core/data_type/order_book_message.py b/hummingbot/core/data_type/order_book_message.py new file mode 100644 index 0000000..a158f4e --- /dev/null +++ b/hummingbot/core/data_type/order_book_message.py @@ -0,0 +1,97 @@ +from collections import namedtuple +from enum import Enum +from functools import total_ordering +from typing import Dict, List, Optional + +from hummingbot.core.data_type.order_book_row import OrderBookRow + + +class OrderBookMessageType(Enum): + SNAPSHOT = 1 + DIFF = 2 + TRADE = 3 + + +@total_ordering +class OrderBookMessage(namedtuple("_OrderBookMessage", "type, content, timestamp")): + type: OrderBookMessageType + content: Dict[str, any] + timestamp: float + + def __new__( + cls, + message_type: OrderBookMessageType, + content: Dict[str, any], + timestamp: Optional[float] = None, + *args, + **kwargs, + ): + return super(OrderBookMessage, cls).__new__(cls, message_type, content, timestamp, *args, **kwargs) + + @property + def update_id(self) -> int: + if self.type in [OrderBookMessageType.DIFF, OrderBookMessageType.SNAPSHOT]: + return self.content["update_id"] + else: + return -1 + + @property + def first_update_id(self) -> int: + if self.type is OrderBookMessageType.DIFF: + return self.content.get("first_update_id", self.update_id) + else: + return -1 + + @property + def trade_id(self) -> int: + if self.type is OrderBookMessageType.TRADE: + return self.content["trade_id"] + return -1 + + @property + def trading_pair(self) -> str: + return self.content["trading_pair"] + + @property + def asks(self) -> List[OrderBookRow]: + return [ + OrderBookRow(float(price), float(amount), self.update_id) for price, amount, *trash in self.content["asks"] + ] + + @property + def bids(self) -> List[OrderBookRow]: + return [ + OrderBookRow(float(price), float(amount), self.update_id) for price, amount, *trash in self.content["bids"] + ] + + @property + def has_update_id(self) -> bool: + return self.type in {OrderBookMessageType.DIFF, OrderBookMessageType.SNAPSHOT} + + @property + def has_trade_id(self) -> bool: + return self.type == OrderBookMessageType.TRADE + + def __eq__(self, other: "OrderBookMessage") -> bool: + eq = ( + (self.type == other.type) + and ( + (self.has_update_id and (self.update_id == other.update_id)) + or (self.trade_id == other.trade_id) + ) + ) + return eq + + def __hash__(self): + return hash(self.type, self.update_id, self.trade_id) + + def __lt__(self, other: "OrderBookMessage") -> bool: + eq = ( + (self.has_update_id and other.has_update_id and self.update_id < other.update_id) + or (self.has_trade_id and other.has_trade_id and self.trade_id < other.trade_id) + or ( + ((self.timestamp != other.timestamp) and self.timestamp < other.timestamp) + or self.has_update_id # if same timestamp, order book messages < trade messages. + ) + ) + return eq diff --git a/hummingbot/core/data_type/order_book_query_result.pxd b/hummingbot/core/data_type/order_book_query_result.pxd new file mode 100644 index 0000000..4a87be2 --- /dev/null +++ b/hummingbot/core/data_type/order_book_query_result.pxd @@ -0,0 +1,16 @@ +# distutils: language=c++ + +cdef class OrderBookQueryResult: + cdef: + public double query_price + public double query_volume + public double result_price + public double result_volume + + +cdef class ClientOrderBookQueryResult: + cdef: + public object query_price + public object query_volume + public object result_price + public object result_volume diff --git a/hummingbot/core/data_type/order_book_query_result.pyx b/hummingbot/core/data_type/order_book_query_result.pyx new file mode 100644 index 0000000..13060a2 --- /dev/null +++ b/hummingbot/core/data_type/order_book_query_result.pyx @@ -0,0 +1,16 @@ +# distutils: language=c++ + +cdef class OrderBookQueryResult: + def __cinit__(self, double query_price, double query_volume, double result_price, double result_volume): + self.query_price = query_price + self.query_volume = query_volume + self.result_price = result_price + self.result_volume = result_volume + + +cdef class ClientOrderBookQueryResult: + def __cinit__(self, object query_price, object query_volume, object result_price, object result_volume): + self.query_price = query_price + self.query_volume = query_volume + self.result_price = result_price + self.result_volume = result_volume diff --git a/hummingbot/core/data_type/order_book_row.py b/hummingbot/core/data_type/order_book_row.py new file mode 100644 index 0000000..71733a9 --- /dev/null +++ b/hummingbot/core/data_type/order_book_row.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +from collections import namedtuple +from decimal import Decimal + + +class OrderBookRow(namedtuple("_OrderBookRow", "price, amount, update_id")): + """ + Used to apply changes to OrderBook. OrderBook classes uses float internally for better performance over Decimal. + """ + price: float + amount: float + update_id: int + + +class ClientOrderBookRow(namedtuple("_OrderBookRow", "price, amount, update_id")): + """ + Used in market classes where OrderBook values are converted to Decimal. + """ + price: Decimal + amount: Decimal + update_id: int diff --git a/hummingbot/core/data_type/order_book_tracker.py b/hummingbot/core/data_type/order_book_tracker.py new file mode 100644 index 0000000..da498f8 --- /dev/null +++ b/hummingbot/core/data_type/order_book_tracker.py @@ -0,0 +1,345 @@ +import asyncio +import logging +import time +from collections import defaultdict, deque +from enum import Enum +from typing import Deque, Dict, List, Optional, Tuple + +import pandas as pd + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.event.events import OrderBookTradeEvent +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class OrderBookTrackerDataSourceType(Enum): + REMOTE_API = 2 + EXCHANGE_API = 3 + + +class OrderBookTracker: + PAST_DIFF_WINDOW_SIZE: int = 32 + _obt_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._obt_logger is None: + cls._obt_logger = logging.getLogger(__name__) + return cls._obt_logger + + def __init__(self, data_source: OrderBookTrackerDataSource, trading_pairs: List[str], domain: Optional[str] = None): + self._domain: Optional[str] = domain + self._data_source: OrderBookTrackerDataSource = data_source + self._trading_pairs: List[str] = trading_pairs + self._order_books_initialized: asyncio.Event = asyncio.Event() + self._tracking_tasks: Dict[str, asyncio.Task] = {} + self._order_books: Dict[str, OrderBook] = {} + self._tracking_message_queues: Dict[str, asyncio.Queue] = {} + self._past_diffs_windows: Dict[str, Deque] = defaultdict(lambda: deque(maxlen=self.PAST_DIFF_WINDOW_SIZE)) + self._order_book_diff_stream: asyncio.Queue = asyncio.Queue() + self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue() + self._order_book_trade_stream: asyncio.Queue = asyncio.Queue() + self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + self._saved_message_queues: Dict[str, Deque[OrderBookMessage]] = defaultdict(lambda: deque(maxlen=1000)) + + self._emit_trade_event_task: Optional[asyncio.Task] = None + self._init_order_books_task: Optional[asyncio.Task] = None + self._order_book_diff_listener_task: Optional[asyncio.Task] = None + self._order_book_trade_listener_task: Optional[asyncio.Task] = None + self._order_book_snapshot_listener_task: Optional[asyncio.Task] = None + self._order_book_diff_router_task: Optional[asyncio.Task] = None + self._order_book_snapshot_router_task: Optional[asyncio.Task] = None + self._update_last_trade_prices_task: Optional[asyncio.Task] = None + self._order_book_stream_listener_task: Optional[asyncio.Task] = None + + @property + def data_source(self) -> OrderBookTrackerDataSource: + return self._data_source + + @property + def order_books(self) -> Dict[str, OrderBook]: + return self._order_books + + @property + def ready(self) -> bool: + return self._order_books_initialized.is_set() + + @property + def snapshot(self) -> Dict[str, Tuple[pd.DataFrame, pd.DataFrame]]: + return { + trading_pair: order_book.snapshot + for trading_pair, order_book in self._order_books.items() + } + + def start(self): + self.stop() + self._init_order_books_task = safe_ensure_future( + self._init_order_books() + ) + self._emit_trade_event_task = safe_ensure_future( + self._emit_trade_event_loop() + ) + self._order_book_diff_listener_task = safe_ensure_future( + self._data_source.listen_for_order_book_diffs(self._ev_loop, self._order_book_diff_stream) + ) + self._order_book_trade_listener_task = safe_ensure_future( + self._data_source.listen_for_trades(self._ev_loop, self._order_book_trade_stream) + ) + self._order_book_snapshot_listener_task = safe_ensure_future( + self._data_source.listen_for_order_book_snapshots(self._ev_loop, self._order_book_snapshot_stream) + ) + self._order_book_stream_listener_task = safe_ensure_future( + self._data_source.listen_for_subscriptions() + ) + self._order_book_diff_router_task = safe_ensure_future( + self._order_book_diff_router() + ) + self._order_book_snapshot_router_task = safe_ensure_future( + self._order_book_snapshot_router() + ) + self._update_last_trade_prices_task = safe_ensure_future( + self._update_last_trade_prices_loop() + ) + + def stop(self): + if self._init_order_books_task is not None: + self._init_order_books_task.cancel() + self._init_order_books_task = None + if self._emit_trade_event_task is not None: + self._emit_trade_event_task.cancel() + self._emit_trade_event_task = None + if self._order_book_diff_listener_task is not None: + self._order_book_diff_listener_task.cancel() + self._order_book_diff_listener_task = None + if self._order_book_snapshot_listener_task is not None: + self._order_book_snapshot_listener_task.cancel() + self._order_book_snapshot_listener_task = None + if self._order_book_trade_listener_task is not None: + self._order_book_trade_listener_task.cancel() + self._order_book_trade_listener_task = None + + if self._order_book_diff_router_task is not None: + self._order_book_diff_router_task.cancel() + self._order_book_diff_router_task = None + if self._order_book_snapshot_router_task is not None: + self._order_book_snapshot_router_task.cancel() + self._order_book_snapshot_router_task = None + if self._update_last_trade_prices_task is not None: + self._update_last_trade_prices_task.cancel() + self._update_last_trade_prices_task = None + if self._order_book_stream_listener_task is not None: + self._order_book_stream_listener_task.cancel() + if len(self._tracking_tasks) > 0: + for _, task in self._tracking_tasks.items(): + task.cancel() + self._tracking_tasks.clear() + self._order_books_initialized.clear() + + async def wait_ready(self): + await self._order_books_initialized.wait() + + async def _update_last_trade_prices_loop(self): + ''' + Updates last trade price for all order books through REST API, it is to initiate last_trade_price and as + fall-back mechanism for when the web socket update channel fails. + ''' + await self._order_books_initialized.wait() + while True: + try: + outdateds = [t_pair for t_pair, o_book in self._order_books.items() + if o_book.last_applied_trade < time.perf_counter() - (60. * 3) + and o_book.last_trade_price_rest_updated < time.perf_counter() - 5] + if outdateds: + args = {"trading_pairs": outdateds} + if self._domain is not None: + args["domain"] = self._domain + last_prices = await self._data_source.get_last_traded_prices(**args) + for trading_pair, last_price in last_prices.items(): + self._order_books[trading_pair].last_trade_price = last_price + self._order_books[trading_pair].last_trade_price_rest_updated = time.perf_counter() + else: + await asyncio.sleep(1) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error while fetching last trade price.", exc_info=True) + await asyncio.sleep(30) + + async def _initial_order_book_for_trading_pair(self, trading_pair: str) -> OrderBook: + return await self._data_source.get_new_order_book(trading_pair) + + async def _init_order_books(self): + """ + Initialize order books + """ + for index, trading_pair in enumerate(self._trading_pairs): + self._order_books[trading_pair] = await self._initial_order_book_for_trading_pair(trading_pair) + self._tracking_message_queues[trading_pair] = asyncio.Queue() + self._tracking_tasks[trading_pair] = safe_ensure_future(self._track_single_book(trading_pair)) + self.logger().info(f"Initialized order book for {trading_pair}. " + f"{index + 1}/{len(self._trading_pairs)} completed.") + await self._sleep(delay=1) + self._order_books_initialized.set() + + async def _order_book_diff_router(self): + """ + Routes the real-time order book diff messages to the correct order book. + """ + last_message_timestamp: float = time.time() + messages_queued: int = 0 + messages_accepted: int = 0 + messages_rejected: int = 0 + + while True: + try: + ob_message: OrderBookMessage = await self._order_book_diff_stream.get() + trading_pair: str = ob_message.trading_pair + + if trading_pair not in self._tracking_message_queues: + messages_queued += 1 + # Save diff messages received before snapshots are ready + self._saved_message_queues[trading_pair].append(ob_message) + continue + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + # Check the order book's initial update ID. If it's larger, don't bother. + order_book: OrderBook = self._order_books[trading_pair] + + if order_book.snapshot_uid > ob_message.update_id: + messages_rejected += 1 + continue + await message_queue.put(ob_message) + messages_accepted += 1 + + # Log some statistics. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Diff messages processed: {messages_accepted}, " + f"rejected: {messages_rejected}, queued: {messages_queued}") + messages_accepted = 0 + messages_rejected = 0 + messages_queued = 0 + + last_message_timestamp = now + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error routing order book messages.", + exc_info=True, + app_warning_msg="Unexpected error routing order book messages. Retrying after 5 seconds." + ) + await asyncio.sleep(5.0) + + async def _order_book_snapshot_router(self): + """ + Route the real-time order book snapshot messages to the correct order book. + """ + await self._order_books_initialized.wait() + while True: + try: + ob_message: OrderBookMessage = await self._order_book_snapshot_stream.get() + trading_pair: str = ob_message.trading_pair + if trading_pair not in self._tracking_message_queues: + continue + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + await message_queue.put(ob_message) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unknown error. Retrying after 5 seconds.", exc_info=True) + await asyncio.sleep(5.0) + + async def _track_single_book(self, trading_pair: str): + past_diffs_window = self._past_diffs_windows[trading_pair] + + message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair] + order_book: OrderBook = self._order_books[trading_pair] + last_message_timestamp: float = time.time() + diff_messages_accepted: int = 0 + + while True: + try: + saved_messages: Deque[OrderBookMessage] = self._saved_message_queues[trading_pair] + + # Process saved messages first if there are any + if len(saved_messages) > 0: + message = saved_messages.popleft() + else: + message = await message_queue.get() + + if message.type is OrderBookMessageType.DIFF: + order_book.apply_diffs(message.bids, message.asks, message.update_id) + past_diffs_window.append(message) + diff_messages_accepted += 1 + + # Output some statistics periodically. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Processed {diff_messages_accepted} order book diffs for {trading_pair}.") + diff_messages_accepted = 0 + last_message_timestamp = now + elif message.type is OrderBookMessageType.SNAPSHOT: + past_diffs: List[OrderBookMessage] = list(past_diffs_window) + order_book.restore_from_snapshot_and_diffs(message, past_diffs) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + f"Unexpected error tracking order book for {trading_pair}.", + exc_info=True, + app_warning_msg="Unexpected error tracking order book. Retrying after 5 seconds." + ) + await asyncio.sleep(5.0) + + async def _emit_trade_event_loop(self): + last_message_timestamp: float = time.time() + messages_accepted: int = 0 + messages_rejected: int = 0 + await self._order_books_initialized.wait() + while True: + try: + trade_message: OrderBookMessage = await self._order_book_trade_stream.get() + trading_pair: str = trade_message.trading_pair + + if trading_pair not in self._order_books: + messages_rejected += 1 + continue + + order_book: OrderBook = self._order_books[trading_pair] + order_book.apply_trade(OrderBookTradeEvent( + trading_pair=trade_message.trading_pair, + timestamp=trade_message.timestamp, + price=float(trade_message.content["price"]), + amount=float(trade_message.content["amount"]), + trade_id=trade_message.trade_id, + type=TradeType.SELL if + trade_message.content["trade_type"] == float(TradeType.SELL.value) else TradeType.BUY + )) + + messages_accepted += 1 + + # Log some statistics. + now: float = time.time() + if int(now / 60.0) > int(last_message_timestamp / 60.0): + self.logger().debug(f"Trade messages processed: {messages_accepted}, rejected: {messages_rejected}") + messages_accepted = 0 + messages_rejected = 0 + + last_message_timestamp = now + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + "Unexpected error routing order book messages.", + exc_info=True, + app_warning_msg="Unexpected error routing order book messages. Retrying after 5 seconds." + ) + await asyncio.sleep(5.0) + + @staticmethod + async def _sleep(delay: float): + await asyncio.sleep(delay=delay) diff --git a/hummingbot/core/data_type/order_book_tracker_data_source.py b/hummingbot/core/data_type/order_book_tracker_data_source.py new file mode 100755 index 0000000..ae8163e --- /dev/null +++ b/hummingbot/core/data_type/order_book_tracker_data_source.py @@ -0,0 +1,258 @@ +import asyncio +import logging +import time +from abc import ABCMeta, abstractmethod +from collections import defaultdict +from typing import Any, Callable, Dict, List, Optional + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class OrderBookTrackerDataSource(metaclass=ABCMeta): + FULL_ORDER_BOOK_RESET_DELTA_SECONDS = 60 * 60 + + _logger: Optional[HummingbotLogger] = None + + def __init__(self, trading_pairs: List[str]): + self._trade_messages_queue_key = "trade" + self._diff_messages_queue_key = "order_book_diff" + self._snapshot_messages_queue_key = "order_book_snapshot" + + self._trading_pairs: List[str] = trading_pairs + self._order_book_create_function = lambda: OrderBook() + self._message_queue: Dict[str, asyncio.Queue] = defaultdict(asyncio.Queue) + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + @property + def order_book_create_function(self) -> Callable[[], OrderBook]: + return self._order_book_create_function + + @order_book_create_function.setter + def order_book_create_function(self, func: Callable[[], OrderBook]): + self._order_book_create_function = func + + @abstractmethod + async def get_last_traded_prices(self, trading_pairs: List[str], domain: Optional[str] = None) -> Dict[str, float]: + """ + Return a dictionary the trading_pair as key and the current price as value for each trading pair passed as + parameter. + This method is required by the order book tracker, to get the last traded prices when no new public trades + are notified by the exchange. + + :param trading_pairs: list of trading pairs to get the prices for + :param domain: which domain we are connecting to + + :return: Dictionary of associations between token pair and its latest price + """ + raise NotImplementedError + + async def get_new_order_book(self, trading_pair: str) -> OrderBook: + """ + Creates a local instance of the exchange order book for a particular trading pair + + :param trading_pair: the trading pair for which the order book has to be retrieved + + :return: a local copy of the current order book in the exchange + """ + snapshot_msg: OrderBookMessage = await self._order_book_snapshot(trading_pair=trading_pair) + order_book: OrderBook = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + return order_book + + async def listen_for_subscriptions(self): + """ + Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the + exchange. Each message is stored in its own queue. + """ + ws: Optional[WSAssistant] = None + while True: + try: + ws: WSAssistant = await self._connected_websocket_assistant() + await self._subscribe_channels(ws) + await self._process_websocket_messages(websocket_assistant=ws) + except asyncio.CancelledError: + raise + except ConnectionError as connection_exception: + self.logger().warning(f"The websocket connection was closed ({connection_exception})") + except Exception: + self.logger().exception( + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + ) + await self._sleep(1.0) + finally: + await self._on_order_stream_interruption(websocket_assistant=ws) + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Reads the order diffs events queue. For each event creates a diff message instance and adds it to the + output queue + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created diff messages + """ + message_queue = self._message_queue[self._diff_messages_queue_key] + while True: + try: + diff_event = await message_queue.get() + await self._parse_order_book_diff_message(raw_message=diff_event, message_queue=output) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public order book updates from exchange") + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Reads the order snapshot events queue. For each event it creates a snapshot message instance and adds it to the + output queue. + This method also request the full order book content from the exchange using HTTP requests if it does not + receive events during one hour. + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created snapshot messages + """ + message_queue = self._message_queue[self._snapshot_messages_queue_key] + while True: + try: + try: + snapshot_event = await asyncio.wait_for(message_queue.get(), + timeout=self.FULL_ORDER_BOOK_RESET_DELTA_SECONDS) + await self._parse_order_book_snapshot_message(raw_message=snapshot_event, message_queue=output) + except asyncio.TimeoutError: + await self._request_order_book_snapshots(output=output) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public order book snapshots from exchange") + await self._sleep(1.0) + + async def listen_for_trades(self, ev_loop: asyncio.AbstractEventLoop, output: asyncio.Queue): + """ + Reads the trade events queue. For each event creates a trade message instance and adds it to the output queue + + :param ev_loop: the event loop the method will run in + :param output: a queue to add the created trade messages + """ + message_queue = self._message_queue[self._trade_messages_queue_key] + while True: + try: + trade_event = await message_queue.get() + await self._parse_trade_message(raw_message=trade_event, message_queue=output) + + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public trade updates from exchange") + + async def _request_order_book_snapshots(self, output: asyncio.Queue): + for trading_pair in self._trading_pairs: + try: + snapshot = await self._order_book_snapshot(trading_pair=trading_pair) + output.put_nowait(snapshot) + except Exception: + self.logger().exception(f"Unexpected error fetching order book snapshot for {trading_pair}.") + raise + + async def _parse_trade_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + """ + Create an instance of OrderBookMessage of type OrderBookMessageType.TRADE + + :param raw_message: the JSON dictionary of the public trade event + :param message_queue: queue where the parsed messages should be stored in + """ + raise NotImplementedError + + async def _parse_order_book_diff_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + """ + Create an instance of OrderBookMessage of type OrderBookMessageType.DIFF + + :param raw_message: the JSON dictionary of the public trade event + :param message_queue: queue where the parsed messages should be stored in + """ + raise NotImplementedError + + async def _parse_order_book_snapshot_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + """ + Create an instance of OrderBookMessage of type OrderBookMessageType.SNAPSHOT + + :param raw_message: the JSON dictionary of the public trade event + :param message_queue: queue where the parsed messages should be stored in + """ + raise NotImplementedError + + async def _order_book_snapshot(self, trading_pair: str) -> OrderBookMessage: + raise NotImplementedError + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + + :return: an instance of WSAssistant connected to the exchange + """ + raise NotImplementedError + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param ws: the websocket assistant used to connect to the exchange + """ + raise NotImplementedError + + def _channel_originating_message(self, event_message: Dict[str, Any]) -> str: + """ + Identifies the channel for a particular event message. Used to find the correct queue to add the message in + + :param event_message: the event received through the websocket connection + + :return: the message channel + """ + raise NotImplementedError + + async def _process_message_for_unknown_channel( + self, event_message: Dict[str, Any], websocket_assistant: WSAssistant + ): + """ + Processes a message coming from a not identified channel. + Does nothing by default but allows subclasses to reimplement + + :param event_message: the event received through the websocket connection + :param websocket_assistant: the websocket connection to use to interact with the exchange + """ + pass + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data is not None: # data will be None when the websocket is disconnected + channel: str = self._channel_originating_message(event_message=data) + valid_channels = self._get_messages_queue_keys() + if channel in valid_channels: + self._message_queue[channel].put_nowait(data) + else: + await self._process_message_for_unknown_channel( + event_message=data, websocket_assistant=websocket_assistant + ) + + def _get_messages_queue_keys(self) -> List[str]: + return [self._snapshot_messages_queue_key, self._diff_messages_queue_key, self._trade_messages_queue_key] + + async def _on_order_stream_interruption(self, websocket_assistant: Optional[WSAssistant] = None): + websocket_assistant and await websocket_assistant.disconnect() + + async def _sleep(self, delay): + """ + Function added only to facilitate patching the sleep in unit tests without affecting the asyncio module + """ + await asyncio.sleep(delay) + + def _time(self): + return time.time() diff --git a/hummingbot/core/data_type/order_book_tracker_entry.py b/hummingbot/core/data_type/order_book_tracker_entry.py new file mode 100644 index 0000000..f9cf68d --- /dev/null +++ b/hummingbot/core/data_type/order_book_tracker_entry.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +from hummingbot.core.data_type.order_book import OrderBook + + +class OrderBookTrackerEntry: + def __init__(self, trading_pair: str, timestamp: float, order_book: OrderBook): + self._trading_pair = trading_pair + self._timestamp = timestamp + self._order_book = order_book + + def __repr__(self) -> str: + return ( + f"OrderBookTrackerEntry(trading_pair='{self._trading_pair}', timestamp='{self._timestamp}', " + f"order_book='{self._order_book}')" + ) + + @property + def trading_pair(self) -> str: + return self._trading_pair + + @property + def timestamp(self) -> float: + return self._timestamp + + @property + def order_book(self) -> OrderBook: + return self._order_book diff --git a/hummingbot/core/data_type/order_candidate.py b/hummingbot/core/data_type/order_candidate.py new file mode 100644 index 0000000..ccb4cd7 --- /dev/null +++ b/hummingbot/core/data_type/order_candidate.py @@ -0,0 +1,365 @@ +import typing +from collections import defaultdict +from dataclasses import dataclass, field +from decimal import Decimal +from typing import Dict, List, Optional + +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee, build_trade_fee + +if typing.TYPE_CHECKING: # avoid circular import problems + from hummingbot.connector.exchange_base import ExchangeBase + + +@dataclass +class OrderCandidate: + """ + WARNING: Do not use this class for sizing. Instead, use the `BudgetChecker`. + + This class contains a full picture of the impact of a potential order on the user account. + + It can return a dictionary with the base collateral required for an order, the percentage-fee collateral + and the fixed-fee collaterals, and any combination of those. In addition, it contains a field sizing + the potential return of an order. + + It also provides logic to adjust the order size, the collateral values, and the return based on + a dictionary of currently available assets in the user account. + """ + trading_pair: str + is_maker: bool + order_type: OrderType + order_side: TradeType + amount: Decimal + price: Decimal + order_collateral: Optional[TokenAmount] = field(default=None, init=False) + percent_fee_collateral: Optional[TokenAmount] = field(default=None, init=False) + percent_fee_value: Optional[TokenAmount] = field(default=None, init=False) + fixed_fee_collaterals: List[TokenAmount] = field(default=list, init=False) + potential_returns: Optional[TokenAmount] = field(default=None, init=False) + resized: bool = field(default=False, init=False) + from_total_balances: bool = False + + @property + def collateral_dict(self) -> Dict: + cd = defaultdict(lambda: Decimal("0")) + if self.order_collateral is not None: + cd[self.order_collateral.token] += self.order_collateral.amount + if self.percent_fee_collateral is not None: + cd[self.percent_fee_collateral.token] += self.percent_fee_collateral.amount + for entry in self.fixed_fee_collaterals: + cd[entry.token] += entry.amount + return cd + + @property + def is_zero_order(self) -> bool: + return self.amount == Decimal("0") + + def get_size_token_and_order_size(self) -> TokenAmount: + trading_pair = self.trading_pair + base, quote = split_hb_trading_pair(trading_pair) + if self.order_side == TradeType.BUY: + order_size = self.amount * self.price + size_token = quote + else: + order_size = self.amount + size_token = base + return TokenAmount(size_token, order_size) + + def set_to_zero(self): + self._scale_order(scaler=Decimal("0")) + + def populate_collateral_entries(self, exchange: 'ExchangeBase'): + self._populate_order_collateral_entry(exchange) + fee = self._get_fee(exchange) + self._populate_percent_fee_collateral_entry(exchange, fee) + self._populate_fixed_fee_collateral_entries(fee) + self._populate_potential_returns_entry(exchange) + self._populate_percent_fee_value(exchange, fee) + self._apply_fee_impact_on_potential_returns(exchange, fee) + + def adjust_from_balances(self, available_balances: Dict[str, Decimal]): + if not self.is_zero_order: + self._adjust_for_order_collateral(available_balances) + if not self.is_zero_order: + self._adjust_for_percent_fee_collateral(available_balances) + if not self.is_zero_order: + self._adjust_for_fixed_fee_collaterals(available_balances) + + def _populate_order_collateral_entry(self, exchange: 'ExchangeBase'): + oc_token = self._get_order_collateral_token(exchange) + if oc_token is not None: + oc_amount = self._get_order_collateral_amount(exchange, oc_token) + self.order_collateral = TokenAmount(oc_token, oc_amount) + + def _get_order_collateral_token(self, exchange: 'ExchangeBase') -> Optional[str]: + trading_pair = self.trading_pair + base, quote = split_hb_trading_pair(trading_pair) + if self.order_side == TradeType.BUY: + oc_token = quote + else: + oc_token = base + return oc_token + + def _get_order_collateral_amount( + self, exchange: 'ExchangeBase', order_collateral_token: str + ) -> Decimal: + size_token, order_size = self.get_size_token_and_order_size() + size_collateral_price = self._get_size_collateral_price(exchange, order_collateral_token) + oc_amount = order_size * size_collateral_price + return oc_amount + + def _populate_percent_fee_collateral_entry(self, exchange: 'ExchangeBase', fee: TradeFeeBase): + impact = fee.get_fee_impact_on_order_cost(self, exchange) + if impact is not None: + token, amount = impact + self.percent_fee_collateral = TokenAmount(token, amount) + + def _populate_fixed_fee_collateral_entries(self, fee: TradeFeeBase): + self.fixed_fee_collaterals = [] + for token, amount in fee.flat_fees: + self.fixed_fee_collaterals.append( + TokenAmount(token, amount)) + + def _populate_potential_returns_entry(self, exchange: 'ExchangeBase'): + r_token = self._get_returns_token(exchange) + if r_token is not None: + r_amount = self._get_returns_amount(exchange) + self.potential_returns = TokenAmount(r_token, r_amount) + + def _populate_percent_fee_value(self, exchange: 'ExchangeBase', fee: TradeFeeBase): + cost_impact = fee.get_fee_impact_on_order_cost(self, exchange) + if cost_impact is not None: + self.percent_fee_value = cost_impact + else: + returns_impact = fee.get_fee_impact_on_order_returns(self, exchange) + if returns_impact is not None: + impact_token = self.potential_returns.token + self.percent_fee_value = TokenAmount(impact_token, returns_impact) + + def _apply_fee_impact_on_potential_returns(self, exchange: 'ExchangeBase', fee: TradeFeeBase): + if self.potential_returns is not None: + impact = fee.get_fee_impact_on_order_returns(self, exchange) + if impact is not None: + self.potential_returns.amount -= impact + + def _get_returns_token(self, exchange: 'ExchangeBase') -> Optional[str]: + trading_pair = self.trading_pair + base, quote = split_hb_trading_pair(trading_pair) + if self.order_side == TradeType.BUY: + r_token = base + else: + r_token = quote + return r_token + + def _get_returns_amount(self, exchange: 'ExchangeBase') -> Decimal: + if self.order_side == TradeType.BUY: + r_amount = self.amount + else: + r_amount = self.amount * self.price + return r_amount + + def _get_size_collateral_price( + self, exchange: 'ExchangeBase', order_collateral_token: str + ) -> Decimal: + size_token, _ = self.get_size_token_and_order_size() + base, quote = split_hb_trading_pair(self.trading_pair) + + if order_collateral_token == size_token: + price = Decimal("1") + elif order_collateral_token == base: # size_token == quote + price = Decimal("1") / self.price + elif order_collateral_token == quote: # size_token == base + price = self.price + else: + size_collateral_pair = combine_to_hb_trading_pair(size_token, order_collateral_token) + price = exchange.get_price(size_collateral_pair, is_buy=True) # we are buying + + return price + + def _adjust_for_order_collateral(self, available_balances: Dict[str, Decimal]): + if self.order_collateral is not None: + token, amount = self.order_collateral + if not amount.is_nan() and available_balances[token] < amount: + scaler = available_balances[token] / amount + self._scale_order(scaler) + + def _adjust_for_percent_fee_collateral(self, available_balances: Dict[str, Decimal]): + if self.percent_fee_collateral is not None: + token, amount = self.percent_fee_collateral + if token == self.order_collateral.token: + amount += self.order_collateral.amount + if available_balances[token] < amount: + scaler = available_balances[token] / amount + self._scale_order(scaler) + + def _adjust_for_fixed_fee_collaterals(self, available_balances: Dict[str, Decimal]): + oc_token = self.order_collateral.token if self.order_collateral is not None else None + pfc_token = self.percent_fee_collateral.token if self.percent_fee_collateral is not None else None + oc_amount, pfc_amount = self._get_order_and_pf_collateral_amounts_for_ff_adjustment() + + for collateral_entry in self.fixed_fee_collaterals: + ffc_token, ffc_amount = collateral_entry + available_balance = available_balances[ffc_token] + if available_balance < ffc_amount: + self._scale_order(scaler=Decimal("0")) + break + if oc_token is not None and ffc_token == oc_token and available_balance < ffc_amount + oc_amount: + scaler = (available_balance - ffc_amount) / oc_amount + self._scale_order(scaler) + oc_amount, pfc_amount = self._get_order_and_pf_collateral_amounts_for_ff_adjustment() + if pfc_token is not None and ffc_token == pfc_token and available_balance < ffc_amount + pfc_amount: + scaler = (available_balance - ffc_amount) / pfc_amount + self._scale_order(scaler) + oc_amount, pfc_amount = self._get_order_and_pf_collateral_amounts_for_ff_adjustment() + if self.is_zero_order: + break + + def _get_order_and_pf_collateral_amounts_for_ff_adjustment(self) -> TokenAmount: + if self.order_collateral is not None: + oc_token, oc_amount = self.order_collateral + else: + oc_token = None + oc_amount = Decimal("0") + if self.percent_fee_collateral is not None: + pfc_token, pfc_amount = self.percent_fee_collateral + if oc_token is not None and pfc_token == oc_token: + oc_amount += pfc_amount + pfc_amount = Decimal("0") + else: + pfc_amount = Decimal("0") + return TokenAmount(oc_amount, pfc_amount) + + def _get_fee(self, exchange: 'ExchangeBase') -> TradeFeeBase: + trading_pair = self.trading_pair + price = self.price + base, quote = split_hb_trading_pair(trading_pair) + fee = build_trade_fee( + exchange.name, + self.is_maker, + base, + quote, + self.order_type, + self.order_side, + self.amount, + price, + ) + + return fee + + def _scale_order(self, scaler: Decimal): + self.amount *= scaler + if self.order_collateral is not None: + self.order_collateral.amount *= scaler + if self.percent_fee_collateral is not None: + self.percent_fee_collateral.amount *= scaler + if self.percent_fee_value is not None: + self.percent_fee_value.amount *= scaler + if self.potential_returns is not None: + self.potential_returns.amount *= scaler + if self.is_zero_order: + self.order_collateral = None + self.percent_fee_collateral = None + self.percent_fee_value = None + self.fixed_fee_collaterals = [] + self.potential_returns = None + self.resized = True + + +@dataclass +class PerpetualOrderCandidate(OrderCandidate): + leverage: Decimal = Decimal("1") + position_close: bool = False + + def _get_order_collateral_token(self, exchange: 'ExchangeBase') -> Optional[str]: + if self.position_close: + oc_token = None # the contract is the collateral + else: + oc_token = self._get_collateral_token(exchange) + return oc_token + + def _get_order_collateral_amount( + self, exchange: 'ExchangeBase', order_collateral_token: str + ) -> Decimal: + if self.position_close: + oc_amount = Decimal("0") # the contract is the collateral + else: + oc_amount = self._get_collateral_amount(exchange) + return oc_amount + + def _populate_percent_fee_collateral_entry(self, exchange: 'ExchangeBase', fee: TradeFeeBase): + if not self.position_close: + super()._populate_percent_fee_collateral_entry(exchange, fee) + if ( + self.percent_fee_collateral is not None + and self.percent_fee_collateral.token == self.order_collateral.token + ): + leverage = self.leverage + self.percent_fee_collateral.amount *= leverage + + def _populate_percent_fee_value(self, exchange: 'ExchangeBase', fee: TradeFeeBase): + if not self.position_close: + super()._populate_percent_fee_value(exchange, fee) + if ( + self.percent_fee_value is not None + and self.percent_fee_value.token == self.order_collateral.token + ): + leverage = self.leverage + self.percent_fee_value.amount *= leverage + + def _get_returns_token(self, exchange: 'ExchangeBase') -> Optional[str]: + if self.position_close: + r_token = self._get_collateral_token(exchange) + else: + r_token = None # the contract is the returns + return r_token + + def _get_returns_amount(self, exchange: 'ExchangeBase') -> Decimal: + if self.position_close: + r_amount = self._get_collateral_amount(exchange) + else: + r_amount = Decimal("0") # the contract is the returns + return r_amount + + def _get_collateral_amount(self, exchange: 'ExchangeBase') -> Decimal: + if self.position_close: + self._flip_order_side() + size_token, order_size = self.get_size_token_and_order_size() + if self.position_close: + self._flip_order_side() + order_token = self._get_collateral_token(exchange) + size_collateral_price = self._get_size_collateral_price(exchange, order_token) + amount = order_size * size_collateral_price / self.leverage + return amount + + def _get_collateral_token(self, exchange: 'ExchangeBase') -> str: + trading_pair = self.trading_pair + if self.order_side == TradeType.BUY: + token = exchange.get_buy_collateral_token(trading_pair) + else: + token = exchange.get_sell_collateral_token(trading_pair) + return token + + def _flip_order_side(self): + self.order_side = ( + TradeType.BUY if self.order_side == TradeType.SELL + else TradeType.SELL + ) + + def _get_fee(self, exchange: 'ExchangeBase') -> TradeFeeBase: + base, quote = split_hb_trading_pair(self.trading_pair) + position_action = PositionAction.CLOSE if self.position_close else PositionAction.OPEN + fee = build_perpetual_trade_fee( + exchange.name, + self.is_maker, + position_action, + base, + quote, + self.order_type, + self.order_side, + self.amount, + self.price, + ) + + return fee diff --git a/hummingbot/core/data_type/order_expiration_entry.pxd b/hummingbot/core/data_type/order_expiration_entry.pxd new file mode 100644 index 0000000..e5548a3 --- /dev/null +++ b/hummingbot/core/data_type/order_expiration_entry.pxd @@ -0,0 +1,11 @@ +# distutils: language=c++ + +from hummingbot.core.data_type.OrderExpirationEntry cimport OrderExpirationEntry as CPPOrderExpirationEntry + + +cdef class OrderExpirationEntry: + cdef: + CPPOrderExpirationEntry _cpp_order_expiration_entry + + +cdef OrderExpirationEntry c_create_order_expiration_from_cpp_order_expiration(const CPPOrderExpirationEntry cpp_order_expiration_entry) diff --git a/hummingbot/core/data_type/order_expiration_entry.pyx b/hummingbot/core/data_type/order_expiration_entry.pyx new file mode 100644 index 0000000..694bfb7 --- /dev/null +++ b/hummingbot/core/data_type/order_expiration_entry.pyx @@ -0,0 +1,69 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderExpirationEntry.cpp + +from libcpp.string cimport string +import pandas as pd +from typing import List + +cdef class OrderExpirationEntry: + @classmethod + def to_pandas(cls, order_expiration_entries: List[OrderExpirationEntry]) -> pd.DataFrame: + cdef: + list columns = ["trading_pair", "order_id", "timestamp", "expiration", "expiration_time"] + list data = [[ + expiration_entry.trading_pair, + expiration_entry.order_id, + expiration_entry.timestamp, + expiration_entry.expiration, + expiration_entry.expiration_time + ] for expiration_entry in order_expiration_entries] + + return pd.DataFrame(data=data, columns=columns) + + def __init__(self, + trading_pair: str, + order_id: str, + timestamp: float, + expiration_ts: float + ): + cdef: + string cpp_trading_pair = trading_pair.encode("utf8") + string cpp_order_id = order_id.encode("utf8") + double cpp_timestamp = timestamp + double cpp_expiration_ts = expiration_ts + + self._cpp_order_expiration_entry = CPPOrderExpirationEntry(cpp_trading_pair, + cpp_order_id, + cpp_timestamp, + cpp_expiration_ts) + + @property + def trading_pair(self) -> str: + cdef: + string cpp_trading_pair = self._cpp_order_expiration_entry.getTradingPair() + str retval = cpp_trading_pair.decode("utf8") + return retval + + @property + def order_id(self) -> str: + cdef: + string cpp_client_order_id = self._cpp_order_expiration_entry.getClientOrderID() + str retval = cpp_client_order_id.decode("utf8") + return retval + + @property + def timestamp(self) -> float: + return self._cpp_order_expiration_entry.getTimestamp() + + @property + def expiration_timestamp(self) -> float: + return self._cpp_order_expiration_entry.getExpirationTimestamp() + + def __repr__(self) -> str: + return f"OrderExpirationEntry('{self.trading_pair}', '{self.order_id}', {self.timestamp}, '{self.expiration_timestamp}')" + + +cdef OrderExpirationEntry c_create_order_expiration_from_cpp_order_expiration(const CPPOrderExpirationEntry cpp_order_expiration_entry): + cdef OrderExpirationEntry retval = OrderExpirationEntry.__new__(OrderExpirationEntry) + retval._cpp_order_expiration_entry = cpp_order_expiration_entry + return retval diff --git a/hummingbot/core/data_type/perpetual_api_order_book_data_source.py b/hummingbot/core/data_type/perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..1683bf9 --- /dev/null +++ b/hummingbot/core/data_type/perpetual_api_order_book_data_source.py @@ -0,0 +1,45 @@ +import asyncio +from abc import ABC, abstractmethod +from typing import Any, Dict, List + +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource + + +class PerpetualAPIOrderBookDataSource(OrderBookTrackerDataSource, ABC): + def __init__(self, trading_pairs: List[str]): + super().__init__(trading_pairs) + self._funding_info_messages_queue_key = "funding_info" + + @abstractmethod + async def get_funding_info(self, trading_pair: str) -> FundingInfo: + """ + Return the funding information for a single trading pair. + """ + raise NotImplementedError + + async def listen_for_funding_info(self, output: asyncio.Queue): + """ + Reads the funding info events queue and updates the local funding info information. + """ + message_queue = self._message_queue[self._funding_info_messages_queue_key] + while True: + try: + funding_info_event = await message_queue.get() + await self._parse_funding_info_message(raw_message=funding_info_event, message_queue=output) + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception("Unexpected error when processing public funding info updates from exchange") + + @abstractmethod + async def _parse_funding_info_message(self, raw_message: Dict[str, Any], message_queue: asyncio.Queue): + raise NotImplementedError + + def _get_messages_queue_keys(self) -> List[str]: + return [ + self._snapshot_messages_queue_key, + self._diff_messages_queue_key, + self._trade_messages_queue_key, + self._funding_info_messages_queue_key, + ] diff --git a/hummingbot/core/data_type/remote_api_order_book_data_source.py b/hummingbot/core/data_type/remote_api_order_book_data_source.py new file mode 100755 index 0000000..d6ad035 --- /dev/null +++ b/hummingbot/core/data_type/remote_api_order_book_data_source.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +import asyncio +import aiohttp +import base64 +import logging +import pandas as pd +from typing import ( + Dict, + Optional, + Tuple, + AsyncIterable +) +import pickle +import time +import websockets +from websockets.exceptions import ConnectionClosed + +import conf +from hummingbot.connector.exchange.binance.binance_order_book import BinanceOrderBook +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.logger import HummingbotLogger +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry + + +class RemoteAPIOrderBookDataSource(OrderBookTrackerDataSource): + SNAPSHOT_REST_URL = "https://api.coinalpha.com/order_book_tracker/snapshot" + SNAPSHOT_STREAM_URL = "wss://api.coinalpha.com/ws/order_book_tracker/snapshot_stream" + DIFF_STREAM_URL = "wss://api.coinalpha.com/ws/order_book_tracker/diff_stream" + + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + + _raobds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._raobds_logger is None: + cls._raobds_logger = logging.getLogger(__name__) + return cls._raobds_logger + + def __init__(self): + super().__init__() + self._client_session: Optional[aiohttp.ClientSession] = None + + @property + def authentication_headers(self) -> Dict[str, str]: + auth_str: str = f"{conf.coinalpha_order_book_api_username}:{conf.coinalpha_order_book_api_password}" + encoded_auth: str = base64.standard_b64encode(auth_str.encode("utf8")).decode("utf8") + return { + "Authorization": f"Basic {encoded_auth}" + } + + async def get_client_session(self) -> aiohttp.ClientSession: + if self._client_session is None: + self._client_session = aiohttp.ClientSession() + return self._client_session + + async def get_tracking_pairs(self) -> Dict[str, OrderBookTrackerEntry]: + auth: aiohttp.BasicAuth = aiohttp.BasicAuth(login=conf.coinalpha_order_book_api_username, + password=conf.coinalpha_order_book_api_password) + client_session: aiohttp.ClientSession = await self.get_client_session() + response: aiohttp.ClientResponse = await client_session.get(self.SNAPSHOT_REST_URL, auth=auth) + timestamp: float = time.time() + if response.status != 200: + raise EnvironmentError(f"Error fetching order book tracker snapshot from {self.SNAPSHOT_REST_URL}.") + + binary_data: bytes = await response.read() + order_book_tracker_data: Dict[str, Tuple[pd.DataFrame, pd.DataFrame]] = pickle.loads(binary_data) + retval: Dict[str, OrderBookTrackerEntry] = {} + + for trading_pair, (bids_df, asks_df) in order_book_tracker_data.items(): + order_book: BinanceOrderBook = BinanceOrderBook() + order_book.apply_numpy_snapshot(bids_df.values, asks_df.values) + retval[trading_pair] = OrderBookTrackerEntry(trading_pair, timestamp, order_book) + + return retval + + async def _inner_messages(self, + ws: websockets.WebSocketClientProtocol) -> AsyncIterable[str]: + # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. + try: + while True: + try: + msg: str = await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) + yield msg + except asyncio.TimeoutError: + pong_waiter = await ws.ping() + await asyncio.wait_for(pong_waiter, timeout=self.PING_TIMEOUT) + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + return + except ConnectionClosed: + return + finally: + await ws.close() + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + async with websockets.connect(self.DIFF_STREAM_URL, + extra_headers=self.authentication_headers) as ws: + ws: websockets.WebSocketClientProtocol = ws + async for msg in self._inner_messages(ws): + output.put_nowait(msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", + exc_info=True) + await asyncio.sleep(30.0) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + async with websockets.connect(self.SNAPSHOT_STREAM_URL, + extra_headers=self.authentication_headers) as ws: + ws: websockets.WebSocketClientProtocol = ws + async for msg in self._inner_messages(ws): + output.put_nowait(msg) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error with WebSocket connection. Retrying after 30 seconds...", + exc_info=True) + await asyncio.sleep(30.0) diff --git a/hummingbot/core/data_type/trade.py b/hummingbot/core/data_type/trade.py new file mode 100644 index 0000000..bb58ff2 --- /dev/null +++ b/hummingbot/core/data_type/trade.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +from collections import namedtuple +from datetime import datetime +from typing import List + +import pandas as pd + +from hummingbot.core.data_type.trade_fee import TradeFeeBase +from hummingbot.core.data_type.common import OrderType, TradeType + + +class Trade(namedtuple("_Trade", "trading_pair, side, price, amount, order_type, market, timestamp, trade_fee")): + trading_pair: str + side: TradeType + price: float + amount: float + order_type: OrderType + market: str + timestamp: float + trade_fee: TradeFeeBase + + @classmethod + def to_pandas(cls, trades: List): + columns: List[str] = ["trading_pair", + "price", + "quantity", + "order_type", + "trade_side", + "market", + "timestamp", + "fee_percent", + "flat_fee / gas"] + data = [] + for trade in trades: + if len(trade.trade_fee.flat_fees) == 0: + flat_fee_str = "None" + else: + fee_strs = [f"{fee_tuple[0]} {fee_tuple[1]}" for fee_tuple in trade.trade_fee.flat_fees] + flat_fee_str = ",".join(fee_strs) + + data.append([ + trade.trading_pair, + trade.price, + trade.amount, + trade.order_type.name.lower(), + trade.side.name.lower(), + trade.market, + datetime.fromtimestamp(trade.timestamp).strftime("%Y-%m-%d %H:%M:%S"), + trade.trade_fee.percent, + flat_fee_str, + ]) + + return pd.DataFrame(data=data, columns=columns) + + @property + def trade_type(self): + return self.side.name diff --git a/hummingbot/core/data_type/trade_fee.py b/hummingbot/core/data_type/trade_fee.py new file mode 100644 index 0000000..c1fcc01 --- /dev/null +++ b/hummingbot/core/data_type/trade_fee.py @@ -0,0 +1,319 @@ +import typing +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from decimal import Decimal +from typing import Any, Dict, List, Optional, Type + +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.data_type.common import PositionAction, PriceType, TradeType + +if typing.TYPE_CHECKING: # avoid circular import problems + from hummingbot.connector.exchange_base import ExchangeBase + from hummingbot.core.data_type.order_candidate import OrderCandidate + from hummingbot.core.rate_oracle.rate_oracle import RateOracle + +S_DECIMAL_0 = Decimal(0) + + +@dataclass +class TokenAmount: + token: str + amount: Decimal + + def __iter__(self): + return iter((self.token, self.amount)) + + def to_json(self) -> Dict[str, Any]: + return { + "token": self.token, + "amount": str(self.amount), + } + + @classmethod + def from_json(cls, data: Dict[str, Any]): + instance = TokenAmount(token=data["token"], amount=Decimal(data["amount"])) + return instance + + +@dataclass +class TradeFeeSchema: + """ + Contains the necessary information to build a `TradeFee` object. + + NOTE: Currently, `percent_fee_token` is only specified if the percent fee is always charged in a particular + token (e.g. the Binance BNB case). To always populate the `percent_fee_token`, this class will require + access to the `exchange` class at runtime to determine the collateral token for the trade (e.g. for derivatives). + This means that, if the `percent_fee_token` is specified, then the fee is always added to the trade + costs, and `buy_percent_fee_deducted_from_returns` cannot be set to `True`. + """ + percent_fee_token: Optional[str] = None + maker_percent_fee_decimal: Decimal = S_DECIMAL_0 + taker_percent_fee_decimal: Decimal = S_DECIMAL_0 + buy_percent_fee_deducted_from_returns: bool = False + maker_fixed_fees: List[TokenAmount] = field(default_factory=list) + taker_fixed_fees: List[TokenAmount] = field(default_factory=list) + + def __post_init__(self): + self.validate_schema() + + def validate_schema(self): + if self.percent_fee_token is not None: + assert not self.buy_percent_fee_deducted_from_returns + self.maker_percent_fee_decimal = Decimal(self.maker_percent_fee_decimal) + self.taker_percent_fee_decimal = Decimal(self.taker_percent_fee_decimal) + for i in range(len(self.taker_fixed_fees)): + self.taker_fixed_fees[i] = TokenAmount( + self.taker_fixed_fees[i].token, Decimal(self.taker_fixed_fees[i].amount) + ) + for i in range(len(self.maker_fixed_fees)): + self.maker_fixed_fees[i] = TokenAmount( + self.maker_fixed_fees[i].token, Decimal(self.maker_fixed_fees[i].amount) + ) + + +@dataclass +class TradeFeeBase(ABC): + """ + Contains the necessary information to apply the trade fee to a particular order. + """ + percent: Decimal = S_DECIMAL_0 + percent_token: Optional[str] = None # only set when fee charged in third token (the Binance BNB case) + flat_fees: List[TokenAmount] = field(default_factory=list) # list of (asset, amount) tuples + + @classmethod + @abstractmethod + def type_descriptor_for_json(cls) -> str: + ... + + @classmethod + def fee_class_for_type(cls, type_descriptor: str): + catalog = {fee_class.type_descriptor_for_json(): fee_class + for fee_class + in [AddedToCostTradeFee, DeductedFromReturnsTradeFee]} + return catalog[type_descriptor] + + @classmethod + def new_spot_fee(cls, + fee_schema: TradeFeeSchema, + trade_type: TradeType, + percent: Decimal = S_DECIMAL_0, + percent_token: Optional[str] = None, + flat_fees: Optional[List[TokenAmount]] = None) -> "TradeFeeBase": + fee_cls: Type[TradeFeeBase] = ( + AddedToCostTradeFee + if (trade_type == TradeType.BUY and + (not fee_schema.buy_percent_fee_deducted_from_returns + or fee_schema.percent_fee_token is not None)) + else DeductedFromReturnsTradeFee) + return fee_cls( + percent=percent, + percent_token=percent_token, + flat_fees=flat_fees or [] + ) + + @classmethod + def new_perpetual_fee(cls, + fee_schema: TradeFeeSchema, + position_action: PositionAction, + percent: Decimal = S_DECIMAL_0, + percent_token: Optional[str] = None, + flat_fees: Optional[List[TokenAmount]] = None) -> "TradeFeeBase": + fee_cls: Type[TradeFeeBase] = ( + AddedToCostTradeFee + if position_action == PositionAction.OPEN or fee_schema.percent_fee_token is not None + else DeductedFromReturnsTradeFee + ) + return fee_cls( + percent=percent, + percent_token=percent_token, + flat_fees=flat_fees or [] + ) + + @classmethod + def from_json(cls, data: Dict[str, Any]): + fee_class = cls.fee_class_for_type(data["fee_type"]) + instance = fee_class( + percent=Decimal(data["percent"]), + percent_token=data["percent_token"], + flat_fees=list(map(TokenAmount.from_json, data["flat_fees"])) + ) + return instance + + def to_json(self) -> Dict[str, any]: + return { + "fee_type": self.type_descriptor_for_json(), + "percent": str(self.percent), + "percent_token": self.percent_token, + "flat_fees": [token_amount.to_json() for token_amount in self.flat_fees] + } + + @property + def fee_asset(self): + first_flat_fee_token = None + if len(self.flat_fees) > 0: + first_flat_fee_token = self.flat_fees[0].token + return self.percent_token or first_flat_fee_token + + @abstractmethod + def get_fee_impact_on_order_cost( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[TokenAmount]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the cost requirements for the candidate order. + """ + ... + + @abstractmethod + def get_fee_impact_on_order_returns( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[Decimal]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the expected returns from the candidate order. + """ + ... + + @staticmethod + def _get_exchange_rate( + trading_pair: str, + exchange: Optional["ExchangeBase"] = None, + rate_source: Optional["RateOracle"] = None # noqa: F821 + ) -> Decimal: + from hummingbot.core.rate_oracle.rate_oracle import RateOracle + + if exchange is not None and trading_pair in exchange.order_books: + rate = exchange.get_price_by_type(trading_pair, PriceType.MidPrice) + else: + local_rate_source: Optional[RateOracle] = rate_source or RateOracle.get_instance() + rate: Decimal = local_rate_source.get_pair_rate(trading_pair) + if rate is None: + raise ValueError(f"Could not find the exchange rate for {trading_pair} using the rate source " + f"{local_rate_source} (please verify it has been correctly configured)") + return rate + + def fee_amount_in_token( + self, + trading_pair: str, + price: Decimal, + order_amount: Decimal, + token: str, + exchange: Optional["ExchangeBase"] = None, + rate_source: Optional["RateOracle"] = None # noqa: F821 + ) -> Decimal: + base, quote = split_hb_trading_pair(trading_pair) + fee_amount: Decimal = S_DECIMAL_0 + if self.percent != S_DECIMAL_0: + amount_from_percentage: Decimal = (price * order_amount) * self.percent + if self._are_tokens_interchangeable(quote, token): + fee_amount += amount_from_percentage + else: + conversion_pair: str = combine_to_hb_trading_pair(base=quote, quote=token) + conversion_rate: Decimal = self._get_exchange_rate(conversion_pair, exchange, rate_source) + fee_amount += amount_from_percentage * conversion_rate + for flat_fee in self.flat_fees: + if self._are_tokens_interchangeable(flat_fee.token, token): + # No need to convert the value + fee_amount += flat_fee.amount + elif (self._are_tokens_interchangeable(flat_fee.token, base) + and (self._are_tokens_interchangeable(quote, token))): + # In this case instead of looking for the rate we use directly the price in the parameters + fee_amount += flat_fee.amount * price + else: + conversion_pair: str = combine_to_hb_trading_pair(base=flat_fee.token, quote=token) + conversion_rate: Decimal = self._get_exchange_rate(conversion_pair, exchange, rate_source) + fee_amount += (flat_fee.amount * conversion_rate) + return fee_amount + + def _are_tokens_interchangeable(self, first_token: str, second_token: str): + interchangeable_tokens = [ + {"WETH", "ETH"}, + {"WBNB", "BNB"}, + {"WMATIC", "MATIC"}, + {"WAVAX", "AVAX"}, + {"WONE", "ONE"}, + {"USDC", "USDC.E"}, + {"WBTC", "BTC"} + ] + return first_token == second_token or any(({first_token, second_token} <= interchangeable_pair + for interchangeable_pair + in interchangeable_tokens)) + + +class AddedToCostTradeFee(TradeFeeBase): + + @classmethod + def type_descriptor_for_json(cls) -> str: + return "AddedToCost" + + def get_fee_impact_on_order_cost( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[TokenAmount]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the cost requirements for the candidate order. + """ + ret = None + if self.percent != S_DECIMAL_0: + fee_token = self.percent_token or order_candidate.order_collateral.token + if order_candidate.order_collateral is None or fee_token != order_candidate.order_collateral.token: + token, size = order_candidate.get_size_token_and_order_size() + if fee_token == token: + exchange_rate = Decimal("1") + else: + exchange_pair = combine_to_hb_trading_pair(token, fee_token) # buy order token w/ pf token + exchange_rate = exchange.get_price(exchange_pair, is_buy=True) + fee_amount = size * exchange_rate * self.percent + else: # self.percent_token == order_candidate.order_collateral.token + fee_amount = order_candidate.order_collateral.amount * self.percent + ret = TokenAmount(fee_token, fee_amount) + return ret + + def get_fee_impact_on_order_returns( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[Decimal]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the expected returns from the candidate order. + """ + return None + + +class DeductedFromReturnsTradeFee(TradeFeeBase): + + @classmethod + def type_descriptor_for_json(cls) -> str: + return "DeductedFromReturns" + + def get_fee_impact_on_order_cost( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[TokenAmount]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the cost requirements for the candidate order. + """ + return None + + def get_fee_impact_on_order_returns( + self, order_candidate: "OrderCandidate", exchange: "ExchangeBase" + ) -> Optional[Decimal]: + """ + WARNING: Do not use this method for sizing. Instead, use the `BudgetChecker`. + + Returns the impact of the fee on the expected returns from the candidate order. + """ + impact = order_candidate.potential_returns.amount * self.percent + return impact + + +@dataclass(frozen=True) +class MakerTakerExchangeFeeRates: + maker: Decimal + taker: Decimal + maker_flat_fees: List[TokenAmount] + taker_flat_fees: List[TokenAmount] diff --git a/hummingbot/core/data_type/transaction_tracker.pxd b/hummingbot/core/data_type/transaction_tracker.pxd new file mode 100644 index 0000000..1f7a8fb --- /dev/null +++ b/hummingbot/core/data_type/transaction_tracker.pxd @@ -0,0 +1,12 @@ +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class TransactionTracker(TimeIterator): + cdef: + dict _tx_time_limits + + cdef c_start_tx_tracking(self, str tx_id, float timeout_seconds) + cdef c_stop_tx_tracking(self, str tx_id) + cdef bint c_is_tx_tracked(self, str tx_id) + cdef c_did_timeout_tx(self, str tx_id) + cdef c_process_tx_timeouts(self) diff --git a/hummingbot/core/data_type/transaction_tracker.pyx b/hummingbot/core/data_type/transaction_tracker.pyx new file mode 100644 index 0000000..75717dc --- /dev/null +++ b/hummingbot/core/data_type/transaction_tracker.pyx @@ -0,0 +1,33 @@ +cdef class TransactionTracker(TimeIterator): + def __init__(self): + super().__init__() + self._tx_time_limits = {} + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self.c_process_tx_timeouts() + + cdef c_start_tx_tracking(self, str tx_id, float timeout_seconds): + if tx_id in self._tx_time_limits: + raise ValueError(f"The transaction {tx_id} is already being monitored.") + self._tx_time_limits[tx_id] = self._current_timestamp + timeout_seconds + + cdef c_stop_tx_tracking(self, str tx_id): + if tx_id not in self._tx_time_limits: + return + del self._tx_time_limits[tx_id] + + cdef bint c_is_tx_tracked(self, str tx_id): + return tx_id in self._tx_time_limits + + cdef c_did_timeout_tx(self, str tx_id): + self.c_stop_tx_tracking(tx_id) + + cdef c_process_tx_timeouts(self): + cdef: + list timed_out_tx_ids = [] + for tx_id, time_limit in self._tx_time_limits.items(): + if self._current_timestamp > time_limit: + timed_out_tx_ids.append(tx_id) + for tx_id in timed_out_tx_ids: + self.c_did_timeout_tx(tx_id) diff --git a/hummingbot/core/data_type/user_stream_tracker.py b/hummingbot/core/data_type/user_stream_tracker.py new file mode 100644 index 0000000..695c08f --- /dev/null +++ b/hummingbot/core/data_type/user_stream_tracker.py @@ -0,0 +1,40 @@ +import asyncio +import logging +from typing import Optional + +from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger + + +class UserStreamTracker: + _ust_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._ust_logger is None: + cls._ust_logger = logging.getLogger(__name__) + return cls._ust_logger + + def __init__(self, data_source: UserStreamTrackerDataSource): + self._user_stream: asyncio.Queue = asyncio.Queue() + self._data_source = data_source + self._user_stream_tracking_task: Optional[asyncio.Task] = None + + @property + def data_source(self) -> UserStreamTrackerDataSource: + return self._data_source + + @property + def last_recv_time(self) -> float: + return self.data_source.last_recv_time + + async def start(self): + self._user_stream_tracking_task = safe_ensure_future( + self.data_source.listen_for_user_stream(self._user_stream) + ) + await safe_gather(self._user_stream_tracking_task) + + @property + def user_stream(self) -> asyncio.Queue: + return self._user_stream diff --git a/hummingbot/core/data_type/user_stream_tracker_data_source.py b/hummingbot/core/data_type/user_stream_tracker_data_source.py new file mode 100755 index 0000000..d8a008b --- /dev/null +++ b/hummingbot/core/data_type/user_stream_tracker_data_source.py @@ -0,0 +1,100 @@ +import asyncio +import logging +import time +from abc import ABCMeta +from typing import Any, Dict, Optional + +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.logger import HummingbotLogger + + +class UserStreamTrackerDataSource(metaclass=ABCMeta): + + _logger: Optional[HummingbotLogger] = None + + def __init__(self): + self._ws_assistant: Optional[WSAssistant] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(HummingbotLogger.logger_name_for_class(cls)) + return cls._logger + + @property + def last_recv_time(self) -> float: + """ + Returns the time of the last received message + + :return: the timestamp of the last received message in seconds + """ + if self._ws_assistant: + return self._ws_assistant.last_recv_time + return 0 + + async def listen_for_user_stream(self, output: asyncio.Queue): + """ + Connects to the user private channel in the exchange using a websocket connection. With the established + connection listens to all balance events and order updates provided by the exchange, and stores them in the + output queue + + :param output: the queue to use to store the received messages + """ + while True: + try: + self._ws_assistant = await self._connected_websocket_assistant() + await self._subscribe_channels(websocket_assistant=self._ws_assistant) + await self._send_ping(websocket_assistant=self._ws_assistant) # to update last_recv_timestamp + await self._process_websocket_messages(websocket_assistant=self._ws_assistant, queue=output) + except asyncio.CancelledError: + raise + except ConnectionError as connection_exception: + self.logger().warning(f"The websocket connection was closed ({connection_exception})") + except Exception: + self.logger().exception("Unexpected error while listening to user stream. Retrying after 5 seconds...") + await self._sleep(1.0) + finally: + await self._on_user_stream_interruption(websocket_assistant=self._ws_assistant) + self._ws_assistant = None + + async def _connected_websocket_assistant(self) -> WSAssistant: + """ + Creates an instance of WSAssistant connected to the exchange + + :return: an instance of WSAssistant connected to the exchange + """ + raise NotImplementedError + + async def _subscribe_channels(self, websocket_assistant: WSAssistant): + """ + Subscribes to the trade events and diff orders events through the provided websocket connection. + + :param websocket_assistant: the websocket assistant used to connect to the exchange + """ + raise NotImplementedError + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant, queue: asyncio.Queue): + async for ws_response in websocket_assistant.iter_messages(): + data = ws_response.data + await self._process_event_message(event_message=data, queue=queue) + + async def _process_event_message(self, event_message: Dict[str, Any], queue: asyncio.Queue): + if len(event_message) > 0: + queue.put_nowait(event_message) + + async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): + websocket_assistant and await websocket_assistant.disconnect() + + async def _send_ping(self, websocket_assistant: WSAssistant): + await websocket_assistant.ping() + + async def _sleep(self, delay: float): + """ + Function added only to facilitate patching the sleep in unit tests without affecting the asyncio module + + :param delay: number of seconds to sleep + """ + await asyncio.sleep(delay) + + def _time(self) -> float: + return time.time() diff --git a/hummingbot/core/event/__init__.py b/hummingbot/core/event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/event/event_forwarder.py b/hummingbot/core/event/event_forwarder.py new file mode 100644 index 0000000..540d170 --- /dev/null +++ b/hummingbot/core/event/event_forwarder.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +from typing import Callable + +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.pubsub import PubSub + + +class EventForwarder(EventListener): + def __init__(self, to_function: Callable[[any], None]): + super().__init__() + self._to_function: Callable[[any], None] = to_function + + def __call__(self, arg: any): + self._to_function(arg) + + +class SourceInfoEventForwarder(EventListener): + def __init__(self, to_function: Callable[[int, PubSub, any], None]): + super().__init__() + self._to_function: Callable[[int, PubSub, any], None] = to_function + + def __call__(self, arg: any): + self._to_function(self.current_event_tag, self.current_event_caller, arg) diff --git a/hummingbot/core/event/event_listener.pxd b/hummingbot/core/event/event_listener.pxd new file mode 100644 index 0000000..722c781 --- /dev/null +++ b/hummingbot/core/event/event_listener.pxd @@ -0,0 +1,13 @@ +from libc.stdint cimport int64_t + +from hummingbot.core.pubsub cimport PubSub + + +cdef class EventListener: + cdef: + object __weakref__ + int64_t _current_event_tag + PubSub _current_event_caller + + cdef c_set_event_info(self, int64_t current_event_tag, PubSub current_event_caller) + cdef c_call(self, object arg) diff --git a/hummingbot/core/event/event_listener.pyx b/hummingbot/core/event/event_listener.pyx new file mode 100644 index 0000000..3b56b6c --- /dev/null +++ b/hummingbot/core/event/event_listener.pyx @@ -0,0 +1,25 @@ +from hummingbot.core.pubsub import PubSub + + +cdef class EventListener: + def __init__(self): + self._current_event_tag = 0 + self._current_event_caller = None + + def __call__(self, arg: any): + raise NotImplementedError + + @property + def current_event_tag(self) -> int: + return self._current_event_tag + + @property + def current_event_caller(self) -> PubSub: + return self._current_event_caller + + cdef c_set_event_info(self, int64_t current_event_tag, PubSub current_event_caller): + self._current_event_tag = current_event_tag + self._current_event_caller = current_event_caller + + cdef c_call(self, object arg): + self(arg) diff --git a/hummingbot/core/event/event_logger.pxd b/hummingbot/core/event/event_logger.pxd new file mode 100644 index 0000000..da76c7c --- /dev/null +++ b/hummingbot/core/event/event_logger.pxd @@ -0,0 +1,12 @@ +from .event_listener cimport EventListener + + +cdef class EventLogger(EventListener): + cdef: + str _event_source + object _logged_events + object _generic_logged_events + object _order_filled_logged_events + dict _waiting + dict _wait_returns + cdef c_call(self, object event_object) diff --git a/hummingbot/core/event/event_logger.pyx b/hummingbot/core/event/event_logger.pyx new file mode 100644 index 0000000..a801684 --- /dev/null +++ b/hummingbot/core/event/event_logger.pyx @@ -0,0 +1,63 @@ +import asyncio +from collections import deque + +from async_timeout import timeout +from typing import ( + List, + Optional, +) + +from hummingbot.core.event.event_listener cimport EventListener +from hummingbot.core.event.events import OrderFilledEvent + +cdef class EventLogger(EventListener): + def __init__(self, event_source: Optional[str] = None): + super().__init__() + self._event_source = event_source + # We limit the amount of events we keep reference to the most recent ones + # But we keep all references to order fill events, because they are required for PnL calculation + self._generic_logged_events = deque(maxlen=50) + self._order_filled_logged_events = deque() + self._logged_events = {OrderFilledEvent: self._order_filled_logged_events} + self._waiting = {} + self._wait_returns = {} + + @property + def event_log(self) -> List[any]: + return list(self._generic_logged_events) + list(self._order_filled_logged_events) + + @property + def event_source(self) -> str: + return self._event_source + + def clear(self): + self._generic_logged_events.clear() + self._order_filled_logged_events.clear() + + async def wait_for(self, event_type, timeout_seconds: float = 180): + notifier = asyncio.Event() + self._waiting[notifier] = event_type + + async with timeout(timeout_seconds): + await notifier.wait() + + retval = self._wait_returns.get(notifier) + if notifier in self._wait_returns: + del self._wait_returns[notifier] + del self._waiting[notifier] + return retval + + def __call__(self, event_object): + self.c_call(event_object) + + cdef c_call(self, object event_object): + self._logged_events.get(type(event_object), self._generic_logged_events).append(event_object) + event_object_type = type(event_object) + + should_notify = [] + for notifier, waiting_event_type in self._waiting.items(): + if event_object_type is waiting_event_type: + should_notify.append(notifier) + self._wait_returns[notifier] = event_object + for notifier in should_notify: + notifier.set() diff --git a/hummingbot/core/event/event_reporter.pxd b/hummingbot/core/event/event_reporter.pxd new file mode 100644 index 0000000..3108f8a --- /dev/null +++ b/hummingbot/core/event/event_reporter.pxd @@ -0,0 +1,7 @@ +from .event_listener cimport EventListener + + +cdef class EventReporter(EventListener): + cdef: + str event_source + cdef c_call(self, object event_object) diff --git a/hummingbot/core/event/event_reporter.pyx b/hummingbot/core/event/event_reporter.pyx new file mode 100644 index 0000000..75e1bff --- /dev/null +++ b/hummingbot/core/event/event_reporter.pyx @@ -0,0 +1,34 @@ +import logging +from typing import Optional +import dataclasses +from hummingbot.core.event.event_listener cimport EventListener + +er_logger = None + +cdef class EventReporter(EventListener): + """ + Event listener that log events to logger + """ + def __init__(self, event_source: Optional[str] = None): + super().__init__() + self.event_source = event_source + + @classmethod + def logger(cls): + global er_logger + if er_logger is None: + er_logger = logging.getLogger(__name__) + return er_logger + + cdef c_call(self, object event_object): + try: + if dataclasses.is_dataclass(event_object): + event_dict = dataclasses.asdict(event_object) + else: + event_dict = event_object._asdict() + + event_dict.update({"event_name": event_object.__class__.__name__, + "event_source": self.event_source}) + self.logger().event_log(event_dict) + except Exception: + self.logger().error("Error logging events.", exc_info=True) diff --git a/hummingbot/core/event/events.py b/hummingbot/core/event/events.py new file mode 100644 index 0000000..f53258a --- /dev/null +++ b/hummingbot/core/event/events.py @@ -0,0 +1,347 @@ +from dataclasses import dataclass +from decimal import Decimal +from enum import Enum +from typing import Dict, List, NamedTuple, Optional + +from hummingbot.core.data_type.common import LPType, OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + +s_decimal_0 = Decimal("0") + + +class MarketEvent(Enum): + ReceivedAsset = 101 + BuyOrderCompleted = 102 + SellOrderCompleted = 103 + # Trade = 104 Deprecated + WithdrawAsset = 105 # Locally Deprecated, but still present in hummingsim + OrderCancelled = 106 + OrderFilled = 107 + OrderExpired = 108 + OrderUpdate = 109 + TradeUpdate = 110 + OrderFailure = 198 + TransactionFailure = 199 + BuyOrderCreated = 200 + SellOrderCreated = 201 + FundingPaymentCompleted = 202 + FundingInfo = 203 + RangePositionLiquidityAdded = 300 + RangePositionLiquidityRemoved = 301 + RangePositionUpdate = 302 + RangePositionUpdateFailure = 303 + RangePositionFeeCollected = 304 + RangePositionClosed = 305 + + +class OrderBookEvent(int, Enum): + TradeEvent = 901 + OrderBookDataSourceUpdateEvent = 904 + + +class OrderBookDataSourceEvent(int, Enum): + SNAPSHOT_EVENT = 1001 + DIFF_EVENT = 1002 + TRADE_EVENT = 1003 + + +class TokenApprovalEvent(Enum): + ApprovalSuccessful = 1101 + ApprovalFailed = 1102 + ApprovalCancelled = 1103 + + +class HummingbotUIEvent(Enum): + Start = 1 + + +class AccountEvent(Enum): + PositionModeChangeSucceeded = 400 + PositionModeChangeFailed = 401 + BalanceEvent = 402 + PositionUpdate = 403 + MarginCall = 404 + LiquidationEvent = 405 + + +class MarketTransactionFailureEvent(NamedTuple): + timestamp: float + order_id: str + + +class MarketOrderFailureEvent(NamedTuple): + timestamp: float + order_id: str + order_type: OrderType + + +@dataclass +class BuyOrderCompletedEvent: + timestamp: float + order_id: str + base_asset: str + quote_asset: str + base_asset_amount: Decimal + quote_asset_amount: Decimal + order_type: OrderType + exchange_order_id: Optional[str] = None + + +@dataclass +class SellOrderCompletedEvent: + timestamp: float + order_id: str + base_asset: str + quote_asset: str + base_asset_amount: Decimal + quote_asset_amount: Decimal + order_type: OrderType + exchange_order_id: Optional[str] = None + + +@dataclass +class OrderCancelledEvent: + timestamp: float + order_id: str + exchange_order_id: Optional[str] = None + + +class OrderExpiredEvent(NamedTuple): + timestamp: float + order_id: str + + +@dataclass +class TokenApprovalSuccessEvent: + timestamp: float + connector: str + token_symbol: str + + +@dataclass +class TokenApprovalFailureEvent: + timestamp: float + connector: str + token_symbol: str + + +@dataclass +class TokenApprovalCancelledEvent: + timestamp: float + connector: str + token_symbol: str + + +@dataclass +class FundingPaymentCompletedEvent: + timestamp: float + market: str + trading_pair: str + amount: Decimal + funding_rate: Decimal + + +class OrderBookTradeEvent(NamedTuple): + trading_pair: str + timestamp: float + type: TradeType + price: Decimal + amount: Decimal + trade_id: Optional[str] = None + is_taker: bool = True # CEXs deliver trade events from the taker's perspective + + +class OrderFilledEvent(NamedTuple): + timestamp: float + order_id: str + trading_pair: str + trade_type: TradeType + order_type: OrderType + price: Decimal + amount: Decimal + trade_fee: TradeFeeBase + exchange_trade_id: str = "" + exchange_order_id: str = "" + leverage: Optional[int] = 1 + position: Optional[str] = PositionAction.NIL.value + + @classmethod + def order_filled_events_from_order_book_rows( + cls, + timestamp: float, + order_id: str, + trading_pair: str, + trade_type: TradeType, + order_type: OrderType, + trade_fee: TradeFeeBase, + order_book_rows: List[OrderBookRow], + exchange_trade_id: Optional[str] = None, + ) -> List["OrderFilledEvent"]: + if exchange_trade_id is None: + exchange_trade_id = order_id + return [ + OrderFilledEvent( + timestamp, + order_id, + trading_pair, + trade_type, + order_type, + Decimal(row.price), + Decimal(row.amount), + trade_fee, + exchange_trade_id=f"{exchange_trade_id}_{index}", + ) + for index, row in enumerate(order_book_rows) + ] + + @classmethod + def order_filled_event_from_binance_execution_report(cls, execution_report: Dict[str, any]) -> "OrderFilledEvent": + execution_type: str = execution_report.get("x") + if execution_type != "TRADE": + raise ValueError(f"Invalid execution type '{execution_type}'.") + return OrderFilledEvent( + execution_report["E"] * 1e-3, + execution_report["c"], + execution_report["s"], + TradeType.BUY if execution_report["S"] == "BUY" else TradeType.SELL, + OrderType[execution_report["o"]], + Decimal(execution_report["L"]), + Decimal(execution_report["l"]), + AddedToCostTradeFee(flat_fees=[TokenAmount(execution_report["N"], Decimal(execution_report["n"]))]), + exchange_trade_id=execution_report["t"], + ) + + +@dataclass +class BuyOrderCreatedEvent: + timestamp: float + type: OrderType + trading_pair: str + amount: Decimal + price: Decimal + order_id: str + creation_timestamp: float + exchange_order_id: Optional[str] = None + leverage: Optional[int] = 1 + position: Optional[str] = PositionAction.NIL.value + + +@dataclass +class SellOrderCreatedEvent: + timestamp: float + type: OrderType + trading_pair: str + amount: Decimal + price: Decimal + order_id: str + creation_timestamp: float + exchange_order_id: Optional[str] = None + leverage: Optional[int] = 1 + position: Optional[str] = PositionAction.NIL.value + + +@dataclass +class RangePositionLiquidityAddedEvent: + timestamp: float + order_id: str + exchange_order_id: str + trading_pair: str + lower_price: Decimal + upper_price: Decimal + amount: Decimal + fee_tier: str + creation_timestamp: float + trade_fee: TradeFeeBase + token_id: Optional[int] = 0 + + +@dataclass +class RangePositionLiquidityRemovedEvent: + timestamp: float + order_id: str + exchange_order_id: str + trading_pair: str + token_id: str + trade_fee: TradeFeeBase + creation_timestamp: float + + +@dataclass +class RangePositionUpdateEvent: + timestamp: float + order_id: str + exchange_order_id: str + order_action: LPType + trading_pair: Optional[str] = "" + fee_tier: Optional[str] = "" + lower_price: Optional[Decimal] = s_decimal_0 + upper_price: Optional[Decimal] = s_decimal_0 + amount: Optional[Decimal] = s_decimal_0 + creation_timestamp: float = 0 + token_id: Optional[int] = 0 + + +@dataclass +class RangePositionUpdateFailureEvent: + timestamp: float + order_id: str + order_action: LPType + + +@dataclass +class RangePositionClosedEvent: + timestamp: float + token_id: int + token_0: str + token_1: str + claimed_fee_0: Decimal = s_decimal_0 + claimed_fee_1: Decimal = s_decimal_0 + + +@dataclass +class RangePositionFeeCollectedEvent: + timestamp: float + order_id: str + exchange_order_id: str + trading_pair: str + trade_fee: TradeFeeBase + creation_timestamp: float + token_id: int = None + + +class LimitOrderStatus(Enum): + UNKNOWN = 0 + NEW = 1 + OPEN = 2 + CANCELING = 3 + CANCELED = 4 + COMPLETED = 5 + FAILED = 6 + + +@dataclass +class PositionModeChangeEvent: + timestamp: float + trading_pair: str + position_mode: PositionMode + message: Optional[str] = None + + +@dataclass +class BalanceUpdateEvent: + timestamp: float + asset_name: str + total_balance: Optional[Decimal] = None + available_balance: Optional[Decimal] = None + + +@dataclass +class PositionUpdateEvent: + timestamp: float + trading_pair: str + position_side: Optional[PositionSide] # None if the event is for a closed position + unrealized_pnl: Decimal + entry_price: Decimal + amount: Decimal + leverage: Decimal diff --git a/hummingbot/core/gateway/__init__.py b/hummingbot/core/gateway/__init__.py new file mode 100644 index 0000000..5e6bd31 --- /dev/null +++ b/hummingbot/core/gateway/__init__.py @@ -0,0 +1,129 @@ +import os +from dataclasses import dataclass +from decimal import Decimal +from pathlib import Path +from typing import TYPE_CHECKING, Dict, List, Optional + +import aioprocessing + +from hummingbot import root_path +from hummingbot.connector.gateway.common_types import Chain +from hummingbot.core.event.events import TradeType + +if TYPE_CHECKING: + from hummingbot import ClientConfigAdapter + +_default_paths: Optional["GatewayPaths"] = None +_hummingbot_pipe: Optional[aioprocessing.AioConnection] = None + +S_DECIMAL_0: Decimal = Decimal(0) + + +@dataclass +class GatewayPaths: + """ + Represents the local paths and Docker mount paths for a gateway container's conf, certs and logs directories. + + Local paths represent where Hummingbot client sees the paths from the perspective of its local environment. If + Hummingbot is being run from source, then the local environment is the same as the host environment. However, if + Hummingbot is being run as a container, then the local environment is the container's environment. + + Mount paths represent where the gateway container's paths are located on the host environment. If Hummingbot is + being run from source, then these should be the same as the local paths. However, if Hummingbot is being run as a + container - then these must be fed to it from external sources (e.g. environment variables), since containers + generally only have very restricted access to the host filesystem. + """ + + local_conf_path: Path + local_certs_path: Path + local_logs_path: Path + mount_conf_path: Path + mount_certs_path: Path + mount_logs_path: Path + + def __post_init__(self): + """ + Ensure the local paths are created when a GatewayPaths object is created. + """ + for path in [self.local_conf_path, self.local_certs_path, self.local_logs_path]: + path.mkdir(mode=0o755, parents=True, exist_ok=True) + + +def get_gateway_paths(client_config_map: "ClientConfigAdapter") -> GatewayPaths: + """ + Calculates the default paths for a gateway container. + + For Hummingbot running from source, the gateway files are to be stored in ~/.hummingbot-gateway// + + For Hummingbot running inside container, the gateway files are to be stored in ~/.hummingbot-gateway/ locally; + and inside the paths pointed to be CERTS_FOLDER, GATEWAY_CONF_FOLDER, GATEWAY_LOGS_FOLDER environment variables + on the host system. + """ + global _default_paths + if _default_paths is not None: + return _default_paths + + external_certs_path: Optional[Path] = os.getenv("CERTS_FOLDER") and Path(os.getenv("CERTS_FOLDER")) + external_conf_path: Optional[Path] = os.getenv("GATEWAY_CONF_FOLDER") and Path(os.getenv("GATEWAY_CONF_FOLDER")) + external_logs_path: Optional[Path] = os.getenv("GATEWAY_LOGS_FOLDER") and Path(os.getenv("GATEWAY_LOGS_FOLDER")) + local_certs_path: Path = client_config_map.certs_path + local_conf_path: Path = root_path().joinpath("gateway/conf") + local_logs_path: Path = root_path().joinpath("gateway/logs") + mount_certs_path: Path = external_certs_path or local_certs_path + mount_conf_path: Path = external_conf_path or local_conf_path + mount_logs_path: Path = external_logs_path or local_logs_path + + _default_paths = GatewayPaths( + local_conf_path=local_conf_path, + local_certs_path=local_certs_path, + local_logs_path=local_logs_path, + mount_conf_path=mount_conf_path, + mount_certs_path=mount_certs_path, + mount_logs_path=mount_logs_path + ) + return _default_paths + + +def check_transaction_exceptions( + allowances: Dict[str, Decimal], + balances: Dict[str, Decimal], + base_asset: str, + quote_asset: str, + amount: Decimal, + side: TradeType, + gas_limit: int, + gas_cost: Decimal, + gas_asset: str, + swaps_count: int, + chain: Chain = Chain.ETHEREUM +) -> List[str]: + """ + Check trade data for Ethereum decentralized exchanges + """ + exception_list = [] + swaps_message: str = f"Total swaps: {swaps_count}" + gas_asset_balance: Decimal = balances.get(gas_asset, S_DECIMAL_0) + + # check for sufficient gas + if gas_asset_balance < gas_cost: + exception_list.append(f"Insufficient {gas_asset} balance to cover gas:" + f" Balance: {gas_asset_balance}. Est. gas cost: {gas_cost}. {swaps_message}") + + asset_out: str = quote_asset if side is TradeType.BUY else base_asset + asset_out_allowance: Decimal = allowances.get(asset_out, S_DECIMAL_0) + + # check for gas limit set to low + if chain == Chain.ETHEREUM: + gas_limit_threshold: int = 21000 + elif chain == Chain.TEZOS.chain: + gas_limit_threshold: int = 0 + else: + raise ValueError(f"Unsupported chain: {chain}") + if gas_limit < gas_limit_threshold: + exception_list.append(f"Gas limit {gas_limit} below recommended {gas_limit_threshold} threshold.") + + # check for insufficient token allowance + if allowances[asset_out] < amount: + exception_list.append(f"Insufficient {asset_out} allowance {asset_out_allowance}. Amount to trade: {amount}") + + return exception_list diff --git a/hummingbot/core/gateway/gateway_http_client.py b/hummingbot/core/gateway/gateway_http_client.py new file mode 100644 index 0000000..365c7b0 --- /dev/null +++ b/hummingbot/core/gateway/gateway_http_client.py @@ -0,0 +1,1159 @@ +import logging +import re +import ssl +from decimal import Decimal +from enum import Enum +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union + +import aiohttp +from aiohttp import ContentTypeError + +from hummingbot.client.config.security import Security +from hummingbot.core.data_type.common import OrderType, PositionSide +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.event.events import TradeType +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class GatewayError(Enum): + """ + The gateway route error codes defined in /gateway/src/services/error-handler.ts + """ + + Network = 1001 + RateLimit = 1002 + OutOfGas = 1003 + TransactionGasPriceTooLow = 1004 + LoadWallet = 1005 + TokenNotSupported = 1006 + TradeFailed = 1007 + SwapPriceExceedsLimitPrice = 1008 + SwapPriceLowerThanLimitPrice = 1009 + ServiceUnitialized = 1010 + UnknownChainError = 1011 + InvalidNonceError = 1012 + PriceFailed = 1013 + UnknownError = 1099 + + +class GatewayHttpClient: + """ + An HTTP client for making requests to the gateway API. + """ + + _ghc_logger: Optional[HummingbotLogger] = None + _shared_client: Optional[aiohttp.ClientSession] = None + _base_url: str + + __instance = None + + @staticmethod + def get_instance(client_config_map: Optional["ClientConfigAdapter"] = None) -> "GatewayHttpClient": + if GatewayHttpClient.__instance is None: + GatewayHttpClient(client_config_map) + return GatewayHttpClient.__instance + + def __init__(self, client_config_map: Optional["ClientConfigAdapter"] = None): + if client_config_map is None: + from hummingbot.client.hummingbot_application import HummingbotApplication + client_config_map = HummingbotApplication.main_application().client_config_map + api_host = client_config_map.gateway.gateway_api_host + api_port = client_config_map.gateway.gateway_api_port + if GatewayHttpClient.__instance is None: + self._base_url = f"https://{api_host}:{api_port}" + self._client_config_map = client_config_map + GatewayHttpClient.__instance = self + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._ghc_logger is None: + cls._ghc_logger = logging.getLogger(__name__) + return cls._ghc_logger + + @classmethod + def _http_client(cls, client_config_map: "ClientConfigAdapter", re_init: bool = False) -> aiohttp.ClientSession: + """ + :returns Shared client session instance + """ + if cls._shared_client is None or re_init: + cert_path = client_config_map.certs_path + ssl_ctx = ssl.create_default_context(cafile=f"{cert_path}/ca_cert.pem") + ssl_ctx.load_cert_chain(certfile=f"{cert_path}/client_cert.pem", + keyfile=f"{cert_path}/client_key.pem", + password=Security.secrets_manager.password.get_secret_value()) + conn = aiohttp.TCPConnector(ssl_context=ssl_ctx) + cls._shared_client = aiohttp.ClientSession(connector=conn) + return cls._shared_client + + @classmethod + def reload_certs(cls, client_config_map: "ClientConfigAdapter"): + """ + Re-initializes the aiohttp.ClientSession. This should be called whenever there is any updates to the + Certificates used to secure a HTTPS connection to the Gateway service. + """ + cls._http_client(client_config_map, re_init=True) + + @property + def base_url(self) -> str: + return self._base_url + + @base_url.setter + def base_url(self, url: str): + self._base_url = url + + def log_error_codes(self, resp: Dict[str, Any]): + """ + If the API returns an error code, interpret the code, log a useful + message to the user, then raise an exception. + """ + error_code: Optional[int] = resp.get("errorCode") if isinstance(resp, dict) else None + if error_code is not None: + if error_code == GatewayError.Network.value: + self.logger().network("Gateway had a network error. Make sure it is still able to communicate with the node.") + elif error_code == GatewayError.RateLimit.value: + self.logger().network("Gateway was unable to communicate with the node because of rate limiting.") + elif error_code == GatewayError.OutOfGas.value: + self.logger().network("There was an out of gas error. Adjust the gas limit in the gateway config.") + elif error_code == GatewayError.TransactionGasPriceTooLow.value: + self.logger().network("The gas price provided by gateway was too low to create a blockchain operation. Consider increasing the gas price.") + elif error_code == GatewayError.LoadWallet.value: + self.logger().network("Gateway failed to load your wallet. Try running 'gateway connect' with the correct wallet settings.") + elif error_code == GatewayError.TokenNotSupported.value: + self.logger().network("Gateway tried to use an unsupported token.") + elif error_code == GatewayError.TradeFailed.value: + self.logger().network("The trade on gateway has failed.") + elif error_code == GatewayError.PriceFailed.value: + self.logger().network("The price query on gateway has failed.") + elif error_code == GatewayError.InvalidNonceError.value: + self.logger().network("The nonce was invalid.") + elif error_code == GatewayError.ServiceUnitialized.value: + self.logger().network("Some values was uninitialized. Please contact dev@hummingbot.io ") + elif error_code == GatewayError.SwapPriceExceedsLimitPrice.value: + self.logger().network("The swap price is greater than your limit buy price. The market may be too volatile or your slippage rate is too low. Try adjusting the strategy's allowed slippage rate.") + elif error_code == GatewayError.SwapPriceLowerThanLimitPrice.value: + self.logger().network("The swap price is lower than your limit sell price. The market may be too volatile or your slippage rate is too low. Try adjusting the strategy's allowed slippage rate.") + elif error_code == GatewayError.UnknownChainError.value: + self.logger().network("An unknown chain error has occurred on gateway. Make sure your gateway settings are correct.") + elif error_code == GatewayError.UnknownError.value: + self.logger().network("An unknown error has occurred on gateway. Please send your logs to dev@hummingbot.io") + + @staticmethod + def is_timeout_error(e) -> bool: + """ + It is hard to consistently return a timeout error from gateway + because it uses many different libraries to communicate with the + chains with their own idiosyncracies and they do not necessarilly + return HTTP status code 504 when there is a timeout error. It is + easier to rely on the presence of the word 'timeout' in the error. + """ + error_string = str(e) + if re.search('timeout', error_string, re.IGNORECASE): + return True + return False + + async def api_request( + self, + method: str, + path_url: str, + params: Dict[str, Any] = {}, + fail_silently: bool = False, + use_body: bool = False, + ) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: + """ + Sends an aiohttp request and waits for a response. + :param method: The HTTP method, e.g. get or post + :param path_url: The path url or the API end point + :param params: A dictionary of required params for the end point + :param fail_silently: used to determine if errors will be raise or silently ignored + :param use_body: used to determine if the request should sent the parameters in the body or as query string + :returns A response in json format. + """ + url = f"{self.base_url}/{path_url}" + client = self._http_client(self._client_config_map) + + parsed_response = {} + try: + if method == "get": + if len(params) > 0: + if use_body: + response = await client.get(url, json=params) + else: + response = await client.get(url, params=params) + else: + response = await client.get(url) + elif method == "post": + response = await client.post(url, json=params) + elif method == 'put': + response = await client.put(url, json=params) + elif method == 'delete': + response = await client.delete(url, json=params) + else: + raise ValueError(f"Unsupported request method {method}") + if not fail_silently and response.status == 504: + self.logger().network(f"The network call to {url} has timed out.") + else: + try: + parsed_response = await response.json() + except ContentTypeError: + parsed_response = await response.text() + if response.status != 200 and \ + not fail_silently and \ + not self.is_timeout_error(parsed_response): + self.log_error_codes(parsed_response) + + if "error" in parsed_response: + raise ValueError(f"Error on {method.upper()} {url} Error: {parsed_response['error']}") + else: + raise ValueError(f"Error on {method.upper()} {url} Error: {parsed_response}") + + except Exception as e: + if not fail_silently: + if self.is_timeout_error(e): + self.logger().network(f"The network call to {url} has timed out.") + else: + self.logger().network( + e, + exc_info=True, + app_warning_msg=f"Call to {url} failed. See logs for more details." + ) + raise e + + return parsed_response + + async def ping_gateway(self) -> bool: + try: + response: Dict[str, Any] = await self.api_request("get", "", fail_silently=True) + return response["status"] == "ok" + except Exception: + return False + + async def get_gateway_status(self, fail_silently: bool = False) -> List[Dict[str, Any]]: + """ + Calls the status endpoint on Gateway to know basic info about connected networks. + """ + try: + return await self.get_network_status(fail_silently=fail_silently) + except Exception as e: + self.logger().network( + "Error fetching gateway status info", + exc_info=True, + app_warning_msg=str(e) + ) + + async def update_config(self, config_path: str, config_value: Any) -> Dict[str, Any]: + response = await self.api_request("post", "config/update", { + "configPath": config_path, + "configValue": config_value, + }) + self.logger().info("Detected change to Gateway config - restarting Gateway...", exc_info=False) + await self.post_restart() + return response + + async def post_restart(self): + await self.api_request("post", "restart", fail_silently=True) + + async def get_connectors(self, fail_silently: bool = False) -> Dict[str, Any]: + return await self.api_request("get", "connectors", fail_silently=fail_silently) + + async def get_wallets(self, fail_silently: bool = False) -> List[Dict[str, Any]]: + return await self.api_request("get", "wallet", fail_silently=fail_silently) + + async def add_wallet( + self, chain: str, network: str, private_key: str, **kwargs + ) -> Dict[str, Any]: + request = {"chain": chain, "network": network, "privateKey": private_key} + request.update(kwargs) + return await self.api_request(method="post", path_url="wallet/add", params=request) + + async def get_configuration(self, fail_silently: bool = False) -> Dict[str, Any]: + return await self.api_request("get", "chain/config", fail_silently=fail_silently) + + async def get_balances( + self, + chain: str, + network: str, + address: str, + token_symbols: List[str], + connector: str = None, + fail_silently: bool = False, + ) -> Dict[str, Any]: + if isinstance(token_symbols, list): + token_symbols = [x for x in token_symbols if isinstance(x, str) and x.strip() != ''] + request_params = { + "chain": chain, + "network": network, + "address": address, + "tokenSymbols": token_symbols, + } + if connector is not None: + request_params["connector"] = connector + return await self.api_request( + method="post", + path_url="chain/balances", + params=request_params, + fail_silently=fail_silently, + ) + else: + return {} + + async def get_tokens( + self, + chain: str, + network: str, + fail_silently: bool = True + ) -> Dict[str, Any]: + return await self.api_request("get", "chain/tokens", { + "chain": chain, + "network": network + }, fail_silently=fail_silently) + + async def get_algorand_assets( + self, + network: str, + fail_silently: bool = True + ) -> Dict[str, Any]: + return await self.get_tokens(**{ + "chain": "algorand", + "network": network, + "fail_silently": fail_silently}) + + async def get_network_status( + self, + chain: str = None, + network: str = None, + fail_silently: bool = False + ) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + req_data: Dict[str, str] = {} + if chain is not None and network is not None: + req_data["chain"] = chain + req_data["network"] = network + return await self.api_request("get", "chain/status", req_data, fail_silently=fail_silently) + + async def approve_token( + self, + chain: str, + network: str, + address: str, + token: str, + spender: str, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "address": address, + "token": token, + "spender": spender + } + if nonce is not None: + request_payload["nonce"] = nonce + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request( + "post", + "chain/approve", + request_payload + ) + + async def get_allowances( + self, + chain: str, + network: str, + address: str, + token_symbols: List[str], + spender: str, + fail_silently: bool = False + ) -> Dict[str, Any]: + return await self.api_request("post", "chain/allowances", { + "chain": chain, + "network": network, + "address": address, + "tokenSymbols": token_symbols, + "spender": spender + }, fail_silently=fail_silently) + + async def get_price( + self, + chain: str, + network: str, + connector: str, + base_asset: str, + quote_asset: str, + amount: Decimal, + side: TradeType, + fail_silently: bool = False + ) -> Dict[str, Any]: + if side not in [TradeType.BUY, TradeType.SELL]: + raise ValueError("Only BUY and SELL prices are supported.") + + # XXX(martin_kou): The amount is always output with 18 decimal places. + return await self.api_request("post", "amm/price", { + "chain": chain, + "network": network, + "connector": connector, + "base": base_asset, + "quote": quote_asset, + "amount": f"{amount:.18f}", + "side": side.name, + "allowedSlippage": "0/1", # hummingbot applies slippage itself + }, fail_silently=fail_silently) + + async def get_transaction_status( + self, + chain: str, + network: str, + transaction_hash: str, + connector: Optional[str] = None, + address: Optional[str] = None, + fail_silently: bool = False + ) -> Dict[str, Any]: + request = { + "chain": chain, + "network": network, + "txHash": transaction_hash + } + if connector: + request["connector"] = connector + if address: + request["address"] = address + return await self.api_request("post", "chain/poll", request, fail_silently=fail_silently) # type: ignore + + async def wallet_sign( + self, + chain: str, + network: str, + address: str, + message: str, + ) -> Dict[str, Any]: + request = { + "chain": chain, + "network": network, + "address": address, + "message": message, + } + return await self.api_request("get", "wallet/sign", request) + + async def get_evm_nonce( + self, + chain: str, + network: str, + address: str, + fail_silently: bool = False + ) -> Dict[str, Any]: + return await self.api_request("post", "chain/nextNonce", { + "chain": chain, + "network": network, + "address": address + }, fail_silently=fail_silently) + + async def cancel_evm_transaction( + self, + chain: str, + network: str, + address: str, + nonce: int + ) -> Dict[str, Any]: + return await self.api_request("post", "chain/cancel", { + "chain": chain, + "network": network, + "address": address, + "nonce": nonce + }) + + async def amm_trade( + self, + chain: str, + network: str, + connector: str, + address: str, + base_asset: str, + quote_asset: str, + side: TradeType, + amount: Decimal, + price: Decimal, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + # XXX(martin_kou): The amount is always output with 18 decimal places. + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "base": base_asset, + "quote": quote_asset, + "side": side.name, + "amount": f"{amount:.18f}", + "limitPrice": str(price), + "allowedSlippage": "0/1", # hummingbot applies slippage itself + } + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/trade", request_payload) + + async def amm_estimate_gas( + self, + chain: str, + network: str, + connector: str, + ) -> Dict[str, Any]: + return await self.api_request("post", "amm/estimateGas", { + "chain": chain, + "network": network, + "connector": connector, + }) + + # perp endpoints + async def get_perp_markets( + self, + chain: str, + network: str, + connector: str, + fail_silently: bool = False + ) -> Dict[str, Any]: + return await self.api_request("post", "amm/perp/pairs", { + "chain": chain, + "network": network, + "connector": connector + }, fail_silently=fail_silently) + + async def get_perp_market_status( + self, + chain: str, + network: str, + connector: str, + base_asset: str, + quote_asset: str, + fail_silently: bool = False + ) -> Dict[str, Any]: + return await self.api_request("post", "amm/perp/market-status", { + "chain": chain, + "network": network, + "connector": connector, + "base": base_asset, + "quote": quote_asset, + }, fail_silently=fail_silently) + + async def get_perp_market_price( + self, + chain: str, + network: str, + connector: str, + base_asset: str, + quote_asset: str, + amount: Decimal, + side: PositionSide, + fail_silently: bool = False + ) -> Dict[str, Any]: + if side not in [PositionSide.LONG, PositionSide.SHORT]: + raise ValueError("Only LONG and SHORT order prices are supported.") + + return await self.api_request("post", "amm/perp/market-prices", { + "chain": chain, + "network": network, + "connector": connector, + "base": base_asset, + "quote": quote_asset, + "amount": f"{amount:.18f}", + "side": side.name, + "allowedSlippage": "0/1", # hummingbot applies slippage itself + }, fail_silently=fail_silently) + + async def get_perp_position( + self, + chain: str, + network: str, + connector: str, + address: str, + base_asset: str, + quote_asset: str, + fail_silently: bool = False + ) -> Dict[str, Any]: + return await self.api_request("post", "amm/perp/position", { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "base": base_asset, + "quote": quote_asset, + }, fail_silently=fail_silently) + + async def amm_perp_open( + self, + chain: str, + network: str, + connector: str, + address: str, + base_asset: str, + quote_asset: str, + side: PositionSide, + amount: Decimal, + price: Decimal, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + if side not in [PositionSide.LONG, PositionSide.SHORT]: + raise ValueError("Only LONG and SHORT order prices are supported.") + + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "base": base_asset, + "quote": quote_asset, + "side": side.name, + "amount": f"{amount:.18f}", + "allowedSlippage": "0/1", # hummingbot applies slippage itself + } + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/perp/open", request_payload) + + async def amm_perp_close( + self, + chain: str, + network: str, + connector: str, + address: str, + base_asset: str, + quote_asset: str, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + # XXX(martin_kou): The amount is always output with 18 decimal places. + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "base": base_asset, + "quote": quote_asset, + "allowedSlippage": "0/1", # hummingbot applies slippage itself + } + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/perp/close", request_payload) + + async def amm_perp_balance( + self, + chain: str, + network: str, + connector: str, + address: str, + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + } + return await self.api_request("post", "amm/perp/balance", request_payload) + + async def amm_perp_estimate_gas( + self, + chain: str, + network: str, + connector: str, + ) -> Dict[str, Any]: + return await self.api_request("post", "amm/perp/estimateGas", { + "chain": chain, + "network": network, + "connector": connector, + }) + + # LP endpoints + async def amm_lp_add( + self, + chain: str, + network: str, + connector: str, + address: str, + token0: str, + token1: str, + amount0: Decimal, + amount1: Decimal, + fee: str, + lowerPrice: Decimal, + upperPrice: Decimal, + token_id: Optional[int] = None, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "token0": token0, + "token1": token1, + "amount0": f"{amount0:.18f}", + "amount1": f"{amount1:.18f}", + "fee": fee, + "lowerPrice": str(lowerPrice), + "upperPrice": str(upperPrice), + "tokenId": token_id, + "nonce": nonce, + } + if token_id is not None: + request_payload["tokenId"] = int(token_id) + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/liquidity/add", request_payload) + + async def amm_lp_remove( + self, + chain: str, + network: str, + connector: str, + address: str, + token_id: int, + decreasePercent: Optional[int] = None, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "tokenId": token_id, + "decreasePercent": decreasePercent, + "nonce": nonce, + } + if decreasePercent is not None: + request_payload["decreasePercent"] = int(decreasePercent) + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/liquidity/remove", request_payload) + + async def amm_lp_collect_fees( + self, + chain: str, + network: str, + connector: str, + address: str, + token_id: int, + nonce: Optional[int] = None, + max_fee_per_gas: Optional[int] = None, + max_priority_fee_per_gas: Optional[int] = None + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "tokenId": token_id, + "nonce": nonce, + } + if nonce is not None: + request_payload["nonce"] = int(nonce) + if max_fee_per_gas is not None: + request_payload["maxFeePerGas"] = str(max_fee_per_gas) + if max_priority_fee_per_gas is not None: + request_payload["maxPriorityFeePerGas"] = str(max_priority_fee_per_gas) + return await self.api_request("post", "amm/liquidity/collect_fees", request_payload) + + async def amm_lp_position( + self, + chain: str, + network: str, + connector: str, + token_id: int, + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "tokenId": token_id, + } + return await self.api_request("post", "amm/liquidity/position", request_payload) + + async def amm_lp_price( + self, + chain: str, + network: str, + connector: str, + token_0: str, + token_1: str, + fee: str, + period: Optional[int] = 1, + interval: Optional[int] = 1, + ) -> Dict[str, Any]: + request_payload: Dict[str, Any] = { + "chain": chain, + "network": network, + "connector": connector, + "token0": token_0, + "token1": token_1, + "fee": fee, + "period": period, + "interval": interval, + } + return await self.api_request("post", "amm/liquidity/price", request_payload) + + async def clob_place_order( + self, + connector: str, + chain: str, + network: str, + trading_pair: str, + address: str, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + size: Decimal, + client_order_id: Optional[str] = None, + ) -> Dict[str, Any]: + request_payload = { + "connector": connector, + "chain": chain, + "network": network, + "market": trading_pair, + "address": address, + "side": trade_type.name, + "orderType": order_type.name, + "price": str(price), + "amount": str(size), + } + if client_order_id is not None: + request_payload["clientOrderID"] = client_order_id + resp = await self.api_request(method="post", path_url="clob/orders", params=request_payload) + return resp + + async def clob_cancel_order( + self, + connector: str, + chain: str, + network: str, + trading_pair: str, + address: str, + exchange_order_id: str, + ): + request_payload = { + "connector": connector, + "chain": chain, + "network": network, + "address": address, + "market": trading_pair, + "orderId": exchange_order_id, + } + resp = await self.api_request(method="delete", path_url="clob/orders", params=request_payload) + return resp + + async def get_clob_order_status_updates( + self, + trading_pair: str, + chain: str, + network: str, + connector: str, + address: str, + exchange_order_id: Optional[str] = None, + ) -> Dict[str, Any]: + request_payload = { + "market": trading_pair, + "chain": chain, + "network": network, + "connector": connector, + "address": address, + } + if exchange_order_id is not None: + request_payload["orderId"] = exchange_order_id + resp = await self.api_request(method="get", path_url="clob/orders", params=request_payload) + return resp + + async def get_clob_markets( + self, connector: str, chain: str, network: str, trading_pair: Optional[str] = None + ) -> Dict[str, Any]: + request_payload = {"connector": connector, "chain": chain, "network": network} + if trading_pair: + request_payload["market"] = trading_pair + resp = await self.api_request(method="get", path_url="clob/markets", params=request_payload) + return resp + + async def get_clob_orderbook_snapshot( + self, trading_pair: str, connector: str, chain: str, network: str + ) -> Dict[str, Any]: + request_payload = { + "market": trading_pair, "connector": connector, "chain": chain, "network": network + } + resp = await self.api_request(method="get", path_url="clob/orderBook", params=request_payload) + return resp + + async def get_clob_ticker( + self, connector: str, chain: str, network: str, trading_pair: Optional[str] = None + ) -> Dict[str, Any]: + request_payload = {"chain": chain, "network": network, "connector": connector} + if trading_pair is not None: + request_payload["market"] = trading_pair + resp = await self.api_request(method="get", path_url="clob/ticker", params=request_payload) + return resp + + async def clob_batch_order_modify( + self, + connector: str, + chain: str, + network: str, + address: str, + orders_to_create: List[InFlightOrder], + orders_to_cancel: List[InFlightOrder], + ): + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + } + if len(orders_to_create) != 0: + request_payload["createOrderParams"] = [ + { + "market": order.trading_pair, + "price": str(order.price), + "amount": str(order.amount), + "side": order.trade_type.name, + "orderType": order.order_type.name, + "clientOrderID": order.client_order_id, + } for order in orders_to_create + ] + if len(orders_to_cancel) != 0: + request_payload["cancelOrderParams"] = [ + { + "market": order.trading_pair, + "orderId": order.exchange_order_id, + } for order in orders_to_cancel + ] + return await self.api_request("post", "clob/batchOrders", request_payload) + + async def clob_perp_batch_order_modify( + self, + connector: str, + chain: str, + network: str, + address: str, + orders_to_create: List[InFlightOrder], + orders_to_cancel: List[InFlightOrder], + ): + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + } + if len(orders_to_create) != 0: + request_payload["createOrderParams"] = [ + { + "market": order.trading_pair, + "price": str(order.price), + "amount": str(order.amount), + "side": order.trade_type.name, + "orderType": order.order_type.name, + "leverage": order.leverage + } for order in orders_to_create + ] + if len(orders_to_cancel) != 0: + request_payload["cancelOrderParams"] = [ + { + "market": order.trading_pair, + "orderId": order.exchange_order_id, + } for order in orders_to_cancel + ] + return await self.api_request("post", "clob/perp/batchOrders", request_payload) + + async def clob_injective_balances( + self, + chain: str, + network: str, + address: str + ): + request_payload = { + "chain": chain, + "network": network, + "address": address, + "token_symbols": [], + } + return await self.get_balances(**request_payload) + + async def clob_perp_funding_info( + self, + chain: str, + network: str, + connector: str, + trading_pair: str + ) -> Dict[str, Any]: + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "market": trading_pair, + } + return await self.api_request("post", "clob/perp/funding/info", request_payload, use_body=True) + + async def clob_perp_funding_payments( + self, + address: str, + chain: str, + connector: str, + network: str, + trading_pair: str, + **kwargs + ): + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "market": trading_pair, + "address": address + } + request_payload.update(kwargs) + return await self.api_request("post", "clob/perp/funding/payments", request_payload, use_body=True) + + async def clob_perp_get_orders( + self, + chain: str, + network: str, + connector: str, + market: str, + address: str = None, + order_id: Optional[str] = None, + ) -> Dict[str, Any]: + request = { + "chain": chain, + "network": network, + "connector": connector, + "market": market + } + + if address is not None: + request["address"] = address + + if order_id is not None: + request["orderId"] = order_id + + return await self.api_request("get", "clob/perp/orders", request) + + async def clob_perp_get_order_trades( + self, + chain: str, + network: str, + connector: str, + address: str = None, + order_id: Optional[str] = None, + ) -> Dict[str, Any]: + request = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "orderId": order_id + } + return await self.api_request("get", "clob/perp/order/trades", request) + + async def clob_perp_positions( + self, + address: str, + chain: str, + connector: str, + network: str, + trading_pairs: List[str], + ): + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "markets": trading_pairs, + "address": address + } + return await self.api_request("post", "clob/perp/positions", request_payload, use_body=True) + + async def clob_perp_last_trade_price( + self, + chain: str, + connector: str, + network: str, + trading_pair: str, + ) -> Dict[str, Any]: + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "market": trading_pair + } + return await self.api_request("get", "clob/perp/lastTradePrice", request_payload) + + async def clob_perp_place_order( + self, + chain: str, + network: str, + connector: str, + address: str, + trading_pair: str, + trade_type: TradeType, + order_type: OrderType, + price: Decimal, + size: Decimal, + leverage: int, + ) -> Dict[str, Any]: + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "market": trading_pair, + "price": str(price), + "amount": str(size), + "leverage": float(leverage), + "side": trade_type.name, + "orderType": order_type.name + } + return await self.api_request("post", "clob/perp/orders", request_payload, use_body=True) + + async def clob_perp_cancel_order( + self, + chain: str, + network: str, + connector: str, + address: str, + trading_pair: str, + exchange_order_id: str + ) -> Dict[str, Any]: + request_payload = { + "chain": chain, + "network": network, + "connector": connector, + "address": address, + "market": trading_pair, + "orderId": exchange_order_id + } + return await self.api_request("delete", "clob/perp/orders", request_payload, use_body=True) diff --git a/hummingbot/core/gateway/gateway_status_monitor.py b/hummingbot/core/gateway/gateway_status_monitor.py new file mode 100644 index 0000000..95d93d3 --- /dev/null +++ b/hummingbot/core/gateway/gateway_status_monitor.py @@ -0,0 +1,135 @@ +import asyncio +import logging +from enum import Enum +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from hummingbot.client.settings import GATEWAY_CONNECTORS +from hummingbot.client.ui.completer import load_completer +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.gateway_config_utils import build_config_namespace_keys + +POLL_INTERVAL = 2.0 +POLL_TIMEOUT = 1.0 + +if TYPE_CHECKING: + from hummingbot.client.hummingbot_application import HummingbotApplication + + +class GatewayStatus(Enum): + ONLINE = 1 + OFFLINE = 2 + + +class GatewayStatusMonitor: + _monitor_task: Optional[asyncio.Task] + _gateway_status: GatewayStatus + _sm_logger: Optional[logging.Logger] = None + + @classmethod + def logger(cls) -> logging.Logger: + if cls._sm_logger is None: + cls._sm_logger = logging.getLogger(__name__) + return cls._sm_logger + + def __init__(self, app: "HummingbotApplication"): + self._app = app + self._gateway_status = GatewayStatus.OFFLINE + self._monitor_task = None + self._gateway_config_keys: List[str] = [] + self._gateway_ready_event: asyncio.Event = asyncio.Event() + + @property + def ready(self) -> bool: + return self.gateway_status is GatewayStatus.ONLINE + + @property + def ready_event(self) -> asyncio.Event: + return self._gateway_ready_event + + @property + def gateway_status(self) -> GatewayStatus: + return self._gateway_status + + @property + def gateway_config_keys(self) -> List[str]: + return self._gateway_config_keys + + @gateway_config_keys.setter + def gateway_config_keys(self, new_config: List[str]): + self._gateway_config_keys = new_config + + def start(self): + self._monitor_task = safe_ensure_future(self._monitor_loop()) + + def stop(self): + if self._monitor_task is not None: + self._monitor_task.cancel() + self._monitor_task = None + + async def wait_for_online_status(self, max_tries: int = 30): + """ + Wait for gateway status to go online with a max number of tries. If it + is online before time is up, it returns early, otherwise it returns the + current status after the max number of tries. + + :param max_tries: maximum number of retries (default is 30) + """ + while True: + if self.ready or max_tries <= 0: + return self.ready + await asyncio.sleep(POLL_INTERVAL) + max_tries = max_tries - 1 + + async def _monitor_loop(self): + while True: + try: + gateway_http_client = self._get_gateway_instance() + if await asyncio.wait_for(gateway_http_client.ping_gateway(), timeout=POLL_TIMEOUT): + if self.gateway_status is GatewayStatus.OFFLINE: + gateway_connectors = await gateway_http_client.get_connectors(fail_silently=True) + GATEWAY_CONNECTORS.clear() + GATEWAY_CONNECTORS.extend([connector["name"] for connector in gateway_connectors.get("connectors", [])]) + await self.update_gateway_config_key_list() + + self._gateway_status = GatewayStatus.ONLINE + else: + if self._gateway_status is GatewayStatus.ONLINE: + self.logger().info("Connection to Gateway container lost...") + self._gateway_status = GatewayStatus.OFFLINE + + except asyncio.CancelledError: + raise + except Exception: + """ + We wouldn't be changing any status here because whatever error happens here would have been a result of manipulation data from + the try block. They wouldn't be as a result of http related error because they're expected to fail silently. + """ + pass + finally: + if self.gateway_status is GatewayStatus.ONLINE: + if not self._gateway_ready_event.is_set(): + self.logger().info("Gateway Service is ONLINE.") + self._gateway_ready_event.set() + else: + self._gateway_ready_event.clear() + await asyncio.sleep(POLL_INTERVAL) + + async def _fetch_gateway_configs(self) -> Dict[str, Any]: + return await self._get_gateway_instance().get_configuration(fail_silently=True) + + async def update_gateway_config_key_list(self): + try: + config_list: List[str] = [] + config_dict: Dict[str, Any] = await self._fetch_gateway_configs() + build_config_namespace_keys(config_list, config_dict) + + self.gateway_config_keys = config_list + self._app.app.input_field.completer = load_completer(self._app) + except Exception: + self.logger().error("Error fetching gateway configs. Please check that Gateway service is online. ", + exc_info=True) + + def _get_gateway_instance(self) -> GatewayHttpClient: + gateway_instance = GatewayHttpClient.get_instance(self._app.client_config_map) + return gateway_instance diff --git a/hummingbot/core/gateway/utils.py b/hummingbot/core/gateway/utils.py new file mode 100644 index 0000000..efdb287 --- /dev/null +++ b/hummingbot/core/gateway/utils.py @@ -0,0 +1,24 @@ +import re +from typing import List, Match, Optional, Pattern + +# W{TOKEN} only applies to a few special tokens. It should NOT match all W-prefixed token names like WAVE or WOW. +CAPITAL_W_SYMBOLS_PATTERN = re.compile(r"^W(BTC|ETH|AVAX|ALBT|XRP)") + +# w{TOKEN} generally means a wrapped token on the Ethereum network. e.g. wNXM, wDGLD. +SMALL_W_SYMBOLS_PATTERN = re.compile(r"^w(\w+)") + +# {TOKEN}.e generally means a wrapped token on the Avalanche network. +DOT_E_SYMBOLS_PATTERN = re.compile(r"(\w+)\.e$", re.IGNORECASE) + + +def unwrap_token_symbol(on_chain_token_symbol: str) -> str: + patterns: List[Pattern] = [ + CAPITAL_W_SYMBOLS_PATTERN, + SMALL_W_SYMBOLS_PATTERN, + DOT_E_SYMBOLS_PATTERN + ] + for p in patterns: + m: Optional[Match] = p.search(on_chain_token_symbol) + if m is not None: + return m.group(1) + return on_chain_token_symbol diff --git a/hummingbot/core/management/__init__.py b/hummingbot/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/management/console.py b/hummingbot/core/management/console.py new file mode 100644 index 0000000..550318a --- /dev/null +++ b/hummingbot/core/management/console.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +import builtins +import json +import logging +import pathlib +from collections.abc import MutableMapping as MutableMappingABC +from typing import Dict, Iterator, List, MutableMapping + +import asyncssh +from prompt_toolkit import print_formatted_text +from prompt_toolkit.contrib.ssh import PromptToolkitSSHServer +from ptpython.repl import embed + + +class MergedNamespace(MutableMappingABC): + def __init__(self, *mappings): + self._mappings: List[MutableMapping] = list(mappings) + self._local_namespace = {} + + def __setitem__(self, k, v) -> None: + self._local_namespace[k] = v + + def __delitem__(self, v) -> None: + for m in [self._local_namespace] + self._mappings: + if v in m: + del m[v] + + def __getitem__(self, k): + for m in [self._local_namespace] + self._mappings: + if k in m: + return m[k] + raise KeyError(k) + + def __len__(self) -> int: + return sum(len(m) for m in [self._local_namespace] + self._mappings) + + def __iter__(self) -> Iterator[any]: + for mapping in [self._local_namespace] + self._mappings: + for k in mapping: + yield k + + def __repr__(self) -> str: + dict_repr: Dict[str, any] = dict(self.items()) + return f"{self.__class__.__name__}({json.dumps(dict_repr)})" + + +def add_diagnosis_tools(local_vars: MutableMapping): + from .diagnosis import active_tasks + local_vars["active_tasks"] = active_tasks + + +def ensure_key(): + file_name = ".debug_console_ssh_host_key" + path = pathlib.Path(file_name) + if not path.exists(): + rsa_key = asyncssh.generate_private_key("ssh-rsa") + path.write_bytes(rsa_key.export_private_key()) + return str(path) + + +async def start_management_console(local_vars: MutableMapping, + host: str = "localhost", + port: int = 8212): + add_diagnosis_tools(local_vars) + + async def interact(_=None): + globals_dict = { + "__name__": "__main__", + "__doc__": None, + "__package__": "", + "__builtins__": builtins, + "print": print_formatted_text, + } + await embed(return_asyncio_coroutine=True, locals=local_vars, globals=globals_dict) + + ssh_server = PromptToolkitSSHServer(interact=interact) + await asyncssh.create_server( + lambda: ssh_server, host, port, server_host_keys=[ensure_key()] + ) + logging.getLogger(__name__).info( + f"Started SSH debug console. Connect by running `ssh user@{host} -p {port}`. Exit with `CTRL + D`." + ) diff --git a/hummingbot/core/management/diagnosis.py b/hummingbot/core/management/diagnosis.py new file mode 100644 index 0000000..0ce06d8 --- /dev/null +++ b/hummingbot/core/management/diagnosis.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +""" +Debug console diagnosis tools. +""" + +import asyncio +import pandas as pd +from typing import Coroutine, Generator, Union, List + + +def get_coro_name(coro: Union[Coroutine, Generator]) -> str: + if hasattr(coro, '__qualname__') and coro.__qualname__: + coro_name = coro.__qualname__ + elif hasattr(coro, '__name__') and coro.__name__: + coro_name = coro.__name__ + else: + coro_name = f'<{type(coro).__name__} without __name__>' + return f'{coro_name}()' + + +def get_wrapped_coroutine(t: asyncio.Task) -> Union[Coroutine, Generator]: + if "safe_wrapper" in str(t): + return t.get_coro().cr_frame.f_locals["c"] + else: + return t.get_coro() + + +def active_tasks() -> pd.DataFrame: + tasks: List[asyncio.Task] = [t for t in asyncio.Task.all_tasks() if not t.done()] + coroutines: List[Union[Coroutine, Generator]] = [get_wrapped_coroutine(t) for t in tasks] + func_names: List[str] = [get_coro_name(c) for c in coroutines] + retval: pd.DataFrame = pd.DataFrame([{"func_name": f, "coroutine": c, "task": t} + for f, c, t in zip(func_names, coroutines, tasks)], + columns=["func_name", "coroutine", "task"]).set_index("func_name") + retval.sort_index(inplace=True) + return retval diff --git a/hummingbot/core/mock_api/__init__.py b/hummingbot/core/mock_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/mock_api/mock_mqtt_server.py b/hummingbot/core/mock_api/mock_mqtt_server.py new file mode 100644 index 0000000..798b2b5 --- /dev/null +++ b/hummingbot/core/mock_api/mock_mqtt_server.py @@ -0,0 +1,102 @@ +import logging +from typing import Any, Dict + +import ujson +from commlib.serializer import JSONSerializer + + +class FakeMQTTMessage(object): + def __init__(self, + topic, + payload): + self.topic = topic + fake_payload = { + 'header': { + 'reply_to': f"test_reply/{topic}" + }, + 'data': payload + } + self.payload = ujson.dumps(fake_payload) + + +class FakeMQTTBroker: + def __init__(self): + self._transport = None + + def create_transport(self, *args, **kwargs): + if not self._transport: + self._transport = FakeMQTTTransport(*args, **kwargs) + return self._transport + + def publish_to_subscription(self, topic, payload): + callback = self._transport._subscriptions[topic] + msg = FakeMQTTMessage(topic=topic, payload=payload) + callback(client=None, + userdata=None, + msg=msg) + + @property + def subscriptions(self): + return self._transport._subscriptions + + @property + def received_msgs(self): + return self._transport._received_msgs + + def is_msg_received(self, topic, content=None, msg_key = 'msg'): + msg_found = False + if topic in self.received_msgs: + if not content: + msg_found = True + else: + for msg in self.received_msgs[topic]: + if str(content) == str(msg[msg_key]): + msg_found = True + break + return msg_found + + def clear(self): + if self._transport is not None: + self._transport._received_msgs = {} + self._transport._subscriptions = {} + + +class FakeMQTTTransport: + + def __init__(self, *args, **kwargs): + self._subscriptions = {} + self._received_msgs = {} + self._connected = False + + @property + def is_connected(self) -> bool: + return self._connected + + # def on_connect(self, *args, **kwargs): + # pass + + # def on_disconnect(self, *args, **kwargs): + # pass + + # def on_message(self, *args, **kwargs): + # pass + + def publish(self, topic: str, payload: Dict[str, Any], qos: Any, retain: bool = False): + logging.info(f"\nFakeMQTT publish on\n> {topic}\n {payload}\n") + payload = ujson.loads(JSONSerializer.serialize(payload)) + if not self._received_msgs.get(topic): + self._received_msgs[topic] = [] + self._received_msgs[topic].append(payload) + + def subscribe(self, topic: str, callback: Any, *args, **kwargs): + self._subscriptions[topic] = callback + return topic + + def start(self): + self._connected = True + + def stop(self): + self._connected = False + + def loop_forever(self): + self.start() diff --git a/hummingbot/core/mock_api/mock_web_server.py b/hummingbot/core/mock_api/mock_web_server.py new file mode 100644 index 0000000..ca00c5b --- /dev/null +++ b/hummingbot/core/mock_api/mock_web_server.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python + +import asyncio +from aiohttp import web +import logging +import random +from typing import Optional +from yarl import URL +from collections import namedtuple +import requests +from threading import Thread + +StockResponse = namedtuple("StockResponse", "method host path params is_json response") + + +def get_open_port() -> int: + """ + Get the open port number + :return: Get the opened port number + """ + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("", 0)) + s.listen(1) + port = s.getsockname()[1] + s.close() + return port + + +class MockWebServer: + """ + A Class to represent the Humming Web App + ''' + Attributes + ---------- + __instance : Humming Web App instance + _ev_loop : event loops run asynchronous task + _impl : web applicaiton + _runner : web runner + _started : if started indicator + _stock_responses : stocked web response + host : host + + Methods + ------- + get_instance() + _handler(self, request: web.Request) + send_ws_msg(self, ws_path, message) + send_ws_json(self, ws_path, data) + update_response(self, method, host, path, data, params=None, is_json=True) + add_host_to_mock(self, host, ignored_paths=[]) + reroute_local(url) + reroute_request(self, method, url, **kwargs) + """ + TEST_RESPONSE = f"hello {str(random.randint(0, 10000000))}" + _hosts_to_mock = {} + host = "127.0.0.1" + _port: Optional[int] = None + + @classmethod + def get_instance(cls): + """ + Initiate a Humming Web App instance + :return: An instance of Humming Web App + """ + instance = cls.__dict__.get("__instance__") + if instance is None: + cls.__instance__ = instance = cls() + return instance + + def __init__(self): + """ + Constructs all the necessary attributes for the Humming Web object + """ + self._ev_loop: asyncio.AbstractEventLoop = None + self._impl: Optional[web.Application] = None + self._runner: Optional[web.AppRunner] = None + self._started: bool = False + self._stock_responses = [] + self.host = "127.0.0.1" + + async def _handler(self, request: web.Request): + """ + Handle the passed in the web request, and return the response based on request query or post + :param request: web request + :return: response in json format, or string, or response itself + """ + # Add a little sleep to simulate real API requests and also to make sure events are triggers before wait_for + # await asyncio.sleep(0.01) + method, req_path = request.method, request.path + req_path = req_path[1:] + host = req_path[0:req_path.find("/")] + path = req_path[req_path.find("/"):] + resps = [x for x in self._stock_responses if x.method == method and x.host == host and x.path == path] + if len(resps) > 1: + params = dict(request.query) + params.update(dict(await request.post())) + resps = [x for x in resps if x.params is not None and all(k in params and str(v) == params[k] + for k, v in x.params.items())] + if not resps: + raise web.HTTPNotFound(text=f"No Match found for {host}{path} {method}") + is_json, response = resps[0].is_json, resps[0].response + if is_json: + return web.json_response(data=response) + elif type(response) == str: + return web.Response(text=response) + else: + return response + + async def send_ws_msg(self, ws_path, message): + """ + Send out the web socket message + :param ws_path: web socket path + message: the web socket message to be sent out + """ + ws = [ws for path, ws in self._ws_response.items() if ws_path in path][0] + await ws.send_str(message) + + async def send_ws_json(self, ws_path, data): + """ + Send out json data by web socket + :param ws_path: web socket path + message: the json data to be sent out + """ + ws = [ws for path, ws in self._ws_response.items() if ws_path in path][0] + await ws.send_json(data=data) + + def clear_responses(self): + self._stock_responses.clear() + + # To add or update data which will later be responded to a request according to its method, host and path + def update_response(self, method, host, path, data, params=None, is_json=True): + """ + Add or update data which will later be responded to a request according to its method, host and path + :param method: request method + host: request host + path: request path + data: data to respond + params=None: request parameters + is_json=True: if it's in Json format + """ + method = method.upper() + resp_data = [x for x in self._stock_responses if x.method == method and x.host == host and x.path == path + and x.params == params] + if resp_data: + self._stock_responses.remove(resp_data[0]) + self._stock_responses.append(StockResponse(method, host, path, params, is_json, data)) + + def add_host_to_mock(self, host, ignored_paths=[]): + """ + Add the request host to the mock + :param host: request host + ignored_paths=[]: the paths for the mock + """ + MockWebServer._hosts_to_mock[host] = ignored_paths + + # reroute a url if it is one of the hosts we handle. + @staticmethod + def reroute_local(url): + """ + reroute a url if it is one of the hosts we handle + :param url: the original url + :return: the rerouted url + """ + a_url = URL(url) + + if a_url.host in MockWebServer._hosts_to_mock and not any(x in a_url.path for x in + MockWebServer._hosts_to_mock[a_url.host]): + host_path = f"/{a_url.host}{a_url.path}" + query = a_url.query + a_url = a_url.with_scheme("http").with_host(MockWebServer.host).with_port(MockWebServer._port)\ + .with_path(host_path).with_query(query) + return a_url + + orig_session_request = requests.Session.request + + # self here is not an instance of HummingWebApp, it is for mocking Session.request + @staticmethod + def reroute_request(self, method, url, **kwargs): + """ + reroute the request from the rerouted url + :param method: request method + url: the rerouted url + :return: the rerouted request + """ + a_url = MockWebServer.reroute_local(url) + return MockWebServer.orig_session_request(self, method, str(a_url), **kwargs) + + @property + def started(self) -> bool: + """ + Check if started + :return: the started indicator + """ + return self._started + + @property + def port(self) -> Optional[int]: + """ + Get the port + :return: the port + """ + return type(self)._port + + async def _start(self): + """ + Start the Humming Wep App instance + """ + try: + MockWebServer._port = get_open_port() + self._impl: Optional[web.Application] = web.Application() + self._impl.add_routes([web.route("*", '/{tail:.*}', self._handler)]) + self._runner = web.AppRunner(self._impl) + await self._runner.setup() + site = web.TCPSite(self._runner, host=MockWebServer.host, port=MockWebServer._port) + await site.start() + self._started = True + except Exception: + logging.error("oops!", exc_info=True) + + def _wait_til_started(self): + """ + Check if the instance started + :return: if the instance started + """ + future = asyncio.run_coroutine_threadsafe(self.wait_til_started(), self._ev_loop) + return future.result() + + async def wait_til_started(self): + """ + Wait until the instance started + """ + while not self._started: + await asyncio.sleep(0.1) + + async def _stop(self): + """ + Stop the instance + """ + if self._runner is None: + return + await self._runner.cleanup() + self._runner = None + self._impl = None + self._port = None + self._started = False + self._ev_loop.stop() + + def _start_web_app(self): + """ + Start the Humming Web App + """ + self._ev_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._ev_loop) + self._ev_loop.run_until_complete(self._start()) + self._ev_loop.run_forever() + + def start(self): + """ + Start the Humming Web App in a thread-safe way + """ + if self.started: + self.stop() + thread = Thread(target=self._start_web_app) + thread.daemon = True + thread.start() + + def stop(self): + """ + Stop the Humming Web App + """ + asyncio.run_coroutine_threadsafe(self._stop(), self._ev_loop) diff --git a/hummingbot/core/mock_api/mock_web_socket_server.py b/hummingbot/core/mock_api/mock_web_socket_server.py new file mode 100644 index 0000000..89a2629 --- /dev/null +++ b/hummingbot/core/mock_api/mock_web_socket_server.py @@ -0,0 +1,260 @@ +import asyncio +from threading import Event, Thread +from typing import Optional +import socket +import errno +from urllib.parse import urlparse + +import aiohttp +from aiohttp import web + + +def detect_available_port(starting_port: int) -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + current_port: int = starting_port + while current_port < 65535: + try: + s.bind(("127.0.0.1", current_port)) + break + except OSError as e: + if e.errno == errno.EADDRINUSE: + current_port += 1 + continue + return current_port + + +class MockWebSocketServerFactory: + """ + A Class to represent the Humming websockets server factory + ''' + Attributes + ---------- + _ws_session : The aiohttp client session. + _orig_ws_connect : web servers connection + _ws_servers : web servers dictionary + host : host + url_host_only : if it's url hosted only + + Methods + ------- + get_ws_server() + start_new_server(url) + reroute_ws_connect(url, **kwargs) + send_str(url, message, delay=0) + send_str_threadsafe(url, msg, delay=0) + send_json(url, data, delay=0) + send_json_threadsafe(url, data, delay=0) + """ + _ws_session = aiohttp.ClientSession() + _orig_ws_connect = _ws_session.ws_connect + _ws_servers = {} + host = "localhost" + # url_host_only is used for creating one HummingWSServer to handle all websockets requests and responses for + # a given url host. + url_host_only = False + + @staticmethod + def get_ws_server(url): + """ + Get the Humming web server + :param url: url + :return: the web server + """ + if MockWebSocketServerFactory.url_host_only: + url = urlparse(url).netloc + return MockWebSocketServerFactory._ws_servers.get(url) + + @staticmethod + def start_new_server(url): + """ + Start the new Humming web server + :param url: url + :return: the web server + """ + port = detect_available_port(8211) + ws_server = MockWebSocketServer(MockWebSocketServerFactory.host, port) + if MockWebSocketServerFactory.url_host_only: + url = urlparse(url).netloc + MockWebSocketServerFactory._ws_servers[url] = ws_server + ws_server.start() + return ws_server + + @staticmethod + def reroute_ws_connect(client_session_instance, url, **kwargs): + """ + Reroute to Humming web server if the server has already connected + :param client_session_instance: The ClientSession instance when calling client.ws_connect(url). + :param url: url + :return: the web server + """ + ws_server = MockWebSocketServerFactory.get_ws_server(url) + if ws_server is None: + return MockWebSocketServerFactory._orig_ws_connect(url, **kwargs) + kwargs.clear() + return MockWebSocketServerFactory._orig_ws_connect(f"ws://{ws_server.host}:{ws_server.port}", **kwargs) + + @staticmethod + async def send_str(url, message, delay=0): + """ + Send web socket message + :param url: url + message: the message to be sent + delay=0: default is no delay + """ + if delay > 0: + await asyncio.sleep(delay) + ws_server = MockWebSocketServerFactory.get_ws_server(url) + ws_server.wait_til_websocket_is_initialized() + await ws_server.websocket.send_str(message) + + @staticmethod + def send_str_threadsafe(url, msg, delay=0): + """ + Send web socket message in a thead-safe way + :param url: url + message: the message to be sent + delay=0: default is no delay + """ + ws_server = MockWebSocketServerFactory.get_ws_server(url) + asyncio.run_coroutine_threadsafe(MockWebSocketServerFactory.send_str(url, msg, delay), ws_server.ev_loop) + + @staticmethod + async def send_json(url, data, delay=0): + """ + Send web socket json data + :param url: url + data: json data + delay=0: default is no delay + """ + try: + if delay > 0: + await asyncio.sleep(delay) + ws_server = MockWebSocketServerFactory.get_ws_server(url) + ws_server.wait_til_websocket_is_initialized() + await ws_server.websocket.send_json(data) + except Exception as e: + print(f"HummingWsServerFactory Error: {str(e)}") + raise e + + @staticmethod + def send_json_threadsafe(url, data, delay=0): + """ + Send web socket json data in a thread-safe way + :param url: url + data: json data + delay=0: default is no delay + """ + ws_server = MockWebSocketServerFactory.get_ws_server(url) + asyncio.run_coroutine_threadsafe(MockWebSocketServerFactory.send_json(url, data, delay), ws_server.ev_loop) + + +class MockWebSocketServer: + """ + A Class to represent the Humming websockets server + ''' + Attributes + ---------- + _ev_loop : event loops run asynchronous task + _started : if started indicator + host : host + port : port + websocket : websocket + _stock_responses : stocked web response + host : host + + Methods + ------- + add_stock_response(self, request, json_response) + _handler(self, websocket, path) + + """ + def __init__(self, host, port): + self.ev_loop: None + self._started: bool = False + self.host = host + self.port = port + self.websocket: Optional[web.WebSocketResponse] = None + self._websocket_initialized_event = Event() + self.stock_responses = {} + self._app: Optional[web.Application] = None + self._runner: Optional[web.AppRunner] = None + self._thread: Optional[Thread] = None + + def add_stock_response(self, request, json_response): + """ + Stock the json response + :param request: web socket request + json response: json response + """ + self.stock_responses[request] = json_response + + def wait_til_websocket_is_initialized(self): + self._websocket_initialized_event.wait() + + async def _handler(self, request: web.Request): + """ + Stock the json response + """ + self.websocket = web.WebSocketResponse() + await self.websocket.prepare(request) + self._websocket_initialized_event.set() + async for msg in self.websocket: + stock_responses = [v for k, v in self.stock_responses.items() if k in msg] + if len(stock_responses) > 0: + await self.websocket.send_json(stock_responses[0]) + return self.websocket + + @property + def started(self) -> bool: + """ + Check if started + :return: the started indicator + """ + return self._started + + def _start(self): + """ + Start the Humming Web Server + """ + self.ev_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.ev_loop) + self.ev_loop.run_until_complete(self._runner.setup()) + site = web.TCPSite(self._runner, self.host, self.port) + self.ev_loop.run_until_complete(site.start()) + self._started = True + self.ev_loop.run_forever() + + async def wait_til_started(self): + """ + Wait until the Humming web server started + """ + while not self._started: + await asyncio.sleep(0.1) + + def start(self): + """ + Start the Humming Web Server in thread-safe way + """ + if self.started: + self.stop() + self._app = web.Application() + self._app.router.add_get("/", self._handler) + self._app.on_shutdown.append(self._on_shutdown) + self._runner = web.AppRunner(self._app) + self._thread = Thread(target=self._start, daemon=True) + self._thread.daemon = True + self._thread.start() + + async def _on_shutdown(self, _: web.Application): + await self.websocket.close() + + def stop(self): + """ + Stop the Humming Web Server in thread-safe way + """ + self.port = None + self._started = False + loop = asyncio.get_event_loop() + loop.run_until_complete(self._runner.shutdown()) + loop.run_until_complete(self._runner.cleanup()) + self.ev_loop.stop() diff --git a/hummingbot/core/network_base.py b/hummingbot/core/network_base.py new file mode 100644 index 0000000..aad76de --- /dev/null +++ b/hummingbot/core/network_base.py @@ -0,0 +1,126 @@ +import asyncio +import logging +from typing import Optional +from hummingbot.logger import HummingbotLogger +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.network_iterator import NetworkStatus + +NaN = float("nan") +nb_logger = None + + +class NetworkBase: + @classmethod + def logger(cls) -> HummingbotLogger: + global nb_logger + if nb_logger is None: + nb_logger = logging.getLogger(__name__) + return nb_logger + + def __init__(self): + self._network_status = NetworkStatus.STOPPED + self._last_connected_timestamp = NaN + self._check_network_interval = 60.0 + self._check_network_timeout = 60.0 + self._network_error_wait_time = 60.0 + self._check_network_task = None + self._started = False + + @property + def network_status(self) -> NetworkStatus: + return self._network_status + + @property + def last_connected_timestamp(self) -> float: + return self._last_connected_timestamp + + @property + def check_network_task(self) -> Optional[asyncio.Task]: + return self._check_network_task + + @property + def check_network_interval(self) -> float: + return self._check_network_interval + + @check_network_interval.setter + def check_network_interval(self, interval): + self._check_network_interval = interval + + @property + def network_error_wait_time(self) -> float: + return self._network_error_wait_time + + @network_error_wait_time.setter + def network_error_wait_time(self, wait_time): + self._network_error_wait_time = wait_time + + @property + def check_network_timeout(self) -> float: + return self._check_network_timeout + + @property + def started(self) -> bool: + return self._started + + @check_network_timeout.setter + def check_network_timeout(self, timeout): + self._check_network_timeout = timeout + + async def start_network(self): + pass + + async def stop_network(self): + pass + + async def check_network(self) -> NetworkStatus: + self.logger().warning("check_network() has not been implemented!") + return NetworkStatus.NOT_CONNECTED + + async def _check_network_loop(self): + while True: + last_status = self._network_status + has_unexpected_error = False + + try: + new_status = await asyncio.wait_for(self.check_network(), timeout=self._check_network_timeout) + except asyncio.CancelledError: + raise + except asyncio.TimeoutError: + self.logger().debug("Check network call has timed out. Network status is not connected.") + new_status = NetworkStatus.NOT_CONNECTED + except Exception: + self.logger().error("Unexpected error while checking for network status.", exc_info=True) + new_status = NetworkStatus.NOT_CONNECTED + has_unexpected_error = True + + try: + self._network_status = new_status + if new_status != last_status: + if new_status is NetworkStatus.CONNECTED: + self.logger().info(f"Network status has changed to {new_status}. Starting networking...") + await self.start_network() + else: + self.logger().info(f"Network status has changed to {new_status}. Stopping networking...") + await self.stop_network() + + if not has_unexpected_error: + await asyncio.sleep(self._check_network_interval) + else: + await asyncio.sleep(self._network_error_wait_time) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error starting or stopping network.", exc_info=True) + + def start(self): + self._check_network_task = safe_ensure_future(self._check_network_loop()) + self._network_status = NetworkStatus.NOT_CONNECTED + self._started = True + + def stop(self): + if self._check_network_task is not None: + self._check_network_task.cancel() + self._check_network_task = None + self._network_status = NetworkStatus.STOPPED + safe_ensure_future(self.stop_network()) + self._started = False diff --git a/hummingbot/core/network_iterator.pxd b/hummingbot/core/network_iterator.pxd new file mode 100644 index 0000000..85e9ae3 --- /dev/null +++ b/hummingbot/core/network_iterator.pxd @@ -0,0 +1,13 @@ +# distutils: language=c++ + +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class NetworkIterator(TimeIterator): + cdef: + object _network_status + double _last_connected_timestamp + double _check_network_interval + double _check_network_timeout + double _network_error_wait_time + object _check_network_task diff --git a/hummingbot/core/network_iterator.pyx b/hummingbot/core/network_iterator.pyx new file mode 100644 index 0000000..1f2253e --- /dev/null +++ b/hummingbot/core/network_iterator.pyx @@ -0,0 +1,139 @@ +# distutils: language=c++ + +import asyncio +from enum import Enum +import logging +from typing import Optional + +from hummingbot.core.clock cimport Clock +from hummingbot.logger import HummingbotLogger +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.time_iterator import TimeIterator + +NaN = float("nan") +ni_logger = None + + +class NetworkStatus(Enum): + STOPPED = 0 + NOT_CONNECTED = 1 + CONNECTED = 2 + + +cdef class NetworkIterator(TimeIterator): + @classmethod + def logger(cls) -> HummingbotLogger: + global ni_logger + if ni_logger is None: + ni_logger = logging.getLogger(__name__) + return ni_logger + + def __init__(self): + super().__init__() + self._network_status = NetworkStatus.STOPPED + self._last_connected_timestamp = NaN + self._check_network_interval = 10.0 + self._check_network_timeout = 5.0 + self._network_error_wait_time = 60.0 + self._check_network_task = None + + @property + def network_status(self) -> NetworkStatus: + return self._network_status + + @property + def last_connected_timestamp(self) -> float: + return self._last_connected_timestamp + + @last_connected_timestamp.setter + def last_connected_timestamp(self, value): + self._last_connected_timestamp = value + + @property + def check_network_task(self) -> Optional[asyncio.Task]: + return self._check_network_task + + @property + def check_network_interval(self) -> float: + return self._check_network_interval + + @check_network_interval.setter + def check_network_interval(self, double interval): + self._check_network_interval = interval + + @property + def network_error_wait_time(self) -> float: + return self._network_error_wait_time + + @network_error_wait_time.setter + def network_error_wait_time(self, double wait_time): + self._network_error_wait_time = wait_time + + @property + def check_network_timeout(self) -> float: + return self._check_network_timeout + + @check_network_timeout.setter + def check_network_timeout(self, double timeout): + self._check_network_timeout = timeout + + async def start_network(self): + pass + + async def stop_network(self): + pass + + async def check_network(self) -> NetworkStatus: + self.logger().warning("check_network() has not been implemented!") + return NetworkStatus.NOT_CONNECTED + + async def _check_network_loop(self): + while True: + new_status = self._network_status + last_status = self._network_status + has_unexpected_error = False + + try: + new_status = await asyncio.wait_for(self.check_network(), timeout=self._check_network_timeout) + except asyncio.CancelledError: + raise + except asyncio.TimeoutError: + self.logger().debug(f"Check network call has timed out. Network status is not connected.") + new_status = NetworkStatus.NOT_CONNECTED + except Exception: + self.logger().error("Unexpected error while checking for network status.", exc_info=True) + new_status = NetworkStatus.NOT_CONNECTED + has_unexpected_error = True + + self._network_status = new_status + if new_status != last_status: + if new_status is NetworkStatus.CONNECTED: + self.logger().info(f"Network status has changed to {new_status}. Starting networking...") + await self.start_network() + else: + self.logger().info(f"Network status has changed to {new_status}. Stopping networking...") + await self.stop_network() + + if not has_unexpected_error: + await asyncio.sleep(self._check_network_interval) + else: + await asyncio.sleep(self._network_error_wait_time) + + cdef c_start(self, Clock clock, double timestamp): + TimeIterator.c_start(self, clock, timestamp) + self._check_network_task = safe_ensure_future(self._check_network_loop()) + self._network_status = NetworkStatus.NOT_CONNECTED + + cdef c_stop(self, Clock clock): + TimeIterator.c_stop(self, clock) + if self._check_network_task is not None: + self._check_network_task.cancel() + self._check_network_task = None + self._network_status = NetworkStatus.STOPPED + safe_ensure_future(self.stop_network()) + + def start(self, clock: Clock, timestamp: float): + self.c_start(clock, timestamp) + + def stop(self, clock: Clock): + self.c_stop(clock) diff --git a/hummingbot/core/pubsub.pxd b/hummingbot/core/pubsub.pxd new file mode 100644 index 0000000..f2f4d8f --- /dev/null +++ b/hummingbot/core/pubsub.pxd @@ -0,0 +1,27 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from libcpp.unordered_map cimport unordered_map +from libcpp.unordered_set cimport unordered_set +from libcpp.utility cimport pair +from hummingbot.core.PyRef cimport PyRef +from hummingbot.core.event.event_listener cimport EventListener + +ctypedef unordered_set[PyRef] EventListenersCollection +ctypedef unordered_set[PyRef].iterator EventListenersIterator +ctypedef unordered_map[int64_t, EventListenersCollection] Events +ctypedef unordered_map[int64_t, EventListenersCollection].iterator EventsIterator +ctypedef pair[int64_t, EventListenersCollection] EventsPair + + +cdef class PubSub: + cdef: + Events _events + object __weakref__ + + cdef c_log_exception(self, int64_t event_tag, object arg) + cdef c_add_listener(self, int64_t event_tag, EventListener listener) + cdef c_remove_listener(self, int64_t event_tag, EventListener listener) + cdef c_remove_dead_listeners(self, int64_t event_tag) + cdef c_get_listeners(self, int64_t event_tag) + cdef c_trigger_event(self, int64_t event_tag, object arg) diff --git a/hummingbot/core/pubsub.pyx b/hummingbot/core/pubsub.pyx new file mode 100644 index 0000000..a86fd9b --- /dev/null +++ b/hummingbot/core/pubsub.pyx @@ -0,0 +1,169 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/PyRef.cpp + +from cpython cimport( + PyObject, + PyWeakref_NewRef, + PyWeakref_GetObject +) +from cython.operator cimport( + postincrement as inc, + dereference as deref, + address +) +from libcpp.vector cimport vector +from enum import Enum +import logging +import random +from typing import List + +from hummingbot.logger import HummingbotLogger +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.event_listener cimport EventListener + +class_logger = None + + +cdef class PubSub: + """ + PubSub with weak references. This avoids the lapsed listener problem by periodically performing GC on dead + event listener. + + Dead listener is done by calling c_remove_dead_listeners(), which checks whether the listener weak references are + alive or not, and removes the dead ones. Each call to c_remove_dead_listeners() takes O(n). + + Here's how the dead listener GC is performed: + + 1. c_add_listener(): + Randomly with ADD_LISTENER_GC_PROBABILITY. This assumes c_add_listener() is called frequently and so it doesn't + make sense to do the GC every time. + 2. c_remove_listener(): + Every time. This assumes c_remove_listener() is called infrequently. + 3. c_get_listeners() and c_trigger_event(): + Every time. Both functions take O(n) already. + """ + + ADD_LISTENER_GC_PROBABILITY = 0.005 + + @classmethod + def logger(cls) -> HummingbotLogger: + global class_logger + if class_logger is None: + class_logger = logging.getLogger(__name__) + return class_logger + + def __init__(self): + self._events = Events() + + def add_listener(self, event_tag: Enum, listener: EventListener): + self.c_add_listener(event_tag.value, listener) + + def remove_listener(self, event_tag: Enum, listener: EventListener): + self.c_remove_listener(event_tag.value, listener) + + def get_listeners(self, event_tag: Enum) -> List[EventListener]: + return self.c_get_listeners(event_tag.value) + + def trigger_event(self, event_tag: Enum, message: any): + self.c_trigger_event(event_tag.value, message) + + cdef c_log_exception(self, int64_t event_tag, object arg): + self.logger().error(f"Unexpected error while processing event {event_tag}.", exc_info=True) + + cdef c_add_listener(self, int64_t event_tag, EventListener listener): + cdef: + EventsIterator it = self._events.find(event_tag) + EventListenersCollection new_listeners + EventListenersCollection *listeners_ptr + object listener_weakref = PyWeakref_NewRef(listener, None) + PyRef listener_wrapper = PyRef(listener_weakref) + if it != self._events.end(): + listeners_ptr = address(deref(it).second) + deref(listeners_ptr).insert(listener_wrapper) + else: + new_listeners.insert(listener_wrapper) + self._events.insert(EventsPair(event_tag, new_listeners)) + + if random.random() < PubSub.ADD_LISTENER_GC_PROBABILITY: + self.c_remove_dead_listeners(event_tag) + + cdef c_remove_listener(self, int64_t event_tag, EventListener listener): + cdef: + EventsIterator it = self._events.find(event_tag) + EventListenersCollection *listeners_ptr + object listener_weakref = PyWeakref_NewRef(listener, None) + PyRef listener_wrapper = PyRef(listener_weakref) + EventListenersIterator lit + if it == self._events.end(): + return + listeners_ptr = address(deref(it).second) + lit = deref(listeners_ptr).find(listener_wrapper) + if lit != deref(listeners_ptr).end(): + deref(listeners_ptr).erase(lit) + self.c_remove_dead_listeners(event_tag) + + cdef c_remove_dead_listeners(self, int64_t event_tag): + cdef: + EventsIterator it = self._events.find(event_tag) + EventListenersCollection *listeners_ptr + object listener_weakref + EventListenersIterator lit + vector[EventListenersIterator] lit_to_remove + if it == self._events.end(): + return + listeners_ptr = address(deref(it).second) + lit = deref(listeners_ptr).begin() + while lit != deref(listeners_ptr).end(): + listener_weakref = (deref(lit).get()) + if (PyWeakref_GetObject(listener_weakref)) is None: + lit_to_remove.push_back(lit) + inc(lit) + for lit in lit_to_remove: + deref(listeners_ptr).erase(lit) + if deref(listeners_ptr).size() < 1: + self._events.erase(it) + + cdef c_get_listeners(self, int64_t event_tag): + self.c_remove_dead_listeners(event_tag) + + cdef: + EventsIterator it = self._events.find(event_tag) + EventListenersCollection *listeners_ptr + object listener_weafref + EventListener typed_listener + + if it == self._events.end(): + return [] + + retval = [] + listeners_ptr = address(deref(it).second) + for pyref in deref(listeners_ptr): + listener_weafref = pyref.get() + typed_listener = PyWeakref_GetObject(listener_weafref) + retval.append(typed_listener) + return retval + + cdef c_trigger_event(self, int64_t event_tag, object arg): + self.c_remove_dead_listeners(event_tag) + + cdef: + EventsIterator it = self._events.find(event_tag) + EventListenersCollection listeners + object listener_weafref + EventListener typed_listener + if it == self._events.end(): + return + + # It is extremely important that this set of listeners is a C++ copy - because listeners are allowed to call + # c_remove_listener(), which breaks the iterator if we're using the underlying set. + listeners = deref(it).second + for pyref in listeners: + listener_weafref = pyref.get() + typed_listener = PyWeakref_GetObject(listener_weafref) + try: + typed_listener.c_set_event_info(event_tag, self) + typed_listener.c_call(arg) + except Exception: + self.c_log_exception(event_tag, arg) + finally: + typed_listener.c_set_event_info(0, None) diff --git a/hummingbot/core/py_time_iterator.pxd b/hummingbot/core/py_time_iterator.pxd new file mode 100644 index 0000000..82a869a --- /dev/null +++ b/hummingbot/core/py_time_iterator.pxd @@ -0,0 +1,7 @@ +# distutils: language=c++ + +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class PyTimeIterator(TimeIterator): + pass diff --git a/hummingbot/core/py_time_iterator.pyx b/hummingbot/core/py_time_iterator.pyx new file mode 100644 index 0000000..17654d5 --- /dev/null +++ b/hummingbot/core/py_time_iterator.pyx @@ -0,0 +1,10 @@ +# distutils: language=c++ + + +cdef class PyTimeIterator(TimeIterator): + def tick(self, double timestamp): + raise NotImplementedError + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self.tick(timestamp) diff --git a/hummingbot/core/rate_oracle/__init__.py b/hummingbot/core/rate_oracle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/rate_oracle/rate_oracle.py b/hummingbot/core/rate_oracle/rate_oracle.py new file mode 100644 index 0000000..2adcd4f --- /dev/null +++ b/hummingbot/core/rate_oracle/rate_oracle.py @@ -0,0 +1,197 @@ +import asyncio +import logging +from decimal import Decimal +from typing import Dict, Optional + +import hummingbot.client.settings # noqa +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.rate_oracle.sources.ascend_ex_rate_source import AscendExRateSource +from hummingbot.core.rate_oracle.sources.binance_rate_source import BinanceRateSource +from hummingbot.core.rate_oracle.sources.coin_cap_rate_source import CoinCapRateSource +from hummingbot.core.rate_oracle.sources.coin_gecko_rate_source import CoinGeckoRateSource +from hummingbot.core.rate_oracle.sources.gate_io_rate_source import GateIoRateSource +from hummingbot.core.rate_oracle.sources.kucoin_rate_source import KucoinRateSource +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.rate_oracle.utils import find_rate +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + +RATE_ORACLE_SOURCES = { + "binance": BinanceRateSource, + "coin_gecko": CoinGeckoRateSource, + "coin_cap": CoinCapRateSource, + "kucoin": KucoinRateSource, + "ascend_ex": AscendExRateSource, + "gate_io": GateIoRateSource, +} + + +class RateOracle(NetworkBase): + """ + RateOracle provides conversion rates for any given pair token symbols in both async and sync fashions. + It achieves this by query URL on a given source for prices and store them, either in cache or as an object member. + The find_rate is then used on these prices to find a rate on a given pair. + """ + _logger: Optional[HummingbotLogger] = None + _shared_instance: "RateOracle" = None + + @classmethod + def get_instance(cls) -> "RateOracle": + if cls._shared_instance is None: + cls._shared_instance = RateOracle() + return cls._shared_instance + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, source: Optional[RateSourceBase] = None, quote_token: Optional[str] = None): + super().__init__() + self._source: RateSourceBase = source if source is not None else BinanceRateSource() + self._prices: Dict[str, Decimal] = {} + self._fetch_price_task: Optional[asyncio.Task] = None + self._ready_event = asyncio.Event() + self._quote_token = quote_token if quote_token is not None else "USD" + + def __str__(self): + return f"{self._source.name} rate oracle" + + async def get_ready(self): + """ + The network is ready when it first successfully get prices for a given source. + """ + try: + if not self._ready_event.is_set(): + await self._ready_event.wait() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error while waiting for data feed to get ready.", + exc_info=True) + + @property + def name(self) -> str: + return "rate_oracle" + + @property + def source(self) -> RateSourceBase: + return self._source + + @source.setter + def source(self, new_source: RateSourceBase): + self._source = new_source + + @property + def quote_token(self) -> str: + return self._quote_token + + @quote_token.setter + def quote_token(self, new_token: str): + if new_token != self._quote_token: + self._quote_token = new_token + self._prices = {} + + @property + def prices(self) -> Dict[str, Decimal]: + """ + Actual prices retrieved from URL + """ + return self._prices.copy() + + async def start_network(self): + await self.stop_network() + self._fetch_price_task = safe_ensure_future(self._fetch_price_loop()) + + async def stop_network(self): + if self._fetch_price_task is not None: + self._fetch_price_task.cancel() + self._fetch_price_task = None + # Reset stored prices so that they are not used if they are not being updated + self._prices = {} + + async def check_network(self) -> NetworkStatus: + try: + prices = await self._source.get_prices(quote_token=self._quote_token) + if not prices: + raise Exception(f"Error fetching new prices from {self._source.name}.") + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + async def get_value(self, amount: Decimal, base_token: str) -> Decimal: + """ + Finds a value in the configured quote of a given token amount. + + :param amount: An amount of token to be converted to value + :param base_token: The token symbol that we want to price, e.g. BTC + :return A value of the token in the configured quote token unit + """ + rate = await self.get_rate(base_token=base_token) + rate = Decimal("0") if rate is None else rate + return amount * rate + + async def get_rate(self, base_token: str) -> Decimal: + """ + Finds a conversion rate of a given token to a global token + + :param base_token: The token symbol that we want to price, e.g. BTC + :return A conversion rate + """ + prices = await self._source.get_prices(quote_token=self._quote_token) + pair = combine_to_hb_trading_pair(base=base_token, quote=self._quote_token) + return find_rate(prices, pair) + + def get_pair_rate(self, pair: str) -> Decimal: + """ + Finds a conversion rate for a given trading pair, this can be direct or indirect prices as + long as it can find a route to achieve this. + + :param pair: A trading pair, e.g. BTC-USDT + :return A conversion rate + """ + return find_rate(self._prices, pair) + + async def stored_or_live_rate(self, pair: str) -> Decimal: + """ + Finds a conversion rate for a given symbol trying to use the local prices. If local prices are not initialized + uses the async rate finder (directly from the exchange) + + :param pair: A trading pair, e.g. BTC-USDT + + :return A conversion rate + """ + if self._prices: + rate = self.get_pair_rate(pair) + else: + rate = await self.rate_async(pair) + + return rate + + async def rate_async(self, pair: str) -> Decimal: + """ + Finds a conversion rate in an async operation, it is a class method which can be used directly without having to + start the RateOracle network. + :param pair: A trading pair, e.g. BTC-USDT + :return A conversion rate + """ + prices = await self._source.get_prices(quote_token=self._quote_token) + return find_rate(prices, pair) + + async def _fetch_price_loop(self): + while True: + try: + self._prices = await self._source.get_prices(quote_token=self._quote_token) + if self._prices: + self._ready_event.set() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network(f"Error fetching new prices from {self.source.name}.", exc_info=True, + app_warning_msg=f"Couldn't fetch newest prices from {self.source.name}.") + await asyncio.sleep(1) diff --git a/hummingbot/core/rate_oracle/sources/__init__.py b/hummingbot/core/rate_oracle/sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/rate_oracle/sources/ascend_ex_rate_source.py b/hummingbot/core/rate_oracle/sources/ascend_ex_rate_source.py new file mode 100644 index 0000000..3d07af6 --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/ascend_ex_rate_source.py @@ -0,0 +1,55 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils import async_ttl_cache + +if TYPE_CHECKING: + from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange + + +class AscendExRateSource(RateSourceBase): + def __init__(self): + super().__init__() + self._exchange: Optional[AscendExExchange] = None # delayed because of circular reference + + @property + def name(self) -> str: + return "ascend_ex" + + @async_ttl_cache(ttl=30, maxsize=1) + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + self._ensure_exchange() + results = {} + try: + records = await self._exchange.get_all_pairs_prices() + for record in records["data"]: + pair = await self._exchange.trading_pair_associated_to_exchange_symbol(record["symbol"]) + if Decimal(record["ask"][0]) > 0 and Decimal(record["bid"][0]) > 0: + results[pair] = (Decimal(str(record["ask"][0])) + Decimal(str(record["bid"][0]))) / Decimal("2") + except Exception: + self.logger().exception( + msg="Unexpected error while retrieving rates from AscendEx. Check the log file for more info.", + ) + return results + + def _ensure_exchange(self): + if self._exchange is None: + self._exchange = self._build_ascend_ex_connector_without_private_keys() + + @staticmethod + def _build_ascend_ex_connector_without_private_keys() -> 'AscendExExchange': + from hummingbot.client.hummingbot_application import HummingbotApplication + from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange + + app = HummingbotApplication.main_application() + client_config_map = app.client_config_map + + return AscendExExchange( + client_config_map=client_config_map, + ascend_ex_api_key="", + ascend_ex_secret_key="", + ascend_ex_group_id="", + trading_pairs=[], + trading_required=False, + ) diff --git a/hummingbot/core/rate_oracle/sources/binance_rate_source.py b/hummingbot/core/rate_oracle/sources/binance_rate_source.py new file mode 100644 index 0000000..5aa6580 --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/binance_rate_source.py @@ -0,0 +1,90 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_gather + +if TYPE_CHECKING: + from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange + + +class BinanceRateSource(RateSourceBase): + def __init__(self): + super().__init__() + self._binance_exchange: Optional[BinanceExchange] = None # delayed because of circular reference + self._binance_us_exchange: Optional[BinanceExchange] = None # delayed because of circular reference + + @property + def name(self) -> str: + return "binance" + + @async_ttl_cache(ttl=30, maxsize=1) + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + self._ensure_exchanges() + results = {} + tasks = [ + self._get_binance_prices(exchange=self._binance_exchange), + self._get_binance_prices(exchange=self._binance_us_exchange, quote_token="USD"), + ] + task_results = await safe_gather(*tasks, return_exceptions=True) + for task_result in task_results: + if isinstance(task_result, Exception): + self.logger().error( + msg="Unexpected error while retrieving rates from Binance. Check the log file for more info.", + exc_info=task_result, + ) + break + else: + results.update(task_result) + return results + + def _ensure_exchanges(self): + if self._binance_exchange is None: + self._binance_exchange = self._build_binance_connector_without_private_keys(domain="com") + self._binance_us_exchange = self._build_binance_connector_without_private_keys(domain="us") + + @staticmethod + async def _get_binance_prices(exchange: 'BinanceExchange', quote_token: str = None) -> Dict[str, Decimal]: + """ + Fetches binance prices + + :param exchange: The exchange instance from which to query prices. + :param quote_token: A quote symbol, if specified only pairs with the quote symbol are included for prices + :return: A dictionary of trading pairs and prices + """ + pairs_prices = await exchange.get_all_pairs_prices() + results = {} + for pair_price in pairs_prices: + try: + trading_pair = await exchange.trading_pair_associated_to_exchange_symbol(symbol=pair_price["symbol"]) + except KeyError: + continue # skip pairs that we don't track + if quote_token is not None: + base, quote = split_hb_trading_pair(trading_pair=trading_pair) + if quote != quote_token: + continue + bid_price = pair_price.get("bidPrice") + ask_price = pair_price.get("askPrice") + if bid_price is not None and ask_price is not None and 0 < Decimal(bid_price) <= Decimal(ask_price): + results[trading_pair] = (Decimal(bid_price) + Decimal(ask_price)) / Decimal("2") + + return results + + @staticmethod + def _build_binance_connector_without_private_keys(domain: str) -> 'BinanceExchange': + from hummingbot.client.hummingbot_application import HummingbotApplication + from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange + + app = HummingbotApplication.main_application() + client_config_map = app.client_config_map + + return BinanceExchange( + client_config_map=client_config_map, + binance_api_key="", + binance_api_secret="", + trading_pairs=[], + trading_required=False, + domain=domain, + ) diff --git a/hummingbot/core/rate_oracle/sources/coin_cap_rate_source.py b/hummingbot/core/rate_oracle/sources/coin_cap_rate_source.py new file mode 100644 index 0000000..afdfafa --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/coin_cap_rate_source.py @@ -0,0 +1,39 @@ +from decimal import Decimal +from typing import Dict, Optional + +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.data_feed.coin_cap_data_feed import CoinCapDataFeed +from hummingbot.logger import HummingbotLogger + + +class CoinCapRateSource(RateSourceBase): + _logger: Optional[HummingbotLogger] = None + + def __init__(self, assets_map: Dict[str, str], api_key: str): + self._coin_cap_data_feed = CoinCapDataFeed(assets_map=assets_map, api_key=api_key) + + @property + def name(self) -> str: + return "coin_cap" + + async def start_network(self): + await self._coin_cap_data_feed.start_network() + + async def stop_network(self): + await self._coin_cap_data_feed.stop_network() + + async def check_network(self) -> NetworkStatus: + return await self._coin_cap_data_feed.check_network() + + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + prices = {} + + if quote_token == self._coin_cap_data_feed.universal_quote_token: + prices = await self._coin_cap_data_feed.get_all_usd_quoted_prices() + else: + self.logger().warning( + "CoinCapRateSource only supports USD as quote token. Please set your global token to USD." + ) + + return prices diff --git a/hummingbot/core/rate_oracle/sources/coin_gecko_rate_source.py b/hummingbot/core/rate_oracle/sources/coin_gecko_rate_source.py new file mode 100644 index 0000000..0984aea --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/coin_gecko_rate_source.py @@ -0,0 +1,158 @@ +import asyncio +import functools +from asyncio import Task +from decimal import Decimal +from typing import Dict, List, Optional, Union + +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils import async_ttl_cache +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.data_feed.coin_gecko_data_feed import CoinGeckoDataFeed +from hummingbot.data_feed.coin_gecko_data_feed.coin_gecko_constants import COOLOFF_AFTER_BAN + + +class CoinGeckoRateSource(RateSourceBase): + def __init__(self, extra_token_ids: List[str]): + super().__init__() + self._coin_gecko_supported_vs_tokens: Optional[List[str]] = None + self._coin_gecko_data_feed: Optional[CoinGeckoDataFeed] = None # delayed because of circular reference + self._extra_token_ids = extra_token_ids + self._rate_limit_exceeded = asyncio.Event() + self._lock = asyncio.Lock() + + @property + def name(self) -> str: + return "coin_gecko" + + @property + def extra_token_ids(self) -> List[str]: + return self._extra_token_ids + + @extra_token_ids.setter + def extra_token_ids(self, new_ids: List[str]): + self._extra_token_ids = new_ids + + def try_event(self, fn): + @functools.wraps(fn) + async def try_raise_event(*args, **kwargs): + while True: + # If the rate limit has been exceeded, wait for the cool-off period to pass + if self._rate_limit_exceeded.is_set(): + await self._rate_limit_exceeded.wait() + + try: + res = await fn(*args, **kwargs) + return res + except IOError as e: + # This is from exceeding the server's rate limit, signal the issue and wait for post-ban cool-off + self.logger().warning("Rate limit exceeded with:") + self.logger().warning(f" {e}") + self.logger().warning(" Report to development team") + self._rate_limit_exceeded.set() + # This is the cool-off after a ban + await self._sleep(COOLOFF_AFTER_BAN) + self.logger().info(f" Continuing after {COOLOFF_AFTER_BAN} seconds") + self._rate_limit_exceeded.clear() + except Exception as e: + self.logger().error(f"Unhandled error in CoinGecko rate source response: {str(e)}", exc_info=True) + raise Exception(f"Unhandled error in CoinGecko rate source response: {str(e)}") + + return try_raise_event + + @async_ttl_cache(ttl=COOLOFF_AFTER_BAN, maxsize=1) + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + """ + Fetches the first 2500 CoinGecko prices ordered by market cap to ~ 500K USD + + :param quote_token: The quote token for which to fetch prices + :return A dictionary of trading pairs and prices + """ + await self._lock.acquire() + + if quote_token is None: + raise NotImplementedError("Must supply a quote token to fetch prices for CoinGecko") + self._ensure_data_feed() + vs_currency = quote_token.lower() + results = {} + if not self._coin_gecko_supported_vs_tokens: + self._coin_gecko_supported_vs_tokens = await self.try_event( + self._coin_gecko_data_feed.get_supported_vs_tokens)() + + if vs_currency not in self._coin_gecko_supported_vs_tokens: + vs_currency = "usd" + + # Extra tokens + r = await self.try_event(self._get_coin_gecko_extra_token_prices)(vs_currency) + results.update(r) + + # Coin Gecko returns 250 assets max per page, 2500th is around 500K USD market cap (as of 2/2023) + tasks: List[Task] = [] + for page_no in range(1, 8): + tasks.append(asyncio.create_task(self._get_coin_gecko_prices_by_page(vs_currency, page_no, None))) + + try: + task_results = await self.try_event(safe_gather)(*tasks, return_exceptions=False) + except Exception: + self.logger().error( + "Unexpected error while retrieving rates from Coingecko. Check the log file for more info.") + raise + + # Collect the results + for i, task_result in enumerate(task_results): + results.update(task_result) + + self._lock.release() + return results + + def _ensure_data_feed(self): + if self._coin_gecko_data_feed is None: + self._coin_gecko_data_feed = CoinGeckoDataFeed() + + async def _get_coin_gecko_prices_by_page(self, + vs_currency: str, + page_no: int, + category: Union[str, None]) -> Dict[str, Decimal]: + """ + Fetches CoinGecko prices by page number. + + :param vs_currency: A currency (crypto or fiat) to get prices of tokens in, see + https://api.coingecko.com/api/v3/simple/supported_vs_currencies for the current supported list + :param page_no: The page number + :param category | None: category to filter tokens to get from the provider (specifying one limits to 50 results) + + :return: A dictionary of trading pairs and prices (50 results max if a category is provided) + """ + results = {} + resp = await self.try_event(self._coin_gecko_data_feed.get_prices_by_page)(vs_currency=vs_currency, + page_no=page_no, category=category) + + for record in resp: + pair = combine_to_hb_trading_pair(base=record['symbol'].upper(), quote=vs_currency.upper()) + if record["current_price"]: + results[pair] = Decimal(str(record["current_price"])) + return results + + async def _get_coin_gecko_extra_token_prices(self, vs_currency: str) -> Dict[str, Decimal]: + """ + Fetches CoinGecko prices for the configured extra tokens. + + :param vs_currency: A currency (crypto or fiat) to get prices of tokens in, see + https://api.coingecko.com/api/v3/simple/supported_vs_currencies for the current supported list + + :return: A dictionary of trading pairs and prices + """ + results = {} + # TODO: Should we force hummingbot to be included? + # self._extra_token_ids.append("hummingbot") - This fails the tests, not sure why + if self._extra_token_ids: + resp = await self.try_event(self._coin_gecko_data_feed.get_prices_by_token_id)(vs_currency=vs_currency, + token_ids=self._extra_token_ids) + for record in resp: + pair = combine_to_hb_trading_pair(base=record["symbol"].upper(), quote=vs_currency.upper()) + if record["current_price"]: + results[pair] = Decimal(str(record["current_price"])) + return results + + async def _sleep(self, delay: float): + await asyncio.sleep(delay) diff --git a/hummingbot/core/rate_oracle/sources/gate_io_rate_source.py b/hummingbot/core/rate_oracle/sources/gate_io_rate_source.py new file mode 100644 index 0000000..03d135d --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/gate_io_rate_source.py @@ -0,0 +1,69 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils import async_ttl_cache + +if TYPE_CHECKING: + from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange + + +class GateIoRateSource(RateSourceBase): + def __init__(self): + super().__init__() + self._exchange: Optional[GateIoExchange] = None # delayed because of circular reference + + @property + def name(self) -> str: + return "gate_io" + + @async_ttl_cache(ttl=30, maxsize=1) + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + self._ensure_exchange() + results = {} + try: + records = await self._exchange._api_get( + path_url=CONSTANTS.TICKER_PATH_URL, + is_auth_required=False, + limit_id=CONSTANTS.TICKER_PATH_URL + ) + for record in records: + try: + pair = await self._exchange.trading_pair_associated_to_exchange_symbol(record["currency_pair"]) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + + if str(record["lowest_ask"]) == '' or str(record["highest_bid"]) == '': + # Ignore results for which the order book is empty + continue + + if Decimal(str(record["lowest_ask"])) > 0 and Decimal(str(record["highest_bid"])) > 0: + results[pair] = (Decimal(str(record["lowest_ask"])) + + Decimal(str(record["highest_bid"]))) / Decimal("2") + except Exception: + self.logger().exception( + msg="Unexpected error while retrieving rates from Gate.IO. Check the log file for more info.", + ) + return results + + def _ensure_exchange(self): + if self._exchange is None: + self._exchange = self._build_gate_io_connector_without_private_keys() + + @staticmethod + def _build_gate_io_connector_without_private_keys() -> 'GateIoExchange': + from hummingbot.client.hummingbot_application import HummingbotApplication + from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange + + app = HummingbotApplication.main_application() + client_config_map = app.client_config_map + + return GateIoExchange( + client_config_map=client_config_map, + gate_io_api_key="", + gate_io_secret_key="", + trading_pairs=[], + trading_required=False, + ) diff --git a/hummingbot/core/rate_oracle/sources/kucoin_rate_source.py b/hummingbot/core/rate_oracle/sources/kucoin_rate_source.py new file mode 100644 index 0000000..a3477d8 --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/kucoin_rate_source.py @@ -0,0 +1,59 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, Optional + +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.utils import async_ttl_cache + +if TYPE_CHECKING: + from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange + + +class KucoinRateSource(RateSourceBase): + def __init__(self): + super().__init__() + self._exchange: Optional[KucoinExchange] = None # delayed because of circular reference + + @property + def name(self) -> str: + return "kucoin" + + @async_ttl_cache(ttl=30, maxsize=1) + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + self._ensure_exchange() + results = {} + try: + records = await self._exchange.get_all_pairs_prices() + for record in records["data"]["ticker"]: + try: + pair = await self._exchange.trading_pair_associated_to_exchange_symbol(record["symbolName"]) + except KeyError: + # Ignore results for which their symbols is not tracked by the connector + continue + if Decimal(record["buy"]) > 0 and Decimal(record["sell"]) > 0: + results[pair] = (Decimal(str(record["buy"])) + Decimal(str(record["sell"]))) / Decimal("2") + except Exception: + self.logger().exception( + msg="Unexpected error while retrieving rates from KuCoin. Check the log file for more info.", + ) + return results + + def _ensure_exchange(self): + if self._exchange is None: + self._exchange = self._build_kucoin_connector_without_private_keys() + + @staticmethod + def _build_kucoin_connector_without_private_keys() -> 'KucoinExchange': + from hummingbot.client.hummingbot_application import HummingbotApplication + from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange + + app = HummingbotApplication.main_application() + client_config_map = app.client_config_map + + return KucoinExchange( + client_config_map=client_config_map, + kucoin_api_key="", + kucoin_passphrase="", + kucoin_secret_key="", + trading_pairs=[], + trading_required=False, + ) diff --git a/hummingbot/core/rate_oracle/sources/rate_source_base.py b/hummingbot/core/rate_oracle/sources/rate_source_base.py new file mode 100644 index 0000000..25ab98f --- /dev/null +++ b/hummingbot/core/rate_oracle/sources/rate_source_base.py @@ -0,0 +1,25 @@ +import logging +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Dict, Optional + +from hummingbot.logger import HummingbotLogger + + +class RateSourceBase(ABC): + _logger: Optional[HummingbotLogger] = None + + @property + @abstractmethod + def name(self) -> str: + ... + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @abstractmethod + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + ... diff --git a/hummingbot/core/rate_oracle/utils.py b/hummingbot/core/rate_oracle/utils.py new file mode 100644 index 0000000..28cc940 --- /dev/null +++ b/hummingbot/core/rate_oracle/utils.py @@ -0,0 +1,37 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.utils import combine_to_hb_trading_pair, split_hb_trading_pair +from hummingbot.core.gateway.utils import unwrap_token_symbol + + +def find_rate(prices: Dict[str, Decimal], pair: str) -> Decimal: + ''' + Finds exchange rate for a given trading pair from a dictionary of prices + For example, given prices of {"HBOT-USDT": Decimal("100"), "AAVE-USDT": Decimal("50"), "USDT-GBP": Decimal("0.75")} + A rate for USDT-HBOT will be 1 / 100 + A rate for HBOT-AAVE will be 100 / 50 + A rate for AAVE-HBOT will be 50 / 100 + A rate for HBOT-GBP will be 100 * 0.75 + :param prices: The dictionary of trading pairs and their prices + :param pair: The trading pair + ''' + if pair in prices: + return prices[pair] + base, quote = split_hb_trading_pair(trading_pair=pair) + base = unwrap_token_symbol(base) + quote = unwrap_token_symbol(quote) + if base == quote: + return Decimal("1") + reverse_pair = combine_to_hb_trading_pair(base=quote, quote=base) + if reverse_pair in prices: + return Decimal("1") / prices[reverse_pair] + base_prices = {k: v for k, v in prices.items() if k.startswith(f"{base}-")} + for base_pair, proxy_price in base_prices.items(): + link_quote = split_hb_trading_pair(base_pair)[1] + link_pair = combine_to_hb_trading_pair(base=link_quote, quote=quote) + if link_pair in prices: + return proxy_price * prices[link_pair] + common_denom_pair = combine_to_hb_trading_pair(base=quote, quote=link_quote) + if common_denom_pair in prices: + return proxy_price / prices[common_denom_pair] diff --git a/hummingbot/core/time_iterator.pxd b/hummingbot/core/time_iterator.pxd new file mode 100644 index 0000000..2becff4 --- /dev/null +++ b/hummingbot/core/time_iterator.pxd @@ -0,0 +1,14 @@ +# distutils: language=c++ + +from hummingbot.core.clock cimport Clock +from hummingbot.core.pubsub cimport PubSub + + +cdef class TimeIterator(PubSub): + cdef: + double _current_timestamp + Clock _clock + + cdef c_start(self, Clock clock, double timestamp) + cdef c_stop(self, Clock clock) + cdef c_tick(self, double timestamp) diff --git a/hummingbot/core/time_iterator.pyx b/hummingbot/core/time_iterator.pyx new file mode 100644 index 0000000..6e18527 --- /dev/null +++ b/hummingbot/core/time_iterator.pyx @@ -0,0 +1,46 @@ +# distutils: language=c++ +from typing import Optional + +from hummingbot.core.clock import Clock + +NaN = float("nan") + + +cdef class TimeIterator(PubSub): + def __init__(self): + self._current_timestamp = NaN + self._clock = None + + cdef c_start(self, Clock clock, double timestamp): + self._clock = clock + self._current_timestamp = timestamp + + cdef c_stop(self, Clock clock): + self._current_timestamp = NaN + self._clock = None + + cdef c_tick(self, double timestamp): + self._current_timestamp = timestamp + + def tick(self, timestamp: float): + self.c_tick(timestamp) + + @property + def current_timestamp(self) -> float: + return self._current_timestamp + + @property + def clock(self) -> Optional[Clock]: + return self._clock + + def start(self, clock: Clock): + self.c_start(clock, clock.current_timestamp) + + def stop(self, clock: Clock): + self.c_stop(clock) + + def _set_current_timestamp(self, timestamp: float): + """ + Method added to be used only for unit testing purposes + """ + self._current_timestamp = timestamp diff --git a/hummingbot/core/utils/__init__.py b/hummingbot/core/utils/__init__.py new file mode 100644 index 0000000..badf740 --- /dev/null +++ b/hummingbot/core/utils/__init__.py @@ -0,0 +1,43 @@ +import cachetools +import errno +import functools +import numpy as np +import socket +import pandas as pd + + +def async_ttl_cache(ttl: int = 3600, maxsize: int = 1): + cache = cachetools.TTLCache(ttl=ttl, maxsize=maxsize) + + def decorator(fn): + @functools.wraps(fn) + async def memoize(*args, **kwargs): + key = str((args, kwargs)) + try: + return cache[key] + except KeyError: + cache[key] = await fn(*args, **kwargs) + return cache[key] + + memoize.cache_clear = lambda: cache.clear() + return memoize + + return decorator + + +def map_df_to_str(df: pd.DataFrame) -> pd.DataFrame: + return df.applymap(lambda x: np.format_float_positional(x, trim="-") if isinstance(x, float) else x).astype(str) + + +def detect_available_port(starting_port: int) -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + current_port: int = starting_port + while current_port < 65535: + try: + s.bind(("127.0.0.1", current_port)) + break + except OSError as e: + if e.errno == errno.EADDRINUSE: + current_port += 1 + continue + return current_port diff --git a/hummingbot/core/utils/async_call_scheduler.py b/hummingbot/core/utils/async_call_scheduler.py new file mode 100644 index 0000000..57a1052 --- /dev/null +++ b/hummingbot/core/utils/async_call_scheduler.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +import asyncio +import logging +from typing import Callable, Coroutine, NamedTuple, Optional + +from async_timeout import timeout + +import hummingbot +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class AsyncCallSchedulerItem(NamedTuple): + future: asyncio.Future + coroutine: Coroutine + timeout_seconds: float + app_warning_msg: str = "API call error." + + +class AsyncCallScheduler: + _acs_shared_instance: Optional["AsyncCallScheduler"] = None + _acs_logger: Optional[HummingbotLogger] = None + + @classmethod + def shared_instance(cls): + if cls._acs_shared_instance is None: + cls._acs_shared_instance = AsyncCallScheduler() + return cls._acs_shared_instance + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._acs_logger is None: + cls._acs_logger = logging.getLogger(__name__) + return cls._acs_logger + + def __init__(self, call_interval: float = 0.01): + self._coro_queue: asyncio.Queue = asyncio.Queue() + self._coro_scheduler_task: Optional[asyncio.Task] = None + self._call_interval: float = call_interval + self.reset_event_loop() + + @property + def coro_queue(self) -> asyncio.Queue: + return self._coro_queue + + @property + def coro_scheduler_task(self) -> Optional[asyncio.Task]: + return self._coro_scheduler_task + + @property + def started(self) -> bool: + return self._coro_scheduler_task is not None + + def reset_event_loop(self): + self._ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + def start(self): + if self._coro_scheduler_task is not None: + self.stop() + self._coro_scheduler_task = safe_ensure_future( + self._coro_scheduler( + self._coro_queue, + self._call_interval + ) + ) + + def stop(self): + if self._coro_scheduler_task is not None: + self._coro_scheduler_task.cancel() + self._coro_scheduler_task = None + + async def _coro_scheduler(self, coro_queue: asyncio.Queue, interval: float = 0.01): + while True: + app_warning_msg = "API call error." + try: + fut, coro, timeout_seconds, app_warning_msg = await coro_queue.get() + async with timeout(timeout_seconds): + fut.set_result(await coro) + except asyncio.CancelledError: + try: + fut.cancel() + except Exception: + pass + raise + except asyncio.InvalidStateError: + # The future is already cancelled from outside. Ignore. + pass + except Exception as e: + # Add exception information. + app_warning_msg += f" [[Got exception: {str(e)}]]" + self.logger().debug(app_warning_msg, + exc_info=True, + app_warning_msg=app_warning_msg) + try: + fut.set_exception(e) + except Exception: + pass + + try: + await asyncio.sleep(interval) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Scheduler sleep interrupted.", exc_info=True) + + async def schedule_async_call(self, + coro: Coroutine, + timeout_seconds: float, + app_warning_msg: str = "API call error.") -> any: + fut: asyncio.Future = self._ev_loop.create_future() + self._coro_queue.put_nowait(AsyncCallSchedulerItem(fut, coro, timeout_seconds, + app_warning_msg=app_warning_msg)) + if self._coro_scheduler_task is None: + self.start() + return await fut + + async def call_async(self, + func: Callable, *args, + timeout_seconds: float = 5.0, + app_warning_msg: str = "API call error.") -> any: + coro: Coroutine = self._ev_loop.run_in_executor( + hummingbot.get_executor(), + func, + *args, + ) + return await self.schedule_async_call(coro, timeout_seconds, app_warning_msg=app_warning_msg) diff --git a/hummingbot/core/utils/async_retry.py b/hummingbot/core/utils/async_retry.py new file mode 100644 index 0000000..fdd8e95 --- /dev/null +++ b/hummingbot/core/utils/async_retry.py @@ -0,0 +1,67 @@ +""" +Tools for running asynchronous functions multiple times. +""" + +import asyncio +import functools +import logging +from typing import ( + Dict, + Optional, + List, + Any, + Type, +) + + +class AllTriesFailedException(EnvironmentError): + pass + + +def async_retry(retry_count: int = 2, + exception_types: List[Type[Exception]] = [Exception], + logger: logging.Logger = logging.getLogger("retry"), + stats: Dict[str, int] = None, + raise_exp: bool = True, + retry_interval: float = 0.5 + ): + """ + A decorator for async functions that will retry a function x times, where x is retry_count. + + :param retry_count: Number of retries + :param exception_types: All exceptions trigger retry, but exceptions in the list also get logging + :param logger: if raise_exp is false then log the last exception instead of raising it + :param stats: + :param raise_exp: raise an exception if all retries failed, otherwise log the last exception + :param retry_interval: time to wait between retries + """ + def decorator(fn): + @functools.wraps(fn) + async def retry(*args, _stats=stats, **kwargs): + last_exception: Optional[Exception] = None + for count in range(1, retry_count + 1): + try: + additional_params: Dict[str, Any] = {} + fn_kwargs = {**additional_params, **kwargs} + return await fn(*args, **fn_kwargs) + + except tuple(exception_types) as exc: + last_exception = exc + logger.info(f"Exception raised for {last_exception}: {fn.__name__}. Retrying {count}/{retry_count} times.") + if _stats is not None and type(_stats) is dict: + metric_name: str = f"retry.{fn.__name__}.count" + if metric_name not in _stats: + _stats[metric_name] = 1 + else: + _stats[metric_name] += 1 + except Exception as exc: + last_exception = exc + raise + await asyncio.sleep(retry_interval) + if raise_exp: + raise AllTriesFailedException() from last_exception + else: + logger.info(f"Last exception raised for {repr(last_exception)}: {fn.__name__}. aborting.") + return retry + + return decorator diff --git a/hummingbot/core/utils/async_utils.py b/hummingbot/core/utils/async_utils.py new file mode 100644 index 0000000..ede7f44 --- /dev/null +++ b/hummingbot/core/utils/async_utils.py @@ -0,0 +1,66 @@ +import asyncio +import inspect +import logging +import time + + +async def safe_wrapper(c): + try: + return await c + except asyncio.CancelledError: + raise + except Exception as e: + logging.getLogger(__name__).error(f"Unhandled error in background task: {str(e)}", exc_info=True) + + +def safe_ensure_future(coro, *args, **kwargs): + return asyncio.ensure_future(safe_wrapper(coro), *args, **kwargs) + + +async def safe_gather(*args, **kwargs): + try: + return await asyncio.gather(*args, **kwargs) + except Exception as e: + logging.getLogger(__name__).debug(f"Unhandled error in background task: {str(e)}", exc_info=True) + raise + + +async def wait_til(condition_func, timeout=10): + start_time = time.perf_counter() + while True: + if condition_func(): + return + elif time.perf_counter() - start_time > timeout: + raise Exception(f"{inspect.getsource(condition_func).strip()} condition is never met. Time out reached.") + else: + await asyncio.sleep(0.1) + + +async def run_command(*args): + process = await asyncio.create_subprocess_exec( + *args, + stdout=asyncio.subprocess.PIPE) + stdout, stderr = await process.communicate() + return stdout.decode().strip() + + +def call_sync(coro, + loop: asyncio.AbstractEventLoop, + timeout: float = 30.0): + import threading + if threading.current_thread() != threading.main_thread(): # pragma: no cover + fut = asyncio.run_coroutine_threadsafe( + asyncio.wait_for(coro, timeout), + loop + ) + return fut.result() + elif not loop.is_running(): + try: + loop = asyncio.get_event_loop() + except RuntimeError: + logging.getLogger(__name__).debug( + "Runtime error in call_sync - Using new event loop to exec coro", + exc_info=True + ) + loop = asyncio.new_event_loop() + return loop.run_until_complete(asyncio.wait_for(coro, timeout)) diff --git a/hummingbot/core/utils/estimate_fee.py b/hummingbot/core/utils/estimate_fee.py new file mode 100644 index 0000000..152d8a5 --- /dev/null +++ b/hummingbot/core/utils/estimate_fee.py @@ -0,0 +1,105 @@ +from decimal import Decimal +from typing import List, Optional +import warnings + +from hummingbot.client.config.trade_fee_schema_loader import TradeFeeSchemaLoader +from hummingbot.core.data_type.trade_fee import ( + TradeFeeBase, + TokenAmount, + TradeFeeSchema +) +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType + + +def build_trade_fee( + exchange: str, + is_maker: bool, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = Decimal("NaN"), + extra_flat_fees: Optional[List[TokenAmount]] = None, +) -> TradeFeeBase: + """ + WARNING: Do not use this method for order sizing. Use the `BudgetChecker` instead. + + Uses the exchange's `TradeFeeSchema` to build a `TradeFee`, given the trade parameters. + """ + trade_fee_schema: TradeFeeSchema = TradeFeeSchemaLoader.configured_schema_for_exchange(exchange_name=exchange) + fee_percent: Decimal = ( + trade_fee_schema.maker_percent_fee_decimal + if is_maker + else trade_fee_schema.taker_percent_fee_decimal + ) + fixed_fees: List[TokenAmount] = ( + trade_fee_schema.maker_fixed_fees + if is_maker + else trade_fee_schema.taker_fixed_fees + ).copy() + if extra_flat_fees is not None and len(extra_flat_fees) > 0: + fixed_fees = fixed_fees + extra_flat_fees + trade_fee: TradeFeeBase = TradeFeeBase.new_spot_fee( + fee_schema=trade_fee_schema, + trade_type=order_side, + percent=fee_percent, + percent_token=trade_fee_schema.percent_fee_token, + flat_fees=fixed_fees + ) + return trade_fee + + +def build_perpetual_trade_fee( + exchange: str, + is_maker: bool, + position_action: PositionAction, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = Decimal("NaN"), +) -> TradeFeeBase: + """ + WARNING: Do not use this method for order sizing. Use the `BudgetChecker` instead. + + Uses the exchange's `TradeFeeSchema` to build a `TradeFee`, given the trade parameters. + """ + trade_fee_schema = TradeFeeSchemaLoader.configured_schema_for_exchange(exchange_name=exchange) + percent = trade_fee_schema.maker_percent_fee_decimal if is_maker else trade_fee_schema.taker_percent_fee_decimal + fixed_fees = trade_fee_schema.maker_fixed_fees if is_maker else trade_fee_schema.taker_fixed_fees + trade_fee = TradeFeeBase.new_perpetual_fee( + fee_schema=trade_fee_schema, + position_action=position_action, + percent=percent, + percent_token=trade_fee_schema.percent_fee_token, + flat_fees=fixed_fees) + return trade_fee + + +def estimate_fee(exchange: str, is_maker: bool) -> TradeFeeBase: + """ + WARNING: This method is deprecated and remains only for backward compatibility. + Use `build_trade_fee` and `build_perpetual_trade_fee` instead. + + Estimate the fee of a transaction on any blockchain. + exchange is the name of the exchange to query. + is_maker if true look at fee from maker side, otherwise from taker side. + """ + warnings.warn( + "The 'estimate_fee' method is deprecated, use 'build_trade_fee' and 'build_perpetual_trade_fee' instead.", + DeprecationWarning, + stacklevel=2, + ) + trade_fee = build_trade_fee( + exchange, + is_maker, + base_currency="", + quote_currency="", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("0"), + price=Decimal("0"), + ) + return trade_fee diff --git a/hummingbot/core/utils/fixed_rate_source.py b/hummingbot/core/utils/fixed_rate_source.py new file mode 100644 index 0000000..9062208 --- /dev/null +++ b/hummingbot/core/utils/fixed_rate_source.py @@ -0,0 +1,31 @@ +from decimal import Decimal + +from hummingbot.core.rate_oracle.utils import find_rate + + +class FixedRateSource: + + def __init__(self): + super().__init__() + + self._known_rates: dict = {} + + def __str__(self): + return "fixed rates" + + def add_rate(self, token_pair: str, rate: Decimal): + """Add the fixed rate to the rate source + :param token_pair: A trading pair, e.g. BTC-USDT + :param rate: The rate to associate to the token pair + """ + self._known_rates[token_pair] = rate + + def get_pair_rate(self, pair: str) -> Decimal: + """ + Finds a conversion rate for a given symbol, this can be direct or indirect prices as long as it can find a route + to achieve this. + + :param pair: A trading pair, e.g. BTC-USDT + :return A conversion rate + """ + return find_rate(self._known_rates, pair) diff --git a/hummingbot/core/utils/gateway_config_utils.py b/hummingbot/core/utils/gateway_config_utils.py new file mode 100644 index 0000000..5bd10d1 --- /dev/null +++ b/hummingbot/core/utils/gateway_config_utils.py @@ -0,0 +1,190 @@ +from copy import deepcopy +from typing import Any, Dict, Iterable, List, Optional + +import pandas as pd + +native_tokens = { + "ethereum": "ETH", + "avalanche": "AVAX", + "algorand": "ALGO", + "cosmos": "ATOM", + "polygon": "MATIC", + "harmony": "ONE", + "binance-smart-chain": "BNB", + "cronos": "CRO", + "near": "NEAR", + "injective": "INJ", + "xdc": "XDC", + "tezos": "XTZ", + "kujira": "KUJI" +} + +SUPPORTED_CHAINS = set(native_tokens.keys()) + + +def flatten(items): + """ + Deep flatten any iterable item. + """ + for x in items: + if isinstance(x, Iterable) and not isinstance(x, (str, bytes)): + yield from flatten(x) + else: + yield x + + +def list_gateway_wallets(wallets: List[Any], chain: str) -> List[str]: + """ + Get the public keys for a chain supported by gateway. + """ + return list(flatten([w["walletAddresses"] for w in wallets if w["chain"] == chain])) + + +def build_wallet_display(native_token: str, wallets: List[Dict[str, Any]]) -> pd.DataFrame: + """ + Display user wallets for a particular chain as a table + """ + columns = ["Wallet", native_token] + data = [] + for dict in wallets: + data.extend([[dict['address'], dict['balance']]]) + + return pd.DataFrame(data=data, columns=columns) + + +def build_connector_display(connectors: List[Dict[str, Any]]) -> pd.DataFrame: + """ + Display connector information as a table + """ + columns = ["Exchange", "Network", "Wallet"] + data = [] + for connector_spec in connectors: + data.extend([ + [ + connector_spec["connector"], + f"{connector_spec['chain']} - {connector_spec['network']}", + connector_spec["wallet_address"], + ] + ]) + + return pd.DataFrame(data=data, columns=columns) + + +def build_list_display(connectors: List[Dict[str, Any]]) -> pd.DataFrame: + """ + Display connector information as a table + """ + columns = ["Exchange", "Chains", "Tier"] + data = [] + for connector_spec in connectors: + data.extend([ + [ + connector_spec["name"], + ', '.join(connector_spec['chains']), + connector_spec["tier"], + ] + ]) + + return pd.DataFrame(data=data, columns=columns) + + +def build_connector_tokens_display(connectors: List[Dict[str, Any]]) -> pd.DataFrame: + """ + Display connector and the tokens the balance command will report on + """ + columns = ["Exchange", "Report Token Balances"] + data = [] + for connector_spec in connectors: + data.extend([ + [ + f"{connector_spec['connector']}_{connector_spec['chain']}_{connector_spec['network']}", + connector_spec.get("tokens", ""), + ] + ]) + + return pd.DataFrame(data=data, columns=columns) + + +def build_balances_allowances_display(symbols: List[str], balances: List[str], allowances: List[str]) -> pd.DataFrame: + """ + Display balances and allowances for a list of symbols as a table + """ + columns = ["Symbol", "Balance", "Allowances"] + data = [] + for i in range(len(symbols)): + data.extend([ + [ + symbols[i], + balances[i], + allowances[i] + ] + ]) + + return pd.DataFrame(data=data, columns=columns) + + +def build_config_dict_display(lines: List[str], config_dict: Dict[str, Any], level: int = 0): + """ + Build display messages on lines for a config dictionary, this function is called recursive. + For example: + config_dict: {"a": 1, "b": {"ba": 2, "bb": 3}, "c": 4} + lines will be + a: 1 + b: + ba: 2 + bb: 3 + c: 4 + :param lines: a list display message (lines) to be built upon. + :param config_dict: a (Gateway) config dictionary + :param level: a nested level. + """ + prefix: str = " " * level + for k, v in config_dict.items(): + if isinstance(v, Dict): + lines.append(f"{prefix}{k}:") + build_config_dict_display(lines, v, level + 1) + else: + lines.append(f"{prefix}{k}: {v}") + + +def build_config_namespace_keys(namespace_keys: List[str], config_dict: Dict[str, Any], prefix: str = ""): + """ + Build namespace keys for a config dictionary, this function is recursive. + For example: + config_dict: {"a": 1, "b": {"ba": 2, "bb": 3}, "c": 4} + namepace_keys will be ["a", "b", "b.ba", "b.bb", "c"] + :param namespace_keys: a key list to be build upon + :param config_dict: a (Gateway) config dictionary + :prefix: a prefix to the namespace (used when the function is called recursively. + """ + for k, v in config_dict.items(): + namespace_keys.append(f"{prefix}{k}") + if isinstance(v, Dict): + build_config_namespace_keys(namespace_keys, v, f"{prefix}{k}.") + + +def search_configs(config_dict: Dict[str, Any], namespace_key: str) \ + -> Optional[Dict[str, Any]]: + """ + Search the config dictionary for a given namespace key and preserve the key hierarchy. + For example: + config_dict: {"a": 1, "b": {"ba": 2, "bb": 3}, "c": 4} + searching for b will result in {"b": {"ba": 2, "bb": 3}} + searching for b.ba will result in {"b": {"ba": 2}} + :param config_dict: The config dictionary + :param namespace_key: The namespace key to search for + :return: A dictionary matching the given key, returns None if not found + """ + key_parts = namespace_key.split(".") + if not key_parts[0] in config_dict: + return + result: Dict[str, Any] = {key_parts[0]: deepcopy(config_dict[key_parts[0]])} + result_val = result[key_parts[0]] + for key_part in key_parts[1:]: + if not isinstance(result_val, Dict) or key_part not in result_val: + return + temp = deepcopy(result_val[key_part]) + result_val.clear() + result_val[key_part] = temp + result_val = result_val[key_part] + return result diff --git a/hummingbot/core/utils/kill_switch.py b/hummingbot/core/utils/kill_switch.py new file mode 100644 index 0000000..27fb1d0 --- /dev/null +++ b/hummingbot/core/utils/kill_switch.py @@ -0,0 +1,82 @@ +import asyncio +import logging +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Optional + +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class KillSwitch(ABC): + @abstractmethod + def start(self): + ... + + @abstractmethod + def stop(self): + ... + + +class ActiveKillSwitch(KillSwitch): + ks_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.ks_logger is None: + cls.ks_logger = logging.getLogger(__name__) + return cls.ks_logger + + def __init__(self, + kill_switch_rate: Decimal, + hummingbot_application: "HummingbotApplication"): # noqa F821 + self._hummingbot_application = hummingbot_application + + self._kill_switch_rate: Decimal = kill_switch_rate / Decimal(100) + self._started = False + self._update_interval = 10.0 + self._check_profitability_task: Optional[asyncio.Task] = None + self._profitability: Optional[Decimal] = None + + async def check_profitability_loop(self): + while True: + try: + self._profitability: Decimal = await self._hummingbot_application.calculate_profitability() + + # Stop the bot if losing too much money, or if gained a certain amount of profit + if (self._profitability <= self._kill_switch_rate < Decimal("0.0")) or \ + (self._profitability >= self._kill_switch_rate > Decimal("0.0")): + self.logger().info("Kill switch threshold reached. Stopping the bot...") + self._hummingbot_application.notify(f"\n[Kill switch triggered]\n" + f"Current profitability " + f"is {self._profitability}. Stopping the bot...") + self._hummingbot_application.stop() + break + + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error(f"Error calculating profitability: {e}", exc_info=True) + + await asyncio.sleep(self._update_interval) + + def start(self): + safe_ensure_future(self.start_loop()) + + async def start_loop(self): + self.stop() + self._check_profitability_task = safe_ensure_future(self.check_profitability_loop()) + self._started = True + + def stop(self): + if self._check_profitability_task and not self._check_profitability_task.done(): + self._check_profitability_task.cancel() + self._started = False + + +class PassThroughKillSwitch(KillSwitch): + def start(self): + pass + + def stop(self): + pass diff --git a/hummingbot/core/utils/market_price.py b/hummingbot/core/utils/market_price.py new file mode 100644 index 0000000..0699653 --- /dev/null +++ b/hummingbot/core/utils/market_price.py @@ -0,0 +1,18 @@ +from decimal import Decimal +from typing import Optional + +from hummingbot.client.settings import AllConnectorSettings, ConnectorType + + +async def get_last_price(exchange: str, trading_pair: str) -> Optional[Decimal]: + if exchange in AllConnectorSettings.get_connector_settings(): + conn_setting = AllConnectorSettings.get_connector_settings()[exchange] + if AllConnectorSettings.get_connector_settings()[exchange].type in [ConnectorType.Exchange, + ConnectorType.Derivative]: + try: + connector = conn_setting.non_trading_connector_instance_with_default_configuration() + last_prices = await connector.get_last_traded_prices(trading_pairs=[trading_pair]) + if last_prices: + return Decimal(str(last_prices[trading_pair])) + except ModuleNotFoundError: + pass diff --git a/hummingbot/core/utils/ssl_cert.py b/hummingbot/core/utils/ssl_cert.py new file mode 100644 index 0000000..e60d27d --- /dev/null +++ b/hummingbot/core/utils/ssl_cert.py @@ -0,0 +1,238 @@ +""" +Functions for generating keys and certificates +""" +from datetime import datetime, timedelta +from os import listdir +from os.path import join +from typing import TYPE_CHECKING + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID + +from hummingbot import root_path +from hummingbot.core.gateway import get_gateway_paths + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +CERT_SUBJECT = [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'localhost'), + x509.NameAttribute(NameOID.COMMON_NAME, 'localhost'), +] +# Set alternative DNS +SAN_DNS = [x509.DNSName('localhost')] +VALIDITY_DURATION = 365 +CONF_DIR_PATH = root_path() / "conf" + + +def generate_private_key(password, filepath): + """ + Generate Private Key + """ + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + + algorithm = serialization.NoEncryption() + if password: + algorithm = serialization.BestAvailableEncryption(password.encode("utf-8")) + + # Write key to cert + # filepath = join(CERT_FILE_PATH, filename) + with open(filepath, "wb") as key_file: + key_file.write( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=algorithm, + ) + ) + + return private_key + + +def generate_public_key(private_key, filepath): + """ + Generate Public Key + """ + + # Subject info for certification + subject = x509.Name(CERT_SUBJECT) + + # Use subject as issuer on self-sign certificate + cert_issuer = subject + + # Set certification validity duration + current_datetime = datetime.utcnow() + expiration_datetime = current_datetime + timedelta(days=VALIDITY_DURATION) + + # Create certification + builder = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(cert_issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(current_datetime) + .not_valid_after(expiration_datetime) + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + ) + + # Use private key to sign cert + public_key = builder.sign( + private_key, + hashes.SHA256(), + default_backend() + ) + + # Write key to cert + # filepath = join(CERT_FILE_PATH, filename) + with open(filepath, "wb") as cert_file: + cert_file.write(public_key.public_bytes(serialization.Encoding.PEM)) + + return public_key + + +def generate_csr(private_key, filepath): + """ + Generate CSR (Certificate Signing Request) + """ + + # CSR subject cannot be the same as CERT_SUBJECT + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, 'localhost'), + ]) + + builder = ( + x509.CertificateSigningRequestBuilder() + .subject_name(subject) + .add_extension(x509.SubjectAlternativeName(SAN_DNS), critical=False) + ) + + csr = builder.sign(private_key, hashes.SHA256(), default_backend()) + + # filepath = join(CERT_FILE_PATH, filename) + with open(filepath, "wb") as csr_file: + csr_file.write(csr.public_bytes(serialization.Encoding.PEM)) + + return csr + + +def sign_csr(csr, ca_public_key, ca_private_key, filepath): + """ + Sign CSR with CA public & private keys & generate a verified public key + """ + + current_datetime = datetime.utcnow() + expiration_datetime = current_datetime + timedelta(days=VALIDITY_DURATION) + + try: + builder = ( + x509.CertificateBuilder() + .subject_name(csr.subject) + .issuer_name(ca_public_key.subject) + .public_key(csr.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(current_datetime) + .not_valid_after(expiration_datetime) + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True,) + ) + + for extension in csr.extensions: + builder = builder.add_extension(extension.value, extension.critical) + + public_key = builder.sign( + private_key=ca_private_key, + algorithm=hashes.SHA256(), + backend=default_backend(), + ) + + # filepath = join(CERT_FILE_PATH, filename) + with open(filepath, "wb") as key_file: + key_file.write(public_key.public_bytes(serialization.Encoding.PEM)) + + return filepath + except Exception as e: + raise Exception(e.output) + + +ca_key_filename = 'ca_key.pem' +ca_cert_filename = 'ca_cert.pem' +server_key_filename = 'server_key.pem' +server_cert_filename = 'server_cert.pem' +server_csr_filename = 'server_csr.pem' +client_key_filename = 'client_key.pem' +client_cert_filename = 'client_cert.pem' +client_csr_filename = 'client_csr.pem' + + +def certs_files_exist(client_config_map: "ClientConfigAdapter") -> bool: + """ + Check if the necessary key and certificate files exist + """ + required_certs = [ca_key_filename, ca_cert_filename, + server_key_filename, server_cert_filename, + client_key_filename, client_cert_filename] + + file_list = listdir(get_gateway_paths(client_config_map).local_certs_path.as_posix()) + return all(elem in file_list for elem in required_certs) + + +def create_self_sign_certs(pass_phase: str, cert_path: str): + """ + Create self-sign CA Cert + """ + + filepath_list = { + 'ca_key': join(cert_path, ca_key_filename), + 'ca_cert': join(cert_path, ca_cert_filename), + 'server_key': join(cert_path, server_key_filename), + 'server_cert': join(cert_path, server_cert_filename), + 'server_csr': join(cert_path, server_csr_filename), + 'client_key': join(cert_path, client_key_filename), + 'client_cert': join(cert_path, client_cert_filename), + 'client_csr': join(cert_path, client_csr_filename) + } + + # Create CA Private & Public Keys for signing + ca_private_key = generate_private_key(pass_phase, filepath_list['ca_key']) + generate_public_key(ca_private_key, filepath_list['ca_cert']) + + # Create Server Private & Public Keys for signing + server_private_key = generate_private_key(pass_phase, filepath_list['server_key']) + # Create CSR + generate_csr(server_private_key, filepath_list['server_csr']) + # Load CSR + with open(filepath_list['server_csr'], 'rb') as server_csr_file: + server_csr = x509.load_pem_x509_csr(server_csr_file.read(), default_backend()) + + # Create Client CSR + # local certificate must be unencrypted. Currently, Requests does not support using encrypted keys. + client_private_key = generate_private_key(None, filepath_list['client_key']) + # Create CSR + generate_csr(client_private_key, filepath_list['client_csr']) + # Load CSR + with open(filepath_list['client_csr'], 'rb') as client_csr_file: + client_csr = x509.load_pem_x509_csr(client_csr_file.read(), default_backend()) + + # Load CA public key + with open(filepath_list['ca_cert'], 'rb') as ca_cert_file: + ca_cert = x509.load_pem_x509_certificate(ca_cert_file.read(), default_backend()) + # Load CA private key + with open(filepath_list['ca_key'], 'rb') as ca_key_file: + ca_key = serialization.load_pem_private_key( + ca_key_file.read(), + pass_phase.encode('utf-8'), + default_backend(), + ) + + # Sign Server Cert with CSR + sign_csr(server_csr, ca_cert, ca_key, filepath_list['server_cert']) + # Sign Client Cert with CSR + sign_csr(client_csr, ca_cert, ca_key, filepath_list['client_cert']) diff --git a/hummingbot/core/utils/ssl_client_request.py b/hummingbot/core/utils/ssl_client_request.py new file mode 100644 index 0000000..92dfd51 --- /dev/null +++ b/hummingbot/core/utils/ssl_client_request.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +from aiohttp import ClientRequest +import certifi +import ssl +from typing import Optional + + +class SSLClientRequest(ClientRequest): + _sslcr_default_ssl_context: Optional[ssl.SSLContext] = None + + @classmethod + def default_ssl_context(cls) -> ssl.SSLContext: + if cls._sslcr_default_ssl_context is None: + cls._sslcr_default_ssl_context = ssl.create_default_context(cafile=certifi.where()) + return cls._sslcr_default_ssl_context + + def __init__(self, *args, **kwargs): + if "ssl" not in kwargs or kwargs["ssl"] is None: + kwargs["ssl"] = self.default_ssl_context() + super().__init__(*args, **kwargs) diff --git a/hummingbot/core/utils/tracking_nonce.py b/hummingbot/core/utils/tracking_nonce.py new file mode 100644 index 0000000..b85c259 --- /dev/null +++ b/hummingbot/core/utils/tracking_nonce.py @@ -0,0 +1,67 @@ +import time +import warnings +from typing import Optional, Union + + +class NonceCreator: + SECONDS_PRECISION = 1 + MILLISECONDS_PRECISION = 1000 + MICROSECONDS_PRECISION = 1000000 + + def __init__(self, precision: int): + self._precision = int(precision) + self._last_tracking_nonce = 0 + + @classmethod + def for_seconds(cls): + return cls(precision=cls.SECONDS_PRECISION) + + @classmethod + def for_milliseconds(cls): + return cls(precision=cls.MILLISECONDS_PRECISION) + + @classmethod + def for_microseconds(cls): + return cls(precision=cls.MICROSECONDS_PRECISION) + + def get_tracking_nonce(self, + timestamp: Optional[Union[float, int]] = None) -> int: + """ + Returns a unique number based on the timestamp provided as parameter or the machine time + :params timestamp: The timestamp to use as the base for the nonce. If not provided the current time will be used. + :return: the generated nonce + """ + nonce_candidate = int((timestamp or self._time()) * self._precision) + self._last_tracking_nonce = (nonce_candidate + if nonce_candidate > self._last_tracking_nonce + else self._last_tracking_nonce + 1) + return self._last_tracking_nonce + + @staticmethod + def _time() -> float: + """Mocked in test cases without affecting system `time.time()`.""" + return time.time() + + +_milliseconds_nonce_provider = NonceCreator.for_milliseconds() +_microseconds_nonce_provider = NonceCreator.for_microseconds() + + +def get_tracking_nonce() -> int: + # todo: remove + warnings.warn( + message=f"This method has been deprecate in favor of {NonceCreator.__class__.__name__}.", + category=DeprecationWarning, + ) + nonce = _microseconds_nonce_provider.get_tracking_nonce() + return nonce + + +def get_tracking_nonce_low_res() -> int: + # todo: remove + warnings.warn( + message=f"This method has been deprecate in favor of {NonceCreator.__class__.__name__}.", + category=DeprecationWarning, + ) + nonce = _milliseconds_nonce_provider.get_tracking_nonce() + return nonce diff --git a/hummingbot/core/utils/trading_pair_fetcher.py b/hummingbot/core/utils/trading_pair_fetcher.py new file mode 100644 index 0000000..64c9ca1 --- /dev/null +++ b/hummingbot/core/utils/trading_pair_fetcher.py @@ -0,0 +1,84 @@ +import logging +from typing import Any, Awaitable, Callable, Dict, List, Optional + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.settings import AllConnectorSettings, ConnectorSetting +from hummingbot.logger import HummingbotLogger + +from .async_utils import safe_ensure_future + + +class TradingPairFetcher: + _sf_shared_instance: "TradingPairFetcher" = None + _tpf_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._tpf_logger is None: + cls._tpf_logger = logging.getLogger(__name__) + return cls._tpf_logger + + @classmethod + def get_instance(cls, client_config_map: Optional["ClientConfigAdapter"] = None) -> "TradingPairFetcher": + if cls._sf_shared_instance is None: + client_config_map = client_config_map or cls._get_client_config_map() + cls._sf_shared_instance = TradingPairFetcher(client_config_map) + return cls._sf_shared_instance + + def __init__(self, client_config_map: ClientConfigAdapter): + self.ready = False + self.trading_pairs: Dict[str, Any] = {} + self.fetch_pairs_from_all_exchanges = client_config_map.fetch_pairs_from_all_exchanges + self._fetch_task = safe_ensure_future(self.fetch_all(client_config_map)) + + def _fetch_pairs_from_connector_setting( + self, + connector_setting: ConnectorSetting, + connector_name: Optional[str] = None): + connector_name = connector_name or connector_setting.name + connector = connector_setting.non_trading_connector_instance_with_default_configuration() + safe_ensure_future(self.call_fetch_pairs(connector.all_trading_pairs(), connector_name)) + + async def fetch_all(self, client_config_map: ClientConfigAdapter): + connector_settings = self._all_connector_settings() + for conn_setting in connector_settings.values(): + # XXX(martin_kou): Some connectors, e.g. uniswap v3, aren't completed yet. Ignore if you can't find the + # data source module for them. + try: + if conn_setting.base_name().endswith("paper_trade"): + self._fetch_pairs_from_connector_setting( + connector_setting=connector_settings[conn_setting.parent_name], + connector_name=conn_setting.name + ) + elif not self.fetch_pairs_from_all_exchanges: + if conn_setting.connector_connected(): + self._fetch_pairs_from_connector_setting(connector_setting=conn_setting) + else: + self._fetch_pairs_from_connector_setting(connector_setting=conn_setting) + except ModuleNotFoundError: + continue + except Exception: + self.logger().exception(f"An error occurred when fetching trading pairs for {conn_setting.name}." + "Please check the logs") + self.ready = True + + async def call_fetch_pairs(self, fetch_fn: Callable[[], Awaitable[List[str]]], exchange_name: str): + try: + pairs = await fetch_fn + self.trading_pairs[exchange_name] = pairs + except Exception: + self.logger().error(f"Connector {exchange_name} failed to retrieve its trading pairs. " + f"Trading pairs autocompletion won't work.", exc_info=True) + # In case of error just assign empty list, this is st. the bot won't stop working + self.trading_pairs[exchange_name] = [] + + def _all_connector_settings(self) -> Dict[str, ConnectorSetting]: + # Method created to enabling patching in unit tests + return AllConnectorSettings.get_connector_settings() + + @staticmethod + def _get_client_config_map() -> "ClientConfigAdapter": + from hummingbot.client.hummingbot_application import HummingbotApplication + + client_config_map = HummingbotApplication.main_application().client_config_map + return client_config_map diff --git a/hummingbot/core/web_assistant/__init__.py b/hummingbot/core/web_assistant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/web_assistant/auth.py b/hummingbot/core/web_assistant/auth.py new file mode 100644 index 0000000..e69fdd8 --- /dev/null +++ b/hummingbot/core/web_assistant/auth.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod + +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSRequest + + +class AuthBase(ABC): + """A base class for authentication objects that can be fed to the `WebAssistantsFactory`. + + Hint: If the authentication requires a simple REST request to acquire information from the + server that is required in the message signature, this class can be passed a `RESTConnection` + object that it can use to that end. + """ + + @abstractmethod + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + ... + + @abstractmethod + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + ... diff --git a/hummingbot/core/web_assistant/connections/__init__.py b/hummingbot/core/web_assistant/connections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/core/web_assistant/connections/connections_factory.py b/hummingbot/core/web_assistant/connections/connections_factory.py new file mode 100644 index 0000000..d10b494 --- /dev/null +++ b/hummingbot/core/web_assistant/connections/connections_factory.py @@ -0,0 +1,39 @@ +from typing import Optional + +import aiohttp + +from hummingbot.core.web_assistant.connections.rest_connection import RESTConnection +from hummingbot.core.web_assistant.connections.ws_connection import WSConnection + + +class ConnectionsFactory: + """This class is a thin wrapper around the underlying REST and WebSocket third-party library. + + The purpose of the class is to isolate the general `web_assistant` infrastructure from the underlying library + (in this case, `aiohttp`) to enable dependency change with minimal refactoring of the code. + + Note: One future possibility is to enable injection of a specific connection factory implementation in the + `WebAssistantsFactory` to accommodate cases such as Bittrex that uses a specific WebSocket technology requiring + a separate third-party library. In that case, a factory can be created that returns `RESTConnection`s using + `aiohttp` and `WSConnection`s using `signalr_aio`. + """ + + def __init__(self): + # _ws_independent_session is intended to be used only in unit tests + self._ws_independent_session: Optional[aiohttp.ClientSession] = None + + self._shared_client: Optional[aiohttp.ClientSession] = None + + async def get_rest_connection(self) -> RESTConnection: + shared_client = await self._get_shared_client() + connection = RESTConnection(aiohttp_client_session=shared_client) + return connection + + async def get_ws_connection(self) -> WSConnection: + shared_client = self._ws_independent_session or await self._get_shared_client() + connection = WSConnection(aiohttp_client_session=shared_client) + return connection + + async def _get_shared_client(self) -> aiohttp.ClientSession: + self._shared_client = self._shared_client or aiohttp.ClientSession() + return self._shared_client diff --git a/hummingbot/core/web_assistant/connections/data_types.py b/hummingbot/core/web_assistant/connections/data_types.py new file mode 100644 index 0000000..1702b9c --- /dev/null +++ b/hummingbot/core/web_assistant/connections/data_types.py @@ -0,0 +1,150 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from enum import Enum +from typing import TYPE_CHECKING, Any, Mapping, Optional + +import aiohttp +import ujson + +if TYPE_CHECKING: + from hummingbot.core.web_assistant.connections.ws_connection import WSConnection + + +class RESTMethod(Enum): + GET = "GET" + POST = "POST" + PUT = "PUT" + DELETE = "DELETE" + + def __str__(self): + obj_str = repr(self) + return obj_str + + def __repr__(self): + return self.value + + +@dataclass +class RESTRequest: + method: RESTMethod + url: Optional[str] = None + endpoint_url: Optional[str] = None + params: Optional[Mapping[str, str]] = None + data: Any = None + headers: Optional[Mapping[str, str]] = None + is_auth_required: bool = False + throttler_limit_id: Optional[str] = None + + +@dataclass +class EndpointRESTRequest(RESTRequest, ABC): + """This request class enable the user to provide either a complete URL or simply an endpoint. + + The endpoint is concatenated with the return value of `base_url`. It can handle endpoints supplied both as + `"endpoint"` and `"/endpoint"`. It also provides the necessary checks to ensure a valid URL can be constructed. + """ + + endpoint: Optional[str] = None + + def __post_init__(self): + self._ensure_url() + self._ensure_params() + self._ensure_data() + + @property + @abstractmethod + def base_url(self) -> str: + ... + + def _ensure_url(self): + if self.url is None and self.endpoint is None: + raise ValueError("Either the full url or the endpoint must be specified.") + if self.url is None: + if self.endpoint.startswith("/"): + self.url = f"{self.base_url}{self.endpoint}" + else: + self.url = f"{self.base_url}/{self.endpoint}" + + def _ensure_params(self): + if self.method == RESTMethod.POST: + if self.params is not None: + raise ValueError("POST requests should not use `params`. Use `data` instead.") + + def _ensure_data(self): + if self.method == RESTMethod.POST: + if self.data is not None: + self.data = ujson.dumps(self.data) + elif self.data is not None: + raise ValueError( + "The `data` field should be used only for POST requests. Use `params` instead." + ) + + +@dataclass(init=False) +class RESTResponse: + url: str + method: RESTMethod + status: int + headers: Optional[Mapping[str, str]] + + def __init__(self, aiohttp_response: aiohttp.ClientResponse): + self._aiohttp_response = aiohttp_response + + @property + def url(self) -> str: + url_str = str(self._aiohttp_response.url) + return url_str + + @property + def method(self) -> RESTMethod: + method_ = RESTMethod[self._aiohttp_response.method.upper()] + return method_ + + @property + def status(self) -> int: + status_ = int(self._aiohttp_response.status) + return status_ + + @property + def headers(self) -> Optional[Mapping[str, str]]: + headers_ = self._aiohttp_response.headers + return headers_ + + async def json(self) -> Any: + json_ = await self._aiohttp_response.json() + return json_ + + async def text(self) -> str: + text_ = await self._aiohttp_response.text() + return text_ + + +class WSRequest(ABC): + @abstractmethod + async def send_with_connection(self, connection: 'WSConnection'): + return NotImplemented + + +@dataclass +class WSJSONRequest(WSRequest): + payload: Mapping[str, Any] + throttler_limit_id: Optional[str] = None + is_auth_required: bool = False + + async def send_with_connection(self, connection: 'WSConnection'): + await connection._send_json(payload=self.payload) + + +@dataclass +class WSPlainTextRequest(WSRequest): + payload: str + throttler_limit_id: Optional[str] = None + is_auth_required: bool = False + + async def send_with_connection(self, connection: 'WSConnection'): + await connection._send_plain_text(payload=self.payload) + + +@dataclass +class WSResponse: + data: Any diff --git a/hummingbot/core/web_assistant/connections/rest_connection.py b/hummingbot/core/web_assistant/connections/rest_connection.py new file mode 100644 index 0000000..66e12ed --- /dev/null +++ b/hummingbot/core/web_assistant/connections/rest_connection.py @@ -0,0 +1,24 @@ +import aiohttp +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, RESTResponse + + +class RESTConnection: + def __init__(self, aiohttp_client_session: aiohttp.ClientSession): + self._client_session = aiohttp_client_session + + async def call(self, request: RESTRequest) -> RESTResponse: + aiohttp_resp = await self._client_session.request( + method=request.method.value, + url=request.url, + params=request.params, + data=request.data, + headers=request.headers, + ) + + resp = await self._build_resp(aiohttp_resp) + return resp + + @staticmethod + async def _build_resp(aiohttp_resp: aiohttp.ClientResponse) -> RESTResponse: + resp = RESTResponse(aiohttp_resp) + return resp diff --git a/hummingbot/core/web_assistant/connections/ws_connection.py b/hummingbot/core/web_assistant/connections/ws_connection.py new file mode 100644 index 0000000..3232404 --- /dev/null +++ b/hummingbot/core/web_assistant/connections/ws_connection.py @@ -0,0 +1,135 @@ +import asyncio +import time +from json import JSONDecodeError +from typing import Any, Dict, Mapping, Optional + +import aiohttp + +from hummingbot.core.web_assistant.connections.data_types import WSRequest, WSResponse + + +class WSConnection: + def __init__(self, aiohttp_client_session: aiohttp.ClientSession): + self._client_session = aiohttp_client_session + self._connection: Optional[aiohttp.ClientWebSocketResponse] = None + self._connected = False + self._message_timeout: Optional[float] = None + self._last_recv_time = 0 + + @property + def last_recv_time(self) -> float: + return self._last_recv_time + + @property + def connected(self) -> bool: + return self._connected + + async def connect( + self, + ws_url: str, + ping_timeout: float = 10, + message_timeout: Optional[float] = None, + ws_headers: Optional[Dict] = {}, + ): + self._ensure_not_connected() + self._connection = await self._client_session.ws_connect( + ws_url, + headers=ws_headers, + autoping=False, + heartbeat=ping_timeout, + ) + self._message_timeout = message_timeout + self._connected = True + + async def disconnect(self): + if self._connection is not None and not self._connection.closed: + await self._connection.close() + self._connection = None + self._connected = False + + async def send(self, request: WSRequest): + self._ensure_connected() + await request.send_with_connection(connection=self) + + async def ping(self): + await self._connection.ping() + + async def receive(self) -> Optional[WSResponse]: + self._ensure_connected() + response = None + while self._connected: + msg = await self._read_message() + msg = await self._process_message(msg) + if msg is not None: + response = self._build_resp(msg) + break + return response + + def _ensure_not_connected(self): + if self._connected: + raise RuntimeError("WS is connected.") + + def _ensure_connected(self): + if not self._connected: + raise RuntimeError("WS is not connected.") + + async def _read_message(self) -> aiohttp.WSMessage: + try: + msg = await self._connection.receive(self._message_timeout) + except asyncio.TimeoutError: + raise asyncio.TimeoutError("Message receive timed out.") + return msg + + async def _process_message(self, msg: aiohttp.WSMessage) -> Optional[aiohttp.WSMessage]: + msg = await self._check_msg_types(msg) + self._update_last_recv_time(msg) + return msg + + async def _check_msg_types(self, msg: aiohttp.WSMessage) -> Optional[aiohttp.WSMessage]: + msg = await self._check_msg_closed_type(msg) + msg = await self._check_msg_ping_type(msg) + msg = await self._check_msg_pong_type(msg) + return msg + + async def _check_msg_closed_type(self, msg: Optional[aiohttp.WSMessage]) -> Optional[aiohttp.WSMessage]: + if msg is not None and msg.type in [aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE]: + if self._connected: + close_code = self._connection.close_code + await self.disconnect() + raise ConnectionError( + f"The WS connection was closed unexpectedly. Close code = {close_code} msg data: {msg.data}" + ) + msg = None + return msg + + async def _check_msg_ping_type(self, msg: Optional[aiohttp.WSMessage]) -> Optional[aiohttp.WSMessage]: + if msg is not None and msg.type == aiohttp.WSMsgType.PING: + await self._connection.pong() + msg = None + return msg + + async def _check_msg_pong_type(self, msg: Optional[aiohttp.WSMessage]) -> Optional[aiohttp.WSMessage]: + if msg is not None and msg.type == aiohttp.WSMsgType.PONG: + msg = None + return msg + + def _update_last_recv_time(self, _: aiohttp.WSMessage): + self._last_recv_time = time.time() + + async def _send_json(self, payload: Mapping[str, Any]): + await self._connection.send_json(payload) + + async def _send_plain_text(self, payload: str): + await self._connection.send_str(payload) + + @staticmethod + def _build_resp(msg: aiohttp.WSMessage) -> WSResponse: + if msg.type == aiohttp.WSMsgType.BINARY: + data = msg.data + else: + try: + data = msg.json() + except JSONDecodeError: + data = msg.data + response = WSResponse(data) + return response diff --git a/hummingbot/core/web_assistant/rest_assistant.py b/hummingbot/core/web_assistant/rest_assistant.py new file mode 100644 index 0000000..be0c94e --- /dev/null +++ b/hummingbot/core/web_assistant/rest_assistant.py @@ -0,0 +1,124 @@ +import json +from asyncio import wait_for +from copy import deepcopy +from typing import Any, Dict, List, Optional, Union + +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, RESTResponse +from hummingbot.core.web_assistant.connections.rest_connection import RESTConnection +from hummingbot.core.web_assistant.rest_post_processors import RESTPostProcessorBase +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase + + +class RESTAssistant: + """A helper class to contain all REST-related logic. + + The class can be injected with additional functionality by passing a list of objects inheriting from + the `RESTPreProcessorBase` and `RESTPostProcessorBase` classes. The pre-processors are applied to a request + before it is sent out, while the post-processors are applied to a response before it is returned to the caller. + """ + def __init__( + self, + connection: RESTConnection, + throttler: AsyncThrottlerBase, + rest_pre_processors: Optional[List[RESTPreProcessorBase]] = None, + rest_post_processors: Optional[List[RESTPostProcessorBase]] = None, + auth: Optional[AuthBase] = None, + ): + self._connection = connection + self._rest_pre_processors = rest_pre_processors or [] + self._rest_post_processors = rest_post_processors or [] + self._auth = auth + self._throttler = throttler + + async def execute_request( + self, + url: str, + throttler_limit_id: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + timeout: Optional[float] = None, + headers: Optional[Dict[str, Any]] = None, + ) -> Union[str, Dict[str, Any]]: + response = await self.execute_request_and_get_response( + url=url, + throttler_limit_id=throttler_limit_id, + params=params, + data=data, + method=method, + is_auth_required=is_auth_required, + return_err=return_err, + timeout=timeout, + headers=headers, + ) + response_json = await response.json() + return response_json + + async def execute_request_and_get_response( + self, + url: str, + throttler_limit_id: str, + params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + method: RESTMethod = RESTMethod.GET, + is_auth_required: bool = False, + return_err: bool = False, + timeout: Optional[float] = None, + headers: Optional[Dict[str, Any]] = None, + ) -> RESTResponse: + + headers = headers or {} + + local_headers = { + "Content-Type": ("application/json" if method != RESTMethod.GET else "application/x-www-form-urlencoded")} + local_headers.update(headers) + + data = json.dumps(data) if data is not None else data + + request = RESTRequest( + method=method, + url=url, + params=params, + data=data, + headers=local_headers, + is_auth_required=is_auth_required, + throttler_limit_id=throttler_limit_id + ) + + async with self._throttler.execute_task(limit_id=throttler_limit_id): + response = await self.call(request=request, timeout=timeout) + + if 400 <= response.status: + if not return_err: + error_response = await response.text() + error_text = "N/A" if " RESTResponse: + request = deepcopy(request) + request = await self._pre_process_request(request) + request = await self._authenticate(request) + resp = await wait_for(self._connection.call(request), timeout) + resp = await self._post_process_response(resp) + return resp + + async def _pre_process_request(self, request: RESTRequest) -> RESTRequest: + for pre_processor in self._rest_pre_processors: + request = await pre_processor.pre_process(request) + return request + + async def _authenticate(self, request: RESTRequest): + if self._auth is not None and request.is_auth_required: + request = await self._auth.rest_authenticate(request) + return request + + async def _post_process_response(self, response: RESTResponse) -> RESTResponse: + for post_processor in self._rest_post_processors: + response = await post_processor.post_process(response) + return response diff --git a/hummingbot/core/web_assistant/rest_post_processors.py b/hummingbot/core/web_assistant/rest_post_processors.py new file mode 100644 index 0000000..1fe2655 --- /dev/null +++ b/hummingbot/core/web_assistant/rest_post_processors.py @@ -0,0 +1,15 @@ +import abc + +from hummingbot.core.web_assistant.connections.data_types import RESTResponse + + +class RESTPostProcessorBase(abc.ABC): + """An interface class that enables functionality injection into the `RESTAssistant`. + + The logic provided by a class implementing this interface is applied to a response + before it is returned to the caller. + """ + + @abc.abstractmethod + async def post_process(self, response: RESTResponse) -> RESTResponse: + ... diff --git a/hummingbot/core/web_assistant/rest_pre_processors.py b/hummingbot/core/web_assistant/rest_pre_processors.py new file mode 100644 index 0000000..56ae4d2 --- /dev/null +++ b/hummingbot/core/web_assistant/rest_pre_processors.py @@ -0,0 +1,15 @@ +import abc + +from hummingbot.core.web_assistant.connections.data_types import RESTRequest + + +class RESTPreProcessorBase(abc.ABC): + """An interface class that enables functionality injection into the `RESTAssistant`. + + The logic provided by a class implementing this interface is applied to a request + before it is sent out to the server. + """ + + @abc.abstractmethod + async def pre_process(self, request: RESTRequest) -> RESTRequest: + ... diff --git a/hummingbot/core/web_assistant/web_assistants_factory.py b/hummingbot/core/web_assistant/web_assistants_factory.py new file mode 100644 index 0000000..e137ef1 --- /dev/null +++ b/hummingbot/core/web_assistant/web_assistants_factory.py @@ -0,0 +1,65 @@ +from typing import List, Optional + +from hummingbot.core.api_throttler.async_throttler_base import AsyncThrottlerBase +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.connections_factory import ConnectionsFactory +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.rest_post_processors import RESTPostProcessorBase +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.core.web_assistant.ws_post_processors import WSPostProcessorBase +from hummingbot.core.web_assistant.ws_pre_processors import WSPreProcessorBase + + +class WebAssistantsFactory: + """Creates `RESTAssistant` and `WSAssistant` objects. + + The purpose of the `web_assistant` layer is to abstract away all WebSocket and REST operations from the exchange + logic. The assistant objects are designed to be injectable with additional logic via the pre- and post-processor + lists. Consult the documentation of the relevant assistant and/or pre-/post-processor class for + additional information. + + todo: integrate AsyncThrottler + """ + def __init__( + self, + throttler: AsyncThrottlerBase, + rest_pre_processors: Optional[List[RESTPreProcessorBase]] = None, + rest_post_processors: Optional[List[RESTPostProcessorBase]] = None, + ws_pre_processors: Optional[List[WSPreProcessorBase]] = None, + ws_post_processors: Optional[List[WSPostProcessorBase]] = None, + auth: Optional[AuthBase] = None, + ): + self._connections_factory = ConnectionsFactory() + self._rest_pre_processors = rest_pre_processors or [] + self._rest_post_processors = rest_post_processors or [] + self._ws_pre_processors = ws_pre_processors or [] + self._ws_post_processors = ws_post_processors or [] + self._auth = auth + self._throttler = throttler + + @property + def throttler(self) -> AsyncThrottlerBase: + return self._throttler + + @property + def auth(self) -> Optional[AuthBase]: + return self._auth + + async def get_rest_assistant(self) -> RESTAssistant: + connection = await self._connections_factory.get_rest_connection() + assistant = RESTAssistant( + connection=connection, + throttler=self._throttler, + rest_pre_processors=self._rest_pre_processors, + rest_post_processors=self._rest_post_processors, + auth=self._auth + ) + return assistant + + async def get_ws_assistant(self) -> WSAssistant: + connection = await self._connections_factory.get_ws_connection() + assistant = WSAssistant( + connection, self._ws_pre_processors, self._ws_post_processors, self._auth + ) + return assistant diff --git a/hummingbot/core/web_assistant/ws_assistant.py b/hummingbot/core/web_assistant/ws_assistant.py new file mode 100644 index 0000000..9e428cc --- /dev/null +++ b/hummingbot/core/web_assistant/ws_assistant.py @@ -0,0 +1,89 @@ +from copy import deepcopy +from typing import AsyncGenerator, Dict, List, Optional + +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import WSRequest, WSResponse +from hummingbot.core.web_assistant.connections.ws_connection import WSConnection +from hummingbot.core.web_assistant.ws_post_processors import WSPostProcessorBase +from hummingbot.core.web_assistant.ws_pre_processors import WSPreProcessorBase + + +class WSAssistant: + """A helper class to contain all WebSocket-related logic. + + The class can be injected with additional functionality by passing a list of objects inheriting from + the `WSPreProcessorBase` and `WSPostProcessorBase` classes. The pre-processors are applied to a request + before it is sent out, while the post-processors are applied to a response before it is returned to the caller. + """ + + def __init__( + self, + connection: WSConnection, + ws_pre_processors: Optional[List[WSPreProcessorBase]] = None, + ws_post_processors: Optional[List[WSPostProcessorBase]] = None, + auth: Optional[AuthBase] = None, + ): + self._connection = connection + self._ws_pre_processors = ws_pre_processors or [] + self._ws_post_processors = ws_post_processors or [] + self._auth = auth + + @property + def last_recv_time(self) -> float: + return self._connection.last_recv_time + + async def connect( + self, + ws_url: str, + *, + ping_timeout: float = 10, + message_timeout: Optional[float] = None, + ws_headers: Optional[Dict] = {}, + ): + await self._connection.connect(ws_url=ws_url, ws_headers=ws_headers, ping_timeout=ping_timeout, message_timeout=message_timeout) + + async def disconnect(self): + await self._connection.disconnect() + + async def subscribe(self, request: WSRequest): + """Will eventually be used to handle automatic re-connection.""" + await self.send(request) + + async def send(self, request: WSRequest): + request = deepcopy(request) + request = await self._pre_process_request(request) + request = await self._authenticate(request) + await self._connection.send(request) + + async def ping(self): + await self._connection.ping() + + async def iter_messages(self) -> AsyncGenerator[Optional[WSResponse], None]: + """Will yield None and stop if `WSDelegate.disconnect()` is called while waiting for a response.""" + while self._connection.connected: + response = await self._connection.receive() + if response is not None: + response = await self._post_process_response(response) + yield response + + async def receive(self) -> Optional[WSResponse]: + """This method will return `None` if `WSDelegate.disconnect()` is called while waiting for a response.""" + response = await self._connection.receive() + if response is not None: + response = await self._post_process_response(response) + return response + + async def _pre_process_request(self, request: WSRequest) -> WSRequest: + for pre_processor in self._ws_pre_processors: + request = await pre_processor.pre_process(request) + return request + + async def _authenticate(self, request: WSRequest) -> WSRequest: + if self._auth is not None and request.is_auth_required: + request = await self._auth.ws_authenticate(request) + return request + + async def _post_process_response(self, response: WSResponse) -> WSResponse: + for post_processor in self._ws_post_processors: + response = await post_processor.post_process(response) + return response diff --git a/hummingbot/core/web_assistant/ws_post_processors.py b/hummingbot/core/web_assistant/ws_post_processors.py new file mode 100644 index 0000000..1d9dac8 --- /dev/null +++ b/hummingbot/core/web_assistant/ws_post_processors.py @@ -0,0 +1,15 @@ +import abc + +from hummingbot.core.web_assistant.connections.data_types import WSResponse + + +class WSPostProcessorBase(abc.ABC): + """An interface class that enables functionality injection into the `WSAssistant`. + + The logic provided by a class implementing this interface is applied to a response + before it is returned to the caller. + """ + + @abc.abstractmethod + async def post_process(self, response: WSResponse) -> WSResponse: + ... diff --git a/hummingbot/core/web_assistant/ws_pre_processors.py b/hummingbot/core/web_assistant/ws_pre_processors.py new file mode 100644 index 0000000..1ecae25 --- /dev/null +++ b/hummingbot/core/web_assistant/ws_pre_processors.py @@ -0,0 +1,15 @@ +import abc + +from hummingbot.core.web_assistant.connections.data_types import WSRequest + + +class WSPreProcessorBase(abc.ABC): + """An interface class that enables functionality injection into the `WSAssistant`. + + The logic provided by a class implementing this interface is applied to a request + before it is sent out to the server. + """ + + @abc.abstractmethod + async def pre_process(self, request: WSRequest) -> WSRequest: + ... diff --git a/hummingbot/data_feed/__init__.py b/hummingbot/data_feed/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/data_feed/amm_gateway_data_feed.py b/hummingbot/data_feed/amm_gateway_data_feed.py new file mode 100644 index 0000000..49273b6 --- /dev/null +++ b/hummingbot/data_feed/amm_gateway_data_feed.py @@ -0,0 +1,145 @@ +import asyncio +import logging +from decimal import Decimal +from typing import Dict, Optional, Set + +from pydantic import BaseModel + +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class TokenBuySellPrice(BaseModel): + base: str + quote: str + connector: str + chain: str + network: str + order_amount_in_base: Decimal + buy_price: Decimal + sell_price: Decimal + + +class AmmGatewayDataFeed(NetworkBase): + dex_logger: Optional[HummingbotLogger] = None + gateway_client = GatewayHttpClient.get_instance() + + def __init__( + self, + connector_chain_network: str, + trading_pairs: Set[str], + order_amount_in_base: Decimal, + update_interval: float = 1.0, + ) -> None: + super().__init__() + self._ev_loop = asyncio.get_event_loop() + self._price_dict: Dict[str, TokenBuySellPrice] = {} + self._update_interval = update_interval + self.fetch_data_loop_task: Optional[asyncio.Task] = None + # param required for DEX API request + self.connector_chain_network = connector_chain_network + self.trading_pairs = trading_pairs + self.order_amount_in_base = order_amount_in_base + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.dex_logger is None: + cls.dex_logger = logging.getLogger(__name__) + return cls.dex_logger + + @property + def name(self) -> str: + return f"AmmDataFeed[{self.connector_chain_network}]" + + @property + def connector(self) -> str: + return self.connector_chain_network.split("_")[0] + + @property + def chain(self) -> str: + return self.connector_chain_network.split("_")[1] + + @property + def network(self) -> str: + return self.connector_chain_network.split("_")[2] + + @property + def price_dict(self) -> Dict[str, TokenBuySellPrice]: + return self._price_dict + + def is_ready(self) -> bool: + return len(self._price_dict) == len(self.trading_pairs) + + async def check_network(self) -> NetworkStatus: + is_gateway_online = await self.gateway_client.ping_gateway() + if not is_gateway_online: + self.logger().warning("Gateway is not online. Please check your gateway connection.") + return NetworkStatus.CONNECTED if is_gateway_online else NetworkStatus.NOT_CONNECTED + + async def start_network(self) -> None: + await self.stop_network() + self.fetch_data_loop_task = safe_ensure_future(self._fetch_data_loop()) + + async def stop_network(self) -> None: + if self.fetch_data_loop_task is not None: + self.fetch_data_loop_task.cancel() + self.fetch_data_loop_task = None + + async def _fetch_data_loop(self) -> None: + while True: + try: + await self._fetch_data() + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error( + f"Error getting data from {self.name}" + f"Check network connection. Error: {e}", + ) + await self._async_sleep(self._update_interval) + + async def _fetch_data(self) -> None: + token_price_tasks = [ + asyncio.create_task(self._register_token_buy_sell_price(trading_pair)) + for trading_pair in self.trading_pairs + ] + await asyncio.gather(*token_price_tasks) + + async def _register_token_buy_sell_price(self, trading_pair: str) -> None: + base, quote = split_hb_trading_pair(trading_pair) + token_buy_price_task = asyncio.create_task(self._request_token_price(trading_pair, TradeType.BUY)) + token_sell_price_task = asyncio.create_task(self._request_token_price(trading_pair, TradeType.SELL)) + self._price_dict[trading_pair] = TokenBuySellPrice( + base=base, + quote=quote, + connector=self.connector, + chain=self.chain, + network=self.network, + order_amount_in_base=self.order_amount_in_base, + buy_price=await token_buy_price_task, + sell_price=await token_sell_price_task, + ) + + async def _request_token_price(self, trading_pair: str, trade_type: TradeType) -> Decimal: + base, quote = split_hb_trading_pair(trading_pair) + connector, chain, network = self.connector_chain_network.split("_") + token_price = await self.gateway_client.get_price( + chain, + network, + connector, + base, + quote, + self.order_amount_in_base, + trade_type, + ) + return Decimal(token_price["price"]) + + @staticmethod + async def _async_sleep(delay: float) -> None: + """Used to mock in test cases.""" + await asyncio.sleep(delay) diff --git a/hummingbot/data_feed/candles_feed/__init__.py b/hummingbot/data_feed/candles_feed/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/__init__.py b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/__init__.py new file mode 100644 index 0000000..e5aed62 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.ascend_ex_spot_candles import AscendExSpotCandles + +__all__ = ["AscendExSpotCandles"] diff --git a/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/ascend_ex_spot_candles.py b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/ascend_ex_spot_candles.py new file mode 100644 index 0000000..e4af3ab --- /dev/null +++ b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/ascend_ex_spot_candles.py @@ -0,0 +1,175 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +import numpy as np + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.ascend_ex_spot_candles import constants as CONSTANTS +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.logger import HummingbotLogger + + +class AscendExSpotCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + + @property + def name(self): + return f"ascend_ex_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return CONSTANTS.WSS_URL + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair.replace("-", "/") + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"symbol": self._ex_trading_pair, "interval": CONSTANTS.INTERVALS[self.interval], "n": limit} + if start_time: + params["from"] = start_time + if end_time: + params["to"] = end_time + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + new_hb_candles = [] + for i in candles["data"]: + timestamp_ms = i["data"]["ts"] + open = i["data"]["o"] + high = i["data"]["h"] + low = i["data"]["l"] + close = i["data"]["c"] + quote_asset_volume = i["data"]["v"] + # no data field + volume = 0 + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + new_hb_candles.append([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume]) + return np.array(new_hb_candles).astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1000) + 1 + requests_executed = 0 + while not self.is_ready: + missing_records = self._candles.maxlen - len(self._candles) + end_timestamp = int(self._candles[0][0]) + try: + if requests_executed < max_request_needed: + # we have to add one more since, the last row is not going to be included + candles = await self.fetch_candles(end_time=end_timestamp, limit=missing_records + 1) + # we are computing again the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[-(missing_records + 1):-1][::-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + payload = {"op": CONSTANTS.SUB_ENDPOINT_NAME, + "ch": f"bar:{CONSTANTS.INTERVALS[self.interval]}:{self._ex_trading_pair}"} + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data.get("m") == "ping": + pong_payloads = {"op": "pong"} + pong_request = WSJSONRequest(payload=pong_payloads) + await websocket_assistant.send(request=pong_request) + if data is not None and data.get("m") == "bar": # data will be None when the websocket is disconnected + timestamp = data["data"]["ts"] + open = data["data"]["o"] + high = data["data"]["h"] + low = data["data"]["l"] + close = data["data"]["c"] + quote_asset_volume = data["data"]["v"] + volume = 0 + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + if len(self._candles) == 0: + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 1M interval). + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + elif timestamp == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) diff --git a/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/constants.py b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/constants.py new file mode 100644 index 0000000..2fdbbe5 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/constants.py @@ -0,0 +1,35 @@ +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +REST_URL = "https://ascendex.com/api/pro/v1/" +HEALTH_CHECK_ENDPOINT = "risk-limit-info" +CANDLES_ENDPOINT = "barhist" +SUB_ENDPOINT_NAME = "sub" + +WSS_URL = "wss://ascendex.com:443/api/pro/v1/websocket-for-hummingbot-liq-mining/stream" + +# Plesae note that the one-month bar (1m) always resets at the month start. +# The intervalInMillis value for the one-month bar is only indicative. +INTERVALS = bidict({ + "1m": "1", + "5m": "5", + "15m": "15", + "30m": "30", + "1h": "60", + "2h": "120", + "4h": "240", + "6h": "360", + "12h": "720", + "1d": "1d", + "1w": "1w", + "1M": "1m" +}) + +ALL_ENDPOINTS_LIMIT = "All" + +RATE_LIMITS = [ + RateLimit(ALL_ENDPOINTS_LIMIT, limit=100, time_interval=1), + RateLimit(CANDLES_ENDPOINT, limit=100, time_interval=1, linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)]), + RateLimit(HEALTH_CHECK_ENDPOINT, limit=100, time_interval=1, + linked_limits=[LinkedLimitWeightPair(ALL_ENDPOINTS_LIMIT)])] diff --git a/hummingbot/data_feed/candles_feed/binance_perpetual_candles/__init__.py b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/__init__.py new file mode 100644 index 0000000..d24f676 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/__init__.py @@ -0,0 +1,5 @@ +from hummingbot.data_feed.candles_feed.binance_perpetual_candles.binance_perpetual_candles import ( + BinancePerpetualCandles, +) + +__all__ = ["BinancePerpetualCandles"] diff --git a/hummingbot/data_feed/candles_feed/binance_perpetual_candles/binance_perpetual_candles.py b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/binance_perpetual_candles.py new file mode 100644 index 0000000..91f72b3 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/binance_perpetual_candles.py @@ -0,0 +1,161 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +import numpy as np + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.binance_perpetual_candles import constants as CONSTANTS +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.logger import HummingbotLogger + + +class BinancePerpetualCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + + @property + def name(self): + return f"binance_perpetual_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return CONSTANTS.WSS_URL + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair.replace("-", "") + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"symbol": self._ex_trading_pair, "interval": self.interval, "limit": limit} + if start_time: + params["startTime"] = start_time + if end_time: + params["endTime"] = end_time + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + + return np.array(candles)[:, [0, 1, 2, 3, 4, 5, 7, 8, 9, 10]].astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1000) + 1 + requests_executed = 0 + while not self.is_ready: + missing_records = self._candles.maxlen - len(self._candles) + end_timestamp = int(self._candles[0][0]) + try: + if requests_executed < max_request_needed: + # we have to add one more since, the last row is not going to be included + candles = await self.fetch_candles(end_time=end_timestamp, limit=min(1000, missing_records + 1)) + # we are computing again the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[-(missing_records + 1):-1][::-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + candle_params = [] + candle_params.append(f"{self._ex_trading_pair.lower()}@kline_{self.interval}") + payload = { + "method": "SUBSCRIBE", + "params": candle_params, + "id": 1 + } + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data is not None and data.get("e") == "kline": # data will be None when the websocket is disconnected + timestamp = data["k"]["t"] + open = data["k"]["o"] + low = data["k"]["l"] + high = data["k"]["h"] + close = data["k"]["c"] + volume = data["k"]["v"] + quote_asset_volume = data["k"]["q"] + n_trades = data["k"]["n"] + taker_buy_base_volume = data["k"]["V"] + taker_buy_quote_volume = data["k"]["Q"] + if len(self._candles) == 0: + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 1M interval). + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + elif timestamp == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) diff --git a/hummingbot/data_feed/candles_feed/binance_perpetual_candles/constants.py b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/constants.py new file mode 100644 index 0000000..3d71323 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_perpetual_candles/constants.py @@ -0,0 +1,34 @@ +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +REST_URL = "https://fapi.binance.com" +HEALTH_CHECK_ENDPOINT = "/fapi/v1/ping" +CANDLES_ENDPOINT = "/fapi/v1/klines" + +WSS_URL = "wss://fstream.binance.com/ws" + +INTERVALS = bidict({ + "1m": 60, + "3m": 180, + "5m": 300, + "15m": 900, + "30m": 1800, + "1h": 3600, + "2h": 7200, + "4h": 14400, + "6h": 21600, + "8h": 28800, + "12h": 43200, + "1d": 86400, + "3d": 259200, + "1w": 604800, + "1M": 2592000 +}) + +REQUEST_WEIGHT = "REQUEST_WEIGHT" + +RATE_LIMITS = [ + RateLimit(REQUEST_WEIGHT, limit=1200, time_interval=60), + RateLimit(CANDLES_ENDPOINT, weight=2, limit=1200, time_interval=60, linked_limits=[LinkedLimitWeightPair("raw", 1)]), + RateLimit(HEALTH_CHECK_ENDPOINT, limit=1200, time_interval=60, linked_limits=[LinkedLimitWeightPair("raw", 1)])] diff --git a/hummingbot/data_feed/candles_feed/binance_spot_candles/__init__.py b/hummingbot/data_feed/candles_feed/binance_spot_candles/__init__.py new file mode 100644 index 0000000..6049b13 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_spot_candles/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles import BinanceSpotCandles + +__all__ = ["BinanceSpotCandles"] diff --git a/hummingbot/data_feed/candles_feed/binance_spot_candles/binance_spot_candles.py b/hummingbot/data_feed/candles_feed/binance_spot_candles/binance_spot_candles.py new file mode 100644 index 0000000..374d821 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_spot_candles/binance_spot_candles.py @@ -0,0 +1,161 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +import numpy as np + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.binance_spot_candles import constants as CONSTANTS +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.logger import HummingbotLogger + + +class BinanceSpotCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + + @property + def name(self): + return f"binance_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return CONSTANTS.WSS_URL + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair.replace("-", "") + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"symbol": self._ex_trading_pair, "interval": self.interval, "limit": limit} + if start_time: + params["startTime"] = start_time + if end_time: + params["endTime"] = end_time + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + + return np.array(candles)[:, [0, 1, 2, 3, 4, 5, 7, 8, 9, 10]].astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1000) + 1 + requests_executed = 0 + while not self.is_ready: + missing_records = self._candles.maxlen - len(self._candles) + end_timestamp = int(self._candles[0][0]) + try: + if requests_executed < max_request_needed: + # we have to add one more since, the last row is not going to be included + candles = await self.fetch_candles(end_time=end_timestamp, limit=missing_records + 1) + # we are computing again the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[-(missing_records + 1):-1][::-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + candle_params = [] + candle_params.append(f"{self._ex_trading_pair.lower()}@kline_{self.interval}") + payload = { + "method": "SUBSCRIBE", + "params": candle_params, + "id": 1 + } + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data is not None and data.get("e") == "kline": # data will be None when the websocket is disconnected + timestamp = data["k"]["t"] + open = data["k"]["o"] + high = data["k"]["h"] + low = data["k"]["l"] + close = data["k"]["c"] + volume = data["k"]["v"] + quote_asset_volume = data["k"]["q"] + n_trades = data["k"]["n"] + taker_buy_base_volume = data["k"]["V"] + taker_buy_quote_volume = data["k"]["Q"] + if len(self._candles) == 0: + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 1M interval). + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + elif timestamp == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(np.array([timestamp, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) diff --git a/hummingbot/data_feed/candles_feed/binance_spot_candles/constants.py b/hummingbot/data_feed/candles_feed/binance_spot_candles/constants.py new file mode 100644 index 0000000..ac3bb66 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/binance_spot_candles/constants.py @@ -0,0 +1,35 @@ +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +REST_URL = "https://api.binance.com" +HEALTH_CHECK_ENDPOINT = "/api/v3/ping" +CANDLES_ENDPOINT = "/api/v3/klines" + +WSS_URL = "wss://stream.binance.com:9443/ws" + +INTERVALS = bidict({ + "1s": "1s", + "1m": "1m", + "3m": "3m", + "5m": "5m", + "15m": "15m", + "30m": "30m", + "1h": "1h", + "2h": "2h", + "4h": "4h", + "6h": "6h", + "8h": "8h", + "12h": "12h", + "1d": "1d", + "3d": "3d", + "1w": "1w", + "1M": "1M" +}) + +REQUEST_WEIGHT = "REQUEST_WEIGHT" + +RATE_LIMITS = [ + RateLimit(REQUEST_WEIGHT, limit=6000, time_interval=60), + RateLimit(CANDLES_ENDPOINT, limit=1200, time_interval=60, linked_limits=[LinkedLimitWeightPair("raw", 1)]), + RateLimit(HEALTH_CHECK_ENDPOINT, limit=1200, time_interval=60, linked_limits=[LinkedLimitWeightPair("raw", 1)])] diff --git a/hummingbot/data_feed/candles_feed/candles_base.py b/hummingbot/data_feed/candles_feed/candles_base.py new file mode 100644 index 0000000..8a79976 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/candles_base.py @@ -0,0 +1,210 @@ +import asyncio +import os +from collections import deque +from typing import Optional + +import pandas as pd +from bidict import bidict + +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + + +class CandlesBase(NetworkBase): + """ + This class serves as a base class for fetching and storing candle data from a cryptocurrency exchange. + The class uses the Rest and WS Assistants for all the IO operations, and a double-ended queue to store candles. + Also implements the Throttler module for API rate limiting, but it's not so necessary since the realtime data should + be updated via websockets mainly. + """ + interval_to_seconds = bidict({ + "1s": 1, + "1m": 60, + "3m": 180, + "5m": 300, + "15m": 900, + "30m": 1800, + "1h": 3600, + "2h": 7200, + "4h": 14400, + "6h": 21600, + "8h": 28800, + "12h": 43200, + "1d": 86400, + "3d": 259200, + "1w": 604800, + "1M": 2592000 + }) + columns = ["timestamp", "open", "high", "low", "close", "volume", "quote_asset_volume", + "n_trades", "taker_buy_base_volume", "taker_buy_quote_volume"] + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__() + async_throttler = AsyncThrottler(rate_limits=self.rate_limits) + self._api_factory = WebAssistantsFactory(throttler=async_throttler) + self._candles = deque(maxlen=max_records) + self._listen_candles_task: Optional[asyncio.Task] = None + self._trading_pair = trading_pair + self._ex_trading_pair = self.get_exchange_trading_pair(trading_pair) + if interval in self.intervals.keys(): + self.interval = interval + else: + self.logger().exception( + f"Interval {interval} is not supported. Available Intervals: {self.intervals.keys()}") + raise + + async def start_network(self): + """ + This method starts the network and starts a task for listen_for_subscriptions. + """ + await self.stop_network() + self._listen_candles_task = safe_ensure_future(self.listen_for_subscriptions()) + + async def stop_network(self): + """ + This method stops the network by canceling the _listen_candles_task task. + """ + if self._listen_candles_task is not None: + self._listen_candles_task.cancel() + self._listen_candles_task = None + + @property + def is_ready(self): + """ + This property returns a boolean indicating whether the _candles deque has reached its maximum length. + """ + return len(self._candles) == self._candles.maxlen + + @property + def name(self): + raise NotImplementedError + + @property + def rest_url(self): + raise NotImplementedError + + @property + def health_check_url(self): + raise NotImplementedError + + @property + def candles_url(self): + raise NotImplementedError + + @property + def wss_url(self): + raise NotImplementedError + + @property + def rate_limits(self): + raise NotImplementedError + + @property + def intervals(self): + raise NotImplementedError + + async def check_network(self) -> NetworkStatus: + raise NotImplementedError + + @property + def candles_df(self) -> pd.DataFrame: + """ + This property returns the candles stored in the _candles deque as a Pandas DataFrame. + """ + return pd.DataFrame(self._candles, columns=self.columns, dtype=float) + + def get_exchange_trading_pair(self, trading_pair): + raise NotImplementedError + + def load_candles_from_csv(self, data_path: str): + """ + This method loads the candles from a CSV file. + :param data_path: data path that holds the CSV file + """ + filename = f"candles_{self.name}_{self.interval}.csv" + file_path = os.path.join(data_path, filename) + if not os.path.exists(file_path): + raise FileNotFoundError(f"File '{file_path}' does not exist.") + df = pd.read_csv(file_path) + df.sort_values(by="timestamp", ascending=False, inplace=True) + self._candles.extendleft(df.values.tolist()) + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + """ + This is an abstract method that must be implemented by a subclass to fetch candles from the exchange API. + :param start_time: start time to fetch candles + :param end_time: end time to fetch candles + :param limit: quantity of candles + :return: numpy array with the candlesticks + """ + raise NotImplementedError + + async def fill_historical_candles(self): + """ + This is an abstract method that must be implemented by a subclass to fill the _candles deque with historical candles. + """ + raise NotImplementedError + + async def listen_for_subscriptions(self): + """ + Connects to the candlestick websocket endpoint and listens to the messages sent by the + exchange. + """ + ws: Optional[WSAssistant] = None + while True: + try: + ws: WSAssistant = await self._connected_websocket_assistant() + await self._subscribe_channels(ws) + await self._process_websocket_messages(websocket_assistant=ws) + except asyncio.CancelledError: + raise + except ConnectionError as connection_exception: + self.logger().warning(f"The websocket connection was closed ({connection_exception})") + except Exception: + self.logger().exception( + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + finally: + await self._on_order_stream_interruption(websocket_assistant=ws) + + async def _connected_websocket_assistant(self) -> WSAssistant: + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=self.wss_url, + ping_timeout=30) + return ws + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + raise NotImplementedError + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + raise NotImplementedError + + async def _sleep(self, delay): + """ + Function added only to facilitate patching the sleep in unit tests without affecting the asyncio module + """ + await asyncio.sleep(delay) + + async def _on_order_stream_interruption(self, websocket_assistant: Optional[WSAssistant] = None): + websocket_assistant and await websocket_assistant.disconnect() + self._candles.clear() + + def get_seconds_from_interval(self, interval: str) -> int: + """ + This method returns the number of seconds from the interval string. + :param interval: interval string + :return: number of seconds + """ + return self.interval_to_seconds[interval] diff --git a/hummingbot/data_feed/candles_feed/candles_factory.py b/hummingbot/data_feed/candles_feed/candles_factory.py new file mode 100644 index 0000000..4da0800 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/candles_factory.py @@ -0,0 +1,57 @@ +from pydantic import BaseModel + +from hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.ascend_ex_spot_candles import AscendExSpotCandles +from hummingbot.data_feed.candles_feed.binance_perpetual_candles import BinancePerpetualCandles +from hummingbot.data_feed.candles_feed.binance_spot_candles import BinanceSpotCandles +from hummingbot.data_feed.candles_feed.gate_io_perpetual_candles import GateioPerpetualCandles +from hummingbot.data_feed.candles_feed.gate_io_spot_candles import GateioSpotCandles +from hummingbot.data_feed.candles_feed.kucoin_spot_candles.kucoin_spot_candles import KucoinSpotCandles + + +class CandlesConfig(BaseModel): + """ + The CandlesConfig class is a data class that stores the configuration of a Candle object. + It has the following attributes: + - connector: str + - trading_pair: str + - interval: str + - max_records: int + """ + connector: str + trading_pair: str + interval: str = "1m" + max_records: int = 500 + + +class CandlesFactory: + """ + The CandlesFactory class creates and returns a Candle object based on the specified connector and trading pair. + It has a class method, get_candle which takes in a connector, trading pair, interval, and max_records as parameters. + Based on the connector provided, the method returns either a BinancePerpetualsCandles or a BinanceSpotCandles object. + If an unsupported connector is provided, it raises an exception. + """ + @classmethod + def get_candle(cls, candles_config: CandlesConfig): + """ + Returns a Candle object based on the specified connector and trading pair. + :param candles_config: CandlesConfig + :return: Candles + """ + connector = candles_config.connector + trading_pair = candles_config.trading_pair + interval = candles_config.interval + max_records = candles_config.max_records + if connector == "binance_perpetual": + return BinancePerpetualCandles(trading_pair, interval, max_records) + elif connector == "binance": + return BinanceSpotCandles(trading_pair, interval, max_records) + elif connector == "gate_io": + return GateioSpotCandles(trading_pair, interval, max_records) + elif connector == "gate_io_perpetual": + return GateioPerpetualCandles(trading_pair, interval, max_records) + elif connector == "kucoin": + return KucoinSpotCandles(trading_pair, interval, max_records) + elif connector == "ascend_ex": + return AscendExSpotCandles(trading_pair, interval, max_records) + else: + raise Exception(f"The connector {connector} is not available. Please select another one.") diff --git a/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/__init__.py b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/__init__.py new file mode 100644 index 0000000..dc509f0 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.gate_io_perpetual_candles import GateioPerpetualCandles + +__all__ = ["GateioPerpetualCandles"] diff --git a/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/constants.py b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/constants.py new file mode 100644 index 0000000..ab8e935 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/constants.py @@ -0,0 +1,35 @@ +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +REST_URL = "https://api.gateio.ws/api/v4" +HEALTH_CHECK_ENDPOINT = "/futures/usdt/contracts/BTC_USDT" +CANDLES_ENDPOINT = "/futures/usdt/candlesticks" +CONTRACT_INFO_URL = "/futures/usdt/contracts/{contract}" + + +WS_CANDLES_ENDPOINT = "futures.candlesticks" + +WSS_URL = "wss://fx-ws.gateio.ws/v4/ws/usdt" +INTERVALS = bidict({ + "1m": "1m", + "5m": "5m", + "15m": "15m", + "30m": "30m", + "1h": "1h", + "4h": "4h", + "8h": "8h", + "1d": "1d", + "7d": "7d", +}) +PUBLIC_URL_POINTS_LIMIT_ID = "PublicPoints" + +RATE_LIMITS = [ + RateLimit(limit_id=PUBLIC_URL_POINTS_LIMIT_ID, limit=300, time_interval=1), + RateLimit(limit_id=HEALTH_CHECK_ENDPOINT, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=CANDLES_ENDPOINT, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=CONTRACT_INFO_URL, limit=300, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), +] diff --git a/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/gate_io_perpetual_candles.py b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/gate_io_perpetual_candles.py new file mode 100644 index 0000000..a413d89 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/gate_io_perpetual_candles.py @@ -0,0 +1,203 @@ +import asyncio +import logging +import time +from typing import Any, Dict, Optional + +import numpy as np + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.data_feed.candles_feed.gate_io_perpetual_candles import constants as CONSTANTS +from hummingbot.logger import HummingbotLogger + + +class GateioPerpetualCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + self.quanto_multiplier = None + + @property + def name(self): + return f"gate_io_perpetual_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return CONSTANTS.WSS_URL + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + async def start_network(self): + """ + This method starts the network and starts a task for listen_for_subscriptions. + """ + await self.stop_network() + await self.get_exchange_trading_pair_quanto_multiplier() + self._listen_candles_task = safe_ensure_future(self.listen_for_subscriptions()) + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair.replace("-", "_") + + async def get_exchange_trading_pair_quanto_multiplier(self): + rest_assistant = await self._api_factory.get_rest_assistant() + data = await rest_assistant.execute_request( + url=self.rest_url + CONSTANTS.CONTRACT_INFO_URL.format(contract=self._ex_trading_pair), + throttler_limit_id=CONSTANTS.CONTRACT_INFO_URL + ) + quanto_multiplier = float(data.get("quanto_multiplier")) + self.quanto_multiplier = quanto_multiplier + return quanto_multiplier + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"contract": self._ex_trading_pair, "interval": self.interval, "limit": limit} + if start_time or end_time: + del params["limit"] + if start_time: + params["from"] = str(start_time) + if end_time: + params["to"] = str(end_time) + + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + new_hb_candles = [] + for i in candles: + timestamp_ms = i.get("t") * 1e3 + open = i.get("o") + high = i.get("h") + low = i.get("l") + close = i.get("c") + volume = i.get("v") * self.quanto_multiplier + quote_asset_volume = i.get("sum") + # no data field + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + new_hb_candles.append([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume]) + return np.array(new_hb_candles).astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1000) + 1 + requests_executed = 0 + while not self.is_ready: + missing_records = self._candles.maxlen - len(self._candles) + end_timestamp = int(int(self._candles[0][0]) * 1e-3) + try: + if requests_executed < max_request_needed: + # we have to add one more since, the last row is not going to be included + candles = await self.fetch_candles(end_time=end_timestamp, limit=missing_records + 1) + # we are computing again the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[-(missing_records + 1):-1][::-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + payload = { + "time": int(time.time()), + "channel": CONSTANTS.WS_CANDLES_ENDPOINT, + "event": "subscribe", + "payload": [self.interval, self._ex_trading_pair] + } + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + if self.interval == '1m': + self.logger().warning("The 1m K-line on gateioperpetual is currently not accurate due to discrepancies between the official ws and rs data...") + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + + if data.get("event") == "update" and data.get("channel") == "futures.candlesticks": + for i in data["result"]: + timestamp_ms = int(i["t"] * 1e3) + open = i["o"] + high = i["h"] + low = i["l"] + close = i["c"] + volume = i["v"] * self.quanto_multiplier + # no data field + quote_asset_volume = 0 + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + if len(self._candles) == 0: + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp_ms > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 1w, 30d interval). + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + elif timestamp_ms == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) diff --git a/hummingbot/data_feed/candles_feed/gate_io_spot_candles/__init__.py b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/__init__.py new file mode 100644 index 0000000..d62bb96 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.candles_feed.gate_io_spot_candles.gate_io_spot_candles import GateioSpotCandles + +__all__ = ["GateioSpotCandles"] diff --git a/hummingbot/data_feed/candles_feed/gate_io_spot_candles/constants.py b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/constants.py new file mode 100644 index 0000000..a6a936a --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/constants.py @@ -0,0 +1,34 @@ +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +REST_URL = "https://api.gateio.ws/api/v4" +HEALTH_CHECK_ENDPOINT = "/spot/currencies/BTC" +CANDLES_ENDPOINT = "/spot/candlesticks" +WS_CANDLES_ENDPOINT = "spot.candlesticks" + +WSS_URL = "wss://api.gateio.ws/ws/v4/" + + +INTERVALS = bidict({ + "10s": "10s", + "1m": "1m", + "5m": "5m", + "15m": "15m", + "30m": "30m", + "1h": "1h", + "4h": "4h", + "8h": "8h", + "1d": "1d", + "7d": "7d", + "30d": "30d", +}) +PUBLIC_URL_POINTS_LIMIT_ID = "PublicPoints" + +RATE_LIMITS = [ + RateLimit(limit_id=PUBLIC_URL_POINTS_LIMIT_ID, limit=900, time_interval=1), + RateLimit(limit_id=HEALTH_CHECK_ENDPOINT, limit=900, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), + RateLimit(limit_id=CANDLES_ENDPOINT, limit=900, time_interval=1, + linked_limits=[LinkedLimitWeightPair(PUBLIC_URL_POINTS_LIMIT_ID)]), +] diff --git a/hummingbot/data_feed/candles_feed/gate_io_spot_candles/gate_io_spot_candles.py b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/gate_io_spot_candles.py new file mode 100644 index 0000000..ceb2182 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/gate_io_spot_candles/gate_io_spot_candles.py @@ -0,0 +1,177 @@ +import asyncio +import logging +import time +from typing import Any, Dict, Optional + +import numpy as np + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.data_feed.candles_feed.gate_io_spot_candles import constants as CONSTANTS +from hummingbot.logger import HummingbotLogger + + +class GateioSpotCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1m", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + + @property + def name(self): + return f"gate_io_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return CONSTANTS.WSS_URL + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair.replace("-", "_") + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"currency_pair": self._ex_trading_pair, "interval": self.interval, "limit": limit} + if start_time: + params["from"] = start_time + if end_time: + params["to"] = end_time + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + new_hb_candles = [] + for i in candles: + timestamp_ms = i[0] + "000" + open = i[5] + high = i[3] + low = i[4] + close = i[2] + volume = i[6] + quote_asset_volume = i[1] + # no data field + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + new_hb_candles.append([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume]) + return np.array(new_hb_candles).astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1000) + 1 + requests_executed = 0 + while not self.is_ready: + missing_records = self._candles.maxlen - len(self._candles) + end_timestamp = int(int(self._candles[0][0]) * 1e-3) + try: + if requests_executed < max_request_needed: + # we have to add one more since, the last row is not going to be included + candles = await self.fetch_candles(end_time=end_timestamp, limit=missing_records + 1) + # we are computing again the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[-(missing_records + 1):-1][::-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + payload = { + "time": int(time.time()), + "channel": CONSTANTS.WS_CANDLES_ENDPOINT, + "event": "subscribe", + "payload": [self.interval, self._ex_trading_pair] + } + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data.get("event") == "update" and data.get("channel") == "spot.candlesticks": + timestamp_ms = int(data["result"]["t"] + "000") + open = data["result"]["o"] + high = data["result"]["h"] + low = data["result"]["l"] + close = data["result"]["c"] + volume = data["result"]["v"] + quote_asset_volume = data["result"]["a"] + # no data field + n_trades = 0 + taker_buy_base_volume = 0 + taker_buy_quote_volume = 0 + if len(self._candles) == 0: + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp_ms > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 30d interval). + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) + elif timestamp_ms == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(np.array([timestamp_ms, open, high, low, close, volume, + quote_asset_volume, n_trades, taker_buy_base_volume, + taker_buy_quote_volume])) diff --git a/hummingbot/data_feed/candles_feed/kucoin_spot_candles/__init__.py b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/__init__.py new file mode 100644 index 0000000..b2fa578 --- /dev/null +++ b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.candles_feed.kucoin_spot_candles.kucoin_spot_candles import KucoinSpotCandles + +__all__ = ["KucoinSpotCandles"] diff --git a/hummingbot/data_feed/candles_feed/kucoin_spot_candles/constants.py b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/constants.py new file mode 100644 index 0000000..0ce530a --- /dev/null +++ b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/constants.py @@ -0,0 +1,34 @@ +import sys + +from bidict import bidict + +from hummingbot.core.api_throttler.data_types import RateLimit + +REST_URL = "https://api.kucoin.com" +HEALTH_CHECK_ENDPOINT = "/api/v1/timestamp" +CANDLES_ENDPOINT = "/api/v1/market/candles" + +PUBLIC_WS_DATA_PATH_URL = "/api/v1/bullet-public" + +INTERVALS = bidict({ + "1m": "1min", + "3m": "3min", + "15m": "15min", + "30m": "30min", + "1h": "1hour", + "2h": "2hour", + "4h": "4hour", + "6h": "6hour", + "8h": "8hour", + "12h": "12hour", + "1d": "1day", + "1w": "1week", +}) + +REQUEST_WEIGHT = "REQUEST_WEIGHT" +NO_LIMIT = sys.maxsize + +RATE_LIMITS = [ + RateLimit(limit_id=PUBLIC_WS_DATA_PATH_URL, limit=NO_LIMIT, time_interval=1), + RateLimit(CANDLES_ENDPOINT, limit=30, time_interval=60), + RateLimit(HEALTH_CHECK_ENDPOINT, limit=30, time_interval=60)] diff --git a/hummingbot/data_feed/candles_feed/kucoin_spot_candles/kucoin_spot_candles.py b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/kucoin_spot_candles.py new file mode 100644 index 0000000..f726aaa --- /dev/null +++ b/hummingbot/data_feed/candles_feed/kucoin_spot_candles/kucoin_spot_candles.py @@ -0,0 +1,209 @@ +import asyncio +import logging +import time +from typing import Any, Dict, Optional + +import numpy as np +import pandas as pd + +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, WSJSONRequest +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.data_feed.candles_feed.kucoin_spot_candles import constants as CONSTANTS +from hummingbot.logger import HummingbotLogger + + +class KucoinSpotCandles(CandlesBase): + _logger: Optional[HummingbotLogger] = None + _last_ws_message_sent_timestamp = 0 + _ping_interval = 0 + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, trading_pair: str, interval: str = "1min", max_records: int = 150): + super().__init__(trading_pair, interval, max_records) + + @property + def name(self): + return f"kucoin_{self._trading_pair}" + + @property + def rest_url(self): + return CONSTANTS.REST_URL + + @property + def wss_url(self): + return None + + @property + def health_check_url(self): + return self.rest_url + CONSTANTS.HEALTH_CHECK_ENDPOINT + + @property + def candles_url(self): + return self.rest_url + CONSTANTS.CANDLES_ENDPOINT + + @property + def public_ws_url(self): + return self.rest_url + CONSTANTS.PUBLIC_WS_DATA_PATH_URL + + @property + def rate_limits(self): + return CONSTANTS.RATE_LIMITS + + @property + def intervals(self): + return CONSTANTS.INTERVALS + + @property + def candles_df(self) -> pd.DataFrame: + df = pd.DataFrame(self._candles, columns=self.columns, dtype=float) + df["timestamp"] = df["timestamp"] * 1000 + return df.sort_values(by="timestamp", ascending=True) + + async def check_network(self) -> NetworkStatus: + rest_assistant = await self._api_factory.get_rest_assistant() + await rest_assistant.execute_request(url=self.health_check_url, + throttler_limit_id=CONSTANTS.HEALTH_CHECK_ENDPOINT) + return NetworkStatus.CONNECTED + + def get_exchange_trading_pair(self, trading_pair): + return trading_pair + + async def fetch_candles(self, + start_time: Optional[int] = None, + end_time: Optional[int] = None, + limit: Optional[int] = 1500): + rest_assistant = await self._api_factory.get_rest_assistant() + params = {"symbol": self._ex_trading_pair, "type": CONSTANTS.INTERVALS[self.interval]} + if start_time: + params["startAt"] = start_time + if end_time: + params["endAt"] = end_time + candles = await rest_assistant.execute_request(url=self.candles_url, + throttler_limit_id=CONSTANTS.CANDLES_ENDPOINT, + params=params) + arr = [[row[0], row[1], row[3], row[4], row[2], row[5], row[6]] for row in candles['data']] + return np.array(arr).astype(float) + + async def fill_historical_candles(self): + max_request_needed = (self._candles.maxlen // 1500) + 1 + requests_executed = 0 + while not self.is_ready: + # missing_records = self._candles.maxlen - len(self._candles) + try: + if requests_executed < max_request_needed: + end_timestamp = int(self._candles[0][0] + 1) + # we have to add one more since, the last row is not going to be included + start_time = end_timestamp - (1500 * self.get_seconds_from_interval(self.interval)) + 1 + candles = await self.fetch_candles(end_time=end_timestamp, start_time=start_time) + # we are computing agaefin the quantity of records again since the websocket process is able to + # modify the deque and if we extend it, the new observations are going to be dropped. + missing_records = self._candles.maxlen - len(self._candles) + self._candles.extendleft(candles[::-1][-(missing_records + 1):-1]) + requests_executed += 1 + else: + self.logger().error(f"There is no data available for the quantity of " + f"candles requested for {self.name}.") + raise + except asyncio.CancelledError: + raise + except Exception: + self.logger().exception( + "Unexpected error occurred when getting historical klines. Retrying in 1 seconds...", + ) + await self._sleep(1.0) + + async def _subscribe_channels(self, ws: WSAssistant): + """ + Subscribes to the candles events through the provided websocket connection. + :param ws: the websocket assistant used to connect to the exchange + """ + try: + payload = { + "id": str(get_tracking_nonce()), + "type": "subscribe", + "topic": f"/market/candles:{self._ex_trading_pair}_{CONSTANTS.INTERVALS[self.interval]}", + "privateChannel": False, + "response": False, + } + subscribe_candles_request: WSJSONRequest = WSJSONRequest(payload=payload) + + await ws.send(subscribe_candles_request) + self.logger().info("Subscribed to public klines...") + except asyncio.CancelledError: + raise + except Exception: + self.logger().error( + "Unexpected error occurred subscribing to public klines...", + exc_info=True + ) + raise + + async def _process_websocket_messages(self, websocket_assistant: WSAssistant): + while True: + try: + seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) + await asyncio.wait_for(self._process_websocket_messages_from_candles(websocket_assistant=websocket_assistant), + timeout=seconds_until_next_ping) + except asyncio.TimeoutError: + payload = { + "id": str(get_tracking_nonce()), + "type": "ping", + } + ping_request = WSJSONRequest(payload=payload) + self._last_ws_message_sent_timestamp = self._time() + await websocket_assistant.send(request=ping_request) + + async def _process_websocket_messages_from_candles(self, websocket_assistant: WSAssistant): + async for ws_response in websocket_assistant.iter_messages(): + data: Dict[str, Any] = ws_response.data + if data is not None and data.get( + "subject") == "trade.candles.update": # data will be None when the websocket is disconnected + candles = data["data"]["candles"] + timestamp = float(candles[0]) + open = candles[1] + close = candles[2] + high = candles[3] + low = candles[4] + volume = candles[5] + quote_asset_volume = candles[6] + n_trades = 0. + taker_buy_base_volume = 0. + taker_buy_quote_volume = 0. + candles_array = np.array([timestamp, open, high, low, close, volume, quote_asset_volume, n_trades, + taker_buy_base_volume, taker_buy_quote_volume]).astype(float) + if len(self._candles) == 0: + self._candles.append(candles_array) + safe_ensure_future(self.fill_historical_candles()) + elif timestamp > int(self._candles[-1][0]): + # TODO: validate also that the diff of timestamp == interval (issue with 1M interval). + self._candles.append(candles_array) + elif timestamp == int(self._candles[-1][0]): + self._candles.pop() + self._candles.append(candles_array) + + async def _connected_websocket_assistant(self) -> WSAssistant: + rest_assistant = await self._api_factory.get_rest_assistant() + connection_info = await rest_assistant.execute_request( + url=self.public_ws_url, + method=RESTMethod.POST, + throttler_limit_id=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, + ) + + ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] + self._ping_interval = int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3 + token = connection_info["data"]["token"] + + ws: WSAssistant = await self._api_factory.get_ws_assistant() + await ws.connect(ws_url=f"{ws_url}?token={token}", message_timeout=self._ping_interval) + return ws + + def _time(self): + return time.time() diff --git a/hummingbot/data_feed/coin_cap_data_feed/__init__.py b/hummingbot/data_feed/coin_cap_data_feed/__init__.py new file mode 100644 index 0000000..b24d31e --- /dev/null +++ b/hummingbot/data_feed/coin_cap_data_feed/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.coin_cap_data_feed.coin_cap_data_feed import CoinCapDataFeed + +__all__ = ["CoinCapDataFeed"] diff --git a/hummingbot/data_feed/coin_cap_data_feed/coin_cap_constants.py b/hummingbot/data_feed/coin_cap_data_feed/coin_cap_constants.py new file mode 100644 index 0000000..94458eb --- /dev/null +++ b/hummingbot/data_feed/coin_cap_data_feed/coin_cap_constants.py @@ -0,0 +1,38 @@ +import sys + +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit + +UNIVERSAL_QUOTE_TOKEN = "USD" # coincap only works with USD + +BASE_REST_URL = "https://api.coincap.io/v2" +BASE_WS_URL = "wss://ws.coincap.io/prices?assets=" + +ALL_ASSETS_ENDPOINT = "/assets" +ASSET_ENDPOINT = "/assets/{}" +HEALTH_CHECK_ENDPOINT = ASSET_ENDPOINT.format("bitcoin") # get a single asset + +ALL_ASSETS_LIMIT_ID = "allAssetsLimitID" +ASSET_LIMIT_ID = "assetLimitID" +NO_KEY_LIMIT_ID = "noKeyLimitID" +API_KEY_LIMIT_ID = "APIKeyLimitID" +WS_CONNECTIONS_LIMIT_ID = "WSConnectionsLimitID" +NO_KEY_LIMIT = 200 +API_KEY_LIMIT = 500 +NO_LIMIT = sys.maxsize +MINUTE = 60 +SECOND = 1 + +RATE_LIMITS = [ + RateLimit(limit_id=API_KEY_LIMIT_ID, limit=API_KEY_LIMIT, time_interval=MINUTE), + RateLimit( + limit_id=NO_KEY_LIMIT_ID, + limit=NO_KEY_LIMIT, + time_interval=MINUTE, + linked_limits=[LinkedLimitWeightPair(API_KEY_LIMIT_ID)], + ), + RateLimit( + limit_id=WS_CONNECTIONS_LIMIT_ID, + limit=NO_LIMIT, + time_interval=SECOND, + ), +] diff --git a/hummingbot/data_feed/coin_cap_data_feed/coin_cap_data_feed.py b/hummingbot/data_feed/coin_cap_data_feed/coin_cap_data_feed.py new file mode 100644 index 0000000..46dacce --- /dev/null +++ b/hummingbot/data_feed/coin_cap_data_feed/coin_cap_data_feed.py @@ -0,0 +1,165 @@ +import asyncio +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Dict, Optional + +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.network_iterator import NetworkStatus, safe_ensure_future +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, RESTResponse +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.data_feed.coin_cap_data_feed import coin_cap_constants as CONSTANTS +from hummingbot.data_feed.data_feed_base import DataFeedBase +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: + from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class CoinCapAPIKeyAppender(RESTPreProcessorBase): + def __init__(self, api_key: str): + super().__init__() + self._api_key = api_key + + async def pre_process(self, request: RESTRequest) -> RESTRequest: + request.headers = request.headers or {} + request.headers["Authorization"] = self._api_key + return request + + +class CoinCapDataFeed(DataFeedBase): + _logger: Optional[HummingbotLogger] = None + _async_throttler: Optional["AsyncThrottler"] = None + + @classmethod + def _get_async_throttler(cls) -> "AsyncThrottler": + """This avoids circular imports.""" + from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + if cls._async_throttler is None: + cls._async_throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + return cls._async_throttler + + def __init__(self, assets_map: Dict[str, str], api_key: str): + super().__init__() + self._assets_map = assets_map + self._price_dict: Dict[str, Decimal] = {} + self._api_factory: Optional[WebAssistantsFactory] = None + self._api_key = api_key + self._is_api_key_authorized = True + self._prices_stream_task: Optional[asyncio.Task] = None + + self._ready_event.set() + + @property + def name(self): + return "coin_cap_api" + + @property + def health_check_endpoint(self): + return f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.HEALTH_CHECK_ENDPOINT}" + + @property + def universal_quote_token(self) -> str: + return CONSTANTS.UNIVERSAL_QUOTE_TOKEN + + async def start_network(self): + self._prices_stream_task = safe_ensure_future(self._stream_prices()) + + async def stop_network(self): + self._prices_stream_task and self._prices_stream_task.cancel() + self._prices_stream_task = None + + async def check_network(self) -> NetworkStatus: + try: + await self._make_request(url=self.health_check_endpoint) + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + async def get_all_usd_quoted_prices(self) -> Dict[str, Decimal]: + prices = ( + self._price_dict + if self._prices_stream_task and len(self._price_dict) != 0 + else await self._get_all_usd_quoted_prices_by_rest_request() + ) + return prices + + def _get_api_factory(self) -> WebAssistantsFactory: + # Avoids circular logic (i.e. CoinCap needs a throttler, which needs a client config map, which needs + # a data feed — CoinCap, in this case) + if self._api_factory is None: + self._api_factory = WebAssistantsFactory( + throttler=self._get_async_throttler(), + rest_pre_processors=[CoinCapAPIKeyAppender(api_key=self._api_key)], + ) + return self._api_factory + + async def _get_all_usd_quoted_prices_by_rest_request(self) -> Dict[str, Decimal]: + prices = {} + url = f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.ALL_ASSETS_ENDPOINT}" + + params = { + "ids": ",".join(self._assets_map.values()), + } + + data = await self._make_request(url=url, params=params) + for asset_data in data["data"]: + base = asset_data["symbol"] + trading_pair = combine_to_hb_trading_pair(base=base, quote=CONSTANTS.UNIVERSAL_QUOTE_TOKEN) + try: + prices[trading_pair] = Decimal(asset_data["priceUsd"]) + except TypeError: + continue + + return prices + + async def _make_request(self, url: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + api_factory = self._get_api_factory() + rest_assistant = await api_factory.get_rest_assistant() + rate_limit_id = CONSTANTS.API_KEY_LIMIT_ID if self._is_api_key_authorized else CONSTANTS.NO_KEY_LIMIT_ID + response = await rest_assistant.execute_request_and_get_response( + url=url, + throttler_limit_id=rate_limit_id, + params=params, + method=RESTMethod.GET, + ) + self._check_is_api_key_authorized(response=response) + data = await response.json() + return data + + def _check_is_api_key_authorized(self, response: RESTResponse): + self.logger().debug(f"CoinCap REST response headers: {response.headers}") + self._is_api_key_authorized = int(response.headers["X-Ratelimit-Limit"]) == CONSTANTS.API_KEY_LIMIT + if not self._is_api_key_authorized and self._api_key != "": + self.logger().warning("CoinCap API key is not authorized. Please check your API key.") + + async def _stream_prices(self): + while True: + try: + api_factory = self._get_api_factory() + self._price_dict = await self._get_all_usd_quoted_prices_by_rest_request() + ws = await api_factory.get_ws_assistant() + symbols_map = {asset_id: symbol for symbol, asset_id in self._assets_map.items()} + ws_url = f"{CONSTANTS.BASE_WS_URL}{','.join(self._assets_map.values())}" + async with api_factory.throttler.execute_task(limit_id=CONSTANTS.WS_CONNECTIONS_LIMIT_ID): + await ws.connect(ws_url=ws_url) + async for msg in ws.iter_messages(): + for asset_id, price_str in msg.data.items(): + base = symbols_map[asset_id] + trading_pair = combine_to_hb_trading_pair(base=base, quote=CONSTANTS.UNIVERSAL_QUOTE_TOKEN) + self._price_dict[trading_pair] = Decimal(price_str) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network( + log_msg="Unexpected error while streaming prices. Restarting the stream.", + exc_info=True, + ) + await self._sleep(delay=1) + + @staticmethod + async def _sleep(delay: float): + """Used for unit-test mocking.""" + await asyncio.sleep(delay) diff --git a/hummingbot/data_feed/coin_gecko_data_feed/__init__.py b/hummingbot/data_feed/coin_gecko_data_feed/__init__.py new file mode 100644 index 0000000..c7de9d1 --- /dev/null +++ b/hummingbot/data_feed/coin_gecko_data_feed/__init__.py @@ -0,0 +1,3 @@ +from hummingbot.data_feed.coin_gecko_data_feed.coin_gecko_data_feed import CoinGeckoDataFeed + +__all__ = ["CoinGeckoDataFeed"] diff --git a/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_constants.py b/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_constants.py new file mode 100644 index 0000000..ca59901 --- /dev/null +++ b/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_constants.py @@ -0,0 +1,20 @@ +from hummingbot.core.api_throttler.data_types import RateLimit + +BASE_URL = "https://api.coingecko.com/api/v3" +PING_REST_ENDPOINT = "/ping" +PRICES_REST_ENDPOINT = "/coins/markets" +SUPPORTED_VS_TOKENS_REST_ENDPOINT = "/simple/supported_vs_currencies" + +COOLOFF_AFTER_BAN = 60.0 * 1.05 + +REST_CALL_RATE_LIMIT_ID = "coin_gecko_rest_rate_limit_id" +RATE_LIMITS = [RateLimit(REST_CALL_RATE_LIMIT_ID, limit=10, time_interval=60)] + +TOKEN_CATEGORIES = [ + "cryptocurrency", + "decentralized-exchange", + "decentralized-finance-defi", + "smart-contract-platform", + "stablecoins", + "wrapped-tokens", +] diff --git a/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_data_feed.py b/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_data_feed.py new file mode 100644 index 0000000..470f9b0 --- /dev/null +++ b/hummingbot/data_feed/coin_gecko_data_feed/coin_gecko_data_feed.py @@ -0,0 +1,144 @@ +import asyncio +import logging +from typing import Any, Dict, List, Optional + +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.data_feed.coin_gecko_data_feed import coin_gecko_constants as CONSTANTS +from hummingbot.data_feed.data_feed_base import DataFeedBase +from hummingbot.logger import HummingbotLogger + + +class CoinGeckoDataFeed(DataFeedBase): + cgdf_logger: Optional[HummingbotLogger] = None + _cgdf_shared_instance: "CoinGeckoDataFeed" = None + + @classmethod + def get_instance(cls) -> "CoinGeckoDataFeed": + if cls._cgdf_shared_instance is None: + cls._cgdf_shared_instance = CoinGeckoDataFeed() + return cls._cgdf_shared_instance + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.cgdf_logger is None: + cls.cgdf_logger = logging.getLogger(__name__) + return cls.cgdf_logger + + def __init__(self, update_interval: float = 30.0): + super().__init__() + self._ev_loop = asyncio.get_event_loop() + self._price_dict: Dict[str, float] = {} + self._update_interval = update_interval + self.fetch_data_loop_task: Optional[asyncio.Task] = None + async_throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self._api_factory = WebAssistantsFactory(throttler=async_throttler) + + @property + def name(self) -> str: + return "coin_gecko_api" + + @property + def price_dict(self) -> Dict[str, float]: + return self._price_dict.copy() + + @property + def health_check_endpoint(self) -> str: + return f"{CONSTANTS.BASE_URL}{CONSTANTS.PING_REST_ENDPOINT}" + + async def start_network(self): + await self.stop_network() + self.fetch_data_loop_task = safe_ensure_future(self._fetch_data_loop()) + + async def stop_network(self): + if self.fetch_data_loop_task is not None: + self.fetch_data_loop_task.cancel() + self.fetch_data_loop_task = None + + def get_price(self, asset: str) -> float: + return self._price_dict.get(asset.upper()) + + async def get_supported_vs_tokens(self) -> List[str]: + rest_assistant = await self._api_factory.get_rest_assistant() + supported_vs_tokens_url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SUPPORTED_VS_TOKENS_REST_ENDPOINT}" + vs_tokens = await rest_assistant.execute_request( + url=supported_vs_tokens_url, throttler_limit_id=CONSTANTS.REST_CALL_RATE_LIMIT_ID + ) + return vs_tokens + + async def get_prices_by_page( + self, vs_currency: str, page_no: int, category: Optional[str] = None + ) -> List[Dict[str, Any]]: + """Fetches prices specified by 250-length page. Only 50 when category is specified""" + rest_assistant = await self._api_factory.get_rest_assistant() + price_url: str = f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + params = { + "vs_currency": vs_currency, + "order": "market_cap_desc", + "per_page": 250, + "page": page_no, + "sparkline": "false", + } + if category is not None: + params["category"] = category + resp = await rest_assistant.execute_request( + url=price_url, throttler_limit_id=CONSTANTS.REST_CALL_RATE_LIMIT_ID, params=params + ) + return resp + + async def get_prices_by_token_id(self, vs_currency: str, token_ids: List[str]) -> List[Dict[str, Any]]: + rest_assistant = await self._api_factory.get_rest_assistant() + price_url: str = f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + token_ids_str = ",".join(map(str.lower, token_ids)) + params = { + "vs_currency": vs_currency, + "ids": token_ids_str, + } + resp = await rest_assistant.execute_request( + url=price_url, throttler_limit_id=CONSTANTS.REST_CALL_RATE_LIMIT_ID, params=params + ) + return resp + + async def _fetch_data_loop(self): + while True: + try: + await self._fetch_data() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network(f"Error getting data from {self.name}", exc_info=True, + app_warning_msg="Couldn't fetch newest prices from Coin Gecko. " + "Check network connection.") + + await self._async_sleep(self._update_interval) + + async def _fetch_data(self): + await self._update_asset_prices() + self._ready_event.set() + + async def _update_asset_prices(self): + price_dict: Dict[str, float] = {} + + for i in range(1, 5): + try: + results = await self.get_prices_by_page(vs_currency="usd", page_no=i) + if 'error' in results: + raise Exception(f"{results['error']}") + for result in results: + symbol = result["symbol"].upper() + price = float(result["current_price"]) if result["current_price"] is not None else 0.0 + if symbol not in price_dict: + price_dict[symbol] = price + self._price_dict[symbol] = price + except Exception as e: + self.logger().warning(f"Coin Gecko API request failed. Exception: {str(e)}") + raise e + if i < 4: + await self._async_sleep(0.1) + self._price_dict = price_dict + + @staticmethod + async def _async_sleep(delay: float): + """Used to mock in test cases.""" + await asyncio.sleep(delay) diff --git a/hummingbot/data_feed/custom_api_data_feed.py b/hummingbot/data_feed/custom_api_data_feed.py new file mode 100644 index 0000000..13d2adc --- /dev/null +++ b/hummingbot/data_feed/custom_api_data_feed.py @@ -0,0 +1,91 @@ +import asyncio +import aiohttp +import logging +from typing import Optional +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.logger import HummingbotLogger +from hummingbot.core.utils.async_utils import safe_ensure_future +from decimal import Decimal + + +class CustomAPIDataFeed(NetworkBase): + cadf_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.cadf_logger is None: + cls.cadf_logger = logging.getLogger(__name__) + return cls.cadf_logger + + def __init__(self, api_url, update_interval: float = 5.0): + super().__init__() + self._ready_event = asyncio.Event() + self._shared_client: Optional[aiohttp.ClientSession] = None + self._api_url = api_url + self._check_network_interval = 30.0 + self._ev_loop = asyncio.get_event_loop() + self._price: Decimal = Decimal("0") + self._update_interval: float = update_interval + self._fetch_price_task: Optional[asyncio.Task] = None + + @property + def name(self): + return "custom_api" + + @property + def health_check_endpoint(self): + return self._api_url + + def _http_client(self) -> aiohttp.ClientSession: + if self._shared_client is None: + self._shared_client = aiohttp.ClientSession() + return self._shared_client + + async def check_network(self) -> NetworkStatus: + client = self._http_client() + async with client.request("GET", self.health_check_endpoint) as resp: + status_text = await resp.text() + if resp.status != 200: + raise Exception(f"Custom API Feed {self.name} server error: {status_text}") + return NetworkStatus.CONNECTED + + def get_price(self) -> Decimal: + return self._price + + async def fetch_price_loop(self): + while True: + try: + await self.fetch_price() + except asyncio.CancelledError: + raise + except Exception: + self.logger().network(f"Error fetching a new price from {self._api_url}.", exc_info=True, + app_warning_msg="Couldn't fetch newest price from CustomAPI. " + "Check network connection.") + + await asyncio.sleep(self._update_interval) + + async def fetch_price(self): + client = self._http_client() + async with client.request("GET", self._api_url) as resp: + resp_text = await resp.text() + if resp.status != 200: + raise Exception(f"Custom API Feed {self.name} server error: {resp_text}") + self._price = Decimal(str(resp_text)) + self._ready_event.set() + + async def start_network(self): + await self.stop_network() + self._fetch_price_task = safe_ensure_future(self.fetch_price_loop()) + + async def stop_network(self): + if self._fetch_price_task is not None: + self._fetch_price_task.cancel() + self._fetch_price_task = None + + def start(self): + NetworkBase.start(self) + + def stop(self): + NetworkBase.stop(self) diff --git a/hummingbot/data_feed/data_feed_base.py b/hummingbot/data_feed/data_feed_base.py new file mode 100644 index 0000000..ff3add1 --- /dev/null +++ b/hummingbot/data_feed/data_feed_base.py @@ -0,0 +1,83 @@ +import asyncio +import logging +from typing import Dict, Optional + +import aiohttp + +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.logger import HummingbotLogger + + +class DataFeedBase(NetworkBase): + dfb_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.dfb_logger is None: + cls.dfb_logger = logging.getLogger(__name__) + return cls.dfb_logger + + def __init__(self): + super().__init__() + self._ready_event = asyncio.Event() + self._shared_client: Optional[aiohttp.ClientSession] = None + + @property + def name(self): + raise NotImplementedError + + @property + def price_dict(self) -> Dict[str, float]: + raise NotImplementedError + + @property + def health_check_endpoint(self) -> str: + raise NotImplementedError + + @property + def ready(self) -> bool: + return self._ready_event.is_set() + + def get_price(self, asset: str) -> float: + raise NotImplementedError + + async def _http_client(self) -> aiohttp.ClientSession: + if self._shared_client is None: + self._shared_client = aiohttp.ClientSession() + return self._shared_client + + async def get_ready(self): + try: + if not self._ready_event.is_set(): + await self._ready_event.wait() + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error while waiting for data feed to get ready.", + exc_info=True) + + async def start_network(self): + raise NotImplementedError + + async def stop_network(self): + raise NotImplementedError + + async def check_network(self) -> NetworkStatus: + try: + async with aiohttp.ClientSession() as session: + async with session.get(self.health_check_endpoint) as resp: + status_text = await resp.text() + if resp.status != 200: + raise Exception(f"Data feed {self.name} server is down. Status is {status_text}") + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED + + def start(self): + NetworkBase.start(self) + + def stop(self): + NetworkBase.stop(self) diff --git a/hummingbot/data_feed/wallet_tracker_data_feed.py b/hummingbot/data_feed/wallet_tracker_data_feed.py new file mode 100644 index 0000000..094d3cc --- /dev/null +++ b/hummingbot/data_feed/wallet_tracker_data_feed.py @@ -0,0 +1,116 @@ +import asyncio +import logging +from decimal import Decimal +from typing import Dict, Optional, Set + +import pandas as pd + +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class WalletTrackerDataFeed(NetworkBase): + dex_logger: Optional[HummingbotLogger] = None + gateway_client = GatewayHttpClient.get_instance() + + def __init__( + self, + chain: str, + network: str, + wallets: Set[str], + tokens: Set[str], + update_interval: float = 1.0, + ) -> None: + super().__init__() + self._ev_loop = asyncio.get_event_loop() + self._chain = chain + self._network = network + self._tokens = tokens + self._wallet_balances: Dict[str, Dict[str, float]] = {wallet: {} for wallet in wallets} + self._update_interval = update_interval + self.fetch_data_loop_task: Optional[asyncio.Task] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.dex_logger is None: + cls.dex_logger = logging.getLogger(__name__) + return cls.dex_logger + + @property + def name(self) -> str: + return f"WalletTrackerDataFeed[{self.chain}-{self.network}]" + + @property + def chain(self) -> str: + return self._chain + + @property + def network(self) -> str: + return self._network + + @property + def tokens(self) -> Set[str]: + return self._tokens + + @property + def wallet_balances(self) -> Dict[str, Dict[str, float]]: + return self._wallet_balances + + @property + def wallet_balances_df(self) -> pd.DataFrame: + return pd.DataFrame(self._wallet_balances).T + + def is_ready(self) -> bool: + return all(len(wallet_balances) > 0 for wallet_balances in self._wallet_balances.values()) + + async def check_network(self) -> NetworkStatus: + is_gateway_online = await self.gateway_client.ping_gateway() + if not is_gateway_online: + self.logger().warning("Gateway is not online. Please check your gateway connection.") + return NetworkStatus.CONNECTED if is_gateway_online else NetworkStatus.NOT_CONNECTED + + async def start_network(self) -> None: + await self.stop_network() + self.fetch_data_loop_task = safe_ensure_future(self._fetch_data_loop()) + + async def stop_network(self) -> None: + if self.fetch_data_loop_task is not None: + self.fetch_data_loop_task.cancel() + self.fetch_data_loop_task = None + + async def _fetch_data_loop(self) -> None: + while True: + try: + await self._fetch_data() + except asyncio.CancelledError: + raise + except Exception as e: + self.logger().error( + f"Error getting data from {self.name}" + f"Check network connection. Error: {e}", + ) + await self._async_sleep(self._update_interval) + + async def _fetch_data(self) -> None: + wallet_balances_tasks = [ + asyncio.create_task(self._update_balances_by_wallet(wallet)) + for wallet in self._wallet_balances.keys() + ] + await asyncio.gather(*wallet_balances_tasks) + + async def _update_balances_by_wallet(self, wallet: str) -> None: + data = await self.gateway_client.get_balances( + self.chain, + self.network, + wallet, + list(self._tokens) + ) + self._wallet_balances[wallet] = {token: Decimal(balance) for token, balance in data['balances'].items()} + + @staticmethod + async def _async_sleep(delay: float) -> None: + """Used to mock in test cases.""" + await asyncio.sleep(delay) diff --git a/hummingbot/exceptions.py b/hummingbot/exceptions.py new file mode 100644 index 0000000..e5cb513 --- /dev/null +++ b/hummingbot/exceptions.py @@ -0,0 +1,28 @@ +""" +Exceptions used in the Hummingbot codebase. +""" + + +class HummingbotBaseException(Exception): + """ + Most errors raised in Hummingbot should inherit this class so we can + differentiate them from errors that come from dependencies. + """ + + +class ArgumentParserError(HummingbotBaseException): + """ + Unable to parse a command (like start, stop, etc) from the hummingbot client + """ + + +class OracleRateUnavailable(HummingbotBaseException): + """ + Asset value from third party is unavailable + """ + + +class InvalidScriptModule(HummingbotBaseException): + """ + The file does not contain a ScriptBase subclass + """ diff --git a/hummingbot/logger/__init__.py b/hummingbot/logger/__init__.py new file mode 100644 index 0000000..eae1773 --- /dev/null +++ b/hummingbot/logger/__init__.py @@ -0,0 +1,40 @@ +import dataclasses +import logging +from decimal import Decimal +from enum import Enum +from logging import ( + DEBUG, + INFO, + WARNING, + ERROR, + CRITICAL +) + + +from .logger import HummingbotLogger + +NETWORK = DEBUG + 6 + + +def log_encoder(obj): + if isinstance(obj, Decimal): + return str(obj) + elif isinstance(obj, Enum): + return str(obj) + elif dataclasses.is_dataclass(obj): + return dataclasses.asdict(obj) + raise TypeError("Object of type '%s' is not JSON serializable" % type(obj).__name__) + + +__all__ = [ + "DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL", + "NETWORK", + "HummingbotLogger", + "log_encoder" +] +logging.setLoggerClass(HummingbotLogger) +logging.addLevelName(NETWORK, "NETWORK") diff --git a/hummingbot/logger/application_warning.py b/hummingbot/logger/application_warning.py new file mode 100644 index 0000000..5871d4f --- /dev/null +++ b/hummingbot/logger/application_warning.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +from typing import ( + NamedTuple, + Tuple, + Optional +) + + +class ApplicationWarning(NamedTuple): + timestamp: float + logger_name: str + caller_info: Tuple[str, int, str, Optional[str]] + warning_msg: str + + @property + def filename(self) -> str: + return self.caller_info[0] + + @property + def line_number(self) -> int: + return self.caller_info[1] + + @property + def function_name(self) -> str: + return self.caller_info[2] + + @property + def stack_info(self) -> Optional[str]: + return self.caller_info[3] diff --git a/hummingbot/logger/cli_handler.py b/hummingbot/logger/cli_handler.py new file mode 100644 index 0000000..90f4bfd --- /dev/null +++ b/hummingbot/logger/cli_handler.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +from logging import StreamHandler +from typing import Optional +from datetime import datetime + + +class CLIHandler(StreamHandler): + def formatException(self, _) -> Optional[str]: + return None + + def format(self, record) -> str: + exc_info = record.exc_info + if record.exc_info is not None: + record.exc_info = None + retval = f'{datetime.fromtimestamp(record.created).strftime("%H:%M:%S")} - {record.name.split(".")[-1]} - ' \ + f'{record.getMessage()}' + if exc_info: + retval += " (See log file for stack trace dump)" + record.exc_info = exc_info + return retval diff --git a/hummingbot/logger/log_server_client.py b/hummingbot/logger/log_server_client.py new file mode 100644 index 0000000..926d738 --- /dev/null +++ b/hummingbot/logger/log_server_client.py @@ -0,0 +1,99 @@ +import asyncio +import logging +from typing import Any, Dict, Optional + +import aiohttp + +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_retry import async_retry +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger + + +class LogServerClient(NetworkBase): + lsc_logger: Optional[HummingbotLogger] = None + _lsc_shared_instance: "LogServerClient" = None + + @classmethod + def get_instance(cls, log_server_url: str = "https://api.coinalpha.com/reporting-proxy-v2/") -> "LogServerClient": + if cls._lsc_shared_instance is None: + cls._lsc_shared_instance = LogServerClient(log_server_url=log_server_url) + return cls._lsc_shared_instance + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.lsc_logger is None: + cls.lsc_logger = logging.getLogger(__name__) + return cls.lsc_logger + + def __init__(self, log_server_url: str = "https://api.coinalpha.com/reporting-proxy-v2/"): + super().__init__() + self.queue: asyncio.Queue = asyncio.Queue() + self.consume_queue_task: Optional[asyncio.Task] = None + self.log_server_url: str = log_server_url + + def request(self, req): + if not self.started: + self.start() + self.queue.put_nowait(req) + + @async_retry(retry_count=3, exception_types=[asyncio.TimeoutError, EnvironmentError], raise_exp=True) + async def send_log(self, session: aiohttp.ClientSession, request_dict: Dict[str, Any]): + async with session.request(request_dict["method"], request_dict["url"], **request_dict["request_obj"]) as resp: + resp_text = await resp.text() + self.logger().debug(f"Sent logs: {resp.status} {resp.url} {resp_text} ", + extra={"do_not_send": True}) + if resp.status != 200 and resp.status not in {404, 405, 400}: + raise EnvironmentError("Failed sending logs to log server.") + + async def consume_queue(self, session): + while True: + try: + req = await self.queue.get() + self.logger().debug(f"Remote logging payload: {req}") + await self.send_log(session, req) + except asyncio.CancelledError: + raise + except aiohttp.ClientError: + self.logger().network("Network error sending logs.", exc_info=True, extra={"do_not_send": True}) + return + except Exception: + self.logger().network("Unexpected error sending logs.", exc_info=True, extra={"do_not_send": True}) + return + + async def request_loop(self): + while True: + loop = asyncio.get_event_loop() + try: + async with aiohttp.ClientSession(loop=loop, + connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + await self.consume_queue(session) + except asyncio.CancelledError: + raise + except Exception: + self.logger().network("Unexpected error running logging task.", + exc_info=True, extra={"do_not_send": True}) + await asyncio.sleep(5.0) + + async def start_network(self): + self.consume_queue_task = safe_ensure_future(self.request_loop()) + + async def stop_network(self): + if self.consume_queue_task is not None: + self.consume_queue_task.cancel() + self.consume_queue_task = None + + async def check_network(self) -> NetworkStatus: + try: + loop = asyncio.get_event_loop() + async with aiohttp.ClientSession(loop=loop, + connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.get(self.log_server_url) as resp: + if resp.status != 200: + raise Exception("Log proxy server is down.") + except asyncio.CancelledError: + raise + except Exception: + return NetworkStatus.NOT_CONNECTED + return NetworkStatus.CONNECTED diff --git a/hummingbot/logger/logger.py b/hummingbot/logger/logger.py new file mode 100644 index 0000000..276eca0 --- /dev/null +++ b/hummingbot/logger/logger.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +import io +import os +import sys +import time +import traceback +from logging import Logger as PythonLogger +from typing import Optional, Type + +import pandas as pd + +from .application_warning import ApplicationWarning + +TESTING_TOOLS = ["nose", "unittest", "pytest"] + +# --- Copied from logging module --- +if hasattr(sys, '_getframe'): + def currentframe(): + return sys._getframe(3) +else: # pragma: no cover + def currentframe(): + """Return the frame object for the caller's stack frame.""" + try: + raise Exception + except Exception: + return sys.exc_info()[2].tb_frame.f_back +# --- Copied from logging module --- + + +class HummingbotLogger(PythonLogger): + def __init__(self, name: str): + super().__init__(name) + + @staticmethod + def logger_name_for_class(model_class: Type): + return f"{model_class.__module__}.{model_class.__qualname__}" + + @staticmethod + def is_testing_mode() -> bool: + return any(tools in arg + for tools in TESTING_TOOLS + for arg in sys.argv) + + def notify(self, msg: str): + from . import INFO + self.log(INFO, msg) + if not HummingbotLogger.is_testing_mode(): + from hummingbot.client.hummingbot_application import HummingbotApplication + hummingbot_app: HummingbotApplication = HummingbotApplication.main_application() + hummingbot_app.notify(f"({pd.Timestamp.fromtimestamp(int(time.time()))}) {msg}") + + def network(self, log_msg: str, app_warning_msg: Optional[str] = None, *args, **kwargs): + if app_warning_msg is not None and not HummingbotLogger.is_testing_mode(): + from hummingbot.client.hummingbot_application import HummingbotApplication + + from . import NETWORK + + self.log(NETWORK, log_msg, *args, **kwargs) + if app_warning_msg is not None and not HummingbotLogger.is_testing_mode(): + app_warning: ApplicationWarning = ApplicationWarning( + time.time(), + self.name, + self.findCaller(), + app_warning_msg + ) + self.warning(app_warning.warning_msg) + hummingbot_app: HummingbotApplication = HummingbotApplication.main_application() + hummingbot_app.add_application_warning(app_warning) + + # --- Copied from logging module --- + def findCaller(self, stack_info=False, stacklevel=1): + """ + Find the stack frame of the caller so that we can note the source + file name, line number and function name. + """ + f = currentframe() + # On some versions of IronPython, currentframe() returns None if + # IronPython isn't run with -X:Frames. + if f is not None: + f = f.f_back + orig_f = f + while f and stacklevel > 1: + f = f.f_back + stacklevel -= 1 + if not f: + f = orig_f + rv = "(unknown file)", 0, "(unknown function)", None + while hasattr(f, "f_code"): + co = f.f_code + filename = os.path.normcase(co.co_filename) + if filename == _srcfile: + f = f.f_back + continue + sinfo = None + if stack_info: + sio = io.StringIO() + sio.write('Stack (most recent call last):\n') + traceback.print_stack(f, file=sio) + sinfo = sio.getvalue() + if sinfo[-1] == '\n': + sinfo = sinfo[:-1] + sio.close() + rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) + break + return rv + # --- Copied from logging module --- + + +_srcfile = os.path.normcase(HummingbotLogger.network.__code__.co_filename) diff --git a/hummingbot/logger/struct_logger.py b/hummingbot/logger/struct_logger.py new file mode 100644 index 0000000..88945ed --- /dev/null +++ b/hummingbot/logger/struct_logger.py @@ -0,0 +1,41 @@ +import json +import logging + +from hummingbot.logger import ( + HummingbotLogger, + log_encoder, +) + +EVENT_LOG_LEVEL = 15 +METRICS_LOG_LEVEL = 14 +logging.addLevelName(EVENT_LOG_LEVEL, "EVENT_LOG") +logging.addLevelName(METRICS_LOG_LEVEL, "METRIC_LOG") + + +class StructLogRecord(logging.LogRecord): + def getMessage(self): + """ + Return dict msg if present + """ + if "dict_msg" in self.__dict__ and isinstance(self.__dict__["dict_msg"], dict): + return json.dumps(self.__dict__["dict_msg"], default=log_encoder) + else: + return super().getMessage() + + +class StructLogger(HummingbotLogger): + def event_log(self, dict_msg, *args, **kwargs): + if self.isEnabledFor(EVENT_LOG_LEVEL): + if not isinstance(dict_msg, dict): + self._log(logging.ERROR, "event_log message must be of type dict.", extra={"do_not_send": True}) + return + extra = { + "dict_msg": dict_msg, + "message_type": "event" + } + if "extra" in kwargs: + kwargs["extra"].update(extra) + else: + kwargs["extra"] = extra + + self._log(EVENT_LOG_LEVEL, "", args, **kwargs) diff --git a/hummingbot/model/__init__.py b/hummingbot/model/__init__.py new file mode 100644 index 0000000..a9d13f6 --- /dev/null +++ b/hummingbot/model/__init__.py @@ -0,0 +1,14 @@ +from sqlalchemy.ext.declarative import declarative_base + +HummingbotBase = declarative_base() + + +def get_declarative_base(): + from .market_state import MarketState # noqa: F401 + from .metadata import Metadata # noqa: F401 + from .order import Order # noqa: F401 + from .order_status import OrderStatus # noqa: F401 + from .range_position_collected_fees import RangePositionCollectedFees # noqa: F401 + from .range_position_update import RangePositionUpdate # noqa: F401 + from .trade_fill import TradeFill # noqa: F401 + return HummingbotBase diff --git a/hummingbot/model/db_migration/__init__.py b/hummingbot/model/db_migration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/model/db_migration/base_transformation.py b/hummingbot/model/db_migration/base_transformation.py new file mode 100644 index 0000000..182e4b4 --- /dev/null +++ b/hummingbot/model/db_migration/base_transformation.py @@ -0,0 +1,57 @@ +import logging +from abc import ABC, abstractmethod +import functools +from sqlalchemy import ( + Column, +) + + +@functools.total_ordering +class DatabaseTransformation(ABC): + def __init__(self, migrator): + self.migrator = migrator + + @abstractmethod + def apply(self, db_handle): + pass + + @property + @abstractmethod + def name(self): + return "" + + @property + def from_version(self): + return 0 + + @property + @abstractmethod + def to_version(self): + return None + + def does_apply_to_version(self, original_version: int, target_version: int) -> bool: + if self.to_version is not None: + # from_version > 0 means from_version property was overridden by transformation class + if self.from_version > 0: + return (self.from_version >= original_version) and (self.to_version <= target_version) + return (self.to_version > original_version) and (self.to_version <= target_version) + return False + + def __eq__(self, other): + return (self.to_version == other.to_version) and (self.from_version == other.to_version) + + def __lt__(self, other): + if self.to_version == other.to_version: + return self.from_version < other.from_version + else: + return self.to_version < other.to_version + + def add_column(self, engine, table_name, column: Column, dry_run=True): + column_name = column.compile(dialect=engine.dialect) + column_type = column.type.compile(engine.dialect) + column_nullable = "NULL" if column.nullable else "NOT NULL" + query_to_execute = f'ALTER TABLE \"{table_name}\" ADD COLUMN {column_name} {column_type} {column_nullable}' + if dry_run: + logging.getLogger().info(f"Query to execute in DB: {query_to_execute}") + else: + engine.execute(query_to_execute) diff --git a/hummingbot/model/db_migration/migrator.py b/hummingbot/model/db_migration/migrator.py new file mode 100644 index 0000000..ec884c5 --- /dev/null +++ b/hummingbot/model/db_migration/migrator.py @@ -0,0 +1,64 @@ +import logging +from inspect import getmembers, isabstract, isclass +from pathlib import Path +from shutil import copyfile, move + +import pandas as pd +from sqlalchemy.exc import SQLAlchemyError + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.model.db_migration.base_transformation import DatabaseTransformation +from hummingbot.model.sql_connection_manager import SQLConnectionManager, SQLConnectionType + + +class Migrator: + @classmethod + def _get_transformations(cls): + import hummingbot.model.db_migration.transformations as transformations + return [o for _, o in getmembers(transformations, + predicate=lambda c: isclass(c) and + issubclass(c, DatabaseTransformation) and + not isabstract(c))] + + def __init__(self): + self.transformations = [t(self) for t in self._get_transformations()] + + def migrate_db_to_version(self, client_config_map: ClientConfigAdapter, db_handle, from_version, to_version): + original_db_path = db_handle.db_path + original_db_name = Path(original_db_path).stem + backup_db_path = original_db_path + '.backup_' + pd.Timestamp.utcnow().strftime("%Y%m%d-%H%M%S") + new_db_path = original_db_path + '.new' + copyfile(original_db_path, new_db_path) + copyfile(original_db_path, backup_db_path) + + db_handle.engine.dispose() + new_db_handle = SQLConnectionManager( + client_config_map, SQLConnectionType.TRADE_FILLS, new_db_path, original_db_name, True + ) + + relevant_transformations = [t for t in self.transformations + if t.does_apply_to_version(from_version, to_version)] + if relevant_transformations: + logging.getLogger().info( + f"Will run DB migration from {from_version} to {to_version}") + + migration_successful = False + try: + for transformation in sorted(relevant_transformations): + logging.getLogger().info(f"Applying {transformation.name} to DB...") + new_db_handle = transformation.apply(new_db_handle) + logging.getLogger().info(f"DONE with {transformation.name}") + migration_successful = True + except SQLAlchemyError: + logging.getLogger().error("Unexpected error while checking and upgrading the local database.", + exc_info=True) + finally: + try: + new_db_handle.engine.dispose() + if migration_successful: + move(new_db_path, original_db_path) + db_handle.__init__(SQLConnectionType.TRADE_FILLS, original_db_path, original_db_name, True) + except Exception as e: + logging.getLogger().error(f"Fatal error migrating DB {original_db_path}") + raise e + return migration_successful diff --git a/hummingbot/model/db_migration/transformations.py b/hummingbot/model/db_migration/transformations.py new file mode 100644 index 0000000..7bcfe4b --- /dev/null +++ b/hummingbot/model/db_migration/transformations.py @@ -0,0 +1,151 @@ +from sqlalchemy import Column, Integer, Text + +from hummingbot.model.db_migration.base_transformation import DatabaseTransformation +from hummingbot.model.decimal_type_decorator import SqliteDecimal +from hummingbot.model.sql_connection_manager import SQLConnectionManager + + +class AddExchangeOrderIdColumnToOrders(DatabaseTransformation): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def apply(self, db_handle: SQLConnectionManager) -> SQLConnectionManager: + exchange_order_id_column = Column("exchange_order_id", Text, nullable=True) + self.add_column(db_handle.engine, "Order", exchange_order_id_column, dry_run=False) + return db_handle + + @property + def name(self): + return "AddExchangeOrderIdColumnToOrders" + + @property + def to_version(self): + return 20210118 + + +class AddLeverageAndPositionColumns(DatabaseTransformation): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def apply(self, db_handle: SQLConnectionManager) -> SQLConnectionManager: + leverage_column = Column("leverage", Integer, nullable=True) + position_column = Column("position", Text, nullable=True) + self.add_column(db_handle.engine, "Order", leverage_column, dry_run=False) + self.add_column(db_handle.engine, "Order", position_column, dry_run=False) + self.add_column(db_handle.engine, "TradeFill", leverage_column, dry_run=False) + self.add_column(db_handle.engine, "TradeFill", position_column, dry_run=False) + return db_handle + + @property + def name(self): + return "AddLeverageAndPositionColumns" + + @property + def to_version(self): + return 20210119 + + +class ConvertPriceAndAmountColumnsToBigint(DatabaseTransformation): + order_migration_queries = [ + ('create table Order_dg_tmp' + '( id TEXT not null' + ' primary key,' + ' config_file_path TEXT not null,' + ' strategy TEXT not null,' + ' market TEXT not null,' + ' symbol TEXT not null,' + ' base_asset TEXT not null,' + ' quote_asset TEXT not null,' + ' creation_timestamp BIGINT not null,' + ' order_type TEXT not null,' + ' amount BIGINT not null,' + ' leverage INTEGER not null,' + ' price FLOAT not null,' + ' last_status TEXT not null,' + ' last_update_timestamp BIGINT not null,' + ' exchange_order_id TEXT,' + ' position TEXT);'), + ('insert into Order_dg_tmp(id, config_file_path, strategy, market, symbol, base_asset, ' + 'quote_asset, creation_timestamp, order_type, amount, leverage, price, last_status, ' + 'last_update_timestamp, exchange_order_id, position) ' + 'select id, config_file_path, strategy, market, symbol, base_asset, quote_asset, ' + 'creation_timestamp, order_type, CAST(amount * 1000000 AS INTEGER), leverage, ' + 'CAST(price * 1000000 AS INTEGER), last_status, last_update_timestamp, exchange_order_id, ' + 'position from "Order";'), + 'drop table "Order";', + 'alter table Order_dg_tmp rename to "Order";', + 'create index o_config_timestamp_index on "Order" (config_file_path, creation_timestamp);', + 'create index o_market_base_asset_timestamp_index on "Order" (market, base_asset, creation_timestamp);', + 'create index o_market_quote_asset_timestamp_index on "Order" (market, quote_asset, creation_timestamp);', + 'create index o_market_trading_pair_timestamp_index on "Order" (market, symbol, creation_timestamp);' + ] + + trade_fill_migration_queries = [ + ('create table TradeFill_dg_tmp' + '( config_file_path TEXT not null,' + ' strategy TEXT not null,' + ' market TEXT not null,' + ' symbol TEXT not null,' + ' base_asset TEXT not null,' + ' quote_asset TEXT not null,' + ' timestamp BIGINT not null,' + ' order_id TEXT not null' + ' references "Order",' + ' trade_type TEXT not null,' + ' order_type TEXT not null,' + ' price BIGINT not null,' + ' amount FLOAT not null,' + ' leverage INTEGER not null,' + ' trade_fee JSON not null,' + ' exchange_trade_id TEXT not null,' + ' position TEXT,' + ' constraint TradeFill_pk' + ' primary key (market, order_id, exchange_trade_id)' + ');'), + ('insert into TradeFill_dg_tmp(config_file_path, strategy, market, symbol, base_asset, ' + 'quote_asset, timestamp, order_id, trade_type, order_type, price, amount, leverage, ' + 'trade_fee, exchange_trade_id, position) ' + 'select config_file_path, strategy, market, symbol, base_asset, quote_asset, timestamp, ' + 'order_id, trade_type, order_type, CAST(price * 1000000 AS INTEGER), ' + "CAST(amount * 1000000 AS INTEGER), leverage, trade_fee, exchange_trade_id || '_' || id, position " + 'from TradeFill;'), + 'drop table TradeFill;', + 'alter table TradeFill_dg_tmp rename to TradeFill;', + 'create index tf_config_timestamp_index on TradeFill (config_file_path, timestamp);', + 'create index tf_market_base_asset_timestamp_index on TradeFill (market, base_asset, timestamp);', + 'create index tf_market_quote_asset_timestamp_index on TradeFill (market, quote_asset, timestamp);', + 'create index tf_market_trading_pair_timestamp_index on TradeFill (market, symbol, timestamp);' + ] + + def apply(self, db_handle: SQLConnectionManager) -> SQLConnectionManager: + for query in self.order_migration_queries: + db_handle.engine.execute(query) + for query in self.trade_fill_migration_queries: + db_handle.engine.execute(query) + return db_handle + + @property + def name(self): + return "ConvertPriceAndAmountColumnsToBigint" + + @property + def to_version(self): + return 20220130 + + +class AddTradeFeeInQuote(DatabaseTransformation): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def apply(self, db_handle: SQLConnectionManager) -> SQLConnectionManager: + trade_fee = Column("trade_fee_in_quote", SqliteDecimal(6), nullable=True) + self.add_column(db_handle.engine, "TradeFill", trade_fee, dry_run=False) + return db_handle + + @property + def name(self): + return "AddTradeFeeInQuote" + + @property + def to_version(self): + return 20230516 diff --git a/hummingbot/model/decimal_type_decorator.py b/hummingbot/model/decimal_type_decorator.py new file mode 100644 index 0000000..2d5189b --- /dev/null +++ b/hummingbot/model/decimal_type_decorator.py @@ -0,0 +1,38 @@ +from decimal import Decimal + +from sqlalchemy import BigInteger, TypeDecorator + + +class SqliteDecimal(TypeDecorator): + """ + This TypeDecorator use Sqlalchemy BigInteger as impl. It converts Decimalsfrom Python to Integers which is later + stored in Sqlite database. + """ + impl = BigInteger + + def __init__(self, scale): + """ + :param scale: number of digits to the right of the decimal point to consider when storing the value in the DB + e.g. value = Column(SqliteDecimal(2)) means a value such as Decimal('12.34') will be converted to 1234 + """ + TypeDecorator.__init__(self) + self.scale = scale + self.multiplier_int = 10 ** self.scale + + @property + def python_type(self): + return Decimal + + def process_bind_param(self, value, dialect): + return self._convert_decimal(value) + + def process_result_value(self, value, dialect): + if value is not None: + value = Decimal(value) / self.multiplier_int + return value + + def process_literal_param(self, value, dialect): + return str(self._convert_decimal(value)) + + def _convert_decimal(self, value: Decimal) -> int: + return int(Decimal(value) * self.multiplier_int) if value is not None else value diff --git a/hummingbot/model/funding_payment.py b/hummingbot/model/funding_payment.py new file mode 100644 index 0000000..cd11bf1 --- /dev/null +++ b/hummingbot/model/funding_payment.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +import pandas as pd +from typing import ( + List, + Optional, +) +from sqlalchemy import ( + Column, + Text, + Index, + BigInteger, + Float, +) +from sqlalchemy.orm import ( + Session +) +from datetime import datetime + +from . import HummingbotBase + + +class FundingPayment(HummingbotBase): + __tablename__ = "FundingPayment" + __table_args__ = (Index("fp_config_timestamp_index", + "config_file_path", "timestamp"), + Index("fp_market_trading_pair_timestamp_index", + "market", "symbol", "timestamp") + ) + + timestamp = Column(BigInteger, primary_key=True, nullable=False) + config_file_path = Column(Text, nullable=False) + market = Column(Text, nullable=False) + rate = Column(Float, nullable=False) + symbol = Column(Text, nullable=False) + amount = Column(Float, nullable=False) + + def __repr__(self) -> str: + return f"FundingPayment(timestamp={self.timestamp}, config_file_path='{self.config_file_path}', " \ + f"market='{self.market}', rate='{self.rate}' symbol='{self.symbol}', amount={self.amount}" + + @staticmethod + def get_funding_payments(sql_session: Session, + timestamp: str = None, + market: str = None, + trading_pair: str = None, + ) -> Optional[List["FundingPayment"]]: + filters = [] + if timestamp is not None: + filters.append(FundingPayment.timestamp == timestamp) + if market is not None: + filters.append(FundingPayment.market == market) + if trading_pair is not None: + filters.append(FundingPayment.symbol == trading_pair) + + payments: Optional[List[FundingPayment]] = (sql_session + .query(FundingPayment) + .filter(*filters) + .order_by(FundingPayment.timestamp.asc()) + .all()) + return payments + + @classmethod + def to_pandas(cls, payments: List): + columns: List[str] = ["Index", + "Timestamp", + "Exchange", + "Market", + "Rate", + "Amount"] + data = [] + index = 0 + for payment in payments: + index += 1 + data.append([ + index, + datetime.fromtimestamp(int(payment.timestamp / 1e3)).strftime("%Y-%m-%d %H:%M:%S"), + payment.market, + payment.rate, + payment.symbol, + payment.amount + ]) + df = pd.DataFrame(data=data, columns=columns) + df.set_index('Index', inplace=True) + + return df diff --git a/hummingbot/model/inventory_cost.py b/hummingbot/model/inventory_cost.py new file mode 100644 index 0000000..f963f00 --- /dev/null +++ b/hummingbot/model/inventory_cost.py @@ -0,0 +1,70 @@ +from decimal import Decimal +from typing import Optional + +from sqlalchemy import ( + Column, + Integer, + Numeric, + String, + UniqueConstraint, +) +from sqlalchemy.orm import Session + +from hummingbot.model import HummingbotBase + + +class InventoryCost(HummingbotBase): + __tablename__ = "InventoryCost" + __table_args__ = ( + UniqueConstraint("base_asset", "quote_asset"), + ) + + id = Column(Integer, primary_key=True, nullable=False) + base_asset = Column(String(45), nullable=False) + quote_asset = Column(String(45), nullable=False) + base_volume = Column(Numeric(48, 18), nullable=False) + quote_volume = Column(Numeric(48, 18), nullable=False) + + @classmethod + def get_record( + cls, sql_session: Session, base_asset: str, quote_asset: str + ) -> Optional["InventoryCost"]: + return ( + sql_session.query(cls) + .filter(cls.base_asset == base_asset, cls.quote_asset == quote_asset) + .first() + ) + + @classmethod + def add_volume( + cls, + sql_session: Session, + base_asset: str, + quote_asset: str, + base_volume: Decimal, + quote_volume: Decimal, + overwrite: bool = False, + ) -> None: + if overwrite: + update = { + "base_volume": base_volume, + "quote_volume": quote_volume, + } + else: + update = { + "base_volume": cls.base_volume + base_volume, + "quote_volume": cls.quote_volume + quote_volume, + } + + rows_updated: int = sql_session.query(cls).filter( + cls.base_asset == base_asset, cls.quote_asset == quote_asset + ).update(update) + + if not rows_updated: + record = InventoryCost( + base_asset=base_asset, + quote_asset=quote_asset, + base_volume=float(base_volume), + quote_volume=float(quote_volume), + ) + sql_session.add(record) diff --git a/hummingbot/model/market_data.py b/hummingbot/model/market_data.py new file mode 100644 index 0000000..52ff1e5 --- /dev/null +++ b/hummingbot/model/market_data.py @@ -0,0 +1,25 @@ +import inspect + +from sqlalchemy import JSON, Column, Index, Text + +from hummingbot.model import HummingbotBase +from hummingbot.model.decimal_type_decorator import SqliteDecimal + + +class MarketData(HummingbotBase): + __tablename__ = "MarketData" + __table_args__ = ( + Index("timestamp", "exchange", "trading_pair"), + ) + + timestamp = Column(SqliteDecimal(6), primary_key=True, nullable=False) + exchange = Column(Text, nullable=False) + trading_pair = Column(Text, nullable=False) + mid_price = Column(SqliteDecimal(6), nullable=False) + best_bid = Column(SqliteDecimal(6), nullable=False) + best_ask = Column(SqliteDecimal(6), nullable=False) + order_book = Column(JSON) + + def __repr__(self) -> str: + list_of_fields = [f"{name}: {value}" for name, value in inspect.getmembers(self) if isinstance(value, Column)] + return ','.join(list_of_fields) diff --git a/hummingbot/model/market_state.py b/hummingbot/model/market_state.py new file mode 100644 index 0000000..543714e --- /dev/null +++ b/hummingbot/model/market_state.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +from sqlalchemy import ( + Column, + Text, + JSON, + Integer, + BigInteger, + Index +) + +from . import HummingbotBase + + +class MarketState(HummingbotBase): + __tablename__ = "MarketState" + __table_args = (Index("ms_config_market_index", + "config_file_path", "market", unique=True),) + + id = Column(Integer, primary_key=True, nullable=False) + config_file_path = Column(Text, nullable=False) + market = Column(Text, nullable=False) + timestamp = Column(BigInteger, nullable=False) + saved_state = Column(JSON, nullable=False) + + def __repr__(self) -> str: + return f"MarketState(id='{self.id}', config_file_path='{self.config_file_path}', market='{self.market}', " \ + f"timestamp={self.timestamp}, saved_state={self.saved_state})" diff --git a/hummingbot/model/metadata.py b/hummingbot/model/metadata.py new file mode 100644 index 0000000..c581883 --- /dev/null +++ b/hummingbot/model/metadata.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +from sqlalchemy import ( + Column, + Text, +) + +from . import HummingbotBase + + +class Metadata(HummingbotBase): + __tablename__ = "Metadata" + + key = Column(Text, primary_key=True, nullable=False) + value = Column(Text, nullable=False) + + def __repr__(self) -> str: + return f"Metadata(key='{self.key}', value='{self.value}')" diff --git a/hummingbot/model/order.py b/hummingbot/model/order.py new file mode 100644 index 0000000..b6bdb11 --- /dev/null +++ b/hummingbot/model/order.py @@ -0,0 +1,73 @@ +from typing import ( + Dict, + Any +) + +import numpy +from sqlalchemy import ( + BigInteger, + Column, + Index, + Integer, + Text, +) +from sqlalchemy.orm import relationship + +from hummingbot.model import HummingbotBase +from hummingbot.model.decimal_type_decorator import SqliteDecimal + + +class Order(HummingbotBase): + __tablename__ = "Order" + __table_args__ = (Index("o_config_timestamp_index", + "config_file_path", "creation_timestamp"), + Index("o_market_trading_pair_timestamp_index", + "market", "symbol", "creation_timestamp"), + Index("o_market_base_asset_timestamp_index", + "market", "base_asset", "creation_timestamp"), + Index("o_market_quote_asset_timestamp_index", + "market", "quote_asset", "creation_timestamp")) + + id = Column(Text, primary_key=True, nullable=False) + config_file_path = Column(Text, nullable=False) + strategy = Column(Text, nullable=False) + market = Column(Text, nullable=False) + symbol = Column(Text, nullable=False) + base_asset = Column(Text, nullable=False) + quote_asset = Column(Text, nullable=False) + creation_timestamp = Column(BigInteger, nullable=False) + order_type = Column(Text, nullable=False) + amount = Column(SqliteDecimal(6), nullable=False) + leverage = Column(Integer, nullable=False, default=1) + price = Column(SqliteDecimal(6), nullable=False) + last_status = Column(Text, nullable=False) + last_update_timestamp = Column(BigInteger, nullable=False) + exchange_order_id = Column(Text, nullable=True) + position = Column(Text, nullable=True) + status = relationship("OrderStatus", back_populates="order") + trade_fills = relationship("TradeFill", back_populates="order") + + def __repr__(self) -> str: + return f"Order(id={self.id}, config_file_path='{self.config_file_path}', strategy='{self.strategy}', " \ + f"market='{self.market}', symbol='{self.symbol}', base_asset='{self.base_asset}', " \ + f"quote_asset='{self.quote_asset}', creation_timestamp={self.creation_timestamp}, " \ + f"order_type='{self.order_type}', amount={self.amount}, leverage={self.leverage}, " \ + f"price={self.price}, last_status='{self.last_status}', " \ + f"last_update_timestamp={self.last_update_timestamp}), " \ + f"exchange_order_id={self.exchange_order_id}, position={self.position}" + + @staticmethod + def to_bounty_api_json(order: "Order") -> Dict[str, Any]: + return { + "order_id": order.id, + "price": numpy.format_float_positional(order.price), + "quantity": numpy.format_float_positional(order.amount), + "symbol": order.symbol, + "market": order.market, + "order_timestamp": order.creation_timestamp, + "order_type": order.order_type, + "base_asset": order.base_asset, + "quote_asset": order.quote_asset, + "raw_json": { + } + } diff --git a/hummingbot/model/order_status.py b/hummingbot/model/order_status.py new file mode 100644 index 0000000..1e63f5d --- /dev/null +++ b/hummingbot/model/order_status.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +from typing import ( + Dict, + Any +) +from sqlalchemy import ( + Column, + Text, + Integer, + BigInteger, + ForeignKey, + Index +) +from sqlalchemy.orm import relationship + +from . import HummingbotBase + + +class OrderStatus(HummingbotBase): + __tablename__ = "OrderStatus" + __table_args__ = (Index("os_order_id_timestamp_index", + "order_id", "timestamp"), + ) + + id = Column(Integer, primary_key=True, nullable=False) + order_id = Column(Text, ForeignKey("Order.id"), nullable=False) + timestamp = Column(BigInteger, nullable=False) + status = Column(Text, nullable=False) + order = relationship("Order", back_populates="status") + + def __repr__(self) -> str: + return f"OrderStatus(id={self.id}, order_id='{self.order_id}', timestamp={self.timestamp}, " \ + f"status='{self.status}')" + + @staticmethod + def to_bounty_api_json(order_status: "OrderStatus") -> Dict[str, Any]: + return { + "order_id": order_status.order_id, + "timestamp": order_status.timestamp, + "event_type": order_status.status, + "raw_json": { + } + } diff --git a/hummingbot/model/position_executors.py b/hummingbot/model/position_executors.py new file mode 100644 index 0000000..6a965f3 --- /dev/null +++ b/hummingbot/model/position_executors.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +from typing import List, Optional + +import pandas as pd +from sqlalchemy import BigInteger, Column, Float, Index, Integer, Text +from sqlalchemy.orm import Session + +from . import HummingbotBase + + +class PositionExecutors(HummingbotBase): + __tablename__ = "PositionExecutors" + __table_args__ = (Index("pe_controller_name_timestamp", + "controller_name", "timestamp"), + Index("pe_exchange_trading_pair_timestamp", + "exchange", "trading_pair", "timestamp"), + Index("pe_controller_name_exchange_trading_pair_timestamp", + "controller_name", "exchange", "trading_pair", "timestamp") + ) + id = Column(Integer, primary_key=True, autoincrement=True) + timestamp = Column(BigInteger, nullable=False) + order_level = Column(Integer, nullable=True) + exchange = Column(Text, nullable=False) + trading_pair = Column(Text, nullable=False) + side = Column(Text, nullable=False) + amount = Column(Float, nullable=False) + trade_pnl = Column(Float, nullable=False) + trade_pnl_quote = Column(Float, nullable=False) + cum_fee_quote = Column(Float, nullable=False) + net_pnl_quote = Column(Float, nullable=False) + net_pnl = Column(Float, nullable=False) + close_timestamp = Column(BigInteger, nullable=True) + executor_status = Column(Text, nullable=False) + close_type = Column(Text, nullable=True) + entry_price = Column(Float, nullable=True) + close_price = Column(Float, nullable=True) + sl = Column(Float, nullable=False) + tp = Column(Float, nullable=False) + tl = Column(Float, nullable=False) + open_order_type = Column(Text, nullable=False) + take_profit_order_type = Column(Text, nullable=False) + stop_loss_order_type = Column(Text, nullable=False) + time_limit_order_type = Column(Text, nullable=False) + leverage = Column(Integer, nullable=False) + controller_name = Column(Text, nullable=True) + + def __repr__(self) -> str: + return f"PositionExecutor(timestamp={self.timestamp}, controller_name='{self.controller_name}', " \ + f"order_level={self.order_level}, " \ + f"exchange='{self.exchange}', trading_pair='{self.trading_pair}', side='{self.side}', " + + @staticmethod + def get_position_executors(sql_session: Session, + controller_name: str = None, + exchange: str = None, + trading_pair: str = None, + ) -> Optional[List["PositionExecutors"]]: + filters = [] + if controller_name is not None: + filters.append(PositionExecutors.controller_name == controller_name) + if exchange is not None: + filters.append(PositionExecutors.exchange == exchange) + if trading_pair is not None: + filters.append(PositionExecutors.trading_pair == trading_pair) + + executors: Optional[List[PositionExecutors]] = (sql_session + .query(PositionExecutors) + .filter(*filters) + .order_by(PositionExecutors.timestamp.asc()) + .all()) + return executors + + @classmethod + def to_pandas(cls, executors: List): + df = pd.DataFrame(data=[executor.to_json() for executor in executors]) + return df + + def to_json(self): + return { + "timestamp": self.timestamp, + "exchange": self.exchange, + "trading_pair": self.trading_pair, + "side": self.side, + "amount": self.amount, + "trade_pnl": self.trade_pnl, + "trade_pnl_quote": self.trade_pnl_quote, + "cum_fee_quote": self.cum_fee_quote, + "net_pnl_quote": self.net_pnl_quote, + "net_pnl": self.net_pnl, + "close_timestamp": self.close_timestamp, + "executor_status": self.executor_status, + "close_type": self.close_type, + "entry_price": self.entry_price, + "close_price": self.close_price, + "sl": self.sl, + "tp": self.tp, + "tl": self.tl, + "open_order_type": self.open_order_type, + "take_profit_order_type": self.take_profit_order_type, + "stop_loss_order_type": self.stop_loss_order_type, + "time_limit_order_type": self.time_limit_order_type, + "leverage": self.leverage, + "controller_name": self.controller_name, + } diff --git a/hummingbot/model/range_position_collected_fees.py b/hummingbot/model/range_position_collected_fees.py new file mode 100644 index 0000000..c9e54f2 --- /dev/null +++ b/hummingbot/model/range_position_collected_fees.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from sqlalchemy import Column, Float, Index, Integer, Text + +from . import HummingbotBase + + +class RangePositionCollectedFees(HummingbotBase): + """ + Table schema used when LP feesmare claimed. + """ + __tablename__ = "RangePositionCollectedFees" + __table_args__ = (Index("rpf_id_index", + "token_id", "config_file_path"), + ) + id = Column(Integer, primary_key=True, nullable=False) + config_file_path = Column(Text, nullable=False) + strategy = Column(Text, nullable=False) + token_id = Column(Integer, nullable=False) + token_0 = Column(Text, nullable=False) + token_1 = Column(Text, nullable=False) + claimed_fee_0 = Column(Float, nullable=False) + claimed_fee_1 = Column(Float, nullable=False) + + def __repr__(self) -> str: + return f"RangePositionCollectedFees(id={self.id}, config_file_path='{self.config_file_path}', strategy='{self.strategy}', " \ + f"token_id={self.token_id}, token_0='{self.token_0}', token_1='{self.token_1}', " \ + f"claimed_fee_0={self.claimed_fee_0}, claimed_fee_1={self.claimed_fee_1})" diff --git a/hummingbot/model/range_position_update.py b/hummingbot/model/range_position_update.py new file mode 100644 index 0000000..1c9f890 --- /dev/null +++ b/hummingbot/model/range_position_update.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +from sqlalchemy import JSON, BigInteger, Column, Index, Integer, Text + +from . import HummingbotBase + + +class RangePositionUpdate(HummingbotBase): + """ + Table schema used when an event to update LP position(Add/Remove/Collect) is triggered. + """ + __tablename__ = "RangePositionUpdate" + __table_args__ = (Index("rpu_timestamp_index", + "hb_id", "timestamp"), + ) + + id = Column(Integer, primary_key=True) + hb_id = Column(Text, nullable=False) + timestamp = Column(BigInteger, nullable=False) + tx_hash = Column(Text, nullable=True) + token_id = Column(Integer, nullable=False) + trade_fee = Column(JSON, nullable=False) + + def __repr__(self) -> str: + return f"RangePositionUpdate(id={self.id}, hb_id='{self.hb_id}', " \ + f"timestamp={self.timestamp}, tx_hash='{self.tx_hash}', token_id={self.token_id}" \ + f"trade_fee={self.trade_fee})" diff --git a/hummingbot/model/sql_connection_manager.py b/hummingbot/model/sql_connection_manager.py new file mode 100644 index 0000000..1564dd1 --- /dev/null +++ b/hummingbot/model/sql_connection_manager.py @@ -0,0 +1,132 @@ +import logging +from enum import Enum +from os.path import join +from typing import TYPE_CHECKING, Optional + +from sqlalchemy import MetaData, create_engine, inspect +from sqlalchemy.engine.base import Engine +from sqlalchemy.orm import Query, Session, sessionmaker +from sqlalchemy.schema import DropConstraint, ForeignKeyConstraint, Table + +from hummingbot import data_path +from hummingbot.logger.logger import HummingbotLogger +from hummingbot.model import get_declarative_base +from hummingbot.model.metadata import Metadata as LocalMetadata +from hummingbot.model.transaction_base import TransactionBase + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class SQLConnectionType(Enum): + TRADE_FILLS = 1 + + +class SQLConnectionManager(TransactionBase): + _scm_logger: Optional[HummingbotLogger] = None + _scm_trade_fills_instance: Optional["SQLConnectionManager"] = None + + LOCAL_DB_VERSION_KEY = "local_db_version" + LOCAL_DB_VERSION_VALUE = "20230516" + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._scm_logger is None: + cls._scm_logger = logging.getLogger(__name__) + return cls._scm_logger + + @classmethod + def get_declarative_base(cls): + return get_declarative_base() + + @classmethod + def get_trade_fills_instance( + cls, client_config_map: "ClientConfigAdapter", db_name: Optional[str] = None + ) -> "SQLConnectionManager": + if cls._scm_trade_fills_instance is None: + cls._scm_trade_fills_instance = SQLConnectionManager( + client_config_map, SQLConnectionType.TRADE_FILLS, db_name=db_name + ) + elif cls.create_db_path(db_name=db_name) != cls._scm_trade_fills_instance.db_path: + cls._scm_trade_fills_instance = SQLConnectionManager( + client_config_map, SQLConnectionType.TRADE_FILLS, db_name=db_name + ) + return cls._scm_trade_fills_instance + + @classmethod + def create_db_path(cls, db_path: Optional[str] = None, db_name: Optional[str] = None) -> str: + if db_path is not None: + return db_path + if db_name is not None: + return join(data_path(), f"{db_name}.sqlite") + else: + return join(data_path(), "hummingbot_trades.sqlite") + + def __init__(self, + client_config_map: "ClientConfigAdapter", + connection_type: SQLConnectionType, + db_path: Optional[str] = None, + db_name: Optional[str] = None, + called_from_migrator = False): + db_path = self.create_db_path(db_path, db_name) + self.db_path = db_path + + if connection_type is SQLConnectionType.TRADE_FILLS: + self._engine: Engine = create_engine(client_config_map.db_mode.get_url(self.db_path)) + self._metadata: MetaData = self.get_declarative_base().metadata + self._metadata.create_all(self._engine) + + # SQLite does not enforce foreign key constraint, but for others engines, we need to drop it. + # See: `hummingbot/market/markets_recorder.py`, at line 213. + with self._engine.begin() as conn: + inspector = inspect(conn) + + for tname, fkcs in reversed( + inspector.get_sorted_table_and_fkc_names()): + if fkcs: + if not self._engine.dialect.supports_alter: + continue + for fkc in fkcs: + fk_constraint = ForeignKeyConstraint((), (), name=fkc) + Table(tname, MetaData(), fk_constraint) + conn.execute(DropConstraint(fk_constraint)) + + self._session_cls = sessionmaker(bind=self._engine) + + if connection_type is SQLConnectionType.TRADE_FILLS and (not called_from_migrator): + self.check_and_migrate_db(client_config_map) + + @property + def engine(self) -> Engine: + return self._engine + + def get_new_session(self) -> Session: + return self._session_cls() + + def get_local_db_version(self, session: Session): + query: Query = (session.query(LocalMetadata) + .filter(LocalMetadata.key == self.LOCAL_DB_VERSION_KEY)) + result: Optional[LocalMetadata] = query.one_or_none() + return result + + def check_and_migrate_db(self, client_config_map: "ClientConfigAdapter"): + from hummingbot.model.db_migration.migrator import Migrator + with self.get_new_session() as session: + with session.begin(): + local_db_version = self.get_local_db_version(session=session) + if local_db_version is None: + version_info: LocalMetadata = LocalMetadata(key=self.LOCAL_DB_VERSION_KEY, + value=self.LOCAL_DB_VERSION_VALUE) + session.add(version_info) + session.commit() + else: + # There's no past db version to upgrade from at this moment. So we'll just update the version value + # if needed. + if local_db_version.value < self.LOCAL_DB_VERSION_VALUE: + was_migration_successful = Migrator().migrate_db_to_version( + client_config_map, self, int(local_db_version.value), int(self.LOCAL_DB_VERSION_VALUE) + ) + if was_migration_successful: + # Cannot use variable local_db_version because reference is not valid + # since Migrator changed it + self.get_local_db_version(session=session).value = self.LOCAL_DB_VERSION_VALUE diff --git a/hummingbot/model/trade_fill.py b/hummingbot/model/trade_fill.py new file mode 100644 index 0000000..b90fc04 --- /dev/null +++ b/hummingbot/model/trade_fill.py @@ -0,0 +1,168 @@ +from datetime import datetime +from typing import Any, Dict, List, Optional + +import numpy +import pandas as pd +from sqlalchemy import JSON, BigInteger, Column, ForeignKey, Index, Integer, Text +from sqlalchemy.orm import Session, relationship + +from hummingbot.core.event.events import PositionAction +from hummingbot.model import HummingbotBase +from hummingbot.model.decimal_type_decorator import SqliteDecimal + + +class TradeFill(HummingbotBase): + __tablename__ = "TradeFill" + __table_args__ = (Index("tf_config_timestamp_index", + "config_file_path", "timestamp"), + Index("tf_market_trading_pair_timestamp_index", + "market", "symbol", "timestamp"), + Index("tf_market_base_asset_timestamp_index", + "market", "base_asset", "timestamp"), + Index("tf_market_quote_asset_timestamp_index", + "market", "quote_asset", "timestamp") + ) + + config_file_path = Column(Text, nullable=False) + strategy = Column(Text, nullable=False) + market = Column(Text, primary_key=True, nullable=False) + symbol = Column(Text, nullable=False) + base_asset = Column(Text, nullable=False) + quote_asset = Column(Text, nullable=False) + timestamp = Column(BigInteger, nullable=False) + order_id = Column(Text, ForeignKey("Order.id"), primary_key=True, nullable=False) + trade_type = Column(Text, nullable=False) + order_type = Column(Text, nullable=False) + price = Column(SqliteDecimal(6), nullable=False) + amount = Column(SqliteDecimal(6), nullable=False) + leverage = Column(Integer, nullable=False, default=1) + trade_fee = Column(JSON, nullable=False) + trade_fee_in_quote = Column(SqliteDecimal(6)) + exchange_trade_id = Column(Text, primary_key=True, nullable=False) + position = Column(Text, nullable=True, default=PositionAction.NIL.value) + order = relationship("Order", back_populates="trade_fills") + + def __repr__(self) -> str: + return f"TradeFill(config_file_path='{self.config_file_path}', strategy='{self.strategy}', " \ + f"market='{self.market}', symbol='{self.symbol}', base_asset='{self.base_asset}', " \ + f"quote_asset='{self.quote_asset}', timestamp={self.timestamp}, order_id='{self.order_id}', " \ + f"trade_type='{self.trade_type}', order_type='{self.order_type}', price={self.price}, " \ + f"amount={self.amount}, leverage={self.leverage}, trade_fee={self.trade_fee}, " \ + f"exchange_trade_id={self.exchange_trade_id}, position={self.position})" + + @staticmethod + def get_trades(sql_session: Session, + strategy: str = None, + market: str = None, + trading_pair: str = None, + base_asset: str = None, + quote_asset: str = None, + trade_type: str = None, + order_type: str = None, + start_time: int = None, + end_time: int = None, + ) -> Optional[List["TradeFill"]]: + filters = [] + if strategy is not None: + filters.append(TradeFill.strategy == strategy) + if market is not None: + filters.append(TradeFill.market == market) + if trading_pair is not None: + filters.append(TradeFill.symbol == trading_pair) + if base_asset is not None: + filters.append(TradeFill.base_asset == base_asset) + if quote_asset is not None: + filters.append(TradeFill.quote_asset == quote_asset) + if trade_type is not None: + filters.append(TradeFill.trade_type == trade_type) + if order_type is not None: + filters.append(TradeFill.order_type == order_type) + if start_time is not None: + filters.append(TradeFill.timestamp >= start_time) + if end_time is not None: + filters.append(TradeFill.timestamp <= end_time) + + trades: Optional[List[TradeFill]] = (sql_session + .query(TradeFill) + .filter(*filters) + .order_by(TradeFill.timestamp.asc()) + .all()) + return trades + + @classmethod + def to_pandas(cls, trades: List): + columns: List[str] = ["Id", + "Timestamp", + "Exchange", + "Market", + "Order_type", + "Side", + "Price", + "Amount", + "Leverage", + "Position", + "Age"] + data = [] + for trade in trades: + + if trade.order is None: # order creation update has not arrived yet + age = pd.Timestamp(0, unit='s').strftime('%H:%M:%S') + else: + age = pd.Timestamp(int(trade.timestamp / 1e3 - trade.order.creation_timestamp / 1e3), + unit='s').strftime('%H:%M:%S') + data.append([ + trade.exchange_trade_id, + datetime.fromtimestamp(int(trade.timestamp / 1e3)).strftime("%Y-%m-%d %H:%M:%S"), + trade.market, + trade.symbol, + trade.order_type.lower(), + trade.trade_type.lower(), + trade.price, + trade.amount, + trade.leverage, + trade.position, + age, + ]) + df = pd.DataFrame(data=data, columns=columns) + df.set_index('Id', inplace=True) + + return df + + @staticmethod + def to_bounty_api_json(trade_fill: "TradeFill") -> Dict[str, Any]: + return { + "market": trade_fill.market, + "trade_id": trade_fill.exchange_trade_id, + "price": numpy.format_float_positional(trade_fill.price), + "quantity": numpy.format_float_positional(trade_fill.amount), + "symbol": trade_fill.symbol, + "trade_timestamp": trade_fill.timestamp, + "trade_type": trade_fill.trade_type, + "base_asset": trade_fill.base_asset, + "quote_asset": trade_fill.quote_asset, + "raw_json": { + "trade_fee": trade_fill.trade_fee, + } + } + + @staticmethod + def attribute_names_for_file_export(): + + return [ + "exchange_trade_id", # Keep the key attribute first in the list + "config_file_path", + "strategy", + "market", + "symbol", + "base_asset", + "quote_asset", + "timestamp", + "order_id", + "trade_type", + "order_type", + "price", + "amount", + "leverage", + "trade_fee", + "trade_fee_in_quote", + "position", ] diff --git a/hummingbot/model/transaction_base.py b/hummingbot/model/transaction_base.py new file mode 100644 index 0000000..b3dc556 --- /dev/null +++ b/hummingbot/model/transaction_base.py @@ -0,0 +1,35 @@ +import logging +from abc import ABC, abstractmethod +from contextlib import contextmanager +from typing import Generator, Optional + +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +log = logging.getLogger(__name__) + + +class TransactionBase(ABC): + @abstractmethod + def get_new_session(self) -> Session: + raise NotImplementedError + + @contextmanager + def begin(self) -> Generator[Session, None, None]: + sql_session: Optional[Session] = None + try: + sql_session = self.get_new_session() + yield sql_session + sql_session.commit() + except SQLAlchemyError: + log.error("Rolling back SQL session due to error.") + try: + if sql_session is not None: + sql_session.rollback() + except Exception: + log.error("Failed to rollback a transaction!", exc_info=True) + # Propagate to caller, as it must be aware of it too. + raise + finally: + if sql_session is not None: + sql_session.close() diff --git a/hummingbot/notifier/__init__.py b/hummingbot/notifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/notifier/notifier_base.py b/hummingbot/notifier/notifier_base.py new file mode 100644 index 0000000..1ac0456 --- /dev/null +++ b/hummingbot/notifier/notifier_base.py @@ -0,0 +1,12 @@ +class NotifierBase: + def __init__(self): + self._started = False + + def add_msg_to_queue(self, msg: str): + raise NotImplementedError + + def start(self): + raise NotImplementedError + + def stop(self): + raise NotImplementedError diff --git a/hummingbot/notifier/telegram_notifier.py b/hummingbot/notifier/telegram_notifier.py new file mode 100644 index 0000000..2970425 --- /dev/null +++ b/hummingbot/notifier/telegram_notifier.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python + +import asyncio +import logging +from os.path import join, realpath +from typing import Any, Callable, List, Optional + +import pandas as pd +from telegram.bot import Bot +from telegram.error import NetworkError, TelegramError +from telegram.ext import Filters, MessageHandler, Updater +from telegram.parsemode import ParseMode +from telegram.replykeyboardmarkup import ReplyKeyboardMarkup +from telegram.update import Update + +import hummingbot +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger +from hummingbot.notifier.notifier_base import NotifierBase + +import sys; sys.path.insert(0, realpath(join(__file__, "../../../"))) + +DISABLED_COMMANDS = { + "connect", # disabled because telegram can't display secondary prompt + "create", # disabled because telegram can't display secondary prompt + "import", # disabled because telegram can't display secondary prompt + "export", # disabled for security +} + +# Telegram does not allow sending messages longer than 4096 characters +TELEGRAM_MSG_LENGTH_LIMIT = 3000 + + +def authorized_only(handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]: + """ Decorator to check if the message comes from the correct chat_id """ + def wrapper(self, *args, **kwargs): + update = kwargs.get('update') or args[1] + + # Reject unauthorized messages + chat_id = int(self._chat_id) + + if int(update.message.chat_id) != chat_id: + TelegramNotifier.logger().info("Rejected unauthorized message from: %s", update.message.chat_id) + return wrapper + + try: + return handler(self, *args, **kwargs) + except Exception as e: + TelegramNotifier.logger().exception(f"Exception occurred within Telegram module: {e}") + + return wrapper + + +class TelegramNotifier(NotifierBase): + tn_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls.tn_logger is None: + cls.tn_logger = logging.getLogger(__name__) + return cls.tn_logger + + def __init__(self, + token: str, + chat_id: str, + hb: "hummingbot.client.hummingbot_application.HummingbotApplication") -> None: + super().__init__() + self._token = token + self._chat_id = chat_id + self._updater = Updater(token=token, workers=0) + self._hb = hb + self._ev_loop = asyncio.get_event_loop() + self._async_call_scheduler = AsyncCallScheduler.shared_instance() + self._msg_queue: asyncio.Queue = asyncio.Queue() + self._send_msg_task: Optional[asyncio.Task] = None + + # Register command handler and start telegram message polling + handles = [MessageHandler(Filters.text, self.handler)] + for handle in handles: + self._updater.dispatcher.add_handler(handle) + + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self._token == other._token + and self._chat_id == other._chat_id + and id(self._hb) == id(other._hb) + ) + + def start(self): + if not self._started: + self._started = True + self._updater.start_polling( + clean=True, + bootstrap_retries=-1, + timeout=30, + read_latency=60, + ) + self._send_msg_task = safe_ensure_future(self.send_msg_from_queue(), loop=self._ev_loop) + self.logger().info("Telegram is listening...") + + def stop(self) -> None: + if self._started or self._updater.running: + self._updater.stop() + self._started = False + if self._send_msg_task: + self._send_msg_task.cancel() + + @authorized_only + def handler(self, bot: Bot, update: Update) -> None: + safe_ensure_future(self.handler_loop(bot, update), loop=self._ev_loop) + + async def handler_loop(self, bot: Bot, update: Update) -> None: + async_scheduler: AsyncCallScheduler = AsyncCallScheduler.shared_instance() + try: + input_text = update.message.text.strip() + output = f"\n[Telegram Input] {input_text}" + + self._hb.app.log(output) + + # if the command does starts with any disabled commands + if any([input_text.lower().startswith(dc) for dc in DISABLED_COMMANDS]): + self.add_msg_to_queue(f"Command {input_text} is disabled from telegram") + else: + # Set display options to max, so that telegram does not display truncated data + pd.set_option('display.max_rows', 500) + pd.set_option('display.max_columns', 500) + pd.set_option('display.width', 1000) + + await async_scheduler.call_async(self._hb._handle_command, input_text) + + # Reset to normal, so that pandas's default autodetect width still works + pd.set_option('display.max_rows', 0) + pd.set_option('display.max_columns', 0) + pd.set_option('display.width', 0) + except Exception as e: + self.add_msg_to_queue(str(e)) + + @staticmethod + def _divide_chunks(arr: List[Any], n: int = 5): + """ Break a list into chunks of size N """ + for i in range(0, len(arr), n): + yield arr[i:i + n] + + def add_msg_to_queue(self, msg: str): + lines: List[str] = msg.split("\n") + msg_chunks: List[List[str]] = self._divide_chunks(lines, 30) + for chunk in msg_chunks: + self._msg_queue.put_nowait("\n".join(chunk)) + + async def send_msg_from_queue(self): + while True: + try: + new_msg: str = await self._msg_queue.get() + if isinstance(new_msg, str) and len(new_msg) > 0: + await self.send_msg_async(new_msg) + except Exception as e: + self.logger().error(str(e)) + await asyncio.sleep(1) + + async def send_msg_async(self, msg: str, bot: Bot = None) -> None: + """ + Send given markdown message + """ + bot = bot or self._updater.bot + + # command options that show up on user's screen + approved_commands = ["start", "stop", "status", "history", "config"] + keyboard = self._divide_chunks(approved_commands) + reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True) + + # wrapping text in ``` to prevent formatting issues + formatted_msg = f'\n{msg}\n' + + try: + try: + await self._async_call_scheduler.call_async(lambda: bot.send_message( + self._chat_id, + text=formatted_msg, + parse_mode=ParseMode.HTML, + reply_markup=reply_markup + )) + except NetworkError as network_err: + # Sometimes the telegram server resets the current connection, + # if this is the case we send the message again. + self.logger().network(f"Telegram NetworkError: {network_err.message}! Trying one more time", + exc_info=True) + await self._async_call_scheduler.call_async(lambda: bot.send_message( + self._chat_id, + text=msg, + parse_mode=ParseMode.MARKDOWN, + reply_markup=reply_markup + )) + except TelegramError as telegram_err: + self.logger().network(f"TelegramError: {telegram_err.message}! Giving up on that message.", + exc_info=True) diff --git a/hummingbot/pmm_script/__init__.py b/hummingbot/pmm_script/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/pmm_script/pmm_script_base.py b/hummingbot/pmm_script/pmm_script_base.py new file mode 100644 index 0000000..b4815f0 --- /dev/null +++ b/hummingbot/pmm_script/pmm_script_base.py @@ -0,0 +1,236 @@ +import asyncio +import traceback +from decimal import Decimal +from multiprocessing import Queue +from operator import itemgetter +from statistics import mean, median +from typing import Any, Callable, Dict, List, Optional + +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + SellOrderCompletedEvent +) +from .pmm_script_interface import ( + CallLog, + CallNotify, + OnCommand, + OnStatus, + OnTick, + PMMParameters, + PMMMarketInfo, + ScriptError +) + + +class PMMScriptBase: + """ + PMMScriptBase provides functionality which a script can use to interact with the main HB application. + A user defined script should derive from this base class to get all its functionality. + """ + def __init__(self): + self._parent_queue: Queue = None + self._child_queue: Queue = None + self._queue_check_interval: float = 0.0 + self.mid_prices: List[Decimal] = [] + self.max_mid_prices_length: int = 86400 # 60 * 60 * 24 = 1 day of prices + self.pmm_parameters: PMMParameters = None + self.pmm_market_info: PMMMarketInfo = None + # all_total_balances stores balances in {exchange: {token: balance}} format + # for example {"binance": {"BTC": Decimal("0.1"), "ETH": Decimal("20"}} + self.all_total_balances: Dict[str, Dict[str, Decimal]] = None + # all_available_balances has the same data structure as all_total_balances + self.all_available_balances: Dict[str, Dict[str, Decimal]] = None + + def assign_init(self, parent_queue: Queue, child_queue: Queue, queue_check_interval: float): + self._parent_queue = parent_queue + self._child_queue = child_queue + self._queue_check_interval = queue_check_interval + + @property + def mid_price(self): + """ + The current market mid price (the average of top bid and top ask) + """ + return self.mid_prices[-1] + + async def run(self): + asyncio.ensure_future(self.listen_to_parent()) + + async def listen_to_parent(self): + while True: + try: + if self._parent_queue.empty(): + await asyncio.sleep(self._queue_check_interval) + continue + item = self._parent_queue.get() + # print(f"child gets {str(item)}") + if item is None: + # print("child exiting..") + asyncio.get_event_loop().stop() + break + if isinstance(item, OnTick): + self.mid_prices.append(item.mid_price) + if len(self.mid_prices) > self.max_mid_prices_length: + self.mid_prices = self.mid_prices[len(self.mid_prices) - self.max_mid_prices_length:] + self.pmm_parameters = item.pmm_parameters + self.all_total_balances = item.all_total_balances + self.all_available_balances = item.all_available_balances + self.on_tick() + elif isinstance(item, BuyOrderCompletedEvent): + self.on_buy_order_completed(item) + elif isinstance(item, SellOrderCompletedEvent): + self.on_sell_order_completed(item) + elif isinstance(item, OnStatus): + status_msg = self.on_status() + if status_msg: + self.notify(f"Script status: {status_msg}") + elif isinstance(item, OnCommand): + self.on_command(item.cmd, item.args) + elif isinstance(item, PMMMarketInfo): + self.pmm_market_info = item + except asyncio.CancelledError: + raise + except Exception as e: + # Capturing traceback here and put it as part of ScriptError, which can then be reported in the parent + # process. + tb = "".join(traceback.TracebackException.from_exception(e).format()) + self._child_queue.put(ScriptError(e, tb)) + + def notify(self, msg: str): + """ + Notifies the user, the message will appear on top left panel of HB application. + If Telegram integration enabled, the message will also be sent to the telegram user. + :param msg: The message. + """ + self._child_queue.put(CallNotify(msg)) + + def log(self, msg: str): + """ + Logs message to the strategy log file and display it on Running Logs section of HB. + :param msg: The message. + """ + self._child_queue.put(CallLog(msg)) + + def avg_mid_price(self, interval: int, length: int) -> Optional[Decimal]: + """ + Calculates average (mean) of the stored mid prices. + Mid prices are stored for each tick (second). + Examples: To get the average of the last 100 minutes mid prices = avg_mid_price(60, 100) + :param interval: The interval (in seconds) in which to sample the mid prices. + :param length: The number of the samples to calculate the average. + :returns None if there is not enough samples, otherwise the average mid price. + """ + samples = self.take_samples(self.mid_prices, interval, length) + if samples is None: + return None + return mean(samples) + + def avg_price_volatility(self, interval: int, length: int) -> Optional[Decimal]: + """ + Calculates average (mean) price volatility, volatility is a price change compared to the previous + cycle regardless of its direction, e.g. if price changes -3% (or 3%), the volatility is 3%. + Examples: To get the average of the last 10 changes on a minute interval = avg_price_volatility(60, 10) + :param interval: The interval (in seconds) in which to sample the mid prices. + :param length: The number of the samples to calculate the average. + :returns None if there is not enough samples, otherwise the average mid price change. + """ + return self.locate_central_price_volatility(interval, length, mean) + + def median_price_volatility(self, interval: int, length: int) -> Optional[Decimal]: + """ + Calculates the median (middle value) price volatility, volatility is a price change compared to the previous + cycle regardless of its direction, e.g. if price changes -3% (or 3%), the volatility is 3%. + Examples: To get the median of the last 10 changes on a minute interval = median_price_volatility(60, 10) + :param interval: The interval (in seconds) in which to sample the mid prices. + :param length: The number of the samples to calculate the average. + :returns None if there is not enough samples, otherwise the median mid price change. + """ + return self.locate_central_price_volatility(interval, length, median) + + def locate_central_price_volatility(self, interval: int, length: int, locate_function: Callable) \ + -> Optional[Decimal]: + """ + Calculates central location of the price volatility, volatility is a price change compared to the previous cycle + regardless of its direction, e.g. if price changes -3% (or 3%), the volatility is 3%. + Examples: To get mean of the last 10 changes on a minute interval locate_central_price_volatility(60, 10, mean) + :param interval: The interval in which to sample the mid prices. + :param length: The number of the samples. + :param locate_function: The function used to calculate the central location, e.g. mean, median, geometric_mean + and many more which are supported by statistics library. + :returns None if there is not enough samples, otherwise the central location of mid price change. + """ + # We need sample size of length + 1, as we need a previous value to calculate the change + samples = self.take_samples(self.mid_prices, interval, length + 1) + if samples is None: + return None + changes = [] + for index in range(1, len(samples)): + changes.append(max(samples[index], samples[index - 1]) / min(samples[index], samples[index - 1]) - 1) + return locate_function(changes) + + @staticmethod + def round_by_step(a_number: Decimal, step_size: Decimal): + """ + Rounds the number down by the step size, e.g. round_by_step(1.8, 0.25) = 1.75 + :param a_number: A number to round + :param step_size: The step size. + :returns rounded number. + """ + return (a_number // step_size) * step_size + + @staticmethod + def take_samples(a_list: List[Any], interval: int, length: int) -> Optional[List[any]]: + """ + Takes samples out of a given list where the last item is the most recent, + Examples: a list = [1, 2, 3, 4, 5, 6, 7] an interval of 3 and length of 2 will return you [4, 7], + for an interval of 2 and length of 4, you'll get [1, 3, 5, 7] + :param a_list: A list which to take samples from + :param interval: The interval at which to take sample, starting from the last item on the list. + :param length: The number of the samples. + :returns None if there is not enough samples to satisfy length, otherwise the sample list. + """ + index_list = list(range(len(a_list) - 1, -1, -1 * interval)) + index_list = sorted(index_list) + index_list = index_list[-1 * length:] + if len(index_list) < length: + return None + if len(index_list) == 1: + # return a list with just 1 item in it. + return [a_list[index_list[0]]] + samples = list(itemgetter(*index_list)(a_list)) + return samples + + def on_tick(self): + """ + Is called upon OnTick message received, which is every second on normal HB configuration. + It is intended to be implemented by the derived class of this class. + """ + pass + + def on_buy_order_completed(self, event: BuyOrderCompletedEvent): + """ + Is called upon a buy order is completely filled. + It is intended to be implemented by the derived class of this class. + """ + pass + + def on_sell_order_completed(self, event: SellOrderCompletedEvent): + """ + Is called upon a sell order is completely filled. + It is intended to be implemented by the derived class of this class. + """ + pass + + def on_status(self) -> str: + """ + Is called upon `status` command is issued on the Hummingbot application. + It is intended to be implemented by the derived class of this class. + :returns status message. + """ + return f"{self.__class__.__name__} is active." + + def on_command(self, cmd: str, args: List[str]): + """ + Called when 'script' command is issued on the Hummingbot application + """ + pass diff --git a/hummingbot/pmm_script/pmm_script_interface.py b/hummingbot/pmm_script/pmm_script_interface.py new file mode 100644 index 0000000..e125bf0 --- /dev/null +++ b/hummingbot/pmm_script/pmm_script_interface.py @@ -0,0 +1,165 @@ +from decimal import Decimal +from typing import Dict, List + +child_queue = None + + +def set_child_queue(queue): + global child_queue + child_queue = queue + + +class StrategyParameter(object): + """ + A strategy parameter class that is used as a property for the collection class with its get and set method. + The set method detects if there is a value change it will put itself into the child queue. + """ + def __init__(self, attr): + self.name = attr + self.attr = "_" + attr + self.updated_value = None + + def __get__(self, obj, objtype): + return getattr(obj, self.attr) + + def __set__(self, obj, value): + global child_queue + old_value = getattr(obj, self.attr) + if old_value is not None and old_value != value: + self.updated_value = value + child_queue.put(self) + setattr(obj, self.attr, value) + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class PMMParameters: + """ + A collection of pure market making strategy parameters which are configurable through script. + The members names need to match the property names of PureMarketMakingStrategy. + """ + def __init__(self): + self._buy_levels = None + self._sell_levels = None + self._order_levels = None + self._bid_spread = None + self._ask_spread = None + self._order_amount = None + self._order_level_spread = None + self._order_level_amount = None + self._order_refresh_time = None + self._order_refresh_tolerance_pct = None + self._filled_order_delay = None + self._hanging_orders_enabled = None + self._hanging_orders_cancel_pct = None + + # These below parameters are yet to open for the script + + self._inventory_skew_enabled = None + self._inventory_target_base_pct = None + self._inventory_range_multiplier = None + self._order_override = None + + # self._order_optimization_enabled = None + # self._ask_order_optimization_depth = None + # self._bid_order_optimization_depth = None + # self._add_transaction_costs_to_orders = None + # self._price_ceiling = None + # self._price_floor = None + # self._ping_pong_enabled = None + # self._minimum_spread = None + + buy_levels = StrategyParameter("buy_levels") + sell_levels = StrategyParameter("sell_levels") + order_levels = StrategyParameter("order_levels") + bid_spread = StrategyParameter("bid_spread") + ask_spread = StrategyParameter("ask_spread") + order_amount = StrategyParameter("order_amount") + order_level_spread = StrategyParameter("order_level_spread") + order_level_amount = StrategyParameter("order_level_amount") + order_refresh_time = StrategyParameter("order_refresh_time") + order_refresh_tolerance_pct = StrategyParameter("order_refresh_tolerance_pct") + filled_order_delay = StrategyParameter("filled_order_delay") + hanging_orders_enabled = StrategyParameter("hanging_orders_enabled") + hanging_orders_cancel_pct = StrategyParameter("hanging_orders_cancel_pct") + + inventory_skew_enabled = StrategyParameter("inventory_skew_enabled") + inventory_target_base_pct = StrategyParameter("inventory_target_base_pct") + inventory_range_multiplier = StrategyParameter("inventory_range_multiplier") + order_override = StrategyParameter("order_override") + + # order_optimization_enabled = PMMParameter("order_optimization_enabled") + # ask_order_optimization_depth = PMMParameter("ask_order_optimization_depth") + # bid_order_optimization_depth = PMMParameter("bid_order_optimization_depth") + # add_transaction_costs_to_orders = PMMParameter("add_transaction_costs_to_orders") + # price_ceiling = PMMParameter("price_ceiling") + # price_floor = PMMParameter("price_floor") + # ping_pong_enabled = PMMParameter("ping_pong_enabled") + # minimum_spread = PMMParameter("minimum_spread") + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class PMMMarketInfo: + def __init__(self, exchange: str, + trading_pair: str,): + self.exchange = exchange + self.trading_pair = trading_pair + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class OnTick: + def __init__(self, mid_price: Decimal, + pmm_parameters: PMMParameters, + all_total_balances: Dict[str, Dict[str, Decimal]], + all_available_balances: Dict[str, Dict[str, Decimal]], + ): + self.mid_price = mid_price + self.pmm_parameters = pmm_parameters + self.all_total_balances = all_total_balances + self.all_available_balances = all_available_balances + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class OnStatus: + pass + + +class CallNotify: + def __init__(self, msg): + self.msg = msg + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class CallLog: + def __init__(self, msg): + self.msg = msg + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class OnCommand: + def __init__(self, cmd: str, args: List[str]): + self.cmd = cmd + self.args = args + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.__dict__)}" + + +class ScriptError: + def __init__(self, error: Exception, traceback: str): + self.error = error + self.traceback = traceback + + def __repr__(self): + return f"{self.__class__.__name__} {str(self.error)} \nTrace back: {self.traceback}" diff --git a/hummingbot/pmm_script/pmm_script_iterator.pxd b/hummingbot/pmm_script/pmm_script_iterator.pxd new file mode 100644 index 0000000..dff288a --- /dev/null +++ b/hummingbot/pmm_script/pmm_script_iterator.pxd @@ -0,0 +1,21 @@ +# distutils: language=c++ + +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class PMMScriptIterator(TimeIterator): + cdef: + str _script_file_path + object _strategy + object _markets + double _queue_check_interval + object _event_pairs + object _did_complete_buy_order_forwarder + object _did_complete_sell_order_forwarder + object _script_module + object _parent_queue + object _child_queue + object _ev_loop + object _script_process + object _listen_to_child_task + bint _is_unit_testing_mode diff --git a/hummingbot/pmm_script/pmm_script_iterator.pyx b/hummingbot/pmm_script/pmm_script_iterator.pyx new file mode 100644 index 0000000..59b5743 --- /dev/null +++ b/hummingbot/pmm_script/pmm_script_iterator.pyx @@ -0,0 +1,159 @@ +# distutils: language=c++ + +import asyncio +import logging +from multiprocessing import Process, Queue +from pathlib import Path +from typing import List + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.clock cimport Clock +from hummingbot.core.clock import Clock +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + SellOrderCompletedEvent, +) +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.pmm_script.pmm_script_interface import ( + CallLog, + CallNotify, + OnTick, + OnCommand, + OnStatus, + PMMParameters, + PMMMarketInfo, + ScriptError, + StrategyParameter, +) +from hummingbot.pmm_script.pmm_script_process import run_pmm_script +from hummingbot.strategy.pure_market_making import PureMarketMakingStrategy + +sir_logger = None + + +cdef class PMMScriptIterator(TimeIterator): + @classmethod + def logger(cls): + global sir_logger + if sir_logger is None: + sir_logger = logging.getLogger(__name__) + return sir_logger + + def __init__(self, + script_file_path: Path, + markets: List[ExchangeBase], + strategy: PureMarketMakingStrategy, + queue_check_interval: float = 0.01, + is_unit_testing_mode: bool = False): + super().__init__() + self._markets = markets + self._strategy = strategy + self._is_unit_testing_mode = is_unit_testing_mode + self._queue_check_interval = queue_check_interval + self._did_complete_buy_order_forwarder = SourceInfoEventForwarder(self._did_complete_buy_order) + self._did_complete_sell_order_forwarder = SourceInfoEventForwarder(self._did_complete_sell_order) + self._event_pairs = [ + (MarketEvent.BuyOrderCompleted, self._did_complete_buy_order_forwarder), + (MarketEvent.SellOrderCompleted, self._did_complete_sell_order_forwarder) + ] + self._ev_loop = asyncio.get_event_loop() + self._parent_queue = Queue() + self._child_queue = Queue() + self._listen_to_child_task = safe_ensure_future(self.listen_to_child_queue(), loop=self._ev_loop) + + self._script_process = Process( + target=run_pmm_script, + args=(str(script_file_path), self._parent_queue, self._child_queue, queue_check_interval,) + ) + self.logger().info(f"starting PMM script in {script_file_path}") + self._script_process.start() + + @property + def strategy(self): + return self._strategy + + cdef c_start(self, Clock clock, double timestamp): + TimeIterator.c_start(self, clock, timestamp) + for market in self._markets: + for event_pair in self._event_pairs: + market.add_listener(event_pair[0], event_pair[1]) + self._parent_queue.put(PMMMarketInfo(self._strategy.market_info.market.name, + self._strategy.trading_pair)) + + cdef c_stop(self, Clock clock): + TimeIterator.c_stop(self, clock) + self._parent_queue.put(None) + self._child_queue.put(None) + self._script_process.join() + if self._listen_to_child_task is not None: + self._listen_to_child_task.cancel() + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + if not self._strategy.all_markets_ready(): + return + cdef object pmm_strategy = PMMParameters() + for attr in PMMParameters.__dict__.keys(): + if attr[:1] != '_': + param_value = getattr(self._strategy, attr) + setattr(pmm_strategy, attr, param_value) + cdef object on_tick = OnTick(self.strategy.get_mid_price(), pmm_strategy, + self.all_total_balances(), self.all_available_balances()) + self._parent_queue.put(on_tick) + + def _did_complete_buy_order(self, + event_tag: int, + market: ExchangeBase, + event: BuyOrderCompletedEvent): + self._parent_queue.put(event) + + def _did_complete_sell_order(self, + event_tag: int, + market: ExchangeBase, + event: SellOrderCompletedEvent): + self._parent_queue.put(event) + + async def listen_to_child_queue(self): + while True: + try: + if self._child_queue.empty(): + await asyncio.sleep(self._queue_check_interval) + continue + item = self._child_queue.get() + if item is None: + break + if isinstance(item, StrategyParameter): + self.logger().info(f"received: {str(item)}") + setattr(self._strategy, item.name, item.updated_value) + elif isinstance(item, CallNotify) and not self._is_unit_testing_mode: + # ignore this on unit testing as the below import will mess up unit testing. + from hummingbot.client.hummingbot_application import HummingbotApplication + HummingbotApplication.main_application().notify(item.msg) + elif isinstance(item, CallLog): + self.logger().info(f"script - {item.msg}") + elif isinstance(item, ScriptError): + self.logger().info(f"{item}") + except asyncio.CancelledError: + raise + except Exception: + self.logger().info("Unexpected error listening to child queue.", exc_info=True) + + def request_status(self): + self._parent_queue.put(OnStatus()) + + def request_command(self, cmd: str, args: List[str]): + self._parent_queue.put(OnCommand(cmd, args)) + + def all_total_balances(self): + all_bals = {m.name: m.get_all_balances() for m in self._markets} + return {exchange: {token: bal for token, bal in bals.items() if bal > 0} for exchange, bals in all_bals.items()} + + def all_available_balances(self): + all_bals = self.all_total_balances() + ret_val = {} + for exchange, balances in all_bals.items(): + connector = [c for c in self._markets if c.name == exchange][0] + ret_val[exchange] = {token: connector.get_available_balance(token) for token in balances.keys()} + return ret_val diff --git a/hummingbot/pmm_script/pmm_script_process.py b/hummingbot/pmm_script/pmm_script_process.py new file mode 100644 index 0000000..3d76ccb --- /dev/null +++ b/hummingbot/pmm_script/pmm_script_process.py @@ -0,0 +1,36 @@ +import asyncio +import importlib +import inspect +import os + +from multiprocessing import Queue +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase +from hummingbot.pmm_script.pmm_script_interface import CallNotify, set_child_queue + + +def run_pmm_script(script_file_name: str, parent_queue: Queue, child_queue: Queue, queue_check_interval: float): + try: + script_class = import_pmm_script_sub_class(script_file_name) + script = script_class() + script.assign_init(parent_queue, child_queue, queue_check_interval) + set_child_queue(child_queue) + policy = asyncio.get_event_loop_policy() + policy.set_event_loop(policy.new_event_loop()) + ev_loop = asyncio.get_event_loop() + ev_loop.create_task(script.run()) + ev_loop.run_forever() + ev_loop.close() + except Exception as ex: + child_queue.put(CallNotify(f'Failed to start script {script_file_name}:')) + child_queue.put(CallNotify(f'{ex}')) + + +def import_pmm_script_sub_class(script_file_name: str): + name = os.path.basename(script_file_name).split(".")[0] + spec = importlib.util.spec_from_file_location(name, script_file_name) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + for x in dir(module): + obj = getattr(module, x) + if inspect.isclass(obj) and issubclass(obj, PMMScriptBase) and obj.__name__ != "PMMScriptBase": + return obj diff --git a/hummingbot/remote_iface/__init__.py b/hummingbot/remote_iface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/remote_iface/messages.py b/hummingbot/remote_iface/messages.py new file mode 100644 index 0000000..993b82b --- /dev/null +++ b/hummingbot/remote_iface/messages.py @@ -0,0 +1,139 @@ +from typing import Any, Dict, List, Optional, Tuple + +from commlib.msg import PubSubMessage, RPCMessage + + +class MQTT_STATUS_CODE: + ERROR: int = 400 + SUCCESS: int = 200 + + +class NotifyMessage(PubSubMessage): + seq: Optional[int] = 0 + timestamp: Optional[int] = -1 + msg: Optional[str] = '' + + +class StatusUpdateMessage(PubSubMessage): + timestamp: Optional[int] = -1 + type: Optional[str] = '' + msg: Optional[str] = '' + + +class InternalEventMessage(PubSubMessage): + timestamp: Optional[int] = -1 + type: Optional[str] = 'ievent' + data: Optional[dict] = {} + + +class LogMessage(PubSubMessage): + timestamp: float = 0.0 + msg: str = '' + level_no: int = 0 + level_name: str = '' + logger_name: str = '' + + +class ExternalEventMessage(PubSubMessage): + timestamp: Optional[int] = -1 + sequence: Optional[int] = 0 + type: Optional[str] = 'eevent' + data: Optional[Dict[str, Any]] = {} + + +class StartCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + log_level: Optional[str] = None + script: Optional[str] = None + is_quickstart: Optional[bool] = False + async_backend: Optional[bool] = True + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + + +class StopCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + skip_order_cancellation: Optional[bool] = False + async_backend: Optional[bool] = True + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + + +class ConfigCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + params: Optional[List[Tuple[str, Any]]] = [] + + class Response(RPCMessage.Response): + changes: Optional[List[Tuple[str, Any]]] = [] + config: Optional[Dict[str, Any]] = {} + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + + +class CommandShortcutMessage(RPCMessage): + class Request(RPCMessage.Request): + params: Optional[List[List[Any]]] = [] + + class Response(RPCMessage.Response): + success: Optional[List[bool]] = [] + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + + +class ImportCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + strategy: str + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + + +class StatusCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + async_backend: Optional[bool] = True + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + data: Optional[Any] = '' + + +class HistoryCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + days: Optional[float] = 0 + verbose: Optional[bool] = False + precision: Optional[int] = None + async_backend: Optional[bool] = True + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + trades: Optional[List[Any]] = [] + + +class BalanceLimitCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + exchange: str + asset: str + amount: float + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + data: Optional[str] = '' + + +class BalancePaperCommandMessage(RPCMessage): + class Request(RPCMessage.Request): + asset: str + amount: float + + class Response(RPCMessage.Response): + status: Optional[int] = MQTT_STATUS_CODE.SUCCESS + msg: Optional[str] = '' + data: Optional[str] = '' diff --git a/hummingbot/remote_iface/mqtt.py b/hummingbot/remote_iface/mqtt.py new file mode 100644 index 0000000..e218ffc --- /dev/null +++ b/hummingbot/remote_iface/mqtt.py @@ -0,0 +1,1142 @@ +#!/usr/bin/env python + +import asyncio +import functools +import logging +import threading +import time +from collections import deque +from dataclasses import asdict, is_dataclass +from datetime import datetime +from decimal import Decimal +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple + +from hummingbot import get_logging_conf +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.logger import HummingbotLogger + +if TYPE_CHECKING: # pragma: no cover + from hummingbot.client.hummingbot_application import HummingbotApplication # noqa: F401 + from hummingbot.core.event.event_listener import EventListener # noqa: F401 + +from commlib.node import Node, NodeState +from commlib.transports.mqtt import ConnectionParameters as MQTTConnectionParameters + +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, DeductedFromReturnsTradeFee +from hummingbot.core.event import events +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.pubsub import PubSub +from hummingbot.core.utils.async_utils import call_sync, safe_ensure_future +from hummingbot.notifier.notifier_base import NotifierBase +from hummingbot.remote_iface.messages import ( + MQTT_STATUS_CODE, + BalanceLimitCommandMessage, + BalancePaperCommandMessage, + CommandShortcutMessage, + ConfigCommandMessage, + ExternalEventMessage, + HistoryCommandMessage, + ImportCommandMessage, + InternalEventMessage, + LogMessage, + NotifyMessage, + StartCommandMessage, + StatusCommandMessage, + StatusUpdateMessage, + StopCommandMessage, +) + +mqtts_logger: HummingbotLogger = None + + +class CommandTopicSpecs: + START: str = '/start' + STOP: str = '/stop' + CONFIG: str = '/config' + IMPORT: str = '/import' + STATUS: str = '/status' + HISTORY: str = '/history' + BALANCE_LIMIT: str = '/balance/limit' + BALANCE_PAPER: str = '/balance/paper' + COMMAND_SHORTCUT: str = '/command_shortcuts' + + +class TopicSpecs: + PREFIX: str = '{namespace}/{instance_id}' + COMMANDS: CommandTopicSpecs = CommandTopicSpecs() + LOGS: str = '/log' + INTERNAL_EVENTS: str = '/events' + NOTIFICATIONS: str = '/notify' + STATUS_UPDATES: str = '/status_updates' + HEARTBEATS: str = '/hb' + EXTERNAL_EVENTS: str = '/external/event/*' + + +class MQTTCommands: + def __init__(self, + hb_app: "HummingbotApplication", + node: Node): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + raise EnvironmentError( + "MQTTCommands can only be initialized from the main thread." + ) + self._hb_app = hb_app + self._node = node + self.logger = self._hb_app.logger + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._start_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.START}' + self._stop_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.STOP}' + self._config_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.CONFIG}' + self._import_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.IMPORT}' + self._status_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.STATUS}' + self._history_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.HISTORY}' + self._balance_limit_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.BALANCE_LIMIT}' + self._balance_paper_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.BALANCE_PAPER}' + self._shortcuts_uri = f'{topic_prefix}{TopicSpecs.COMMANDS.COMMAND_SHORTCUT}' + + self._init_commands() + + def _init_commands(self): + self._node.create_rpc( + rpc_name=self._start_uri, + msg_type=StartCommandMessage, + on_request=self._on_cmd_start + ) + self._node.create_rpc( + rpc_name=self._stop_uri, + msg_type=StopCommandMessage, + on_request=self._on_cmd_stop + ) + self._node.create_rpc( + rpc_name=self._config_uri, + msg_type=ConfigCommandMessage, + on_request=self._on_cmd_config + ) + self._node.create_rpc( + rpc_name=self._import_uri, + msg_type=ImportCommandMessage, + on_request=self._on_cmd_import + ) + self._node.create_rpc( + rpc_name=self._status_uri, + msg_type=StatusCommandMessage, + on_request=self._on_cmd_status + ) + self._node.create_rpc( + rpc_name=self._history_uri, + msg_type=HistoryCommandMessage, + on_request=self._on_cmd_history + ) + self._node.create_rpc( + rpc_name=self._balance_limit_uri, + msg_type=BalanceLimitCommandMessage, + on_request=self._on_cmd_balance_limit + ) + self._node.create_rpc( + rpc_name=self._balance_paper_uri, + msg_type=BalancePaperCommandMessage, + on_request=self._on_cmd_balance_paper + ) + self._node.create_rpc( + rpc_name=self._shortcuts_uri, + msg_type=CommandShortcutMessage, + on_request=self._on_cmd_command_shortcut + ) + + def _on_cmd_start(self, msg: StartCommandMessage.Request): + response = StartCommandMessage.Response() + timeout = 30 + try: + if self._hb_app.strategy_name is None and msg.script is None: + raise Exception('Strategy check: Please import or create a strategy.') + if self._hb_app.strategy is not None: + raise Exception('The bot is already running - please run "stop" first') + if msg.async_backend: + self._hb_app.start( + log_level=msg.log_level, + script=msg.script, + is_quickstart=msg.is_quickstart + ) + else: + res = call_sync( + self._hb_app.start_check( + log_level=msg.log_level, + script=msg.script, + is_quickstart=msg.is_quickstart + ), + loop=self._ev_loop, + timeout=timeout + ) + response.msg = res if res is not None else '' + except asyncio.exceptions.TimeoutError: + response.msg = f'Hummingbot start command timed out after {timeout} seconds' + response.status = MQTT_STATUS_CODE.ERROR + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_stop(self, msg: StopCommandMessage.Request): + response = StopCommandMessage.Response() + timeout = 30 + try: + if msg.async_backend: + self._hb_app.stop( + skip_order_cancellation=msg.skip_order_cancellation + ) + else: + res = call_sync( + self._hb_app.stop_loop(), + loop=self._ev_loop, + timeout=timeout + ) + response.msg = res if res is not None else '' + except asyncio.exceptions.TimeoutError: + response.msg = f'Hummingbot start command timed out after {timeout} seconds' + response.status = MQTT_STATUS_CODE.ERROR + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_config(self, msg: ConfigCommandMessage.Request): + response = ConfigCommandMessage.Response() + try: + if len(msg.params) == 0: + self._hb_app.config() + else: + invalid_params = [] + for param in msg.params: + if param[0] in self._hb_app.configurable_keys(): + self._ev_loop.call_soon_threadsafe( + self._hb_app.config, + param[0], + param[1] + ) + response.changes.append((param[0], param[1])) + else: + invalid_params.append(param[0]) + if len(invalid_params): + raise ValueError(f'Invalid param key(s): {invalid_params}') + strategy_config = {} + client_config = {} + if isinstance(self._hb_app.client_config_map, dict): # pragma: no cover + client_config = self._hb_app.client_config_map + for key, value in client_config.items(): + if isinstance(value, ConfigVar): + client_config[key] = value.value + else: + client_config[key] = value + elif isinstance(self._hb_app.client_config_map, + ClientConfigAdapter): + client_config = self._hb_app.client_config_map.dict() + if isinstance(self._hb_app._strategy_config_map, dict): # pragma: no cover + for key, value in self._hb_app._strategy_config_map.items(): + if isinstance(value, ConfigVar): + strategy_config[key] = value.value + else: + strategy_config[key] = value + elif isinstance(self._hb_app._strategy_config_map, + ClientConfigAdapter): + strategy_config = self._hb_app._strategy_config_map.dict() + response.config = { + "client": client_config, + "strategy": strategy_config + } + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + self._hb_app.logger().error(e) + return response + + def _on_cmd_import(self, msg: ImportCommandMessage.Request): + response = ImportCommandMessage.Response() + timeout = 30 # seconds + strategy_name = msg.strategy + if strategy_name in (None, ''): + response.status = MQTT_STATUS_CODE.ERROR + response.msg = 'Empty strategy_name given!' + return response + strategy_file_name = f'{strategy_name}.yml' + try: + res = call_sync( + self._hb_app.import_config_file(strategy_file_name), + loop=self._ev_loop, + timeout=timeout + ) + response.msg = res if res is not None else '' + except asyncio.exceptions.TimeoutError: + response.msg = f'Hummingbot import command timed out after {timeout} seconds' + response.status = MQTT_STATUS_CODE.ERROR + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_status(self, msg: StatusCommandMessage.Request): + response = StatusCommandMessage.Response() + timeout = 30 # seconds + try: + if self._hb_app.strategy is None: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = 'No strategy is currently running!' + return response + if msg.async_backend: + self._ev_loop.call_soon_threadsafe( + self._hb_app.status + ) + else: + res = call_sync( + self._hb_app.strategy_status(), + loop=self._ev_loop, + timeout=timeout + ) + response.msg = res if res is not None else '' + except asyncio.exceptions.TimeoutError: + response.msg = f'Hummingbot status command timed out after {timeout} seconds' + response.status = MQTT_STATUS_CODE.ERROR + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_history(self, msg: HistoryCommandMessage.Request): + response = HistoryCommandMessage.Response() + try: + if msg.async_backend: + self._hb_app.history(msg.days, msg.verbose, msg.precision) + else: + trades = self._hb_app.get_history_trades_json(msg.days) + if trades: + response.trades = trades + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_balance_limit(self, msg: BalanceLimitCommandMessage.Request): + response = BalanceLimitCommandMessage.Response() + try: + data = self._hb_app.balance( + 'limit', + [msg.exchange, msg.asset, msg.amount] + ) + response.data = data + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_balance_paper(self, msg: BalancePaperCommandMessage.Request): + response = BalancePaperCommandMessage.Response() + try: + data = self._hb_app.balance( + 'paper', + [msg.asset, msg.amount] + ) + response.data = data + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + def _on_cmd_command_shortcut(self, msg: CommandShortcutMessage.Request): + response = CommandShortcutMessage.Response() + try: + for param in msg.params: + response.success.append(self._hb_app._handle_shortcut(param)) + except Exception as e: + response.status = MQTT_STATUS_CODE.ERROR + response.msg = str(e) + return response + + +class MQTTMarketEventForwarder: + @classmethod + def logger(cls) -> HummingbotLogger: + global mqtts_logger + if mqtts_logger is None: # pragma: no cover + mqtts_logger = HummingbotLogger(__name__) + return mqtts_logger + + def __init__(self, + hb_app: "HummingbotApplication", + node: Node): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + raise EnvironmentError( + "MQTTMarketEventForwarder can only be initialized from the main thread." + ) + self._hb_app = hb_app + self._node = node + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + self._markets: List[ConnectorBase] = list(self._hb_app.markets.values()) + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._topic = f'{topic_prefix}{TopicSpecs.INTERNAL_EVENTS}' + + self._mqtt_fowarder: SourceInfoEventForwarder = \ + SourceInfoEventForwarder(self._send_mqtt_event) + self._market_event_pairs: List[Tuple[int, EventListener]] = [ + (events.MarketEvent.BuyOrderCreated, self._mqtt_fowarder), + (events.MarketEvent.BuyOrderCompleted, self._mqtt_fowarder), + (events.MarketEvent.SellOrderCreated, self._mqtt_fowarder), + (events.MarketEvent.SellOrderCompleted, self._mqtt_fowarder), + (events.MarketEvent.OrderFilled, self._mqtt_fowarder), + (events.MarketEvent.OrderFailure, self._mqtt_fowarder), + (events.MarketEvent.OrderCancelled, self._mqtt_fowarder), + (events.MarketEvent.OrderExpired, self._mqtt_fowarder), + (events.MarketEvent.FundingPaymentCompleted, self._mqtt_fowarder), + (events.MarketEvent.RangePositionLiquidityAdded, self._mqtt_fowarder), + (events.MarketEvent.RangePositionLiquidityRemoved, self._mqtt_fowarder), + (events.MarketEvent.RangePositionUpdate, self._mqtt_fowarder), + (events.MarketEvent.RangePositionUpdateFailure, self._mqtt_fowarder), + (events.MarketEvent.RangePositionFeeCollected, self._mqtt_fowarder), + (events.MarketEvent.RangePositionClosed, self._mqtt_fowarder), + ] + + self.event_fw_pub = self._node.create_publisher( + topic=self._topic, msg_type=InternalEventMessage + ) + self._start_event_listeners() + + def _send_mqtt_event(self, event_tag: int, pubsub: PubSub, event): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe( + self._send_mqtt_event, + event_tag, + pubsub, + event + ) + return + try: + event_types = { + events.MarketEvent.BuyOrderCreated.value: "BuyOrderCreated", + events.MarketEvent.BuyOrderCompleted.value: "BuyOrderCompleted", + events.MarketEvent.SellOrderCreated.value: "SellOrderCreated", + events.MarketEvent.SellOrderCompleted.value: "SellOrderCompleted", + events.MarketEvent.OrderFilled.value: "OrderFilled", + events.MarketEvent.OrderCancelled.value: "OrderCancelled", + events.MarketEvent.OrderExpired.value: "OrderExpired", + events.MarketEvent.OrderFailure.value: "OrderFailure", + events.MarketEvent.FundingPaymentCompleted.value: "FundingPaymentCompleted", + events.MarketEvent.RangePositionLiquidityAdded.value: "RangePositionLiquidityAdded", + events.MarketEvent.RangePositionLiquidityRemoved.value: "RangePositionLiquidityRemoved", + events.MarketEvent.RangePositionUpdate.value: "RangePositionUpdate", + events.MarketEvent.RangePositionUpdateFailure.value: "RangePositionUpdateFailure", + events.MarketEvent.RangePositionFeeCollected.value: "RangePositionFeeCollected", + events.MarketEvent.RangePositionClosed.value: "RangePositionClosed", + } + event_type = event_types[event_tag] + except KeyError: + event_type = "Unknown" + + if is_dataclass(event): + event_data = asdict(event) + elif isinstance(event, tuple) and hasattr(event, '_fields'): + event_data = event._asdict() + else: + try: + event_data = dict(event) + except (TypeError, ValueError): + event_data = {} + + try: + timestamp = event_data.pop('timestamp') + except KeyError: + timestamp = datetime.now().timestamp() + + event_data = self._make_event_payload(event_data) + + self.event_fw_pub.publish( + InternalEventMessage( + timestamp=int(timestamp), + type=event_type, + data=event_data + ) + ) + + def _make_event_payload(self, event_data): + if 'type' in event_data: + event_data['type'] = str(event_data['type']) + if 'order_type' in event_data: + event_data['order_type'] = str(event_data['order_type']) + if 'trade_type' in event_data: + event_data['trade_type'] = str(event_data['trade_type']) + + for key, val in event_data.items(): + if isinstance(val, dict): + self._make_event_payload(val) + elif isinstance(val, Decimal): + event_data[key] = float(val) + elif isinstance(val, DeductedFromReturnsTradeFee): + event_data[key] = val.to_json() + self._make_event_payload(event_data[key]) + elif isinstance(val, AddedToCostTradeFee): + event_data[key] = val.to_json() + self._make_event_payload(event_data[key]) + return event_data + + def _start_event_listeners(self): + for market in self._markets: + for event_pair in self._market_event_pairs: + market.add_listener(event_pair[0], event_pair[1]) + self.logger().debug( + f'Created MQTT bridge for event: {event_pair[0]}, {event_pair[1]}' + ) + + def _stop_event_listeners(self): + for market in self._markets: + for event_pair in self._market_event_pairs: + market.remove_listener(event_pair[0], event_pair[1]) + + +class MQTTNotifier(NotifierBase): + def __init__(self, + hb_app: "HummingbotApplication", + node: Node) -> None: + super().__init__() + self._node = node + self._hb_app = hb_app + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._topic = f'{topic_prefix}{TopicSpecs.NOTIFICATIONS}' + self.notify_pub = self._node.create_publisher( + topic=self._topic, + msg_type=NotifyMessage + ) + + def add_msg_to_queue(self, msg: str): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe(self.add_msg_to_queue, msg) + return + self.notify_pub.publish(NotifyMessage(msg=msg)) + + def start(self) -> None: + return None + + def stop(self) -> None: + return None + + +class MQTTStatusUpdates: + def __init__(self, + hb_app: "HummingbotApplication", + node: Node) -> None: + self._node = node + self._hb_app = hb_app + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._topic = f'{topic_prefix}{TopicSpecs.STATUS_UPDATES}' + self.status_updates_pub = self._node.create_publisher( + topic=self._topic, + msg_type=StatusUpdateMessage + ) + + if self._node.state == NodeState.RUNNING: + self.status_updates_pub.run() + + def add_msg_to_queue(self, msg: str, msg_type: str = 'hbapp'): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe(self.add_msg_to_queue, msg, msg_type) + return + + self.status_updates_pub.publish( + StatusUpdateMessage( + msg=msg, + type=msg_type, + timestamp=int(time.time() * 1e3) + ) + ) + + def stop(self): + self.status_updates_pub.stop() + + +class MQTTGateway(Node): + NODE_NAME: str = 'hbot.$instance_id' + _instance: Optional["MQTTGateway"] = None + _INTERVAL_HEALTH_CHECK = 1.0 + _INTERVAL_RESTART_SHORT = 5.0 + _INTERVAL_RESTART_LONG = 10.0 + + @classmethod + def logger(cls) -> HummingbotLogger: + global mqtts_logger + if mqtts_logger is None: # pragma: no cover + mqtts_logger = logging.getLogger(__name__) + return mqtts_logger + + @classmethod + def main(cls) -> "MQTTGateway": + return cls._instance + + def __init__(self, + hb_app: "HummingbotApplication", + *args, **kwargs + ): + self._health = False + self._initial_connection_succeeded = False + self._restarting = False + self._stop_event_async = asyncio.Event() + self._notifier: MQTTNotifier = None + self._status_updates: MQTTStatusUpdates = None + self._market_events: MQTTMarketEventForwarder = None + self._commands: MQTTCommands = None + self._logh: MQTTLogHandler = None + self._external_events: MQTTExternalEvents = None + self._hb_app: "HummingbotApplication" = hb_app + self._ev_loop = self._hb_app.ev_loop + self._params = self._create_mqtt_params_from_conf() + self.namespace = self._hb_app.client_config_map.mqtt_bridge.mqtt_namespace + if self.namespace[-1] in ('/', '.'): + self.namespace = self.namespace[:-1] + + self._topic_prefix = TopicSpecs.PREFIX.format( + namespace=self.namespace, + instance_id=self._hb_app.instance_id + ) + _hb_topic = f'{self._topic_prefix}{TopicSpecs.HEARTBEATS}' + + super().__init__( + node_name=self.NODE_NAME.replace('$instance_id', hb_app.instance_id), + connection_params=self._params, + heartbeats=True, + heartbeat_uri=_hb_topic, + *args, + **kwargs + ) + MQTTGateway._instance = self + + @property + def health(self): + return self._health + + def _safe_get_log_handlers(self, max_tries=3): # pragma: no cover + current_try = 0 + while current_try < max_tries: + try: + return list([logging.getLogger(name) for name in logging.root.manager.loggerDict]) + except RuntimeError: + current_try += 1 + + log_keys = logging.root.manager.loggerDict.keys() + return list([logging.getLogger(name) for name in log_keys]) + + def _remove_log_handlers(self): # pragma: no cover + loggers = self._safe_get_log_handlers() + log_conf = get_logging_conf() + + if 'loggers' not in log_conf: + return + + logs = [key for key, val in log_conf.get('loggers').items()] + for logger in loggers: + if 'hummingbot' in logger.name: + for log in logs: + if log in logger.name: + self.remove_log_handler(logger) + + self._logh = None + + def _init_logger(self): + self._logh = MQTTLogHandler(self._hb_app, self) + self.patch_loggers() + + def patch_loggers(self): # pragma: no cover + loggers = self._safe_get_log_handlers() + + log_conf = get_logging_conf() + if 'root' in log_conf: + if log_conf.get('root').get('mqtt'): + self.remove_log_handler(self._get_root_logger()) + self.add_log_handler(self._get_root_logger()) + + if 'loggers' not in log_conf: + return + + log_conf_names = [key for key, val in log_conf.get('loggers').items()] + loggers_filtered = [logger for logger in loggers if + logger.name in log_conf_names] + loggers_filtered = [logger for logger in loggers_filtered if + log_conf.get('loggers').get(logger.name).get('mqtt', False)] + + for logger in loggers_filtered: + self.remove_log_handler(logger) + self.add_log_handler(logger) + + def _get_root_logger(self): + return logging.getLogger() + + def remove_log_handler(self, logger: HummingbotLogger): + logger.removeHandler(self._logh) + + def add_log_handler(self, logger: HummingbotLogger): + logger.addHandler(self._logh) + + def _init_notifier(self): + if self._hb_app.client_config_map.mqtt_bridge.mqtt_notifier: + self._notifier = MQTTNotifier(self._hb_app, self) + self._hb_app.notifiers.append(self._notifier) + + def _remove_notifier(self): + self._hb_app.notifiers.remove(self._notifier) if self._notifier \ + in self._hb_app.notifiers else None + + def _init_status_updates(self): + self._status_updates = MQTTStatusUpdates(self._hb_app, self) + + def _remove_status_updates(self): + if self._status_updates is not None: + self._status_updates.stop() + self._status_updates = None + + def broadcast_status_update(self, *args, **kwargs): + if self._status_updates is not None: + self._status_updates.add_msg_to_queue(*args, **kwargs) + + def _init_commands(self): + if self._hb_app.client_config_map.mqtt_bridge.mqtt_commands: + self._commands = MQTTCommands(self._hb_app, self) + + def start_market_events_fw(self): + # Must be called after loading the strategy. + # HummingbotApplication._initialize_markets() must be be called before + if self._hb_app.client_config_map.mqtt_bridge.mqtt_events: + self._market_events = MQTTMarketEventForwarder(self._hb_app, self) + if self.state == NodeState.RUNNING: + self._market_events.event_fw_pub.run() + + def _remove_market_event_listeners(self): + if self._market_events is not None: + self._market_events._stop_event_listeners() + + def _init_external_events(self): + if self._hb_app.client_config_map.mqtt_bridge.mqtt_external_events: + self._external_events = MQTTExternalEvents(self._hb_app, self) + + def add_external_event_listener(self, + event_name: str, + callback: Callable[[ExternalEventMessage, str], None]): + if event_name == '*': + self._external_events.add_global_listener(callback) + else: + self._external_events.add_listener(event_name, callback) + + def remove_external_event_listener(self, + event_name: str, + callback: Callable[[ExternalEventMessage, str], None]): + if event_name == '*': + self._external_events.remove_global_listener(callback) + else: + self._external_events.remove_listener(event_name, callback) + + def _create_mqtt_params_from_conf(self): + host = self._hb_app.client_config_map.mqtt_bridge.mqtt_host + port = self._hb_app.client_config_map.mqtt_bridge.mqtt_port + username = self._hb_app.client_config_map.mqtt_bridge.mqtt_username + password = self._hb_app.client_config_map.mqtt_bridge.mqtt_password + ssl = self._hb_app.client_config_map.mqtt_bridge.mqtt_ssl + conn_params = MQTTConnectionParameters( + host=host, + port=int(port), + username=username, + password=password, + ssl=ssl + ) + return conn_params + + def _check_connections(self) -> bool: + if self._restarting: + return False + for c in self._publishers: + if not c._transport.is_connected: + return False + for c in self._rpc_services: + if not c._transport.is_connected: + return False + # Will use if subscribtions are integrated + for c in self._subscribers: + if not c._transport.is_connected: + return False + # Will use if rpc clients are integrated + # for c in self._rpc_clients: + # if not c._transport.is_connected: + # return False + return True + + def _start_health_monitoring_loop(self): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe(self._start_health_monitoring_loop) + return + self._stop_event_async.clear() + safe_ensure_future(self._monitor_health_loop(), + loop=self._ev_loop) + + async def _monitor_health_loop(self): + while not self._stop_event_async.is_set(): + # Maybe we can include more checks here to determine the health! + self._health = await self._ev_loop.run_in_executor( + None, self._check_connections) + if self.health: + if not self._initial_connection_succeeded: + self._initial_connection_succeeded = True + self._hb_app.logger().debug('Monitoring MQTT Gateway health for disconnections.') + + await asyncio.sleep(self._INTERVAL_HEALTH_CHECK) + elif self._initial_connection_succeeded and not self._stop_event_async.is_set(): + await self._restart_gateway() + + async def _restart_gateway(self): + self._hb_app.logger().warning('MQTT Gateway is disconnected, attempting to reconnect.') + + try: + self._restarting = True + self.stop(False) + await asyncio.sleep(self._INTERVAL_RESTART_SHORT) + + self._publishers = [] + self._subscribers = [] + self._rpc_services = [] + # self._rpc_clients = [] + + self.start(False) + if self._hb_app.strategy is not None: + self.start_market_events_fw() + + await asyncio.sleep(self._INTERVAL_RESTART_SHORT) + + self._restarting = False + + self._health = await self._ev_loop.run_in_executor( + None, self._check_connections) + + if self._health: + self._hb_app.logger().warning('MQTT Gateway successfully reconnected.') + + except Exception as e: + self._hb_app.logger().error(f'MQTT Gateway failed to reconnect: {e}. Sleeping 10 seconds before retry.') + + await asyncio.sleep(self._INTERVAL_RESTART_LONG) + + def _stop_health_monitoring_loop(self): + self._stop_event_async.set() + + def start(self, with_health: bool = True) -> None: + self._init_logger() + self._init_notifier() + self._init_status_updates() + self._init_commands() + self._init_external_events() + + if with_health: + self._start_health_monitoring_loop() + + self.run() + self.broadcast_status_update("online", msg_type="availability") + + def stop(self, with_health: bool = True): + self.broadcast_status_update("offline", msg_type="availability") + super().stop() + if self._hb_thread: + self._hb_thread.stop() + self._remove_status_updates() + self._remove_notifier() + self._remove_log_handlers() + self._remove_market_event_listeners() + + if with_health: + self._stop_health_monitoring_loop() + + def __del__(self): + self.stop() + + +class MQTTLogHandler(logging.Handler): + def __init__(self, + hb_app: "HummingbotApplication", + node: Node): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + raise EnvironmentError( + "MQTTLogHandler can only be initialized from the main thread." + ) + self._hb_app = hb_app + self._node = node + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._topic = f'{topic_prefix}{TopicSpecs.LOGS}' + + super().__init__() + self.name = self.__class__.__name__ + self.log_pub = self._node.create_publisher(topic=self._topic, + msg_type=LogMessage) + + def emit(self, record: logging.LogRecord): + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe(self.emit, record) + return + msg_str = self.format(record) + msg = LogMessage( + timestamp=time.time(), + msg=msg_str, + level_no=record.levelno, + level_name=record.levelname, + logger_name=record.name + + ) + self.log_pub.publish(msg) + + +class MQTTExternalEvents: + def __init__(self, + hb_app: "HummingbotApplication", + node: Node + ): + self._node: Node = node + self._hb_app: 'HummingbotApplication' = hb_app + self._ev_loop: asyncio.AbstractEventLoop = self._hb_app.ev_loop + + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._hb_app.instance_id + ) + self._topic = f'{topic_prefix}{TopicSpecs.EXTERNAL_EVENTS}' + + self._node.create_psubscriber( + topic=self._topic, + msg_type=ExternalEventMessage, + on_message=self._on_event_arrived + ) + self._listeners: Dict[ + str, + List[Callable[ExternalEventMessage, str], None] + ] = {'*': []} + + def _event_uri_to_name(self, topic: str) -> str: + return topic.split('event/')[1].replace('/', '.') + + def _on_event_arrived(self, + msg: ExternalEventMessage, + topic: str + ) -> None: + if threading.current_thread() != threading.main_thread(): # pragma: no cover + self._ev_loop.call_soon_threadsafe(self._on_event_arrived, + msg, topic) + return + event_name = self._event_uri_to_name(topic) + self._hb_app.logger().debug( + f'Received external event {event_name} -> {msg} - ' + 'Broadcasting to listeners...' + ) + if event_name in self._listeners: + for fenc in self._listeners[event_name]: + fenc(msg, event_name) + for fenc in self._listeners['*']: + fenc(msg, event_name) + + def add_listener(self, + event_name: str, + callback: Callable[[ExternalEventMessage, str], None] + ) -> None: + # TODO validate event_name with regex + if event_name in self._listeners: + self._listeners.get(event_name).append(callback) + else: + self._listeners[event_name] = [callback] + + def remove_listener(self, + event_name: str, + callback: Callable[[ExternalEventMessage, str], None] + ): + # TODO validate event_name with regex + if event_name in self._listeners: + self._listeners.get(event_name).remove(callback) + + def add_global_listener(self, + callback: Callable[[ExternalEventMessage, str], None] + ): + if '*' in self._listeners: + self._listeners.get('*').append(callback) + else: + self._listeners['*'] = [callback] + + def remove_global_listener(self, + callback: Callable[[ExternalEventMessage, str], None] + ): + if '*' in self._listeners: + self._listeners.get('*').remove(callback) + + +class ETopicListener: + def __init__(self, + topic: str, + on_message: Callable[[Dict[str, Any], str], None], + use_bot_prefix: Optional[bool] = True + ): + self._node = MQTTGateway.main() + if self._node is None: + raise Exception('MQTT Gateway not yet initialized') + topic_prefix = TopicSpecs.PREFIX.format( + namespace=self._node.namespace, + instance_id=self._node._hb_app.instance_id + ) + if use_bot_prefix: + self._topic = f'{topic_prefix}/{topic}' + else: + self._topic = topic + self._on_message = on_message + self._sub = self._node.create_psubscriber(topic=self._topic, + on_message=self._on_message) + if self._node.state == NodeState.RUNNING: + self._sub.run() + + def stop(self): + self._sub.stop() + + +class EEventQueueFactory: + @classmethod + def create(cls, + event_name: str, + queue_size: Optional[int] = 1000 + ) -> deque: + gw = MQTTGateway.main() + queue = deque(maxlen=queue_size) + if gw is None: + raise Exception('MQTTGateway is offline!') + _on_event_clb = functools.partial(cls._on_event, queue) + gw.add_external_event_listener(event_name, _on_event_clb) + return queue + + @classmethod + def _on_event(cls, queue: deque, msg: Dict[str, Any], name): + queue.append((name, msg)) + + +class EEventListenerFactory: + @classmethod + def create(cls, + event_name: str, + callback: Callable[[Dict[str, Any], str], None], + ) -> None: + gw = MQTTGateway.main() + if gw is None: + raise Exception('MQTTGateway is offline!') + gw.add_external_event_listener(event_name, callback) + + @classmethod + def remove(cls, + event_name: str, + callback: Callable[[Dict[str, Any], str], None], + ) -> None: + gw = MQTTGateway.main() + if gw is None: + raise Exception('MQTTGateway is offline!') + gw.remove_external_event_listener(event_name, callback) + + +class ETopicListenerFactory: + @classmethod + def create(cls, + topic: str, + callback: Callable[[Dict[str, Any], str], None], + use_bot_prefix: Optional[bool] = True + ) -> ETopicListener: + listener = ETopicListener( + topic=topic, + on_message=callback, + use_bot_prefix=use_bot_prefix + ) + return listener + + @classmethod + def remove(cls, listener): + listener.stop() + del listener + + +class ETopicQueueFactory: + @classmethod + def create(cls, + topic: str, + queue_size: Optional[int] = 1000, + use_bot_prefix: Optional[bool] = True + ) -> deque: + queue = deque(maxlen=queue_size) + on_msg = functools.partial(cls._on_message, queue) + _ = ETopicListener( + topic=topic, + on_message=on_msg, + use_bot_prefix=use_bot_prefix + ) + return queue + + @classmethod + def _on_message(cls, queue: deque, msg: Dict[str, Any], topic: str): + queue.append((topic, msg)) + + +class ExternalEventFactory: + @classmethod + def create_queue(cls, + event_name: str, + queue_size: Optional[int] = 1000 + ) -> deque: + return EEventQueueFactory.create(event_name, queue_size) + + @classmethod + def create_async(cls, + event_name: str, + callback: Callable[[Dict[str, Any], str], None], + ) -> None: + return EEventListenerFactory.create(event_name, callback) + + @classmethod + def remove_listener(cls, + event_name: str, + callback: Callable[[Dict[str, Any], str], None], + ) -> None: + EEventListenerFactory.remove(event_name, callback) + + +class ExternalTopicFactory: + @classmethod + def create_queue(cls, + topic: str, + queue_size: Optional[int] = 1000, + use_bot_prefix: Optional[bool] = True + ) -> deque: + return ETopicQueueFactory.create(topic, queue_size, use_bot_prefix) + + @classmethod + def create_async(cls, + topic: str, + callback: Callable[[Dict[str, Any], str], None], + use_bot_prefix: Optional[bool] = True + ) -> ETopicListener: + return ETopicListenerFactory.create(topic, callback, use_bot_prefix) + + @classmethod + def remove_listener(cls, listener): + return ETopicListenerFactory.remove(listener) diff --git a/hummingbot/smart_components/__init__.py b/hummingbot/smart_components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/controllers/__init__.py b/hummingbot/smart_components/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/controllers/bollingrid.py b/hummingbot/smart_components/controllers/bollingrid.py new file mode 100644 index 0000000..9a1a837 --- /dev/null +++ b/hummingbot/smart_components/controllers/bollingrid.py @@ -0,0 +1,104 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class BollingGridConfig(MarketMakingControllerConfigBase): + strategy_name: str = "bollinger_grid" + bb_length: int = 12 + bb_std: float = 2.0 + natr_length: int = 14 + + +class BollingGrid(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic and shift the mid price. + """ + + def __init__(self, config: BollingGridConfig): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + natr = ta.natr(candles_df["high"], candles_df["low"], candles_df["close"], length=self.config.natr_length) / 100 + candles_df.ta.bbands(length=self.config.bb_length, std=self.config.bb_std, append=True) + bbp = candles_df[f"BBP_{self.config.bb_length}_{self.config.bb_std}"] + + candles_df["spread_multiplier"] = natr + candles_df["price_multiplier"] = bbp + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.trading_pair) + bbp, spread_multiplier = self.get_price_and_spread_multiplier() + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + + if (bbp > 0.7 and side_multiplier == 1) or (bbp < 0.3 and side_multiplier == -1): + order_price = close_price * (1 + order_level.spread_factor * spread_multiplier * side_multiplier) + amount = order_level.order_amount_usd / order_price + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/hummingbot/smart_components/controllers/dman_v1.py b/hummingbot/smart_components/controllers/dman_v1.py new file mode 100644 index 0000000..782e055 --- /dev/null +++ b/hummingbot/smart_components/controllers/dman_v1.py @@ -0,0 +1,101 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV1Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v1" + natr_length: int = 14 + + +class DManV1(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic. + """ + + def __init__(self, config: DManV1Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + natr = ta.natr(candles_df["high"], candles_df["low"], candles_df["close"], length=self.config.natr_length) / 100 + + candles_df["spread_multiplier"] = natr + candles_df["price_multiplier"] = 0.0 + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.trading_pair) + price_multiplier, spread_multiplier = self.get_price_and_spread_multiplier() + + price_adjusted = close_price * (1 + price_multiplier) + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + order_price = price_adjusted * (1 + order_level.spread_factor * spread_multiplier * side_multiplier) + amount = order_level.order_amount_usd / order_price + + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/hummingbot/smart_components/controllers/dman_v2.py b/hummingbot/smart_components/controllers/dman_v2.py new file mode 100644 index 0000000..402d479 --- /dev/null +++ b/hummingbot/smart_components/controllers/dman_v2.py @@ -0,0 +1,112 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV2Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v2" + macd_fast: int = 12 + macd_slow: int = 26 + macd_signal: int = 9 + natr_length: int = 14 + + +class DManV2(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic and shift the mid price. + """ + + def __init__(self, config: DManV2Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + natr = ta.natr(candles_df["high"], candles_df["low"], candles_df["close"], length=self.config.natr_length) / 100 + + macd_output = ta.macd(candles_df["close"], fast=self.config.macd_fast, slow=self.config.macd_slow, signal=self.config.macd_signal) + macd = macd_output[f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + macdh = macd_output[f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + macd_signal = - (macd - macd.mean()) / macd.std() + macdh_signal = macdh.apply(lambda x: 1 if x > 0 else -1) + max_price_shift = natr / 2 + + price_multiplier = (0.5 * macd_signal + 0.5 * macdh_signal) * max_price_shift + + candles_df["spread_multiplier"] = natr + candles_df["price_multiplier"] = price_multiplier + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.trading_pair) + price_multiplier, spread_multiplier = self.get_price_and_spread_multiplier() + + price_adjusted = close_price * (1 + price_multiplier) + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + order_price = price_adjusted * (1 + order_level.spread_factor * spread_multiplier * side_multiplier) + amount = order_level.order_amount_usd / order_price + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/hummingbot/smart_components/controllers/dman_v3.py b/hummingbot/smart_components/controllers/dman_v3.py new file mode 100644 index 0000000..caea6f2 --- /dev/null +++ b/hummingbot/smart_components/controllers/dman_v3.py @@ -0,0 +1,125 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV3Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v3" + bb_length: int = 100 + bb_std: float = 2.0 + side_filter: bool = False + smart_activation: bool = False + activation_threshold: Decimal = Decimal("0.001") + dynamic_spread_factor: bool = True + dynamic_target_spread: bool = False + + +class DManV3(MarketMakingControllerBase): + """ + Mean reversion strategy with Grid execution making use of Bollinger Bands indicator to make spreads dynamic + and shift the mid price. + """ + + def __init__(self, config: DManV3Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + bbp = ta.bbands(candles_df["close"], length=self.config.bb_length, std=self.config.bb_std) + + candles_df["price_multiplier"] = bbp[f"BBM_{self.config.bb_length}_{self.config.bb_std}"] + candles_df["spread_multiplier"] = bbp[f"BBB_{self.config.bb_length}_{self.config.bb_std}"] / 200 + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.trading_pair) + + bollinger_mid_price, spread_multiplier = self.get_price_and_spread_multiplier() + if not self.config.dynamic_spread_factor: + spread_multiplier = 1 + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + order_spread_multiplier = order_level.spread_factor * spread_multiplier * side_multiplier + order_price = bollinger_mid_price * (1 + order_spread_multiplier) + amount = order_level.order_amount_usd / order_price + + # Avoid placing the order from the opposite side + side_filter_condition = self.config.side_filter and ( + (bollinger_mid_price > close_price and side_multiplier == 1) or + (bollinger_mid_price < close_price and side_multiplier == -1)) + if side_filter_condition: + return + + # Smart activation of orders + smart_activation_condition = self.config.smart_activation and ( + side_multiplier == 1 and (close_price < order_price * (1 + self.config.activation_threshold)) or + (side_multiplier == -1 and (close_price > order_price * (1 - self.config.activation_threshold)))) + if smart_activation_condition: + return + + target_spread = spread_multiplier if self.config.dynamic_target_spread else 1 + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta * target_spread, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta * target_spread, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit * target_spread, + stop_loss=order_level.triple_barrier_conf.stop_loss * target_spread, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/hummingbot/smart_components/controllers/macd_bb_v1.py b/hummingbot/smart_components/controllers/macd_bb_v1.py new file mode 100644 index 0000000..6504577 --- /dev/null +++ b/hummingbot/smart_components/controllers/macd_bb_v1.py @@ -0,0 +1,77 @@ +import time +from typing import Optional + +import pandas as pd +from pydantic import Field + +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_controller_base import ( + DirectionalTradingControllerBase, + DirectionalTradingControllerConfigBase, +) + + +class MACDBBV1Config(DirectionalTradingControllerConfigBase): + strategy_name: str = "dman_v1" + bb_length: int = Field(default=24, ge=2, le=1000) + bb_std: float = Field(default=2.0, ge=0.5, le=4.0) + bb_long_threshold: float = Field(default=0.0, ge=-3.0, le=0.5) + bb_short_threshold: float = Field(default=1.0, ge=0.5, le=3.0) + macd_fast: int = Field(default=21, ge=2, le=100) + macd_slow: int = Field(default=42, ge=30, le=1000) + macd_signal: int = Field(default=9, ge=2, le=100) + std_span: Optional[int] = None + + +class MACDBBV1(DirectionalTradingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic. + """ + + def __init__(self, config: MACDBBV1Config): + super().__init__(config) + self.config = config + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self) -> pd.DataFrame: + df = self.candles[0].candles_df + + # Add indicators + df.ta.bbands(length=self.config.bb_length, std=self.config.bb_std, append=True) + df.ta.macd(fast=self.config.macd_fast, slow=self.config.macd_slow, signal=self.config.macd_signal, append=True) + bbp = df[f"BBP_{self.config.bb_length}_{self.config.bb_std}"] + macdh = df[f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + macd = df[f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + + # Generate signal + long_condition = (bbp < self.config.bb_long_threshold) & (macdh > 0) & (macd < 0) + short_condition = (bbp > self.config.bb_short_threshold) & (macdh < 0) & (macd > 0) + df["signal"] = 0 + df.loc[long_condition, "signal"] = 1 + df.loc[short_condition, "signal"] = -1 + + # Optional: Generate spread multiplier + if self.config.std_span: + df["target"] = df["close"].rolling(self.config.std_span).std() / df["close"] + return df + + def extra_columns_to_show(self): + return [f"BBP_{self.config.bb_length}_{self.config.bb_std}", + f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}", + f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] diff --git a/hummingbot/smart_components/executors/__init__.py b/hummingbot/smart_components/executors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/executors/arbitrage_executor/__init__.py b/hummingbot/smart_components/executors/arbitrage_executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/executors/arbitrage_executor/arbitrage_executor.py b/hummingbot/smart_components/executors/arbitrage_executor/arbitrage_executor.py new file mode 100644 index 0000000..1da2a22 --- /dev/null +++ b/hummingbot/smart_components/executors/arbitrage_executor/arbitrage_executor.py @@ -0,0 +1,269 @@ +import asyncio +import logging +from decimal import Decimal +from functools import lru_cache +from typing import Union + +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.event.events import BuyOrderCreatedEvent, MarketOrderFailureEvent, SellOrderCreatedEvent +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.logger import HummingbotLogger +from hummingbot.smart_components.executors.arbitrage_executor.data_types import ArbitrageConfig, ArbitrageExecutorStatus +from hummingbot.smart_components.executors.position_executor.data_types import TrackedOrder +from hummingbot.smart_components.smart_component_base import SmartComponentBase +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class ArbitrageExecutor(SmartComponentBase): + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + @staticmethod + @lru_cache(maxsize=10) + def is_amm(exchange: str) -> bool: + return exchange in sorted( + AllConnectorSettings.get_gateway_amm_connector_names() + ) + + @property + def is_closed(self): + return self.arbitrage_status in [ArbitrageExecutorStatus.COMPLETED, ArbitrageExecutorStatus.FAILED] + + @staticmethod + def _are_tokens_interchangeable(first_token: str, second_token: str): + interchangeable_tokens = [ + {"WETH", "ETH"}, + {"WBTC", "BTC"}, + {"WBNB", "BNB"}, + {"WMATIC", "MATIC"}, + {"WAVAX", "AVAX"}, + {"WONE", "ONE"}, + ] + same_token_condition = first_token == second_token + tokens_interchangeable_condition = any(({first_token, second_token} <= interchangeable_pair + for interchangeable_pair + in interchangeable_tokens)) + # for now, we will consider all the stablecoins interchangeable + stable_coins_condition = "USD" in first_token and "USD" in second_token + return same_token_condition or tokens_interchangeable_condition or stable_coins_condition + + def __init__(self, strategy: ScriptStrategyBase, arbitrage_config: ArbitrageConfig, update_interval: float = 1.0): + if not self.is_arbitrage_valid(pair1=arbitrage_config.buying_market.trading_pair, + pair2=arbitrage_config.selling_market.trading_pair): + raise Exception("Arbitrage is not valid since the trading pairs are not interchangeable.") + connectors = [arbitrage_config.buying_market.exchange, arbitrage_config.selling_market.exchange] + self.buying_market = arbitrage_config.buying_market + self.selling_market = arbitrage_config.selling_market + self.min_profitability = arbitrage_config.min_profitability + self.order_amount = arbitrage_config.order_amount + self.max_retries = arbitrage_config.max_retries + self.arbitrage_status = ArbitrageExecutorStatus.NOT_STARTED + + # Order tracking + self._buy_order: TrackedOrder = TrackedOrder() + self._sell_order: TrackedOrder = TrackedOrder() + + self._last_buy_price = Decimal("1") + self._last_sell_price = Decimal("1") + self._last_tx_cost = Decimal("1") + self._cumulative_failures = 0 + super().__init__(strategy, list(connectors), update_interval) + + def is_arbitrage_valid(self, pair1, pair2): + base_asset1, quote_asset1 = split_hb_trading_pair(pair1) + base_asset2, quote_asset2 = split_hb_trading_pair(pair2) + return self._are_tokens_interchangeable(base_asset1, base_asset2) and \ + self._are_tokens_interchangeable(quote_asset1, quote_asset2) + + @property + def net_pnl(self) -> Decimal: + if self.arbitrage_status == ArbitrageExecutorStatus.COMPLETED: + sell_quote_amount = self.sell_order.order.executed_amount_base * self.sell_order.average_executed_price + buy_quote_amount = self.buy_order.order.executed_amount_base * self.buy_order.average_executed_price + cum_fees = self.buy_order.cum_fees + self.sell_order.cum_fees + return sell_quote_amount - buy_quote_amount - cum_fees + else: + return Decimal("0") + + @property + def net_pnl_pct(self) -> Decimal: + if self.arbitrage_status == ArbitrageExecutorStatus.COMPLETED: + return self.net_pnl / self.buy_order.order.executed_amount_base + else: + return Decimal("0") + + @property + def buy_order(self) -> TrackedOrder: + return self._buy_order + + @buy_order.setter + def buy_order(self, value: TrackedOrder): + self._buy_order = value + + @property + def sell_order(self) -> TrackedOrder: + return self._sell_order + + @sell_order.setter + def sell_order(self, value: TrackedOrder): + self._sell_order = value + + async def get_resulting_price_for_amount(self, exchange: str, trading_pair: str, is_buy: bool, order_amount: Decimal): + return await self.connectors[exchange].get_quote_price(trading_pair, is_buy, order_amount) + + async def control_task(self): + if self.arbitrage_status == ArbitrageExecutorStatus.NOT_STARTED: + try: + trade_pnl_pct = await self.get_trade_pnl_pct() + fee_pct = await self.get_tx_cost_pct() + profitability = trade_pnl_pct - fee_pct + if profitability > self.min_profitability: + await self.execute_arbitrage() + except Exception as e: + self.logger().error(f"Error calculating profitability: {e}") + elif self.arbitrage_status == ArbitrageExecutorStatus.ACTIVE_ARBITRAGE: + if self._cumulative_failures > self.max_retries: + self.arbitrage_status = ArbitrageExecutorStatus.FAILED + self.terminate_control_loop() + else: + self.check_order_status() + + def check_order_status(self): + if self.buy_order.order and self.buy_order.order.is_filled and \ + self.sell_order.order and self.sell_order.order.is_filled: + self.arbitrage_status = ArbitrageExecutorStatus.COMPLETED + self.terminate_control_loop() + + async def execute_arbitrage(self): + self.arbitrage_status = ArbitrageExecutorStatus.ACTIVE_ARBITRAGE + self.place_buy_arbitrage_order() + self.place_sell_arbitrage_order() + + def place_buy_arbitrage_order(self): + self.buy_order.order_id = self.place_order( + connector_name=self.buying_market.exchange, + trading_pair=self.buying_market.trading_pair, + order_type=OrderType.MARKET, + side=TradeType.BUY, + amount=self.order_amount, + price=self._last_buy_price, + ) + + def place_sell_arbitrage_order(self): + self.sell_order.order_id = self.place_order( + connector_name=self.selling_market.exchange, + trading_pair=self.selling_market.trading_pair, + order_type=OrderType.MARKET, + side=TradeType.SELL, + amount=self.order_amount, + price=self._last_sell_price, + ) + + async def get_tx_cost_pct(self) -> Decimal: + base, quote = split_hb_trading_pair(trading_pair=self.buying_market.trading_pair) + # TODO: also due the fact that we don't have a good rate oracle source we have to use a fixed token + base_without_wrapped = base[1:] if base.startswith("W") else base + buy_fee = await self.get_tx_cost_in_asset( + exchange=self.buying_market.exchange, + trading_pair=self.buying_market.trading_pair, + is_buy=True, + order_amount=self.order_amount, + asset=base_without_wrapped + ) + sell_fee = await self.get_tx_cost_in_asset( + exchange=self.selling_market.exchange, + trading_pair=self.selling_market.trading_pair, + is_buy=False, + order_amount=self.order_amount, + asset=base_without_wrapped) + self._last_tx_cost = buy_fee + sell_fee + return self._last_tx_cost / self.order_amount + + async def get_buy_and_sell_prices(self): + buy_price_task = asyncio.create_task(self.get_resulting_price_for_amount( + exchange=self.buying_market.exchange, + trading_pair=self.buying_market.trading_pair, + is_buy=True, + order_amount=self.order_amount)) + sell_price_task = asyncio.create_task(self.get_resulting_price_for_amount( + exchange=self.selling_market.exchange, + trading_pair=self.selling_market.trading_pair, + is_buy=False, + order_amount=self.order_amount)) + + buy_price, sell_price = await asyncio.gather(buy_price_task, sell_price_task) + return buy_price, sell_price + + async def get_trade_pnl_pct(self): + self._last_buy_price, self._last_sell_price = await self.get_buy_and_sell_prices() + if not self._last_buy_price or not self._last_sell_price: + raise Exception("Could not get buy and sell prices") + return (self._last_sell_price - self._last_buy_price) / self._last_buy_price + + async def get_tx_cost_in_asset(self, exchange: str, trading_pair: str, is_buy: bool, order_amount: Decimal, asset: str): + connector = self.connectors[exchange] + price = await self.get_resulting_price_for_amount(exchange, trading_pair, is_buy, order_amount) + if self.is_amm(exchange=exchange): + gas_cost = connector.network_transaction_fee + conversion_price = RateOracle.get_instance().get_pair_rate(f"{asset}-{gas_cost.token}") + return gas_cost.amount / conversion_price + else: + fee = connector.get_fee( + base_currency=asset, + quote_currency=asset, + order_type=OrderType.MARKET, + order_side=TradeType.BUY if is_buy else TradeType.SELL, + amount=order_amount, + price=price, + is_maker=False + ) + return fee.fee_amount_in_token( + trading_pair=trading_pair, + price=price, + order_amount=order_amount, + token=asset, + exchange=connector, + ) + + def process_order_created_event(self, _, market, event: Union[BuyOrderCreatedEvent, SellOrderCreatedEvent]): + if self.buy_order.order_id == event.order_id: + self.buy_order.order = self.get_in_flight_order(self.buying_market.exchange, event.order_id) + self.logger().info("Buy Order Created") + elif self.sell_order.order_id == event.order_id: + self.logger().info("Sell Order Created") + self.sell_order.order = self.get_in_flight_order(self.selling_market.exchange, event.order_id) + + def process_order_failed_event(self, _, market, event: MarketOrderFailureEvent): + if self.buy_order.order_id == event.order_id: + self.place_buy_arbitrage_order() + self._cumulative_failures += 1 + elif self.sell_order.order_id == event.order_id: + self.place_sell_arbitrage_order() + self._cumulative_failures += 1 + + def to_format_status(self): + lines = [] + if self._last_buy_price and self._last_sell_price: + trade_pnl_pct = (self._last_sell_price - self._last_buy_price) / self._last_buy_price + tx_cost_pct = self._last_tx_cost / self.order_amount + base, quote = split_hb_trading_pair(trading_pair=self.buying_market.trading_pair) + lines.extend([f""" + Arbitrage Status: {self.arbitrage_status} + - BUY: {self.buying_market.exchange}:{self.buying_market.trading_pair} --> SELL: {self.selling_market.exchange}:{self.selling_market.trading_pair} | Amount: {self.order_amount:.2f} + - Trade PnL (%): {trade_pnl_pct * 100:.2f} % | TX Cost (%): -{tx_cost_pct * 100:.2f} % | Net PnL (%): {(trade_pnl_pct - tx_cost_pct) * 100:.2f} % + ------------------------------------------------------------------------------- + """]) + if self.arbitrage_status == ArbitrageExecutorStatus.COMPLETED: + lines.extend([f"Total Profit (%): {self.net_pnl_pct * 100:.2f} | Total Profit ({quote}): {self.net_pnl:.4f}"]) + return lines + else: + msg = ["There was an error while formatting the status for the executor."] + self.logger().warning(msg) + return lines.extend(msg) diff --git a/hummingbot/smart_components/executors/arbitrage_executor/data_types.py b/hummingbot/smart_components/executors/arbitrage_executor/data_types.py new file mode 100644 index 0000000..f86c489 --- /dev/null +++ b/hummingbot/smart_components/executors/arbitrage_executor/data_types.py @@ -0,0 +1,24 @@ +from decimal import Decimal +from enum import Enum + +from pydantic import BaseModel + + +class ExchangePair(BaseModel): + exchange: str + trading_pair: str + + +class ArbitrageConfig(BaseModel): + buying_market: ExchangePair + selling_market: ExchangePair + order_amount: Decimal + min_profitability: Decimal + max_retries: int = 3 + + +class ArbitrageExecutorStatus(Enum): + NOT_STARTED = 1 + ACTIVE_ARBITRAGE = 2 + COMPLETED = 3 + FAILED = 4 diff --git a/hummingbot/smart_components/executors/position_executor/__init__.py b/hummingbot/smart_components/executors/position_executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/executors/position_executor/data_types.py b/hummingbot/smart_components/executors/position_executor/data_types.py new file mode 100644 index 0000000..4993e06 --- /dev/null +++ b/hummingbot/smart_components/executors/position_executor/data_types.py @@ -0,0 +1,90 @@ +from enum import Enum +from typing import Optional + +from pydantic import BaseModel +from pydantic.types import Decimal + +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder + + +class TrailingStop(BaseModel): + activation_price_delta: Decimal + trailing_delta: Decimal + + +class PositionConfig(BaseModel): + timestamp: float + trading_pair: str + exchange: str + side: TradeType + amount: Decimal + take_profit: Optional[Decimal] = None + stop_loss: Optional[Decimal] = None + trailing_stop: Optional[TrailingStop] = None + time_limit: Optional[int] = None + entry_price: Optional[Decimal] = None + open_order_type: OrderType = OrderType.MARKET + take_profit_order_type: OrderType = OrderType.MARKET + stop_loss_order_type: OrderType = OrderType.MARKET + time_limit_order_type: OrderType = OrderType.MARKET + leverage: int = 1 + + +class PositionExecutorStatus(Enum): + NOT_STARTED = 1 + ACTIVE_POSITION = 2 + COMPLETED = 3 + + +class CloseType(Enum): + TIME_LIMIT = 1 + STOP_LOSS = 2 + TAKE_PROFIT = 3 + EXPIRED = 4 + EARLY_STOP = 5 + TRAILING_STOP = 6 + INSUFFICIENT_BALANCE = 7 + + +class TrackedOrder: + def __init__(self, order_id: Optional[str] = None): + self._order_id = order_id + self._order = None + + @property + def order_id(self): + return self._order_id + + @order_id.setter + def order_id(self, order_id: str): + self._order_id = order_id + + @property + def order(self): + return self._order + + @order.setter + def order(self, order: InFlightOrder): + self._order = order + + @property + def average_executed_price(self): + if self.order: + return self.order.average_executed_price + else: + return None + + @property + def executed_amount_base(self): + if self.order: + return self.order.executed_amount_base + else: + return Decimal("0") + + @property + def cum_fees(self): + if self.order: + return self.order.cumulative_fee_paid(token=self.order.quote_asset) + else: + return Decimal("0") diff --git a/hummingbot/smart_components/executors/position_executor/position_executor.py b/hummingbot/smart_components/executors/position_executor/position_executor.py new file mode 100644 index 0000000..1147e46 --- /dev/null +++ b/hummingbot/smart_components/executors/position_executor/position_executor.py @@ -0,0 +1,524 @@ +import logging +import math +from decimal import Decimal +from typing import Union + +from hummingbot.core.data_type.common import OrderType, PositionAction, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate, PerpetualOrderCandidate +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.logger import HummingbotLogger +from hummingbot.smart_components.executors.position_executor.data_types import ( + CloseType, + PositionConfig, + PositionExecutorStatus, + TrackedOrder, +) +from hummingbot.smart_components.smart_component_base import SmartComponentBase +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class PositionExecutor(SmartComponentBase): + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, strategy: ScriptStrategyBase, position_config: PositionConfig, update_interval: float = 1.0): + if not (position_config.take_profit or position_config.stop_loss or position_config.time_limit): + error = "At least one of take_profit, stop_loss or time_limit must be set" + self.logger().error(error) + raise ValueError(error) + if position_config.time_limit_order_type != OrderType.MARKET or position_config.stop_loss_order_type != OrderType.MARKET: + error = "Only market orders are supported for time_limit and stop_loss" + self.logger().error(error) + raise ValueError(error) + self._position_config: PositionConfig = position_config + self.close_type = None + self.close_timestamp = None + self._executor_status: PositionExecutorStatus = PositionExecutorStatus.NOT_STARTED + + # Order tracking + self._open_order: TrackedOrder = TrackedOrder() + self._close_order: TrackedOrder = TrackedOrder() + self._take_profit_order: TrackedOrder = TrackedOrder() + self._trailing_stop_price = Decimal("0") + self._trailing_stop_activated = False + super().__init__(strategy=strategy, connectors=[position_config.exchange], update_interval=update_interval) + + @property + def executor_status(self): + return self._executor_status + + @executor_status.setter + def executor_status(self, status: PositionExecutorStatus): + self._executor_status = status + + @property + def is_closed(self): + return self.executor_status == PositionExecutorStatus.COMPLETED + + @property + def is_perpetual(self): + return self.exchange.split("_")[-1] == "perpetual" + + @property + def position_config(self): + return self._position_config + + @property + def exchange(self): + return self.position_config.exchange + + @property + def trading_pair(self): + return self.position_config.trading_pair + + @property + def amount(self): + return self.position_config.amount + + @property + def filled_amount(self): + return self.open_order.executed_amount_base + + @property + def entry_price(self): + if self.open_order.average_executed_price: + return self.open_order.average_executed_price + elif self.position_config.entry_price: + return self.position_config.entry_price + else: + price_type = PriceType.BestAsk if self.side == TradeType.BUY else PriceType.BestBid + return self.get_price(self.exchange, self.trading_pair, price_type=price_type) + + @property + def trailing_stop_config(self): + return self.position_config.trailing_stop + + @property + def close_price(self): + if self.executor_status == PositionExecutorStatus.NOT_STARTED or self.close_type in [CloseType.EXPIRED, CloseType.INSUFFICIENT_BALANCE]: + return self.entry_price + elif self.executor_status == PositionExecutorStatus.ACTIVE_POSITION: + price_type = PriceType.BestBid if self.side == TradeType.BUY else PriceType.BestAsk + return self.get_price(self.exchange, self.trading_pair, price_type=price_type) + else: + return self.close_order.average_executed_price + + @property + def trade_pnl(self): + if self.side == TradeType.BUY: + return (self.close_price - self.entry_price) / self.entry_price + else: + return (self.entry_price - self.close_price) / self.entry_price + + @property + def trade_pnl_quote(self): + return self.trade_pnl * self.filled_amount * self.entry_price + + @property + def net_pnl_quote(self): + return self.trade_pnl_quote - self.cum_fee_quote + + @property + def net_pnl(self): + if self.filled_amount == Decimal("0"): + return Decimal("0") + else: + return self.net_pnl_quote / (self.filled_amount * self.entry_price) + + @property + def cum_fee_quote(self): + return self.open_order.cum_fees + self.close_order.cum_fees + + @property + def end_time(self): + if not self.position_config.time_limit: + return None + return self.position_config.timestamp + self.position_config.time_limit + + @property + def side(self): + return self.position_config.side + + @property + def open_order_type(self): + return self.position_config.open_order_type + + @property + def take_profit_order_type(self): + return self.position_config.take_profit_order_type + + @property + def stop_loss_order_type(self): + return self.position_config.stop_loss_order_type + + @property + def time_limit_order_type(self): + return self.position_config.time_limit_order_type + + @property + def stop_loss_price(self): + stop_loss_price = self.entry_price * (1 - self._position_config.stop_loss) if self.side == TradeType.BUY else \ + self.entry_price * (1 + self._position_config.stop_loss) + return stop_loss_price + + @property + def take_profit_price(self): + take_profit_price = self.entry_price * (1 + self._position_config.take_profit) if self.side == TradeType.BUY else \ + self.entry_price * (1 - self._position_config.take_profit) + return take_profit_price + + @property + def open_order(self): + return self._open_order + + @property + def close_order(self): + return self._close_order + + @property + def take_profit_order(self): + return self._take_profit_order + + def take_profit_condition(self): + if self.side == TradeType.BUY: + return self.close_price >= self.take_profit_price + else: + return self.close_price <= self.take_profit_price + + def stop_loss_condition(self): + if self.side == TradeType.BUY: + return self.close_price <= self.stop_loss_price + else: + return self.close_price >= self.stop_loss_price + + def time_limit_condition(self): + return self._strategy.current_timestamp >= self.end_time + + def on_start(self): + self.check_budget() + + def on_stop(self): + if self.take_profit_order.order and self.take_profit_order.order.is_open: + self.logger().info(f"Take profit order status: {self.take_profit_order.order.current_state}") + self.remove_take_profit() + + async def control_task(self): + if self.executor_status == PositionExecutorStatus.NOT_STARTED: + self.control_open_order() + elif self.executor_status == PositionExecutorStatus.ACTIVE_POSITION: + self.control_barriers() + + def control_open_order(self): + if not self.open_order.order_id: + if not self.end_time or self.end_time >= self._strategy.current_timestamp: + self.place_open_order() + else: + self.executor_status = PositionExecutorStatus.COMPLETED + self.close_type = CloseType.EXPIRED + self.terminate_control_loop() + else: + self.control_open_order_expiration() + + def place_open_order(self): + order_id = self.place_order( + connector_name=self.exchange, + trading_pair=self.trading_pair, + order_type=self.open_order_type, + amount=self.amount, + price=self.entry_price, + side=self.side, + position_action=PositionAction.OPEN, + ) + self._open_order.order_id = order_id + self.logger().info("Placing open order") + + def control_open_order_expiration(self): + if self.end_time and self.end_time <= self._strategy.current_timestamp: + self._strategy.cancel( + connector_name=self.exchange, + trading_pair=self.trading_pair, + order_id=self._open_order.order_id + ) + self.logger().info("Removing open order by time limit") + + def control_barriers(self): + if not self.close_order.order_id: + if self.position_config.stop_loss: + self.control_stop_loss() + if self.position_config.take_profit: + self.control_take_profit() + if self.position_config.time_limit: + self.control_time_limit() + + def place_close_order(self, close_type: CloseType, price: Decimal = Decimal("NaN")): + tp_partial_execution = self.take_profit_order.executed_amount_base if self.take_profit_order.executed_amount_base else Decimal("0") + order_id = self.place_order( + connector_name=self.exchange, + trading_pair=self.trading_pair, + order_type=OrderType.MARKET, + amount=self.filled_amount - tp_partial_execution, + price=price, + side=TradeType.SELL if self.side == TradeType.BUY else TradeType.BUY, + position_action=PositionAction.CLOSE, + ) + self.close_type = close_type + self._close_order.order_id = order_id + self.logger().info(f"Placing close order --> Filled amount: {self.filled_amount} | TP Partial execution: {tp_partial_execution}") + + def control_stop_loss(self): + if self.stop_loss_condition(): + self.place_close_order(close_type=CloseType.STOP_LOSS) + elif self.trailing_stop_condition(): + self.place_close_order(close_type=CloseType.TRAILING_STOP) + + def control_take_profit(self): + if self.take_profit_order_type.is_limit_type(): + if not self.take_profit_order.order_id: + self.place_take_profit_limit_order() + elif not math.isclose(self.take_profit_order.order.amount, self.open_order.executed_amount_base): + self.renew_take_profit_order() + elif self.take_profit_condition(): + self.place_close_order(close_type=CloseType.TAKE_PROFIT) + + def control_time_limit(self): + if self.time_limit_condition(): + self.place_close_order(close_type=CloseType.TIME_LIMIT) + + def place_take_profit_limit_order(self): + order_id = self.place_order( + connector_name=self._position_config.exchange, + trading_pair=self._position_config.trading_pair, + amount=self.filled_amount, + price=self.take_profit_price, + order_type=self.take_profit_order_type, + position_action=PositionAction.CLOSE, + side=TradeType.BUY if self.side == TradeType.SELL else TradeType.SELL, + ) + self.take_profit_order.order_id = order_id + self.logger().info("Placing take profit order") + + def renew_take_profit_order(self): + self.remove_take_profit() + self.place_take_profit_limit_order() + self.logger().info("Renewing take profit order") + + def remove_take_profit(self): + self._strategy.cancel( + connector_name=self.exchange, + trading_pair=self.trading_pair, + order_id=self._take_profit_order.order_id + ) + self.logger().info("Removing take profit") + + def early_stop(self): + if self.executor_status == PositionExecutorStatus.ACTIVE_POSITION: + self.place_close_order(close_type=CloseType.EARLY_STOP) + elif self.executor_status == PositionExecutorStatus.NOT_STARTED and self._open_order.order_id: + self._strategy.cancel( + connector_name=self.exchange, + trading_pair=self.trading_pair, + order_id=self._open_order.order_id + ) + + def process_order_created_event(self, _, market, event: Union[BuyOrderCreatedEvent, SellOrderCreatedEvent]): + if self.open_order.order_id == event.order_id: + self.open_order.order = self.get_in_flight_order(self.exchange, event.order_id) + self.logger().info("Open Order Created") + elif self.close_order.order_id == event.order_id: + self.logger().info("Close Order Created") + self.close_order.order = self.get_in_flight_order(self.exchange, event.order_id) + elif self.take_profit_order.order_id == event.order_id: + self.take_profit_order.order = self.get_in_flight_order(self.exchange, event.order_id) + self.logger().info("Take profit Created") + + def process_order_completed_event(self, _, market, event: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent]): + if self.open_order.order_id == event.order_id: + self.logger().info("Open Order Completed") + self.executor_status = PositionExecutorStatus.ACTIVE_POSITION + elif self.close_order.order_id == event.order_id: + self.close_timestamp = event.timestamp + self.executor_status = PositionExecutorStatus.COMPLETED + self.logger().info(f"Closed by {self.close_type}") + self.terminate_control_loop() + elif self.take_profit_order.order_id == event.order_id: + self.close_type = CloseType.TAKE_PROFIT + self.executor_status = PositionExecutorStatus.COMPLETED + self.close_timestamp = event.timestamp + self.close_order.order_id = event.order_id + self.close_order.order = self.take_profit_order.order + self.logger().info(f"Closed by {self.close_type}") + self.terminate_control_loop() + + def process_order_canceled_event(self, _, market, event: OrderCancelledEvent): + if self.open_order.order_id == event.order_id: + self.executor_status = PositionExecutorStatus.COMPLETED + self.close_type = CloseType.EXPIRED + self.close_timestamp = event.timestamp + + def process_order_filled_event(self, _, market, event: OrderFilledEvent): + if self.open_order.order_id == event.order_id: + if self.executor_status == PositionExecutorStatus.ACTIVE_POSITION: + self.logger().info("Position incremented, updating take profit next tick.") + else: + self.executor_status = PositionExecutorStatus.ACTIVE_POSITION + + def process_order_failed_event(self, _, market, event: MarketOrderFailureEvent): + if self.open_order.order_id == event.order_id: + self.place_open_order() + elif self.close_order.order_id == event.order_id: + self.place_close_order(self.close_type) + elif self.take_profit_order.order_id == event.order_id: + self.take_profit_order.order_id = None + + def to_json(self): + return { + "timestamp": self.position_config.timestamp, + "exchange": self.exchange, + "trading_pair": self.trading_pair, + "side": self.side.name, + "amount": self.filled_amount, + "trade_pnl": self.trade_pnl, + "trade_pnl_quote": self.trade_pnl_quote, + "cum_fee_quote": self.cum_fee_quote, + "net_pnl_quote": self.net_pnl_quote, + "net_pnl": self.net_pnl, + "close_timestamp": self.close_timestamp, + "executor_status": self.executor_status.name, + "close_type": self.close_type.name if self.close_type else None, + "entry_price": self.entry_price, + "close_price": self.close_price, + "sl": self.position_config.stop_loss, + "tp": self.position_config.take_profit, + "tl": self.position_config.time_limit, + "open_order_type": self.open_order_type.name, + "take_profit_order_type": self.take_profit_order_type.name, + "stop_loss_order_type": self.stop_loss_order_type.name, + "time_limit_order_type": self.time_limit_order_type.name, + "leverage": self.position_config.leverage, + } + + def to_format_status(self, scale=1.0): + lines = [] + current_price = self.get_price(self.exchange, self.trading_pair) + amount_in_quote = self.entry_price * (self.filled_amount if self.filled_amount > Decimal("0") else self.amount) + quote_asset = self.trading_pair.split("-")[1] + if self.is_closed: + lines.extend([f""" +| Trading Pair: {self.trading_pair} | Exchange: {self.exchange} | Side: {self.side} +| Entry price: {self.entry_price:.6f} | Close price: {self.close_price:.6f} | Amount: {amount_in_quote:.4f} {quote_asset} +| Realized PNL: {self.trade_pnl_quote:.6f} {quote_asset} | Total Fee: {self.cum_fee_quote:.6f} {quote_asset} +| PNL (%): {self.net_pnl * 100:.2f}% | PNL (abs): {self.net_pnl_quote:.6f} {quote_asset} | Close Type: {self.close_type} +"""]) + else: + lines.extend([f""" +| Trading Pair: {self.trading_pair} | Exchange: {self.exchange} | Side: {self.side} | +| Entry price: {self.entry_price:.6f} | Close price: {self.close_price:.6f} | Amount: {amount_in_quote:.4f} {quote_asset} +| Unrealized PNL: {self.trade_pnl_quote:.6f} {quote_asset} | Total Fee: {self.cum_fee_quote:.6f} {quote_asset} +| PNL (%): {self.net_pnl * 100:.2f}% | PNL (abs): {self.net_pnl_quote:.6f} {quote_asset} | Close Type: {self.close_type} + """]) + + if self.executor_status == PositionExecutorStatus.ACTIVE_POSITION: + progress = 0 + if self.position_config.time_limit: + time_scale = int(scale * 60) + seconds_remaining = (self.end_time - self._strategy.current_timestamp) + time_progress = (self.position_config.time_limit - seconds_remaining) / self.position_config.time_limit + time_bar = "".join(['*' if i < time_scale * time_progress else '-' for i in range(time_scale)]) + lines.extend([f"Time limit: {time_bar}"]) + + if self.position_config.take_profit and self.position_config.stop_loss: + price_scale = int(scale * 60) + stop_loss_price = self.stop_loss_price + take_profit_price = self.take_profit_price + if self.side == TradeType.BUY: + price_range = take_profit_price - stop_loss_price + progress = (current_price - stop_loss_price) / price_range + elif self.side == TradeType.SELL: + price_range = stop_loss_price - take_profit_price + progress = (stop_loss_price - current_price) / price_range + price_bar = [f'--{current_price:.5f}--' if i == int(price_scale * progress) else '-' for i in range(price_scale)] + price_bar.insert(0, f"SL:{stop_loss_price:.5f}") + price_bar.append(f"TP:{take_profit_price:.5f}") + lines.extend(["".join(price_bar)]) + if self.trailing_stop_config: + lines.extend([f"Trailing stop status: {self._trailing_stop_activated} | Trailing stop price: {self._trailing_stop_price:.5f}"]) + lines.extend(["-----------------------------------------------------------------------------------------------------------"]) + return lines + + def trailing_stop_condition(self): + if self.trailing_stop_config: + price = self.close_price + if not self._trailing_stop_activated and self.activation_price_condition(price): + self._trailing_stop_activated = True + self._trailing_stop_price = price + self.logger().info(f"Trailing stop activated at {price}") + if self._trailing_stop_activated: + self.update_trailing_stop_price(price) + if self.side == TradeType.BUY: + return price < self._trailing_stop_price + else: + return price > self._trailing_stop_price + else: + return False + + def activation_price_condition(self, price): + side = 1 if self.side == TradeType.BUY else -1 + activation_price = self.entry_price * (1 + side * self.trailing_stop_config.activation_price_delta) + return price >= activation_price if self.side == TradeType.BUY \ + else price <= activation_price + + def update_trailing_stop_price(self, price): + if self.side == TradeType.BUY: + trailing_stop_price = price * (1 - self.trailing_stop_config.trailing_delta) + if trailing_stop_price > self._trailing_stop_price: + self._trailing_stop_price = trailing_stop_price + else: + trailing_stop_price = price * (1 + self.trailing_stop_config.trailing_delta) + if trailing_stop_price < self._trailing_stop_price: + self._trailing_stop_price = trailing_stop_price + + def check_budget(self): + if self.is_perpetual: + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=self.open_order_type.is_limit_type(), + order_type=self.open_order_type, + order_side=self.side, + amount=self.amount, + price=self.entry_price, + leverage=Decimal(self.position_config.leverage), + ) + else: + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=self.open_order_type.is_limit_type(), + order_type=self.open_order_type, + order_side=self.side, + amount=self.amount, + price=self.entry_price, + ) + adjusted_order_candidate = self.adjust_order_candidate(order_candidate) + if not adjusted_order_candidate: + self.terminate_control_loop() + self.close_type = CloseType.INSUFFICIENT_BALANCE + self.executor_status = PositionExecutorStatus.COMPLETED + error = "Not enough budget to open position." + self.logger().error(error) + + def adjust_order_candidate(self, order_candidate): + adjusted_order_candidate: OrderCandidate = self.connectors[self.exchange].budget_checker.adjust_candidate(order_candidate) + if adjusted_order_candidate.amount > Decimal("0"): + return adjusted_order_candidate diff --git a/hummingbot/smart_components/smart_component_base.py b/hummingbot/smart_components/smart_component_base.py new file mode 100644 index 0000000..0cb3781 --- /dev/null +++ b/hummingbot/smart_components/smart_component_base.py @@ -0,0 +1,159 @@ +import asyncio +from decimal import Decimal +from enum import Enum +from typing import List, Tuple, Union + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PriceType, TradeType +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class SmartComponentStatus(Enum): + NOT_STARTED = 1 + ACTIVE = 2 + TERMINATED = 3 + + +class SmartComponentBase: + def __init__(self, strategy: ScriptStrategyBase, connectors: List[str], update_interval: float = 0.5): + self._strategy: ScriptStrategyBase = strategy + self.update_interval = update_interval + self.connectors = {connector_name: connector for connector_name, connector in strategy.connectors.items() if + connector_name in connectors} + self._status: SmartComponentStatus = SmartComponentStatus.NOT_STARTED + self._states: list = [] + + self._create_buy_order_forwarder = SourceInfoEventForwarder(self.process_order_created_event) + self._create_sell_order_forwarder = SourceInfoEventForwarder(self.process_order_created_event) + self._fill_order_forwarder = SourceInfoEventForwarder(self.process_order_filled_event) + self._complete_buy_order_forwarder = SourceInfoEventForwarder(self.process_order_completed_event) + self._complete_sell_order_forwarder = SourceInfoEventForwarder(self.process_order_completed_event) + self._cancel_order_forwarder = SourceInfoEventForwarder(self.process_order_canceled_event) + self._failed_order_forwarder = SourceInfoEventForwarder(self.process_order_failed_event) + + self._event_pairs: List[Tuple[MarketEvent, SourceInfoEventForwarder]] = [ + (MarketEvent.OrderCancelled, self._cancel_order_forwarder), + (MarketEvent.BuyOrderCreated, self._create_buy_order_forwarder), + (MarketEvent.SellOrderCreated, self._create_sell_order_forwarder), + (MarketEvent.OrderFilled, self._fill_order_forwarder), + (MarketEvent.BuyOrderCompleted, self._complete_buy_order_forwarder), + (MarketEvent.SellOrderCompleted, self._complete_sell_order_forwarder), + (MarketEvent.OrderFailure, self._failed_order_forwarder), + ] + self.register_events() + self.terminated = asyncio.Event() + safe_ensure_future(self.control_loop()) + + @property + def status(self): + return self._status + + def get_in_flight_order(self, connector_name: str, order_id: str): + connector = self.connectors[connector_name] + order = connector._order_tracker.fetch_order(client_order_id=order_id) + return order + + async def control_loop(self): + self.on_start() + self._status = SmartComponentStatus.ACTIVE + while not self.terminated.is_set(): + await self.control_task() + await asyncio.sleep(self.update_interval) + self._status = SmartComponentStatus.TERMINATED + self.on_stop() + + def on_stop(self): + pass + + def on_start(self): + pass + + def terminate_control_loop(self): + self.terminated.set() + self.unregister_events() + + async def control_task(self): + pass + + def register_events(self): + """Start listening to events from the given market.""" + for connector in self.connectors.values(): + for event_pair in self._event_pairs: + connector.add_listener(event_pair[0], event_pair[1]) + + def unregister_events(self): + """Stop listening to events from the given market.""" + for connector in self.connectors.values(): + for event_pair in self._event_pairs: + connector.remove_listener(event_pair[0], event_pair[1]) + + def place_order(self, + connector_name: str, + trading_pair: str, + order_type: OrderType, + side: TradeType, + amount: Decimal, + position_action: PositionAction = PositionAction.NIL, + price=Decimal("NaN"), + ): + if side == TradeType.BUY: + return self._strategy.buy(connector_name, trading_pair, amount, order_type, price, position_action) + else: + return self._strategy.sell(connector_name, trading_pair, amount, order_type, price, position_action) + + def get_price(self, connector_name: str, trading_pair: str, price_type: PriceType = PriceType.MidPrice): + return self.connectors[connector_name].get_price_by_type(trading_pair, price_type) + + def get_order_book(self, connector_name: str, trading_pair: str): + return self.connectors[connector_name].get_order_book(connector_name, trading_pair) + + def get_balance(self, connector_name: str, asset: str): + return self.connectors[connector_name].get_balance(asset) + + def get_available_balance(self, connector_name: str, asset: str): + return self.connectors[connector_name].get_available_balance(asset) + + def get_active_orders(self, connector_name: str): + return self._strategy.get_active_orders(connector_name) + + def process_order_completed_event(self, + event_tag: int, + market: ConnectorBase, + event: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent]): + pass + + def process_order_created_event(self, + event_tag: int, + market: ConnectorBase, + event: Union[BuyOrderCreatedEvent, SellOrderCreatedEvent]): + pass + + def process_order_canceled_event(self, + event_tag: int, + market: ConnectorBase, + event: OrderCancelledEvent): + pass + + def process_order_filled_event(self, + event_tag: int, + market: ConnectorBase, + event: OrderFilledEvent): + pass + + def process_order_failed_event(self, + event_tag: int, + market: ConnectorBase, + event: MarketOrderFailureEvent): + pass diff --git a/hummingbot/smart_components/strategy_frameworks/__init__.py b/hummingbot/smart_components/strategy_frameworks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/strategy_frameworks/backtesting_engine_base.py b/hummingbot/smart_components/strategy_frameworks/backtesting_engine_base.py new file mode 100644 index 0000000..854b386 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/backtesting_engine_base.py @@ -0,0 +1,198 @@ +from datetime import datetime +from typing import Optional + +import numpy as np +import pandas as pd + +from hummingbot import data_path +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase + + +class BacktestingEngineBase: + def __init__(self, controller: ControllerBase): + """ + Initialize the BacktestExecutorBase. + + :param controller: The controller instance. + :param start_date: Start date for backtesting. + :param end_date: End date for backtesting. + """ + self.controller = controller + self.level_executors = {level.level_id: pd.Timestamp.min for level in self.controller.config.order_levels} + self.processed_data = None + self.executors_df = None + self.results = None + + @staticmethod + def filter_df_by_time(df, start: Optional[str] = None, end: Optional[str] = None): + if start is not None: + start_condition = pd.to_datetime(df["timestamp"], unit="ms") >= datetime.strptime(start, "%Y-%m-%d") + else: + start_condition = pd.Series([True] * len(df)) + if end is not None: + end_condition = pd.to_datetime(df["timestamp"], unit="ms") <= datetime.strptime(end, "%Y-%m-%d") + else: + end_condition = pd.Series([True] * len(df)) + return df[start_condition & end_condition] + + def apply_triple_barrier_method(self, df, tp=1.0, sl=1.0, tl=5, trade_cost=0.0006): + df.index = pd.to_datetime(df.timestamp, unit="ms") + if "target" not in df.columns: + df["target"] = 1 + df["tl"] = df.index + pd.Timedelta(seconds=tl) + df.dropna(subset="target", inplace=True) + + df = self.apply_tp_sl_on_tl(df, tp=tp, sl=sl) + + df = self.get_bins(df, trade_cost) + df["tp"] = df["target"] * tp + df["sl"] = df["target"] * sl + + df["take_profit_price"] = df["close"] * (1 + df["tp"] * df["signal"]) + df["stop_loss_price"] = df["close"] * (1 - df["sl"] * df["signal"]) + + return df + + @staticmethod + def get_bins(df, trade_cost): + # 1) prices aligned with events + px = df.index.union(df["tl"].values).drop_duplicates() + px = df.close.reindex(px, method="ffill") + + # 2) create out object + df["trade_pnl"] = (px.loc[df["close_time"].values].values / px.loc[df.index] - 1) * df["signal"] + df["net_pnl"] = df["trade_pnl"] - trade_cost + df["profitable"] = np.sign(df["trade_pnl"] - trade_cost) + df["close_price"] = px.loc[df["close_time"].values].values + return df + + @staticmethod + def apply_tp_sl_on_tl(df: pd.DataFrame, tp: float, sl: float): + events = df[df["signal"] != 0].copy() + if tp > 0: + take_profit = tp * events["target"] + else: + take_profit = pd.Series(index=df.index) # NaNs + if sl > 0: + stop_loss = - sl * events["target"] + else: + stop_loss = pd.Series(index=df.index) # NaNs + + for loc, tl in events["tl"].fillna(df.index[-1]).items(): + df0 = df.close[loc:tl] # path prices + df0 = (df0 / df.close[loc] - 1) * events.at[loc, "signal"] # path returns + df.loc[loc, "stop_loss_time"] = df0[df0 < stop_loss[loc]].index.min() # earliest stop loss. + df.loc[loc, "take_profit_time"] = df0[df0 > take_profit[loc]].index.min() # earliest profit taking. + df["close_time"] = df[["tl", "take_profit_time", "stop_loss_time"]].dropna(how="all").min(axis=1) + df["close_type"] = df[["take_profit_time", "stop_loss_time", "tl"]].dropna(how="all").idxmin(axis=1) + df["close_type"].replace({"take_profit_time": "tp", "stop_loss_time": "sl"}, inplace=True) + return df + + def load_controller_data(self, data_path: str = data_path()): + self.controller.load_historical_data(data_path=data_path) + + def get_data(self, start: Optional[str] = None, end: Optional[str] = None): + df = self.controller.get_processed_data() + return self.filter_df_by_time(df, start, end).copy() + + def run_backtesting(self, initial_portfolio_usd=1000, trade_cost=0.0006, + start: Optional[str] = None, end: Optional[str] = None): + # Load historical candles + processed_data = self.get_data(start=start, end=end) + + # Apply the specific execution logic of the executor handler vectorized + executors_df = self.simulate_execution(processed_data, initial_portfolio_usd=initial_portfolio_usd, trade_cost=trade_cost) + + # Store data for further analysis + self.processed_data = processed_data + self.executors_df = executors_df + self.results = self.summarize_results(executors_df) + return { + "processed_data": processed_data, + "executors_df": executors_df, + "results": self.results + } + + def simulate_execution(self, df: pd.DataFrame, initial_portfolio_usd: float, trade_cost: float): + raise NotImplementedError + + @staticmethod + def summarize_results(executors_df): + if len(executors_df) > 0: + net_pnl = executors_df["net_pnl"].sum() + net_pnl_quote = executors_df["net_pnl_quote"].sum() + total_executors = executors_df.shape[0] + executors_with_position = executors_df[executors_df["net_pnl"] != 0] + total_executors_with_position = executors_with_position.shape[0] + total_volume = executors_with_position["amount"].sum() * 2 + total_long = (executors_with_position["side"] == "BUY").sum() + total_short = (executors_with_position["side"] == "SELL").sum() + correct_long = ((executors_with_position["side"] == "BUY") & (executors_with_position["net_pnl"] > 0)).sum() + correct_short = ((executors_with_position["side"] == "SELL") & (executors_with_position["net_pnl"] > 0)).sum() + accuracy_long = correct_long / total_long if total_long > 0 else 0 + accuracy_short = correct_short / total_short if total_short > 0 else 0 + close_types = executors_df.groupby("close_type")["timestamp"].count() + + # Additional metrics + total_positions = executors_df.shape[0] + win_signals = executors_df.loc[(executors_df["profitable"] > 0) & (executors_df["signal"] != 0)] + loss_signals = executors_df.loc[(executors_df["profitable"] < 0) & (executors_df["signal"] != 0)] + accuracy = win_signals.shape[0] / total_positions + cumulative_returns = executors_df["net_pnl_quote"].cumsum() + peak = np.maximum.accumulate(cumulative_returns) + drawdown = (cumulative_returns - peak) + max_draw_down = np.min(drawdown) + max_drawdown_pct = max_draw_down / executors_df["inventory"].iloc[0] + returns = executors_df["net_pnl_quote"] / net_pnl + sharpe_ratio = returns.mean() / returns.std() + total_won = win_signals.loc[:, "net_pnl_quote"].sum() + total_loss = - loss_signals.loc[:, "net_pnl_quote"].sum() + profit_factor = total_won / total_loss if total_loss > 0 else 1 + duration_minutes = (executors_df.close_time.max() - executors_df.index.min()).total_seconds() / 60 + avg_trading_time_minutes = (pd.to_datetime(executors_df["close_time"]) - executors_df.index).dt.total_seconds() / 60 + avg_trading_time = avg_trading_time_minutes.mean() + + return { + "net_pnl": net_pnl, + "net_pnl_quote": net_pnl_quote, + "total_executors": total_executors, + "total_executors_with_position": total_executors_with_position, + "total_volume": total_volume, + "total_long": total_long, + "total_short": total_short, + "close_types": close_types, + "accuracy_long": accuracy_long, + "accuracy_short": accuracy_short, + "total_positions": total_positions, + "accuracy": accuracy, + "max_drawdown_usd": max_draw_down, + "max_drawdown_pct": max_drawdown_pct, + "sharpe_ratio": sharpe_ratio, + "profit_factor": profit_factor, + "duration_minutes": duration_minutes, + "avg_trading_time_minutes": avg_trading_time, + "win_signals": win_signals.shape[0], + "loss_signals": loss_signals.shape[0], + } + return { + "net_pnl": 0, + "net_pnl_quote": 0, + "total_executors": 0, + "total_executors_with_position": 0, + "total_volume": 0, + "total_long": 0, + "total_short": 0, + "close_types": 0, + "accuracy_long": 0, + "accuracy_short": 0, + "total_positions": 0, + "accuracy": 0, + "max_drawdown_usd": 0, + "max_drawdown_pct": 0, + "sharpe_ratio": 0, + "profit_factor": 0, + "duration_minutes": 0, + "avg_trading_time_minutes": 0, + "win_signals": 0, + "loss_signals": 0, + } diff --git a/hummingbot/smart_components/strategy_frameworks/controller_base.py b/hummingbot/smart_components/strategy_frameworks/controller_base.py new file mode 100644 index 0000000..675fc75 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/controller_base.py @@ -0,0 +1,135 @@ +from abc import ABC +from decimal import Decimal +from typing import List, Optional + +from pydantic import BaseModel + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel + + +class ControllerConfigBase(BaseModel): + exchange: str + trading_pair: str + strategy_name: str + candles_config: List[CandlesConfig] + order_levels: List[OrderLevel] + + +class ControllerBase(ABC): + """ + Abstract base class for controllers. + """ + + def __init__(self, + config: ControllerConfigBase, + excluded_parameters: Optional[List[str]] = None): + """ + Initialize the ControllerBase. + + :param config: Configuration for the controller. + :param mode: Mode of the controller (LIVE or other modes). + :param excluded_parameters: List of parameters to exclude from status formatting. + """ + self.config = config + self._excluded_parameters = excluded_parameters or ["order_levels", "candles_config"] + self.candles = self.initialize_candles(config.candles_config) + + def get_processed_data(self): + """ + Get the processed data. + """ + pass + + def filter_executors_df(self, df): + """ + In case that you are running the multiple controllers in the same script, you should implement this method + to recognize the executors that belongs to this controller. + """ + return df + + def initialize_candles(self, candles_config: List[CandlesConfig]): + return [CandlesFactory.get_candle(candles_config) for candles_config in candles_config] + + def get_close_price(self, trading_pair: str): + """ + Gets the close price of the last candlestick. + """ + candles = self.get_candles_by_trading_pair(trading_pair) + first_candle = list(candles.values())[0] + return Decimal(first_candle.candles_df["close"].iloc[-1]) + + def get_candles_by_trading_pair(self, trading_pair: str): + """ + Gets all the candlesticks with the given trading pair. + """ + candles = {} + for candle in self.candles: + if candle._trading_pair == trading_pair: + candles[candle.interval] = candle + return candles + + def get_candles_by_connector_trading_pair(self, connector: str, trading_pair: str): + """ + Gets all the candlesticks with the given connector and trading pair. + """ + candle_name = f"{connector}_{trading_pair}" + return self.get_candles_dict()[candle_name] + + def get_candle(self, connector: str, trading_pair: str, interval: str): + """ + Gets the candlestick with the given connector, trading pair and interval. + """ + return self.get_candles_by_connector_trading_pair(connector, trading_pair)[interval] + + def get_candles_dict(self) -> dict: + candles = {candle.name: {} for candle in self.candles} + for candle in self.candles: + candles[candle.name][candle.interval] = candle + return candles + + @property + def all_candles_ready(self): + """ + Checks if the candlesticks are full. + """ + return all([candle.is_ready for candle in self.candles]) + + def start(self) -> None: + """ + Start the controller. + """ + for candle in self.candles: + candle.start() + + def load_historical_data(self, data_path: str): + for candle in self.candles: + candle.load_candles_from_csv(data_path) + + def stop(self) -> None: + """ + Stop the controller. + """ + for candle in self.candles: + candle.stop() + + def get_csv_prefix(self) -> str: + """ + Get the CSV prefix based on the strategy name. + + :return: CSV prefix string. + """ + return f"{self.config.strategy_name}" + + def to_format_status(self) -> list: + """ + Format and return the status of the controller. + + :return: Formatted status string. + """ + lines = [] + lines.extend(["\n################################ Controller Config ################################"]) + for parameter, value in self.config.dict().items(): + if parameter not in self._excluded_parameters: + lines.extend([f" {parameter}: {value}"]) + return lines diff --git a/hummingbot/smart_components/strategy_frameworks/data_types.py b/hummingbot/smart_components/strategy_frameworks/data_types.py new file mode 100644 index 0000000..94248f0 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/data_types.py @@ -0,0 +1,50 @@ +from decimal import Decimal +from enum import Enum +from typing import Optional + +from pydantic import BaseModel, validator + +from hummingbot.core.data_type.common import OrderType, TradeType + + +class ExecutorHandlerStatus(Enum): + NOT_STARTED = 1 + ACTIVE = 2 + TERMINATED = 3 + + +class TripleBarrierConf(BaseModel): + # Configure the parameters for the position + stop_loss: Optional[Decimal] + take_profit: Optional[Decimal] + time_limit: Optional[int] + trailing_stop_activation_price_delta: Optional[Decimal] + trailing_stop_trailing_delta: Optional[Decimal] + # Configure the parameters for the order + open_order_type: OrderType = OrderType.LIMIT + take_profit_order_type: OrderType = OrderType.MARKET + stop_loss_order_type: OrderType = OrderType.MARKET + time_limit_order_type: OrderType = OrderType.MARKET + + @validator("stop_loss", "take_profit", "trailing_stop_activation_price_delta", "trailing_stop_trailing_delta", + pre=True) + def float_to_decimal(cls, v): + return Decimal(v) + + +class OrderLevel(BaseModel): + level: int + side: TradeType + order_amount_usd: Decimal + spread_factor: Decimal = Decimal("0.0") + order_refresh_time: int = 60 + cooldown_time: int = 0 + triple_barrier_conf: TripleBarrierConf + + @property + def level_id(self): + return f"{self.side.name}_{self.level}" + + @validator("order_amount_usd", "spread_factor", pre=True) + def float_to_decimal(cls, v): + return Decimal(v) diff --git a/hummingbot/smart_components/strategy_frameworks/directional_trading/__init__.py b/hummingbot/smart_components/strategy_frameworks/directional_trading/__init__.py new file mode 100644 index 0000000..07af70f --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/directional_trading/__init__.py @@ -0,0 +1,13 @@ +from .directional_trading_backtesting_engine import DirectionalTradingBacktestingEngine +from .directional_trading_controller_base import ( + DirectionalTradingControllerBase, + DirectionalTradingControllerConfigBase, +) +from .directional_trading_executor_handler import DirectionalTradingExecutorHandler + +__all__ = [ + "DirectionalTradingControllerConfigBase", + "DirectionalTradingControllerBase", + "DirectionalTradingBacktestingEngine", + "DirectionalTradingExecutorHandler" +] diff --git a/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_backtesting_engine.py b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_backtesting_engine.py new file mode 100644 index 0000000..facfd01 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_backtesting_engine.py @@ -0,0 +1,28 @@ +import pandas as pd + +from hummingbot.smart_components.strategy_frameworks.backtesting_engine_base import BacktestingEngineBase + + +class DirectionalTradingBacktestingEngine(BacktestingEngineBase): + def simulate_execution(self, df, initial_portfolio_usd, trade_cost): + executors = [] + df["side"] = df["signal"].apply(lambda x: "BUY" if x > 0 else "SELL" if x < 0 else 0) + for order_level in self.controller.config.order_levels: + df = self.apply_triple_barrier_method(df, + tp=float(order_level.triple_barrier_conf.take_profit), + sl=float(order_level.triple_barrier_conf.stop_loss), + tl=int(order_level.triple_barrier_conf.time_limit), + trade_cost=trade_cost) + for index, row in df[(df["side"] == order_level.side.name)].iterrows(): + last_close_time = self.level_executors[order_level.level_id] + if index >= last_close_time + pd.Timedelta(seconds=order_level.cooldown_time): + row["order_level"] = order_level.level_id + row["amount"] = float(order_level.order_amount_usd) + row["net_pnl_quote"] = row["net_pnl"] * row["amount"] + executors.append(row) + self.level_executors[order_level.level_id] = row["close_time"] + executors_df = pd.DataFrame(executors).sort_index() + executors_df["inventory"] = initial_portfolio_usd + if len(executors_df) > 0: + executors_df["inventory"] = initial_portfolio_usd + executors_df["net_pnl_quote"].cumsum().shift().fillna(0) + return executors_df diff --git a/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_controller_base.py b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_controller_base.py new file mode 100644 index 0000000..9b7f61b --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_controller_base.py @@ -0,0 +1,118 @@ +import time +from decimal import Decimal +from typing import List, Optional, Set + +import pandas as pd +from pydantic import Field + +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.core.data_type.common import PositionMode, TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase, ControllerConfigBase +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel + + +class DirectionalTradingControllerConfigBase(ControllerConfigBase): + exchange: str = Field(default="binance_perpetual") + trading_pair: str = Field(default="BTC-USDT") + leverage: int = Field(10, ge=1) + position_mode: PositionMode = Field(PositionMode.HEDGE) + + +class DirectionalTradingControllerBase(ControllerBase): + + def __init__(self, + config: DirectionalTradingControllerConfigBase, + excluded_parameters: Optional[List[str]] = None): + super().__init__(config, excluded_parameters) + self.config = config # this is only for type hints + + def filter_executors_df(self, df): + return df[df["trading_pair"] == self.config.trading_pair] + + def update_strategy_markets_dict(self, markets_dict: dict[str, Set] = {}): + if self.config.exchange not in markets_dict: + markets_dict[self.config.exchange] = {self.config.trading_pair} + else: + markets_dict[self.config.exchange].add(self.config.trading_pair) + return markets_dict + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + # TODO: Refactor this as a method of the base class that receives the exchange name as a parameter + return "perpetual" in self.config.exchange + + def get_signal(self): + df = self.get_processed_data() + return df["signal"].iloc[-1] + + def get_spread_multiplier(self): + df = self.get_processed_data() + if "target" in df.columns: + return Decimal(df["target"].iloc[-1]) + else: + return Decimal("1.0") + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + raise NotImplementedError + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + raise NotImplementedError + + def get_position_config(self, order_level: OrderLevel, signal: int) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + if (signal == 1 and order_level.side == TradeType.BUY) or (signal == -1 and order_level.side == TradeType.SELL): + # Here you can use the weight of the signal to tweak for example the order amount + close_price = self.get_close_price(self.config.trading_pair) + amount = order_level.order_amount_usd / close_price + spread_multiplier = self.get_spread_multiplier() + order_price = close_price * (1 + order_level.spread_factor * spread_multiplier * signal) + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config + + def get_processed_data(self) -> pd.DataFrame: + """ + Retrieves the processed dataframe with indicators, signal, weight and spreads multipliers. + Returns: + pd.DataFrame: The processed dataframe with indicators, signal, weight and spreads multipliers. + """ + raise NotImplementedError + + def to_format_status(self) -> list: + lines = super().to_format_status() + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "signal"] + self.extra_columns_to_show() + df = self.get_processed_data() + prices_str = format_df_for_printout(df[columns_to_show].tail(4), table_format="psql") + lines.extend([f"{prices_str}"]) + return lines + + def extra_columns_to_show(self): + return [] diff --git a/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_executor_handler.py b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_executor_handler.py new file mode 100644 index 0000000..bf5d208 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/directional_trading/directional_trading_executor_handler.py @@ -0,0 +1,45 @@ +from hummingbot.smart_components.executors.position_executor.data_types import PositionExecutorStatus +from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_controller_base import ( + DirectionalTradingControllerBase, +) +from hummingbot.smart_components.strategy_frameworks.executor_handler_base import ExecutorHandlerBase +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DirectionalTradingExecutorHandler(ExecutorHandlerBase): + def __init__(self, strategy: ScriptStrategyBase, controller: DirectionalTradingControllerBase, + update_interval: float = 1.0): + super().__init__(strategy, controller, update_interval) + self.controller = controller + + def on_stop(self): + if self.controller.is_perpetual: + self.close_open_positions(connector_name=self.controller.config.exchange, trading_pair=self.controller.config.trading_pair) + super().on_stop() + + def on_start(self): + if self.controller.is_perpetual: + self.set_leverage_and_position_mode() + + def set_leverage_and_position_mode(self): + connector = self.strategy.connectors[self.controller.config.exchange] + connector.set_position_mode(self.controller.config.position_mode) + connector.set_leverage(trading_pair=self.controller.config.trading_pair, leverage=self.controller.config.leverage) + + async def control_task(self): + if self.controller.all_candles_ready: + signal = self.controller.get_signal() + if signal != 0: + for order_level in self.controller.config.order_levels: + current_executor = self.level_executors[order_level.level_id] + if current_executor: + closed_and_not_in_cooldown = current_executor.is_closed and not self.controller.cooldown_condition(current_executor, order_level) + active_and_early_stop_condition = current_executor.executor_status == PositionExecutorStatus.ACTIVE_POSITION and self.controller.early_stop_condition(current_executor, order_level) + if closed_and_not_in_cooldown: + self.store_executor(current_executor, order_level) + elif active_and_early_stop_condition: + current_executor.early_stop() + else: + position_config = self.controller.get_position_config(order_level, signal) + if position_config: + self.create_executor(position_config, order_level) diff --git a/hummingbot/smart_components/strategy_frameworks/executor_handler_base.py b/hummingbot/smart_components/strategy_frameworks/executor_handler_base.py new file mode 100644 index 0000000..b98738b --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/executor_handler_base.py @@ -0,0 +1,264 @@ +import asyncio +import datetime +import logging +from pathlib import Path + +import pandas as pd + +from hummingbot import data_path +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger +from hummingbot.model.position_executors import PositionExecutors +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase +from hummingbot.smart_components.strategy_frameworks.data_types import ExecutorHandlerStatus, OrderLevel +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class ExecutorHandlerBase: + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, strategy: ScriptStrategyBase, controller: ControllerBase, update_interval: float = 1.0, + executors_update_interval: float = 1.0): + """ + Initialize the ExecutorHandlerBase. + + :param strategy: The strategy instance. + :param controller: The controller instance. + :param update_interval: Update interval in seconds. + """ + self.strategy = strategy + self.controller = controller + self.update_interval = update_interval + self.executors_update_interval = executors_update_interval + self.terminated = asyncio.Event() + self.level_executors = {level.level_id: None for level in self.controller.config.order_levels} + self.status = ExecutorHandlerStatus.NOT_STARTED + + def start(self): + """Start the executor handler.""" + self.controller.start() + safe_ensure_future(self.control_loop()) + + def stop(self): + """Stop the executor handler.""" + self.terminated.set() + + def on_stop(self): + """Actions to perform on stop.""" + self.controller.stop() + + def on_start(self): + """Actions to perform on start.""" + pass + + async def control_task(self): + """Control task to be implemented by subclasses.""" + raise NotImplementedError + + def get_csv_path(self) -> Path: + """ + Get the CSV path for storing executor data. + + :return: Path object for the CSV. + """ + today = datetime.datetime.today() + return Path(data_path()) / f"{self.controller.get_csv_prefix()}_{today.day:02d}-{today.month:02d}-{today.year}.csv" + + def store_executor(self, executor: PositionExecutor, order_level: OrderLevel): + """ + Store executor data to CSV. + + :param executor: The executor instance. + :param order_level: The order level instance. + """ + if executor: + executor_data = executor.to_json() + executor_data["order_level"] = order_level.level_id + executor_data["controller_name"] = self.controller.config.strategy_name + MarketsRecorder.get_instance().store_executor(executor_data) + self.level_executors[order_level.level_id] = None + + def create_executor(self, position_config: PositionConfig, order_level: OrderLevel): + """ + Create an executor. + + :param position_config: The position configuration. + :param order_level: The order level instance. + """ + executor = PositionExecutor(self.strategy, position_config, update_interval=self.executors_update_interval) + self.level_executors[order_level.level_id] = executor + + async def control_loop(self): + """Main control loop.""" + self.on_start() + self.status = ExecutorHandlerStatus.ACTIVE + while not self.terminated.is_set(): + try: + await self.control_task() + except Exception as e: + self.logger().error(e, exc_info=True) + await self._sleep(self.update_interval) + self.status = ExecutorHandlerStatus.TERMINATED + self.on_stop() + + def close_open_positions(self, connector_name: str = None, trading_pair: str = None): + """ + Close all open positions. + + :param connector_name: The connector name. + :param trading_pair: The trading pair. + """ + connector = self.strategy.connectors[connector_name] + for pos_key, position in connector.account_positions.items(): + if position.trading_pair == trading_pair: + action = self.strategy.sell if position.position_side == PositionSide.LONG else self.strategy.buy + action(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def get_closed_executors_df(self): + executors = MarketsRecorder.get_instance().get_position_executors( + self.controller.config.strategy_name, + self.controller.config.exchange, + self.controller.config.trading_pair) + executors_df = PositionExecutors.to_pandas(executors) + return executors_df + + def get_active_executors_df(self) -> pd.DataFrame: + """ + Get active executors as a DataFrame. + + :return: DataFrame containing active executors. + """ + executors_info = [] + for level, executor in self.level_executors.items(): + if executor: + executor_info = executor.to_json() + executor_info["level_id"] = level + executors_info.append(executor_info) + if len(executors_info) > 0: + executors_df = pd.DataFrame(executors_info) + executors_df.sort_values(by="entry_price", ascending=False, inplace=True) + executors_df["spread_to_next_level"] = -1 * executors_df["entry_price"].pct_change(periods=1) + return executors_df + else: + return pd.DataFrame() + + @staticmethod + def get_executors_df(csv_prefix: str) -> pd.DataFrame: + """ + Get executors from CSV. + + :param csv_prefix: The CSV prefix. + :return: DataFrame containing executors. + """ + dfs = [pd.read_csv(file) for file in Path(data_path()).glob(f"{csv_prefix}*")] + return pd.concat(dfs) if dfs else pd.DataFrame() + + @staticmethod + def summarize_executors_df(executors_df): + if len(executors_df) > 0: + net_pnl = executors_df["net_pnl"].sum() + net_pnl_quote = executors_df["net_pnl_quote"].sum() + total_executors = executors_df.shape[0] + executors_with_position = executors_df[executors_df["net_pnl"] != 0] + total_executors_with_position = executors_with_position.shape[0] + total_volume = executors_with_position["amount"].sum() * 2 + total_long = (executors_with_position["side"] == "BUY").sum() + total_short = (executors_with_position["side"] == "SELL").sum() + correct_long = ((executors_with_position["side"] == "BUY") & (executors_with_position["net_pnl"] > 0)).sum() + correct_short = ((executors_with_position["side"] == "SELL") & (executors_with_position["net_pnl"] > 0)).sum() + accuracy_long = correct_long / total_long if total_long > 0 else 0 + accuracy_short = correct_short / total_short if total_short > 0 else 0 + + close_types = executors_df.groupby("close_type")["timestamp"].count() + return { + "net_pnl": net_pnl, + "net_pnl_quote": net_pnl_quote, + "total_executors": total_executors, + "total_executors_with_position": total_executors_with_position, + "total_volume": total_volume, + "total_long": total_long, + "total_short": total_short, + "close_types": close_types, + "accuracy_long": accuracy_long, + "accuracy_short": accuracy_short, + } + return { + "net_pnl": 0, + "net_pnl_quote": 0, + "total_executors": 0, + "total_executors_with_position": 0, + "total_volume": 0, + "total_long": 0, + "total_short": 0, + "close_types": 0, + "accuracy_long": 0, + "accuracy_short": 0, + } + + def closed_executors_info(self): + closed_executors = self.get_closed_executors_df() + return self.summarize_executors_df(closed_executors) + + def active_executors_info(self): + active_executors = self.get_active_executors_df() + return self.summarize_executors_df(active_executors) + + def to_format_status(self) -> str: + """ + Base status for executor handler. + """ + lines = [] + lines.extend(self.controller.to_format_status()) + lines.extend(["\n################################ Active Executors ################################"]) + executors_df = self.get_active_executors_df() + if len(executors_df) > 0: + executors_df["amount_quote"] = executors_df["amount"] * executors_df["entry_price"] + columns_to_show = ["level_id", "side", "entry_price", "close_price", "spread_to_next_level", "net_pnl", + "net_pnl_quote", "amount", "amount_quote", "timestamp", "close_type", "executor_status"] + executors_df_str = format_df_for_printout(executors_df[columns_to_show].round(decimals=3), + table_format="psql") + lines.extend([executors_df_str]) + lines.extend(["\n################################## Performance ##################################"]) + closed_executors_info = self.closed_executors_info() + active_executors_info = self.active_executors_info() + unrealized_pnl = float(active_executors_info["net_pnl"]) + realized_pnl = closed_executors_info["net_pnl"] + total_pnl = unrealized_pnl + realized_pnl + total_volume = closed_executors_info["total_volume"] + float(active_executors_info["total_volume"]) + total_long = closed_executors_info["total_long"] + float(active_executors_info["total_long"]) + total_short = closed_executors_info["total_short"] + float(active_executors_info["total_short"]) + accuracy_long = closed_executors_info["accuracy_long"] + accuracy_short = closed_executors_info["accuracy_short"] + total_accuracy = (accuracy_long * total_long + accuracy_short * total_short) \ + / (total_long + total_short) if (total_long + total_short) > 0 else 0 + lines.extend([f""" +| Unrealized PNL: {unrealized_pnl * 100:.2f} % | Realized PNL: {realized_pnl * 100:.2f} % | Total PNL: {total_pnl * 100:.2f} % | Total Volume: {total_volume} +| Total positions: {total_short + total_long} --> Accuracy: {total_accuracy:.2%} + | Long: {total_long} --> Accuracy: {accuracy_long:.2%} | Short: {total_short} --> Accuracy: {accuracy_short:.2%} + +Closed executors: {closed_executors_info["total_executors"]} + {closed_executors_info["close_types"]} + """]) + return "\n".join(lines) + + async def _sleep(self, delay: float): + """ + Method created to enable tests to prevent processes from sleeping + """ + await asyncio.sleep(delay) diff --git a/hummingbot/smart_components/strategy_frameworks/market_making/__init__.py b/hummingbot/smart_components/strategy_frameworks/market_making/__init__.py new file mode 100644 index 0000000..b43dd9a --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/market_making/__init__.py @@ -0,0 +1,8 @@ +from .market_making_controller_base import MarketMakingControllerBase, MarketMakingControllerConfigBase +from .market_making_executor_handler import MarketMakingExecutorHandler + +__all__ = [ + "MarketMakingControllerConfigBase", + "MarketMakingControllerBase", + "MarketMakingExecutorHandler" +] diff --git a/hummingbot/smart_components/strategy_frameworks/market_making/market_making_controller_base.py b/hummingbot/smart_components/strategy_frameworks/market_making/market_making_controller_base.py new file mode 100644 index 0000000..e1dfe6d --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/market_making/market_making_controller_base.py @@ -0,0 +1,67 @@ +from decimal import Decimal +from typing import List, Optional, Set + +from hummingbot.core.data_type.common import PositionMode +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase, ControllerConfigBase +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel + + +class MarketMakingControllerConfigBase(ControllerConfigBase): + exchange: str + trading_pair: str + leverage: int = 10 + position_mode: PositionMode = PositionMode.HEDGE + + +class MarketMakingControllerBase(ControllerBase): + + def __init__(self, + config: MarketMakingControllerConfigBase, + excluded_parameters: Optional[List[str]] = None): + super().__init__(config, excluded_parameters) + self.config = config # this is only for type hints + + def filter_executors_df(self, df): + return df[df["trading_pair"] == self.config.trading_pair] + + def get_price_and_spread_multiplier(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.get_processed_data() + return Decimal(candles_df["price_multiplier"].iloc[-1]), Decimal(candles_df["spread_multiplier"].iloc[-1]) + + def update_strategy_markets_dict(self, markets_dict: dict[str, Set] = {}): + if self.config.exchange not in markets_dict: + markets_dict[self.config.exchange] = {self.config.trading_pair} + else: + markets_dict[self.config.exchange].add(self.config.trading_pair) + return markets_dict + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.config.exchange + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + raise NotImplementedError + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + raise NotImplementedError + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + raise NotImplementedError + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + raise NotImplementedError + + def get_processed_data(self): + raise NotImplementedError diff --git a/hummingbot/smart_components/strategy_frameworks/market_making/market_making_executor_handler.py b/hummingbot/smart_components/strategy_frameworks/market_making/market_making_executor_handler.py new file mode 100644 index 0000000..6038c25 --- /dev/null +++ b/hummingbot/smart_components/strategy_frameworks/market_making/market_making_executor_handler.py @@ -0,0 +1,63 @@ +import logging +from decimal import Decimal + +from hummingbot.logger import HummingbotLogger +from hummingbot.smart_components.executors.position_executor.data_types import PositionExecutorStatus +from hummingbot.smart_components.strategy_frameworks.executor_handler_base import ExecutorHandlerBase +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingExecutorHandler(ExecutorHandlerBase): + _logger = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def __init__(self, strategy: ScriptStrategyBase, controller: MarketMakingControllerBase, + update_interval: float = 1.0, executors_update_interval: float = 1.0): + super().__init__(strategy, controller, update_interval, executors_update_interval) + self.controller = controller + + def on_stop(self): + if self.controller.is_perpetual: + self.close_open_positions(connector_name=self.controller.config.exchange, trading_pair=self.controller.config.trading_pair) + super().on_stop() + + def on_start(self): + if self.controller.is_perpetual: + self.set_leverage_and_position_mode() + + def set_leverage_and_position_mode(self): + connector = self.strategy.connectors[self.controller.config.exchange] + connector.set_position_mode(self.controller.config.position_mode) + connector.set_leverage(trading_pair=self.controller.config.trading_pair, leverage=self.controller.config.leverage) + + @staticmethod + def empty_metrics_dict(): + return {"amount": Decimal("0"), "net_pnl_quote": Decimal("0"), "executors": []} + + async def control_task(self): + if self.controller.all_candles_ready: + for order_level in self.controller.config.order_levels: + current_executor = self.level_executors[order_level.level_id] + if current_executor: + closed_and_not_in_cooldown = current_executor.is_closed and not self.controller.cooldown_condition( + current_executor, order_level) + active_and_early_stop_condition = current_executor.executor_status == PositionExecutorStatus.ACTIVE_POSITION and self.controller.early_stop_condition( + current_executor, order_level) + order_placed_and_refresh_condition = current_executor.executor_status == PositionExecutorStatus.NOT_STARTED and self.controller.refresh_order_condition( + current_executor, order_level) + if closed_and_not_in_cooldown: + self.store_executor(current_executor, order_level) + elif active_and_early_stop_condition or order_placed_and_refresh_condition: + current_executor.early_stop() + else: + position_config = self.controller.get_position_config(order_level) + if position_config: + self.create_executor(position_config, order_level) diff --git a/hummingbot/smart_components/utils/__init__.py b/hummingbot/smart_components/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/smart_components/utils/config_encoder_decoder.py b/hummingbot/smart_components/utils/config_encoder_decoder.py new file mode 100644 index 0000000..4576e48 --- /dev/null +++ b/hummingbot/smart_components/utils/config_encoder_decoder.py @@ -0,0 +1,52 @@ +import json +from decimal import Decimal +from enum import Enum + +import yaml + + +class ConfigEncoderDecoder: + + def __init__(self, *enum_classes): + self.enum_classes = {enum_class.__name__: enum_class for enum_class in enum_classes} + + def recursive_encode(self, value): + if isinstance(value, dict): + return {key: self.recursive_encode(val) for key, val in value.items()} + elif isinstance(value, list): + return [self.recursive_encode(val) for val in value] + elif isinstance(value, Enum): + return {"__enum__": True, "class": type(value).__name__, "value": value.name} + elif isinstance(value, Decimal): + return {"__decimal__": True, "value": str(value)} + else: + return value + + def recursive_decode(self, value): + if isinstance(value, dict): + if value.get("__enum__"): + enum_class = self.enum_classes.get(value['class']) + if enum_class: + return enum_class[value["value"]] + elif value.get("__decimal__"): + return Decimal(value["value"]) + else: + return {key: self.recursive_decode(val) for key, val in value.items()} + elif isinstance(value, list): + return [self.recursive_decode(val) for val in value] + else: + return value + + def encode(self, d): + return json.dumps(self.recursive_encode(d)) + + def decode(self, s): + return self.recursive_decode(json.loads(s)) + + def yaml_dump(self, d, file_path): + with open(file_path, 'w') as file: + yaml.dump(self.recursive_encode(d), file) + + def yaml_load(self, file_path): + with open(file_path, 'r') as file: + return self.recursive_decode(yaml.safe_load(file)) diff --git a/hummingbot/smart_components/utils/distributions.py b/hummingbot/smart_components/utils/distributions.py new file mode 100644 index 0000000..86704a7 --- /dev/null +++ b/hummingbot/smart_components/utils/distributions.py @@ -0,0 +1,107 @@ +from decimal import Decimal +from math import exp, log +from typing import List + + +class Distributions: + """ + A utility class containing methods to generate various types of numeric distributions. + """ + + @classmethod + def linear(cls, n_levels: int, start: float = 0.0, end: float = 1.0) -> List[Decimal]: + """ + Generate a linear sequence of spreads. + + Parameters: + - n_levels: The number of spread levels to be generated. + - start: The starting value of the sequence. + - end: The ending value of the sequence. + + Returns: + List[Decimal]: A list containing the generated linear sequence. + """ + return [Decimal(start) + (Decimal(end) - Decimal(start)) * Decimal(i) / (Decimal(n_levels) - 1) for i in range(n_levels)] + + @classmethod + def fibonacci(cls, n_levels: int, start: float = 0.01) -> List[Decimal]: + """ + Generate a Fibonacci sequence of spreads represented as percentages. + + The Fibonacci sequence is a series of numbers in which each number (Fibonacci number) + is the sum of the two preceding ones. In this implementation, the sequence starts with + the provided initial_value (represented as a percentage) and the value derived by adding + the initial_value to itself as the first two terms. Each subsequent term is derived by + adding the last two terms of the sequence. + + Parameters: + - n_levels (int): The number of spread levels to be generated. + - initial_value (float, default=0.01): The value from which the Fibonacci sequence will start, + represented as a percentage. Default is 1%. + + Returns: + List[Decimal]: A list containing the generated Fibonacci sequence of spreads, represented as percentages. + + Example: + If initial_value=0.01 and n_levels=5, the sequence would represent: [1%, 2%, 3%, 5%, 8%] + """ + + if n_levels == 1: + return [Decimal(start)] + + fib_sequence = [Decimal(start), Decimal(start) * 2] + for i in range(2, n_levels): + fib_sequence.append(fib_sequence[-1] + fib_sequence[-2]) + return fib_sequence[:n_levels] + + @classmethod + def logarithmic(cls, n_levels: int, base: float = exp(1), scaling_factor: float = 1.0, + start: float = 0.4) -> List[Decimal]: + """ + Generate a logarithmic sequence of spreads. + + Parameters: + - n_levels: The number of spread levels to be generated. + - base: The base value for the logarithm. Default is Euler's number. + - scaling_factor: The factor to scale the logarithmic value. + - initial_value: Initial value for translation. + + Returns: + List[Decimal]: A list containing the generated logarithmic sequence. + """ + translation = Decimal(start) - Decimal(scaling_factor) * Decimal(log(2, base)) + return [Decimal(scaling_factor) * Decimal(log(i + 2, base)) + translation for i in range(n_levels)] + + @classmethod + def arithmetic(cls, n_levels: int, start: float, step: float) -> List[Decimal]: + """ + Generate an arithmetic sequence of spreads. + + Parameters: + - n_levels: The number of spread levels to be generated. + - start: The starting value of the sequence. + - increment: The constant value to be added in each iteration. + + Returns: + List[Decimal]: A list containing the generated arithmetic sequence. + """ + return [Decimal(start) + i * Decimal(step) for i in range(n_levels)] + + @classmethod + def geometric(cls, n_levels: int, start: float, ratio: float) -> List[Decimal]: + """ + Generate a geometric sequence of spreads. + + Parameters: + - n_levels: The number of spread levels to be generated. + - start: The starting value of the sequence. + - ratio: The ratio to multiply the current value in each iteration. Should be greater than 1 for increasing sequence. + + Returns: + List[Decimal]: A list containing the generated geometric sequence. + """ + if ratio <= 1: + raise ValueError( + "Ratio for modified geometric distribution should be greater than 1 for increasing spreads.") + + return [Decimal(start) * Decimal(ratio) ** Decimal(i) for i in range(n_levels)] diff --git a/hummingbot/smart_components/utils/order_level_builder.py b/hummingbot/smart_components/utils/order_level_builder.py new file mode 100644 index 0000000..d73d2f8 --- /dev/null +++ b/hummingbot/smart_components/utils/order_level_builder.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from decimal import Decimal +from typing import Any, Dict, List, Optional, Union + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf +from hummingbot.smart_components.utils.distributions import Distributions + + +class OrderLevelBuilder: + def __init__(self, n_levels: int): + """ + Initialize the OrderLevelBuilder with the number of levels. + + Args: + n_levels (int): The number of order levels. + """ + self.n_levels = n_levels + + def resolve_input(self, input_data: Union[Decimal | float, List[Decimal | float], Dict[str, Any]]) -> List[Decimal | float | int]: + """ + Resolve the provided input data into a list of Decimal values. + + Args: + input_data: The input data to resolve. Can be a single value, list, or dictionary. + + Returns: + List[Decimal | float | int]: List of resolved Decimal values. + """ + if isinstance(input_data, Decimal) or isinstance(input_data, float) or isinstance(input_data, int): + return [input_data] * self.n_levels + elif isinstance(input_data, list): + if len(input_data) != self.n_levels: + raise ValueError(f"List length must match the number of levels: {self.n_levels}") + return input_data + elif isinstance(input_data, dict): + distribution_method = input_data["method"] + distribution_func = getattr(Distributions, distribution_method, None) + if not distribution_func: + raise ValueError(f"Unsupported distribution method: {distribution_method}") + return distribution_func(self.n_levels, **input_data["params"]) + else: + raise ValueError(f"Unsupported input data type: {type(input_data)}") + + def build_order_levels(self, + amounts: Union[Decimal, List[Decimal], Dict[str, Any]], + spreads: Union[Decimal, List[Decimal], Dict[str, Any]], + triple_barrier_confs: Union[TripleBarrierConf, List[TripleBarrierConf]], + order_refresh_time: Union[int, List[int], Dict[str, Any]] = 60 * 5, + cooldown_time: Union[int, List[int], Dict[str, Any]] = 0, + sides: Optional[List[TradeType]] = None) -> List[OrderLevel]: + """ + Build a list of OrderLevels based on the given parameters. + + Args: + amounts: Amounts to be used for each order level. + spreads: Spread factors for each order level. + triple_barrier_confs: Triple barrier configurations. + order_refresh_time: Time in seconds to wait before refreshing orders. + cooldown_time: Time in seconds to wait after an order fills before placing a new one. + sides: Trading sides, either BUY or SELL. Default is both. + + Returns: + List[OrderLevel]: List of constructed OrderLevel objects. + """ + if sides is None: + sides = [TradeType.BUY, TradeType.SELL] + + resolved_amounts = self.resolve_input(amounts) + resolved_spreads = self.resolve_input(spreads) + resolved_order_refresh_time = self.resolve_input(order_refresh_time) + resolved_cooldown_time = self.resolve_input(cooldown_time) + + if not isinstance(triple_barrier_confs, list): + triple_barrier_confs = [triple_barrier_confs] * self.n_levels + + order_levels = [] + for i in range(self.n_levels): + for side in sides: + order_level = OrderLevel( + level=i + 1, + side=side, + order_amount_usd=resolved_amounts[i], + spread_factor=resolved_spreads[i], + triple_barrier_conf=triple_barrier_confs[i], + order_refresh_time=resolved_order_refresh_time[i], + cooldown_time=resolved_cooldown_time[i] + ) + order_levels.append(order_level) + + return order_levels diff --git a/hummingbot/strategy/__init__.py b/hummingbot/strategy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/__utils__/__init__.py b/hummingbot/strategy/__utils__/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/__utils__/ring_buffer.pxd b/hummingbot/strategy/__utils__/ring_buffer.pxd new file mode 100644 index 0000000..9b51c4e --- /dev/null +++ b/hummingbot/strategy/__utils__/ring_buffer.pxd @@ -0,0 +1,20 @@ +import numpy as np +from libc.stdint cimport int64_t +cimport numpy as np + +cdef class RingBuffer: + cdef: + np.float64_t[:] _buffer + int64_t _delimiter + int64_t _length + bint _is_full + + cdef void c_add_value(self, float val) + cdef void c_increment_delimiter(self) + cdef double c_get_last_value(self) + cdef bint c_is_full(self) + cdef bint c_is_empty(self) + cdef double c_mean_value(self) + cdef double c_variance(self) + cdef double c_std_dev(self) + cdef np.ndarray[np.double_t, ndim=1] c_get_as_numpy_array(self) diff --git a/hummingbot/strategy/__utils__/ring_buffer.pyx b/hummingbot/strategy/__utils__/ring_buffer.pyx new file mode 100644 index 0000000..4f480d4 --- /dev/null +++ b/hummingbot/strategy/__utils__/ring_buffer.pyx @@ -0,0 +1,118 @@ +import numpy as np +import logging +cimport numpy as np + + +pmm_logger = None + +cdef class RingBuffer: + @classmethod + def logger(cls): + global pmm_logger + if pmm_logger is None: + pmm_logger = logging.getLogger(__name__) + return pmm_logger + + def __cinit__(self, int length): + self._length = length + self._buffer = np.zeros(length, dtype=np.float64) + self._delimiter = 0 + self._is_full = False + + def __dealloc__(self): + self._buffer = None + + cdef void c_add_value(self, float val): + self._buffer[self._delimiter] = val + self.c_increment_delimiter() + + cdef void c_increment_delimiter(self): + self._delimiter = (self._delimiter + 1) % self._length + if not self._is_full and self._delimiter == 0: + self._is_full = True + + cdef bint c_is_empty(self): + return (not self._is_full) and (0==self._delimiter) + + cdef double c_get_last_value(self): + if self.c_is_empty(): + return np.nan + return self._buffer[self._delimiter-1] + + cdef bint c_is_full(self): + return self._is_full + + cdef double c_mean_value(self): + result = np.nan + if self._is_full: + result=np.mean(self.c_get_as_numpy_array()) + return result + + cdef double c_variance(self): + result = np.nan + if self._is_full: + result = np.var(self.c_get_as_numpy_array()) + return result + + cdef double c_std_dev(self): + result = np.nan + if self._is_full: + result = np.std(self.c_get_as_numpy_array()) + return result + + cdef np.ndarray[np.double_t, ndim=1] c_get_as_numpy_array(self): + cdef np.ndarray[np.int16_t, ndim=1] indexes + + if not self._is_full: + indexes = np.arange(0, stop=self._delimiter, dtype=np.int16) + else: + indexes = np.arange(self._delimiter, stop=self._delimiter + self._length, + dtype=np.int16) % self._length + return np.asarray(self._buffer)[indexes] + + def __init__(self, length): + self._length = length + self._buffer = np.zeros(length, dtype=np.double) + self._delimiter = 0 + self._is_full = False + + def add_value(self, val): + self.c_add_value(val) + + def get_as_numpy_array(self): + return self.c_get_as_numpy_array() + + def get_last_value(self): + return self.c_get_last_value() + + @property + def is_full(self): + return self.c_is_full() + + @property + def mean_value(self): + return self.c_mean_value() + + @property + def std_dev(self): + return self.c_std_dev() + + @property + def variance(self): + return self.c_variance() + + @property + def length(self) -> int: + return self._length + + @length.setter + def length(self, value): + data = self.get_as_numpy_array() + + self._length = value + self._buffer = np.zeros(value, dtype=np.float64) + self._delimiter = 0 + self._is_full = False + + for val in data[-value:]: + self.add_value(val) diff --git a/hummingbot/strategy/__utils__/trailing_indicators/__init__.py b/hummingbot/strategy/__utils__/trailing_indicators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/__utils__/trailing_indicators/base_trailing_indicator.py b/hummingbot/strategy/__utils__/trailing_indicators/base_trailing_indicator.py new file mode 100644 index 0000000..0718eaf --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/base_trailing_indicator.py @@ -0,0 +1,73 @@ +import logging +from abc import ABC, abstractmethod + +import numpy as np + +from ..ring_buffer import RingBuffer + +pmm_logger = None + + +class BaseTrailingIndicator(ABC): + @classmethod + def logger(cls): + global pmm_logger + if pmm_logger is None: + pmm_logger = logging.getLogger(__name__) + return pmm_logger + + def __init__(self, sampling_length: int = 30, processing_length: int = 15): + self._sampling_buffer = RingBuffer(sampling_length) + self._processing_buffer = RingBuffer(processing_length) + self._samples_length = 0 + + def add_sample(self, value: float): + self._sampling_buffer.add_value(value) + indicator_value = self._indicator_calculation() + self._processing_buffer.add_value(indicator_value) + + @abstractmethod + def _indicator_calculation(self) -> float: + raise NotImplementedError + + def _processing_calculation(self) -> float: + """ + Processing of the processing buffer to return final value. + Default behavior is buffer average + """ + return np.mean(self._processing_buffer.get_as_numpy_array()) + + @property + def current_value(self) -> float: + return self._processing_calculation() + + @property + def is_sampling_buffer_full(self) -> bool: + return self._sampling_buffer.is_full + + @property + def is_processing_buffer_full(self) -> bool: + return self._processing_buffer.is_full + + @property + def is_sampling_buffer_changed(self) -> bool: + buffer_len = len(self._sampling_buffer.get_as_numpy_array()) + is_changed = self._samples_length != buffer_len + self._samples_length = buffer_len + return is_changed + + @property + def sampling_length(self) -> int: + return self._sampling_buffer.length + + @sampling_length.setter + def sampling_length(self, value): + self._sampling_buffer.length = value + + @property + def processing_length(self) -> int: + return self._processing_buffer.length + + @processing_length.setter + def processing_length(self, value): + self._processing_buffer.length = value diff --git a/hummingbot/strategy/__utils__/trailing_indicators/exponential_moving_average.py b/hummingbot/strategy/__utils__/trailing_indicators/exponential_moving_average.py new file mode 100644 index 0000000..ed380fc --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/exponential_moving_average.py @@ -0,0 +1,17 @@ +from base_trailing_indicator import BaseTrailingIndicator +import pandas as pd + + +class ExponentialMovingAverageIndicator(BaseTrailingIndicator): + def __init__(self, sampling_length: int = 30, processing_length: int = 1): + if processing_length != 1: + raise Exception("Exponential moving average processing_length should be 1") + super().__init__(sampling_length, processing_length) + + def _indicator_calculation(self) -> float: + ema = pd.Series(self._sampling_buffer.get_as_numpy_array())\ + .ewm(span=self._sampling_length, adjust=True).mean() + return ema[-1] + + def _processing_calculation(self) -> float: + return self._processing_buffer.get_last_value() diff --git a/hummingbot/strategy/__utils__/trailing_indicators/historical_volatility.py b/hummingbot/strategy/__utils__/trailing_indicators/historical_volatility.py new file mode 100644 index 0000000..f2f97ef --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/historical_volatility.py @@ -0,0 +1,18 @@ +from .base_trailing_indicator import BaseTrailingIndicator +import numpy as np + + +class HistoricalVolatilityIndicator(BaseTrailingIndicator): + def __init__(self, sampling_length: int = 30, processing_length: int = 15): + super().__init__(sampling_length, processing_length) + + def _indicator_calculation(self) -> float: + prices = self._sampling_buffer.get_as_numpy_array() + if prices.size > 0: + log_returns = np.diff(np.log(prices)) + return np.var(log_returns) + + def _processing_calculation(self) -> float: + processing_array = self._processing_buffer.get_as_numpy_array() + if processing_array.size > 0: + return np.sqrt(np.mean(np.nan_to_num(processing_array))) diff --git a/hummingbot/strategy/__utils__/trailing_indicators/instant_volatility.py b/hummingbot/strategy/__utils__/trailing_indicators/instant_volatility.py new file mode 100644 index 0000000..0f32253 --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/instant_volatility.py @@ -0,0 +1,19 @@ +from .base_trailing_indicator import BaseTrailingIndicator +import numpy as np + + +class InstantVolatilityIndicator(BaseTrailingIndicator): + def __init__(self, sampling_length: int = 30, processing_length: int = 15): + super().__init__(sampling_length, processing_length) + + def _indicator_calculation(self) -> float: + # The standard deviation should be calculated between ticks and not with a mean of the whole buffer + # Otherwise if the asset is trending, changing the length of the buffer would result in a greater volatility as more ticks would be further away from the mean + # which is a nonsense result. If volatility of the underlying doesn't change in fact, changing the length of the buffer shouldn't change the result. + np_sampling_buffer = self._sampling_buffer.get_as_numpy_array() + vol = np.sqrt(np.sum(np.square(np.diff(np_sampling_buffer))) / np_sampling_buffer.size) + return vol + + def _processing_calculation(self) -> float: + # Only the last calculated volatlity, not an average of multiple past volatilities + return self._processing_buffer.get_last_value() diff --git a/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pxd b/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pxd new file mode 100644 index 0000000..a6a2aad --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pxd @@ -0,0 +1,30 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from libcpp.set cimport set + +from hummingbot.core.data_type.OrderBookEntry cimport OrderBookEntry +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_listener cimport EventListener + +cdef class TradingIntensityIndicator: + cdef: + double _alpha + double _kappa + dict _trade_samples + list _current_trade_sample + object _trades_forwarder + OrderBook _order_book + object _price_delegate + list _last_quotes + int _sampling_length + int _samples_length + + cdef c_calculate(self, timestamp) + cdef c_register_trade(self, object trade) + cdef c_estimate_intensity(self) + +cdef class TradesForwarder(EventListener): + cdef: + TradingIntensityIndicator _indicator diff --git a/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pyx b/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pyx new file mode 100644 index 0000000..9e9d13e --- /dev/null +++ b/hummingbot/strategy/__utils__/trailing_indicators/trading_intensity.pyx @@ -0,0 +1,166 @@ +# distutils: language=c++ +# distutils: sources=hummingbot/core/cpp/OrderBookEntry.cpp + +import warnings +from decimal import Decimal +from typing import Tuple + +import numpy as np +from scipy.optimize import curve_fit +from scipy.optimize import OptimizeWarning + +from hummingbot.core.data_type.common import ( + PriceType, +) +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_listener cimport EventListener +from hummingbot.core.event.events import OrderBookEvent +from hummingbot.strategy.asset_price_delegate import AssetPriceDelegate + +cdef class TradesForwarder(EventListener): + def __init__(self, indicator: 'TradingIntensityIndicator'): + self._indicator = indicator + + cdef c_call(self, object arg): + self._indicator.c_register_trade(arg) + + +cdef class TradingIntensityIndicator: + + def __init__(self, order_book: OrderBook, price_delegate: AssetPriceDelegate, sampling_length: int = 30): + self._alpha = 0 + self._kappa = 0 + self._trade_samples = {} + self._current_trade_sample = [] + self._trades_forwarder = TradesForwarder(self) + self._order_book = order_book + self._order_book.c_add_listener(OrderBookEvent.TradeEvent, self._trades_forwarder) + self._price_delegate = price_delegate + self._sampling_length = sampling_length + self._samples_length = 0 + self._last_quotes = [] + + warnings.simplefilter("ignore", OptimizeWarning) + + @property + def current_value(self) -> Tuple[float, float]: + return self._alpha, self._kappa + + @property + def is_sampling_buffer_full(self) -> bool: + return len(self._trade_samples.keys()) == self._sampling_length + + @property + def is_sampling_buffer_changed(self) -> bool: + is_changed = self._samples_length != len(self._trade_samples.keys()) + self._samples_length = len(self._trade_samples.keys()) + return is_changed + + @property + def sampling_length(self) -> int: + return self._sampling_length + + @sampling_length.setter + def sampling_length(self, new_len: int): + self._sampling_length = new_len + + @property + def last_quotes(self) -> list: + """A helper method to be used in unit tests""" + return self._last_quotes + + @last_quotes.setter + def last_quotes(self, value): + """A helper method to be used in unit tests""" + self._last_quotes = value + + def calculate(self, timestamp): + """A helper method to be used in unit tests""" + self.c_calculate(timestamp) + + cdef c_calculate(self, timestamp): + price = self._price_delegate.get_price_by_type(PriceType.MidPrice) + # Descending order of price-timestamp quotes + self._last_quotes = [{'timestamp': timestamp, 'price': price}] + self._last_quotes + + latest_processed_quote_idx = None + for trade in self._current_trade_sample: + for i, quote in enumerate(self._last_quotes): + if quote["timestamp"] < trade.timestamp: + if latest_processed_quote_idx is None or i < latest_processed_quote_idx: + latest_processed_quote_idx = i + trade = {"price_level": abs(trade.price - float(quote["price"])), "amount": trade.amount} + + if quote["timestamp"] + 1 not in self._trade_samples.keys(): + self._trade_samples[quote["timestamp"] + 1] = [] + + self._trade_samples[quote["timestamp"] + 1] += [trade] + break + + # THere are no trades left to process + self._current_trade_sample = [] + # Store quotes that happened after the latest trade + one before + if latest_processed_quote_idx is not None: + self._last_quotes = self._last_quotes[0:latest_processed_quote_idx + 1] + + if len(self._trade_samples.keys()) > self._sampling_length: + timestamps = list(self._trade_samples.keys()) + timestamps.sort() + timestamps = timestamps[-self._sampling_length:] + + trade_samples = {} + for timestamp in timestamps: + trade_samples[timestamp] = self._trade_samples[timestamp] + self._trade_samples = trade_samples + + if self.is_sampling_buffer_full: + self.c_estimate_intensity() + + def register_trade(self, trade): + """A helper method to be used in unit tests""" + self.c_register_trade(trade) + + cdef c_register_trade(self, object trade): + self._current_trade_sample.append(trade) + + cdef c_estimate_intensity(self): + cdef: + dict trades_consolidated + list lambdas + list price_levels + + # Calculate lambdas / trading intensities + lambdas = [] + + trades_consolidated = {} + price_levels = [] + for timestamp in self._trade_samples.keys(): + tick = self._trade_samples[timestamp] + for trade in tick: + if trade['price_level'] not in trades_consolidated.keys(): + trades_consolidated[trade['price_level']] = 0 + price_levels += [trade['price_level']] + + trades_consolidated[trade['price_level']] += trade['amount'] + + price_levels = sorted(price_levels, reverse=True) + + for price_level in price_levels: + lambdas += [trades_consolidated[price_level]] + + # Adjust to be able to calculate log + lambdas_adj = [10**-10 if x==0 else x for x in lambdas] + + # Fit the probability density function; reuse previously calculated parameters as initial values + try: + params = curve_fit(lambda t, a, b: a*np.exp(-b*t), + price_levels, + lambdas_adj, + p0=(self._alpha, self._kappa), + method='dogbox', + bounds=([0, 0], [np.inf, np.inf])) + + self._kappa = Decimal(str(params[0][1])) + self._alpha = Decimal(str(params[0][0])) + except (RuntimeError, ValueError) as e: + pass diff --git a/hummingbot/strategy/amm_arb/__init__.py b/hummingbot/strategy/amm_arb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/amm_arb/amm_arb.py b/hummingbot/strategy/amm_arb/amm_arb.py new file mode 100644 index 0000000..25e36e0 --- /dev/null +++ b/hummingbot/strategy/amm_arb/amm_arb.py @@ -0,0 +1,542 @@ +import asyncio +import logging +from decimal import Decimal +from functools import lru_cache +from typing import Callable, Dict, List, Optional, Tuple, cast + +import pandas as pd + +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.client.settings import AllConnectorSettings, GatewayConnectionSetting +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_price_shim import GatewayPriceShim +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderType, + SellOrderCompletedEvent, +) +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.amm_arb.data_types import ArbProposalSide +from hummingbot.strategy.amm_arb.utils import ArbProposal, create_arb_proposals +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +NaN = float("nan") +s_decimal_zero = Decimal(0) +amm_logger = None + + +class AmmArbStrategy(StrategyPyBase): + """ + This is a basic arbitrage strategy which can be used for most types of connectors (CEX, DEX or AMM). + For a given order amount, the strategy checks both sides of the trade (market_1 and market_2) for arb opportunity. + If presents, the strategy submits taker orders to both market. + """ + + _market_info_1: MarketTradingPairTuple + _market_info_2: MarketTradingPairTuple + _min_profitability: Decimal + _order_amount: Decimal + _market_1_slippage_buffer: Decimal + _market_2_slippage_buffer: Decimal + _concurrent_orders_submission: bool + _last_no_arb_reported: float + _arb_proposals: Optional[List[ArbProposal]] + _all_markets_ready: bool + _ev_loop: asyncio.AbstractEventLoop + _main_task: Optional[asyncio.Task] + _last_timestamp: float + _status_report_interval: float + _quote_eth_rate_fetch_loop_task: Optional[asyncio.Task] + _market_1_quote_eth_rate: None # XXX (martin_kou): Why are these here? + _market_2_quote_eth_rate: None # XXX (martin_kou): Why are these here? + _rate_source: Optional[RateOracle] + _cancel_outdated_orders_task: Optional[asyncio.Task] + _gateway_transaction_cancel_interval: int + + @classmethod + def logger(cls) -> HummingbotLogger: + global amm_logger + if amm_logger is None: + amm_logger = logging.getLogger(__name__) + return amm_logger + + def init_params(self, + market_info_1: MarketTradingPairTuple, + market_info_2: MarketTradingPairTuple, + min_profitability: Decimal, + order_amount: Decimal, + market_1_slippage_buffer: Decimal = Decimal("0"), + market_2_slippage_buffer: Decimal = Decimal("0"), + concurrent_orders_submission: bool = True, + status_report_interval: float = 900, + gateway_transaction_cancel_interval: int = 600, + rate_source: Optional[RateOracle] = RateOracle.get_instance(), + ): + """ + Assigns strategy parameters, this function must be called directly after init. + The reason for this is to make the parameters discoverable on introspect (it is not possible on init of + a Cython class). + :param market_info_1: The first market + :param market_info_2: The second market + :param min_profitability: The minimum profitability for execute trades (e.g. 0.0003 for 0.3%) + :param order_amount: The order amount + :param market_1_slippage_buffer: The buffer for which to adjust order price for higher chance of + the order getting filled. This is quite important for AMM which transaction takes a long time where a slippage + is acceptable rather having the transaction get rejected. The submitted order price will be adjust higher + for buy order and lower for sell order. + :param market_2_slippage_buffer: The slipper buffer for market_2 + :param concurrent_orders_submission: whether to submit both arbitrage taker orders (buy and sell) simultaneously + If false, the bot will wait for first exchange order filled before submitting the other order. + :param status_report_interval: Amount of seconds to wait to refresh the status report + :param gateway_transaction_cancel_interval: Amount of seconds to wait before trying to cancel orders that are + blockchain transactions that have not been included in a block (they are still in the mempool). + :param rate_source: The rate source to use for conversion rate - (RateOracle or FixedRateSource) - default is FixedRateSource + """ + self._market_info_1 = market_info_1 + self._market_info_2 = market_info_2 + self._min_profitability = min_profitability + self._order_amount = order_amount + self._market_1_slippage_buffer = market_1_slippage_buffer + self._market_2_slippage_buffer = market_2_slippage_buffer + self._concurrent_orders_submission = concurrent_orders_submission + self._last_no_arb_reported = 0 + self._all_arb_proposals = None + self._all_markets_ready = False + + self._ev_loop = asyncio.get_event_loop() + self._main_task = None + + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self.add_markets([market_info_1.market, market_info_2.market]) + self._quote_eth_rate_fetch_loop_task = None + + self._rate_source = rate_source + + self._cancel_outdated_orders_task = None + self._gateway_transaction_cancel_interval = gateway_transaction_cancel_interval + + self._order_id_side_map: Dict[str, ArbProposalSide] = {} + + @property + def all_markets_ready(self) -> bool: + return self._all_markets_ready + + @all_markets_ready.setter + def all_markets_ready(self, value: bool): + self._all_markets_ready = value + + @property + def min_profitability(self) -> Decimal: + return self._min_profitability + + @property + def order_amount(self) -> Decimal: + return self._order_amount + + @order_amount.setter + def order_amount(self, value: Decimal): + self._order_amount = value + + @property + def rate_source(self) -> Optional[RateOracle]: + return self._rate_source + + @rate_source.setter + def rate_source(self, src: Optional[RateOracle]): + self._rate_source = src + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @staticmethod + @lru_cache(maxsize=10) + def is_gateway_market(market_info: MarketTradingPairTuple) -> bool: + return market_info.market.name in sorted( + AllConnectorSettings.get_gateway_amm_connector_names() + ) + + @staticmethod + @lru_cache(maxsize=10) + def is_gateway_market_evm_compatible(market_info: MarketTradingPairTuple) -> bool: + connector_spec: Dict[str, str] = GatewayConnectionSetting.get_connector_spec_from_market_name(market_info.market.name) + return connector_spec["chain_type"] == "EVM" + + def tick(self, timestamp: float): + """ + Clock tick entry point, is run every second (on normal tick setting). + :param timestamp: current tick timestamp + """ + if not self.all_markets_ready: + self.all_markets_ready = all([market.ready for market in self.active_markets]) + if not self.all_markets_ready: + if int(timestamp) % 10 == 0: # prevent spamming by logging every 10 secs + unready_markets = [market for market in self.active_markets if market.ready is False] + for market in unready_markets: + msg = ', '.join([k for k, v in market.status_dict.items() if v is False]) + self.logger().warning(f"{market.name} not ready: waiting for {msg}.") + return + else: + self.logger().info("Markets are ready. Trading started.") + + if self.ready_for_new_arb_trades(): + if self._main_task is None or self._main_task.done(): + self._main_task = safe_ensure_future(self.main()) + if self._cancel_outdated_orders_task is None or self._cancel_outdated_orders_task.done(): + self._cancel_outdated_orders_task = safe_ensure_future(self.apply_gateway_transaction_cancel_interval()) + + async def main(self): + """ + The main procedure for the arbitrage strategy. It first creates arbitrage proposals, filters for ones that meets + min profitability required, applies the slippage buffer, applies budget constraint, then finally execute the + arbitrage. + """ + self._all_arb_proposals = await create_arb_proposals( + market_info_1=self._market_info_1, + market_info_2=self._market_info_2, + market_1_extra_flat_fees=( + [getattr(self._market_info_1.market, "network_transaction_fee")] + if hasattr(self._market_info_1.market, "network_transaction_fee") + else [] + ), + market_2_extra_flat_fees=( + [getattr(self._market_info_2.market, "network_transaction_fee")] + if hasattr(self._market_info_2.market, "network_transaction_fee") + else [] + ), + order_amount=self._order_amount, + ) + profitable_arb_proposals: List[ArbProposal] = [ + t.copy() for t in self._all_arb_proposals + if t.profit_pct( + rate_source=self._rate_source, + account_for_fee=True, + ) >= self._min_profitability + ] + if len(profitable_arb_proposals) == 0: + if self._last_no_arb_reported < self.current_timestamp - 20.: + self.logger().info("No arbitrage opportunity.\n" + + "\n".join(self.short_proposal_msg(self._all_arb_proposals, False))) + self._last_no_arb_reported = self.current_timestamp + return + await self.apply_slippage_buffers(profitable_arb_proposals) + self.apply_budget_constraint(profitable_arb_proposals) + await self.execute_arb_proposals(profitable_arb_proposals) + + async def apply_gateway_transaction_cancel_interval(self): + # XXX (martin_kou): Concurrent cancellations are not supported before the nonce architecture is fixed. + # See: https://app.shortcut.com/coinalpha/story/24553/nonce-architecture-in-current-amm-trade-and-evm-approve-apis-is-incorrect-and-causes-trouble-with-concurrent-requests + gateway_connectors = [] + if self.is_gateway_market(self._market_info_1) and self.is_gateway_market_evm_compatible(self._market_info_1): + gateway_connectors.append(cast(GatewayEVMAMM, self._market_info_1.market)) + if self.is_gateway_market(self._market_info_2) and self.is_gateway_market_evm_compatible(self._market_info_2): + gateway_connectors.append(cast(GatewayEVMAMM, self._market_info_2.market)) + + for gateway in gateway_connectors: + await gateway.cancel_outdated_orders(self._gateway_transaction_cancel_interval) + + async def apply_slippage_buffers(self, arb_proposals: List[ArbProposal]): + """ + Updates arb_proposals by adjusting order price for slipper buffer percentage. + E.g. if it is a buy order, for an order price of 100 and 1% slipper buffer, the new order price is 101, + for a sell order, the new order price is 99. + :param arb_proposals: the arbitrage proposal + """ + for arb_proposal in arb_proposals: + for arb_side in (arb_proposal.first_side, arb_proposal.second_side): + market = arb_side.market_info.market + arb_side.amount = market.quantize_order_amount(arb_side.market_info.trading_pair, arb_side.amount) + s_buffer = self._market_1_slippage_buffer if market == self._market_info_1.market \ + else self._market_2_slippage_buffer + if not arb_side.is_buy: + s_buffer *= Decimal("-1") + arb_side.order_price *= Decimal("1") + s_buffer + arb_side.order_price = market.quantize_order_price(arb_side.market_info.trading_pair, + arb_side.order_price) + + def apply_budget_constraint(self, arb_proposals: List[ArbProposal]): + """ + Updates arb_proposals by setting proposal amount to 0 if there is not enough balance to submit order with + required order amount. + :param arb_proposals: the arbitrage proposal + """ + for arb_proposal in arb_proposals: + for arb_side in (arb_proposal.first_side, arb_proposal.second_side): + market = arb_side.market_info.market + token = arb_side.market_info.quote_asset if arb_side.is_buy else arb_side.market_info.base_asset + balance = market.get_available_balance(token) + required = arb_side.amount * arb_side.order_price if arb_side.is_buy else arb_side.amount + if balance < required: + arb_side.amount = s_decimal_zero + self.logger().info(f"Can't arbitrage, {market.display_name} " + f"{token} balance " + f"({balance}) is below required order amount ({required}).") + continue + + def prioritize_evm_exchanges(self, arb_proposal: ArbProposal) -> ArbProposal: + """ + Prioritize the EVM exchanges in the arbitrage proposals + + :param arb_proposal: The arbitrage proposal from which the sides are to be prioritized. + :type arb_proposal: ArbProposal + :return: A new ArbProposal object with evm exchanges prioritized. + :rtype: ArbProposal + """ + + results = [] + for side in [arb_proposal.first_side, arb_proposal.second_side]: + if self.is_gateway_market(side.market_info): + results.insert(0, side) + else: + results.append(side) + + return ArbProposal(first_side=results[0], second_side=results[1]) + + async def execute_arb_proposals(self, arb_proposals: List[ArbProposal]): + """ + Execute both sides of the arbitrage trades. If concurrent_orders_submission is False, it will wait for the + first order to fill before submit the second order. + :param arb_proposals: the arbitrage proposal + """ + for arb_proposal in arb_proposals: + if any(p.amount <= s_decimal_zero for p in (arb_proposal.first_side, arb_proposal.second_side)): + continue + + if not self._concurrent_orders_submission: + arb_proposal = self.prioritize_evm_exchanges(arb_proposal) + + self.logger().info(f"Found arbitrage opportunity!: {arb_proposal}") + + for arb_side in (arb_proposal.first_side, arb_proposal.second_side): + side: str = "BUY" if arb_side.is_buy else "SELL" + self.log_with_clock(logging.INFO, + f"Placing {side} order for {arb_side.amount} {arb_side.market_info.base_asset} " + f"at {arb_side.market_info.market.display_name} at {arb_side.order_price} price") + + order_id: str = await self.place_arb_order( + arb_side.market_info, + arb_side.is_buy, + arb_side.amount, + arb_side.order_price + ) + + self._order_id_side_map.update({ + order_id: arb_side + }) + + if not self._concurrent_orders_submission: + await arb_side.completed_event.wait() + if arb_side.is_failed: + self.log_with_clock(logging.ERROR, + f"Order {order_id} seems to have failed in this arbitrage opportunity. " + f"Dropping Arbitrage Proposal. ") + return + + await arb_proposal.wait() + + async def place_arb_order( + self, + market_info: MarketTradingPairTuple, + is_buy: bool, + amount: Decimal, + order_price: Decimal) -> str: + place_order_fn: Callable[[MarketTradingPairTuple, Decimal, OrderType, Decimal], str] = \ + cast(Callable, self.buy_with_specific_market if is_buy else self.sell_with_specific_market) + + # If I'm placing order under a gateway price shim, then the prices in the proposal are fake - I should fetch + # the real prices before I make the order on the gateway side. Otherwise, the orders are gonna fail because + # the limit price set for them will not match market prices. + if self.is_gateway_market(market_info): + slippage_buffer: Decimal = self._market_1_slippage_buffer + if market_info == self._market_info_2: + slippage_buffer = self._market_2_slippage_buffer + slippage_buffer_factor: Decimal = Decimal(1) + slippage_buffer + if not is_buy: + slippage_buffer_factor = Decimal(1) - slippage_buffer + market: GatewayEVMAMM = cast(GatewayEVMAMM, market_info.market) + if GatewayPriceShim.get_instance().has_price_shim( + market.connector_name, market.chain, market.network, market_info.trading_pair): + order_price = await market.get_order_price(market_info.trading_pair, is_buy, amount, ignore_shim=True) + order_price *= slippage_buffer_factor + + return place_order_fn(market_info, amount, market_info.market.get_taker_order_type(), order_price) + + def ready_for_new_arb_trades(self) -> bool: + """ + Returns True if there is no outstanding unfilled order. + """ + # outstanding_orders = self.market_info_to_active_orders.get(self._market_info, []) + for market_info in [self._market_info_1, self._market_info_2]: + if len(self.market_info_to_active_orders.get(market_info, [])) > 0: + return False + return True + + def short_proposal_msg(self, arb_proposal: List[ArbProposal], indented: bool = True) -> List[str]: + """ + Composes a short proposal message. + :param arb_proposal: The arbitrage proposal + :param indented: If the message should be indented (by 4 spaces) + :return A list of messages + """ + lines = [] + for proposal in arb_proposal: + side1: str = "buy" if proposal.first_side.is_buy else "sell" + side2: str = "buy" if proposal.second_side.is_buy else "sell" + market_1_name: str = proposal.first_side.market_info.market.display_name + market_2_name: str = proposal.second_side.market_info.market.display_name + profit_pct = proposal.profit_pct( + rate_source=self._rate_source, + account_for_fee=True, + ) + lines.append(f"{' ' if indented else ''}{side1} at {market_1_name}" + f", {side2} at {market_2_name}: " + f"{profit_pct:.2%}") + return lines + + def get_fixed_rates_df(self): + columns = ["Pair", "Rate"] + quotes_pair: str = f"{self._market_info_2.quote_asset}-{self._market_info_1.quote_asset}" + bases_pair: str = f"{self._market_info_2.base_asset}-{self._market_info_1.base_asset}" + data = [[quotes_pair, PerformanceMetrics.smart_round(self._rate_source.get_pair_rate(quotes_pair))], + [bases_pair, PerformanceMetrics.smart_round(self._rate_source.get_pair_rate(bases_pair))]] + return pd.DataFrame(data=data, columns=columns) + + async def format_status(self) -> str: + """ + Returns a status string formatted to display nicely on terminal. The strings composes of 4 parts: markets, + assets, profitability and warnings(if any). + """ + + if self._all_arb_proposals is None: + return " The strategy is not ready, please try again later." + columns = ["Exchange", "Market", "Sell Price", "Buy Price", "Mid Price"] + data = [] + for market_info in [self._market_info_1, self._market_info_2]: + market, trading_pair, base_asset, quote_asset = market_info + buy_price = await market.get_quote_price(trading_pair, True, self._order_amount) + sell_price = await market.get_quote_price(trading_pair, False, self._order_amount) + + # check for unavailable price data + buy_price = PerformanceMetrics.smart_round(Decimal(str(buy_price)), 8) if buy_price is not None else '-' + sell_price = PerformanceMetrics.smart_round(Decimal(str(sell_price)), 8) if sell_price is not None else '-' + mid_price = PerformanceMetrics.smart_round(((buy_price + sell_price) / 2), 8) if '-' not in [buy_price, sell_price] else '-' + + data.append([ + market.display_name, + trading_pair, + sell_price, + buy_price, + mid_price + ]) + markets_df = pd.DataFrame(data=data, columns=columns) + lines = [] + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + columns = ["Exchange", "Gas Fees"] + data = [] + for market_info in [self._market_info_1, self._market_info_2]: + if hasattr(market_info.market, "network_transaction_fee"): + transaction_fee: TokenAmount = getattr(market_info.market, "network_transaction_fee") + data.append([market_info.market.display_name, f"{transaction_fee.amount} {transaction_fee.token}"]) + network_fees_df = pd.DataFrame(data=data, columns=columns) + if len(data) > 0: + lines.extend( + ["", " Network Fees:"] + + [" " + line for line in network_fees_df.to_string(index=False).split("\n")] + ) + + assets_df = self.wallet_balance_data_frame([self._market_info_1, self._market_info_2]) + lines.extend(["", " Assets:"] + + [" " + line for line in str(assets_df).split("\n")]) + + lines.extend(["", " Profitability:"] + self.short_proposal_msg(self._all_arb_proposals)) + + fixed_rates_df = self.get_fixed_rates_df() + lines.extend(["", f" Exchange Rates: ({str(self._rate_source)})"] + + [" " + line for line in str(fixed_rates_df).split("\n")]) + + warning_lines = self.network_warning([self._market_info_1]) + warning_lines.extend(self.network_warning([self._market_info_2])) + warning_lines.extend(self.balance_warning([self._market_info_1])) + warning_lines.extend(self.balance_warning([self._market_info_2])) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def set_order_completed(self, order_id: str): + arb_side: Optional[ArbProposalSide] = self._order_id_side_map.get(order_id) + if arb_side: + arb_side.set_completed() + + def set_order_failed(self, order_id: str): + arb_side: Optional[ArbProposalSide] = self._order_id_side_map.get(order_id) + if arb_side: + arb_side.set_failed() + arb_side.set_completed() + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + self.set_order_completed(order_id=order_completed_event.order_id) + + market_info: MarketTradingPairTuple = self.order_tracker.get_market_pair_from_order_id( + order_completed_event.order_id + ) + log_msg: str = f"Buy order completed on {market_info.market.name}: {order_completed_event.order_id}." + if self.is_gateway_market(market_info): + log_msg += f" txHash: {order_completed_event.exchange_order_id}" + self.log_with_clock(logging.INFO, log_msg) + self.notify_hb_app_with_timestamp(f"Bought {order_completed_event.base_asset_amount:.8f} " + f"{order_completed_event.base_asset}-{order_completed_event.quote_asset} " + f"on {market_info.market.name}.") + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + self.set_order_completed(order_id=order_completed_event.order_id) + + market_info: MarketTradingPairTuple = self.order_tracker.get_market_pair_from_order_id( + order_completed_event.order_id + ) + log_msg: str = f"Sell order completed on {market_info.market.name}: {order_completed_event.order_id}." + if self.is_gateway_market(market_info): + log_msg += f" txHash: {order_completed_event.exchange_order_id}" + self.log_with_clock(logging.INFO, log_msg) + self.notify_hb_app_with_timestamp(f"Sold {order_completed_event.base_asset_amount:.8f} " + f"{order_completed_event.base_asset}-{order_completed_event.quote_asset} " + f"on {market_info.market.name}.") + + def did_fail_order(self, order_failed_event: MarketOrderFailureEvent): + self.set_order_failed(order_id=order_failed_event.order_id) + + def did_cancel_order(self, cancelled_event: OrderCancelledEvent): + self.set_order_completed(order_id=cancelled_event.order_id) + + def did_expire_order(self, expired_event: OrderExpiredEvent): + self.set_order_completed(order_id=expired_event.order_id) + + @property + def tracked_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return self._sb_order_tracker.tracked_limit_orders + + @property + def tracked_market_orders(self) -> List[Tuple[ConnectorBase, MarketOrder]]: + return self._sb_order_tracker.tracked_market_orders + + def start(self, clock: Clock, timestamp: float): + super().start(clock, timestamp) + + def stop(self, clock: Clock): + if self._main_task is not None: + self._main_task.cancel() + self._main_task = None + super().stop(clock) diff --git a/hummingbot/strategy/amm_arb/amm_arb_config_map.py b/hummingbot/strategy/amm_arb/amm_arb_config_map.py new file mode 100644 index 0000000..4ffb542 --- /dev/null +++ b/hummingbot/strategy/amm_arb/amm_arb_config_map.py @@ -0,0 +1,156 @@ +from decimal import Decimal + +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_connector, + validate_decimal, + validate_int, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges, requried_connector_trading_pairs + + +def exchange_on_validated(value: str) -> None: + required_exchanges.add(value) + + +def market_1_validator(value: str) -> None: + exchange = amm_arb_config_map["connector_1"].value + return validate_market_trading_pair(exchange, value) + + +def market_1_on_validated(value: str) -> None: + requried_connector_trading_pairs[amm_arb_config_map["connector_1"].value] = [value] + + +def market_2_validator(value: str) -> None: + exchange = amm_arb_config_map["connector_2"].value + return validate_market_trading_pair(exchange, value) + + +def market_2_on_validated(value: str) -> None: + requried_connector_trading_pairs[amm_arb_config_map["connector_2"].value] = [value] + + +def market_1_prompt() -> str: + connector = amm_arb_config_map.get("connector_1").value + example = AllConnectorSettings.get_example_pairs().get(connector) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (connector, f" (e.g. {example})" if example else "") + + +def market_2_prompt() -> str: + connector = amm_arb_config_map.get("connector_2").value + example = AllConnectorSettings.get_example_pairs().get(connector) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (connector, f" (e.g. {example})" if example else "") + + +def order_amount_prompt() -> str: + trading_pair = amm_arb_config_map["market_1"].value + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order? >>> " + + +amm_arb_config_map = { + "strategy": ConfigVar( + key="strategy", + prompt="", + default="amm_arb"), + "connector_1": ConfigVar( + key="connector_1", + prompt="Enter your first connector (Exchange/AMM/CLOB) >>> ", + prompt_on_new=True, + validator=validate_connector, + on_validated=exchange_on_validated), + "market_1": ConfigVar( + key="market_1", + prompt=market_1_prompt, + prompt_on_new=True, + validator=market_1_validator, + on_validated=market_1_on_validated), + "connector_2": ConfigVar( + key="connector_2", + prompt="Enter your second connector (Exchange/AMM/CLOB) >>> ", + prompt_on_new=True, + validator=validate_connector, + on_validated=exchange_on_validated), + "market_2": ConfigVar( + key="market_2", + prompt=market_2_prompt, + prompt_on_new=True, + validator=market_2_validator, + on_validated=market_2_on_validated), + "order_amount": ConfigVar( + key="order_amount", + prompt=order_amount_prompt, + type_str="decimal", + validator=lambda v: validate_decimal(v, Decimal("0")), + prompt_on_new=True), + "min_profitability": ConfigVar( + key="min_profitability", + prompt="What is the minimum profitability for you to make a trade? (Enter 1 to indicate 1%) >>> ", + prompt_on_new=True, + default=Decimal("1"), + validator=lambda v: validate_decimal(v), + type_str="decimal"), + "market_1_slippage_buffer": ConfigVar( + key="market_1_slippage_buffer", + prompt="How much buffer do you want to add to the price to account for slippage for orders on the first market " + "(Enter 1 for 1%)? >>> ", + prompt_on_new=True, + default=lambda: Decimal(1) if amm_arb_config_map["connector_1"].value in sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ) else Decimal(0), + validator=lambda v: validate_decimal(v), + type_str="decimal"), + "market_2_slippage_buffer": ConfigVar( + key="market_2_slippage_buffer", + prompt="How much buffer do you want to add to the price to account for slippage for orders on the second market" + " (Enter 1 for 1%)? >>> ", + prompt_on_new=True, + default=lambda: Decimal(1) if amm_arb_config_map["connector_2"].value in sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ) else Decimal(0), + validator=lambda v: validate_decimal(v), + type_str="decimal"), + "concurrent_orders_submission": ConfigVar( + key="concurrent_orders_submission", + prompt="Do you want to submit both arb orders concurrently (Yes/No) ? If No, the bot will wait for first " + "connector order filled before submitting the other order >>> ", + prompt_on_new=True, + default=False, + validator=validate_bool, + type_str="bool"), + "debug_price_shim": ConfigVar( + key="debug_price_shim", + prompt="Do you want to enable the debug price shim for integration tests? If you don't know what this does " + "you should keep it disabled. >>> ", + default=False, + validator=validate_bool, + type_str="bool"), + "gateway_transaction_cancel_interval": ConfigVar( + key="gateway_transaction_cancel_interval", + prompt="After what time should blockchain transactions be cancelled if they are not included in a block? " + "(this only affects decentralized exchanges) (Enter time in seconds) >>> ", + default=600, + validator=lambda v: validate_int(v, min_value=1, inclusive=True), + type_str="int"), + "rate_oracle_enabled": ConfigVar( + key="rate_oracle_enabled", + prompt="Do you want to use the rate oracle? (Yes/No) >>> ", + default=True, + validator=validate_bool, + type_str="bool"), + "quote_conversion_rate": ConfigVar( + key="quote_conversion_rate", + prompt="What is the fixed_rate used to convert quote assets? >>> ", + default=Decimal("1"), + validator=lambda v: validate_decimal(v), + type_str="decimal"), +} diff --git a/hummingbot/strategy/amm_arb/data_types.py b/hummingbot/strategy/amm_arb/data_types.py new file mode 100644 index 0000000..01556ee --- /dev/null +++ b/hummingbot/strategy/amm_arb/data_types.py @@ -0,0 +1,169 @@ +import asyncio +import logging +from dataclasses import dataclass +from decimal import Decimal +from typing import List, Optional + +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase +from hummingbot.core.event.events import OrderType, TradeType +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +s_decimal_nan = Decimal("NaN") +s_decimal_0 = Decimal("0") +arbprop_logger: Optional[HummingbotLogger] = None + + +@dataclass +class ArbProposalSide: + """ + An arbitrage proposal side which contains info needed for order submission. + """ + market_info: MarketTradingPairTuple + is_buy: bool + quote_price: Decimal + order_price: Decimal + amount: Decimal + extra_flat_fees: List[TokenAmount] + completed_event: asyncio.Event = asyncio.Event() + failed_event: asyncio.Event = asyncio.Event() + + def __repr__(self): + side = "buy" if self.is_buy else "sell" + return f"Connector: {self.market_info.market.display_name} Side: {side} Quote Price: {self.quote_price} " \ + f"Order Price: {self.order_price} Amount: {self.amount} Extra Fees: {self.extra_flat_fees}" + + @property + def is_completed(self) -> bool: + return self.completed_event.is_set() + + @property + def is_failed(self) -> bool: + return self.failed_event.is_set() + + def set_completed(self): + self.completed_event.set() + + def set_failed(self): + self.failed_event.set() + + +class ArbProposal: + @classmethod + def logger(cls) -> HummingbotLogger: + global arbprop_logger + if arbprop_logger is None: + arbprop_logger = logging.getLogger(__name__) + return arbprop_logger + + """ + An arbitrage proposal which contains 2 sides of the proposal - one buy and one sell. + """ + def __init__(self, first_side: ArbProposalSide, second_side: ArbProposalSide): + if first_side.is_buy == second_side.is_buy: + raise Exception("first_side and second_side must be on different side of buy and sell.") + self.first_side: ArbProposalSide = first_side + self.second_side: ArbProposalSide = second_side + + @property + def has_failed_orders(self) -> bool: + return any([self.first_side.is_failed, self.second_side.is_failed]) + + def profit_pct( + self, + rate_source: Optional[RateOracle] = None, + account_for_fee: bool = False, + ) -> Decimal: + """ + Returns a profit in percentage value (e.g. 0.01 for 1% profitability) + Assumes the base token is the same in both arbitrage sides + """ + if not rate_source: + rate_source = RateOracle.get_instance() + + buy_side: ArbProposalSide = self.first_side if self.first_side.is_buy else self.second_side + sell_side: ArbProposalSide = self.first_side if not self.first_side.is_buy else self.second_side + base_conversion_pair: str = f"{sell_side.market_info.base_asset}-{buy_side.market_info.base_asset}" + quote_conversion_pair: str = f"{sell_side.market_info.quote_asset}-{buy_side.market_info.quote_asset}" + + sell_base_to_buy_base_rate: Decimal = Decimal(1) + sell_quote_to_buy_quote_rate: Decimal = rate_source.get_pair_rate(quote_conversion_pair) + + buy_fee_amount: Decimal = s_decimal_0 + sell_fee_amount: Decimal = s_decimal_0 + result: Decimal = s_decimal_0 + + if sell_quote_to_buy_quote_rate and sell_base_to_buy_base_rate: + if account_for_fee: + buy_trade_fee: TradeFeeBase = build_trade_fee( + exchange=buy_side.market_info.market.name, + is_maker=False, + base_currency=buy_side.market_info.base_asset, + quote_currency=buy_side.market_info.quote_asset, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, + amount=buy_side.amount, + price=buy_side.order_price, + extra_flat_fees=buy_side.extra_flat_fees + ) + sell_trade_fee: TradeFeeBase = build_trade_fee( + exchange=sell_side.market_info.market.name, + is_maker=False, + base_currency=sell_side.market_info.base_asset, + quote_currency=sell_side.market_info.quote_asset, + order_type=OrderType.MARKET, + order_side=TradeType.SELL, + amount=sell_side.amount, + price=sell_side.order_price, + extra_flat_fees=sell_side.extra_flat_fees + ) + buy_fee_amount: Decimal = buy_trade_fee.fee_amount_in_token( + trading_pair=buy_side.market_info.trading_pair, + price=buy_side.quote_price, + order_amount=buy_side.amount, + token=buy_side.market_info.quote_asset, + rate_source=rate_source + ) + sell_fee_amount: Decimal = sell_trade_fee.fee_amount_in_token( + trading_pair=sell_side.market_info.trading_pair, + price=sell_side.quote_price, + order_amount=sell_side.amount, + token=sell_side.market_info.quote_asset, + rate_source=rate_source + ) + + buy_spent_net: Decimal = (buy_side.amount * buy_side.quote_price) + buy_fee_amount + sell_gained_net: Decimal = (sell_side.amount * sell_side.quote_price) - sell_fee_amount + sell_gained_net_in_buy_quote_currency: Decimal = ( + sell_gained_net * sell_quote_to_buy_quote_rate / sell_base_to_buy_base_rate + ) + + result: Decimal = ( + ((sell_gained_net_in_buy_quote_currency - buy_spent_net) / buy_spent_net) + if buy_spent_net != s_decimal_0 + else s_decimal_0 + ) + else: + self.logger().warning("The arbitrage proposal profitability could not be calculated due to a missing rate" + f" ({base_conversion_pair}={sell_base_to_buy_base_rate}," + f" {quote_conversion_pair}={sell_quote_to_buy_quote_rate})") + return result + + def __repr__(self): + return f"First Side - {self.first_side}\nSecond Side - {self.second_side}" + + def copy(self): + return ArbProposal( + ArbProposalSide(self.first_side.market_info, self.first_side.is_buy, + self.first_side.quote_price, self.first_side.order_price, + self.first_side.amount, self.first_side.extra_flat_fees), + ArbProposalSide(self.second_side.market_info, self.second_side.is_buy, + self.second_side.quote_price, self.second_side.order_price, + self.second_side.amount, self.second_side.extra_flat_fees) + ) + + async def wait(self): + return await safe_gather(*[self.first_side.completed_event.wait(), self.second_side.completed_event.wait()]) diff --git a/hummingbot/strategy/amm_arb/dummy.pxd b/hummingbot/strategy/amm_arb/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/amm_arb/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/amm_arb/dummy.pyx b/hummingbot/strategy/amm_arb/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/amm_arb/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/amm_arb/start.py b/hummingbot/strategy/amm_arb/start.py new file mode 100644 index 0000000..0063b37 --- /dev/null +++ b/hummingbot/strategy/amm_arb/start.py @@ -0,0 +1,78 @@ +from decimal import Decimal +from typing import cast + +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.amm.gateway_tezos_amm import GatewayTezosAMM +from hummingbot.connector.gateway.common_types import Chain +from hummingbot.connector.gateway.gateway_price_shim import GatewayPriceShim +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.fixed_rate_source import FixedRateSource +from hummingbot.strategy.amm_arb.amm_arb import AmmArbStrategy +from hummingbot.strategy.amm_arb.amm_arb_config_map import amm_arb_config_map +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + connector_1 = amm_arb_config_map.get("connector_1").value.lower() + market_1 = amm_arb_config_map.get("market_1").value + connector_2 = amm_arb_config_map.get("connector_2").value.lower() + market_2 = amm_arb_config_map.get("market_2").value + order_amount = amm_arb_config_map.get("order_amount").value + min_profitability = amm_arb_config_map.get("min_profitability").value / Decimal("100") + market_1_slippage_buffer = amm_arb_config_map.get("market_1_slippage_buffer").value / Decimal("100") + market_2_slippage_buffer = amm_arb_config_map.get("market_2_slippage_buffer").value / Decimal("100") + concurrent_orders_submission = amm_arb_config_map.get("concurrent_orders_submission").value + debug_price_shim = amm_arb_config_map.get("debug_price_shim").value + gateway_transaction_cancel_interval = amm_arb_config_map.get("gateway_transaction_cancel_interval").value + rate_oracle_enabled = amm_arb_config_map.get("rate_oracle_enabled").value + quote_conversion_rate = amm_arb_config_map.get("quote_conversion_rate").value + + self._initialize_markets([(connector_1, [market_1]), (connector_2, [market_2])]) + base_1, quote_1 = market_1.split("-") + base_2, quote_2 = market_2.split("-") + + market_info_1 = MarketTradingPairTuple(self.markets[connector_1], market_1, base_1, quote_1) + market_info_2 = MarketTradingPairTuple(self.markets[connector_2], market_2, base_2, quote_2) + self.market_trading_pair_tuples = [market_info_1, market_info_2] + + if debug_price_shim: + amm_market_info: MarketTradingPairTuple = market_info_1 + other_market_info: MarketTradingPairTuple = market_info_2 + other_market_name: str = connector_2 + if AmmArbStrategy.is_gateway_market(other_market_info): + amm_market_info = market_info_2 + other_market_info = market_info_1 + other_market_name = connector_1 + if Chain.ETHEREUM.chain == amm_market_info.market.chain: + amm_connector: GatewayEVMAMM = cast(GatewayEVMAMM, amm_market_info.market) + elif Chain.TEZOS.chain == amm_market_info.market.chain: + amm_connector: GatewayTezosAMM = cast(GatewayTezosAMM, amm_market_info.market) + else: + raise ValueError(f"Unsupported chain: {amm_market_info.market.chain}") + GatewayPriceShim.get_instance().patch_prices( + other_market_name, + other_market_info.trading_pair, + amm_connector.connector_name, + amm_connector.chain, + amm_connector.network, + amm_market_info.trading_pair + ) + + if rate_oracle_enabled: + rate_source = RateOracle.get_instance() + else: + rate_source = FixedRateSource() + rate_source.add_rate(f"{quote_2}-{quote_1}", Decimal(str(quote_conversion_rate))) # reverse rate is already handled in FixedRateSource find_rate method. + rate_source.add_rate(f"{quote_1}-{quote_2}", Decimal(str(1 / quote_conversion_rate))) # reverse rate is already handled in FixedRateSource find_rate method. + + self.strategy = AmmArbStrategy() + self.strategy.init_params(market_info_1=market_info_1, + market_info_2=market_info_2, + min_profitability=min_profitability, + order_amount=order_amount, + market_1_slippage_buffer=market_1_slippage_buffer, + market_2_slippage_buffer=market_2_slippage_buffer, + concurrent_orders_submission=concurrent_orders_submission, + gateway_transaction_cancel_interval=gateway_transaction_cancel_interval, + rate_source=rate_source, + ) diff --git a/hummingbot/strategy/amm_arb/utils.py b/hummingbot/strategy/amm_arb/utils.py new file mode 100644 index 0000000..ac42d5c --- /dev/null +++ b/hummingbot/strategy/amm_arb/utils.py @@ -0,0 +1,66 @@ +from decimal import Decimal +from enum import Enum +from typing import List + +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +from .data_types import ArbProposal, ArbProposalSide, TokenAmount + +s_decimal_nan = Decimal("NaN") + + +class TradeDirection(Enum): + BUY = 1 + SELL = 0 + + +async def create_arb_proposals( + market_info_1: MarketTradingPairTuple, + market_info_2: MarketTradingPairTuple, + market_1_extra_flat_fees: List[TokenAmount], + market_2_extra_flat_fees: List[TokenAmount], + order_amount: Decimal +) -> List[ArbProposal]: + order_amount = Decimal(str(order_amount)) + results = [] + + tasks = [] + for trade_direction in TradeDirection: + is_buy = trade_direction == TradeDirection.BUY + tasks.append([ + market_info_1.market.get_quote_price(market_info_1.trading_pair, is_buy, order_amount), + market_info_1.market.get_order_price(market_info_1.trading_pair, is_buy, order_amount), + market_info_2.market.get_quote_price(market_info_2.trading_pair, not is_buy, order_amount), + market_info_2.market.get_order_price(market_info_2.trading_pair, not is_buy, order_amount) + ]) + + results_raw = await safe_gather(*[safe_gather(*task_group) for task_group in tasks]) + + for trade_direction, task_group_result in zip(TradeDirection, results_raw): + is_buy = trade_direction == TradeDirection.BUY + m_1_q_price, m_1_o_price, m_2_q_price, m_2_o_price = task_group_result + + if any(p is None for p in (m_1_o_price, m_1_q_price, m_2_o_price, m_2_q_price)): + continue + + first_side = ArbProposalSide( + market_info=market_info_1, + is_buy=is_buy, + quote_price=m_1_q_price, + order_price=m_1_o_price, + amount=order_amount, + extra_flat_fees=market_1_extra_flat_fees, + ) + second_side = ArbProposalSide( + market_info=market_info_2, + is_buy=not is_buy, + quote_price=m_2_q_price, + order_price=m_2_o_price, + amount=order_amount, + extra_flat_fees=market_2_extra_flat_fees + ) + + results.append(ArbProposal(first_side, second_side)) + + return results diff --git a/hummingbot/strategy/api_asset_price_delegate.pxd b/hummingbot/strategy/api_asset_price_delegate.pxd new file mode 100644 index 0000000..9de31a1 --- /dev/null +++ b/hummingbot/strategy/api_asset_price_delegate.pxd @@ -0,0 +1,7 @@ +from hummingbot.connector.exchange_base cimport ExchangeBase +from .asset_price_delegate cimport AssetPriceDelegate + +cdef class APIAssetPriceDelegate(AssetPriceDelegate): + cdef: + ExchangeBase _market + object _custom_api_feed diff --git a/hummingbot/strategy/api_asset_price_delegate.pyx b/hummingbot/strategy/api_asset_price_delegate.pyx new file mode 100644 index 0000000..f9d64ab --- /dev/null +++ b/hummingbot/strategy/api_asset_price_delegate.pyx @@ -0,0 +1,30 @@ +from decimal import Decimal + +from hummingbot.core.data_type.common import PriceType +from hummingbot.data_feed.custom_api_data_feed import CustomAPIDataFeed, NetworkStatus +from .asset_price_delegate cimport AssetPriceDelegate + +cdef class APIAssetPriceDelegate(AssetPriceDelegate): + def __init__(self, market: ExchangeBase, api_url: str, update_interval: float = 5.0): + super().__init__() + self._market = market + self._custom_api_feed = CustomAPIDataFeed(api_url=api_url, update_interval=update_interval) + self._custom_api_feed.start() + + def get_price_by_type(self, _: PriceType) -> Decimal: + return self.c_get_mid_price() + + cdef object c_get_mid_price(self): + return self._custom_api_feed.get_price() + + @property + def ready(self) -> bool: + return self._custom_api_feed.network_status == NetworkStatus.CONNECTED + + @property + def market(self) -> ExchangeBase: + return self._market + + @property + def custom_api_feed(self) -> CustomAPIDataFeed: + return self._custom_api_feed diff --git a/hummingbot/strategy/asset_price_delegate.pxd b/hummingbot/strategy/asset_price_delegate.pxd new file mode 100644 index 0000000..af6a7bf --- /dev/null +++ b/hummingbot/strategy/asset_price_delegate.pxd @@ -0,0 +1,3 @@ + +cdef class AssetPriceDelegate: + cdef object c_get_mid_price(self) diff --git a/hummingbot/strategy/asset_price_delegate.pyx b/hummingbot/strategy/asset_price_delegate.pyx new file mode 100644 index 0000000..06436c9 --- /dev/null +++ b/hummingbot/strategy/asset_price_delegate.pyx @@ -0,0 +1,25 @@ +from decimal import Decimal + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import PriceType + +cdef class AssetPriceDelegate: + # The following exposed Python functions are meant for unit tests + # --------------------------------------------------------------- + def get_mid_price(self) -> Decimal: + return self.c_get_mid_price() + # --------------------------------------------------------------- + + def get_price_by_type(self, price_type: PriceType) -> Decimal: + raise NotImplementedError + + cdef object c_get_mid_price(self): + raise NotImplementedError + + @property + def ready(self) -> bool: + raise NotImplementedError + + @property + def market(self) -> ExchangeBase: + raise NotImplementedError diff --git a/hummingbot/strategy/avellaneda_market_making/__init__.py b/hummingbot/strategy/avellaneda_market_making/__init__.py new file mode 100644 index 0000000..d29aaf1 --- /dev/null +++ b/hummingbot/strategy/avellaneda_market_making/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from .avellaneda_market_making import AvellanedaMarketMakingStrategy +__all__ = [ + AvellanedaMarketMakingStrategy, +] diff --git a/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pxd b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pxd new file mode 100644 index 0000000..e7ae70e --- /dev/null +++ b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pxd @@ -0,0 +1,82 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from hummingbot.strategy.__utils__.trailing_indicators.trading_intensity cimport TradingIntensityIndicator +from hummingbot.strategy.strategy_base cimport StrategyBase + + +cdef class AvellanedaMarketMakingStrategy(StrategyBase): + cdef: + object _config_map + object _market_info + object _price_delegate + object _minimum_spread + bint _hanging_orders_enabled + object _hanging_orders_cancel_pct + object _hanging_orders_tracker + bint _add_transaction_costs_to_orders + bint _hb_app_notification + bint _is_debug + + double _cancel_timestamp + double _create_timestamp + object _limit_order_type + bint _all_markets_ready + int _filled_buys_balance + int _filled_sells_balance + double _last_timestamp + double _status_report_interval + int64_t _logging_options + object _last_own_trade_price + int _volatility_sampling_period + double _last_sampling_timestamp + bint _parameters_based_on_spread + int _volatility_buffer_size + int _trading_intensity_buffer_size + int _ticks_to_be_ready + object _alpha + object _kappa + object _gamma + object _eta + str _execution_mode + str _execution_timeframe + object _execution_state + object _start_time + object _end_time + double _min_spread + object _q_adjustment_factor + object _reservation_price + object _optimal_spread + object _optimal_bid + object _optimal_ask + str _debug_csv_path + object _avg_vol + TradingIntensityIndicator _trading_intensity + bint _should_wait_order_cancel_confirmation + + cdef object c_get_mid_price(self) + cdef _create_proposal_based_on_order_levels(self) + cdef _create_proposal_based_on_order_override(self) + cdef _create_basic_proposal(self) + cdef object c_create_base_proposal(self) + cdef tuple c_get_adjusted_available_balance(self, list orders) + cdef c_apply_order_price_modifiers(self, object proposal) + cdef c_apply_order_amount_eta_transformation(self, object proposal) + cdef c_apply_budget_constraint(self, object proposal) + cdef c_apply_order_optimization(self, object proposal) + cdef c_apply_add_transaction_costs(self, object proposal) + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices) + cdef c_cancel_active_orders(self, object proposal) + cdef c_cancel_active_orders_on_max_age_limit(self) + cdef bint c_to_create_orders(self, object proposal) + cdef c_execute_orders_proposal(self, object proposal) + cdef c_set_timers(self) + cdef double c_get_spread(self) + cdef c_collect_market_variables(self, double timestamp) + cdef bint c_is_algorithm_ready(self) + cdef bint c_is_algorithm_changed(self) + cdef c_measure_order_book_liquidity(self) + cdef c_calculate_reservation_price_and_optimal_spread(self) + cdef object c_calculate_target_inventory(self) + cdef object c_calculate_inventory(self) + cdef c_did_complete_order(self, object order_completed_event) diff --git a/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pyx b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pyx new file mode 100644 index 0000000..04d5ebe --- /dev/null +++ b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making.pyx @@ -0,0 +1,1406 @@ +import datetime +import logging +import os +import time +from decimal import Decimal +from math import ceil, floor, isnan +from typing import Dict, List, Tuple, Union + +import numpy as np +import pandas as pd + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.clock cimport Clock + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.core.data_type.common import ( + OrderType, + PriceType, + TradeType +) +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.__utils__.trailing_indicators.instant_volatility import InstantVolatilityIndicator +from hummingbot.strategy.__utils__.trailing_indicators.trading_intensity import TradingIntensityIndicator +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, + DailyBetweenTimesModel, + FromDateToDateModel, + MultiOrderLevelModel, + TrackHangingOrdersModel, +) +from hummingbot.strategy.conditional_execution_state import ( + RunAlwaysExecutionState, + RunInTimeConditionalExecutionState +) +from hummingbot.strategy.data_types import ( + PriceSize, + Proposal, +) +from hummingbot.strategy.hanging_orders_tracker import ( + CreatedPairOfOrders, + HangingOrdersTracker, +) +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.order_tracker cimport OrderTracker +from hummingbot.strategy.strategy_base import StrategyBase +from hummingbot.strategy.utils import order_age + +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_neg_one = Decimal(-1) +s_decimal_one = Decimal(1) +pmm_logger = None + + +cdef class AvellanedaMarketMakingStrategy(StrategyBase): + OPTION_LOG_CREATE_ORDER = 1 << 3 + OPTION_LOG_MAKER_ORDER_FILLED = 1 << 4 + OPTION_LOG_STATUS_REPORT = 1 << 5 + OPTION_LOG_ALL = 0x7fffffffffffffff + + @classmethod + def logger(cls): + global pmm_logger + if pmm_logger is None: + pmm_logger = logging.getLogger(__name__) + return pmm_logger + + def init_params(self, + config_map: Union[AvellanedaMarketMakingConfigMap, ClientConfigAdapter], + market_info: MarketTradingPairTuple, + logging_options: int = OPTION_LOG_ALL, + status_report_interval: float = 900, + hb_app_notification: bool = False, + debug_csv_path: str = '', + is_debug: bool = False, + ): + self._sb_order_tracker = OrderTracker() + self._config_map = config_map + self._market_info = market_info + self._price_delegate = OrderBookAssetPriceDelegate(market_info.market, market_info.trading_pair) + self._hb_app_notification = hb_app_notification + self._hanging_orders_enabled = False + self._hanging_orders_cancel_pct = Decimal("10") + self._hanging_orders_tracker = HangingOrdersTracker(self, + self._hanging_orders_cancel_pct / Decimal('100')) + + self._cancel_timestamp = 0 + self._create_timestamp = 0 + self._limit_order_type = self._market_info.market.get_maker_order_type() + self._all_markets_ready = False + self._filled_buys_balance = 0 + self._filled_sells_balance = 0 + self._logging_options = logging_options + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._last_own_trade_price = Decimal('nan') + + self.c_add_markets([market_info.market]) + self._volatility_buffer_size = 0 + self._trading_intensity_buffer_size = 0 + self._ticks_to_be_ready = -1 + self._avg_vol = None + self._trading_intensity = None + self._last_sampling_timestamp = 0 + self._alpha = None + self._kappa = None + self._execution_mode = None + self._execution_timeframe = None + self._execution_state = None + self._start_time = None + self._end_time = None + self._reservation_price = s_decimal_zero + self._optimal_spread = s_decimal_zero + self._optimal_ask = s_decimal_zero + self._optimal_bid = s_decimal_zero + self._debug_csv_path = debug_csv_path + self._is_debug = is_debug + try: + if self._is_debug: + os.unlink(self._debug_csv_path) + except FileNotFoundError: + pass + + self.get_config_map_execution_mode() + self.get_config_map_hanging_orders() + + def all_markets_ready(self): + return all([market.ready for market in self._sb_markets]) + + @property + def min_spread(self): + return self._config_map.min_spread + + @property + def avg_vol(self): + return self._avg_vol + + @avg_vol.setter + def avg_vol(self, indicator: InstantVolatilityIndicator): + self._avg_vol = indicator + + @property + def trading_intensity(self): + return self._trading_intensity + + @trading_intensity.setter + def trading_intensity(self, indicator: TradingIntensityIndicator): + self._trading_intensity = indicator + + @property + def market_info(self) -> MarketTradingPairTuple: + return self._market_info + + @property + def order_refresh_tolerance_pct(self) -> Decimal: + return self._config_map.order_refresh_tolerance_pct + + @property + def order_refresh_tolerance(self) -> Decimal: + return self._config_map.order_refresh_tolerance_pct / Decimal('100') + + @property + def order_amount(self) -> Decimal: + return self._config_map.order_amount + + @property + def inventory_target_base_pct(self) -> Decimal: + return self._config_map.inventory_target_base_pct + + @property + def inventory_target_base(self) -> Decimal: + return self.inventory_target_base_pct / Decimal('100') + + @inventory_target_base.setter + def inventory_target_base(self, value: Decimal): + self.inventory_target_base_pct = value * Decimal('100') + + @property + def order_optimization_enabled(self) -> bool: + return self._config_map.order_optimization_enabled + + @property + def order_refresh_time(self) -> float: + return self._config_map.order_refresh_time + + @property + def filled_order_delay(self) -> float: + return self._config_map.filled_order_delay + + @property + def order_override(self) -> Dict[str, any]: + return self._config_map.order_override + + @property + def order_levels(self) -> int: + if self._config_map.order_levels_mode.title == MultiOrderLevelModel.Config.title: + return self._config_map.order_levels_mode.order_levels + else: + return 0 + + @property + def level_distances(self) -> int: + if self._config_map.order_levels_mode.title == MultiOrderLevelModel.Config.title: + return self._config_map.order_levels_mode.level_distances + else: + return 0 + + @property + def max_order_age(self): + return self._config_map.max_order_age + + @property + def add_transaction_costs_to_orders(self) -> bool: + return self._config_map.add_transaction_costs + + @property + def base_asset(self): + return self._market_info.base_asset + + @property + def quote_asset(self): + return self._market_info.quote_asset + + @property + def trading_pair(self): + return self._market_info.trading_pair + + @property + def gamma(self): + return self._config_map.risk_factor + + @property + def alpha(self): + return self._alpha + + @alpha.setter + def alpha(self, value): + self._alpha = value + + @property + def kappa(self): + return self._kappa + + @kappa.setter + def kappa(self, value): + self._kappa = value + + @property + def eta(self): + return self._config_map.order_amount_shape_factor + + @property + def reservation_price(self): + return self._reservation_price + + @reservation_price.setter + def reservation_price(self, value): + self._reservation_price = value + + @property + def optimal_spread(self): + return self._optimal_spread + + @property + def optimal_ask(self): + return self._optimal_ask + + @optimal_ask.setter + def optimal_ask(self, value): + self._optimal_ask = value + + @property + def optimal_bid(self): + return self._optimal_bid + + @optimal_bid.setter + def optimal_bid(self, value): + self._optimal_bid = value + + @property + def execution_timeframe(self): + return self._execution_timeframe + + @property + def start_time(self) -> time: + return self._start_time + + @start_time.setter + def start_time(self, value): + self._start_time = value + + @property + def end_time(self) -> time: + return self._end_time + + @end_time.setter + def end_time(self, value): + self._end_time = value + + def get_price(self) -> float: + return self.get_mid_price() + + def get_last_price(self) -> float: + return self._market_info.get_last_price() + + def get_mid_price(self) -> float: + return self.c_get_mid_price() + + cdef object c_get_mid_price(self): + return self._price_delegate.get_price_by_type(PriceType.MidPrice) + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @property + def active_orders(self) -> List[LimitOrder]: + if self._market_info not in self.market_info_to_active_orders: + return [] + return self.market_info_to_active_orders[self._market_info] + + @property + def active_non_hanging_orders(self) -> List[LimitOrder]: + orders = [o for o in self.active_orders if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)] + return orders + + @property + def active_buys(self) -> List[LimitOrder]: + return [o for o in self.active_orders if o.is_buy] + + @property + def active_sells(self) -> List[LimitOrder]: + return [o for o in self.active_orders if not o.is_buy] + + @property + def logging_options(self) -> int: + return self._logging_options + + @logging_options.setter + def logging_options(self, int64_t logging_options): + self._logging_options = logging_options + + @property + def hanging_orders_tracker(self): + return self._hanging_orders_tracker + + def update_from_config_map(self): + self.get_config_map_execution_mode() + self.get_config_map_hanging_orders() + self.get_config_map_indicators() + + def get_config_map_execution_mode(self): + try: + execution_mode = self._config_map.execution_timeframe_mode.title + execution_timeframe = self._config_map.execution_timeframe_mode.Config.title + if execution_mode == FromDateToDateModel.Config.title: + start_time = self._config_map.execution_timeframe_mode.start_datetime + end_time = self._config_map.execution_timeframe_mode.end_datetime + execution_state = RunInTimeConditionalExecutionState(start_timestamp=start_time, end_timestamp=end_time) + elif execution_mode == DailyBetweenTimesModel.Config.title: + start_time = self._config_map.execution_timeframe_mode.start_time + end_time = self._config_map.execution_timeframe_mode.end_time + execution_state = RunInTimeConditionalExecutionState(start_timestamp=start_time, end_timestamp=end_time) + else: + start_time = None + end_time = None + execution_state = RunAlwaysExecutionState() + + # Something has changed? + if self._execution_state is None or self._execution_state != execution_state: + self._execution_state = execution_state + self._execution_mode = execution_mode + self._execution_timeframe = execution_timeframe + self._start_time = start_time + self._end_time = end_time + except AttributeError: + # A parameter is missing in the execution timeframe mode configuration + pass + + def get_config_map_hanging_orders(self): + if self._config_map.hanging_orders_mode.title == TrackHangingOrdersModel.Config.title: + hanging_orders_enabled = True + hanging_orders_cancel_pct = self._config_map.hanging_orders_mode.hanging_orders_cancel_pct + else: + hanging_orders_enabled = False + hanging_orders_cancel_pct = Decimal("0") + + if self._hanging_orders_enabled != hanging_orders_enabled: + # Hanging order tracker instance doesn't exist - create from scratch + self._hanging_orders_enabled = hanging_orders_enabled + self._hanging_orders_cancel_pct = hanging_orders_cancel_pct + self._hanging_orders_tracker = HangingOrdersTracker(self, + hanging_orders_cancel_pct / Decimal('100')) + self._hanging_orders_tracker.register_events(self.active_markets) + elif self._hanging_orders_cancel_pct != hanging_orders_cancel_pct: + # Hanging order tracker instance existst - only update variable + self._hanging_orders_cancel_pct = hanging_orders_cancel_pct + self._hanging_orders_tracker.hanging_orders_cancel_pct = hanging_orders_cancel_pct / Decimal('100') + + def get_config_map_indicators(self): + volatility_buffer_size = self._config_map.volatility_buffer_size + trading_intensity_buffer_size = self._config_map.trading_intensity_buffer_size + ticks_to_be_ready_after = max(volatility_buffer_size, trading_intensity_buffer_size) + ticks_to_be_ready_before = max(self._volatility_buffer_size, self._trading_intensity_buffer_size) + + if self._volatility_buffer_size == 0 or self._volatility_buffer_size != volatility_buffer_size: + self._volatility_buffer_size = volatility_buffer_size + + if self._avg_vol is None: + self._avg_vol = InstantVolatilityIndicator(sampling_length=volatility_buffer_size) + else: + self._avg_vol.sampling_length = volatility_buffer_size + + if ( + self._trading_intensity_buffer_size == 0 + or self._trading_intensity_buffer_size != trading_intensity_buffer_size + ): + self._trading_intensity_buffer_size = trading_intensity_buffer_size + if self._trading_intensity is not None: + self._trading_intensity.sampling_length = trading_intensity_buffer_size + + if self._trading_intensity is None and self.market_info.market.ready: + self._trading_intensity = TradingIntensityIndicator( + order_book=self.market_info.order_book, + price_delegate=self._price_delegate, + sampling_length=self._trading_intensity_buffer_size, + ) + + self._ticks_to_be_ready += (ticks_to_be_ready_after - ticks_to_be_ready_before) + if self._ticks_to_be_ready < 0: + self._ticks_to_be_ready = 0 + + def pure_mm_assets_df(self, to_show_current_pct: bool) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self._price_delegate.get_price_by_type(PriceType.MidPrice) + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + base_value = base_balance * float(price) + total_in_quote = base_value + quote_balance + base_ratio = base_value / total_in_quote if total_in_quote > 0 else 0 + quote_ratio = quote_balance / total_in_quote if total_in_quote > 0 else 0 + data = [ + ["", base_asset, quote_asset], + ["Total Balance", round(base_balance, 4), round(quote_balance, 4)], + ["Available Balance", round(available_base_balance, 4), round(available_quote_balance, 4)], + [f"Current Value ({quote_asset})", round(base_value, 4), round(quote_balance, 4)] + ] + if to_show_current_pct: + data.append(["Current %", f"{base_ratio:.1%}", f"{quote_ratio:.1%}"]) + df = pd.DataFrame(data=data) + return df + + def active_orders_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self.get_price() + active_orders = self.active_orders + no_sells = len([o for o in active_orders if not o.is_buy and o.client_order_id and + not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)]) + active_orders.sort(key=lambda x: x.price, reverse=True) + columns = ["Level", "Type", "Price", "Spread", "Amount (Orig)", "Amount (Adj)", "Age"] + data = [] + lvl_buy, lvl_sell = 0, 0 + for idx in range(0, len(active_orders)): + order = active_orders[idx] + is_hanging_order = self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id) + if not is_hanging_order: + if order.is_buy: + level = lvl_buy + 1 + lvl_buy += 1 + else: + level = no_sells - lvl_sell + lvl_sell += 1 + spread = 0 if price == 0 else abs(order.price - price) / price + age = pd.Timestamp(order_age(order, self._current_timestamp), unit='s').strftime('%H:%M:%S') + + amount_orig = self._config_map.order_amount + if is_hanging_order: + amount_orig = order.quantity + level = "hang" + data.append([ + level, + "buy" if order.is_buy else "sell", + float(order.price), + f"{spread:.2%}", + float(amount_orig), + float(order.quantity), + age + ]) + + return pd.DataFrame(data=data, columns=columns) + + def market_status_data_frame(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> pd.DataFrame: + markets_data = [] + markets_columns = ["Exchange", "Market", "Best Bid", "Best Ask", f"MidPrice"] + markets_columns.append('Reservation Price') + markets_columns.append('Optimal Spread') + market_books = [(self._market_info.market, self._market_info.trading_pair)] + for market, trading_pair in market_books: + bid_price = market.get_price(trading_pair, False) + ask_price = market.get_price(trading_pair, True) + ref_price = self.get_price() + markets_data.append([ + market.display_name, + trading_pair, + float(bid_price), + float(ask_price), + float(ref_price), + round(self._reservation_price, 5), + round(self._optimal_spread, 5), + ]) + return pd.DataFrame(data=markets_data, columns=markets_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + if not self._all_markets_ready: + return "Market connectors are not ready." + cdef: + list lines = [] + list warning_lines = [] + warning_lines.extend(self.network_warning([self._market_info])) + + markets_df = self.market_status_data_frame([self._market_info]) + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.pure_mm_assets_df(True)) + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + # See if there are any open orders. + if len(self.active_orders) > 0: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active maker orders."]) + + volatility_pct = self._avg_vol.current_value / float(self.get_price()) * 100.0 + if all((self.gamma, self._alpha, self._kappa, not isnan(volatility_pct))): + lines.extend(["", f" Strategy parameters:", + f" risk_factor(\u03B3)= {self.gamma:.5E}", + f" order_book_intensity_factor(\u0391)= {self._alpha:.5E}", + f" order_book_depth_factor(\u03BA)= {self._kappa:.5E}", + f" volatility= {volatility_pct:.3f}%"]) + if self._execution_state.time_left is not None: + lines.extend([f" time until end of trading cycle = {str(datetime.timedelta(seconds=float(self._execution_state.time_left)//1e3))}"]) + else: + lines.extend([f" time until end of trading cycle = N/A"]) + + warning_lines.extend(self.balance_warning([self._market_info])) + + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def execute_orders_proposal(self, proposal: Proposal): + return self.c_execute_orders_proposal(proposal) + + def cancel_order(self, order_id: str): + return self.c_cancel_order(self._market_info, order_id) + + cdef c_start(self, Clock clock, double timestamp): + StrategyBase.c_start(self, clock, timestamp) + self.update_from_config_map() + self._last_timestamp = timestamp + + self._hanging_orders_tracker.register_events(self.active_markets) + + if self._hanging_orders_enabled: + # start tracking any restored limit order + restored_order_ids = self.c_track_restored_orders(self.market_info) + for order_id in restored_order_ids: + order = next(o for o in self.market_info.market.limit_orders if o.client_order_id == order_id) + if order: + self._hanging_orders_tracker.add_as_hanging_order(order) + + self._execution_state.time_left = self._execution_state.closing_time + + def start(self, clock: Clock, timestamp: float): + self.c_start(clock, timestamp) + + cdef c_stop(self, Clock clock): + self._hanging_orders_tracker.unregister_events(self.active_markets) + StrategyBase.c_stop(self, clock) + + cdef c_tick(self, double timestamp): + StrategyBase.c_tick(self, timestamp) + cdef: + int64_t current_tick = (timestamp // self._status_report_interval) + int64_t last_tick = (self._last_timestamp // self._status_report_interval) + bint should_report_warnings = ((current_tick > last_tick) and + (self._logging_options & self.OPTION_LOG_STATUS_REPORT)) + object proposal + + try: + if not self._all_markets_ready: + self._all_markets_ready = all([mkt.ready for mkt in self._sb_markets]) + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning(f"Markets are not ready. No market making trades are permitted.") + return + + if should_report_warnings: + if not all([mkt.network_status is NetworkStatus.CONNECTED for mkt in self._sb_markets]): + self.logger().warning(f"WARNING: Some markets are not connected or are down at the moment. Market " + f"making may be dangerous when markets or networks are unstable.") + + # Updates settings from config map if changed + self.update_from_config_map() + + self.c_collect_market_variables(timestamp) + + if self.c_is_algorithm_ready(): + if self._create_timestamp <= self._current_timestamp: + # Measure order book liquidity + self.c_measure_order_book_liquidity() + + self._hanging_orders_tracker.process_tick() + + # Needs to be executed at all times to not to have active order leftovers after a trading session ends + self.c_cancel_active_orders_on_max_age_limit() + + # process_tick() is only called if within a trading timeframe + self._execution_state.process_tick(timestamp, self) + + else: + # Only if snapshots are different - for trading intensity - a market order happened + if self.c_is_algorithm_changed(): + self._ticks_to_be_ready -= 1 + if self._ticks_to_be_ready % 5 == 0: + self.logger().info(f"Calculating volatility, estimating order book liquidity ... {self._ticks_to_be_ready} ticks to fill buffers") + else: + self.logger().info(f"Calculating volatility, estimating order book liquidity ... no trades tick") + finally: + self._last_timestamp = timestamp + + def process_tick(self, timestamp: float): + proposal = None + # Trading is allowed + if self._create_timestamp <= self._current_timestamp: + # 1. Calculate reservation price and optimal spread from gamma, alpha, kappa and volatility + self.c_calculate_reservation_price_and_optimal_spread() + # 2. Check if calculated prices make sense + if self._optimal_bid > 0 and self._optimal_ask > 0: + # 3. Create base order proposals + proposal = self.c_create_base_proposal() + # 4. Apply functions that modify orders amount + self.c_apply_order_amount_eta_transformation(proposal) + # 5. Apply functions that modify orders price + self.c_apply_order_price_modifiers(proposal) + # 6. Apply budget constraint, i.e. can't buy/sell more than what you have. + self.c_apply_budget_constraint(proposal) + + self.c_cancel_active_orders(proposal) + + if self.c_to_create_orders(proposal): + self.c_execute_orders_proposal(proposal) + + if self._is_debug: + self.dump_debug_variables() + + cdef c_collect_market_variables(self, double timestamp): + market, trading_pair, base_asset, quote_asset = self._market_info + self._last_sampling_timestamp = timestamp + + price = self.get_price() + self._avg_vol.add_sample(price) + self._trading_intensity.calculate(timestamp) + # Calculate adjustment factor to have 0.01% of inventory resolution + base_balance = market.get_balance(base_asset) + quote_balance = market.get_balance(quote_asset) + inventory_in_base = quote_balance / price + base_balance + + def collect_market_variables(self, timestamp: float): + self.c_collect_market_variables(timestamp) + + cdef double c_get_spread(self): + cdef: + ExchangeBase market = self._market_info.market + str trading_pair = self._market_info.trading_pair + + return market.c_get_price(trading_pair, True) - market.c_get_price(trading_pair, False) + + def get_spread(self): + return self.c_get_spread() + + def get_volatility(self): + vol = Decimal(str(self._avg_vol.current_value)) + return vol + + cdef c_measure_order_book_liquidity(self): + + self._alpha, self._kappa = self._trading_intensity.current_value + + self._alpha = Decimal(self._alpha) + self._kappa = Decimal(self._kappa) + + if self._is_debug: + self.logger().info(f"alpha={self._alpha:.4f} | " + f"kappa={self._kappa:.4f}") + + def measure_order_book_liquidity(self): + return self.c_measure_order_book_liquidity() + + cdef c_calculate_reservation_price_and_optimal_spread(self): + cdef: + ExchangeBase market = self._market_info.market + + # Current mid price + price = self.get_price() + + # The amount of stocks owned - q - has to be in relative units, not absolute, because changing the portfolio size shouldn't change the reservation price + # The reservation price should concern itself only with the strategy performance, i.e. amount of stocks relative to the target + inventory = Decimal(str(self.c_calculate_inventory())) + if inventory == 0: + return + + q_target = Decimal(str(self.c_calculate_target_inventory())) + q = (market.get_balance(self.base_asset) - q_target) / (inventory) + # Volatility has to be in absolute values (prices) because in calculation of reservation price it's not multiplied by the current price, therefore + # it can't be a percentage. The result of the multiplication has to be an absolute price value because it's being subtracted from the current price + vol = self.get_volatility() + + # order book liquidity - kappa and alpha have to represent absolute values because the second member of the optimal spread equation has to be an absolute price + # and from the reservation price calculation we know that gamma's unit is not absolute price + if all((self.gamma, self._kappa)) and self._alpha != 0 and self._kappa > 0 and vol != 0: + if self._execution_state.time_left is not None and self._execution_state.closing_time is not None: + # Avellaneda-Stoikov for a fixed timespan + time_left_fraction = Decimal(str(self._execution_state.time_left / self._execution_state.closing_time)) + else: + # Avellaneda-Stoikov for an infinite timespan + # The equations in the paper for this contain a few mistakes + # - the units don't align with the rest of the paper + # - volatility cancels itself out completely + # - the risk factor gets partially canceled + # The proposed solution is to use the same equation as for the constrained timespan but with + # a fixed time left + time_left_fraction = 1 + + # Here seems to be another mistake in the paper + # It doesn't make sense to use mid_price_variance because its units would be absolute price units ^2, yet that side of the equation is subtracted + # from the actual mid price of the asset in absolute price units + # gamma / risk_factor gains a meaning of a fraction (percentage) of the volatility (standard deviation between ticks) to be subtraced from the + # current mid price + # This leads to normalization of the risk_factor and will guaranetee consistent behavior on all price ranges of the asset, and across assets + + self._reservation_price = price - (q * self.gamma * vol * time_left_fraction) + + self._optimal_spread = self.gamma * vol * time_left_fraction + self._optimal_spread += 2 * Decimal(1 + self.gamma / self._kappa).ln() / self.gamma + + min_spread = price / 100 * Decimal(str(self._config_map.min_spread)) + + max_limit_bid = price - min_spread / 2 + min_limit_ask = price + min_spread / 2 + + self._optimal_ask = max(self._reservation_price + self._optimal_spread / 2, min_limit_ask) + self._optimal_bid = min(self._reservation_price - self._optimal_spread / 2, max_limit_bid) + + # This is not what the algorithm will use as proposed bid and ask. This is just the raw output. + # Optimal bid and optimal ask prices will be used + if self._is_debug: + self.logger().info(f"q={q:.4f} | " + f"vol={vol:.10f}") + self.logger().info(f"mid_price={price:.10f} | " + f"reservation_price={self._reservation_price:.10f} | " + f"optimal_spread={self._optimal_spread:.10f}") + self.logger().info(f"optimal_bid={(price-(self._reservation_price - self._optimal_spread / 2)) / price * 100:.4f}% | " + f"optimal_ask={((self._reservation_price + self._optimal_spread / 2) - price) / price * 100:.4f}%") + + def calculate_reservation_price_and_optimal_spread(self): + return self.c_calculate_reservation_price_and_optimal_spread() + + cdef object c_calculate_target_inventory(self): + cdef: + ExchangeBase market = self._market_info.market + str trading_pair = self._market_info.trading_pair + str base_asset = self._market_info.base_asset + str quote_asset = self._market_info.quote_asset + object mid_price + object base_value + object inventory_value + object target_inventory_value + + price = self.get_price() + base_asset_amount = market.get_balance(base_asset) + quote_asset_amount = market.get_balance(quote_asset) + # Base asset value in quote asset prices + base_value = base_asset_amount * price + # Total inventory value in quote asset prices + inventory_value = base_value + quote_asset_amount + # Target base asset value in quote asset prices + target_inventory_value = inventory_value * self.inventory_target_base + # Target base asset amount + target_inventory_amount = target_inventory_value / price + return market.c_quantize_order_amount(trading_pair, Decimal(str(target_inventory_amount))) + + def calculate_target_inventory(self) -> Decimal: + return self.c_calculate_target_inventory() + + cdef c_calculate_inventory(self): + cdef: + ExchangeBase market = self._market_info.market + str base_asset = self._market_info.base_asset + str quote_asset = self._market_info.quote_asset + object mid_price + object base_value + object inventory_value + object inventory_value_quote + object inventory_value_base + + price = self.get_price() + base_asset_amount = market.get_balance(base_asset) + quote_asset_amount = market.get_balance(quote_asset) + # Base asset value in quote asset prices + base_value = base_asset_amount * price + # Total inventory value in quote asset prices + inventory_value_quote = base_value + quote_asset_amount + # Total inventory value in base asset prices + inventory_value_base = inventory_value_quote / price + return inventory_value_base + + def calculate_inventory(self) -> Decimal: + return self.c_calculate_inventory() + + cdef bint c_is_algorithm_ready(self): + return self._avg_vol.is_sampling_buffer_full and self._trading_intensity.is_sampling_buffer_full + + cdef bint c_is_algorithm_changed(self): + return self._trading_intensity.is_sampling_buffer_changed or self._avg_vol.is_sampling_buffer_changed + + def is_algorithm_ready(self) -> bool: + return self.c_is_algorithm_ready() + + def is_algorithm_changed(self) -> bool: + return self.c_is_algorithm_changed() + + def _get_level_spreads(self): + level_step = ((self._optimal_spread / 2) / 100) * self.level_distances + + bid_level_spreads = [i * level_step for i in range(self.order_levels)] + ask_level_spreads = [i * level_step for i in range(self.order_levels)] + + return bid_level_spreads, ask_level_spreads + + cdef _create_proposal_based_on_order_override(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list sells = [] + reference_price = self.get_price() + if self.order_override is not None: + for key, value in self.order_override.items(): + if str(value[0]) in ["buy", "sell"]: + list_to_be_appended = buys if str(value[0]) == "buy" else sells + size = Decimal(str(value[2])) + size = market.c_quantize_order_amount(self.trading_pair, size) + if str(value[0]) == "buy": + price = reference_price * (Decimal("1") - Decimal(str(value[1])) / Decimal("100")) + elif str(value[0]) == "sell": + price = reference_price * (Decimal("1") + Decimal(str(value[1])) / Decimal("100")) + price = market.c_quantize_order_price(self.trading_pair, price) + if size > 0 and price > 0: + list_to_be_appended.append(PriceSize(price, size)) + return buys, sells + + def create_proposal_based_on_order_override(self) -> Tuple[List[Proposal], List[Proposal]]: + return self._create_proposal_based_on_order_override() + + cdef _create_proposal_based_on_order_levels(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list sells = [] + bid_level_spreads, ask_level_spreads = self._get_level_spreads() + size = market.c_quantize_order_amount(self.trading_pair, self._config_map.order_amount) + if size > 0: + for level in range(self.order_levels): + bid_price = market.c_quantize_order_price(self.trading_pair, + self._optimal_bid - Decimal(str(bid_level_spreads[level]))) + ask_price = market.c_quantize_order_price(self.trading_pair, + self._optimal_ask + Decimal(str(ask_level_spreads[level]))) + + buys.append(PriceSize(bid_price, size)) + sells.append(PriceSize(ask_price, size)) + return buys, sells + + def create_proposal_based_on_order_levels(self): + return self._create_proposal_based_on_order_levels() + + cdef _create_basic_proposal(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list sells = [] + price = market.c_quantize_order_price(self.trading_pair, Decimal(str(self._optimal_bid))) + size = market.c_quantize_order_amount(self.trading_pair, self._config_map.order_amount) + if size > 0: + buys.append(PriceSize(price, size)) + + price = market.c_quantize_order_price(self.trading_pair, Decimal(str(self._optimal_ask))) + size = market.c_quantize_order_amount(self.trading_pair, self._config_map.order_amount) + if size > 0: + sells.append(PriceSize(price, size)) + return buys, sells + + def create_basic_proposal(self): + return self._create_basic_proposal() + + cdef object c_create_base_proposal(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list sells = [] + + if self.order_override is not None and len(self.order_override) > 0: + # If order_override is set, it will override order_levels + buys, sells = self._create_proposal_based_on_order_override() + elif self.order_levels > 0: + # Simple order levels + buys, sells = self._create_proposal_based_on_order_levels() + else: + # No order levels nor order_overrides. Just 1 bid and 1 ask order + buys, sells = self._create_basic_proposal() + + return Proposal(buys, sells) + + def create_base_proposal(self): + return self.c_create_base_proposal() + + cdef tuple c_get_adjusted_available_balance(self, list orders): + """ + Calculates the available balance, plus the amount attributed to orders. + :return: (base amount, quote amount) in Decimal + """ + cdef: + ExchangeBase market = self._market_info.market + object base_balance = market.c_get_available_balance(self.base_asset) + object quote_balance = market.c_get_available_balance(self.quote_asset) + + for order in orders: + if order.is_buy: + quote_balance += order.quantity * order.price + else: + base_balance += order.quantity + + return base_balance, quote_balance + + def get_adjusted_available_balance(self, orders: List[LimitOrder]): + return self.c_get_adjusted_available_balance(orders) + + cdef c_apply_order_price_modifiers(self, object proposal): + if self._config_map.order_optimization_enabled: + self.c_apply_order_optimization(proposal) + + if self._config_map.add_transaction_costs: + self.c_apply_add_transaction_costs(proposal) + + def apply_order_price_modifiers(self, proposal: Proposal): + self.c_apply_order_price_modifiers(proposal) + + def apply_budget_constraint(self, proposal: Proposal): + return self.c_apply_budget_constraint(proposal) + + def adjusted_available_balance_for_orders_budget_constrain(self): + candidate_hanging_orders = self.hanging_orders_tracker.candidate_hanging_orders_from_pairs() + non_hanging = [] + if self.market_info in self._sb_order_tracker.get_limit_orders(): + all_orders = self._sb_order_tracker.get_limit_orders()[self.market_info].values() + non_hanging = [order for order in all_orders + if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id)] + all_non_hanging_orders = list(set(non_hanging) - set(candidate_hanging_orders)) + return self.c_get_adjusted_available_balance(all_non_hanging_orders) + + cdef c_apply_budget_constraint(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object quote_size + object base_size + object adjusted_amount + + base_balance, quote_balance = self.adjusted_available_balance_for_orders_budget_constrain() + + for buy in proposal.buys: + buy_fee = market.c_get_fee(self.base_asset, self.quote_asset, OrderType.LIMIT, TradeType.BUY, + buy.size, buy.price) + quote_size = buy.size * buy.price * (Decimal(1) + buy_fee.percent) + + # Adjust buy order size to use remaining balance if less than the order amount + if quote_balance < quote_size: + adjusted_amount = quote_balance / (buy.price * (Decimal("1") + buy_fee.percent)) + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, adjusted_amount) + buy.size = adjusted_amount + quote_balance = s_decimal_zero + elif quote_balance == s_decimal_zero: + buy.size = s_decimal_zero + else: + quote_balance -= quote_size + + proposal.buys = [o for o in proposal.buys if o.size > 0] + + for sell in proposal.sells: + base_size = sell.size + + # Adjust sell order size to use remaining balance if less than the order amount + if base_balance < base_size: + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, base_balance) + sell.size = adjusted_amount + base_balance = s_decimal_zero + elif base_balance == s_decimal_zero: + sell.size = s_decimal_zero + else: + base_balance -= base_size + + proposal.sells = [o for o in proposal.sells if o.size > 0] + + def apply_budget_constraint(self, proposal: Proposal): + return self.c_apply_budget_constraint(proposal) + + # Compare the market price with the top bid and top ask price + cdef c_apply_order_optimization(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object own_buy_size = s_decimal_zero + object own_sell_size = s_decimal_zero + object best_order_spread + + for order in self.active_orders: + if order.is_buy: + own_buy_size = order.quantity + else: + own_sell_size = order.quantity + + if len(proposal.buys) > 0: + # Get the top bid price in the market using order_optimization_depth and your buy order volume + top_bid_price = self._market_info.get_price_for_volume( + False, own_buy_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_bid_price + ) + # Get the price above the top bid + price_above_bid = (ceil(top_bid_price / price_quantum) + 1) * price_quantum + + # If the price_above_bid is lower than the price suggested by the top pricing proposal, + # lower the price and from there apply the best_order_spread to each order in the next levels + proposal.buys = sorted(proposal.buys, key = lambda p: p.price, reverse = True) + for i, proposed in enumerate(proposal.buys): + if proposal.buys[i].price > price_above_bid: + proposal.buys[i].price = market.c_quantize_order_price(self.trading_pair, price_above_bid) + + if len(proposal.sells) > 0: + # Get the top ask price in the market using order_optimization_depth and your sell order volume + top_ask_price = self._market_info.get_price_for_volume( + True, own_sell_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_ask_price + ) + # Get the price below the top ask + price_below_ask = (floor(top_ask_price / price_quantum) - 1) * price_quantum + + # If the price_below_ask is higher than the price suggested by the pricing proposal, + # increase your price and from there apply the best_order_spread to each order in the next levels + proposal.sells = sorted(proposal.sells, key = lambda p: p.price) + for i, proposed in enumerate(proposal.sells): + if proposal.sells[i].price < price_below_ask: + proposal.sells[i].price = market.c_quantize_order_price(self.trading_pair, price_below_ask) + + def apply_order_optimization(self, proposal: Proposal): + return self.c_apply_order_optimization(proposal) + + cdef c_apply_order_amount_eta_transformation(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + str trading_pair = self._market_info.trading_pair + + # Order amounts should be changed only if order_override is not active + if (self.order_override is None) or (len(self.order_override) == 0): + # eta parameter is described in the paper as the shape parameter for having exponentially decreasing order amount + # for orders that go against inventory target (i.e. Want to buy when excess inventory or sell when deficit inventory) + + # q cannot be in absolute values - cannot be dependent on the size of the inventory or amount of base asset + # because it's a scaling factor + inventory = Decimal(str(self.c_calculate_inventory())) + + if inventory == 0: + return + + q_target = Decimal(str(self.c_calculate_target_inventory())) + q = (market.get_balance(self.base_asset) - q_target) / (inventory) + + if len(proposal.buys) > 0: + if q > 0: + for i, proposed in enumerate(proposal.buys): + + proposal.buys[i].size = market.c_quantize_order_amount(trading_pair, proposal.buys[i].size * Decimal.exp(-self.eta * q)) + proposal.buys = [o for o in proposal.buys if o.size > 0] + + if len(proposal.sells) > 0: + if q < 0: + for i, proposed in enumerate(proposal.sells): + proposal.sells[i].size = market.c_quantize_order_amount(trading_pair, proposal.sells[i].size * Decimal.exp(self.eta * q)) + proposal.sells = [o for o in proposal.sells if o.size > 0] + + def apply_order_amount_eta_transformation(self, proposal: Proposal): + self.c_apply_order_amount_eta_transformation(proposal) + + cdef c_apply_add_transaction_costs(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + for buy in proposal.buys: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.BUY, buy.size, buy.price) + price = buy.price * (Decimal(1) - fee.percent) + buy.price = market.c_quantize_order_price(self.trading_pair, price) + for sell in proposal.sells: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.SELL, sell.size, sell.price) + price = sell.price * (Decimal(1) + fee.percent) + sell.price = market.c_quantize_order_price(self.trading_pair, price) + + def apply_add_transaction_costs(self, proposal: Proposal): + self.c_apply_add_transaction_costs(proposal) + + cdef c_did_fill_order(self, object order_filled_event): + cdef: + str order_id = order_filled_event.order_id + object market_info = self._sb_order_tracker.c_get_shadow_market_pair_from_order_id(order_id) + tuple order_fill_record + + if market_info is not None: + limit_order_record = self._sb_order_tracker.c_get_shadow_limit_order(order_id) + order_fill_record = (limit_order_record, order_filled_event) + + if order_filled_event.trade_type is TradeType.BUY: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker buy order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + else: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker sell order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + + cdef c_did_complete_buy_order(self, object order_completed_event): + self.c_did_complete_order(order_completed_event) + + cdef c_did_complete_sell_order(self, object order_completed_event): + self.c_did_complete_order(order_completed_event) + + cdef c_did_complete_order(self, object order_completed_event): + cdef: + str order_id = order_completed_event.order_id + LimitOrder limit_order_record = self._sb_order_tracker.c_get_limit_order(self._market_info, order_id) + + if limit_order_record is None: + return + + # Continue only if the order is not a hanging order + if (not self._hanging_orders_tracker.is_order_id_in_hanging_orders(order_id) + and not self.hanging_orders_tracker.is_order_id_in_completed_hanging_orders(order_id)): + # delay order creation by filled_order_delay (in seconds) + self._create_timestamp = self._current_timestamp + self.filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + if limit_order_record.is_buy: + self._filled_buys_balance += 1 + order_action_string = "buy" + else: + self._filled_sells_balance += 1 + order_action_string = "sell" + + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker {order_action_string} order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker {order_action_string.upper()} order " + f"{limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices): + if len(current_prices) != len(proposal_prices): + return False + current_prices = sorted(current_prices) + proposal_prices = sorted(proposal_prices) + for current, proposal in zip(current_prices, proposal_prices): + # if spread diff is more than the tolerance or order quantities are different, return false. + if abs(proposal - current) / current > self.order_refresh_tolerance: + return False + return True + + def is_within_tolerance(self, current_prices: List[Decimal], proposal_prices: List[Decimal]) -> bool: + return self.c_is_within_tolerance(current_prices, proposal_prices) + + cdef c_cancel_active_orders_on_max_age_limit(self): + """ + Cancels active non hanging orders if they are older than max age limit + """ + cdef: + list active_orders = self.active_non_hanging_orders + for order in active_orders: + if order_age(order, self._current_timestamp) > self._config_map.max_order_age: + self.c_cancel_order(self._market_info, order.client_order_id) + + cdef c_cancel_active_orders(self, object proposal): + if self._cancel_timestamp > self._current_timestamp: + return + + cdef: + list active_buy_prices = [] + list active_sells = [] + bint to_defer_canceling = False + + if len(self.active_non_hanging_orders) == 0: + return + if proposal is not None: + active_buy_prices = [Decimal(str(o.price)) for o in self.active_non_hanging_orders if o.is_buy] + active_sell_prices = [Decimal(str(o.price)) for o in self.active_non_hanging_orders if not o.is_buy] + proposal_buys = [buy.price for buy in proposal.buys] + proposal_sells = [sell.price for sell in proposal.sells] + + if self.c_is_within_tolerance(active_buy_prices, proposal_buys) and \ + self.c_is_within_tolerance(active_sell_prices, proposal_sells): + to_defer_canceling = True + + if not to_defer_canceling: + self._hanging_orders_tracker.update_strategy_orders_with_equivalent_orders() + for order in self.active_non_hanging_orders: + # If is about to be added to hanging_orders then don't cancel + if not self._hanging_orders_tracker.is_potential_hanging_order(order): + self.c_cancel_order(self._market_info, order.client_order_id) + else: + self.c_set_timers() + + def cancel_active_orders(self, proposal: Proposal = None): + return self.c_cancel_active_orders(proposal) + + cdef bint c_to_create_orders(self, object proposal): + non_hanging_orders_non_cancelled = [o for o in self.active_non_hanging_orders if not + self._hanging_orders_tracker.is_potential_hanging_order(o)] + + return (self._create_timestamp < self._current_timestamp + and (not self._config_map.should_wait_order_cancel_confirmation or + len(self._sb_order_tracker.in_flight_cancels) == 0) + and proposal is not None + and len(non_hanging_orders_non_cancelled) == 0) + + def to_create_orders(self, proposal: Proposal) -> bool: + return self.c_to_create_orders(proposal) + + cdef c_execute_orders_proposal(self, object proposal): + cdef: + double expiration_seconds = NaN + str bid_order_id, ask_order_id + bint orders_created = False + # Number of pair of orders to track for hanging orders + number_of_pairs = min((len(proposal.buys), len(proposal.sells))) if self._hanging_orders_enabled else 0 + + if len(proposal.buys) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{buy.size.normalize()} {self.base_asset}, " + f"{buy.price.normalize()} {self.quote_asset}" + for buy in proposal.buys] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.buys)} bid orders " + f"at (Size, Price): {price_quote_str}" + ) + for idx, buy in enumerate(proposal.buys): + bid_order_id = self.c_buy_with_specific_market( + self._market_info, + buy.size, + order_type=self._limit_order_type, + price=buy.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == bid_order_id)) + if order: + self._hanging_orders_tracker.add_current_pairs_of_proposal_orders_executed_by_strategy( + CreatedPairOfOrders(order, None)) + if len(proposal.sells) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{sell.size.normalize()} {self.base_asset}, " + f"{sell.price.normalize()} {self.quote_asset}" + for sell in proposal.sells] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.sells)} ask " + f"orders at (Size, Price): {price_quote_str}" + ) + for idx, sell in enumerate(proposal.sells): + ask_order_id = self.c_sell_with_specific_market( + self._market_info, + sell.size, + order_type=self._limit_order_type, + price=sell.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == ask_order_id)) + if order: + self._hanging_orders_tracker.current_created_pairs_of_orders[idx].sell_order = order + if orders_created: + self.c_set_timers() + + def execute_orders_proposal(self, proposal: Proposal): + self.c_execute_orders_proposal(proposal) + + cdef c_set_timers(self): + cdef double next_cycle = self._current_timestamp + self.order_refresh_time + if self._create_timestamp <= self._current_timestamp: + self._create_timestamp = next_cycle + if self._cancel_timestamp <= self._current_timestamp: + self._cancel_timestamp = min(self._create_timestamp, next_cycle) + + def set_timers(self): + self.c_set_timers() + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + def dump_debug_variables(self): + market = self._market_info.market + mid_price = self.get_price() + spread = Decimal(str(self.c_get_spread())) + + best_ask = mid_price + spread / 2 + new_ask = self._reservation_price + self._optimal_spread / 2 + best_bid = mid_price - spread / 2 + new_bid = self._reservation_price - self._optimal_spread / 2 + + vol = self.get_volatility() + mid_price_variance = vol ** 2 + + if not os.path.exists(self._debug_csv_path): + df_header = pd.DataFrame([('mid_price', + 'best_bid', + 'best_ask', + 'reservation_price', + 'optimal_spread', + 'optimal_bid', + 'optimal_ask', + 'optimal_bid_to_mid_%', + 'optimal_ask_to_mid_%', + 'current_inv', + 'target_inv', + 'time_left_fraction', + 'mid_price std_dev', + 'risk_factor', + 'gamma', + 'alpha', + 'kappa', + 'eta', + 'volatility', + 'mid_price_variance', + 'inventory_target_pct')]) + df_header.to_csv(self._debug_csv_path, mode='a', header=False, index=False) + + if self._execution_state.time_left is not None and self._execution_state.closing_time is not None: + time_left_fraction = self._execution_state.time_left / self._execution_state.closing_time + else: + time_left_fraction = None + + df = pd.DataFrame([(mid_price, + best_bid, + best_ask, + self._reservation_price, + self._optimal_spread, + self._optimal_bid, + self._optimal_ask, + (mid_price - (self._reservation_price - self._optimal_spread / 2)) / mid_price, + ((self._reservation_price + self._optimal_spread / 2) - mid_price) / mid_price, + market.get_balance(self.base_asset), + self.c_calculate_target_inventory(), + time_left_fraction, + self._avg_vol.current_value, + self.gamma, + self._alpha, + self._kappa, + self.eta, + vol, + mid_price_variance, + self.inventory_target_base_pct)]) + df.to_csv(self._debug_csv_path, mode='a', header=False, index=False) diff --git a/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making_config_map_pydantic.py b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making_config_map_pydantic.py new file mode 100644 index 0000000..7876876 --- /dev/null +++ b/hummingbot/strategy/avellaneda_market_making/avellaneda_market_making_config_map_pydantic.py @@ -0,0 +1,481 @@ +from datetime import datetime, time +from decimal import Decimal +from typing import Dict, Optional, Union + +from pydantic import Field, root_validator, validator + +from hummingbot.client.config.config_data_types import BaseClientModel, ClientFieldData +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_datetime_iso_string, + validate_decimal, + validate_int, + validate_time_iso_string, +) +from hummingbot.client.config.strategy_config_data_types import BaseTradingStrategyConfigMap +from hummingbot.client.settings import required_exchanges +from hummingbot.connector.utils import split_hb_trading_pair + + +class InfiniteModel(BaseClientModel): + class Config: + title = "infinite" + + +class FromDateToDateModel(BaseClientModel): + start_datetime: datetime = Field( + default=..., + description="The start date and time for date-to-date execution timeframe.", + client_data=ClientFieldData( + prompt=lambda mi: "Please enter the start date and time (YYYY-MM-DD HH:MM:SS)", + prompt_on_new=True, + ), + ) + end_datetime: datetime = Field( + default=..., + description="The end date and time for date-to-date execution timeframe.", + client_data=ClientFieldData( + prompt=lambda mi: "Please enter the end date and time (YYYY-MM-DD HH:MM:SS)", + prompt_on_new=True, + ), + ) + + class Config: + title = "from_date_to_date" + + @validator("start_datetime", "end_datetime", pre=True) + def validate_execution_time(cls, v: Union[str, datetime]) -> Optional[str]: + if not isinstance(v, str): + v = v.strftime("%Y-%m-%d %H:%M:%S") + ret = validate_datetime_iso_string(v) + if ret is not None: + raise ValueError(ret) + return v + + +class DailyBetweenTimesModel(BaseClientModel): + start_time: time = Field( + default=..., + description="The start time for daily-between-times execution timeframe.", + client_data=ClientFieldData( + prompt=lambda mi: "Please enter the start time (HH:MM:SS)", + prompt_on_new=True, + ), + ) + end_time: time = Field( + default=..., + description="The end time for daily-between-times execution timeframe.", + client_data=ClientFieldData( + prompt=lambda mi: "Please enter the end time (HH:MM:SS)", + prompt_on_new=True, + ), + ) + + class Config: + title = "daily_between_times" + + @validator("start_time", "end_time", pre=True) + def validate_execution_time(cls, v: Union[str, datetime]) -> Optional[str]: + if not isinstance(v, str): + v = v.strftime("%H:%M:%S") + ret = validate_time_iso_string(v) + if ret is not None: + raise ValueError(ret) + return v + + +EXECUTION_TIMEFRAME_MODELS = { + InfiniteModel.Config.title: InfiniteModel, + FromDateToDateModel.Config.title: FromDateToDateModel, + DailyBetweenTimesModel.Config.title: DailyBetweenTimesModel, +} + + +class SingleOrderLevelModel(BaseClientModel): + class Config: + title = "single_order_level" + + +class MultiOrderLevelModel(BaseClientModel): + order_levels: int = Field( + default=2, + description="The number of orders placed on either side of the order book.", + ge=2, + client_data=ClientFieldData( + prompt=lambda mi: "How many orders do you want to place on both sides?", + prompt_on_new=True, + ), + ) + level_distances: Decimal = Field( + default=Decimal("0"), + description="The spread between order levels, expressed in % of optimal spread.", + ge=0, + client_data=ClientFieldData( + prompt=lambda mi: "How far apart in % of optimal spread should orders on one side be?", + prompt_on_new=True, + ), + ) + + class Config: + title = "multi_order_level" + + @validator("order_levels", pre=True) + def validate_int_zero_or_above(cls, v: str): + ret = validate_int(v, min_value=2) + if ret is not None: + raise ValueError(ret) + return v + + @validator("level_distances", pre=True) + def validate_decimal_zero_or_above(cls, v: str): + ret = validate_decimal(v, min_value=Decimal("0"), inclusive=True) + if ret is not None: + raise ValueError(ret) + return v + + +ORDER_LEVEL_MODELS = { + SingleOrderLevelModel.Config.title: SingleOrderLevelModel, + MultiOrderLevelModel.Config.title: MultiOrderLevelModel, +} + + +class TrackHangingOrdersModel(BaseClientModel): + hanging_orders_cancel_pct: Decimal = Field( + default=Decimal("10"), + description="The spread percentage at which hanging orders will be cancelled.", + gt=0, + lt=100, + client_data=ClientFieldData( + prompt=lambda mi: ( + "At what spread percentage (from mid price) will hanging orders be canceled?" + " (Enter 1 to indicate 1%)" + ), + ) + ) + + class Config: + title = "track_hanging_orders" + + @validator("hanging_orders_cancel_pct", pre=True) + def validate_pct_exclusive(cls, v: str): + ret = validate_decimal(v, min_value=Decimal("0"), max_value=Decimal("100"), inclusive=False) + if ret is not None: + raise ValueError(ret) + return v + + +class IgnoreHangingOrdersModel(BaseClientModel): + class Config: + title = "ignore_hanging_orders" + + +HANGING_ORDER_MODELS = { + TrackHangingOrdersModel.Config.title: TrackHangingOrdersModel, + IgnoreHangingOrdersModel.Config.title: IgnoreHangingOrdersModel, +} + + +class AvellanedaMarketMakingConfigMap(BaseTradingStrategyConfigMap): + strategy: str = Field(default="avellaneda_market_making", client_data=None) + execution_timeframe_mode: Union[InfiniteModel, FromDateToDateModel, DailyBetweenTimesModel] = Field( + default=..., + description="The execution timeframe.", + client_data=ClientFieldData( + prompt=lambda mi: f"Select the execution timeframe ({'/'.join(EXECUTION_TIMEFRAME_MODELS.keys())})", + prompt_on_new=True, + ), + ) + order_amount: Decimal = Field( + default=..., + description="The strategy order amount.", + gt=0, + client_data=ClientFieldData( + prompt=lambda mi: AvellanedaMarketMakingConfigMap.order_amount_prompt(mi), + prompt_on_new=True, + ) + ) + order_optimization_enabled: bool = Field( + default=True, + description=( + "Allows the bid and ask order prices to be adjusted based on" + " the current top bid and ask prices in the market." + ), + client_data=ClientFieldData( + prompt=lambda mi: "Do you want to enable best bid ask jumping? (Yes/No)" + ), + ) + risk_factor: Decimal = Field( + default=Decimal("1"), + description="The risk factor (\u03B3).", + gt=0, + client_data=ClientFieldData( + prompt=lambda mi: "Enter risk factor (\u03B3)", + prompt_on_new=True, + ), + ) + order_amount_shape_factor: Decimal = Field( + default=Decimal("0"), + description="The amount shape factor (\u03b7)", + ge=0, + le=1, + client_data=ClientFieldData( + prompt=lambda mi: "Enter order amount shape factor (\u03B7)", + ), + ) + min_spread: Decimal = Field( + default=Decimal("0"), + description="The minimum spread limit as percentage of the mid price.", + ge=0, + client_data=ClientFieldData( + prompt=lambda mi: "Enter minimum spread limit (as % of mid price)", + ), + ) + order_refresh_time: float = Field( + default=..., + description="The frequency at which the orders' spreads will be re-evaluated.", + gt=0., + client_data=ClientFieldData( + prompt=lambda mi: "How often do you want to cancel and replace bids and asks (in seconds)?", + prompt_on_new=True, + ), + ) + max_order_age: float = Field( + default=1800., + description="A given order's maximum lifetime irrespective of spread.", + gt=0., + client_data=ClientFieldData( + prompt=lambda mi: ( + "How long do you want to cancel and replace bids and asks with the same price (in seconds)?" + ), + ), + ) + order_refresh_tolerance_pct: Decimal = Field( + default=Decimal("0"), + description=( + "The range of spreads tolerated on refresh cycles." + " Orders over that range are cancelled and re-submitted." + ), + ge=-10, + le=10, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Enter the percent change in price needed to refresh orders at each cycle" + " (Enter 1 to indicate 1%)" + ) + ), + ) + filled_order_delay: float = Field( + default=60., + description="The delay before placing a new order after an order fill.", + gt=0., + client_data=ClientFieldData( + prompt=lambda mi: ( + "How long do you want to wait before placing the next order" + " if your order gets filled (in seconds)?" + ) + ), + ) + inventory_target_base_pct: Decimal = Field( + default=Decimal("50"), + description="Defines the inventory target for the base asset.", + ge=0, + le=100, + client_data=ClientFieldData( + prompt=lambda mi: "What is the inventory target for the base asset? Enter 50 for 50%", + prompt_on_new=True, + ), + ) + add_transaction_costs: bool = Field( + default=False, + description="If activated, transaction costs will be added to order prices.", + client_data=ClientFieldData( + prompt=lambda mi: "Do you want to add transaction costs automatically to order prices? (Yes/No)", + ), + ) + volatility_buffer_size: int = Field( + default=200, + description="The number of ticks that will be stored to calculate volatility.", + ge=1, + le=10_000, + client_data=ClientFieldData( + prompt=lambda mi: "Enter amount of ticks that will be stored to estimate order book liquidity", + ), + ) + trading_intensity_buffer_size: int = Field( + default=200, + description="The number of ticks that will be stored to calculate order book liquidity.", + ge=1, + le=10_000, + client_data=ClientFieldData( + prompt=lambda mi: "Enter amount of ticks that will be stored to estimate order book liquidity", + ), + ) + order_levels_mode: Union[SingleOrderLevelModel, MultiOrderLevelModel] = Field( + default=SingleOrderLevelModel.construct(), + description="Allows activating multi-order levels.", + client_data=ClientFieldData( + prompt=lambda mi: f"Select the order levels mode ({'/'.join(list(ORDER_LEVEL_MODELS.keys()))})", + ), + ) + order_override: Optional[Dict] = Field( + default=None, + description="Allows custom specification of the order levels and their spreads and amounts.", + client_data=None, + ) + hanging_orders_mode: Union[IgnoreHangingOrdersModel, TrackHangingOrdersModel] = Field( + default=IgnoreHangingOrdersModel(), + description="When tracking hanging orders, the orders on the side opposite to the filled orders remain active.", + client_data=ClientFieldData( + prompt=( + lambda mi: f"How do you want to handle hanging orders? ({'/'.join(list(HANGING_ORDER_MODELS.keys()))})" + ), + ), + ) + should_wait_order_cancel_confirmation: bool = Field( + default=True, + description=( + "If activated, the strategy will await cancellation confirmation from the exchange" + " before placing a new order." + ), + client_data=ClientFieldData( + prompt=lambda mi: ( + "Should the strategy wait to receive a confirmation for orders cancellation" + " before creating a new set of orders?" + " (Not waiting requires enough available balance) (Yes/No)" + ), + ) + ) + + class Config: + title = "avellaneda_market_making" + + # === prompts === + + @classmethod + def order_amount_prompt(cls, model_instance: 'AvellanedaMarketMakingConfigMap') -> str: + trading_pair = model_instance.market + base_asset, quote_asset = split_hb_trading_pair(trading_pair) + return f"What is the amount of {base_asset} per order?" + + # === specific validations === + + @validator("execution_timeframe_mode", pre=True) + def validate_execution_timeframe( + cls, v: Union[str, InfiniteModel, FromDateToDateModel, DailyBetweenTimesModel] + ): + if isinstance(v, (InfiniteModel, FromDateToDateModel, DailyBetweenTimesModel, Dict)): + sub_model = v + elif v not in EXECUTION_TIMEFRAME_MODELS: + raise ValueError( + f"Invalid timeframe, please choose value from {list(EXECUTION_TIMEFRAME_MODELS.keys())}" + ) + else: + sub_model = EXECUTION_TIMEFRAME_MODELS[v].construct() + return sub_model + + @validator("order_refresh_tolerance_pct", pre=True) + def validate_order_refresh_tolerance_pct(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_decimal(v, min_value=Decimal("-10"), max_value=Decimal("10"), inclusive=True) + if ret is not None: + raise ValueError(ret) + return v + + @validator("volatility_buffer_size", "trading_intensity_buffer_size", pre=True) + def validate_buffer_size(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_int(v, 1, 10_000) + if ret is not None: + raise ValueError(ret) + return v + + @validator("order_levels_mode", pre=True) + def validate_order_levels_mode(cls, v: Union[str, SingleOrderLevelModel, MultiOrderLevelModel]): + if isinstance(v, (SingleOrderLevelModel, MultiOrderLevelModel, Dict)): + sub_model = v + elif v not in ORDER_LEVEL_MODELS: + raise ValueError( + f"Invalid order levels mode, please choose value from {list(ORDER_LEVEL_MODELS.keys())}." + ) + else: + sub_model = ORDER_LEVEL_MODELS[v].construct() + return sub_model + + @validator("hanging_orders_mode", pre=True) + def validate_hanging_orders_mode(cls, v: Union[str, IgnoreHangingOrdersModel, TrackHangingOrdersModel]): + if isinstance(v, (TrackHangingOrdersModel, IgnoreHangingOrdersModel, Dict)): + sub_model = v + elif v not in HANGING_ORDER_MODELS: + raise ValueError( + f"Invalid hanging order mode, please choose value from {list(HANGING_ORDER_MODELS.keys())}." + ) + else: + sub_model = HANGING_ORDER_MODELS[v].construct() + return sub_model + + # === generic validations === + + @validator( + "order_optimization_enabled", + "add_transaction_costs", + "should_wait_order_cancel_confirmation", + pre=True, + ) + def validate_bool(cls, v: str): + """Used for client-friendly error output.""" + if isinstance(v, str): + ret = validate_bool(v) + if ret is not None: + raise ValueError(ret) + return v + + @validator("order_amount_shape_factor", pre=True) + def validate_decimal_from_zero_to_one(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_decimal(v, min_value=Decimal("0"), max_value=Decimal("1"), inclusive=True) + if ret is not None: + raise ValueError(ret) + return v + + @validator( + "order_amount", + "risk_factor", + "order_refresh_time", + "max_order_age", + "filled_order_delay", + pre=True, + ) + def validate_decimal_above_zero(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_decimal(v, min_value=Decimal("0"), inclusive=False) + if ret is not None: + raise ValueError(ret) + return v + + @validator("min_spread", pre=True) + def validate_decimal_zero_or_above(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_decimal(v, min_value=Decimal("0"), inclusive=True) + if ret is not None: + raise ValueError(ret) + return v + + @validator("inventory_target_base_pct", pre=True) + def validate_pct_inclusive(cls, v: str): + """Used for client-friendly error output.""" + ret = validate_decimal(v, min_value=Decimal("0"), max_value=Decimal("100"), inclusive=True) + if ret is not None: + raise ValueError(ret) + return v + + # === post-validations === + + @root_validator(skip_on_failure=True) + def post_validations(cls, values: Dict): + cls.exchange_post_validation(values) + return values + + @classmethod + def exchange_post_validation(cls, values: Dict): + required_exchanges.add(values["exchange"]) diff --git a/hummingbot/strategy/avellaneda_market_making/start.py b/hummingbot/strategy/avellaneda_market_making/start.py new file mode 100644 index 0000000..6b228b9 --- /dev/null +++ b/hummingbot/strategy/avellaneda_market_making/start.py @@ -0,0 +1,42 @@ +import os.path +from typing import List, Tuple + +import pandas as pd + +from hummingbot import data_path +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.strategy.avellaneda_market_making import AvellanedaMarketMakingStrategy +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + try: + c_map = self.strategy_config_map + exchange = c_map.exchange + raw_trading_pair = c_map.market + + trading_pair: str = raw_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(exchange, [trading_pair])[0] + market_names: List[Tuple[str, List[str]]] = [(exchange, [trading_pair])] + self._initialize_markets(market_names) + maker_data = [self.markets[exchange], trading_pair] + list(maker_assets) + self.market_trading_pair_tuples = [MarketTradingPairTuple(*maker_data)] + + strategy_logging_options = AvellanedaMarketMakingStrategy.OPTION_LOG_ALL + + debug_csv_path = os.path.join(data_path(), + HummingbotApplication.main_application().strategy_file_name.rsplit('.', 1)[0] + + f"_{pd.Timestamp.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv") + + self.strategy = AvellanedaMarketMakingStrategy() + self.strategy.init_params( + config_map=c_map, + market_info=MarketTradingPairTuple(*maker_data), + logging_options=strategy_logging_options, + hb_app_notification=True, + debug_csv_path=debug_csv_path, + is_debug=False + ) + except Exception as e: + self.notify(str(e)) + self.logger().error("Unknown error during initialization.", exc_info=True) diff --git a/hummingbot/strategy/conditional_execution_state.py b/hummingbot/strategy/conditional_execution_state.py new file mode 100644 index 0000000..e409250 --- /dev/null +++ b/hummingbot/strategy/conditional_execution_state.py @@ -0,0 +1,126 @@ +from abc import ABC, abstractmethod +from datetime import datetime, time +from typing import Union + +from hummingbot.strategy.strategy_base import StrategyBase + + +class ConditionalExecutionState(ABC): + """ + This class hierarchy models different execution conditions that can be used to alter the normal + response from strategies to the tick (or c_tick) message. + The default subclass is RunAlwaysExecutionState + """ + + _closing_time: int = None + _time_left: int = None + + def __eq__(self, other): + return type(self) == type(other) + + @property + def time_left(self): + return self._time_left + + @time_left.setter + def time_left(self, value): + self._time_left = value + + @property + def closing_time(self): + return self._closing_time + + @closing_time.setter + def closing_time(self, value): + self._closing_time = value + + @abstractmethod + def process_tick(self, timestamp: float, strategy: StrategyBase): + pass + + +class RunAlwaysExecutionState(ConditionalExecutionState): + """ + Execution configuration to always run the strategy for every tick + """ + + def __str__(self): + return "run continuously" + + def process_tick(self, timestamp: float, strategy: StrategyBase): + self._closing_time = None + self._time_left = None + strategy.process_tick(timestamp) + + +class RunInTimeConditionalExecutionState(ConditionalExecutionState): + """ + Execution configuration to always run the strategy only for the ticks that happen between the specified start + timestamp and stop timestamp + :param start_timestamp: Specifies the moment to start running the strategy (datetime or datetime.time) + :param end_timestamp: Specifies the moment to stop running the strategy (datetime or datetime.time) + """ + + def __init__(self, start_timestamp: Union[datetime, time], end_timestamp: Union[datetime, time] = None): + super().__init__() + + self._start_timestamp: Union[datetime, time] = start_timestamp + self._end_timestamp: Union[datetime, time] = end_timestamp + + def __str__(self): + if type(self._start_timestamp) is datetime: + if self._end_timestamp is not None: + return f"run between {self._start_timestamp} and {self._end_timestamp}" + else: + return f"run from {self._start_timestamp}" + if type(self._start_timestamp) is time: + if self._end_timestamp is not None: + return f"run daily between {self._start_timestamp} and {self._end_timestamp}" + + def __eq__(self, other): + return type(self) == type(other) and \ + self._start_timestamp == other._start_timestamp and \ + self._end_timestamp == other._end_timestamp + + def process_tick(self, timestamp: float, strategy: StrategyBase): + if isinstance(self._start_timestamp, datetime): + # From datetime + # From datetime to datetime + if self._end_timestamp is not None: + + self._closing_time = (self._end_timestamp.timestamp() - self._start_timestamp.timestamp()) * 1000 + + if self._start_timestamp.timestamp() <= timestamp < self._end_timestamp.timestamp(): + self._time_left = max((self._end_timestamp.timestamp() - timestamp) * 1000, 0) + strategy.process_tick(timestamp) + else: + self._time_left = 0 + strategy.cancel_active_orders() + strategy.logger().debug("Time span execution: tick will not be processed " + f"(executing between {self._start_timestamp.isoformat(sep=' ')} " + f"and {self._end_timestamp.isoformat(sep=' ')})") + else: + self._closing_time = None + self._time_left = None + if self._start_timestamp.timestamp() <= timestamp: + strategy.process_tick(timestamp) + else: + strategy.cancel_active_orders() + strategy.logger().debug("Delayed start execution: tick will not be processed " + f"(executing from {self._start_timestamp.isoformat(sep=' ')})") + if isinstance(self._start_timestamp, time): + # Daily between times + if self._end_timestamp is not None: + + self._closing_time = (datetime.combine(datetime.today(), self._end_timestamp) - datetime.combine(datetime.today(), self._start_timestamp)).total_seconds() * 1000 + current_time = datetime.fromtimestamp(timestamp).time() + + if self._start_timestamp <= current_time < self._end_timestamp: + self._time_left = max((datetime.combine(datetime.today(), self._end_timestamp) - datetime.combine(datetime.today(), current_time)).total_seconds() * 1000, 0) + strategy.process_tick(timestamp) + else: + self._time_left = 0 + strategy.cancel_active_orders() + strategy.logger().debug("Time span execution: tick will not be processed " + f"(executing between {self._start_timestamp} " + f"and {self._end_timestamp})") diff --git a/hummingbot/strategy/cross_exchange_market_making/__init__.py b/hummingbot/strategy/cross_exchange_market_making/__init__.py new file mode 100644 index 0000000..d61af10 --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/__init__.py @@ -0,0 +1,8 @@ +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair + +from .cross_exchange_market_making import CrossExchangeMarketMakingStrategy + +__all__ = [ + MakerTakerMarketPair, + CrossExchangeMarketMakingStrategy, +] diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.py b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.py new file mode 100755 index 0000000..f47fbec --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.py @@ -0,0 +1,1824 @@ +import logging +from collections import defaultdict, deque +from decimal import Decimal +from enum import Enum +from functools import lru_cache +from math import ceil, floor +from typing import Dict, List, Tuple, cast + +import pandas as pd +from bidict import bidict + +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderFilledEvent, + SellOrderCompletedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + CrossExchangeMarketMakingConfigMap, + PassiveOrderRefreshMode, +) +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +from .order_id_market_pair_tracker import OrderIDMarketPairTracker + +s_float_nan = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_nan = Decimal("nan") +s_logger = None + + +class LogOption(Enum): + NULL_ORDER_SIZE = 0 + REMOVING_ORDER = 1 + ADJUST_ORDER = 2 + CREATE_ORDER = 3 + MAKER_ORDER_FILLED = 4 + STATUS_REPORT = 5 + MAKER_ORDER_HEDGED = 6 + + +class CrossExchangeMarketMakingStrategy(StrategyPyBase): + + OPTION_LOG_ALL = ( + LogOption.NULL_ORDER_SIZE, + LogOption.REMOVING_ORDER, + LogOption.ADJUST_ORDER, + LogOption.CREATE_ORDER, + LogOption.MAKER_ORDER_FILLED, + LogOption.STATUS_REPORT, + LogOption.MAKER_ORDER_HEDGED + ) + + ORDER_ADJUST_SAMPLE_INTERVAL = 5 + ORDER_ADJUST_SAMPLE_WINDOW = 12 + + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 15 + CANCEL_EXPIRY_DURATION = 60.0 + + @classmethod + def logger(cls): + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def init_params(self, + config_map: CrossExchangeMarketMakingConfigMap, + market_pairs: List[MakerTakerMarketPair], + status_report_interval: float = 900, + logging_options: int = OPTION_LOG_ALL, + hb_app_notification: bool = False + ): + """ + Initializes a cross exchange market making strategy object. + + :param config_map: Strategy configuration map + :param market_pairs: list of cross exchange market pairs + :param logging_options: bit field for what types of logging to enable in this strategy object + :param hb_app_notification: + """ + self._config_map = config_map + self._market_pairs = { + (market_pair.maker.market, market_pair.maker.trading_pair): market_pair + for market_pair in market_pairs + } + self._maker_markets = set([market_pair.maker.market for market_pair in market_pairs]) + self._taker_markets = set([market_pair.taker.market for market_pair in market_pairs]) + self._all_markets_ready = False + self._conversions_ready = False + + self._anti_hysteresis_timers = {} + self._order_fill_buy_events = {} + self._order_fill_sell_events = {} + self._suggested_price_samples = {} + + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._market_pair_tracker = OrderIDMarketPairTracker() + + # Holds ongoing hedging orders mapped to their respective maker fill trades + self._ongoing_hedging = bidict() + + self._logging_options = logging_options + + self._last_taker_buy_price = None + self._last_taker_sell_price = None + + self._main_task = None + self._gateway_quotes_task = None + self._cancel_outdated_orders_task = None + self._hedge_maker_order_tasks = [] + + self._last_conv_rates_logged = 0 + self._hb_app_notification = hb_app_notification + + # Holds active maker orders, all its taker orders ever created + self._maker_to_taker_order_ids = {} + # Holds active taker orders, and their respective maker orders + self._taker_to_maker_order_ids = {} + # Holds hedging trade ids for respective maker orders + self._maker_to_hedging_trades = {} + + all_markets = list(self._maker_markets | self._taker_markets) + + self.add_markets(all_markets) + + @property + def order_amount(self): + return self._config_map.order_amount + + @property + def min_profitability(self): + return self._config_map.min_profitability / Decimal("100") + + @property + def order_size_taker_volume_factor(self): + return self._config_map.order_size_taker_volume_factor / Decimal("100") + + @property + def order_size_taker_balance_factor(self): + return self._config_map.order_size_taker_balance_factor / Decimal("100") + + @property + def order_size_portfolio_ratio_limit(self): + return self._config_map.order_size_portfolio_ratio_limit / Decimal("100") + + @property + def top_depth_tolerance(self): + return self._config_map.top_depth_tolerance + + @property + def anti_hysteresis_duration(self): + return self._config_map.anti_hysteresis_duration + + @property + def limit_order_min_expiration(self): + return self._config_map.limit_order_min_expiration + + @property + def status_report_interval(self): + return self._status_report_interval + + @property + def adjust_order_enabled(self): + return self._config_map.adjust_order_enabled + + @property + def use_oracle_conversion_rate(self): + return self._config_map.use_oracle_conversion_rate + + @property + def taker_to_maker_base_conversion_rate(self): + return self._config_map.conversion_rate_mode.taker_to_maker_base_conversion_rate + + @property + def taker_to_maker_quote_conversion_rate(self): + return self._config_map.taker_to_maker_quote_conversion_rate + + @property + def slippage_buffer(self): + return self._config_map.slippage_buffer / Decimal("100") + + @property + def active_maker_limit_orders(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return [(ex, order, order.client_order_id) for ex, order in self._sb_order_tracker.active_limit_orders + if order.client_order_id in self._maker_to_taker_order_ids.keys()] + + @property + def cached_limit_orders(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return self._sb_order_tracker.shadow_limit_orders + + @property + def active_maker_bids(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return [(market, limit_order) for market, limit_order, order_id in self.active_maker_limit_orders + if limit_order.is_buy] + + @property + def active_maker_asks(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return [(market, limit_order) for market, limit_order, order_id in self.active_maker_limit_orders + if not limit_order.is_buy] + + @property + def active_order_canceling(self): + return self._config_map.active_order_canceling + + @property + def adjust_orders_enabled(self): + return self._config_map.adjust_orders_enabled + + @property + def gas_to_maker_base_conversion_rate(self): + return self._config_map.gas_to_maker_base_conversion_rate + + @property + def gateway_transaction_cancel_interval(self): + return self._config_map.gateway_transaction_cancel_interval + + @property + def logging_options(self) -> int: + return self._logging_options + + @logging_options.setter + def logging_options(self, logging_options: Tuple): + self._logging_options = logging_options + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @staticmethod + @lru_cache(maxsize=10) + def is_gateway_market(market_info: MarketTradingPairTuple) -> bool: + return market_info.market.name in AllConnectorSettings.get_gateway_amm_connector_names() + + def get_conversion_rates(self, market_pair: MarketTradingPairTuple): + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, gas_rate_source, \ + gas_rate = self._config_map.conversion_rate_mode.get_conversion_rates(market_pair) + if quote_rate is None: + self.logger().warning(f"Can't find a conversion rate for {quote_pair}") + if base_rate is None: + self.logger().warning(f"Can't find a conversion rate for {base_pair}") + if gas_rate is None: + self.logger().warning(f"Can't find a conversion rate for {gas_pair}") + return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, \ + gas_rate_source, gas_rate + + def log_conversion_rates(self): + for market_pair in self._market_pairs.values(): + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, \ + gas_rate_source, gas_rate = self.get_conversion_rates(market_pair) + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + self.logger().info(f"{quote_pair} ({quote_rate_source}) conversion rate: {PerformanceMetrics.smart_round(quote_rate)}") + if base_pair.split("-")[0] != base_pair.split("-")[1]: + self.logger().info(f"{base_pair} ({base_rate_source}) conversion rate: {PerformanceMetrics.smart_round(base_rate)}") + if self.is_gateway_market(market_pair.taker): + if gas_pair is not None and gas_pair.split("-")[0] != gas_pair.split("-")[1]: + self.logger().info(f"{gas_pair} ({gas_rate_source}) conversion rate: {PerformanceMetrics.smart_round(gas_rate)}") + + def oracle_status_df(self): + columns = ["Source", "Pair", "Rate"] + data = [] + for market_pair in self._market_pairs.values(): + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, \ + gas_rate_source, gas_rate = self.get_conversion_rates(market_pair) + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + data.extend([ + [quote_rate_source, quote_pair, PerformanceMetrics.smart_round(quote_rate)], + ]) + if base_pair.split("-")[0] != base_pair.split("-")[1]: + data.extend([ + [base_rate_source, base_pair, PerformanceMetrics.smart_round(base_rate)], + ]) + if self.is_gateway_market(market_pair.taker): + if gas_pair is not None and gas_pair.split("-")[0] != gas_pair.split("-")[1]: + data.extend([ + [gas_rate_source, gas_pair, PerformanceMetrics.smart_round(gas_rate)], + ]) + return pd.DataFrame(data=data, columns=columns) + + def format_status(self) -> str: + lines = [] + warning_lines = [] + tracked_maker_orders = {} + + # Go through the currently open limit orders, and group them by market pair. + for market, limit_order, order_id in self.active_maker_limit_orders: + typed_limit_order = limit_order + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(typed_limit_order.client_order_id) + if market_pair not in tracked_maker_orders: + tracked_maker_orders[market_pair] = {typed_limit_order.client_order_id: typed_limit_order} + else: + tracked_maker_orders[market_pair][typed_limit_order.client_order_id] = typed_limit_order + + for market_pair in self._market_pairs.values(): + warning_lines.extend(self.network_warning([market_pair.maker, market_pair.taker])) + + if not self.is_gateway_market(market_pair.taker): + markets_df = self.market_status_data_frame([market_pair.maker, market_pair.taker]) + else: + markets_df = self.market_status_data_frame([market_pair.maker]) + # Market status for gateway + bid_price = "" if self._last_taker_buy_price is None else self._last_taker_buy_price + ask_price = "" if self._last_taker_sell_price is None else self._last_taker_sell_price + if self._last_taker_buy_price is not None and self._last_taker_sell_price is not None: + mid_price = (self._last_taker_buy_price + self._last_taker_sell_price) / 2 + else: + mid_price = "" + taker_data = { + "Exchange": market_pair.taker.market.display_name, + "Market": market_pair.taker.trading_pair, + "Best Bid Price": bid_price, + "Best Ask Price": ask_price, + "Mid Price": mid_price + } + if markets_df is not None: + markets_df = markets_df.append(taker_data, ignore_index=True) + lines.extend(["", " Markets:"] + + [" " + line for line in str(markets_df).split("\n")]) + + oracle_df = self.oracle_status_df() + if not oracle_df.empty: + lines.extend(["", " Rate conversion:"] + + [" " + line for line in str(oracle_df).split("\n")]) + + assets_df = self.wallet_balance_data_frame([market_pair.maker, market_pair.taker]) + lines.extend(["", " Assets:"] + + [" " + line for line in str(assets_df).split("\n")]) + + # See if there're any open orders. + if market_pair in tracked_maker_orders and len(tracked_maker_orders[market_pair]) > 0: + limit_orders = list(tracked_maker_orders[market_pair].values()) + bid, ask = self.get_top_bid_ask(market_pair) + mid_price = (bid + ask) / 2 + df = LimitOrder.to_pandas(limit_orders, float(mid_price)) + df_lines = str(df).split("\n") + lines.extend(["", " Active maker market orders:"] + + [" " + line for line in df_lines]) + else: + lines.extend(["", " No active maker market orders."]) + + warning_lines.extend(self.balance_warning([market_pair.maker, market_pair.taker])) + + if len(warning_lines) > 0: + lines.extend(["", " *** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def start(self, clock: Clock, timestamp: float): + super().start(clock, timestamp) + self._last_timestamp = timestamp + + def tick(self, timestamp: float): + """ + Clock tick entry point. + + For cross exchange market making strategy, this function mostly just checks the readiness and connection + status of markets, and then delegates the processing of each market pair to process_market_pair(). + + :param timestamp: current tick timestamp + """ + current_tick = (timestamp // self._status_report_interval) + last_tick = (self._last_timestamp // self._status_report_interval) + should_report_warnings = ((current_tick > last_tick) and + (LogOption.STATUS_REPORT in self.logging_options) + ) + + # Perform clock tick with the market pair tracker. + self._market_pair_tracker.tick(timestamp) + + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self.active_markets]) + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning("Markets are not ready. No market making trades are permitted.") + return + else: + # Markets are ready, ok to proceed. + if LogOption.STATUS_REPORT: + self.logger().info("Markets are ready.") + + if not self._conversions_ready: + for market_pair in self._market_pairs.values(): + _, _, quote_rate, _, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + if not quote_rate or not base_rate: + if should_report_warnings: + self.logger().warning("Conversion rates are not ready. No market making trades are permitted.") + return + + # Conversion rates are ready, ok to proceed. + self._conversions_ready = True + if LogOption.STATUS_REPORT: + self.logger().info("Conversion rates are ready. Trading started.") + + if should_report_warnings: + # Check if all markets are still connected or not. If not, log a warning. + if not all([market.network_status is NetworkStatus.CONNECTED for market in self.active_markets]): + self.logger().warning("WARNING: Some markets are not connected or are down at the moment. Market " + "making may be dangerous when markets or networks are unstable.") + + if self._gateway_quotes_task is None or self._gateway_quotes_task.done(): + self._gateway_quotes_task = safe_ensure_future(self.get_gateway_quotes()) + + if self.ready_for_new_trades(): + if self._main_task is None or self._main_task.done(): + self._main_task = safe_ensure_future(self.main(timestamp)) + + if self._cancel_outdated_orders_task is None or self._cancel_outdated_orders_task.done(): + self._cancel_outdated_orders_task = safe_ensure_future(self.apply_gateway_transaction_cancel_interval()) + + async def main(self, timestamp: float): + try: + # Calculate a mapping from market pair to list of active limit orders on the market. + market_pair_to_active_orders = defaultdict(list) + + for maker_market, limit_order, order_id in self.active_maker_limit_orders: + market_pair = self._market_pairs.get((maker_market, limit_order.trading_pair)) + if market_pair is None: + self.log_with_clock(logging.WARNING, + f"The in-flight maker order in for the trading pair '{limit_order.trading_pair}' " + f"does not correspond to any whitelisted trading pairs. Skipping.") + continue + + if not self._sb_order_tracker.has_in_flight_cancel(limit_order.client_order_id) and \ + limit_order.client_order_id in self._maker_to_taker_order_ids.keys(): + market_pair_to_active_orders[market_pair].append(limit_order) + + # Process each market pair independently. + for market_pair in self._market_pairs.values(): + await self.process_market_pair(timestamp, market_pair, market_pair_to_active_orders[market_pair]) + + # log conversion rates every 5 minutes + if self._last_conv_rates_logged + (60. * 5) < timestamp: + self.log_conversion_rates() + self._last_conv_rates_logged = timestamp + finally: + self._last_timestamp = timestamp + + async def get_gateway_quotes(self): + for market_pair in self._market_pairs.values(): + if self.is_gateway_market(market_pair.taker): + _, _, quote_rate, _, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + order_amount = self._config_map.order_amount * base_rate + order_price = await market_pair.taker.market.get_order_price( + market_pair.taker.trading_pair, + True, + order_amount + ) + self._last_taker_buy_price = order_price + order_price = await market_pair.taker.market.get_order_price( + market_pair.taker.trading_pair, + False, + order_amount + ) + self._last_taker_sell_price = order_price + + def ready_for_new_trades(self) -> bool: + """ + Returns True if there is no outstanding unfilled order. + """ + if len(self._ongoing_hedging.keys()) > 0: + return False + return True + + async def apply_gateway_transaction_cancel_interval(self): + # XXX (martin_kou): Concurrent cancellations are not supported before the nonce architecture is fixed. + # See: https://app.shortcut.com/coinalpha/story/24553/nonce-architecture-in-current-amm-trade-and-evm-approve-apis-is-incorrect-and-causes-trouble-with-concurrent-requests + from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM + gateway_connectors: List[GatewayEVMAMM] = [] + for market_pair in self._market_pairs.values(): + if self.is_gateway_market(market_pair.taker): + gateway_connectors.append(cast(GatewayEVMAMM, market_pair.taker.market)) + + def has_active_taker_order(self, market_pair: MarketTradingPairTuple): + # Market orders are not being submitted as taker orders, limit orders are preferred at all times + limit_orders = self._sb_order_tracker.get_limit_orders() + limit_orders = limit_orders.get(market_pair, {}) + if len(limit_orders) > 0: + if len(set(limit_orders.keys()).intersection(set(self._taker_to_maker_order_ids.keys()))) > 0: + return True + return False + + async def process_market_pair(self, timestamp: float, market_pair: MarketTradingPairTuple, active_orders: List): + """ + For market pair being managed by this strategy object, do the following: + + 1. Check whether any of the existing orders need to be canceled. + 2. Check if new orders should be created. + + For each market pair, only 1 active bid offer and 1 active ask offer is allowed at a time at maximum. + + If an active order is determined to be not needed at step 1, it would cancel the order within step 1. + + If there's no active order found in step 1, and condition allows (i.e. profitability, account balance, etc.), + then a new limit order would be created at step 2. + + Combining step 1 and step 2 over time, means the offers made to the maker market side would be adjusted over + time regularly. + + :param market_pair: cross exchange market pair + :param active_orders: list of active maker limit orders associated with the market pair + """ + has_active_bid = False + has_active_ask = False + need_adjust_order = False + anti_hysteresis_timer = self._anti_hysteresis_timers.get(market_pair, 0) + + global s_decimal_zero + + self.take_suggested_price_sample(timestamp, market_pair) + + for active_order in active_orders: + # Mark the has_active_bid and has_active_ask flags + is_buy = active_order.is_buy + if is_buy: + has_active_bid = True + else: + has_active_ask = True + + # Suppose the active order is hedged on the taker market right now, what's the average price the hedge + # would happen? + current_hedging_price = await self.calculate_effective_hedging_price( + market_pair, + is_buy, + active_order.quantity + ) + + # See if it's still profitable to keep the order on maker market. If not, remove it. + if not await self.check_if_still_profitable(market_pair, active_order, current_hedging_price): + continue + + if isinstance(self._config_map.order_refresh_mode, PassiveOrderRefreshMode): + continue + + # See if I still have enough balance on my wallet to fill the order on maker market, and to hedge the + # order on taker market. If not, adjust it. + if not await self.check_if_sufficient_balance(market_pair, active_order): + continue + + # If prices have moved, one side is still profitable, here cancel and + # place at the next tick. + if timestamp > anti_hysteresis_timer: + if not await self.check_if_price_has_drifted(market_pair, active_order): + need_adjust_order = True + continue + + # If order adjustment is needed in the next tick, set the anti-hysteresis timer s.t. the next order adjustment + # for the same pair wouldn't happen within the time limit. + if need_adjust_order: + self._anti_hysteresis_timers[market_pair] = timestamp + self._config_map.anti_hysteresis_duration + + # If there's both an active bid and ask, then there's no need to think about making new limit orders. + if has_active_bid and has_active_ask: + return + + # If there are pending taker orders, wait for them to complete + if self.has_active_taker_order(market_pair): + return + + # See if it's profitable to place a limit order on maker market. + await self.check_and_create_new_orders(market_pair, has_active_bid, has_active_ask) + + async def hedge_filled_maker_order(self, + maker_order_id: str, + order_filled_event: OrderFilledEvent): + """ + If a limit order previously made to the maker side has been filled, hedge it on the taker side. + :param order_filled_event: event object + """ + order_id = order_filled_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + + # Make sure to only hedge limit orders. + if market_pair is not None and order_id not in self._taker_to_maker_order_ids.keys(): + limit_order_record = self._sb_order_tracker.get_shadow_limit_order(order_id) + order_fill_record = (limit_order_record, order_filled_event) + + # Store the limit order fill event in a map, s.t. it can be processed in check_and_hedge_orders() + # later. + if order_filled_event.trade_type is TradeType.BUY: + if market_pair not in self._order_fill_buy_events: + self._order_fill_buy_events[market_pair] = [order_fill_record] + else: + self._order_fill_buy_events[market_pair].append(order_fill_record) + + if LogOption.MAKER_ORDER_FILLED in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Maker buy order of " + f"{order_filled_event.amount} {market_pair.maker.base_asset} filled." + ) + + else: + if market_pair not in self._order_fill_sell_events: + self._order_fill_sell_events[market_pair] = [order_fill_record] + else: + self._order_fill_sell_events[market_pair].append(order_fill_record) + + if LogOption.MAKER_ORDER_FILLED in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Maker sell order of " + f"{order_filled_event.amount} {market_pair.maker.base_asset} filled." + ) + + # Call check_and_hedge_orders() to emit the orders on the taker side. + try: + await self.check_and_hedge_orders(maker_order_id, market_pair) + except Exception: + self.log_with_clock(logging.ERROR, "Unexpected error.", exc_info=True) + + def hedge_tasks_cleanup(self): + hedge_maker_order_tasks = [] + for task in self._hedge_maker_order_tasks: + if not task.done(): + hedge_maker_order_tasks += [task] + self._hedge_maker_order_tasks = hedge_maker_order_tasks + + def handle_unfilled_taker_order(self, order_event): + order_id = order_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + + # Resubmit hedging order + self.hedge_tasks_cleanup() + self._hedge_maker_order_tasks += [safe_ensure_future( + self.check_and_hedge_orders(order_id, market_pair) + )] + + # Remove the cancelled, failed or expired taker order + del self._taker_to_maker_order_ids[order_event.order_id] + + def did_fill_order(self, order_filled_event: OrderFilledEvent): + maker_order_id = order_filled_event.order_id + exchange_trade_id = order_filled_event.exchange_trade_id + if maker_order_id in self._maker_to_taker_order_ids.keys(): + # Maker order filled + # Check if this fill was already processed or not + if maker_order_id not in self._maker_to_hedging_trades.keys(): + self._maker_to_hedging_trades[maker_order_id] = [] + if exchange_trade_id not in self._maker_to_hedging_trades[maker_order_id]: + # This maker fill has not been processed yet, submit Taker hedge order + # Values have to be unique in a bidict + + self._maker_to_hedging_trades[maker_order_id] += [exchange_trade_id] + + self.hedge_tasks_cleanup() + self._hedge_maker_order_task = safe_ensure_future( + self.hedge_filled_maker_order(maker_order_id, order_filled_event) + ) + + def did_cancel_order(self, order_canceled_event: OrderCancelledEvent): + if order_canceled_event.order_id in self._taker_to_maker_order_ids.keys(): + self.handle_unfilled_taker_order(order_canceled_event) + + def did_fail_order(self, order_failed_event: MarketOrderFailureEvent): + if order_failed_event.order_id in self._taker_to_maker_order_ids.keys(): + self.handle_unfilled_taker_order(order_failed_event) + + def did_expire_order(self, order_expired_event: OrderExpiredEvent): + if order_expired_event.order_id in self._taker_to_maker_order_ids.keys(): + self.handle_unfilled_taker_order(order_expired_event) + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + """ + Output log message when a bid order (on maker side or taker side) is completely taken. + :param order_completed_event: event object + """ + order_id = order_completed_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + + if market_pair is not None: + if order_id in self._maker_to_taker_order_ids.keys(): + limit_order_record = self._sb_order_tracker.get_limit_order(market_pair.maker, order_id) + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker BUY order ({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) is filled." + ) + # Leftover other side maker order will be left in the market until its expiration or potential fill + # Since the buy side side was filled, the sell side maker order is unlikely to be filled, therefore + # it'll likey expire + # The others are left in the market to collect market making fees + # Meanwhile new maker order may be placed + if order_id in self._taker_to_maker_order_ids.keys(): + self.log_with_clock( + logging.INFO, + f"({market_pair.taker.trading_pair}) Taker buy order {order_id} for " + f"({order_completed_event.base_asset_amount} {order_completed_event.base_asset} has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Taker BUY order ({order_completed_event.base_asset_amount} {order_completed_event.base_asset} " + f"{order_completed_event.quote_asset}) is filled." + ) + maker_order_id = self._taker_to_maker_order_ids[order_id] + # Remove the completed taker order + del self._taker_to_maker_order_ids[order_id] + # Get all active taker order ids for the maker order id + active_taker_ids = set(self._taker_to_maker_order_ids.keys()).intersection(set( + self._maker_to_taker_order_ids[maker_order_id])) + if len(active_taker_ids) == 0: + # Was maker order fully filled? + maker_order_ids = list(order_id for market, limit_order, order_id in self.active_maker_limit_orders) + if maker_order_id not in maker_order_ids: + # Remove the completed fully hedged maker order + del self._maker_to_taker_order_ids[maker_order_id] + del self._maker_to_hedging_trades[maker_order_id] + + try: + self.del_order_from_ongoing_hedging(order_id) + except KeyError: + self.logger().warning(f"Ongoing hedging not found for order id {order_id}") + + # Delete hedged maker fill event + fill_events = [] + for fill_event in self._order_fill_sell_events[market_pair]: + if self.is_fill_event_in_ongoing_hedging(fill_event): + fill_events += [fill_event] + self._order_fill_sell_events[market_pair] = fill_events + + # Cleanup maker fill events - no longer needed to create taker orders if all fills were hedged + if len(self._order_fill_sell_events[market_pair]) == 0: + del self._order_fill_sell_events[market_pair] + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + """ + Output log message when a ask order (on maker side or taker side) is completely taken. + :param order_completed_event: event object + """ + order_id = order_completed_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + + if market_pair is not None: + if order_id in self._maker_to_taker_order_ids.keys(): + limit_order_record = self._sb_order_tracker.get_limit_order(market_pair.maker, order_id) + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker sell order ({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) is filled." + ) + # Leftover other side maker order will be left in the market until its expiration or potential fill + # Since the sell side side was filled, the buy side maker order is unlikely to be filled, therefore + # it'll likey expire + # The others are left in the market to collect market making fees + # Meanwhile new maker order may be placed + if order_id in self._taker_to_maker_order_ids.keys(): + self.log_with_clock( + logging.INFO, + f"({market_pair.taker.trading_pair}) Taker sell order {order_id} for " + f"({order_completed_event.base_asset_amount} {order_completed_event.base_asset} " + f"has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Taker SELL order ({order_completed_event.base_asset_amount} {order_completed_event.base_asset} " + f"{order_completed_event.quote_asset}) is filled." + ) + maker_order_id = self._taker_to_maker_order_ids[order_id] + # Remove the completed taker order + del self._taker_to_maker_order_ids[order_id] + # Get all active taker order ids for the maker order id + active_taker_ids = set(self._taker_to_maker_order_ids.keys()).intersection(set( + self._maker_to_taker_order_ids[maker_order_id])) + if len(active_taker_ids) == 0: + # Was maker order fully filled? + maker_order_ids = list(order_id for market, limit_order, order_id in self.active_maker_limit_orders) + if maker_order_id not in maker_order_ids: + # Remove the completed fully hedged maker order + del self._maker_to_taker_order_ids[maker_order_id] + del self._maker_to_hedging_trades[maker_order_id] + + try: + self.del_order_from_ongoing_hedging(order_id) + except KeyError: + self.logger().warning(f"Ongoing hedging not found for order id {order_id}") + + # Delete hedged maker fill event + fill_events = [] + for fill_event in self._order_fill_buy_events[market_pair]: + if self.is_fill_event_in_ongoing_hedging(fill_event): + fill_events += [fill_event] + self._order_fill_buy_events[market_pair] = fill_events + + # Cleanup maker fill events - no longer needed to create taker orders if all fills were hedged + if len(self._order_fill_buy_events[market_pair]) == 0: + del self._order_fill_buy_events[market_pair] + + async def check_if_price_has_drifted(self, market_pair: MakerTakerMarketPair, active_order: LimitOrder): + """ + Given a currently active limit order on maker side, check if its current price is still valid, based on the + current hedging price on taker market, depth tolerance, and transient orders on the maker market captured by + recent suggested price samples. + + If the active order's price is no longer valid, the order will be canceled. + + This function is only used when active order cancelation is enabled. + + :param market_pair: cross exchange market pair + :param active_order: a current active limit order in the market pair + :return: True if the order stays, False if the order has been canceled and we need to re place the orders. + """ + is_buy = active_order.is_buy + order_price = active_order.price + order_quantity = active_order.quantity + suggested_price = await self.get_market_making_price(market_pair, is_buy, order_quantity) + + if suggested_price != order_price: + if LogOption.ADJUST_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) The current limit {'bid' if is_buy else 'ask'} order for " + f"{active_order.quantity} {market_pair.maker.base_asset} at " + f"{order_price:.8g} {market_pair.maker.quote_asset} is now below the suggested order " + f"price at {suggested_price}. Going to cancel the old order and create a new one..." + ) + self.cancel_maker_order(market_pair, active_order.client_order_id) + self.log_with_clock(logging.DEBUG, + f"Current {'buy' if is_buy else 'sell'} order price={order_price}, " + f"suggested order price={suggested_price}") + return False + + return True + + async def check_and_hedge_orders(self, + maker_order_id: str, + market_pair: MakerTakerMarketPair): + """ + Look into the stored and un-hedged limit order fill events, and emit orders to hedge them, depending on + availability of funds on the taker market. + + :param market_pair: cross exchange market pair + """ + + buy_fill_records = self.get_unhedged_buy_records(market_pair) + sell_fill_records = self.get_unhedged_sell_records(market_pair) + + buy_fill_quantity = sum([fill_event.amount for _, fill_event in buy_fill_records]) + sell_fill_quantity = sum([fill_event.amount for _, fill_event in sell_fill_records]) + + global s_decimal_zero + + taker_trading_pair = market_pair.taker.trading_pair + taker_market = market_pair.taker.market + + # Convert maker order size (in maker base asset) to taker order size (in taker base asset) + _, _, quote_rate, _, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + + if buy_fill_quantity > 0: + # Maker buy + # Taker sell + taker_slippage_adjustment_factor = Decimal("1") - self.slippage_buffer + + hedged_order_quantity = min( + buy_fill_quantity / base_rate, + taker_market.get_available_balance(market_pair.taker.base_asset) * + self.order_size_taker_balance_factor + ) + quantized_hedge_amount = taker_market.quantize_order_amount(taker_trading_pair, Decimal(hedged_order_quantity)) + + avg_fill_price = (sum([r.price * r.amount for _, r in buy_fill_records]) / + sum([r.amount for _, r in buy_fill_records])) + + self.check_multiple_buy_orders(buy_fill_records) + if self.is_gateway_market(market_pair.taker): + order_price = await market_pair.taker.market.get_order_price( + taker_trading_pair, + False, + quantized_hedge_amount) + if order_price is None: + self.logger().warning("Gateway: failed to obtain order price. No hedging order will be submitted.") + return + taker_top = order_price + else: + taker_top = taker_market.get_price(taker_trading_pair, False) + order_price = taker_market.get_price_for_volume( + taker_trading_pair, False, quantized_hedge_amount + ).result_price + + self.log_with_clock(logging.INFO, f"Calculated by HB order_price: {order_price}") + order_price *= taker_slippage_adjustment_factor + order_price = taker_market.quantize_order_price(taker_trading_pair, order_price) + self.log_with_clock(logging.INFO, f"Slippage buffer adjusted order_price: {order_price}") + + if quantized_hedge_amount > s_decimal_zero: + self.place_order( + market_pair, + False, + False, + quantized_hedge_amount, + order_price, + maker_order_id, + buy_fill_records + ) + + if LogOption.MAKER_ORDER_HEDGED in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Hedged maker buy order(s) of " + f"{buy_fill_quantity} {market_pair.maker.base_asset} on taker market to lock in profits. " + f"(maker avg price={avg_fill_price}, taker top={taker_top})" + ) + else: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Current maker buy fill amount of " + f"{buy_fill_quantity} {market_pair.maker.base_asset} is less than the minimum order amount " + f"allowed on the taker market. No hedging possible yet." + ) + + if sell_fill_quantity > 0: + # Maker sell + # Taker buy + taker_slippage_adjustment_factor = Decimal("1") + self.slippage_buffer + + if self.is_gateway_market(market_pair.taker): + taker_price = await market_pair.taker.market.get_order_price( + taker_trading_pair, + True, + sell_fill_quantity / base_rate + ) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price. No hedging order will be submitted.") + return + else: + taker_price = taker_market.get_price_for_volume( + taker_trading_pair, + True, + sell_fill_quantity / base_rate + ).result_price + + hedged_order_quantity = min( + sell_fill_quantity / base_rate, + taker_market.get_available_balance(market_pair.taker.quote_asset) / + taker_price * self.order_size_taker_balance_factor + ) + quantized_hedge_amount = taker_market.quantize_order_amount( + taker_trading_pair, + Decimal(hedged_order_quantity) + ) + + avg_fill_price = (sum([r.price * r.amount for _, r in sell_fill_records]) / + sum([r.amount for _, r in sell_fill_records])) + + self.check_multiple_sell_orders(sell_fill_records) + if self.is_gateway_market(market_pair.taker): + order_price = await market_pair.taker.market.get_order_price( + taker_trading_pair, + True, + quantized_hedge_amount) + if order_price is None: + self.logger().warning("Gateway: failed to obtain order price. No hedging order will be submitted.") + return + taker_top = order_price + else: + taker_top = taker_market.get_price(taker_trading_pair, True) + order_price = taker_market.get_price_for_volume( + taker_trading_pair, True, quantized_hedge_amount + ).result_price + + self.log_with_clock(logging.INFO, f"Calculated by HB order_price: {order_price}") + order_price *= taker_slippage_adjustment_factor + order_price = taker_market.quantize_order_price(taker_trading_pair, order_price) + self.log_with_clock(logging.INFO, f"Slippage buffer adjusted order_price: {order_price}") + + if quantized_hedge_amount > s_decimal_zero: + self.place_order( + market_pair, + True, + False, + quantized_hedge_amount, + order_price, + maker_order_id, + sell_fill_records, + ) + + if LogOption.MAKER_ORDER_HEDGED in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Hedged maker sell order(s) of " + f"{sell_fill_quantity} {market_pair.maker.base_asset} on taker market to lock in profits. " + f"(maker avg price={avg_fill_price}, taker top={taker_top})" + ) + else: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Current maker sell fill amount of " + f"{sell_fill_quantity} {market_pair.maker.base_asset} is less than the minimum order amount " + f"allowed on the taker market. No hedging possible yet." + ) + + def get_adjusted_limit_order_size(self, market_pair: MakerTakerMarketPair) -> Tuple[Decimal, Decimal]: + """ + Given the proposed order size of a proposed limit order (regardless of bid or ask), adjust and refine the order + sizing according to either the trade size override setting (if it exists), or the portfolio ratio limit (if + no trade size override exists). + + Also, this function will convert the input order size proposal from floating point to Decimal by quantizing the + order size. + + :param market_pair: cross exchange market pair + :rtype: Decimal + """ + maker_market = market_pair.maker.market + trading_pair = market_pair.maker.trading_pair + if self._config_map.order_amount and self._config_map.order_amount > 0: + base_order_size = self._config_map.order_amount + return maker_market.quantize_order_amount(trading_pair, Decimal(base_order_size)) + else: + return self.get_order_size_after_portfolio_ratio_limit(market_pair) + + def get_order_size_after_portfolio_ratio_limit(self, market_pair: MakerTakerMarketPair) -> Decimal: + """ + Given the proposed order size of a proposed limit order (regardless of bid or ask), adjust the order sizing + according to the portfolio ratio limit. + + Also, this function will convert the input order size proposal from floating point to Decimal by quantizing the + order size. + + :param market_pair: cross exchange market pair + :rtype: Decimal + """ + maker_market = market_pair.maker.market + trading_pair = market_pair.maker.trading_pair + base_balance = maker_market.get_balance(market_pair.maker.base_asset) + quote_balance = maker_market.get_balance(market_pair.maker.quote_asset) + current_price = (maker_market.get_price(trading_pair, True) + + maker_market.get_price(trading_pair, False)) * Decimal(0.5) + maker_portfolio_value = base_balance + quote_balance / current_price + adjusted_order_size = maker_portfolio_value * self.order_size_portfolio_ratio_limit + + return maker_market.quantize_order_amount(trading_pair, Decimal(adjusted_order_size)) + + async def get_market_making_size(self, + market_pair: MakerTakerMarketPair, + is_bid: bool): + """ + Get the ideal market making order size given a market pair and a side. + + This function does a few things: + 1. Calculate the largest order size possible given the current balances on both maker and taker markets. + 2. Calculate the largest order size possible that's still profitable after hedging. + + + :param market_pair: The cross exchange market pair to calculate order price/size limits. + :param is_bid: Whether the order to make will be bid or ask. + :return: a Decimal which is the size of maker order. + """ + taker_trading_pair = market_pair.taker.trading_pair + maker_market = market_pair.maker.market + taker_market = market_pair.taker.market + + # Maker order size (in base asset) + size = self.get_adjusted_limit_order_size(market_pair) + + # Convert maker order size (in maker base asset) to taker order size (in taker base asset) + _, _, quote_rate, _, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + taker_size = size / base_rate + + if is_bid: + # Maker buy + # Taker sell + maker_balance_in_quote = maker_market.get_available_balance(market_pair.maker.quote_asset) + taker_balance = taker_market.get_available_balance(market_pair.taker.base_asset) * \ + self.order_size_taker_balance_factor + + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + False, + taker_size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "No market making order will be submitted.") + return s_decimal_zero + else: + try: + taker_price = taker_market.get_vwap_for_volume( + taker_trading_pair, False, taker_size + ).result_price + except ZeroDivisionError: + assert size == s_decimal_zero + return s_decimal_zero + + if taker_price is None: + self.logger().warning("Failed to obtain a taker sell order price. No order will be submitted.") + order_amount = Decimal("0") + else: + maker_balance = maker_balance_in_quote / \ + (taker_price * self.markettaker_to_maker_base_conversion_rate(market_pair)) + taker_balance *= base_rate + order_amount = min(maker_balance, taker_balance, size) + + return maker_market.quantize_order_amount(market_pair.maker.trading_pair, Decimal(order_amount)) + + else: + # Maker sell + # Taker buy + maker_balance = maker_market.get_available_balance(market_pair.maker.base_asset) + taker_balance_in_quote = taker_market.get_available_balance(market_pair.taker.quote_asset) * \ + self.order_size_taker_balance_factor + + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + True, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "No market making order will be submitted.") + return s_decimal_zero + else: + try: + taker_price = taker_market.get_price_for_quote_volume( + taker_trading_pair, True, taker_balance_in_quote + ).result_price + except ZeroDivisionError: + assert size == s_decimal_zero + return s_decimal_zero + + if taker_price is None: + self.logger().warning("Failed to obtain a taker buy order price. No order will be submitted.") + order_amount = Decimal("0") + else: + taker_slippage_adjustment_factor = Decimal("1") + self.slippage_buffer + taker_balance = taker_balance_in_quote / (taker_price * taker_slippage_adjustment_factor) + taker_balance *= base_rate + order_amount = min(maker_balance, taker_balance, size) + + return maker_market.quantize_order_amount(market_pair.maker.trading_pair, Decimal(order_amount)) + + async def get_market_making_price(self, + market_pair: MarketTradingPairTuple, + is_bid: bool, + size: Decimal): + """ + Get the ideal market making order price given a market pair, side and size. + + The price returned is calculated by adding the profitability to vwap of hedging it on the taker side. + or if it's not possible to hedge the trade profitably, then the returned order price will be none. + + :param market_pair: The cross exchange market pair to calculate order price/size limits. + :param is_bid: Whether the order to make will be bid or ask. + :param size: size of the maker order. + :return: a Decimal which is the price or None if order cannot be hedged on the taker market + """ + taker_trading_pair = market_pair.taker.trading_pair + maker_market = market_pair.maker.market + taker_market = market_pair.taker.market + top_bid_price = s_decimal_nan + top_ask_price = s_decimal_nan + next_price_below_top_ask = s_decimal_nan + + # Convert maker order size (in maker base asset) to taker order size (in taker base asset) + _, _, quote_rate, base_pair, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + size /= base_rate + + top_bid_price, top_ask_price = self.get_top_bid_ask_from_price_samples(market_pair) + + if is_bid: + # Maker buy + # Taker sell + if not Decimal.is_nan(top_bid_price): + # Calculate the next price above top bid + price_quantum = maker_market.get_order_price_quantum( + market_pair.maker.trading_pair, + top_bid_price + ) + price_above_bid = (ceil(top_bid_price / price_quantum) + 1) * price_quantum + + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + False, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "No market making order will be submitted.") + return s_decimal_nan + else: + try: + taker_price = taker_market.get_vwap_for_volume(taker_trading_pair, False, size).result_price + except ZeroDivisionError: + return s_decimal_nan + + if taker_price is None: + return + + # Convert them from taker's price to maker's price + taker_price *= self.markettaker_to_maker_base_conversion_rate(market_pair) + + # you are buying on the maker market and selling on the taker market + maker_price = taker_price / (1 + self.min_profitability) + + # # If your bid is higher than highest bid price, reduce it to one tick above the top bid price + if self.adjust_order_enabled: + # If maker bid order book is not empty + if not Decimal.is_nan(price_above_bid): + maker_price = min(maker_price, price_above_bid) + + price_quantum = maker_market.get_order_price_quantum( + market_pair.maker.trading_pair, + maker_price + ) + + # Rounds down for ensuring profitability + maker_price = (floor(maker_price / price_quantum)) * price_quantum + + return maker_price + else: + # Maker sell + # Taker buy + if not Decimal.is_nan(top_ask_price): + # Calculate the next price below top ask + price_quantum = maker_market.get_order_price_quantum( + market_pair.maker.trading_pair, + top_ask_price + ) + next_price_below_top_ask = (floor(top_ask_price / price_quantum) - 1) * price_quantum + + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + True, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "No market making order will be submitted.") + return s_decimal_nan + else: + try: + taker_price = taker_market.get_vwap_for_volume(taker_trading_pair, True, size).result_price + except ZeroDivisionError: + return s_decimal_nan + + taker_price *= self.markettaker_to_maker_base_conversion_rate(market_pair) + + # You are selling on the maker market and buying on the taker market + maker_price = taker_price * (1 + self.min_profitability) + + # If your ask is lower than the the top ask, increase it to just one tick below top ask + if self.adjust_order_enabled: + # If maker ask order book is not empty + if not Decimal.is_nan(next_price_below_top_ask): + maker_price = max(maker_price, next_price_below_top_ask) + + price_quantum = maker_market.get_order_price_quantum( + market_pair.maker.trading_pair, + maker_price + ) + + # Rounds up for ensuring profitability + maker_price = (ceil(maker_price / price_quantum)) * price_quantum + + return maker_price + + async def calculate_effective_hedging_price(self, + market_pair: MarketTradingPairTuple, + is_bid: bool, + size: Decimal): + """ + Returns current possible taker price expressed in units of the maker market quote asset + :param market_pair: The cross exchange market pair to calculate order price/size limits. + :param is_bid: Whether the order to make will be bid or ask. + :param size: The size of the maker order. + :return: a Decimal which is the hedging price on the maker market given the current taker market + """ + taker_trading_pair = market_pair.taker.trading_pair + taker_market = market_pair.taker.market + + # Convert maker order size (in maker base asset) to taker order size (in taker base asset) + _, _, quote_rate, _, _, base_rate, _, _, gas_rate = self.get_conversion_rates(market_pair) + size /= base_rate + + # Calculate the next price from the top, and the order size limit. + if is_bid: + # Maker buy + # Taker sell + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + False, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "Failed to calculate effective hedging price.") + return s_decimal_nan + else: + try: + taker_price = taker_market.get_vwap_for_volume(taker_trading_pair, False, size).result_price + except ZeroDivisionError: + return None + + # If quote assets are not same, convert them from taker's quote asset to maker's quote asset + taker_price *= self.markettaker_to_maker_base_conversion_rate(market_pair) + + return taker_price + else: + # Maker sell + # Taker buy + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + True, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "Failed to calculate effective hedging price.") + return s_decimal_nan + else: + try: + taker_price = taker_market.get_vwap_for_volume(taker_trading_pair, True, size).result_price + except ZeroDivisionError: + return None + + # Convert them from taker's price to maker's price + taker_price *= self.markettaker_to_maker_base_conversion_rate(market_pair) + + return taker_price + + def get_suggested_price_samples(self, market_pair: MakerTakerMarketPair): + """ + Get the queues of order book price samples for a market pair. + + :param market_pair: The market pair under which samples were collected for. + :return: (bid order price samples, ask order price samples) + """ + if market_pair in self._suggested_price_samples: + return self._suggested_price_samples[market_pair] + return deque(), deque() + + def get_top_bid_ask(self, market_pair: MakerTakerMarketPair): + """ + Calculate the top bid and ask using top depth tolerance in maker order book + + :param market_pair: cross exchange market pair + :return: (top bid: Decimal, top ask: Decimal) + """ + trading_pair = market_pair.maker.trading_pair + maker_market = market_pair.maker.market + + if self._config_map.top_depth_tolerance == 0: + top_bid_price = maker_market.get_price(trading_pair, False) + top_ask_price = maker_market.get_price(trading_pair, True) + + else: + # Use bid entries in maker order book + top_bid_price = maker_market.get_price_for_volume(trading_pair, + False, + self._config_map.top_depth_tolerance).result_price + + # Use ask entries in maker order book + top_ask_price = maker_market.get_price_for_volume(trading_pair, + True, + self._config_map.top_depth_tolerance).result_price + + return top_bid_price, top_ask_price + + def take_suggested_price_sample(self, timestamp: float, market_pair: MakerTakerMarketPair): + """ + Record the bid and ask sample queues. + + These samples are later taken to check if price has drifted for new limit orders, s.t. new limit orders can + properly take into account transient orders that appear and disappear frequently on the maker market. + + :param market_pair: cross exchange market pair + """ + if ((self._last_timestamp // self.ORDER_ADJUST_SAMPLE_INTERVAL) < + (timestamp // self.ORDER_ADJUST_SAMPLE_INTERVAL)): + if market_pair not in self._suggested_price_samples: + self._suggested_price_samples[market_pair] = (deque(), deque()) + + top_bid_price, top_ask_price = self.get_top_bid_ask_from_price_samples(market_pair) + + bid_price_samples_deque, ask_price_samples_deque = self._suggested_price_samples[market_pair] + bid_price_samples_deque.append(top_bid_price) + ask_price_samples_deque.append(top_ask_price) + while len(bid_price_samples_deque) > self.ORDER_ADJUST_SAMPLE_WINDOW: + bid_price_samples_deque.popleft() + while len(ask_price_samples_deque) > self.ORDER_ADJUST_SAMPLE_WINDOW: + ask_price_samples_deque.popleft() + + def get_top_bid_ask_from_price_samples(self, market_pair: MakerTakerMarketPair): + """ + Calculate the top bid and ask using earlier samples + + :param market_pair: cross exchange market pair + :return: (top bid, top ask) + """ + # Incorporate the past bid & ask price samples. + current_top_bid_price, current_top_ask_price = self.get_top_bid_ask(market_pair) + + bid_price_samples, ask_price_samples = self.get_suggested_price_samples(market_pair) + + if not any(Decimal.is_nan(p) for p in bid_price_samples) and not Decimal.is_nan(current_top_bid_price): + top_bid_price = max(list(bid_price_samples) + [current_top_bid_price]) + else: + top_bid_price = current_top_bid_price + + if not any(Decimal.is_nan(p) for p in ask_price_samples) and not Decimal.is_nan(current_top_ask_price): + top_ask_price = min(list(ask_price_samples) + [current_top_ask_price]) + else: + top_ask_price = current_top_ask_price + + return top_bid_price, top_ask_price + + async def check_if_still_profitable(self, + market_pair, + active_order: LimitOrder, + current_hedging_price: Decimal): + """ + Check whether a currently active limit order should be canceled or not, according to profitability metric. + + If active order canceling is enabled (e.g. for centralized exchanges), then the min profitability config is + used as the threshold. If it is disabled (e.g. for decentralized exchanges), then the cancel order threshold + is used instead. + + :param market_pair: cross exchange market pair + :param active_order: the currently active order to check for cancelation + :param current_hedging_price: the current average hedging price on taker market for the limit order + :return: True if the limit order stays, False if the limit order is being canceled. + """ + is_buy = active_order.is_buy + limit_order_type_str = "bid" if is_buy else "ask" + order_price = active_order.price + + cancel_order_threshold = self._config_map.order_refresh_mode.get_cancel_order_threshold() + if cancel_order_threshold.is_nan(): + cancel_order_threshold = self.min_profitability + + if current_hedging_price is None: + if LogOption.REMOVING_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Limit {limit_order_type_str} order at " + f"{order_price:.8g} {market_pair.maker.quote_asset} is no longer profitable. " + f"Removing the order." + ) + self.cancel_maker_order(market_pair, active_order.client_order_id) + return False + + transaction_fee = 0 + pl = 0 + if self.is_gateway_market(market_pair.taker): + if hasattr(market_pair.taker.market, "network_transaction_fee"): + _, _, quote_rate, _, _, base_rate, _, _, gas_rate = self.get_conversion_rates(market_pair) + transaction_fee: TokenAmount = getattr(market_pair.taker.market, "network_transaction_fee") + transaction_fee = transaction_fee.amount + # Transaction fee in maker quote asset + transaction_fee *= gas_rate + + # The remaining quantity of the maker order to potentially hedge against + quantity_remaining = active_order.quantity + if not active_order.filled_quantity.is_nan(): + quantity_remaining = active_order.quantity - active_order.filled_quantity + + if active_order.is_buy: + # Maker buy + # Taker sell + hedged_order_quantity = min( + quantity_remaining * base_rate, + market_pair.taker.market.get_available_balance(market_pair.taker.base_asset) * + self.order_size_taker_balance_factor + ) + # Convert from taker to maker order amount + hedged_order_quantity = hedged_order_quantity / base_rate + # Calculate P/L including potential gas fees (if gateway) + pl = hedged_order_quantity * current_hedging_price - \ + quantity_remaining * active_order.price - \ + transaction_fee + else: + # Maker sell + # Taker buy + taker_price = await market_pair.taker.market.get_order_price( + market_pair.taker.trading_pair, + True, + quantity_remaining * base_rate + ) + hedged_order_quantity = min( + quantity_remaining * base_rate, + market_pair.taker.market.get_available_balance(market_pair.taker.quote_asset) / + taker_price * self.order_size_taker_balance_factor + ) + # Convert from taker to maker order amount + hedged_order_quantity = hedged_order_quantity / base_rate + # Calculate P/L including potential gas fees (if gateway) + pl = quantity_remaining * active_order.price - \ + hedged_order_quantity * current_hedging_price - \ + transaction_fee + + # Profitability based on a price multiplier (cancel_order_threshold) + # Profitability based on absolute P/L including fees + if ((is_buy and current_hedging_price < order_price * (1 + cancel_order_threshold)) or + (not is_buy and order_price < current_hedging_price * (1 + cancel_order_threshold)) or + pl < 0): + + if LogOption.REMOVING_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Limit {limit_order_type_str} order at " + f"{order_price:.8g} {market_pair.maker.quote_asset} is no longer profitable. " + f"Removing the order." + ) + self.cancel_maker_order(market_pair, active_order.client_order_id) + return False + return True + + async def check_if_sufficient_balance(self, market_pair: MakerTakerMarketPair, active_order: LimitOrder) -> bool: + """ + Check whether there's enough asset balance for a currently active limit order. If there's not enough asset + balance for the order (e.g. because the required asset has been moved), cancel the active order. + + This function is only used when active order canceled is enabled. + + :param market_pair: cross exchange market pair + :param active_order: current maker limit order + :return: True if there's sufficient balance for the limit order, False if there isn't and the order is being + canceled. + """ + is_buy = active_order.is_buy + order_price = active_order.price + # Maker order size + size = self.get_adjusted_limit_order_size(market_pair) + maker_market = market_pair.maker.market + taker_trading_pair = market_pair.taker.trading_pair + + # Convert maker order size (in maker base asset) to taker order size (in taker base asset) + _, _, quote_rate, _, _, base_rate, _, _, gas_rate = self.get_conversion_rates(market_pair) + size /= base_rate + + taker_market = market_pair.taker.market + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, gas_rate_source, gas_rate = \ + self.get_conversion_rates(market_pair) + + if is_buy: + # Maker buy + # Taker sell + taker_slippage_adjustment_factor = Decimal("1") - self.slippage_buffer + + quote_asset_amount = maker_market.get_balance(market_pair.maker.quote_asset) + base_asset_amount = taker_market.get_balance(market_pair.taker.base_asset) * base_rate + + order_size_limit = min(base_asset_amount, quote_asset_amount / order_price) + else: + # Maker sell + # Taker buy + taker_slippage_adjustment_factor = Decimal("1") + self.slippage_buffer + + base_asset_amount = maker_market.get_balance(market_pair.maker.base_asset) + quote_asset_amount = taker_market.get_balance(market_pair.taker.quote_asset) + + if self.is_gateway_market(market_pair.taker): + taker_price = await taker_market.get_order_price(taker_trading_pair, + True, + size) + if taker_price is None: + self.logger().warning("Gateway: failed to obtain order price." + "Failed to determine sufficient balance.") + return False + else: + taker_price = taker_market.get_price_for_quote_volume( + taker_trading_pair, True, quote_asset_amount + ).result_price + + adjusted_taker_price = (taker_price / base_rate) * taker_slippage_adjustment_factor + order_size_limit = min(base_asset_amount, quote_asset_amount / adjusted_taker_price) + + quantized_size_limit = maker_market.quantize_order_amount(active_order.trading_pair, order_size_limit) + + if active_order.quantity > quantized_size_limit: + if LogOption.ADJUST_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Order size limit ({order_size_limit:.8g}) " + f"is now less than the current active order amount ({active_order.quantity:.8g}). " + f"Going to adjust the order." + ) + self.cancel_maker_order(market_pair, active_order.client_order_id) + return False + return True + + def markettaker_to_maker_base_conversion_rate(self, market_pair: MarketTradingPairTuple) -> Decimal: + """ + Return price conversion rate for from taker quote asset to maker quote asset + """ + _, _, quote_rate, _, _, base_rate, _, _, _ = self.get_conversion_rates(market_pair) + return quote_rate / base_rate + # else: + # market_pairs = list(self._market_pairs.values())[0] + # quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" + # base_pair = f"{market_pairs.taker.base_asset}-{market_pairs.maker.base_asset}" + # quote_rate = RateOracle.get_instance().rate(quote_pair) + # base_rate = RateOracle.get_instance().rate(base_pair) + # return quote_rate / base_rate + + async def check_and_create_new_orders(self, + market_pair: MarketTradingPairTuple, + has_active_bid: bool, + has_active_ask: bool): + """ + Check and account for all applicable conditions for creating new limit orders (e.g. profitability, what's the + right price given depth tolerance and transient orders on the market, account balances, etc.), and create new + limit orders for market making. + + :param market_pair: cross exchange market pair + :param has_active_bid: True if there's already an active bid on the maker side, False otherwise + :param has_active_ask: True if there's already an active ask on the maker side, False otherwise + """ + + # if there is no active bid, place bid again + if not has_active_bid: + bid_size = await self.get_market_making_size(market_pair, True) + + if bid_size > s_decimal_zero: + bid_price = await self.get_market_making_price(market_pair, True, bid_size) + if not Decimal.is_nan(bid_price): + effective_hedging_price = await self.calculate_effective_hedging_price( + market_pair, + True, + bid_size + ) + effective_hedging_price_adjusted = effective_hedging_price / \ + self.markettaker_to_maker_base_conversion_rate(market_pair) + if LogOption.CREATE_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Creating limit bid order for " + f"{bid_size} {market_pair.maker.base_asset} at " + f"{bid_price} {market_pair.maker.quote_asset}. " + f"Current hedging price: {effective_hedging_price:.8f} {market_pair.maker.quote_asset} " + f"(Rate adjusted: {effective_hedging_price_adjusted:.8f} {market_pair.taker.quote_asset})." + ) + self.place_order(market_pair, True, True, bid_size, bid_price) + else: + if LogOption.NULL_ORDER_SIZE in self.logging_options: + self.log_with_clock( + logging.WARNING, + f"({market_pair.maker.trading_pair})" + f"Order book on taker is too thin to place order for size: {bid_size}" + f"Reduce order_size_portfolio_ratio_limit" + ) + else: + if LogOption.NULL_ORDER_SIZE in self.logging_options: + self.log_with_clock( + logging.WARNING, + f"({market_pair.maker.trading_pair}) Attempting to place a limit bid but the " + f"bid size is 0. Skipping. Check available balance." + ) + # if there is no active ask, place ask again + if not has_active_ask: + ask_size = await self.get_market_making_size(market_pair, False) + + if ask_size > s_decimal_zero: + ask_price = await self.get_market_making_price(market_pair, False, ask_size) + if not Decimal.is_nan(ask_price): + effective_hedging_price = await self.calculate_effective_hedging_price( + market_pair, + False, + ask_size + ) + effective_hedging_price_adjusted = effective_hedging_price / \ + self.markettaker_to_maker_base_conversion_rate(market_pair) + if LogOption.CREATE_ORDER in self.logging_options: + self.log_with_clock( + logging.INFO, + f"({market_pair.maker.trading_pair}) Creating limit ask order for " + f"{ask_size} {market_pair.maker.base_asset} at " + f"{ask_price} {market_pair.maker.quote_asset}. " + f"Current hedging price: {effective_hedging_price:.8f} {market_pair.maker.quote_asset} " + f"(Rate adjusted: {effective_hedging_price_adjusted:.8f} {market_pair.taker.quote_asset})." + ) + self.place_order(market_pair, False, True, ask_size, ask_price) + else: + if LogOption.NULL_ORDER_SIZE in self.logging_options: + self.log_with_clock( + logging.WARNING, + f"({market_pair.maker.trading_pair})" + f"Order book on taker is too thin to place order for size: {ask_size}" + f"Reduce order_size_portfolio_ratio_limit" + ) + else: + if LogOption.NULL_ORDER_SIZE in self.logging_options: + self.log_with_clock( + logging.WARNING, + f"({market_pair.maker.trading_pair}) Attempting to place a limit ask but the " + f"ask size is 0. Skipping. Check available balance." + ) + + def place_order( + self, + market_pair: MakerTakerMarketPair, + is_buy: bool, + is_maker: bool, # True for maker order, False for taker order + amount: Decimal, + price: Decimal, + maker_order_id: str = None, + fill_records: List[OrderFilledEvent] = None, + ): + expiration_seconds = s_float_nan + market_info = market_pair.maker if is_maker else market_pair.taker + # Market orders are not being submitted as taker orders, limit orders are preferred at all times + order_type = market_info.market.get_maker_order_type() if is_maker else \ + OrderType.LIMIT + if order_type is OrderType.MARKET: + price = s_decimal_nan + expiration_seconds = self._config_map.order_refresh_mode.get_expiration_seconds() + order_id = None + if is_buy: + try: + order_id = self.buy_with_specific_market(market_info, amount, + order_type=order_type, price=price, + expiration_seconds=expiration_seconds) + except ValueError as e: + self.logger().warning(f"Placing an order on market {str(market_info.market.name)} " + f"failed with the following error: {str(e)}") + else: + try: + order_id = self.sell_with_specific_market(market_info, amount, + order_type=order_type, price=price, + expiration_seconds=expiration_seconds) + except ValueError as e: + self.logger().warning(f"Placing an order on market {str(market_info.market.name)} " + f"failed with the following error: {str(e)}") + if order_id is None: + return + self._sb_order_tracker.add_create_order_pending(order_id) + self._market_pair_tracker.start_tracking_order_id(order_id, market_info.market, market_pair) + if is_maker: + self._maker_to_taker_order_ids[order_id] = [] + else: + self._taker_to_maker_order_ids[order_id] = maker_order_id + self._maker_to_taker_order_ids[maker_order_id] += [order_id] + self.set_ongoing_hedging(fill_records, order_id) + return order_id + + def cancel_maker_order(self, market_pair: MakerTakerMarketPair, order_id: str): + market_trading_pair_tuple = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + super().cancel_order(market_trading_pair_tuple.maker, order_id) + # ---------------------------------------------------------------------------------------------------------- + # + + # + # Override the stop tracking entry points to include the market pair tracker as well. + # ---------------------------------------------------------------------------------------------------------- + def stop_tracking_limit_order(self, market_trading_pair_tuple, order_id: str): + self._market_pair_tracker.stop_tracking_order_id(order_id) + self.stop_tracking_limit_order(self, market_trading_pair_tuple, order_id) + + def stop_tracking_market_order(self, market_trading_pair_tuple, order_id: str): + self._market_pair_tracker.stop_tracking_order_id(order_id) + self.stop_tracking_market_order(self, market_trading_pair_tuple, order_id) + # ---------------------------------------------------------------------------------------------------------- + # + + # Removes orders from pending_create + def did_create_buy_order(self, order_created_event): + order_id = order_created_event.order_id + self._sb_order_tracker.remove_create_order_pending(order_id) + + def did_create_sell_order(self, order_created_event): + order_id = order_created_event.order_id + self._sb_order_tracker.remove_create_order_pending(order_id) + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + # ---------------------------------------------------------------------------------------------------------- + # Helpers + def check_multiple_buy_orders(self, fill_records: List[OrderFilledEvent]): + maker_order_ids = [r.order_id for _, r in fill_records] + if len(set(maker_order_ids)) != 1: + self.logger().warning("Multiple buy maker orders") + + def check_multiple_sell_orders(self, fill_records: List[OrderFilledEvent]): + maker_order_ids = [r.order_id for _, r in fill_records] + if len(set(maker_order_ids)) != 1: + self.logger().warning("Multiple sell maker orders") + + def get_unhedged_buy_records(self, market_pair: MakerTakerMarketPair) -> List[OrderFilledEvent]: + buy_fill_records = self._order_fill_buy_events.get(market_pair, []) + return self.get_unhedged_events(buy_fill_records) + + def get_unhedged_sell_records(self, market_pair: MakerTakerMarketPair) -> List[OrderFilledEvent]: + sell_fill_records = self._order_fill_sell_events.get(market_pair, []) + return self.get_unhedged_events(sell_fill_records) + + def get_unhedged_events(self, fill_records: List[OrderFilledEvent]) -> List[OrderFilledEvent]: + return [ + fill_event for fill_event in fill_records if ( + not self.is_fill_event_in_ongoing_hedging(fill_event) + ) + ] + + def is_fill_event_in_ongoing_hedging(self, fill_event: OrderFilledEvent) -> bool: + trade_id = fill_event[1].exchange_trade_id + for maker_exchange_trade_ids in self._ongoing_hedging: + for id in maker_exchange_trade_ids: + if trade_id is id: + return True + return False + + def set_ongoing_hedging(self, fill_records: List[OrderFilledEvent], order_id: str): + maker_exchange_trade_ids = tuple(r.exchange_trade_id for _, r in fill_records) + self._ongoing_hedging[maker_exchange_trade_ids] = order_id + + def del_order_from_ongoing_hedging(self, taker_order_id: str): + maker_exchange_trade_ids = self._ongoing_hedging.inverse[taker_order_id] + del self._ongoing_hedging[maker_exchange_trade_ids] diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map_pydantic.py b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map_pydantic.py new file mode 100644 index 0000000..78be284 --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map_pydantic.py @@ -0,0 +1,503 @@ +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import Dict, Tuple, Union + +from pydantic import BaseModel, Field, root_validator, validator + +import hummingbot.client.settings as settings +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_validators import validate_bool, validate_connector +from hummingbot.client.config.strategy_config_data_types import BaseTradingStrategyMakerTakerConfigMap +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair + + +class ConversionRateModel(BaseClientModel, ABC): + @abstractmethod + def get_conversion_rates( + self, market_pair: MakerTakerMarketPair + ) -> Tuple[str, str, Decimal, str, str, Decimal]: + pass + + +class OracleConversionRateMode(ConversionRateModel): + class Config: + title = "rate_oracle_conversion_rate" + + def get_conversion_rates( + self, market_pair: MakerTakerMarketPair + ) -> Tuple[str, str, Decimal, str, str, Decimal]: + """ + Find conversion rates from taker market to maker market + :param market_pair: maker and taker trading pairs for which to do conversion + :return: A tuple of quote pair symbol, quote conversion rate source, quote conversion rate, + base pair symbol, base conversion rate source, base conversion rate + """ + from .cross_exchange_market_making import CrossExchangeMarketMakingStrategy + quote_pair = f"{market_pair.taker.quote_asset}-{market_pair.maker.quote_asset}" + if market_pair.taker.quote_asset != market_pair.maker.quote_asset: + quote_rate_source = RateOracle.get_instance().source.name + quote_rate = RateOracle.get_instance().get_pair_rate(quote_pair) + else: + quote_rate_source = "fixed" + quote_rate = Decimal("1") + + base_pair = f"{market_pair.taker.base_asset}-{market_pair.maker.base_asset}" + if market_pair.taker.base_asset != market_pair.maker.base_asset: + base_rate_source = RateOracle.get_instance().source.name + base_rate = RateOracle.get_instance().get_pair_rate(base_pair) + else: + base_rate_source = "fixed" + base_rate = Decimal("1") + + gas_pair = None + if CrossExchangeMarketMakingStrategy.is_gateway_market(market_pair.taker): + if hasattr(market_pair.taker.market, "network_transaction_fee"): + transaction_fee: TokenAmount = market_pair.taker.market.network_transaction_fee + if transaction_fee is not None: + gas_pair = f"{transaction_fee.token}-{market_pair.maker.quote_asset}" + + if gas_pair is not None and transaction_fee.token != market_pair.maker.quote_asset: + gas_rate_source = RateOracle.get_instance().source.name + gas_rate = RateOracle.get_instance().get_pair_rate(gas_pair) + else: + gas_rate_source = "fixed" + gas_rate = Decimal("1") + + return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, gas_rate_source, gas_rate + + +class TakerToMakerConversionRateMode(ConversionRateModel): + taker_to_maker_base_conversion_rate: Decimal = Field( + default=Decimal("1.0"), + description="A fixed conversion rate between the maker and taker trading pairs based on the maker base asset.", + gt=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Enter conversion rate for taker base asset value to maker base asset value, e.g. " + "if maker base asset is USD and the taker is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25" + ), + prompt_on_new=True, + ), + ) + taker_to_maker_quote_conversion_rate: Decimal = Field( + default=Decimal("1.0"), + description="A fixed conversion rate between the maker and taker trading pairs based on the maker quote asset.", + gt=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Enter conversion rate for taker quote asset value to maker quote asset value, e.g. " + "if maker quote asset is USD and the taker is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25" + ), + prompt_on_new=True, + ), + ) + gas_to_maker_base_conversion_rate: Decimal = Field( + default=Decimal("1.0"), + description="A fixed conversion rate between the maker quote asset and taker gas asset.", + gt=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Enter conversion rate for gas token value of taker gateway exchange to maker base asset value, e.g. " + "if maker base asset is USD and the gas token is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25" + ), + prompt_on_new=True, + ), + ) + + class Config: + title = "fixed_conversion_rate" + + def get_conversion_rates( + self, market_pair: MakerTakerMarketPair + ) -> Tuple[str, str, Decimal, str, str, Decimal]: + """ + Find conversion rates from taker market to maker market + :param market_pair: maker and taker trading pairs for which to do conversion + :return: A tuple of quote pair symbol, quote conversion rate source, quote conversion rate, + base pair symbol, base conversion rate source, base conversion rate + """ + from .cross_exchange_market_making import CrossExchangeMarketMakingStrategy + quote_pair = f"{market_pair.taker.quote_asset}-{market_pair.maker.quote_asset}" + quote_rate_source = "fixed" + quote_rate = self.taker_to_maker_quote_conversion_rate + + base_pair = f"{market_pair.taker.base_asset}-{market_pair.maker.base_asset}" + base_rate_source = "fixed" + base_rate = self.taker_to_maker_base_conversion_rate + + gas_pair = None + if CrossExchangeMarketMakingStrategy.is_gateway_market(market_pair.taker): + if hasattr(market_pair.taker.market, "network_transaction_fee"): + transaction_fee: TokenAmount = market_pair.taker.market.network_transaction_fee + if transaction_fee is not None: + gas_pair = f"{transaction_fee.token}-{market_pair.maker.quote_asset}" + + gas_rate_source = "fixed" + gas_rate = self.taker_to_maker_base_conversion_rate + + return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate, gas_pair, gas_rate_source, gas_rate + + @validator( + "taker_to_maker_base_conversion_rate", + "taker_to_maker_quote_conversion_rate", + "gas_to_maker_base_conversion_rate", + pre=True, + ) + def validate_decimal(cls, v: str, values: Dict, config: BaseModel.Config, field: Field): + return super().validate_decimal(v=v, field=field) + + +CONVERSION_RATE_MODELS = { + OracleConversionRateMode.Config.title: OracleConversionRateMode, + TakerToMakerConversionRateMode.Config.title: TakerToMakerConversionRateMode, +} + + +class OrderRefreshMode(BaseClientModel, ABC): + @abstractmethod + def get_cancel_order_threshold(self) -> Decimal: + pass + + @abstractmethod + def get_expiration_seconds(self) -> Decimal: + pass + + +class PassiveOrderRefreshMode(OrderRefreshMode): + cancel_order_threshold: Decimal = Field( + default=Decimal("5.0"), + description="Profitability threshold to cancel a trade.", + gt=-100.0, + lt=100.0, + client_data=ClientFieldData( + prompt=lambda mi: "What is the threshold of profitability to cancel a trade? (Enter 1 to indicate 1%)", + prompt_on_new=True, + ), + ) + + limit_order_min_expiration: Decimal = Field( + default=130.0, + description="Limit order expiration time limit.", + gt=0.0, + client_data=ClientFieldData( + prompt=lambda mi: "How often do you want limit orders to expire (in seconds)?", + prompt_on_new=True, + ), + ) + + class Config: + title = "passive_order_refresh" + + def get_cancel_order_threshold(self) -> Decimal: + return self.cancel_order_threshold / Decimal("100") + + def get_expiration_seconds(self) -> Decimal: + return self.limit_order_min_expiration + + @validator( + "cancel_order_threshold", + "limit_order_min_expiration", + pre=True, + ) + def validate_decimal(cls, v: str, values: Dict, config: BaseModel.Config, field: Field): + return super().validate_decimal(v=v, field=field) + + +class ActiveOrderRefreshMode(OrderRefreshMode): + class Config: + title = "active_order_refresh" + + def get_cancel_order_threshold(self) -> Decimal: + return Decimal('nan') + + def get_expiration_seconds(self) -> Decimal: + return Decimal('nan') + + +ORDER_REFRESH_MODELS = { + PassiveOrderRefreshMode.Config.title: PassiveOrderRefreshMode, + ActiveOrderRefreshMode.Config.title: ActiveOrderRefreshMode, +} + + +class CrossExchangeMarketMakingConfigMap(BaseTradingStrategyMakerTakerConfigMap): + strategy: str = Field(default="cross_exchange_market_making", client_data=None) + + min_profitability: Decimal = Field( + default=..., + description="The minimum estimated profitability required to open a position.", + ge=-100.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: "What is the minimum profitability for you to make a trade? (Enter 1 to indicate 1%)", + prompt_on_new=True, + ), + ) + order_amount: Decimal = Field( + default=..., + description="The strategy order amount.", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: CrossExchangeMarketMakingConfigMap.order_amount_prompt(mi), + prompt_on_new=True, + ) + ) + adjust_order_enabled: bool = Field( + default=True, + description="Adjust order price to be one tick above the top bid or below the top ask.", + client_data=ClientFieldData( + prompt=lambda mi: "Do you want to enable adjust order? (Yes/No)" + ), + ) + order_refresh_mode: Union[ActiveOrderRefreshMode, PassiveOrderRefreshMode] = Field( + default=ActiveOrderRefreshMode.construct(), + description="Refresh orders by cancellation or by letting them expire.", + client_data=ClientFieldData( + prompt=lambda mi: f"Select the order refresh mode ({'/'.join(list(ORDER_REFRESH_MODELS.keys()))})", + prompt_on_new=True, + ), + ) + top_depth_tolerance: Decimal = Field( + default=Decimal("0.0"), + description="Volume requirement for determining a possible top bid or ask price from the order book.", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: CrossExchangeMarketMakingConfigMap.top_depth_tolerance_prompt(mi), + ), + ) + anti_hysteresis_duration: float = Field( + default=60.0, + description="Minimum time limit between two subsequent order adjustments.", + gt=0.0, + client_data=ClientFieldData( + prompt=lambda mi: "What is the minimum time interval you want limit orders to be adjusted? (in seconds)", + ), + ) + order_size_taker_volume_factor: Decimal = Field( + default=Decimal("25.0"), + description="Taker order size as a percentage of volume.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What percentage of hedge-able volume would you like to be traded on the taker market? " + "(Enter 1 to indicate 1%)" + ), + ), + ) + order_size_taker_balance_factor: Decimal = Field( + default=Decimal("99.5"), + description="Taker order size as a percentage of the available balance.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What percentage of asset balance would you like to use for hedging trades on the taker market? " + "(Enter 1 to indicate 1%)" + ), + ), + ) + order_size_portfolio_ratio_limit: Decimal = Field( + default=Decimal("16.67"), + description="Order size as a maker and taker account balance ratio.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What ratio of your total portfolio value would you like to trade on the maker and taker markets? " + "Enter 50 for 50%" + ), + ), + ) + conversion_rate_mode: Union[OracleConversionRateMode, TakerToMakerConversionRateMode] = Field( + default=OracleConversionRateMode.construct(), + description="Convert between different trading pairs using fixed conversion rates or using the rate oracle.", + client_data=ClientFieldData( + prompt=lambda mi: f"Select the conversion rate mode ({'/'.join(list(CONVERSION_RATE_MODELS.keys()))})", + prompt_on_new=True, + ), + ) + slippage_buffer: Decimal = Field( + default=Decimal("5.0"), + description="Allowed slippage to fill ensure taker orders are filled.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "How much buffer do you want to add to the price to account for slippage for taker orders " + "Enter 1 to indicate 1%" + ), + prompt_on_new=True, + ), + ) + + debug_price_shim: bool = Field( + default=False, + description="Usd the debug price shim to mock gateway price.", + client_data=ClientFieldData( + prompt=lambda mi: ( + "Do you want to enable the debug price shim for integration tests? If you don't know what this does " + "you should keep it disabled." + ), + ), + ) + gateway_transaction_cancel_interval: int = Field( + default= 600, + description="Gateway transaction cancellation timeout.", + ge=1, + client_data=ClientFieldData( + prompt=lambda mi: ( + "After what time should blockchain transactions be cancelled if they are not included in a block? " + "(this only affects decentralized exchanges) (Enter time in seconds)" + ), + prompt_on_new=True, + ), + ) + taker_market: ClientConfigEnum( + value="TakerMarkets", # noqa: F821 + names={e: e for e in + sorted(AllConnectorSettings.get_exchange_names().union( + AllConnectorSettings.get_gateway_amm_connector_names() + ))}, + type=str, + ) = Field( + default=..., + description="The name of the taker exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter your taker connector (Exchange/AMM/CLOB)", + prompt_on_new=True, + ), + ) + + # === prompts === + + @classmethod + def top_depth_tolerance_prompt(cls, model_instance: 'CrossExchangeMarketMakingConfigMap') -> str: + maker_market = model_instance.maker_market_trading_pair + base_asset, quote_asset = maker_market.split("-") + return f"What is your top depth tolerance? (in {base_asset})" + + @classmethod + def order_amount_prompt(cls, model_instance: 'CrossExchangeMarketMakingConfigMap') -> str: + trading_pair = model_instance.maker_market_trading_pair + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order?" + + # === specific validations === + @validator("order_refresh_mode", pre=True) + def validate_order_refresh_mode(cls, v: Union[str, ActiveOrderRefreshMode, PassiveOrderRefreshMode]): + if isinstance(v, (ActiveOrderRefreshMode, PassiveOrderRefreshMode, Dict)): + sub_model = v + elif v not in ORDER_REFRESH_MODELS: + raise ValueError( + f"Invalid order refresh mode, please choose value from {list(ORDER_REFRESH_MODELS.keys())}." + ) + else: + sub_model = ORDER_REFRESH_MODELS[v].construct() + return sub_model + + @validator("conversion_rate_mode", pre=True) + def validate_conversion_rate_mode(cls, v: Union[str, OracleConversionRateMode, TakerToMakerConversionRateMode]): + if isinstance(v, (OracleConversionRateMode, TakerToMakerConversionRateMode, Dict)): + sub_model = v + elif v not in CONVERSION_RATE_MODELS: + raise ValueError( + f"Invalid conversion rate mode, please choose value from {list(CONVERSION_RATE_MODELS.keys())}." + ) + else: + sub_model = CONVERSION_RATE_MODELS[v].construct() + return sub_model + + # === generic validations === + + @validator( + "adjust_order_enabled", + pre=True, + ) + def validate_bool(cls, v: str): + """Used for client-friendly error output.""" + if isinstance(v, str): + ret = validate_bool(v) + if ret is not None: + raise ValueError(ret) + return v + + @validator( + "min_profitability", + "order_amount", + "top_depth_tolerance", + "anti_hysteresis_duration", + "order_size_taker_volume_factor", + "order_size_taker_balance_factor", + "order_size_portfolio_ratio_limit", + "slippage_buffer", + pre=True, + ) + def validate_decimal(cls, v: str, field: Field): + """Used for client-friendly error output.""" + return super().validate_decimal(v, field) + + # === post-validations === + + @root_validator() + def post_validations(cls, values: Dict): + cls.exchange_post_validation(values) + cls.update_oracle_settings(values) + return values + + @classmethod + def exchange_post_validation(cls, values: Dict): + if "maker_market" in values.keys(): + settings.required_exchanges.add(values["maker_market"]) + if "taker_market" in values.keys(): + settings.required_exchanges.add(values["taker_market"]) + + @classmethod + def update_oracle_settings(cls, values: str): + if not ("use_oracle_conversion_rate" in values.keys() and + "maker_market_trading_pair" in values.keys() and + "taker_market_trading_pair" in values.keys()): + return + use_oracle = values["use_oracle_conversion_rate"] + first_base, first_quote = values["maker_market_trading_pair"].split("-") + second_base, second_quote = values["taker_market_trading_pair"].split("-") + if use_oracle and (first_base != second_base or first_quote != second_quote): + settings.required_rate_oracle = True + settings.rate_oracle_pairs = [] + if first_base != second_base: + settings.rate_oracle_pairs.append(f"{second_base}-{first_base}") + if first_quote != second_quote: + settings.rate_oracle_pairs.append(f"{second_quote}-{first_quote}") + else: + settings.required_rate_oracle = False + settings.rate_oracle_pairs = [] + + @validator( + "maker_market", + "taker_market", + pre=True + ) + def validate_exchange(cls, v: str, field: Field): + """Used for client-friendly error output.""" + if field.name == "maker_market": + super().validate_exchange(v=v, field=field) + if field.name == "taker_market": + ret = validate_connector(v) + if ret is not None: + raise ValueError(ret) + cls.__fields__["taker_market"].type_ = ClientConfigEnum( # rebuild the exchanges enum + value="TakerMarkets", # noqa: F821 + names={e: e for e in sorted( + AllConnectorSettings.get_exchange_names().union( + AllConnectorSettings.get_gateway_amm_connector_names() + ))}, + type=str, + ) + cls._clear_schema_cache() + return v diff --git a/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pxd b/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pxd new file mode 100644 index 0000000..54989ff --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pxd @@ -0,0 +1,13 @@ +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class OrderIDMarketPairTracker(TimeIterator): + cdef: + object _order_id_to_tracking_item + float _expiry_timeout + + cdef object c_get_market_pair_from_order_id(self, str order_id) + cdef object c_get_exchange_from_order_id(self, str order_id) + cdef c_start_tracking_order_id(self, str order_id, object exchange, object market_pair) + cdef c_stop_tracking_order_id(self, str order_id) + cdef c_check_and_expire_tracking_items(self) diff --git a/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pyx b/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pyx new file mode 100644 index 0000000..0ce5c62 --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/order_id_market_pair_tracker.pyx @@ -0,0 +1,88 @@ +from collections import OrderedDict + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair + +s_float_nan = float("nan") + + +cdef class OrderIDMarketPairTrackingItem: + cdef: + public str order_id + public object exchange + public object market_pair + public double expiry_timestamp + + def __init__(self, str order_id, object exchange, object market_pair): + self.order_id = order_id + self.exchange = exchange + self.market_pair = market_pair + self.expiry_timestamp = s_float_nan + +cdef class OrderIDMarketPairTracker(TimeIterator): + def __init__(self, double expiry_timeout=3 * 60): + super().__init__() + + self._order_id_to_tracking_item = OrderedDict() + self._expiry_timeout = expiry_timeout + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self.c_check_and_expire_tracking_items() + + cdef object c_get_market_pair_from_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is not None: + return item.market_pair + return None + + def get_market_pair_from_order_id(self, order_id: str): + return self.c_get_market_pair_from_order_id(order_id) + + cdef object c_get_exchange_from_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is not None: + return item.exchange + return None + + def get_exchange_from_order_id(self, order_id: str): + return self.c_get_exchange_from_order_id(order_id) + + cdef c_start_tracking_order_id(self, str order_id, object exchange, object market_pair): + self._order_id_to_tracking_item[order_id] = OrderIDMarketPairTrackingItem(order_id, exchange, market_pair) + + def start_tracking_order_id(self, order_id: str, exchange: ExchangeBase, market_pair: MakerTakerMarketPair): + self.c_start_tracking_order_id(order_id, exchange, market_pair) + + cdef c_stop_tracking_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is None: + return + item.expiry_timestamp = self._current_timestamp + self._expiry_timeout + + def stop_tracking_order_id(self, order_id: str): + self.c_stop_tracking_order_id(order_id) + + cdef c_check_and_expire_tracking_items(self): + cdef: + list order_ids_to_delete = [] + OrderIDMarketPairTrackingItem typed_tracking_item + + for order_id, tracking_item in self._order_id_to_tracking_item.items(): + typed_tracking_item = tracking_item + if self._current_timestamp > typed_tracking_item.expiry_timestamp: + order_ids_to_delete.append(order_id) + else: + break + + for order_id in order_ids_to_delete: + del self._order_id_to_tracking_item[order_id] + + def check_and_expire_tracking_items(self): + self.c_check_and_expire_tracking_items() diff --git a/hummingbot/strategy/cross_exchange_market_making/start.py b/hummingbot/strategy/cross_exchange_market_making/start.py new file mode 100644 index 0000000..78fecda --- /dev/null +++ b/hummingbot/strategy/cross_exchange_market_making/start.py @@ -0,0 +1,72 @@ +from typing import List, Tuple, cast + +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_price_shim import GatewayPriceShim +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making import ( + CrossExchangeMarketMakingStrategy, + LogOption, +) +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + c_map = self.strategy_config_map + maker_market = c_map.maker_market.lower() + taker_market = c_map.taker_market.lower() + raw_maker_trading_pair = c_map.maker_market_trading_pair + raw_taker_trading_pair = c_map.taker_market_trading_pair + status_report_interval = self.client_config_map.strategy_report_interval + debug_price_shim = c_map.debug_price_shim + + try: + maker_trading_pair: str = raw_maker_trading_pair + taker_trading_pair: str = raw_taker_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(maker_market, [maker_trading_pair])[0] + taker_assets: Tuple[str, str] = self._initialize_market_assets(taker_market, [taker_trading_pair])[0] + except ValueError as e: + self.notify(str(e)) + return + + market_names: List[Tuple[str, List[str]]] = [ + (maker_market, [maker_trading_pair]), + (taker_market, [taker_trading_pair]), + ] + + self._initialize_markets(market_names) + maker_data = [self.markets[maker_market], maker_trading_pair] + list(maker_assets) + taker_data = [self.markets[taker_market], taker_trading_pair] + list(taker_assets) + maker_market_trading_pair_tuple = MarketTradingPairTuple(*maker_data) + taker_market_trading_pair_tuple = MarketTradingPairTuple(*taker_data) + self.market_trading_pair_tuples = [maker_market_trading_pair_tuple, taker_market_trading_pair_tuple] + self.market_pair = MakerTakerMarketPair(maker=maker_market_trading_pair_tuple, taker=taker_market_trading_pair_tuple) + + if taker_market in AllConnectorSettings.get_gateway_amm_connector_names(): + if debug_price_shim: + amm_connector: GatewayEVMAMM = cast(GatewayEVMAMM, self.market_pair.taker.market) + GatewayPriceShim.get_instance().patch_prices( + maker_market, + maker_trading_pair, + amm_connector.connector_name, + amm_connector.chain, + amm_connector.network, + taker_trading_pair + ) + + strategy_logging_options = ( + LogOption.CREATE_ORDER, + LogOption.ADJUST_ORDER, + LogOption.MAKER_ORDER_FILLED, + LogOption.REMOVING_ORDER, + LogOption.STATUS_REPORT, + LogOption.MAKER_ORDER_HEDGED + ) + self.strategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=c_map, + market_pairs=[self.market_pair], + status_report_interval=status_report_interval, + logging_options=strategy_logging_options, + hb_app_notification=True, + ) diff --git a/hummingbot/strategy/cross_exchange_mining/__init__.py b/hummingbot/strategy/cross_exchange_mining/__init__.py new file mode 100644 index 0000000..56feb8e --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/__init__.py @@ -0,0 +1,7 @@ +from .cross_exchange_mining import CrossExchangeMiningStrategy +from .cross_exchange_mining_pair import CrossExchangeMiningPair + +__all__ = [ + CrossExchangeMiningPair, + CrossExchangeMiningStrategy, +] diff --git a/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pxd b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pxd new file mode 100644 index 0000000..227de9d --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pxd @@ -0,0 +1,56 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t + +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.order_book cimport OrderBook +from hummingbot.strategy.strategy_base cimport StrategyBase + +from .order_id_market_pair_tracker cimport OrderIDMarketPairTracker + + +cdef class CrossExchangeMiningStrategy(StrategyBase): + cdef: + object _config_map + set _maker_markets + set _taker_markets + bint _all_markets_ready + bint _adjust_orders_enabled + dict _anti_hysteresis_timers + double _last_timestamp + double _status_report_interval + dict _order_fill_buy_events + dict _order_fill_sell_events + dict _suggested_price_samples + dict _market_pairs + int64_t _logging_options + OrderIDMarketPairTracker _market_pair_tracker + bint _hb_app_notification + list _maker_order_ids + double _last_conv_rates_logged + object _volatility_pct + object _balance_timer + object _volatility_timer + object _balance_flag + object _min_prof_adj + object _min_prof_adj_t + object _td_bias_min + object _avg_vol + object _tol_o + object _adjcount + object _limit_order_timer + bint _maker_side + + cdef check_order(self, object market_pair, object active_order, object is_buy) + cdef check_balance(self, object market_pair) + cdef set_order(self, object market_pair, object is_buy) + cdef str c_place_order(self, + object market_pair, + bint is_buy, + bint is_maker, + object amount, + object price, + bint is_limit) + cdef volatility_rate(self, object market_pair) + + cdef object adjust_profitability_lag(self, object market_pair) diff --git a/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pyx b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pyx new file mode 100644 index 0000000..b84a62d --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining.pyx @@ -0,0 +1,596 @@ +import datetime as dt +import glob +import logging +import os +from decimal import Decimal +from typing import List, Tuple + +import numpy as np +import pandas as pd + +from hummingbot.connector.exchange_base import ExchangeBase + +from hummingbot.connector.exchange_base cimport ExchangeBase + +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.strategy.__utils__.trailing_indicators.instant_volatility import InstantVolatilityIndicator + +from hummingbot.core.data_type.limit_order cimport LimitOrder + +from hummingbot.core.event.events import BuyOrderCompletedEvent, SellOrderCompletedEvent +from hummingbot.strategy.cross_exchange_mining.cross_exchange_mining_config_map_pydantic import ( + CrossExchangeMiningConfigMap, +) +from hummingbot.strategy.strategy_base import StrategyBase + +from .cross_exchange_mining_pair import CrossExchangeMiningPair +from .order_id_market_pair_tracker import OrderIDMarketPairTracker + +# Cross exchange Mining script by bensmeaton@gmail.com +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_nan = Decimal("nan") +s_logger = None + + +cdef class CrossExchangeMiningStrategy(StrategyBase): + OPTION_LOG_NULL_ORDER_SIZE = 1 << 0 + OPTION_LOG_REMOVING_ORDER = 1 << 1 + OPTION_LOG_ADJUST_ORDER = 1 << 2 + OPTION_LOG_CREATE_ORDER = 1 << 3 + OPTION_LOG_MAKER_ORDER_FILLED = 1 << 4 + OPTION_LOG_STATUS_REPORT = 1 << 5 + OPTION_LOG_MAKER_ORDER_HEDGED = 1 << 6 + OPTION_LOG_ALL = 0x7fffffffffffffff + + ORDER_ADJUST_SAMPLE_INTERVAL = 5 + ORDER_ADJUST_SAMPLE_WINDOW = 12 + + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 15 + CANCEL_EXPIRY_DURATION = 60.0 + + @classmethod + def logger(cls): + global s_logger + if s_logger is None: + s_logger = logging.getLogger(__name__) + return s_logger + + def init_params(self, + config_map: CrossExchangeMiningConfigMap, + market_pairs: List[CrossExchangeMiningPair], + status_report_interval: float = 900, + logging_options: int = OPTION_LOG_ALL, + hb_app_notification: bool = False, + ): + """ + Initializes a cross exchange market making strategy object. + + :param config_map: Strategy configuration map + :param market_pairs: list of cross exchange market pairs + :param logging_options: bit field for what types of logging to enable in this strategy object + :param hb_app_notification: + """ + self._config_map = config_map + self._market_pairs = { + (market_pair.maker.market, market_pair.maker.trading_pair): market_pair + for market_pair in market_pairs + } + self._maker_markets = set([market_pair.maker.market for market_pair in market_pairs]) + self._taker_markets = set([market_pair.taker.market for market_pair in market_pairs]) + self._all_markets_ready = False + self._volatility_pct = 0 + self._volatility_timer = 0 + self._balance_timer = 0 + self._maker_side = True + self._balance_flag = True + self._tol_o = 0.1 / 100 + self._min_prof_adj = 0 + self._min_prof_adj_t = 0 + self._adjcount = [] + self._avg_vol = InstantVolatilityIndicator(sampling_length=self.volatility_buffer_size) + self._anti_hysteresis_timers = {} + self._order_fill_buy_events = {} + self._order_fill_sell_events = {} + self._suggested_price_samples = {} + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._market_pair_tracker = OrderIDMarketPairTracker() + self._last_conv_rates_logged = 0 + self._hb_app_notification = hb_app_notification + + self._maker_order_ids = [] + cdef: + list all_markets = list(self._maker_markets | self._taker_markets) + + self.c_add_markets(all_markets) + + @property + def order_amount(self): + return self._config_map.order_amount + + @property + def min_profitability(self): + return self._config_map.min_profitability / Decimal("100") + + @property + def avg_vol(self): + return self._avg_vol + + @avg_vol.setter + def avg_vol(self, indicator: InstantVolatilityIndicator): + self._avg_vol = indicator + + @property + def min_prof_adj_timer(self): + return self._config_map.min_prof_adj_timer + + @property + def volatility_buffer_size(self): + return self._config_map.volatility_buffer_size + + @property + def min_order_amount(self): + return self._config_map.min_order_amount + + @property + def rate_curve(self): + return self._config_map.rate_curve + + @property + def trade_fee(self): + return self._config_map.trade_fee + + @property + def status_report_interval(self): + return self._status_report_interval + + @property + def balance_adjustment_duration(self): + return self._config_map.balance_adjustment_duration + + @property + def min_prof_tol_high(self): + return self._config_map.min_prof_tol_high / Decimal("100") + + @property + def min_prof_tol_low(self): + return self._config_map.min_prof_tol_low / Decimal("100") + + @property + def slippage_buffer(self): + return self._config_map.slippage_buffer / Decimal("100") + + @property + def hanging_order_ids(self) -> List[str]: + return self._hanging_order_ids + + @property + def active_limit_orders(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return [(ex, order) for ex, order in self._sb_order_tracker.active_limit_orders + if order.client_order_id in self._maker_order_ids] + + def format_status(self) -> str: + cdef: + list lines = [] + list warning_lines = [] + dict tracked_maker_orders = {} + LimitOrder typed_limit_order + + # Go through the currently open limit orders, and group them by market pair. + active_orders_status = [] + limit_orders = list(self._sb_order_tracker.c_get_limit_orders().values()) + if limit_orders: + for key in [elem for elem in limit_orders[0]]: + active_orders_status.append(limit_orders[0][key]) + + for market_pair in self._market_pairs.values(): + warning_lines.extend(self.network_warning([market_pair.maker, market_pair.taker])) + + markets_df = self.market_status_data_frame([market_pair.maker, market_pair.taker]) + lines.extend(["", " Markets:"] + + [" " + line for line in str(markets_df).split("\n")]) + + assets_df = self.wallet_balance_data_frame([market_pair.maker, market_pair.taker]) + lines.extend(["", " Assets:"] + + [" " + line for line in str(assets_df).split("\n")]) + + # See if there're any open orders. + if active_orders_status: + lines.extend(["", " Active orders:"]) + for order in active_orders_status: + if order.is_buy: + lines.extend(["Current buy order of : " + str(order.trading_pair) + " with quantity: " + str(round(order.quantity, 5)) + " of " + str(order.base_currency) + " at price: " + str(round(order.price, 5))]) + else: + lines.extend(["Current sell order of : " + str(order.trading_pair) + " with quantity: " + str(round(order.quantity, 5)) + " of " + str(order.base_currency) + " at price: " + str(round(order.price, 5))]) + else: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning([market_pair.maker, market_pair.taker])) + + if len(warning_lines) > 0: + lines.extend(["", " *** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + cdef volatility_rate(self, market_pair): + cdef: + ExchangeBase maker_market = market_pair.maker.market + ExchangeBase taker_market = market_pair.taker.market + mid_price = market_pair.maker.get_mid_price() + self._avg_vol.add_sample(mid_price) + vol_abs = Decimal((self._avg_vol.current_value / float(mid_price)) * 3) # 3 sigma VOLATILITY ADJUSTMENT + base = Decimal(0.00025) + if self._volatility_timer + float(self.volatility_buffer_size) < self._current_timestamp or (base * round(vol_abs / base)) >= self._volatility_pct: + self._volatility_pct = base * round(vol_abs / base) + self._volatility_timer = self._current_timestamp + + cdef check_order(self, object market_pair, object active_order, object is_buy): + cdef: + ExchangeBase taker_market = market_pair.taker.market + + # Check current profitability for existing buy limit order + if is_buy: + taker_sell_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, False, active_order.quantity).result_price + current_buy_price = active_order.price + current_prof = 1 - (current_buy_price / taker_sell_price) + # self.notify_hb_app("Check buy order, Current price: " + str(round(active_order.price,5)) + " Current qty: " + str(round(active_order.quantity,3)) + " can sell on taker for: " + str(round(taker_sell_price,5)) + " profitability is: " + str(round(current_prof,5))) + # Check current profitability for existing sell limit order + if not is_buy: + taker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, active_order.quantity).result_price + current_sell_price = active_order.price + current_prof = 1 - (taker_buy_price / current_sell_price) + # self.notify_hb_app("Check sell order, Current price: " + str(round(active_order.price,5)) + " Current qty: " + str(round(active_order.quantity,3)) + " can buy on taker for: " + str(round(taker_buy_price,5)) + " profitability is: " + str(round(current_prof,5))) + # Check profitability is within tolerance around (Min profitability + volatility modifier + long term trade performance modifier) + # If not cancel limit order + prof_set = (self.min_profitability + self._volatility_pct + self._min_prof_adj) + if current_prof < (prof_set - self.min_prof_tol_low) or current_prof > (prof_set + self.min_prof_tol_high): + # self.notify_hb_app("Cancelling: " + str(current_prof) + " < " + str(prof_set - self.min_prof_tol_low) + " or > "+ str(prof_set + self.min_prof_tol_high)) + # market_trading_pair_tuple = self._sb_order_tracker.c_get_market_pair_from_order_id(active_order.client_order_id) + market_trading_pair_tuple = self._sb_order_tracker.c_get_market_pair_from_order_id(active_order.client_order_id) + if market_trading_pair_tuple: + StrategyBase.c_cancel_order(self, market_trading_pair_tuple, active_order.client_order_id) + StrategyBase.stop_tracking_limit_order(self, market_trading_pair_tuple, active_order.client_order_id) + + cdef set_order(self, object market_pair, object is_buy): + cdef: + ExchangeBase maker_market = market_pair.maker.market + ExchangeBase taker_market = market_pair.taker.market + + maker_base_side_balance = maker_market.c_get_balance(market_pair.maker.base_asset) + taker_base_side_balance = taker_market.c_get_balance(market_pair.taker.base_asset) + try: + # Average Price for buying order amount on maker market + maker_buy_price = maker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, True, self.order_amount).result_price # True = buy + except ZeroDivisionError: + maker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, True, self.order_amount).result_price # True = buy + try: + # Average Price for buying order amount on taker market + taker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, self.order_amount).result_price # True = buy + except ZeroDivisionError: + taker_buy_price = maker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, self.order_amount).result_price # True = buy + # quantity of maker quote amount in base if sold on maker market + maker_quote_side_balance_in_base = maker_market.c_get_available_balance(market_pair.maker.quote_asset) / maker_buy_price + # quantity of taker quote amount in base if sold on taker market + taker_quote_side_balance_in_base = taker_market.c_get_available_balance(market_pair.taker.quote_asset) / taker_buy_price + + var_list = [maker_base_side_balance, taker_base_side_balance, maker_buy_price, taker_buy_price, maker_quote_side_balance_in_base, taker_quote_side_balance_in_base] + # self.notify_hb_app("Maker Buffer Price Sell Set: " + str(var_list)) + for item in var_list: + if Decimal.is_nan(item): + return s_decimal_nan, s_decimal_nan + try: + if not is_buy: # Sell base on Maker side + # Sell base on Maker side, take minimum of order amount in base, base amount on maker side and quote amount in base on taker side + order_amount_maker_sell = min(float(taker_quote_side_balance_in_base), float(maker_base_side_balance), float(self.order_amount)) + if Decimal(order_amount_maker_sell) < self.min_order_amount: + return s_decimal_nan, s_decimal_nan + # you are selling base asset on maker, using the actual order amount calculate the average price you would get if you bought that amount back on the taker side. + taker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, Decimal(order_amount_maker_sell)).result_price + # Therefore you can sell the amount for the price you would get if you bought on the taker market + min profitabilty + maker_set_price_sell = taker_buy_price * (1 + (self.min_profitability + self._volatility_pct + self._min_prof_adj)) + # self.notify_hb_app("Looking to sell on maker, Can buy " + str(round(order_amount_maker_sell,2)) + " on taker for: " + str(round(taker_buy_price,5)) + " so sell on maker for: " + str(round(maker_set_price_sell,5))) + return Decimal(maker_set_price_sell), Decimal(order_amount_maker_sell) + + if is_buy: # buy base on Maker side + # Buy base on Maker side + # Buy base on Maker side, take minimum of order amount in base, quote amount on maker side in base and base amount on taker side + order_amount_maker_buy = min(float(taker_base_side_balance), float(maker_quote_side_balance_in_base), float(self.order_amount)) + if Decimal(order_amount_maker_buy) < self.min_order_amount: + return s_decimal_nan, s_decimal_nan + # you are buying base asset on maker, using the actual order amount calculate the average price you would get if you sold that amount back on the taker side. + taker_sell_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, False, Decimal(order_amount_maker_buy)).result_price + # Therefore you can sell the amount for the price you would get if you sold on the taker market - min profitabilty + maker_set_price_buy = taker_sell_price * (1 - (self.min_profitability + self._volatility_pct + self._min_prof_adj)) + # self.notify_hb_app("Looking to buy on maker, Can sell " + str(round(order_amount_maker_buy,2)) + " on taker for: " + str(round(taker_sell_price,5)) + " so buy on maker for: " + str(round(maker_set_price_buy,5))) + return Decimal(maker_set_price_buy), Decimal(order_amount_maker_buy) + except Exception: + return s_decimal_nan, s_decimal_nan + + cdef check_balance(self, object market_pair): + cdef: + ExchangeBase maker_market = market_pair.maker.market + ExchangeBase taker_market = market_pair.taker.market + + # Script aims to continuosely check and reblance base assets on each exchange to maintain order amount across exchanges. This is also used to complete the XEMM trade after a limit order has been completed. + maker_base_side_balance = maker_market.c_get_balance(market_pair.maker.base_asset) + taker_base_side_balance = taker_market.c_get_balance(market_pair.taker.base_asset) + self._balance_flag = False + # self.notify_hb_app(str(taker_base_side_balance) +" " + str (maker_base_side_balance) + " " + str(self.order_amount)) + if (self.order_amount - self.min_order_amount) <= (taker_base_side_balance + maker_base_side_balance) <= (self.order_amount + self.min_order_amount): + return + + try: + maker_buy_price = maker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, True, self.order_amount).result_price # True = buy + except ZeroDivisionError: + maker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, True, self.order_amount).result_price # True = buy + try: + taker_buy_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, self.order_amount).result_price # True = buy + except ZeroDivisionError: + taker_buy_price = maker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, self.order_amount).result_price # True = buy + + maker_quote_side_balance_in_base = maker_market.c_get_available_balance(market_pair.maker.quote_asset) / maker_buy_price + taker_quote_side_balance_in_base = taker_market.c_get_available_balance(market_pair.taker.quote_asset) / taker_buy_price + + var_list = [maker_base_side_balance, taker_base_side_balance, maker_buy_price, taker_buy_price, maker_quote_side_balance_in_base, taker_quote_side_balance_in_base] + + for item in var_list: + if Decimal.is_nan(item): + return + + check_mat = [] + buytaker = False + selltaker = False + + if taker_base_side_balance + maker_base_side_balance < (self.order_amount - self.min_order_amount): # Need to Buy taker base balance as maker side does not balance + taker_qty = Decimal.min((self.order_amount - (taker_base_side_balance + maker_base_side_balance)), taker_quote_side_balance_in_base) + if taker_qty: + taker_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, True, Decimal(taker_qty)).result_price + # self.notify_hb_app(str(taker_qty) + " t " + str(taker_price)) + if taker_qty > self.min_order_amount: + buytaker = True + check_mat = [False, market_pair, True, taker_qty, taker_price] + + if taker_base_side_balance + maker_base_side_balance > (self.order_amount + self.min_order_amount): # Need to Sell taker base balance as maker side does not balance + taker_qty = Decimal.min(((taker_base_side_balance + maker_base_side_balance) - self.order_amount), taker_base_side_balance) + if taker_qty: + taker_price = taker_market.c_get_vwap_for_volume(market_pair.taker.trading_pair, False, Decimal(taker_qty)).result_price + if taker_qty > self.min_order_amount: + selltaker = True + check_mat = [False, market_pair, False, taker_qty, taker_price] + + if maker_base_side_balance + taker_base_side_balance < (self.order_amount - self.min_order_amount): # Need to Buy maker base balance as taker side does not balance + maker_qty = Decimal.min((self.order_amount - (maker_base_side_balance + taker_base_side_balance)), maker_quote_side_balance_in_base) + if maker_qty: + maker_price = maker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, True, Decimal(maker_qty)).result_price + # self.notify_hb_app(str(maker_qty) + " m " + str(maker_price)) + if maker_qty > self.min_order_amount: + if ((maker_price < taker_price and buytaker) or not buytaker): + check_mat = [True, market_pair, True, maker_qty, maker_price] + + if maker_base_side_balance + taker_base_side_balance > (self.order_amount + self.min_order_amount): # Need to Sell maker base balance as taker side does not balance + maker_qty = Decimal.min(((maker_base_side_balance + taker_base_side_balance) - self.order_amount), maker_base_side_balance) + if maker_qty: + maker_price = maker_market.c_get_vwap_for_volume(market_pair.maker.trading_pair, False, Decimal(maker_qty)).result_price + if maker_qty > self.min_order_amount: + if ((maker_price > taker_price and selltaker) or not selltaker): + check_mat = [True, market_pair, False, maker_qty, maker_price] + # self.notify_hb_app(str(check_mat)) + if check_mat: + self._balance_timer = self._current_timestamp + self.balance_adjustment_duration + self._balance_flag = True + return check_mat + else: + return + + cdef c_tick(self, double timestamp): + """ + Clock tick entry point. + + :param timestamp: current tick timestamp + """ + StrategyBase.c_tick(self, timestamp) + # Perform clock tick with the market pair tracker. + self._market_pair_tracker.c_tick(timestamp) + + cdef: + list active_limit_orders = self.active_limit_orders + LimitOrder limit_order + bint has_active_bid = False + bint has_active_ask = False + + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self._sb_markets]) + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + return + else: + # Markets are ready, ok to proceed. + if self.OPTION_LOG_STATUS_REPORT: + self.logger().info("Markets are ready. Trading started.") + + for market_pair in self._market_pairs.values(): + buy_orders = [] + sell_orders = [] + active_orders = [] + + # Check for active limit orders and create buy and sell order lists + limit_orders = list(self._sb_order_tracker.c_get_limit_orders().values()) + if limit_orders: + for key in [elem for elem in limit_orders[0]]: + active_orders.append(limit_orders[0][key]) + if limit_orders[0][key].is_buy: + buy_orders.append(limit_orders[0][key]) + else: + sell_orders.append(limit_orders[0][key]) + + self.volatility_rate(market_pair) + # If there are buy orders check buy orders + if buy_orders: + for active_buy_order in buy_orders: + self.check_order(market_pair, active_buy_order, True) + + # If there are sell orders check sell orders + if sell_orders: + for active_sell_order in sell_orders: + self.check_order(market_pair, active_sell_order, False) + + # If there are no buy orders set them + if not buy_orders: + buyprice, buysize = self.set_order(market_pair, True) + if not Decimal.is_nan(buyprice): + if not self._balance_flag: + self.c_place_order(market_pair, True, True, buysize, buyprice, True) + + # If there are no sell orders set them + if not sell_orders: + sellprice, sellsize = self.set_order(market_pair, False) + if not Decimal.is_nan(sellprice): + if not self._balance_flag: + self.c_place_order(market_pair, False, True, sellsize, sellprice, True) + + # Balance base across exchanges + if self._current_timestamp > self._balance_timer: + bal_check = self.check_balance(market_pair) + if bal_check: + # clear any existing trades to prioritise balance + self.cancel_all_trades(active_orders) + if bal_check[2]: # buy order + self.c_place_order(bal_check[1], True, bal_check[0], bal_check[3], bal_check[4] * (1 + self.slippage_buffer), False) + return + else: # sell order + self.c_place_order(bal_check[1], False, bal_check[0], bal_check[3], bal_check[4] * (1 - self.slippage_buffer), False) + return + + if self._current_timestamp > self._min_prof_adj_t: + self._min_prof_adj = Decimal(self.adjust_profitability_lag(market_pair)) + self._min_prof_adj_t = self._current_timestamp + self.min_prof_adj_timer + self.cancel_all_trades(active_orders) + self.logger().info("Adjusted Min Profitability from previous trades") + + cdef adjust_profitability_lag(self, object market_pair): + cdef: + ExchangeBase maker_market = market_pair.maker.market + ExchangeBase taker_market = market_pair.taker.market + + feea1 = float(self.trade_fee) + min_prof = 0 + rate_curve = float(self.rate_curve) + try: + fileloc = max(glob.glob('data/trades_*.csv'), key=os.path.getmtime) + except Exception: + return 0 + df = pd.read_csv(fileloc) + df["timestamp"] = pd.to_datetime(df["timestamp"], unit='ms') + # Show last 24 hour results + df = df[df['timestamp'] >= (dt.datetime.now() - dt.timedelta(hours=24))].reset_index(drop=True) + an_1 = [] + while (len(df) > 0): + try: + start = df['timestamp'][0] - pd.Timedelta(seconds=1) + stop = df['timestamp'][0] + pd.Timedelta(minutes=1) + mattime = df.loc[(df['timestamp'] > start) & (df['timestamp'] < stop)].reindex(columns = ['order_id', 'symbol', 'price', 'amount', 'timestamp', 'trade_type']).reset_index(drop=True) + mattime = mattime.loc[(mattime['price'] < mattime['price'][0] * 1.1) & (mattime['price'] > mattime['price'][0] * 0.9)] + mattime = mattime.loc[(mattime['trade_type'] == mattime['trade_type'][0])].reset_index(drop=True) + if mattime['trade_type'][0] == 'BUY': + sidex = 'SELL' + else: + sidex = 'BUY' + tattime = df.loc[(df['timestamp'] > start) & (df['timestamp'] < stop)].reindex(columns = ['order_id', 'symbol', 'price', 'amount', 'timestamp', 'trade_type']).reset_index(drop=True) + tattime = tattime.loc[(tattime['price'] < mattime['price'][0] * 1.1) & (tattime['price'] > mattime['price'][0] * 0.9)] + tattime = tattime.loc[(tattime['trade_type'] == sidex)].reset_index(drop=True) + df = df.iloc[len(mattime):].reset_index(drop=True) + df = df.iloc[len(tattime):].reset_index(drop=True) + s_id = str(tattime['order_id'][0]) + str(mattime['order_id'][0]) + s_msum = mattime['amount'].astype(float).sum() + s_tsum = tattime['amount'].astype(float).sum() + s_mside = mattime['trade_type'][0] + s_tside = tattime['trade_type'][0] + if s_msum < s_tsum * 0.8 or s_msum > s_tsum * 1.2: + tattime = (tattime.iloc[(tattime['amount'] - s_msum).abs().argsort()[:1]]).reset_index(drop=True) + s_tsum = tattime['amount'].astype(float).sum() + s_pmak = 0 + for x in range(len(mattime)): + s_pmak += float(mattime['price'][x]) * float(mattime['amount'][x]) + s_pmak = s_pmak / s_msum + s_ptak = 0 + for y in range(len(tattime)): + s_ptak += float(tattime['price'][y]) * float(tattime['amount'][y]) + s_ptak = s_ptak / s_tsum + sym_q = mattime['symbol'][0].split('-')[1] + if mattime['trade_type'][0] == 'BUY': + s_per = ((s_ptak - s_pmak) / ((s_ptak + s_pmak) / 2)) * 100 + if mattime['trade_type'][0] == 'SELL': + s_per = ((s_pmak - s_ptak) / ((s_ptak + s_pmak) / 2)) * 100 + base = ((s_ptak + s_pmak) / 2) * ((s_msum + s_tsum) / 2) * ((s_per - feea1) / 100) + per_am = (s_per - feea1) * ((s_msum + s_tsum) / 2) + per_am_sum = ((s_msum + s_tsum) / 2) + an_1.append([s_id, (tattime['timestamp'][0] - mattime['timestamp'][0]) / np.timedelta64(1, 's'), mattime['symbol'][0], s_msum, s_tsum, s_mside, s_tside, s_pmak, s_ptak, s_per, s_per - feea1, base, per_am, per_am_sum]) + except Exception: + pass + an_final = pd.DataFrame(an_1, columns = ['id', 'time', 'symbol', 'Total_Maker', 'Total_Taker', 'Maker_Side', 'Taker_Side', 'Price_Maker', 'Price_Taker', 'Percentage', 'Percentage_+_Fee', 'cost (quote)', 'per_am', 'per_am_sum']) + if len(an_final) == 0: + self.logger().info("No trade records within the past 24 hours found within datafile") + return 0 + per_am = an_final['per_am'].sum() / an_final['per_am_sum'].sum() + sum_base = an_final['cost (quote)'].sum() + time_av = an_final['time'].mean() + prof_adj = (- (per_am * rate_curve)**3 + min_prof) / 100 + self.logger().info(f"Percentage for trades over the last 24 Hours: {round(per_am, 3)}, No trades: {round(len(an_final))}, Min Profitability: {round(self.min_profitability + Decimal(prof_adj), 5)}, Profit: {round(sum_base, 2)} {sym_q}, Mean time to balance after fill: {round(time_av, 2)} s") + return prof_adj + + cdef str c_place_order(self, object market_pair, bint is_buy, bint is_maker, object amount, object price, bint is_limit): + + cdef: + str order_id + object market_info = market_pair.maker if is_maker else market_pair.taker + object order_type = market_info.market.get_maker_order_type() if is_limit else market_info.market.get_taker_order_type() + + amount = amount * Decimal(1 - self._tol_o) + if is_buy: + order_id = StrategyBase.c_buy_with_specific_market(self, market_info, amount, order_type=order_type, price=price, expiration_seconds=NaN) + else: + order_id = StrategyBase.c_sell_with_specific_market(self, market_info, amount, order_type=order_type, price=price, expiration_seconds=NaN) + # market_trading_pair_tuple = self._sb_order_tracker.c_get_market_pair_from_order_id(order_id) + mid_price = market_info.get_mid_price() + if is_limit: + StrategyBase.start_tracking_limit_order(self, market_info, order_id, is_buy, price, amount) + self.logger().info(f"Maker Order Created: {market_pair.maker.trading_pair}, Amount: {round(amount, 3)}, Price: {round(price, 3)}, Min profitability: {self.min_profitability} + {round(self._volatility_pct, 5)} + {round(self._min_prof_adj, 5)}, % from Mid Market: {round(((max(price, mid_price)/min(price, mid_price))-1)*100, 3)} ") + else: + self.logger().info(f"Taker Order Created: {market_pair.maker.trading_pair}, Amount: {round(amount, 3)}, Price: {round(price, 3)}, Min profitability: {self.min_profitability} + {round(self._volatility_pct, 5)} + {round(self._min_prof_adj, 5)}, % from Mid Market: {round(((max(price, mid_price)/min(price, mid_price))-1)*100, 3)} ") + # self.notify_hb_app("Order Created: " + str(market_pair.maker.trading_pair) +" Amount: " + str(amount) + " Price: " + str(price) + " Min profitability: " + str(self.min_profitability) + " + " + str(self._volatility_pct) + " + " + str(self._min_prof_adj)) + return order_id + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + def cancel_all_trades(self, active_orders): + for active_order in active_orders: + market_trading_pair_tuple = self._sb_order_tracker.c_get_market_pair_from_order_id(active_order.client_order_id) + if market_trading_pair_tuple: + StrategyBase.c_cancel_order(self, market_trading_pair_tuple, active_order.client_order_id) + StrategyBase.stop_tracking_limit_order(self, market_trading_pair_tuple, active_order.client_order_id) + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + """ + Output log message when a bid order (on maker side or taker side) is completely taken. + :param order_completed_event: event object + """ + order_id = order_completed_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + if market_pair is not None: + limit_order_record = self._sb_order_tracker.get_limit_order(market_pair.maker, order_id) + self.notify_hb_app("Buy Order Completed: " + str(market_pair.maker.trading_pair) + " Quantity: " + str(limit_order_record.quantity) + " of " + str(limit_order_record.base_currency) + " Price: " + str(limit_order_record.price)) + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + """ + Output log message when a ask order (on maker side or taker side) is completely taken. + :param order_completed_event: event object + """ + order_id = order_completed_event.order_id + market_pair = self._market_pair_tracker.get_market_pair_from_order_id(order_id) + + if market_pair is not None: + limit_order_record = self._sb_order_tracker.get_limit_order(market_pair.maker, order_id) + self.notify_hb_app("Sell Order Completed: " + str(market_pair.maker.trading_pair) + " Quantity: " + str(limit_order_record.quantity) + " of " + str(limit_order_record.base_currency) + " Price: " + str(limit_order_record.price)) diff --git a/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_config_map_pydantic.py b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_config_map_pydantic.py new file mode 100644 index 0000000..cacae8b --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_config_map_pydantic.py @@ -0,0 +1,139 @@ + +from decimal import Decimal + +from pydantic import Field + +from hummingbot.client.config.config_data_types import ClientFieldData +from hummingbot.client.config.strategy_config_data_types import BaseTradingStrategyMakerTakerConfigMap + + +class CrossExchangeMiningConfigMap(BaseTradingStrategyMakerTakerConfigMap): + strategy: str = Field(default="cross_exchange_mining", client_data=None) + + min_profitability: Decimal = Field( + default=..., + description="The minimum estimated profitability required to open a position.", + ge=-100.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: "What is the minimum profitability for you to make a trade? (Enter 1 to indicate 1%)", + prompt_on_new=True, + ), + ) + order_amount: Decimal = Field( + default=..., + description="The amount of base currency for the strategy to maintain over exchanges.", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: CrossExchangeMiningConfigMap.order_amount_prompt(mi), + prompt_on_new=True, + ) + ) + + balance_adjustment_duration: float = Field( + default=Decimal("5"), + description="Time interval to rebalance portfolio >>> ", + client_data=ClientFieldData( + prompt=lambda mi: "Time interval between subsequent portfolio rebalances ", + prompt_on_new=True, + ), + ) + + slippage_buffer: Decimal = Field( + default=Decimal("5.0"), + description="Allowed slippage to fill ensure taker orders are filled.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "How much buffer do you want to add to the price to account for slippage for taker orders " + "Enter 1 to indicate 1%" + ), + prompt_on_new=True, + ), + ) + + min_prof_tol_low: Decimal = Field( + default=Decimal("0.05"), + description="Tolerance below min prof to cancel order.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What percentage below the min profitability do you want to cancel the set order" + "Enter 0.1 to indicate 0.1%" + ), + prompt_on_new=True, + ), + ) + + min_prof_tol_high: Decimal = Field( + default=Decimal("0.05"), + description="Tolerance above min prof to cancel order.", + ge=0.0, + le=100.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What percentage above the min profitability level do you want to cancel the set order" + "Enter 0.1 to indicate 0.1%" + ), + prompt_on_new=True, + ), + ) + volatility_buffer_size: int = Field( + default=Decimal("120"), + description="The period in seconds to calulate volatility over: ", + client_data=ClientFieldData( + prompt=lambda mi: "The period in seconds to calulate volatility over: ", + prompt_on_new=True, + ), + ) + + min_prof_adj_timer: float = Field( + default=Decimal("3600"), + description="Time interval to adjust min profitability over", + client_data=ClientFieldData( + prompt=lambda mi: "Time interval to adjust min profitability over by using results of previous trades in last 24 hrs", + prompt_on_new=True, + ), + ) + min_order_amount: Decimal = Field( + default=Decimal("0.0"), + description="What is the minimum order amount required for bid or ask orders?: ", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "What is the minimum order amount required for bid or ask orders?: " + ), + prompt_on_new=True, + ), + ) + rate_curve: Decimal = Field( + default=Decimal("1.0"), + description="Multiplier for rate curve for the adjustment of min profitability based on previous trades over last 24 hrs: ", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Multiplier for rate curve for the adjustment of min profitability based on previous trades over last 24 hrs: " + ), + prompt_on_new=True, + ), + ) + trade_fee: Decimal = Field( + default=Decimal("0.25"), + description="Complete trade fee covering both taker and maker trades: ", + ge=0.0, + client_data=ClientFieldData( + prompt=lambda mi: ( + "Complete trade fee covering both taker and maker trades: " + ), + prompt_on_new=True, + ), + ) + # === prompts === + + @classmethod + def order_amount_prompt(cls, model_instance: 'CrossExchangeMiningConfigMap') -> str: + trading_pair = model_instance.maker_market_trading_pair + base_asset, quote_asset = trading_pair.split("-") + return f"The amount of {base_asset} for the strategy to maintain in wallet over exchanges (Will autobalance by buying or selling to maintain amount).?" diff --git a/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_pair.py b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_pair.py new file mode 100644 index 0000000..984ca51 --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/cross_exchange_mining_pair.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +from typing import NamedTuple + +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class CrossExchangeMiningPair(NamedTuple): + """ + Specifies a pair of markets for cross exchange market making. + + e.g. If I want to market make on DDEX WETH-DAI, and hedge on Binance ETHUSDT... then, + CrossExchangeMarketPair(ddex, "WETH-DAI", "WETH", "DAI", + binance, "ETHUSDT", "ETH", "USDT") + """ + maker: MarketTradingPairTuple + taker: MarketTradingPairTuple diff --git a/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pxd b/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pxd new file mode 100644 index 0000000..54989ff --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pxd @@ -0,0 +1,13 @@ +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class OrderIDMarketPairTracker(TimeIterator): + cdef: + object _order_id_to_tracking_item + float _expiry_timeout + + cdef object c_get_market_pair_from_order_id(self, str order_id) + cdef object c_get_exchange_from_order_id(self, str order_id) + cdef c_start_tracking_order_id(self, str order_id, object exchange, object market_pair) + cdef c_stop_tracking_order_id(self, str order_id) + cdef c_check_and_expire_tracking_items(self) diff --git a/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pyx b/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pyx new file mode 100644 index 0000000..d673da1 --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/order_id_market_pair_tracker.pyx @@ -0,0 +1,70 @@ +from collections import OrderedDict + +NaN = float("nan") + + +cdef class OrderIDMarketPairTrackingItem: + cdef: + public str order_id + public object exchange + public object market_pair + public double expiry_timestamp + + def __init__(self, str order_id, object exchange, object market_pair): + self.order_id = order_id + self.exchange = exchange + self.market_pair = market_pair + self.expiry_timestamp = NaN + +cdef class OrderIDMarketPairTracker(TimeIterator): + def __init__(self, double expiry_timeout=3 * 60): + super().__init__() + + self._order_id_to_tracking_item = OrderedDict() + self._expiry_timeout = expiry_timeout + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self.c_check_and_expire_tracking_items() + + cdef object c_get_market_pair_from_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is not None: + return item.market_pair + return None + + cdef object c_get_exchange_from_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is not None: + return item.exchange + return None + + cdef c_start_tracking_order_id(self, str order_id, object exchange, object market_pair): + self._order_id_to_tracking_item[order_id] = OrderIDMarketPairTrackingItem(order_id, exchange, market_pair) + + cdef c_stop_tracking_order_id(self, str order_id): + cdef: + OrderIDMarketPairTrackingItem item = self._order_id_to_tracking_item.get(order_id) + + if item is None: + return + item.expiry_timestamp = self._current_timestamp + self._expiry_timeout + + cdef c_check_and_expire_tracking_items(self): + cdef: + list order_ids_to_delete = [] + OrderIDMarketPairTrackingItem typed_tracking_item + + for order_id, tracking_item in self._order_id_to_tracking_item.items(): + typed_tracking_item = tracking_item + if self._current_timestamp > typed_tracking_item.expiry_timestamp: + order_ids_to_delete.append(order_id) + else: + break + + for order_id in order_ids_to_delete: + del self._order_id_to_tracking_item[order_id] diff --git a/hummingbot/strategy/cross_exchange_mining/start.py b/hummingbot/strategy/cross_exchange_mining/start.py new file mode 100644 index 0000000..026696d --- /dev/null +++ b/hummingbot/strategy/cross_exchange_mining/start.py @@ -0,0 +1,53 @@ +from typing import List, Tuple + +from hummingbot.strategy.cross_exchange_mining.cross_exchange_mining import CrossExchangeMiningStrategy +from hummingbot.strategy.cross_exchange_mining.cross_exchange_mining_pair import CrossExchangeMiningPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + c_map = self.strategy_config_map + maker_market = c_map.maker_market.lower() + taker_market = c_map.taker_market.lower() + raw_maker_trading_pair = c_map.maker_market_trading_pair + raw_taker_trading_pair = c_map.taker_market_trading_pair + status_report_interval = self.client_config_map.strategy_report_interval + + try: + maker_trading_pair: str = raw_maker_trading_pair + taker_trading_pair: str = raw_taker_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(maker_market, [maker_trading_pair])[0] + taker_assets: Tuple[str, str] = self._initialize_market_assets(taker_market, [taker_trading_pair])[0] + except ValueError as e: + self.notify(str(e)) + return + + market_names: List[Tuple[str, List[str]]] = [ + (maker_market, [maker_trading_pair]), + (taker_market, [taker_trading_pair]), + ] + + self._initialize_markets(market_names) + maker_data = [self.markets[maker_market], maker_trading_pair] + list(maker_assets) + taker_data = [self.markets[taker_market], taker_trading_pair] + list(taker_assets) + maker_market_trading_pair_tuple = MarketTradingPairTuple(*maker_data) + taker_market_trading_pair_tuple = MarketTradingPairTuple(*taker_data) + self.market_trading_pair_tuples = [maker_market_trading_pair_tuple, taker_market_trading_pair_tuple] + self.market_pair = CrossExchangeMiningPair(maker=maker_market_trading_pair_tuple, taker=taker_market_trading_pair_tuple) + + strategy_logging_options = ( + CrossExchangeMiningStrategy.OPTION_LOG_CREATE_ORDER + | CrossExchangeMiningStrategy.OPTION_LOG_ADJUST_ORDER + | CrossExchangeMiningStrategy.OPTION_LOG_MAKER_ORDER_FILLED + | CrossExchangeMiningStrategy.OPTION_LOG_REMOVING_ORDER + | CrossExchangeMiningStrategy.OPTION_LOG_STATUS_REPORT + | CrossExchangeMiningStrategy.OPTION_LOG_MAKER_ORDER_HEDGED + ) + self.strategy = CrossExchangeMiningStrategy() + self.strategy.init_params( + config_map=c_map, + market_pairs=[self.market_pair], + status_report_interval=status_report_interval, + logging_options=strategy_logging_options, + hb_app_notification=True, + ) diff --git a/hummingbot/strategy/data_types.py b/hummingbot/strategy/data_types.py new file mode 100644 index 0000000..81ecf1e --- /dev/null +++ b/hummingbot/strategy/data_types.py @@ -0,0 +1,81 @@ +from dataclasses import dataclass +from decimal import Decimal +from typing import List, NamedTuple + +from hummingbot.core.data_type.common import OrderType + +ORDER_PROPOSAL_ACTION_CREATE_ORDERS = 1 +ORDER_PROPOSAL_ACTION_CANCEL_ORDERS = 1 << 1 + +NaN = float("nan") + + +class OrdersProposal(NamedTuple): + actions: int + buy_order_type: OrderType + buy_order_prices: List[Decimal] + buy_order_sizes: List[Decimal] + sell_order_type: OrderType + sell_order_prices: List[Decimal] + sell_order_sizes: List[Decimal] + cancel_order_ids: List[str] + + +class PricingProposal(NamedTuple): + buy_order_prices: List[Decimal] + sell_order_prices: List[Decimal] + + +class SizingProposal(NamedTuple): + buy_order_sizes: List[Decimal] + sell_order_sizes: List[Decimal] + + +class PriceSize: + def __init__(self, price: Decimal, size: Decimal): + self.price: Decimal = price + self.size: Decimal = size + + def __repr__(self): + return f"[ p: {self.price} s: {self.size} ]" + + +class Proposal: + def __init__(self, buys: List[PriceSize], sells: List[PriceSize]): + self.buys: List[PriceSize] = buys + self.sells: List[PriceSize] = sells + + def __repr__(self): + return f"{len(self.buys)} buys: {', '.join([str(o) for o in self.buys])} " \ + f"{len(self.sells)} sells: {', '.join([str(o) for o in self.sells])}" + + +@dataclass(frozen=True) +class HangingOrder: + order_id: str + trading_pair: str + is_buy: bool + price: Decimal + amount: Decimal + creation_timestamp: float + + @property + def base_asset(self): + return self.trading_pair.split('-')[0] + + @property + def quote_asset(self): + return self.trading_pair.split('-')[1] + + def distance_to_price(self, price: Decimal): + return abs(self.price - price) + + def __eq__(self, other): + return isinstance(other, HangingOrder) and all( + (self.trading_pair == other.trading_pair, + self.is_buy == other.is_buy, + self.price == other.price, + self.amount == other.amount)) + + def __hash__(self): + return hash((self.trading_pair, self.is_buy, self.price, self.amount)) diff --git a/hummingbot/strategy/directional_strategy_base.py b/hummingbot/strategy/directional_strategy_base.py new file mode 100644 index 0000000..0e3c8e6 --- /dev/null +++ b/hummingbot/strategy/directional_strategy_base.py @@ -0,0 +1,314 @@ +import datetime +import os +from decimal import Decimal +from typing import Dict, List, Set + +import pandas as pd +import pandas_ta as ta # noqa: F401 + +from hummingbot import data_path +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.data_feed.candles_feed.candles_base import CandlesBase +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DirectionalStrategyBase(ScriptStrategyBase): + """ + Base class to create directional strategies using the PositionExecutor. + + Attributes: + directional_strategy_name (str): The name of the directional strategy. + trading_pair (str): The trading pair to be used. + exchange (str): The exchange to be used. + max_executors (int): Maximum number of position executors to be active at a time. + position_mode (PositionMode): The position mode to be used. + active_executors (List[PositionExecutor]): List of currently active position executors. + stored_executors (List[PositionExecutor]): List of closed position executors that have been stored. + stop_loss (float): The stop loss percentage. + take_profit (float): The take profit percentage. + time_limit (int): The time limit for the position. + open_order_type (OrderType): The order type for opening the position. + open_order_slippage_buffer (float): The slippage buffer for the opening order. + take_profit_order_type (OrderType): The order type for the take profit order. + stop_loss_order_type (OrderType): The order type for the stop loss order. + time_limit_order_type (OrderType): The order type for the time limit order. + trailing_stop_activation_delta (float): The delta for activating the trailing stop. + trailing_stop_trailing_delta (float): The delta for trailing the stop loss. + candles (List[CandlesBase]): List of candlestick data sources to be used. + set_leverage_flag (None): Flag indicating whether leverage has been set. + leverage (float): The leverage to be used. + order_amount_usd (Decimal): The order amount in USD. + markets (Dict[str, Set[str]]): Dictionary mapping exchanges to trading pairs. + """ + directional_strategy_name: str + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str + exchange: str + + # Maximum position executors at a time + max_executors: int = 1 + position_mode: PositionMode = PositionMode.HEDGE + active_executors: List[PositionExecutor] = [] + stored_executors: List[PositionExecutor] = [] + + # Configure the parameters for the position + stop_loss: float = 0.01 + take_profit: float = 0.01 + time_limit: int = 120 + open_order_type = OrderType.MARKET + open_order_slippage_buffer: float = 0.001 + take_profit_order_type: OrderType = OrderType.MARKET + stop_loss_order_type: OrderType = OrderType.MARKET + time_limit_order_type: OrderType = OrderType.MARKET + trailing_stop_activation_delta = 0.003 + trailing_stop_trailing_delta = 0.001 + cooldown_after_execution = 30 + + # Create the candles that we want to use and the thresholds for the indicators + candles: List[CandlesBase] + + # Configure the leverage and order amount the bot is going to use + set_leverage_flag = None + leverage = 1 + order_amount_usd = Decimal("10") + markets: Dict[str, Set[str]] = {} + + @property + def all_candles_ready(self): + """ + Checks if the candlesticks are full. + """ + return all([candle.is_ready for candle in self.candles]) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + @property + def max_active_executors_condition(self): + return len(self.get_active_executors()) < self.max_executors + + @property + def time_between_signals_condition(self): + seconds_since_last_signal = self.current_timestamp - self.get_timestamp_of_last_executor() + return seconds_since_last_signal > self.cooldown_after_execution + + def get_csv_path(self) -> str: + today = datetime.datetime.today() + csv_path = data_path() + f"/{self.directional_strategy_name}_position_executors_{self.exchange}_{self.trading_pair}_{today.day:02d}-{today.month:02d}-{today.year}.csv" + return csv_path + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + for candle in self.candles: + candle.start() + + def candles_formatted_list(self, candles_df: pd.DataFrame, columns_to_show: List): + lines = [] + candles_df = candles_df.copy() + candles_df["timestamp"] = pd.to_datetime(candles_df["timestamp"], unit="ms") + lines.extend([" " + line for line in candles_df[columns_to_show].tail().to_string(index=False).split("\n")]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + return lines + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + """ + if self.is_perpetual: + # we are going to close all the open positions when the bot stops + self.close_open_positions() + for candle in self.candles: + candle.stop() + + def get_active_executors(self) -> List[PositionExecutor]: + return [signal_executor for signal_executor in self.active_executors + if not signal_executor.is_closed] + + def get_closed_executors(self) -> List[PositionExecutor]: + return [signal_executor for signal_executor in self.active_executors + if signal_executor.is_closed] + + def get_timestamp_of_last_executor(self): + if len(self.stored_executors) > 0: + return self.stored_executors[-1].close_timestamp + else: + return 0 + + def on_tick(self): + self.clean_and_store_executors() + if self.is_perpetual: + self.check_and_set_leverage() + if self.max_active_executors_condition and self.all_candles_ready and self.time_between_signals_condition: + position_config = self.get_position_config() + if position_config: + signal_executor = PositionExecutor( + strategy=self, + position_config=position_config, + ) + self.active_executors.append(signal_executor) + + def get_position_config(self): + signal = self.get_signal() + if signal == 0: + return None + else: + price = self.connectors[self.exchange].get_mid_price(self.trading_pair) + side = TradeType.BUY if signal == 1 else TradeType.SELL + if self.open_order_type.is_limit_type(): + price = price * (1 - signal * self.open_order_slippage_buffer) + if self.trailing_stop_activation_delta and self.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=Decimal(self.trailing_stop_activation_delta), + trailing_delta=Decimal(self.trailing_stop_trailing_delta), + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=self.current_timestamp, + trading_pair=self.trading_pair, + exchange=self.exchange, + side=side, + amount=self.order_amount_usd / price, + take_profit=Decimal(self.take_profit), + stop_loss=Decimal(self.stop_loss), + time_limit=self.time_limit, + entry_price=price, + open_order_type=self.open_order_type, + take_profit_order_type=self.take_profit_order_type, + stop_loss_order_type=self.stop_loss_order_type, + time_limit_order_type=self.time_limit_order_type, + trailing_stop=trailing_stop, + leverage=self.leverage, + ) + return position_config + + def get_signal(self): + """Base method to get the signal from the candles.""" + raise NotImplementedError + + def format_status(self) -> str: + """ + Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + if len(self.stored_executors) > 0: + lines.extend(["\n################################## Closed Executors ##################################"]) + for executor in self.stored_executors: + lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) + lines.extend(executor.to_format_status()) + lines.extend([ + "-----------------------------------------------------------------------------------------------------------"]) + + if len(self.active_executors) > 0: + lines.extend(["\n################################## Active Executors ##################################"]) + + for executor in self.active_executors: + lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) + lines.extend(executor.to_format_status()) + if self.all_candles_ready: + lines.extend(["\n################################## Market Data ##################################\n"]) + lines.extend([f"Value: {self.get_signal()}"]) + lines.extend(self.market_data_extra_info()) + else: + lines.extend(["", " No data collected."]) + + return "\n".join(lines) + + def check_and_set_leverage(self): + if not self.set_leverage_flag: + for connector in self.connectors.values(): + for trading_pair in connector.trading_pairs: + connector.set_position_mode(self.position_mode) + connector.set_leverage(trading_pair=trading_pair, leverage=self.leverage) + self.set_leverage_flag = True + + def clean_and_store_executors(self): + executors_to_store = [executor for executor in self.active_executors if executor.is_closed] + csv_path = self.get_csv_path() + if not os.path.exists(csv_path): + df_header = pd.DataFrame([("timestamp", + "exchange", + "trading_pair", + "side", + "amount", + "trade_pnl", + "trade_pnl_quote", + "cum_fee_quote", + "net_pnl_quote", + "net_pnl", + "close_timestamp", + "executor_status", + "close_type", + "entry_price", + "close_price", + "sl", + "tp", + "tl", + "open_order_type", + "take_profit_order_type", + "stop_loss_order_type", + "time_limit_order_type", + "leverage" + )]) + df_header.to_csv(csv_path, mode='a', header=False, index=False) + for executor in executors_to_store: + self.stored_executors.append(executor) + df = pd.DataFrame([(executor.position_config.timestamp, + executor.exchange, + executor.trading_pair, + executor.side, + executor.amount, + executor.trade_pnl, + executor.trade_pnl_quote, + executor.cum_fee_quote, + executor.net_pnl_quote, + executor.net_pnl, + executor.close_timestamp, + executor.executor_status, + executor.close_type, + executor.entry_price, + executor.close_price, + executor.position_config.stop_loss, + executor.position_config.take_profit, + executor.position_config.time_limit, + executor.open_order_type, + executor.take_profit_order_type, + executor.stop_loss_order_type, + executor.time_limit_order_type, + self.leverage)]) + df.to_csv(self.get_csv_path(), mode='a', header=False, index=False) + self.active_executors = [executor for executor in self.active_executors if not executor.is_closed] + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def market_data_extra_info(self): + return ["\n"] diff --git a/hummingbot/strategy/hanging_orders_tracker.py b/hummingbot/strategy/hanging_orders_tracker.py new file mode 100644 index 0000000..999cf44 --- /dev/null +++ b/hummingbot/strategy/hanging_orders_tracker.py @@ -0,0 +1,379 @@ +import logging +from decimal import Decimal +from typing import Dict, List, Optional, Set, Tuple, Union + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + OrderCancelledEvent, + SellOrderCompletedEvent, +) +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.data_types import HangingOrder +from hummingbot.strategy.strategy_base import StrategyBase + +s_decimal_zero = Decimal(0) +sb_logger = None + + +class CreatedPairOfOrders: + def __init__(self, buy_order: Optional[LimitOrder], sell_order: Optional[LimitOrder]): + self.buy_order = buy_order + self.sell_order = sell_order + self.filled_buy = False + self.filled_sell = False + + def contains_order(self, order_id: str): + return ((self.buy_order is not None) and (self.buy_order.client_order_id == order_id)) or \ + ((self.sell_order is not None) and (self.sell_order.client_order_id == order_id)) + + def partially_filled(self): + return self.filled_buy != self.filled_sell + + def get_unfilled_order(self): + if self.partially_filled(): + if not self.filled_buy: + return self.buy_order + else: + return self.sell_order + + +class HangingOrdersTracker: + + @classmethod + def logger(cls) -> HummingbotLogger: + global sb_logger + if sb_logger is None: + sb_logger = logging.getLogger(__name__) + return sb_logger + + def __init__(self, + strategy: StrategyBase, + hanging_orders_cancel_pct=None, + orders: Dict[str, HangingOrder] = None, + trading_pair: str = None): + self.strategy: StrategyBase = strategy + self._hanging_orders_cancel_pct: Decimal = hanging_orders_cancel_pct or Decimal("0.1") + self.trading_pair: str = trading_pair or self.strategy.trading_pair + self.orders_being_renewed: Set[HangingOrder] = set() + self.orders_being_cancelled: Set[str] = set() + self.current_created_pairs_of_orders: List[CreatedPairOfOrders] = list() + self.original_orders: Set[LimitOrder] = orders or set() + self.strategy_current_hanging_orders: Set[HangingOrder] = set() + self.completed_hanging_orders: Set[HangingOrder] = set() + + self._cancel_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder(self._did_cancel_order) + self._complete_buy_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder( + self._did_complete_buy_order) + self._complete_sell_order_forwarder: SourceInfoEventForwarder = SourceInfoEventForwarder( + self._did_complete_sell_order) + self._event_pairs: List[Tuple[MarketEvent, SourceInfoEventForwarder]] = [ + (MarketEvent.OrderCancelled, self._cancel_order_forwarder), + (MarketEvent.BuyOrderCompleted, self._complete_buy_order_forwarder), + (MarketEvent.SellOrderCompleted, self._complete_sell_order_forwarder)] + + @property + def hanging_orders_cancel_pct(self): + return self._hanging_orders_cancel_pct + + @hanging_orders_cancel_pct.setter + def hanging_orders_cancel_pct(self, value): + self._hanging_orders_cancel_pct = value + + def register_events(self, markets: List[ConnectorBase]): + """Start listening to events from the given markets.""" + for market in markets: + for event_pair in self._event_pairs: + market.add_listener(event_pair[0], event_pair[1]) + + def unregister_events(self, markets: List[ConnectorBase]): + """Stop listening to events from the given market.""" + for market in markets: + for event_pair in self._event_pairs: + market.remove_listener(event_pair[0], event_pair[1]) + + def _did_cancel_order(self, + event_tag: int, + market: ConnectorBase, + event: OrderCancelledEvent): + + self._process_cancel_as_part_of_renew(event) + + self.orders_being_cancelled.discard(event.order_id) + order_to_be_removed = next((order for order in self.strategy_current_hanging_orders + if order.order_id == event.order_id), None) + if order_to_be_removed: + self.strategy_current_hanging_orders.remove(order_to_be_removed) + self.logger().notify(f"({self.trading_pair}) Hanging order {event.order_id} canceled.") + + limit_order_to_be_removed = next((order for order in self.original_orders + if order.client_order_id == event.order_id), None) + if limit_order_to_be_removed: + self.remove_order(limit_order_to_be_removed) + + def _did_complete_buy_order(self, + event_tag: int, + market: ConnectorBase, + event: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent]): + self._did_complete_order(event, True) + + def _did_complete_sell_order(self, + event_tag: int, + market: ConnectorBase, + event: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent]): + self._did_complete_order(event, False) + + def _did_complete_order(self, + event: Union[BuyOrderCompletedEvent, SellOrderCompletedEvent], + is_buy: bool): + hanging_order = next((hanging_order for hanging_order in self.strategy_current_hanging_orders + if hanging_order.order_id == event.order_id), None) + + if hanging_order: + self._did_complete_hanging_order(hanging_order) + else: + for pair in self.current_created_pairs_of_orders: + if pair.contains_order(event.order_id): + pair.filled_buy = pair.filled_buy or is_buy + pair.filled_sell = pair.filled_sell or not is_buy + + def _did_complete_hanging_order(self, order: HangingOrder): + + if order: + order_side = "BUY" if order.is_buy else "SELL" + self.completed_hanging_orders.add(order) + self.strategy_current_hanging_orders.remove(order) + self.logger().notify( + f"({self.trading_pair}) Hanging maker {order_side} order {order.order_id} " + f"({order.trading_pair} {order.amount} @ " + f"{order.price}) has been completely filled." + ) + + limit_order_to_be_removed = next((original_order for original_order in self.original_orders + if original_order.client_order_id == order.order_id), None) + if limit_order_to_be_removed: + self.remove_order(limit_order_to_be_removed) + + def process_tick(self): + """Updates the currently active hanging orders. + + Removes active and pending hanging orders with prices that have surpassed + the cancelation percent and renews active hanging orders that have passed + the max order age. + + This method should be called on each clock tick. + """ + self.remove_orders_far_from_price() + self.renew_hanging_orders_past_max_order_age() + + def _process_cancel_as_part_of_renew(self, event: OrderCancelledEvent): + renewing_order = next((order for order in self.orders_being_renewed if order.order_id == event.order_id), None) + if renewing_order: + self.logger().info(f"({self.trading_pair}) Hanging order {event.order_id} " + f"has been canceled as part of the renew process. " + f"Now the replacing order will be created.") + self.strategy_current_hanging_orders.remove(renewing_order) + self.orders_being_renewed.remove(renewing_order) + order_to_be_created = HangingOrder(None, + renewing_order.trading_pair, + renewing_order.is_buy, + renewing_order.price, + renewing_order.amount, + self.strategy.current_timestamp) + + executed_orders = self._execute_orders_in_strategy([order_to_be_created]) + self.strategy_current_hanging_orders = self.strategy_current_hanging_orders.union(executed_orders) + for new_hanging_order in executed_orders: + limit_order_from_hanging_order = next((o for o in self.strategy.active_orders + if o.client_order_id == new_hanging_order.order_id), None) + if limit_order_from_hanging_order: + self.add_order(limit_order_from_hanging_order) + + def add_order(self, order: LimitOrder): + self.original_orders.add(order) + + def add_as_hanging_order(self, order: LimitOrder): + self.strategy_current_hanging_orders.add(self._get_hanging_order_from_limit_order(order)) + self.add_order(order) + + def remove_order(self, order: LimitOrder): + if order in self.original_orders: + self.original_orders.remove(order) + + def remove_all_orders(self): + self.original_orders.clear() + + def remove_all_buys(self): + to_be_removed = [] + for order in self.original_orders: + if order.is_buy: + to_be_removed.append(order) + for order in to_be_removed: + self.original_orders.remove(order) + + def remove_all_sells(self): + to_be_removed = [] + for order in self.original_orders: + if not order.is_buy: + to_be_removed.append(order) + for order in to_be_removed: + self.original_orders.remove(order) + + def hanging_order_age(self, hanging_order: HangingOrder) -> float: + """ + Returns the number of seconds between the current time (taken from the strategy) and the order creation time + """ + return (self.strategy.current_timestamp - hanging_order.creation_timestamp + if hanging_order.creation_timestamp + else -1) + + def renew_hanging_orders_past_max_order_age(self): + to_be_cancelled: Set[HangingOrder] = set() + max_order_age = getattr(self.strategy, "max_order_age", None) + if max_order_age: + for order in self.strategy_current_hanging_orders: + if self.hanging_order_age(order) > max_order_age and order not in self.orders_being_renewed: + self.logger().info(f"Reached max_order_age={max_order_age}sec hanging order: {order}. Renewing...") + to_be_cancelled.add(order) + + self._cancel_multiple_orders_in_strategy([o.order_id for o in to_be_cancelled if o.order_id]) + self.orders_being_renewed = self.orders_being_renewed.union(to_be_cancelled) + + def remove_orders_far_from_price(self): + current_price = self.strategy.get_price() + orders_to_be_removed = set() + for order in self.original_orders: + if (order.client_order_id not in self.orders_being_cancelled + and abs(order.price - current_price) / current_price > self._hanging_orders_cancel_pct): + self.logger().info( + f"Hanging order passed max_distance from price={self._hanging_orders_cancel_pct * 100}% {order}. Removing...") + orders_to_be_removed.add(order) + + self._cancel_multiple_orders_in_strategy([order.client_order_id for order in orders_to_be_removed]) + + def _get_equivalent_orders(self) -> Set[HangingOrder]: + if self.original_orders: + return self._get_equivalent_orders_no_aggregation(self.original_orders) + return set() + + @property + def equivalent_orders(self) -> Set[HangingOrder]: + """Creates a list of `HangingOrder`s from the registered `LimitOrder`s.""" + return self._get_equivalent_orders() + + def is_order_id_in_hanging_orders(self, order_id: str) -> bool: + return any((o.order_id == order_id for o in self.strategy_current_hanging_orders)) + + def is_order_id_in_completed_hanging_orders(self, order_id: str) -> bool: + return any((o.order_id == order_id for o in self.completed_hanging_orders)) + + def is_hanging_order_in_strategy_active_orders(self, order: HangingOrder) -> bool: + return any(all(order.trading_pair == o.trading_pair, + order.is_buy == o.is_buy, + order.price == o.price, + order.amount == o.quantity) for o in self.strategy.active_orders) + + def is_potential_hanging_order(self, order: LimitOrder) -> bool: + """Checks if the order is registered as a hanging order.""" + return order in self.original_orders + + def update_strategy_orders_with_equivalent_orders(self): + """Updates the strategy hanging orders. + + Checks the internal list of hanging orders that should exist for the strategy + and ensures that those orders do exist by creating/canceling orders + within the strategy accordingly. + """ + + self._add_hanging_orders_based_on_partially_executed_pairs() + + equivalent_orders = self.equivalent_orders + orders_to_create = equivalent_orders.difference(self.strategy_current_hanging_orders) + orders_to_cancel = self.strategy_current_hanging_orders.difference(equivalent_orders) + + self._cancel_multiple_orders_in_strategy([o.order_id for o in orders_to_cancel]) + + if any((orders_to_cancel, orders_to_create)): + self.logger().info("Updating hanging orders...") + self.logger().info(f"Original hanging orders: {self.original_orders}") + self.logger().info(f"Equivalent hanging orders: {equivalent_orders}") + self.logger().info(f"Need to create: {orders_to_create}") + self.logger().info(f"Need to cancel: {orders_to_cancel}") + + executed_orders = self._execute_orders_in_strategy(orders_to_create) + self.strategy_current_hanging_orders = self.strategy_current_hanging_orders.union(executed_orders) + + def _execute_orders_in_strategy(self, candidate_orders: Set[HangingOrder]): + new_hanging_orders = set() + order_type = self.strategy.market_info.market.get_maker_order_type() + for order in candidate_orders: + # Only execute if order is new + if order.order_id is None: + if order.amount > 0: + if order.is_buy: + order_id = self.strategy.buy_with_specific_market( + self.strategy.market_info, + amount=order.amount, + order_type=order_type, + price=order.price, + expiration_seconds=self.strategy.order_refresh_time + ) + else: + order_id = self.strategy.sell_with_specific_market( + self.strategy.market_info, + amount=order.amount, + order_type=order_type, + price=order.price, + expiration_seconds=self.strategy.order_refresh_time + ) + new_hanging_order = HangingOrder(order_id, + order.trading_pair, + order.is_buy, + order.price, + order.amount, + self.strategy.current_timestamp) + + new_hanging_orders.add(new_hanging_order) + # If it's a preexistent order we don't create it but we add it to hanging orders + else: + new_hanging_orders.add(order) + return new_hanging_orders + + def _cancel_multiple_orders_in_strategy(self, order_ids: List[str]): + for order_id in order_ids: + if any(o.client_order_id == order_id for o in self.strategy.active_orders): + self.strategy.cancel_order(order_id) + self.orders_being_cancelled.add(order_id) + + def _get_equivalent_orders_no_aggregation(self, orders): + return frozenset(self._get_hanging_order_from_limit_order(o) for o in orders) + + def add_current_pairs_of_proposal_orders_executed_by_strategy(self, pair: CreatedPairOfOrders): + self.current_created_pairs_of_orders.append(pair) + + def _add_hanging_orders_based_on_partially_executed_pairs(self): + for unfilled_order in self.candidate_hanging_orders_from_pairs(): + self.add_order(unfilled_order) + self.current_created_pairs_of_orders.clear() + + def _get_hanging_order_from_limit_order(self, order: LimitOrder): + return HangingOrder( + order.client_order_id, + order.trading_pair, + order.is_buy, + order.price, + order.quantity, + order.creation_timestamp * 1e-6) + + def candidate_hanging_orders_from_pairs(self): + candidate_orders = [] + for pair in self.current_created_pairs_of_orders: + if pair.partially_filled(): + unfilled_order = pair.get_unfilled_order() + # Check if the unfilled order is in active_orders because it might have failed before being created + if unfilled_order in self.strategy.active_orders: + candidate_orders.append(unfilled_order) + return candidate_orders diff --git a/hummingbot/strategy/hedge/__init__.py b/hummingbot/strategy/hedge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/hedge/hedge.py b/hummingbot/strategy/hedge/hedge.py new file mode 100644 index 0000000..d5b87af --- /dev/null +++ b/hummingbot/strategy/hedge/hedge.py @@ -0,0 +1,638 @@ +import logging +from decimal import Decimal +from typing import Any, Dict, List, Tuple, Union + +import pandas as pd + +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.derivative.position import Position +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_candidate import OrderCandidate, PerpetualOrderCandidate +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.hedge.hedge_config_map_pydantic import HedgeConfigMap +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase +from hummingbot.strategy.utils import order_age + +hedge_logger = None + + +class HedgeStrategy(StrategyPyBase): + """ + This strategy contains 2 mode of hedging. + 1. Hedge by amount + 2. Hedge by value + + 1. Hedge by amount + The strategy will hedge by amount by calculating the amount to hedge by each asset. + The amount of asset to hedge is calculated by the following formula: + for each asset in the hedge market pair, + amount_to_hedge = sum of asset amount with the same base asset * hedge_ratio + hedge asset amount + The amount of asset to hedge must be greater than the minimum trade size to be traded. + + 2. Hedge by value + The strategy will hedge by value by calculating the amount of asset to hedge. + The amount of asset to hedge is calculated by the following formula: + amount_to_hedge = sum of asset value of all market pairs * hedge_ratio + hedge asset value + The amount of asset to hedge must be greater than the minimum trade size to be traded. + """ + + @classmethod + def logger(cls) -> HummingbotLogger: + global hedge_logger + if hedge_logger is None: + hedge_logger = logging.getLogger(__name__) + return hedge_logger + + def __init__( + self, + config_map: HedgeConfigMap, + hedge_market_pairs: List[MarketTradingPairTuple], + market_pairs: List[MarketTradingPairTuple], + offsets: Dict[MarketTradingPairTuple, Decimal], + status_report_interval: float = 900, + max_order_age: float = 5, + enable_auto_set_position_mode: bool = True, + ): + """ + Initializes the hedge strategy. + :param hedge_market_pair: Market pair to hedge. + :param market_pairs: Market pairs to trade. + :param hedge_ratio: Ratio of total asset value to hedge. + :param hedge_leverage: Leverage to use for hedging. + :param slippage: Slippage to use for hedging. + :param max_order_age: Maximum age of an order before it is cancelled. + :param min_trade_size: Minimum trade size. + :param hedge_interval: Interval to check for hedging. + :param value_mode: True if the strategy is in value mode, False otherwise. + :param hedge_position_mode: Position mode (ONEWAY or HEDGE) to use for hedging. + :param status_report_interval: Interval to report status. + """ + super().__init__() + self._hedge_market_pairs = hedge_market_pairs + self._market_pairs = market_pairs + self._hedge_ratio = config_map.hedge_ratio + self._leverage = config_map.hedge_leverage + self._position_mode = PositionMode.ONEWAY if config_map.hedge_position_mode == "ONEWAY" else PositionMode.HEDGE + self._slippage = config_map.slippage + self._min_trade_size = config_map.min_trade_size + self._hedge_interval = config_map.hedge_interval + self._value_mode = config_map.value_mode + self._offsets = offsets + self._status_report_interval = status_report_interval + self._all_markets = self._hedge_market_pairs + self._market_pairs + self._last_timestamp = 0 + self._all_markets_ready = False + self._max_order_age = max_order_age + self._status_messages = [] + self._last_report_timestamp = {} + self._enable_auto_set_position_mode = enable_auto_set_position_mode + if config_map.value_mode: + self.hedge = self.hedge_by_value + self._hedge_market_pair = hedge_market_pairs[0] + self.logger().info(f"Hedge market pair: {self._hedge_market_pair}") + else: + self.hedge = self.hedge_by_amount + self._market_pair_by_asset = self.get_market_pair_by_asset() + self.logger().info(f"Market pair by asset: {self._market_pair_by_asset}") + + derivative_markets = AllConnectorSettings.get_derivative_names() + self._derivatives_list = [ + market_pair for market_pair in self._all_markets if market_pair.market.name in derivative_markets + ] + self.get_order_candidates = ( + self.get_perpetual_order_candidates + if self.is_derivative(self._hedge_market_pairs[0]) + else self.get_spot_order_candidates + ) + + all_markets = list(set([market_pair.market for market_pair in self._all_markets])) + self.add_markets(all_markets) + + def get_market_pair_by_asset(self) -> Dict[MarketTradingPairTuple, List[MarketTradingPairTuple]]: + """ + sort market pair belonging to the same market as hedge market together + :return: market pair belonging to the same market as hedge market together + """ + self.logger().info(f"Market pairs: {self._market_pairs}") + return { + hedge_pair: [ + market_pair for market_pair in self._market_pairs + if market_pair.trading_pair.split("-")[0] == hedge_pair.trading_pair.split("-")[0] + ] + for hedge_pair in self._hedge_market_pairs + } + + def is_derivative(self, market_pair: MarketTradingPairTuple) -> bool: + """ + Check if the market is derivative. + :param market_pair: Market pair to check. + :return: True if the market is derivative, False otherwise. + """ + return market_pair in self._derivatives_list + + def active_positions_df(self) -> pd.DataFrame: + """ + Get the active positions of all markets. + :return: The active positions of all markets. + """ + columns = ["Connector", "Symbol", "Type", "Entry", "Amount", "Leverage"] + data = [] + for market_pair in self._all_markets: + if not self.is_derivative(market_pair): + continue + for position in self.get_positions(market_pair): + if not position: + continue + data.append( + [ + market_pair.market.name, + position.trading_pair, + position.position_side.name, + position.entry_price, + position.amount, + position.leverage, + ] + ) + return pd.DataFrame(data=data, columns=columns) + + def wallet_df(self) -> pd.DataFrame: + """ + Processes the data required for wallet dataframe. + :return: wallet dataframe + """ + data = [] + columns = ["Connector", "Asset", "Price", "Amount", "Value"] + + def get_data(market_pair: MarketTradingPairTuple) -> List[Any]: + market, trading_pair = market_pair.market, market_pair.trading_pair + return [ + market.name, + trading_pair, + market_pair.get_mid_price(), + f"{self.get_base_amount(market_pair):.6g}", + f"{self.get_base_amount(market_pair) * market_pair.get_mid_price():.6g}", + ] + + for market_pair in self._all_markets: + data.append(get_data(market_pair)) + return pd.DataFrame(data=data, columns=columns) + + @property + def active_orders(self) -> List[Tuple[Any, LimitOrder]]: + """ + Get the active orders of all markets. + :return: The active orders of all hedge markets. + + """ + return self.order_tracker.active_limit_orders + + def format_status(self) -> str: + """ + Format the status of the strategy. + """ + def get_wallet_status_str() -> List[str]: + wallet_df = self.wallet_balance_data_frame(self._all_markets) + return ["", " Wallet:"] + [" " + line for line in str(wallet_df).split("\n")] + + def get_asset_status_str() -> List[str]: + assets_df = self.wallet_df() + return ["", " Assets:"] + [" " + line for line in str(assets_df).split("\n")] + + def get_position_status_str() -> List[str]: + positions_df = self.active_positions_df() + if not positions_df.empty: + return ["", " Positions:"] + [" " + line for line in str(positions_df).split("\n")] + return ["", " No positions."] + + def get_order_status_str() -> List[str]: + if self.active_orders: + orders = [order[1] for order in self.active_orders] + df = LimitOrder.to_pandas(orders) + df_lines = str(df).split("\n") + return ["", " Active orders:"] + [" " + line for line in df_lines] + return ["", " No active maker orders."] + + def get_value_mode_status_str(value_mode: bool) -> List[str]: + if not value_mode: + return [] + + lines = [] + total_value = sum(self.get_base_value(market_pair) for market_pair in self._market_pairs) + hedge_value = self.get_base_value(self._hedge_market_pair) + is_buy, value_to_hedge = self.get_hedge_direction_and_value() + price, amount = self.calculate_hedge_price_and_amount(is_buy, value_to_hedge) + lines.extend(["", f" Mode: Value, Total value: {total_value:.6g}, Hedge value: {hedge_value:.6g}"]) + if amount > 0: + lines.extend( + [ + "", + f" Next Hedge direction: {'buy' if is_buy else 'sell'}, Hedge price: {price:.6g}, Hedge amount: {amount:.6g}", + ] + ) + return lines + + def get_amount_mode_status_str(value_mode: bool) -> List[str]: + if value_mode: + return [] + lines = ["", " Mode: Amount"] + data = [] + for hedge_market, market_list in self._market_pair_by_asset.items(): + hedge_market_name = hedge_market.market.name + asset = hedge_market.trading_pair.split("-")[0] + market_names = ", ".join([market.market.name for market in market_list]) + total_amount = sum(self.get_base_amount(market_pair) for market_pair in market_list) + hedge_amount = self.get_base_amount(hedge_market) + net_amount = total_amount * self._hedge_ratio + hedge_amount + data.append([ + hedge_market_name, + asset, + total_amount, + hedge_amount, + net_amount, + market_names, + ]) + + df = pd.DataFrame(data=data, columns=["Hedge Market", "Asset", "Total Amount", "Hedge Amount", "Net Amount", "Markets"]) + lines.extend([" " + line for line in str(df).split("\n")]) + return lines + + def get_last_checked_seconds_str() -> List[str]: + if self._last_timestamp < 1e9: + return [" Last checked: Not started."] + return [f" Last checked {self.current_timestamp - self._last_timestamp} seconds ago."] + + def get_status_messages() -> List[str]: + if self._status_messages: + return ["", " Status Messages:"] + [" " + line for line in self._status_messages] + return [] + + lines = ( + get_wallet_status_str() + + get_asset_status_str() + + get_position_status_str() + + get_order_status_str() + + get_value_mode_status_str(self._value_mode) + + get_amount_mode_status_str(self._value_mode) + + get_last_checked_seconds_str() + + get_status_messages() + + [""] + + self.network_warning(self._all_markets) + ) + return "\n".join(lines) + + def start(self, clock: Clock, timestamp: float) -> None: + """ + Start the strategy. + :param clock: Clock to use. + :param timestamp: Current time. + """ + self._last_timestamp = timestamp + self.apply_initial_setting() + + def apply_initial_setting(self) -> None: + """ + Check if the market is derivative, and if so, set the initial setting. + """ + if not self.is_derivative(self._hedge_market_pairs[0]): + return + if not self._enable_auto_set_position_mode: + logging.info("Auto set position mode is disabled.") + return + position_mode = "ONEWAY" if self._position_mode == PositionMode.ONEWAY else "HEDGE" + msg = ( + f"Please ensure that the position mode on {self._hedge_market_pairs[0].market.name} " + f"is set to {position_mode}. " + f"The bot will try to automatically set position mode to {position_mode}. " + f"You may ignore the message if the position mode is already set to {position_mode}.") + self.notify_hb_app(msg) + self.logger().warning(msg) + for market_pair in self._hedge_market_pairs: + market = market_pair.market + trading_pair = market_pair.trading_pair + market.set_leverage(trading_pair, self._leverage) + market.set_position_mode(self._position_mode) + + def interval_log(self, key: str, message: str) -> None: + """ + Log message at interval. + :param key: Key to identify the last recorded timestamp. + :param message: Message to log. + """ + if self._last_timestamp - self._last_report_timestamp.get(key, 0) > self._status_report_interval: + self.logger().info(message) + self._last_report_timestamp[key] = self._last_timestamp + + def tick(self, timestamp: float) -> None: + """ + Check if hedge interval has passed and process hedge if so + :param timestamp: clock timestamp + """ + if self.check_and_cancel_active_orders(): + self.interval_log("hedge", "Active orders present. Skipping hedge check until active orders expires.") + return + if timestamp - self._last_timestamp < self._hedge_interval: + return + self._all_markets_ready = all([market.ready for market in self.active_markets]) + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + for market in self.active_markets: + if not market.ready: + self.logger().warning(f"Market {market.name} is not ready.") + self.logger().warning("Markets are not ready. No hedge trades are permitted.") + return + + if not all([market.network_status is NetworkStatus.CONNECTED for market in self.active_markets]): + self.logger().warning( + "WARNING: Some markets are not connected or are down at the moment. " + "Hedging may be dangerous when markets or networks are unstable. " + "Retrying after %ss.", + self._hedge_interval, + ) + return + self.interval_log("hedge", "Checking hedge conditions...") + self._status_messages = [] + self.hedge() + self._last_timestamp = timestamp + + def get_positions(self, market_pair: MarketTradingPairTuple, position_side: PositionSide = None) -> List[Position]: + """ + Get the active positions of a market. + :param market_pair: Market pair to get the positions of. + :return: The active positions of the market. + """ + trading_pair = market_pair.trading_pair + positions: List[Position] = [ + position + for position in market_pair.market.account_positions.values() + if not isinstance(position, PositionMode) and position.trading_pair == trading_pair + ] + if position_side: + return [position for position in positions if position.position_side == position_side] + return positions + + def get_derivative_base_amount(self, market_pair: MarketTradingPairTuple) -> Decimal: + """ + Get the value of the derivative base asset. + :param market_pair: The market pair to get the value of the derivative base asset. + :return: The value of the derivative base asset. + """ + positions = self.get_positions(market_pair) + amount = 0 + + for position in positions: + if position.position_side in [PositionSide.LONG, PositionSide.BOTH]: + amount += position.amount + if position.position_side == PositionSide.SHORT: + amount -= abs(position.amount) + return amount + self._offsets[market_pair] + + def get_base_amount(self, market_pair: MarketTradingPairTuple) -> Decimal: + """ + Get the amount of the base asset of the market pair. + + :params market_pair: The market pair to get the amount of the base asset of. + :returns: The amount of the base asset of the market pair. + """ + if self.is_derivative(market_pair): + self.logger().debug(f"Getting derivative base amount for {market_pair.trading_pair}") + return self.get_derivative_base_amount(market_pair) + return market_pair.base_balance + self._offsets[market_pair] + + def get_base_value(self, market_pair: MarketTradingPairTuple) -> Decimal: + """ + Get the base asset value of a market. e.g BTC/USDT = BTC amount * BTC/USDT price. + + :params market_pair: The market pair to get the base asset value of. + :returns: The base asset value of the market pair. + """ + base_amount = self.get_base_amount(market_pair) + base_price = market_pair.get_mid_price() + return base_amount * base_price + + def get_hedge_direction_and_value(self) -> Tuple[bool, Decimal]: + """ + Calculate the value that is required to be hedged. + :returns: A tuple of the hedge direction (buy/sell) and the value to be hedged. + """ + total_value = sum(self.get_base_value(market_pair) for market_pair in self._market_pairs) + hedge_value = self.get_base_value(self._hedge_market_pair) + net_value = total_value * self._hedge_ratio + hedge_value + is_buy = net_value < 0 + value_to_hedge = abs(net_value) + return is_buy, value_to_hedge + + def get_slippage_ratio(self, is_buy: bool) -> Decimal: + """ + Get the slippage ratio for a buy or sell. + :param is_buy: True if buy, False if sell. + :returns: The ratio to multiply the price by to account for slippage. + """ + return 1 + self._slippage if is_buy else 1 - self._slippage + + def calculate_hedge_price_and_amount(self, is_buy: bool, value_to_hedge: Decimal) -> Tuple[Decimal, Decimal]: + """ + Calculate the price and amount to hedge. + :params is_buy: The direction of the hedge. + :params value_to_hedge: The value to hedge. + :returns: The price and amount to hedge. + """ + price = self._hedge_market_pair.get_mid_price() + amount = value_to_hedge / price + price = price * self.get_slippage_ratio(is_buy) + trading_pair = self._hedge_market_pair.trading_pair + quantized_price = self._hedge_market_pair.market.quantize_order_price(trading_pair, price) + quantized_amount = self._hedge_market_pair.market.quantize_order_amount(trading_pair, amount) + return quantized_price, quantized_amount + + def hedge_by_value(self) -> None: + """ + The main process of the strategy for value mode = True. + """ + is_buy, value_to_hedge = self.get_hedge_direction_and_value() + price, amount = self.calculate_hedge_price_and_amount(is_buy, value_to_hedge) + if amount == Decimal("0"): + self.logger().debug("No hedge required.") + self._status_messages.append("No hedge required.") + return + self.logger().info( + f"Hedging by value. Hedge direction: {'buy' if is_buy else 'sell'}. " + f"Hedge price: {price}. Hedge amount: {amount}." + ) + order_candidates = self.get_order_candidates(self._hedge_market_pair, is_buy, amount, price) + if not order_candidates: + self.logger().info("No order candidates.") + self._status_messages.append("No order candidates.") + return + self.place_orders(self._hedge_market_pair, order_candidates) + + def get_hedge_direction_and_amount_by_asset( + self, hedge_pair: MarketTradingPairTuple, market_list: List[MarketTradingPairTuple] + ) -> Tuple[bool, Decimal]: + """ + Calculate the amount that is required to be hedged. + :params hedge_pair: The market pair to hedge. + :params market_list: The list of markets to get the amount of the base asset of. + :returns: The direction to hedge (buy/sell) and the amount of the base asset of the market pair. + """ + total_amount = 0 + for market_pair in market_list: + amount = self.get_base_amount(market_pair) + total_amount += self.get_base_amount(market_pair) + self.logger().debug("Market pair: %s amount: %s, total_amount: %s", market_pair, amount, total_amount) + + hedge_amount = self.get_base_amount(hedge_pair) + net_amount = total_amount * self._hedge_ratio + hedge_amount + is_buy = net_amount < 0 + amount_to_hedge = abs(net_amount) + self.logger().debug("Hedge direction: %s, amount to hedge: %s net amount: %s", is_buy, amount_to_hedge, net_amount) + return is_buy, amount_to_hedge + + def hedge_by_amount(self) -> None: + """ + The main process of the strategy for value mode = False. + """ + for hedge_market, market_list in self._market_pair_by_asset.items(): + is_buy, amount_to_hedge = self.get_hedge_direction_and_amount_by_asset(hedge_market, market_list) + asset = hedge_market.trading_pair.split("-")[0] + self.logger().debug("Hedge by amount for %s: %s", asset, amount_to_hedge) + self._status_messages.append(f"Hedge by amount for {asset}: {amount_to_hedge}") + if amount_to_hedge == 0: + self.logger().debug("No hedge required for %s.", asset) + self._status_messages.append(f"No hedge required for {asset}.") + continue + price = hedge_market.get_mid_price() * self.get_slippage_ratio(is_buy) + self.logger().info( + "Hedge by amount. Mid price: %s Hedge direction: %s. Hedge price: %s. Hedge amount: %s", + hedge_market.get_mid_price(), is_buy, price, amount_to_hedge + ) + order_candidates = self.get_order_candidates(hedge_market, is_buy, amount_to_hedge, price) + if not order_candidates: + self.logger().info("Difference in hedge_amount found but no order candidates for %s is available. “This is either due to insufficient balance to perform hedge or min trade size not reached or minimum exchange trade size not met", asset) + self._status_messages.append(f"No order candidates for {asset}.") + continue + self.place_orders(hedge_market, order_candidates) + + def get_perpetual_order_candidates( + self, market_pair: MarketTradingPairTuple, is_buy: bool, amount: Decimal, price: Decimal + ) -> List[PerpetualOrderCandidate]: + """ + Check if the balance is sufficient to place an order. + if not, adjust the amount to the balance available. + returns the order candidate if the order meets the accepted criteria + else, return None + """ + self.logger().info("Checking perpetual order candidates for %s %s %s %s", market_pair, "buy" if is_buy else "sell", amount, price) + + def get_closing_order_candidate(is_buy: bool, amount: Decimal, price: Decimal) -> Union[PerpetualOrderCandidate, None]: + opp_position_side = PositionSide.SHORT if is_buy else PositionSide.LONG + opp_position_list = self.get_positions(market_pair, opp_position_side) + # opp_position_list should only have 1 position + for opp_position in opp_position_list: + close_amount = min(amount, abs(opp_position.amount)) + order_candidate = PerpetualOrderCandidate( + trading_pair=market_pair.trading_pair, + is_maker=False, + order_side=TradeType.BUY if is_buy else TradeType.SELL, + amount=close_amount, + price=price, + order_type=OrderType.LIMIT, + leverage=Decimal(self._leverage), + position_close=True, + ) + adjusted_candidate_order = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + return adjusted_candidate_order + return None + + budget_checker = market_pair.market.budget_checker + if amount * price < self._min_trade_size: + self.logger().info("trade value (%s) is less than min trade size. (%s)", amount * price, self._min_trade_size) + return [] + order_candidates = [] + if self._position_mode == PositionMode.HEDGE: + order_candidate = get_closing_order_candidate(is_buy, amount, price) + if order_candidate: + order_candidates.append(order_candidate) + amount -= order_candidate.amount + order_candidate = PerpetualOrderCandidate( + trading_pair=market_pair.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY if is_buy else TradeType.SELL, + amount=amount, + price=price, + leverage=Decimal(self._leverage), + ) + self.logger().info("order candidate: %s", order_candidate) + adjusted_candidate_order = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + self.logger().info("adjusted order candidate: %s", adjusted_candidate_order) + if adjusted_candidate_order.amount > 0: + order_candidates.append(adjusted_candidate_order) + return order_candidates + + def get_spot_order_candidates( + self, market_pair: MarketTradingPairTuple, is_buy: bool, amount: Decimal, price: Decimal + ) -> List[OrderCandidate]: + """ + Check if the balance is sufficient to place an order. + if not, adjust the amount to the balance available. + returns the order candidate if the order meets the accepted criteria + else, return None + """ + budget_checker = market_pair.market.budget_checker + if amount * price < self._min_trade_size: + self.logger().info("trade value (%s) is less than min trade size. (%s)", amount * price, self._min_trade_size) + return [] + order_candidate = OrderCandidate( + trading_pair=market_pair.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY if is_buy else TradeType.SELL, + amount=amount, + price=price, + ) + self.logger().info("order candidate: %s", order_candidate) + adjusted_candidate_order = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + self.logger().info("adjusted order candidate: %s", adjusted_candidate_order) + if adjusted_candidate_order.amount > 0: + return [adjusted_candidate_order] + return [] + + def place_orders( + self, market_pair: MarketTradingPairTuple, orders: Union[List[OrderCandidate], List[PerpetualOrderCandidate]] + ) -> None: + """ + Place an order refering the order candidates. + :params market_pair: The market pair to place the order. + :params orders: The list of orders to place. + """ + self.logger().info("Placing %s orders", len(orders)) + for order in orders: + self.logger().info(f"Create {order.order_side} {order.amount} {order.trading_pair} at {order.price}") + is_buy = order.order_side == TradeType.BUY + amount = order.amount + price = order.price + position_action = PositionAction.OPEN + if isinstance(order, PerpetualOrderCandidate) and order.position_close: + position_action = PositionAction.CLOSE + trade = self.buy_with_specific_market if is_buy else self.sell_with_specific_market + trade(market_pair, amount, order_type=OrderType.LIMIT, price=price, position_action=position_action) + + def check_and_cancel_active_orders(self) -> bool: + """ + Check if there are any active orders and cancel them + :return: True if there are active orders, False otherwise. + """ + if not self.active_orders: + return False + for market_pair, order in self.active_orders: + if order_age(order, self.current_timestamp) < self._max_order_age: + continue + self.logger().info( + f"Cancel {'buy' if order.is_buy else 'sell'} {order.quantity} {order.trading_pair} at {order.price}" + ) + market_pair.cancel(order.trading_pair, order.client_order_id) + return True diff --git a/hummingbot/strategy/hedge/hedge_config_map_pydantic.py b/hummingbot/strategy/hedge/hedge_config_map_pydantic.py new file mode 100644 index 0000000..d5ef938 --- /dev/null +++ b/hummingbot/strategy/hedge/hedge_config_map_pydantic.py @@ -0,0 +1,275 @@ +from decimal import Decimal +from typing import Dict, List, Literal, Union + +from pydantic import Field, validator + +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_validators import validate_bool, validate_decimal, validate_market_trading_pair +from hummingbot.client.config.strategy_config_data_types import BaseStrategyConfigMap +from hummingbot.client.settings import AllConnectorSettings + +ExchangeEnum = ClientConfigEnum( # rebuild the exchanges enum + value="Exchanges", # noqa: F821 + # using get_connector_settings instead of get_all_connector_names + # due to all_connector_names does not include testnet + names={e: e for e in AllConnectorSettings.get_connector_settings()}, + type=str, +) + + +def get_field(i: int) -> Field: + return Field( + default="", + description="The name of the hedge exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: f"Do you want to monitor connector {i}? (y/n)", + prompt_on_new=True, + ), + ) + + +MAX_CONNECTOR = 5 + + +class EmptyMarketConfigMap(BaseClientModel): + connector: Union[None, ExchangeEnum] = None + markets: Union[None, List[str]] = None + offsets: Union[None, List[Decimal]] = None + + class Config: + title = "n" + + +class MarketConfigMap(BaseClientModel): + connector: Union[None, ExchangeEnum] = Field( + default=..., + description="The name of the exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter name of the exchange to use", + prompt_on_new=True, + ), + ) + markets: Union[None, List[str]] = Field( + default=..., + description="The name of the trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: MarketConfigMap.trading_pair_prompt(mi), + prompt_on_new=True, + ), + ) + offsets: Union[None, List[Decimal]] = Field( + default=Decimal("0.0"), + description="The offsets for each trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter the offsets to use to hedge the markets comma seperated. " + "the remainder will be assumed as 0 if no inputs. " + "e.g if markets is BTC-USDT,ETH-USDT,LTC-USDT. " + "and offsets is 0.1, -0.2. " + "then the offset amount that will be added is 0.1 BTC, -0.2 ETH and 0 LTC. ", + prompt_on_new=True, + ), + ) + + @validator("offsets", pre=True) + def validate_offsets(cls, offsets: Union[str, List[Decimal]], values: Dict): + """checks and ensure offsets are of decimal type""" + if offsets is None: + return None + if isinstance(offsets, str): + offsets = offsets.split(",") + for offset in offsets: + if validate_decimal(offset): + return validate_decimal(offset) + markets = values["markets"] + if len(offsets) >= len(markets): + return offsets[: len(markets)] + return offsets + ["0"] * (len(markets) - len(offsets)) + + @validator("markets", pre=True) + def validate_markets(cls, markets: Union[str, List[str]], values: Dict): + """checks and ensure offsets are of decimal type""" + if markets is None: + return None + if isinstance(markets, str): + markets = markets.split(",") + for market in markets: + validated = validate_market_trading_pair(values["connector"], market) + if validated: + return validated + return markets + + @staticmethod + def trading_pair_prompt(model_instance: "MarketConfigMap") -> str: + exchange = model_instance.connector + if exchange is None: + return "" + example = AllConnectorSettings.get_example_pairs().get(exchange) + return ( + f"Enter the token trading pair you would like to hedge/monitor on comma seperated" + f" {exchange}{f' (e.g. {example})' if example else ''}" + ) + + class Config: + title = "y" + + +market_config_map = Union[EmptyMarketConfigMap, MarketConfigMap] + + +class HedgeConfigMap(BaseStrategyConfigMap): + strategy: str = Field(default="hedge", client_data=None) + value_mode: bool = Field( + default=True, + description="Whether to hedge based on value or amount", + client_data=ClientFieldData( + prompt=lambda mi: "Do you want to hedge by asset value [y] or asset amount[n] (y/n)?", + prompt_on_new=True, + ), + ) + hedge_ratio: Decimal = Field( + default=Decimal("1"), + description="The ratio of the hedge amount to the total asset amount", + client_data=ClientFieldData( + prompt=lambda mi: "Enter ratio of asset to hedge, e.g 0.5 means 50 percent of the total asset value will be hedged.", + prompt_on_new=True, + ), + ) + hedge_interval: int = Field( + default=60, + description="The interval in seconds to check for hedge.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter the interval in seconds to check for hedge", + prompt_on_new=True, + ), + ) + min_trade_size: Decimal = Field( + default=Decimal("0.0"), + description="The minimum trade size in quote asset.", + ge=0, + client_data=ClientFieldData( + prompt=lambda mi: "Enter the minimum trade size in quote asset", + prompt_on_new=True, + ), + ) + slippage: Decimal = Field( + default=Decimal("0.02"), + description="The slippage tolerance for the hedge order.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter the slippage tolerance for the hedge order", + prompt_on_new=True, + ), + ) + hedge_connector: ExchangeEnum = Field( + default=..., + description="The name of the hedge exchange connector.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter name of the exchange to hedge overall assets", + prompt_on_new=True, + ), + ) + hedge_markets: List[str] = Field( + default=..., + description="The name of the trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: HedgeConfigMap.hedge_markets_prompt(mi), + prompt_on_new=True, + ), + ) + hedge_offsets: List[Decimal] = Field( + default=Decimal("0.0"), + description="The offsets for each trading pair.", + client_data=ClientFieldData( + prompt=lambda mi: HedgeConfigMap.hedge_offsets_prompt(mi), + prompt_on_new=True, + ), + ) + hedge_leverage: int = Field( + default=1, + description="The leverage to use for the market.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter the leverage to use for the hedge market", + prompt_on_new=True, + ), + ) + hedge_position_mode: Literal["ONEWAY", "HEDGE"] = Field( + default="ONEWAY", + description="The position mode to use for the market.", + client_data=ClientFieldData( + prompt=lambda mi: "Enter the position mode to use for the hedge market", + prompt_on_new=True, + ), + ) + enable_auto_set_position_mode: bool = Field( + default=False, + description="Whether to automatically set the exchange position mode to one-way or hedge based ratio.", + client_data=ClientFieldData( + prompt=lambda mi: "Do you want to automatically set the exchange position mode to one-way or hedge [y/n]?", + prompt_on_new=False, + ) + ) + connector_0: market_config_map = get_field(0) + connector_1: market_config_map = get_field(1) + connector_2: market_config_map = get_field(2) + connector_3: market_config_map = get_field(3) + connector_4: market_config_map = get_field(4) + + @validator("connector_0", "connector_1", "connector_2", "connector_3", "connector_4", pre=True) + def construct_connector(cls, v: Union[str, bool, EmptyMarketConfigMap, MarketConfigMap, Dict]): + if isinstance(v, (EmptyMarketConfigMap, MarketConfigMap, Dict)): + return v + if validate_bool(v): + raise ValueError("enter a boolean value") + if v.lower() in (True, "true", "yes", "y"): + return MarketConfigMap.construct() + return EmptyMarketConfigMap.construct() + + @validator("hedge_offsets", pre=True) + def validate_offsets(cls, offsets: Union[str, List[Decimal]], values: Dict): + """checks and ensure offsets are of decimal type""" + if isinstance(offsets, str): + offsets = offsets.split(",") + for offset in offsets: + if validate_decimal(offset): + return validate_decimal(offset) + markets = values["hedge_markets"] + if len(offsets) >= len(markets): + return offsets[: len(markets)] + return offsets + ["0"] * (len(markets) - len(offsets)) + + @validator("hedge_markets", pre=True) + def validate_markets(cls, markets: Union[str, List[str]], values: Dict): + """checks and ensure offsets are of decimal type""" + if isinstance(markets, str): + markets = markets.split(",") + for market in markets: + validated = validate_market_trading_pair(values["hedge_connector"], market) + if validated: + raise ValueError(validated) + if len(markets) == 0: + raise ValueError("No market entered") + if values["value_mode"] and len(markets) > 1: + raise ValueError("Only one market can be used for value mode") + return markets + + @staticmethod + def hedge_markets_prompt(mi: "HedgeConfigMap") -> str: + """prompts for the markets to hedge""" + exchange = mi.hedge_connector + if mi.value_mode: + return f"Value mode: Enter the trading pair you would like to hedge on {exchange}. (Example: BTC-USDT)" + return f"Amount mode: Enter the list of trading pair you would like to hedge on {exchange}. comma seperated. \ + (Example: BTC-USDT,ETH-USDT) Only markets with the same base as the hedge markets will be hedged." \ + "WARNING: currently only supports hedging of base assets." + + @staticmethod + def hedge_offsets_prompt(mi: "HedgeConfigMap") -> str: + """prompts for the markets to hedge""" + if mi.value_mode: + trading_pair = mi.hedge_markets[0] + base = trading_pair.split("-")[0] + return f"Enter the offset for {base}. (Example: 0.1 = +0.1{base} used in calculation of hedged value)" + return ( + "Enter the offsets to use to hedge the markets comma seperated. " + "(Example: 0.1,-0.2 = +0.1BTC,-0.2ETH, 0LTC will be offset for the exchange amount " + "if markets is BTC-USDT,ETH-USDT,LTC-USDT)" + ) diff --git a/hummingbot/strategy/hedge/start.py b/hummingbot/strategy/hedge/start.py new file mode 100644 index 0000000..9b23088 --- /dev/null +++ b/hummingbot/strategy/hedge/start.py @@ -0,0 +1,37 @@ +from hummingbot.strategy.hedge.hedge import HedgeStrategy +from hummingbot.strategy.hedge.hedge_config_map_pydantic import MAX_CONNECTOR, HedgeConfigMap +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + c_map: HedgeConfigMap = self.strategy_config_map + hedge_connector = c_map.hedge_connector.lower() + hedge_markets = c_map.hedge_markets + hedge_offsets = c_map.hedge_offsets + offsets_dict = {hedge_connector: hedge_offsets} + initialize_markets = [(hedge_connector, hedge_markets)] + for i in range(MAX_CONNECTOR): + connector_config = getattr(c_map, f"connector_{i}") + connector = connector_config.connector + if not connector: + continue + connector = connector.lower() + markets = connector_config.markets + offsets_dict[connector] = connector_config.offsets + initialize_markets.append((connector, markets)) + self._initialize_markets(initialize_markets) + self.market_trading_pair_tuples = [] + offsets_market_dict = {} + for connector, markets in initialize_markets: + offsets = offsets_dict[connector] + for market, offset in zip(markets, offsets): + base, quote = market.split("-") + market_info = MarketTradingPairTuple(self.markets[connector], market, base, quote) + self.market_trading_pair_tuples.append(market_info) + offsets_market_dict[market_info] = offset + index = len(hedge_markets) + hedge_market_pairs = self.market_trading_pair_tuples[0:index] + market_pairs = self.market_trading_pair_tuples[index:] + self.strategy = HedgeStrategy( + config_map=c_map, hedge_market_pairs=hedge_market_pairs, market_pairs=market_pairs, offsets=offsets_market_dict + ) diff --git a/hummingbot/strategy/limit_order/__init__.py b/hummingbot/strategy/limit_order/__init__.py new file mode 100644 index 0000000..ce9d1a3 --- /dev/null +++ b/hummingbot/strategy/limit_order/__init__.py @@ -0,0 +1,3 @@ +# Initializing the project +from .limit_order import LimitOrder +__all__ = [limit_order] \ No newline at end of file diff --git a/hummingbot/strategy/limit_order/limit_order.py b/hummingbot/strategy/limit_order/limit_order.py new file mode 100644 index 0000000..a4f387c --- /dev/null +++ b/hummingbot/strategy/limit_order/limit_order.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +from decimal import Decimal +import logging +from hummingbot.core.event.events import OrderType, BuyOrderCompletedEvent +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +hws_logger = None + +class LimitOrder(StrategyPyBase): + + @classmethod + def logger(cls) -> HummingbotLogger: + global hws_logger + if hws_logger is None: + hws_logger = logging.getLogger(__name__) + return hws_logger + + def __init__(self, market_info: MarketTradingPairTuple): + super().__init__() + self._market_info = market_info + self._connector_ready = False + self._limit_order_placed = False + self.add_markets([market_info.market]) + + def tick(self, timestamp: float): + if not self._connector_ready: + self._connector_ready = self._market_info.market.ready + if not self._connector_ready: + self.logger().warning(f"{self._market_info.market.name} is not ready. Please wait...") + return + else: + self.logger().warning(f"{self._market_info.market.name} is ready. Trading started") + + if not self._limit_order_placed: + mid_price = self._market_info.get_mid_price() + order_id = self.buy_with_specific_market( + self._market_info, + Decimal("500"), + OrderType.LIMIT, + mid_price + ) + self.logger().info(f"Submitted limit buy order {order_id}") + self._limit_order_placed = True + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + self.logger().info(f"Your limit buy order {order_completed_event.order_id} has been executed") + # Place a market order once the limit order is filled + self.place_market_order(order_completed_event) + + def place_market_order(self): + market_order_id = self.buy_with_specific_market( + self._market_info, + Decimal("500"), # The amount of the asset to buy + OrderType.MARKET + ) + self.logger().info(f"Submitted market buy order {market_order_id}") + + def format_status(self) -> str: + lines = [] + lines.append(f"Exchange: {self._market_info.market.name}") + lines.append(f"Connector ready: {'Yes' if self._connector_ready else 'No'}") + lines.append(f"Limit Order Placed: {'Yes' if self._limit_order_placed else 'No'}") + return "\n".join(lines) diff --git a/hummingbot/strategy/limit_order/limit_order_config_map.py b/hummingbot/strategy/limit_order/limit_order_config_map.py new file mode 100644 index 0000000..ed245f0 --- /dev/null +++ b/hummingbot/strategy/limit_order/limit_order_config_map.py @@ -0,0 +1,25 @@ +from hummingbot.client.config.config_var import ConfigVar + +# Returns a market prompt that incorporates the connector value set by the user +def market_prompt() -> str: + connector = limit_order_config_map.get("connector").value + return f'Enter the token trading pair on {connector} >>> ' + +# List of parameters defined by the strategy +limit_order_config_map ={ + "strategy": + ConfigVar(key="strategy", + prompt="", + default="limit_order", + ), + "connector": + ConfigVar(key="connector", + prompt="Enter the name of the exchange >>> ", + prompt_on_new=True, + ), + "market": ConfigVar( + key="market", + prompt=market_prompt, + prompt_on_new=True, + ), +} \ No newline at end of file diff --git a/hummingbot/strategy/limit_order/start.py b/hummingbot/strategy/limit_order/start.py new file mode 100644 index 0000000..23e712e --- /dev/null +++ b/hummingbot/strategy/limit_order/start.py @@ -0,0 +1,14 @@ +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.limit_order import LimitOrder +from hummingbot.strategy.limit_order.limit_order_config_map import limit_order_config_map as c_map + +def start(self): + connector = c_map.get("connector").value.lower() + market = c_map.get("market").value + + self._initialize_markets([(connector, [market])]) + base, quote = market.split("-") + market_info = MarketTradingPairTuple(self.markets[connector], market, base, quote) + self.market_trading_pair_tuples = [market_info] + + self.strategy = LimitOrder(market_info) \ No newline at end of file diff --git a/hummingbot/strategy/liquidity_mining/__init__.py b/hummingbot/strategy/liquidity_mining/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/liquidity_mining/data_types.py b/hummingbot/strategy/liquidity_mining/data_types.py new file mode 100644 index 0000000..4601c01 --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/data_types.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +""" +Liquidity mining data types... +""" + +from decimal import Decimal + + +class PriceSize: + """ + Order price and order size. + """ + def __init__(self, price: Decimal, size: Decimal): + self.price: Decimal = price + self.size: Decimal = size + + def __repr__(self): + return f"[ p: {self.price} s: {self.size} ]" + + +class Proposal: + """ + An order proposal for liquidity mining. + market is the base quote pair like "ETH-USDT". + buy is a buy order proposal. + sell is a sell order proposal. + """ + def __init__(self, market: str, buy: PriceSize, sell: PriceSize): + self.market: str = market + self.buy: PriceSize = buy + self.sell: PriceSize = sell + + def __repr__(self): + return f"{self.market} buy: {self.buy} sell: {self.sell}" + + def base(self): + return self.market.split("-")[0] + + def quote(self): + return self.market.split("-")[1] diff --git a/hummingbot/strategy/liquidity_mining/dummy.pxd b/hummingbot/strategy/liquidity_mining/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/liquidity_mining/dummy.pyx b/hummingbot/strategy/liquidity_mining/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/liquidity_mining/liquidity_mining.py b/hummingbot/strategy/liquidity_mining/liquidity_mining.py new file mode 100644 index 0000000..6334b1a --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/liquidity_mining.py @@ -0,0 +1,599 @@ +import asyncio +import logging +from decimal import Decimal +from statistics import mean +from typing import Dict, List, Set, Union + +import numpy as np +import pandas as pd + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.parrot import get_campaign_summary +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.utils.estimate_fee import build_trade_fee +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.pure_market_making.inventory_skew_calculator import ( + calculate_bid_ask_ratios_from_base_asset_ratio, +) +from hummingbot.strategy.strategy_py_base import StrategyPyBase +from hummingbot.strategy.utils import order_age + +from ...client.config.client_config_map import ClientConfigMap +from ...client.config.config_helpers import ClientConfigAdapter +from .data_types import PriceSize, Proposal + +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_nan = Decimal("NaN") +lms_logger = None + + +class LiquidityMiningStrategy(StrategyPyBase): + + @classmethod + def logger(cls) -> HummingbotLogger: + global lms_logger + if lms_logger is None: + lms_logger = logging.getLogger(__name__) + return lms_logger + + def init_params(self, + client_config_map: Union[ClientConfigAdapter, ClientConfigMap], + exchange: ExchangeBase, + market_infos: Dict[str, MarketTradingPairTuple], + token: str, + order_amount: Decimal, + spread: Decimal, + inventory_skew_enabled: bool, + target_base_pct: Decimal, + order_refresh_time: float, + order_refresh_tolerance_pct: Decimal, + inventory_range_multiplier: Decimal = Decimal("1"), + volatility_interval: int = 60 * 5, + avg_volatility_period: int = 10, + volatility_to_spread_multiplier: Decimal = Decimal("1"), + max_spread: Decimal = Decimal("-1"), + max_order_age: float = 60. * 60., + status_report_interval: float = 900, + hb_app_notification: bool = False): + self._client_config_map = client_config_map + self._exchange = exchange + self._market_infos = market_infos + self._empty_ob_market_infos = {} + self._token = token + self._order_amount = order_amount + self._spread = spread + self._order_refresh_time = order_refresh_time + self._order_refresh_tolerance_pct = order_refresh_tolerance_pct + self._inventory_skew_enabled = inventory_skew_enabled + self._target_base_pct = target_base_pct + self._inventory_range_multiplier = inventory_range_multiplier + self._volatility_interval = volatility_interval + self._avg_volatility_period = avg_volatility_period + self._volatility_to_spread_multiplier = volatility_to_spread_multiplier + self._max_spread = max_spread + self._max_order_age = max_order_age + self._ev_loop = asyncio.get_event_loop() + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._ready_to_trade = False + self._refresh_times = {market: 0 for market in market_infos} + self._token_balances = {} + self._sell_budgets = {} + self._buy_budgets = {} + self._mid_prices = {market: [] for market in market_infos} + self._volatility = {market: s_decimal_nan for market in self._market_infos} + self._last_vol_reported = 0. + self._hb_app_notification = hb_app_notification + + self.add_markets([exchange]) + + @property + def active_orders(self): + """ + List active orders (they have been sent to the market and have not been canceled yet) + """ + limit_orders = self.order_tracker.active_limit_orders + return [o[1] for o in limit_orders] + + @property + def sell_budgets(self): + return self._sell_budgets + + @property + def buy_budgets(self): + return self._buy_budgets + + def tick(self, timestamp: float): + """ + Clock tick entry point, is run every second (on normal tick setting). + :param timestamp: current tick timestamp + """ + if not self._ready_to_trade: + # Check if there are restored orders, they should be canceled before strategy starts. + self._ready_to_trade = self._exchange.ready and len(self._exchange.limit_orders) == 0 + if not self._exchange.ready: + self.logger().warning(f"{self._exchange.name} is not ready. Please wait...") + return + else: + self.logger().info(f"{self._exchange.name} is ready. Trading started.") + if self._validate_order_book_for_markets() >= 1: + self.create_budget_allocation() + else: + self.logger().warning(f"{self._exchange.name} has no pairs with order book. Consider redefining your strategy.") + return + + self.update_mid_prices() + self.update_volatility() + proposals = self.create_base_proposals() + self._token_balances = self.adjusted_available_balances() + if self._inventory_skew_enabled: + self.apply_inventory_skew(proposals) + self.apply_budget_constraint(proposals) + self.cancel_active_orders(proposals) + self.execute_orders_proposal(proposals) + + self._last_timestamp = timestamp + + async def active_orders_df(self) -> pd.DataFrame: + """ + Return the active orders in a DataFrame. + """ + size_q_col = f"Amt({self._token})" if self.is_token_a_quote_token() else "Amt(Quote)" + columns = ["Market", "Side", "Price", "Spread", "Amount", size_q_col, "Age"] + data = [] + for order in self.active_orders: + mid_price = self._market_infos[order.trading_pair].get_mid_price() + spread = 0 if mid_price == 0 else abs(order.price - mid_price) / mid_price + size_q = order.quantity * mid_price + age = order_age(order, self.current_timestamp) + # // indicates order is a paper order so 'n/a'. For real orders, calculate age. + age_txt = "n/a" if age <= 0. else pd.Timestamp(age, unit='s').strftime('%H:%M:%S') + data.append([ + order.trading_pair, + "buy" if order.is_buy else "sell", + float(order.price), + f"{spread:.2%}", + float(order.quantity), + float(size_q), + age_txt + ]) + df = pd.DataFrame(data=data, columns=columns) + df.sort_values(by=["Market", "Side"], inplace=True) + return df + + def budget_status_df(self) -> pd.DataFrame: + """ + Return the trader's budget in a DataFrame + """ + data = [] + columns = ["Market", f"Budget({self._token})", "Base bal", "Quote bal", "Base/Quote"] + for market, market_info in self._market_infos.items(): + mid_price = market_info.get_mid_price() + base_bal = self._sell_budgets[market] + quote_bal = self._buy_budgets[market] + total_bal_in_quote = (base_bal * mid_price) + quote_bal + total_bal_in_token = total_bal_in_quote + if not self.is_token_a_quote_token(): + total_bal_in_token = base_bal + (quote_bal / mid_price) + base_pct = (base_bal * mid_price) / total_bal_in_quote if total_bal_in_quote > 0 else s_decimal_zero + quote_pct = quote_bal / total_bal_in_quote if total_bal_in_quote > 0 else s_decimal_zero + data.append([ + market, + float(total_bal_in_token), + float(base_bal), + float(quote_bal), + f"{base_pct:.0%} / {quote_pct:.0%}" + ]) + df = pd.DataFrame(data=data, columns=columns).replace(np.nan, '', regex=True) + df.sort_values(by=["Market"], inplace=True) + return df + + def market_status_df(self) -> pd.DataFrame: + """ + Return the market status (prices, volatility) in a DataFrame + """ + data = [] + columns = ["Market", "Mid price", "Best bid", "Best ask", "Volatility"] + for market, market_info in self._market_infos.items(): + mid_price = market_info.get_mid_price() + best_bid = self._exchange.get_price(market, False) + best_ask = self._exchange.get_price(market, True) + best_bid_pct = abs(best_bid - mid_price) / mid_price + best_ask_pct = (best_ask - mid_price) / mid_price + data.append([ + market, + float(mid_price), + f"{best_bid_pct:.2%}", + f"{best_ask_pct:.2%}", + "" if self._volatility[market].is_nan() else f"{self._volatility[market]:.2%}", + ]) + df = pd.DataFrame(data=data, columns=columns).replace(np.nan, '', regex=True) + df.sort_values(by=["Market"], inplace=True) + return df + + async def miner_status_df(self) -> pd.DataFrame: + """ + Return the miner status (payouts, rewards, liquidity, etc.) in a DataFrame + """ + data = [] + g_sym = self._client_config_map.global_token.global_token_symbol + columns = ["Market", "Payout", "Reward/wk", "Liquidity", "Yield/yr", "Max spread"] + campaigns = await get_campaign_summary(self._exchange.display_name, list(self._market_infos.keys())) + for market, campaign in campaigns.items(): + reward = await RateOracle.get_instance().get_value( + amount=campaign.reward_per_wk, base_token=campaign.payout_asset + ) + data.append([ + market, + campaign.payout_asset, + f"{g_sym}{reward:.0f}", + f"{g_sym}{campaign.liquidity_usd:.0f}", + f"{campaign.apy:.2%}", + f"{campaign.spread_max:.2%}%" + ]) + df = pd.DataFrame(data=data, columns=columns).replace(np.nan, '', regex=True) + df.sort_values(by=["Market"], inplace=True) + return df + + async def format_status(self) -> str: + """ + Return the budget, market, miner and order statuses. + """ + if not self._ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(list(self._market_infos.values()))) + + budget_df = self.budget_status_df() + lines.extend(["", " Budget:"] + [" " + line for line in budget_df.to_string(index=False).split("\n")]) + + market_df = self.market_status_df() + lines.extend(["", " Markets:"] + [" " + line for line in market_df.to_string(index=False).split("\n")]) + + miner_df = await self.miner_status_df() + if not miner_df.empty: + lines.extend(["", " Miner:"] + [" " + line for line in miner_df.to_string(index=False).split("\n")]) + + # See if there are any open orders. + if len(self.active_orders) > 0: + df = await self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning(list(self._market_infos.values()))) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def start(self, clock: Clock, timestamp: float): + restored_orders = self._exchange.limit_orders + for order in restored_orders: + self._exchange.cancel(order.trading_pair, order.client_order_id) + + def stop(self, clock: Clock): + pass + + def _validate_order_book_for_markets(self): + """ + Verify that the active markets have non-empty order books. Put the trading on hold + for pairs with empty order books, in case it is a glitch? + """ + markets = self._market_infos.copy() + markets.update(self._empty_ob_market_infos) + for market, market_info in markets.items(): + if markets[market].get_price(False).is_nan(): + self._empty_ob_market_infos[market] = market_info + if market in self._market_infos: + self.logger().warning(f"{market} has an empty order book. Trading is paused") + self._market_infos.pop(market) + else: + self._market_infos[market] = market_info + if market in self._empty_ob_market_infos: + self.logger().warning(f"{market} is being reactivated") + self._empty_ob_market_infos.pop(market) + return len(self._market_infos) + + def create_base_proposals(self): + """ + Each tick this strategy creates a set of proposals based on the market_info and the parameters from the + constructor. + """ + proposals = [] + for market, market_info in self._market_infos.items(): + spread = self._spread + if not self._volatility[market].is_nan(): + # volatility applies only when it is higher than the spread setting. + spread = max(spread, self._volatility[market] * self._volatility_to_spread_multiplier) + if self._max_spread > s_decimal_zero: + spread = min(spread, self._max_spread) + mid_price = market_info.get_mid_price() + buy_price = mid_price * (Decimal("1") - spread) + buy_price = self._exchange.quantize_order_price(market, buy_price) + buy_size = self.base_order_size(market, buy_price) + sell_price = mid_price * (Decimal("1") + spread) + sell_price = self._exchange.quantize_order_price(market, sell_price) + sell_size = self.base_order_size(market, sell_price) + proposals.append(Proposal(market, PriceSize(buy_price, buy_size), PriceSize(sell_price, sell_size))) + return proposals + + def total_port_value_in_token(self) -> Decimal: + """ + Total portfolio value in self._token amount + """ + all_bals = self.adjusted_available_balances() + port_value = all_bals.get(self._token, s_decimal_zero) + for market, market_info in self._market_infos.items(): + base, quote = market.split("-") + if self.is_token_a_quote_token(): + port_value += all_bals[base] * market_info.get_mid_price() + else: + port_value += all_bals[quote] / market_info.get_mid_price() + return port_value + + def create_budget_allocation(self): + """ + Create buy and sell budgets for every market + """ + self._sell_budgets = {m: s_decimal_zero for m in self._market_infos} + self._buy_budgets = {m: s_decimal_zero for m in self._market_infos} + portfolio_value = self.total_port_value_in_token() + market_portion = portfolio_value / len(self._market_infos) + balances = self.adjusted_available_balances() + for market, market_info in self._market_infos.items(): + base, quote = market.split("-") + if self.is_token_a_quote_token(): + self._sell_budgets[market] = balances[base] + buy_budget = market_portion - (balances[base] * market_info.get_mid_price()) + if buy_budget > s_decimal_zero: + self._buy_budgets[market] = buy_budget + else: + self._buy_budgets[market] = balances[quote] + sell_budget = market_portion - (balances[quote] / market_info.get_mid_price()) + if sell_budget > s_decimal_zero: + self._sell_budgets[market] = sell_budget + + def base_order_size(self, trading_pair: str, price: Decimal = s_decimal_zero): + base, quote = trading_pair.split("-") + if self._token == base: + return self._order_amount + if price == s_decimal_zero: + price = self._market_infos[trading_pair].get_mid_price() + return self._order_amount / price + + def apply_budget_constraint(self, proposals: List[Proposal]): + balances = self._token_balances.copy() + for proposal in proposals: + if balances[proposal.base()] < proposal.sell.size: + proposal.sell.size = balances[proposal.base()] + proposal.sell.size = self._exchange.quantize_order_amount(proposal.market, proposal.sell.size) + balances[proposal.base()] -= proposal.sell.size + + quote_size = proposal.buy.size * proposal.buy.price + quote_size = balances[proposal.quote()] if balances[proposal.quote()] < quote_size else quote_size + buy_fee = build_trade_fee(self._exchange.name, True, proposal.base(), proposal.quote(), + OrderType.LIMIT, TradeType.BUY, proposal.buy.size, proposal.buy.price) + buy_size = quote_size / (proposal.buy.price * (Decimal("1") + buy_fee.percent)) + proposal.buy.size = self._exchange.quantize_order_amount(proposal.market, buy_size) + balances[proposal.quote()] -= quote_size + + def is_within_tolerance(self, cur_orders: List[LimitOrder], proposal: Proposal): + """ + False if there are no buys or sells or if the difference between the proposed price and current price is less + than the tolerance. The tolerance value is strict max, cannot be equal. + """ + cur_buy = [o for o in cur_orders if o.is_buy] + cur_sell = [o for o in cur_orders if not o.is_buy] + if (cur_buy and proposal.buy.size <= 0) or (cur_sell and proposal.sell.size <= 0): + return False + if cur_buy and \ + abs(proposal.buy.price - cur_buy[0].price) / cur_buy[0].price > self._order_refresh_tolerance_pct: + return False + if cur_sell and \ + abs(proposal.sell.price - cur_sell[0].price) / cur_sell[0].price > self._order_refresh_tolerance_pct: + return False + return True + + def cancel_active_orders(self, proposals: List[Proposal]): + """ + Cancel any orders that have an order age greater than self._max_order_age or if orders are not within tolerance + """ + for proposal in proposals: + to_cancel = False + cur_orders = [o for o in self.active_orders if o.trading_pair == proposal.market] + if cur_orders and any(order_age(o, self.current_timestamp) > self._max_order_age for o in cur_orders): + to_cancel = True + elif self._refresh_times[proposal.market] <= self.current_timestamp and \ + cur_orders and not self.is_within_tolerance(cur_orders, proposal): + to_cancel = True + if to_cancel: + for order in cur_orders: + self.cancel_order(self._market_infos[proposal.market], order.client_order_id) + # To place new order on the next tick + self._refresh_times[order.trading_pair] = self.current_timestamp + 0.1 + + def execute_orders_proposal(self, proposals: List[Proposal]): + """ + Execute a list of proposals if the current timestamp is less than its refresh timestamp. + Update the refresh timestamp. + """ + for proposal in proposals: + maker_order_type: OrderType = self._exchange.get_maker_order_type() + cur_orders = [o for o in self.active_orders if o.trading_pair == proposal.market] + if cur_orders or self._refresh_times[proposal.market] > self.current_timestamp: + continue + mid_price = self._market_infos[proposal.market].get_mid_price() + spread = s_decimal_zero + if proposal.buy.size > 0: + spread = abs(proposal.buy.price - mid_price) / mid_price + self.logger().info(f"({proposal.market}) Creating a bid order {proposal.buy} value: " + f"{proposal.buy.size * proposal.buy.price:.2f} {proposal.quote()} spread: " + f"{spread:.2%}") + self.buy_with_specific_market( + self._market_infos[proposal.market], + proposal.buy.size, + order_type=maker_order_type, + price=proposal.buy.price + ) + if proposal.sell.size > 0: + spread = abs(proposal.sell.price - mid_price) / mid_price + self.logger().info(f"({proposal.market}) Creating an ask order at {proposal.sell} value: " + f"{proposal.sell.size * proposal.sell.price:.2f} {proposal.quote()} spread: " + f"{spread:.2%}") + self.sell_with_specific_market( + self._market_infos[proposal.market], + proposal.sell.size, + order_type=maker_order_type, + price=proposal.sell.price + ) + if proposal.buy.size > 0 or proposal.sell.size > 0: + if not self._volatility[proposal.market].is_nan() and spread > self._spread: + adjusted_vol = self._volatility[proposal.market] * self._volatility_to_spread_multiplier + if adjusted_vol > self._spread: + self.logger().info(f"({proposal.market}) Spread is widened to {spread:.2%} due to high " + f"market volatility") + + self._refresh_times[proposal.market] = self.current_timestamp + self._order_refresh_time + + def is_token_a_quote_token(self): + """ + Check if self._token is a quote token + """ + quotes = self.all_quote_tokens() + if len(quotes) == 1 and self._token in quotes: + return True + return False + + def all_base_tokens(self) -> Set[str]: + """ + Get the base token (left-hand side) from all markets in this strategy + """ + tokens = set() + for market in self._market_infos: + tokens.add(market.split("-")[0]) + return tokens + + def all_quote_tokens(self) -> Set[str]: + """ + Get the quote token (right-hand side) from all markets in this strategy + """ + tokens = set() + for market in self._market_infos: + tokens.add(market.split("-")[1]) + return tokens + + def all_tokens(self) -> Set[str]: + """ + Return a list of all tokens involved in this strategy (base and quote) + """ + tokens = set() + for market in self._market_infos: + tokens.update(market.split("-")) + return tokens + + def adjusted_available_balances(self) -> Dict[str, Decimal]: + """ + Calculates all available balances, account for amount attributed to orders and reserved balance. + :return: a dictionary of token and its available balance + """ + tokens = self.all_tokens() + adjusted_bals = {t: s_decimal_zero for t in tokens} + total_bals = {t: s_decimal_zero for t in tokens} + total_bals.update(self._exchange.get_all_balances()) + for token in tokens: + adjusted_bals[token] = self._exchange.get_available_balance(token) + for order in self.active_orders: + base, quote = order.trading_pair.split("-") + if order.is_buy: + adjusted_bals[quote] += order.quantity * order.price + else: + adjusted_bals[base] += order.quantity + return adjusted_bals + + def apply_inventory_skew(self, proposals: List[Proposal]): + """ + Apply an inventory split between the quote and base asset + """ + for proposal in proposals: + buy_budget = self._buy_budgets[proposal.market] + sell_budget = self._sell_budgets[proposal.market] + mid_price = self._market_infos[proposal.market].get_mid_price() + total_order_size = proposal.sell.size + proposal.buy.size + bid_ask_ratios = calculate_bid_ask_ratios_from_base_asset_ratio( + float(sell_budget), + float(buy_budget), + float(mid_price), + float(self._target_base_pct), + float(total_order_size * self._inventory_range_multiplier) + ) + proposal.buy.size *= Decimal(bid_ask_ratios.bid_ratio) + proposal.sell.size *= Decimal(bid_ask_ratios.ask_ratio) + + def did_fill_order(self, event): + """ + Check if order has been completed, log it, notify the hummingbot application, and update budgets. + """ + order_id = event.order_id + market_info = self.order_tracker.get_shadow_market_pair_from_order_id(order_id) + if market_info is not None: + if event.trade_type is TradeType.BUY: + msg = f"({market_info.trading_pair}) Maker BUY order (price: {event.price}) of {event.amount} " \ + f"{market_info.base_asset} is filled." + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + self._buy_budgets[market_info.trading_pair] -= (event.amount * event.price) + self._sell_budgets[market_info.trading_pair] += event.amount + else: + msg = f"({market_info.trading_pair}) Maker SELL order (price: {event.price}) of {event.amount} " \ + f"{market_info.base_asset} is filled." + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + self._sell_budgets[market_info.trading_pair] -= event.amount + self._buy_budgets[market_info.trading_pair] += (event.amount * event.price) + + def update_mid_prices(self): + """ + Query asset markets for mid price + """ + for market in self._market_infos: + mid_price = self._market_infos[market].get_mid_price() + self._mid_prices[market].append(mid_price) + # To avoid memory leak, we store only the last part of the list needed for volatility calculation + max_len = self._volatility_interval * self._avg_volatility_period + self._mid_prices[market] = self._mid_prices[market][-1 * max_len:] + + def update_volatility(self): + """ + Update volatility data from the market + """ + self._volatility = {market: s_decimal_nan for market in self._market_infos} + for market, mid_prices in self._mid_prices.items(): + last_index = len(mid_prices) - 1 + atr = [] + first_index = last_index - (self._volatility_interval * self._avg_volatility_period) + first_index = max(first_index, 0) + for i in range(last_index, first_index, self._volatility_interval * -1): + prices = mid_prices[i - self._volatility_interval + 1: i + 1] + if not prices: + break + atr.append((max(prices) - min(prices)) / min(prices)) + if atr: + self._volatility[market] = mean(atr) + if self._last_vol_reported < self.current_timestamp - self._volatility_interval: + for market, vol in self._volatility.items(): + if not vol.is_nan(): + self.logger().info(f"{market} volatility: {vol:.2%}") + self._last_vol_reported = self.current_timestamp + + def notify_hb_app(self, msg: str): + """ + Send a message to the hummingbot application + """ + if self._hb_app_notification: + super().notify_hb_app(msg) diff --git a/hummingbot/strategy/liquidity_mining/liquidity_mining_config_map.py b/hummingbot/strategy/liquidity_mining/liquidity_mining_config_map.py new file mode 100644 index 0000000..95dfb85 --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/liquidity_mining_config_map.py @@ -0,0 +1,161 @@ +""" +The configuration parameters for a user made liquidity_mining strategy. +""" + +import re +from decimal import Decimal +from typing import Optional + +from hummingbot.client.config.config_validators import validate_bool, validate_decimal, validate_exchange, validate_int +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import required_exchanges + + +def exchange_on_validated(value: str) -> None: + required_exchanges.add(value) + + +def market_validate(value: str) -> Optional[str]: + pairs = list() + if len(value.strip()) == 0: + # Whitespace + return "Invalid market(s). The given entry is empty." + markets = list(value.upper().split(",")) + for market in markets: + if len(market.strip()) == 0: + return "Invalid markets. The given entry contains an empty market." + tokens = market.strip().split("-") + if len(tokens) != 2: + return f"Invalid market. {market} doesn't contain exactly 2 tickers." + for token in tokens: + # Check allowed ticker lengths + if len(token.strip()) == 0: + return f"Invalid market. Ticker {token} has an invalid length." + if(bool(re.search('^[a-zA-Z0-9]*$', token)) is False): + return f"Invalid market. Ticker {token} contains invalid characters." + # The pair is valid + pair = f"{tokens[0]}-{tokens[1]}" + if pair in pairs: + return f"Duplicate market {pair}." + pairs.append(pair) + + +def token_validate(value: str) -> Optional[str]: + value = value.upper() + markets = list(liquidity_mining_config_map["markets"].value.split(",")) + tokens = set() + for market in markets: + # Tokens in markets already validated in market_validate() + for token in market.strip().upper().split("-"): + tokens.add(token.strip()) + if value not in tokens: + return f"Invalid token. {value} is not one of {','.join(sorted(tokens))}" + + +def order_size_prompt() -> str: + token = liquidity_mining_config_map["token"].value + return f"What is the size of each order (in {token} amount)? >>> " + + +liquidity_mining_config_map = { + "strategy": ConfigVar( + key="strategy", + prompt="", + default="liquidity_mining"), + "exchange": + ConfigVar(key="exchange", + prompt="Enter the spot connector to use for liquidity mining >>> ", + validator=validate_exchange, + on_validated=exchange_on_validated, + prompt_on_new=True), + "markets": + ConfigVar(key="markets", + prompt="Enter a list of markets (comma separated, e.g. LTC-USDT,ETH-USDT) >>> ", + type_str="str", + validator=market_validate, + prompt_on_new=True), + "token": + ConfigVar(key="token", + prompt="What asset (base or quote) do you want to use to provide liquidity? >>> ", + type_str="str", + validator=token_validate, + prompt_on_new=True), + "order_amount": + ConfigVar(key="order_amount", + prompt=order_size_prompt, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True), + "spread": + ConfigVar(key="spread", + prompt="How far away from the mid price do you want to place bid and ask orders? " + "(Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "inventory_skew_enabled": + ConfigVar(key="inventory_skew_enabled", + prompt="Would you like to enable inventory skew? (Yes/No) >>> ", + type_str="bool", + default=True, + validator=validate_bool), + "target_base_pct": + ConfigVar(key="target_base_pct", + prompt="For each pair, what is your target base asset percentage? (Enter 20 to indicate 20%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "order_refresh_time": + ConfigVar(key="order_refresh_time", + prompt="How often do you want to cancel and replace bids and asks " + "(in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, 0, inclusive=False), + default=10.), + "order_refresh_tolerance_pct": + ConfigVar(key="order_refresh_tolerance_pct", + prompt="Enter the percent change in price needed to refresh orders at each cycle " + "(Enter 1 to indicate 1%) >>> ", + type_str="decimal", + default=Decimal("0.2"), + validator=lambda v: validate_decimal(v, -10, 10, inclusive=True)), + "inventory_range_multiplier": + ConfigVar(key="inventory_range_multiplier", + prompt="What is your tolerable range of inventory around the target, " + "expressed in multiples of your total order size? ", + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=Decimal("1")), + "volatility_interval": + ConfigVar(key="volatility_interval", + prompt="What is an interval, in second, in which to pick historical mid price data from to calculate " + "market volatility? >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=1, inclusive=False), + default=60 * 5), + "avg_volatility_period": + ConfigVar(key="avg_volatility_period", + prompt="How many interval does it take to calculate average market volatility? >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=1, inclusive=False), + default=10), + "volatility_to_spread_multiplier": + ConfigVar(key="volatility_to_spread_multiplier", + prompt="Enter a multiplier used to convert average volatility to spread " + "(enter 1 for 1 to 1 conversion) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=Decimal("1")), + "max_spread": + ConfigVar(key="max_spread", + prompt="What is the maximum spread? (Enter 1 to indicate 1% or -1 to ignore this setting) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v), + default=Decimal("-1")), + "max_order_age": + ConfigVar(key="max_order_age", + prompt="What is the maximum life time of your orders (in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=60. * 60.), +} diff --git a/hummingbot/strategy/liquidity_mining/start.py b/hummingbot/strategy/liquidity_mining/start.py new file mode 100644 index 0000000..3b33d39 --- /dev/null +++ b/hummingbot/strategy/liquidity_mining/start.py @@ -0,0 +1,54 @@ +from decimal import Decimal + +from hummingbot.strategy.liquidity_mining.liquidity_mining import LiquidityMiningStrategy +from hummingbot.strategy.liquidity_mining.liquidity_mining_config_map import liquidity_mining_config_map as c_map +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +def start(self): + exchange = c_map.get("exchange").value.lower() + el_markets = list(c_map.get("markets").value.split(",")) + token = c_map.get("token").value.upper() + el_markets = [m.strip().upper() for m in el_markets] + quote_markets = [m for m in el_markets if m.split("-")[1] == token] + base_markets = [m for m in el_markets if m.split("-")[0] == token] + markets = quote_markets if quote_markets else base_markets + order_amount = c_map.get("order_amount").value + spread = c_map.get("spread").value / Decimal("100") + inventory_skew_enabled = c_map.get("inventory_skew_enabled").value + target_base_pct = c_map.get("target_base_pct").value / Decimal("100") + order_refresh_time = c_map.get("order_refresh_time").value + order_refresh_tolerance_pct = c_map.get("order_refresh_tolerance_pct").value / Decimal("100") + inventory_range_multiplier = c_map.get("inventory_range_multiplier").value + volatility_interval = c_map.get("volatility_interval").value + avg_volatility_period = c_map.get("avg_volatility_period").value + volatility_to_spread_multiplier = c_map.get("volatility_to_spread_multiplier").value + max_spread = c_map.get("max_spread").value / Decimal("100") + max_order_age = c_map.get("max_order_age").value + + self._initialize_markets([(exchange, markets)]) + exchange = self.markets[exchange] + market_infos = {} + for market in markets: + base, quote = market.split("-") + market_infos[market] = MarketTradingPairTuple(exchange, market, base, quote) + self.strategy = LiquidityMiningStrategy() + self.strategy.init_params( + client_config_map=self.client_config_map, + exchange=exchange, + market_infos=market_infos, + token=token, + order_amount=order_amount, + spread=spread, + inventory_skew_enabled=inventory_skew_enabled, + target_base_pct=target_base_pct, + order_refresh_time=order_refresh_time, + order_refresh_tolerance_pct=order_refresh_tolerance_pct, + inventory_range_multiplier=inventory_range_multiplier, + volatility_interval=volatility_interval, + avg_volatility_period=avg_volatility_period, + volatility_to_spread_multiplier=volatility_to_spread_multiplier, + max_spread=max_spread, + max_order_age=max_order_age, + hb_app_notification=True + ) diff --git a/hummingbot/strategy/maker_taker_market_pair.py b/hummingbot/strategy/maker_taker_market_pair.py new file mode 100644 index 0000000..5f9cc13 --- /dev/null +++ b/hummingbot/strategy/maker_taker_market_pair.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +from typing import NamedTuple + +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class MakerTakerMarketPair(NamedTuple): + """ + Specifies a pair of maker and taker markets + + e.g. If I want to market make on DDEX WETH-DAI, and hedge on Binance ETHUSDT... then, + MakerTakerMarketPair(ddex, "WETH-DAI", "WETH", "DAI", + binance, "ETHUSDT", "ETH", "USDT") + """ + maker: MarketTradingPairTuple + taker: MarketTradingPairTuple diff --git a/hummingbot/strategy/market_trading_pair_tuple.py b/hummingbot/strategy/market_trading_pair_tuple.py new file mode 100644 index 0000000..7a80d51 --- /dev/null +++ b/hummingbot/strategy/market_trading_pair_tuple.py @@ -0,0 +1,52 @@ +from decimal import Decimal +from typing import ( + NamedTuple, Iterator +) +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_query_result import ClientOrderBookQueryResult +from hummingbot.core.data_type.order_book_row import ClientOrderBookRow +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import PriceType + + +class MarketTradingPairTuple(NamedTuple): + market: ExchangeBase + trading_pair: str + base_asset: str + quote_asset: str + + def __repr__(self) -> str: + return f"MarketTradingPairTuple({self.market.name}, {self.trading_pair}, {self.base_asset}, {self.quote_asset})" + + @property + def order_book(self) -> OrderBook: + return self.market.get_order_book(self.trading_pair) + + @property + def quote_balance(self) -> Decimal: + return self.market.get_balance(self.quote_asset) + + @property + def base_balance(self) -> Decimal: + return self.market.get_balance(self.base_asset) + + def get_mid_price(self) -> Decimal: + return self.market.get_mid_price(self.trading_pair) + + def get_price(self, is_buy: bool) -> Decimal: + return self.market.get_price(self.trading_pair, is_buy) + + def get_price_by_type(self, price_type: PriceType) -> Decimal: + return self.market.get_price_by_type(self.trading_pair, price_type) + + def get_vwap_for_volume(self, is_buy: bool, volume: Decimal) -> ClientOrderBookQueryResult: + return self.market.get_vwap_for_volume(self.trading_pair, is_buy, volume) + + def get_price_for_volume(self, is_buy: bool, volume: Decimal) -> ClientOrderBookQueryResult: + return self.market.get_price_for_volume(self.trading_pair, is_buy, volume) + + def order_book_bid_entries(self) -> Iterator[ClientOrderBookRow]: + return self.market.order_book_bid_entries(self.trading_pair) + + def order_book_ask_entries(self) -> Iterator[ClientOrderBookRow]: + return self.market.order_book_ask_entries(self.trading_pair) diff --git a/hummingbot/strategy/order_book_asset_price_delegate.pxd b/hummingbot/strategy/order_book_asset_price_delegate.pxd new file mode 100644 index 0000000..59e6347 --- /dev/null +++ b/hummingbot/strategy/order_book_asset_price_delegate.pxd @@ -0,0 +1,7 @@ +from hummingbot.connector.exchange_base cimport ExchangeBase +from .asset_price_delegate cimport AssetPriceDelegate + +cdef class OrderBookAssetPriceDelegate(AssetPriceDelegate): + cdef: + ExchangeBase _market + str _trading_pair diff --git a/hummingbot/strategy/order_book_asset_price_delegate.pyx b/hummingbot/strategy/order_book_asset_price_delegate.pyx new file mode 100644 index 0000000..af1071e --- /dev/null +++ b/hummingbot/strategy/order_book_asset_price_delegate.pyx @@ -0,0 +1,29 @@ +from hummingbot.core.data_type.common import PriceType +from hummingbot.connector.exchange_base import ExchangeBase +from decimal import Decimal +from .asset_price_delegate cimport AssetPriceDelegate + +cdef class OrderBookAssetPriceDelegate(AssetPriceDelegate): + def __init__(self, market: ExchangeBase, trading_pair: str): + super().__init__() + self._market = market + self._trading_pair = trading_pair + + cdef object c_get_mid_price(self): + return (self._market.c_get_price(self._trading_pair, True) + + self._market.c_get_price(self._trading_pair, False))/Decimal('2') + + @property + def ready(self) -> bool: + return self._market.ready + + def get_price_by_type(self, price_type: PriceType) -> Decimal: + return self._market.get_price_by_type(self._trading_pair, price_type) + + @property + def market(self) -> ExchangeBase: + return self._market + + @property + def trading_pair(self) -> str: + return self._trading_pair diff --git a/hummingbot/strategy/order_tracker.pxd b/hummingbot/strategy/order_tracker.pxd new file mode 100644 index 0000000..bf77f08 --- /dev/null +++ b/hummingbot/strategy/order_tracker.pxd @@ -0,0 +1,35 @@ +# distutils: language=c++ + +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.time_iterator cimport TimeIterator + + +cdef class OrderTracker(TimeIterator): + cdef: + dict _tracked_limit_orders + dict _tracked_market_orders + dict _order_id_to_market_pair + dict _shadow_tracked_limit_orders + dict _shadow_order_id_to_market_pair + object _shadow_gc_requests + object _in_flight_cancels + object _in_flight_pending_created + + cdef dict c_get_limit_orders(self) + cdef dict c_get_market_orders(self) + cdef dict c_get_shadow_limit_orders(self) + cdef bint c_has_in_flight_cancel(self, str order_id) + cdef bint c_check_and_track_cancel(self, str order_id) + cdef object c_get_market_pair_from_order_id(self, str order_id) + cdef object c_get_shadow_market_pair_from_order_id(self, str order_id) + cdef LimitOrder c_get_limit_order(self, object market_pair, str order_id) + cdef object c_get_market_order(self, object market_pair, str order_id) + cdef LimitOrder c_get_shadow_limit_order(self, str order_id) + cdef c_start_tracking_limit_order(self, object market_pair, str order_id, bint is_buy, object price, + object quantity) + cdef c_stop_tracking_limit_order(self, object market_pair, str order_id) + cdef c_start_tracking_market_order(self, object market_pair, str order_id, bint is_buy, object quantity) + cdef c_stop_tracking_market_order(self, object market_pair, str order_id) + cdef c_check_and_cleanup_shadow_records(self) + cdef c_add_create_order_pending(self, str order_id) + cdef c_remove_create_order_pending(self, str order_id) diff --git a/hummingbot/strategy/order_tracker.pyx b/hummingbot/strategy/order_tracker.pyx new file mode 100644 index 0000000..b0be6ac --- /dev/null +++ b/hummingbot/strategy/order_tracker.pyx @@ -0,0 +1,314 @@ +from collections import ( + deque, + OrderedDict +) +from decimal import Decimal +from typing import ( + Dict, + List, + Tuple +) + +import pandas as pd + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +NaN = float("nan") + +cdef class OrderTracker(TimeIterator): + # ETH confirmation requirement of Binance has shortened to 12 blocks as of 7/15/2019. + # 12 * 15 / 60 = 3 minutes + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 3 + + CANCEL_EXPIRY_DURATION = 60.0 + + def __init__(self): + super().__init__() + self._tracked_limit_orders = {} + self._tracked_market_orders = {} + self._order_id_to_market_pair = {} + self._shadow_tracked_limit_orders = {} + self._shadow_order_id_to_market_pair = {} + self._shadow_gc_requests = deque() + self._in_flight_pending_created = set() + self._in_flight_cancels = OrderedDict() + + @property + def active_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._tracked_limit_orders.items(): + for limit_order in orders_map.values(): + if self.c_has_in_flight_cancel(limit_order.client_order_id): + continue + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def shadow_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._shadow_tracked_limit_orders.items(): + for limit_order in orders_map.values(): + if self.c_has_in_flight_cancel(limit_order.client_order_id): + continue + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def market_pair_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + market_pair_to_orders = {} + market_pairs = self._tracked_limit_orders.keys() + for market_pair in market_pairs: + limit_orders = [] + for limit_order in self._tracked_limit_orders[market_pair].values(): + if self.c_has_in_flight_cancel(limit_order.client_order_id): + continue + limit_orders.append(limit_order) + market_pair_to_orders[market_pair] = limit_orders + return market_pair_to_orders + + @property + def active_bids(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return [(market, limit_order) for market, limit_order in self.active_limit_orders if limit_order.is_buy] + + @property + def active_asks(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return [(market, limit_order) for market, limit_order in self.active_limit_orders if not limit_order.is_buy] + + @property + def tracked_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return [(market_trading_pair_tuple[0], order) for market_trading_pair_tuple, order_map in + self._tracked_limit_orders.items() + for order in order_map.values()] + + @property + def tracked_limit_orders_map(self) -> Dict[ConnectorBase, Dict[str, LimitOrder]]: + return self._tracked_limit_orders + + @property + def tracked_limit_orders_data_frame(self) -> pd.DataFrame: + limit_orders = [ + [market_trading_pair_tuple.market.display_name, market_trading_pair_tuple.trading_pair, order_id, + order.quantity, + pd.Timestamp(order.creation_timestamp / 1e6, unit='s', tz='UTC').strftime('%Y-%m-%d %H:%M:%S') + ] + for market_trading_pair_tuple, order_map in self._tracked_limit_orders.items() + for order_id, order in order_map.items()] + + return pd.DataFrame(data=limit_orders, columns=["market", "trading_pair", "order_id", "quantity", "timestamp"]) + + @property + def tracked_market_orders(self) -> List[Tuple[ConnectorBase, MarketOrder]]: + return [(market_trading_pair_tuple[0], order) for market_trading_pair_tuple, order_map + in self._tracked_market_orders.items() for order in order_map.values()] + + @property + def tracked_market_orders_data_frame(self) -> List[pd.DataFrame]: + market_orders = [[market_trading_pair_tuple.market.display_name, market_trading_pair_tuple.trading_pair, + order_id, order.amount, + pd.Timestamp(order.timestamp, unit='s', tz='UTC').strftime('%Y-%m-%d %H:%M:%S')] + for market_trading_pair_tuple, order_map in self._tracked_market_orders.items() + for order_id, order in order_map.items()] + + return pd.DataFrame(data=market_orders, columns=["market", "trading_pair", "order_id", "quantity", "timestamp"]) + + @property + def in_flight_cancels(self) -> Dict[str, float]: + return self._in_flight_cancels + + @property + def in_flight_pending_created(self) -> Dict[str, float]: + return self._in_flight_pending_created + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self.c_check_and_cleanup_shadow_records() + + cdef dict c_get_limit_orders(self): + return self._tracked_limit_orders + + def get_limit_orders(self): + return self.c_get_limit_orders() + + cdef dict c_get_market_orders(self): + return self._tracked_market_orders + + def get_market_orders(self): + return self.c_get_market_orders() + + cdef dict c_get_shadow_limit_orders(self): + return self._shadow_tracked_limit_orders + + def get_shadow_limit_orders(self) -> Dict[MarketTradingPairTuple, Dict[str, LimitOrder]]: + return self.c_get_shadow_limit_orders() + + cdef bint c_has_in_flight_cancel(self, str order_id): + return self._in_flight_cancels.get(order_id, NaN) + self.CANCEL_EXPIRY_DURATION > self._current_timestamp + + def has_in_flight_cancel(self, order_id: str): + return self.c_has_in_flight_cancel(order_id) + + cdef bint c_check_and_track_cancel(self, str order_id): + """ + :param order_id: the order id to be canceled + :return: True if there's no existing in flight cancel for the order id, False otherwise. + """ + cdef: + list keys_to_delete = [] + + if order_id in self._in_flight_pending_created: # Checks if a Buy/SellOrderCreatedEvent has been received + return False + + # Maintain the cancel expiry time invariant. + for k, cancel_timestamp in self._in_flight_cancels.items(): + if cancel_timestamp < self._current_timestamp - self.CANCEL_EXPIRY_DURATION: + keys_to_delete.append(k) + for k in keys_to_delete: + del self._in_flight_cancels[k] + + if order_id in self.in_flight_cancels: + return False + + # Track the cancel. + self._in_flight_cancels[order_id] = self._current_timestamp + return True + + def check_and_track_cancel(self, order_id: str) -> bool: + return self.c_check_and_track_cancel(order_id) + + cdef object c_get_market_pair_from_order_id(self, str order_id): + return self._order_id_to_market_pair.get(order_id) + + def get_market_pair_from_order_id(self, order_id: str): + return self.c_get_market_pair_from_order_id(order_id) + + cdef object c_get_shadow_market_pair_from_order_id(self, str order_id): + return self._shadow_order_id_to_market_pair.get(order_id) + + def get_shadow_market_pair_from_order_id(self, order_id: str) -> MarketTradingPairTuple: + return self.c_get_shadow_market_pair_from_order_id(order_id) + + cdef LimitOrder c_get_limit_order(self, object market_pair, str order_id): + return self._tracked_limit_orders.get(market_pair, {}).get(order_id) + + def get_limit_order(self, market_pair, order_id: str) -> LimitOrder: + return self.c_get_limit_order(market_pair, order_id) + + cdef object c_get_market_order(self, object market_pair, str order_id): + return self._tracked_market_orders.get(market_pair, {}).get(order_id) + + def get_market_order(self, market_pair, order_id: str) -> MarketOrder: + return self.c_get_market_order(market_pair, order_id) + + cdef LimitOrder c_get_shadow_limit_order(self, str order_id): + cdef: + object market_pair = self._shadow_order_id_to_market_pair.get(order_id) + + return self._shadow_tracked_limit_orders.get(market_pair, {}).get(order_id) + + def get_shadow_limit_order(self, order_id: str) -> LimitOrder: + return self.c_get_shadow_limit_order(order_id) + + cdef c_start_tracking_limit_order(self, object market_pair, str order_id, bint is_buy, object price, + object quantity): + if market_pair not in self._tracked_limit_orders: + self._tracked_limit_orders[market_pair] = {} + if market_pair not in self._shadow_tracked_limit_orders: + self._shadow_tracked_limit_orders[market_pair] = {} + + cdef: + LimitOrder limit_order = LimitOrder(order_id, + market_pair.trading_pair, + is_buy, + market_pair.base_asset, + market_pair.quote_asset, + price, + quantity, + creation_timestamp=int(self._current_timestamp * 1e6)) + self._tracked_limit_orders[market_pair][order_id] = limit_order + self._shadow_tracked_limit_orders[market_pair][order_id] = limit_order + self._order_id_to_market_pair[order_id] = market_pair + self._shadow_order_id_to_market_pair[order_id] = market_pair + + def start_tracking_limit_order(self, market_pair: MarketTradingPairTuple, order_id: str, is_buy: bool, price: Decimal, + quantity: Decimal): + return self.c_start_tracking_limit_order(market_pair, order_id, is_buy, price, quantity) + + cdef c_stop_tracking_limit_order(self, object market_pair, str order_id): + if market_pair in self._tracked_limit_orders and order_id in self._tracked_limit_orders[market_pair]: + del self._tracked_limit_orders[market_pair][order_id] + if len(self._tracked_limit_orders[market_pair]) < 1: + del self._tracked_limit_orders[market_pair] + self._shadow_gc_requests.append(( + self._current_timestamp + self.SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION, + market_pair, + order_id + )) + + if order_id in self._order_id_to_market_pair: + del self._order_id_to_market_pair[order_id] + if order_id in self._in_flight_cancels: + del self._in_flight_cancels[order_id] + + def stop_tracking_limit_order(self, market_pair: MarketTradingPairTuple, order_id: str): + return self.c_stop_tracking_limit_order(market_pair, order_id) + + cdef c_start_tracking_market_order(self, object market_pair, str order_id, bint is_buy, object quantity): + if market_pair not in self._tracked_market_orders: + self._tracked_market_orders[market_pair] = {} + self._tracked_market_orders[market_pair][order_id] = MarketOrder( + order_id, + market_pair.trading_pair, + is_buy, + market_pair.base_asset, + market_pair.quote_asset, + float(quantity), + self._current_timestamp + ) + self._order_id_to_market_pair[order_id] = market_pair + + def start_tracking_market_order(self, market_pair: MarketTradingPairTuple, order_id: str, is_buy: bool, quantity: Decimal): + return self.c_start_tracking_market_order(market_pair, order_id, is_buy, quantity) + + cdef c_stop_tracking_market_order(self, object market_pair, str order_id): + if market_pair in self._tracked_market_orders and order_id in self._tracked_market_orders[market_pair]: + del self._tracked_market_orders[market_pair][order_id] + if len(self._tracked_market_orders[market_pair]) < 1: + del self._tracked_market_orders[market_pair] + if order_id in self._order_id_to_market_pair: + del self._order_id_to_market_pair[order_id] + + def stop_tracking_market_order(self, market_pair: MarketTradingPairTuple, order_id: str): + return self.c_stop_tracking_market_order(market_pair, order_id) + + cdef c_check_and_cleanup_shadow_records(self): + cdef: + double current_timestamp = self._current_timestamp + + while len(self._shadow_gc_requests) > 0 and self._shadow_gc_requests[0][0] < current_timestamp: + _, market_pair, order_id = self._shadow_gc_requests.popleft() + if (market_pair in self._shadow_tracked_limit_orders and + order_id in self._shadow_tracked_limit_orders[market_pair]): + del self._shadow_tracked_limit_orders[market_pair][order_id] + if len(self._shadow_tracked_limit_orders[market_pair]) < 1: + del self._shadow_tracked_limit_orders[market_pair] + if order_id in self._shadow_order_id_to_market_pair: + del self._shadow_order_id_to_market_pair[order_id] + + def check_and_cleanup_shadow_records(self): + self.c_check_and_cleanup_shadow_records() + + cdef c_add_create_order_pending(self, str order_id): + self.in_flight_pending_created.add(order_id) + + def add_create_order_pending(self, order_id: str): + self.c_add_create_order_pending(order_id) + + cdef c_remove_create_order_pending(self, str order_id): + self._in_flight_pending_created.discard(order_id) + + def remove_create_order_pending(self, order_id: str): + self.c_remove_create_order_pending(order_id) diff --git a/hummingbot/strategy/perpetual_market_making/__init__.py b/hummingbot/strategy/perpetual_market_making/__init__.py new file mode 100644 index 0000000..4458646 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from .perpetual_market_making import PerpetualMarketMakingStrategy +__all__ = [ + PerpetualMarketMakingStrategy, +] diff --git a/hummingbot/strategy/perpetual_market_making/data_types.py b/hummingbot/strategy/perpetual_market_making/data_types.py new file mode 100644 index 0000000..aa2cdbe --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/data_types.py @@ -0,0 +1,55 @@ +from decimal import Decimal +from typing import ( + List, + NamedTuple, +) + +from hummingbot.core.data_type.common import OrderType + +ORDER_PROPOSAL_ACTION_CREATE_ORDERS = 1 +ORDER_PROPOSAL_ACTION_CANCEL_ORDERS = 1 << 1 + + +class OrdersProposal(NamedTuple): + actions: int + buy_order_type: OrderType + buy_order_prices: List[Decimal] + buy_order_sizes: List[Decimal] + sell_order_type: OrderType + sell_order_prices: List[Decimal] + sell_order_sizes: List[Decimal] + cancel_order_ids: List[str] + + +class PricingProposal(NamedTuple): + buy_order_prices: List[Decimal] + sell_order_prices: List[Decimal] + + +class SizingProposal(NamedTuple): + buy_order_sizes: List[Decimal] + sell_order_sizes: List[Decimal] + + +class InventorySkewBidAskRatios(NamedTuple): + bid_ratio: float + ask_ratio: float + + +class PriceSize: + def __init__(self, price: Decimal, size: Decimal): + self.price: Decimal = price + self.size: Decimal = size + + def __repr__(self): + return f"[ p: {self.price} s: {self.size} ]" + + +class Proposal: + def __init__(self, buys: List[PriceSize], sells: List[PriceSize]): + self.buys: List[PriceSize] = buys + self.sells: List[PriceSize] = sells + + def __repr__(self): + return f"{len(self.buys)} buys: {', '.join([str(o) for o in self.buys])} " \ + f"{len(self.sells)} sells: {', '.join([str(o) for o in self.sells])}" diff --git a/hummingbot/strategy/perpetual_market_making/dummy.pxd b/hummingbot/strategy/perpetual_market_making/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/perpetual_market_making/dummy.pyx b/hummingbot/strategy/perpetual_market_making/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/perpetual_market_making/perpetual_market_making.py b/hummingbot/strategy/perpetual_market_making/perpetual_market_making.py new file mode 100644 index 0000000..bf260c3 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/perpetual_market_making.py @@ -0,0 +1,1027 @@ +import logging +from decimal import Decimal +from itertools import chain +from math import ceil, floor +from typing import Dict, List + +import numpy as np +import pandas as pd + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.derivative_base import DerivativeBase +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PriceType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_candidate import PerpetualOrderCandidate +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + OrderFilledEvent, + PositionModeChangeEvent, + SellOrderCompletedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.asset_price_delegate import AssetPriceDelegate +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.perpetual_market_making.data_types import PriceSize, Proposal +from hummingbot.strategy.perpetual_market_making.perpetual_market_making_order_tracker import ( + PerpetualMarketMakingOrderTracker, +) +from hummingbot.strategy.strategy_py_base import StrategyPyBase +from hummingbot.strategy.utils import order_age + +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_neg_one = Decimal(-1) + + +class PerpetualMarketMakingStrategy(StrategyPyBase): + OPTION_LOG_CREATE_ORDER = 1 << 3 + OPTION_LOG_MAKER_ORDER_FILLED = 1 << 4 + OPTION_LOG_STATUS_REPORT = 1 << 5 + OPTION_LOG_ALL = 0x7fffffffffffffff + _logger = None + + @classmethod + def logger(cls): + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + def init_params(self, + market_info: MarketTradingPairTuple, + leverage: int, + position_mode: str, + bid_spread: Decimal, + ask_spread: Decimal, + order_amount: Decimal, + long_profit_taking_spread: Decimal, + short_profit_taking_spread: Decimal, + stop_loss_spread: Decimal, + time_between_stop_loss_orders: float, + stop_loss_slippage_buffer: Decimal, + order_levels: int = 1, + order_level_spread: Decimal = s_decimal_zero, + order_level_amount: Decimal = s_decimal_zero, + order_refresh_time: float = 30.0, + order_refresh_tolerance_pct: Decimal = s_decimal_neg_one, + filled_order_delay: float = 60.0, + order_optimization_enabled: bool = False, + ask_order_optimization_depth: Decimal = s_decimal_zero, + bid_order_optimization_depth: Decimal = s_decimal_zero, + asset_price_delegate: AssetPriceDelegate = None, + price_type: str = "mid_price", + price_ceiling: Decimal = s_decimal_neg_one, + price_floor: Decimal = s_decimal_neg_one, + logging_options: int = OPTION_LOG_ALL, + status_report_interval: float = 900, + minimum_spread: Decimal = Decimal(0), + hb_app_notification: bool = False, + order_override: Dict[str, List[str]] = {}, + ): + + if price_ceiling != s_decimal_neg_one and price_ceiling < price_floor: + raise ValueError("Parameter price_ceiling cannot be lower than price_floor.") + + self._sb_order_tracker = PerpetualMarketMakingOrderTracker() + self._market_info = market_info + self._leverage = leverage + self._position_mode = PositionMode.HEDGE if position_mode == "Hedge" else PositionMode.ONEWAY + self._bid_spread = bid_spread + self._ask_spread = ask_spread + self._minimum_spread = minimum_spread + self._order_amount = order_amount + self._long_profit_taking_spread = long_profit_taking_spread + self._short_profit_taking_spread = short_profit_taking_spread + self._stop_loss_spread = stop_loss_spread + self._order_levels = order_levels + self._buy_levels = order_levels + self._sell_levels = order_levels + self._order_level_spread = order_level_spread + self._order_level_amount = order_level_amount + self._order_refresh_time = order_refresh_time + self._order_refresh_tolerance_pct = order_refresh_tolerance_pct + self._filled_order_delay = filled_order_delay + self._order_optimization_enabled = order_optimization_enabled + self._ask_order_optimization_depth = ask_order_optimization_depth + self._bid_order_optimization_depth = bid_order_optimization_depth + self._asset_price_delegate = asset_price_delegate + self._price_type = self.get_price_type(price_type) + self._price_ceiling = price_ceiling + self._price_floor = price_floor + self._hb_app_notification = hb_app_notification + self._order_override = order_override + + self._cancel_timestamp = 0 + self._create_timestamp = 0 + self._all_markets_ready = False + self._logging_options = logging_options + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._last_own_trade_price = Decimal('nan') + self._ts_peak_bid_price = Decimal('0') + self._ts_peak_ask_price = Decimal('0') + self._exit_orders = dict() + self._next_buy_exit_order_timestamp = 0 + self._next_sell_exit_order_timestamp = 0 + + self.add_markets([market_info.market]) + + self._close_order_type = OrderType.LIMIT + self._time_between_stop_loss_orders = time_between_stop_loss_orders + self._stop_loss_slippage_buffer = stop_loss_slippage_buffer + + self._position_mode_ready = False + self._position_mode_not_ready_counter = 0 + + def all_markets_ready(self): + return all([market.ready for market in self.active_markets]) + + @property + def order_refresh_tolerance_pct(self) -> Decimal: + return self._order_refresh_tolerance_pct + + @order_refresh_tolerance_pct.setter + def order_refresh_tolerance_pct(self, value: Decimal): + self._order_refresh_tolerance_pct = value + + @property + def order_amount(self) -> Decimal: + return self._order_amount + + @order_amount.setter + def order_amount(self, value: Decimal): + self._order_amount = value + + @property + def order_levels(self) -> int: + return self._order_levels + + @order_levels.setter + def order_levels(self, value: int): + self._order_levels = value + self._buy_levels = value + self._sell_levels = value + + @property + def buy_levels(self) -> int: + return self._buy_levels + + @buy_levels.setter + def buy_levels(self, value: int): + self._buy_levels = value + + @property + def sell_levels(self) -> int: + return self._sell_levels + + @sell_levels.setter + def sell_levels(self, value: int): + self._sell_levels = value + + @property + def order_level_amount(self) -> Decimal: + return self._order_level_amount + + @order_level_amount.setter + def order_level_amount(self, value: Decimal): + self._order_level_amount = value + + @property + def order_level_spread(self) -> Decimal: + return self._order_level_spread + + @order_level_spread.setter + def order_level_spread(self, value: Decimal): + self._order_level_spread = value + + @property + def bid_spread(self) -> Decimal: + return self._bid_spread + + @bid_spread.setter + def bid_spread(self, value: Decimal): + self._bid_spread = value + + @property + def ask_spread(self) -> Decimal: + return self._ask_spread + + @ask_spread.setter + def ask_spread(self, value: Decimal): + self._ask_spread = value + + @property + def order_optimization_enabled(self) -> bool: + return self._order_optimization_enabled + + @order_optimization_enabled.setter + def order_optimization_enabled(self, value: bool): + self._order_optimization_enabled = value + + @property + def order_refresh_time(self) -> float: + return self._order_refresh_time + + @order_refresh_time.setter + def order_refresh_time(self, value: float): + self._order_refresh_time = value + + @property + def filled_order_delay(self) -> float: + return self._filled_order_delay + + @filled_order_delay.setter + def filled_order_delay(self, value: float): + self._filled_order_delay = value + + @property + def price_ceiling(self) -> Decimal: + return self._price_ceiling + + @price_ceiling.setter + def price_ceiling(self, value: Decimal): + self._price_ceiling = value + + @property + def price_floor(self) -> Decimal: + return self._price_floor + + @price_floor.setter + def price_floor(self, value: Decimal): + self._price_floor = value + + @property + def base_asset(self): + return self._market_info.base_asset + + @property + def quote_asset(self): + return self._market_info.quote_asset + + @property + def trading_pair(self): + return self._market_info.trading_pair + + def get_price(self) -> float: + if self._asset_price_delegate is not None: + price_provider = self._asset_price_delegate + else: + price_provider = self._market_info + if self._price_type is PriceType.LastOwnTrade: + price = self._last_own_trade_price + else: + price = price_provider.get_price_by_type(self._price_type) + if price.is_nan(): + price = price_provider.get_price_by_type(PriceType.MidPrice) + return price + + def get_last_price(self) -> float: + return self._market_info.get_last_price() + + def get_mid_price(self) -> Decimal: + delegate: AssetPriceDelegate = self._asset_price_delegate + if delegate is not None: + mid_price = delegate.get_mid_price() + else: + mid_price = self._market_info.get_mid_price() + return mid_price + + @property + def active_orders(self) -> List[LimitOrder]: + if self._market_info not in self._sb_order_tracker.market_pair_to_active_orders: + return [] + return self._sb_order_tracker.market_pair_to_active_orders[self._market_info] + + @property + def active_positions(self) -> Dict[str, Position]: + return self._market_info.market.account_positions + + @property + def active_buys(self) -> List[LimitOrder]: + return [o for o in self.active_orders if o.is_buy] + + @property + def active_sells(self) -> List[LimitOrder]: + return [o for o in self.active_orders if not o.is_buy] + + @property + def logging_options(self) -> int: + return self._logging_options + + @logging_options.setter + def logging_options(self, logging_options: int): + self._logging_options = logging_options + + @property + def asset_price_delegate(self) -> AssetPriceDelegate: + return self._asset_price_delegate + + @asset_price_delegate.setter + def asset_price_delegate(self, value): + self._asset_price_delegate = value + + def perpetual_mm_assets_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + quote_balance = float(market.get_balance(quote_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + data = [ + ["", quote_asset], + ["Total Balance", round(quote_balance, 4)], + ["Available Balance", round(available_quote_balance, 4)] + ] + df = pd.DataFrame(data=data) + return df + + def active_orders_df(self) -> pd.DataFrame: + price = self.get_price() + active_orders = self.active_orders + no_sells = len([o for o in active_orders if not o.is_buy]) + active_orders.sort(key=lambda x: x.price, reverse=True) + columns = ["Level", "Type", "Price", "Spread", "Amount (Orig)", "Amount (Adj)", "Age"] + data = [] + lvl_buy, lvl_sell = 0, 0 + for idx in range(0, len(active_orders)): + order = active_orders[idx] + level = None + if order.is_buy: + level = lvl_buy + 1 + lvl_buy += 1 + else: + level = no_sells - lvl_sell + lvl_sell += 1 + spread = 0 if price == 0 else abs(order.price - price) / price + age = pd.Timestamp(order_age(order, self.current_timestamp), unit='s').strftime('%H:%M:%S') + + amount_orig = "" if level is None else self._order_amount + ((level - 1) * self._order_level_amount) + data.append([ + level, + "buy" if order.is_buy else "sell", + float(order.price), + f"{spread:.2%}", + amount_orig, + float(order.quantity), + age + ]) + + return pd.DataFrame(data=data, columns=columns) + + def active_positions_df(self) -> pd.DataFrame: + columns = ["Symbol", "Type", "Entry Price", "Amount", "Leverage", "Unrealized PnL"] + data = [] + market, trading_pair = self._market_info.market, self._market_info.trading_pair + for idx in self.active_positions.values(): + is_buy = True if idx.amount > 0 else False + unrealized_profit = ((market.get_price(trading_pair, is_buy) - idx.entry_price) * idx.amount) + data.append([ + idx.trading_pair, + idx.position_side.name, + idx.entry_price, + idx.amount, + idx.leverage, + unrealized_profit + ]) + + return pd.DataFrame(data=data, columns=columns) + + def market_status_data_frame(self) -> pd.DataFrame: + markets_data = [] + markets_columns = ["Exchange", "Market", "Best Bid", "Best Ask", f"Ref Price ({self._price_type.name})"] + if self._price_type is PriceType.LastOwnTrade and self._last_own_trade_price.is_nan(): + markets_columns[-1] = "Ref Price (MidPrice)" + market_books = [(self._market_info.market, self._market_info.trading_pair)] + if type(self._asset_price_delegate) is OrderBookAssetPriceDelegate: + market_books.append((self._asset_price_delegate.market, self._asset_price_delegate.trading_pair)) + for market, trading_pair in market_books: + bid_price = market.get_price(trading_pair, False) + ask_price = market.get_price(trading_pair, True) + ref_price = float("nan") + if market == self._market_info.market and self._asset_price_delegate is None: + ref_price = self.get_price() + elif market == self._asset_price_delegate.market and self._price_type is not PriceType.LastOwnTrade: + ref_price = self._asset_price_delegate.get_price_by_type(self._price_type) + markets_data.append([ + market.display_name, + trading_pair, + float(bid_price), + float(ask_price), + float(ref_price) + ]) + return pd.DataFrame(data=markets_data, columns=markets_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + if not self._all_markets_ready: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + + markets_df = self.market_status_data_frame() + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.perpetual_mm_assets_df()) + + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + # See if there're any open orders. + if len(self.active_orders) > 0: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active maker orders."]) + + # See if there're any active positions. + if len(self.active_positions) > 0: + df = self.active_positions_df() + lines.extend(["", " Positions:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active positions."]) + + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def start(self, clock: Clock, timestamp: float): + self._market_info.market.set_leverage(self.trading_pair, self._leverage) + + def tick(self, timestamp: float): + if not self._position_mode_ready: + self._position_mode_not_ready_counter += 1 + # Attempt to switch position mode every 10 ticks only to not spam and DDOS + if self._position_mode_not_ready_counter == 10: + market: DerivativeBase = self._market_info.market + if market.ready: + market.set_leverage(self.trading_pair, self._leverage) + market.set_position_mode(self._position_mode) + self._position_mode_not_ready_counter = 0 + return + self._position_mode_not_ready_counter = 0 + market: DerivativeBase = self._market_info.market + session_positions = [s for s in self.active_positions.values() if s.trading_pair == self.trading_pair] + current_tick = timestamp // self._status_report_interval + last_tick = self._last_timestamp // self._status_report_interval + should_report_warnings = ((current_tick > last_tick) and + (self._logging_options & self.OPTION_LOG_STATUS_REPORT)) + try: + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self.active_markets]) + if self._asset_price_delegate is not None and self._all_markets_ready: + self._all_markets_ready = self._asset_price_delegate.ready + if not self._all_markets_ready: + # M({self.trading_pair}) Maker sell order {order_id}arkets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning("Markets are not ready. No market making trades are permitted.") + return + + if should_report_warnings: + if not all([market.network_status is NetworkStatus.CONNECTED for market in self.active_markets]): + self.logger().warning("WARNING: Some markets are not connected or are down at the moment. Market " + "making may be dangerous when markets or networks are unstable.") + + if len(session_positions) == 0: + self._exit_orders = dict() # Empty list of exit order at this point to reduce size + proposal = None + if self._create_timestamp <= self.current_timestamp: + # 1. Create base order proposals + proposal = self.create_base_proposal() + self.logger().debug(f"Initial proposals: {proposal}") + # 2. Apply functions that limit numbers of buys and sells proposal + self.apply_order_levels_modifiers(proposal) + self.logger().debug(f"Proposals after order level modifier: {proposal}") + # 3. Apply functions that modify orders price + self.apply_order_price_modifiers(proposal) + self.logger().debug(f"Proposals after order price modifiers: {proposal}") + # 4. Apply budget constraint, i.e. can't buy/sell more than what you have. + self.apply_budget_constraint(proposal) + self.logger().debug(f"Proposals after budget constraints: {proposal}") + + self.filter_out_takers(proposal) + self.logger().debug(f"Proposals after takers filter: {proposal}") + + self.cancel_active_orders(proposal) + self.cancel_orders_below_min_spread() + if self.to_create_orders(proposal): + self.execute_orders_proposal(proposal, PositionAction.OPEN) + # Reset peak ask and bid prices + self._ts_peak_ask_price = market.get_price(self.trading_pair, False) + self._ts_peak_bid_price = market.get_price(self.trading_pair, True) + else: + self.manage_positions(session_positions) + finally: + self._last_timestamp = timestamp + + def manage_positions(self, session_positions: List[Position]): + mode = self._position_mode + + proposals = self.profit_taking_proposal(mode, session_positions) + if proposals is not None: + self.execute_orders_proposal(proposals, PositionAction.CLOSE) + + # check if stop loss needs to be placed + proposals = self.stop_loss_proposal(mode, session_positions) + if proposals is not None: + self.execute_orders_proposal(proposals, PositionAction.CLOSE) + + def profit_taking_proposal(self, mode: PositionMode, active_positions: List) -> Proposal: + + market: DerivativeBase = self._market_info.market + unwanted_exit_orders = [o for o in self.active_orders + if o.client_order_id not in self._exit_orders.keys()] + ask_price = market.get_price(self.trading_pair, True) + bid_price = market.get_price(self.trading_pair, False) + buys = [] + sells = [] + + if mode == PositionMode.ONEWAY: + # in one-way mode, only one active position is expected per time + if len(active_positions) > 1: + self.logger().error(f"More than one open position in {mode.name} position mode. " + "Kindly ensure you do not interact with the exchange through " + "other platforms and restart this strategy.") + else: + # Cancel open order that could potentially close position before reaching take_profit_limit + for order in unwanted_exit_orders: + if ((active_positions[0].amount < 0 and order.is_buy) + or (active_positions[0].amount > 0 and not order.is_buy)): + self.cancel_order(self._market_info, order.client_order_id) + self.logger().info(f"Initiated cancelation of {'buy' if order.is_buy else 'sell'} order " + f"{order.client_order_id} in favour of take profit order.") + + for position in active_positions: + if (ask_price > position.entry_price and position.amount > 0) or ( + bid_price < position.entry_price and position.amount < 0): + # check if there is an active order to take profit, and create if none exists + profit_spread = self._long_profit_taking_spread if position.amount > 0 else self._short_profit_taking_spread + take_profit_price = position.entry_price * (Decimal("1") + profit_spread) if position.amount > 0 \ + else position.entry_price * (Decimal("1") - profit_spread) + price = market.quantize_order_price(self.trading_pair, take_profit_price) + size = market.quantize_order_amount(self.trading_pair, abs(position.amount)) + old_exit_orders = [ + o for o in self.active_orders + if ((o.price != price or o.quantity != size) + and o.client_order_id in self._exit_orders.keys() + and ((position.amount < 0 and o.is_buy) or (position.amount > 0 and not o.is_buy)))] + for old_order in old_exit_orders: + self.cancel_order(self._market_info, old_order.client_order_id) + self.logger().info( + f"Initiated cancelation of previous take profit order {old_order.client_order_id} in favour of new take profit order.") + exit_order_exists = [o for o in self.active_orders if o.price == price] + if len(exit_order_exists) == 0: + if size > 0 and price > 0: + if position.amount < 0: + buys.append(PriceSize(price, size)) + else: + sells.append(PriceSize(price, size)) + return Proposal(buys, sells) + + def _should_renew_stop_loss(self, stop_loss_order: LimitOrder) -> bool: + stop_loss_creation_timestamp = self._exit_orders.get(stop_loss_order.client_order_id) + time_since_stop_loss = self.current_timestamp - stop_loss_creation_timestamp + return time_since_stop_loss >= self._time_between_stop_loss_orders + + def stop_loss_proposal(self, mode: PositionMode, active_positions: List[Position]) -> Proposal: + market: DerivativeBase = self._market_info.market + top_ask = market.get_price(self.trading_pair, False) + top_bid = market.get_price(self.trading_pair, True) + buys = [] + sells = [] + + for position in active_positions: + # check if stop loss order needs to be placed + stop_loss_price = position.entry_price * (Decimal("1") + self._stop_loss_spread) if position.amount < 0 \ + else position.entry_price * (Decimal("1") - self._stop_loss_spread) + existent_stop_loss_orders = [order for order in self.active_orders + if order.client_order_id in self._exit_orders.keys() + and ((position.amount > 0 and not order.is_buy) + or (position.amount < 0 and order.is_buy))] + if (not existent_stop_loss_orders + or (self._should_renew_stop_loss(existent_stop_loss_orders[0]))): + previous_stop_loss_price = None + for order in existent_stop_loss_orders: + previous_stop_loss_price = order.price + self.cancel_order(self._market_info, order.client_order_id) + self.logger().info(f"Canceling the limit order {order.client_order_id} to renew stop loss.") + new_price = previous_stop_loss_price or stop_loss_price + if (top_ask <= stop_loss_price and position.amount > 0): + price = market.quantize_order_price( + self.trading_pair, + new_price * (Decimal(1) - self._stop_loss_slippage_buffer)) + take_profit_orders = [o for o in self.active_orders + if (not o.is_buy and o.price > price + and o.client_order_id in self._exit_orders.keys())] + # cancel take profit orders if they exist + for old_order in take_profit_orders: + self.cancel_order(self._market_info, old_order.client_order_id) + self.logger().info( + f"Canceling existing take profit order {order.client_order_id} in favor of stop loss." + ) + size = market.quantize_order_amount(self.trading_pair, abs(position.amount)) + if size > 0 and price > 0: + self.logger().info("Creating stop loss sell order to close long position.") + sells.append(PriceSize(price, size)) + elif (top_bid >= stop_loss_price and position.amount < 0): + price = market.quantize_order_price( + self.trading_pair, + new_price * (Decimal(1) + self._stop_loss_slippage_buffer)) + take_profit_orders = [o for o in self.active_orders + if (o.is_buy and o.price < price + and o.client_order_id in self._exit_orders.keys())] + # cancel take profit orders if they exist + for old_order in take_profit_orders: + self.cancel_order(self._market_info, old_order.client_order_id) + self.logger().info( + f"Canceling existing take profit order {order.client_order_id} in favor of stop loss." + ) + size = market.quantize_order_amount(self.trading_pair, abs(position.amount)) + if size > 0 and price > 0: + self.logger().info("Creating stop loss buy order to close short position.") + buys.append(PriceSize(price, size)) + return Proposal(buys, sells) + + def create_base_proposal(self): + market: DerivativeBase = self._market_info.market + buys = [] + sells = [] + + # First to check if a customized order override is configured, otherwise the proposal will be created according + # to order spread, amount, and levels setting. + order_override = self._order_override + if order_override is not None and len(order_override) > 0: + for key, value in order_override.items(): + if str(value[0]) in ["buy", "sell"]: + if str(value[0]) == "buy": + price = self.get_price() * (Decimal("1") - Decimal(str(value[1])) / Decimal("100")) + price = market.quantize_order_price(self.trading_pair, price) + size = Decimal(str(value[2])) + size = market.quantize_order_amount(self.trading_pair, size) + if size > 0 and price > 0: + buys.append(PriceSize(price, size)) + elif str(value[0]) == "sell": + price = self.get_price() * (Decimal("1") + Decimal(str(value[1])) / Decimal("100")) + price = market.quantize_order_price(self.trading_pair, price) + size = Decimal(str(value[2])) + size = market.quantize_order_amount(self.trading_pair, size) + if size > 0 and price > 0: + sells.append(PriceSize(price, size)) + else: + for level in range(0, self._buy_levels): + price = self.get_price() * (Decimal("1") - self._bid_spread - (level * self._order_level_spread)) + price = market.quantize_order_price(self.trading_pair, price) + size = self._order_amount + (self._order_level_amount * level) + size = market.quantize_order_amount(self.trading_pair, size) + if size > 0: + buys.append(PriceSize(price, size)) + for level in range(0, self._sell_levels): + price = self.get_price() * (Decimal("1") + self._ask_spread + (level * self._order_level_spread)) + price = market.quantize_order_price(self.trading_pair, price) + size = self._order_amount + (self._order_level_amount * level) + size = market.quantize_order_amount(self.trading_pair, size) + if size > 0: + sells.append(PriceSize(price, size)) + + return Proposal(buys, sells) + + def apply_order_levels_modifiers(self, proposal: Proposal): + self.apply_price_band(proposal) + + def apply_price_band(self, proposal: Proposal): + if self._price_ceiling > 0 and self.get_price() >= self._price_ceiling: + proposal.buys = [] + if self._price_floor > 0 and self.get_price() <= self._price_floor: + proposal.sells = [] + + def apply_order_price_modifiers(self, proposal: Proposal): + if self._order_optimization_enabled: + self.apply_order_optimization(proposal) + + def apply_budget_constraint(self, proposal: Proposal): + checker = self._market_info.market.budget_checker + + order_candidates = self.create_order_candidates_for_budget_check(proposal) + adjusted_candidates = checker.adjust_candidates(order_candidates, all_or_none=True) + self.apply_adjusted_order_candidates_to_proposal(adjusted_candidates, proposal) + + def create_order_candidates_for_budget_check(self, proposal: Proposal): + order_candidates = [] + + is_maker = True + order_candidates.extend( + [ + PerpetualOrderCandidate( + self.trading_pair, + is_maker, + OrderType.LIMIT, + TradeType.BUY, + buy.size, + buy.price, + leverage=Decimal(self._leverage), + ) + for buy in proposal.buys + ] + ) + order_candidates.extend( + [ + PerpetualOrderCandidate( + self.trading_pair, + is_maker, + OrderType.LIMIT, + TradeType.SELL, + sell.size, + sell.price, + leverage=Decimal(self._leverage), + ) + for sell in proposal.sells + ] + ) + return order_candidates + + def apply_adjusted_order_candidates_to_proposal(self, + adjusted_candidates: List[PerpetualOrderCandidate], + proposal: Proposal): + for order in chain(proposal.buys, proposal.sells): + adjusted_candidate = adjusted_candidates.pop(0) + if adjusted_candidate.amount == s_decimal_zero: + self.logger().info( + f"Insufficient balance: {adjusted_candidate.order_side.name} order (price: {order.price}," + f" size: {order.size}) is omitted." + ) + self.logger().warning( + "You are also at a possible risk of being liquidated if there happens to be an open loss.") + order.size = s_decimal_zero + proposal.buys = [o for o in proposal.buys if o.size > 0] + proposal.sells = [o for o in proposal.sells if o.size > 0] + + def filter_out_takers(self, proposal: Proposal): + market: DerivativeBase = self._market_info.market + top_ask = market.get_price(self.trading_pair, True) + if not top_ask.is_nan(): + proposal.buys = [buy for buy in proposal.buys if buy.price < top_ask] + top_bid = market.get_price(self.trading_pair, False) + if not top_bid.is_nan(): + proposal.sells = [sell for sell in proposal.sells if sell.price > top_bid] + + # Compare the market price with the top bid and top ask price + def apply_order_optimization(self, proposal: Proposal): + market: DerivativeBase = self._market_info.market + own_buy_size = s_decimal_zero + own_sell_size = s_decimal_zero + + # If there are multiple orders, do not jump prices + if self._order_levels > 1: + return + + for order in self.active_orders: + if order.is_buy: + own_buy_size = order.quantity + else: + own_sell_size = order.quantity + + if len(proposal.buys) == 1: + # Get the top bid price in the market using order_optimization_depth and your buy order volume + top_bid_price = self._market_info.get_price_for_volume( + False, self._bid_order_optimization_depth + own_buy_size).result_price + price_quantum = market.get_order_price_quantum( + self.trading_pair, + top_bid_price + ) + # Get the price above the top bid + price_above_bid = (ceil(top_bid_price / price_quantum) + 1) * price_quantum + + # If the price_above_bid is lower than the price suggested by the pricing proposal, + # lower your price to this + lower_buy_price = min(proposal.buys[0].price, price_above_bid) + proposal.buys[0].price = market.quantize_order_price(self.trading_pair, lower_buy_price) + + if len(proposal.sells) == 1: + # Get the top ask price in the market using order_optimization_depth and your sell order volume + top_ask_price = self._market_info.get_price_for_volume( + True, self._ask_order_optimization_depth + own_sell_size).result_price + price_quantum = market.get_order_price_quantum( + self.trading_pair, + top_ask_price + ) + # Get the price below the top ask + price_below_ask = (floor(top_ask_price / price_quantum) - 1) * price_quantum + + # If the price_below_ask is higher than the price suggested by the pricing proposal, + # increase your price to this + higher_sell_price = max(proposal.sells[0].price, price_below_ask) + proposal.sells[0].price = market.quantize_order_price(self.trading_pair, higher_sell_price) + + def did_fill_order(self, order_filled_event: OrderFilledEvent): + order_id = order_filled_event.order_id + market_info = self._sb_order_tracker.get_shadow_market_pair_from_order_id(order_id) + + if market_info is not None: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker " + f"{'buy' if order_filled_event.trade_type is TradeType.BUY else 'sell'} order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + order_id = order_completed_event.order_id + limit_order_record = self._sb_order_tracker.get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + + # delay order creation by filled_order_delay (in seconds) + self._create_timestamp = self.current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker BUY order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + order_id = order_completed_event.order_id + limit_order_record: LimitOrder = self._sb_order_tracker.get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + + # delay order creation by filled_order_delay (in seconds) + self._create_timestamp = self.current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker SELL order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + def did_change_position_mode_succeed(self, position_mode_changed_event: PositionModeChangeEvent): + if self._position_mode is position_mode_changed_event.position_mode: + self.logger().info( + f"Changing position mode to {self._position_mode.name} succeeded.") + self._position_mode_ready = True + else: + self.logger().warning( + f"Changing position mode to {self._position_mode.name} did not succeed.") + self._position_mode_ready = False + + def did_change_position_mode_fail(self, position_mode_changed_event: PositionModeChangeEvent): + self.logger().error( + f"Changing position mode to {self._position_mode.name} failed. " + f"Reason: {position_mode_changed_event.message}.") + self._position_mode_ready = False + self.logger().warning("Cannot continue. Please resolve the issue in the account.") + + def is_within_tolerance(self, current_prices: List[Decimal], proposal_prices: List[Decimal]) -> bool: + if len(current_prices) != len(proposal_prices): + return False + current_prices = sorted(current_prices) + proposal_prices = sorted(proposal_prices) + for current, proposal in zip(current_prices, proposal_prices): + # if spread diff is more than the tolerance or order quantities are different, return false. + if abs(proposal - current) / current > self._order_refresh_tolerance_pct: + return False + return True + + # Return value: whether order cancelation is deferred. + def cancel_active_orders(self, proposal: Proposal): + if self._cancel_timestamp > self.current_timestamp: + return + + to_defer_canceling = False + if len(self.active_orders) == 0: + return + if proposal is not None and self._order_refresh_tolerance_pct >= 0: + + active_buy_prices = [Decimal(str(o.price)) for o in self.active_orders if o.is_buy] + active_sell_prices = [Decimal(str(o.price)) for o in self.active_orders if not o.is_buy] + proposal_buys = [buy.price for buy in proposal.buys] + proposal_sells = [sell.price for sell in proposal.sells] + if self.is_within_tolerance(active_buy_prices, proposal_buys) and \ + self.is_within_tolerance(active_sell_prices, proposal_sells): + to_defer_canceling = True + + if not to_defer_canceling: + for order in self.active_orders: + self.cancel_order(self._market_info, order.client_order_id) + self.logger().info(f"Canceling active order {order.client_order_id}.") + else: + self.logger().info(f"Not canceling active orders since difference between new order prices " + f"and current order prices is within " + f"{self._order_refresh_tolerance_pct:.2%} order_refresh_tolerance_pct") + self.set_timers() + + def cancel_orders_below_min_spread(self): + price = self.get_price() + for order in self.active_orders: + negation = -1 if order.is_buy else 1 + if (negation * (order.price - price) / price) < self._minimum_spread: + self.logger().info(f"Order is below minimum spread ({self._minimum_spread})." + f" Canceling Order: ({'Buy' if order.is_buy else 'Sell'}) " + f"ID - {order.client_order_id}") + self.cancel_order(self._market_info, order.client_order_id) + self.logger().info(f"Canceling order {order.client_order_id} below min spread.") + + def to_create_orders(self, proposal: Proposal) -> bool: + return (self._create_timestamp < self.current_timestamp and + proposal is not None and + len(self.active_orders) == 0) + + def execute_orders_proposal(self, proposal: Proposal, position_action: PositionAction): + orders_created = False + + if len(proposal.buys) > 0: + if position_action == PositionAction.CLOSE: + if self.current_timestamp < self._next_buy_exit_order_timestamp: + return + else: + self._next_buy_exit_order_timestamp = self.current_timestamp + self.filled_order_delay + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{buy.size.normalize()} {self.base_asset}, " + f"{buy.price.normalize()} {self.quote_asset}" + for buy in proposal.buys] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.buys)} {self._close_order_type.name} bid orders " + f"at (Size, Price): {price_quote_str} to {position_action.name} position." + ) + for buy in proposal.buys: + bid_order_id = self.buy_with_specific_market( + self._market_info, + buy.size, + order_type=self._close_order_type, + price=buy.price, + position_action=position_action + ) + if position_action == PositionAction.CLOSE: + self._exit_orders[bid_order_id] = self.current_timestamp + orders_created = True + if len(proposal.sells) > 0: + if position_action == PositionAction.CLOSE: + if self.current_timestamp < self._next_sell_exit_order_timestamp: + return + else: + self._next_sell_exit_order_timestamp = self.current_timestamp + self.filled_order_delay + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{sell.size.normalize()} {self.base_asset}, " + f"{sell.price.normalize()} {self.quote_asset}" + for sell in proposal.sells] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.sells)} {self._close_order_type.name} ask " + f"orders at (Size, Price): {price_quote_str} to {position_action.name} position." + ) + for sell in proposal.sells: + ask_order_id = self.sell_with_specific_market( + self._market_info, + sell.size, + order_type=self._close_order_type, + price=sell.price, + position_action=position_action + ) + if position_action == PositionAction.CLOSE: + self._exit_orders[ask_order_id] = self.current_timestamp + orders_created = True + if orders_created: + self.set_timers() + + def set_timers(self): + next_cycle = self.current_timestamp + self._order_refresh_time + if self._create_timestamp <= self.current_timestamp: + self._create_timestamp = next_cycle + if self._cancel_timestamp <= self.current_timestamp: + self._cancel_timestamp = min(self._create_timestamp, next_cycle) + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + def get_price_type(self, price_type_str: str) -> PriceType: + if price_type_str == "mid_price": + return PriceType.MidPrice + elif price_type_str == "best_bid": + return PriceType.BestBid + elif price_type_str == "best_ask": + return PriceType.BestAsk + elif price_type_str == "last_price": + return PriceType.LastTrade + elif price_type_str == 'last_own_trade_price': + return PriceType.LastOwnTrade + elif price_type_str == "custom": + return PriceType.Custom + else: + raise ValueError(f"Unrecognized price type string {price_type_str}.") diff --git a/hummingbot/strategy/perpetual_market_making/perpetual_market_making_config_map.py b/hummingbot/strategy/perpetual_market_making/perpetual_market_making_config_map.py new file mode 100644 index 0000000..67ee863 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/perpetual_market_making_config_map.py @@ -0,0 +1,324 @@ +from decimal import Decimal +from typing import Optional + +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_decimal, + validate_derivative, + validate_exchange, + validate_int, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges + + +def maker_trading_pair_prompt(): + derivative = perpetual_market_making_config_map.get("derivative").value + example = AllConnectorSettings.get_example_pairs().get(derivative) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (derivative, f" (e.g. {example})" if example else "") + + +# strategy specific validators +def validate_derivative_trading_pair(value: str) -> Optional[str]: + derivative = perpetual_market_making_config_map.get("derivative").value + return validate_market_trading_pair(derivative, value) + + +def validate_derivative_position_mode(value: str) -> Optional[str]: + if value not in ["One-way", "Hedge"]: + return "Position mode can either be One-way or Hedge mode" + + +def order_amount_prompt() -> str: + trading_pair = perpetual_market_making_config_map["market"].value + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order? >>> " + + +def validate_price_source(value: str) -> Optional[str]: + if value not in {"current_market", "external_market", "custom_api"}: + return "Invalid price source type." + + +def on_validate_price_source(value: str): + if value != "external_market": + perpetual_market_making_config_map["price_source_derivative"].value = None + perpetual_market_making_config_map["price_source_market"].value = None + if value != "custom_api": + perpetual_market_making_config_map["price_source_custom_api"].value = None + else: + perpetual_market_making_config_map["price_type"].value = "custom" + + +def validate_price_type(value: str) -> Optional[str]: + error = None + price_source = perpetual_market_making_config_map.get("price_source").value + if price_source != "custom_api": + valid_values = {"mid_price", + "last_price", + "last_own_trade_price", + "best_bid", + "best_ask"} + if value not in valid_values: + error = "Invalid price type." + elif value != "custom": + error = "Invalid price type." + return error + + +def price_source_market_prompt() -> str: + external_market = perpetual_market_making_config_map.get("price_source_derivative").value + return f'Enter the token trading pair on {external_market} >>> ' + + +def validate_price_source_derivative(value: str) -> Optional[str]: + if value == perpetual_market_making_config_map.get("derivative").value: + return "Price source derivative cannot be the same as maker derivative." + if validate_derivative(value) is not None and validate_exchange(value) is not None: + return "Price source must must be a valid exchange or derivative connector." + + +def on_validated_price_source_derivative(value: str): + if value is None: + perpetual_market_making_config_map["price_source_market"].value = None + + +def validate_price_source_market(value: str) -> Optional[str]: + market = perpetual_market_making_config_map.get("price_source_derivative").value + return validate_market_trading_pair(market, value) + + +def validate_price_floor_ceiling(value: str) -> Optional[str]: + try: + decimal_value = Decimal(value) + except Exception: + return f"{value} is not in decimal format." + if not (decimal_value == Decimal("-1") or decimal_value > Decimal("0")): + return "Value must be more than 0 or -1 to disable this feature." + + +def derivative_on_validated(value: str): + required_exchanges.add(value) + + +perpetual_market_making_config_map = { + "strategy": + ConfigVar(key="strategy", + prompt=None, + default="perpetual_market_making"), + "derivative": + ConfigVar(key="derivative", + prompt="Enter your maker derivative connector exchange name >>> ", + validator=validate_derivative, + on_validated=derivative_on_validated, + prompt_on_new=True), + "market": + ConfigVar(key="market", + prompt=maker_trading_pair_prompt, + validator=validate_derivative_trading_pair, + prompt_on_new=True), + "leverage": + ConfigVar(key="leverage", + prompt="How much leverage do you want to use? " + "(Binance Perpetual supports up to 75X for most pairs) >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=0, inclusive=False), + prompt_on_new=True), + "position_mode": + ConfigVar(key="position_mode", + prompt="Which position mode do you want to use? (One-way/Hedge) >>> ", + validator=validate_derivative_position_mode, + type_str="str", + default="One-way", + prompt_on_new=True), + "bid_spread": + ConfigVar(key="bid_spread", + prompt="How far away from the mid price do you want to place the " + "first bid order? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "ask_spread": + ConfigVar(key="ask_spread", + prompt="How far away from the mid price do you want to place the " + "first ask order? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "minimum_spread": + ConfigVar(key="minimum_spread", + prompt="At what minimum spread should the bot automatically cancel orders? (Enter 1 for 1%) >>> ", + required_if=lambda: False, + type_str="decimal", + default=Decimal(-100), + validator=lambda v: validate_decimal(v, -100, 100, True)), + "order_refresh_time": + ConfigVar(key="order_refresh_time", + prompt="How often do you want to cancel and replace bids and asks " + "(in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True), + "order_refresh_tolerance_pct": + ConfigVar(key="order_refresh_tolerance_pct", + prompt="Enter the percent change in price needed to refresh orders at each cycle " + "(Enter 1 to indicate 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, -10, 10, inclusive=True)), + "order_amount": + ConfigVar(key="order_amount", + prompt=order_amount_prompt, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=False), + prompt_on_new=True), + "long_profit_taking_spread": + ConfigVar(key="long_profit_taking_spread", + prompt="At what spread from the entry price do you want to place a short order to reduce position? (Enter 1 for 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, 0, 100, True), + prompt_on_new=True), + "short_profit_taking_spread": + ConfigVar(key="short_profit_taking_spread", + prompt="At what spread from the position entry price do you want to place a long order to reduce position? (Enter 1 for 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, 0, 100, True), + prompt_on_new=True), + "stop_loss_spread": + ConfigVar(key="stop_loss_spread", + prompt="At what spread from position entry price do you want to place stop_loss order? (Enter 1 for 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, 0, 101, False), + prompt_on_new=True), + "time_between_stop_loss_orders": + ConfigVar(key="time_between_stop_loss_orders", + prompt="How much time should pass before refreshing a stop loss order that has not been executed? (in seconds) >>> ", + type_str="float", + default=60, + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True), + "stop_loss_slippage_buffer": + ConfigVar(key="stop_loss_slippage_buffer", + prompt="How much buffer should be added in stop loss orders' price to account for slippage? (Enter 1 for 1%)? >>> ", + type_str="decimal", + default=Decimal("0.5"), + validator=lambda v: validate_decimal(v, 0, inclusive=True), + prompt_on_new=True), + "price_ceiling": + ConfigVar(key="price_ceiling", + prompt="Enter the price point above which only sell orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "price_floor": + ConfigVar(key="price_floor", + prompt="Enter the price below which only buy orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "order_levels": + ConfigVar(key="order_levels", + prompt="How many orders do you want to place on both sides? >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=0, inclusive=False), + default=1), + "order_level_amount": + ConfigVar(key="order_level_amount", + prompt="How much do you want to increase or decrease the order size for each " + "additional order? (decrease < 0 > increase) >>> ", + required_if=lambda: perpetual_market_making_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v), + default=0), + "order_level_spread": + ConfigVar(key="order_level_spread", + prompt="Enter the price increments (as percentage) for subsequent " + "orders? (Enter 1 to indicate 1%) >>> ", + required_if=lambda: perpetual_market_making_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + default=Decimal("1")), + "filled_order_delay": + ConfigVar(key="filled_order_delay", + prompt="How long do you want to wait before placing the next order " + "if your order gets filled (in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=60), + "order_optimization_enabled": + ConfigVar(key="order_optimization_enabled", + prompt="Do you want to enable best bid ask jumping? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "ask_order_optimization_depth": + ConfigVar(key="ask_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top ask, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: perpetual_market_making_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "bid_order_optimization_depth": + ConfigVar(key="bid_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top bid, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: perpetual_market_making_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "price_source": + ConfigVar(key="price_source", + prompt="Which price source to use? (current_market/external_market/custom_api) >>> ", + type_str="str", + default="current_market", + validator=validate_price_source, + on_validated=on_validate_price_source), + "price_type": + ConfigVar(key="price_type", + prompt="Which price type to use? (mid_price/last_price/last_own_trade_price/best_bid/best_ask) >>> ", + type_str="str", + required_if=lambda: perpetual_market_making_config_map.get("price_source").value != "custom_api", + default="mid_price", + validator=validate_price_type), + "price_source_derivative": + ConfigVar(key="price_source_derivative", + prompt="Enter external price source connector name or derivative name >>> ", + required_if=lambda: perpetual_market_making_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_derivative, + on_validated=on_validated_price_source_derivative), + "price_source_market": + ConfigVar(key="price_source_market", + prompt=price_source_market_prompt, + required_if=lambda: perpetual_market_making_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_market), + "price_source_custom_api": + ConfigVar(key="price_source_custom_api", + prompt="Enter pricing API URL >>> ", + required_if=lambda: perpetual_market_making_config_map.get("price_source").value == "custom_api", + type_str="str"), + "custom_api_update_interval": + ConfigVar(key="custom_api_update_interval", + prompt="Enter custom API update interval in second (default: 5.0, min: 0.5) >>> ", + required_if=lambda: False, + default=float(5), + type_str="float", + validator=lambda v: validate_decimal(v, Decimal("0.5"))), + "order_override": + ConfigVar(key="order_override", + prompt=None, + required_if=lambda: False, + default=None, + type_str="json"), +} diff --git a/hummingbot/strategy/perpetual_market_making/perpetual_market_making_order_tracker.py b/hummingbot/strategy/perpetual_market_making/perpetual_market_making_order_tracker.py new file mode 100644 index 0000000..887e54f --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/perpetual_market_making_order_tracker.py @@ -0,0 +1,36 @@ +from typing import Dict, List, Tuple + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_tracker import OrderTracker + +NaN = float("nan") + + +class PerpetualMarketMakingOrderTracker(OrderTracker): + # ETH confirmation requirement of Binance has shortened to 12 blocks as of 7/15/2019. + # 12 * 15 / 60 = 3 minutes + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 3 + + def __init__(self): + super().__init__() + + @property + def active_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return self.tracked_limit_orders + + @property + def shadow_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self.get_shadow_limit_orders().items(): + for limit_order in orders_map.values(): + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def market_pair_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + market_pair_to_orders = {} + for market_pair, orders_map in self.tracked_limit_orders_map.items(): + market_pair_to_orders[market_pair] = list(self.tracked_limit_orders_map[market_pair].values()) + return market_pair_to_orders diff --git a/hummingbot/strategy/perpetual_market_making/start.py b/hummingbot/strategy/perpetual_market_making/start.py new file mode 100644 index 0000000..4e5d148 --- /dev/null +++ b/hummingbot/strategy/perpetual_market_making/start.py @@ -0,0 +1,105 @@ +from decimal import Decimal +from typing import List, Tuple + +from hummingbot.connector.exchange.paper_trade import create_paper_trade_market +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.api_asset_price_delegate import APIAssetPriceDelegate +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.perpetual_market_making import PerpetualMarketMakingStrategy +from hummingbot.strategy.perpetual_market_making.perpetual_market_making_config_map import ( + perpetual_market_making_config_map as c_map, +) + + +def start(self): + try: + leverage = c_map.get("leverage").value + position_mode = c_map.get("position_mode").value + order_amount = c_map.get("order_amount").value + order_refresh_time = c_map.get("order_refresh_time").value + bid_spread = c_map.get("bid_spread").value / Decimal('100') + ask_spread = c_map.get("ask_spread").value / Decimal('100') + long_profit_taking_spread = c_map.get("long_profit_taking_spread").value / Decimal('100') + short_profit_taking_spread = c_map.get("short_profit_taking_spread").value / Decimal('100') + stop_loss_spread = c_map.get("stop_loss_spread").value / Decimal('100') + time_between_stop_loss_orders = c_map.get("time_between_stop_loss_orders").value + stop_loss_slippage_buffer = c_map.get("stop_loss_slippage_buffer").value / Decimal('100') + minimum_spread = c_map.get("minimum_spread").value / Decimal('100') + price_ceiling = c_map.get("price_ceiling").value + price_floor = c_map.get("price_floor").value + order_levels = c_map.get("order_levels").value + order_level_amount = c_map.get("order_level_amount").value + order_level_spread = c_map.get("order_level_spread").value / Decimal('100') + exchange = c_map.get("derivative").value.lower() + raw_trading_pair = c_map.get("market").value + filled_order_delay = c_map.get("filled_order_delay").value + order_optimization_enabled = c_map.get("order_optimization_enabled").value + ask_order_optimization_depth = c_map.get("ask_order_optimization_depth").value + bid_order_optimization_depth = c_map.get("bid_order_optimization_depth").value + price_source = c_map.get("price_source").value + price_type = c_map.get("price_type").value + price_source_exchange = c_map.get("price_source_derivative").value + price_source_market = c_map.get("price_source_market").value + price_source_custom_api = c_map.get("price_source_custom_api").value + custom_api_update_interval = c_map.get("custom_api_update_interval").value + order_refresh_tolerance_pct = c_map.get("order_refresh_tolerance_pct").value / Decimal('100') + order_override = c_map.get("order_override").value + + trading_pair: str = raw_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(exchange, [trading_pair])[0] + market_names: List[Tuple[str, List[str]]] = [(exchange, [trading_pair])] + self._initialize_markets(market_names) + maker_data = [self.markets[exchange], trading_pair] + list(maker_assets) + self.market_trading_pair_tuples = [MarketTradingPairTuple(*maker_data)] + asset_price_delegate = None + if price_source == "external_market": + asset_trading_pair: str = price_source_market + ext_market = create_paper_trade_market( + price_source_exchange, self.client_config_map, [asset_trading_pair] + ) + self.markets[price_source_exchange]: ExchangeBase = ext_market + asset_price_delegate = OrderBookAssetPriceDelegate(ext_market, asset_trading_pair) + elif price_source == "custom_api": + ext_market = create_paper_trade_market( + exchange, self.client_config_map, [raw_trading_pair] + ) + asset_price_delegate = APIAssetPriceDelegate(ext_market, price_source_custom_api, + custom_api_update_interval) + + strategy_logging_options = PerpetualMarketMakingStrategy.OPTION_LOG_ALL + + self.strategy = PerpetualMarketMakingStrategy() + self.strategy.init_params( + market_info=MarketTradingPairTuple(*maker_data), + leverage=leverage, + position_mode=position_mode, + bid_spread=bid_spread, + ask_spread=ask_spread, + order_amount=order_amount, + long_profit_taking_spread=long_profit_taking_spread, + short_profit_taking_spread=short_profit_taking_spread, + stop_loss_spread=stop_loss_spread, + time_between_stop_loss_orders=time_between_stop_loss_orders, + stop_loss_slippage_buffer=stop_loss_slippage_buffer, + order_levels=order_levels, + order_level_spread=order_level_spread, + order_level_amount=order_level_amount, + order_refresh_time=order_refresh_time, + order_refresh_tolerance_pct=order_refresh_tolerance_pct, + filled_order_delay=filled_order_delay, + order_optimization_enabled=order_optimization_enabled, + ask_order_optimization_depth=ask_order_optimization_depth, + bid_order_optimization_depth=bid_order_optimization_depth, + asset_price_delegate=asset_price_delegate, + price_type=price_type, + price_ceiling=price_ceiling, + price_floor=price_floor, + logging_options=strategy_logging_options, + minimum_spread=minimum_spread, + hb_app_notification=True, + order_override=order_override, + ) + except Exception as e: + self.notify(str(e)) + self.logger().error("Unknown error during initialization.", exc_info=True) diff --git a/hummingbot/strategy/pure_market_making/__init__.py b/hummingbot/strategy/pure_market_making/__init__.py new file mode 100644 index 0000000..a0f49f4 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from .pure_market_making import PureMarketMakingStrategy +from .inventory_cost_price_delegate import InventoryCostPriceDelegate +__all__ = [ + PureMarketMakingStrategy, + InventoryCostPriceDelegate, +] diff --git a/hummingbot/strategy/pure_market_making/data_types.py b/hummingbot/strategy/pure_market_making/data_types.py new file mode 100644 index 0000000..aa2cdbe --- /dev/null +++ b/hummingbot/strategy/pure_market_making/data_types.py @@ -0,0 +1,55 @@ +from decimal import Decimal +from typing import ( + List, + NamedTuple, +) + +from hummingbot.core.data_type.common import OrderType + +ORDER_PROPOSAL_ACTION_CREATE_ORDERS = 1 +ORDER_PROPOSAL_ACTION_CANCEL_ORDERS = 1 << 1 + + +class OrdersProposal(NamedTuple): + actions: int + buy_order_type: OrderType + buy_order_prices: List[Decimal] + buy_order_sizes: List[Decimal] + sell_order_type: OrderType + sell_order_prices: List[Decimal] + sell_order_sizes: List[Decimal] + cancel_order_ids: List[str] + + +class PricingProposal(NamedTuple): + buy_order_prices: List[Decimal] + sell_order_prices: List[Decimal] + + +class SizingProposal(NamedTuple): + buy_order_sizes: List[Decimal] + sell_order_sizes: List[Decimal] + + +class InventorySkewBidAskRatios(NamedTuple): + bid_ratio: float + ask_ratio: float + + +class PriceSize: + def __init__(self, price: Decimal, size: Decimal): + self.price: Decimal = price + self.size: Decimal = size + + def __repr__(self): + return f"[ p: {self.price} s: {self.size} ]" + + +class Proposal: + def __init__(self, buys: List[PriceSize], sells: List[PriceSize]): + self.buys: List[PriceSize] = buys + self.sells: List[PriceSize] = sells + + def __repr__(self): + return f"{len(self.buys)} buys: {', '.join([str(o) for o in self.buys])} " \ + f"{len(self.sells)} sells: {', '.join([str(o) for o in self.sells])}" diff --git a/hummingbot/strategy/pure_market_making/inventory_cost_price_delegate.py b/hummingbot/strategy/pure_market_making/inventory_cost_price_delegate.py new file mode 100644 index 0000000..19ebfa4 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/inventory_cost_price_delegate.py @@ -0,0 +1,74 @@ +from decimal import Decimal, InvalidOperation +from typing import Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.model.inventory_cost import InventoryCost +from hummingbot.model.sql_connection_manager import SQLConnectionManager + +s_decimal_0 = Decimal("0") + + +class InventoryCostPriceDelegate: + def __init__(self, sql: SQLConnectionManager, trading_pair: str) -> None: + self.base_asset, self.quote_asset = trading_pair.split("-") + self.sql_manager = sql + + @property + def ready(self) -> bool: + return True + + def get_price(self) -> Optional[Decimal]: + with self.sql_manager.get_new_session() as session: + with session.begin(): + record = InventoryCost.get_record( + session, self.base_asset, self.quote_asset + ) + + if record is None or record.base_volume is None or record.quote_volume is None: + return None + + try: + price = record.quote_volume / record.base_volume + except InvalidOperation: + return None + return Decimal(price) + + def process_order_fill_event(self, fill_event: OrderFilledEvent) -> None: + base_asset, quote_asset = fill_event.trading_pair.split("-") + quote_volume = fill_event.amount * fill_event.price + base_volume = fill_event.amount + + for fee_asset, fee_amount in fill_event.trade_fee.flat_fees: + if fill_event.trade_type == TradeType.BUY: + if fee_asset == base_asset: + base_volume -= fee_amount + elif fee_asset == quote_asset: + quote_volume += fee_amount + else: + # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity + base_volume /= 1 + fill_event.trade_fee.percent + else: + if fee_asset == base_asset: + base_volume += fee_amount + elif fee_asset == quote_asset: + # TODO: with new logic, this quote volume adjustment does not impacts anything + quote_volume -= fee_amount + else: + # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity + base_volume /= 1 + fill_event.trade_fee.percent + + with self.sql_manager.get_new_session() as session: + with session.begin(): + if fill_event.trade_type == TradeType.SELL: + record = InventoryCost.get_record(session, base_asset, quote_asset) + if not record: + raise RuntimeError("Sold asset without having inventory price set. This should not happen.") + + # We're keeping initial buy price intact. Profits are not changing inventory price intentionally. + quote_volume = -(Decimal(record.quote_volume / record.base_volume) * base_volume) + base_volume = -base_volume + + InventoryCost.add_volume( + session, base_asset, quote_asset, base_volume, quote_volume + ) diff --git a/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pxd b/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pxd new file mode 100644 index 0000000..57f5c98 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pxd @@ -0,0 +1,5 @@ +cdef object c_calculate_bid_ask_ratios_from_base_asset_ratio(double base_asset_amount, + double quote_asset_amount, + double price, + double target_base_asset_ratio, + double base_asset_range) diff --git a/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pyx b/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pyx new file mode 100644 index 0000000..758e58e --- /dev/null +++ b/hummingbot/strategy/pure_market_making/inventory_skew_calculator.pyx @@ -0,0 +1,57 @@ +from decimal import Decimal +import numpy as np + +from .data_types import InventorySkewBidAskRatios + +decimal_0 = Decimal(0) +decimal_1 = Decimal(1) +decimal_2 = Decimal(2) + + +def calculate_total_order_size(order_start_size: Decimal, order_step_size: Decimal = decimal_0, + order_levels: int = 1) -> Decimal: + order_levels_decimal = order_levels + return (decimal_2 * + (order_levels_decimal * order_start_size + + order_levels_decimal * (order_levels_decimal - decimal_1) / decimal_2 * order_step_size + ) + ) + + +def calculate_bid_ask_ratios_from_base_asset_ratio( + base_asset_amount: float, quote_asset_amount: float, price: float, + target_base_asset_ratio: float, base_asset_range: float) -> InventorySkewBidAskRatios: + return c_calculate_bid_ask_ratios_from_base_asset_ratio(base_asset_amount, + quote_asset_amount, + price, + target_base_asset_ratio, + base_asset_range) + + +cdef object c_calculate_bid_ask_ratios_from_base_asset_ratio( + double base_asset_amount, double quote_asset_amount, double price, + double target_base_asset_ratio, double base_asset_range): + cdef: + double total_portfolio_value = base_asset_amount * price + quote_asset_amount + + if total_portfolio_value <= 0.0 or base_asset_range <= 0.0: + return InventorySkewBidAskRatios(0.0, 0.0) + + cdef: + double base_asset_value = base_asset_amount * price + double base_asset_range_value = min(base_asset_range * price, total_portfolio_value * 0.5) + double target_base_asset_value = total_portfolio_value * target_base_asset_ratio + double left_base_asset_value_limit = max(target_base_asset_value - base_asset_range_value, 0.0) + double right_base_asset_value_limit = target_base_asset_value + base_asset_range_value + double left_inventory_ratio = np.interp(base_asset_value, + [left_base_asset_value_limit, target_base_asset_value], + [0.0, 0.5]) + double right_inventory_ratio = np.interp(base_asset_value, + [target_base_asset_value, right_base_asset_value_limit], + [0.5, 1.0]) + double bid_adjustment = (np.interp(left_inventory_ratio, [0, 0.5], [2.0, 1.0]) + if base_asset_value < target_base_asset_value + else np.interp(right_inventory_ratio, [0.5, 1], [1.0, 0.0])) + double ask_adjustment = 2.0 - bid_adjustment + + return InventorySkewBidAskRatios(bid_adjustment, ask_adjustment) diff --git a/hummingbot/strategy/pure_market_making/moving_price_band.py b/hummingbot/strategy/pure_market_making/moving_price_band.py new file mode 100644 index 0000000..fb6aa07 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/moving_price_band.py @@ -0,0 +1,88 @@ +import logging +from dataclasses import dataclass +from decimal import Decimal + +mpb_logger = None + + +@dataclass +class MovingPriceBand: + ''' + move price floor and ceiling to percentage of current price + at every price_band_refresh_time + + :param price_floor_pct: set the price floor pct + :param price_ceiling_pct: reference price to set price band + :param price_band_refresh_time: reference price to set price band + ''' + price_floor_pct: Decimal = -1 + price_ceiling_pct: Decimal = 1 + price_band_refresh_time: float = 86400 + enabled: bool = False + _price_floor: Decimal = 0 + _price_ceiling: Decimal = 0 + _set_time: float = 0 + + @classmethod + def logger(cls): + global mpb_logger + if mpb_logger is None: + mpb_logger = logging.getLogger(__name__) + return mpb_logger + + @property + def price_floor(self) -> Decimal: + '''get price floor''' + return self._price_floor + + @property + def price_ceiling(self) -> Decimal: + '''get price ceiling''' + return self._price_ceiling + + def update(self, timestamp: float, price: Decimal) -> None: + """ + Updates the price band. + + :param timestamp: current timestamp of the strategy/connector + :param price: reference price to set price band + """ + self._price_floor = (Decimal("100") + self.price_floor_pct) / Decimal("100") * price + self._price_ceiling = (Decimal("100") + self.price_ceiling_pct) / Decimal("100") * price + self._set_time = timestamp + self.logger().info( + "moving price band updated: price_floor: %s price_ceiling: %s", self._price_floor, self._price_ceiling) + + def check_and_update_price_band(self, timestamp: float, price: Decimal) -> None: + ''' + check if the timestamp has passed the defined refresh time before updating + + :param timestamp: current timestamp of the strategy/connector + :param price: reference price to set price band + ''' + if timestamp >= self._set_time + self.price_band_refresh_time: + self.update(timestamp, price) + + def check_price_floor_exceeded(self, price: Decimal) -> bool: + ''' + check if the price has exceeded the price floor + + :param price: price to check + ''' + return price <= self.price_floor + + def check_price_ceiling_exceeded(self, price: Decimal) -> bool: + ''' + check if the price has exceeded the price ceiling + + :param price: price to check + ''' + return price >= self.price_ceiling + + def switch(self, value: bool) -> None: + ''' + switch between enabled and disabled state + + :param value: set whether to enable or disable MovingPriceBand + ''' + self.enabled = value diff --git a/hummingbot/strategy/pure_market_making/pure_market_making.pxd b/hummingbot/strategy/pure_market_making/pure_market_making.pxd new file mode 100644 index 0000000..f2b08d7 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/pure_market_making.pxd @@ -0,0 +1,83 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t + +from hummingbot.strategy.strategy_base cimport StrategyBase + + +cdef class PureMarketMakingStrategy(StrategyBase): + cdef: + object _market_info + + object _bid_spread + object _ask_spread + object _minimum_spread + object _order_amount + int _order_levels + int _buy_levels + int _sell_levels + object _split_order_levels_enabled + object _bid_order_level_spreads + object _ask_order_level_spreads + object _order_level_spread + object _order_level_amount + double _order_refresh_time + double _max_order_age + object _order_refresh_tolerance_pct + double _filled_order_delay + bint _inventory_skew_enabled + object _inventory_target_base_pct + object _inventory_range_multiplier + bint _hanging_orders_enabled + object _hanging_orders_tracker + bint _order_optimization_enabled + object _ask_order_optimization_depth + object _bid_order_optimization_depth + bint _add_transaction_costs_to_orders + object _asset_price_delegate + object _inventory_cost_price_delegate + object _price_type + bint _take_if_crossed + object _price_ceiling + object _price_floor + bint _ping_pong_enabled + list _ping_pong_warning_lines + bint _hb_app_notification + object _order_override + + double _cancel_timestamp + double _create_timestamp + object _limit_order_type + bint _all_markets_ready + int _filled_buys_balance + int _filled_sells_balance + double _last_timestamp + double _status_report_interval + int64_t _logging_options + object _last_own_trade_price + bint _should_wait_order_cancel_confirmation + + object _moving_price_band + + cdef object c_get_mid_price(self) + cdef object c_create_base_proposal(self) + cdef tuple c_get_adjusted_available_balance(self, list orders) + cdef c_apply_order_levels_modifiers(self, object proposal) + cdef c_apply_price_band(self, object proposal) + cdef c_apply_ping_pong(self, object proposal) + cdef c_apply_order_price_modifiers(self, object proposal) + cdef c_apply_order_size_modifiers(self, object proposal) + cdef c_apply_inventory_skew(self, object proposal) + cdef c_apply_budget_constraint(self, object proposal) + + cdef c_filter_out_takers(self, object proposal) + cdef c_apply_order_optimization(self, object proposal) + cdef c_apply_add_transaction_costs(self, object proposal) + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices) + cdef c_cancel_active_orders(self, object proposal) + cdef c_cancel_orders_below_min_spread(self) + cdef c_cancel_active_orders_on_max_age_limit(self) + cdef bint c_to_create_orders(self, object proposal) + cdef c_execute_orders_proposal(self, object proposal) + cdef set_timers(self) + cdef c_apply_moving_price_band(self, object proposal) diff --git a/hummingbot/strategy/pure_market_making/pure_market_making.pyx b/hummingbot/strategy/pure_market_making/pure_market_making.pyx new file mode 100644 index 0000000..79ba080 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/pure_market_making.pyx @@ -0,0 +1,1329 @@ +import logging +from decimal import Decimal +from math import ceil, floor +from typing import Dict, List, Optional + +import numpy as np +import pandas as pd + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.asset_price_delegate cimport AssetPriceDelegate +from hummingbot.strategy.asset_price_delegate import AssetPriceDelegate +from hummingbot.strategy.hanging_orders_tracker import CreatedPairOfOrders, HangingOrdersTracker +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate cimport OrderBookAssetPriceDelegate +from hummingbot.strategy.strategy_base import StrategyBase +from hummingbot.strategy.utils import order_age +from .data_types import PriceSize, Proposal +from .inventory_cost_price_delegate import InventoryCostPriceDelegate +from .inventory_skew_calculator cimport c_calculate_bid_ask_ratios_from_base_asset_ratio +from .inventory_skew_calculator import calculate_total_order_size +from .pure_market_making_order_tracker import PureMarketMakingOrderTracker +from .moving_price_band import MovingPriceBand + + +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_neg_one = Decimal(-1) +pmm_logger = None + + +cdef class PureMarketMakingStrategy(StrategyBase): + OPTION_LOG_CREATE_ORDER = 1 << 3 + OPTION_LOG_MAKER_ORDER_FILLED = 1 << 4 + OPTION_LOG_STATUS_REPORT = 1 << 5 + OPTION_LOG_ALL = 0x7fffffffffffffff + + @classmethod + def logger(cls): + global pmm_logger + if pmm_logger is None: + pmm_logger = logging.getLogger(__name__) + return pmm_logger + + def init_params(self, + market_info: MarketTradingPairTuple, + bid_spread: Decimal, + ask_spread: Decimal, + order_amount: Decimal, + order_levels: int = 1, + order_level_spread: Decimal = s_decimal_zero, + order_level_amount: Decimal = s_decimal_zero, + order_refresh_time: float = 30.0, + max_order_age: float = 1800.0, + order_refresh_tolerance_pct: Decimal = s_decimal_neg_one, + filled_order_delay: float = 60.0, + inventory_skew_enabled: bool = False, + inventory_target_base_pct: Decimal = s_decimal_zero, + inventory_range_multiplier: Decimal = s_decimal_zero, + hanging_orders_enabled: bool = False, + hanging_orders_cancel_pct: Decimal = Decimal("0.1"), + order_optimization_enabled: bool = False, + ask_order_optimization_depth: Decimal = s_decimal_zero, + bid_order_optimization_depth: Decimal = s_decimal_zero, + add_transaction_costs_to_orders: bool = False, + asset_price_delegate: AssetPriceDelegate = None, + inventory_cost_price_delegate: InventoryCostPriceDelegate = None, + price_type: str = "mid_price", + take_if_crossed: bool = False, + price_ceiling: Decimal = s_decimal_neg_one, + price_floor: Decimal = s_decimal_neg_one, + ping_pong_enabled: bool = False, + logging_options: int = OPTION_LOG_ALL, + status_report_interval: float = 900, + minimum_spread: Decimal = Decimal(0), + hb_app_notification: bool = False, + order_override: Dict[str, List[str]] = None, + split_order_levels_enabled: bool = False, + bid_order_level_spreads: List[Decimal] = None, + ask_order_level_spreads: List[Decimal] = None, + should_wait_order_cancel_confirmation: bool = True, + moving_price_band: Optional[MovingPriceBand] = None + ): + if order_override is None: + order_override = {} + if moving_price_band is None: + moving_price_band = MovingPriceBand() + if price_ceiling != s_decimal_neg_one and price_ceiling < price_floor: + raise ValueError("Parameter price_ceiling cannot be lower than price_floor.") + self._sb_order_tracker = PureMarketMakingOrderTracker() + self._market_info = market_info + self._bid_spread = bid_spread + self._ask_spread = ask_spread + self._minimum_spread = minimum_spread + self._order_amount = order_amount + self._order_levels = order_levels + self._buy_levels = order_levels + self._sell_levels = order_levels + self._order_level_spread = order_level_spread + self._order_level_amount = order_level_amount + self._order_refresh_time = order_refresh_time + self._max_order_age = max_order_age + self._order_refresh_tolerance_pct = order_refresh_tolerance_pct + self._filled_order_delay = filled_order_delay + self._inventory_skew_enabled = inventory_skew_enabled + self._inventory_target_base_pct = inventory_target_base_pct + self._inventory_range_multiplier = inventory_range_multiplier + self._hanging_orders_enabled = hanging_orders_enabled + self._hanging_orders_tracker = HangingOrdersTracker(self, hanging_orders_cancel_pct) + self._order_optimization_enabled = order_optimization_enabled + self._ask_order_optimization_depth = ask_order_optimization_depth + self._bid_order_optimization_depth = bid_order_optimization_depth + self._add_transaction_costs_to_orders = add_transaction_costs_to_orders + self._asset_price_delegate = asset_price_delegate + self._inventory_cost_price_delegate = inventory_cost_price_delegate + self._price_type = self.get_price_type(price_type) + self._take_if_crossed = take_if_crossed + self._price_ceiling = price_ceiling + self._price_floor = price_floor + self._ping_pong_enabled = ping_pong_enabled + self._ping_pong_warning_lines = [] + self._hb_app_notification = hb_app_notification + self._order_override = order_override + self._split_order_levels_enabled=split_order_levels_enabled + self._bid_order_level_spreads=bid_order_level_spreads + self._ask_order_level_spreads=ask_order_level_spreads + self._cancel_timestamp = 0 + self._create_timestamp = 0 + self._limit_order_type = self._market_info.market.get_maker_order_type() + if take_if_crossed: + self._limit_order_type = OrderType.LIMIT + self._all_markets_ready = False + self._filled_buys_balance = 0 + self._filled_sells_balance = 0 + self._logging_options = logging_options + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._last_own_trade_price = Decimal('nan') + self._should_wait_order_cancel_confirmation = should_wait_order_cancel_confirmation + self._moving_price_band = moving_price_band + self.c_add_markets([market_info.market]) + + def all_markets_ready(self): + return all([market.ready for market in self._sb_markets]) + + @property + def market_info(self) -> MarketTradingPairTuple: + return self._market_info + + @property + def max_order_age(self) -> float: + return self._max_order_age + + @property + def minimum_spread(self) -> Decimal: + return self._minimum_spread + + @property + def ping_pong_enabled(self) -> bool: + return self._ping_pong_enabled + + @property + def ask_order_optimization_depth(self) -> Decimal: + return self._ask_order_optimization_depth + + @property + def bid_order_optimization_depth(self) -> Decimal: + return self._bid_order_optimization_depth + + @property + def price_type(self) -> PriceType: + return self._price_type + + @property + def order_refresh_tolerance_pct(self) -> Decimal: + return self._order_refresh_tolerance_pct + + @order_refresh_tolerance_pct.setter + def order_refresh_tolerance_pct(self, value: Decimal): + self._order_refresh_tolerance_pct = value + + @property + def order_amount(self) -> Decimal: + return self._order_amount + + @order_amount.setter + def order_amount(self, value: Decimal): + self._order_amount = value + + @property + def order_levels(self) -> int: + return self._order_levels + + @order_levels.setter + def order_levels(self, value: int): + self._order_levels = value + self._buy_levels = value + self._sell_levels = value + + @property + def buy_levels(self) -> int: + return self._buy_levels + + @buy_levels.setter + def buy_levels(self, value: int): + self._buy_levels = value + + @property + def sell_levels(self) -> int: + return self._sell_levels + + @sell_levels.setter + def sell_levels(self, value: int): + self._sell_levels = value + + @property + def order_level_amount(self) -> Decimal: + return self._order_level_amount + + @order_level_amount.setter + def order_level_amount(self, value: Decimal): + self._order_level_amount = value + + @property + def order_level_spread(self) -> Decimal: + return self._order_level_spread + + @order_level_spread.setter + def order_level_spread(self, value: Decimal): + self._order_level_spread = value + + @property + def inventory_skew_enabled(self) -> bool: + return self._inventory_skew_enabled + + @inventory_skew_enabled.setter + def inventory_skew_enabled(self, value: bool): + self._inventory_skew_enabled = value + + @property + def inventory_target_base_pct(self) -> Decimal: + return self._inventory_target_base_pct + + @inventory_target_base_pct.setter + def inventory_target_base_pct(self, value: Decimal): + self._inventory_target_base_pct = value + + @property + def inventory_range_multiplier(self) -> Decimal: + return self._inventory_range_multiplier + + @inventory_range_multiplier.setter + def inventory_range_multiplier(self, value: Decimal): + self._inventory_range_multiplier = value + + @property + def hanging_orders_enabled(self) -> bool: + return self._hanging_orders_enabled + + @hanging_orders_enabled.setter + def hanging_orders_enabled(self, value: bool): + self._hanging_orders_enabled = value + + @property + def hanging_orders_cancel_pct(self) -> Decimal: + return self._hanging_orders_tracker._hanging_orders_cancel_pct + + @hanging_orders_cancel_pct.setter + def hanging_orders_cancel_pct(self, value: Decimal): + self._hanging_orders_tracker._hanging_orders_cancel_pct = value + + @property + def bid_spread(self) -> Decimal: + return self._bid_spread + + @bid_spread.setter + def bid_spread(self, value: Decimal): + self._bid_spread = value + + @property + def ask_spread(self) -> Decimal: + return self._ask_spread + + @ask_spread.setter + def ask_spread(self, value: Decimal): + self._ask_spread = value + + @property + def order_optimization_enabled(self) -> bool: + return self._order_optimization_enabled + + @order_optimization_enabled.setter + def order_optimization_enabled(self, value: bool): + self._order_optimization_enabled = value + + @property + def order_refresh_time(self) -> float: + return self._order_refresh_time + + @order_refresh_time.setter + def order_refresh_time(self, value: float): + self._order_refresh_time = value + + @property + def filled_order_delay(self) -> float: + return self._filled_order_delay + + @filled_order_delay.setter + def filled_order_delay(self, value: float): + self._filled_order_delay = value + + @property + def add_transaction_costs_to_orders(self) -> bool: + return self._add_transaction_costs_to_orders + + @add_transaction_costs_to_orders.setter + def add_transaction_costs_to_orders(self, value: bool): + self._add_transaction_costs_to_orders = value + + @property + def price_ceiling(self) -> Decimal: + return self._price_ceiling + + @price_ceiling.setter + def price_ceiling(self, value: Decimal): + self._price_ceiling = value + + @property + def price_floor(self) -> Decimal: + return self._price_floor + + @price_floor.setter + def price_floor(self, value: Decimal): + self._price_floor = value + + @property + def base_asset(self): + return self._market_info.base_asset + + @property + def quote_asset(self): + return self._market_info.quote_asset + + @property + def trading_pair(self): + return self._market_info.trading_pair + + @property + def order_override(self): + return self._order_override + + @property + def split_order_levels_enabled(self): + return self._split_order_levels_enabled + + @property + def bid_order_level_spreads(self): + return self._bid_order_level_spreads + + @property + def ask_order_level_spreads(self): + return self._ask_order_level_spreads + + @order_override.setter + def order_override(self, value: Dict[str, List[str]]): + self._order_override = value + + @property + def moving_price_band_enabled(self) -> bool: + return self._moving_price_band.enabled + + @moving_price_band_enabled.setter + def moving_price_band_enabled(self, value: bool): + self._moving_price_band.switch(value) + + @property + def price_ceiling_pct(self) -> Decimal: + return self._moving_price_band.price_ceiling_pct + + @price_ceiling_pct.setter + def price_ceiling_pct(self, value: Decimal): + self._moving_price_band.price_ceiling_pct = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def price_floor_pct(self) -> Decimal: + return self._moving_price_band.price_floor_pct + + @price_floor_pct.setter + def price_floor_pct(self, value: Decimal): + self._moving_price_band.price_floor_pct = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def price_band_refresh_time(self) -> float: + return self._moving_price_band.price_band_refresh_time + + @price_band_refresh_time.setter + def price_band_refresh_time(self, value: Decimal): + self._moving_price_band.price_band_refresh_time = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def moving_price_band(self) -> MovingPriceBand: + return self._moving_price_band + + def get_price(self) -> Decimal: + price_provider = self._asset_price_delegate or self._market_info + if self._price_type is PriceType.LastOwnTrade: + price = self._last_own_trade_price + elif self._price_type is PriceType.InventoryCost: + price = price_provider.get_price_by_type(PriceType.MidPrice) + else: + price = price_provider.get_price_by_type(self._price_type) + + if price.is_nan(): + price = price_provider.get_price_by_type(PriceType.MidPrice) + + return price + + def get_mid_price(self) -> Decimal: + return self.c_get_mid_price() + + cdef object c_get_mid_price(self): + cdef: + AssetPriceDelegate delegate = self._asset_price_delegate + object mid_price + if self._asset_price_delegate is not None: + mid_price = delegate.c_get_mid_price() + else: + mid_price = self._market_info.get_mid_price() + return mid_price + + @property + def hanging_order_ids(self) -> List[str]: + return [o.order_id for o in self._hanging_orders_tracker.strategy_current_hanging_orders] + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @property + def active_orders(self) -> List[LimitOrder]: + if self._market_info not in self.market_info_to_active_orders: + return [] + return self.market_info_to_active_orders[self._market_info] + + @property + def active_buys(self) -> List[LimitOrder]: + return [o for o in self.active_orders if o.is_buy] + + @property + def active_sells(self) -> List[LimitOrder]: + return [o for o in self.active_orders if not o.is_buy] + + @property + def active_non_hanging_orders(self) -> List[LimitOrder]: + orders = [o for o in self.active_orders if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)] + return orders + + @property + def logging_options(self) -> int: + return self._logging_options + + @logging_options.setter + def logging_options(self, int64_t logging_options): + self._logging_options = logging_options + + @property + def hanging_orders_tracker(self): + return self._hanging_orders_tracker + + @property + def asset_price_delegate(self) -> AssetPriceDelegate: + return self._asset_price_delegate + + @asset_price_delegate.setter + def asset_price_delegate(self, value): + self._asset_price_delegate = value + + @property + def inventory_cost_price_delegate(self) -> AssetPriceDelegate: + return self._inventory_cost_price_delegate + + @inventory_cost_price_delegate.setter + def inventory_cost_price_delegate(self, value): + self._inventory_cost_price_delegate = value + + def inventory_skew_stats_data_frame(self) -> Optional[pd.DataFrame]: + cdef: + ExchangeBase market = self._market_info.market + + price = self.get_price() + base_asset_amount, quote_asset_amount = self.c_get_adjusted_available_balance(self.active_orders) + total_order_size = calculate_total_order_size(self._order_amount, self._order_level_amount, self._order_levels) + + base_asset_value = base_asset_amount * price + quote_asset_value = quote_asset_amount / price if price > s_decimal_zero else s_decimal_zero + total_value = base_asset_amount + quote_asset_value + total_value_in_quote = (base_asset_amount * price) + quote_asset_amount + + base_asset_ratio = (base_asset_amount / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + quote_asset_ratio = Decimal("1") - base_asset_ratio if total_value > 0 else 0 + target_base_ratio = self._inventory_target_base_pct + inventory_range_multiplier = self._inventory_range_multiplier + target_base_amount = (total_value * target_base_ratio + if price > s_decimal_zero + else s_decimal_zero) + target_base_amount_in_quote = target_base_ratio * total_value_in_quote + target_quote_amount = (1 - target_base_ratio) * total_value_in_quote + + base_asset_range = total_order_size * self._inventory_range_multiplier + base_asset_range = min(base_asset_range, total_value * Decimal("0.5")) + high_water_mark = target_base_amount + base_asset_range + low_water_mark = max(target_base_amount - base_asset_range, s_decimal_zero) + low_water_mark_ratio = (low_water_mark / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + high_water_mark_ratio = (high_water_mark / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + high_water_mark_ratio = min(1.0, high_water_mark_ratio) + total_order_size_ratio = (self._order_amount * Decimal("2") / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + bid_ask_ratios = c_calculate_bid_ask_ratios_from_base_asset_ratio( + float(base_asset_amount), + float(quote_asset_amount), + float(price), + float(target_base_ratio), + float(base_asset_range) + ) + inventory_skew_df = pd.DataFrame(data=[ + [f"Target Value ({self.quote_asset})", f"{target_base_amount_in_quote:.4f}", + f"{target_quote_amount:.4f}"], + ["Current %", f"{base_asset_ratio:.1%}", f"{quote_asset_ratio:.1%}"], + ["Target %", f"{target_base_ratio:.1%}", f"{1 - target_base_ratio:.1%}"], + ["Inventory Range", f"{low_water_mark_ratio:.1%} - {high_water_mark_ratio:.1%}", + f"{1 - high_water_mark_ratio:.1%} - {1 - low_water_mark_ratio:.1%}"], + ["Order Adjust %", f"{bid_ask_ratios.bid_ratio:.1%}", f"{bid_ask_ratios.ask_ratio:.1%}"] + ]) + return inventory_skew_df + + def pure_mm_assets_df(self, to_show_current_pct: bool) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self._market_info.get_mid_price() + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + base_value = base_balance * float(price) + total_in_quote = base_value + quote_balance + base_ratio = base_value / total_in_quote if total_in_quote > 0 else 0 + quote_ratio = quote_balance / total_in_quote if total_in_quote > 0 else 0 + data=[ + ["", base_asset, quote_asset], + ["Total Balance", round(base_balance, 4), round(quote_balance, 4)], + ["Available Balance", round(available_base_balance, 4), round(available_quote_balance, 4)], + [f"Current Value ({quote_asset})", round(base_value, 4), round(quote_balance, 4)] + ] + if to_show_current_pct: + data.append(["Current %", f"{base_ratio:.1%}", f"{quote_ratio:.1%}"]) + df = pd.DataFrame(data=data) + return df + + def active_orders_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self.get_price() + active_orders = self.active_orders + no_sells = len([o for o in active_orders if not o.is_buy and o.client_order_id and + not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)]) + active_orders.sort(key=lambda x: x.price, reverse=True) + columns = ["Level", "Type", "Price", "Spread", "Amount (Orig)", "Amount (Adj)", "Age"] + data = [] + lvl_buy, lvl_sell = 0, 0 + for idx in range(0, len(active_orders)): + order = active_orders[idx] + is_hanging_order = self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id) + amount_orig = "" + if not is_hanging_order: + if order.is_buy: + level = lvl_buy + 1 + lvl_buy += 1 + else: + level = no_sells - lvl_sell + lvl_sell += 1 + amount_orig = self._order_amount + ((level - 1) * self._order_level_amount) + else: + level_for_calculation = lvl_buy if order.is_buy else lvl_sell + amount_orig = self._order_amount + ((level_for_calculation - 1) * self._order_level_amount) + level = "hang" + spread = 0 if price == 0 else abs(order.price - price)/price + age = pd.Timestamp(order_age(order, self._current_timestamp), unit='s').strftime('%H:%M:%S') + data.append([ + level, + "buy" if order.is_buy else "sell", + float(order.price), + f"{spread:.2%}", + amount_orig, + float(order.quantity), + age + ]) + + return pd.DataFrame(data=data, columns=columns) + + def market_status_data_frame(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> pd.DataFrame: + markets_data = [] + markets_columns = ["Exchange", "Market", "Best Bid", "Best Ask", f"Ref Price ({self._price_type.name})"] + if self._price_type is PriceType.LastOwnTrade and self._last_own_trade_price.is_nan(): + markets_columns[-1] = "Ref Price (MidPrice)" + market_books = [(self._market_info.market, self._market_info.trading_pair)] + if type(self._asset_price_delegate) is OrderBookAssetPriceDelegate: + market_books.append((self._asset_price_delegate.market, self._asset_price_delegate.trading_pair)) + for market, trading_pair in market_books: + bid_price = market.get_price(trading_pair, False) + ask_price = market.get_price(trading_pair, True) + ref_price = float("nan") + if market == self._market_info.market and self._inventory_cost_price_delegate is not None: + # We're using inventory_cost, show it's price + ref_price = self._inventory_cost_price_delegate.get_price() + if ref_price is None: + ref_price = self.get_price() + elif market == self._market_info.market and self._asset_price_delegate is None: + ref_price = self.get_price() + elif ( + self._asset_price_delegate is not None + and market == self._asset_price_delegate.market + and self._price_type is not PriceType.LastOwnTrade + ): + ref_price = self._asset_price_delegate.get_price_by_type(self._price_type) + markets_data.append([ + market.display_name, + trading_pair, + float(bid_price), + float(ask_price), + float(ref_price) + ]) + return pd.DataFrame(data=markets_data, columns=markets_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + if not self._all_markets_ready: + return "Market connectors are not ready." + cdef: + list lines = [] + list warning_lines = [] + warning_lines.extend(self._ping_pong_warning_lines) + warning_lines.extend(self.network_warning([self._market_info])) + + markets_df = map_df_to_str(self.market_status_data_frame([self._market_info])) + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.pure_mm_assets_df(not self._inventory_skew_enabled)) + # append inventory skew stats. + if self._inventory_skew_enabled: + inventory_skew_df = map_df_to_str(self.inventory_skew_stats_data_frame()) + assets_df = assets_df.append(inventory_skew_df) + + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + # See if there're any open orders. + if len(self.active_orders) > 0: + df = map_df_to_str(self.active_orders_df()) + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning([self._market_info])) + + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + # The following exposed Python functions are meant for unit tests + # --------------------------------------------------------------- + def execute_orders_proposal(self, proposal: Proposal): + return self.c_execute_orders_proposal(proposal) + + def cancel_order(self, order_id: str): + return self.c_cancel_order(self._market_info, order_id) + + # --------------------------------------------------------------- + + cdef c_start(self, Clock clock, double timestamp): + StrategyBase.c_start(self, clock, timestamp) + self._last_timestamp = timestamp + + self._hanging_orders_tracker.register_events(self.active_markets) + + if self._hanging_orders_enabled: + # start tracking any restored limit order + restored_order_ids = self.c_track_restored_orders(self.market_info) + # make restored order hanging orders + for order_id in restored_order_ids: + order = next(o for o in self.market_info.market.limit_orders if o.client_order_id == order_id) + if order: + self._hanging_orders_tracker.add_as_hanging_order(order) + + cdef c_stop(self, Clock clock): + self._hanging_orders_tracker.unregister_events(self.active_markets) + StrategyBase.c_stop(self, clock) + + cdef c_tick(self, double timestamp): + StrategyBase.c_tick(self, timestamp) + + cdef: + int64_t current_tick = (timestamp // self._status_report_interval) + int64_t last_tick = (self._last_timestamp // self._status_report_interval) + bint should_report_warnings = ((current_tick > last_tick) and + (self._logging_options & self.OPTION_LOG_STATUS_REPORT)) + cdef object proposal + try: + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self._sb_markets]) + if self._asset_price_delegate is not None and self._all_markets_ready: + self._all_markets_ready = self._asset_price_delegate.ready + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning(f"Markets are not ready. No market making trades are permitted.") + return + + if should_report_warnings: + if not all([market.network_status is NetworkStatus.CONNECTED for market in self._sb_markets]): + self.logger().warning(f"WARNING: Some markets are not connected or are down at the moment. Market " + f"making may be dangerous when markets or networks are unstable.") + + proposal = None + if self._create_timestamp <= self._current_timestamp: + # 1. Create base order proposals + proposal = self.c_create_base_proposal() + # 2. Apply functions that limit numbers of buys and sells proposal + self.c_apply_order_levels_modifiers(proposal) + # 3. Apply functions that modify orders price + self.c_apply_order_price_modifiers(proposal) + # 4. Apply functions that modify orders size + self.c_apply_order_size_modifiers(proposal) + # 5. Apply budget constraint, i.e. can't buy/sell more than what you have. + self.c_apply_budget_constraint(proposal) + + if not self._take_if_crossed: + self.c_filter_out_takers(proposal) + + self._hanging_orders_tracker.process_tick() + + self.c_cancel_active_orders_on_max_age_limit() + self.c_cancel_active_orders(proposal) + self.c_cancel_orders_below_min_spread() + if self.c_to_create_orders(proposal): + self.c_execute_orders_proposal(proposal) + finally: + self._last_timestamp = timestamp + + cdef object c_create_base_proposal(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list sells = [] + + buy_reference_price = sell_reference_price = self.get_price() + + if self._inventory_cost_price_delegate is not None: + inventory_cost_price = self._inventory_cost_price_delegate.get_price() + if inventory_cost_price is not None: + # Only limit sell price. Buy are always allowed. + sell_reference_price = max(inventory_cost_price, sell_reference_price) + else: + base_balance = float(market.get_balance(self._market_info.base_asset)) + if base_balance > 0: + raise RuntimeError("Initial inventory price is not set while inventory_cost feature is active.") + + # First to check if a customized order override is configured, otherwise the proposal will be created according + # to order spread, amount, and levels setting. + order_override = self._order_override + if order_override is not None and len(order_override) > 0: + for key, value in order_override.items(): + if str(value[0]) in ["buy", "sell"]: + if str(value[0]) == "buy" and not buy_reference_price.is_nan(): + price = buy_reference_price * (Decimal("1") - Decimal(str(value[1])) / Decimal("100")) + price = market.c_quantize_order_price(self.trading_pair, price) + size = Decimal(str(value[2])) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0 and price > 0: + buys.append(PriceSize(price, size)) + elif str(value[0]) == "sell" and not sell_reference_price.is_nan(): + price = sell_reference_price * (Decimal("1") + Decimal(str(value[1])) / Decimal("100")) + price = market.c_quantize_order_price(self.trading_pair, price) + size = Decimal(str(value[2])) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0 and price > 0: + sells.append(PriceSize(price, size)) + else: + if not buy_reference_price.is_nan(): + for level in range(0, self._buy_levels): + price = buy_reference_price * (Decimal("1") - self._bid_spread - (level * self._order_level_spread)) + price = market.c_quantize_order_price(self.trading_pair, price) + size = self._order_amount + (self._order_level_amount * level) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0: + buys.append(PriceSize(price, size)) + if not sell_reference_price.is_nan(): + for level in range(0, self._sell_levels): + price = sell_reference_price * (Decimal("1") + self._ask_spread + (level * self._order_level_spread)) + price = market.c_quantize_order_price(self.trading_pair, price) + size = self._order_amount + (self._order_level_amount * level) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0: + sells.append(PriceSize(price, size)) + + return Proposal(buys, sells) + + cdef tuple c_get_adjusted_available_balance(self, list orders): + """ + Calculates the available balance, plus the amount attributed to orders. + :return: (base amount, quote amount) in Decimal + """ + cdef: + ExchangeBase market = self._market_info.market + object base_balance = market.c_get_available_balance(self.base_asset) + object quote_balance = market.c_get_available_balance(self.quote_asset) + + for order in orders: + if order.is_buy: + quote_balance += order.quantity * order.price + else: + base_balance += order.quantity + + return base_balance, quote_balance + + cdef c_apply_order_levels_modifiers(self, proposal): + self.c_apply_price_band(proposal) + if self.moving_price_band_enabled: + self.c_apply_moving_price_band(proposal) + if self._ping_pong_enabled: + self.c_apply_ping_pong(proposal) + + cdef c_apply_price_band(self, proposal): + if self._price_ceiling > 0 and self.get_price() >= self._price_ceiling: + proposal.buys = [] + if self._price_floor > 0 and self.get_price() <= self._price_floor: + proposal.sells = [] + + cdef c_apply_moving_price_band(self, proposal): + price = self.get_price() + self._moving_price_band.check_and_update_price_band( + self.current_timestamp, price) + if self._moving_price_band.check_price_ceiling_exceeded(price): + proposal.buys = [] + if self._moving_price_band.check_price_floor_exceeded(price): + proposal.sells = [] + + cdef c_apply_ping_pong(self, object proposal): + self._ping_pong_warning_lines = [] + if self._filled_buys_balance == self._filled_sells_balance: + self._filled_buys_balance = self._filled_sells_balance = 0 + if self._filled_buys_balance > 0: + proposal.buys = proposal.buys[self._filled_buys_balance:] + self._ping_pong_warning_lines.extend( + [f" Ping-pong removed {self._filled_buys_balance} buy orders."] + ) + if self._filled_sells_balance > 0: + proposal.sells = proposal.sells[self._filled_sells_balance:] + self._ping_pong_warning_lines.extend( + [f" Ping-pong removed {self._filled_sells_balance} sell orders."] + ) + + cdef c_apply_order_price_modifiers(self, object proposal): + if self._order_optimization_enabled: + self.c_apply_order_optimization(proposal) + + if self._add_transaction_costs_to_orders: + self.c_apply_add_transaction_costs(proposal) + + cdef c_apply_order_size_modifiers(self, object proposal): + if self._inventory_skew_enabled: + self.c_apply_inventory_skew(proposal) + + cdef c_apply_inventory_skew(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object bid_adj_ratio + object ask_adj_ratio + object size + + base_balance, quote_balance = self.c_get_adjusted_available_balance(self.active_orders) + + total_order_size = calculate_total_order_size(self._order_amount, self._order_level_amount, self._order_levels) + bid_ask_ratios = c_calculate_bid_ask_ratios_from_base_asset_ratio( + float(base_balance), + float(quote_balance), + float(self.get_price()), + float(self._inventory_target_base_pct), + float(total_order_size * self._inventory_range_multiplier) + ) + bid_adj_ratio = Decimal(bid_ask_ratios.bid_ratio) + ask_adj_ratio = Decimal(bid_ask_ratios.ask_ratio) + + for buy in proposal.buys: + size = buy.size * bid_adj_ratio + size = market.c_quantize_order_amount(self.trading_pair, size) + buy.size = size + + for sell in proposal.sells: + size = sell.size * ask_adj_ratio + size = market.c_quantize_order_amount(self.trading_pair, size, sell.price) + sell.size = size + + def adjusted_available_balance_for_orders_budget_constrain(self): + candidate_hanging_orders = self.hanging_orders_tracker.candidate_hanging_orders_from_pairs() + non_hanging = [] + if self.market_info in self._sb_order_tracker.get_limit_orders(): + all_orders = self._sb_order_tracker.get_limit_orders()[self.market_info].values() + non_hanging = [order for order in all_orders + if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id)] + all_non_hanging_orders = list(set(non_hanging) - set(candidate_hanging_orders)) + return self.c_get_adjusted_available_balance(all_non_hanging_orders) + + cdef c_apply_budget_constraint(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object quote_size + object base_size + object adjusted_amount + + base_balance, quote_balance = self.adjusted_available_balance_for_orders_budget_constrain() + + for buy in proposal.buys: + buy_fee = market.c_get_fee(self.base_asset, self.quote_asset, OrderType.LIMIT, TradeType.BUY, + buy.size, buy.price) + quote_size = buy.size * buy.price * (Decimal(1) + buy_fee.percent) + + # Adjust buy order size to use remaining balance if less than the order amount + if quote_balance < quote_size: + adjusted_amount = quote_balance / (buy.price * (Decimal("1") + buy_fee.percent)) + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, adjusted_amount) + buy.size = adjusted_amount + quote_balance = s_decimal_zero + elif quote_balance == s_decimal_zero: + buy.size = s_decimal_zero + else: + quote_balance -= quote_size + + proposal.buys = [o for o in proposal.buys if o.size > 0] + + for sell in proposal.sells: + base_size = sell.size + + # Adjust sell order size to use remaining balance if less than the order amount + if base_balance < base_size: + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, base_balance) + sell.size = adjusted_amount + base_balance = s_decimal_zero + elif base_balance == s_decimal_zero: + sell.size = s_decimal_zero + else: + base_balance -= base_size + + proposal.sells = [o for o in proposal.sells if o.size > 0] + + cdef c_filter_out_takers(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + list new_buys = [] + list new_sells = [] + top_ask = market.c_get_price(self.trading_pair, True) + if not top_ask.is_nan(): + proposal.buys = [buy for buy in proposal.buys if buy.price < top_ask] + top_bid = market.c_get_price(self.trading_pair, False) + if not top_bid.is_nan(): + proposal.sells = [sell for sell in proposal.sells if sell.price > top_bid] + + # Compare the market price with the top bid and top ask price + cdef c_apply_order_optimization(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object own_buy_size = s_decimal_zero + object own_sell_size = s_decimal_zero + + for order in self.active_orders: + if order.is_buy: + own_buy_size = order.quantity + else: + own_sell_size = order.quantity + + if len(proposal.buys) > 0: + # Get the top bid price in the market using order_optimization_depth and your buy order volume + top_bid_price = self._market_info.get_price_for_volume( + False, self._bid_order_optimization_depth + own_buy_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_bid_price + ) + # Get the price above the top bid + price_above_bid = (ceil(top_bid_price / price_quantum) + 1) * price_quantum + + # If the price_above_bid is lower than the price suggested by the top pricing proposal, + # lower the price and from there apply the order_level_spread to each order in the next levels + proposal.buys = sorted(proposal.buys, key = lambda p: p.price, reverse = True) + lower_buy_price = min(proposal.buys[0].price, price_above_bid) + for i, proposed in enumerate(proposal.buys): + if self._split_order_levels_enabled: + proposal.buys[i].price = (market.c_quantize_order_price(self.trading_pair, lower_buy_price) + * (1 - self._bid_order_level_spreads[i] / Decimal("100")) + / (1-self._bid_order_level_spreads[0] / Decimal("100"))) + continue + proposal.buys[i].price = market.c_quantize_order_price(self.trading_pair, lower_buy_price) * (1 - self.order_level_spread * i) + + if len(proposal.sells) > 0: + # Get the top ask price in the market using order_optimization_depth and your sell order volume + top_ask_price = self._market_info.get_price_for_volume( + True, self._ask_order_optimization_depth + own_sell_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_ask_price + ) + # Get the price below the top ask + price_below_ask = (floor(top_ask_price / price_quantum) - 1) * price_quantum + + # If the price_below_ask is higher than the price suggested by the pricing proposal, + # increase your price and from there apply the order_level_spread to each order in the next levels + proposal.sells = sorted(proposal.sells, key = lambda p: p.price) + higher_sell_price = max(proposal.sells[0].price, price_below_ask) + for i, proposed in enumerate(proposal.sells): + if self._split_order_levels_enabled: + proposal.sells[i].price = (market.c_quantize_order_price(self.trading_pair, higher_sell_price) + * (1 + self._ask_order_level_spreads[i] / Decimal("100")) + / (1 + self._ask_order_level_spreads[0] / Decimal("100"))) + continue + proposal.sells[i].price = market.c_quantize_order_price(self.trading_pair, higher_sell_price) * (1 + self.order_level_spread * i) + + cdef object c_apply_add_transaction_costs(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + for buy in proposal.buys: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.BUY, buy.size, buy.price) + price = buy.price * (Decimal(1) - fee.percent) + buy.price = market.c_quantize_order_price(self.trading_pair, price) + for sell in proposal.sells: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.SELL, sell.size, sell.price) + price = sell.price * (Decimal(1) + fee.percent) + sell.price = market.c_quantize_order_price(self.trading_pair, price) + + cdef c_did_fill_order(self, object order_filled_event): + cdef: + str order_id = order_filled_event.order_id + object market_info = self._sb_order_tracker.c_get_shadow_market_pair_from_order_id(order_id) + tuple order_fill_record + + if market_info is not None: + limit_order_record = self._sb_order_tracker.c_get_shadow_limit_order(order_id) + order_fill_record = (limit_order_record, order_filled_event) + + if order_filled_event.trade_type is TradeType.BUY: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker buy order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + else: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker sell order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + + if self._inventory_cost_price_delegate is not None: + self._inventory_cost_price_delegate.process_order_fill_event(order_filled_event) + + cdef c_did_complete_buy_order(self, object order_completed_event): + cdef: + str order_id = order_completed_event.order_id + limit_order_record = self._sb_order_tracker.c_get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + active_sell_ids = [x.client_order_id for x in self.active_orders if not x.is_buy] + + if self._hanging_orders_enabled: + # If the filled order is a hanging order, do nothing + if order_id in self.hanging_order_ids: + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Hanging maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Hanging maker BUY order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + return + + # delay order creation by filled_order_dalay (in seconds) + self._create_timestamp = self._current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._filled_buys_balance += 1 + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker BUY order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + cdef c_did_complete_sell_order(self, object order_completed_event): + cdef: + str order_id = order_completed_event.order_id + LimitOrder limit_order_record = self._sb_order_tracker.c_get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + active_buy_ids = [x.client_order_id for x in self.active_orders if x.is_buy] + if self._hanging_orders_enabled: + # If the filled order is a hanging order, do nothing + if order_id in self.hanging_order_ids: + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Hanging maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Hanging maker SELL order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + return + + # delay order creation by filled_order_dalay (in seconds) + self._create_timestamp = self._current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._filled_sells_balance += 1 + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker SELL order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices): + if len(current_prices) != len(proposal_prices): + return False + current_prices = sorted(current_prices) + proposal_prices = sorted(proposal_prices) + for current, proposal in zip(current_prices, proposal_prices): + # if spread diff is more than the tolerance or order quantities are different, return false. + if abs(proposal - current)/current > self._order_refresh_tolerance_pct: + return False + return True + + cdef c_cancel_active_orders_on_max_age_limit(self): + """ + Cancels active non hanging orders if they are older than max age limit + """ + cdef: + list active_orders = self.active_non_hanging_orders + + if active_orders and any(order_age(o, self._current_timestamp) > self._max_order_age for o in active_orders): + for order in active_orders: + self.c_cancel_order(self._market_info, order.client_order_id) + + cdef c_cancel_active_orders(self, object proposal): + """ + Cancels active non hanging orders, checks if the order prices are within tolerance threshold + """ + if self._cancel_timestamp > self._current_timestamp: + return + + cdef: + list active_orders = self.active_non_hanging_orders + list active_buy_prices = [] + list active_sells = [] + bint to_defer_canceling = False + if len(active_orders) == 0: + return + if proposal is not None and \ + self._order_refresh_tolerance_pct >= 0: + + active_buy_prices = [Decimal(str(o.price)) for o in active_orders if o.is_buy] + active_sell_prices = [Decimal(str(o.price)) for o in active_orders if not o.is_buy] + proposal_buys = [buy.price for buy in proposal.buys] + proposal_sells = [sell.price for sell in proposal.sells] + + if self.c_is_within_tolerance(active_buy_prices, proposal_buys) and \ + self.c_is_within_tolerance(active_sell_prices, proposal_sells): + to_defer_canceling = True + + if not to_defer_canceling: + self._hanging_orders_tracker.update_strategy_orders_with_equivalent_orders() + for order in self.active_non_hanging_orders: + # If is about to be added to hanging_orders then don't cancel + if not self._hanging_orders_tracker.is_potential_hanging_order(order): + self.c_cancel_order(self._market_info, order.client_order_id) + # else: + # self.set_timers() + + # Cancel Non-Hanging, Active Orders if Spreads are below minimum_spread + cdef c_cancel_orders_below_min_spread(self): + cdef: + list active_orders = self.market_info_to_active_orders.get(self._market_info, []) + object price = self.get_price() + active_orders = [order for order in active_orders + if order.client_order_id not in self.hanging_order_ids] + for order in active_orders: + negation = -1 if order.is_buy else 1 + if (negation * (order.price - price) / price) < self._minimum_spread: + self.logger().info(f"Order is below minimum spread ({self._minimum_spread})." + f" Canceling Order: ({'Buy' if order.is_buy else 'Sell'}) " + f"ID - {order.client_order_id}") + self.c_cancel_order(self._market_info, order.client_order_id) + + cdef bint c_to_create_orders(self, object proposal): + non_hanging_orders_non_cancelled = [o for o in self.active_non_hanging_orders if not + self._hanging_orders_tracker.is_potential_hanging_order(o)] + return (self._create_timestamp < self._current_timestamp + and (not self._should_wait_order_cancel_confirmation or + len(self._sb_order_tracker.in_flight_cancels) == 0) + and proposal is not None + and len(non_hanging_orders_non_cancelled) == 0) + + cdef c_execute_orders_proposal(self, object proposal): + cdef: + double expiration_seconds = NaN + str bid_order_id, ask_order_id + bint orders_created = False + # Number of pair of orders to track for hanging orders + number_of_pairs = min((len(proposal.buys), len(proposal.sells))) if self._hanging_orders_enabled else 0 + + if len(proposal.buys) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{buy.size.normalize()} {self.base_asset}, " + f"{buy.price.normalize()} {self.quote_asset}" + for buy in proposal.buys] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.buys)} bid orders " + f"at (Size, Price): {price_quote_str}" + ) + for idx, buy in enumerate(proposal.buys): + bid_order_id = self.c_buy_with_specific_market( + self._market_info, + buy.size, + order_type=self._limit_order_type, + price=buy.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == bid_order_id)) + if order: + self._hanging_orders_tracker.add_current_pairs_of_proposal_orders_executed_by_strategy( + CreatedPairOfOrders(order, None)) + if len(proposal.sells) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{sell.size.normalize()} {self.base_asset}, " + f"{sell.price.normalize()} {self.quote_asset}" + for sell in proposal.sells] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.sells)} ask " + f"orders at (Size, Price): {price_quote_str}" + ) + for idx, sell in enumerate(proposal.sells): + ask_order_id = self.c_sell_with_specific_market( + self._market_info, + sell.size, + order_type=self._limit_order_type, + price=sell.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == ask_order_id)) + if order: + self._hanging_orders_tracker.current_created_pairs_of_orders[idx].sell_order = order + if orders_created: + self.set_timers() + + cdef set_timers(self): + cdef double next_cycle = self._current_timestamp + self._order_refresh_time + if self._create_timestamp <= self._current_timestamp: + self._create_timestamp = next_cycle + if self._cancel_timestamp <= self._current_timestamp: + self._cancel_timestamp = min(self._create_timestamp, next_cycle) + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + def get_price_type(self, price_type_str: str) -> PriceType: + if price_type_str == "mid_price": + return PriceType.MidPrice + elif price_type_str == "best_bid": + return PriceType.BestBid + elif price_type_str == "best_ask": + return PriceType.BestAsk + elif price_type_str == "last_price": + return PriceType.LastTrade + elif price_type_str == 'last_own_trade_price': + return PriceType.LastOwnTrade + elif price_type_str == 'inventory_cost': + return PriceType.InventoryCost + elif price_type_str == "custom": + return PriceType.Custom + else: + raise ValueError(f"Unrecognized price type string {price_type_str}.") diff --git a/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py b/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py new file mode 100644 index 0000000..180f92e --- /dev/null +++ b/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py @@ -0,0 +1,448 @@ +import decimal +from decimal import Decimal +from typing import Optional + +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_connector, + validate_decimal, + validate_exchange, + validate_int, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges + + +def maker_trading_pair_prompt(): + exchange = pure_market_making_config_map.get("exchange").value + example = AllConnectorSettings.get_example_pairs().get(exchange) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (exchange, f" (e.g. {example})" if example else "") + + +# strategy specific validators +def validate_exchange_trading_pair(value: str) -> Optional[str]: + exchange = pure_market_making_config_map.get("exchange").value + return validate_market_trading_pair(exchange, value) + + +def order_amount_prompt() -> str: + trading_pair = pure_market_making_config_map["market"].value + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order? >>> " + + +def validate_price_source(value: str) -> Optional[str]: + if value not in {"current_market", "external_market", "custom_api"}: + return "Invalid price source type." + + +def on_validate_price_source(value: str): + if value != "external_market": + pure_market_making_config_map["price_source_exchange"].value = None + pure_market_making_config_map["price_source_market"].value = None + pure_market_making_config_map["take_if_crossed"].value = None + if value != "custom_api": + pure_market_making_config_map["price_source_custom_api"].value = None + else: + pure_market_making_config_map["price_type"].value = "custom" + + +def price_source_market_prompt() -> str: + external_market = pure_market_making_config_map.get("price_source_exchange").value + return f'Enter the token trading pair on {external_market} >>> ' + + +def validate_price_source_exchange(value: str) -> Optional[str]: + if value == pure_market_making_config_map.get("exchange").value: + return "Price source exchange cannot be the same as maker exchange." + return validate_connector(value) + + +def on_validated_price_source_exchange(value: str): + if value is None: + pure_market_making_config_map["price_source_market"].value = None + + +def validate_price_source_market(value: str) -> Optional[str]: + market = pure_market_making_config_map.get("price_source_exchange").value + return validate_market_trading_pair(market, value) + + +def validate_price_floor_ceiling(value: str) -> Optional[str]: + try: + decimal_value = Decimal(value) + except Exception: + return f"{value} is not in decimal format." + if not (decimal_value == Decimal("-1") or decimal_value > Decimal("0")): + return "Value must be more than 0 or -1 to disable this feature." + + +def validate_price_type(value: str) -> Optional[str]: + error = None + price_source = pure_market_making_config_map.get("price_source").value + if price_source != "custom_api": + valid_values = {"mid_price", + "last_price", + "last_own_trade_price", + "best_bid", + "best_ask", + "inventory_cost", + } + if value not in valid_values: + error = "Invalid price type." + elif value != "custom": + error = "Invalid price type." + return error + + +def on_validated_price_type(value: str): + if value == 'inventory_cost': + pure_market_making_config_map["inventory_price"].value = None + + +def exchange_on_validated(value: str): + required_exchanges.add(value) + + +def validate_decimal_list(value: str) -> Optional[str]: + decimal_list = list(value.split(",")) + for number in decimal_list: + try: + validate_result = validate_decimal(Decimal(number), 0, 100, inclusive=False) + except decimal.InvalidOperation: + return "Please enter valid decimal numbers" + if validate_result is not None: + return validate_result + + +pure_market_making_config_map = { + "strategy": + ConfigVar(key="strategy", + prompt=None, + default="pure_market_making"), + "exchange": + ConfigVar(key="exchange", + prompt="Enter your maker spot connector >>> ", + validator=validate_exchange, + on_validated=exchange_on_validated, + prompt_on_new=True), + "market": + ConfigVar(key="market", + prompt=maker_trading_pair_prompt, + validator=validate_exchange_trading_pair, + prompt_on_new=True), + "bid_spread": + ConfigVar(key="bid_spread", + prompt="How far away from the mid price do you want to place the " + "first bid order? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "ask_spread": + ConfigVar(key="ask_spread", + prompt="How far away from the mid price do you want to place the " + "first ask order? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "minimum_spread": + ConfigVar(key="minimum_spread", + prompt="At what minimum spread should the bot automatically cancel orders? (Enter 1 for 1%) >>> ", + required_if=lambda: False, + type_str="decimal", + default=Decimal(-100), + validator=lambda v: validate_decimal(v, -100, 100, True)), + "order_refresh_time": + ConfigVar(key="order_refresh_time", + prompt="How often do you want to cancel and replace bids and asks " + "(in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True), + "max_order_age": + ConfigVar(key="max_order_age", + prompt="How long do you want to cancel and replace bids and asks " + "with the same price (in seconds)? >>> ", + type_str="float", + default=Decimal("1800"), + validator=lambda v: validate_decimal(v, 0, inclusive=False)), + "order_refresh_tolerance_pct": + ConfigVar(key="order_refresh_tolerance_pct", + prompt="Enter the percent change in price needed to refresh orders at each cycle " + "(Enter 1 to indicate 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, -10, 10, inclusive=True)), + "order_amount": + ConfigVar(key="order_amount", + prompt=order_amount_prompt, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=False), + prompt_on_new=True), + "price_ceiling": + ConfigVar(key="price_ceiling", + prompt="Enter the price point above which only sell orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "price_floor": + ConfigVar(key="price_floor", + prompt="Enter the price below which only buy orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "moving_price_band_enabled": + ConfigVar(key="moving_price_band_enabled", + prompt="Would you like to enable moving price floor and ceiling? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "price_ceiling_pct": + ConfigVar(key="price_ceiling_pct", + prompt="Enter a percentage to the current price that sets the price ceiling. Above this price, only sell orders will be placed >>> ", + type_str="decimal", + default=Decimal("1"), + required_if=lambda: pure_market_making_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "price_floor_pct": + ConfigVar(key="price_floor_pct", + prompt="Enter a percentage to the current price that sets the price floor. Below this price, only buy orders will be placed >>> ", + type_str="decimal", + default=Decimal("-1"), + required_if=lambda: pure_market_making_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "price_band_refresh_time": + ConfigVar(key="price_band_refresh_time", + prompt="After this amount of time (in seconds), the price bands are reset based on the current price >>> ", + type_str="float", + default=86400, + required_if=lambda: pure_market_making_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "ping_pong_enabled": + ConfigVar(key="ping_pong_enabled", + prompt="Would you like to use the ping pong feature and alternate between buy and sell orders after fills? (Yes/No) >>> ", + type_str="bool", + default=False, + prompt_on_new=True, + validator=validate_bool), + "order_levels": + ConfigVar(key="order_levels", + prompt="How many orders do you want to place on both sides? >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=-1, inclusive=False), + default=1), + "order_level_amount": + ConfigVar(key="order_level_amount", + prompt="How much do you want to increase or decrease the order size for each " + "additional order? (decrease < 0 > increase) >>> ", + required_if=lambda: pure_market_making_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v), + default=0), + "order_level_spread": + ConfigVar(key="order_level_spread", + prompt="Enter the price increments (as percentage) for subsequent " + "orders? (Enter 1 to indicate 1%) >>> ", + required_if=lambda: pure_market_making_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + default=Decimal("1")), + "inventory_skew_enabled": + ConfigVar(key="inventory_skew_enabled", + prompt="Would you like to enable inventory skew? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "inventory_target_base_pct": + ConfigVar(key="inventory_target_base_pct", + prompt="What is your target base asset percentage? Enter 50 for 50% >>> ", + required_if=lambda: pure_market_making_config_map.get("inventory_skew_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100), + default=Decimal("50")), + "inventory_range_multiplier": + ConfigVar(key="inventory_range_multiplier", + prompt="What is your tolerable range of inventory around the target, " + "expressed in multiples of your total order size? ", + required_if=lambda: pure_market_making_config_map.get("inventory_skew_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=Decimal("1")), + "inventory_price": + ConfigVar(key="inventory_price", + prompt="What is the price of your base asset inventory? ", + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=True), + required_if=lambda: pure_market_making_config_map.get("price_type").value == "inventory_cost", + default=Decimal("1"), + ), + "filled_order_delay": + ConfigVar(key="filled_order_delay", + prompt="How long do you want to wait before placing the next order " + "if your order gets filled (in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=60), + "hanging_orders_enabled": + ConfigVar(key="hanging_orders_enabled", + prompt="Do you want to enable hanging orders? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "hanging_orders_cancel_pct": + ConfigVar(key="hanging_orders_cancel_pct", + prompt="At what spread percentage (from mid price) will hanging orders be canceled? " + "(Enter 1 to indicate 1%) >>> ", + required_if=lambda: pure_market_making_config_map.get("hanging_orders_enabled").value, + type_str="decimal", + default=Decimal("10"), + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False)), + "order_optimization_enabled": + ConfigVar(key="order_optimization_enabled", + prompt="Do you want to enable best bid ask jumping? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "ask_order_optimization_depth": + ConfigVar(key="ask_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top ask, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: pure_market_making_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "bid_order_optimization_depth": + ConfigVar(key="bid_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top bid, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: pure_market_making_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "add_transaction_costs": + ConfigVar(key="add_transaction_costs", + prompt="Do you want to add transaction costs automatically to order prices? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "price_source": + ConfigVar(key="price_source", + prompt="Which price source to use? (current_market/external_market/custom_api) >>> ", + type_str="str", + default="current_market", + validator=validate_price_source, + on_validated=on_validate_price_source), + "price_type": + ConfigVar(key="price_type", + prompt="Which price type to use? (" + "mid_price/last_price/last_own_trade_price/best_bid/best_ask/inventory_cost) >>> ", + type_str="str", + required_if=lambda: pure_market_making_config_map.get("price_source").value != "custom_api", + default="mid_price", + on_validated=on_validated_price_type, + validator=validate_price_type), + "price_source_exchange": + ConfigVar(key="price_source_exchange", + prompt="Enter external price source exchange name >>> ", + required_if=lambda: pure_market_making_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_exchange, + on_validated=on_validated_price_source_exchange), + "price_source_market": + ConfigVar(key="price_source_market", + prompt=price_source_market_prompt, + required_if=lambda: pure_market_making_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_market), + "take_if_crossed": + ConfigVar(key="take_if_crossed", + prompt="Do you want to take the best order if orders cross the orderbook? ((Yes/No) >>> ", + required_if=lambda: pure_market_making_config_map.get( + "price_source").value == "external_market", + type_str="bool", + validator=validate_bool), + "price_source_custom_api": + ConfigVar(key="price_source_custom_api", + prompt="Enter pricing API URL >>> ", + required_if=lambda: pure_market_making_config_map.get("price_source").value == "custom_api", + type_str="str"), + "custom_api_update_interval": + ConfigVar(key="custom_api_update_interval", + prompt="Enter custom API update interval in second (default: 5.0, min: 0.5) >>> ", + required_if=lambda: False, + default=float(5), + type_str="float", + validator=lambda v: validate_decimal(v, Decimal("0.5"))), + "order_override": + ConfigVar(key="order_override", + prompt=None, + required_if=lambda: False, + default=None, + type_str="json"), + "should_wait_order_cancel_confirmation": + ConfigVar(key="should_wait_order_cancel_confirmation", + prompt="Should the strategy wait to receive a confirmation for orders cancelation " + "before creating a new set of orders? " + "(Not waiting requires enough available balance) (Yes/No) >>> ", + type_str="bool", + default=True, + validator=validate_bool), + "split_order_levels_enabled": + ConfigVar(key="split_order_levels_enabled", + prompt="Do you want bid and ask orders to be placed at multiple defined spread and amount? " + "This acts as an overrides which replaces order_amount, order_spreads, " + "order_level_amount, order_level_spreads (Yes/No) >>> ", + default=False, + type_str="bool", + validator=validate_bool), + "bid_order_level_spreads": + ConfigVar(key="bid_order_level_spreads", + prompt="Enter the spreads (as percentage) for all bid spreads " + "e.g 1,2,3,4 to represent 1%,2%,3%,4%. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_market_making_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "ask_order_level_spreads": + ConfigVar(key="ask_order_level_spreads", + prompt="Enter the spreads (as percentage) for all ask spreads " + "e.g 1,2,3,4 to represent 1%,2%,3%,4%. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_market_making_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "bid_order_level_amounts": + ConfigVar(key="bid_order_level_amounts", + prompt="Enter the amount for all bid amounts. " + "e.g 1,2,3,4. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_market_making_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "ask_order_level_amounts": + ConfigVar(key="ask_order_level_amounts", + prompt="Enter the amount for all ask amounts. " + "e.g 1,2,3,4. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + required_if=lambda: pure_market_making_config_map.get( + "split_order_levels_enabled").value, + type_str="str", + validator=validate_decimal_list), +} diff --git a/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pxd b/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pxd new file mode 100644 index 0000000..e25c74d --- /dev/null +++ b/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pxd @@ -0,0 +1,8 @@ +# distutils: language=c++ + +from hummingbot.strategy.order_tracker import OrderTracker +from hummingbot.strategy.order_tracker cimport OrderTracker + + +cdef class PureMarketMakingOrderTracker(OrderTracker): + pass diff --git a/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pyx b/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pyx new file mode 100644 index 0000000..dd98d02 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/pure_market_making_order_tracker.pyx @@ -0,0 +1,49 @@ +from typing import ( + Dict, + List, + Tuple +) + +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_tracker cimport OrderTracker + +NaN = float("nan") + + +cdef class PureMarketMakingOrderTracker(OrderTracker): + # ETH confirmation requirement of Binance has shortened to 12 blocks as of 7/15/2019. + # 12 * 15 / 60 = 3 minutes + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 3 + + def __init__(self): + super().__init__() + + @property + def active_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._tracked_limit_orders.items(): + for limit_order in orders_map.values(): + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def shadow_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._shadow_tracked_limit_orders.items(): + for limit_order in orders_map.values(): + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def market_pair_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + market_pair_to_orders = {} + market_pairs = self._tracked_limit_orders.keys() + for market_pair in market_pairs: + maker_orders = [] + for limit_order in self._tracked_limit_orders[market_pair].values(): + maker_orders.append(limit_order) + market_pair_to_orders[market_pair] = maker_orders + return market_pair_to_orders diff --git a/hummingbot/strategy/pure_market_making/start.py b/hummingbot/strategy/pure_market_making/start.py new file mode 100644 index 0000000..663ed73 --- /dev/null +++ b/hummingbot/strategy/pure_market_making/start.py @@ -0,0 +1,145 @@ +from decimal import Decimal +from typing import List, Optional, Tuple + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange.paper_trade import create_paper_trade_market +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.api_asset_price_delegate import APIAssetPriceDelegate +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.pure_market_making import InventoryCostPriceDelegate, PureMarketMakingStrategy +from hummingbot.strategy.pure_market_making.moving_price_band import MovingPriceBand +from hummingbot.strategy.pure_market_making.pure_market_making_config_map import pure_market_making_config_map as c_map + + +def start(self): + def convert_decimal_string_to_list(string: Optional[str], divisor: Decimal = Decimal("1")) -> List[Decimal]: + '''convert order level spread string into a list of decimal divided by divisor ''' + if string is None: + return [] + string_list = list(string.split(",")) + return [Decimal(v) / divisor for v in string_list] + + try: + order_amount = c_map.get("order_amount").value + order_refresh_time = c_map.get("order_refresh_time").value + max_order_age = c_map.get("max_order_age").value + bid_spread = c_map.get("bid_spread").value / Decimal('100') + ask_spread = c_map.get("ask_spread").value / Decimal('100') + minimum_spread = c_map.get("minimum_spread").value / Decimal('100') + price_ceiling = c_map.get("price_ceiling").value + price_floor = c_map.get("price_floor").value + ping_pong_enabled = c_map.get("ping_pong_enabled").value + order_levels = c_map.get("order_levels").value + order_level_amount = c_map.get("order_level_amount").value + order_level_spread = c_map.get("order_level_spread").value / Decimal('100') + exchange = c_map.get("exchange").value.lower() + raw_trading_pair = c_map.get("market").value + inventory_skew_enabled = c_map.get("inventory_skew_enabled").value + inventory_target_base_pct = 0 if c_map.get("inventory_target_base_pct").value is None else \ + c_map.get("inventory_target_base_pct").value / Decimal('100') + inventory_range_multiplier = c_map.get("inventory_range_multiplier").value + filled_order_delay = c_map.get("filled_order_delay").value + hanging_orders_enabled = c_map.get("hanging_orders_enabled").value + hanging_orders_cancel_pct = c_map.get("hanging_orders_cancel_pct").value / Decimal('100') + order_optimization_enabled = c_map.get("order_optimization_enabled").value + ask_order_optimization_depth = c_map.get("ask_order_optimization_depth").value + bid_order_optimization_depth = c_map.get("bid_order_optimization_depth").value + add_transaction_costs_to_orders = c_map.get("add_transaction_costs").value + price_source = c_map.get("price_source").value + price_type = c_map.get("price_type").value + price_source_exchange = c_map.get("price_source_exchange").value + price_source_market = c_map.get("price_source_market").value + price_source_custom_api = c_map.get("price_source_custom_api").value + custom_api_update_interval = c_map.get("custom_api_update_interval").value + order_refresh_tolerance_pct = c_map.get("order_refresh_tolerance_pct").value / Decimal('100') + order_override = c_map.get("order_override").value + split_order_levels_enabled = c_map.get("split_order_levels_enabled").value + moving_price_band = MovingPriceBand( + enabled=c_map.get("moving_price_band_enabled").value, + price_floor_pct=c_map.get("price_floor_pct").value, + price_ceiling_pct=c_map.get("price_ceiling_pct").value, + price_band_refresh_time=c_map.get("price_band_refresh_time").value + ) + bid_order_level_spreads = convert_decimal_string_to_list( + c_map.get("bid_order_level_spreads").value) + ask_order_level_spreads = convert_decimal_string_to_list( + c_map.get("ask_order_level_spreads").value) + bid_order_level_amounts = convert_decimal_string_to_list( + c_map.get("bid_order_level_amounts").value) + ask_order_level_amounts = convert_decimal_string_to_list( + c_map.get("ask_order_level_amounts").value) + if split_order_levels_enabled: + buy_list = [['buy', spread, amount] for spread, amount in zip(bid_order_level_spreads, bid_order_level_amounts)] + sell_list = [['sell', spread, amount] for spread, amount in zip(ask_order_level_spreads, ask_order_level_amounts)] + both_list = buy_list + sell_list + order_override = { + f'split_level_{i}': order for i, order in enumerate(both_list) + } + trading_pair: str = raw_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(exchange, [trading_pair])[0] + market_names: List[Tuple[str, List[str]]] = [(exchange, [trading_pair])] + self._initialize_markets(market_names) + maker_data = [self.markets[exchange], trading_pair] + list(maker_assets) + self.market_trading_pair_tuples = [MarketTradingPairTuple(*maker_data)] + + asset_price_delegate = None + if price_source == "external_market": + asset_trading_pair: str = price_source_market + ext_market = create_paper_trade_market(price_source_exchange, self.client_config_map, [asset_trading_pair]) + self.markets[price_source_exchange]: ExchangeBase = ext_market + asset_price_delegate = OrderBookAssetPriceDelegate(ext_market, asset_trading_pair) + elif price_source == "custom_api": + asset_price_delegate = APIAssetPriceDelegate(self.markets[exchange], price_source_custom_api, + custom_api_update_interval) + inventory_cost_price_delegate = None + if price_type == "inventory_cost": + db = HummingbotApplication.main_application().trade_fill_db + inventory_cost_price_delegate = InventoryCostPriceDelegate(db, trading_pair) + take_if_crossed = c_map.get("take_if_crossed").value + + should_wait_order_cancel_confirmation = c_map.get("should_wait_order_cancel_confirmation") + + strategy_logging_options = PureMarketMakingStrategy.OPTION_LOG_ALL + self.strategy = PureMarketMakingStrategy() + self.strategy.init_params( + market_info=MarketTradingPairTuple(*maker_data), + bid_spread=bid_spread, + ask_spread=ask_spread, + order_levels=order_levels, + order_amount=order_amount, + order_level_spread=order_level_spread, + order_level_amount=order_level_amount, + inventory_skew_enabled=inventory_skew_enabled, + inventory_target_base_pct=inventory_target_base_pct, + inventory_range_multiplier=inventory_range_multiplier, + filled_order_delay=filled_order_delay, + hanging_orders_enabled=hanging_orders_enabled, + order_refresh_time=order_refresh_time, + max_order_age=max_order_age, + order_optimization_enabled=order_optimization_enabled, + ask_order_optimization_depth=ask_order_optimization_depth, + bid_order_optimization_depth=bid_order_optimization_depth, + add_transaction_costs_to_orders=add_transaction_costs_to_orders, + logging_options=strategy_logging_options, + asset_price_delegate=asset_price_delegate, + inventory_cost_price_delegate=inventory_cost_price_delegate, + price_type=price_type, + take_if_crossed=take_if_crossed, + price_ceiling=price_ceiling, + price_floor=price_floor, + ping_pong_enabled=ping_pong_enabled, + hanging_orders_cancel_pct=hanging_orders_cancel_pct, + order_refresh_tolerance_pct=order_refresh_tolerance_pct, + minimum_spread=minimum_spread, + hb_app_notification=True, + order_override={} if order_override is None else order_override, + split_order_levels_enabled=split_order_levels_enabled, + bid_order_level_spreads=bid_order_level_spreads, + ask_order_level_spreads=ask_order_level_spreads, + should_wait_order_cancel_confirmation=should_wait_order_cancel_confirmation, + moving_price_band=moving_price_band + ) + except Exception as e: + self.notify(str(e)) + self.logger().error("Unknown error during initialization.", exc_info=True) diff --git a/hummingbot/strategy/pure_volume.zip b/hummingbot/strategy/pure_volume.zip new file mode 100644 index 0000000..549423b Binary files /dev/null and b/hummingbot/strategy/pure_volume.zip differ diff --git a/hummingbot/strategy/pure_volume/__init__.py b/hummingbot/strategy/pure_volume/__init__.py new file mode 100644 index 0000000..c13aed2 --- /dev/null +++ b/hummingbot/strategy/pure_volume/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from .pure_volume import PureVolumeStrategy +from .inventory_cost_price_delegate import InventoryCostPriceDelegate +__all__ = [ + PureVolumeStrategy, + InventoryCostPriceDelegate, +] diff --git a/hummingbot/strategy/pure_volume/data_types.py b/hummingbot/strategy/pure_volume/data_types.py new file mode 100644 index 0000000..aa2cdbe --- /dev/null +++ b/hummingbot/strategy/pure_volume/data_types.py @@ -0,0 +1,55 @@ +from decimal import Decimal +from typing import ( + List, + NamedTuple, +) + +from hummingbot.core.data_type.common import OrderType + +ORDER_PROPOSAL_ACTION_CREATE_ORDERS = 1 +ORDER_PROPOSAL_ACTION_CANCEL_ORDERS = 1 << 1 + + +class OrdersProposal(NamedTuple): + actions: int + buy_order_type: OrderType + buy_order_prices: List[Decimal] + buy_order_sizes: List[Decimal] + sell_order_type: OrderType + sell_order_prices: List[Decimal] + sell_order_sizes: List[Decimal] + cancel_order_ids: List[str] + + +class PricingProposal(NamedTuple): + buy_order_prices: List[Decimal] + sell_order_prices: List[Decimal] + + +class SizingProposal(NamedTuple): + buy_order_sizes: List[Decimal] + sell_order_sizes: List[Decimal] + + +class InventorySkewBidAskRatios(NamedTuple): + bid_ratio: float + ask_ratio: float + + +class PriceSize: + def __init__(self, price: Decimal, size: Decimal): + self.price: Decimal = price + self.size: Decimal = size + + def __repr__(self): + return f"[ p: {self.price} s: {self.size} ]" + + +class Proposal: + def __init__(self, buys: List[PriceSize], sells: List[PriceSize]): + self.buys: List[PriceSize] = buys + self.sells: List[PriceSize] = sells + + def __repr__(self): + return f"{len(self.buys)} buys: {', '.join([str(o) for o in self.buys])} " \ + f"{len(self.sells)} sells: {', '.join([str(o) for o in self.sells])}" diff --git a/hummingbot/strategy/pure_volume/inventory_cost_price_delegate.py b/hummingbot/strategy/pure_volume/inventory_cost_price_delegate.py new file mode 100644 index 0000000..19ebfa4 --- /dev/null +++ b/hummingbot/strategy/pure_volume/inventory_cost_price_delegate.py @@ -0,0 +1,74 @@ +from decimal import Decimal, InvalidOperation +from typing import Optional + +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.model.inventory_cost import InventoryCost +from hummingbot.model.sql_connection_manager import SQLConnectionManager + +s_decimal_0 = Decimal("0") + + +class InventoryCostPriceDelegate: + def __init__(self, sql: SQLConnectionManager, trading_pair: str) -> None: + self.base_asset, self.quote_asset = trading_pair.split("-") + self.sql_manager = sql + + @property + def ready(self) -> bool: + return True + + def get_price(self) -> Optional[Decimal]: + with self.sql_manager.get_new_session() as session: + with session.begin(): + record = InventoryCost.get_record( + session, self.base_asset, self.quote_asset + ) + + if record is None or record.base_volume is None or record.quote_volume is None: + return None + + try: + price = record.quote_volume / record.base_volume + except InvalidOperation: + return None + return Decimal(price) + + def process_order_fill_event(self, fill_event: OrderFilledEvent) -> None: + base_asset, quote_asset = fill_event.trading_pair.split("-") + quote_volume = fill_event.amount * fill_event.price + base_volume = fill_event.amount + + for fee_asset, fee_amount in fill_event.trade_fee.flat_fees: + if fill_event.trade_type == TradeType.BUY: + if fee_asset == base_asset: + base_volume -= fee_amount + elif fee_asset == quote_asset: + quote_volume += fee_amount + else: + # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity + base_volume /= 1 + fill_event.trade_fee.percent + else: + if fee_asset == base_asset: + base_volume += fee_amount + elif fee_asset == quote_asset: + # TODO: with new logic, this quote volume adjustment does not impacts anything + quote_volume -= fee_amount + else: + # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity + base_volume /= 1 + fill_event.trade_fee.percent + + with self.sql_manager.get_new_session() as session: + with session.begin(): + if fill_event.trade_type == TradeType.SELL: + record = InventoryCost.get_record(session, base_asset, quote_asset) + if not record: + raise RuntimeError("Sold asset without having inventory price set. This should not happen.") + + # We're keeping initial buy price intact. Profits are not changing inventory price intentionally. + quote_volume = -(Decimal(record.quote_volume / record.base_volume) * base_volume) + base_volume = -base_volume + + InventoryCost.add_volume( + session, base_asset, quote_asset, base_volume, quote_volume + ) diff --git a/hummingbot/strategy/pure_volume/inventory_skew_calculator.pxd b/hummingbot/strategy/pure_volume/inventory_skew_calculator.pxd new file mode 100644 index 0000000..57f5c98 --- /dev/null +++ b/hummingbot/strategy/pure_volume/inventory_skew_calculator.pxd @@ -0,0 +1,5 @@ +cdef object c_calculate_bid_ask_ratios_from_base_asset_ratio(double base_asset_amount, + double quote_asset_amount, + double price, + double target_base_asset_ratio, + double base_asset_range) diff --git a/hummingbot/strategy/pure_volume/inventory_skew_calculator.pyx b/hummingbot/strategy/pure_volume/inventory_skew_calculator.pyx new file mode 100644 index 0000000..758e58e --- /dev/null +++ b/hummingbot/strategy/pure_volume/inventory_skew_calculator.pyx @@ -0,0 +1,57 @@ +from decimal import Decimal +import numpy as np + +from .data_types import InventorySkewBidAskRatios + +decimal_0 = Decimal(0) +decimal_1 = Decimal(1) +decimal_2 = Decimal(2) + + +def calculate_total_order_size(order_start_size: Decimal, order_step_size: Decimal = decimal_0, + order_levels: int = 1) -> Decimal: + order_levels_decimal = order_levels + return (decimal_2 * + (order_levels_decimal * order_start_size + + order_levels_decimal * (order_levels_decimal - decimal_1) / decimal_2 * order_step_size + ) + ) + + +def calculate_bid_ask_ratios_from_base_asset_ratio( + base_asset_amount: float, quote_asset_amount: float, price: float, + target_base_asset_ratio: float, base_asset_range: float) -> InventorySkewBidAskRatios: + return c_calculate_bid_ask_ratios_from_base_asset_ratio(base_asset_amount, + quote_asset_amount, + price, + target_base_asset_ratio, + base_asset_range) + + +cdef object c_calculate_bid_ask_ratios_from_base_asset_ratio( + double base_asset_amount, double quote_asset_amount, double price, + double target_base_asset_ratio, double base_asset_range): + cdef: + double total_portfolio_value = base_asset_amount * price + quote_asset_amount + + if total_portfolio_value <= 0.0 or base_asset_range <= 0.0: + return InventorySkewBidAskRatios(0.0, 0.0) + + cdef: + double base_asset_value = base_asset_amount * price + double base_asset_range_value = min(base_asset_range * price, total_portfolio_value * 0.5) + double target_base_asset_value = total_portfolio_value * target_base_asset_ratio + double left_base_asset_value_limit = max(target_base_asset_value - base_asset_range_value, 0.0) + double right_base_asset_value_limit = target_base_asset_value + base_asset_range_value + double left_inventory_ratio = np.interp(base_asset_value, + [left_base_asset_value_limit, target_base_asset_value], + [0.0, 0.5]) + double right_inventory_ratio = np.interp(base_asset_value, + [target_base_asset_value, right_base_asset_value_limit], + [0.5, 1.0]) + double bid_adjustment = (np.interp(left_inventory_ratio, [0, 0.5], [2.0, 1.0]) + if base_asset_value < target_base_asset_value + else np.interp(right_inventory_ratio, [0.5, 1], [1.0, 0.0])) + double ask_adjustment = 2.0 - bid_adjustment + + return InventorySkewBidAskRatios(bid_adjustment, ask_adjustment) diff --git a/hummingbot/strategy/pure_volume/moving_price_band.py b/hummingbot/strategy/pure_volume/moving_price_band.py new file mode 100644 index 0000000..fb6aa07 --- /dev/null +++ b/hummingbot/strategy/pure_volume/moving_price_band.py @@ -0,0 +1,88 @@ +import logging +from dataclasses import dataclass +from decimal import Decimal + +mpb_logger = None + + +@dataclass +class MovingPriceBand: + ''' + move price floor and ceiling to percentage of current price + at every price_band_refresh_time + + :param price_floor_pct: set the price floor pct + :param price_ceiling_pct: reference price to set price band + :param price_band_refresh_time: reference price to set price band + ''' + price_floor_pct: Decimal = -1 + price_ceiling_pct: Decimal = 1 + price_band_refresh_time: float = 86400 + enabled: bool = False + _price_floor: Decimal = 0 + _price_ceiling: Decimal = 0 + _set_time: float = 0 + + @classmethod + def logger(cls): + global mpb_logger + if mpb_logger is None: + mpb_logger = logging.getLogger(__name__) + return mpb_logger + + @property + def price_floor(self) -> Decimal: + '''get price floor''' + return self._price_floor + + @property + def price_ceiling(self) -> Decimal: + '''get price ceiling''' + return self._price_ceiling + + def update(self, timestamp: float, price: Decimal) -> None: + """ + Updates the price band. + + :param timestamp: current timestamp of the strategy/connector + :param price: reference price to set price band + """ + self._price_floor = (Decimal("100") + self.price_floor_pct) / Decimal("100") * price + self._price_ceiling = (Decimal("100") + self.price_ceiling_pct) / Decimal("100") * price + self._set_time = timestamp + self.logger().info( + "moving price band updated: price_floor: %s price_ceiling: %s", self._price_floor, self._price_ceiling) + + def check_and_update_price_band(self, timestamp: float, price: Decimal) -> None: + ''' + check if the timestamp has passed the defined refresh time before updating + + :param timestamp: current timestamp of the strategy/connector + :param price: reference price to set price band + ''' + if timestamp >= self._set_time + self.price_band_refresh_time: + self.update(timestamp, price) + + def check_price_floor_exceeded(self, price: Decimal) -> bool: + ''' + check if the price has exceeded the price floor + + :param price: price to check + ''' + return price <= self.price_floor + + def check_price_ceiling_exceeded(self, price: Decimal) -> bool: + ''' + check if the price has exceeded the price ceiling + + :param price: price to check + ''' + return price >= self.price_ceiling + + def switch(self, value: bool) -> None: + ''' + switch between enabled and disabled state + + :param value: set whether to enable or disable MovingPriceBand + ''' + self.enabled = value diff --git a/hummingbot/strategy/pure_volume/pure_volume.pxd b/hummingbot/strategy/pure_volume/pure_volume.pxd new file mode 100644 index 0000000..dba934e --- /dev/null +++ b/hummingbot/strategy/pure_volume/pure_volume.pxd @@ -0,0 +1,87 @@ +# distutils: language=c++ + +from libc.stdint cimport int64_t +from decimal import Decimal + +from hummingbot.strategy.strategy_base cimport StrategyBase + + +cdef class PureVolumeStrategy(StrategyBase): + cdef: + object _market_info + + object _bid_spread + object _minimum_spread + object _order_amount + int _order_levels + int _buy_levels + int _sell_levels + object _split_order_levels_enabled + object _bid_order_level_spreads + object _ask_order_level_spreads + object _order_level_spread + object _order_level_amount + double _order_refresh_time + double _max_order_age + object _order_refresh_tolerance_pct + double _filled_order_delay + bint _inventory_skew_enabled + object _inventory_target_base_pct + object _inventory_range_multiplier + bint _hanging_orders_enabled + object _hanging_orders_tracker + bint _order_optimization_enabled + object _ask_order_optimization_depth + object _bid_order_optimization_depth + bint _add_transaction_costs_to_orders + object _asset_price_delegate + object _inventory_cost_price_delegate + object _price_type + bint _take_if_crossed + object _price_ceiling + object _price_floor + bint _ping_pong_enabled + list _ping_pong_warning_lines + bint _hb_app_notification + object _order_override + + double _cancel_timestamp + double _create_timestamp + object _limit_order_type + bint _all_markets_ready + int _filled_buys_balance + int _filled_sells_balance + double _last_timestamp + double _status_report_interval + int64_t _logging_options + object _last_own_trade_price + bint _should_wait_order_cancel_confirmation + + object _moving_price_band + + cdef object c_get_mid_price(self) + cdef object c_create_base_proposal(self) + cdef tuple c_get_adjusted_available_balance(self, list orders) + cdef c_apply_order_levels_modifiers(self, object proposal) + cdef c_apply_price_band(self, object proposal) + cdef c_apply_ping_pong(self, object proposal) + cdef c_apply_order_price_modifiers(self, object proposal) + cdef c_apply_order_size_modifiers(self, object proposal) + cdef c_apply_inventory_skew(self, object proposal) + cdef c_apply_budget_constraint(self, object proposal) + + cdef c_filter_out_takers(self, object proposal) + cdef c_apply_order_optimization(self, object proposal) + cdef c_apply_add_transaction_costs(self, object proposal) + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices) + cdef c_cancel_active_orders(self, object proposal) + cdef c_cancel_orders_below_min_spread(self) + cdef c_cancel_active_orders_on_max_age_limit(self) + cdef bint c_to_create_orders(self, object proposal) + cdef c_execute_orders_proposal(self, object proposal) + cdef set_timers(self) + cdef c_apply_moving_price_band(self, object proposal) + cdef c_place_market_sell_orders(self) + cdef public bint ready_to_place_sell_order + cdef public object last_buy_order_id + cdef public bint buy_order_placed diff --git a/hummingbot/strategy/pure_volume/pure_volume.pyx b/hummingbot/strategy/pure_volume/pure_volume.pyx new file mode 100644 index 0000000..42c86f0 --- /dev/null +++ b/hummingbot/strategy/pure_volume/pure_volume.pyx @@ -0,0 +1,1354 @@ +import logging +from decimal import Decimal +from math import ceil, floor +from typing import Dict, List, Optional + +import numpy as np +import pandas as pd + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.exchange_base cimport ExchangeBase +from hummingbot.core.clock cimport Clock +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +#from hummingbot.core.data_type.market_order cimport MarketOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.asset_price_delegate cimport AssetPriceDelegate +from hummingbot.strategy.asset_price_delegate import AssetPriceDelegate +from hummingbot.strategy.hanging_orders_tracker import CreatedPairOfOrders, HangingOrdersTracker +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate cimport OrderBookAssetPriceDelegate +from hummingbot.strategy.strategy_base import StrategyBase +from hummingbot.strategy.utils import order_age +from .data_types import PriceSize, Proposal +from .inventory_cost_price_delegate import InventoryCostPriceDelegate +from .inventory_skew_calculator cimport c_calculate_bid_ask_ratios_from_base_asset_ratio +from .inventory_skew_calculator import calculate_total_order_size +from .pure_volume_order_tracker import PureVolumeOrderTracker +from .moving_price_band import MovingPriceBand + + +NaN = float("nan") +s_decimal_zero = Decimal(0) +s_decimal_neg_one = Decimal(-1) +pmm_logger = None + + +cdef class PureVolumeStrategy(StrategyBase): + OPTION_LOG_CREATE_ORDER = 1 << 3 + OPTION_LOG_MAKER_ORDER_FILLED = 1 << 4 + OPTION_LOG_STATUS_REPORT = 1 << 5 + OPTION_LOG_ALL = 0x7fffffffffffffff + + def __init__(self): + super().__init__() + self.buy_order_placed = False + + @classmethod + def logger(cls): + global pmm_logger + if pmm_logger is None: + pmm_logger = logging.getLogger(__name__) + return pmm_logger + + def init_params(self, + market_info: MarketTradingPairTuple, + bid_spread: Decimal, + #ask_spread: Decimal, + order_amount: Decimal, + order_levels: int = 1, + order_level_spread: Decimal = s_decimal_zero, + order_level_amount: Decimal = s_decimal_zero, + order_refresh_time: float = 30.0, + max_order_age: float = 1800.0, + order_refresh_tolerance_pct: Decimal = s_decimal_neg_one, + filled_order_delay: float = 60.0, + inventory_skew_enabled: bool = False, + inventory_target_base_pct: Decimal = s_decimal_zero, + inventory_range_multiplier: Decimal = s_decimal_zero, + hanging_orders_enabled: bool = False, + hanging_orders_cancel_pct: Decimal = Decimal("0.1"), + order_optimization_enabled: bool = False, + ask_order_optimization_depth: Decimal = s_decimal_zero, + bid_order_optimization_depth: Decimal = s_decimal_zero, + add_transaction_costs_to_orders: bool = False, + asset_price_delegate: AssetPriceDelegate = None, + inventory_cost_price_delegate: InventoryCostPriceDelegate = None, + price_type: str = "mid_price", + take_if_crossed: bool = False, + price_ceiling: Decimal = s_decimal_neg_one, + price_floor: Decimal = s_decimal_neg_one, + ping_pong_enabled: bool = False, + logging_options: int = OPTION_LOG_ALL, + status_report_interval: float = 900, + minimum_spread: Decimal = Decimal(0), + hb_app_notification: bool = False, + order_override: Dict[str, List[str]] = None, + split_order_levels_enabled: bool = False, + bid_order_level_spreads: List[Decimal] = None, + ask_order_level_spreads: List[Decimal] = None, + should_wait_order_cancel_confirmation: bool = True, + moving_price_band: Optional[MovingPriceBand] = None + ): + if order_override is None: + order_override = {} + if moving_price_band is None: + moving_price_band = MovingPriceBand() + if price_ceiling != s_decimal_neg_one and price_ceiling < price_floor: + raise ValueError("Parameter price_ceiling cannot be lower than price_floor.") + self._sb_order_tracker = PureVolumeOrderTracker() + self._market_info = market_info + self._bid_spread = bid_spread + #self._ask_spread = ask_spread + self._minimum_spread = minimum_spread + self._order_amount = order_amount + self._order_levels = order_levels + self._buy_levels = order_levels + self._sell_levels = order_levels + self._order_level_spread = order_level_spread + self._order_level_amount = order_level_amount + self._order_refresh_time = order_refresh_time + self._max_order_age = max_order_age + self._order_refresh_tolerance_pct = order_refresh_tolerance_pct + self._filled_order_delay = filled_order_delay + self._inventory_skew_enabled = inventory_skew_enabled + self._inventory_target_base_pct = inventory_target_base_pct + self._inventory_range_multiplier = inventory_range_multiplier + self._hanging_orders_enabled = hanging_orders_enabled + self._hanging_orders_tracker = HangingOrdersTracker(self, hanging_orders_cancel_pct) + self._order_optimization_enabled = order_optimization_enabled + self._ask_order_optimization_depth = ask_order_optimization_depth + self._bid_order_optimization_depth = bid_order_optimization_depth + self._add_transaction_costs_to_orders = add_transaction_costs_to_orders + self._asset_price_delegate = asset_price_delegate + self._inventory_cost_price_delegate = inventory_cost_price_delegate + self._price_type = self.get_price_type(price_type) + self._take_if_crossed = take_if_crossed + self._price_ceiling = price_ceiling + self._price_floor = price_floor + self._ping_pong_enabled = ping_pong_enabled + self._ping_pong_warning_lines = [] + self._hb_app_notification = hb_app_notification + self._order_override = order_override + self._split_order_levels_enabled=split_order_levels_enabled + self._bid_order_level_spreads=bid_order_level_spreads + self._ask_order_level_spreads=ask_order_level_spreads + self._cancel_timestamp = 0 + self._create_timestamp = 0 + self._limit_order_type = self._market_info.market.get_maker_order_type() + if take_if_crossed: + self._limit_order_type = OrderType.LIMIT + self._all_markets_ready = False + self._filled_buys_balance = 0 + self._filled_sells_balance = 0 + self._logging_options = logging_options + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self._last_own_trade_price = Decimal('nan') + self._should_wait_order_cancel_confirmation = should_wait_order_cancel_confirmation + self._moving_price_band = moving_price_band + self.c_add_markets([market_info.market]) + #self.buy_order_placed = False + + def all_markets_ready(self): + return all([market.ready for market in self._sb_markets]) + + @property + def market_info(self) -> MarketTradingPairTuple: + return self._market_info + + @property + def max_order_age(self) -> float: + return self._max_order_age + + @property + def minimum_spread(self) -> Decimal: + return self._minimum_spread + + @property + def ping_pong_enabled(self) -> bool: + return self._ping_pong_enabled + + @property + def ask_order_optimization_depth(self) -> Decimal: + return self._ask_order_optimization_depth + + @property + def bid_order_optimization_depth(self) -> Decimal: + return self._bid_order_optimization_depth + + @property + def price_type(self) -> PriceType: + return self._price_type + + @property + def order_refresh_tolerance_pct(self) -> Decimal: + return self._order_refresh_tolerance_pct + + @order_refresh_tolerance_pct.setter + def order_refresh_tolerance_pct(self, value: Decimal): + self._order_refresh_tolerance_pct = value + + @property + def order_amount(self) -> Decimal: + return self._order_amount + + @order_amount.setter + def order_amount(self, value: Decimal): + self._order_amount = value + + @property + def order_levels(self) -> int: + return self._order_levels + + @order_levels.setter + def order_levels(self, value: int): + self._order_levels = value + self._buy_levels = value + self._sell_levels = value + + @property + def buy_levels(self) -> int: + return self._buy_levels + + @buy_levels.setter + def buy_levels(self, value: int): + self._buy_levels = value + + @property + def sell_levels(self) -> int: + return self._sell_levels + + @sell_levels.setter + def sell_levels(self, value: int): + self._sell_levels = value + + @property + def order_level_amount(self) -> Decimal: + return self._order_level_amount + + @order_level_amount.setter + def order_level_amount(self, value: Decimal): + self._order_level_amount = value + + @property + def order_level_spread(self) -> Decimal: + return self._order_level_spread + + @order_level_spread.setter + def order_level_spread(self, value: Decimal): + self._order_level_spread = value + + @property + def inventory_skew_enabled(self) -> bool: + return self._inventory_skew_enabled + + @inventory_skew_enabled.setter + def inventory_skew_enabled(self, value: bool): + self._inventory_skew_enabled = value + + @property + def inventory_target_base_pct(self) -> Decimal: + return self._inventory_target_base_pct + + @inventory_target_base_pct.setter + def inventory_target_base_pct(self, value: Decimal): + self._inventory_target_base_pct = value + + @property + def inventory_range_multiplier(self) -> Decimal: + return self._inventory_range_multiplier + + @inventory_range_multiplier.setter + def inventory_range_multiplier(self, value: Decimal): + self._inventory_range_multiplier = value + + @property + def hanging_orders_enabled(self) -> bool: + return self._hanging_orders_enabled + + @hanging_orders_enabled.setter + def hanging_orders_enabled(self, value: bool): + self._hanging_orders_enabled = value + + @property + def hanging_orders_cancel_pct(self) -> Decimal: + return self._hanging_orders_tracker._hanging_orders_cancel_pct + + @hanging_orders_cancel_pct.setter + def hanging_orders_cancel_pct(self, value: Decimal): + self._hanging_orders_tracker._hanging_orders_cancel_pct = value + + @property + def bid_spread(self) -> Decimal: + return self._bid_spread + + @bid_spread.setter + def bid_spread(self, value: Decimal): + self._bid_spread = value + + # @property + # def ask_spread(self) -> Decimal: + # return self._ask_spread + + # @ask_spread.setter + # def ask_spread(self, value: Decimal): + # self._ask_spread = value + + @property + def order_optimization_enabled(self) -> bool: + return self._order_optimization_enabled + + @order_optimization_enabled.setter + def order_optimization_enabled(self, value: bool): + self._order_optimization_enabled = value + + @property + def order_refresh_time(self) -> float: + return self._order_refresh_time + + @order_refresh_time.setter + def order_refresh_time(self, value: float): + self._order_refresh_time = value + + @property + def filled_order_delay(self) -> float: + return self._filled_order_delay + + @filled_order_delay.setter + def filled_order_delay(self, value: float): + self._filled_order_delay = value + + @property + def add_transaction_costs_to_orders(self) -> bool: + return self._add_transaction_costs_to_orders + + @add_transaction_costs_to_orders.setter + def add_transaction_costs_to_orders(self, value: bool): + self._add_transaction_costs_to_orders = value + + @property + def price_ceiling(self) -> Decimal: + return self._price_ceiling + + @price_ceiling.setter + def price_ceiling(self, value: Decimal): + self._price_ceiling = value + + @property + def price_floor(self) -> Decimal: + return self._price_floor + + @price_floor.setter + def price_floor(self, value: Decimal): + self._price_floor = value + + @property + def base_asset(self): + return self._market_info.base_asset + + @property + def quote_asset(self): + return self._market_info.quote_asset + + @property + def trading_pair(self): + return self._market_info.trading_pair + + @property + def order_override(self): + return self._order_override + + @property + def split_order_levels_enabled(self): + return self._split_order_levels_enabled + + @property + def bid_order_level_spreads(self): + return self._bid_order_level_spreads + + @property + def ask_order_level_spreads(self): + return self._ask_order_level_spreads + + @order_override.setter + def order_override(self, value: Dict[str, List[str]]): + self._order_override = value + + @property + def moving_price_band_enabled(self) -> bool: + return self._moving_price_band.enabled + + @moving_price_band_enabled.setter + def moving_price_band_enabled(self, value: bool): + self._moving_price_band.switch(value) + + @property + def price_ceiling_pct(self) -> Decimal: + return self._moving_price_band.price_ceiling_pct + + @price_ceiling_pct.setter + def price_ceiling_pct(self, value: Decimal): + self._moving_price_band.price_ceiling_pct = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def price_floor_pct(self) -> Decimal: + return self._moving_price_band.price_floor_pct + + @price_floor_pct.setter + def price_floor_pct(self, value: Decimal): + self._moving_price_band.price_floor_pct = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def price_band_refresh_time(self) -> float: + return self._moving_price_band.price_band_refresh_time + + @price_band_refresh_time.setter + def price_band_refresh_time(self, value: Decimal): + self._moving_price_band.price_band_refresh_time = value + self._moving_price_band.update(self._current_timestamp, self.get_price()) + + @property + def moving_price_band(self) -> MovingPriceBand: + return self._moving_price_band + + def get_price(self) -> Decimal: + price_provider = self._asset_price_delegate or self._market_info + if self._price_type is PriceType.LastOwnTrade: + price = self._last_own_trade_price + elif self._price_type is PriceType.InventoryCost: + price = price_provider.get_price_by_type(PriceType.MidPrice) + else: + price = price_provider.get_price_by_type(self._price_type) + + if price.is_nan(): + price = price_provider.get_price_by_type(PriceType.MidPrice) + + return price + + def get_mid_price(self) -> Decimal: + return self.c_get_mid_price() + + cdef object c_get_mid_price(self): + cdef: + AssetPriceDelegate delegate = self._asset_price_delegate + object mid_price + if self._asset_price_delegate is not None: + mid_price = delegate.c_get_mid_price() + else: + mid_price = self._market_info.get_mid_price() + return mid_price + + @property + def hanging_order_ids(self) -> List[str]: + return [o.order_id for o in self._hanging_orders_tracker.strategy_current_hanging_orders] + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @property + def active_orders(self) -> List[LimitOrder]: + if self._market_info not in self.market_info_to_active_orders: + return [] + return self.market_info_to_active_orders[self._market_info] + + @property + def active_buys(self) -> List[LimitOrder]: + return [o for o in self.active_orders if o.is_buy] + + @property + def active_sells(self) -> List[LimitOrder]: + return [o for o in self.active_orders if not o.is_buy] + + @property + def active_non_hanging_orders(self) -> List[LimitOrder]: + orders = [o for o in self.active_orders if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)] + return orders + + @property + def logging_options(self) -> int: + return self._logging_options + + @logging_options.setter + def logging_options(self, int64_t logging_options): + self._logging_options = logging_options + + @property + def hanging_orders_tracker(self): + return self._hanging_orders_tracker + + @property + def asset_price_delegate(self) -> AssetPriceDelegate: + return self._asset_price_delegate + + @asset_price_delegate.setter + def asset_price_delegate(self, value): + self._asset_price_delegate = value + + @property + def inventory_cost_price_delegate(self) -> AssetPriceDelegate: + return self._inventory_cost_price_delegate + + @inventory_cost_price_delegate.setter + def inventory_cost_price_delegate(self, value): + self._inventory_cost_price_delegate = value + + def inventory_skew_stats_data_frame(self) -> Optional[pd.DataFrame]: + cdef: + ExchangeBase market = self._market_info.market + + price = self.get_price() + base_asset_amount, quote_asset_amount = self.c_get_adjusted_available_balance(self.active_orders) + total_order_size = calculate_total_order_size(self._order_amount, self._order_level_amount, self._order_levels) + + base_asset_value = base_asset_amount * price + quote_asset_value = quote_asset_amount / price if price > s_decimal_zero else s_decimal_zero + total_value = base_asset_amount + quote_asset_value + total_value_in_quote = (base_asset_amount * price) + quote_asset_amount + + base_asset_ratio = (base_asset_amount / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + quote_asset_ratio = Decimal("1") - base_asset_ratio if total_value > 0 else 0 + target_base_ratio = self._inventory_target_base_pct + inventory_range_multiplier = self._inventory_range_multiplier + target_base_amount = (total_value * target_base_ratio + if price > s_decimal_zero + else s_decimal_zero) + target_base_amount_in_quote = target_base_ratio * total_value_in_quote + target_quote_amount = (1 - target_base_ratio) * total_value_in_quote + + base_asset_range = total_order_size * self._inventory_range_multiplier + base_asset_range = min(base_asset_range, total_value * Decimal("0.5")) + high_water_mark = target_base_amount + base_asset_range + low_water_mark = max(target_base_amount - base_asset_range, s_decimal_zero) + low_water_mark_ratio = (low_water_mark / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + high_water_mark_ratio = (high_water_mark / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + high_water_mark_ratio = min(1.0, high_water_mark_ratio) + total_order_size_ratio = (self._order_amount * Decimal("2") / total_value + if total_value > s_decimal_zero + else s_decimal_zero) + bid_ask_ratios = c_calculate_bid_ask_ratios_from_base_asset_ratio( + float(base_asset_amount), + float(quote_asset_amount), + float(price), + float(target_base_ratio), + float(base_asset_range) + ) + inventory_skew_df = pd.DataFrame(data=[ + [f"Target Value ({self.quote_asset})", f"{target_base_amount_in_quote:.4f}", + f"{target_quote_amount:.4f}"], + ["Current %", f"{base_asset_ratio:.1%}", f"{quote_asset_ratio:.1%}"], + ["Target %", f"{target_base_ratio:.1%}", f"{1 - target_base_ratio:.1%}"], + ["Inventory Range", f"{low_water_mark_ratio:.1%} - {high_water_mark_ratio:.1%}", + f"{1 - high_water_mark_ratio:.1%} - {1 - low_water_mark_ratio:.1%}"], + ["Order Adjust %", f"{bid_ask_ratios.bid_ratio:.1%}", f"{bid_ask_ratios.ask_ratio:.1%}"] + ]) + return inventory_skew_df + + def pure_mm_assets_df(self, to_show_current_pct: bool) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self._market_info.get_mid_price() + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + base_value = base_balance * float(price) + total_in_quote = base_value + quote_balance + base_ratio = base_value / total_in_quote if total_in_quote > 0 else 0 + quote_ratio = quote_balance / total_in_quote if total_in_quote > 0 else 0 + data=[ + ["", base_asset, quote_asset], + ["Total Balance", round(base_balance, 4), round(quote_balance, 4)], + ["Available Balance", round(available_base_balance, 4), round(available_quote_balance, 4)], + [f"Current Value ({quote_asset})", round(base_value, 4), round(quote_balance, 4)] + ] + if to_show_current_pct: + data.append(["Current %", f"{base_ratio:.1%}", f"{quote_ratio:.1%}"]) + df = pd.DataFrame(data=data) + return df + + def active_orders_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self._market_info + price = self.get_price() + active_orders = self.active_orders + no_sells = len([o for o in active_orders if not o.is_buy and o.client_order_id and + not self._hanging_orders_tracker.is_order_id_in_hanging_orders(o.client_order_id)]) + active_orders.sort(key=lambda x: x.price, reverse=True) + columns = ["Level", "Type", "Price", "Spread", "Amount (Orig)", "Amount (Adj)", "Age"] + data = [] + lvl_buy, lvl_sell = 0, 0 + for idx in range(0, len(active_orders)): + order = active_orders[idx] + is_hanging_order = self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id) + amount_orig = "" + if not is_hanging_order: + if order.is_buy: + level = lvl_buy + 1 + lvl_buy += 1 + else: + level = no_sells - lvl_sell + lvl_sell += 1 + amount_orig = self._order_amount + ((level - 1) * self._order_level_amount) + else: + level_for_calculation = lvl_buy if order.is_buy else lvl_sell + amount_orig = self._order_amount + ((level_for_calculation - 1) * self._order_level_amount) + level = "hang" + spread = 0 if price == 0 else abs(order.price - price)/price + age = pd.Timestamp(order_age(order, self._current_timestamp), unit='s').strftime('%H:%M:%S') + data.append([ + level, + "buy" if order.is_buy else "sell", + float(order.price), + f"{spread:.2%}", + amount_orig, + float(order.quantity), + age + ]) + + return pd.DataFrame(data=data, columns=columns) + + def market_status_data_frame(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> pd.DataFrame: + markets_data = [] + markets_columns = ["Exchange", "Market", "Best Bid", "Best Ask", f"Ref Price ({self._price_type.name})"] + if self._price_type is PriceType.LastOwnTrade and self._last_own_trade_price.is_nan(): + markets_columns[-1] = "Ref Price (MidPrice)" + market_books = [(self._market_info.market, self._market_info.trading_pair)] + if type(self._asset_price_delegate) is OrderBookAssetPriceDelegate: + market_books.append((self._asset_price_delegate.market, self._asset_price_delegate.trading_pair)) + for market, trading_pair in market_books: + bid_price = market.get_price(trading_pair, False) + ask_price = market.get_price(trading_pair, True) + ref_price = float("nan") + if market == self._market_info.market and self._inventory_cost_price_delegate is not None: + # We're using inventory_cost, show it's price + ref_price = self._inventory_cost_price_delegate.get_price() + if ref_price is None: + ref_price = self.get_price() + elif market == self._market_info.market and self._asset_price_delegate is None: + ref_price = self.get_price() + elif ( + self._asset_price_delegate is not None + and market == self._asset_price_delegate.market + and self._price_type is not PriceType.LastOwnTrade + ): + ref_price = self._asset_price_delegate.get_price_by_type(self._price_type) + markets_data.append([ + market.display_name, + trading_pair, + float(bid_price), + float(ask_price), + float(ref_price) + ]) + return pd.DataFrame(data=markets_data, columns=markets_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + if not self._all_markets_ready: + return "Market connectors are not ready." + cdef: + list lines = [] + list warning_lines = [] + warning_lines.extend(self._ping_pong_warning_lines) + warning_lines.extend(self.network_warning([self._market_info])) + + markets_df = map_df_to_str(self.market_status_data_frame([self._market_info])) + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.pure_mm_assets_df(not self._inventory_skew_enabled)) + # append inventory skew stats. + if self._inventory_skew_enabled: + inventory_skew_df = map_df_to_str(self.inventory_skew_stats_data_frame()) + assets_df = assets_df.append(inventory_skew_df) + + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + # See if there're any open orders. + if len(self.active_orders) > 0: + df = map_df_to_str(self.active_orders_df()) + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning([self._market_info])) + + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + # The following exposed Python functions are meant for unit tests + # --------------------------------------------------------------- + def execute_orders_proposal(self, proposal: Proposal): + return self.c_execute_orders_proposal(proposal) + + def cancel_order(self, order_id: str): + return self.c_cancel_order(self._market_info, order_id) + + # --------------------------------------------------------------- + + cdef c_start(self, Clock clock, double timestamp): + StrategyBase.c_start(self, clock, timestamp) + self._last_timestamp = timestamp + + self._hanging_orders_tracker.register_events(self.active_markets) + + if self._hanging_orders_enabled: + # start tracking any restored limit order + restored_order_ids = self.c_track_restored_orders(self.market_info) + # make restored order hanging orders + for order_id in restored_order_ids: + order = next(o for o in self.market_info.market.limit_orders if o.client_order_id == order_id) + if order: + self._hanging_orders_tracker.add_as_hanging_order(order) + + cdef c_stop(self, Clock clock): + self._hanging_orders_tracker.unregister_events(self.active_markets) + StrategyBase.c_stop(self, clock) + + cdef c_tick(self, double timestamp): + StrategyBase.c_tick(self, timestamp) + + cdef: + int64_t current_tick = (timestamp // self._status_report_interval) + int64_t last_tick = (self._last_timestamp // self._status_report_interval) + bint should_report_warnings = ((current_tick > last_tick) and + (self._logging_options & self.OPTION_LOG_STATUS_REPORT)) + cdef object proposal + try: + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self._sb_markets]) + if self._asset_price_delegate is not None and self._all_markets_ready: + self._all_markets_ready = self._asset_price_delegate.ready + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning(f"Markets are not ready. No market making trades are permitted.") + return + + if should_report_warnings: + if not all([market.network_status is NetworkStatus.CONNECTED for market in self._sb_markets]): + self.logger().warning(f"WARNING: Some markets are not connected or are down at the moment. Market " + f"making may be dangerous when markets or networks are unstable.") + + proposal = None + if self._create_timestamp <= self._current_timestamp: + # 1. Create base order proposals + proposal = self.c_create_base_proposal() + # 2. Apply functions that limit numbers of buys and sells proposal + self.c_apply_order_levels_modifiers(proposal) + # 3. Apply functions that modify orders price + self.c_apply_order_price_modifiers(proposal) + # 4. Apply functions that modify orders size + self.c_apply_order_size_modifiers(proposal) + # 5. Apply budget constraint, i.e. can't buy/sell more than what you have. + self.c_apply_budget_constraint(proposal) + + if not self._take_if_crossed: + self.c_filter_out_takers(proposal) + + self._hanging_orders_tracker.process_tick() + + self.c_cancel_active_orders_on_max_age_limit() + self.c_cancel_active_orders(proposal) + self.c_cancel_orders_below_min_spread() + if self.c_to_create_orders(proposal): + self.c_execute_orders_proposal(proposal) + #if self.buy_order_placed: + self.c_place_market_sell_orders() + finally: + self._last_timestamp = timestamp + + cdef object c_create_base_proposal(self): + cdef: + ExchangeBase market = self._market_info.market + list buys = [] + list market_buys = [] + + buy_reference_price = sell_reference_price = self.get_price() + + if self._inventory_cost_price_delegate is not None: + inventory_cost_price = self._inventory_cost_price_delegate.get_price() + if inventory_cost_price is not None: + # Only limit sell price. Buy are always allowed. + sell_reference_price = max(inventory_cost_price, sell_reference_price) + else: + base_balance = float(market.get_balance(self._market_info.base_asset)) + if base_balance > 0: + raise RuntimeError("Initial inventory price is not set while inventory_cost feature is active.") + + # First to check if a customized order override is configured, otherwise the proposal will be created according + # to order spread, amount, and levels setting. + order_override = self._order_override + if order_override is not None and len(order_override) > 0: + for key, value in order_override.items(): + if str(value[0]) in ["buy"]: + if str(value[0]) == "buy" and not buy_reference_price.is_nan(): + price = buy_reference_price * (Decimal("1") - Decimal(str(value[1])) / Decimal("100")) + price = market.c_quantize_order_price(self.trading_pair, price) + size = Decimal(str(value[2])) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0 and price > 0: + buys.append(PriceSize(price, size)) + else: + if not buy_reference_price.is_nan(): + for level in range(0, self._buy_levels): + price = buy_reference_price * (Decimal("1") - self._bid_spread - (level * self._order_level_spread)) + price = market.c_quantize_order_price(self.trading_pair, price) + size = self._order_amount + (self._order_level_amount * level) + size = market.c_quantize_order_amount(self.trading_pair, size) + if size > 0: + buys.append(PriceSize(price, size)) + + # Market buy orders logic + # Assume that we want to match each limit buy order with a market buy order + for buy_order in buys: + # Here, we assume the size of the market buy is the same as the limit buy + market_buys.append(PriceSize(Decimal("0"), buy_order.size)) # Price is set to 0 for market orders + + return Proposal(buys, market_buys) + + cdef tuple c_get_adjusted_available_balance(self, list orders): + """ + Calculates the available balance, plus the amount attributed to orders. + :return: (base amount, quote amount) in Decimal + """ + cdef: + ExchangeBase market = self._market_info.market + object base_balance = market.c_get_available_balance(self.base_asset) + object quote_balance = market.c_get_available_balance(self.quote_asset) + + for order in orders: + if order.is_buy: + quote_balance += order.quantity * order.price + else: + base_balance += order.quantity + + return base_balance, quote_balance + + cdef c_apply_order_levels_modifiers(self, proposal): + self.c_apply_price_band(proposal) + if self.moving_price_band_enabled: + self.c_apply_moving_price_band(proposal) + if self._ping_pong_enabled: + self.c_apply_ping_pong(proposal) + + cdef c_apply_price_band(self, proposal): + if self._price_ceiling > 0 and self.get_price() >= self._price_ceiling: + proposal.buys = [] + if self._price_floor > 0 and self.get_price() <= self._price_floor: + proposal.sells = [] + + cdef c_apply_moving_price_band(self, proposal): + price = self.get_price() + self._moving_price_band.check_and_update_price_band( + self.current_timestamp, price) + if self._moving_price_band.check_price_ceiling_exceeded(price): + proposal.buys = [] + if self._moving_price_band.check_price_floor_exceeded(price): + proposal.sells = [] + + cdef c_apply_ping_pong(self, object proposal): + self._ping_pong_warning_lines = [] + if self._filled_buys_balance == self._filled_sells_balance: + self._filled_buys_balance = self._filled_sells_balance = 0 + if self._filled_buys_balance > 0: + proposal.buys = proposal.buys[self._filled_buys_balance:] + self._ping_pong_warning_lines.extend( + [f" Ping-pong removed {self._filled_buys_balance} buy orders."] + ) + if self._filled_sells_balance > 0: + proposal.sells = proposal.sells[self._filled_sells_balance:] + self._ping_pong_warning_lines.extend( + [f" Ping-pong removed {self._filled_sells_balance} sell orders."] + ) + + cdef c_apply_order_price_modifiers(self, object proposal): + if self._order_optimization_enabled: + self.c_apply_order_optimization(proposal) + + if self._add_transaction_costs_to_orders: + self.c_apply_add_transaction_costs(proposal) + + cdef c_apply_order_size_modifiers(self, object proposal): + if self._inventory_skew_enabled: + self.c_apply_inventory_skew(proposal) + + cdef c_apply_inventory_skew(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object bid_adj_ratio + object ask_adj_ratio + object size + + base_balance, quote_balance = self.c_get_adjusted_available_balance(self.active_orders) + + total_order_size = calculate_total_order_size(self._order_amount, self._order_level_amount, self._order_levels) + bid_ask_ratios = c_calculate_bid_ask_ratios_from_base_asset_ratio( + float(base_balance), + float(quote_balance), + float(self.get_price()), + float(self._inventory_target_base_pct), + float(total_order_size * self._inventory_range_multiplier) + ) + bid_adj_ratio = Decimal(bid_ask_ratios.bid_ratio) + ask_adj_ratio = Decimal(bid_ask_ratios.ask_ratio) + + for buy in proposal.buys: + size = buy.size * bid_adj_ratio + size = market.c_quantize_order_amount(self.trading_pair, size) + buy.size = size + + for sell in proposal.sells: + size = sell.size * ask_adj_ratio + size = market.c_quantize_order_amount(self.trading_pair, size, sell.price) + sell.size = size + + def adjusted_available_balance_for_orders_budget_constrain(self): + candidate_hanging_orders = self.hanging_orders_tracker.candidate_hanging_orders_from_pairs() + non_hanging = [] + if self.market_info in self._sb_order_tracker.get_limit_orders(): + all_orders = self._sb_order_tracker.get_limit_orders()[self.market_info].values() + non_hanging = [order for order in all_orders + if not self._hanging_orders_tracker.is_order_id_in_hanging_orders(order.client_order_id)] + all_non_hanging_orders = list(set(non_hanging) - set(candidate_hanging_orders)) + return self.c_get_adjusted_available_balance(all_non_hanging_orders) + + cdef c_apply_budget_constraint(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object quote_size + object base_size + object adjusted_amount + + base_balance, quote_balance = self.adjusted_available_balance_for_orders_budget_constrain() + + for buy in proposal.buys: + buy_fee = market.c_get_fee(self.base_asset, self.quote_asset, OrderType.LIMIT, TradeType.BUY, + buy.size, buy.price) + quote_size = buy.size * buy.price * (Decimal(1) + buy_fee.percent) + + # Adjust buy order size to use remaining balance if less than the order amount + if quote_balance < quote_size: + adjusted_amount = quote_balance / (buy.price * (Decimal("1") + buy_fee.percent)) + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, adjusted_amount) + buy.size = adjusted_amount + quote_balance = s_decimal_zero + elif quote_balance == s_decimal_zero: + buy.size = s_decimal_zero + else: + quote_balance -= quote_size + + proposal.buys = [o for o in proposal.buys if o.size > 0] + + for sell in proposal.sells: + base_size = sell.size + + # Adjust sell order size to use remaining balance if less than the order amount + if base_balance < base_size: + adjusted_amount = market.c_quantize_order_amount(self.trading_pair, base_balance) + sell.size = adjusted_amount + base_balance = s_decimal_zero + elif base_balance == s_decimal_zero: + sell.size = s_decimal_zero + else: + base_balance -= base_size + + proposal.sells = [o for o in proposal.sells if o.size > 0] + + cdef c_filter_out_takers(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + list new_buys = [] + list new_sells = [] + top_ask = market.c_get_price(self.trading_pair, True) + if not top_ask.is_nan(): + proposal.buys = [buy for buy in proposal.buys if buy.price < top_ask] + top_bid = market.c_get_price(self.trading_pair, False) + if not top_bid.is_nan(): + proposal.sells = [sell for sell in proposal.sells if sell.price > top_bid] + + # Compare the market price with the top bid and top ask price + cdef c_apply_order_optimization(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + object own_buy_size = s_decimal_zero + object own_sell_size = s_decimal_zero + + for order in self.active_orders: + if order.is_buy: + own_buy_size = order.quantity + else: + own_sell_size = order.quantity + + if len(proposal.buys) > 0: + # Get the top bid price in the market using order_optimization_depth and your buy order volume + top_bid_price = self._market_info.get_price_for_volume( + False, self._bid_order_optimization_depth + own_buy_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_bid_price + ) + # Get the price above the top bid + price_above_bid = (ceil(top_bid_price / price_quantum) + 1) * price_quantum + + # If the price_above_bid is lower than the price suggested by the top pricing proposal, + # lower the price and from there apply the order_level_spread to each order in the next levels + proposal.buys = sorted(proposal.buys, key = lambda p: p.price, reverse = True) + lower_buy_price = min(proposal.buys[0].price, price_above_bid) + for i, proposed in enumerate(proposal.buys): + if self._split_order_levels_enabled: + proposal.buys[i].price = (market.c_quantize_order_price(self.trading_pair, lower_buy_price) + * (1 - self._bid_order_level_spreads[i] / Decimal("100")) + / (1-self._bid_order_level_spreads[0] / Decimal("100"))) + continue + proposal.buys[i].price = market.c_quantize_order_price(self.trading_pair, lower_buy_price) * (1 - self.order_level_spread * i) + + if len(proposal.sells) > 0: + # Get the top ask price in the market using order_optimization_depth and your sell order volume + top_ask_price = self._market_info.get_price_for_volume( + True, self._ask_order_optimization_depth + own_sell_size).result_price + price_quantum = market.c_get_order_price_quantum( + self.trading_pair, + top_ask_price + ) + # Get the price below the top ask + price_below_ask = (floor(top_ask_price / price_quantum) - 1) * price_quantum + + # If the price_below_ask is higher than the price suggested by the pricing proposal, + # increase your price and from there apply the order_level_spread to each order in the next levels + proposal.sells = sorted(proposal.sells, key = lambda p: p.price) + higher_sell_price = max(proposal.sells[0].price, price_below_ask) + for i, proposed in enumerate(proposal.sells): + if self._split_order_levels_enabled: + proposal.sells[i].price = (market.c_quantize_order_price(self.trading_pair, higher_sell_price) + * (1 + self._ask_order_level_spreads[i] / Decimal("100")) + / (1 + self._ask_order_level_spreads[0] / Decimal("100"))) + continue + proposal.sells[i].price = market.c_quantize_order_price(self.trading_pair, higher_sell_price) * (1 + self.order_level_spread * i) + + cdef object c_apply_add_transaction_costs(self, object proposal): + cdef: + ExchangeBase market = self._market_info.market + for buy in proposal.buys: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.BUY, buy.size, buy.price) + price = buy.price * (Decimal(1) - fee.percent) + buy.price = market.c_quantize_order_price(self.trading_pair, price) + for sell in proposal.sells: + fee = market.c_get_fee(self.base_asset, self.quote_asset, + self._limit_order_type, TradeType.SELL, sell.size, sell.price) + price = sell.price * (Decimal(1) + fee.percent) + sell.price = market.c_quantize_order_price(self.trading_pair, price) + + cdef c_did_fill_order(self, object order_filled_event): + cdef: + str order_id = order_filled_event.order_id + object market_info = self._sb_order_tracker.c_get_shadow_market_pair_from_order_id(order_id) + tuple order_fill_record + + if market_info is not None: + limit_order_record = self._sb_order_tracker.c_get_shadow_limit_order(order_id) + order_fill_record = (limit_order_record, order_filled_event) + + if order_filled_event.trade_type is TradeType.BUY: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker buy order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + else: + if self._logging_options & self.OPTION_LOG_MAKER_ORDER_FILLED: + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Maker sell order of " + f"{order_filled_event.amount} {market_info.base_asset} filled." + ) + + if self._inventory_cost_price_delegate is not None: + self._inventory_cost_price_delegate.process_order_fill_event(order_filled_event) + + cdef c_did_complete_buy_order(self, object order_completed_event): + self.logger().info(f"Order completed event received: {order_completed_event}") + cdef: + str order_id = order_completed_event.order_id + limit_order_record = self._sb_order_tracker.c_get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + active_sell_ids = [x.client_order_id for x in self.active_orders if not x.is_buy] + + if self._hanging_orders_enabled: + # If the filled order is a hanging order, do nothing + if order_id in self.hanging_order_ids: + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Hanging maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Hanging maker BUY order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + return + + # delay order creation by filled_order_dalay (in seconds) + self._create_timestamp = self._current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._filled_buys_balance += 1 + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker buy order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker BUY order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + + cdef c_place_market_sell_orders(self): + self.logger().info(f"Checking if a buy order was placed: {self.buy_order_placed}") + if self.buy_order_placed: + self.logger().info(f"Attempting to place market sell order for {self._market_info.trading_pair} with amount {self._order_amount}") + try: + result = self.sell_with_specific_market( + self._market_info, + self._order_amount, + OrderType.MARKET + ) + self.logger().info(f"Market sell order result: {result}") + except Exception as e: + self.logger().error(f"Error placing market sell order: {str(e)}", exc_info=True) + finally: + # Reset the flag after attempting to place a sell order + self.buy_order_placed = False + else: + self.logger().info("No buy order placed yet, skipping market sell order.") + + + cdef c_did_complete_sell_order(self, object order_completed_event): + cdef: + str order_id = order_completed_event.order_id + LimitOrder limit_order_record = self._sb_order_tracker.c_get_limit_order(self._market_info, order_id) + if limit_order_record is None: + return + active_buy_ids = [x.client_order_id for x in self.active_orders if x.is_buy] + if self._hanging_orders_enabled: + # If the filled order is a hanging order, do nothing + if order_id in self.hanging_order_ids: + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Hanging maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Hanging maker SELL order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + return + + # delay order creation by filled_order_dalay (in seconds) + self._create_timestamp = self._current_timestamp + self._filled_order_delay + self._cancel_timestamp = min(self._cancel_timestamp, self._create_timestamp) + + self._filled_sells_balance += 1 + self._last_own_trade_price = limit_order_record.price + + self.log_with_clock( + logging.INFO, + f"({self.trading_pair}) Maker sell order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been completely filled." + ) + self.notify_hb_app_with_timestamp( + f"Maker SELL order {limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency} is filled." + ) + + cdef bint c_is_within_tolerance(self, list current_prices, list proposal_prices): + if len(current_prices) != len(proposal_prices): + return False + current_prices = sorted(current_prices) + proposal_prices = sorted(proposal_prices) + for current, proposal in zip(current_prices, proposal_prices): + # if spread diff is more than the tolerance or order quantities are different, return false. + if abs(proposal - current)/current > self._order_refresh_tolerance_pct: + return False + return True + + cdef c_cancel_active_orders_on_max_age_limit(self): + """ + Cancels active non hanging orders if they are older than max age limit + """ + cdef: + list active_orders = self.active_non_hanging_orders + + if active_orders and any(order_age(o, self._current_timestamp) > self._max_order_age for o in active_orders): + for order in active_orders: + self.c_cancel_order(self._market_info, order.client_order_id) + + cdef c_cancel_active_orders(self, object proposal): + """ + Cancels active non hanging orders, checks if the order prices are within tolerance threshold + """ + if self._cancel_timestamp > self._current_timestamp: + return + + cdef: + list active_orders = self.active_non_hanging_orders + list active_buy_prices = [] + list active_sells = [] + bint to_defer_canceling = False + if len(active_orders) == 0: + return + if proposal is not None and \ + self._order_refresh_tolerance_pct >= 0: + + active_buy_prices = [Decimal(str(o.price)) for o in active_orders if o.is_buy] + active_sell_prices = [Decimal(str(o.price)) for o in active_orders if not o.is_buy] + proposal_buys = [buy.price for buy in proposal.buys] + proposal_sells = [sell.price for sell in proposal.sells] + + if self.c_is_within_tolerance(active_buy_prices, proposal_buys) and \ + self.c_is_within_tolerance(active_sell_prices, proposal_sells): + to_defer_canceling = True + + if not to_defer_canceling: + self._hanging_orders_tracker.update_strategy_orders_with_equivalent_orders() + for order in self.active_non_hanging_orders: + # If is about to be added to hanging_orders then don't cancel + if not self._hanging_orders_tracker.is_potential_hanging_order(order): + self.c_cancel_order(self._market_info, order.client_order_id) + # else: + # self.set_timers() + + # Cancel Non-Hanging, Active Orders if Spreads are below minimum_spread + cdef c_cancel_orders_below_min_spread(self): + cdef: + list active_orders = self.market_info_to_active_orders.get(self._market_info, []) + object price = self.get_price() + active_orders = [order for order in active_orders + if order.client_order_id not in self.hanging_order_ids] + for order in active_orders: + negation = -1 if order.is_buy else 1 + if (negation * (order.price - price) / price) < self._minimum_spread: + self.logger().info(f"Order is below minimum spread ({self._minimum_spread})." + f" Canceling Order: ({'Buy' if order.is_buy else 'Sell'}) " + f"ID - {order.client_order_id}") + self.c_cancel_order(self._market_info, order.client_order_id) + + cdef bint c_to_create_orders(self, object proposal): + non_hanging_orders_non_cancelled = [o for o in self.active_non_hanging_orders if not + self._hanging_orders_tracker.is_potential_hanging_order(o)] + return (self._create_timestamp < self._current_timestamp + and (not self._should_wait_order_cancel_confirmation or + len(self._sb_order_tracker.in_flight_cancels) == 0) + and proposal is not None + and len(non_hanging_orders_non_cancelled) == 0) + + cdef c_execute_orders_proposal(self, object proposal): + cdef: + double expiration_seconds = NaN + str bid_order_id, ask_order_id + bint orders_created = False + # Number of pair of orders to track for hanging orders + number_of_pairs = min((len(proposal.buys), len(proposal.sells))) if self._hanging_orders_enabled else 0 + + if len(proposal.buys) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{buy.size.normalize()} {self.base_asset}, " + f"{buy.price.normalize()} {self.quote_asset}" + for buy in proposal.buys] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.buys)} bid orders " + f"at (Size, Price): {price_quote_str}" + ) + for idx, buy in enumerate(proposal.buys): + bid_order_id = self.c_buy_with_specific_market( + self._market_info, + buy.size, + order_type=self._limit_order_type, + price=buy.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + self.buy_order_placed = True # Set the flag to True after placing a buy order + self.logger().info(f"Buy order placed, order_id: {bid_order_id}") # Logging for confirmation + + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == bid_order_id)) + if order: + self._hanging_orders_tracker.add_current_pairs_of_proposal_orders_executed_by_strategy( + CreatedPairOfOrders(order, None)) + if len(proposal.sells) > 0: + if self._logging_options & self.OPTION_LOG_CREATE_ORDER: + price_quote_str = [f"{sell.size.normalize()} {self.base_asset}, " + f"{sell.price.normalize()} {self.quote_asset}" + for sell in proposal.sells] + self.logger().info( + f"({self.trading_pair}) Creating {len(proposal.sells)} ask " + f"orders at (Size, Price): {price_quote_str}" + ) + for idx, sell in enumerate(proposal.sells): + ask_order_id = self.c_sell_with_specific_market( + self._market_info, + sell.size, + order_type=self._limit_order_type, + price=sell.price, + expiration_seconds=expiration_seconds + ) + orders_created = True + if idx < number_of_pairs: + order = next((o for o in self.active_orders if o.client_order_id == ask_order_id)) + if order: + self._hanging_orders_tracker.current_created_pairs_of_orders[idx].sell_order = order + if orders_created: + self.set_timers() + + cdef set_timers(self): + cdef double next_cycle = self._current_timestamp + self._order_refresh_time + if self._create_timestamp <= self._current_timestamp: + self._create_timestamp = next_cycle + if self._cancel_timestamp <= self._current_timestamp: + self._cancel_timestamp = min(self._create_timestamp, next_cycle) + + def notify_hb_app(self, msg: str): + if self._hb_app_notification: + super().notify_hb_app(msg) + + def get_price_type(self, price_type_str: str) -> PriceType: + if price_type_str == "mid_price": + return PriceType.MidPrice + elif price_type_str == "best_bid": + return PriceType.BestBid + elif price_type_str == "best_ask": + return PriceType.BestAsk + elif price_type_str == "last_price": + return PriceType.LastTrade + elif price_type_str == 'last_own_trade_price': + return PriceType.LastOwnTrade + elif price_type_str == 'inventory_cost': + return PriceType.InventoryCost + elif price_type_str == "custom": + return PriceType.Custom + else: + raise ValueError(f"Unrecognized price type string {price_type_str}.") \ No newline at end of file diff --git a/hummingbot/strategy/pure_volume/pure_volume_config_map.py b/hummingbot/strategy/pure_volume/pure_volume_config_map.py new file mode 100644 index 0000000..a46a62a --- /dev/null +++ b/hummingbot/strategy/pure_volume/pure_volume_config_map.py @@ -0,0 +1,441 @@ +import decimal +from decimal import Decimal +from typing import Optional + +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_connector, + validate_decimal, + validate_exchange, + validate_int, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges + + +def maker_trading_pair_prompt(): + exchange = pure_volume_config_map.get("exchange").value + example = AllConnectorSettings.get_example_pairs().get(exchange) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (exchange, f" (e.g. {example})" if example else "") + + +# strategy specific validators +def validate_exchange_trading_pair(value: str) -> Optional[str]: + exchange = pure_volume_config_map.get("exchange").value + return validate_market_trading_pair(exchange, value) + + +def order_amount_prompt() -> str: + trading_pair = pure_volume_config_map["market"].value + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order? >>> " + + +def validate_price_source(value: str) -> Optional[str]: + if value not in {"current_market", "external_market", "custom_api"}: + return "Invalid price source type." + + +def on_validate_price_source(value: str): + if value != "external_market": + pure_volume_config_map["price_source_exchange"].value = None + pure_volume_config_map["price_source_market"].value = None + pure_volume_config_map["take_if_crossed"].value = None + if value != "custom_api": + pure_volume_config_map["price_source_custom_api"].value = None + else: + pure_volume_config_map["price_type"].value = "custom" + + +def price_source_market_prompt() -> str: + external_market = pure_volume_config_map.get("price_source_exchange").value + return f'Enter the token trading pair on {external_market} >>> ' + + +def validate_price_source_exchange(value: str) -> Optional[str]: + if value == pure_volume_config_map.get("exchange").value: + return "Price source exchange cannot be the same as maker exchange." + return validate_connector(value) + + +def on_validated_price_source_exchange(value: str): + if value is None: + pure_volume_config_map["price_source_market"].value = None + + +def validate_price_source_market(value: str) -> Optional[str]: + market = pure_volume_config_map.get("price_source_exchange").value + return validate_market_trading_pair(market, value) + + +def validate_price_floor_ceiling(value: str) -> Optional[str]: + try: + decimal_value = Decimal(value) + except Exception: + return f"{value} is not in decimal format." + if not (decimal_value == Decimal("-1") or decimal_value > Decimal("0")): + return "Value must be more than 0 or -1 to disable this feature." + + +def validate_price_type(value: str) -> Optional[str]: + error = None + price_source = pure_volume_config_map.get("price_source").value + if price_source != "custom_api": + valid_values = {"mid_price", + "last_price", + "last_own_trade_price", + "best_bid", + "best_ask", + "inventory_cost", + } + if value not in valid_values: + error = "Invalid price type." + elif value != "custom": + error = "Invalid price type." + return error + + +def on_validated_price_type(value: str): + if value == 'inventory_cost': + pure_volume_config_map["inventory_price"].value = None + + +def exchange_on_validated(value: str): + required_exchanges.add(value) + + +def validate_decimal_list(value: str) -> Optional[str]: + decimal_list = list(value.split(",")) + for number in decimal_list: + try: + validate_result = validate_decimal(Decimal(number), 0, 100, inclusive=False) + except decimal.InvalidOperation: + return "Please enter valid decimal numbers" + if validate_result is not None: + return validate_result + + +pure_volume_config_map = { + "strategy": + ConfigVar(key="strategy", + prompt=None, + default="pure_volume"), + "exchange": + ConfigVar(key="exchange", + prompt="Enter your maker spot connector >>> ", + validator=validate_exchange, + on_validated=exchange_on_validated, + prompt_on_new=True), + "market": + ConfigVar(key="market", + prompt=maker_trading_pair_prompt, + validator=validate_exchange_trading_pair, + prompt_on_new=True), + "bid_spread": + ConfigVar(key="bid_spread", + prompt="How far away from the mid price do you want to place the " + "first bid order? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + prompt_on_new=True), + "minimum_spread": + ConfigVar(key="minimum_spread", + prompt="At what minimum spread should the bot automatically cancel orders? (Enter 1 for 1%) >>> ", + required_if=lambda: False, + type_str="decimal", + default=Decimal(-100), + validator=lambda v: validate_decimal(v, -100, 100, True)), + "order_refresh_time": + ConfigVar(key="order_refresh_time", + prompt="How often do you want to place a new bid after the market order fills " + "(in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True), + "max_order_age": + ConfigVar(key="max_order_age", + prompt="How long do you want to cancel and replace bids and asks " + "with the same price (in seconds)? >>> ", + type_str="float", + default=Decimal("1800"), + validator=lambda v: validate_decimal(v, 0, inclusive=False)), + "order_refresh_tolerance_pct": + ConfigVar(key="order_refresh_tolerance_pct", + prompt="Enter the percent change in price needed to refresh orders at each cycle " + "(Enter 1 to indicate 1%) >>> ", + type_str="decimal", + default=Decimal("0"), + validator=lambda v: validate_decimal(v, -10, 10, inclusive=True)), + "order_amount": + ConfigVar(key="order_amount", + prompt=order_amount_prompt, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=False), + prompt_on_new=True), + "price_ceiling": + ConfigVar(key="price_ceiling", + prompt="Enter the price point above which only sell orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "price_floor": + ConfigVar(key="price_floor", + prompt="Enter the price below which only buy orders will be placed " + "(Enter -1 to deactivate this feature) >>> ", + type_str="decimal", + default=Decimal("-1"), + validator=validate_price_floor_ceiling), + "moving_price_band_enabled": + ConfigVar(key="moving_price_band_enabled", + prompt="Would you like to enable moving price floor and ceiling? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "price_ceiling_pct": + ConfigVar(key="price_ceiling_pct", + prompt="Enter a percentage to the current price that sets the price ceiling. Above this price, only sell orders will be placed >>> ", + type_str="decimal", + default=Decimal("1"), + required_if=lambda: pure_volume_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "price_floor_pct": + ConfigVar(key="price_floor_pct", + prompt="Enter a percentage to the current price that sets the price floor. Below this price, only buy orders will be placed >>> ", + type_str="decimal", + default=Decimal("-1"), + required_if=lambda: pure_volume_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "price_band_refresh_time": + ConfigVar(key="price_band_refresh_time", + prompt="After this amount of time (in seconds), the price bands are reset based on the current price >>> ", + type_str="float", + default=86400, + required_if=lambda: pure_volume_config_map.get("moving_price_band_enabled").value, + validator=validate_decimal), + "ping_pong_enabled": + ConfigVar(key="ping_pong_enabled", + prompt="Would you like to use the ping pong feature and alternate between buy and sell orders after fills? (Yes/No) >>> ", + type_str="bool", + default=False, + prompt_on_new=False, + validator=validate_bool), + "order_levels": + ConfigVar(key="order_levels", + prompt="How many orders do you want to place on both sides? >>> ", + type_str="int", + validator=lambda v: validate_int(v, min_value=-1, inclusive=False), + default=1), + "order_level_amount": + ConfigVar(key="order_level_amount", + prompt="How much do you want to increase or decrease the order size for each " + "additional order? (decrease < 0 > increase) >>> ", + required_if=lambda: pure_volume_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v), + default=0), + "order_level_spread": + ConfigVar(key="order_level_spread", + prompt="Enter the price increments (as percentage) for subsequent " + "orders? (Enter 1 to indicate 1%) >>> ", + required_if=lambda: pure_volume_config_map.get("order_levels").value > 1, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False), + default=Decimal("1")), + "inventory_skew_enabled": + ConfigVar(key="inventory_skew_enabled", + prompt="Would you like to enable inventory skew? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "inventory_target_base_pct": + ConfigVar(key="inventory_target_base_pct", + prompt="What is your target base asset percentage? Enter 50 for 50% >>> ", + required_if=lambda: pure_volume_config_map.get("inventory_skew_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, 0, 100), + default=Decimal("50")), + "inventory_range_multiplier": + ConfigVar(key="inventory_range_multiplier", + prompt="What is your tolerable range of inventory around the target, " + "expressed in multiples of your total order size? ", + required_if=lambda: pure_volume_config_map.get("inventory_skew_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=Decimal("1")), + "inventory_price": + ConfigVar(key="inventory_price", + prompt="What is the price of your base asset inventory? ", + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=True), + required_if=lambda: pure_volume_config_map.get("price_type").value == "inventory_cost", + default=Decimal("1"), + ), + "filled_order_delay": + ConfigVar(key="filled_order_delay", + prompt="How long do you want to wait before placing the market order " + "if your order gets filled (in seconds)? >>> ", + type_str="float", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=60), + "hanging_orders_enabled": + ConfigVar(key="hanging_orders_enabled", + prompt="Do you want to enable hanging orders? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "hanging_orders_cancel_pct": + ConfigVar(key="hanging_orders_cancel_pct", + prompt="At what spread percentage (from mid price) will hanging orders be canceled? " + "(Enter 1 to indicate 1%) >>> ", + required_if=lambda: pure_volume_config_map.get("hanging_orders_enabled").value, + type_str="decimal", + default=Decimal("10"), + validator=lambda v: validate_decimal(v, 0, 100, inclusive=False)), + "order_optimization_enabled": + ConfigVar(key="order_optimization_enabled", + prompt="Do you want to enable best bid ask jumping? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "ask_order_optimization_depth": + ConfigVar(key="ask_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top ask, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: pure_volume_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "bid_order_optimization_depth": + ConfigVar(key="bid_order_optimization_depth", + prompt="How deep do you want to go into the order book for calculating " + "the top bid, ignoring dust orders on the top " + "(expressed in base asset amount)? >>> ", + required_if=lambda: pure_volume_config_map.get("order_optimization_enabled").value, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=0), + default=0), + "add_transaction_costs": + ConfigVar(key="add_transaction_costs", + prompt="Do you want to add transaction costs automatically to order prices? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool), + "price_source": + ConfigVar(key="price_source", + prompt="Which price source to use? (current_market/external_market/custom_api) >>> ", + type_str="str", + default="current_market", + validator=validate_price_source, + on_validated=on_validate_price_source), + "price_type": + ConfigVar(key="price_type", + prompt="Which price type to use? (" + "mid_price/last_price/last_own_trade_price/best_bid/best_ask/inventory_cost) >>> ", + type_str="str", + required_if=lambda: pure_volume_config_map.get("price_source").value != "custom_api", + default="mid_price", + on_validated=on_validated_price_type, + validator=validate_price_type), + "price_source_exchange": + ConfigVar(key="price_source_exchange", + prompt="Enter external price source exchange name >>> ", + required_if=lambda: pure_volume_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_exchange, + on_validated=on_validated_price_source_exchange), + "price_source_market": + ConfigVar(key="price_source_market", + prompt=price_source_market_prompt, + required_if=lambda: pure_volume_config_map.get("price_source").value == "external_market", + type_str="str", + validator=validate_price_source_market), + "take_if_crossed": + ConfigVar(key="take_if_crossed", + prompt="Do you want to take the best order if orders cross the orderbook? ((Yes/No) >>> ", + required_if=lambda: pure_volume_config_map.get( + "price_source").value == "external_market", + type_str="bool", + validator=validate_bool), + "price_source_custom_api": + ConfigVar(key="price_source_custom_api", + prompt="Enter pricing API URL >>> ", + required_if=lambda: pure_volume_config_map.get("price_source").value == "custom_api", + type_str="str"), + "custom_api_update_interval": + ConfigVar(key="custom_api_update_interval", + prompt="Enter custom API update interval in second (default: 5.0, min: 0.5) >>> ", + required_if=lambda: False, + default=float(5), + type_str="float", + validator=lambda v: validate_decimal(v, Decimal("0.5"))), + "order_override": + ConfigVar(key="order_override", + prompt=None, + required_if=lambda: False, + default=None, + type_str="json"), + "should_wait_order_cancel_confirmation": + ConfigVar(key="should_wait_order_cancel_confirmation", + prompt="Should the strategy wait to receive a confirmation for orders cancelation " + "before creating a new set of orders? " + "(Not waiting requires enough available balance) (Yes/No) >>> ", + type_str="bool", + default=True, + validator=validate_bool), + "split_order_levels_enabled": + ConfigVar(key="split_order_levels_enabled", + prompt="Do you want bid and ask orders to be placed at multiple defined spread and amount? " + "This acts as an overrides which replaces order_amount, order_spreads, " + "order_level_amount, order_level_spreads (Yes/No) >>> ", + default=False, + type_str="bool", + validator=validate_bool), + "bid_order_level_spreads": + ConfigVar(key="bid_order_level_spreads", + prompt="Enter the spreads (as percentage) for all bid spreads " + "e.g 1,2,3,4 to represent 1%,2%,3%,4%. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_volume_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "ask_order_level_spreads": + ConfigVar(key="ask_order_level_spreads", + prompt="Enter the spreads (as percentage) for all ask spreads " + "e.g 1,2,3,4 to represent 1%,2%,3%,4%. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_volume_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "bid_order_level_amounts": + ConfigVar(key="bid_order_level_amounts", + prompt="Enter the amount for all bid amounts. " + "e.g 1,2,3,4. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + type_str="str", + required_if=lambda: pure_volume_config_map.get( + "split_order_levels_enabled").value, + validator=validate_decimal_list), + "ask_order_level_amounts": + ConfigVar(key="ask_order_level_amounts", + prompt="Enter the amount for all ask amounts. " + "e.g 1,2,3,4. " + "The number of levels set will be equal to the " + "minimum length of bid_order_level_spreads and bid_order_level_amounts >>> ", + default=None, + required_if=lambda: pure_volume_config_map.get( + "split_order_levels_enabled").value, + type_str="str", + validator=validate_decimal_list), +} diff --git a/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pxd b/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pxd new file mode 100644 index 0000000..fdc79c8 --- /dev/null +++ b/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pxd @@ -0,0 +1,8 @@ +# distutils: language=c++ + +from hummingbot.strategy.order_tracker import OrderTracker +from hummingbot.strategy.order_tracker cimport OrderTracker + + +cdef class PureVolumeOrderTracker(OrderTracker): + pass diff --git a/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pyx b/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pyx new file mode 100644 index 0000000..792fb77 --- /dev/null +++ b/hummingbot/strategy/pure_volume/pure_volume_order_tracker.pyx @@ -0,0 +1,49 @@ +from typing import ( + Dict, + List, + Tuple +) + +from hummingbot.core.data_type.limit_order cimport LimitOrder +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_tracker cimport OrderTracker + +NaN = float("nan") + + +cdef class PureVolumeOrderTracker(OrderTracker): + # ETH confirmation requirement of Binance has shortened to 12 blocks as of 7/15/2019. + # 12 * 15 / 60 = 3 minutes + SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION = 60.0 * 3 + + def __init__(self): + super().__init__() + + @property + def active_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._tracked_limit_orders.items(): + for limit_order in orders_map.values(): + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def shadow_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + limit_orders = [] + for market_pair, orders_map in self._shadow_tracked_limit_orders.items(): + for limit_order in orders_map.values(): + limit_orders.append((market_pair.market, limit_order)) + return limit_orders + + @property + def market_pair_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + market_pair_to_orders = {} + market_pairs = self._tracked_limit_orders.keys() + for market_pair in market_pairs: + maker_orders = [] + for limit_order in self._tracked_limit_orders[market_pair].values(): + maker_orders.append(limit_order) + market_pair_to_orders[market_pair] = maker_orders + return market_pair_to_orders diff --git a/hummingbot/strategy/pure_volume/start.py b/hummingbot/strategy/pure_volume/start.py new file mode 100644 index 0000000..3f46e73 --- /dev/null +++ b/hummingbot/strategy/pure_volume/start.py @@ -0,0 +1,145 @@ +from decimal import Decimal +from typing import List, Optional, Tuple + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange.paper_trade import create_paper_trade_market +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.api_asset_price_delegate import APIAssetPriceDelegate +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.pure_volume import InventoryCostPriceDelegate, PureVolumeStrategy +from hummingbot.strategy.pure_volume.moving_price_band import MovingPriceBand +from hummingbot.strategy.pure_volume.pure_volume_config_map import pure_volume_config_map as c_map + + +def start(self): + def convert_decimal_string_to_list(string: Optional[str], divisor: Decimal = Decimal("1")) -> List[Decimal]: + '''convert order level spread string into a list of decimal divided by divisor ''' + if string is None: + return [] + string_list = list(string.split(",")) + return [Decimal(v) / divisor for v in string_list] + + try: + order_amount = c_map.get("order_amount").value + order_refresh_time = c_map.get("order_refresh_time").value + max_order_age = c_map.get("max_order_age").value + bid_spread = c_map.get("bid_spread").value / Decimal('100') + #ask_spread = c_map.get("ask_spread").value / Decimal('100') + minimum_spread = c_map.get("minimum_spread").value / Decimal('100') + price_ceiling = c_map.get("price_ceiling").value + price_floor = c_map.get("price_floor").value + ping_pong_enabled = c_map.get("ping_pong_enabled").value + order_levels = c_map.get("order_levels").value + order_level_amount = c_map.get("order_level_amount").value + order_level_spread = c_map.get("order_level_spread").value / Decimal('100') + exchange = c_map.get("exchange").value.lower() + raw_trading_pair = c_map.get("market").value + inventory_skew_enabled = c_map.get("inventory_skew_enabled").value + inventory_target_base_pct = 0 if c_map.get("inventory_target_base_pct").value is None else \ + c_map.get("inventory_target_base_pct").value / Decimal('100') + inventory_range_multiplier = c_map.get("inventory_range_multiplier").value + filled_order_delay = c_map.get("filled_order_delay").value + hanging_orders_enabled = c_map.get("hanging_orders_enabled").value + hanging_orders_cancel_pct = c_map.get("hanging_orders_cancel_pct").value / Decimal('100') + order_optimization_enabled = c_map.get("order_optimization_enabled").value + ask_order_optimization_depth = c_map.get("ask_order_optimization_depth").value + bid_order_optimization_depth = c_map.get("bid_order_optimization_depth").value + add_transaction_costs_to_orders = c_map.get("add_transaction_costs").value + price_source = c_map.get("price_source").value + price_type = c_map.get("price_type").value + price_source_exchange = c_map.get("price_source_exchange").value + price_source_market = c_map.get("price_source_market").value + price_source_custom_api = c_map.get("price_source_custom_api").value + custom_api_update_interval = c_map.get("custom_api_update_interval").value + order_refresh_tolerance_pct = c_map.get("order_refresh_tolerance_pct").value / Decimal('100') + order_override = c_map.get("order_override").value + split_order_levels_enabled = c_map.get("split_order_levels_enabled").value + moving_price_band = MovingPriceBand( + enabled=c_map.get("moving_price_band_enabled").value, + price_floor_pct=c_map.get("price_floor_pct").value, + price_ceiling_pct=c_map.get("price_ceiling_pct").value, + price_band_refresh_time=c_map.get("price_band_refresh_time").value + ) + bid_order_level_spreads = convert_decimal_string_to_list( + c_map.get("bid_order_level_spreads").value) + ask_order_level_spreads = convert_decimal_string_to_list( + c_map.get("ask_order_level_spreads").value) + bid_order_level_amounts = convert_decimal_string_to_list( + c_map.get("bid_order_level_amounts").value) + ask_order_level_amounts = convert_decimal_string_to_list( + c_map.get("ask_order_level_amounts").value) + if split_order_levels_enabled: + buy_list = [['buy', spread, amount] for spread, amount in zip(bid_order_level_spreads, bid_order_level_amounts)] + sell_list = [['sell', spread, amount] for spread, amount in zip(ask_order_level_spreads, ask_order_level_amounts)] + both_list = buy_list + sell_list + order_override = { + f'split_level_{i}': order for i, order in enumerate(both_list) + } + trading_pair: str = raw_trading_pair + maker_assets: Tuple[str, str] = self._initialize_market_assets(exchange, [trading_pair])[0] + market_names: List[Tuple[str, List[str]]] = [(exchange, [trading_pair])] + self._initialize_markets(market_names) + maker_data = [self.markets[exchange], trading_pair] + list(maker_assets) + self.market_trading_pair_tuples = [MarketTradingPairTuple(*maker_data)] + + asset_price_delegate = None + if price_source == "external_market": + asset_trading_pair: str = price_source_market + ext_market = create_paper_trade_market(price_source_exchange, self.client_config_map, [asset_trading_pair]) + self.markets[price_source_exchange]: ExchangeBase = ext_market + asset_price_delegate = OrderBookAssetPriceDelegate(ext_market, asset_trading_pair) + elif price_source == "custom_api": + asset_price_delegate = APIAssetPriceDelegate(self.markets[exchange], price_source_custom_api, + custom_api_update_interval) + inventory_cost_price_delegate = None + if price_type == "inventory_cost": + db = HummingbotApplication.main_application().trade_fill_db + inventory_cost_price_delegate = InventoryCostPriceDelegate(db, trading_pair) + take_if_crossed = c_map.get("take_if_crossed").value + + should_wait_order_cancel_confirmation = c_map.get("should_wait_order_cancel_confirmation") + + strategy_logging_options = PureVolumeStrategy.OPTION_LOG_ALL + self.strategy = PureVolumeStrategy() + self.strategy.init_params( + market_info=MarketTradingPairTuple(*maker_data), + bid_spread=bid_spread, + #ask_spread=ask_spread, + order_levels=order_levels, + order_amount=order_amount, + order_level_spread=order_level_spread, + order_level_amount=order_level_amount, + inventory_skew_enabled=inventory_skew_enabled, + inventory_target_base_pct=inventory_target_base_pct, + inventory_range_multiplier=inventory_range_multiplier, + filled_order_delay=filled_order_delay, + hanging_orders_enabled=hanging_orders_enabled, + order_refresh_time=order_refresh_time, + max_order_age=max_order_age, + order_optimization_enabled=order_optimization_enabled, + ask_order_optimization_depth=ask_order_optimization_depth, + bid_order_optimization_depth=bid_order_optimization_depth, + add_transaction_costs_to_orders=add_transaction_costs_to_orders, + logging_options=strategy_logging_options, + asset_price_delegate=asset_price_delegate, + inventory_cost_price_delegate=inventory_cost_price_delegate, + price_type=price_type, + take_if_crossed=take_if_crossed, + price_ceiling=price_ceiling, + price_floor=price_floor, + ping_pong_enabled=ping_pong_enabled, + hanging_orders_cancel_pct=hanging_orders_cancel_pct, + order_refresh_tolerance_pct=order_refresh_tolerance_pct, + minimum_spread=minimum_spread, + hb_app_notification=True, + order_override={} if order_override is None else order_override, + split_order_levels_enabled=split_order_levels_enabled, + bid_order_level_spreads=bid_order_level_spreads, + ask_order_level_spreads=ask_order_level_spreads, + should_wait_order_cancel_confirmation=should_wait_order_cancel_confirmation, + moving_price_band=moving_price_band + ) + except Exception as e: + self.notify(str(e)) + self.logger().error("Unknown error during initialization.", exc_info=True) diff --git a/hummingbot/strategy/script_strategy_base.py b/hummingbot/strategy/script_strategy_base.py new file mode 100644 index 0000000..c15d874 --- /dev/null +++ b/hummingbot/strategy/script_strategy_base.py @@ -0,0 +1,241 @@ +import logging +from decimal import Decimal +from typing import Any, Dict, List, Set + +import numpy as np +import pandas as pd + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.event.events import OrderType, PositionAction +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +lsb_logger = None +s_decimal_nan = Decimal("NaN") + + +class ScriptStrategyBase(StrategyPyBase): + """ + This is a strategy base class that simplifies strategy creation and implements basic functionality to create scripts. + """ + + # This class member defines connectors and their trading pairs needed for the strategy operation, + markets: Dict[str, Set[str]] + + @classmethod + def logger(cls) -> HummingbotLogger: + global lsb_logger + if lsb_logger is None: + lsb_logger = logging.getLogger(__name__) + return lsb_logger + + def __init__(self, connectors: Dict[str, ConnectorBase]): + """ + Initialising a new script strategy object. + + :param connectors: A dictionary of connector names and their corresponding connector. + """ + super().__init__() + self.connectors: Dict[str, ConnectorBase] = connectors + self.ready_to_trade: bool = False + self.add_markets(list(connectors.values())) + + def tick(self, timestamp: float): + """ + Clock tick entry point, is run every second (on normal tick setting). + Checks if all connectors are ready, if so the strategy is ready to trade. + + :param timestamp: current tick timestamp + """ + if not self.ready_to_trade: + self.ready_to_trade = all(ex.ready for ex in self.connectors.values()) + if not self.ready_to_trade: + for con in [c for c in self.connectors.values() if not c.ready]: + self.logger().warning(f"{con.name} is not ready. Please wait...") + return + else: + self.on_tick() + + def on_tick(self): + """ + An event which is called on every tick, a sub class implements this to define what operation the strategy needs + to operate on a regular tick basis. + """ + pass + + def on_stop(self): + pass + + def buy(self, + connector_name: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price=s_decimal_nan, + position_action=PositionAction.OPEN) -> str: + """ + A wrapper function to buy_with_specific_market. + + :param connector_name: The name of the connector + :param trading_pair: The market trading pair + :param amount: An order amount in base token value + :param order_type: The type of the order + :param price: An order price + :param position_action: A position action (for perpetual market only) + + :return: The client assigned id for the new order + """ + market_pair = self._market_trading_pair_tuple(connector_name, trading_pair) + self.logger().info(f"Creating {trading_pair} buy order: price: {price} amount: {amount}.") + return self.buy_with_specific_market(market_pair, amount, order_type, price, position_action=position_action) + + def sell(self, + connector_name: str, + trading_pair: str, + amount: Decimal, + order_type: OrderType, + price=s_decimal_nan, + position_action=PositionAction.OPEN) -> str: + """ + A wrapper function to sell_with_specific_market. + + :param connector_name: The name of the connector + :param trading_pair: The market trading pair + :param amount: An order amount in base token value + :param order_type: The type of the order + :param price: An order price + :param position_action: A position action (for perpetual market only) + + :return: The client assigned id for the new order + """ + market_pair = self._market_trading_pair_tuple(connector_name, trading_pair) + self.logger().info(f"Creating {trading_pair} sell order: price: {price} amount: {amount}.") + return self.sell_with_specific_market(market_pair, amount, order_type, price, position_action=position_action) + + def cancel(self, + connector_name: str, + trading_pair: str, + order_id: str): + """ + A wrapper function to cancel_order. + + :param connector_name: The name of the connector + :param trading_pair: The market trading pair + :param order_id: The identifier assigned by the client of the order to be cancelled + """ + market_pair = self._market_trading_pair_tuple(connector_name, trading_pair) + self.cancel_order(market_trading_pair_tuple=market_pair, order_id=order_id) + + def get_active_orders(self, connector_name: str) -> List[LimitOrder]: + """ + Returns a list of active orders for a connector. + :param connector_name: The name of the connector. + :return: A list of active orders + """ + orders = self.order_tracker.active_limit_orders + connector = self.connectors[connector_name] + return [o[1] for o in orders if o[0] == connector] + + def get_assets(self, connector_name: str) -> List[str]: + """ + Returns a unique list of unique of token names sorted alphabetically + + :param connector_name: The name of the connector + + :return: A list of token names + """ + result: Set = set() + for trading_pair in self.markets[connector_name]: + result.update(split_hb_trading_pair(trading_pair)) + return sorted(result) + + def get_market_trading_pair_tuples(self) -> List[MarketTradingPairTuple]: + """ + Returns a list of MarketTradingPairTuple for all connectors and trading pairs combination. + """ + + result: List[MarketTradingPairTuple] = [] + for name, connector in self.connectors.items(): + for trading_pair in self.markets[name]: + result.append(self._market_trading_pair_tuple(name, trading_pair)) + return result + + def get_balance_df(self) -> pd.DataFrame: + """ + Returns a data frame for all asset balances for displaying purpose. + """ + columns: List[str] = ["Exchange", "Asset", "Total Balance", "Available Balance"] + data: List[Any] = [] + for connector_name, connector in self.connectors.items(): + for asset in self.get_assets(connector_name): + data.append([connector_name, + asset, + float(connector.get_balance(asset)), + float(connector.get_available_balance(asset))]) + df = pd.DataFrame(data=data, columns=columns).replace(np.nan, '', regex=True) + df.sort_values(by=["Exchange", "Asset"], inplace=True) + return df + + def active_orders_df(self) -> pd.DataFrame: + """ + Return a data frame of all active orders for displaying purpose. + """ + columns = ["Exchange", "Market", "Side", "Price", "Amount", "Age"] + data = [] + for connector_name, connector in self.connectors.items(): + for order in self.get_active_orders(connector_name): + age_txt = "n/a" if order.age() <= 0. else pd.Timestamp(order.age(), unit='s').strftime('%H:%M:%S') + data.append([ + connector_name, + order.trading_pair, + "buy" if order.is_buy else "sell", + float(order.price), + float(order.quantity), + age_txt + ]) + if not data: + raise ValueError + df = pd.DataFrame(data=data, columns=columns) + df.sort_values(by=["Exchange", "Market", "Side"], inplace=True) + return df + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def _market_trading_pair_tuple(self, + connector_name: str, + trading_pair: str) -> MarketTradingPairTuple: + """ + Creates and returns a new MarketTradingPairTuple + + :param connector_name: The name of the connector + :param trading_pair: The trading pair + :return: A new MarketTradingPairTuple object. + """ + base, quote = split_hb_trading_pair(trading_pair) + return MarketTradingPairTuple(self.connectors[connector_name], trading_pair, base, quote) diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/__init__.py b/hummingbot/strategy/spot_perpetual_arbitrage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/arb_proposal.py b/hummingbot/strategy/spot_perpetual_arbitrage/arb_proposal.py new file mode 100644 index 0000000..4f3cdba --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/arb_proposal.py @@ -0,0 +1,66 @@ +from decimal import Decimal + +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +s_decimal_nan = Decimal("NaN") +s_decimal_0 = Decimal("0") + + +class ArbProposalSide: + """ + An arbitrage proposal side which contains info needed for order submission. + """ + def __init__(self, + market_info: MarketTradingPairTuple, + is_buy: bool, + order_price: Decimal + ): + """ + :param market_info: The market where to submit the order + :param is_buy: True if buy order + :param order_price: The price required for order submission, this could differ from the quote price + """ + self.market_info: MarketTradingPairTuple = market_info + self.is_buy: bool = is_buy + self.order_price: Decimal = order_price + + def __repr__(self): + side = "Buy" if self.is_buy else "Sell" + base, quote = self.market_info.trading_pair.split("-") + return f"{self.market_info.market.display_name.capitalize()}: {side} {base}" \ + f" at {self.order_price} {quote}." + + +class ArbProposal: + """ + An arbitrage proposal which contains 2 sides of the proposal - one on spot market and one on perpetual market. + """ + def __init__(self, + spot_side: ArbProposalSide, + perp_side: ArbProposalSide, + order_amount: Decimal): + """ + Creates ArbProposal + :param spot_side: An ArbProposalSide on spot market + :param perp_side: An ArbProposalSide on perpetual market + :param order_amount: An order amount for both spot and perpetual market + """ + if spot_side.is_buy == perp_side.is_buy: + raise Exception("Spot and perpetual arb proposal cannot be on the same side.") + self.spot_side: ArbProposalSide = spot_side + self.perp_side: ArbProposalSide = perp_side + self.order_amount: Decimal = order_amount + + def profit_pct(self) -> Decimal: + """ + Calculates and returns arbitrage profit (in percentage value). + """ + buy_price = self.spot_side.order_price if self.spot_side.is_buy else self.perp_side.order_price + sell_price = self.spot_side.order_price if not self.spot_side.is_buy else self.perp_side.order_price + if sell_price and buy_price: + return (sell_price - buy_price) / buy_price + return s_decimal_0 + + def __repr__(self): + return f"Spot: {self.spot_side}\nPerpetual: {self.perp_side}\nOrder amount: {self.order_amount}\n" \ + f"Profit: {self.profit_pct():.2%}" diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pxd b/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pyx b/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage.py b/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage.py new file mode 100644 index 0000000..e36d02e --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage.py @@ -0,0 +1,553 @@ +import asyncio +import logging +from decimal import Decimal +from enum import Enum +from typing import Dict, List, Tuple + +import pandas as pd + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.derivative.position import Position +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.order_candidate import OrderCandidate, PerpetualOrderCandidate +from hummingbot.core.event.events import BuyOrderCompletedEvent, PositionModeChangeEvent, SellOrderCompletedEvent +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.spot_perpetual_arbitrage.arb_proposal import ArbProposal, ArbProposalSide +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +NaN = float("nan") +s_decimal_zero = Decimal(0) +spa_logger = None + + +class StrategyState(Enum): + Closed = 0 + Opening = 1 + Opened = 2 + Closing = 3 + + +class SpotPerpetualArbitrageStrategy(StrategyPyBase): + """ + This strategy arbitrages between a spot and a perpetual exchange. + For a given order amount, the strategy checks for price discrepancy between buy and sell price on the 2 exchanges. + Since perpetual contract requires closing position before profit is realised, there are 2 stages to this arbitrage + operation - first to open and second to close. + """ + + @classmethod + def logger(cls) -> HummingbotLogger: + global spa_logger + if spa_logger is None: + spa_logger = logging.getLogger(__name__) + return spa_logger + + def init_params(self, + spot_market_info: MarketTradingPairTuple, + perp_market_info: MarketTradingPairTuple, + order_amount: Decimal, + perp_leverage: int, + min_opening_arbitrage_pct: Decimal, + min_closing_arbitrage_pct: Decimal, + spot_market_slippage_buffer: Decimal = Decimal("0"), + perp_market_slippage_buffer: Decimal = Decimal("0"), + next_arbitrage_opening_delay: float = 120, + status_report_interval: float = 10): + """ + :param spot_market_info: The spot market info + :param perp_market_info: The perpetual market info + :param order_amount: The order amount + :param perp_leverage: The leverage level to use on perpetual market + :param min_opening_arbitrage_pct: The minimum spread to open arbitrage position (e.g. 0.0003 for 0.3%) + :param min_closing_arbitrage_pct: The minimum spread to close arbitrage position (e.g. 0.0003 for 0.3%) + :param spot_market_slippage_buffer: The buffer for which to adjust order price for higher chance of + the order getting filled on spot market. + :param perp_market_slippage_buffer: The slipper buffer for perpetual market. + :param next_arbitrage_opening_delay: The number of seconds to delay before the next arb position can be opened + :param status_report_interval: Amount of seconds to wait to refresh the status report + """ + self._spot_market_info = spot_market_info + self._perp_market_info = perp_market_info + self._min_opening_arbitrage_pct = min_opening_arbitrage_pct + self._min_closing_arbitrage_pct = min_closing_arbitrage_pct + self._order_amount = order_amount + self._perp_leverage = perp_leverage + self._spot_market_slippage_buffer = spot_market_slippage_buffer + self._perp_market_slippage_buffer = perp_market_slippage_buffer + self._next_arbitrage_opening_delay = next_arbitrage_opening_delay + self._next_arbitrage_opening_ts = 0 # next arbitrage opening timestamp + self._all_markets_ready = False + self._ev_loop = asyncio.get_event_loop() + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self.add_markets([spot_market_info.market, perp_market_info.market]) + + self._main_task = None + self._in_flight_opening_order_ids = [] + self._completed_opening_order_ids = [] + self._completed_closing_order_ids = [] + self._strategy_state = StrategyState.Closed + self._ready_to_start = False + self._last_arb_op_reported_ts = 0 + self._position_mode_ready = False + self._position_mode_not_ready_counter = 0 + self._trading_started = False + + def all_markets_ready(self): + return all([market.ready for market in self.active_markets]) + + @property + def strategy_state(self) -> StrategyState: + return self._strategy_state + + @property + def min_opening_arbitrage_pct(self) -> Decimal: + return self._min_opening_arbitrage_pct + + @property + def min_closing_arbitrage_pct(self) -> Decimal: + return self._min_closing_arbitrage_pct + + @property + def order_amount(self) -> Decimal: + return self._order_amount + + @order_amount.setter + def order_amount(self, value): + self._order_amount = value + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self._sb_order_tracker.market_pair_to_active_orders + + @property + def perp_positions(self) -> List[Position]: + return [s for s in self._perp_market_info.market.account_positions.values() if + s.trading_pair == self._perp_market_info.trading_pair and s.amount != s_decimal_zero] + + def apply_initial_settings(self): + self._perp_market_info.market.set_leverage(self._perp_market_info.trading_pair, self._perp_leverage) + self._perp_market_info.market.set_position_mode(PositionMode.ONEWAY) + + def tick(self, timestamp: float): + """ + Clock tick entry point, is run every second (on normal tick setting). + :param timestamp: current tick timestamp + """ + if not self._all_markets_ready or not self._position_mode_ready or not self._trading_started: + self._all_markets_ready = self.all_markets_ready() + if not self._all_markets_ready: + return + else: + self.logger().info("Markets are ready.") + + if not self._position_mode_ready: + self._position_mode_not_ready_counter += 1 + # Attempt to switch position mode every 10 ticks only to not to spam and DDOS + if self._position_mode_not_ready_counter == 10: + self._perp_market_info.market.set_position_mode(PositionMode.ONEWAY) + self._position_mode_not_ready_counter = 0 + return + self._position_mode_not_ready_counter = 0 + + self.logger().info("Trading started.") + self._trading_started = True + + if not self.check_budget_available(): + self.logger().info("Trading not possible.") + return + + if self._perp_market_info.market.position_mode != PositionMode.ONEWAY or \ + len(self.perp_positions) > 1: + self.logger().info("This strategy supports only Oneway position mode. Attempting to switch ...") + self._perp_market_info.market.set_position_mode(PositionMode.ONEWAY) + return + + if len(self.perp_positions) == 1: + adj_perp_amount = self._perp_market_info.market.quantize_order_amount( + self._perp_market_info.trading_pair, self._order_amount) + if abs(self.perp_positions[0].amount) == adj_perp_amount: + self.logger().info(f"There is an existing {self._perp_market_info.trading_pair} " + f"{self.perp_positions[0].position_side.name} position. The bot resumes " + f"operation to close out the arbitrage position") + self._strategy_state = StrategyState.Opened + self._ready_to_start = True + else: + self.logger().info(f"There is an existing {self._perp_market_info.trading_pair} " + f"{self.perp_positions[0].position_side.name} position with unmatched " + f"position amount. Please manually close out the position before starting " + f"this strategy.") + return + else: + self._ready_to_start = True + + if self._ready_to_start and (self._main_task is None or self._main_task.done()): + self._main_task = safe_ensure_future(self.main(timestamp)) + + async def main(self, timestamp): + """ + The main procedure for the arbitrage strategy. + """ + self.update_strategy_state() + if self._strategy_state in (StrategyState.Opening, StrategyState.Closing): + return + if self.strategy_state == StrategyState.Closed and self._next_arbitrage_opening_ts > self.current_timestamp: + return + proposals = await self.create_base_proposals() + if self._strategy_state == StrategyState.Opened: + perp_is_buy = False if self.perp_positions[0].amount > 0 else True + proposals = [p for p in proposals if p.perp_side.is_buy == perp_is_buy and p.profit_pct() >= + self._min_closing_arbitrage_pct] + else: + proposals = [p for p in proposals if p.profit_pct() >= self._min_opening_arbitrage_pct] + if len(proposals) == 0: + return + proposal = proposals[0] + if self._last_arb_op_reported_ts + 60 < self.current_timestamp: + pos_txt = "closing" if self._strategy_state == StrategyState.Opened else "opening" + self.logger().info(f"Arbitrage position {pos_txt} opportunity found.") + self.logger().info(f"Profitability ({proposal.profit_pct():.2%}) is now above min_{pos_txt}_arbitrage_pct.") + self._last_arb_op_reported_ts = self.current_timestamp + self.apply_slippage_buffers(proposal) + if self.check_budget_constraint(proposal): + self.execute_arb_proposal(proposal) + + def update_strategy_state(self): + """ + Updates strategy state to either Opened or Closed if the condition is right. + """ + if self._strategy_state == StrategyState.Opening and len(self._completed_opening_order_ids) == 2 and \ + self.perp_positions: + self._strategy_state = StrategyState.Opened + self._completed_opening_order_ids.clear() + elif self._strategy_state == StrategyState.Closing and len(self._completed_closing_order_ids) == 2 and \ + len(self.perp_positions) == 0: + self._strategy_state = StrategyState.Closed + self._completed_closing_order_ids.clear() + self._next_arbitrage_opening_ts = self.current_timestamp + self._next_arbitrage_opening_delay + + async def create_base_proposals(self) -> List[ArbProposal]: + """ + Creates a list of 2 base proposals, no filter. + :return: A list of 2 base proposals. + """ + tasks = [self._spot_market_info.market.get_order_price(self._spot_market_info.trading_pair, True, + self._order_amount), + self._spot_market_info.market.get_order_price(self._spot_market_info.trading_pair, False, + self._order_amount), + self._perp_market_info.market.get_order_price(self._perp_market_info.trading_pair, True, + self._order_amount), + self._perp_market_info.market.get_order_price(self._perp_market_info.trading_pair, False, + self._order_amount)] + prices = await safe_gather(*tasks, return_exceptions=True) + spot_buy, spot_sell, perp_buy, perp_sell = [*prices] + return [ + ArbProposal(ArbProposalSide(self._spot_market_info, True, spot_buy), + ArbProposalSide(self._perp_market_info, False, perp_sell), + self._order_amount), + ArbProposal(ArbProposalSide(self._spot_market_info, False, spot_sell), + ArbProposalSide(self._perp_market_info, True, perp_buy), + self._order_amount) + ] + + def apply_slippage_buffers(self, proposal: ArbProposal): + """ + Updates arb_proposals by adjusting order price for slipper buffer percentage. + E.g. if it is a buy order, for an order price of 100 and 1% slipper buffer, the new order price is 101, + for a sell order, the new order price is 99. + :param proposal: the arbitrage proposal + """ + for arb_side in (proposal.spot_side, proposal.perp_side): + market = arb_side.market_info.market + # arb_side.amount = market.quantize_order_amount(arb_side.market_info.trading_pair, arb_side.amount) + s_buffer = self._spot_market_slippage_buffer if market == self._spot_market_info.market \ + else self._perp_market_slippage_buffer + if not arb_side.is_buy: + s_buffer *= Decimal("-1") + arb_side.order_price *= Decimal("1") + s_buffer + arb_side.order_price = market.quantize_order_price(arb_side.market_info.trading_pair, + arb_side.order_price) + + def check_budget_available(self) -> bool: + """ + Checks if there's any balance for trading to be possible at all + :return: True if user has available balance enough for orders submission. + """ + + spot_base, spot_quote = self._spot_market_info.trading_pair.split("-") + perp_base, perp_quote = self._perp_market_info.trading_pair.split("-") + + balance_spot_base = self._spot_market_info.market.get_available_balance(spot_base) + balance_spot_quote = self._spot_market_info.market.get_available_balance(spot_quote) + + balance_perp_quote = self._perp_market_info.market.get_available_balance(perp_quote) + + if balance_spot_base == s_decimal_zero and balance_spot_quote == s_decimal_zero: + self.logger().info(f"Cannot arbitrage, {self._spot_market_info.market.display_name} {spot_base} balance " + f"({balance_spot_base}) is 0 and {self._spot_market_info.market.display_name} {spot_quote} balance " + f"({balance_spot_quote}) is 0.") + return False + + if balance_perp_quote == s_decimal_zero: + self.logger().info(f"Cannot arbitrage, {self._perp_market_info.market.display_name} {perp_quote} balance " + f"({balance_perp_quote}) is 0.") + return False + + return True + + def check_budget_constraint(self, proposal: ArbProposal) -> bool: + """ + Check balances on both exchanges if there is enough to submit both orders in a proposal. + :param proposal: An arbitrage proposal + :return: True if user has available balance enough for both orders submission. + """ + return self.check_spot_budget_constraint(proposal) and self.check_perpetual_budget_constraint(proposal) + + def check_spot_budget_constraint(self, proposal: ArbProposal) -> bool: + """ + Check balance on spot exchange. + :param proposal: An arbitrage proposal + :return: True if user has available balance enough for both orders submission. + """ + proposal_side = proposal.spot_side + order_amount = proposal.order_amount + market_info = proposal_side.market_info + budget_checker = market_info.market.budget_checker + order_candidate = OrderCandidate( + trading_pair=market_info.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY if proposal_side.is_buy else TradeType.SELL, + amount=order_amount, + price=proposal_side.order_price, + ) + + adjusted_candidate_order = budget_checker.adjust_candidate(order_candidate, all_or_none=True) + + if adjusted_candidate_order.amount < order_amount: + self.logger().info( + f"Cannot arbitrage, {proposal_side.market_info.market.display_name} balance" + f" is insufficient to place the order candidate {order_candidate}." + ) + return False + + return True + + def check_perpetual_budget_constraint(self, proposal: ArbProposal) -> bool: + """ + Check balance on spot exchange. + :param proposal: An arbitrage proposal + :return: True if user has available balance enough for both orders submission. + """ + proposal_side = proposal.perp_side + order_amount = proposal.order_amount + market_info = proposal_side.market_info + budget_checker = market_info.market.budget_checker + + position_close = False + if self.perp_positions and abs(self.perp_positions[0].amount) == order_amount: + perp_side = proposal.perp_side + cur_perp_pos_is_buy = True if self.perp_positions[0].amount > 0 else False + if perp_side != cur_perp_pos_is_buy: + position_close = True + + order_candidate = PerpetualOrderCandidate( + trading_pair=market_info.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY if proposal_side.is_buy else TradeType.SELL, + amount=order_amount, + price=proposal_side.order_price, + leverage=Decimal(self._perp_leverage), + position_close=position_close, + ) + + adjusted_candidate_order = budget_checker.adjust_candidate(order_candidate, all_or_none=True) + + if adjusted_candidate_order.amount < order_amount: + self.logger().info( + f"Cannot arbitrage, {proposal_side.market_info.market.display_name} balance" + f" is insufficient to place the order candidate {order_candidate}." + ) + return False + + return True + + def execute_arb_proposal(self, proposal: ArbProposal): + """ + Execute both sides of the arbitrage trades concurrently. + :param proposal: the arbitrage proposal + """ + if proposal.order_amount == s_decimal_zero: + return + spot_side = proposal.spot_side + spot_order_fn = self.buy_with_specific_market if spot_side.is_buy else self.sell_with_specific_market + side = "BUY" if spot_side.is_buy else "SELL" + self.log_with_clock( + logging.INFO, + f"Placing {side} order for {proposal.order_amount} {spot_side.market_info.base_asset} " + f"at {spot_side.market_info.market.display_name} at {spot_side.order_price} price" + ) + spot_order_fn( + spot_side.market_info, + proposal.order_amount, + spot_side.market_info.market.get_taker_order_type(), + spot_side.order_price, + ) + perp_side = proposal.perp_side + perp_order_fn = self.buy_with_specific_market if perp_side.is_buy else self.sell_with_specific_market + side = "BUY" if perp_side.is_buy else "SELL" + position_action = PositionAction.CLOSE if self._strategy_state == StrategyState.Opened else PositionAction.OPEN + self.log_with_clock( + logging.INFO, + f"Placing {side} order for {proposal.order_amount} {perp_side.market_info.base_asset} " + f"at {perp_side.market_info.market.display_name} at {perp_side.order_price} price to " + f"{position_action.name} position." + ) + perp_order_fn( + perp_side.market_info, + proposal.order_amount, + perp_side.market_info.market.get_taker_order_type(), + perp_side.order_price, + position_action=position_action + ) + if self._strategy_state == StrategyState.Opened: + self._strategy_state = StrategyState.Closing + self._completed_closing_order_ids.clear() + else: + self._strategy_state = StrategyState.Opening + self._completed_opening_order_ids.clear() + + def active_positions_df(self) -> pd.DataFrame: + """ + Returns a new dataframe on current active perpetual positions. + """ + columns = ["Symbol", "Type", "Entry Price", "Amount", "Leverage", "Unrealized PnL"] + data = [] + for pos in self.perp_positions: + data.append([ + pos.trading_pair, + "LONG" if pos.amount > 0 else "SHORT", + pos.entry_price, + pos.amount, + pos.leverage, + pos.unrealized_pnl + ]) + + return pd.DataFrame(data=data, columns=columns) + + async def format_status(self) -> str: + """ + Returns a status string formatted to display nicely on terminal. The strings composes of 4 parts: markets, + assets, spread and warnings(if any). + """ + columns = ["Exchange", "Market", "Sell Price", "Buy Price", "Mid Price"] + data = [] + for market_info in [self._spot_market_info, self._perp_market_info]: + market, trading_pair, base_asset, quote_asset = market_info + buy_price = await market.get_quote_price(trading_pair, True, self._order_amount) + sell_price = await market.get_quote_price(trading_pair, False, self._order_amount) + mid_price = (buy_price + sell_price) / 2 + data.append([ + market.display_name, + trading_pair, + float(sell_price), + float(buy_price), + float(mid_price) + ]) + markets_df = pd.DataFrame(data=data, columns=columns) + lines = [] + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + # See if there're any active positions. + if len(self.perp_positions) > 0: + df = self.active_positions_df() + lines.extend(["", " Positions:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active positions."]) + + assets_df = self.wallet_balance_data_frame([self._spot_market_info, self._perp_market_info]) + lines.extend(["", " Assets:"] + + [" " + line for line in str(assets_df).split("\n")]) + + proposals = await self.create_base_proposals() + lines.extend(["", " Opportunity:"] + self.short_proposal_msg(proposals)) + + warning_lines = self.network_warning([self._spot_market_info]) + warning_lines.extend(self.network_warning([self._perp_market_info])) + warning_lines.extend(self.balance_warning([self._spot_market_info])) + warning_lines.extend(self.balance_warning([self._perp_market_info])) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def short_proposal_msg(self, arb_proposal: List[ArbProposal], indented: bool = True) -> List[str]: + """ + Composes a short proposal message. + :param arb_proposal: The arbitrage proposal + :param indented: If the message should be indented (by 4 spaces) + :return A list of messages + """ + lines = [] + for proposal in arb_proposal: + spot_side = "buy" if proposal.spot_side.is_buy else "sell" + perp_side = "buy" if proposal.perp_side.is_buy else "sell" + profit_pct = proposal.profit_pct() + lines.append(f"{' ' if indented else ''}{spot_side} at " + f"{proposal.spot_side.market_info.market.display_name}" + f", {perp_side} at {proposal.perp_side.market_info.market.display_name}: " + f"{profit_pct:.2%}") + return lines + + @property + def tracked_market_orders(self) -> List[Tuple[ConnectorBase, MarketOrder]]: + return self._sb_order_tracker.tracked_market_orders + + @property + def tracked_limit_orders(self) -> List[Tuple[ConnectorBase, LimitOrder]]: + return self._sb_order_tracker.tracked_limit_orders + + def start(self, clock: Clock, timestamp: float): + self._ready_to_start = False + self.apply_initial_settings() + + def stop(self, clock: Clock): + if self._main_task is not None: + self._main_task.cancel() + self._main_task = None + self._ready_to_start = False + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + self.update_complete_order_id_lists(event.order_id) + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + self.update_complete_order_id_lists(event.order_id) + + def did_change_position_mode_succeed(self, position_mode_changed_event: PositionModeChangeEvent): + if position_mode_changed_event.position_mode is PositionMode.ONEWAY: + self.logger().info( + f"Changing position mode to {PositionMode.ONEWAY.name} succeeded.") + self._position_mode_ready = True + else: + self.logger().warning( + f"Changing position mode to {PositionMode.ONEWAY.name} did not succeed.") + self._position_mode_ready = False + + def did_change_position_mode_fail(self, position_mode_changed_event: PositionModeChangeEvent): + self.logger().error( + f"Changing position mode to {PositionMode.ONEWAY.name} failed. " + f"Reason: {position_mode_changed_event.message}.") + self._position_mode_ready = False + self.logger().warning("Cannot continue. Please resolve the issue in the account.") + + def update_complete_order_id_lists(self, order_id: str): + if self._strategy_state == StrategyState.Opening: + self._completed_opening_order_ids.append(order_id) + elif self._strategy_state == StrategyState.Closing: + self._completed_closing_order_ids.append(order_id) diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage_config_map.py b/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage_config_map.py new file mode 100644 index 0000000..2c992d6 --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/spot_perpetual_arbitrage_config_map.py @@ -0,0 +1,136 @@ +from decimal import Decimal + +from hummingbot.client.config.config_validators import ( + validate_connector, + validate_decimal, + validate_derivative, + validate_int, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges, requried_connector_trading_pairs + + +def exchange_on_validated(value: str) -> None: + required_exchanges.add(value) + + +def spot_market_validator(value: str) -> None: + exchange = spot_perpetual_arbitrage_config_map["spot_connector"].value + return validate_market_trading_pair(exchange, value) + + +def spot_market_on_validated(value: str) -> None: + requried_connector_trading_pairs[spot_perpetual_arbitrage_config_map["spot_connector"].value] = [value] + + +def perpetual_market_validator(value: str) -> None: + exchange = spot_perpetual_arbitrage_config_map["perpetual_connector"].value + return validate_market_trading_pair(exchange, value) + + +def perpetual_market_on_validated(value: str) -> None: + requried_connector_trading_pairs[spot_perpetual_arbitrage_config_map["perpetual_connector"].value] = [value] + + +def spot_market_prompt() -> str: + connector = spot_perpetual_arbitrage_config_map.get("spot_connector").value + example = AllConnectorSettings.get_example_pairs().get(connector) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (connector, f" (e.g. {example})" if example else "") + + +def perpetual_market_prompt() -> str: + connector = spot_perpetual_arbitrage_config_map.get("perpetual_connector").value + example = AllConnectorSettings.get_example_pairs().get(connector) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (connector, f" (e.g. {example})" if example else "") + + +def order_amount_prompt() -> str: + trading_pair = spot_perpetual_arbitrage_config_map["spot_market"].value + base_asset, quote_asset = trading_pair.split("-") + return f"What is the amount of {base_asset} per order? >>> " + + +spot_perpetual_arbitrage_config_map = { + "strategy": ConfigVar( + key="strategy", + prompt="", + default="spot_perpetual_arbitrage"), + "spot_connector": ConfigVar( + key="spot_connector", + prompt="Enter a spot connector (Exchange/AMM/CLOB) >>> ", + prompt_on_new=True, + validator=validate_connector, + on_validated=exchange_on_validated), + "spot_market": ConfigVar( + key="spot_market", + prompt=spot_market_prompt, + prompt_on_new=True, + validator=spot_market_validator, + on_validated=spot_market_on_validated), + "perpetual_connector": ConfigVar( + key="perpetual_connector", + prompt="Enter a derivative connector >>> ", + prompt_on_new=True, + validator=validate_derivative, + on_validated=exchange_on_validated), + "perpetual_market": ConfigVar( + key="perpetual_market", + prompt=perpetual_market_prompt, + prompt_on_new=True, + validator=perpetual_market_validator, + on_validated=perpetual_market_on_validated), + "order_amount": ConfigVar( + key="order_amount", + prompt=order_amount_prompt, + type_str="decimal", + prompt_on_new=True), + "perpetual_leverage": ConfigVar( + key="perpetual_leverage", + prompt="How much leverage would you like to use on the perpetual exchange? (Enter 1 to indicate 1X) >>> ", + type_str="int", + default=1, + validator= lambda v: validate_int(v), + prompt_on_new=True), + "min_opening_arbitrage_pct": ConfigVar( + key="min_opening_arbitrage_pct", + prompt="What is the minimum arbitrage percentage between the spot and perpetual market price before opening " + "an arbitrage position? (Enter 1 to indicate 1%) >>> ", + prompt_on_new=True, + default=Decimal("1"), + validator=lambda v: validate_decimal(v, Decimal(-100), 100, inclusive=False), + type_str="decimal"), + "min_closing_arbitrage_pct": ConfigVar( + key="min_closing_arbitrage_pct", + prompt="What is the minimum arbitrage percentage between the spot and perpetual market price before closing " + "an existing arbitrage position? (Enter 1 to indicate 1%) (This can be negative value to close out the " + "position with lesser profit at higher chance of closing) >>> ", + prompt_on_new=True, + default=Decimal("-0.1"), + validator=lambda v: validate_decimal(v, Decimal(-100), 100, inclusive=False), + type_str="decimal"), + "spot_market_slippage_buffer": ConfigVar( + key="spot_market_slippage_buffer", + prompt="How much buffer do you want to add to the price to account for slippage for orders on the spot market " + "(Enter 1 for 1%)? >>> ", + prompt_on_new=True, + default=Decimal("0.05"), + validator=lambda v: validate_decimal(v), + type_str="decimal"), + "perpetual_market_slippage_buffer": ConfigVar( + key="perpetual_market_slippage_buffer", + prompt="How much buffer do you want to add to the price to account for slippage for orders on the perpetual " + "market (Enter 1 for 1%)? >>> ", + prompt_on_new=True, + default=Decimal("0.05"), + validator=lambda v: validate_decimal(v), + type_str="decimal"), + "next_arbitrage_opening_delay": ConfigVar( + key="next_arbitrage_opening_delay", + prompt="How long do you want the strategy to wait before opening the next arbitrage position (in seconds)?", + type_str="float", + validator=lambda v: validate_decimal(v, min_value=0, inclusive=False), + default=120), +} diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/start.py b/hummingbot/strategy/spot_perpetual_arbitrage/start.py new file mode 100644 index 0000000..6c8cc00 --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/start.py @@ -0,0 +1,37 @@ +from decimal import Decimal +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.spot_perpetual_arbitrage.spot_perpetual_arbitrage import SpotPerpetualArbitrageStrategy +from hummingbot.strategy.spot_perpetual_arbitrage.spot_perpetual_arbitrage_config_map import spot_perpetual_arbitrage_config_map + + +def start(self): + spot_connector = spot_perpetual_arbitrage_config_map.get("spot_connector").value.lower() + spot_market = spot_perpetual_arbitrage_config_map.get("spot_market").value + perpetual_connector = spot_perpetual_arbitrage_config_map.get("perpetual_connector").value.lower() + perpetual_market = spot_perpetual_arbitrage_config_map.get("perpetual_market").value + order_amount = spot_perpetual_arbitrage_config_map.get("order_amount").value + perpetual_leverage = spot_perpetual_arbitrage_config_map.get("perpetual_leverage").value + min_opening_arbitrage_pct = spot_perpetual_arbitrage_config_map.get("min_opening_arbitrage_pct").value / Decimal("100") + min_closing_arbitrage_pct = spot_perpetual_arbitrage_config_map.get("min_closing_arbitrage_pct").value / Decimal("100") + spot_market_slippage_buffer = spot_perpetual_arbitrage_config_map.get("spot_market_slippage_buffer").value / Decimal("100") + perpetual_market_slippage_buffer = spot_perpetual_arbitrage_config_map.get("perpetual_market_slippage_buffer").value / Decimal("100") + next_arbitrage_opening_delay = spot_perpetual_arbitrage_config_map.get("next_arbitrage_opening_delay").value + + self._initialize_markets([(spot_connector, [spot_market]), (perpetual_connector, [perpetual_market])]) + base_1, quote_1 = spot_market.split("-") + base_2, quote_2 = perpetual_market.split("-") + + spot_market_info = MarketTradingPairTuple(self.markets[spot_connector], spot_market, base_1, quote_1) + perpetual_market_info = MarketTradingPairTuple(self.markets[perpetual_connector], perpetual_market, base_2, quote_2) + + self.market_trading_pair_tuples = [spot_market_info, perpetual_market_info] + self.strategy = SpotPerpetualArbitrageStrategy() + self.strategy.init_params(spot_market_info, + perpetual_market_info, + order_amount, + perpetual_leverage, + min_opening_arbitrage_pct, + min_closing_arbitrage_pct, + spot_market_slippage_buffer, + perpetual_market_slippage_buffer, + next_arbitrage_opening_delay) diff --git a/hummingbot/strategy/spot_perpetual_arbitrage/utils.py b/hummingbot/strategy/spot_perpetual_arbitrage/utils.py new file mode 100644 index 0000000..f5b2e39 --- /dev/null +++ b/hummingbot/strategy/spot_perpetual_arbitrage/utils.py @@ -0,0 +1,44 @@ +from decimal import Decimal +from typing import List +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from .data_types import ArbProposal, ArbProposalSide + +s_decimal_nan = Decimal("NaN") + + +async def create_arb_proposals(market_info_1: MarketTradingPairTuple, + market_info_2: MarketTradingPairTuple, + order_amount: Decimal) -> List[ArbProposal]: + """ + Creates base arbitrage proposals for given markets without any filtering. + :param market_info_1: The first market + :param market_info_2: The second market + :param order_amount: The required order amount. + :return A list of 2 proposal - (market_1 buy, market_2 sell) and (market_1 sell, market_2 buy) + """ + order_amount = Decimal(str(order_amount)) + results = [] + for index in range(0, 2): + is_buy = not bool(index) # bool(0) is False, so start with buy first + m_1_q_price = await market_info_1.market.get_quote_price(market_info_1.trading_pair, is_buy, order_amount) + m_1_o_price = await market_info_1.market.get_order_price(market_info_1.trading_pair, is_buy, order_amount) + m_2_q_price = await market_info_2.market.get_quote_price(market_info_2.trading_pair, not is_buy, order_amount) + m_2_o_price = await market_info_2.market.get_order_price(market_info_2.trading_pair, not is_buy, order_amount) + if any(p is None for p in (m_1_o_price, m_1_q_price, m_2_o_price, m_2_q_price)): + continue + first_side = ArbProposalSide( + market_info_1, + is_buy, + m_1_q_price, + m_1_o_price, + order_amount + ) + second_side = ArbProposalSide( + market_info_2, + not is_buy, + m_2_q_price, + m_2_o_price, + order_amount + ) + results.append(ArbProposal(first_side, second_side)) + return results diff --git a/hummingbot/strategy/strategy_base.pxd b/hummingbot/strategy/strategy_base.pxd new file mode 100644 index 0000000..bfd5379 --- /dev/null +++ b/hummingbot/strategy/strategy_base.pxd @@ -0,0 +1,71 @@ +# distutils: language=c++ + +from hummingbot.core.time_iterator cimport TimeIterator +from hummingbot.core.event.event_listener cimport EventListener + +from .order_tracker cimport OrderTracker + +cdef class StrategyBase(TimeIterator): + cdef: + set _sb_markets + EventListener _sb_create_buy_order_listener + EventListener _sb_create_sell_order_listener + EventListener _sb_fill_order_listener + EventListener _sb_fail_order_listener + EventListener _sb_cancel_order_listener + EventListener _sb_expire_order_listener + EventListener _sb_complete_buy_order_listener + EventListener _sb_complete_sell_order_listener + EventListener _sb_complete_funding_payment_listener + EventListener _sb_position_mode_change_success_listener + EventListener _sb_position_mode_change_failure_listener + EventListener _sb_range_position_liquidity_added_listener + EventListener _sb_range_position_liquidity_removed_listener + EventListener _sb_range_position_update_listener + EventListener _sb_range_position_update_failure_listener + EventListener _sb_range_position_fee_collected_listener + EventListener _sb_range_position_closed_listener + bint _sb_delegate_lock + public OrderTracker _sb_order_tracker + + cdef c_add_markets(self, list markets) + cdef c_remove_markets(self, list markets) + cdef c_did_create_buy_order(self, object order_created_event) + cdef c_did_create_sell_order(self, object order_created_event) + cdef c_did_fill_order(self, object order_filled_event) + cdef c_did_fail_order(self, object order_failed_event) + cdef c_did_cancel_order(self, object cancelled_event) + cdef c_did_expire_order(self, object expired_event) + cdef c_did_complete_buy_order(self, object order_completed_event) + cdef c_did_complete_sell_order(self, object order_completed_event) + cdef c_did_complete_funding_payment(self, object funding_payment_completed_event) + cdef c_did_change_position_mode_succeed(self, object position_mode_changed_event) + cdef c_did_change_position_mode_fail(self, object position_mode_changed_event) + cdef c_did_add_liquidity(self, object add_liquidity_event) + cdef c_did_remove_liquidity(self, object remove_liquidity_event) + cdef c_did_update_lp_order(self, object update_lp_event) + cdef c_did_fail_lp_update(self, object fail_lp_update_event) + cdef c_did_collect_fee(self, object collect_fee_event) + cdef c_did_close_position(self, object closed_event) + + cdef c_did_fail_order_tracker(self, object order_failed_event) + cdef c_did_cancel_order_tracker(self, object order_cancelled_event) + cdef c_did_expire_order_tracker(self, object order_expired_event) + cdef c_did_complete_buy_order_tracker(self, object order_completed_event) + cdef c_did_complete_sell_order_tracker(self, object order_completed_event) + + cdef str c_buy_with_specific_market(self, object market_trading_pair_tuple, object amount, object order_type = *, + object price = *, double expiration_seconds = *, position_action = *) + cdef str c_sell_with_specific_market(self, object market_trading_pair_tuple, object amount, object order_type = *, + object price = *, double expiration_seconds = *, position_action = *, ) + cdef c_cancel_order(self, object market_pair, str order_id) + + cdef c_start_tracking_limit_order(self, object market_pair, str order_id, bint is_buy, object price, + object quantity) + cdef c_stop_tracking_limit_order(self, object market_pair, str order_id) + cdef c_start_tracking_market_order(self, object market_pair, str order_id, bint is_buy, object quantity) + cdef c_stop_tracking_market_order(self, object market_pair, str order_id) + cdef c_track_restored_orders(self, object market_pair) + cdef object c_sum_flat_fees(self, + str quote_currency, + list flat_fees) diff --git a/hummingbot/strategy/strategy_base.pyx b/hummingbot/strategy/strategy_base.pyx new file mode 100755 index 0000000..9d83618 --- /dev/null +++ b/hummingbot/strategy/strategy_base.pyx @@ -0,0 +1,668 @@ +from decimal import Decimal +import logging +import pandas as pd +from typing import ( + List) + +from hummingbot.core.clock cimport Clock +from hummingbot.core.event.events import MarketEvent, AccountEvent +from hummingbot.core.event.event_listener cimport EventListener +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.core.time_iterator cimport TimeIterator +from hummingbot.connector.connector_base cimport ConnectorBase +from hummingbot.core.data_type.trade import Trade +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.core.data_type.common import OrderType, PositionAction +from hummingbot.strategy.order_tracker import OrderTracker +from hummingbot.connector.derivative_base import DerivativeBase + +NaN = float("nan") +s_decimal_nan = Decimal("NaN") +s_decimal_0 = Decimal("0") + +# +cdef class BaseStrategyEventListener(EventListener): + cdef: + StrategyBase _owner + + def __init__(self, StrategyBase owner): + super().__init__() + self._owner = owner + + +cdef class BuyOrderCompletedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_complete_buy_order(arg) + self._owner.c_did_complete_buy_order_tracker(arg) + + +cdef class SellOrderCompletedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_complete_sell_order(arg) + self._owner.c_did_complete_sell_order_tracker(arg) + + +cdef class FundingPaymentCompletedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_complete_funding_payment(arg) + + +cdef class PositionModeChangeSuccessListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_change_position_mode_succeed(arg) + +cdef class PositionModeChangeFailureListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_change_position_mode_fail(arg) + +cdef class OrderFilledListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_fill_order(arg) + + +cdef class OrderFailedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_fail_order(arg) + self._owner.c_did_fail_order_tracker(arg) + + +cdef class OrderCancelledListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_cancel_order(arg) + self._owner.c_did_cancel_order_tracker(arg) + + +cdef class OrderExpiredListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_expire_order(arg) + self._owner.c_did_expire_order_tracker(arg) + + +cdef class BuyOrderCreatedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_create_buy_order(arg) + + +cdef class SellOrderCreatedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_create_sell_order(arg) + +cdef class RangePositionLiquidityAddedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_add_liquidity(arg) + +cdef class RangePositionLiquidityRemovedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_remove_liquidity(arg) + +cdef class RangePositionUpdateListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_update_lp_order(arg) + +cdef class RangePositionUpdateFailureListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_fail_lp_update(arg) + +cdef class RangePositionFeeCollectedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_collect_fee(arg) + +cdef class RangePositionClosedListener(BaseStrategyEventListener): + cdef c_call(self, object arg): + self._owner.c_did_close_position(arg) +# + + +cdef class StrategyBase(TimeIterator): + BUY_ORDER_COMPLETED_EVENT_TAG = MarketEvent.BuyOrderCompleted.value + SELL_ORDER_COMPLETED_EVENT_TAG = MarketEvent.SellOrderCompleted.value + FUNDING_PAYMENT_COMPLETED_EVENT_TAG = MarketEvent.FundingPaymentCompleted.value + POSITION_MODE_CHANGE_SUCCEEDED_EVENT_TAG = AccountEvent.PositionModeChangeSucceeded.value + POSITION_MODE_CHANGE_FAILED_EVENT_TAG = AccountEvent.PositionModeChangeFailed.value + ORDER_FILLED_EVENT_TAG = MarketEvent.OrderFilled.value + ORDER_CANCELED_EVENT_TAG = MarketEvent.OrderCancelled.value + ORDER_EXPIRED_EVENT_TAG = MarketEvent.OrderExpired.value + ORDER_FAILURE_EVENT_TAG = MarketEvent.OrderFailure.value + BUY_ORDER_CREATED_EVENT_TAG = MarketEvent.BuyOrderCreated.value + SELL_ORDER_CREATED_EVENT_TAG = MarketEvent.SellOrderCreated.value + RANGE_POSITION_LIQUIDITY_ADDED_EVENT_TAG = MarketEvent.RangePositionLiquidityAdded.value + RANGE_POSITION_LIQUIDITY_REMOVED_EVENT_TAG = MarketEvent.RangePositionLiquidityRemoved.value + RANGE_POSITION_UPDATE_EVENT_TAG = MarketEvent.RangePositionUpdate.value + RANGE_POSITION_UPDATE_FAILURE_EVENT_TAG = MarketEvent.RangePositionUpdateFailure.value + RANGE_POSITION_FEE_COLLECTED_EVENT_TAG = MarketEvent.RangePositionFeeCollected.value + RANGE_POSITION_CLOSED_EVENT_TAG = MarketEvent.RangePositionClosed.value + + + @classmethod + def logger(cls) -> logging.Logger: + raise NotImplementedError + + def __init__(self): + super().__init__() + self._sb_markets = set() + self._sb_create_buy_order_listener = BuyOrderCreatedListener(self) + self._sb_create_sell_order_listener = SellOrderCreatedListener(self) + self._sb_fill_order_listener = OrderFilledListener(self) + self._sb_fail_order_listener = OrderFailedListener(self) + self._sb_cancel_order_listener = OrderCancelledListener(self) + self._sb_expire_order_listener = OrderExpiredListener(self) + self._sb_complete_buy_order_listener = BuyOrderCompletedListener(self) + self._sb_complete_sell_order_listener = SellOrderCompletedListener(self) + self._sb_complete_funding_payment_listener = FundingPaymentCompletedListener(self) + self._sb_position_mode_change_success_listener = PositionModeChangeSuccessListener(self) + self._sb_position_mode_change_failure_listener = PositionModeChangeFailureListener(self) + self._sb_range_position_liquidity_added_listener = RangePositionLiquidityAddedListener(self) + self._sb_range_position_liquidity_removed_listener = RangePositionLiquidityRemovedListener(self) + self._sb_range_position_update_listener = RangePositionUpdateListener(self) + self._sb_range_position_update_failure_listener = RangePositionUpdateFailureListener(self) + self._sb_range_position_fee_collected_listener = RangePositionFeeCollectedListener(self) + self._sb_range_position_closed_listener = RangePositionClosedListener(self) + + self._sb_delegate_lock = False + + self._sb_order_tracker = OrderTracker() + + def init_params(self, *args, **kwargs): + """ + Assigns strategy parameters, this function must be called directly after init. + The reason for this is to make the parameters discoverable through introspect (this is not possible on init of + a Cython class). + """ + raise NotImplementedError + + @property + def active_markets(self) -> List[ConnectorBase]: + return list(self._sb_markets) + + @property + def order_tracker(self) -> OrderTracker: + return self._sb_order_tracker + + def format_status(self): + raise NotImplementedError + + def log_with_clock(self, log_level: int, msg: str, **kwargs): + clock_timestamp = pd.Timestamp(self._current_timestamp, unit="s", tz="UTC") + self.logger().log(log_level, f"{msg} [clock={str(clock_timestamp)}]", **kwargs) + + @property + def trades(self) -> List[Trade]: + """ + Returns a list of all completed trades from the market. + The trades are taken from the market event logs. + """ + def event_to_trade(order_filled_event: OrderFilledEvent, market_name: str): + return Trade(order_filled_event.trading_pair, + order_filled_event.trade_type, + order_filled_event.price, + order_filled_event.amount, + order_filled_event.order_type, + market_name, + order_filled_event.timestamp, + order_filled_event.trade_fee) + past_trades = [] + for market in self.active_markets: + event_logs = market.event_logs + order_filled_events = list(filter(lambda e: isinstance(e, OrderFilledEvent), event_logs)) + past_trades += list(map(lambda ofe: event_to_trade(ofe, market.display_name), order_filled_events)) + + return sorted(past_trades, key=lambda x: x.timestamp) + + def market_status_data_frame(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> pd.DataFrame: + cdef: + ConnectorBase market + str trading_pair + str base_asset + str quote_asset + object bid_price + object ask_price + list markets_data = [] + list markets_columns = ["Exchange", "Market", "Best Bid Price", "Best Ask Price", "Mid Price"] + try: + for market_trading_pair_tuple in market_trading_pair_tuples: + market, trading_pair, base_asset, quote_asset = market_trading_pair_tuple + bid_price = market.get_price(trading_pair, False) + ask_price = market.get_price(trading_pair, True) + mid_price = (bid_price + ask_price)/2 + markets_data.append([ + market.display_name, + trading_pair, + float(bid_price), + float(ask_price), + float(mid_price) + ]) + return pd.DataFrame(data=markets_data, columns=markets_columns) + + except Exception: + self.logger().error("Error formatting market stats.", exc_info=True) + + def wallet_balance_data_frame(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> pd.DataFrame: + cdef: + ConnectorBase market + str base_asset + str quote_asset + double base_balance + double quote_balance + double base_asset_conversion_rate + double quote_asset_conversion_rate + list assets_data = [] + list assets_columns = ["Exchange", "Asset", "Total Balance", "Available Balance"] + try: + for market_trading_pair_tuple in market_trading_pair_tuples: + market, trading_pair, base_asset, quote_asset = market_trading_pair_tuple + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + assets_data.extend([ + [market.display_name, base_asset, base_balance, available_base_balance], + [market.display_name, quote_asset, quote_balance, available_quote_balance] + ]) + + return pd.DataFrame(data=assets_data, columns=assets_columns) + + except Exception: + self.logger().error("Error formatting wallet balance stats.", exc_info=True) + + def balance_warning(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> List[str]: + cdef: + double base_balance + double quote_balance + list warning_lines = [] + # Add warning lines on null balances. + # TO-DO: $Use min order size logic to replace the hard-coded 0.0001 value for each asset. + for market_trading_pair_tuple in market_trading_pair_tuples: + base_balance = market_trading_pair_tuple.market.get_balance(market_trading_pair_tuple.base_asset) + quote_balance = market_trading_pair_tuple.market.get_balance(market_trading_pair_tuple.quote_asset) + if base_balance <= Decimal("0.0001") and not isinstance(market_trading_pair_tuple.market, DerivativeBase): + warning_lines.append(f" {market_trading_pair_tuple.market.name} market " + f"{market_trading_pair_tuple.base_asset} balance is too low. Cannot place order.") + if quote_balance <= Decimal("0.0001"): + warning_lines.append(f" {market_trading_pair_tuple.market.name} market " + f"{market_trading_pair_tuple.quote_asset} balance is too low. Cannot place order.") + return warning_lines + + def network_warning(self, market_trading_pair_tuples: List[MarketTradingPairTuple]) -> List[str]: + cdef: + list warning_lines = [] + str trading_pairs + if not all([market_trading_pair_tuple.market.network_status is NetworkStatus.CONNECTED for + market_trading_pair_tuple in market_trading_pair_tuples]): + trading_pairs = " // ".join([market_trading_pair_tuple.trading_pair for market_trading_pair_tuple in market_trading_pair_tuples]) + warning_lines.extend([ + f" Markets are offline for the {trading_pairs} pair. Continued trading " + f"with these markets may be dangerous.", + "" + ]) + return warning_lines + + cdef c_start(self, Clock clock, double timestamp): + TimeIterator.c_start(self, clock, timestamp) + self._sb_order_tracker.c_start(clock, timestamp) + + cdef c_tick(self, double timestamp): + TimeIterator.c_tick(self, timestamp) + self._sb_order_tracker.c_tick(timestamp) + + cdef c_stop(self, Clock clock): + TimeIterator.c_stop(self, clock) + self._sb_order_tracker.c_stop(clock) + self.c_remove_markets(list(self._sb_markets)) + + cdef c_add_markets(self, list markets): + cdef: + ConnectorBase typed_market + + for market in markets: + typed_market = market + typed_market.c_add_listener(self.BUY_ORDER_CREATED_EVENT_TAG, self._sb_create_buy_order_listener) + typed_market.c_add_listener(self.SELL_ORDER_CREATED_EVENT_TAG, self._sb_create_sell_order_listener) + typed_market.c_add_listener(self.ORDER_FILLED_EVENT_TAG, self._sb_fill_order_listener) + typed_market.c_add_listener(self.ORDER_FAILURE_EVENT_TAG, self._sb_fail_order_listener) + typed_market.c_add_listener(self.ORDER_CANCELED_EVENT_TAG, self._sb_cancel_order_listener) + typed_market.c_add_listener(self.ORDER_EXPIRED_EVENT_TAG, self._sb_expire_order_listener) + typed_market.c_add_listener(self.BUY_ORDER_COMPLETED_EVENT_TAG, self._sb_complete_buy_order_listener) + typed_market.c_add_listener(self.SELL_ORDER_COMPLETED_EVENT_TAG, self._sb_complete_sell_order_listener) + typed_market.c_add_listener(self.FUNDING_PAYMENT_COMPLETED_EVENT_TAG, self._sb_complete_funding_payment_listener) + typed_market.c_add_listener(self.POSITION_MODE_CHANGE_SUCCEEDED_EVENT_TAG, self._sb_position_mode_change_success_listener) + typed_market.c_add_listener(self.POSITION_MODE_CHANGE_FAILED_EVENT_TAG, self._sb_position_mode_change_failure_listener) + typed_market.c_add_listener(self.RANGE_POSITION_LIQUIDITY_ADDED_EVENT_TAG, self._sb_range_position_liquidity_added_listener) + typed_market.c_add_listener(self.RANGE_POSITION_LIQUIDITY_REMOVED_EVENT_TAG, self._sb_range_position_liquidity_removed_listener) + typed_market.c_add_listener(self.RANGE_POSITION_UPDATE_EVENT_TAG, self._sb_range_position_update_listener) + typed_market.c_add_listener(self.RANGE_POSITION_UPDATE_FAILURE_EVENT_TAG, self._sb_range_position_update_failure_listener) + typed_market.c_add_listener(self.RANGE_POSITION_FEE_COLLECTED_EVENT_TAG, self._sb_range_position_fee_collected_listener) + typed_market.c_add_listener(self.RANGE_POSITION_CLOSED_EVENT_TAG, self._sb_range_position_closed_listener) + self._sb_markets.add(typed_market) + + def add_markets(self, markets: List[ConnectorBase]): + self.c_add_markets(markets) + + cdef c_remove_markets(self, list markets): + cdef: + ConnectorBase typed_market + + for market in markets: + typed_market = market + if typed_market not in self._sb_markets: + continue + typed_market.c_remove_listener(self.BUY_ORDER_CREATED_EVENT_TAG, self._sb_create_buy_order_listener) + typed_market.c_remove_listener(self.SELL_ORDER_CREATED_EVENT_TAG, self._sb_create_sell_order_listener) + typed_market.c_remove_listener(self.ORDER_FILLED_EVENT_TAG, self._sb_fill_order_listener) + typed_market.c_remove_listener(self.ORDER_FAILURE_EVENT_TAG, self._sb_fail_order_listener) + typed_market.c_remove_listener(self.ORDER_CANCELED_EVENT_TAG, self._sb_cancel_order_listener) + typed_market.c_remove_listener(self.ORDER_EXPIRED_EVENT_TAG, self._sb_expire_order_listener) + typed_market.c_remove_listener(self.BUY_ORDER_COMPLETED_EVENT_TAG, self._sb_complete_buy_order_listener) + typed_market.c_remove_listener(self.SELL_ORDER_COMPLETED_EVENT_TAG, self._sb_complete_sell_order_listener) + typed_market.c_remove_listener(self.FUNDING_PAYMENT_COMPLETED_EVENT_TAG, self._sb_complete_funding_payment_listener) + typed_market.c_remove_listener(self.POSITION_MODE_CHANGE_SUCCEEDED_EVENT_TAG, self._sb_position_mode_change_success_listener) + typed_market.c_remove_listener(self.POSITION_MODE_CHANGE_FAILED_EVENT_TAG, self._sb_position_mode_change_failure_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_LIQUIDITY_ADDED_EVENT_TAG, self._sb_range_position_liquidity_added_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_LIQUIDITY_REMOVED_EVENT_TAG, self._sb_range_position_liquidity_removed_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_UPDATE_EVENT_TAG, self._sb_range_position_update_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_UPDATE_FAILURE_EVENT_TAG, self._sb_range_position_update_failure_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_FEE_COLLECTED_EVENT_TAG, self._sb_range_position_fee_collected_listener) + typed_market.c_remove_listener(self.RANGE_POSITION_CLOSED_EVENT_TAG, self._sb_range_position_closed_listener) + self._sb_markets.remove(typed_market) + + def remove_markets(self, markets: List[ConnectorBase]): + self.c_remove_markets(markets) + + cdef object c_sum_flat_fees(self, str quote_asset, list flat_fees): + + """ + Converts flat fees to quote token and sums up all flat fees + """ + cdef: + object total_flat_fees = s_decimal_0 + + for flat_fee_currency, flat_fee_amount in flat_fees: + if flat_fee_currency == quote_asset: + total_flat_fees += flat_fee_amount + else: + # if the flat fee currency asset does not match quote asset, raise exception for now + # as we don't support different token conversion atm. + raise Exception("Flat fee in other token than quote asset is not supported.") + return total_flat_fees + + def cum_flat_fees(self, quote_asset: str, flat_fees: List): + return self.c_sum_flat_fees(quote_asset, flat_fees) + + # + # ---------------------------------------------------------------------------------------------------------- + cdef c_did_create_buy_order(self, object order_created_event): + """ + In the case of asynchronous order creation on the exchange's server, this event is NOT triggered + upon submission of the order request to the server - it is only triggered once the server has sent + the acknowledgment that the order is successfully created. + """ + pass + + cdef c_did_create_sell_order(self, object order_created_event): + """ + In the case of asynchronous order creation on the exchange's server, this event is NOT triggered + upon submission of the order request to the server - it is only triggered once the server has sent + the acknowledgment that the order is successfully created. + """ + pass + + cdef c_did_fill_order(self, object order_filled_event): + pass + + cdef c_did_fail_order(self, object order_failed_event): + pass + + cdef c_did_cancel_order(self, object cancelled_event): + pass + + cdef c_did_expire_order(self, object expired_event): + pass + + cdef c_did_complete_buy_order(self, object order_completed_event): + pass + + cdef c_did_complete_sell_order(self, object order_completed_event): + pass + + cdef c_did_complete_funding_payment(self, object funding_payment_completed_event): + pass + + cdef c_did_change_position_mode_succeed(self, object position_mode_changed_event): + pass + + cdef c_did_change_position_mode_fail(self, object position_mode_changed_event): + pass + + cdef c_did_add_liquidity(self, object add_liquidity_event): + pass + + cdef c_did_remove_liquidity(self, object remove_liquidity_event): + pass + + cdef c_did_update_lp_order(self, object update_lp_event): + pass + + cdef c_did_fail_lp_update(self, object fail_lp_update_event): + pass + + cdef c_did_collect_fee(self, object collect_fee_event): + pass + + cdef c_did_close_position(self, object closed_event): + pass + # ---------------------------------------------------------------------------------------------------------- + # + + # + # ---------------------------------------------------------------------------------------------------------- + cdef c_did_fail_order_tracker(self, object order_failed_event): + cdef: + str order_id = order_failed_event.order_id + object order_type = order_failed_event.order_type + object market_pair = self._sb_order_tracker.c_get_market_pair_from_order_id(order_id) + + if order_type.is_limit_type(): + self.c_stop_tracking_limit_order(market_pair, order_id) + elif order_type == OrderType.MARKET: + self.c_stop_tracking_market_order(market_pair, order_id) + + cdef c_did_cancel_order_tracker(self, object order_cancelled_event): + cdef: + str order_id = order_cancelled_event.order_id + object market_pair = self._sb_order_tracker.c_get_market_pair_from_order_id(order_id) + + self.c_stop_tracking_limit_order(market_pair, order_id) + + cdef c_did_expire_order_tracker(self, object order_expired_event): + self.c_did_cancel_order_tracker(order_expired_event) + + cdef c_did_complete_buy_order_tracker(self, object order_completed_event): + cdef: + str order_id = order_completed_event.order_id + object market_pair = self._sb_order_tracker.c_get_market_pair_from_order_id(order_id) + object order_type = order_completed_event.order_type + + if market_pair is not None: + if order_type.is_limit_type(): + self.c_stop_tracking_limit_order(market_pair, order_id) + elif order_type == OrderType.MARKET: + self.c_stop_tracking_market_order(market_pair, order_id) + + cdef c_did_complete_sell_order_tracker(self, object order_completed_event): + self.c_did_complete_buy_order_tracker(order_completed_event) + + # ---------------------------------------------------------------------------------------------------------- + # + + # + # ---------------------------------------------------------------------------------------------------------- + + def buy_with_specific_market(self, market_trading_pair_tuple, amount, + order_type=OrderType.MARKET, + price=s_decimal_nan, + expiration_seconds=NaN, + position_action=PositionAction.OPEN): + return self.c_buy_with_specific_market(market_trading_pair_tuple, amount, + order_type, + price, + expiration_seconds, + position_action) + + cdef str c_buy_with_specific_market(self, object market_trading_pair_tuple, object amount, + object order_type=OrderType.MARKET, + object price=s_decimal_nan, + double expiration_seconds=NaN, + position_action=PositionAction.OPEN): + if self._sb_delegate_lock: + raise RuntimeError("Delegates are not allowed to execute orders directly.") + + if not (isinstance(amount, Decimal) and isinstance(price, Decimal)): + raise TypeError("price and amount must be Decimal objects.") + + cdef: + kwargs = {"expiration_ts": self._current_timestamp + expiration_seconds, + "position_action": position_action} + ConnectorBase market = market_trading_pair_tuple.market + + if market not in self._sb_markets: + raise ValueError(f"Market object for buy order is not in the whitelisted markets set.") + + cdef: + str order_id = market.c_buy(market_trading_pair_tuple.trading_pair, + amount=amount, + order_type=order_type, + price=price, + kwargs=kwargs) + + # Start order tracking + if order_type.is_limit_type(): + self.c_start_tracking_limit_order(market_trading_pair_tuple, order_id, True, price, amount) + elif order_type == OrderType.MARKET: + self.c_start_tracking_market_order(market_trading_pair_tuple, order_id, True, amount) + + return order_id + + def sell_with_specific_market(self, market_trading_pair_tuple, amount, + order_type=OrderType.MARKET, + price=s_decimal_nan, + expiration_seconds=NaN, + position_action=PositionAction.OPEN): + return self.c_sell_with_specific_market(market_trading_pair_tuple, amount, + order_type, + price, + expiration_seconds, + position_action) + + cdef str c_sell_with_specific_market(self, object market_trading_pair_tuple, object amount, + object order_type=OrderType.MARKET, + object price=s_decimal_nan, + double expiration_seconds=NaN, + position_action=PositionAction.OPEN): + if self._sb_delegate_lock: + raise RuntimeError("Delegates are not allowed to execute orders directly.") + + if not (isinstance(amount, Decimal) and isinstance(price, Decimal)): + raise TypeError("price and amount must be Decimal objects.") + + cdef: + kwargs = {"expiration_ts": self._current_timestamp + expiration_seconds, + "position_action": position_action} + ConnectorBase market = market_trading_pair_tuple.market + + if market not in self._sb_markets: + raise ValueError(f"Market object for sell order is not in the whitelisted markets set.") + + cdef: + str order_id = market.c_sell(market_trading_pair_tuple.trading_pair, amount, + order_type=order_type, price=price, kwargs=kwargs) + + # Start order tracking + if order_type.is_limit_type(): + self.c_start_tracking_limit_order(market_trading_pair_tuple, order_id, False, price, amount) + elif order_type == OrderType.MARKET: + self.c_start_tracking_market_order(market_trading_pair_tuple, order_id, False, amount) + + return order_id + + cdef c_cancel_order(self, object market_trading_pair_tuple, str order_id): + cdef: + ConnectorBase market = market_trading_pair_tuple.market + + if self._sb_order_tracker.c_check_and_track_cancel(order_id): + self.log_with_clock( + logging.INFO, + f"({market_trading_pair_tuple.trading_pair}) Canceling the limit order {order_id}." + ) + market.c_cancel(market_trading_pair_tuple.trading_pair, order_id) + + def cancel_order(self, market_trading_pair_tuple: MarketTradingPairTuple, order_id: str): + self.c_cancel_order(market_trading_pair_tuple, order_id) + # ---------------------------------------------------------------------------------------------------------- + # + + # + # The following exposed tracking functions are meant to allow extending order tracking behavior in strategy + # classes. + # ---------------------------------------------------------------------------------------------------------- + cdef c_start_tracking_limit_order(self, object market_pair, str order_id, bint is_buy, object price, + object quantity): + self._sb_order_tracker.c_start_tracking_limit_order(market_pair, order_id, is_buy, price, quantity) + + def start_tracking_limit_order(self, market_pair: MarketTradingPairTuple, order_id: str, is_buy: bool, price: Decimal, + quantity: Decimal): + + self.c_start_tracking_limit_order(market_pair, order_id, is_buy, price, quantity) + + cdef c_stop_tracking_limit_order(self, object market_pair, str order_id): + self._sb_order_tracker.c_stop_tracking_limit_order(market_pair, order_id) + + def stop_tracking_limit_order(self, market_pair: MarketTradingPairTuple, order_id: str): + self.c_stop_tracking_limit_order(market_pair, order_id) + + cdef c_start_tracking_market_order(self, object market_pair, str order_id, bint is_buy, object quantity): + self._sb_order_tracker.c_start_tracking_market_order(market_pair, order_id, is_buy, quantity) + + def start_tracking_market_order(self, market_pair: MarketTradingPairTuple, order_id: str, is_buy: bool, quantity: Decimal): + self.c_start_tracking_market_order(market_pair, order_id, is_buy, quantity) + + cdef c_stop_tracking_market_order(self, object market_pair, str order_id): + self._sb_order_tracker.c_stop_tracking_market_order(market_pair, order_id) + + def stop_tracking_market_order(self, market_pair: MarketTradingPairTuple, order_id: str): + self.c_stop_tracking_market_order(market_pair, order_id) + + cdef c_track_restored_orders(self, object market_pair): + cdef: + list limit_orders = market_pair.market.limit_orders + list restored_order_ids = [] + + for order in limit_orders: + restored_order_ids.append(order.client_order_id) + self.c_start_tracking_limit_order(market_pair, + order.client_order_id, + order.is_buy, + order.price, + order.quantity) + return restored_order_ids + + def track_restored_orders(self, market_pair: MarketTradingPairTuple): + return self.c_track_restored_orders(market_pair) + + def notify_hb_app(self, msg: str): + """ + Method called to display message on the Output Panel(upper left) + :param msg: The message to be notified + """ + from hummingbot.client.hummingbot_application import HummingbotApplication + HummingbotApplication.main_application().notify(msg) + + def notify_hb_app_with_timestamp(self, msg: str): + """ + Method called to display message on the Output Panel(upper left) + This implementation adds the timestamp as the first element of the notification + :param msg: The message to be notified + """ + timestamp = pd.Timestamp.fromtimestamp(self._current_timestamp) + self.notify_hb_app(f"({timestamp}) {msg}") + # ---------------------------------------------------------------------------------------------------------- + # diff --git a/hummingbot/strategy/strategy_py_base.pxd b/hummingbot/strategy/strategy_py_base.pxd new file mode 100644 index 0000000..da7a596 --- /dev/null +++ b/hummingbot/strategy/strategy_py_base.pxd @@ -0,0 +1,4 @@ +from hummingbot.strategy.strategy_base cimport StrategyBase + +cdef class StrategyPyBase(StrategyBase): + pass diff --git a/hummingbot/strategy/strategy_py_base.pyx b/hummingbot/strategy/strategy_py_base.pyx new file mode 100644 index 0000000..f194631 --- /dev/null +++ b/hummingbot/strategy/strategy_py_base.pyx @@ -0,0 +1,149 @@ +from hummingbot.strategy.strategy_base cimport StrategyBase +from hummingbot.core.clock import Clock +from hummingbot.core.clock cimport Clock +from hummingbot.core.event.events import ( + BuyOrderCreatedEvent, + SellOrderCreatedEvent, + OrderFilledEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + BuyOrderCompletedEvent, + SellOrderCompletedEvent, + FundingPaymentCompletedEvent, + PositionModeChangeEvent, + RangePositionLiquidityAddedEvent, + RangePositionLiquidityRemovedEvent, + RangePositionUpdateEvent, + RangePositionUpdateFailureEvent, + RangePositionFeeCollectedEvent, + RangePositionClosedEvent +) + + +cdef class StrategyPyBase(StrategyBase): + def __init__(self): + super().__init__() + + cdef c_start(self, Clock clock, double timestamp): + StrategyBase.c_start(self, clock, timestamp) + self.start(clock, timestamp) + + def start(self, clock: Clock, timestamp: float): + pass + + cdef c_stop(self, Clock clock): + StrategyBase.c_stop(self, clock) + self.stop(clock) + + def stop(self, clock: Clock): + pass + + cdef c_tick(self, double timestamp): + StrategyBase.c_tick(self, timestamp) + self.tick(timestamp) + + def tick(self, timestamp: float): + raise NotImplementedError + + cdef c_did_create_buy_order(self, object order_created_event): + self.did_create_buy_order(order_created_event) + + def did_create_buy_order(self, order_created_event: BuyOrderCreatedEvent): + pass + + cdef c_did_create_sell_order(self, object order_created_event): + self.did_create_sell_order(order_created_event) + + def did_create_sell_order(self, order_created_event: SellOrderCreatedEvent): + pass + + cdef c_did_fill_order(self, object order_filled_event): + self.did_fill_order(order_filled_event) + + def did_fill_order(self, order_filled_event: OrderFilledEvent): + pass + + cdef c_did_fail_order(self, object order_failed_event): + self.did_fail_order(order_failed_event) + + def did_fail_order(self, order_failed_event: MarketOrderFailureEvent): + pass + + cdef c_did_cancel_order(self, object cancelled_event): + self.did_cancel_order(cancelled_event) + + def did_cancel_order(self, cancelled_event: OrderCancelledEvent): + pass + + cdef c_did_expire_order(self, object expired_event): + self.did_expire_order(expired_event) + + def did_expire_order(self, expired_event: OrderExpiredEvent): + pass + + cdef c_did_complete_buy_order(self, object order_completed_event): + self.did_complete_buy_order(order_completed_event) + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + pass + + cdef c_did_complete_sell_order(self, object order_completed_event): + self.did_complete_sell_order(order_completed_event) + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + pass + + cdef c_did_complete_funding_payment(self, object funding_payment_completed_event): + self.did_complete_funding_payment(funding_payment_completed_event) + + def did_complete_funding_payment(self, funding_payment_completed_event: FundingPaymentCompletedEvent): + pass + + cdef c_did_change_position_mode_succeed(self, object position_mode_changed_event): + self.did_change_position_mode_succeed(position_mode_changed_event) + + def did_change_position_mode_succeed(self, position_mode_changed_event: PositionModeChangeEvent): + pass + + cdef c_did_change_position_mode_fail(self, object position_mode_changed_event): + self.did_change_position_mode_fail(position_mode_changed_event) + + def did_change_position_mode_fail(self, position_mode_changed_event: PositionModeChangeEvent): + pass + + cdef c_did_add_liquidity(self, object add_liquidity_event): + self.did_add_liquidity(add_liquidity_event) + + def did_add_liquidity(self, add_liquidity_event: RangePositionLiquidityAddedEvent): + pass + + cdef c_did_remove_liquidity(self, object remove_liquidity_event): + self.did_remove_liquidity(remove_liquidity_event) + + def did_remove_liquidity(self, remove_liquidity_event: RangePositionLiquidityRemovedEvent): + pass + + cdef c_did_update_lp_order(self, object update_lp_event): + self.did_update_lp_order(update_lp_event) + + def did_update_lp_order(self, update_lp_event: RangePositionUpdateEvent): + pass + + cdef c_did_fail_lp_update(self, object fail_lp_update_event): + self.did_fail_lp_update(fail_lp_update_event) + + def did_fail_lp_update(self, fail_lp_update_event: RangePositionUpdateFailureEvent): + pass + + cdef c_did_collect_fee(self, object collect_fee_event): + self.did_collect_fee(collect_fee_event) + + def did_collect_fee(self, collect_fee_event: RangePositionFeeCollectedEvent): + pass + + cdef c_did_close_position(self, object closed_position_event): + self.did_close_position(closed_position_event) + + def did_close_position(self, closed_position_event: RangePositionClosedEvent): + pass diff --git a/hummingbot/strategy/twap/__init__.py b/hummingbot/strategy/twap/__init__.py new file mode 100644 index 0000000..d6fe5f9 --- /dev/null +++ b/hummingbot/strategy/twap/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from .twap import TwapTradeStrategy + + +__all__ = [ + TwapTradeStrategy +] diff --git a/hummingbot/strategy/twap/dummy.pxd b/hummingbot/strategy/twap/dummy.pxd new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/twap/dummy.pxd @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/twap/dummy.pyx b/hummingbot/strategy/twap/dummy.pyx new file mode 100644 index 0000000..4b098d6 --- /dev/null +++ b/hummingbot/strategy/twap/dummy.pyx @@ -0,0 +1,2 @@ +cdef class dummy(): + pass diff --git a/hummingbot/strategy/twap/start.py b/hummingbot/strategy/twap/start.py new file mode 100644 index 0000000..07253d9 --- /dev/null +++ b/hummingbot/strategy/twap/start.py @@ -0,0 +1,71 @@ +from datetime import datetime +from typing import ( + List, + Tuple, +) + +from hummingbot.strategy.conditional_execution_state import ( + RunAlwaysExecutionState, + RunInTimeConditionalExecutionState) +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.twap import ( + TwapTradeStrategy +) +from hummingbot.strategy.twap.twap_config_map import twap_config_map + + +def start(self): + try: + order_step_size = twap_config_map.get("order_step_size").value + trade_side = twap_config_map.get("trade_side").value + target_asset_amount = twap_config_map.get("target_asset_amount").value + is_time_span_execution = twap_config_map.get("is_time_span_execution").value + is_delayed_start_execution = twap_config_map.get("is_delayed_start_execution").value + exchange = twap_config_map.get("connector").value.lower() + raw_market_trading_pair = twap_config_map.get("trading_pair").value + order_price = twap_config_map.get("order_price").value + cancel_order_wait_time = twap_config_map.get("cancel_order_wait_time").value + + try: + assets: Tuple[str, str] = self._initialize_market_assets(exchange, [raw_market_trading_pair])[0] + except ValueError as e: + self.notify(str(e)) + return + + market_names: List[Tuple[str, List[str]]] = [(exchange, [raw_market_trading_pair])] + + self._initialize_markets(market_names) + maker_data = [self.markets[exchange], raw_market_trading_pair] + list(assets) + self.market_trading_pair_tuples = [MarketTradingPairTuple(*maker_data)] + + is_buy = trade_side == "buy" + + if is_time_span_execution: + start_datetime_string = twap_config_map.get("start_datetime").value + end_datetime_string = twap_config_map.get("end_datetime").value + start_time = datetime.fromisoformat(start_datetime_string) + end_time = datetime.fromisoformat(end_datetime_string) + + order_delay_time = twap_config_map.get("order_delay_time").value + execution_state = RunInTimeConditionalExecutionState(start_timestamp=start_time, end_timestamp=end_time) + elif is_delayed_start_execution: + start_datetime_string = twap_config_map.get("start_datetime").value + start_time = datetime.fromisoformat(start_datetime_string) + + order_delay_time = twap_config_map.get("order_delay_time").value + execution_state = RunInTimeConditionalExecutionState(start_timestamp=start_time) + else: + order_delay_time = twap_config_map.get("order_delay_time").value + execution_state = RunAlwaysExecutionState() + + self.strategy = TwapTradeStrategy(market_infos=[MarketTradingPairTuple(*maker_data)], + is_buy=is_buy, + target_asset_amount=target_asset_amount, + order_step_size=order_step_size, + order_price=order_price, + order_delay_time=order_delay_time, + execution_state=execution_state, + cancel_order_wait_time=cancel_order_wait_time) + except Exception as e: + self.notify(str(e)) + self.logger().error("Unknown error during initialization.", exc_info=True) diff --git a/hummingbot/strategy/twap/twap.py b/hummingbot/strategy/twap/twap.py new file mode 100644 index 0000000..1fa01f3 --- /dev/null +++ b/hummingbot/strategy/twap/twap.py @@ -0,0 +1,398 @@ +import logging +import statistics +from datetime import datetime +from decimal import Decimal +from typing import Dict, List, Optional, Tuple + +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.clock import Clock +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.events import MarketOrderFailureEvent, OrderCancelledEvent, OrderExpiredEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.conditional_execution_state import ConditionalExecutionState, RunAlwaysExecutionState +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +twap_logger = None + + +class TwapTradeStrategy(StrategyPyBase): + """ + Time-Weighted Average Price strategy + This strategy is intended for executing trades evenly over a specified time period. + """ + + @classmethod + def logger(cls) -> HummingbotLogger: + global twap_logger + if twap_logger is None: + twap_logger = logging.getLogger(__name__) + return twap_logger + + def __init__(self, + market_infos: List[MarketTradingPairTuple], + is_buy: bool, + target_asset_amount: Decimal, + order_step_size: Decimal, + order_price: Decimal, + order_delay_time: float = 10.0, + execution_state: ConditionalExecutionState = None, + cancel_order_wait_time: Optional[float] = 60.0, + status_report_interval: float = 900): + """ + :param market_infos: list of market trading pairs + :param is_buy: if the order is to buy + :param target_asset_amount: qty of the order to place + :param order_step_size: amount of base asset to be configured in each order + :param order_price: price to place the order at + :param order_delay_time: how long to wait between placing trades + :param execution_state: execution state object with the conditions that should be satisfied to run each tick + :param cancel_order_wait_time: how long to wait before canceling an order + :param status_report_interval: how often to report network connection related warnings, if any + """ + + if len(market_infos) < 1: + raise ValueError("market_infos must not be empty.") + + super().__init__() + self._market_infos = { + (market_info.market, market_info.trading_pair): market_info + for market_info in market_infos + } + self._all_markets_ready = False + self._place_orders = True + self._status_report_interval = status_report_interval + self._order_delay_time = order_delay_time + self._quantity_remaining = target_asset_amount + self._time_to_cancel = {} + self._is_buy = is_buy + self._target_asset_amount = target_asset_amount + self._order_step_size = order_step_size + self._first_order = True + self._previous_timestamp = 0 + self._last_timestamp = 0 + self._order_price = order_price + self._execution_state = execution_state or RunAlwaysExecutionState() + + if cancel_order_wait_time is not None: + self._cancel_order_wait_time = cancel_order_wait_time + + all_markets = set([market_info.market for market_info in market_infos]) + self.add_markets(list(all_markets)) + + @property + def active_bids(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return self.order_tracker.active_bids + + @property + def active_asks(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return self.order_tracker.active_asks + + @property + def active_limit_orders(self) -> List[Tuple[ExchangeBase, LimitOrder]]: + return self.order_tracker.active_limit_orders + + @property + def in_flight_cancels(self) -> Dict[str, float]: + return self.order_tracker.in_flight_cancels + + @property + def market_info_to_active_orders(self) -> Dict[MarketTradingPairTuple, List[LimitOrder]]: + return self.order_tracker.market_pair_to_active_orders + + @property + def place_orders(self): + return self._place_orders + + def configuration_status_lines(self,): + lines = ["", " Configuration:"] + + for market_info in self._market_infos.values(): + lines.append(" " + f"Total amount: {PerformanceMetrics.smart_round(self._target_asset_amount)} " + f"{market_info.base_asset} " + f"Order price: {PerformanceMetrics.smart_round(self._order_price)} " + f"{market_info.quote_asset} " + f"Order size: {PerformanceMetrics.smart_round(self._order_step_size)} " + f"{market_info.base_asset}") + + lines.append(f" Execution type: {self._execution_state}") + + return lines + + def filled_trades(self): + """ + Returns a list of all filled trades generated from limit orders with the same trade type the strategy + has in its configuration + """ + trade_type = TradeType.BUY if self._is_buy else TradeType.SELL + return [trade + for trade + in self.trades + if trade.trade_type == trade_type.name and trade.order_type == OrderType.LIMIT] + + def format_status(self) -> str: + lines: list = [] + warning_lines: list = [] + + lines.extend(self.configuration_status_lines()) + + for market_info in self._market_infos.values(): + + active_orders = self.market_info_to_active_orders.get(market_info, []) + + warning_lines.extend(self.network_warning([market_info])) + + markets_df = self.market_status_data_frame([market_info]) + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string().split("\n")]) + + assets_df = self.wallet_balance_data_frame([market_info]) + lines.extend(["", " Assets:"] + [" " + line for line in assets_df.to_string().split("\n")]) + + # See if there're any open orders. + if len(active_orders) > 0: + price_provider = None + for market_info in self._market_infos.values(): + price_provider = market_info + if price_provider is not None: + df = LimitOrder.to_pandas(active_orders, mid_price=float(price_provider.get_mid_price())) + if self._is_buy: + # Descend from the price closest to the mid price + df = df.sort_values(by=['Price'], ascending=False) + else: + # Ascend from the price closest to the mid price + df = df.sort_values(by=['Price'], ascending=True) + df = df.reset_index(drop=True) + df_lines = df.to_string().split("\n") + lines.extend(["", " Active orders:"] + + [" " + line for line in df_lines]) + else: + lines.extend(["", " No active maker orders."]) + + filled_trades = self.filled_trades() + average_price = (statistics.mean([trade.price for trade in filled_trades]) + if filled_trades + else Decimal(0)) + lines.extend(["", + f" Average filled orders price: " + f"{PerformanceMetrics.smart_round(average_price)} " + f"{market_info.quote_asset}"]) + + lines.extend([f" Pending amount: {PerformanceMetrics.smart_round(self._quantity_remaining)} " + f"{market_info.base_asset}"]) + + warning_lines.extend(self.balance_warning([market_info])) + + if warning_lines: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def did_fill_order(self, order_filled_event): + """ + Output log for filled order. + :param order_filled_event: Order filled event + """ + order_id: str = order_filled_event.order_id + market_info = self.order_tracker.get_shadow_market_pair_from_order_id(order_id) + + if market_info is not None: + self.log_with_clock(logging.INFO, + f"({market_info.trading_pair}) Limit {order_filled_event.trade_type.name.lower()} order of " + f"{order_filled_event.amount} {market_info.base_asset} filled.") + + def did_complete_buy_order(self, order_completed_event): + """ + Output log for completed buy order. + :param order_completed_event: Order completed event + """ + self.log_complete_order(order_completed_event) + + def did_complete_sell_order(self, order_completed_event): + """ + Output log for completed sell order. + :param order_completed_event: Order completed event + """ + self.log_complete_order(order_completed_event) + + def log_complete_order(self, order_completed_event): + """ + Output log for completed order. + :param order_completed_event: Order completed event + """ + order_id: str = order_completed_event.order_id + market_info = self.order_tracker.get_market_pair_from_order_id(order_id) + + if market_info is not None: + limit_order_record = self.order_tracker.get_limit_order(market_info, order_id) + order_type = "buy" if limit_order_record.is_buy else "sell" + self.log_with_clock( + logging.INFO, + f"({market_info.trading_pair}) Limit {order_type} order {order_id} " + f"({limit_order_record.quantity} {limit_order_record.base_currency} @ " + f"{limit_order_record.price} {limit_order_record.quote_currency}) has been filled." + ) + + def did_cancel_order(self, cancelled_event: OrderCancelledEvent): + self.update_remaining_after_removing_order(cancelled_event.order_id, 'cancel') + + def did_fail_order(self, order_failed_event: MarketOrderFailureEvent): + self.update_remaining_after_removing_order(order_failed_event.order_id, 'fail') + + def did_expire_order(self, expired_event: OrderExpiredEvent): + self.update_remaining_after_removing_order(expired_event.order_id, 'expire') + + def update_remaining_after_removing_order(self, order_id: str, event_type: str): + market_info = self.order_tracker.get_market_pair_from_order_id(order_id) + + if market_info is not None: + limit_order_record = self.order_tracker.get_limit_order(market_info, order_id) + if limit_order_record is not None: + self.log_with_clock(logging.INFO, f"Updating status after order {event_type} (id: {order_id})") + self._quantity_remaining += limit_order_record.quantity + + def process_market(self, market_info): + """ + Checks if enough time has elapsed from previous order to place order and if so, calls place_orders_for_market() and + cancels orders if they are older than self._cancel_order_wait_time. + + :param market_info: a market trading pair + """ + if self._quantity_remaining > 0: + + # If current timestamp is greater than the start timestamp and its the first order + if (self.current_timestamp > self._previous_timestamp) and self._first_order: + + self.logger().info("Trying to place orders now. ") + self._previous_timestamp = self.current_timestamp + self.place_orders_for_market(market_info) + self._first_order = False + + # If current timestamp is greater than the start timestamp + time delay place orders + elif (self.current_timestamp > self._previous_timestamp + self._order_delay_time) and (self._first_order is False): + self.logger().info("Current time: " + f"{datetime.fromtimestamp(self.current_timestamp).strftime('%Y-%m-%d %H:%M:%S')} " + "is now greater than " + "Previous time: " + f"{datetime.fromtimestamp(self._previous_timestamp).strftime('%Y-%m-%d %H:%M:%S')} " + f" with time delay: {self._order_delay_time}. Trying to place orders now. ") + self._previous_timestamp = self.current_timestamp + self.place_orders_for_market(market_info) + + active_orders = self.market_info_to_active_orders.get(market_info, []) + + orders_to_cancel = (active_order + for active_order + in active_orders + if self.current_timestamp >= self._time_to_cancel[active_order.client_order_id]) + + for order in orders_to_cancel: + self.cancel_order(market_info, order.client_order_id) + + def start(self, clock: Clock, timestamp: float): + self.logger().info(f"Waiting for {self._order_delay_time} to place orders") + self._previous_timestamp = timestamp + self._last_timestamp = timestamp + + def tick(self, timestamp: float): + """ + Clock tick entry point. + For the TWAP strategy, this function simply checks for the readiness and connection status of markets, and + then delegates the processing of each market info to process_market(). + + :param timestamp: current tick timestamp + """ + + try: + self._execution_state.process_tick(timestamp, self) + finally: + self._last_timestamp = timestamp + + def process_tick(self, timestamp: float): + """ + Clock tick entry point. + For the TWAP strategy, this function simply checks for the readiness and connection status of markets, and + then delegates the processing of each market info to process_market(). + """ + current_tick = timestamp // self._status_report_interval + last_tick = (self._last_timestamp // self._status_report_interval) + should_report_warnings = current_tick > last_tick + + if not self._all_markets_ready: + self._all_markets_ready = all([market.ready for market in self.active_markets]) + if not self._all_markets_ready: + # Markets not ready yet. Don't do anything. + if should_report_warnings: + self.logger().warning("Markets are not ready. No market making trades are permitted.") + return + + if (should_report_warnings + and not all([market.network_status is NetworkStatus.CONNECTED for market in self.active_markets])): + self.logger().warning("WARNING: Some markets are not connected or are down at the moment. Market " + "making may be dangerous when markets or networks are unstable.") + + for market_info in self._market_infos.values(): + self.process_market(market_info) + + def cancel_active_orders(self): + # Nothing to do here + pass + + def place_orders_for_market(self, market_info): + """ + Places an individual order specified by the user input if the user has enough balance and if the order quantity + can be broken up to the number of desired orders + :param market_info: a market trading pair + """ + market: ExchangeBase = market_info.market + curr_order_amount = min(self._order_step_size, self._quantity_remaining) + quantized_amount = market.quantize_order_amount(market_info.trading_pair, Decimal(curr_order_amount)) + quantized_price = market.quantize_order_price(market_info.trading_pair, Decimal(self._order_price)) + + self.logger().debug("Checking to see if the incremental order size is possible") + self.logger().debug("Checking to see if the user has enough balance to place orders") + + if quantized_amount != 0: + if self.has_enough_balance(market_info, quantized_amount): + if self._is_buy: + order_id = self.buy_with_specific_market(market_info, + amount=quantized_amount, + order_type=OrderType.LIMIT, + price=quantized_price) + self.logger().info("Limit buy order has been placed") + else: + order_id = self.sell_with_specific_market(market_info, + amount=quantized_amount, + order_type=OrderType.LIMIT, + price=quantized_price) + self.logger().info("Limit sell order has been placed") + self._time_to_cancel[order_id] = self.current_timestamp + self._cancel_order_wait_time + + self._quantity_remaining = Decimal(self._quantity_remaining) - quantized_amount + + else: + self.logger().info("Not enough balance to run the strategy. Please check balances and try again.") + else: + self.logger().warning("Not possible to break the order into the desired number of segments.") + + def has_enough_balance(self, market_info, amount: Decimal): + """ + Checks to make sure the user has the sufficient balance in order to place the specified order + + :param market_info: a market trading pair + :param amount: order amount + :return: True if user has enough balance, False if not + """ + market: ExchangeBase = market_info.market + base_asset_balance = market.get_balance(market_info.base_asset) + quote_asset_balance = market.get_balance(market_info.quote_asset) + order_book: OrderBook = market_info.order_book + price = order_book.get_price_for_volume(True, float(amount)).result_price + + return quote_asset_balance >= (amount * Decimal(price)) \ + if self._is_buy \ + else base_asset_balance >= amount diff --git a/hummingbot/strategy/twap/twap_config_map.py b/hummingbot/strategy/twap/twap_config_map.py new file mode 100644 index 0000000..66c3b86 --- /dev/null +++ b/hummingbot/strategy/twap/twap_config_map.py @@ -0,0 +1,160 @@ +import math +from datetime import datetime +from decimal import Decimal +from typing import Optional + +from hummingbot.client.config.config_validators import ( + validate_bool, + validate_datetime_iso_string, + validate_decimal, + validate_exchange, + validate_market_trading_pair, +) +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, required_exchanges + + +def trading_pair_prompt(): + exchange = twap_config_map.get("connector").value + example = AllConnectorSettings.get_example_pairs().get(exchange) + return "Enter the token trading pair you would like to trade on %s%s >>> " \ + % (exchange, f" (e.g. {example})" if example else "") + + +def target_asset_amount_prompt(): + trading_pair = twap_config_map.get("trading_pair").value + base_token, _ = trading_pair.split("-") + + return f"What is the total amount of {base_token} to be traded? (Default is 1.0) >>> " + + +def str2bool(value: str): + return str(value).lower() in ("yes", "y", "true", "t", "1") + + +# checks if the trading pair is valid +def validate_market_trading_pair_tuple(value: str) -> Optional[str]: + exchange = twap_config_map.get("connector").value + return validate_market_trading_pair(exchange, value) + + +def set_order_delay_default(value: str = None): + start_datetime_string = twap_config_map.get("start_datetime").value + end_datetime_string = twap_config_map.get("end_datetime").value + start_datetime = datetime.fromisoformat(start_datetime_string) + end_datetime = datetime.fromisoformat(end_datetime_string) + + target_asset_amount = twap_config_map.get("target_asset_amount").value + order_step_size = twap_config_map.get("order_step_size").value + + default = math.floor((end_datetime - start_datetime).total_seconds() / math.ceil(target_asset_amount / order_step_size)) + twap_config_map.get("order_delay_time").default = default + + +def validate_order_step_size(value: str = None): + """ + Invalidates non-decimal input and checks if order_step_size is less than the target_asset_amount value + :param value: User input for order_step_size parameter + :return: Error message printed in output pane + """ + result = validate_decimal(value, min_value=Decimal("0"), inclusive=False) + if result is not None: + return result + target_asset_amount = twap_config_map.get("target_asset_amount").value + if Decimal(value) > target_asset_amount: + return "Order step size cannot be greater than the total trade amount." + + +twap_config_map = { + "strategy": + ConfigVar(key="strategy", + prompt=None, + default="twap"), + "connector": + ConfigVar(key="connector", + prompt="Enter the name of spot connector >>> ", + validator=validate_exchange, + on_validated=lambda value: required_exchanges.add(value), + prompt_on_new=True), + "trading_pair": + ConfigVar(key="trading_pair", + prompt=trading_pair_prompt, + validator=validate_market_trading_pair_tuple, + prompt_on_new=True), + "trade_side": + ConfigVar(key="trade_side", + prompt="What operation will be executed? (buy/sell) >>> ", + type_str="str", + validator=lambda v: None if v in {"buy", "sell", ""} else "Invalid operation type.", + default="buy", + prompt_on_new=True), + "target_asset_amount": + ConfigVar(key="target_asset_amount", + prompt=target_asset_amount_prompt, + default=1.0, + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=False), + prompt_on_new=True), + "order_step_size": + ConfigVar(key="order_step_size", + prompt="What is the amount of each individual order (denominated in the base asset, default is 1)? " + ">>> ", + default=1.0, + type_str="decimal", + validator=validate_order_step_size, + prompt_on_new=True), + "order_price": + ConfigVar(key="order_price", + prompt="What is the price for the limit orders? >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=False), + prompt_on_new=True), + "is_delayed_start_execution": + ConfigVar(key="is_delayed_start_execution", + prompt="Do you want to specify a start time for the execution? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool, + prompt_on_new=True), + "start_datetime": + ConfigVar(key="start_datetime", + prompt="Please enter the start date and time" + " (YYYY-MM-DD HH:MM:SS) >>> ", + type_str="str", + validator=validate_datetime_iso_string, + required_if=lambda: twap_config_map.get("is_time_span_execution").value or twap_config_map.get("is_delayed_start_execution").value, + prompt_on_new=True), + "is_time_span_execution": + ConfigVar(key="is_time_span_execution", + prompt="Do you want to specify an end time for the execution? (Yes/No) >>> ", + type_str="bool", + default=False, + validator=validate_bool, + prompt_on_new=True), + "end_datetime": + ConfigVar(key="end_datetime", + prompt="Please enter the end date and time" + " (YYYY-MM-DD HH:MM:SS) >>> ", + type_str="str", + validator=validate_datetime_iso_string, + on_validated=set_order_delay_default, + required_if=lambda: twap_config_map.get("is_time_span_execution").value, + prompt_on_new=True), + "order_delay_time": + ConfigVar(key="order_delay_time", + prompt="How many seconds do you want to wait between each individual order?" + " (Enter 10 to indicate 10 seconds)? >>> ", + type_str="float", + default=10, + validator=lambda v: validate_decimal(v, 0, inclusive=False), + required_if=lambda: twap_config_map.get("is_time_span_execution").value or twap_config_map.get("is_delayed_start_execution").value, + prompt_on_new=True), + "cancel_order_wait_time": + ConfigVar(key="cancel_order_wait_time", + prompt="How long do you want to wait before canceling your limit order (in seconds). " + "(Default is 60 seconds) ? >>> ", + type_str="float", + default=60, + validator=lambda v: validate_decimal(v, 0, inclusive=False), + prompt_on_new=True) +} diff --git a/hummingbot/strategy/uniswap_v3_lp/__init__.py b/hummingbot/strategy/uniswap_v3_lp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/strategy/uniswap_v3_lp/start.py b/hummingbot/strategy/uniswap_v3_lp/start.py new file mode 100644 index 0000000..14079ec --- /dev/null +++ b/hummingbot/strategy/uniswap_v3_lp/start.py @@ -0,0 +1,25 @@ +from decimal import Decimal + +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp import UniswapV3LpStrategy +from hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp_config_map import uniswap_v3_lp_config_map as c_map + + +def start(self): + connector = c_map.get("connector").value + pair = c_map.get("market").value + fee_tier = c_map.get("fee_tier").value + price_spread = c_map.get("price_spread").value / Decimal("100") + amount = c_map.get("amount").value + min_profitability = c_map.get("min_profitability").value + + self._initialize_markets([(connector, [pair])]) + base, quote = pair.split("-") + + market_info = MarketTradingPairTuple(self.markets[connector], pair, base, quote) + self.market_trading_pair_tuples = [market_info] + self.strategy = UniswapV3LpStrategy(market_info, + fee_tier, + price_spread, + amount, + min_profitability) diff --git a/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp.py b/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp.py new file mode 100644 index 0000000..6aaa67b --- /dev/null +++ b/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp.py @@ -0,0 +1,224 @@ +import asyncio +import logging +from decimal import Decimal + +import pandas as pd + +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.core.clock import Clock +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + +ulp_logger = None +s_decimal_0 = Decimal("0") + + +class UniswapV3LpStrategy(StrategyPyBase): + + @classmethod + def logger(cls) -> HummingbotLogger: + global ulp_logger + if ulp_logger is None: + ulp_logger = logging.getLogger(__name__) + return ulp_logger + + def __init__(self, + market_info: MarketTradingPairTuple, + fee_tier: str, + price_spread: Decimal, + amount: Decimal, + min_profitability: Decimal, + status_report_interval: float = 900): + super().__init__() + self._market_info = market_info + self._fee_tier = fee_tier + self._price_spread = price_spread + self._amount = amount + self._min_profitability = min_profitability + + self._ev_loop = asyncio.get_event_loop() + self._last_timestamp = 0 + self._status_report_interval = status_report_interval + self.add_markets([market_info.market]) + self._connector_ready = False + self._last_price = s_decimal_0 + self._main_task = None + self._fetch_prices_task = None + + @property + def connector_name(self): + return self._market_info.market.display_name + + @property + def base_asset(self): + return self._market_info.base_asset + + @property + def quote_asset(self): + return self._market_info.quote_asset + + @property + def trading_pair(self): + return self._market_info.trading_pair + + @property + def active_positions(self): + return [pos for pos in self._market_info.market.amm_lp_orders if pos.is_nft and pos.trading_pair == self.trading_pair] + + @property + def active_orders(self): + return [pos for pos in self._market_info.market.amm_lp_orders if not pos.is_nft and pos.trading_pair == self.trading_pair] + + async def get_pool_price(self, update_volatility: bool = False) -> float: + prices = await self._market_info.market.get_price(self.trading_pair, self._fee_tier) + if prices: + return Decimal(prices[-1]) + else: + return s_decimal_0 + + def active_positions_df(self) -> pd.DataFrame: + columns = ["Id", "Fee Tier", "Symbol", "Price Range", "Base/Quote Amount", "Unclaimed Base/Quote Fees", ""] + data = [] + if len(self.active_positions) > 0: + for position in self.active_positions: + data.append([ + position.token_id, + position.fee_tier, + position.trading_pair, + f"{PerformanceMetrics.smart_round(position.adjusted_lower_price, 8)} - " + f"{PerformanceMetrics.smart_round(position.adjusted_upper_price, 8)}", + f"{PerformanceMetrics.smart_round(position.amount_0, 8)} / " + f"{PerformanceMetrics.smart_round(position.amount_1, 8)}", + f"{PerformanceMetrics.smart_round(position.unclaimed_fee_0, 8)} / " + f"{PerformanceMetrics.smart_round(position.unclaimed_fee_1, 8)}", + "[In range]" if self._last_price >= position.adjusted_lower_price and self._last_price <= position.adjusted_upper_price else "[Out of range]" + ]) + return pd.DataFrame(data=data, columns=columns) + + async def format_status(self) -> str: + """ + Returns a status string formatted to display nicely on terminal. The strings composes of 4 parts: market, + assets, spread and warnings(if any). + """ + if not self._connector_ready: + return f"{self.connector_name} connector not ready." + + columns = ["Exchange", "Market", "Pool Price"] + data = [] + market, trading_pair, base_asset, quote_asset = self._market_info + data.append([ + market.display_name, + trading_pair, + PerformanceMetrics.smart_round(Decimal(str(self._last_price)), 8) + ]) + markets_df = pd.DataFrame(data=data, columns=columns) + lines = [] + lines.extend(["", " Markets:"] + [" " + line for line in markets_df.to_string(index=False).split("\n")]) + + # See if there're any active positions. + if len(self.active_positions) > 0: + pos_info_df = self.active_positions_df() + lines.extend(["", " Positions:"] + [" " + line for line in pos_info_df.to_string(index=False).split("\n")]) + else: + lines.extend(["", " No active positions."]) + + assets_df = self.wallet_balance_data_frame([self._market_info]) + lines.extend(["", " Assets:"] + + [" " + line for line in str(assets_df).split("\n")]) + + warning_lines = self.network_warning([self._market_info]) + warning_lines.extend(self.balance_warning([self._market_info])) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) + + def tick(self, timestamp: float): + """ + Clock tick entry point, is run every second (on normal tick setting). + :param timestamp: current tick timestamp + """ + if not self._connector_ready: + self._connector_ready = self._market_info.market.ready + if not self._connector_ready: + self.logger().warning(f"{self.connector_name} connector is not ready. Please wait...") + return + else: + self.logger().info(f"{self.connector_name} connector is ready. Trading started.") + + if self._main_task is None or self._main_task.done(): + self._main_task = safe_ensure_future(self.main()) + + async def main(self): + if len(self.active_orders) == 0: # this ensures that there'll always be one lp order per time + lower_price, upper_price = await self.propose_position_boundary() + if lower_price + upper_price != s_decimal_0: + self.execute_proposal(lower_price, upper_price) + self.close_matured_positions() + + def any_active_position(self, current_price: Decimal): + """ + We use this to know if any existing position is in-range. + :return: True/False + """ + for position in self.active_positions: + if current_price >= position.lower_price and current_price <= position.upper_price: + return True + return False + + async def propose_position_boundary(self): + """ + We use this to create proposal for new range positions + :return : lower_price, upper_price + """ + lower_price = s_decimal_0 + upper_price = s_decimal_0 + current_price = await self.get_pool_price() + + if current_price != s_decimal_0: + self._last_price = current_price + if not self.any_active_position(current_price): # only set prices if there's no active position + half_spread = self._price_spread / Decimal("2") + lower_price = (current_price * (Decimal("1") - half_spread)) + upper_price = (current_price * (Decimal("1") + half_spread)) + lower_price = max(s_decimal_0, lower_price) + return lower_price, upper_price + + def execute_proposal(self, lower_price: Decimal, upper_price: Decimal): + """ + This execute proposal generated earlier by propose_position_boundary function. + :param lower_price: lower price for position to be created + :param upper_price: upper price for position to be created + """ + base_balance = self._market_info.market.get_available_balance(self.base_asset) + quote_balance = self._market_info.market.get_available_balance(self.quote_asset) + if base_balance + quote_balance == s_decimal_0: + self.log_with_clock(logging.INFO, + "Both balances exhausted. Add more assets.") + else: + self.log_with_clock(logging.INFO, f"Creating new position over {lower_price} to {upper_price} price range.") + self._market_info.market.add_liquidity(self.trading_pair, + min(base_balance, self._amount), + min(quote_balance, (self._amount * self._last_price)), + lower_price, + upper_price, + self._fee_tier) + + def close_matured_positions(self): + """ + This closes out-of-range positions that have more than the min profitability. + """ + for position in self.active_positions: + if self._last_price <= position.lower_price or self._last_price >= position.upper_price: # out-of-range + if position.unclaimed_fee_0 + (position.unclaimed_fee_1 / self._last_price) > self._min_profitability: # matured + self.log_with_clock(logging.INFO, + f"Closing position with Id {position.token_id}." + f"Unclaimed base fee: {position.unclaimed_fee_0}, unclaimed quote fee: {position.unclaimed_fee_1}") + self._market_info.market.remove_liquidity(self.trading_pair, position.token_id) + + def stop(self, clock: Clock): + if self._main_task is not None: + self._main_task.cancel() + self._main_task = None diff --git a/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp_config_map.py b/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp_config_map.py new file mode 100644 index 0000000..1148605 --- /dev/null +++ b/hummingbot/strategy/uniswap_v3_lp/uniswap_v3_lp_config_map.py @@ -0,0 +1,87 @@ +from decimal import Decimal + +from hummingbot.client.config.config_validators import validate_decimal, validate_market_trading_pair +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import ( + AllConnectorSettings, + ConnectorType, + required_exchanges, + requried_connector_trading_pairs, +) + + +def exchange_on_validated(value: str): + required_exchanges.add(value) + + +def validate_connector(value: str): + connector = AllConnectorSettings.get_connector_settings().get(value, None) + if not connector or connector.type != ConnectorType.AMM_LP: + return "Only AMM_LP connectors allowed." + + +def market_validator(value: str) -> None: + connector = uniswap_v3_lp_config_map.get("connector").value + return validate_market_trading_pair(connector, value) + + +def market_on_validated(value: str) -> None: + connector = uniswap_v3_lp_config_map.get("connector").value + requried_connector_trading_pairs[connector] = [value] + + +def market_prompt() -> str: + connector = uniswap_v3_lp_config_map.get("connector").value + example = AllConnectorSettings.get_example_pairs().get(connector) + return "Enter the trading pair you would like to provide liquidity on {}>>> ".format( + f"(e.g. {example}) " if example else "") + + +uniswap_v3_lp_config_map = { + "strategy": ConfigVar( + key="strategy", + prompt="", + default="uniswap_v3_lp"), + "connector": ConfigVar( + key="connector", + prompt="Enter name of LP connector >>> ", + validator=validate_connector, + on_validated=exchange_on_validated, + prompt_on_new=True), + "market": ConfigVar( + key="market", + prompt=market_prompt, + prompt_on_new=True, + validator=market_validator, + on_validated=market_on_validated), + "fee_tier": ConfigVar( + key="fee_tier", + prompt="On which fee tier do you want to provide liquidity on? (LOWEST/LOW/MEDIUM/HIGH) ", + validator=lambda s: None if s in {"LOWEST", + "LOW", + "MEDIUM", + "HIGH", + } else + "Invalid fee tier.", + prompt_on_new=True), + "price_spread": ConfigVar( + key="price_spread", + prompt="How wide around current pool price and/or last created positions do you want new positions to span? (Enter 1 to indicate 1%) >>> ", + type_str="decimal", + validator=lambda v: validate_decimal(v, Decimal("0"), inclusive=False), + default=Decimal("1"), + prompt_on_new=True), + "amount": ConfigVar( + key="amount", + prompt="Enter the maximum value(in terms of base asset) to use for providing liquidity. >>>", + prompt_on_new=True, + validator=lambda v: validate_decimal(v, Decimal("0"), inclusive=False), + type_str="decimal"), + "min_profitability": ConfigVar( + key="min_profitability", + prompt="What is the minimum unclaimed fees an out of range position must have before it is closed? (in terms of base asset) >>>", + prompt_on_new=False, + validator=lambda v: validate_decimal(v, Decimal("0"), inclusive=False), + default=Decimal("1"), + type_str="decimal"), +} diff --git a/hummingbot/strategy/utils.py b/hummingbot/strategy/utils.py new file mode 100644 index 0000000..66bc3c3 --- /dev/null +++ b/hummingbot/strategy/utils.py @@ -0,0 +1,20 @@ +import math +import time +from typing import Optional + +from hummingbot.core.data_type.limit_order import LimitOrder + + +def order_age(order: LimitOrder, current_time: Optional[float] = None) -> int: + """ + Get the age of a limit order in second. + :param order: the order to calculate the age for + :param current_time: the current time in seconds. If not specified the function will take the machine time + :return: number of seconds since the order was created until the current time + """ + now = current_time if current_time is not None and not math.isnan(current_time) else _time() + return int(now - (order.creation_timestamp / 1e6)) + + +def _time() -> float: + return time.time() diff --git a/hummingbot/templates/conf_amm_arb_strategy_TEMPLATE.yml b/hummingbot/templates/conf_amm_arb_strategy_TEMPLATE.yml new file mode 100644 index 0000000..4985575 --- /dev/null +++ b/hummingbot/templates/conf_amm_arb_strategy_TEMPLATE.yml @@ -0,0 +1,47 @@ +########################################## +### AMM Arbitrage strategy config ### +########################################## + +template_version: 6 +strategy: null + +# The following configurations are only required for the AMM arbitrage trading strategy + +# Connectors and markets parameters +connector_1: null +market_1: null +connector_2: null +market_2: null + +order_amount: null + +# Minimum profitability target required to place an order +# Expressed in percentage value, e.g. 1 = 1% target profit +min_profitability: null + +# A buffer for which to adjust order price for higher chance of the order getting filled. +# This is important for AMM which transaction takes a long time where a slippage is acceptable rather having +# the transaction get rejected. The submitted order price will be adjust higher (by percentage value) for buy order +# and lower for sell order. (Enter 1 for 1%) +market_1_slippage_buffer: null + +# A buffer to add to the price to account for slippage when buying/selling on second connector market +# (Enter 1 for 1%) +market_2_slippage_buffer: null + +# A flag (true/false), if true the bot submits both arbitrage taker orders (buy and sell) simultaneously +# If false, the bot will wait for first exchange order filled before submitting the other order +concurrent_orders_submission: null + +# A flag (true/false), if true would enable a price shim feature on the AMM connector to allow developer to simulate +# different prices on the AMM market. This is used for development and testing purpose only. +debug_price_shim: false + +# After how many seconds should blockchain transactions be cancelled if they are not included in a block? +gateway_transaction_cancel_interval: 600 + +# What rate source should be used for quote assets pair - between fixed_rate_source and rate_oracle_source? +rate_oracle_enabled: true + +# What is the fixed_rate used to convert quote assets? +quote_conversion_rate: 1 \ No newline at end of file diff --git a/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml b/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml new file mode 100644 index 0000000..3c55399 --- /dev/null +++ b/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml @@ -0,0 +1,146 @@ +######################################## +### Fee overrides configurations ### +######################################## + +# For more detailed information: https://docs.hummingbot.io +template_version: 14 + +# Example of the fields that can be specified to override the `TradeFeeFactory` default settings. +# If the field is missing or the value is left blank, the default value will be used. +# The percentage values are specified as 0.1 for 0.1%. +# +# [exchange name]_percent_fee_token: +# [exchange name]_maker_percent_fee: +# [exchange name]_taker_percent_fee: +# [exchange name]_buy_percent_fee_deducted_from_returns: # if False, the buy fee is added to the order costs +# [exchange name]_maker_fixed_fees: # a list of lists of token-fee pairs (e.g. [["ETH", 1]]) +# [exchange name]_taker_fixed_fees: # a list of lists of token-fee pairs (e.g. [["ETH", 1]]) + +binance_percent_fee_token: # BNB +binance_maker_percent_fee: # 0.75 +binance_taker_percent_fee: # 0.75 +binance_buy_percent_fee_deducted_from_returns: # True + +# List of supported Exchanges for which the user's conf/conf_fee_override.yml +# will work. This file currently needs to be in sync with hummingbot list of +# supported exchanges +ascend_ex_buy_percent_fee_deducted_from_returns: +ascend_ex_maker_fixed_fees: +ascend_ex_maker_percent_fee: +ascend_ex_percent_fee_token: +ascend_ex_taker_fixed_fees: +ascend_ex_taker_percent_fee: +binance_maker_fixed_fees: +binance_perpetual_buy_percent_fee_deducted_from_returns: +binance_perpetual_maker_fixed_fees: +binance_perpetual_maker_percent_fee: +binance_perpetual_percent_fee_token: +binance_perpetual_taker_fixed_fees: +binance_perpetual_taker_percent_fee: +binance_perpetual_testnet_buy_percent_fee_deducted_from_returns: +binance_perpetual_testnet_maker_fixed_fees: +binance_perpetual_testnet_maker_percent_fee: +binance_perpetual_testnet_percent_fee_token: +binance_perpetual_testnet_taker_fixed_fees: +binance_perpetual_testnet_taker_percent_fee: +binance_taker_fixed_fees: +binance_us_buy_percent_fee_deducted_from_returns: +binance_us_maker_fixed_fees: +binance_us_maker_percent_fee: +binance_us_percent_fee_token: +binance_us_taker_fixed_fees: +binance_us_taker_percent_fee: +bitfinex_buy_percent_fee_deducted_from_returns: +bitfinex_maker_fixed_fees: +bitfinex_maker_percent_fee: +bitfinex_percent_fee_token: +bitfinex_taker_fixed_fees: +bitfinex_taker_percent_fee: +bitmart_buy_percent_fee_deducted_from_returns: +bitmart_maker_fixed_fees: +bitmart_maker_percent_fee: +bitmart_percent_fee_token: +bitmart_taker_fixed_fees: +bitmart_taker_percent_fee: +btc_markets_percent_fee_token: +btc_markets_maker_percent_fee: +btc_markets_taker_percent_fee: +btc_markets_buy_percent_fee_deducted_from_returns: +bybit_perpetual_buy_percent_fee_deducted_from_returns: +bybit_perpetual_maker_fixed_fees: +bybit_perpetual_maker_percent_fee: +bybit_perpetual_percent_fee_token: +bybit_perpetual_taker_fixed_fees: +bybit_perpetual_taker_percent_fee: +bybit_perpetual_testnet_buy_percent_fee_deducted_from_returns: +bybit_perpetual_testnet_maker_fixed_fees: +bybit_perpetual_testnet_maker_percent_fee: +bybit_perpetual_testnet_percent_fee_token: +bybit_perpetual_testnet_taker_fixed_fees: +bybit_perpetual_testnet_taker_percent_fee: +coinbase_pro_buy_percent_fee_deducted_from_returns: +coinbase_pro_maker_fixed_fees: +coinbase_pro_maker_percent_fee: +coinbase_pro_percent_fee_token: +coinbase_pro_taker_fixed_fees: +coinbase_pro_taker_percent_fee: +dydx_perpetual_buy_percent_fee_deducted_from_returns: +dydx_perpetual_maker_fixed_fees: +dydx_perpetual_maker_percent_fee: +dydx_perpetual_percent_fee_token: +dydx_perpetual_taker_fixed_fees: +dydx_perpetual_taker_percent_fee: +gate_io_buy_percent_fee_deducted_from_returns: +gate_io_maker_fixed_fees: +gate_io_maker_percent_fee: +gate_io_percent_fee_token: +gate_io_taker_fixed_fees: +gate_io_taker_percent_fee: +hitbtc_buy_percent_fee_deducted_from_returns: +hitbtc_maker_fixed_fees: +hitbtc_maker_percent_fee: +hitbtc_percent_fee_token: +hitbtc_taker_fixed_fees: +hitbtc_taker_percent_fee: +huobi_buy_percent_fee_deducted_from_returns: +huobi_maker_fixed_fees: +huobi_maker_percent_fee: +huobi_percent_fee_token: +huobi_taker_fixed_fees: +huobi_taker_percent_fee: +kraken_buy_percent_fee_deducted_from_returns: +kraken_maker_fixed_fees: +kraken_maker_percent_fee: +kraken_percent_fee_token: +kraken_taker_fixed_fees: +kraken_taker_percent_fee: +kucoin_buy_percent_fee_deducted_from_returns: +kucoin_maker_fixed_fees: +kucoin_maker_percent_fee: +kucoin_percent_fee_token: +kucoin_taker_fixed_fees: +kucoin_taker_percent_fee: +mexc_buy_percent_fee_deducted_from_returns: +mexc_maker_fixed_fees: +mexc_maker_percent_fee: +mexc_percent_fee_token: +mexc_taker_fixed_fees: +mexc_taker_percent_fee: +ndax_buy_percent_fee_deducted_from_returns: +ndax_maker_fixed_fees: +ndax_maker_percent_fee: +ndax_percent_fee_token: +ndax_taker_fixed_fees: +ndax_taker_percent_fee: +ndax_testnet_buy_percent_fee_deducted_from_returns: +ndax_testnet_maker_fixed_fees: +ndax_testnet_maker_percent_fee: +ndax_testnet_percent_fee_token: +ndax_testnet_taker_fixed_fees: +ndax_testnet_taker_percent_fee: +okx_buy_percent_fee_deducted_from_returns: +okx_maker_fixed_fees: +okx_maker_percent_fee: +okx_percent_fee_token: +okx_taker_fixed_fees: +okx_taker_percent_fee: diff --git a/hummingbot/templates/conf_limit_order_strategy_TEMPLATE.yml b/hummingbot/templates/conf_limit_order_strategy_TEMPLATE.yml new file mode 100644 index 0000000..863ab34 --- /dev/null +++ b/hummingbot/templates/conf_limit_order_strategy_TEMPLATE.yml @@ -0,0 +1,4 @@ +template_version: 1 +strategy: null +connector: null +market: null \ No newline at end of file diff --git a/hummingbot/templates/conf_liquidity_mining_strategy_TEMPLATE.yml b/hummingbot/templates/conf_liquidity_mining_strategy_TEMPLATE.yml new file mode 100644 index 0000000..6b8e5c7 --- /dev/null +++ b/hummingbot/templates/conf_liquidity_mining_strategy_TEMPLATE.yml @@ -0,0 +1,56 @@ +######################################################## +### Liquidity Mining strategy config ### +######################################################## + +template_version: 3 +strategy: null + +# The exchange to run this strategy. +exchange: null + +# The list of markets, comma separated, e.g. LTC-USDT,ETH-USDT +# Note: The actual markets will be determined by token below, only markets with the token specified below. +markets: null + +# The asset (base or quote) to use to provide liquidity +token: null + +# The size of each order in specified token amount +order_amount: null + +# The spread from mid price to place bid and ask orders, enter 1 to indicate 1% +spread: null + +# Whether to enable Inventory skew feature (true/false). +inventory_skew_enabled: null + +# The target base asset percentage for all markets, enter 50 to indicate 50% target +target_base_pct: null + +# Time in seconds before cancelling and placing new orders. +# If the value is 60, the bot cancels active orders and placing new ones after a minute. +order_refresh_time: null + +# The spread (from mid price) to defer order refresh process to the next cycle. +# (Enter 1 to indicate 1%), value below 0, e.g. -1, is to disable this feature - not recommended. +order_refresh_tolerance_pct: null + +# The range around the inventory target base percent to maintain, expressed in multiples of total order size (for +# inventory skew feature). +inventory_range_multiplier: null + +# The interval, in second, in which to pick historical mid price data from to calculate market volatility +# E.g 300 for 5 minutes interval +volatility_interval: null + +# The number of interval to calculate average market volatility. +avg_volatility_period: null + +# The multiplier used to convert average volatility to spread, enter 1 for 1 to 1 conversion +volatility_to_spread_multiplier: null + +# The maximum value for spread, enter 1 to indicate 1% or -1 to ignore this setting +max_spread: null + +# The maximum life time of your orders in seconds +max_order_age: null diff --git a/hummingbot/templates/conf_perpetual_market_making_strategy_TEMPLATE.yml b/hummingbot/templates/conf_perpetual_market_making_strategy_TEMPLATE.yml new file mode 100644 index 0000000..3ae11ba --- /dev/null +++ b/hummingbot/templates/conf_perpetual_market_making_strategy_TEMPLATE.yml @@ -0,0 +1,118 @@ +######################################################## +### Perpetual market making strategy config ### +######################################################## + +template_version: 6 +strategy: null + +# derivative and token parameters. +derivative: null + +# Token trading pair for the exchange, e.g. BTC-USDT +market: null + +# What leverage to used +leverage: null + +# Position mode to use, hedge mode or one-way position mode. +position_mode: null + +# How far away from mid price to place the bid order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and bid_spread is 1. +# Your bid is placed at 99. +bid_spread: null + +# How far away from mid price to place the ask order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and ask_spread is 1. +# Your bid is placed at 101. +ask_spread: null + +# Minimum Spread +# How far away from the mid price to cancel active orders +minimum_spread: null + +# Time in seconds before cancelling and placing new orders. +# If the value is 60, the bot cancels active orders and placing new ones after a minute. +order_refresh_time: null + +# The spread (from mid price) to defer order refresh process to the next cycle. +# (Enter 1 to indicate 1%), value below 0, e.g. -1, is to disable this feature - not recommended. +order_refresh_tolerance_pct: null + +# Size of your bid and ask order. +order_amount: null + +# long position take profit spread +long_profit_taking_spread: null + +# short position take profit spread +short_profit_taking_spread: null + +# Spread from position entry price to place a stop-loss order to close position +stop_loss_spread: null + +# Time to wait before refreshing a stop loss order that has not been executed +time_between_stop_loss_orders: null + +# Spread to include in stop loss orders covering possible price slippages in the market +stop_loss_slippage_buffer: null + +# Price band ceiling. +price_ceiling: null + +# Price band floor. +price_floor: null + +# Number of levels of orders to place on each side of the order book. +order_levels: null + +# Increase or decrease size of consecutive orders after the first order (if order_levels > 1). +order_level_amount: null + +# Order price space between orders (if order_levels > 1). +order_level_spread: null + +# How long to wait before placing the next order in case your order gets filled. +filled_order_delay: null + +# Whether to enable order optimization mode (true/false). +order_optimization_enabled: null + +# The depth in base asset amount to be used for finding top ask (for order optimization mode). +ask_order_optimization_depth: null + +# The depth in base asset amount to be used for finding top bid (for order optimization mode). +bid_order_optimization_depth: null + +# The price source (current_market/external_market/custom_api). +price_source: null + +# The price type (mid_price/last_price/last_own_trade_price/best_bid/best_ask). +price_type: null + +# An external exchange name (for external exchange pricing source). +price_source_derivative: null + +# A trading pair for the external exchange, e.g. BTC-USDT (for external exchange pricing source). +price_source_market: null + +# An external api that returns price (for custom_api pricing source). +price_source_custom_api: null + +# An interval time in second to update the price from custom api (for custom_api pricing source). +custom_api_update_interval: null + +# Use user provided orders to directly override the orders placed by order_amount and order_level_parameter +# This is an advanced feature and user is expected to directly edit this field in config file +# Below is an sample input, the format is a dictionary, the key is user-defined order name, the value is a list which includes buy/sell, order spread, and order amount +# order_override: +# order_1: [buy, 0.5, 100] +# order_2: [buy, 0.75, 200] +# order_3: [sell, 0.1, 500] +# Please make sure there is a space between : and [ +order_override: null + +# For more detailed information, see: +# https://docs.hummingbot.io/strategies/pure-market-making/#configuration-parameters diff --git a/hummingbot/templates/conf_pure_market_making_strategy_TEMPLATE.yml b/hummingbot/templates/conf_pure_market_making_strategy_TEMPLATE.yml new file mode 100644 index 0000000..ecec270 --- /dev/null +++ b/hummingbot/templates/conf_pure_market_making_strategy_TEMPLATE.yml @@ -0,0 +1,147 @@ +######################################################## +### Pure market making strategy config ### +######################################################## + +template_version: 24 +strategy: null + +# Exchange and token parameters. +exchange: null + +# Token trading pair for the exchange, e.g. BTC-USDT +market: null + +# How far away from mid price to place the bid order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and bid_spread is 1. +# Your bid is placed at 99. +bid_spread: null + +# How far away from mid price to place the ask order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and ask_spread is 1. +# Your bid is placed at 101. +ask_spread: null + +# Minimum Spread +# How far away from the mid price to cancel active orders +minimum_spread: null + +# Time in seconds before cancelling and placing new orders. +# If the value is 60, the bot cancels active orders and placing new ones after a minute. +order_refresh_time: null + +# Time in seconds before replacing existing order with new orders at the same price. +max_order_age: null + +# The spread (from mid price) to defer order refresh process to the next cycle. +# (Enter 1 to indicate 1%), value below 0, e.g. -1, is to disable this feature - not recommended. +order_refresh_tolerance_pct: null + +# Size of your bid and ask order. +order_amount: null + +# Price band ceiling. +price_ceiling: null + +# Price band floor. +price_floor: null + +# enable moving price floor and ceiling. +moving_price_band_enabled: null + +# Price band ceiling pct. +price_ceiling_pct: null + +# Price band floor pct. +price_floor_pct: null + +# price_band_refresh_time. +price_band_refresh_time: null + +# Whether to alternate between buys and sells (true/false). +ping_pong_enabled: null + +# Whether to enable Inventory skew feature (true/false). +inventory_skew_enabled: null + +# Target base asset inventory percentage target to be maintained (for Inventory skew feature). +inventory_target_base_pct: null + +# The range around the inventory target base percent to maintain, expressed in multiples of total order size (for +# inventory skew feature). +inventory_range_multiplier: null + +# Initial price of the base asset. Note: this setting is not affects anything, the price is kept in the database. +inventory_price: null + +# Number of levels of orders to place on each side of the order book. +order_levels: null + +# Increase or decrease size of consecutive orders after the first order (if order_levels > 1). +order_level_amount: null + +# Order price space between orders (if order_levels > 1). +order_level_spread: null + +# How long to wait before placing the next order in case your order gets filled. +filled_order_delay: null + +# Whether to stop cancellations of orders on the other side (of the order book), +# when one side is filled (hanging orders feature) (true/false). +hanging_orders_enabled: null + +# Spread (from mid price, in percentage) hanging orders will be canceled (Enter 1 to indicate 1%) +hanging_orders_cancel_pct: null + +# Whether to enable order optimization mode (true/false). +order_optimization_enabled: null + +# The depth in base asset amount to be used for finding top ask (for order optimization mode). +ask_order_optimization_depth: null + +# The depth in base asset amount to be used for finding top bid (for order optimization mode). +bid_order_optimization_depth: null + +# Whether to enable adding transaction costs to order price calculation (true/false). +add_transaction_costs: null + +# The price source (current_market/external_market/custom_api). +price_source: null + +# The price type (mid_price/last_price/last_own_trade_price/best_bid/best_ask/inventory_cost). +price_type: null + +# An external exchange name (for external exchange pricing source). +price_source_exchange: null + +# A trading pair for the external exchange, e.g. BTC-USDT (for external exchange pricing source). +price_source_market: null + +# An external api that returns price (for custom_api pricing source). +price_source_custom_api: null + +# An interval time in second to update the price from custom api (for custom_api pricing source). +custom_api_update_interval: null + +#Take order if they cross order book when external price source is enabled +take_if_crossed: null + +# Use user provided orders to directly override the orders placed by order_amount and order_level_parameter +# This is an advanced feature and user is expected to directly edit this field in config file +# Below is an sample input, the format is a dictionary, the key is user-defined order name, the value is a list which includes buy/sell, order spread, and order amount +# order_override: +# order_1: [buy, 0.5, 100] +# order_2: [buy, 0.75, 200] +# order_3: [sell, 0.1, 500] +# Please make sure there is a space between : and [ +order_override: null + +# Simpler override config for separate bid and order level spreads +split_order_levels_enabled: null +bid_order_level_spreads: null +ask_order_level_spreads: null +bid_order_level_amounts: null +ask_order_level_amounts: null +# If the strategy should wait to receive cancellations confirmation before creating new orders during refresh time +should_wait_order_cancel_confirmation: True diff --git a/hummingbot/templates/conf_pure_volume_strategy_TEMPLATE.yml b/hummingbot/templates/conf_pure_volume_strategy_TEMPLATE.yml new file mode 100644 index 0000000..2d991c0 --- /dev/null +++ b/hummingbot/templates/conf_pure_volume_strategy_TEMPLATE.yml @@ -0,0 +1,147 @@ +######################################################## +### Pure volume strategy config ### +######################################################## + +template_version: 1 +strategy: null + +# Exchange and token parameters. +exchange: null + +# Token trading pair for the exchange, e.g. BTC-USDT +market: null + +# How far away from mid price to place the bid order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and bid_spread is 1. +# Your bid is placed at 99. +bid_spread: null + +# How far away from mid price to place the ask order. +# Spread of 1 = 1% away from mid price at that time. +# Example if mid price is 100 and ask_spread is 1. +# Your bid is placed at 101. +#ask_spread: null + +# Minimum Spread +# How far away from the mid price to cancel active orders +minimum_spread: null + +# Time in seconds before cancelling and placing new orders. +# If the value is 60, the bot cancels active orders and placing new ones after a minute. +order_refresh_time: null + +# Time in seconds before replacing existing order with new orders at the same price. +max_order_age: null + +# The spread (from mid price) to defer order refresh process to the next cycle. +# (Enter 1 to indicate 1%), value below 0, e.g. -1, is to disable this feature - not recommended. +order_refresh_tolerance_pct: null + +# Size of your bid and ask order. +order_amount: null + +# Price band ceiling. +price_ceiling: null + +# Price band floor. +price_floor: null + +# enable moving price floor and ceiling. +moving_price_band_enabled: null + +# Price band ceiling pct. +price_ceiling_pct: null + +# Price band floor pct. +price_floor_pct: null + +# price_band_refresh_time. +price_band_refresh_time: null + +# Whether to alternate between buys and sells (true/false). +ping_pong_enabled: null + +# Whether to enable Inventory skew feature (true/false). +inventory_skew_enabled: null + +# Target base asset inventory percentage target to be maintained (for Inventory skew feature). +inventory_target_base_pct: null + +# The range around the inventory target base percent to maintain, expressed in multiples of total order size (for +# inventory skew feature). +inventory_range_multiplier: null + +# Initial price of the base asset. Note: this setting is not affects anything, the price is kept in the database. +inventory_price: null + +# Number of levels of orders to place on each side of the order book. +order_levels: null + +# Increase or decrease size of consecutive orders after the first order (if order_levels > 1). +order_level_amount: null + +# Order price space between orders (if order_levels > 1). +order_level_spread: null + +# How long to wait before placing the next order in case your order gets filled. +filled_order_delay: null + +# Whether to stop cancellations of orders on the other side (of the order book), +# when one side is filled (hanging orders feature) (true/false). +hanging_orders_enabled: null + +# Spread (from mid price, in percentage) hanging orders will be canceled (Enter 1 to indicate 1%) +hanging_orders_cancel_pct: null + +# Whether to enable order optimization mode (true/false). +order_optimization_enabled: null + +# The depth in base asset amount to be used for finding top ask (for order optimization mode). +ask_order_optimization_depth: null + +# The depth in base asset amount to be used for finding top bid (for order optimization mode). +bid_order_optimization_depth: null + +# Whether to enable adding transaction costs to order price calculation (true/false). +add_transaction_costs: null + +# The price source (current_market/external_market/custom_api). +price_source: null + +# The price type (mid_price/last_price/last_own_trade_price/best_bid/best_ask/inventory_cost). +price_type: null + +# An external exchange name (for external exchange pricing source). +price_source_exchange: null + +# A trading pair for the external exchange, e.g. BTC-USDT (for external exchange pricing source). +price_source_market: null + +# An external api that returns price (for custom_api pricing source). +price_source_custom_api: null + +# An interval time in second to update the price from custom api (for custom_api pricing source). +custom_api_update_interval: null + +#Take order if they cross order book when external price source is enabled +take_if_crossed: null + +# Use user provided orders to directly override the orders placed by order_amount and order_level_parameter +# This is an advanced feature and user is expected to directly edit this field in config file +# Below is an sample input, the format is a dictionary, the key is user-defined order name, the value is a list which includes buy/sell, order spread, and order amount +# order_override: +# order_1: [buy, 0.5, 100] +# order_2: [buy, 0.75, 200] +# order_3: [sell, 0.1, 500] +# Please make sure there is a space between : and [ +order_override: null + +# Simpler override config for separate bid and order level spreads +split_order_levels_enabled: null +bid_order_level_spreads: null +#ask_order_level_spreads: null +bid_order_level_amounts: null +#ask_order_level_amounts: null +# If the strategy should wait to receive cancellations confirmation before creating new orders during refresh time +should_wait_order_cancel_confirmation: True diff --git a/hummingbot/templates/conf_spot_perpetual_arbitrage_strategy_TEMPLATE.yml b/hummingbot/templates/conf_spot_perpetual_arbitrage_strategy_TEMPLATE.yml new file mode 100644 index 0000000..7e90363 --- /dev/null +++ b/hummingbot/templates/conf_spot_perpetual_arbitrage_strategy_TEMPLATE.yml @@ -0,0 +1,39 @@ +########################################## +### Spot-Perpetual Arbitrage strategy config ### +########################################## + +template_version: 3 +strategy: null + +# The following configurations are only required for the AMM arbitrage trading strategy + +# Connectors and markets parameters +spot_connector: null +spot_market: null +perpetual_connector: null +perpetual_market: null + +# Order amount to submit orders on both spot and perpetual markets +order_amount: null + +perpetual_leverage: null + +# Minimum profit percentage required before opening an arbitrage position, expressed in percentage value, e.g. 1 = 1% +# The profitability is calculated from the gap between sell and buy price, it does not account for fees and slippages +min_opening_arbitrage_pct: null + +# Minimum profit percentage required before closing the arbitrage position, expressed in percentage value, e.g. 1 = 1% +min_closing_arbitrage_pct: null + +# A buffer for which to adjust order price for higher chance of the order getting filled. +# This is important for AMM which transaction takes a long time where a slippage is acceptable rather having +# the transaction get rejected. The submitted order price will be adjust higher (by percentage value) for buy order +# and lower for sell order. (Enter 1 for 1%) +spot_market_slippage_buffer: null + +# A buffer to add to the price to account for slippage when buying/selling on second connector market +# (Enter 1 for 1%) +perpetual_market_slippage_buffer: null + +# cool off period between arbitrage cycles +next_arbitrage_opening_delay: null diff --git a/hummingbot/templates/conf_twap_strategy_TEMPLATE.yml b/hummingbot/templates/conf_twap_strategy_TEMPLATE.yml new file mode 100644 index 0000000..7599f29 --- /dev/null +++ b/hummingbot/templates/conf_twap_strategy_TEMPLATE.yml @@ -0,0 +1,47 @@ +######################################################## +### Execution4 strategy config ### +######################################################## + +template_version: 6 +strategy: null +# The following configurations are only required for the +# twap strategy + +# Connector and token parameters +connector: null +trading_pair: null + +# Total amount to be traded, considering all orders +target_asset_amount: null + +# Size of the Order +order_step_size: null + +# Price of Order (in case of Limit Order) +order_price: null + +# Time in seconds before cancelling the limit order +# If cancel_order wait time is 60 and the order is still open after 60 seconds since placing the order, +# it will cancel the limit order. +cancel_order_wait_time: null + +# Specify buy/sell. +trade_side: "buy" + +# Specifies if the strategy should run during a fixed time span +is_time_span_execution: False + +# Specifies if the strategy should run during a with a delayed start +is_delayed_start_execution: False + +# Date and time the strategy should start running +# Only valid if is_time_span_execution is True +start_datetime: null + +# Date and time the strategy should stop running +# Only valid if is_time_span_execution is True +end_datetime: null + +# How long to between placing incremental orders +# Only valid if is_time_span_execution is False +order_delay_time: null diff --git a/hummingbot/templates/conf_uniswap_v3_lp_strategy_TEMPLATE.yml b/hummingbot/templates/conf_uniswap_v3_lp_strategy_TEMPLATE.yml new file mode 100644 index 0000000..ece047f --- /dev/null +++ b/hummingbot/templates/conf_uniswap_v3_lp_strategy_TEMPLATE.yml @@ -0,0 +1,24 @@ +######################################### +### Uniswap v3 LP strategy config ### +######################################### + +template_version: 3 +strategy: null + +#The name of connector to use +connector: null + +# The pair you provide liquidity to +market: null + +# The pool you provide liquidity to +fee_tier: null + +# The spread between lower price to the upper price +price_spread: null + +# The amount of token (liquidity) provided to the pool +amount: null + +# The minimum profit required before positions can be adjusted +min_profitability: null diff --git a/hummingbot/templates/hummingbot_logs_TEMPLATE.yml b/hummingbot/templates/hummingbot_logs_TEMPLATE.yml new file mode 100755 index 0000000..8e65271 --- /dev/null +++ b/hummingbot/templates/hummingbot_logs_TEMPLATE.yml @@ -0,0 +1,83 @@ +--- +version: 1 +template_version: 12 + +formatters: + simple: + format: "%(asctime)s - %(process)d - %(name)s - %(levelname)s - %(message)s" + +handlers: + console: + class: hummingbot.logger.cli_handler.CLIHandler + level: DEBUG + formatter: simple + stream: ext://sys.stdout + console_warning: + class: hummingbot.logger.cli_handler.CLIHandler + level: WARNING + formatter: simple + stream: ext://sys.stdout + console_info: + class: hummingbot.logger.cli_handler.CLIHandler + level: INFO + formatter: simple + stream: ext://sys.stdout + file_handler: + class: logging.handlers.TimedRotatingFileHandler + level: DEBUG + formatter: simple + filename: $PROJECT_DIR/logs/logs_$STRATEGY_FILE_PATH.log + encoding: utf8 + when: "D" + interval: 1 + backupCount: 7 + "null": + class: logging.NullHandler + level: DEBUG + +loggers: + hummingbot.core.utils.eth_gas_station_lookup: + level: NETWORK + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.logger.log_server_client: + level: WARNING + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.logger.reporting_proxy_handler: + level: WARNING + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.strategy: + level: NETWORK + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.connector: + level: NETWORK + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.client: + level: NETWORK + propagate: false + handlers: [console, file_handler] + mqtt: true + hummingbot.core.event.event_reporter: + level: EVENT_LOG + propagate: false + handlers: [file_handler] + mqtt: false + conf: + level: NETWORK + handlers: ["null"] + propagate: false + mqtt: false + +root: + level: INFO + handlers: [console, file_handler] + mqtt: true diff --git a/hummingbot/user/__init__.py b/hummingbot/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hummingbot/user/user_balances.py b/hummingbot/user/user_balances.py new file mode 100644 index 0000000..7069cbd --- /dev/null +++ b/hummingbot/user/user_balances.py @@ -0,0 +1,174 @@ +import logging +from decimal import Decimal +from functools import lru_cache +from typing import Dict, List, Optional, Set + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ReadOnlyClientConfigAdapter, get_connector_class +from hummingbot.client.config.security import Security +from hummingbot.client.settings import AllConnectorSettings, GatewayConnectionSetting, gateway_connector_trading_pairs +from hummingbot.core.utils.async_utils import safe_gather +from hummingbot.core.utils.gateway_config_utils import flatten +from hummingbot.core.utils.market_price import get_last_price + + +class UserBalances: + __instance = None + + @staticmethod + def connect_market(exchange, client_config_map: ClientConfigMap, **api_details): + connector = None + conn_setting = AllConnectorSettings.get_connector_settings()[exchange] + if api_details or conn_setting.uses_gateway_generic_connector(): + connector_class = get_connector_class(exchange) + read_only_client_config = ReadOnlyClientConfigAdapter.lock_config(client_config_map) + init_params = conn_setting.conn_init_parameters( + trading_pairs=gateway_connector_trading_pairs(conn_setting.name), + api_keys=api_details, + client_config_map=read_only_client_config, + ) + + # collect trading pairs from the gateway connector settings + trading_pairs: List[str] = gateway_connector_trading_pairs(conn_setting.name) + + # collect unique trading pairs that are for balance reporting only + if conn_setting.uses_gateway_generic_connector(): + config: Optional[Dict[str, str]] = GatewayConnectionSetting.get_connector_spec_from_market_name(conn_setting.name) + if config is not None: + existing_pairs = set(flatten([x.split("-") for x in trading_pairs])) + + other_tokens: Set[str] = set(config.get("tokens", "").split(",")) + other_tokens.discard("") + tokens: List[str] = [t for t in other_tokens if t not in existing_pairs] + if tokens != [""]: + trading_pairs.append("-".join(tokens)) + + connector = connector_class(**init_params) + return connector + + # return error message if the _update_balances fails + @staticmethod + async def _update_balances(market) -> Optional[str]: + try: + await market._update_balances() + except Exception as e: + logging.getLogger().debug(f"Failed to update balances for {market}", exc_info=True) + return str(e) + return None + + @staticmethod + def instance(): + if UserBalances.__instance is None: + UserBalances() + return UserBalances.__instance + + @staticmethod + @lru_cache(maxsize=10) + def is_gateway_market(exchange_name: str) -> bool: + return ( + exchange_name in sorted( + AllConnectorSettings.get_gateway_amm_connector_names().union( + AllConnectorSettings.get_gateway_evm_amm_lp_connector_names() + ).union( + AllConnectorSettings.get_gateway_clob_connector_names() + ) + ) + ) + + def __init__(self): + if UserBalances.__instance is not None: + raise Exception("This class is a singleton!") + else: + UserBalances.__instance = self + self._markets = {} + + async def add_exchange(self, exchange, client_config_map: ClientConfigMap, **api_details) -> Optional[str]: + self._markets.pop(exchange, None) + is_gateway_market = self.is_gateway_market(exchange) + if not is_gateway_market: + market = UserBalances.connect_market(exchange, client_config_map, **api_details) + if not market: + return "API keys have not been added." + err_msg = await UserBalances._update_balances(market) + if err_msg is None: + self._markets[exchange] = market + return err_msg + + def all_balances(self, exchange) -> Dict[str, Decimal]: + if exchange not in self._markets: + return {} + return self._markets[exchange].get_all_balances() + + async def update_exchange_balance(self, exchange_name: str, client_config_map: ClientConfigMap) -> Optional[str]: + is_gateway_market = self.is_gateway_market(exchange_name) + if is_gateway_market and exchange_name in self._markets: + # we want to refresh gateway connectors always, since the applicable tokens change over time. + # doing this will reinitialize and fetch balances for active trading pair + del self._markets[exchange_name] + if exchange_name in self._markets: + return await self._update_balances(self._markets[exchange_name]) + else: + await Security.wait_til_decryption_done() + api_keys = Security.api_keys(exchange_name) if not is_gateway_market else {} + return await self.add_exchange(exchange_name, client_config_map, **api_keys) + + # returns error message for each exchange + async def update_exchanges( + self, + client_config_map: ClientConfigMap, + reconnect: bool = False, + exchanges: Optional[List[str]] = None + ) -> Dict[str, Optional[str]]: + exchanges = exchanges or [] + tasks = [] + # Update user balances + if len(exchanges) == 0: + exchanges = [cs.name for cs in AllConnectorSettings.get_connector_settings().values()] + exchanges: List[str] = [ + cs.name + for cs in AllConnectorSettings.get_connector_settings().values() + if not cs.use_ethereum_wallet + and cs.name in exchanges + and not cs.name.endswith("paper_trade") + ] + + if reconnect: + self._markets.clear() + for exchange in exchanges: + tasks.append(self.update_exchange_balance(exchange, client_config_map)) + results = await safe_gather(*tasks) + return {ex: err_msg for ex, err_msg in zip(exchanges, results)} + + # returns only for non-gateway connectors since balance command no longer reports gateway connector balances + async def all_balances_all_exchanges(self, client_config_map: ClientConfigMap) -> Dict[str, Dict[str, Decimal]]: + await self.update_exchanges(client_config_map) + return {k: v.get_all_balances() for k, v in sorted(self._markets.items(), key=lambda x: x[0]) if not self.is_gateway_market(k)} + + # returns only for non-gateway connectors since balance command no longer reports gateway connector balances + def all_available_balances_all_exchanges(self) -> Dict[str, Dict[str, Decimal]]: + return {k: v.available_balances for k, v in sorted(self._markets.items(), key=lambda x: x[0]) if not self.is_gateway_market(k)} + + async def balances(self, exchange, client_config_map: ClientConfigMap, *symbols) -> Dict[str, Decimal]: + if await self.update_exchange_balance(exchange, client_config_map) is None: + results = {} + for token, bal in self.all_balances(exchange).items(): + matches = [s for s in symbols if s.lower() == token.lower()] + if matches: + results[matches[0]] = bal + return results + + @staticmethod + def validate_ethereum_wallet() -> Optional[str]: + return "Connector deprecated." + + @staticmethod + async def base_amount_ratio(exchange, trading_pair, balances) -> Optional[Decimal]: + try: + base, quote = trading_pair.split("-") + base_amount = balances.get(base, 0) + quote_amount = balances.get(quote, 0) + price = await get_last_price(exchange, trading_pair) + total_value = base_amount + (quote_amount / price) + return None if total_value <= 0 else base_amount / total_value + except Exception: + return None diff --git a/install b/install new file mode 100755 index 0000000..123662a --- /dev/null +++ b/install @@ -0,0 +1,49 @@ +#!/bin/bash + +cd $(dirname $0) + +# Compatibility logic for older Anaconda versions. +if [ "${CONDA_EXE} " == " " ]; then + CONDA_EXE=$((find /opt/conda/bin/conda || find ~/anaconda3/bin/conda || \ + find /usr/local/anaconda3/bin/conda || find ~/miniconda3/bin/conda || \ + find /root/miniconda/bin/conda || find ~/Anaconda3/Scripts/conda || \ + find $CONDA/bin/conda) 2>/dev/null) +fi + +if [ "${CONDA_EXE}_" == "_" ]; then + echo "Please install Anaconda w/ Python 3.7+ first" + echo "See: https://www.anaconda.com/distribution/" + exit 1 +fi + +CONDA_BIN=$(dirname ${CONDA_EXE}) +ENV_FILE=setup/environment.yml + +if ${CONDA_EXE} env list | egrep -qe "^hummingbot"; then + ${CONDA_EXE} env update -f $ENV_FILE +else + ${CONDA_EXE} env create -f $ENV_FILE +fi + +source "${CONDA_BIN}/activate" hummingbot + +# Add the project directory to module search paths. +conda develop . + +# For some reason, this needs to be installed outside of the environment file, +# or it'll give you the graphviz install error. +pip install objgraph + +pre-commit install + +# The following logic is required to replace the grpcio package installed from conda binaries in Mac Intel +# for binaries from Pypi. We need to do this because the conda binaries fro Mac Intel are broken. +# We can't use the Pypi binaries universally because they are broken for Mac ARM (M1 and M2). +# This logic can be removed once the grpcio conda binaries for Mac Intel are fixed +OS=`uname` +ARCH=`uname -m` + +if [[ "$OS" = "Darwin" && "$ARCH" = "x86_64" ]]; then + pip install grpcio --ignore-installed +fi + diff --git a/installation/README.md b/installation/README.md new file mode 100644 index 0000000..97a2bfa --- /dev/null +++ b/installation/README.md @@ -0,0 +1,13 @@ +# Installation + +Formerly, this folder contains automated scripts for installing Hummingbot via Docker and from source. It has now been deprecated in favor of newer, updated instructions. + +### Via Docker + +Visit the [Deploy Examples](https://github.com/hummingbot/deploy-examples) repo for instructions for deploying Hummingbot in various configurations with Docker. + +### From Source + +For advanced users and developers who would like to access and modify the Hummingbot program files, installation from source is preferred. + +Visit the [Installation](https://docs.hummingbot.io/installation/) section in the Hummingbot docs for platform-specific guides. \ No newline at end of file diff --git a/pmm_scripts/dynamic_price_band_script.py b/pmm_scripts/dynamic_price_band_script.py new file mode 100644 index 0000000..6c26f01 --- /dev/null +++ b/pmm_scripts/dynamic_price_band_script.py @@ -0,0 +1,45 @@ +from decimal import Decimal + +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + +s_decimal_1 = Decimal("1") + + +class DynamicPriceBandPMMScript(PMMScriptBase): + """ + Demonstrates how to set a band around a mid price moving average, the strategy is to stop buying when the mid price + reaches the upper bound of the band and to stop selling when the mid price breaches the lower bound. + """ + + # Let's set the upper bound of the band to 5% away from the mid price moving average + band_upper_bound_pct = Decimal("0.05") + # Let's set the lower bound of the band to 3% away from the mid price moving average + band_lower_bound_pct = Decimal("0.03") + # Let's sample mid prices once every 10 seconds + avg_interval = 10 + # Let's average the last 5 samples + avg_length = 5 + + def __init__(self): + super().__init__() + + def on_tick(self): + avg_mid_price = self.avg_mid_price(self.avg_interval, self.avg_length) + # The avg can be None when the bot just started as there are not enough mid prices to sample values from. + if avg_mid_price is None: + return + upper_bound = avg_mid_price * (s_decimal_1 + self.band_upper_bound_pct) + lower_bound = avg_mid_price * (s_decimal_1 - self.band_lower_bound_pct) + # When mid_price reaches the upper bound, we expect the price to bounce back as such we don't want be a buyer + # (as we can probably buy back at a cheaper price later). + # If you anticipate the opposite, i.e. the price breaks out on a run away move, you can protect your inventory + # by stop selling (setting the sell_levels to 0). + if self.mid_price >= upper_bound: + self.pmm_parameters.buy_levels = 0 + else: + self.pmm_parameters.buy_levels = self.pmm_parameters.order_levels + # When mid_price reaches the lower bound, we don't want to be a seller. + if self.mid_price <= lower_bound: + self.pmm_parameters.sell_levels = 0 + else: + self.pmm_parameters.sell_levels = self.pmm_parameters.order_levels diff --git a/pmm_scripts/hello_world_script.py b/pmm_scripts/hello_world_script.py new file mode 100644 index 0000000..5013324 --- /dev/null +++ b/pmm_scripts/hello_world_script.py @@ -0,0 +1,21 @@ +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + + +class HelloWorldPMMScript(PMMScriptBase): + """ + Demonstrates how to send messages using notify and log functions. It also shows how errors and commands are handled. + """ + + def on_tick(self): + if len(self.mid_prices) < 3: + self.notify("Hello Hummingbots World!") + self.log("Hello world logged.") + elif 3 <= len(self.mid_prices) < 5: + # This below statement will cause ZeroDivisionError, Hummingbot will later report this on the log screen. + _ = 1 / 0 + + def on_command(self, cmd, args): + if cmd == 'ping': + self.notify('pong!') + else: + self.notify(f'Unrecognised command: {cmd}') diff --git a/pmm_scripts/inv_skew_using_spread_script.py b/pmm_scripts/inv_skew_using_spread_script.py new file mode 100644 index 0000000..c98fa78 --- /dev/null +++ b/pmm_scripts/inv_skew_using_spread_script.py @@ -0,0 +1,91 @@ +from decimal import Decimal + +from hummingbot.core.event.events import BuyOrderCompletedEvent, SellOrderCompletedEvent +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + +# Enter the inventory % threshold. When the % of an asset goes below this value, the script will change the spread value +inv_pct_limit = 0.10 +# Enter the spread value to be used if the inventory % threshold is reached +new_spread = Decimal("0.005") + + +class InventorySkewUsingSpread(PMMScriptBase): + + def __init__(self): + super().__init__() + # Declaration of variables used by the script + self.base_asset = None + self.quote_asset = None + self.base_balance = Decimal("0.0000") + self.quote_balance = Decimal("0.0000") + self.original_bid_spread = None + self.original_ask_spread = None + self.base_inv_value = Decimal("0.0000") + self.quote_pct = Decimal("0.0000") + self.base_pct = Decimal("0.0000") + self.ask_skew_active = False + self.bid_skew_active = False + self.total_inv_value = None + + def on_tick(self): + + # Separate and store the assets of the market the bot is working on + if self.base_asset is None or self.quote_asset is None: + self.base_asset, self.quote_asset = self.pmm_market_info.trading_pair.split("-") + + # Check what is the current balance of each asset + self.base_balance = self.all_total_balances[f"{self.pmm_market_info.exchange}"].get(self.base_asset, self.base_balance) + self.quote_balance = self.all_total_balances[f"{self.pmm_market_info.exchange}"].get(self.quote_asset, self.quote_balance) + + # At the script start, the values of the original configuration bid and ask spread is stored for later use + if self.original_bid_spread is None or self.original_ask_spread is None: + self.original_bid_spread = self.pmm_parameters.bid_spread + self.original_ask_spread = self.pmm_parameters.ask_spread + + if self.ask_skew_active is False: + self.original_ask_spread = self.pmm_parameters.ask_spread + + if self.bid_skew_active is False: + self.original_bid_spread = self.pmm_parameters.bid_spread + + # calculate the total % value and it's proportion + self.base_inv_value = self.base_balance * self.mid_price + self.total_inv_value = self.base_inv_value + self.quote_balance + self.base_pct = self.base_inv_value / self.total_inv_value + self.quote_pct = self.quote_balance / self.total_inv_value + + # check if the inventory value % of an asset is below the chosen threshold to define what spread will be used + if self.quote_pct < inv_pct_limit: + self.ask_skew_active = True + self.pmm_parameters.ask_spread = new_spread + # self.log(f"{self.base_asset} inventory % below {inv_pct_limit:.2%}. Changing ask_spread to {new_spread:.2%}") + else: + self.ask_skew_active = False + self.pmm_parameters.ask_spread = self.original_ask_spread + if self.base_pct < inv_pct_limit: + self.bid_skew_active = True + self.pmm_parameters.bid_spread = new_spread + # self.log(f"{self.quote_asset} inventory % below {inv_pct_limit:.2%}. Changing bid_spread to {new_spread:.2%}") + else: + self.bid_skew_active = False + self.pmm_parameters.bid_spread = self.original_bid_spread + + return + + def on_buy_order_completed(self, event: BuyOrderCompletedEvent): + return + + def on_sell_order_completed(self, event: SellOrderCompletedEvent): + return + + def on_status(self) -> str: + # Show the current values when using the `status` command + return f"\n"\ + f"original bid spread = {self.original_bid_spread:.2%} \n" \ + f"original ask spread = {self.original_ask_spread:.2%} \n" \ + f"ask skew active? = {self.ask_skew_active} \n" \ + f"current ask spread = {self.pmm_parameters.ask_spread:.2%} \n" \ + f"bid skew active? = {self.bid_skew_active} \n" \ + f"current bid spread = {self.pmm_parameters.bid_spread:.2%}" + + # --------------- diff --git a/pmm_scripts/ping_pong_script.py b/pmm_scripts/ping_pong_script.py new file mode 100644 index 0000000..79ff9d0 --- /dev/null +++ b/pmm_scripts/ping_pong_script.py @@ -0,0 +1,41 @@ +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + SellOrderCompletedEvent +) +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + + +class PingPongPMMScript(PMMScriptBase): + """ + Demonstrates how to set up a ping pong trading strategy which alternates buy and sell orders. + If a buy order is filled, there will be one less buy order submitted at the next refresh cycle. + If a sell order is filled, there will be one less sell order submitted at the next refresh cycle. + The balance is positive if there are more completed buy orders than sell orders. + """ + + def __init__(self): + super().__init__() + self.ping_pong_balance = 0 + + def on_tick(self): + strategy = self.pmm_parameters + buys = strategy.order_levels + sells = strategy.order_levels + if self.ping_pong_balance > 0: + buys -= self.ping_pong_balance + buys = max(0, buys) + elif self.ping_pong_balance < 0: + sells -= abs(self.ping_pong_balance) + sells = max(0, sells) + strategy.buy_levels = buys + strategy.sell_levels = sells + + def on_buy_order_completed(self, event: BuyOrderCompletedEvent): + self.ping_pong_balance += 1 + + def on_sell_order_completed(self, event: SellOrderCompletedEvent): + self.ping_pong_balance -= 1 + + def on_status(self): + # return the current balance here to be displayed when status command is executed. + return f"ping_pong_balance: {self.ping_pong_balance}" diff --git a/pmm_scripts/price_band_script.py b/pmm_scripts/price_band_script.py new file mode 100644 index 0000000..4257c0a --- /dev/null +++ b/pmm_scripts/price_band_script.py @@ -0,0 +1,29 @@ +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + + +class PriceBandPMMScript(PMMScriptBase): + """ + Demonstrates how to set a fixed band, the strategy is to stop buying when the mid price reaches the upper bound + of the band and to stop selling when the mid price breaches the lower bound. + """ + + band_upper_bound = 105 + band_lower_bound = 95 + + def __init__(self): + super().__init__() + + def on_tick(self): + # When mid_price reaches the upper bound, we expect the price to bounce back as such we don't want be a buyer + # (as we can probably buy back at a cheaper price later). + # If you anticipate the opposite, i.e. the price breaks out on a run away move, you can protect your inventory + # by stop selling (setting the sell_levels to 0). + if self.mid_price >= self.band_upper_bound: + self.pmm_parameters.buy_levels = 0 + else: + self.pmm_parameters.buy_levels = self.pmm_parameters.order_levels + # When mid_price breaches the lower bound, we don't want to be a seller. + if self.mid_price <= self.band_lower_bound: + self.pmm_parameters.sell_levels = 0 + else: + self.pmm_parameters.sell_levels = self.pmm_parameters.order_levels diff --git a/pmm_scripts/spreads_adjusted_on_volatility_script.py b/pmm_scripts/spreads_adjusted_on_volatility_script.py new file mode 100644 index 0000000..43c847d --- /dev/null +++ b/pmm_scripts/spreads_adjusted_on_volatility_script.py @@ -0,0 +1,108 @@ +import time +from datetime import datetime +from decimal import Decimal +from os.path import join, realpath + +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + +s_decimal_1 = Decimal("1") +LOGS_PATH = realpath(join(__file__, "../../logs/")) +SCRIPT_LOG_FILE = f"{LOGS_PATH}/logs_script.log" + + +def log_to_file(file_name, message): + with open(file_name, "a+") as f: + f.write(datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " - " + message + "\n") + + +class SpreadsAdjustedOnVolatility(PMMScriptBase): + """ + Demonstrates how to adjust bid and ask spreads based on price volatility. + The volatility, in this example, is simply a price change compared to the previous cycle regardless of its + direction, e.g. if price changes -3% (or 3%), the volatility is 3%. + To update our pure market making spreads, we're gonna smooth out the volatility by averaging it over a short period + (short_period), and we need a benchmark to compare its value against. In this example the benchmark is a median + long period price volatility (you can also use a fixed number, e.g. 3% - if you expect this to be the norm for your + market). + For example, if our bid_spread and ask_spread are at 0.8%, and the median long term volatility is 1.5%. + Recently the volatility jumps to 2.6% (on short term average), we're gonna adjust both our bid and ask spreads to + 1.9% (the original spread - 0.8% plus the volatility delta - 1.1%). Then after a short while the volatility drops + back to 1.5%, our spreads are now adjusted back to 0.8%. + """ + + # Let's set interval and sample sizes as below. + # These numbers are for testing purposes only (in reality, they should be larger numbers) + # interval is a interim which to pick historical mid price samples from, if you set it to 5, the first sample is + # the last (current) mid price, the second sample is a past mid price 5 seconds before the last, and so on. + interval = 5 + # short_period is how many interval to pick the samples for the average short term volatility calculation, + # for short_period of 3, this is 3 samples (5 seconds interval), of the last 15 seconds + short_period = 3 + # long_period is how many interval to pick the samples for the median long term volatility calculation, + # for long_period of 10, this is 10 samples (5 seconds interval), of the last 50 seconds + long_period = 10 + last_stats_logged = 0 + + def __init__(self): + super().__init__() + self.original_bid_spread = None + self.original_ask_spread = None + self.avg_short_volatility = None + self.median_long_volatility = None + + def volatility_msg(self, include_mid_price=False): + if self.avg_short_volatility is None or self.median_long_volatility is None: + return "short_volatility: N/A long_volatility: N/A" + mid_price_msg = f" mid_price: {self.mid_price:<15}" if include_mid_price else "" + return f"short_volatility: {self.avg_short_volatility:.2%} " \ + f"long_volatility: {self.median_long_volatility:.2%}{mid_price_msg}" + + def on_tick(self): + # First, let's keep the original spreads. + if self.original_bid_spread is None: + self.original_bid_spread = self.pmm_parameters.bid_spread + self.original_ask_spread = self.pmm_parameters.ask_spread + + # Average volatility (price change) over a short period of time, this is to detect recent sudden changes. + self.avg_short_volatility = self.avg_price_volatility(self.interval, self.short_period) + # Median volatility over a long period of time, this is to find the market norm volatility. + # We use median (instead of average) to find the middle volatility value - this is to avoid recent + # spike affecting the average value. + self.median_long_volatility = self.median_price_volatility(self.interval, self.long_period) + + # If the bot just got started, we'll not have these numbers yet as there is not enough mid_price sample size. + # We'll start to have these numbers after interval * long_term_period. + if self.avg_short_volatility is None or self.median_long_volatility is None: + return + + # Let's log some stats once every 5 minutes + if time.time() - self.last_stats_logged > 60 * 5: + log_to_file(SCRIPT_LOG_FILE, self.volatility_msg(True)) + self.last_stats_logged = time.time() + + # This volatility delta will be used to adjust spreads. + delta = self.avg_short_volatility - self.median_long_volatility + # Let's round the delta into 0.25% increment to ignore noise and to avoid adjusting the spreads too often. + spread_adjustment = self.round_by_step(delta, Decimal("0.0025")) + # Show the user on what's going, you can remove this statement to stop the notification. + # self.notify(f"avg_short_volatility: {avg_short_volatility} median_long_volatility: {median_long_volatility} " + # f"spread_adjustment: {spread_adjustment}") + new_bid_spread = self.original_bid_spread + spread_adjustment + # Let's not set the spreads below the originals, this is to avoid having spreads to be too close + # to the mid price. + new_bid_spread = max(self.original_bid_spread, new_bid_spread) + old_bid_spread = self.pmm_parameters.bid_spread + if new_bid_spread != self.pmm_parameters.bid_spread: + self.pmm_parameters.bid_spread = new_bid_spread + + new_ask_spread = self.original_ask_spread + spread_adjustment + new_ask_spread = max(self.original_ask_spread, new_ask_spread) + if new_ask_spread != self.pmm_parameters.ask_spread: + self.pmm_parameters.ask_spread = new_ask_spread + if old_bid_spread != new_bid_spread: + log_to_file(SCRIPT_LOG_FILE, self.volatility_msg(True)) + log_to_file(SCRIPT_LOG_FILE, f"spreads adjustment: Old Value: {old_bid_spread:.2%} " + f"New Value: {new_bid_spread:.2%}") + + def on_status(self) -> str: + return self.volatility_msg() diff --git a/pmm_scripts/update_parameters_test_script.py b/pmm_scripts/update_parameters_test_script.py new file mode 100644 index 0000000..1b0957b --- /dev/null +++ b/pmm_scripts/update_parameters_test_script.py @@ -0,0 +1,34 @@ +from decimal import Decimal +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + + +class UpdateParametersTestPMMScript(PMMScriptBase): + """ + This PMM script is intended for unit testing purpose only. + """ + + def __init__(self): + super().__init__() + self._has_updated = False + + def on_tick(self): + if len(self.mid_prices) >= 5 and not self._has_updated: + self.pmm_parameters.buy_levels = 1 + self.pmm_parameters.sell_levels = 2 + self.pmm_parameters.order_levels = 3 + self.pmm_parameters.bid_spread = Decimal("0.1") + self.pmm_parameters.ask_spread = Decimal("0.2") + self.pmm_parameters.hanging_orders_cancel_pct = Decimal("0.3") + self.pmm_parameters.hanging_orders_enabled = True + self.pmm_parameters.filled_order_delay = 50.0 + self.pmm_parameters.order_refresh_tolerance_pct = Decimal("0.01") + self.pmm_parameters.order_refresh_time = 10.0 + self.pmm_parameters.order_level_amount = Decimal("4") + self.pmm_parameters.order_level_spread = Decimal("0.05") + self.pmm_parameters.order_amount = Decimal("20") + self.pmm_parameters.inventory_skew_enabled = True + self.pmm_parameters.inventory_range_multiplier = Decimal("2") + self.pmm_parameters.inventory_target_base_pct = Decimal("0.6") + self.pmm_parameters.order_override = {"order_1": ["buy", Decimal("0.5"), Decimal("100")], + "order_2": ["sell", Decimal("0.55"), Decimal("101")], } + self._has_updated = True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..90f1343 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[tool.black] +line-length = 120 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' + +[build-system] +requires = ["setuptools", "wheel", "numpy", "cython==3.0.0a10"] + +[tool.isort] +line_length = 120 +multi_line_output = 3 +include_trailing_comma = true +use_parentheses = true +ensure_newline_before_comments = true +combine_as_imports = true +conda_env = "hummingbot" +filter_files = true +skip = ["setup.py"] diff --git a/redist/VC_redist.x64.exe b/redist/VC_redist.x64.exe new file mode 100644 index 0000000..7476d75 Binary files /dev/null and b/redist/VC_redist.x64.exe differ diff --git a/scripts/archived_scripts/community_scripts/1overN_portfolio.py b/scripts/archived_scripts/community_scripts/1overN_portfolio.py new file mode 100644 index 0000000..487be00 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/1overN_portfolio.py @@ -0,0 +1,213 @@ +import decimal +import logging +import math +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase +from hummingbot.strategy.strategy_py_base import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) + + +def create_differences_bar_chart(differences_dict): + diff_str = "Differences to 1/N:\n" + bar_length = 20 + for asset, deficit in differences_dict.items(): + deficit_percentage = deficit * 100 + filled_length = math.ceil(abs(deficit) * bar_length) + + if deficit > 0: + bar = f"{asset:6}: {' ' * bar_length}|{'#' * filled_length:<{bar_length}} +{deficit_percentage:.4f}%" + else: + bar = f"{asset:6}: {'#' * filled_length:>{bar_length}}|{' ' * bar_length} -{-deficit_percentage:.4f}%" + diff_str += bar + "\n" + return diff_str + + +class OneOverNPortfolio(ScriptStrategyBase): + """ + This strategy aims to create a 1/N cryptocurrency portfolio, providing perfect diversification without + parametrization and giving a reasonable baseline performance. + https://www.notion.so/1-N-Index-Portfolio-26752a174c5a4648885b8c344f3f1013 + Future improvements: + - add quote_currency balance as funding so that it can be traded, and it is not stuck when some trades are lost by + the exchange + - create a state machine so that all sells are executed before buy orders are submitted. Thus guaranteeing the + funding + """ + + exchange_name = "binance_paper_trade" + quote_currency = "USDT" + # top 10 coins by market cap, excluding stablecoins + base_currencies = ["BTC", "ETH", "MATIC", "XRP", "BNB", "ADA", "DOT", "LTC", "DOGE", "SOL"] + pairs = {f"{currency}-USDT" for currency in base_currencies} + + #: Define markets to instruct Hummingbot to create connectors on the exchanges and markets you need + markets = {exchange_name: pairs} + activeOrders = 0 + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.total_available_balance = None + self.differences_dict = None + self.quote_balances = None + self.base_balances = None + + def on_tick(self): + #: check current balance of coins + balance_df = self.get_balance_df() + #: Filter by exchange "binance_paper_trade" + exchange_balance_df = balance_df.loc[balance_df["Exchange"] == self.exchange_name] + self.base_balances = self.calculate_base_balances(exchange_balance_df) + self.quote_balances = self.calculate_quote_balances(self.base_balances) + + #: Sum the available balances + self.total_available_balance = sum(balances[1] for balances in self.quote_balances.values()) + self.logger().info(f"TOT ({self.quote_currency}): {self.total_available_balance}") + self.logger().info( + f"TOT/{len(self.base_currencies)} ({self.quote_currency}): {self.total_available_balance / len(self.base_currencies)}") + #: Calculate the percentage of each available_balance over total_available_balance + total_available_balance = self.total_available_balance + percentages_dict = {} + for asset, balances in self.quote_balances.items(): + available_balance = balances[1] + percentage = (available_balance / total_available_balance) + percentages_dict[asset] = percentage + self.logger().info(f"Total share {asset}: {percentage * 100}%") + number_of_assets = Decimal(len(self.quote_balances)) + #: Calculate the difference between each percentage and 1/number_of_assets + differences_dict = self.calculate_deficit_percentages(number_of_assets, percentages_dict) + self.differences_dict = differences_dict + + # Calculate the absolute differences in quote currency + deficit_over_current_price = {} + for asset, deficit in differences_dict.items(): + current_price = self.quote_balances[asset][2] + deficit_over_current_price[asset] = deficit / current_price + #: Calculate the difference in pieces of each base asset + differences_in_base_asset = {} + for asset, deficit in deficit_over_current_price.items(): + differences_in_base_asset[asset] = deficit * total_available_balance + #: Create an ordered list of asset-deficit pairs starting from the smallest negative deficit ending with the + # biggest positive deficit + ordered_trades = sorted(differences_in_base_asset.items(), key=lambda x: x[1]) + #: log the planned ordered trades with sequence number + for i, (asset, deficit) in enumerate(ordered_trades): + trade_number = i + 1 + trade_type = "sell" if deficit < Decimal('0') else "buy" + self.logger().info(f"Trade {trade_number}: {trade_type} {asset}: {deficit}") + + if 0 < self.activeOrders: + self.logger().info(f"Wait to trade until all active orders have completed: {self.activeOrders}") + return + for i, (asset, deficit) in enumerate(ordered_trades): + quote_price = self.quote_balances[asset][2] + # We don't trade under 1 quote value, e.g. dollar. We can save trading fees by increasing this amount + if abs(deficit * quote_price) < 1: + self.logger().info(f"{abs(deficit * quote_price)} < 1 too small to trade") + continue + trade_is_buy = True if deficit > Decimal('0') else False + try: + if trade_is_buy: + self.buy(connector_name=self.exchange_name, trading_pair=f"{asset}-{self.quote_currency}", + amount=abs(deficit), order_type=OrderType.MARKET, price=quote_price) + else: + self.sell(connector_name=self.exchange_name, trading_pair=f"{asset}-{self.quote_currency}", + amount=abs(deficit), order_type=OrderType.MARKET, price=quote_price) + except decimal.InvalidOperation as e: + # Handle the error by logging it or taking other appropriate actions + print(f"Caught an error: {e}") + self.activeOrders -= 1 + + return + + def calculate_deficit_percentages(self, number_of_assets, percentages_dict): + differences_dict = {} + for asset, percentage in percentages_dict.items(): + deficit = (Decimal('1') / number_of_assets) - percentage + differences_dict[asset] = deficit + self.logger().info(f"Missing from 1/N {asset}: {deficit * 100}%") + return differences_dict + + def calculate_quote_balances(self, base_balances): + #: Multiply each balance with the current price to get the balances in the quote currency + quote_balances = {} + connector = self.connectors[self.exchange_name] + for asset, balances in base_balances.items(): + trading_pair = f"{asset}-{self.quote_currency}" + # noinspection PyUnresolvedReferences + current_price = Decimal(connector.get_mid_price(trading_pair)) + total_balance = balances[0] * current_price + available_balance = balances[1] * current_price + quote_balances[asset] = (total_balance, available_balance, current_price) + self.logger().info( + f"{asset} * {current_price} {self.quote_currency} = {available_balance} {self.quote_currency}") + return quote_balances + + def calculate_base_balances(self, exchange_balance_df): + base_balances = {} + for _, row in exchange_balance_df.iterrows(): + asset_name = row["Asset"] + if asset_name in self.base_currencies: + total_balance = Decimal(row["Total Balance"]) + available_balance = Decimal(row["Available Balance"]) + base_balances[asset_name] = (total_balance, available_balance) + logging.info(f"{available_balance:015,.5f} {asset_name} \n") + return base_balances + + def format_status(self) -> str: + # checking if last member variable in on_tick is set, so we can start + if self.differences_dict is None: + return "SYSTEM NOT READY... booting" + # create a table of base_balances and quote_balances and the summed up total of the quote_balances + table_of_balances = "base balances quote balances price\n" + for asset_name, base_balances in self.base_balances.items(): + quote_balance = self.quote_balances[asset_name][1] + price = self.quote_balances[asset_name][2] + table_of_balances += f"{base_balances[1]:15,.5f} {asset_name:5} {quote_balance:15,.5f} {price:15,.5f} {self.quote_currency}\n" + table_of_balances += f"TOT ({self.quote_currency}): {self.total_available_balance:15,.2f}\n" + table_of_balances += f"TOT/{len(self.base_currencies)} ({self.quote_currency}): {self.total_available_balance / len(self.base_currencies):15,.2f}\n" + return f"active orders: {self.activeOrders}\n" + \ + table_of_balances + "\n" + \ + create_differences_bar_chart(self.differences_dict) + + def did_create_buy_order(self, event: BuyOrderCreatedEvent): + self.activeOrders += 1 + logging.info(f"Created Buy - Active Orders ++: {self.activeOrders}") + + def did_create_sell_order(self, event: SellOrderCreatedEvent): + self.activeOrders += 1 + logging.info(f"Created Sell - Active Orders ++: {self.activeOrders}") + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + self.activeOrders -= 1 + logging.info(f"Completed Buy - Active Orders --: {self.activeOrders}") + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + self.activeOrders -= 1 + logging.info(f"Completed Sell - Active Orders --: {self.activeOrders}") + + def did_cancel_order(self, event: OrderCancelledEvent): + self.activeOrders -= 1 + logging.info(f"Canceled Order - Active Order --: {self.activeOrders}") + + def did_expire_order(self, event: OrderExpiredEvent): + self.activeOrders -= 1 + logging.info(f"Expired Order - Active Order --: {self.activeOrders}") + + def did_fail_order(self, event: MarketOrderFailureEvent): + self.activeOrders -= 1 + logging.info(f"Failed Order - Active Order --: {self.activeOrders}") + + def did_fill_order(self, event: OrderFilledEvent): + logging.info(f"Filled Order - Active Order ??: {self.activeOrders}") diff --git a/scripts/archived_scripts/community_scripts/adjusted_mid_price.py b/scripts/archived_scripts/community_scripts/adjusted_mid_price.py new file mode 100644 index 0000000..82e9830 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/adjusted_mid_price.py @@ -0,0 +1,147 @@ +from decimal import Decimal +from typing import List + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class AdjustedMidPrice(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/PMM-with-Adjusted-Midpoint-4259e7aef7bf403dbed35d1ed90f36fe + Video: - + Description: + This is an example of a pure market making strategy with an adjusted mid price. The mid price is adjusted to + the midpoint of a hypothetical buy and sell of a user defined {test_volume}. + Example: + let test_volume = 10 and the pair = BTC-USDT, then the new mid price will be the mid price of the following two points: + 1) the average fill price of a hypothetical market buy of 10 BTC + 2) the average fill price of a hypothetical market sell of 10 BTC + """ + + # The following strategy dictionary are parameters that the script operator can adjustS + strategy = { + "test_volume": 50, # the amount in base currancy to make the hypothetical market buy and market sell. + "bid_spread": .1, # how far away from the mid price do you want to place the first bid order (1 indicated 1%) + "ask_spread": .1, # how far away from the mid price do you want to place the first bid order (1 indicated 1%) + "amount": .1, # the amount in base currancy you want to buy or sell + "order_refresh_time": 60, + "market": "binance_paper_trade", + "pair": "BTC-USDT" + } + + markets = {strategy["market"]: {strategy["pair"]}} + + @property + def connector(self) -> ExchangeBase: + return self.connectors[self.strategy["market"]] + + def on_tick(self): + """ + Runs every tick_size seconds, this is the main operation of the strategy. + This method does two things: + - Refreshes the current bid and ask if they are set to None + - Cancels the current bid or current ask if they are past their order_refresh_time + The canceled orders will be refreshed next tic + """ + ## + # refresh order logic + ## + active_orders = self.get_active_orders(self.strategy["market"]) + # determine if we have an active bid and ask. We will only ever have 1 bid and 1 ask, so this logic would not work in the case of hanging orders + active_bid = None + active_ask = None + for order in active_orders: + if order.is_buy: + active_bid = order + else: + active_ask = order + proposal: List(OrderCandidate) = [] + if active_bid is None: + proposal.append(self.create_order(True)) + if active_ask is None: + proposal.append(self.create_order(False)) + if (len(proposal) > 0): + # we have proposed orders to place + # the next line will set the amount to 0 if we do not have the budget for the order and will quantize the amount if we have the budget + adjusted_proposal: List(OrderCandidate) = self.connector.budget_checker.adjust_candidates(proposal, all_or_none=True) + # we will set insufficient funds to true if any of the orders were set to zero + insufficient_funds = False + for order in adjusted_proposal: + if (order.amount == 0): + insufficient_funds = True + # do not place any orders if we have any insufficient funds and notify user + if (insufficient_funds): + self.logger().info("Insufficient funds. No more orders will be placed") + else: + # place orders + for order in adjusted_proposal: + if order.order_side == TradeType.BUY: + self.buy(self.strategy["market"], order.trading_pair, Decimal(self.strategy['amount']), order.order_type, Decimal(order.price)) + elif order.order_side == TradeType.SELL: + self.sell(self.strategy["market"], order.trading_pair, Decimal(self.strategy['amount']), order.order_type, Decimal(order.price)) + ## + # cancel order logic + # (canceled orders will be refreshed next tick) + ## + for order in active_orders: + if (order.age() > self.strategy["order_refresh_time"]): + self.cancel(self.strategy["market"], self.strategy["pair"], order.client_order_id) + + def create_order(self, is_bid: bool) -> OrderCandidate: + """ + Create a propsal for the current bid or ask using the adjusted mid price. + """ + mid_price = Decimal(self.adjusted_mid_price()) + bid_spread = Decimal(self.strategy["bid_spread"]) + ask_spread = Decimal(self.strategy["ask_spread"]) + bid_price = mid_price - mid_price * bid_spread * Decimal(.01) + ask_price = mid_price + mid_price * ask_spread * Decimal(.01) + price = bid_price if is_bid else ask_price + price = self.connector.quantize_order_price(self.strategy["pair"], Decimal(price)) + order = OrderCandidate( + trading_pair=self.strategy["pair"], + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY if is_bid else TradeType.SELL, + amount=Decimal(self.strategy["amount"]), + price=price) + return order + + def adjusted_mid_price(self): + """ + Returns the price of a hypothetical buy and sell or the base asset where the amout is {strategy.test_volume} + """ + ask_result = self.connector.get_quote_volume_for_base_amount(self.strategy["pair"], True, self.strategy["test_volume"]) + bid_result = self.connector.get_quote_volume_for_base_amount(self.strategy["pair"], False, self.strategy["test_volume"]) + average_ask = ask_result.result_volume / ask_result.query_volume + average_bid = bid_result.result_volume / bid_result.query_volume + return average_bid + ((average_ask - average_bid) / 2) + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + actual_mid_price = self.connector.get_mid_price(self.strategy["pair"]) + adjusted_mid_price = self.adjusted_mid_price() + lines.extend(["", " Adjusted mid price: " + str(adjusted_mid_price)] + [" Actual mid price: " + str(actual_mid_price)]) + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) diff --git a/scripts/archived_scripts/community_scripts/amm_price_example.py b/scripts/archived_scripts/community_scripts/amm_price_example.py new file mode 100644 index 0000000..8abb750 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/amm_price_example.py @@ -0,0 +1,49 @@ +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.strategy.script_strategy_base import Decimal, ScriptStrategyBase + + +class AmmPriceExample(ScriptStrategyBase): + """ + This example shows how to call the /amm/price Gateway endpoint to fetch price for a swap + """ + # swap params + connector_chain_network = "uniswap_ethereum_goerli" + trading_pair = {"WETH-DAI"} + side = "SELL" + order_amount = Decimal("0.01") + markets = { + connector_chain_network: trading_pair + } + on_going_task = False + + def on_tick(self): + # only execute once + if not self.on_going_task: + self.on_going_task = True + # wrap async task in safe_ensure_future + safe_ensure_future(self.async_task()) + + # async task since we are using Gateway + async def async_task(self): + base, quote = list(self.trading_pair)[0].split("-") + connector, chain, network = self.connector_chain_network.split("_") + if (self.side == "BUY"): + trade_type = TradeType.BUY + else: + trade_type = TradeType.SELL + + # fetch price + self.logger().info(f"POST /amm/price [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side} ]") + data = await GatewayHttpClient.get_instance().get_price( + chain, + network, + connector, + base, + quote, + self.order_amount, + trade_type + ) + self.logger().info(f"Price: {data['price']}") + self.logger().info(f"Amount: {data['amount']}") diff --git a/scripts/archived_scripts/community_scripts/amm_trade_example.py b/scripts/archived_scripts/community_scripts/amm_trade_example.py new file mode 100644 index 0000000..e123fd8 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/amm_trade_example.py @@ -0,0 +1,120 @@ +import asyncio + +from hummingbot.client.settings import GatewayConnectionSetting +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.strategy.script_strategy_base import Decimal, ScriptStrategyBase + + +class AmmTradeExample(ScriptStrategyBase): + """ + This example shows how to call the /amm/trade Gateway endpoint to execute a swap transaction + """ + # swap params + connector_chain_network = "uniswap_ethereum_goerli" + trading_pair = {"WETH-DAI"} + side = "SELL" + order_amount = Decimal("0.01") + slippage_buffer = 0.01 + markets = { + connector_chain_network: trading_pair + } + on_going_task = False + + def on_tick(self): + # only execute once + if not self.on_going_task: + self.on_going_task = True + # wrap async task in safe_ensure_future + safe_ensure_future(self.async_task()) + + # async task since we are using Gateway + async def async_task(self): + base, quote = list(self.trading_pair)[0].split("-") + connector, chain, network = self.connector_chain_network.split("_") + if (self.side == "BUY"): + trade_type = TradeType.BUY + else: + trade_type = TradeType.SELL + + # fetch current price + self.logger().info(f"POST /amm/price [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side} ]") + priceData = await GatewayHttpClient.get_instance().get_price( + chain, + network, + connector, + base, + quote, + self.order_amount, + trade_type + ) + self.logger().info(f"Price: {priceData['price']}") + self.logger().info(f"Amount: {priceData['amount']}") + + # add slippage buffer to current price + if (self.side == "BUY"): + price = float(priceData['price']) * (1 + self.slippage_buffer) + else: + price = float(priceData['price']) * (1 - self.slippage_buffer) + self.logger().info(f"Swap Limit Price: {price}") + + # fetch wallet address and print balances + gateway_connections_conf = GatewayConnectionSetting.load() + if len(gateway_connections_conf) < 1: + self.notify("No existing wallet.\n") + return + wallet = [w for w in gateway_connections_conf if w["chain"] == chain and w["connector"] == connector and w["network"] == network] + address = wallet[0]['wallet_address'] + await self.get_balance(chain, network, address, base, quote) + + # execute swap + self.logger().info(f"POST /amm/trade [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side}, price: {price} ]") + tradeData = await GatewayHttpClient.get_instance().amm_trade( + chain, + network, + connector, + address, + base, + quote, + trade_type, + self.order_amount, + Decimal(price) + ) + + # poll for swap result and print resulting balances + await self.poll_transaction(chain, network, tradeData['txHash']) + await self.get_balance(chain, network, address, base, quote) + + # fetch and print balance of base and quote tokens + async def get_balance(self, chain, network, address, base, quote): + self.logger().info(f"POST /network/balance [ address: {address}, base: {base}, quote: {quote} ]") + balanceData = await GatewayHttpClient.get_instance().get_balances( + chain, + network, + address, + [base, quote] + ) + self.logger().info(f"Balances for {address}: {balanceData['balances']}") + + # continuously poll for transaction until confirmed + async def poll_transaction(self, chain, network, txHash): + pending: bool = True + while pending is True: + self.logger().info(f"POST /network/poll [ txHash: {txHash} ]") + pollData = await GatewayHttpClient.get_instance().get_transaction_status( + chain, + network, + txHash + ) + transaction_status = pollData.get("txStatus") + if transaction_status == 1: + self.logger().info(f"Trade with transaction hash {txHash} has been executed successfully.") + pending = False + elif transaction_status in [-1, 0, 2]: + self.logger().info(f"Trade is pending confirmation, Transaction hash: {txHash}") + await asyncio.sleep(2) + else: + self.logger().info(f"Unknown txStatus: {transaction_status}") + self.logger().info(f"{pollData}") + pending = False diff --git a/scripts/archived_scripts/community_scripts/backtest_mm_example.py b/scripts/archived_scripts/community_scripts/backtest_mm_example.py new file mode 100644 index 0000000..4b5d580 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/backtest_mm_example.py @@ -0,0 +1,181 @@ +import logging +from datetime import datetime + +import numpy as np +import pandas as pd + +from hummingbot import data_path +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class BacktestMM(ScriptStrategyBase): + """ + BotCamp Cohort: 4 + Design Template: https://www.notion.so/hummingbot-foundation/Backtestable-Market-Making-Stategy-95c0d17e4042485bb90b7b2914af7f68?pvs=4 + Video: https://www.loom.com/share/e18380429e9443ceb1ef86eb131c14a2 + Description: This bot implements a simpler backtester for a market making strategy using the Binance candles feed. + After processing the user-defined backtesting parameters through historical OHLCV candles, it calculates a summary + table displayed in 'status' and saves the data to a CSV file. + + You may need to run 'balance paper [asset] [amount]' beforehand to set the initial balances used for backtesting. + """ + + # User-defined parameters + exchange = "binance" + trading_pair = "ETH-USDT" + order_amount = 0.1 + bid_spread_bps = 10 + ask_spread_bps = 10 + fee_bps = 10 + days = 7 + paper_trade_enabled = True + + # System parameters + precision = 2 + base, quote = trading_pair.split("-") + execution_exchange = f"{exchange}_paper_trade" if paper_trade_enabled else exchange + interval = "1m" + results_df = None + candle = CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval=interval, max_records=days * 60 * 24)) + candle.start() + + csv_path = data_path() + f"/backtest_{trading_pair}_{bid_spread_bps}_bid_{ask_spread_bps}_ask.csv" + markets = {f"{execution_exchange}": {trading_pair}} + + def on_tick(self): + if not self.candle.is_ready: + self.logger().info(f"Candles not ready yet for {self.trading_pair}! Missing {self.candle._candles.maxlen - len(self.candle._candles)}") + pass + else: + df = self.candle.candles_df + df['ask_price'] = df["open"] * (1 + self.ask_spread_bps / 10000) + df['bid_price'] = df["open"] * (1 - self.bid_spread_bps / 10000) + df['buy_amount'] = df['low'].le(df['bid_price']) * self.order_amount + df['sell_amount'] = df['high'].ge(df['ask_price']) * self.order_amount + df['fees_paid'] = (df['buy_amount'] * df['bid_price'] + df['sell_amount'] * df['ask_price']) * self.fee_bps / 10000 + df['base_delta'] = df['buy_amount'] - df['sell_amount'] + df['quote_delta'] = df['sell_amount'] * df['ask_price'] - df['buy_amount'] * df['bid_price'] - df['fees_paid'] + + if self.candle.is_ready and self.results_df is None: + df.to_csv(self.csv_path, index=False) + self.results_df = df + msg = "Backtesting complete - run 'status' to see results." + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + def on_stop(self): + self.candle.stop() + + def get_trades_df(self, df): + total_buy_trades = df['buy_amount'].ne(0).sum() + total_sell_trades = df['sell_amount'].ne(0).sum() + amount_bought = df['buy_amount'].sum() + amount_sold = df['sell_amount'].sum() + end_price = df.tail(1)['close'].values[0] + amount_bought_quote = amount_bought * end_price + amount_sold_quote = amount_sold * end_price + avg_buy_price = np.dot(df['bid_price'], df['buy_amount']) / amount_bought + avg_sell_price = np.dot(df['ask_price'], df['sell_amount']) / amount_sold + avg_total_price = (avg_buy_price * amount_bought + avg_sell_price * amount_sold) / (amount_bought + amount_sold) + + trades_columns = ["", "buy", "sell", "total"] + trades_data = [ + [f"{'Number of trades':<27}", total_buy_trades, total_sell_trades, total_buy_trades + total_sell_trades], + [f"{f'Total trade volume ({self.base})':<27}", + round(amount_bought, self.precision), + round(amount_sold, self.precision), + round(amount_bought + amount_sold, self.precision)], + [f"{f'Total trade volume ({self.quote})':<27}", + round(amount_bought_quote, self.precision), + round(amount_sold_quote, self.precision), + round(amount_bought_quote + amount_sold_quote, self.precision)], + [f"{'Avg price':<27}", + round(avg_buy_price, self.precision), + round(avg_sell_price, self.precision), + round(avg_total_price, self.precision)], + ] + return pd.DataFrame(data=trades_data, columns=trades_columns) + + def get_assets_df(self, df): + for connector_name, connector in self.connectors.items(): + base_bal_start = float(connector.get_balance(self.base)) + quote_bal_start = float(connector.get_balance(self.quote)) + base_bal_change = df['base_delta'].sum() + quote_bal_change = df['quote_delta'].sum() + base_bal_end = base_bal_start + base_bal_change + quote_bal_end = quote_bal_start + quote_bal_change + start_price = df.head(1)['open'].values[0] + end_price = df.tail(1)['close'].values[0] + base_bal_start_pct = base_bal_start / (base_bal_start + quote_bal_start / start_price) + base_bal_end_pct = base_bal_end / (base_bal_end + quote_bal_end / end_price) + + assets_columns = ["", "start", "end", "change"] + assets_data = [ + [f"{f'{self.base}':<27}", f"{base_bal_start:2}", round(base_bal_end, self.precision), round(base_bal_change, self.precision)], + [f"{f'{self.quote}':<27}", f"{quote_bal_start:2}", round(quote_bal_end, self.precision), round(quote_bal_change, self.precision)], + [f"{f'{self.base}-{self.quote} price':<27}", start_price, end_price, end_price - start_price], + [f"{'Base asset %':<27}", f"{base_bal_start_pct:.2%}", + f"{base_bal_end_pct:.2%}", + f"{base_bal_end_pct - base_bal_start_pct:.2%}"], + ] + return pd.DataFrame(data=assets_data, columns=assets_columns) + + def get_performance_df(self, df): + for connector_name, connector in self.connectors.items(): + base_bal_start = float(connector.get_balance(self.base)) + quote_bal_start = float(connector.get_balance(self.quote)) + base_bal_change = df['base_delta'].sum() + quote_bal_change = df['quote_delta'].sum() + start_price = df.head(1)['open'].values[0] + end_price = df.tail(1)['close'].values[0] + base_bal_end = base_bal_start + base_bal_change + quote_bal_end = quote_bal_start + quote_bal_change + hold_value = base_bal_end * start_price + quote_bal_end + current_value = base_bal_end * end_price + quote_bal_end + total_pnl = current_value - hold_value + fees_paid = df['fees_paid'].sum() + return_pct = total_pnl / hold_value + perf_data = [ + ["Hold portfolio value ", f"{round(hold_value, self.precision)} {self.quote}"], + ["Current portfolio value ", f"{round(current_value, self.precision)} {self.quote}"], + ["Trade P&L ", f"{round(total_pnl + fees_paid, self.precision)} {self.quote}"], + ["Fees paid ", f"{round(fees_paid, self.precision)} {self.quote}"], + ["Total P&L ", f"{round(total_pnl, self.precision)} {self.quote}"], + ["Return % ", f"{return_pct:2%} {self.quote}"], + ] + return pd.DataFrame(data=perf_data) + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + if not self.candle.is_ready: + return (f"Candles not ready yet for {self.trading_pair}! Missing {self.candle._candles.maxlen - len(self.candle._candles)}") + + df = self.results_df + base, quote = self.trading_pair.split("-") + lines = [] + start_time = datetime.fromtimestamp(int(df.head(1)['timestamp'].values[0] / 1000)) + end_time = datetime.fromtimestamp(int(df.tail(1)['timestamp'].values[0] / 1000)) + + lines.extend( + [f"\n Start Time: {start_time.strftime('%Y-%m-%d %H:%M:%S')}"] + + [f" End Time: {end_time.strftime('%Y-%m-%d %H:%M:%S')}"] + + [f" Duration: {pd.Timedelta(seconds=(end_time - start_time).seconds)}"] + ) + lines.extend( + [f"\n Market: {self.exchange} / {self.trading_pair}"] + + [f" Spread(bps): {self.bid_spread_bps} bid / {self.ask_spread_bps} ask"] + + [f" Order Amount: {self.order_amount} {base}"] + ) + + trades_df = self.get_trades_df(df) + lines.extend(["", " Trades:"] + [" " + line for line in trades_df.to_string(index=False).split("\n")]) + + assets_df = self.get_assets_df(df) + lines.extend(["", " Assets:"] + [" " + line for line in assets_df.to_string(index=False).split("\n")]) + + performance_df = self.get_performance_df(df) + lines.extend(["", " Performance:"] + [" " + line for line in performance_df.to_string(index=False, header=False).split("\n")]) + + return "\n".join(lines) diff --git a/scripts/archived_scripts/community_scripts/batch_order_update.py b/scripts/archived_scripts/community_scripts/batch_order_update.py new file mode 100644 index 0000000..531c07d --- /dev/null +++ b/scripts/archived_scripts/community_scripts/batch_order_update.py @@ -0,0 +1,122 @@ +from collections import defaultdict +from decimal import Decimal +from typing import List + +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + +CONNECTOR = "dexalot_avalanche_dexalot" +BASE = "AVAX" +QUOTE = "USDC" +TRADING_PAIR = combine_to_hb_trading_pair(base=BASE, quote=QUOTE) +AMOUNT = Decimal("0.5") +ORDERS_INTERVAL = 20 +PRICE_OFFSET_RATIO = Decimal("0.1") # 10% + + +class BatchOrderUpdate(ScriptStrategyBase): + markets = {CONNECTOR: {TRADING_PAIR}} + pingpong = 0 + + script_phase = 0 + + def on_tick(self): + if self.script_phase == 0: + self.place_two_orders_successfully() + elif self.script_phase == ORDERS_INTERVAL: + self.cancel_orders() + elif self.script_phase == ORDERS_INTERVAL * 2: + self.place_two_orders_with_one_zero_amount_that_will_fail() + elif self.script_phase == ORDERS_INTERVAL * 3: + self.cancel_orders() + self.script_phase += 1 + + def place_two_orders_successfully(self): + price = self.connectors[CONNECTOR].get_price(trading_pair=TRADING_PAIR, is_buy=True) + orders_to_create = [ + LimitOrder( + client_order_id="", + trading_pair=TRADING_PAIR, + is_buy=True, + base_currency=BASE, + quote_currency=QUOTE, + price=price * (1 - PRICE_OFFSET_RATIO), + quantity=AMOUNT, + ), + LimitOrder( + client_order_id="", + trading_pair=TRADING_PAIR, + is_buy=False, + base_currency=BASE, + quote_currency=QUOTE, + price=price * (1 + PRICE_OFFSET_RATIO), + quantity=AMOUNT, + ), + ] + + market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) + market = market_pair.market + + submitted_orders: List[LimitOrder] = market.batch_order_create( + orders_to_create=orders_to_create, + ) + + for order in submitted_orders: + self.start_tracking_limit_order( + market_pair=market_pair, + order_id=order.client_order_id, + is_buy=order.is_buy, + price=order.price, + quantity=order.quantity, + ) + + def cancel_orders(self): + exchanges_to_orders = defaultdict(lambda: []) + exchanges_dict = {} + + for exchange, order in self.order_tracker.active_limit_orders: + exchanges_to_orders[exchange.name].append(order) + exchanges_dict[exchange.name] = exchange + + for exchange_name, orders_to_cancel in exchanges_to_orders.items(): + exchanges_dict[exchange_name].batch_order_cancel(orders_to_cancel=orders_to_cancel) + + def place_two_orders_with_one_zero_amount_that_will_fail(self): + price = self.connectors[CONNECTOR].get_price(trading_pair=TRADING_PAIR, is_buy=True) + orders_to_create = [ + LimitOrder( + client_order_id="", + trading_pair=TRADING_PAIR, + is_buy=True, + base_currency=BASE, + quote_currency=QUOTE, + price=price * (1 - PRICE_OFFSET_RATIO), + quantity=AMOUNT, + ), + LimitOrder( + client_order_id="", + trading_pair=TRADING_PAIR, + is_buy=False, + base_currency=BASE, + quote_currency=QUOTE, + price=price * (1 + PRICE_OFFSET_RATIO), + quantity=Decimal("0"), + ), + ] + + market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) + market = market_pair.market + + submitted_orders: List[LimitOrder] = market.batch_order_create( + orders_to_create=orders_to_create, + ) + + for order in submitted_orders: + self.start_tracking_limit_order( + market_pair=market_pair, + order_id=order.client_order_id, + is_buy=order.is_buy, + price=order.price, + quantity=order.quantity, + ) diff --git a/scripts/archived_scripts/community_scripts/batch_order_update_market_orders.py b/scripts/archived_scripts/community_scripts/batch_order_update_market_orders.py new file mode 100644 index 0000000..66696ed --- /dev/null +++ b/scripts/archived_scripts/community_scripts/batch_order_update_market_orders.py @@ -0,0 +1,104 @@ +import time +from decimal import Decimal +from typing import List + +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + +CONNECTOR = "bybit" +BASE = "ETH" +QUOTE = "BTC" +TRADING_PAIR = combine_to_hb_trading_pair(base=BASE, quote=QUOTE) +AMOUNT = Decimal("0.003") +ORDERS_INTERVAL = 20 +PRICE_OFFSET_RATIO = Decimal("0.1") # 10% + + +class BatchOrderUpdate(ScriptStrategyBase): + markets = {CONNECTOR: {TRADING_PAIR}} + pingpong = 0 + + script_phase = 0 + + def on_tick(self): + if self.script_phase == 0: + self.place_two_orders_successfully() + elif self.script_phase == ORDERS_INTERVAL: + self.place_two_orders_with_one_zero_amount_that_will_fail() + self.script_phase += 1 + + def place_two_orders_successfully(self): + orders_to_create = [ + MarketOrder( + order_id="", + trading_pair=TRADING_PAIR, + is_buy=True, + base_asset=BASE, + quote_asset=QUOTE, + amount=AMOUNT, + timestamp=time.time(), + ), + MarketOrder( + order_id="", + trading_pair=TRADING_PAIR, + is_buy=False, + base_asset=BASE, + quote_asset=QUOTE, + amount=AMOUNT, + timestamp=time.time(), + ), + ] + + market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) + market = market_pair.market + + submitted_orders: List[LimitOrder, MarketOrder] = market.batch_order_create( + orders_to_create=orders_to_create, + ) + + for order in submitted_orders: + self.start_tracking_market_order( + market_pair=market_pair, + order_id=order.order_id, + is_buy=order.is_buy, + quantity=order.amount, + ) + + def place_two_orders_with_one_zero_amount_that_will_fail(self): + orders_to_create = [ + MarketOrder( + order_id="", + trading_pair=TRADING_PAIR, + is_buy=True, + base_asset=BASE, + quote_asset=QUOTE, + amount=AMOUNT, + timestamp=time.time(), + ), + MarketOrder( + order_id="", + trading_pair=TRADING_PAIR, + is_buy=True, + base_asset=BASE, + quote_asset=QUOTE, + amount=Decimal("0"), + timestamp=time.time(), + ), + ] + + market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) + market = market_pair.market + + submitted_orders: List[LimitOrder, MarketOrder] = market.batch_order_create( + orders_to_create=orders_to_create, + ) + + for order in submitted_orders: + self.start_tracking_market_order( + market_pair=market_pair, + order_id=order.order_id, + is_buy=order.is_buy, + quantity=order.amount, + ) diff --git a/scripts/archived_scripts/community_scripts/buy_dip_example.py b/scripts/archived_scripts/community_scripts/buy_dip_example.py new file mode 100644 index 0000000..399a59f --- /dev/null +++ b/scripts/archived_scripts/community_scripts/buy_dip_example.py @@ -0,0 +1,130 @@ +import logging +import time +from decimal import Decimal +from statistics import mean +from typing import List + +import requests + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import OrderFilledEvent, OrderType, TradeType +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class BuyDipExample(ScriptStrategyBase): + """ + THis strategy buys ETH (with BTC) when the ETH-BTC drops 5% below 50 days moving average (of a previous candle) + This example demonstrates: + - How to call Binance REST API for candle stick data + - How to incorporate external pricing source (Coingecko) into the strategy + - How to listen to order filled event + - How to structure order execution on a more complex strategy + Before running this example, make sure you run `config rate_oracle_source coingecko` + """ + connector_name: str = "binance_paper_trade" + trading_pair: str = "ETH-BTC" + base_asset, quote_asset = split_hb_trading_pair(trading_pair) + conversion_pair: str = f"{quote_asset}-USD" + buy_usd_amount: Decimal = Decimal("100") + moving_avg_period: int = 50 + dip_percentage: Decimal = Decimal("0.05") + #: A cool off period before the next buy (in seconds) + cool_off_interval: float = 10. + #: The last buy timestamp + last_ordered_ts: float = 0. + + markets = {connector_name: {trading_pair}} + + @property + def connector(self) -> ExchangeBase: + """ + The only connector in this strategy, define it here for easy access + """ + return self.connectors[self.connector_name] + + def on_tick(self): + """ + Runs every tick_size seconds, this is the main operation of the strategy. + - Create proposal (a list of order candidates) + - Check the account balance and adjust the proposal accordingly (lower order amount if needed) + - Lastly, execute the proposal on the exchange + """ + proposal: List[OrderCandidate] = self.create_proposal() + proposal = self.connector.budget_checker.adjust_candidates(proposal, all_or_none=False) + if proposal: + self.execute_proposal(proposal) + + def create_proposal(self) -> List[OrderCandidate]: + """ + Creates and returns a proposal (a list of order candidate), in this strategy the list has 1 element at most. + """ + daily_closes = self._get_daily_close_list(self.trading_pair) + start_index = (-1 * self.moving_avg_period) - 1 + # Calculate the average of the 50 element prior to the last element + avg_close = mean(daily_closes[start_index:-1]) + proposal = [] + # If the current price (the last close) is below the dip, add a new order candidate to the proposal + if daily_closes[-1] < avg_close * (Decimal("1") - self.dip_percentage): + order_price = self.connector.get_price(self.trading_pair, False) * Decimal("0.9") + usd_conversion_rate = RateOracle.get_instance().get_pair_rate(self.conversion_pair) + amount = (self.buy_usd_amount / usd_conversion_rate) / order_price + proposal.append(OrderCandidate(self.trading_pair, False, OrderType.LIMIT, TradeType.BUY, amount, + order_price)) + return proposal + + def execute_proposal(self, proposal: List[OrderCandidate]): + """ + Places the order candidates on the exchange, if it is not within cool off period and order candidate is valid. + """ + if self.last_ordered_ts > time.time() - self.cool_off_interval: + return + for order_candidate in proposal: + if order_candidate.amount > Decimal("0"): + self.buy(self.connector_name, self.trading_pair, order_candidate.amount, order_candidate.order_type, + order_candidate.price) + self.last_ordered_ts = time.time() + + def did_fill_order(self, event: OrderFilledEvent): + """ + Listens to fill order event to log it and notify the hummingbot application. + If you set up Telegram bot, you will get notification there as well. + """ + msg = (f"({event.trading_pair}) {event.trade_type.name} order (price: {event.price}) of {event.amount} " + f"{split_hb_trading_pair(event.trading_pair)[0]} is filled.") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + def _get_daily_close_list(self, trading_pair: str) -> List[Decimal]: + """ + Fetches binance candle stick data and returns a list daily close + This is the API response data structure: + [ + [ + 1499040000000, // Open time + "0.01634790", // Open + "0.80000000", // High + "0.01575800", // Low + "0.01577100", // Close + "148976.11427815", // Volume + 1499644799999, // Close time + "2434.19055334", // Quote asset volume + 308, // Number of trades + "1756.87402397", // Taker buy base asset volume + "28.46694368", // Taker buy quote asset volume + "17928899.62484339" // Ignore. + ] + ] + + :param trading_pair: A market trading pair to + + :return: A list of daily close + """ + + url = "https://api.binance.com/api/v3/klines" + params = {"symbol": trading_pair.replace("-", ""), + "interval": "1d"} + records = requests.get(url=url, params=params).json() + return [Decimal(str(record[4])) for record in records] diff --git a/scripts/archived_scripts/community_scripts/buy_low_sell_high.py b/scripts/archived_scripts/community_scripts/buy_low_sell_high.py new file mode 100644 index 0000000..8f2c463 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/buy_low_sell_high.py @@ -0,0 +1,58 @@ +from collections import deque +from decimal import Decimal +from statistics import mean + +from hummingbot.core.data_type.common import OrderType +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class BuyLowSellHigh(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Buy-low-sell-high-35b89d84f0d94d379951a98f97179053 + Video: - + Description: + The script will be calculating the MA for a certain pair, and will execute a buy_order at the golden cross + and a sell_order at the death cross. + For the sake of simplicity in testing, we will define fast MA as the 5-secondly-MA, and slow MA as the + 20-secondly-MA. User can change this as desired + """ + markets = {"binance_paper_trade": {"BTC-USDT"}} + #: pingpong is a variable to allow alternating between buy & sell signals + pingpong = 0 + de_fast_ma = deque([], maxlen=5) + de_slow_ma = deque([], maxlen=20) + + def on_tick(self): + p = self.connectors["binance_paper_trade"].get_price("BTC-USDT", True) + + #: with every tick, the new price of the trading_pair will be appended to the deque and MA will be calculated + self.de_fast_ma.append(p) + self.de_slow_ma.append(p) + fast_ma = mean(self.de_fast_ma) + slow_ma = mean(self.de_slow_ma) + + #: logic for golden cross + if (fast_ma > slow_ma) & (self.pingpong == 0): + self.buy( + connector_name="binance_paper_trade", + trading_pair="BTC-USDT", + amount=Decimal(0.01), + order_type=OrderType.MARKET, + ) + self.logger().info(f'{"0.01 BTC bought"}') + self.pingpong = 1 + + #: logic for death cross + elif (slow_ma > fast_ma) & (self.pingpong == 1): + self.sell( + connector_name="binance_paper_trade", + trading_pair="BTC-USDT", + amount=Decimal(0.01), + order_type=OrderType.MARKET, + ) + self.logger().info(f'{"0.01 BTC sold"}') + self.pingpong = 0 + + else: + self.logger().info(f'{"wait for a signal to be generated"}') diff --git a/scripts/archived_scripts/community_scripts/dca_example.py b/scripts/archived_scripts/community_scripts/dca_example.py new file mode 100644 index 0000000..36bcbcc --- /dev/null +++ b/scripts/archived_scripts/community_scripts/dca_example.py @@ -0,0 +1,77 @@ +import logging + +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase + + +class DCAExample(ScriptStrategyBase): + """ + This example shows how to set up a simple strategy to buy a token on fixed (dollar) amount on a regular basis + """ + #: Define markets to instruct Hummingbot to create connectors on the exchanges and markets you need + markets = {"binance_paper_trade": {"BTC-USDT"}} + #: The last time the strategy places a buy order + last_ordered_ts = 0. + #: Buying interval (in seconds) + buy_interval = 10. + #: Buying amount (in dollars - USDT) + buy_quote_amount = Decimal("100") + + def on_tick(self): + # Check if it is time to buy + if self.last_ordered_ts < (self.current_timestamp - self.buy_interval): + # Lets set the order price to the best bid + price = self.connectors["binance_paper_trade"].get_price("BTC-USDT", False) + amount = self.buy_quote_amount / price + self.buy("binance_paper_trade", "BTC-USDT", amount, OrderType.LIMIT, price) + self.last_ordered_ts = self.current_timestamp + + def did_create_buy_order(self, event: BuyOrderCreatedEvent): + """ + Method called when the connector notifies a buy order has been created + """ + self.logger().info(logging.INFO, f"The buy order {event.order_id} has been created") + + def did_create_sell_order(self, event: SellOrderCreatedEvent): + """ + Method called when the connector notifies a sell order has been created + """ + self.logger().info(logging.INFO, f"The sell order {event.order_id} has been created") + + def did_fill_order(self, event: OrderFilledEvent): + """ + Method called when the connector notifies that an order has been partially or totally filled (a trade happened) + """ + self.logger().info(logging.INFO, f"The order {event.order_id} has been filled") + + def did_fail_order(self, event: MarketOrderFailureEvent): + """ + Method called when the connector notifies an order has failed + """ + self.logger().info(logging.INFO, f"The order {event.order_id} failed") + + def did_cancel_order(self, event: OrderCancelledEvent): + """ + Method called when the connector notifies an order has been cancelled + """ + self.logger().info(f"The order {event.order_id} has been cancelled") + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + """ + Method called when the connector notifies a buy order has been completed (fully filled) + """ + self.logger().info(f"The buy order {event.order_id} has been completed") + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + """ + Method called when the connector notifies a sell order has been completed (fully filled) + """ + self.logger().info(f"The sell order {event.order_id} has been completed") diff --git a/scripts/archived_scripts/community_scripts/external_events_example.py b/scripts/archived_scripts/community_scripts/external_events_example.py new file mode 100644 index 0000000..7695497 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/external_events_example.py @@ -0,0 +1,74 @@ +from hummingbot.core.event.events import BuyOrderCreatedEvent, MarketOrderFailureEvent, SellOrderCreatedEvent +from hummingbot.remote_iface.mqtt import ExternalEventFactory, ExternalTopicFactory +from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase + + +class ExternalEventsExample(ScriptStrategyBase): + """ + Simple script that uses the external events plugin to create buy and sell + market orders. + """ + #: Define markets + markets = {"kucoin_paper_trade": {"BTC-USDT"}} + + # ------ Using Factory Classes ------ + # hbot/{id}/external/events/* + eevents = ExternalEventFactory.create_queue('*') + # hbot/{id}/test/a + etopic_queue = ExternalTopicFactory.create_queue('test/a') + + # ---- Using callback functions ---- + # ---------------------------------- + def __init__(self, *args, **kwargs): + ExternalEventFactory.create_async('*', self.on_event) + self.listener = ExternalTopicFactory.create_async('test/a', self.on_message) + super().__init__(*args, **kwargs) + + def on_event(self, msg, name): + self.logger().info(f'OnEvent Callback fired: {name} -> {msg}') + + def on_message(self, msg, topic): + self.logger().info(f'Topic Message Callback fired: {topic} -> {msg}') + + def on_stop(self): + ExternalEventFactory.remove_listener('*', self.on_event) + ExternalTopicFactory.remove_listener(self.listener) + # ---------------------------------- + + def on_tick(self): + while len(self.eevents) > 0: + event = self.eevents.popleft() + self.logger().info(f'External Event in Queue: {event}') + # event = (name, msg) + if event[0] == 'order.market': + if event[1].data['type'] in ('buy', 'Buy', 'BUY'): + self.execute_order(Decimal(event[1].data['amount']), True) + elif event[1].data['type'] in ('sell', 'Sell', 'SELL'): + self.execute_order(Decimal(event[1].data['amount']), False) + while len(self.etopic_queue) > 0: + entry = self.etopic_queue.popleft() + self.logger().info(f'Topic Message in Queue: {entry[0]} -> {entry[1]}') + + def execute_order(self, amount: Decimal, is_buy: bool): + if is_buy: + self.buy("kucoin_paper_trade", "BTC-USDT", amount, OrderType.MARKET) + else: + self.sell("kucoin_paper_trade", "BTC-USDT", amount, OrderType.MARKET) + + def did_create_buy_order(self, event: BuyOrderCreatedEvent): + """ + Method called when the connector notifies a buy order has been created + """ + self.logger().info(f"The buy order {event.order_id} has been created") + + def did_create_sell_order(self, event: SellOrderCreatedEvent): + """ + Method called when the connector notifies a sell order has been created + """ + self.logger().info(f"The sell order {event.order_id} has been created") + + def did_fail_order(self, event: MarketOrderFailureEvent): + """ + Method called when the connector notifies an order has failed + """ + self.logger().info(f"The order {event.order_id} failed") diff --git a/scripts/archived_scripts/community_scripts/microprice_calculator.py b/scripts/archived_scripts/community_scripts/microprice_calculator.py new file mode 100644 index 0000000..bf1e65e --- /dev/null +++ b/scripts/archived_scripts/community_scripts/microprice_calculator.py @@ -0,0 +1,305 @@ +import datetime +import os +from decimal import Decimal +from operator import itemgetter + +import numpy as np +import pandas as pd +from scipy.linalg import block_diag + +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MicropricePMM(ScriptStrategyBase): + # ! Configuration + trading_pair = "ETH-USDT" + exchange = "kucoin_paper_trade" + range_of_imbalance = 1 # ? Compute imbalance from [best bid/ask, +/- ticksize*range_of_imbalance) + + # ! Microprice configuration + dt = 1 + n_imb = 6 # ? Needs to be large enough to capture shape of imbalance adjustmnts without being too large to capture noise + + # ! Advanced configuration variables + show_data = False # ? Controls whether current df is shown in status + path_to_data = './data' # ? Default file format './data/microprice_{trading_pair}_{exchange}_{date}.csv' + interval_to_write = 60 + price_line_width = 60 + precision = 4 # ? should be the length of the ticksize + data_size_min = 10000 # ? Seems to be the ideal value to get microprice adjustment values for other spreads + day_offset = 1 # ? How many days back to start looking for csv files to load data from + + # ! Script variabes + columns = ['date', 'time', 'bid', 'bs', 'ask', 'as'] + current_dataframe = pd.DataFrame(columns=columns) + time_to_write = 0 + markets = {exchange: {trading_pair}} + g_star = None + recording_data = True + ticksize = None + n_spread = None + + # ! System methods + def on_tick(self): + # Record data, dump data, update write timestamp + self.record_data() + if self.time_to_write < self.current_timestamp: + self.time_to_write = self.interval_to_write + self.current_timestamp + self.dump_data() + + def format_status(self) -> str: + bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) + bar = '=' * self.price_line_width + '\n' + header = f'Trading pair: {self.trading_pair}\nExchange: {self.exchange}\n' + price_line = f'Adjusted Midprice: {self.compute_adjusted_midprice()}\n Midprice: {round((bid + ask) / 2, 8)}\n = {round(self.compute_adjusted_midprice() - ((bid + ask) / 2), 20)}\n\n{self.get_price_line()}\n' + imbalance_line = f'Imbalance: {self.compute_imbalance()}\n{self.get_imbalance_line()}\n' + data = f'Data path: {self.get_csv_path()}\n' + g_star = f'g_star:\n{self.g_star}' if self.g_star is not None else '' + + return f"\n\n\n{bar}\n\n{header}\n{price_line}\n\n{imbalance_line}\nn_spread: {self.n_spread} {'tick' if self.n_spread == 1 else 'ticks'}\n\n\n{g_star}\n\n{data}\n\n{bar}\n\n\n" + + # ! Data recording methods + # Records a new row to the dataframe every tick + # Every 'time_to_write' ticks, writes the dataframe to a csv file + def record_data(self): + # Fetch bid and ask data + bid, ask, bid_volume, ask_volume = itemgetter('bid', 'ask', 'bs', 'as')(self.get_bid_ask()) + # Fetch date and time in seconds + date = datetime.datetime.now().strftime("%Y-%m-%d") + time = self.current_timestamp + + data = [[date, time, bid, bid_volume, ask, ask_volume]] + self.current_dataframe = self.current_dataframe.append(pd.DataFrame(data, columns=self.columns), ignore_index=True) + return + + def dump_data(self): + if len(self.current_dataframe) < 2 * self.range_of_imbalance: + return + # Dump data to csv file + csv_path = f'{self.path_to_data}/microprice_{self.trading_pair}_{self.exchange}_{datetime.datetime.now().strftime("%Y-%m-%d")}.csv' + try: + data = pd.read_csv(csv_path, index_col=[0]) + except Exception as e: + self.logger().info(e) + self.logger().info(f'Creating new csv file at {csv_path}') + data = pd.DataFrame(columns=self.columns) + + data = data.append(self.current_dataframe.iloc[:-self.range_of_imbalance], ignore_index=True) + data.to_csv(csv_path) + self.current_dataframe = self.current_dataframe.iloc[-self.range_of_imbalance:] + return + +# ! Data methods + def get_csv_path(self): + # Get all files in self.path_to_data directory + files = os.listdir(self.path_to_data) + for i in files: + if i.startswith(f'microprice_{self.trading_pair}_{self.exchange}'): + len_data = len(pd.read_csv(f'{self.path_to_data}/{i}', index_col=[0])) + if len_data > self.data_size_min: + return f'{self.path_to_data}/{i}' + + # Otherwise just return today's file + return f'{self.path_to_data}/microprice_{self.trading_pair}_{self.exchange}_{datetime.datetime.now().strftime("%Y-%m-%d")}.csv' + + def get_bid_ask(self): + bids, asks = self.connectors[self.exchange].get_order_book(self.trading_pair).snapshot + # if size > 0, return average of range + best_ask = asks.iloc[0].price + ask_volume = asks.iloc[0].amount + best_bid = bids.iloc[0].price + bid_volume = bids.iloc[0].amount + return {'bid': best_bid, 'ask': best_ask, 'bs': bid_volume, 'as': ask_volume} + + # ! Microprice methods + def compute_adjusted_midprice(self): + data = self.get_df() + if len(data) < self.data_size_min or self.current_dataframe.empty: + self.recording_data = True + return -1 + if self.n_spread is None: + self.n_spread = self.compute_n_spread() + if self.g_star is None: + ticksize, g_star = self.compute_G_star(data) + self.g_star = g_star + self.ticksize = ticksize + # Compute adjusted midprice from G_star and mid + bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) + mid = (bid + ask) / 2 + G_star = self.g_star + ticksize = self.ticksize + n_spread = self.n_spread + + # ? Compute adjusted midprice + last_row = self.current_dataframe.iloc[-1] + imb = last_row['bs'].astype(float) / (last_row['bs'].astype(float) + last_row['as'].astype(float)) + # Compute bucket of imbalance + imb_bucket = [abs(x - imb) for x in G_star.columns].index(min([abs(x - imb) for x in G_star.columns])) + # Compute and round spread index to nearest ticksize + spreads = G_star[G_star.columns[imb_bucket]].values + spread = last_row['ask'].astype(float) - last_row['bid'].astype(float) + # ? Generally we expect this value to be < self._n_spread so we log when it's > self._n_spread + spread_bucket = round(spread / ticksize) * ticksize // ticksize - 1 + if spread_bucket >= n_spread: + spread_bucket = n_spread - 1 + spread_bucket = int(spread_bucket) + # Compute adjusted midprice + adj_midprice = mid + spreads[spread_bucket] + return round(adj_midprice, self.precision * 2) + + def compute_G_star(self, data): + n_spread = self.n_spread + T, ticksize = self.prep_data_sym(data, self.n_imb, self.dt, n_spread) + imb = np.linspace(0, 1, self.n_imb) + G1, B = self.estimate(T, n_spread, self.n_imb) + # Calculate G1 then B^6*G1 + G2 = np.dot(B, G1) + G1 + G3 = G2 + np.dot(np.dot(B, B), G1) + G4 = G3 + np.dot(np.dot(np.dot(B, B), B), G1) + G5 = G4 + np.dot(np.dot(np.dot(np.dot(B, B), B), B), G1) + G6 = G5 + np.dot(np.dot(np.dot(np.dot(np.dot(B, B), B), B), B), G1) + # Reorganize G6 into buckets + index = [str(i + 1) for i in range(0, n_spread)] + G_star = pd.DataFrame(G6.reshape(n_spread, self.n_imb), index=index, columns=imb) + return ticksize, G_star + + def G_star_invalid(self, G_star, ticksize): + # Check if any values of G_star > ticksize/2 + if np.any(G_star > ticksize / 2): + return True + # Check if any values of G_star < -ticksize/2 + if np.any(G_star < -ticksize / 2): + return True + # Round middle values of G_star to self.precision and check if any values are 0 + if np.any(np.round(G_star.iloc[int(self.n_imb / 2)], self.precision) == 0): + return True + return False + + def estimate(self, T, n_spread, n_imb): + no_move = T[T['dM'] == 0] + no_move_counts = no_move.pivot_table(index=['next_imb_bucket'], + columns=['spread', 'imb_bucket'], + values='time', + fill_value=0, + aggfunc='count').unstack() + Q_counts = np.resize(np.array(no_move_counts[0:(n_imb * n_imb)]), (n_imb, n_imb)) + # loop over all spreads and add block matrices + for i in range(1, n_spread): + Qi = np.resize(np.array(no_move_counts[(i * n_imb * n_imb):(i + 1) * (n_imb * n_imb)]), (n_imb, n_imb)) + Q_counts = block_diag(Q_counts, Qi) + move_counts = T[(T['dM'] != 0)].pivot_table(index=['dM'], + columns=['spread', 'imb_bucket'], + values='time', + fill_value=0, + aggfunc='count').unstack() + + R_counts = np.resize(np.array(move_counts), (n_imb * n_spread, 4)) + T1 = np.concatenate((Q_counts, R_counts), axis=1).astype(float) + for i in range(0, n_imb * n_spread): + T1[i] = T1[i] / T1[i].sum() + Q = T1[:, 0:(n_imb * n_spread)] + R1 = T1[:, (n_imb * n_spread):] + + K = np.array([-0.01, -0.005, 0.005, 0.01]) + move_counts = T[(T['dM'] != 0)].pivot_table(index=['spread', 'imb_bucket'], + columns=['next_spread', 'next_imb_bucket'], + values='time', + fill_value=0, + aggfunc='count') + + R2_counts = np.resize(np.array(move_counts), (n_imb * n_spread, n_imb * n_spread)) + T2 = np.concatenate((Q_counts, R2_counts), axis=1).astype(float) + + for i in range(0, n_imb * n_spread): + T2[i] = T2[i] / T2[i].sum() + R2 = T2[:, (n_imb * n_spread):] + G1 = np.dot(np.dot(np.linalg.inv(np.eye(n_imb * n_spread) - Q), R1), K) + B = np.dot(np.linalg.inv(np.eye(n_imb * n_spread) - Q), R2) + return G1, B + + def compute_n_spread(self, T=None): + if not T: + T = self.get_df() + spread = T.ask - T.bid + spread_counts = spread.value_counts() + return len(spread_counts[spread_counts > self.data_size_min]) + + def prep_data_sym(self, T, n_imb, dt, n_spread): + spread = T.ask - T.bid + ticksize = np.round(min(spread.loc[spread > 0]) * 100) / 100 + # T.spread=T.ask-T.bid + # adds the spread and mid prices + T['spread'] = np.round((T['ask'] - T['bid']) / ticksize) * ticksize + T['mid'] = (T['bid'] + T['ask']) / 2 + # filter out spreads >= n_spread + T = T.loc[(T.spread <= n_spread * ticksize) & (T.spread > 0)] + T['imb'] = T['bs'] / (T['bs'] + T['as']) + # discretize imbalance into percentiles + T['imb_bucket'] = pd.qcut(T['imb'], n_imb, labels=False, duplicates='drop') + T['next_mid'] = T['mid'].shift(-dt) + # step ahead state variables + T['next_spread'] = T['spread'].shift(-dt) + T['next_time'] = T['time'].shift(-dt) + T['next_imb_bucket'] = T['imb_bucket'].shift(-dt) + # step ahead change in price + T['dM'] = np.round((T['next_mid'] - T['mid']) / ticksize * 2) * ticksize / 2 + T = T.loc[(T.dM <= ticksize * 1.1) & (T.dM >= -ticksize * 1.1)] + # symetrize data + T2 = T.copy(deep=True) + T2['imb_bucket'] = n_imb - 1 - T2['imb_bucket'] + T2['next_imb_bucket'] = n_imb - 1 - T2['next_imb_bucket'] + T2['dM'] = -T2['dM'] + T2['mid'] = -T2['mid'] + T3 = pd.concat([T, T2]) + T3.index = pd.RangeIndex(len(T3.index)) + return T3, ticksize + + def get_df(self): + csv_path = self.get_csv_path() + try: + df = pd.read_csv(csv_path, index_col=[0]) + df = df.append(self.current_dataframe) + except Exception as e: + self.logger().info(e) + df = self.current_dataframe + + df['time'] = df['time'].astype(float) + df['bid'] = df['bid'].astype(float) + df['ask'] = df['ask'].astype(float) + df['bs'] = df['bs'].astype(float) + df['as'] = df['as'].astype(float) + df['mid'] = (df['bid'] + df['ask']) / float(2) + df['imb'] = df['bs'] / (df['bs'] + df['as']) + return df + + def compute_imbalance(self) -> Decimal: + if self.get_df().empty or self.current_dataframe.empty: + self.logger().info('No data to compute imbalance, recording data') + self.recording_data = True + return Decimal(-1) + bid_size = self.current_dataframe['bs'].sum() + ask_size = self.current_dataframe['as'].sum() + return round(Decimal(bid_size) / Decimal(bid_size + ask_size), self.precision * 2) + + # ! Format status methods + def get_price_line(self) -> str: + # Get best bid and ask + bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) + # Mid price is center of line + price_line = int(self.price_line_width / 2) * '-' + '|' + int(self.price_line_width / 2) * '-' + # Add bid, adjusted midprice, + bid_offset = int(self.price_line_width / 2 - len(str(bid)) - (len(str(self.compute_adjusted_midprice())) / 2)) + ask_offset = int(self.price_line_width / 2 - len(str(ask)) - (len(str(self.compute_adjusted_midprice())) / 2)) + labels = str(bid) + bid_offset * ' ' + str(self.compute_adjusted_midprice()) + ask_offset * ' ' + str(ask) + '\n' + # Create microprice of size 'price_line_width' with ends best bid and ask + mid = (bid + ask) / 2 + spread = ask - bid + microprice_adjustment = self.compute_adjusted_midprice() - mid + (spread / 2) + adjusted_midprice_i = int(microprice_adjustment / spread * self.price_line_width) + 1 + price_line = price_line[:adjusted_midprice_i] + 'm' + price_line[adjusted_midprice_i:] + return labels + price_line + + def get_imbalance_line(self) -> str: + imb_line = int(self.price_line_width / 2) * '-' + '|' + int(self.price_line_width / 2) * '-' + imb_line = imb_line[:int(self.compute_imbalance() * self.price_line_width)] + 'i' + imb_line[int(self.compute_imbalance() * self.price_line_width):] + return imb_line diff --git a/scripts/archived_scripts/community_scripts/simple_rsi_example.py b/scripts/archived_scripts/community_scripts/simple_rsi_example.py new file mode 100644 index 0000000..7f8fa59 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/simple_rsi_example.py @@ -0,0 +1,259 @@ +import math +import os +from decimal import Decimal +from typing import Optional + +import pandas as pd + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent, OrderFilledEvent +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class SimpleRSIScript(ScriptStrategyBase): + """ + The strategy is to buy on overbought signal and sell on oversold. + """ + connector_name = os.getenv("CONNECTOR_NAME", "binance_paper_trade") + base = os.getenv("BASE", "BTC") + quote = os.getenv("QUOTE", "USDT") + timeframe = os.getenv("TIMEFRAME", "1s") + + position_amount_usd = Decimal(os.getenv("POSITION_AMOUNT_USD", "50")) + + rsi_length = int(os.getenv("RSI_LENGTH", "14")) + + # If true - uses Exponential Moving Average, if false - Simple Moving Average. + rsi_is_ema = os.getenv("RSI_IS_EMA", 'True').lower() in ('true', '1', 't') + + buy_rsi = int(os.getenv("BUY_RSI", "30")) + sell_rsi = int(os.getenv("SELL_RSI", "70")) + + # It depends on a timeframe. Make sure you have enough trades to calculate rsi_length number of candlesticks. + trade_count_limit = int(os.getenv("TRADE_COUNT_LIMIT", "100000")) + + trading_pair = combine_to_hb_trading_pair(base, quote) + markets = {connector_name: {trading_pair}} + + subscribed_to_order_book_trade_event: bool = False + position: Optional[OrderFilledEvent] = None + + _trades: 'list[OrderBookTradeEvent]' = [] + _cumulative_price_change_pct = Decimal(0) + _filling_position: bool = False + + def on_tick(self): + """ + On every tick calculate OHLCV candlesticks, calculate RSI, react on overbought or oversold signal with creating, + adjusting and sending an order. + """ + if not self.subscribed_to_order_book_trade_event: + # Set pandas resample rule for a timeframe + self._set_resample_rule(self.timeframe) + self.subscribe_to_order_book_trade_event() + elif len(self._trades) > 0: + df = self.calculate_candlesticks() + df = self.calculate_rsi(df, self.rsi_length, self.rsi_is_ema) + should_open_position = self.should_open_position(df) + should_close_position = self.should_close_position(df) + if should_open_position or should_close_position: + order_side = TradeType.BUY if should_open_position else TradeType.SELL + order_candidate = self.create_order_candidate(order_side) + # Adjust OrderCandidate + order_adjusted = self.connectors[self.connector_name].budget_checker.adjust_candidate(order_candidate, all_or_none=False) + if math.isclose(order_adjusted.amount, Decimal("0"), rel_tol=1E-5): + self.logger().info(f"Order adjusted: {order_adjusted.amount}, too low to place an order") + else: + self.send_order(order_adjusted) + else: + self._rsi = df.iloc[-1]['rsi'] + self.logger().info(f"RSI is {self._rsi:.0f}") + + def _set_resample_rule(self, timeframe): + """ + Convert timeframe to pandas resample rule value. + https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html + """ + timeframe_to_rule = { + "1s": "1S", + "10s": "10S", + "30s": "30S", + "1m": "1T", + "15m": "15T" + } + if timeframe not in timeframe_to_rule.keys(): + self.logger().error(f"{timeframe} timeframe is not mapped to resample rule.") + HummingbotApplication.main_application().stop() + self._resample_rule = timeframe_to_rule[timeframe] + + def should_open_position(self, df: pd.DataFrame) -> bool: + """ + If overbought and not in the position. + """ + rsi: float = df.iloc[-1]['rsi'] + rsi_is_calculated = pd.notna(rsi) + time_to_buy = rsi_is_calculated and rsi <= self.buy_rsi + can_buy = self.position is None and not self._filling_position + return can_buy and time_to_buy + + def should_close_position(self, df: pd.DataFrame) -> bool: + """ + If oversold and in the position. + """ + rsi: float = df.iloc[-1]['rsi'] + rsi_is_calculated = pd.notna(rsi) + time_to_sell = rsi_is_calculated and rsi >= self.sell_rsi + can_sell = self.position is not None and not self._filling_position + return can_sell and time_to_sell + + def create_order_candidate(self, order_side: bool) -> OrderCandidate: + """ + Create and quantize order candidate. + """ + connector: ConnectorBase = self.connectors[self.connector_name] + is_buy = order_side == TradeType.BUY + price = connector.get_price(self.trading_pair, is_buy) + if is_buy: + conversion_rate = RateOracle.get_instance().get_pair_rate(self.trading_pair) + amount = self.position_amount_usd / conversion_rate + else: + amount = self.position.amount + + amount = connector.quantize_order_amount(self.trading_pair, amount) + price = connector.quantize_order_price(self.trading_pair, price) + return OrderCandidate( + trading_pair=self.trading_pair, + is_maker = False, + order_type = OrderType.LIMIT, + order_side = order_side, + amount = amount, + price = price) + + def send_order(self, order: OrderCandidate): + """ + Send order to the exchange, indicate that position is filling, and send log message with a trade. + """ + is_buy = order.order_side == TradeType.BUY + place_order = self.buy if is_buy else self.sell + place_order( + connector_name=self.connector_name, + trading_pair=self.trading_pair, + amount=order.amount, + order_type=order.order_type, + price=order.price + ) + self._filling_position = True + if is_buy: + msg = f"RSI is below {self.buy_rsi:.2f}, buying {order.amount:.5f} {self.base} with limit order at {order.price:.2f} ." + else: + msg = (f"RSI is above {self.sell_rsi:.2f}, selling {self.position.amount:.5f} {self.base}" + f" with limit order at ~ {order.price:.2f}, entry price was {self.position.price:.2f}.") + self.notify_hb_app_with_timestamp(msg) + self.logger().info(msg) + + def calculate_candlesticks(self) -> pd.DataFrame: + """ + Convert raw trades to OHLCV dataframe. + """ + df = pd.DataFrame(self._trades) + df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s') + df["timestamp"] = pd.to_datetime(df["timestamp"]) + df.drop(columns=[df.columns[0]], axis=1, inplace=True) + df = df.set_index('timestamp') + df = df.resample(self._resample_rule).agg({ + 'price': ['first', 'max', 'min', 'last'], + 'amount': 'sum', + }) + df.columns = df.columns.to_flat_index().map(lambda x: x[1]) + df.rename(columns={'first': 'open', 'max': 'high', 'min': 'low', 'last': 'close', 'sum': 'volume'}, inplace=True) + return df + + def did_fill_order(self, event: OrderFilledEvent): + """ + Indicate that position is filled, save position properties on enter, calculate cumulative price change on exit. + """ + if event.trade_type == TradeType.BUY: + self.position = event + self._filling_position = False + elif event.trade_type == TradeType.SELL: + delta_price = (event.price - self.position.price) / self.position.price + self._cumulative_price_change_pct += delta_price + self.position = None + self._filling_position = False + else: + self.logger().warn(f"Unsupported order type filled: {event.trade_type}") + + @staticmethod + def calculate_rsi(df: pd.DataFrame, length: int = 14, is_ema: bool = True): + """ + Calculate relative strength index and add it to the dataframe. + """ + close_delta = df['close'].diff() + up = close_delta.clip(lower=0) + down = close_delta.clip(upper=0).abs() + + if is_ema: + # Exponential Moving Average + ma_up = up.ewm(com = length - 1, adjust=True, min_periods = length).mean() + ma_down = down.ewm(com = length - 1, adjust=True, min_periods = length).mean() + else: + # Simple Moving Average + ma_up = up.rolling(window = length, adjust=False).mean() + ma_down = down.rolling(window = length, adjust=False).mean() + + rs = ma_up / ma_down + df["rsi"] = 100 - (100 / (1 + rs)) + return df + + def subscribe_to_order_book_trade_event(self): + """ + Subscribe to raw trade event. + """ + self.order_book_trade_event = SourceInfoEventForwarder(self._process_public_trade) + for market in self.connectors.values(): + for order_book in market.order_books.values(): + order_book.add_listener(OrderBookEvent.TradeEvent, self.order_book_trade_event) + self.subscribed_to_order_book_trade_event = True + + def _process_public_trade(self, event_tag: int, market: ConnectorBase, event: OrderBookTradeEvent): + """ + Add new trade to list, remove old trade event, if count greater than trade_count_limit. + """ + if len(self._trades) >= self.trade_count_limit: + self._trades.pop(0) + self._trades.append(event) + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + # Strategy specific info + lines.extend(["", " Current RSI:"] + [" " + f"{self._rsi:.0f}"]) + lines.extend(["", " Simple RSI strategy total price change with all trades:"] + [" " + f"{self._cumulative_price_change_pct:.5f}" + " %"]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) diff --git a/scripts/archived_scripts/community_scripts/spot_perp_arb.py b/scripts/archived_scripts/community_scripts/spot_perp_arb.py new file mode 100644 index 0000000..65b4762 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/spot_perp_arb.py @@ -0,0 +1,492 @@ +from csv import writer as csv_writer +from datetime import datetime +from decimal import Decimal +from enum import Enum +from typing import Dict, List + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode +from hummingbot.core.event.events import BuyOrderCompletedEvent, PositionModeChangeEvent, SellOrderCompletedEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class StrategyState(Enum): + Closed = 0 # static state + Opening = 1 # in flight state + Opened = 2 # static state + Closing = 3 # in flight state + + +class StrategyAction(Enum): + NULL = 0 + BUY_SPOT_SHORT_PERP = 1 + SELL_SPOT_LONG_PERP = 2 + + +# TODO: handle corner cases -- spot price and perp price never cross again after position is opened +class SpotPerpArb(ScriptStrategyBase): + """ + PRECHECK: + 1. enough base and quote balance in spot (base is optional if you do one side only), enough quote balance in perp + 2. better to empty your position in perp + 3. check you have set one way mode (instead of hedge mode) in your futures account + + REFERENCE: hummingbot/strategy/spot_perpetual_arbitrage + """ + + spot_connector = "kucoin" + perp_connector = "kucoin_perpetual" + trading_pair = "HIGH-USDT" + markets = {spot_connector: {trading_pair}, perp_connector: {trading_pair}} + + leverage = 2 + is_position_mode_ready = False + + base_order_amount = Decimal("0.1") + buy_spot_short_perp_profit_margin_bps = 100 + sell_spot_long_perp_profit_margin_bps = 100 + # buffer to account for slippage when placing limit taker orders + slippage_buffer_bps = 15 + + strategy_state = StrategyState.Closed + last_strategy_action = StrategyAction.NULL + completed_order_ids = [] + next_arbitrage_opening_ts = 0 + next_arbitrage_opening_delay = 10 + in_flight_state_start_ts = 0 + in_flight_state_tolerance = 60 + opened_state_start_ts = 0 + opened_state_tolerance = 60 * 60 * 2 + + # write order book csv + order_book_csv = f"./data/spot_perp_arb_order_book_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.csv" + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.set_leverage() + self.init_order_book_csv() + + def set_leverage(self) -> None: + perp_connector = self.connectors[self.perp_connector] + perp_connector.set_position_mode(PositionMode.ONEWAY) + perp_connector.set_leverage( + trading_pair=self.trading_pair, leverage=self.leverage + ) + self.logger().info( + f"Setting leverage to {self.leverage}x for {self.perp_connector} on {self.trading_pair}" + ) + + def init_order_book_csv(self) -> None: + self.logger().info("Preparing order book csv...") + with open(self.order_book_csv, "a") as f_object: + writer = csv_writer(f_object) + writer.writerow( + [ + "timestamp", + "spot_exchange", + "perp_exchange", + "spot_best_bid", + "spot_best_ask", + "perp_best_bid", + "perp_best_ask", + ] + ) + self.logger().info(f"Order book csv created: {self.order_book_csv}") + + def append_order_book_csv(self) -> None: + spot_best_bid_price = self.connectors[self.spot_connector].get_price( + self.trading_pair, False + ) + spot_best_ask_price = self.connectors[self.spot_connector].get_price( + self.trading_pair, True + ) + perp_best_bid_price = self.connectors[self.perp_connector].get_price( + self.trading_pair, False + ) + perp_best_ask_price = self.connectors[self.perp_connector].get_price( + self.trading_pair, True + ) + row = [ + str(self.current_timestamp), + self.spot_connector, + self.perp_connector, + str(spot_best_bid_price), + str(spot_best_ask_price), + str(perp_best_bid_price), + str(perp_best_ask_price), + ] + with open(self.order_book_csv, "a", newline="") as f_object: + writer = csv_writer(f_object) + writer.writerow(row) + self.logger().info(f"Order book csv updated: {self.order_book_csv}") + return + + def on_tick(self) -> None: + # precheck before running any trading logic + if not self.is_position_mode_ready: + return + + self.append_order_book_csv() + + # skip if orders are pending for completion + self.update_in_flight_state() + if self.strategy_state in (StrategyState.Opening, StrategyState.Closing): + if ( + self.current_timestamp + > self.in_flight_state_start_ts + self.in_flight_state_tolerance + ): + self.logger().warning( + "Orders has been submitted but not completed yet " + f"for more than {self.in_flight_state_tolerance} seconds. Please check your orders!" + ) + return + + # skip if its still in buffer time before next arbitrage opportunity + if ( + self.strategy_state == StrategyState.Closed + and self.current_timestamp < self.next_arbitrage_opening_ts + ): + return + + # flag out if position waits too long without any sign of closing + if ( + self.strategy_state == StrategyState.Opened + and self.current_timestamp + > self.opened_state_start_ts + self.opened_state_tolerance + ): + self.logger().warning( + f"Position has been opened for more than {self.opened_state_tolerance} seconds without any sign of closing. " + "Consider undoing the position manually or lower the profitability margin." + ) + + # TODO: change to async on order execution + # find opportunity and trade + if self.should_buy_spot_short_perp() and self.can_buy_spot_short_perp(): + self.update_static_state() + self.last_strategy_action = StrategyAction.BUY_SPOT_SHORT_PERP + self.buy_spot_short_perp() + elif self.should_sell_spot_long_perp() and self.can_sell_spot_long_perp(): + self.update_static_state() + self.last_strategy_action = StrategyAction.SELL_SPOT_LONG_PERP + self.sell_spot_long_perp() + + def update_in_flight_state(self) -> None: + if ( + self.strategy_state == StrategyState.Opening + and len(self.completed_order_ids) == 2 + ): + self.strategy_state = StrategyState.Opened + self.logger().info( + f"Position is opened with order_ids: {self.completed_order_ids}. " + "Changed the state from Opening to Opened." + ) + self.completed_order_ids.clear() + self.opened_state_start_ts = self.current_timestamp + elif ( + self.strategy_state == StrategyState.Closing + and len(self.completed_order_ids) == 2 + ): + self.strategy_state = StrategyState.Closed + self.next_arbitrage_opening_ts = ( + self.current_timestamp + self.next_arbitrage_opening_ts + ) + self.logger().info( + f"Position is closed with order_ids: {self.completed_order_ids}. " + "Changed the state from Closing to Closed.\n" + f"No arbitrage opportunity will be opened before {self.next_arbitrage_opening_ts}. " + f"(Current timestamp: {self.current_timestamp})" + ) + self.completed_order_ids.clear() + return + + def update_static_state(self) -> None: + if self.strategy_state == StrategyState.Closed: + self.strategy_state = StrategyState.Opening + self.logger().info("The state changed from Closed to Opening") + elif self.strategy_state == StrategyState.Opened: + self.strategy_state = StrategyState.Closing + self.logger().info("The state changed from Opened to Closing") + self.in_flight_state_start_ts = self.current_timestamp + return + + def should_buy_spot_short_perp(self) -> bool: + spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) + perp_sell_price = self.limit_taker_price(self.perp_connector, is_buy=False) + ret_pbs = float((perp_sell_price - spot_buy_price) / spot_buy_price) * 10000 + is_profitable = ret_pbs >= self.buy_spot_short_perp_profit_margin_bps + is_repeat = self.last_strategy_action == StrategyAction.BUY_SPOT_SHORT_PERP + return is_profitable and not is_repeat + + # TODO: check if balance is deducted when it has position + def can_buy_spot_short_perp(self) -> bool: + spot_balance = self.get_balance(self.spot_connector, is_base=False) + buy_price_with_slippage = self.limit_taker_price_with_slippage( + self.spot_connector, is_buy=True + ) + spot_required = buy_price_with_slippage * self.base_order_amount + is_spot_enough = Decimal(spot_balance) >= spot_required + if not is_spot_enough: + _, quote = split_hb_trading_pair(self.trading_pair) + float_spot_required = float(spot_required) + self.logger().info( + f"Insufficient balance in {self.spot_connector}: {spot_balance} {quote}. " + f"Required {float_spot_required:.4f} {quote}." + ) + perp_balance = self.get_balance(self.perp_connector, is_base=False) + # short order WITHOUT any splippage takes more capital + short_price = self.limit_taker_price(self.perp_connector, is_buy=False) + perp_required = short_price * self.base_order_amount + is_perp_enough = Decimal(perp_balance) >= perp_required + if not is_perp_enough: + _, quote = split_hb_trading_pair(self.trading_pair) + float_perp_required = float(perp_required) + self.logger().info( + f"Insufficient balance in {self.perp_connector}: {perp_balance:.4f} {quote}. " + f"Required {float_perp_required:.4f} {quote}." + ) + return is_spot_enough and is_perp_enough + + # TODO: use OrderCandidate and check for budget + def buy_spot_short_perp(self) -> None: + spot_buy_price_with_slippage = self.limit_taker_price_with_slippage( + self.spot_connector, is_buy=True + ) + perp_short_price_with_slippage = self.limit_taker_price_with_slippage( + self.perp_connector, is_buy=False + ) + spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) + perp_short_price = self.limit_taker_price(self.perp_connector, is_buy=False) + + self.buy( + self.spot_connector, + self.trading_pair, + amount=self.base_order_amount, + order_type=OrderType.LIMIT, + price=spot_buy_price_with_slippage, + ) + trade_state_log = self.trade_state_log() + + self.logger().info( + f"Submitted buy order in {self.spot_connector} for {self.trading_pair} " + f"at price {spot_buy_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Buy price without slippage: {spot_buy_price})" + ) + position_action = self.perp_trade_position_action() + self.sell( + self.perp_connector, + self.trading_pair, + amount=self.base_order_amount, + order_type=OrderType.LIMIT, + price=perp_short_price_with_slippage, + position_action=position_action, + ) + self.logger().info( + f"Submitted short order in {self.perp_connector} for {self.trading_pair} " + f"at price {perp_short_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Short price without slippage: {perp_short_price})" + ) + + self.opened_state_start_ts = self.current_timestamp + return + + def should_sell_spot_long_perp(self) -> bool: + spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) + perp_buy_price = self.limit_taker_price(self.perp_connector, is_buy=True) + ret_pbs = float((spot_sell_price - perp_buy_price) / perp_buy_price) * 10000 + is_profitable = ret_pbs >= self.sell_spot_long_perp_profit_margin_bps + is_repeat = self.last_strategy_action == StrategyAction.SELL_SPOT_LONG_PERP + return is_profitable and not is_repeat + + def can_sell_spot_long_perp(self) -> bool: + spot_balance = self.get_balance(self.spot_connector, is_base=True) + spot_required = self.base_order_amount + is_spot_enough = Decimal(spot_balance) >= spot_required + if not is_spot_enough: + base, _ = split_hb_trading_pair(self.trading_pair) + float_spot_required = float(spot_required) + self.logger().info( + f"Insufficient balance in {self.spot_connector}: {spot_balance} {base}. " + f"Required {float_spot_required:.4f} {base}." + ) + perp_balance = self.get_balance(self.perp_connector, is_base=False) + # long order WITH any splippage takes more capital + long_price_with_slippage = self.limit_taker_price( + self.perp_connector, is_buy=True + ) + perp_required = long_price_with_slippage * self.base_order_amount + is_perp_enough = Decimal(perp_balance) >= perp_required + if not is_perp_enough: + _, quote = split_hb_trading_pair(self.trading_pair) + float_perp_required = float(perp_required) + self.logger().info( + f"Insufficient balance in {self.perp_connector}: {perp_balance:.4f} {quote}. " + f"Required {float_perp_required:.4f} {quote}." + ) + return is_spot_enough and is_perp_enough + + def sell_spot_long_perp(self) -> None: + perp_long_price_with_slippage = self.limit_taker_price_with_slippage( + self.perp_connector, is_buy=True + ) + spot_sell_price_with_slippage = self.limit_taker_price_with_slippage( + self.spot_connector, is_buy=False + ) + perp_long_price = self.limit_taker_price(self.perp_connector, is_buy=True) + spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) + + position_action = self.perp_trade_position_action() + self.buy( + self.perp_connector, + self.trading_pair, + amount=self.base_order_amount, + order_type=OrderType.LIMIT, + price=perp_long_price_with_slippage, + position_action=position_action, + ) + trade_state_log = self.trade_state_log() + self.logger().info( + f"Submitted long order in {self.perp_connector} for {self.trading_pair} " + f"at price {perp_long_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Long price without slippage: {perp_long_price})" + ) + self.sell( + self.spot_connector, + self.trading_pair, + amount=self.base_order_amount, + order_type=OrderType.LIMIT, + price=spot_sell_price_with_slippage, + ) + self.logger().info( + f"Submitted sell order in {self.spot_connector} for {self.trading_pair} " + f"at price {spot_sell_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Sell price without slippage: {spot_sell_price})" + ) + + self.opened_state_start_ts = self.current_timestamp + return + + def limit_taker_price_with_slippage( + self, connector_name: str, is_buy: bool + ) -> Decimal: + price = self.limit_taker_price(connector_name, is_buy) + slippage = ( + Decimal(1 + self.slippage_buffer_bps / 10000) + if is_buy + else Decimal(1 - self.slippage_buffer_bps / 10000) + ) + return price * slippage + + def limit_taker_price(self, connector_name: str, is_buy: bool) -> Decimal: + limit_taker_price_result = self.connectors[connector_name].get_price_for_volume( + self.trading_pair, is_buy, self.base_order_amount + ) + return limit_taker_price_result.result_price + + def get_balance(self, connector_name: str, is_base: bool) -> float: + if connector_name == self.perp_connector: + assert not is_base, "Perpetual connector does not have base asset" + base, quote = split_hb_trading_pair(self.trading_pair) + balance = self.connectors[connector_name].get_available_balance( + base if is_base else quote + ) + return float(balance) + + def trade_state_log(self) -> str: + if self.strategy_state == StrategyState.Opening: + return "open position" + elif self.strategy_state == StrategyState.Closing: + return "close position" + else: + raise ValueError( + f"Strategy state: {self.strategy_state} shouldnt happen during trade." + ) + + def perp_trade_position_action(self) -> PositionAction: + if self.strategy_state == StrategyState.Opening: + return PositionAction.OPEN + elif self.strategy_state == StrategyState.Closing: + return PositionAction.CLOSE + else: + raise ValueError( + f"Strategy state: {self.strategy_state} shouldnt happen during trade." + ) + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + + lines: List[str] = [] + self._append_buy_spot_short_perp_status(lines) + lines.extend(["", ""]) + self._append_sell_spot_long_perp_status(lines) + lines.extend(["", ""]) + self._append_balances_status(lines) + lines.extend(["", ""]) + self._append_bot_states(lines) + lines.extend(["", ""]) + return "\n".join(lines) + + def _append_buy_spot_short_perp_status(self, lines: List[str]) -> None: + spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) + perp_short_price = self.limit_taker_price(self.perp_connector, is_buy=False) + return_pbs = ( + float((perp_short_price - spot_buy_price) / spot_buy_price) * 100 * 100 + ) + lines.append(f"Buy Spot Short Perp Opportunity ({self.trading_pair}):") + lines.append(f"Buy Spot: {spot_buy_price}") + lines.append(f"Short Perp: {perp_short_price}") + lines.append(f"Return (bps): {return_pbs:.1f}%") + return + + def _append_sell_spot_long_perp_status(self, lines: List[str]) -> None: + perp_long_price = self.limit_taker_price(self.perp_connector, is_buy=True) + spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) + return_pbs = ( + float((spot_sell_price - perp_long_price) / perp_long_price) * 100 * 100 + ) + lines.append(f"Long Perp Sell Spot Opportunity ({self.trading_pair}):") + lines.append(f"Long Perp: {perp_long_price}") + lines.append(f"Sell Spot: {spot_sell_price}") + lines.append(f"Return (bps): {return_pbs:.1f}%") + return + + def _append_balances_status(self, lines: List[str]) -> None: + base, quote = split_hb_trading_pair(self.trading_pair) + spot_base_balance = self.get_balance(self.spot_connector, is_base=True) + spot_quote_balance = self.get_balance(self.spot_connector, is_base=False) + perp_quote_balance = self.get_balance(self.perp_connector, is_base=False) + lines.append("Balances:") + lines.append(f"Spot Base Balance: {spot_base_balance:.04f} {base}") + lines.append(f"Spot Quote Balance: {spot_quote_balance:.04f} {quote}") + lines.append(f"Perp Balance: {perp_quote_balance:04f} USDT") + return + + def _append_bot_states(self, lines: List[str]) -> None: + lines.append("Bot States:") + lines.append(f"Current Timestamp: {self.current_timestamp}") + lines.append(f"Strategy State: {self.strategy_state.name}") + lines.append(f"Open Next Opportunity after: {self.next_arbitrage_opening_ts}") + lines.append(f"Last In Flight State at: {self.in_flight_state_start_ts}") + lines.append(f"Last Opened State at: {self.opened_state_start_ts}") + lines.append(f"Completed Ordered IDs: {self.completed_order_ids}") + return + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent) -> None: + self.completed_order_ids.append(event.order_id) + + def did_complete_sell_order(self, event: SellOrderCompletedEvent) -> None: + self.completed_order_ids.append(event.order_id) + + def did_change_position_mode_succeed(self, _): + self.logger().info( + f"Completed setting position mode to ONEWAY for {self.perp_connector}" + ) + self.is_position_mode_ready = True + + def did_change_position_mode_fail( + self, position_mode_changed_event: PositionModeChangeEvent + ): + self.logger().error( + "Failed to set position mode to ONEWAY. " + f"Reason: {position_mode_changed_event.message}." + ) + self.logger().warning( + "Cannot continue. Please resolve the issue in the account." + ) diff --git a/scripts/archived_scripts/community_scripts/triangular_arbitrage.py b/scripts/archived_scripts/community_scripts/triangular_arbitrage.py new file mode 100644 index 0000000..6f6186b --- /dev/null +++ b/scripts/archived_scripts/community_scripts/triangular_arbitrage.py @@ -0,0 +1,456 @@ +import logging +import math + +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase + + +class TriangularArbitrage(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Triangular-Arbitrage-07ef29ee97d749e1afa798a024813c88 + Video: https://www.loom.com/share/b6781130251945d4b51d6de3f8434047 + Description: + This script executes arbitrage trades on 3 markets of the same exchange when a price discrepancy + among those markets found. + + - All orders are executed linearly. That is the second order is placed after the first one is + completely filled and the third order is placed after the second. + - The script allows you to hold mainly one asset in your inventory (holding_asset). + - It always starts trades round by selling the holding asset and ends by buying it. + - There are 2 possible arbitrage trades directions: "direct" and "reverse". + Example with USDT holding asset: + 1. Direct: buy ADA-USDT > sell ADA-BTC > sell BTC-USDT + 2. Reverse: buy BTC-USDT > buy ADA-BTC > sell ADA-USDT + - The order amount is fixed and set in holding asset + - The strategy has 2nd and 3d orders creation check and makes several trials if there is a failure + - Profit is calculated each round and total profit is checked for the kill_switch to prevent from excessive losses + - !!! Profitability calculation doesn't take into account trading fees, set min_profitability to at least 3 * fee + """ + # Config params + connector_name: str = "kucoin" + first_pair: str = "ADA-USDT" + second_pair: str = "ADA-BTC" + third_pair: str = "BTC-USDT" + holding_asset: str = "USDT" + + min_profitability: Decimal = Decimal("0.5") + order_amount_in_holding_asset: Decimal = Decimal("20") + + kill_switch_enabled: bool = True + kill_switch_rate = Decimal("-2") + + # Class params + status: str = "NOT_INIT" + trading_pair: dict = {} + order_side: dict = {} + profit: dict = {} + order_amount: dict = {} + profitable_direction: str = "" + place_order_trials_count: int = 0 + place_order_trials_limit: int = 10 + place_order_failure: bool = False + order_candidate = None + initial_spent_amount = Decimal("0") + total_profit = Decimal("0") + total_profit_pct = Decimal("0") + + markets = {connector_name: {first_pair, second_pair, third_pair}} + + @property + def connector(self): + """ + The only connector in this strategy, define it here for easy access + """ + return self.connectors[self.connector_name] + + def on_tick(self): + """ + Every tick the strategy calculates the profitability of both direct and reverse direction. + If the profitability of any direction is large enough it starts the arbitrage by creating and processing + the first order candidate. + """ + if self.status == "NOT_INIT": + self.init_strategy() + + if self.arbitrage_started(): + return + + if not self.ready_for_new_orders(): + return + + self.profit["direct"], self.order_amount["direct"] = self.calculate_profit(self.trading_pair["direct"], + self.order_side["direct"]) + self.profit["reverse"], self.order_amount["reverse"] = self.calculate_profit(self.trading_pair["reverse"], + self.order_side["reverse"]) + self.log_with_clock(logging.INFO, f"Profit direct: {round(self.profit['direct'], 2)}, " + f"Profit reverse: {round(self.profit['reverse'], 2)}") + + if self.profit["direct"] < self.min_profitability and self.profit["reverse"] < self.min_profitability: + return + + self.profitable_direction = "direct" if self.profit["direct"] > self.profit["reverse"] else "reverse" + self.start_arbitrage(self.trading_pair[self.profitable_direction], + self.order_side[self.profitable_direction], + self.order_amount[self.profitable_direction]) + + def init_strategy(self): + """ + Initializes strategy once before the start. + """ + self.status = "ACTIVE" + self.check_trading_pair() + self.set_trading_pair() + self.set_order_side() + + def check_trading_pair(self): + """ + Checks if the pairs specified in the config are suitable for the triangular arbitrage. + They should have only 3 common assets with holding_asset among them. + """ + base_1, quote_1 = split_hb_trading_pair(self.first_pair) + base_2, quote_2 = split_hb_trading_pair(self.second_pair) + base_3, quote_3 = split_hb_trading_pair(self.third_pair) + all_assets = {base_1, base_2, base_3, quote_1, quote_2, quote_3} + if len(all_assets) != 3 or self.holding_asset not in all_assets: + self.status = "NOT_ACTIVE" + self.log_with_clock(logging.WARNING, f"Pairs {self.first_pair}, {self.second_pair}, {self.third_pair} " + f"are not suited for triangular arbitrage!") + + def set_trading_pair(self): + """ + Rearrange trading pairs so that the first and last pair contains holding asset. + We start trading round by selling holding asset and finish by buying it. + Makes 2 tuples for "direct" and "reverse" directions and assigns them to the corresponding dictionary. + """ + if self.holding_asset not in self.first_pair: + pairs_ordered = (self.second_pair, self.first_pair, self.third_pair) + elif self.holding_asset not in self.second_pair: + pairs_ordered = (self.first_pair, self.second_pair, self.third_pair) + else: + pairs_ordered = (self.first_pair, self.third_pair, self.second_pair) + + self.trading_pair["direct"] = pairs_ordered + self.trading_pair["reverse"] = pairs_ordered[::-1] + + def set_order_side(self): + """ + Sets order sides (1 = buy, 0 = sell) for already ordered trading pairs. + Makes 2 tuples for "direct" and "reverse" directions and assigns them to the corresponding dictionary. + """ + base_1, quote_1 = split_hb_trading_pair(self.trading_pair["direct"][0]) + base_2, quote_2 = split_hb_trading_pair(self.trading_pair["direct"][1]) + base_3, quote_3 = split_hb_trading_pair(self.trading_pair["direct"][2]) + + order_side_1 = 0 if base_1 == self.holding_asset else 1 + order_side_2 = 0 if base_1 == base_2 else 1 + order_side_3 = 1 if base_3 == self.holding_asset else 0 + + self.order_side["direct"] = (order_side_1, order_side_2, order_side_3) + self.order_side["reverse"] = (1 - order_side_3, 1 - order_side_2, 1 - order_side_1) + + def arbitrage_started(self) -> bool: + """ + Checks for an unfinished arbitrage round. + If there is a failure in placing 2nd or 3d order tries to place an order again + until place_order_trials_limit reached. + """ + if self.status == "ARBITRAGE_STARTED": + if self.order_candidate and self.place_order_failure: + if self.place_order_trials_count <= self.place_order_trials_limit: + self.log_with_clock(logging.INFO, f"Failed to place {self.order_candidate.trading_pair} " + f"{self.order_candidate.order_side} order. Trying again!") + self.process_candidate(self.order_candidate, True) + else: + msg = f"Error placing {self.order_candidate.trading_pair} {self.order_candidate.order_side} order" + self.notify_hb_app_with_timestamp(msg) + self.log_with_clock(logging.WARNING, msg) + self.status = "NOT_ACTIVE" + return True + + return False + + def ready_for_new_orders(self) -> bool: + """ + Checks if we are ready for new orders: + - Current status check + - Holding asset balance check + Return boolean True if we are ready and False otherwise + """ + if self.status == "NOT_ACTIVE": + return False + + if self.connector.get_available_balance(self.holding_asset) < self.order_amount_in_holding_asset: + self.log_with_clock(logging.INFO, + f"{self.connector_name} {self.holding_asset} balance is too low. Cannot place order.") + return False + + return True + + def calculate_profit(self, trading_pair, order_side): + """ + Calculates profitability and order amounts for 3 trading pairs based on the orderbook depth. + """ + exchanged_amount = self.order_amount_in_holding_asset + order_amount = [0, 0, 0] + + for i in range(3): + order_amount[i] = self.get_order_amount_from_exchanged_amount(trading_pair[i], order_side[i], + exchanged_amount) + # Update exchanged_amount for the next cycle + if order_side[i]: + exchanged_amount = order_amount[i] + else: + exchanged_amount = self.connector.get_quote_volume_for_base_amount(trading_pair[i], order_side[i], + order_amount[i]).result_volume + start_amount = self.order_amount_in_holding_asset + end_amount = exchanged_amount + profit = (end_amount / start_amount - 1) * 100 + + return profit, order_amount + + def get_order_amount_from_exchanged_amount(self, pair, side, exchanged_amount) -> Decimal: + """ + Calculates order amount using the amount that we want to exchange. + - If the side is buy then exchanged asset is a quote asset. Get base amount using the orderbook + - If the side is sell then exchanged asset is a base asset. + """ + if side: + orderbook = self.connector.get_order_book(pair) + order_amount = self.get_base_amount_for_quote_volume(orderbook.ask_entries(), exchanged_amount) + else: + order_amount = exchanged_amount + + return order_amount + + def get_base_amount_for_quote_volume(self, orderbook_entries, quote_volume) -> Decimal: + """ + Calculates base amount that you get for the quote volume using the orderbook entries + """ + cumulative_volume = 0. + cumulative_base_amount = 0. + quote_volume = float(quote_volume) + + for order_book_row in orderbook_entries: + row_amount = order_book_row.amount + row_price = order_book_row.price + row_volume = row_amount * row_price + if row_volume + cumulative_volume >= quote_volume: + row_volume = quote_volume - cumulative_volume + row_amount = row_volume / row_price + cumulative_volume += row_volume + cumulative_base_amount += row_amount + if cumulative_volume >= quote_volume: + break + + return Decimal(cumulative_base_amount) + + def start_arbitrage(self, trading_pair, order_side, order_amount): + """ + Starts arbitrage by creating and processing the first order candidate + """ + first_candidate = self.create_order_candidate(trading_pair[0], order_side[0], order_amount[0]) + if first_candidate: + if self.process_candidate(first_candidate, False): + self.status = "ARBITRAGE_STARTED" + + def create_order_candidate(self, pair, side, amount): + """ + Creates order candidate. Checks the quantized amount + """ + side = TradeType.BUY if side else TradeType.SELL + price = self.connector.get_price_for_volume(pair, side, amount).result_price + price_quantize = self.connector.quantize_order_price(pair, Decimal(price)) + amount_quantize = self.connector.quantize_order_amount(pair, Decimal(amount)) + + if amount_quantize == Decimal("0"): + self.log_with_clock(logging.INFO, f"Order amount on {pair} is too low to place an order") + return None + + return OrderCandidate( + trading_pair=pair, + is_maker=False, + order_type=OrderType.MARKET, + order_side=side, + amount=amount_quantize, + price=price_quantize) + + def process_candidate(self, order_candidate, multiple_trials_enabled) -> bool: + """ + Checks order candidate balance and either places an order or sets a failure for the next trials + """ + order_candidate_adjusted = self.connector.budget_checker.adjust_candidate(order_candidate, all_or_none=True) + if math.isclose(order_candidate.amount, Decimal("0"), rel_tol=1E-6): + self.logger().info(f"Order adjusted amount: {order_candidate.amount} on {order_candidate.trading_pair}, " + f"too low to place an order") + if multiple_trials_enabled: + self.place_order_trials_count += 1 + self.place_order_failure = True + return False + else: + is_buy = True if order_candidate.order_side == TradeType.BUY else False + self.place_order(self.connector_name, + order_candidate.trading_pair, + is_buy, + order_candidate_adjusted.amount, + order_candidate.order_type, + order_candidate_adjusted.price) + return True + + def place_order(self, + connector_name: str, + trading_pair: str, + is_buy: bool, + amount: Decimal, + order_type: OrderType, + price=Decimal("NaN"), + ): + if is_buy: + self.buy(connector_name, trading_pair, amount, order_type, price) + else: + self.sell(connector_name, trading_pair, amount, order_type, price) + + # Events + def did_create_buy_order(self, event: BuyOrderCreatedEvent): + self.log_with_clock(logging.INFO, f"Buy order is created on the market {event.trading_pair}") + if self.order_candidate: + if self.order_candidate.trading_pair == event.trading_pair: + self.reset_order_candidate() + + def did_create_sell_order(self, event: SellOrderCreatedEvent): + self.log_with_clock(logging.INFO, f"Sell order is created on the market {event.trading_pair}") + if self.order_candidate: + if self.order_candidate.trading_pair == event.trading_pair: + self.reset_order_candidate() + + def reset_order_candidate(self): + """ + Deletes order candidate variable and resets counter + """ + self.order_candidate = None + self.place_order_trials_count = 0 + self.place_order_failure = False + + def did_fail_order(self, event: MarketOrderFailureEvent): + if self.order_candidate: + self.place_order_failure = True + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + msg = f"Buy {round(event.base_asset_amount, 6)} {event.base_asset} " \ + f"for {round(event.quote_asset_amount, 6)} {event.quote_asset} is completed" + self.notify_hb_app_with_timestamp(msg) + self.log_with_clock(logging.INFO, msg) + self.process_next_pair(event) + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + msg = f"Sell {round(event.base_asset_amount, 6)} {event.base_asset} " \ + f"for {round(event.quote_asset_amount, 6)} {event.quote_asset} is completed" + self.notify_hb_app_with_timestamp(msg) + self.log_with_clock(logging.INFO, msg) + self.process_next_pair(event) + + def process_next_pair(self, order_event): + """ + Processes 2nd or 3d order and finalizes the arbitrage + - Gets the completed order index + - Calculates order amount + - Creates and processes order candidate + - Finalizes arbitrage if the 3d order was completed + """ + event_pair = f"{order_event.base_asset}-{order_event.quote_asset}" + trading_pair = self.trading_pair[self.profitable_direction] + order_side = self.order_side[self.profitable_direction] + + event_order_index = trading_pair.index(event_pair) + + if order_side[event_order_index]: + exchanged_amount = order_event.base_asset_amount + else: + exchanged_amount = order_event.quote_asset_amount + + # Save initial amount spent for further profit calculation + if event_order_index == 0: + self.initial_spent_amount = order_event.quote_asset_amount if order_side[event_order_index] \ + else order_event.base_asset_amount + + if event_order_index < 2: + order_amount = self.get_order_amount_from_exchanged_amount(trading_pair[event_order_index + 1], + order_side[event_order_index + 1], + exchanged_amount) + self.order_candidate = self.create_order_candidate(trading_pair[event_order_index + 1], + order_side[event_order_index + 1], order_amount) + if self.order_candidate: + self.process_candidate(self.order_candidate, True) + else: + self.finalize_arbitrage(exchanged_amount) + + def finalize_arbitrage(self, final_exchanged_amount): + """ + Finalizes arbitrage + - Calculates trading round profit + - Updates total profit + - Checks the kill switch threshold + """ + order_profit = round(final_exchanged_amount - self.initial_spent_amount, 6) + order_profit_pct = round(100 * order_profit / self.initial_spent_amount, 2) + msg = f"*** Arbitrage completed! Profit: {order_profit} {self.holding_asset} ({order_profit_pct})%" + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + self.total_profit += order_profit + self.total_profit_pct = round(100 * self.total_profit / self.order_amount_in_holding_asset, 2) + self.status = "ACTIVE" + if self.kill_switch_enabled and self.total_profit_pct < self.kill_switch_rate: + self.status = "NOT_ACTIVE" + self.log_with_clock(logging.INFO, "Kill switch threshold reached. Stop trading") + self.notify_hb_app_with_timestamp("Kill switch threshold reached. Stop trading") + + def format_status(self) -> str: + """ + Returns status of the current strategy, total profit, current profitability of possible trades and balances. + This function is called when status command is issued. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + lines.extend(["", " Strategy status:"] + [" " + self.status]) + + lines.extend(["", " Total profit:"] + [" " + f"{self.total_profit} {self.holding_asset}" + f"({self.total_profit_pct}%)"]) + + for direction in self.trading_pair: + pairs_str = [f"{'buy' if side else 'sell'} {pair}" + for side, pair in zip(self.order_side[direction], self.trading_pair[direction])] + pairs_str = " > ".join(pairs_str) + profit_str = str(round(self.profit[direction], 2)) + lines.extend(["", f" {direction.capitalize()}:", f" {pairs_str}", f" profitability: {profit_str}%"]) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active orders."]) + + if self.connector.get_available_balance(self.holding_asset) < self.order_amount_in_holding_asset: + warning_lines.extend( + [f"{self.connector_name} {self.holding_asset} balance is too low. Cannot place order."]) + + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + + return "\n".join(lines) diff --git a/scripts/archived_scripts/community_scripts/wallet_hedge_example.py b/scripts/archived_scripts/community_scripts/wallet_hedge_example.py new file mode 100644 index 0000000..6d74d67 --- /dev/null +++ b/scripts/archived_scripts/community_scripts/wallet_hedge_example.py @@ -0,0 +1,90 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType +from hummingbot.data_feed.wallet_tracker_data_feed import WalletTrackerDataFeed +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class WalletHedgeExample(ScriptStrategyBase): + # Wallet params + token = "WETH" + wallet_balance_data_feed = WalletTrackerDataFeed( + chain="ethereum", + network="goerli", + wallets={"0xDA50C69342216b538Daf06FfECDa7363E0B96684"}, + tokens={token}, + ) + hedge_threshold = 0.05 + + # Hedge params + hedge_exchange = "kucoin_paper_trade" + hedge_pair = "ETH-USDT" + base, quote = hedge_pair.split("-") + + # Balances variables + balance = 0 + balance_start = 0 + balance_delta = 0 + balance_hedge = 0 + exchange_balance_start = 0 + exchange_balance = 0 + + markets = {hedge_exchange: {hedge_pair}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.wallet_balance_data_feed.start() + + def on_stop(self): + self.wallet_balance_data_feed.stop() + + def on_tick(self): + self.balance = self.wallet_balance_data_feed.wallet_balances_df[self.token].sum() + self.exchange_balance = self.get_exchange_base_asset_balance() + + if self.balance_start == 0: # first run + self.balance_start = self.balance + self.balance_hedge = self.balance + self.exchange_balance_start = self.get_exchange_base_asset_balance() + else: + self.balance_delta = self.balance - self.balance_hedge + + mid_price = self.connectors[self.hedge_exchange].get_mid_price(self.hedge_pair) + if self.balance_delta > 0 and self.balance_delta >= self.hedge_threshold: + self.sell(self.hedge_exchange, self.hedge_pair, self.balance_delta, OrderType.MARKET, mid_price) + self.balance_hedge = self.balance + elif self.balance_delta < 0 and self.balance_delta <= -self.hedge_threshold: + self.buy(self.hedge_exchange, self.hedge_pair, -self.balance_delta, OrderType.MARKET, mid_price) + self.balance_hedge = self.balance + + def get_exchange_base_asset_balance(self): + balance_df = self.get_balance_df() + row = balance_df.iloc[0] + return Decimal(row["Total Balance"]) + + def format_status(self) -> str: + if self.wallet_balance_data_feed.is_ready(): + lines = [] + prices_str = format_df_for_printout(self.wallet_balance_data_feed.wallet_balances_df, + table_format="psql", index=True) + lines.append(f"\nWallet Data Feed:\n{prices_str}") + + precision = 3 + if self.balance_start > 0: + lines.append("\nWallets:") + lines.append(f" Starting {self.token} balance: {round(self.balance_start, precision)}") + lines.append(f" Current {self.token} balance: {round(self.balance, precision)}") + lines.append(f" Delta: {round(self.balance - self.balance_start, precision)}") + lines.append("\nExchange:") + lines.append(f" Starting {self.base} balance: {round(self.exchange_balance_start, precision)}") + lines.append(f" Current {self.base} balance: {round(self.exchange_balance, precision)}") + lines.append(f" Delta: {round(self.exchange_balance - self.exchange_balance_start, precision)}") + lines.append("\nHedge:") + lines.append(f" Threshold: {self.hedge_threshold}") + lines.append(f" Delta from last hedge: {round(self.balance_delta, precision)}") + return "\n".join(lines) + else: + return "Wallet Data Feed is not ready." diff --git a/scripts/archived_scripts/examples_simple_tasks/buy_only_three_times_example.py b/scripts/archived_scripts/examples_simple_tasks/buy_only_three_times_example.py new file mode 100644 index 0000000..b0d31ee --- /dev/null +++ b/scripts/archived_scripts/examples_simple_tasks/buy_only_three_times_example.py @@ -0,0 +1,43 @@ +from decimal import Decimal + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.events import BuyOrderCreatedEvent +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class BuyOnlyThreeTimesExample(ScriptStrategyBase): + """ + This example places shows how to add a logic to only place three buy orders in the market, + use an event to increase the counter and stop the strategy once the task is done. + """ + order_amount_usd = Decimal(100) + orders_created = 0 + orders_to_create = 3 + base = "ETH" + quote = "USDT" + markets = { + "kucoin_paper_trade": {f"{base}-{quote}"} + } + + def on_tick(self): + if self.orders_created < self.orders_to_create: + conversion_rate = RateOracle.get_instance().get_pair_rate(f"{self.base}-USD") + amount = self.order_amount_usd / conversion_rate + price = self.connectors["kucoin_paper_trade"].get_mid_price(f"{self.base}-{self.quote}") * Decimal(0.99) + self.buy( + connector_name="kucoin_paper_trade", + trading_pair="ETH-USDT", + amount=amount, + order_type=OrderType.LIMIT, + price=price + ) + + def did_create_buy_order(self, event: BuyOrderCreatedEvent): + trading_pair = f"{self.base}-{self.quote}" + if event.trading_pair == trading_pair: + self.orders_created += 1 + if self.orders_created == self.orders_to_create: + self.logger().info("All order created !") + HummingbotApplication.main_application().stop() diff --git a/scripts/archived_scripts/examples_simple_tasks/format_status_example.py b/scripts/archived_scripts/examples_simple_tasks/format_status_example.py new file mode 100644 index 0000000..42d38c3 --- /dev/null +++ b/scripts/archived_scripts/examples_simple_tasks/format_status_example.py @@ -0,0 +1,47 @@ +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class FormatStatusExample(ScriptStrategyBase): + """ + This example shows how to add a custom format_status to a strategy and query the order book. + Run the command status --live, once the strategy starts. + """ + markets = { + "binance_paper_trade": {"ETH-USDT", "BTC-USDT", "MATIC-USDT", "AVAX-USDT"}, + "kucoin_paper_trade": {"ETH-USDT", "BTC-USDT", "MATIC-USDT", "AVAX-USDT"}, + "gate_io_paper_trade": {"ETH-USDT", "BTC-USDT", "MATIC-USDT", "AVAX-USDT"}, + } + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + market_status_df = self.get_market_status_df_with_depth() + lines.extend(["", " Market Status Data Frame:"] + [" " + line for line in market_status_df.to_string(index=False).split("\n")]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def get_market_status_df_with_depth(self): + market_status_df = self.market_status_data_frame(self.get_market_trading_pair_tuples()) + market_status_df["Exchange"] = market_status_df.apply(lambda x: x["Exchange"].strip("PaperTrade") + "paper_trade", axis=1) + market_status_df["Volume (+1%)"] = market_status_df.apply(lambda x: self.get_volume_for_percentage_from_mid_price(x, 0.01), axis=1) + market_status_df["Volume (-1%)"] = market_status_df.apply(lambda x: self.get_volume_for_percentage_from_mid_price(x, -0.01), axis=1) + return market_status_df + + def get_volume_for_percentage_from_mid_price(self, row, percentage): + price = row["Mid Price"] * (1 + percentage) + is_buy = percentage > 0 + result = self.connectors[row["Exchange"]].get_volume_for_price(row["Market"], is_buy, price) + return result.result_volume diff --git a/scripts/archived_scripts/examples_simple_tasks/log_price_example.py b/scripts/archived_scripts/examples_simple_tasks/log_price_example.py new file mode 100644 index 0000000..405df72 --- /dev/null +++ b/scripts/archived_scripts/examples_simple_tasks/log_price_example.py @@ -0,0 +1,19 @@ +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class LogPricesExample(ScriptStrategyBase): + """ + This example shows how to get the ask and bid of a market and log it to the console. + """ + markets = { + "binance_paper_trade": {"ETH-USDT"}, + "kucoin_paper_trade": {"ETH-USDT"}, + "gate_io_paper_trade": {"ETH-USDT"} + } + + def on_tick(self): + for connector_name, connector in self.connectors.items(): + self.logger().info(f"Connector: {connector_name}") + self.logger().info(f"Best ask: {connector.get_price('ETH-USDT', True)}") + self.logger().info(f"Best bid: {connector.get_price('ETH-USDT', False)}") + self.logger().info(f"Mid price: {connector.get_mid_price('ETH-USDT')}") diff --git a/scripts/archived_scripts/examples_using_data_feeds/amm_data_feed_example.py b/scripts/archived_scripts/examples_using_data_feeds/amm_data_feed_example.py new file mode 100644 index 0000000..ee35cec --- /dev/null +++ b/scripts/archived_scripts/examples_using_data_feeds/amm_data_feed_example.py @@ -0,0 +1,48 @@ +from decimal import Decimal +from typing import Dict + +import pandas as pd + +from hummingbot.client.ui.interface_utils import format_df_for_printout +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.data_feed.amm_gateway_data_feed import AmmGatewayDataFeed +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class AMMDataFeedExample(ScriptStrategyBase): + amm_data_feed_uniswap = AmmGatewayDataFeed( + connector_chain_network="uniswap_polygon_mainnet", + trading_pairs={"LINK-USDC", "AAVE-USDC", "WMATIC-USDT"}, + order_amount_in_base=Decimal("1"), + ) + amm_data_feed_quickswap = AmmGatewayDataFeed( + connector_chain_network="quickswap_polygon_mainnet", + trading_pairs={"LINK-USDC", "AAVE-USDC", "WMATIC-USDT"}, + order_amount_in_base=Decimal("1"), + ) + markets = {"binance_paper_trade": {"BTC-USDT"}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.amm_data_feed_uniswap.start() + self.amm_data_feed_quickswap.start() + + def on_stop(self): + self.amm_data_feed_uniswap.stop() + self.amm_data_feed_quickswap.stop() + + def on_tick(self): + pass + + def format_status(self) -> str: + if self.amm_data_feed_uniswap.is_ready() and self.amm_data_feed_quickswap.is_ready(): + lines = [] + rows = [] + rows.extend(dict(price) for token, price in self.amm_data_feed_uniswap.price_dict.items()) + rows.extend(dict(price) for token, price in self.amm_data_feed_quickswap.price_dict.items()) + df = pd.DataFrame(rows) + prices_str = format_df_for_printout(df, table_format="psql") + lines.append(f"AMM Data Feed is ready.\n{prices_str}") + return "\n".join(lines) + else: + return "AMM Data Feed is not ready." diff --git a/scripts/archived_scripts/examples_using_data_feeds/candles_example.py b/scripts/archived_scripts/examples_using_data_feeds/candles_example.py new file mode 100644 index 0000000..f909731 --- /dev/null +++ b/scripts/archived_scripts/examples_using_data_feeds/candles_example.py @@ -0,0 +1,85 @@ +from typing import Dict + +import pandas as pd +import pandas_ta as ta # noqa: F401 + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class CandlesExample(ScriptStrategyBase): + """ + This is a strategy that shows how to use the new Candlestick component. + It acquires data from both Binance spot and Binance perpetuals to initialize three different timeframes + of candlesticks. + The candlesticks are then displayed in the status, which is coded using a custom format status that + includes technical indicators. + This strategy serves as a clear example for other users on how to effectively utilize candlesticks in their own + trading strategies by utilizing the new Candlestick component. The integration of multiple timeframes and technical + indicators provides a comprehensive view of market trends and conditions, making this strategy a valuable tool for + informed trading decisions. + """ + # Available intervals: |1s|1m|3m|5m|15m|30m|1h|2h|4h|6h|8h|12h|1d|3d|1w|1M| + # Is possible to use the Candles Factory to create the candlestick that you want, and then you have to start it. + # Also, you can use the class directly like BinancePerpetualsCandles(trading_pair, interval, max_records), but + # this approach is better if you want to initialize multiple candles with a list or dict of configurations. + eth_1m_candles = CandlesFactory.get_candle(CandlesConfig(connector="binance", trading_pair="ETH-USDT", interval="1m", max_records=1000)) + eth_1h_candles = CandlesFactory.get_candle(CandlesConfig(connector="binance", trading_pair="ETH-USDT", interval="1h", max_records=1000)) + eth_1w_candles = CandlesFactory.get_candle(CandlesConfig(connector="binance", trading_pair="ETH-USDT", interval="1w", max_records=200)) + + # The markets are the connectors that you can use to execute all the methods of the scripts strategy base + # The candlesticks are just a component that provides the information of the candlesticks + markets = {"binance_paper_trade": {"SOL-USDT"}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + self.eth_1m_candles.start() + self.eth_1h_candles.start() + self.eth_1w_candles.start() + + @property + def all_candles_ready(self): + """ + Checks if the candlesticks are full. + :return: + """ + return all([self.eth_1h_candles.is_ready, self.eth_1m_candles.is_ready, self.eth_1w_candles.is_ready]) + + def on_tick(self): + pass + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + :return: + """ + self.eth_1m_candles.stop() + self.eth_1h_candles.stop() + self.eth_1w_candles.stop() + + def format_status(self) -> str: + """ + Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + if self.all_candles_ready: + lines.extend(["\n############################################ Market Data ############################################\n"]) + for candles in [self.eth_1w_candles, self.eth_1m_candles, self.eth_1h_candles]: + candles_df = candles.candles_df + # Let's add some technical indicators + candles_df.ta.rsi(length=14, append=True) + candles_df.ta.bbands(length=20, std=2, append=True) + candles_df.ta.ema(length=14, offset=None, append=True) + candles_df["timestamp"] = pd.to_datetime(candles_df["timestamp"], unit="ms") + lines.extend([f"Candles: {candles.name} | Interval: {candles.interval}"]) + lines.extend([" " + line for line in candles_df.tail().to_string(index=False).split("\n")]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + else: + lines.extend(["", " No data collected."]) + + return "\n".join(lines) diff --git a/scripts/archived_scripts/examples_using_smart_components/arbitrage_with_smart_component.py b/scripts/archived_scripts/examples_using_smart_components/arbitrage_with_smart_component.py new file mode 100644 index 0000000..7a303a9 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/arbitrage_with_smart_component.py @@ -0,0 +1,98 @@ +from decimal import Decimal + +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.smart_components.executors.arbitrage_executor.arbitrage_executor import ArbitrageExecutor +from hummingbot.smart_components.executors.arbitrage_executor.data_types import ArbitrageConfig, ExchangePair +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class ArbitrageWithSmartComponent(ScriptStrategyBase): + # Parameters + exchange_pair_1 = ExchangePair(exchange="binance", trading_pair="MATIC-USDT") + exchange_pair_2 = ExchangePair(exchange="uniswap_polygon_mainnet", trading_pair="WMATIC-USDT") + order_amount = Decimal("50") # in base asset + min_profitability = Decimal("0.004") + + markets = {exchange_pair_1.exchange: {exchange_pair_1.trading_pair}, + exchange_pair_2.exchange: {exchange_pair_2.trading_pair}} + active_buy_arbitrages = [] + active_sell_arbitrages = [] + closed_arbitrage_executors = [] + + def on_tick(self): + self.cleanup_arbitrages() + if len(self.active_buy_arbitrages) < 1: + buy_arbitrage_executor = self.create_arbitrage_executor( + buying_exchange_pair=self.exchange_pair_1, + selling_exchange_pair=self.exchange_pair_2, + ) + if buy_arbitrage_executor: + self.active_buy_arbitrages.append(buy_arbitrage_executor) + if len(self.active_sell_arbitrages) < 1: + sell_arbitrage_executor = self.create_arbitrage_executor( + buying_exchange_pair=self.exchange_pair_2, + selling_exchange_pair=self.exchange_pair_1, + ) + if sell_arbitrage_executor: + self.active_sell_arbitrages.append(sell_arbitrage_executor) + + def on_stop(self): + for arbitrage in self.active_buy_arbitrages: + arbitrage.terminate_control_loop() + for arbitrage in self.active_sell_arbitrages: + arbitrage.terminate_control_loop() + + def create_arbitrage_executor(self, buying_exchange_pair: ExchangePair, selling_exchange_pair: ExchangePair): + try: + base_asset_for_selling_exchange = self.connectors[selling_exchange_pair.exchange].get_available_balance( + selling_exchange_pair.trading_pair.split("-")[0]) + if self.order_amount > base_asset_for_selling_exchange: + self.logger().info(f"Insufficient balance in exchange {selling_exchange_pair.exchange} " + f"to sell {selling_exchange_pair.trading_pair.split('-')[0]} " + f"Actual: {base_asset_for_selling_exchange} --> Needed: {self.order_amount}") + return + + # Harcoded for now since we don't have a price oracle for WMATIC (CoinMarketCap rate source is requested and coming) + pair_conversion = selling_exchange_pair.trading_pair.replace("W", "") + price = RateOracle.get_instance().get_pair_rate(pair_conversion) + quote_asset_for_buying_exchange = self.connectors[buying_exchange_pair.exchange].get_available_balance( + buying_exchange_pair.trading_pair.split("-")[1]) + if self.order_amount * price > quote_asset_for_buying_exchange: + self.logger().info(f"Insufficient balance in exchange {buying_exchange_pair.exchange} " + f"to buy {buying_exchange_pair.trading_pair.split('-')[1]} " + f"Actual: {quote_asset_for_buying_exchange} --> Needed: {self.order_amount * price}") + return + + arbitrage_config = ArbitrageConfig( + buying_market=buying_exchange_pair, + selling_market=selling_exchange_pair, + order_amount=self.order_amount, + min_profitability=self.min_profitability, + ) + arbitrage_executor = ArbitrageExecutor(strategy=self, + arbitrage_config=arbitrage_config) + return arbitrage_executor + except Exception: + self.logger().error(f"Error creating executor to buy on {buying_exchange_pair.exchange} and sell on {selling_exchange_pair.exchange}") + + def format_status(self) -> str: + status = [] + status.extend([f"Closed Arbtriages: {len(self.closed_arbitrage_executors)}"]) + for arbitrage in self.closed_arbitrage_executors: + status.extend(arbitrage.to_format_status()) + status.extend([f"Active Arbitrages: {len(self.active_sell_arbitrages) + len(self.active_buy_arbitrages)}"]) + for arbitrage in self.active_sell_arbitrages: + status.extend(arbitrage.to_format_status()) + for arbitrage in self.active_buy_arbitrages: + status.extend(arbitrage.to_format_status()) + return "\n".join(status) + + def cleanup_arbitrages(self): + for arbitrage in self.active_buy_arbitrages: + if arbitrage.is_closed: + self.closed_arbitrage_executors.append(arbitrage) + self.active_buy_arbitrages.remove(arbitrage) + for arbitrage in self.active_sell_arbitrages: + if arbitrage.is_closed: + self.closed_arbitrage_executors.append(arbitrage) + self.active_sell_arbitrages.remove(arbitrage) diff --git a/scripts/archived_scripts/examples_using_smart_components/directional_strategy_bb_rsi_multi_timeframe.py b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_bb_rsi_multi_timeframe.py new file mode 100644 index 0000000..560318d --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_bb_rsi_multi_timeframe.py @@ -0,0 +1,116 @@ +from decimal import Decimal + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class MultiTimeframeBBRSI(DirectionalStrategyBase): + """ + MultiTimeframeBBRSI strategy implementation based on the DirectionalStrategyBase. + + This strategy combines multiple timeframes of Bollinger Bands (BB) and Relative Strength Index (RSI) indicators to + generate trading signals and execute trades based on the composed signal value. It defines the specific parameters + and configurations for the MultiTimeframeBBRSI strategy. + + Parameters: + directional_strategy_name (str): The name of the strategy. + trading_pair (str): The trading pair to be traded. + exchange (str): The exchange to be used for trading. + order_amount_usd (Decimal): The amount of the order in USD. + leverage (int): The leverage to be used for trading. + + Position Parameters: + stop_loss (float): The stop-loss percentage for the position. + take_profit (float): The take-profit percentage for the position. + time_limit (int or None): The time limit for the position in seconds. Set to `None` for no time limit. + trailing_stop_activation_delta (float): The activation delta for the trailing stop. + trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. + + Candlestick Configuration: + candles (List[CandlesBase]): The list of candlesticks used for generating signals. + + Markets: + A dictionary specifying the markets and trading pairs for the strategy. + + Inherits from: + DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. + """ + directional_strategy_name: str = "bb_rsi_multi_timeframe" + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str = "ETH-USDT" + exchange: str = "binance_perpetual" + order_amount_usd = Decimal("40") + leverage = 10 + + # Configure the parameters for the position + stop_loss: float = 0.0075 + take_profit: float = 0.015 + time_limit: int = None + trailing_stop_activation_delta = 0.004 + trailing_stop_trailing_delta = 0.001 + CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000) + candles = [ + CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="1m", max_records=1000)), + CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000)), + ] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the composed signal value from multiple timeframes. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + signals = [] + for candle in self.candles: + candles_df = self.get_processed_df(candle.candles_df) + last_row = candles_df.iloc[-1] + # We are going to normalize the values of the signals between -1 and 1. + # -1 --> short | 1 --> long, so in the normalization we also need to switch side by changing the sign + sma_rsi_normalized = -1 * (last_row["RSI_21_SMA_10"].item() - 50) / 50 + bb_percentage_normalized = -1 * (last_row["BBP_21_2.0"].item() - 0.5) / 0.5 + # we assume that the weigths of sma of rsi and bb are equal + signal_value = (sma_rsi_normalized + bb_percentage_normalized) / 2 + signals.append(signal_value) + # Here we have a list with the values of the signals for each candle + # The idea is that you can define rules between the signal values of multiple trading pairs or timeframes + # In this example, we are going to prioritize the short term signal, so the weight of the 1m candle + # is going to be 0.7 and the weight of the 3m candle 0.3 + composed_signal_value = 0.7 * signals[0] + 0.3 * signals[1] + # Here we are applying thresholds to the composed signal value + if composed_signal_value > 0.5: + return 1 + elif composed_signal_value < -0.5: + return -1 + else: + return 0 + + @staticmethod + def get_processed_df(candles): + """ + Retrieves the processed dataframe with Bollinger Bands and RSI values for a specific candlestick. + Args: + candles (pd.DataFrame): The raw candlestick dataframe. + Returns: + pd.DataFrame: The processed dataframe with Bollinger Bands and RSI values. + """ + candles_df = candles.copy() + # Let's add some technical indicators + candles_df.ta.bbands(length=21, append=True) + candles_df.ta.rsi(length=21, append=True) + candles_df.ta.sma(length=10, close="RSI_21", prefix="RSI_21", append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data for each candlestick. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_21_SMA_10", "BBP_21_2.0"] + for candle in self.candles: + candles_df = self.get_processed_df(candle.candles_df) + lines.extend([f"Candles: {candle.name} | Interval: {candle.interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/archived_scripts/examples_using_smart_components/directional_strategy_macd_bb.py b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_macd_bb.py new file mode 100644 index 0000000..59c13d4 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_macd_bb.py @@ -0,0 +1,96 @@ +from decimal import Decimal + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class MacdBB(DirectionalStrategyBase): + """ + MacdBB strategy implementation based on the DirectionalStrategyBase. + + This strategy combines the MACD (Moving Average Convergence Divergence) and Bollinger Bands indicators to generate + trading signals and execute trades based on the indicator values. It defines the specific parameters and + configurations for the MacdBB strategy. + + Parameters: + directional_strategy_name (str): The name of the strategy. + trading_pair (str): The trading pair to be traded. + exchange (str): The exchange to be used for trading. + order_amount_usd (Decimal): The amount of the order in USD. + leverage (int): The leverage to be used for trading. + + Position Parameters: + stop_loss (float): The stop-loss percentage for the position. + take_profit (float): The take-profit percentage for the position. + time_limit (int): The time limit for the position in seconds. + trailing_stop_activation_delta (float): The activation delta for the trailing stop. + trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. + + Candlestick Configuration: + candles (List[CandlesBase]): The list of candlesticks used for generating signals. + + Markets: + A dictionary specifying the markets and trading pairs for the strategy. + + Inherits from: + DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. + """ + directional_strategy_name: str = "MACD_BB" + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str = "BTC-USDT" + exchange: str = "binance_perpetual" + order_amount_usd = Decimal("40") + leverage = 10 + + # Configure the parameters for the position + stop_loss: float = 0.0075 + take_profit: float = 0.015 + time_limit: int = 60 * 55 + trailing_stop_activation_delta = 0.003 + trailing_stop_trailing_delta = 0.0007 + + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000))] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the MACD and Bollinger Bands indicators. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + candles_df = self.get_processed_df() + last_candle = candles_df.iloc[-1] + bbp = last_candle["BBP_100_2.0"] + macdh = last_candle["MACDh_21_42_9"] + macd = last_candle["MACD_21_42_9"] + if bbp < 0.4 and macdh > 0 and macd < 0: + signal_value = 1 + elif bbp > 0.6 and macdh < 0 and macd > 0: + signal_value = -1 + else: + signal_value = 0 + return signal_value + + def get_processed_df(self): + """ + Retrieves the processed dataframe with MACD and Bollinger Bands values. + Returns: + pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. + """ + candles_df = self.candles[0].candles_df + candles_df.ta.bbands(length=100, append=True) + candles_df.ta.macd(fast=21, slow=42, signal=9, append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "BBP_100_2.0", "MACDh_21_42_9", "MACD_21_42_9"] + candles_df = self.get_processed_df() + lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/archived_scripts/examples_using_smart_components/directional_strategy_rsi_spot.py b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_rsi_spot.py new file mode 100644 index 0000000..db5f3e0 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_rsi_spot.py @@ -0,0 +1,95 @@ +from decimal import Decimal + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class RSISpot(DirectionalStrategyBase): + """ + RSI (Relative Strength Index) strategy implementation based on the DirectionalStrategyBase. + + This strategy uses the RSI indicator to generate trading signals and execute trades based on the RSI values. + It defines the specific parameters and configurations for the RSI strategy. + + Parameters: + directional_strategy_name (str): The name of the strategy. + trading_pair (str): The trading pair to be traded. + exchange (str): The exchange to be used for trading. + order_amount_usd (Decimal): The amount of the order in USD. + leverage (int): The leverage to be used for trading. + + Position Parameters: + stop_loss (float): The stop-loss percentage for the position. + take_profit (float): The take-profit percentage for the position. + time_limit (int): The time limit for the position in seconds. + trailing_stop_activation_delta (float): The activation delta for the trailing stop. + trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. + + Candlestick Configuration: + candles (List[CandlesBase]): The list of candlesticks used for generating signals. + + Markets: + A dictionary specifying the markets and trading pairs for the strategy. + + Methods: + get_signal(): Generates the trading signal based on the RSI indicator. + get_processed_df(): Retrieves the processed dataframe with RSI values. + market_data_extra_info(): Provides additional information about the market data. + + Inherits from: + DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. + """ + directional_strategy_name: str = "RSI_spot" + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str = "ETH-USDT" + exchange: str = "binance" + order_amount_usd = Decimal("40") + leverage = 10 + + # Configure the parameters for the position + stop_loss: float = 0.0075 + take_profit: float = 0.015 + time_limit: int = 60 * 55 + trailing_stop_activation_delta = 0.004 + trailing_stop_trailing_delta = 0.001 + + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000))] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the RSI indicator. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + candles_df = self.get_processed_df() + rsi_value = candles_df.iat[-1, -1] + if rsi_value > 70: + return -1 + elif rsi_value < 30: + return 1 + else: + return 0 + + def get_processed_df(self): + """ + Retrieves the processed dataframe with RSI values. + Returns: + pd.DataFrame: The processed dataframe with RSI values. + """ + candles_df = self.candles[0].candles_df + candles_df.ta.rsi(length=7, append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data to the format status. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_7"] + candles_df = self.get_processed_df() + lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/archived_scripts/examples_using_smart_components/directional_strategy_trend_follower.py b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_trend_follower.py new file mode 100644 index 0000000..23b22f7 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_trend_follower.py @@ -0,0 +1,73 @@ +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import OrderType +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class TrendFollowingStrategy(DirectionalStrategyBase): + directional_strategy_name = "trend_following" + trading_pair = "DOGE-USDT" + exchange = "binance_perpetual" + order_amount_usd = Decimal("40") + leverage = 10 + + # Configure the parameters for the position + stop_loss: float = 0.01 + take_profit: float = 0.05 + time_limit: int = 60 * 60 * 3 + open_order_type = OrderType.MARKET + take_profit_order_type: OrderType = OrderType.MARKET + trailing_stop_activation_delta = 0.01 + trailing_stop_trailing_delta = 0.003 + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000))] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the MACD and Bollinger Bands indicators. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + candles_df = self.get_processed_df() + last_candle = candles_df.iloc[-1] + bbp = last_candle["BBP_100_2.0"] + sma_21 = last_candle["SMA_21"] + sma_200 = last_candle["SMA_200"] + trend = sma_21 > sma_200 + filter = (bbp > 0.35) and (bbp < 0.65) + + if trend and filter: + signal_value = 1 + elif not trend and filter: + signal_value = -1 + else: + signal_value = 0 + return signal_value + + def get_processed_df(self): + """ + Retrieves the processed dataframe with MACD and Bollinger Bands values. + Returns: + pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. + """ + candles_df = self.candles[0].candles_df + candles_df.ta.sma(length=21, append=True) + candles_df.ta.sma(length=200, append=True) + candles_df.ta.bbands(length=100, append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "BBP_100_2.0", "SMA_21", "SMA_200"] + candles_df = self.get_processed_df() + lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/archived_scripts/examples_using_smart_components/directional_strategy_widening_ema_bands.py b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_widening_ema_bands.py new file mode 100644 index 0000000..beb3e03 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/directional_strategy_widening_ema_bands.py @@ -0,0 +1,99 @@ +from decimal import Decimal + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class WideningEMABands(DirectionalStrategyBase): + """ + WideningEMABands strategy implementation based on the DirectionalStrategyBase. + + This strategy uses two EMAs one short and one long to generate trading signals and execute trades based on the + percentage of distance between them. + + Parameters: + directional_strategy_name (str): The name of the strategy. + trading_pair (str): The trading pair to be traded. + exchange (str): The exchange to be used for trading. + order_amount_usd (Decimal): The amount of the order in USD. + leverage (int): The leverage to be used for trading. + distance_pct_threshold (float): The percentage of distance between the EMAs to generate a signal. + + Position Parameters: + stop_loss (float): The stop-loss percentage for the position. + take_profit (float): The take-profit percentage for the position. + time_limit (int): The time limit for the position in seconds. + trailing_stop_activation_delta (float): The activation delta for the trailing stop. + trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. + + Candlestick Configuration: + candles (List[CandlesBase]): The list of candlesticks used for generating signals. + + Markets: + A dictionary specifying the markets and trading pairs for the strategy. + + Inherits from: + DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. + """ + directional_strategy_name: str = "Widening_EMA_Bands" + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str = "LINA-USDT" + exchange: str = "binance_perpetual" + order_amount_usd = Decimal("40") + leverage = 10 + distance_pct_threshold = 0.02 + + # Configure the parameters for the position + stop_loss: float = 0.015 + take_profit: float = 0.03 + time_limit: int = 60 * 60 * 5 + trailing_stop_activation_delta = 0.008 + trailing_stop_trailing_delta = 0.003 + + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000))] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the MACD and Bollinger Bands indicators. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + candles_df = self.get_processed_df() + last_candle = candles_df.iloc[-1] + ema_8 = last_candle["EMA_8"] + ema_54 = last_candle["EMA_54"] + distance = ema_8 - ema_54 + average = (ema_8 + ema_54) / 2 + distance_pct = distance / average + if distance_pct > self.distance_pct_threshold: + signal_value = -1 + elif distance_pct < -self.distance_pct_threshold: + signal_value = 1 + else: + signal_value = 0 + return signal_value + + def get_processed_df(self): + """ + Retrieves the processed dataframe with MACD and Bollinger Bands values. + Returns: + pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. + """ + candles_df = self.candles[0].candles_df + candles_df.ta.ema(length=8, append=True) + candles_df.ta.ema(length=54, append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "EMA_8", "EMA_54"] + candles_df = self.get_processed_df() + lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/archived_scripts/examples_using_smart_components/macd_bb_directional_strategy.py b/scripts/archived_scripts/examples_using_smart_components/macd_bb_directional_strategy.py new file mode 100644 index 0000000..e8cb192 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/macd_bb_directional_strategy.py @@ -0,0 +1,233 @@ +import datetime +import os +from collections import deque +from decimal import Decimal +from typing import Deque, Dict, List + +import pandas as pd +import pandas_ta as ta # noqa: F401 + +from hummingbot import data_path +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MACDBBDirectionalStrategy(ScriptStrategyBase): + """ + A simple trading strategy that uses RSI in one timeframe to determine whether to go long or short. + IMPORTANT: Binance perpetual has to be in Single Asset Mode, soon we are going to support Multi Asset Mode. + """ + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair = "APE-BUSD" + exchange = "binance_perpetual" + + # Maximum position executors at a time + max_executors = 1 + active_executors: List[PositionExecutor] = [] + stored_executors: Deque[PositionExecutor] = deque(maxlen=10) # Store only the last 10 executors for reporting + + # Configure the parameters for the position + stop_loss_multiplier = 0.75 + take_profit_multiplier = 1.5 + time_limit = 60 * 55 + + # Create the candles that we want to use and the thresholds for the indicators + # IMPORTANT: The connector name of the candles can be binance or binance_perpetual, and can be different from the + # connector that you define to trade + candles = CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000)) + + # Configure the leverage and order amount the bot is going to use + set_leverage_flag = None + leverage = 20 + order_amount_usd = Decimal("15") + + today = datetime.datetime.today() + csv_path = data_path() + f"/{exchange}_{trading_pair}_{today.day:02d}-{today.month:02d}-{today.year}.csv" + markets = {exchange: {trading_pair}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + self.candles.start() + + def get_active_executors(self): + return [signal_executor for signal_executor in self.active_executors + if not signal_executor.is_closed] + + def get_closed_executors(self): + return self.stored_executors + + def on_tick(self): + self.check_and_set_leverage() + if len(self.get_active_executors()) < self.max_executors and self.candles.is_ready: + signal_value, take_profit, stop_loss, indicators = self.get_signal_tp_and_sl() + if self.is_margin_enough() and signal_value != 0: + price = self.connectors[self.exchange].get_mid_price(self.trading_pair) + self.notify_hb_app_with_timestamp(f""" + Creating new position! + Price: {price} + BB%: {indicators[0]} + MACDh: {indicators[1]} + MACD: {indicators[2]} + """) + signal_executor = PositionExecutor( + position_config=PositionConfig( + timestamp=self.current_timestamp, trading_pair=self.trading_pair, + exchange=self.exchange, order_type=OrderType.MARKET, + side=PositionSide.SHORT if signal_value < 0 else PositionSide.LONG, + entry_price=price, + amount=self.order_amount_usd / price, + stop_loss=stop_loss, + take_profit=take_profit, + time_limit=self.time_limit), + strategy=self, + ) + self.active_executors.append(signal_executor) + self.clean_and_store_executors() + + def get_signal_tp_and_sl(self): + candles_df = self.candles.candles_df + # Let's add some technical indicators + candles_df.ta.bbands(length=100, append=True) + candles_df.ta.macd(fast=21, slow=42, signal=9, append=True) + candles_df["std"] = candles_df["close"].rolling(100).std() + candles_df["std_close"] = candles_df["std"] / candles_df["close"] + last_candle = candles_df.iloc[-1] + bbp = last_candle["BBP_100_2.0"] + macdh = last_candle["MACDh_21_42_9"] + macd = last_candle["MACD_21_42_9"] + std_pct = last_candle["std_close"] + if bbp < 0.2 and macdh > 0 and macd < 0: + signal_value = 1 + elif bbp > 0.8 and macdh < 0 and macd > 0: + signal_value = -1 + else: + signal_value = 0 + take_profit = std_pct * self.take_profit_multiplier + stop_loss = std_pct * self.stop_loss_multiplier + indicators = [bbp, macdh, macd] + return signal_value, take_profit, stop_loss, indicators + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + """ + # we are going to close all the open positions when the bot stops + self.close_open_positions() + self.candles.stop() + + def format_status(self) -> str: + """ + Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + if len(self.stored_executors) > 0: + lines.extend([ + "\n########################################## Closed Executors ##########################################"]) + + for executor in self.stored_executors: + lines.extend([f"|Signal id: {executor.timestamp}"]) + lines.extend(executor.to_format_status()) + lines.extend([ + "-----------------------------------------------------------------------------------------------------------"]) + + if len(self.active_executors) > 0: + lines.extend([ + "\n########################################## Active Executors ##########################################"]) + + for executor in self.active_executors: + lines.extend([f"|Signal id: {executor.timestamp}"]) + lines.extend(executor.to_format_status()) + if self.candles.is_ready: + lines.extend([ + "\n############################################ Market Data ############################################\n"]) + signal, take_profit, stop_loss, indicators = self.get_signal_tp_and_sl() + lines.extend([f"Signal: {signal} | Take Profit: {take_profit} | Stop Loss: {stop_loss}"]) + lines.extend([f"BB%: {indicators[0]} | MACDh: {indicators[1]} | MACD: {indicators[2]}"]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + else: + lines.extend(["", " No data collected."]) + + return "\n".join(lines) + + def check_and_set_leverage(self): + if not self.set_leverage_flag: + for connector in self.connectors.values(): + for trading_pair in connector.trading_pairs: + connector.set_position_mode(PositionMode.HEDGE) + connector.set_leverage(trading_pair=trading_pair, leverage=self.leverage) + self.set_leverage_flag = True + + def clean_and_store_executors(self): + executors_to_store = [executor for executor in self.active_executors if executor.is_closed] + if not os.path.exists(self.csv_path): + df_header = pd.DataFrame([("timestamp", + "exchange", + "trading_pair", + "side", + "amount", + "pnl", + "close_timestamp", + "entry_price", + "close_price", + "last_status", + "sl", + "tp", + "tl", + "order_type", + "leverage")]) + df_header.to_csv(self.csv_path, mode='a', header=False, index=False) + for executor in executors_to_store: + self.stored_executors.append(executor) + df = pd.DataFrame([(executor.timestamp, + executor.exchange, + executor.trading_pair, + executor.side, + executor.amount, + executor.trade_pnl, + executor.close_timestamp, + executor.entry_price, + executor.close_price, + executor.status, + executor.position_config.stop_loss, + executor.position_config.take_profit, + executor.position_config.time_limit, + executor.open_order_type, + self.leverage)]) + df.to_csv(self.csv_path, mode='a', header=False, index=False) + self.active_executors = [executor for executor in self.active_executors if not executor.is_closed] + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def is_margin_enough(self): + quote_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[-1]) + if self.order_amount_usd < quote_balance * self.leverage: + return True + else: + self.logger().info("No enough margin to place orders.") + return False diff --git a/scripts/archived_scripts/examples_using_smart_components/pmm_with_position_executor.py b/scripts/archived_scripts/examples_using_smart_components/pmm_with_position_executor.py new file mode 100644 index 0000000..0052e9c --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/pmm_with_position_executor.py @@ -0,0 +1,340 @@ +import datetime +import os +import time +from decimal import Decimal +from typing import Dict, List + +import pandas as pd + +from hummingbot import data_path +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, PriceType, TradeType +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.smart_components.executors.position_executor.data_types import ( + CloseType, + PositionConfig, + PositionExecutorStatus, + TrailingStop, +) +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class PMMWithPositionExecutor(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-63cc765486dd42228d3da0b32537fc92 + Video: - + Description: + The bot will place two orders around the price_source (mid price or last traded price) in a trading_pair on + exchange, with a distance defined by the ask_spread and bid_spread. Every order_refresh_time in seconds, + the bot will cancel and replace the orders. + """ + market_making_strategy_name = "pmm_with_position_executor" + trading_pair = "FRONT-BUSD" + exchange = "binance" + + # Configure order levels and spreads + order_levels = { + 1: {"spread_factor": 1.7, "order_amount_usd": Decimal("13")}, + 2: {"spread_factor": 3.4, "order_amount_usd": Decimal("21")}, + } + position_mode: PositionMode = PositionMode.HEDGE + active_executors: List[PositionExecutor] = [] + stored_executors: List[PositionExecutor] = [] + + # Configure the parameters for the position + stop_loss: float = 0.03 + take_profit: float = 0.015 + time_limit: int = 3600 * 24 + executor_refresh_time: int = 30 + open_order_type = OrderType.LIMIT + take_profit_order_type: OrderType = OrderType.MARKET + stop_loss_order_type: OrderType = OrderType.MARKET + time_limit_order_type: OrderType = OrderType.MARKET + trailing_stop_activation_delta = 0.003 + trailing_stop_trailing_delta = 0.001 + # Here you can use for example the LastTrade price to use in your strategy + price_source = PriceType.MidPrice + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000)) + ] + + # Configure the leverage and order amount the bot is going to use + set_leverage_flag = None + leverage = 1 + inventory_balance_pct = Decimal("0.4") + inventory_balance_tol = Decimal("0.05") + _inventory_balanced = False + spreads = None + reference_price = None + + markets = {exchange: {trading_pair}} + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def get_csv_path(self) -> str: + today = datetime.datetime.today() + csv_path = data_path() + f"/{self.market_making_strategy_name}_position_executors_{self.exchange}_{self.trading_pair}_{today.day:02d}-{today.month:02d}-{today.year}.csv" + return csv_path + + @property + def all_candles_ready(self): + """ + Checks if the candlesticks are full. + """ + return all([candle.is_ready for candle in self.candles]) + + def get_active_executors(self): + return [signal_executor for signal_executor in self.active_executors + if not signal_executor.is_closed] + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + for candle in self.candles: + candle.start() + self._active_bids = {level: None for level in self.order_levels.keys()} + self._active_asks = {level: None for level in self.order_levels.keys()} + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + """ + if self.is_perpetual: + # we are going to close all the open positions when the bot stops + self.close_open_positions() + else: + self.check_and_rebalance_inventory() + for candle in self.candles: + candle.stop() + + def on_tick(self): + if self.is_perpetual: + self.check_and_set_leverage() + elif not self._inventory_balanced: + self.check_and_rebalance_inventory() + if self.all_candles_ready: + self.update_parameters() + self.check_and_create_executors() + self.clean_and_store_executors() + + def update_parameters(self): + candles_df = self.get_candles_with_features() + natr = candles_df["NATR_21"].iloc[-1] + bbp = candles_df["BBP_200_2.0"].iloc[-1] + price_multiplier = ((0.5 - bbp) / 0.5) * natr * 0.3 + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + self.spreads = natr + self.reference_price = price * Decimal(str(1 + price_multiplier)) + + def get_candles_with_features(self): + candles_df = self.candles[0].candles_df + candles_df.ta.bbands(length=200, append=True) + candles_df.ta.natr(length=21, scalar=2, append=True) + return candles_df + + def create_executor(self, side: TradeType, price: Decimal, amount_usd: Decimal): + position_config = PositionConfig( + timestamp=self.current_timestamp, + trading_pair=self.trading_pair, + exchange=self.exchange, + side=side, + amount=amount_usd / price, + take_profit=self.take_profit, + stop_loss=self.stop_loss, + time_limit=self.time_limit, + entry_price=price, + open_order_type=self.open_order_type, + take_profit_order_type=self.take_profit_order_type, + stop_loss_order_type=self.stop_loss_order_type, + time_limit_order_type=self.time_limit_order_type, + trailing_stop=TrailingStop( + activation_price_delta=self.trailing_stop_activation_delta, + trailing_delta=self.trailing_stop_trailing_delta + ), + leverage=self.leverage, + ) + executor = PositionExecutor( + strategy=self, + position_config=position_config, + ) + return executor + + def check_and_set_leverage(self): + if not self.set_leverage_flag: + for connector in self.connectors.values(): + for trading_pair in connector.trading_pairs: + connector.set_position_mode(self.position_mode) + connector.set_leverage(trading_pair=trading_pair, leverage=self.leverage) + self.set_leverage_flag = True + + def clean_and_store_executors(self): + executors_to_store = [] + for level, executor in self._active_bids.items(): + if executor: + age = time.time() - executor.position_config.timestamp + if age > self.executor_refresh_time and executor.executor_status == PositionExecutorStatus.NOT_STARTED: + executor.early_stop() + if executor.is_closed: + executors_to_store.append(executor) + self._active_bids[level] = None + for level, executor in self._active_asks.items(): + if executor: + age = time.time() - executor.position_config.timestamp + if age > self.executor_refresh_time and executor.executor_status == PositionExecutorStatus.NOT_STARTED: + executor.early_stop() + if executor.is_closed: + executors_to_store.append(executor) + self._active_asks[level] = None + + csv_path = self.get_csv_path() + if not os.path.exists(csv_path): + df_header = pd.DataFrame([("timestamp", + "exchange", + "trading_pair", + "side", + "amount", + "trade_pnl", + "trade_pnl_quote", + "cum_fee_quote", + "net_pnl_quote", + "net_pnl", + "close_timestamp", + "executor_status", + "close_type", + "entry_price", + "close_price", + "sl", + "tp", + "tl", + "open_order_type", + "take_profit_order_type", + "stop_loss_order_type", + "time_limit_order_type", + "leverage" + )]) + df_header.to_csv(csv_path, mode='a', header=False, index=False) + for executor in executors_to_store: + self.stored_executors.append(executor) + df = pd.DataFrame([(executor.position_config.timestamp, + executor.exchange, + executor.trading_pair, + executor.side, + executor.amount, + executor.trade_pnl, + executor.trade_pnl_quote, + executor.cum_fee_quote, + executor.net_pnl_quote, + executor.net_pnl, + executor.close_timestamp, + executor.executor_status, + executor.close_type, + executor.entry_price, + executor.close_price, + executor.position_config.stop_loss, + executor.position_config.take_profit, + executor.position_config.time_limit, + executor.open_order_type, + executor.take_profit_order_type, + executor.stop_loss_order_type, + executor.time_limit_order_type, + self.leverage)]) + df.to_csv(self.get_csv_path(), mode='a', header=False, index=False) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def market_data_extra_info(self): + return ["\n"] + + def format_status(self) -> str: + """ + Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + if len(self.stored_executors) > 0: + lines.extend(["\n################################## Closed Executors ##################################"]) + for executor in [executor for executor in self.stored_executors if executor.close_type not in [CloseType.EXPIRED, CloseType.INSUFFICIENT_BALANCE]]: + lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) + lines.extend(executor.to_format_status()) + lines.extend([ + "-----------------------------------------------------------------------------------------------------------"]) + + lines.extend(["\n################################## Active Bids ##################################"]) + for level, executor in self._active_bids.items(): + if executor: + lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) + lines.extend(executor.to_format_status()) + lines.extend([ + "-----------------------------------------------------------------------------------------------------------"]) + lines.extend(["\n################################## Active Asks ##################################"]) + for level, executor in self._active_asks.items(): + if executor: + lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) + lines.extend(executor.to_format_status()) + lines.extend([ + "-----------------------------------------------------------------------------------------------------------"]) + if self.all_candles_ready: + lines.extend(["\n################################## Market Data ##################################\n"]) + lines.extend(self.market_data_extra_info()) + else: + lines.extend(["", " No data collected."]) + + return "\n".join(lines) + + def check_and_rebalance_inventory(self): + base_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[0]) + quote_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[1]) + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + total_balance = base_balance + quote_balance / price + balance_ratio = base_balance / total_balance + if abs(balance_ratio - self.inventory_balance_pct) < self.inventory_balance_tol: + self._inventory_balanced = True + return + base_target_balance = total_balance * Decimal(self.inventory_balance_pct) + base_delta = base_target_balance - base_balance + if base_delta > 0: + self.buy(self.exchange, self.trading_pair, base_delta, OrderType.MARKET, price) + elif base_delta < 0: + self.sell(self.exchange, self.trading_pair, base_delta, OrderType.MARKET, price) + self._inventory_balanced = True + + def check_and_create_executors(self): + for level, executor in self._active_asks.items(): + if executor is None: + level_config = self.order_levels[level] + price = self.reference_price * Decimal(1 + self.spreads * level_config["spread_factor"]) + executor = self.create_executor(side=TradeType.SELL, price=price, amount_usd=level_config["order_amount_usd"]) + self._active_asks[level] = executor + + for level, executor in self._active_bids.items(): + if executor is None: + level_config = self.order_levels[level] + price = self.reference_price * Decimal(1 - self.spreads * level_config["spread_factor"]) + executor = self.create_executor(side=TradeType.BUY, price=price, amount_usd=level_config["order_amount_usd"]) + self._active_bids[level] = executor diff --git a/scripts/archived_scripts/examples_using_smart_components/pmm_with_shifted_mid_dynamic_spreads.py b/scripts/archived_scripts/examples_using_smart_components/pmm_with_shifted_mid_dynamic_spreads.py new file mode 100644 index 0000000..3bbd438 --- /dev/null +++ b/scripts/archived_scripts/examples_using_smart_components/pmm_with_shifted_mid_dynamic_spreads.py @@ -0,0 +1,172 @@ +import logging +from decimal import Decimal +from typing import Dict, List + +import pandas_ta as ta # noqa: F401 + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import BuyOrderCompletedEvent, OrderFilledEvent, SellOrderCompletedEvent +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class PMMhShiftedMidPriceDynamicSpread(ScriptStrategyBase): + """ + Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-with-shifted-mid-price-and-dynamic-spreads-63cc765486dd42228d3da0b32537fc92 + Video: - + Description: + The bot will place two orders around the `reference_price` (mid price or last traded price +- %based on `RSI` value ) + in a `trading_pair` on `exchange`, with a distance defined by the `spread` multiplied by `spreads_factors` + based on `NATR`. Every `order_refresh_time` seconds, the bot will cancel and replace the orders. + """ + # Define the variables that we are going to use for the spreads + # We are going to divide the NATR by the spread_base to get the spread_multiplier + # If NATR = 0.002 = 0.2% --> the spread_factor will be 0.002 / 0.008 = 0.25 + # Formula: spread_multiplier = NATR / spread_base + spread_base = 0.008 + spread_multiplier = 1 + + # Define the price source and the multiplier that shifts the price + # We are going to use the max price shift in percentage as the middle of the NATR + # If NATR = 0.002 = 0.2% --> the maximum shift from the mid-price is 0.2%, and that will be calculated with RSI + # If RSI = 100 --> it will shift the mid-price -0.2% and if RSI = 0 --> it will shift the mid-price +0.2% + # Formula: price_multiplier = ((50 - RSI) / 50)) * NATR + price_source = PriceType.MidPrice + price_multiplier = 1 + + # Trading conf + order_refresh_time = 15 + order_amount = 7 + trading_pair = "RLC-USDT" + exchange = "binance" + + # Creating instance of the candles + candles = CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000)) + + # Variables to store the volume and quantity of orders + + total_sell_orders = 0 + total_buy_orders = 0 + total_sell_volume = 0 + total_buy_volume = 0 + create_timestamp = 0 + + markets = {exchange: {trading_pair}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + self.candles.start() + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + """ + # we are going to close all the open positions when the bot stops + self.candles.stop() + + def on_tick(self): + if self.create_timestamp <= self.current_timestamp and self.candles.is_ready: + self.cancel_all_orders() + self.update_multipliers() + proposal: List[OrderCandidate] = self.create_proposal() + proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal) + self.place_orders(proposal_adjusted) + self.create_timestamp = self.order_refresh_time + self.current_timestamp + + def get_candles_with_features(self): + candles_df = self.candles.candles_df + candles_df.ta.rsi(length=14, append=True) + candles_df.ta.natr(length=14, scalar=0.5, append=True) + return candles_df + + def update_multipliers(self): + candles_df = self.get_candles_with_features() + self.price_multiplier = ((50 - candles_df["RSI_14"].iloc[-1]) / 50) * (candles_df["NATR_14"].iloc[-1]) + self.spread_multiplier = candles_df["NATR_14"].iloc[-1] / self.spread_base + + def create_proposal(self) -> List[OrderCandidate]: + mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + reference_price = mid_price * Decimal(str(1 + self.price_multiplier)) + spreads_adjusted = self.spread_multiplier * self.spread_base + buy_price = reference_price * Decimal(1 - spreads_adjusted) + sell_price = reference_price * Decimal(1 + spreads_adjusted) + + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=Decimal(self.order_amount), price=buy_price) + + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=Decimal(self.order_amount), price=sell_price) + + return [buy_order, sell_order] + + def adjust_proposal_to_budget(self, proposal: List[OrderCandidate]) -> List[OrderCandidate]: + proposal_adjusted = self.connectors[self.exchange].budget_checker.adjust_candidates(proposal, all_or_none=True) + return proposal_adjusted + + def place_orders(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + if order.amount != 0: + self.place_order(connector_name=self.exchange, order=order) + else: + self.logger().info(f"Not enough funds to place the {order.order_type} order") + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def cancel_all_orders(self): + for order in self.get_active_orders(connector_name=self.exchange): + self.cancel(self.exchange, order.trading_pair, order.client_order_id) + + def did_fill_order(self, event: OrderFilledEvent): + msg = ( + f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.total_buy_volume += event.amount if event.trade_type == TradeType.BUY else 0 + self.total_sell_volume += event.amount if event.trade_type == TradeType.SELL else 0 + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + self.total_buy_orders += 1 + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + self.total_sell_orders += 1 + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + reference_price = mid_price * Decimal(str(1 + self.price_multiplier)) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + lines.extend(["", f" Total Buy Orders: {self.total_buy_orders:.2f} | Total Sell Orders: {self.total_sell_orders:.2f}"]) + lines.extend(["", f" Total Buy Volume: {self.total_buy_volume:.2f} | Total Sell Volume: {self.total_sell_volume:.2f}"]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + lines.extend(["", f" Spread Base: {self.spread_base:.4f} | Spread Adjusted: {(self.spread_multiplier * self.spread_base):.4f} | Spread Multiplier: {self.spread_multiplier:.4f}"]) + lines.extend(["", f" Mid Price: {mid_price:.4f} | Price shifted: {reference_price:.4f} | Price Multiplier: {self.price_multiplier:.4f}"]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + candles_df = self.get_candles_with_features() + lines.extend([f"Candles: {self.candles.name} | Interval: {self.candles.interval}"]) + lines.extend([" " + line for line in candles_df.tail().to_string(index=False).split("\n")]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + return "\n".join(lines) diff --git a/scripts/bot-battle-oct-23/dashboard.png b/scripts/bot-battle-oct-23/dashboard.png new file mode 100644 index 0000000..2682e91 Binary files /dev/null and b/scripts/bot-battle-oct-23/dashboard.png differ diff --git a/scripts/bot-battle-oct-23/history.png b/scripts/bot-battle-oct-23/history.png new file mode 100644 index 0000000..f32d40a Binary files /dev/null and b/scripts/bot-battle-oct-23/history.png differ diff --git a/scripts/bot-battle-oct-23/status.png b/scripts/bot-battle-oct-23/status.png new file mode 100644 index 0000000..c153781 Binary files /dev/null and b/scripts/bot-battle-oct-23/status.png differ diff --git a/scripts/bot-battle-oct-23/trades_v2_market-making_dma.csv b/scripts/bot-battle-oct-23/trades_v2_market-making_dma.csv new file mode 100644 index 0000000..582306f --- /dev/null +++ b/scripts/bot-battle-oct-23/trades_v2_market-making_dma.csv @@ -0,0 +1,187 @@ +exchange_trade_id,config_file_path,strategy,market,symbol,base_asset,quote_asset,timestamp,order_id,trade_type,order_type,price,amount,leverage,trade_fee,trade_fee_in_quote,position,age +12541009,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698440184000,x-3QreWesySSIUT608b8e9bb96e996f4,SELL,LIMIT,0.1058000,141,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298356'}]}",0.00298356,OPEN,00:00:36 +12543561,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698446347000,x-3QreWesyBSIUT608ba503b183396f4,BUY,LIMIT,0.1061000,141,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299202'}]}",0.00299202,OPEN,00:03:04 +12543797,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698447004000,x-3QreWesySSIUT608ba7669a02996f4,SELL,LIMIT,0.1062000,283,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601092'}]}",0.00601092,OPEN,00:03:21 +12544068,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698447701000,x-3QreWesyBSIUT608baabeb5be596f4,BUY,MARKET,0.1059000,283,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01498485'}]}",0.01498485,CLOSE,00:00:00 +12544774,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698449688000,x-3QreWesySSIUT608bb1e0e322396f4,SELL,LIMIT,0.1063000,282,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00599532'}]}",0.00599532,OPEN,00:01:12 +12544859,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698449747000,x-3QreWesySSIUT608bb25d844a396f4,SELL,MARKET,0.1063000,141,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00749415'}]}",0.00749415,CLOSE,00:00:00 +12546236,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698453423000,x-3QreWesyBSIUT608bbf693676d96f4,BUY,LIMIT,0.1067000,140,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298760'}]}",0.00298760,OPEN,00:02:55 +12546311,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698453437000,x-3QreWesyBSIUT608bbf05ce59d96f4,BUY,LIMIT,0.1064000,280,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595840'}]}",0.00595840,OPEN,00:04:53 +12546553,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698453867000,x-3QreWesySSIUT608bc1b7092df96f4,SELL,MARKET,0.1066000,280,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01492400'}]}",0.01492400,CLOSE,00:00:00 +12548284,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698458330000,x-3QreWesySSIUT608bd2570fa5e96f4,SELL,MARKET,0.1072000,140,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00750400'}]}",0.00750400,CLOSE,00:00:00 +12548719,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698459087000,x-3QreWesyBSIUT608bd4c0ffc7f96f4,BUY,LIMIT,0.1069000,140,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299320'}]}",0.00299320,OPEN,00:01:49 +12549320,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698461208000,x-3QreWesySSIUT608bdd0f987cd96f4,SELL,MARKET,0.1073000,140,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00751100'}]}",0.00751100,CLOSE,00:00:00 +12549918,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698461736000,x-3QreWesyBSIUT608bde4dde5aa96f4,BUY,LIMIT,0.1074000,139,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298572'}]}",0.00298572,OPEN,00:03:15 +12551663,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698464844000,x-3QreWesySSIUT608bea9b3013996f4,SELL,MARKET,0.1077000,139,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748515'}]}",0.00748515,CLOSE,00:00:00 +12553624,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698468428000,x-3QreWesyBSIUT608bf7aa6fbbe96f4,BUY,LIMIT,0.1085000,137,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297290'}]}",0.00297290,OPEN,00:01:19 +12554235,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698468779000,x-3QreWesySSIUT608bf9443405496f4,SELL,MARKET,0.1091000,137,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747335'}]}",0.00747335,CLOSE,00:00:00 +12555546,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469161000,x-3QreWesyBSIUT608bfab079d5096f4,BUY,MARKET,0.1100000,141,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00775500'}]}",0.00775500,CLOSE,00:00:00 +12555993,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469212000,x-3QreWesySSIUT608bfabfe46ce96f4,SELL,LIMIT,0.1104000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00300288'}]}",0.00300288,OPEN,00:00:35 +12556225,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469233000,x-3QreWesyBSIUT608bfaf4f64d696f4,BUY,MARKET,0.1099000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747320'}]}",0.00747320,CLOSE,n/a +12556879,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469361000,x-3QreWesySSIUT608bfb044c2de96f4,SELL,LIMIT,0.1102000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299744'}]}",0.00299744,OPEN,00:01:52 +12557031,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469369000,x-3QreWesyBSIUT608bfb76d1d1496f4,BUY,MARKET,0.1106000,282,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01559460'}]}",0.01559460,CLOSE,n/a +12557963,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469453000,x-3QreWesySSIUT608bfb864ec7496f4,SELL,LIMIT,0.1114000,270,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601560'}]}",0.00601560,OPEN,00:01:08 +12558541,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469475000,x-3QreWesyBSIUT608bfbdc34a4596f4,BUY,MARKET,0.1109000,270,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01497150'}]}",0.01497150,CLOSE,n/a +12561167,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469665000,x-3QreWesySSIUT608bfbebb7ba596f4,SELL,LIMIT,0.1122000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00603636'}]}",0.00603636,OPEN,00:02:54 +12562155,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698469685000,x-3QreWesyBSIUT608bfca4a8cb996f4,BUY,MARKET,0.1119000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01505055'}]}",0.01505055,CLOSE,00:00:00 +12566606,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698470746000,x-3QreWesyBSIUT608c009881cde96f4,BUY,MARKET,0.1100000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748000'}]}",0.00748000,CLOSE,n/a +12566696,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698470827000,x-3QreWesySSIUT608c00a7f618896f4,SELL,LIMIT,0.1100000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299200'}]}",0.00299200,OPEN,00:01:04 +12567287,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698471190000,x-3QreWesyBSIUT608c023f4604f96f4,BUY,MARKET,0.1098000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00746640'}]}",0.00746640,CLOSE,n/a +12567365,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698471221000,x-3QreWesySSIUT608c024ebc82396f4,SELL,LIMIT,0.1098000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298656'}]}",0.00298656,OPEN,00:00:15 +12568909,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698472096000,x-3QreWesyBSIUT608c059fec5fa96f4,BUY,MARKET,0.1096000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745280'}]}",0.00745280,CLOSE,n/a +12571594,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698474372000,x-3QreWesySSIUT608c0df014fbe96f4,SELL,LIMIT,0.1093000,137,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299482'}]}",0.00299482,OPEN,00:00:44 +12573951,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698477499000,x-3QreWesySSIUT608c19042311a96f4,SELL,LIMIT,0.1096000,274,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00600608'}]}",0.00600608,OPEN,00:03:17 +12578515,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698480379000,x-3QreWesyBSIUT608c23d5b575696f4,BUY,LIMIT,0.1103000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297810'}]}",0.00297810,OPEN,00:02:53 +12578597,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698480394000,x-3QreWesyBSIUT608c23c29b1a596f4,BUY,LIMIT,0.1100000,271,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596200'}]}",0.00596200,OPEN,00:03:28 +12578810,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698480585000,x-3QreWesySSIUT608c253f1849096f4,SELL,MARKET,0.1104000,271,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01495920'}]}",0.01495920,CLOSE,00:00:00 +12578875,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698480660000,x-3QreWesySSIUT608c2586c9d7596f4,SELL,MARKET,0.1105000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745875'}]}",0.00745875,CLOSE,00:00:00 +12581552,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698483404000,x-3QreWesyBSIUT608c2f0957f7196f4,BUY,LIMIT,0.1107000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298890'}]}",0.00298890,OPEN,00:03:11 +12581586,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698483412000,x-3QreWesyBSIUT608c2ebeeb6ba96f4,BUY,LIMIT,0.1106000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595028'}]}",0.00595028,OPEN,00:04:37 +12582833,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698484142000,x-3QreWesySSIUT608c327f3109b96f4,SELL,MARKET,0.1108000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01490260'}]}",0.01490260,CLOSE,00:00:00 +12583069,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698484390000,x-3QreWesySSIUT608c336c50f0996f4,SELL,MARKET,0.1109000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748575'}]}",0.00748575,CLOSE,00:00:00 +12583405,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698484694000,x-3QreWesyBSIUT608c337b53de496f4,BUY,LIMIT,0.1104000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298080'}]}",0.00298080,OPEN,00:04:48 +12583679,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698484826000,x-3QreWesySSIUT608c350b6474c96f4,SELL,MARKET,0.1107000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747225'}]}",0.00747225,CLOSE,00:00:00 +12587242,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698488555000,x-3QreWesyBSIUT608c4214db5eb96f4,BUY,LIMIT,0.1093000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297296'}]}",0.00297296,OPEN,00:03:50 +12587289,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698488580000,x-3QreWesyBSIUT608c4307aff4e96f4,BUY,MARKET,0.1094000,274,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01498780'}]}",0.01498780,CLOSE,n/a +12588698,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698490684000,x-3QreWesySSIUT608c4ade8df0096f4,SELL,MARKET,0.1095000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744600'}]}",0.00744600,CLOSE,n/a +12588819,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698490828000,x-3QreWesyBSIUT608c4aef0bbf296f4,BUY,LIMIT,0.1094000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297568'}]}",0.00297568,OPEN,00:02:07 +12589072,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698491103000,x-3QreWesySSIUT608c4b580133f96f4,SELL,LIMIT,0.1101000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601146'}]}",0.00601146,OPEN,00:04:52 +12589213,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698491114000,x-3QreWesySSIUT608c4c77e2bfe96f4,SELL,MARKET,0.1100000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748000'}]}",0.00748000,CLOSE,00:00:01 +12590698,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698492109000,x-3QreWesyBSIUT608c501278b9d96f4,BUY,LIMIT,0.1104000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298080'}]}",0.00298080,OPEN,00:00:28 +12590813,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698492116000,x-3QreWesyBSIUT608c50105808c96f4,BUY,LIMIT,0.1101000,271,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596742'}]}",0.00596742,OPEN,00:00:38 +12591408,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698492320000,x-3QreWesySSIUT608c50f68d1a396f4,SELL,MARKET,0.1103000,271,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01494565'}]}",0.01494565,CLOSE,n/a +12592196,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698493079000,x-3QreWesyBSIUT608c536297aa396f4,BUY,LIMIT,0.1096000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596224'}]}",0.00596224,OPEN,00:01:49 +12592332,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698493087000,x-3QreWesyBSIUT608c53d1fa1e396f4,BUY,MARKET,0.1095000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01494675'}]}",0.01494675,CLOSE,00:00:01 +12593486,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698493915000,x-3QreWesySSIUT608c5640cb96696f4,SELL,LIMIT,0.1097000,274,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601156'}]}",0.00601156,OPEN,00:02:56 +12595231,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698494828000,x-3QreWesySSIUT608c5a4e0b3f396f4,SELL,MARKET,0.1098000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01493280'}]}",0.01493280,CLOSE,00:00:00 +12595757,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698495229000,x-3QreWesyBSIUT608c5bccf63f896f4,BUY,MARKET,0.1094000,274,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01498780'}]}",0.01498780,CLOSE,00:00:00 +12596681,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698496081000,x-3QreWesyBSIUT608c5de8ee48796f4,BUY,LIMIT,0.1087000,274,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595676'}]}",0.00595676,OPEN,00:04:46 +12596759,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698496096000,x-3QreWesyBSIUT608c5f073425096f4,BUY,MARKET,0.1089000,137,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745965'}]}",0.00745965,CLOSE,00:00:01 +12599571,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698497765000,x-3QreWesySSIUT608c6505dac9296f4,SELL,LIMIT,0.1084000,138,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299184'}]}",0.00299184,OPEN,00:01:01 +12599810,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698497913000,x-3QreWesyBSIUT608c65ccd8b6596f4,BUY,MARKET,0.1081000,138,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745890'}]}",0.00745890,CLOSE,00:00:00 +12600344,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698498352000,x-3QreWesySSIUT608c670ca907596f4,SELL,LIMIT,0.1084000,138,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299184'}]}",0.00299184,OPEN,00:01:44 +12601432,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698499394000,x-3QreWesySSIUT608c6b50b2d1496f4,SELL,MARKET,0.1091000,274,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01494670'}]}",0.01494670,CLOSE,00:00:00 +12602865,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698501514000,x-3QreWesySSIUT608c72543cedb96f4,SELL,LIMIT,0.1094000,275,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601700'}]}",0.00601700,OPEN,00:03:58 +12604202,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698503306000,x-3QreWesyBSIUT608c79e334e0a96f4,BUY,MARKET,0.1092000,275,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01501500'}]}",0.01501500,CLOSE,00:00:01 +12608348,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698509211000,x-3QreWesySSIUT608c8f3f9564b96f4,SELL,LIMIT,0.1103000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00602238'}]}",0.00602238,OPEN,00:02:53 +12609020,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698509749000,x-3QreWesyBSIUT608c91e44f2a496f4,BUY,MARKET,0.1099000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01500135'}]}",0.01500135,CLOSE,n/a +12610063,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698511755000,x-3QreWesySSIUT608c990a7803296f4,SELL,LIMIT,0.1104000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00600576'}]}",0.00600576,OPEN,00:01:27 +12610786,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698512999000,x-3QreWesyBSIUT608c9dffc826e96f4,BUY,MARKET,0.1102000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01498720'}]}",0.01498720,CLOSE,00:00:00 +12611532,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698513699000,x-3QreWesySSIUT608ca06d39cdf96f4,SELL,LIMIT,0.1102000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601692'}]}",0.00601692,OPEN,00:00:48 +12612834,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698515157000,x-3QreWesyBSIUT608ca60980f2b96f4,BUY,MARKET,0.1100000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01501500'}]}",0.01501500,CLOSE,n/a +12614193,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698517996000,x-3QreWesySSIUT608caf8eaa0f096f4,SELL,LIMIT,0.1098000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00599508'}]}",0.00599508,OPEN,00:04:44 +12616057,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698521739000,x-3QreWesyBSIUT608cbe8ecb61d96f4,BUY,MARKET,0.1096000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496040'}]}",0.01496040,CLOSE,n/a +12619366,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698528123000,x-3QreWesySSIUT608cd63f9974796f4,SELL,LIMIT,0.1107000,271,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00599994'}]}",0.00599994,OPEN,00:00:25 +12619603,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698528493000,x-3QreWesyBSIUT608cd7b70c11596f4,BUY,MARKET,0.1105000,271,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01497275'}]}",0.01497275,CLOSE,00:00:02 +12620275,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698529923000,x-3QreWesySSIUT608cdc837bc8d96f4,SELL,LIMIT,0.1105000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601120'}]}",0.00601120,OPEN,00:02:23 +12621001,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698531725000,x-3QreWesyBSIUT608ce2c06878996f4,BUY,LIMIT,0.1100000,271,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596200'}]}",0.00596200,OPEN,00:04:30 +12621149,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698531964000,x-3QreWesyBSIUT608ce4a68898696f4,BUY,MARKET,0.1100000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496000'}]}",0.01496000,CLOSE,00:00:00 +12622508,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698533702000,x-3QreWesySSIUT608ceb1fe4efe96f4,SELL,MARKET,0.1102000,271,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01493210'}]}",0.01493210,CLOSE,n/a +12623855,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698535749000,x-3QreWesySSIUT608cf1bab79fc96f4,SELL,LIMIT,0.1101000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601146'}]}",0.00601146,OPEN,00:04:34 +12625532,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698538410000,x-3QreWesyBSIUT608cfca98aa3896f4,BUY,MARKET,0.1099000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01500135'}]}",0.01500135,CLOSE,00:00:00 +12625545,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698538420000,x-3QreWesyBSIUT608cfbb8eb6f796f4,BUY,LIMIT,0.1097000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596768'}]}",0.00596768,OPEN,00:04:23 +12625694,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698538545000,x-3QreWesySSIUT608cfcbb45ce696f4,SELL,LIMIT,0.1101000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601146'}]}",0.00601146,OPEN,00:01:58 +12625774,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698538569000,x-3QreWesySSIUT608cfd413063096f4,SELL,MARKET,0.1100000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496000'}]}",0.01496000,CLOSE,n/a +12626144,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698539050000,x-3QreWesyBSIUT608cff0c882e196f4,BUY,MARKET,0.1099000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01500135'}]}",0.01500135,CLOSE,00:00:00 +12626171,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698539117000,x-3QreWesyBSIUT608cfe7f89bf596f4,BUY,LIMIT,0.1097000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596768'}]}",0.00596768,OPEN,00:03:34 +12626400,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698539432000,x-3QreWesySSIUT608d0077f434496f4,SELL,MARKET,0.1100000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496000'}]}",0.01496000,CLOSE,00:00:00 +12626933,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698540146000,x-3QreWesySSIUT608d02aa7925896f4,SELL,LIMIT,0.1102000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601692'}]}",0.00601692,OPEN,00:02:05 +12627499,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698540789000,x-3QreWesyBSIUT608d0540d8b3796f4,BUY,LIMIT,0.1101000,271,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596742'}]}",0.00596742,OPEN,00:01:13 +12627821,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698541046000,x-3QreWesyBSIUT608d067b77f2896f4,BUY,MARKET,0.1100000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01501500'}]}",0.01501500,CLOSE,00:00:00 +12631116,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698542621000,x-3QreWesyBSIUT608d0c5947d2e96f4,BUY,MARKET,0.1079000,138,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744510'}]}",0.00744510,CLOSE,00:00:00 +12632299,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698543415000,x-3QreWesySSIUT608d0ec79b1f296f4,SELL,LIMIT,0.1091000,138,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00301116'}]}",0.00301116,OPEN,00:02:22 +12633407,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698545120000,x-3QreWesyBSIUT608d15a86ccf496f4,BUY,MARKET,0.1086000,138,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00749340'}]}",0.00749340,CLOSE,00:00:00 +12633724,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698545399000,x-3QreWesySSIUT608d15b85d23c96f4,SELL,LIMIT,0.1090000,138,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00300840'}]}",0.00300840,OPEN,00:04:23 +12634666,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698547096000,x-3QreWesyBSIUT608d1d04b22c996f4,BUY,MARKET,0.1088000,138,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00750720'}]}",0.00750720,CLOSE,n/a +12634777,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698547258000,x-3QreWesySSIUT608d1d169bae796f4,SELL,LIMIT,0.1090000,137,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298660'}]}",0.00298660,OPEN,00:02:24 +12634874,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698547259000,x-3QreWesySSIUT608d1d040399c96f4,SELL,LIMIT,0.1093000,275,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601150'}]}",0.00601150,OPEN,00:02:45 +12636890,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698551720000,x-3QreWesyBSIUT608d2e3ee17cb96f4,BUY,MARKET,0.1089000,275,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01497375'}]}",0.01497375,CLOSE,n/a +12637434,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698552167000,x-3QreWesyBSIUT608d2fe8f6d1596f4,BUY,MARKET,0.1087000,137,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744595'}]}",0.00744595,CLOSE,00:00:00 +12637753,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698552420000,x-3QreWesySSIUT608d2ff97648a96f4,SELL,LIMIT,0.1087000,138,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00300012'}]}",0.00300012,OPEN,00:03:56 +12638338,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698553121000,x-3QreWesySSIUT608d3306cbb0396f4,SELL,LIMIT,0.1093000,275,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00601150'}]}",0.00601150,OPEN,00:01:58 +12640483,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698557272000,x-3QreWesyBSIUT608d42edbaefa96f4,BUY,MARKET,0.1091000,275,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01500125'}]}",0.01500125,CLOSE,n/a +12640905,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698558298000,x-3QreWesySSIUT608d468d1090596f4,SELL,LIMIT,0.1095000,274,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00600060'}]}",0.00600060,OPEN,00:00:55 +12641132,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698558653000,x-3QreWesyBSIUT608d4812c7c4996f4,BUY,MARKET,0.1093000,274,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01497410'}]}",0.01497410,CLOSE,00:00:00 +12644135,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698563447000,x-3QreWesySSIUT608d59e1edaee96f4,SELL,LIMIT,0.1099000,273,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00600054'}]}",0.00600054,OPEN,00:00:13 +12645157,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698564536000,x-3QreWesySSIUT608d5dfb91d2a96f4,SELL,MARKET,0.1103000,271,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01494565'}]}",0.01494565,CLOSE,00:00:02 +12646857,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698566633000,x-3QreWesySSIUT608d65cc9670d96f4,SELL,MARKET,0.1106000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00746550'}]}",0.00746550,CLOSE,n/a +12647657,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698567056000,x-3QreWesyBSIUT608d670ca8e8396f4,BUY,LIMIT,0.1108000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299160'}]}",0.00299160,OPEN,00:01:28 +12648372,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698567897000,x-3QreWesySSIUT608d6a8392d8096f4,SELL,MARKET,0.1110000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00749250'}]}",0.00749250,CLOSE,n/a +12653134,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698573851000,x-3QreWesyBSIUT608d7fdf7a83596f4,BUY,LIMIT,0.1106000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298620'}]}",0.00298620,OPEN,00:03:40 +12653460,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698574019000,x-3QreWesyBSIUT608d8059fa90b96f4,BUY,LIMIT,0.1102000,270,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595080'}]}",0.00595080,OPEN,00:04:19 +12654292,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698575008000,x-3QreWesySSIUT608d84ffd3a9d96f4,SELL,MARKET,0.1104000,270,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01490400'}]}",0.01490400,CLOSE,n/a +12655680,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698576655000,x-3QreWesySSIUT608d8b22dde9e96f4,SELL,MARKET,0.1108000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747900'}]}",0.00747900,CLOSE,n/a +12656253,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698576927000,x-3QreWesyBSIUT608d8b32223af96f4,BUY,LIMIT,0.1106000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298620'}]}",0.00298620,OPEN,00:04:16 +12656591,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698577073000,x-3QreWesySSIUT608d8cb1221ab96f4,SELL,MARKET,0.1108000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747900'}]}",0.00747900,CLOSE,n/a +12658481,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698579056000,x-3QreWesyBSIUT608d93df7ecba96f4,BUY,LIMIT,0.1110000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297480'}]}",0.00297480,OPEN,00:00:56 +12658657,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698579082000,x-3QreWesyBSIUT608d93429147596f4,BUY,LIMIT,0.1109000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596642'}]}",0.00596642,OPEN,00:04:06 +12659371,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698579126000,x-3QreWesySSIUT608d94574963d96f4,SELL,MARKET,0.1112000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01495640'}]}",0.01495640,CLOSE,00:00:00 +12659372,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698579126000,x-3QreWesySSIUT608d945746e9f96f4,SELL,MARKET,0.1112000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745040'}]}",0.00745040,CLOSE,00:00:00 +12663284,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698584067000,x-3QreWesyBSIUT608da6258083b96f4,BUY,LIMIT,0.1113000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298284'}]}",0.00298284,OPEN,00:02:42 +12670735,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698590300000,x-3QreWesySSIUT608dbdf79471796f4,SELL,MARKET,0.1115000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747050'}]}",0.00747050,CLOSE,n/a +12671373,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698590823000,x-3QreWesyBSIUT608dbf354af7d96f4,BUY,LIMIT,0.1113000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298284'}]}",0.00298284,OPEN,00:03:10 +12672097,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698591676000,x-3QreWesySSIUT608dc3184304096f4,SELL,MARKET,0.1115000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747050'}]}",0.00747050,CLOSE,n/a +12673245,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698592136000,x-3QreWesyBSIUT608dc4568c6d996f4,BUY,LIMIT,0.1115000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298820'}]}",0.00298820,OPEN,00:02:06 +12674111,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698592267000,x-3QreWesySSIUT608dc54be86a096f4,SELL,MARKET,0.1117000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748390'}]}",0.00748390,CLOSE,00:00:00 +12675084,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698593021000,x-3QreWesyBSIUT608dc7b8a330e96f4,BUY,LIMIT,0.1115000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298820'}]}",0.00298820,OPEN,00:01:43 +12678794,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698595935000,x-3QreWesySSIUT608dd2f596bd296f4,SELL,MARKET,0.1117000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748390'}]}",0.00748390,CLOSE,00:00:00 +12679182,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698596408000,x-3QreWesyBSIUT608dd437fa57296f4,BUY,LIMIT,0.1116000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299088'}]}",0.00299088,OPEN,00:02:16 +12684582,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698602287000,x-3QreWesyBSIUT608de99c9249996f4,BUY,LIMIT,0.1112000,268,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596032'}]}",0.00596032,OPEN,00:04:33 +12684929,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698602611000,x-3QreWesySSIUT608debd49284296f4,SELL,MARKET,0.1117000,268,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496780'}]}",0.01496780,CLOSE,00:00:00 +12685311,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698602952000,x-3QreWesySSIUT608ded190b16896f4,SELL,MARKET,0.1118000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00749060'}]}",0.00749060,CLOSE,00:00:01 +12686647,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698604463000,x-3QreWesyBSIUT608df1e87a45b96f4,BUY,LIMIT,0.1114000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298552'}]}",0.00298552,OPEN,00:03:40 +12686842,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698604658000,x-3QreWesySSIUT608df3742fcb796f4,SELL,MARKET,0.1116000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747720'}]}",0.00747720,CLOSE,00:00:00 +12688497,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698606147000,x-3QreWesyBSIUT608df8419f0fc96f4,BUY,LIMIT,0.1116000,134,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299088'}]}",0.00299088,OPEN,00:03:21 +12691898,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698611413000,x-3QreWesyBSIUT608e0be1f695296f4,BUY,LIMIT,0.1113000,268,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596568'}]}",0.00596568,OPEN,00:03:18 +12692114,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698611867000,x-3QreWesySSIUT608e0e4fe3ea396f4,SELL,MARKET,0.1116000,268,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01495440'}]}",0.01495440,CLOSE,00:00:00 +12693318,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698613811000,x-3QreWesySSIUT608e158db97f596f4,SELL,MARKET,0.1119000,134,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00749730'}]}",0.00749730,CLOSE,00:00:00 +12695563,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698616607000,x-3QreWesyBSIUT608e1f1225c5296f4,BUY,LIMIT,0.1116000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00296856'}]}",0.00296856,OPEN,00:04:01 +12696272,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698617277000,x-3QreWesySSIUT608e22774dfe796f4,SELL,MARKET,0.1119000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744135'}]}",0.00744135,CLOSE,00:00:00 +12698100,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698620378000,x-3QreWesyBSIUT608e2d33a9df096f4,BUY,LIMIT,0.1121000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298186'}]}",0.00298186,OPEN,00:03:39 +12698334,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698620548000,x-3QreWesySSIUT608e2ea6c59a996f4,SELL,MARKET,0.1123000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00746795'}]}",0.00746795,CLOSE,00:00:00 +12698682,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698620849000,x-3QreWesyBSIUT608e2eb7c784296f4,BUY,LIMIT,0.1123000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298718'}]}",0.00298718,OPEN,00:04:43 +12703020,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698626093000,x-3QreWesyBSIUT608e427618ea596f4,BUY,LIMIT,0.1114000,268,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00597104'}]}",0.00597104,OPEN,00:03:47 +12704873,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698627830000,x-3QreWesySSIUT608e49c86012c96f4,SELL,MARKET,0.1116000,268,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01495440'}]}",0.01495440,CLOSE,00:00:00 +12707000,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698631255000,x-3QreWesyBSIUT608e55a629a4f96f4,BUY,LIMIT,0.1107000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595566'}]}",0.00595566,OPEN,00:03:58 +12707684,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698631416000,x-3QreWesySSIUT608e5722e89e796f4,SELL,MARKET,0.1109000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01491605'}]}",0.01491605,CLOSE,00:00:00 +12708982,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698632969000,x-3QreWesyBSIUT608e5bf167b1396f4,BUY,LIMIT,0.1102000,270,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595080'}]}",0.00595080,OPEN,00:04:24 +12709255,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698633112000,x-3QreWesySSIUT608e5d74ca32d96f4,SELL,MARKET,0.1105000,270,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01491750'}]}",0.01491750,CLOSE,00:00:00 +12710648,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698633882000,x-3QreWesyBSIUT608e5fe347e0d96f4,BUY,LIMIT,0.1107000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595566'}]}",0.00595566,OPEN,00:01:57 +12710973,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698634063000,x-3QreWesySSIUT608e610051fe896f4,SELL,MARKET,0.1113000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01496985'}]}",0.01496985,CLOSE,n/a +12714006,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698638585000,x-3QreWesyBSIUT608e71d7c5a4696f4,BUY,MARKET,0.1104000,138,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00761760'}]}",0.00761760,CLOSE,00:00:00 +12715080,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698640791000,x-3QreWesySSIUT608e79082c63196f4,SELL,LIMIT,0.1110000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299700'}]}",0.00299700,OPEN,00:04:36 +12717647,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698643629000,x-3QreWesyBSIUT608e84a20d39696f4,BUY,MARKET,0.1108000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747900'}]}",0.00747900,CLOSE,00:00:00 +12717921,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698644071000,x-3QreWesySSIUT608e85e1d5e8396f4,SELL,LIMIT,0.1109000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299430'}]}",0.00299430,OPEN,00:01:47 +12718398,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698645263000,x-3QreWesyBSIUT608e8ab9c629696f4,BUY,MARKET,0.1106000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00746550'}]}",0.00746550,CLOSE,00:00:00 +12718611,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698645660000,x-3QreWesySSIUT608e8bf846a8c96f4,SELL,LIMIT,0.1105000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298350'}]}",0.00298350,OPEN,00:01:03 +12718935,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698646085000,x-3QreWesyBSIUT608e8dc99303296f4,BUY,MARKET,0.1103000,135,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744525'}]}",0.00744525,CLOSE,n/a +12720556,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698646376000,x-3QreWesyBSIUT608e8eddc3b2b96f4,BUY,MARKET,0.1095000,273,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01494675'}]}",0.01494675,CLOSE,00:00:00 +12721009,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698646540000,x-3QreWesySSIUT608e8f07d921f96f4,SELL,LIMIT,0.1099000,136,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298928'}]}",0.00298928,OPEN,00:02:00 +12723592,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698650281000,x-3QreWesyBSIUT608e9c4de85a396f4,BUY,LIMIT,0.1108000,269,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00596104'}]}",0.00596104,OPEN,00:04:58 +12728132,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698654954000,x-3QreWesySSIUT608eadba6864f96f4,SELL,LIMIT,0.1109000,272,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00603296'}]}",0.00603296,OPEN,00:04:54 +12728232,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698654977000,x-3QreWesySSIUT608eaee80936c96f4,SELL,MARKET,0.1110000,269,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01492950'}]}",0.01492950,CLOSE,n/a +12741681,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698665665000,x-3QreWesySSIUT608ed6b8df61996f4,SELL,MARKET,0.1126000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748790'}]}",0.00748790,CLOSE,00:00:02 +12741960,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698665878000,x-3QreWesyBSIUT608ed6ca99dc296f4,BUY,LIMIT,0.1123000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298718'}]}",0.00298718,OPEN,00:03:16 +12742354,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698666070000,x-3QreWesySSIUT608ed83b1a5ba96f4,SELL,MARKET,0.1125000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748125'}]}",0.00748125,CLOSE,00:00:01 +12750423,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698669201000,x-3QreWesyBSIUT608ee3e529ff496f4,BUY,MARKET,0.1142000,136,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00776560'}]}",0.00776560,CLOSE,n/a +12751202,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698669537000,x-3QreWesySSIUT608ee525b256c96f4,SELL,LIMIT,0.1137000,131,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744735'}]}",0.00744735,OPEN,00:00:01 +12751905,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698670051000,x-3QreWesyBSIUT608ee7108d26d96f4,BUY,MARKET,0.1135000,131,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00743425'}]}",0.00743425,CLOSE,00:00:00 +12753662,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698670608000,x-3QreWesySSIUT608ee8516329296f4,SELL,LIMIT,0.1130000,132,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298320'}]}",0.00298320,OPEN,00:03:42 +12754514,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698670870000,x-3QreWesyBSIUT608eea1ccd3a696f4,BUY,MARKET,0.1128000,132,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00744480'}]}",0.00744480,CLOSE,00:00:00 +12758017,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698672898000,x-3QreWesySSIUT608ef1464133196f4,SELL,LIMIT,0.1135000,132,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299640'}]}",0.00299640,OPEN,00:01:46 +12758120,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698672978000,x-3QreWesyBSIUT608ef1f7ca08196f4,BUY,MARKET,0.1132000,132,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747120'}]}",0.00747120,CLOSE,00:00:00 +12759145,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698673613000,x-3QreWesySSIUT608ef33690ac296f4,SELL,LIMIT,0.1134000,132,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299376'}]}",0.00299376,OPEN,00:05:01 +12759851,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698673745000,x-3QreWesyBSIUT608ef4d319c7896f4,BUY,MARKET,0.1127000,132,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00743820'}]}",0.00743820,CLOSE,00:00:00 +12762842,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698675517000,x-3QreWesyBSIUT608efaa67ad5e96f4,BUY,LIMIT,0.1127000,132,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00297528'}]}",0.00297528,OPEN,00:03:29 +12763137,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698675648000,x-3QreWesySSIUT608efbe97da9596f4,SELL,MARKET,0.1132000,132,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00747120'}]}",0.00747120,CLOSE,n/a +12763379,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698675837000,x-3QreWesyBSIUT608efbf90ae8096f4,BUY,LIMIT,0.1129000,132,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298056'}]}",0.00298056,OPEN,00:02:53 +12765492,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698677107000,x-3QreWesyBSIUT608f00a7127c796f4,BUY,LIMIT,0.1118000,266,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00594776'}]}",0.00594776,OPEN,00:03:07 +12765861,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698677208000,x-3QreWesySSIUT608f01b93140c96f4,SELL,MARKET,0.1123000,266,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01493590'}]}",0.01493590,CLOSE,n/a +12768077,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698679148000,x-3QreWesySSIUT608f07d63019696f4,SELL,LIMIT,0.1128000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00300048'}]}",0.00300048,OPEN,00:05:00 +12768355,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698679386000,x-3QreWesyBSIUT608f09d6576d696f4,BUY,MARKET,0.1125000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00748125'}]}",0.00748125,CLOSE,00:00:00 +12768920,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698679915000,x-3QreWesySSIUT608f0b18cc5be96f4,SELL,LIMIT,0.1124000,133,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00298984'}]}",0.00298984,OPEN,00:03:12 +12772933,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698685931000,x-3QreWesyBSIUT608f22389a23f96f4,BUY,MARKET,0.1121000,133,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00745465'}]}",0.00745465,CLOSE,00:00:00 +12773137,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698685949000,x-3QreWesyBSIUT608f21c61923f96f4,BUY,LIMIT,0.1116000,267,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00595944'}]}",0.00595944,OPEN,00:02:18 +12775523,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698686186000,x-3QreWesyBSIUT608f232b162f796f4,BUY,MARKET,0.1107000,272,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01505520'}]}",0.01505520,CLOSE,00:00:01 +12777312,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698686490000,x-3QreWesySSIUT608f23778c91696f4,SELL,LIMIT,0.1111000,135,10,"{'fee_type': 'AddedToCost', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00299970'}]}",0.00299970,OPEN,00:03:45 +12780747,v2_market-making_dman_v2,v2_market-making_dman_v2,binance_perpetual,SEI-USDT,SEI,USDT,1698689172000,x-3QreWesySSIUT608f2e4b185ef96f4,SELL,MARKET,0.1118000,267,10,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01492530'}]}",0.01492530,CLOSE,00:00:00 diff --git a/scripts/bot-battle-oct-23/v2_market-making_dman_v2 - Michael Feng.sqlite b/scripts/bot-battle-oct-23/v2_market-making_dman_v2 - Michael Feng.sqlite new file mode 100644 index 0000000..7db0e32 Binary files /dev/null and b/scripts/bot-battle-oct-23/v2_market-making_dman_v2 - Michael Feng.sqlite differ diff --git a/scripts/bot-battle-oct-23/v2_market-making_dman_v2.py b/scripts/bot-battle-oct-23/v2_market-making_dman_v2.py new file mode 100644 index 0000000..7a8ae0d --- /dev/null +++ b/scripts/bot-battle-oct-23/v2_market-making_dman_v2.py @@ -0,0 +1,96 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanV2(ScriptStrategyBase): + trading_pair = "SEI-USDT" + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.04"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 24, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + config_v2 = DManV2Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=10, + natr_length=21, macd_fast=12, macd_slow=26, macd_signal=9 + ) + dman_v2 = DManV2(config=config_v2) + + empty_markets = {} + markets = dman_v2.update_strategy_markets_dict(empty_markets) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.dman_v2_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v2) + + def on_stop(self): + self.close_open_positions() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + if self.dman_v2_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v2_executor.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + lines.extend(["DMAN V2", self.dman_v2_executor.to_format_status()]) + lines.extend(["\n-----------------------------------------\n"]) + return "\n".join(lines) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) diff --git a/scripts/bot_battle/10 min.png b/scripts/bot_battle/10 min.png new file mode 100644 index 0000000..a88f36f Binary files /dev/null and b/scripts/bot_battle/10 min.png differ diff --git a/scripts/bot_battle/22 hours.png b/scripts/bot_battle/22 hours.png new file mode 100644 index 0000000..9cb69cb Binary files /dev/null and b/scripts/bot_battle/22 hours.png differ diff --git a/scripts/bot_battle/29 hours copy.png b/scripts/bot_battle/29 hours copy.png new file mode 100644 index 0000000..9a69f2f Binary files /dev/null and b/scripts/bot_battle/29 hours copy.png differ diff --git a/scripts/bot_battle/29 hours.png b/scripts/bot_battle/29 hours.png new file mode 100644 index 0000000..9a69f2f Binary files /dev/null and b/scripts/bot_battle/29 hours.png differ diff --git a/scripts/bot_battle/bot_battle.py b/scripts/bot_battle/bot_battle.py new file mode 100644 index 0000000..c01e7d0 --- /dev/null +++ b/scripts/bot_battle/bot_battle.py @@ -0,0 +1,200 @@ +import logging +from decimal import Decimal +from typing import Dict, List + +import pandas_ta as ta # noqa: F401 + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import BuyOrderCompletedEvent, OrderFilledEvent, SellOrderCompletedEvent +from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class PMMhShiftedMidPriceDynamicSpread(ScriptStrategyBase): + """ + Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-with-shifted-mid-price-and-dynamic-spreads-63cc765486dd42228d3da0b32537fc92 + Video: - + Description: + The bot will place two orders around the `reference_price` (mid price or last traded price +- %based on `RSI` value ) + in a `trading_pair` on `exchange`, with a distance defined by the `spread` multiplied by `spreads_factors` + based on `NATR`. Every `order_refresh_time` seconds, the bot will cancel and replace the orders. + """ + # Define the variables that we are going to use for the spreads + # spread = (NATR * natr_scalar) / 2 + natr_scalar = 5 + spread = 1 + + # Define the price source and the multiplier that shifts the price + # price_multiplier = (RSI - 50) / 50 * spread_base + # reference_price = orig_price * (1 + price_multiplier) + price_source = PriceType.BestBid + orig_price = 1 + reference_price = 1 + price_multiplier = 1 + inventory_multipler = 1 + + # Trading conf + order_refresh_time = 55 + order_amount = 80 + trading_pair = "SEI-USDT" + exchange = "binance" + candle_exchange = "binance" + candles_length = 8 + + # Inventory variables + target_ratio = 0.5 + inventory_delta = 0 + inventory_multiplier = 0 + + # Creating instance of the candles + candles = CandlesFactory.get_candle(connector=candle_exchange, + trading_pair=trading_pair, + interval="1m") + + # Variables to store the volume and quantity of orders + total_sell_orders = 0 + total_buy_orders = 0 + total_sell_volume = 0 + total_buy_volume = 0 + create_timestamp = 0 + + markets = {exchange: {trading_pair}} + + def __init__(self, connectors: Dict[str, ConnectorBase]): + # Is necessary to start the Candles Feed. + super().__init__(connectors) + self.candles.start() + + def on_stop(self): + """ + Without this functionality, the network iterator will continue running forever after stopping the strategy + That's why is necessary to introduce this new feature to make a custom stop with the strategy. + """ + # we are going to close all the open positions when the bot stops + self.candles.stop() + + def on_tick(self): + if self.create_timestamp <= self.current_timestamp and self.candles.is_ready: + self.cancel_all_orders() + self.update_multipliers() + proposal: List[OrderCandidate] = self.create_proposal() + proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal) + self.place_orders(proposal_adjusted) + self.create_timestamp = self.order_refresh_time + self.current_timestamp + + def get_candles_with_features(self): + candles_df = self.candles.candles_df + candles_df.ta.rsi(length=self.candles_length, append=True) + candles_df.ta.natr(length=self.candles_length, scalar=self.natr_scalar, append=True) + return candles_df + + def update_multipliers(self): + candles_df = self.get_candles_with_features() + self.spread = candles_df[f"NATR_{self.candles_length}"].iloc[-1] / 2 + + # Trend Price Shift + rsi = candles_df[f"RSI_{self.candles_length}"].iloc[-1] + if rsi > 70: + self.price_multiplier = self.spread + elif rsi < 30: + self.price_multiplier = -self.spread + else: + self.price_multiplier = (rsi - 50) / 50 * self.spread + + # Inventory Price Shift + base_bal = self.connectors[self.exchange].get_balance("SEI") + base_bal_in_quote = base_bal * self.orig_price + quote_bal = self.connectors[self.exchange].get_balance("USDT") + current_ratio = float(base_bal_in_quote / (base_bal_in_quote + quote_bal)) + self.inventory_delta = (self.target_ratio - current_ratio) / self.target_ratio + self.inventory_multiplier = self.inventory_delta * self.spread + + def create_proposal(self) -> List[OrderCandidate]: + self.orig_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + best_bid = self.connectors[self.exchange].get_price(self.trading_pair, False) + best_ask = self.connectors[self.exchange].get_price(self.trading_pair, True) + self.reference_price = self.orig_price * Decimal(str(1 + self.price_multiplier)) * Decimal(str(1 + self.inventory_multiplier)) + buy_price = min(self.reference_price * Decimal(1 - self.spread), best_bid) + sell_price = max(self.reference_price * Decimal(1 + self.spread), best_ask) + + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=Decimal(self.order_amount), price=buy_price) + + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=Decimal(self.order_amount), price=sell_price) + + return [buy_order, sell_order] + + def adjust_proposal_to_budget(self, proposal: List[OrderCandidate]) -> List[OrderCandidate]: + proposal_adjusted = self.connectors[self.exchange].budget_checker.adjust_candidates(proposal, all_or_none=True) + return proposal_adjusted + + def place_orders(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + if order.amount != 0: + self.place_order(connector_name=self.exchange, order=order) + else: + self.logger().info(f"Not enough funds to place the {order.order_type} order") + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def cancel_all_orders(self): + for order in self.get_active_orders(connector_name=self.exchange): + self.cancel(self.exchange, order.trading_pair, order.client_order_id) + + def did_fill_order(self, event: OrderFilledEvent): + msg = ( + f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.total_buy_volume += event.amount if event.trade_type == TradeType.BUY else 0 + self.total_sell_volume += event.amount if event.trade_type == TradeType.SELL else 0 + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + self.total_buy_orders += 1 + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + self.total_sell_orders += 1 + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + spread_price = Decimal(self.spread) * Decimal(self.reference_price) + trend_price_shift = Decimal(self.price_multiplier) * Decimal(self.reference_price) + inventory_price_shift = Decimal(self.inventory_multiplier) * Decimal(self.reference_price) + + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + lines.extend(["", f" Total Buy Orders: {self.total_buy_orders:.2f} | Total Sell Orders: {self.total_sell_orders:.2f}"]) + lines.extend(["", f" Total Buy Volume: {self.total_buy_volume:.2f} | Total Sell Volume: {self.total_sell_volume:.2f}"]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + lines.extend(["", f" Spread bps: {self.spread * 10000:.1f} | Spread Price: {spread_price:.4f}"]) + lines.extend(["", f" Trending Price: {self.reference_price:.4f} | Trend Price Shift: {self.price_multiplier:.4f}"]) + lines.extend(["", f" Target Inventory Ratio: {self.target_ratio:.4f} | Inventory Delta: {self.inventory_delta:.4f}"]) + lines.extend(["", f" Orig Price: {self.orig_price:.4f} | Trend Shift: {trend_price_shift:.4f} | Inventory Shift: {inventory_price_shift:.4f} | Reference Price: {self.reference_price:.4f}"]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + candles_df = self.get_candles_with_features() + lines.extend([f"Candles: {self.candles.name} | Interval: {self.candles.interval}"]) + lines.extend([" " + line for line in candles_df.tail(self.candles_length).to_string(index=False).split("\n")]) + lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) + return "\n".join(lines) diff --git a/scripts/bot_battle/bot_battle.sqlite b/scripts/bot_battle/bot_battle.sqlite new file mode 100644 index 0000000..c67a649 Binary files /dev/null and b/scripts/bot_battle/bot_battle.sqlite differ diff --git a/scripts/bot_battle/logs_bot_battle.log.2023-09-18 b/scripts/bot_battle/logs_bot_battle.log.2023-09-18 new file mode 100644 index 0000000..e681575 --- /dev/null +++ b/scripts/bot_battle/logs_bot_battle.log.2023-09-18 @@ -0,0 +1,21025 @@ +2023-09-18 10:12:55,926 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 10:12:55,929 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 10:12:56,033 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 10:12:56,122 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:12:56,323 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:12:56,779 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 10:12:56,808 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 10:12:57,160 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 10:12:57,180 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 10:12:57,535 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 10:12:58,170 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 10:13:00,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233689672672210119239665717 amount: 100. +2023-09-18 10:13:00,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:13:00,920 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f63109b190582 for 100.00000000 SEI-USDT. +2023-09-18 10:13:00,994 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695031980.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "100.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT6059f63109b190582", "creation_timestamp": 1695031980.0, "exchange_order_id": "35153165", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:13:02,646 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:13:55,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f63109b190582. [clock=2023-09-18 10:13:55+00:00] +2023-09-18 10:13:55,357 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232759630106630119912915010 amount: 100. +2023-09-18 10:13:55,358 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:13:55,960 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032035.0, "order_id": "x-XEKWYICXBSIUT6059f63109b190582", "exchange_order_id": "35153165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:13:55,961 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f63109b190582. +2023-09-18 10:13:57,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f6659b6c50582 for 100.00000000 SEI-USDT. +2023-09-18 10:13:57,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032037.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "100.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT6059f6659b6c50582", "creation_timestamp": 1695032035.0, "exchange_order_id": "35153559", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:14:50,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f6659b6c50582. [clock=2023-09-18 10:14:50+00:00] +2023-09-18 10:14:50,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233993038599519448094491353 amount: 100. +2023-09-18 10:14:50,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:14:50,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032090.0, "order_id": "x-XEKWYICXBSIUT6059f6659b6c50582", "exchange_order_id": "35153559", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:14:50,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f6659b6c50582. +2023-09-18 10:14:51,598 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f699c2f400582 for 100.00000000 SEI-USDT. +2023-09-18 10:14:51,659 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "100.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT6059f699c2f400582", "creation_timestamp": 1695032090.0, "exchange_order_id": "35153684", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:15:45,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f699c2f400582. [clock=2023-09-18 10:15:45+00:00] +2023-09-18 10:15:45,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235062760784432384301377023 amount: 100. +2023-09-18 10:15:45,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:15:45,078 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032145.0, "order_id": "x-XEKWYICXBSIUT6059f699c2f400582", "exchange_order_id": "35153684", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:15:45,078 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f699c2f400582. +2023-09-18 10:15:45,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f6ce2f4340582 for 100.00000000 SEI-USDT. +2023-09-18 10:15:45,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032145.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "100.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT6059f6ce2f4340582", "creation_timestamp": 1695032145.0, "exchange_order_id": "35153979", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:16:40,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f6ce2f4340582. [clock=2023-09-18 10:16:40+00:00] +2023-09-18 10:16:40,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236383411531736068733358180 amount: 100. +2023-09-18 10:16:40,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:16:40,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032200.0, "order_id": "x-XEKWYICXBSIUT6059f6ce2f4340582", "exchange_order_id": "35153979", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:16:40,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f6ce2f4340582. +2023-09-18 10:16:40,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f702a3eac0582 for 100.00000000 SEI-USDT. +2023-09-18 10:16:40,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032200.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "100.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT6059f702a3eac0582", "creation_timestamp": 1695032200.0, "exchange_order_id": "35154061", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:17:29,836 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 10:17:29,909 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032249.0, "order_id": "x-XEKWYICXBSIUT6059f702a3eac0582", "exchange_order_id": "35154061", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:17:29,909 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f702a3eac0582. +2023-09-18 10:17:51,803 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 10:17:51,806 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 10:17:51,947 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:17:52,010 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 10:17:52,079 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:17:52,241 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 10:17:52,251 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 10:17:52,315 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 10:17:52,616 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 10:17:53,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 10:17:53,354 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 10:17:59,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235345867230034075219568787 amount: 80. +2023-09-18 10:17:59,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238595296458148534908003633 amount: 80. +2023-09-18 10:17:59,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f74dfd90f0582 for 80.00000000 SEI-USDT. +2023-09-18 10:17:59,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032279.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT6059f74dfd90f0582", "creation_timestamp": 1695032279.0, "exchange_order_id": "35154645", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:17:59,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059f74dfdbd60582 for 80.00000000 SEI-USDT. +2023-09-18 10:17:59,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032279.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT6059f74dfdbd60582", "creation_timestamp": 1695032279.0, "exchange_order_id": "35154646", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:18:54,431 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f74dfd90f0582. [clock=2023-09-18 10:18:54+00:00] +2023-09-18 10:18:54,432 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059f74dfdbd60582. [clock=2023-09-18 10:18:54+00:00] +2023-09-18 10:18:54,464 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234044060892346610170073746 amount: 80. +2023-09-18 10:18:54,465 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237576635524610094469890268 amount: 80. +2023-09-18 10:18:54,836 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032334.0, "order_id": "x-XEKWYICXBSIUT6059f74dfd90f0582", "exchange_order_id": "35154645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:18:54,836 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f74dfd90f0582. +2023-09-18 10:18:55,779 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032335.0, "order_id": "x-XEKWYICXSSIUT6059f74dfdbd60582", "exchange_order_id": "35154646", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:18:55,779 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059f74dfdbd60582. +2023-09-18 10:18:56,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059f782dbe640582 for 80.00000000 SEI-USDT. +2023-09-18 10:18:56,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032335.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT6059f782dbe640582", "creation_timestamp": 1695032334.0, "exchange_order_id": "35155127", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:18:56,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f782dba7b0582 for 80.00000000 SEI-USDT. +2023-09-18 10:18:56,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032335.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT6059f782dba7b0582", "creation_timestamp": 1695032334.0, "exchange_order_id": "35155128", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:19:04,205 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT6059f782dbe640582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:19:04,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 10:19:03+00:00] +2023-09-18 10:19:04,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032343.0, "order_id": "x-XEKWYICXSSIUT6059f782dbe640582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00989600"}]}, "exchange_trade_id": "4952744", "exchange_order_id": "35155127", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:19:04,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032344.0, "order_id": "x-XEKWYICXSSIUT6059f782dbe640582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35155127", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:19:04,503 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT6059f782dbe640582 completely filled. +2023-09-18 10:19:49,549 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f782dba7b0582. [clock=2023-09-18 10:19:49+00:00] +2023-09-18 10:19:49,595 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236312685878435018959304327 amount: 80. +2023-09-18 10:19:49,613 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239654118238411981503885409 amount: 80. +2023-09-18 10:19:50,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032389.0, "order_id": "x-XEKWYICXBSIUT6059f782dba7b0582", "exchange_order_id": "35155128", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:19:50,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f782dba7b0582. +2023-09-18 10:19:51,763 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f7b77368d0582 for 80.00000000 SEI-USDT. +2023-09-18 10:19:51,792 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT6059f7b77368d0582", "creation_timestamp": 1695032389.0, "exchange_order_id": "35155689", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:19:51,933 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059f7b773a700582 for 80.00000000 SEI-USDT. +2023-09-18 10:19:51,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT6059f7b773a700582", "creation_timestamp": 1695032389.0, "exchange_order_id": "35155695", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:20:00,607 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT6059f7b773a700582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:20:00,616 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 10:20:00+00:00] +2023-09-18 10:20:00,692 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032400.0, "order_id": "x-XEKWYICXSSIUT6059f7b773a700582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4952802", "exchange_order_id": "35155695", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:20:00,940 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032400.0, "order_id": "x-XEKWYICXSSIUT6059f7b773a700582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35155695", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:20:00,941 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT6059f7b773a700582 completely filled. +2023-09-18 10:20:44,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f7b77368d0582. [clock=2023-09-18 10:20:44+00:00] +2023-09-18 10:20:44,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237488903200163218430269177 amount: 80. +2023-09-18 10:20:44,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:20:44,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032444.0, "order_id": "x-XEKWYICXBSIUT6059f7b77368d0582", "exchange_order_id": "35155689", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:20:44,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f7b77368d0582. +2023-09-18 10:20:44,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f7eb589760582 for 80.00000000 SEI-USDT. +2023-09-18 10:20:44,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032444.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT6059f7eb589760582", "creation_timestamp": 1695032444.0, "exchange_order_id": "35156278", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:21:06,404 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059f7eb589760582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:21:06,405 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 10:21:06+00:00] +2023-09-18 10:21:06,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032466.0, "order_id": "x-XEKWYICXBSIUT6059f7eb589760582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4952853", "exchange_order_id": "35156278", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:21:06,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032466.0, "order_id": "x-XEKWYICXBSIUT6059f7eb589760582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35156278", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:21:06,523 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059f7eb589760582 completely filled. +2023-09-18 10:21:39,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235154324073985569900281612 amount: 80. +2023-09-18 10:21:39,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238622485868276656899250821 amount: 80. +2023-09-18 10:21:39,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f81fc85030582 for 80.00000000 SEI-USDT. +2023-09-18 10:21:39,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032499.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT6059f81fc85030582", "creation_timestamp": 1695032499.0, "exchange_order_id": "35156696", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:21:39,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059f81fc8af30582 for 80.00000000 SEI-USDT. +2023-09-18 10:21:39,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032499.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT6059f81fc8af30582", "creation_timestamp": 1695032499.0, "exchange_order_id": "35156697", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:22:34,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f81fc85030582. [clock=2023-09-18 10:22:34+00:00] +2023-09-18 10:22:34,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059f81fc8af30582. [clock=2023-09-18 10:22:34+00:00] +2023-09-18 10:22:34,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234196991482690545378045584 amount: 80. +2023-09-18 10:22:34,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:22:34,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032554.0, "order_id": "x-XEKWYICXBSIUT6059f81fc85030582", "exchange_order_id": "35156696", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:22:34,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f81fc85030582. +2023-09-18 10:22:35,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032555.0, "order_id": "x-XEKWYICXSSIUT6059f81fc8af30582", "exchange_order_id": "35156697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:22:35,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059f81fc8af30582. +2023-09-18 10:22:35,570 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f85454c9b0582 for 80.00000000 SEI-USDT. +2023-09-18 10:22:35,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032555.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT6059f85454c9b0582", "creation_timestamp": 1695032554.0, "exchange_order_id": "35157053", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:23:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f85454c9b0582. [clock=2023-09-18 10:23:29+00:00] +2023-09-18 10:23:29,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236336363692904832494048156 amount: 80. +2023-09-18 10:23:29,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239652055376728973567986292 amount: 80. +2023-09-18 10:23:29,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032609.0, "order_id": "x-XEKWYICXBSIUT6059f85454c9b0582", "exchange_order_id": "35157053", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:23:29,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f85454c9b0582. +2023-09-18 10:23:29,589 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f888b950f0582 for 80.00000000 SEI-USDT. +2023-09-18 10:23:29,632 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032609.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT6059f888b950f0582", "creation_timestamp": 1695032609.0, "exchange_order_id": "35157241", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:23:29,633 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059f888bd9700582 for 80.00000000 SEI-USDT. +2023-09-18 10:23:29,673 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032609.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT6059f888bd9700582", "creation_timestamp": 1695032609.0, "exchange_order_id": "35157242", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:24:19,576 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT6059f888bd9700582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:24:19,577 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 10:24:19+00:00] +2023-09-18 10:24:19,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032659.0, "order_id": "x-XEKWYICXSSIUT6059f888bd9700582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4952948", "exchange_order_id": "35157242", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:24:19,683 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032659.0, "order_id": "x-XEKWYICXSSIUT6059f888bd9700582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35157242", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:24:19,684 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT6059f888bd9700582 completely filled. +2023-09-18 10:24:24,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f888b950f0582. [clock=2023-09-18 10:24:24+00:00] +2023-09-18 10:24:24,380 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238464640345307631167524043 amount: 80. +2023-09-18 10:24:24,381 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:24:24,809 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032664.0, "order_id": "x-XEKWYICXBSIUT6059f888b950f0582", "exchange_order_id": "35157241", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:24:24,810 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f888b950f0582. +2023-09-18 10:24:26,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f8bd7d6440582 for 80.00000000 SEI-USDT. +2023-09-18 10:24:26,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032665.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT6059f8bd7d6440582", "creation_timestamp": 1695032664.0, "exchange_order_id": "35157759", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:25:19,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f8bd7d6440582. [clock=2023-09-18 10:25:19+00:00] +2023-09-18 10:25:19,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239518491443475108842361913 amount: 80. +2023-09-18 10:25:19,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:25:19,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032719.0, "order_id": "x-XEKWYICXBSIUT6059f8bd7d6440582", "exchange_order_id": "35157759", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:25:19,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f8bd7d6440582. +2023-09-18 10:25:19,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f8f1a6fdf0582 for 80.00000000 SEI-USDT. +2023-09-18 10:25:19,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032719.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT6059f8f1a6fdf0582", "creation_timestamp": 1695032719.0, "exchange_order_id": "35158104", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:26:14,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f8f1a6fdf0582. [clock=2023-09-18 10:26:14+00:00] +2023-09-18 10:26:14,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239703714282311851044091989 amount: 80. +2023-09-18 10:26:14,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:26:14,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032774.0, "order_id": "x-XEKWYICXBSIUT6059f8f1a6fdf0582", "exchange_order_id": "35158104", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:26:14,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f8f1a6fdf0582. +2023-09-18 10:26:14,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f92638c8f0582 for 80.00000000 SEI-USDT. +2023-09-18 10:26:14,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032774.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT6059f92638c8f0582", "creation_timestamp": 1695032774.0, "exchange_order_id": "35158509", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:27:09,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f92638c8f0582. [clock=2023-09-18 10:27:09+00:00] +2023-09-18 10:27:09,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241800693951153914698560803 amount: 80. +2023-09-18 10:27:09,191 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:27:09,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032829.0, "order_id": "x-XEKWYICXBSIUT6059f92638c8f0582", "exchange_order_id": "35158509", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:27:09,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f92638c8f0582. +2023-09-18 10:27:10,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f95aaa4e50582 for 80.00000000 SEI-USDT. +2023-09-18 10:27:10,383 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032830.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT6059f95aaa4e50582", "creation_timestamp": 1695032829.0, "exchange_order_id": "35158823", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:28:04,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f95aaa4e50582. [clock=2023-09-18 10:28:04+00:00] +2023-09-18 10:28:04,176 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241778794514222026794543337 amount: 80. +2023-09-18 10:28:04,177 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:28:04,461 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032884.0, "order_id": "x-XEKWYICXBSIUT6059f95aaa4e50582", "exchange_order_id": "35158823", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:28:04,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f95aaa4e50582. +2023-09-18 10:28:04,792 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f98f1a9bc0582 for 80.00000000 SEI-USDT. +2023-09-18 10:28:04,830 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032884.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT6059f98f1a9bc0582", "creation_timestamp": 1695032884.0, "exchange_order_id": "35159035", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:28:59,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f98f1a9bc0582. [clock=2023-09-18 10:28:59+00:00] +2023-09-18 10:28:59,227 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242654988073866916429109082 amount: 80. +2023-09-18 10:28:59,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:28:59,831 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032939.0, "order_id": "x-XEKWYICXBSIUT6059f98f1a9bc0582", "exchange_order_id": "35159035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:28:59,832 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f98f1a9bc0582. +2023-09-18 10:29:00,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f9c39d3580582 for 80.00000000 SEI-USDT. +2023-09-18 10:29:00,682 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032940.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT6059f9c39d3580582", "creation_timestamp": 1695032939.0, "exchange_order_id": "35159429", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:29:54,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f9c39d3580582. [clock=2023-09-18 10:29:54+00:00] +2023-09-18 10:29:54,379 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242729998849397546713207391 amount: 80. +2023-09-18 10:29:54,380 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:29:54,761 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032994.0, "order_id": "x-XEKWYICXBSIUT6059f9c39d3580582", "exchange_order_id": "35159429", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:29:54,761 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f9c39d3580582. +2023-09-18 10:29:56,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059f9f8339570582 for 80.00000000 SEI-USDT. +2023-09-18 10:29:56,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695032995.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT6059f9f8339570582", "creation_timestamp": 1695032994.0, "exchange_order_id": "35159617", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:30:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059f9f8339570582. [clock=2023-09-18 10:30:49+00:00] +2023-09-18 10:30:49,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242702439178222256806279014 amount: 80. +2023-09-18 10:30:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:30:49,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033049.0, "order_id": "x-XEKWYICXBSIUT6059f9f8339570582", "exchange_order_id": "35159617", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:30:49,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059f9f8339570582. +2023-09-18 10:30:49,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fa2c4ea600582 for 80.00000000 SEI-USDT. +2023-09-18 10:30:49,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033049.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT6059fa2c4ea600582", "creation_timestamp": 1695033049.0, "exchange_order_id": "35160193", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:31:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fa2c4ea600582. [clock=2023-09-18 10:31:44+00:00] +2023-09-18 10:31:44,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242864702300267026054681040 amount: 80. +2023-09-18 10:31:44,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:31:44,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033104.0, "order_id": "x-XEKWYICXBSIUT6059fa2c4ea600582", "exchange_order_id": "35160193", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:31:44,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fa2c4ea600582. +2023-09-18 10:31:44,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fa60c2b3e0582 for 80.00000000 SEI-USDT. +2023-09-18 10:31:44,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033104.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT6059fa60c2b3e0582", "creation_timestamp": 1695033104.0, "exchange_order_id": "35160791", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:32:39,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fa60c2b3e0582. [clock=2023-09-18 10:32:39+00:00] +2023-09-18 10:32:39,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244966796529395287885432183 amount: 80. +2023-09-18 10:32:39,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:32:39,587 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033159.0, "order_id": "x-XEKWYICXBSIUT6059fa60c2b3e0582", "exchange_order_id": "35160791", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:32:39,587 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fa60c2b3e0582. +2023-09-18 10:32:40,891 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fa95548380582 for 80.00000000 SEI-USDT. +2023-09-18 10:32:40,947 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033160.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT6059fa95548380582", "creation_timestamp": 1695033159.0, "exchange_order_id": "35161486", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:33:34,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fa95548380582. [clock=2023-09-18 10:33:34+00:00] +2023-09-18 10:33:34,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243800320357261196849772981 amount: 80. +2023-09-18 10:33:34,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:33:34,525 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033214.0, "order_id": "x-XEKWYICXBSIUT6059fa95548380582", "exchange_order_id": "35161486", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:33:34,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fa95548380582. +2023-09-18 10:33:35,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fac9b512c0582 for 80.00000000 SEI-USDT. +2023-09-18 10:33:35,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033215.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT6059fac9b512c0582", "creation_timestamp": 1695033214.0, "exchange_order_id": "35161850", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:34:29,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fac9b512c0582. [clock=2023-09-18 10:34:29+00:00] +2023-09-18 10:34:29,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245064825285520033422391179 amount: 80. +2023-09-18 10:34:29,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:34:29,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033269.0, "order_id": "x-XEKWYICXBSIUT6059fac9b512c0582", "exchange_order_id": "35161850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:34:29,608 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fac9b512c0582. +2023-09-18 10:34:30,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fafe3800f0582 for 80.00000000 SEI-USDT. +2023-09-18 10:34:30,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033270.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT6059fafe3800f0582", "creation_timestamp": 1695033269.0, "exchange_order_id": "35162072", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:35:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fafe3800f0582. [clock=2023-09-18 10:35:24+00:00] +2023-09-18 10:35:24,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247997613220889221525233534 amount: 80. +2023-09-18 10:35:24,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:35:24,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033324.0, "order_id": "x-XEKWYICXBSIUT6059fafe3800f0582", "exchange_order_id": "35162072", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:35:24,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fafe3800f0582. +2023-09-18 10:35:24,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fb3290f0d0582 for 80.00000000 SEI-USDT. +2023-09-18 10:35:24,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033324.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT6059fb3290f0d0582", "creation_timestamp": 1695033324.0, "exchange_order_id": "35162908", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:36:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fb3290f0d0582. [clock=2023-09-18 10:36:19+00:00] +2023-09-18 10:36:19,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246899046184408429171252485 amount: 80. +2023-09-18 10:36:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:36:19,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033379.0, "order_id": "x-XEKWYICXBSIUT6059fb3290f0d0582", "exchange_order_id": "35162908", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:36:19,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fb3290f0d0582. +2023-09-18 10:36:19,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fb6704f710582 for 80.00000000 SEI-USDT. +2023-09-18 10:36:19,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033379.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT6059fb6704f710582", "creation_timestamp": 1695033379.0, "exchange_order_id": "35163500", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:37:14,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fb6704f710582. [clock=2023-09-18 10:37:14+00:00] +2023-09-18 10:37:14,333 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 10:37:14,335 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:37:14,787 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033434.0, "order_id": "x-XEKWYICXBSIUT6059fb6704f710582", "exchange_order_id": "35163500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:37:14,787 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fb6704f710582. +2023-09-18 10:37:16,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fb9bc66fc0582 for 80.00000000 SEI-USDT. +2023-09-18 10:37:16,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT6059fb9bc66fc0582", "creation_timestamp": 1695033434.0, "exchange_order_id": "35163996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:37:16,555 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059fb9bc66fc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:37:16,557 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 10:37:16+00:00] +2023-09-18 10:37:16,665 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033436.0, "order_id": "x-XEKWYICXBSIUT6059fb9bc66fc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4953616", "exchange_order_id": "35163996", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:37:17,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033436.0, "order_id": "x-XEKWYICXBSIUT6059fb9bc66fc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35163996", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:37:17,746 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059fb9bc66fc0582 completely filled. +2023-09-18 10:38:09,682 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-18 10:38:09,684 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258209995051218996290324062 amount: 80. +2023-09-18 10:38:10,530 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fbd08f8440582 for 80.00000000 SEI-USDT. +2023-09-18 10:38:10,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033490.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT6059fbd08f8440582", "creation_timestamp": 1695033489.0, "exchange_order_id": "35165345", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:38:11,915 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fbd08fb540582 for 80.00000000 SEI-USDT. +2023-09-18 10:38:11,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT6059fbd08fb540582", "creation_timestamp": 1695033489.0, "exchange_order_id": "35165366", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:39:04,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fbd08f8440582. [clock=2023-09-18 10:39:04+00:00] +2023-09-18 10:39:04,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fbd08fb540582. [clock=2023-09-18 10:39:04+00:00] +2023-09-18 10:39:04,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253996567465048044627667916 amount: 80. +2023-09-18 10:39:04,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:39:04,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033544.0, "order_id": "x-XEKWYICXBSIUT6059fbd08f8440582", "exchange_order_id": "35165345", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:39:04,564 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fbd08f8440582. +2023-09-18 10:39:05,380 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033545.0, "order_id": "x-XEKWYICXSSIUT6059fbd08fb540582", "exchange_order_id": "35165366", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:39:05,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fbd08fb540582. +2023-09-18 10:39:05,732 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fc0473b250582 for 80.00000000 SEI-USDT. +2023-09-18 10:39:05,785 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033545.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT6059fc0473b250582", "creation_timestamp": 1695033544.0, "exchange_order_id": "35166106", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:39:59,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fc0473b250582. [clock=2023-09-18 10:39:59+00:00] +2023-09-18 10:39:59,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-18 10:39:59,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258403267644969477696280459 amount: 80. +2023-09-18 10:39:59,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033599.0, "order_id": "x-XEKWYICXBSIUT6059fc0473b250582", "exchange_order_id": "35166106", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:39:59,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fc0473b250582. +2023-09-18 10:39:59,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fc38d98eb0582 for 80.00000000 SEI-USDT. +2023-09-18 10:39:59,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033599.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT6059fc38d98eb0582", "creation_timestamp": 1695033599.0, "exchange_order_id": "35166430", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:39:59,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fc38dcc270582 for 80.00000000 SEI-USDT. +2023-09-18 10:39:59,429 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033599.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT6059fc38dcc270582", "creation_timestamp": 1695033599.0, "exchange_order_id": "35166431", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:40:50,617 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059fc38d98eb0582 amounting to 19.00000000/80.00000000 SEI has been filled. +2023-09-18 10:40:50,618 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 19.00 SEI-USDT binance at 0.13 [clock=2023-09-18 10:40:50+00:00] +2023-09-18 10:40:50,647 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033650.0, "order_id": "x-XEKWYICXBSIUT6059fc38d98eb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "19.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.01900000"}]}, "exchange_trade_id": "4953980", "exchange_order_id": "35166430", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:40:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fc38d98eb0582. [clock=2023-09-18 10:40:54+00:00] +2023-09-18 10:40:54,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fc38dcc270582. [clock=2023-09-18 10:40:54+00:00] +2023-09-18 10:40:54,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252995779081608328380091559 amount: 80. +2023-09-18 10:40:54,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 10:40:54,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033654.0, "order_id": "x-XEKWYICXBSIUT6059fc38d98eb0582", "exchange_order_id": "35166430", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:40:54,085 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fc38d98eb0582. +2023-09-18 10:40:54,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033654.0, "order_id": "x-XEKWYICXSSIUT6059fc38dcc270582", "exchange_order_id": "35166431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:40:54,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fc38dcc270582. +2023-09-18 10:40:54,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fc6d47e2f0582 for 80.00000000 SEI-USDT. +2023-09-18 10:40:54,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033654.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT6059fc6d47e2f0582", "creation_timestamp": 1695033654.0, "exchange_order_id": "35167032", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:41:25,997 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059fc6d47e2f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:41:25,999 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 10:41:25+00:00] +2023-09-18 10:41:26,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033685.0, "order_id": "x-XEKWYICXBSIUT6059fc6d47e2f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4954033", "exchange_order_id": "35167032", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:41:26,036 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033685.0, "order_id": "x-XEKWYICXBSIUT6059fc6d47e2f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35167032", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:41:26,036 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059fc6d47e2f0582 completely filled. +2023-09-18 10:41:49,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249278503379651111396823980 amount: 80. +2023-09-18 10:41:49,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254055596773366497817477932 amount: 80. +2023-09-18 10:41:49,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fca1c69c00582 for 80.00000000 SEI-USDT. +2023-09-18 10:41:49,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033709.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT6059fca1c69c00582", "creation_timestamp": 1695033709.0, "exchange_order_id": "35167692", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:41:49,351 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fca1c6d8b0582 for 80.00000000 SEI-USDT. +2023-09-18 10:41:49,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033709.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT6059fca1c6d8b0582", "creation_timestamp": 1695033709.0, "exchange_order_id": "35167696", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:42:44,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fca1c69c00582. [clock=2023-09-18 10:42:44+00:00] +2023-09-18 10:42:44,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fca1c6d8b0582. [clock=2023-09-18 10:42:44+00:00] +2023-09-18 10:42:44,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247961829087497886690505818 amount: 80. +2023-09-18 10:42:44,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253141843993575724470349940 amount: 80. +2023-09-18 10:42:44,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033764.0, "order_id": "x-XEKWYICXBSIUT6059fca1c69c00582", "exchange_order_id": "35167692", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:42:44,497 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fca1c69c00582. +2023-09-18 10:42:45,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033765.0, "order_id": "x-XEKWYICXSSIUT6059fca1c6d8b0582", "exchange_order_id": "35167696", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:42:45,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fca1c6d8b0582. +2023-09-18 10:42:45,565 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fcd64720a0582 for 80.00000000 SEI-USDT. +2023-09-18 10:42:45,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033765.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT6059fcd64720a0582", "creation_timestamp": 1695033764.0, "exchange_order_id": "35168475", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:42:45,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fcd646e990582 for 80.00000000 SEI-USDT. +2023-09-18 10:42:45,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033765.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT6059fcd646e990582", "creation_timestamp": 1695033764.0, "exchange_order_id": "35168477", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:43:39,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fcd646e990582. [clock=2023-09-18 10:43:39+00:00] +2023-09-18 10:43:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fcd64720a0582. [clock=2023-09-18 10:43:39+00:00] +2023-09-18 10:43:39,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250205945684597963462070315 amount: 80. +2023-09-18 10:43:39,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255239362255691172098925096 amount: 80. +2023-09-18 10:43:39,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033819.0, "order_id": "x-XEKWYICXBSIUT6059fcd646e990582", "exchange_order_id": "35168477", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:43:39,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fcd646e990582. +2023-09-18 10:43:39,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033819.0, "order_id": "x-XEKWYICXSSIUT6059fcd64720a0582", "exchange_order_id": "35168475", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:43:39,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fcd64720a0582. +2023-09-18 10:43:39,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fd0aa566c0582 for 80.00000000 SEI-USDT. +2023-09-18 10:43:39,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033819.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT6059fd0aa566c0582", "creation_timestamp": 1695033819.0, "exchange_order_id": "35168845", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:43:39,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fd0aa53870582 for 80.00000000 SEI-USDT. +2023-09-18 10:43:39,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033819.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT6059fd0aa53870582", "creation_timestamp": 1695033819.0, "exchange_order_id": "35168846", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:44:34,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fd0aa53870582. [clock=2023-09-18 10:44:34+00:00] +2023-09-18 10:44:34,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fd0aa566c0582. [clock=2023-09-18 10:44:34+00:00] +2023-09-18 10:44:34,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250257769049392992600591454 amount: 80. +2023-09-18 10:44:34,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255162247640104829118149544 amount: 80. +2023-09-18 10:44:34,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033874.0, "order_id": "x-XEKWYICXBSIUT6059fd0aa53870582", "exchange_order_id": "35168846", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:44:34,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fd0aa53870582. +2023-09-18 10:44:34,444 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fd3f290bc0582 for 80.00000000 SEI-USDT. +2023-09-18 10:44:34,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033874.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT6059fd3f290bc0582", "creation_timestamp": 1695033874.0, "exchange_order_id": "35169223", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:44:34,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fd3f2b4320582 for 80.00000000 SEI-USDT. +2023-09-18 10:44:34,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033874.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT6059fd3f2b4320582", "creation_timestamp": 1695033874.0, "exchange_order_id": "35169224", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:44:34,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033874.0, "order_id": "x-XEKWYICXSSIUT6059fd0aa566c0582", "exchange_order_id": "35168845", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:44:34,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fd0aa566c0582. +2023-09-18 10:45:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fd3f290bc0582. [clock=2023-09-18 10:45:29+00:00] +2023-09-18 10:45:29,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fd3f2b4320582. [clock=2023-09-18 10:45:29+00:00] +2023-09-18 10:45:29,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253385920567313943935939883 amount: 80. +2023-09-18 10:45:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258679258728333458400782144 amount: 80. +2023-09-18 10:45:29,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033929.0, "order_id": "x-XEKWYICXBSIUT6059fd3f290bc0582", "exchange_order_id": "35169223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:45:29,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fd3f290bc0582. +2023-09-18 10:45:29,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fd738b0970582 for 80.00000000 SEI-USDT. +2023-09-18 10:45:29,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033929.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT6059fd738b0970582", "creation_timestamp": 1695033929.0, "exchange_order_id": "35169844", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:45:29,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033929.0, "order_id": "x-XEKWYICXSSIUT6059fd3f2b4320582", "exchange_order_id": "35169224", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:45:29,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fd3f2b4320582. +2023-09-18 10:45:29,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fd738ad140582 for 80.00000000 SEI-USDT. +2023-09-18 10:45:29,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033929.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT6059fd738ad140582", "creation_timestamp": 1695033929.0, "exchange_order_id": "35169846", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:46:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fd738ad140582. [clock=2023-09-18 10:46:24+00:00] +2023-09-18 10:46:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fd738b0970582. [clock=2023-09-18 10:46:24+00:00] +2023-09-18 10:46:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255994310057236864139121636 amount: 80. +2023-09-18 10:46:24,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261352303117634757624493436 amount: 80. +2023-09-18 10:46:24,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033984.0, "order_id": "x-XEKWYICXBSIUT6059fd738ad140582", "exchange_order_id": "35169846", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:46:24,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fd738ad140582. +2023-09-18 10:46:24,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033984.0, "order_id": "x-XEKWYICXSSIUT6059fd738b0970582", "exchange_order_id": "35169844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:46:24,590 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fd738b0970582. +2023-09-18 10:46:24,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fda7fe3c80582 for 80.00000000 SEI-USDT. +2023-09-18 10:46:24,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033984.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT6059fda7fe3c80582", "creation_timestamp": 1695033984.0, "exchange_order_id": "35170484", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:46:24,608 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fda7fe7820582 for 80.00000000 SEI-USDT. +2023-09-18 10:46:24,620 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033984.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT6059fda7fe7820582", "creation_timestamp": 1695033984.0, "exchange_order_id": "35170483", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:46:32,842 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059fda7fe3c80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:46:32,843 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 10:46:32+00:00] +2023-09-18 10:46:32,865 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033992.0, "order_id": "x-XEKWYICXBSIUT6059fda7fe3c80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4954408", "exchange_order_id": "35170484", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:46:32,876 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695033992.0, "order_id": "x-XEKWYICXBSIUT6059fda7fe3c80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35170484", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:46:32,877 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059fda7fe3c80582 completely filled. +2023-09-18 10:47:19,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fda7fe7820582. [clock=2023-09-18 10:47:19+00:00] +2023-09-18 10:47:19,225 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252173208028829291484498027 amount: 80. +2023-09-18 10:47:19,226 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257323315793995702602268101 amount: 80. +2023-09-18 10:47:20,006 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034039.0, "order_id": "x-XEKWYICXSSIUT6059fda7fe7820582", "exchange_order_id": "35170483", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:47:20,007 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fda7fe7820582. +2023-09-18 10:47:21,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fddca50490582 for 80.00000000 SEI-USDT. +2023-09-18 10:47:21,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT6059fddca50490582", "creation_timestamp": 1695034039.0, "exchange_order_id": "35171371", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:47:22,696 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fddca52c00582 for 80.00000000 SEI-USDT. +2023-09-18 10:47:22,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT6059fddca52c00582", "creation_timestamp": 1695034039.0, "exchange_order_id": "35171372", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:47:52,672 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 10:48:14,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fddca50490582. [clock=2023-09-18 10:48:14+00:00] +2023-09-18 10:48:14,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fddca52c00582. [clock=2023-09-18 10:48:14+00:00] +2023-09-18 10:48:14,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252246338002291329953144485 amount: 80. +2023-09-18 10:48:14,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257190325418634966026434210 amount: 80. +2023-09-18 10:48:14,804 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034094.0, "order_id": "x-XEKWYICXBSIUT6059fddca50490582", "exchange_order_id": "35171371", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:48:14,804 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fddca50490582. +2023-09-18 10:48:14,855 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034094.0, "order_id": "x-XEKWYICXSSIUT6059fddca52c00582", "exchange_order_id": "35171372", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:48:14,855 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fddca52c00582. +2023-09-18 10:48:16,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fe111466d0582 for 80.00000000 SEI-USDT. +2023-09-18 10:48:16,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034095.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT6059fe111466d0582", "creation_timestamp": 1695034094.0, "exchange_order_id": "35171830", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:48:17,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fe1114a820582 for 80.00000000 SEI-USDT. +2023-09-18 10:48:17,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT6059fe1114a820582", "creation_timestamp": 1695034094.0, "exchange_order_id": "35171831", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:49:09,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fe111466d0582. [clock=2023-09-18 10:49:09+00:00] +2023-09-18 10:49:09,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fe1114a820582. [clock=2023-09-18 10:49:09+00:00] +2023-09-18 10:49:09,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255460605501363417890549301 amount: 80. +2023-09-18 10:49:09,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260444140788169332659788779 amount: 80. +2023-09-18 10:49:09,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034149.0, "order_id": "x-XEKWYICXBSIUT6059fe111466d0582", "exchange_order_id": "35171830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:49:09,482 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fe111466d0582. +2023-09-18 10:49:10,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034150.0, "order_id": "x-XEKWYICXSSIUT6059fe1114a820582", "exchange_order_id": "35171831", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:49:10,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fe1114a820582. +2023-09-18 10:49:10,660 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fe456eb050582 for 80.00000000 SEI-USDT. +2023-09-18 10:49:10,687 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034150.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT6059fe456eb050582", "creation_timestamp": 1695034149.0, "exchange_order_id": "35172340", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:49:10,688 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fe4570e550582 for 80.00000000 SEI-USDT. +2023-09-18 10:49:10,727 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034150.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT6059fe4570e550582", "creation_timestamp": 1695034149.0, "exchange_order_id": "35172341", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:50:04,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fe456eb050582. [clock=2023-09-18 10:50:04+00:00] +2023-09-18 10:50:04,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fe4570e550582. [clock=2023-09-18 10:50:04+00:00] +2023-09-18 10:50:04,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254446123059508233990388284 amount: 80. +2023-09-18 10:50:04,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259024844730547952573697906 amount: 80. +2023-09-18 10:50:04,743 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034204.0, "order_id": "x-XEKWYICXBSIUT6059fe456eb050582", "exchange_order_id": "35172340", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:50:04,743 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fe456eb050582. +2023-09-18 10:50:05,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034205.0, "order_id": "x-XEKWYICXSSIUT6059fe4570e550582", "exchange_order_id": "35172341", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:50:05,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fe4570e550582. +2023-09-18 10:50:05,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fe79fbfed0582 for 80.00000000 SEI-USDT. +2023-09-18 10:50:05,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034205.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT6059fe79fbfed0582", "creation_timestamp": 1695034204.0, "exchange_order_id": "35172521", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:50:05,630 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fe79fc4f50582 for 80.00000000 SEI-USDT. +2023-09-18 10:50:05,682 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034205.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT6059fe79fc4f50582", "creation_timestamp": 1695034204.0, "exchange_order_id": "35172520", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:50:59,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fe79fbfed0582. [clock=2023-09-18 10:50:59+00:00] +2023-09-18 10:50:59,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fe79fc4f50582. [clock=2023-09-18 10:50:59+00:00] +2023-09-18 10:50:59,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254276176725000382642363315 amount: 80. +2023-09-18 10:50:59,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259355515963810663768453807 amount: 80. +2023-09-18 10:50:59,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034259.0, "order_id": "x-XEKWYICXBSIUT6059fe79fbfed0582", "exchange_order_id": "35172521", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:50:59,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fe79fbfed0582. +2023-09-18 10:50:59,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034259.0, "order_id": "x-XEKWYICXSSIUT6059fe79fc4f50582", "exchange_order_id": "35172520", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:50:59,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fe79fc4f50582. +2023-09-18 10:50:59,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059feae43e3c0582 for 80.00000000 SEI-USDT. +2023-09-18 10:50:59,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034259.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT6059feae43e3c0582", "creation_timestamp": 1695034259.0, "exchange_order_id": "35172832", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:50:59,472 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059feae4417a0582 for 80.00000000 SEI-USDT. +2023-09-18 10:50:59,494 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034259.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT6059feae4417a0582", "creation_timestamp": 1695034259.0, "exchange_order_id": "35172833", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:51:21,480 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059feae43e3c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:51:21,482 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 10:51:21+00:00] +2023-09-18 10:51:21,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034281.0, "order_id": "x-XEKWYICXBSIUT6059feae43e3c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4954760", "exchange_order_id": "35172832", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:51:21,516 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034281.0, "order_id": "x-XEKWYICXBSIUT6059feae43e3c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35172832", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:51:21,516 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059feae43e3c0582 completely filled. +2023-09-18 10:51:54,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059feae4417a0582. [clock=2023-09-18 10:51:54+00:00] +2023-09-18 10:51:54,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250531262549252256740016158 amount: 80. +2023-09-18 10:51:54,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256224515838773110381977022 amount: 80. +2023-09-18 10:51:54,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034314.0, "order_id": "x-XEKWYICXSSIUT6059feae4417a0582", "exchange_order_id": "35172833", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:51:54,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059feae4417a0582. +2023-09-18 10:51:54,438 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059fee2c18dd0582 for 80.00000000 SEI-USDT. +2023-09-18 10:51:54,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034314.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT6059fee2c18dd0582", "creation_timestamp": 1695034314.0, "exchange_order_id": "35173710", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:51:54,587 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059fee2c1ce10582 for 80.00000000 SEI-USDT. +2023-09-18 10:51:54,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034314.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT6059fee2c1ce10582", "creation_timestamp": 1695034314.0, "exchange_order_id": "35173711", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:52:49,198 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059fee2c18dd0582. [clock=2023-09-18 10:52:49+00:00] +2023-09-18 10:52:49,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059fee2c1ce10582. [clock=2023-09-18 10:52:49+00:00] +2023-09-18 10:52:49,262 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251825709340043312699348219 amount: 80. +2023-09-18 10:52:49,264 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257057646152212104131394167 amount: 80. +2023-09-18 10:52:49,939 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034369.0, "order_id": "x-XEKWYICXBSIUT6059fee2c18dd0582", "exchange_order_id": "35173710", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:52:49,939 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059fee2c18dd0582. +2023-09-18 10:52:51,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034370.0, "order_id": "x-XEKWYICXSSIUT6059fee2c1ce10582", "exchange_order_id": "35173711", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:52:51,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059fee2c1ce10582. +2023-09-18 10:52:52,050 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059ff17649770582 for 80.00000000 SEI-USDT. +2023-09-18 10:52:52,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT6059ff17649770582", "creation_timestamp": 1695034369.0, "exchange_order_id": "35174267", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:52:52,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059ff1764fc40582 for 80.00000000 SEI-USDT. +2023-09-18 10:52:52,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT6059ff1764fc40582", "creation_timestamp": 1695034369.0, "exchange_order_id": "35174266", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:53:44,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059ff17649770582. [clock=2023-09-18 10:53:44+00:00] +2023-09-18 10:53:44,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059ff1764fc40582. [clock=2023-09-18 10:53:44+00:00] +2023-09-18 10:53:44,431 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250873396234480061733213425 amount: 80. +2023-09-18 10:53:44,452 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255700835009799052349335813 amount: 80. +2023-09-18 10:53:44,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034424.0, "order_id": "x-XEKWYICXBSIUT6059ff17649770582", "exchange_order_id": "35174267", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:53:44,967 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059ff17649770582. +2023-09-18 10:53:46,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034426.0, "order_id": "x-XEKWYICXSSIUT6059ff1764fc40582", "exchange_order_id": "35174266", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:53:46,617 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059ff1764fc40582. +2023-09-18 10:53:47,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059ff4c06a500582 for 80.00000000 SEI-USDT. +2023-09-18 10:53:47,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT6059ff4c06a500582", "creation_timestamp": 1695034424.0, "exchange_order_id": "35174854", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:53:47,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059ff4c066db0582 for 80.00000000 SEI-USDT. +2023-09-18 10:53:47,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT6059ff4c066db0582", "creation_timestamp": 1695034424.0, "exchange_order_id": "35174855", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:54:39,428 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059ff4c066db0582. [clock=2023-09-18 10:54:39+00:00] +2023-09-18 10:54:39,430 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059ff4c06a500582. [clock=2023-09-18 10:54:39+00:00] +2023-09-18 10:54:39,510 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250918920698051338066984773 amount: 80. +2023-09-18 10:54:39,512 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255643021035776790416082789 amount: 80. +2023-09-18 10:54:40,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034479.0, "order_id": "x-XEKWYICXBSIUT6059ff4c066db0582", "exchange_order_id": "35174855", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:54:40,617 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059ff4c066db0582. +2023-09-18 10:54:42,498 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059ff8088b380582 for 80.00000000 SEI-USDT. +2023-09-18 10:54:42,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034482.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT6059ff8088b380582", "creation_timestamp": 1695034479.0, "exchange_order_id": "35175265", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:54:42,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034482.0, "order_id": "x-XEKWYICXSSIUT6059ff4c06a500582", "exchange_order_id": "35174854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:54:42,581 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059ff4c06a500582. +2023-09-18 10:54:42,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059ff8089ae00582 for 80.00000000 SEI-USDT. +2023-09-18 10:54:42,784 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034482.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT6059ff8089ae00582", "creation_timestamp": 1695034479.0, "exchange_order_id": "35175266", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:55:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059ff8088b380582. [clock=2023-09-18 10:55:34+00:00] +2023-09-18 10:55:34,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059ff8089ae00582. [clock=2023-09-18 10:55:34+00:00] +2023-09-18 10:55:34,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252053950909460353835571432 amount: 80. +2023-09-18 10:55:34,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256687982468027423782249550 amount: 80. +2023-09-18 10:55:34,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034534.0, "order_id": "x-XEKWYICXBSIUT6059ff8088b380582", "exchange_order_id": "35175265", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:55:34,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059ff8088b380582. +2023-09-18 10:55:34,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034534.0, "order_id": "x-XEKWYICXSSIUT6059ff8089ae00582", "exchange_order_id": "35175266", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:55:34,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059ff8089ae00582. +2023-09-18 10:55:34,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059ffb4837a60582 for 80.00000000 SEI-USDT. +2023-09-18 10:55:34,700 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034534.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT6059ffb4837a60582", "creation_timestamp": 1695034534.0, "exchange_order_id": "35175645", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:55:34,702 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059ffb483b5b0582 for 80.00000000 SEI-USDT. +2023-09-18 10:55:34,728 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034534.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT6059ffb483b5b0582", "creation_timestamp": 1695034534.0, "exchange_order_id": "35175646", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:55:46,663 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT6059ffb4837a60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 10:55:46,664 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 10:55:46+00:00] +2023-09-18 10:55:46,692 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034546.0, "order_id": "x-XEKWYICXBSIUT6059ffb4837a60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4955027", "exchange_order_id": "35175645", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 10:55:46,705 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034546.0, "order_id": "x-XEKWYICXBSIUT6059ffb4837a60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35175645", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 10:55:46,706 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT6059ffb4837a60582 completely filled. +2023-09-18 10:56:29,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059ffb483b5b0582. [clock=2023-09-18 10:56:29+00:00] +2023-09-18 10:56:29,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249889347029055461979991072 amount: 80. +2023-09-18 10:56:29,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254412220128909718208823816 amount: 80. +2023-09-18 10:56:29,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034589.0, "order_id": "x-XEKWYICXSSIUT6059ffb483b5b0582", "exchange_order_id": "35175646", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:56:29,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059ffb483b5b0582. +2023-09-18 10:56:29,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT6059ffe90183e0582 for 80.00000000 SEI-USDT. +2023-09-18 10:56:29,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034589.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT6059ffe90183e0582", "creation_timestamp": 1695034589.0, "exchange_order_id": "35176201", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:56:29,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT6059ffe901bd50582 for 80.00000000 SEI-USDT. +2023-09-18 10:56:29,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034589.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT6059ffe901bd50582", "creation_timestamp": 1695034589.0, "exchange_order_id": "35176202", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:57:24,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT6059ffe90183e0582. [clock=2023-09-18 10:57:24+00:00] +2023-09-18 10:57:24,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT6059ffe901bd50582. [clock=2023-09-18 10:57:24+00:00] +2023-09-18 10:57:24,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247826012295578145899103790 amount: 80. +2023-09-18 10:57:24,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252032812123061715330854951 amount: 80. +2023-09-18 10:57:24,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034644.0, "order_id": "x-XEKWYICXBSIUT6059ffe90183e0582", "exchange_order_id": "35176201", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:57:24,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT6059ffe90183e0582. +2023-09-18 10:57:25,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034645.0, "order_id": "x-XEKWYICXSSIUT6059ffe901bd50582", "exchange_order_id": "35176202", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:57:25,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT6059ffe901bd50582. +2023-09-18 10:57:25,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a001d7cda10582 for 80.00000000 SEI-USDT. +2023-09-18 10:57:25,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034645.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a001d7cda10582", "creation_timestamp": 1695034644.0, "exchange_order_id": "35176550", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:57:25,562 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a001d7d1e00582 for 80.00000000 SEI-USDT. +2023-09-18 10:57:25,586 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034645.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a001d7d1e00582", "creation_timestamp": 1695034644.0, "exchange_order_id": "35176551", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:58:19,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a001d7cda10582. [clock=2023-09-18 10:58:19+00:00] +2023-09-18 10:58:19,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a001d7d1e00582. [clock=2023-09-18 10:58:19+00:00] +2023-09-18 10:58:19,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246897494470030334082168113 amount: 80. +2023-09-18 10:58:19,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250796925119089108437331799 amount: 80. +2023-09-18 10:58:19,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034699.0, "order_id": "x-XEKWYICXBSIUT605a001d7cda10582", "exchange_order_id": "35176550", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:58:19,633 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a001d7cda10582. +2023-09-18 10:58:20,831 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034700.0, "order_id": "x-XEKWYICXSSIUT605a001d7d1e00582", "exchange_order_id": "35176551", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:58:20,832 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a001d7d1e00582. +2023-09-18 10:58:21,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0051f67800582 for 80.00000000 SEI-USDT. +2023-09-18 10:58:21,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034700.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a0051f67800582", "creation_timestamp": 1695034699.0, "exchange_order_id": "35177291", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:58:21,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0051f6c010582 for 80.00000000 SEI-USDT. +2023-09-18 10:58:21,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034700.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a0051f6c010582", "creation_timestamp": 1695034699.0, "exchange_order_id": "35177293", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:59:14,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0051f67800582. [clock=2023-09-18 10:59:14+00:00] +2023-09-18 10:59:14,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0051f6c010582. [clock=2023-09-18 10:59:14+00:00] +2023-09-18 10:59:14,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246961670002821461577156035 amount: 80. +2023-09-18 10:59:14,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250595104049787113945500551 amount: 80. +2023-09-18 10:59:14,999 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034754.0, "order_id": "x-XEKWYICXBSIUT605a0051f67800582", "exchange_order_id": "35177291", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:59:14,999 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0051f67800582. +2023-09-18 10:59:16,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034755.0, "order_id": "x-XEKWYICXSSIUT605a0051f6c010582", "exchange_order_id": "35177293", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 10:59:16,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0051f6c010582. +2023-09-18 10:59:16,776 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0086848690582 for 80.00000000 SEI-USDT. +2023-09-18 10:59:16,813 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a0086848690582", "creation_timestamp": 1695034754.0, "exchange_order_id": "35177472", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 10:59:16,814 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a00868449a0582 for 80.00000000 SEI-USDT. +2023-09-18 10:59:16,862 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a00868449a0582", "creation_timestamp": 1695034754.0, "exchange_order_id": "35177473", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:00:00,958 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a0086848690582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:00:00,968 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:00:00+00:00] +2023-09-18 11:00:01,058 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034800.0, "order_id": "x-XEKWYICXSSIUT605a0086848690582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4955180", "exchange_order_id": "35177472", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:00:01,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034800.0, "order_id": "x-XEKWYICXSSIUT605a0086848690582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35177472", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:00:01,295 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a0086848690582 completely filled. +2023-09-18 11:00:09,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a00868449a0582. [clock=2023-09-18 11:00:09+00:00] +2023-09-18 11:00:09,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248038013530651042004344055 amount: 80. +2023-09-18 11:00:09,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251902694973242346770242684 amount: 80. +2023-09-18 11:00:09,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a00bad036b0582 for 80.00000000 SEI-USDT. +2023-09-18 11:00:09,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034809.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a00bad036b0582", "creation_timestamp": 1695034809.0, "exchange_order_id": "35177828", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:00:09,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034809.0, "order_id": "x-XEKWYICXBSIUT605a00868449a0582", "exchange_order_id": "35177473", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:00:09,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a00868449a0582. +2023-09-18 11:00:09,563 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a00bad05d80582 for 80.00000000 SEI-USDT. +2023-09-18 11:00:09,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034809.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a00bad05d80582", "creation_timestamp": 1695034809.0, "exchange_order_id": "35177829", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:01:04,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a00bad036b0582. [clock=2023-09-18 11:01:04+00:00] +2023-09-18 11:01:04,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a00bad05d80582. [clock=2023-09-18 11:01:04+00:00] +2023-09-18 11:01:04,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247194627389345941467061782 amount: 80. +2023-09-18 11:01:04,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250575993330453437250690582 amount: 80. +2023-09-18 11:01:04,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034864.0, "order_id": "x-XEKWYICXBSIUT605a00bad036b0582", "exchange_order_id": "35177828", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:01:04,059 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a00bad036b0582. +2023-09-18 11:01:04,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a00ef3ca0b0582 for 80.00000000 SEI-USDT. +2023-09-18 11:01:04,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034864.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a00ef3ca0b0582", "creation_timestamp": 1695034864.0, "exchange_order_id": "35178099", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:01:04,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034864.0, "order_id": "x-XEKWYICXSSIUT605a00bad05d80582", "exchange_order_id": "35177829", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:01:04,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a00bad05d80582. +2023-09-18 11:01:04,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a00ef3c79e0582 for 80.00000000 SEI-USDT. +2023-09-18 11:01:04,399 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034864.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a00ef3c79e0582", "creation_timestamp": 1695034864.0, "exchange_order_id": "35178100", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:01:06,835 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a00ef3ca0b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:01:06,837 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:01:06+00:00] +2023-09-18 11:01:06,857 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034866.0, "order_id": "x-XEKWYICXSSIUT605a00ef3ca0b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4955210", "exchange_order_id": "35178099", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:01:06,874 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034866.0, "order_id": "x-XEKWYICXSSIUT605a00ef3ca0b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35178099", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:01:06,874 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a00ef3ca0b0582 completely filled. +2023-09-18 11:01:59,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a00ef3c79e0582. [clock=2023-09-18 11:01:59+00:00] +2023-09-18 11:01:59,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246927719183904616631865815 amount: 80. +2023-09-18 11:01:59,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250808986511404237338904341 amount: 80. +2023-09-18 11:01:59,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034919.0, "order_id": "x-XEKWYICXBSIUT605a00ef3c79e0582", "exchange_order_id": "35178100", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:01:59,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a00ef3c79e0582. +2023-09-18 11:01:59,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0123ae0b20582 for 80.00000000 SEI-USDT. +2023-09-18 11:01:59,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034919.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a0123ae0b20582", "creation_timestamp": 1695034919.0, "exchange_order_id": "35178810", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:01:59,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0123add3f0582 for 80.00000000 SEI-USDT. +2023-09-18 11:01:59,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034919.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a0123add3f0582", "creation_timestamp": 1695034919.0, "exchange_order_id": "35178811", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:02:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0123add3f0582. [clock=2023-09-18 11:02:54+00:00] +2023-09-18 11:02:54,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0123ae0b20582. [clock=2023-09-18 11:02:54+00:00] +2023-09-18 11:02:54,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247053277060385490521972847 amount: 80. +2023-09-18 11:02:54,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250699382910764390196191417 amount: 80. +2023-09-18 11:02:54,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034974.0, "order_id": "x-XEKWYICXBSIUT605a0123add3f0582", "exchange_order_id": "35178811", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:02:54,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0123add3f0582. +2023-09-18 11:02:54,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a015823b600582 for 80.00000000 SEI-USDT. +2023-09-18 11:02:54,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034974.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a015823b600582", "creation_timestamp": 1695034974.0, "exchange_order_id": "35179035", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:02:54,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034974.0, "order_id": "x-XEKWYICXSSIUT605a0123ae0b20582", "exchange_order_id": "35178810", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:02:54,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0123ae0b20582. +2023-09-18 11:02:54,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a015823dc90582 for 80.00000000 SEI-USDT. +2023-09-18 11:02:54,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034974.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a015823dc90582", "creation_timestamp": 1695034974.0, "exchange_order_id": "35179034", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:03:19,562 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a015823dc90582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:03:19,562 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:03:19+00:00] +2023-09-18 11:03:19,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034999.0, "order_id": "x-XEKWYICXSSIUT605a015823dc90582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4955307", "exchange_order_id": "35179034", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:03:19,648 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695034999.0, "order_id": "x-XEKWYICXSSIUT605a015823dc90582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35179034", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:03:19,649 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a015823dc90582 completely filled. +2023-09-18 11:03:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a015823b600582. [clock=2023-09-18 11:03:49+00:00] +2023-09-18 11:03:49,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247896318046540775326812683 amount: 80. +2023-09-18 11:03:49,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252086948539782974673187317 amount: 80. +2023-09-18 11:03:49,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035029.0, "order_id": "x-XEKWYICXBSIUT605a015823b600582", "exchange_order_id": "35179035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:03:49,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a015823b600582. +2023-09-18 11:03:49,720 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a018c99a590582 for 80.00000000 SEI-USDT. +2023-09-18 11:03:49,758 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a018c99a590582", "creation_timestamp": 1695035029.0, "exchange_order_id": "35179670", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:03:49,761 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a018c99c7c0582 for 80.00000000 SEI-USDT. +2023-09-18 11:03:49,824 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a018c99c7c0582", "creation_timestamp": 1695035029.0, "exchange_order_id": "35179671", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:04:20,868 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a018c99c7c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:04:20,870 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 11:04:20+00:00] +2023-09-18 11:04:20,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035060.0, "order_id": "x-XEKWYICXSSIUT605a018c99c7c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4955369", "exchange_order_id": "35179671", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:04:21,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035060.0, "order_id": "x-XEKWYICXSSIUT605a018c99c7c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35179671", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:04:21,059 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a018c99c7c0582 completely filled. +2023-09-18 11:04:44,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a018c99a590582. [clock=2023-09-18 11:04:44+00:00] +2023-09-18 11:04:44,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248916018014123089419178729 amount: 80. +2023-09-18 11:04:44,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253333284337105209090625387 amount: 80. +2023-09-18 11:04:44,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035084.0, "order_id": "x-XEKWYICXBSIUT605a018c99a590582", "exchange_order_id": "35179670", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:04:44,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a018c99a590582. +2023-09-18 11:04:44,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a01c10bd0a0582 for 80.00000000 SEI-USDT. +2023-09-18 11:04:44,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035084.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a01c10bd0a0582", "creation_timestamp": 1695035084.0, "exchange_order_id": "35180502", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:04:44,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a01c10c0af0582 for 80.00000000 SEI-USDT. +2023-09-18 11:04:44,375 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035084.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a01c10c0af0582", "creation_timestamp": 1695035084.0, "exchange_order_id": "35180503", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:05:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a01c10bd0a0582. [clock=2023-09-18 11:05:39+00:00] +2023-09-18 11:05:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a01c10c0af0582. [clock=2023-09-18 11:05:39+00:00] +2023-09-18 11:05:39,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249058572800747054110536816 amount: 80. +2023-09-18 11:05:39,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:05:39,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035139.0, "order_id": "x-XEKWYICXBSIUT605a01c10bd0a0582", "exchange_order_id": "35180502", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:05:39,086 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a01c10bd0a0582. +2023-09-18 11:05:39,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035139.0, "order_id": "x-XEKWYICXSSIUT605a01c10c0af0582", "exchange_order_id": "35180503", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:05:39,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a01c10c0af0582. +2023-09-18 11:05:39,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a01f57ce660582 for 80.00000000 SEI-USDT. +2023-09-18 11:05:39,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035139.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a01f57ce660582", "creation_timestamp": 1695035139.0, "exchange_order_id": "35180891", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:06:21,300 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a01f57ce660582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:06:21,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:06:21+00:00] +2023-09-18 11:06:21,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035181.0, "order_id": "x-XEKWYICXBSIUT605a01f57ce660582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4955471", "exchange_order_id": "35180891", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:06:21,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035181.0, "order_id": "x-XEKWYICXBSIUT605a01f57ce660582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35180891", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:06:21,358 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a01f57ce660582 completely filled. +2023-09-18 11:06:34,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248547702945498366693750440 amount: 80. +2023-09-18 11:06:34,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253123550673937461866943276 amount: 80. +2023-09-18 11:06:34,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0229f0a570582 for 80.00000000 SEI-USDT. +2023-09-18 11:06:34,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035194.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a0229f0a570582", "creation_timestamp": 1695035194.0, "exchange_order_id": "35181823", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:06:34,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0229f12160582 for 80.00000000 SEI-USDT. +2023-09-18 11:06:34,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035194.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a0229f12160582", "creation_timestamp": 1695035194.0, "exchange_order_id": "35181824", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:07:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0229f0a570582. [clock=2023-09-18 11:07:29+00:00] +2023-09-18 11:07:29,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0229f12160582. [clock=2023-09-18 11:07:29+00:00] +2023-09-18 11:07:29,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247977372192561890457381147 amount: 80. +2023-09-18 11:07:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251975287492299720767368690 amount: 80. +2023-09-18 11:07:29,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035249.0, "order_id": "x-XEKWYICXBSIUT605a0229f0a570582", "exchange_order_id": "35181823", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:07:29,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0229f0a570582. +2023-09-18 11:07:29,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a025e645100582 for 80.00000000 SEI-USDT. +2023-09-18 11:07:29,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035249.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a025e645100582", "creation_timestamp": 1695035249.0, "exchange_order_id": "35182164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:07:29,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a025e648920582 for 80.00000000 SEI-USDT. +2023-09-18 11:07:29,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035249.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a025e648920582", "creation_timestamp": 1695035249.0, "exchange_order_id": "35182165", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:07:29,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035249.0, "order_id": "x-XEKWYICXSSIUT605a0229f12160582", "exchange_order_id": "35181824", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:07:29,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0229f12160582. +2023-09-18 11:08:12,275 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a025e648920582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:08:12,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 11:08:12+00:00] +2023-09-18 11:08:12,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035292.0, "order_id": "x-XEKWYICXSSIUT605a025e648920582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "4955535", "exchange_order_id": "35182165", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:08:12,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035292.0, "order_id": "x-XEKWYICXSSIUT605a025e648920582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35182165", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:08:12,359 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a025e648920582 completely filled. +2023-09-18 11:08:24,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a025e645100582. [clock=2023-09-18 11:08:24+00:00] +2023-09-18 11:08:24,266 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249241145513788465880890471 amount: 80. +2023-09-18 11:08:24,267 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252958434141419103833291075 amount: 80. +2023-09-18 11:08:24,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035304.0, "order_id": "x-XEKWYICXBSIUT605a025e645100582", "exchange_order_id": "35182164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:08:24,822 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a025e645100582. +2023-09-18 11:08:25,043 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a029314ed60582 for 80.00000000 SEI-USDT. +2023-09-18 11:08:25,076 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035305.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a029314ed60582", "creation_timestamp": 1695035304.0, "exchange_order_id": "35182499", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:08:25,078 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0293153f20582 for 80.00000000 SEI-USDT. +2023-09-18 11:08:25,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035305.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a0293153f20582", "creation_timestamp": 1695035304.0, "exchange_order_id": "35182500", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:09:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a029314ed60582. [clock=2023-09-18 11:09:19+00:00] +2023-09-18 11:09:19,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0293153f20582. [clock=2023-09-18 11:09:19+00:00] +2023-09-18 11:09:19,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249234707435493564186468436 amount: 80. +2023-09-18 11:09:19,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:09:19,334 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035359.0, "order_id": "x-XEKWYICXBSIUT605a029314ed60582", "exchange_order_id": "35182499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:09:19,335 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a029314ed60582. +2023-09-18 11:09:19,694 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035359.0, "order_id": "x-XEKWYICXSSIUT605a0293153f20582", "exchange_order_id": "35182500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:09:19,694 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0293153f20582. +2023-09-18 11:09:19,699 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a02c7531020582 for 80.00000000 SEI-USDT. +2023-09-18 11:09:19,735 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035359.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a02c7531020582", "creation_timestamp": 1695035359.0, "exchange_order_id": "35182720", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:10:10,471 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a02c7531020582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:10:10,473 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:10:10+00:00] +2023-09-18 11:10:10,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035410.0, "order_id": "x-XEKWYICXBSIUT605a02c7531020582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4955585", "exchange_order_id": "35182720", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:10:10,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035410.0, "order_id": "x-XEKWYICXBSIUT605a02c7531020582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35182720", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:10:10,515 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a02c7531020582 completely filled. +2023-09-18 11:10:14,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246991223514326296351111012 amount: 80. +2023-09-18 11:10:14,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250715514585996912618682866 amount: 80. +2023-09-18 11:10:14,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a02fbc0c130582 for 80.00000000 SEI-USDT. +2023-09-18 11:10:14,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035414.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a02fbc0c130582", "creation_timestamp": 1695035414.0, "exchange_order_id": "35182960", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:10:14,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a02fbc10220582 for 80.00000000 SEI-USDT. +2023-09-18 11:10:14,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035414.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a02fbc10220582", "creation_timestamp": 1695035414.0, "exchange_order_id": "35182961", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:11:06,740 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a02fbc10220582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:11:06,742 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:11:06+00:00] +2023-09-18 11:11:06,775 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035466.0, "order_id": "x-XEKWYICXSSIUT605a02fbc10220582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4955618", "exchange_order_id": "35182961", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:11:06,806 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035466.0, "order_id": "x-XEKWYICXSSIUT605a02fbc10220582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35182961", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:11:06,807 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a02fbc10220582 completely filled. +2023-09-18 11:11:09,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a02fbc0c130582. [clock=2023-09-18 11:11:09+00:00] +2023-09-18 11:11:09,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248238732132340424209411167 amount: 80. +2023-09-18 11:11:09,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251747850605260214567722103 amount: 80. +2023-09-18 11:11:09,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035469.0, "order_id": "x-XEKWYICXBSIUT605a02fbc0c130582", "exchange_order_id": "35182960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:11:09,047 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a02fbc0c130582. +2023-09-18 11:11:09,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0330331340582 for 80.00000000 SEI-USDT. +2023-09-18 11:11:09,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035469.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a0330331340582", "creation_timestamp": 1695035469.0, "exchange_order_id": "35183188", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:11:09,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a033032f1c0582 for 80.00000000 SEI-USDT. +2023-09-18 11:11:09,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035469.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a033032f1c0582", "creation_timestamp": 1695035469.0, "exchange_order_id": "35183189", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:12:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a033032f1c0582. [clock=2023-09-18 11:12:04+00:00] +2023-09-18 11:12:04,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0330331340582. [clock=2023-09-18 11:12:04+00:00] +2023-09-18 11:12:04,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248458889585856631789573297 amount: 80. +2023-09-18 11:12:04,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:12:04,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035524.0, "order_id": "x-XEKWYICXBSIUT605a033032f1c0582", "exchange_order_id": "35183189", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:12:04,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a033032f1c0582. +2023-09-18 11:12:04,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035524.0, "order_id": "x-XEKWYICXSSIUT605a0330331340582", "exchange_order_id": "35183188", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:12:04,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0330331340582. +2023-09-18 11:12:04,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0364a75b30582 for 80.00000000 SEI-USDT. +2023-09-18 11:12:04,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035524.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a0364a75b30582", "creation_timestamp": 1695035524.0, "exchange_order_id": "35183497", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:12:59,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0364a75b30582. [clock=2023-09-18 11:12:59+00:00] +2023-09-18 11:12:59,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249448672287338435008554250 amount: 80. +2023-09-18 11:12:59,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252769456917395395002985268 amount: 80. +2023-09-18 11:12:59,048 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035579.0, "order_id": "x-XEKWYICXBSIUT605a0364a75b30582", "exchange_order_id": "35183497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:12:59,049 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0364a75b30582. +2023-09-18 11:12:59,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a03991a4790582 for 80.00000000 SEI-USDT. +2023-09-18 11:12:59,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035579.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a03991a4790582", "creation_timestamp": 1695035579.0, "exchange_order_id": "35183780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:12:59,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a03991a7c90582 for 80.00000000 SEI-USDT. +2023-09-18 11:12:59,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035579.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a03991a7c90582", "creation_timestamp": 1695035579.0, "exchange_order_id": "35183781", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:13:23,854 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a03991a7c90582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:13:23,856 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 11:13:23+00:00] +2023-09-18 11:13:23,949 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035603.0, "order_id": "x-XEKWYICXSSIUT605a03991a7c90582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4955690", "exchange_order_id": "35183781", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:13:23,991 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035603.0, "order_id": "x-XEKWYICXSSIUT605a03991a7c90582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35183781", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:13:23,991 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a03991a7c90582 completely filled. +2023-09-18 11:13:54,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a03991a4790582. [clock=2023-09-18 11:13:54+00:00] +2023-09-18 11:13:54,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249525795662519787287882253 amount: 80. +2023-09-18 11:13:54,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:13:54,657 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035634.0, "order_id": "x-XEKWYICXBSIUT605a03991a4790582", "exchange_order_id": "35183780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:13:54,657 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a03991a4790582. +2023-09-18 11:13:54,868 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a03cda7d490582 for 80.00000000 SEI-USDT. +2023-09-18 11:13:54,939 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035634.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a03cda7d490582", "creation_timestamp": 1695035634.0, "exchange_order_id": "35184114", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:14:49,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a03cda7d490582. [clock=2023-09-18 11:14:49+00:00] +2023-09-18 11:14:49,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249593278028994820450129715 amount: 80. +2023-09-18 11:14:49,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:14:49,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035689.0, "order_id": "x-XEKWYICXBSIUT605a03cda7d490582", "exchange_order_id": "35184114", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:14:49,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a03cda7d490582. +2023-09-18 11:14:49,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a040207d950582 for 80.00000000 SEI-USDT. +2023-09-18 11:14:49,540 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035689.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a040207d950582", "creation_timestamp": 1695035689.0, "exchange_order_id": "35184763", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:15:44,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a040207d950582. [clock=2023-09-18 11:15:44+00:00] +2023-09-18 11:15:44,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250757972989591189246190259 amount: 80. +2023-09-18 11:15:44,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:15:44,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035744.0, "order_id": "x-XEKWYICXBSIUT605a040207d950582", "exchange_order_id": "35184763", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:15:44,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a040207d950582. +2023-09-18 11:15:44,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0436763730582 for 80.00000000 SEI-USDT. +2023-09-18 11:15:44,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035744.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a0436763730582", "creation_timestamp": 1695035744.0, "exchange_order_id": "35185129", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:16:39,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0436763730582. [clock=2023-09-18 11:16:39+00:00] +2023-09-18 11:16:39,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252885136211575043689850972 amount: 80. +2023-09-18 11:16:39,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:16:39,081 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035799.0, "order_id": "x-XEKWYICXBSIUT605a0436763730582", "exchange_order_id": "35185129", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:16:39,082 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0436763730582. +2023-09-18 11:16:39,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a046ae94af0582 for 80.00000000 SEI-USDT. +2023-09-18 11:16:39,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035799.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a046ae94af0582", "creation_timestamp": 1695035799.0, "exchange_order_id": "35185860", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:17:06,928 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a046ae94af0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:17:06,930 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 11:17:06+00:00] +2023-09-18 11:17:06,960 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035826.0, "order_id": "x-XEKWYICXBSIUT605a046ae94af0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4955877", "exchange_order_id": "35185860", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:17:06,975 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035826.0, "order_id": "x-XEKWYICXBSIUT605a046ae94af0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35185860", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:17:06,975 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a046ae94af0582 completely filled. +2023-09-18 11:17:34,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249480305668230979801705021 amount: 80. +2023-09-18 11:17:34,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252626483704545204920645491 amount: 80. +2023-09-18 11:17:34,068 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a049f5d0c40582 for 80.00000000 SEI-USDT. +2023-09-18 11:17:34,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035854.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a049f5d0c40582", "creation_timestamp": 1695035854.0, "exchange_order_id": "35186441", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:17:34,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a049f5d5ba0582 for 80.00000000 SEI-USDT. +2023-09-18 11:17:34,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035854.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a049f5d5ba0582", "creation_timestamp": 1695035854.0, "exchange_order_id": "35186445", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:17:52,691 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 11:18:00,965 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a049f5d5ba0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:18:00,967 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 11:18:00+00:00] +2023-09-18 11:18:00,991 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035880.0, "order_id": "x-XEKWYICXSSIUT605a049f5d5ba0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4955921", "exchange_order_id": "35186445", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:18:01,005 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035880.0, "order_id": "x-XEKWYICXSSIUT605a049f5d5ba0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35186445", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:18:01,005 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a049f5d5ba0582 completely filled. +2023-09-18 11:18:18,872 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a049f5d0c40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:18:18,873 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:18:18+00:00] +2023-09-18 11:18:18,934 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035898.0, "order_id": "x-XEKWYICXBSIUT605a049f5d0c40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4955965", "exchange_order_id": "35186441", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:18:18,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035898.0, "order_id": "x-XEKWYICXBSIUT605a049f5d0c40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35186441", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:18:18,981 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a049f5d0c40582 completely filled. +2023-09-18 11:18:29,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246809950766441752475319363 amount: 80. +2023-09-18 11:18:29,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250780838455810081402417127 amount: 80. +2023-09-18 11:18:29,448 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a04d3d77170582 for 80.00000000 SEI-USDT. +2023-09-18 11:18:29,543 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035909.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a04d3d77170582", "creation_timestamp": 1695035909.0, "exchange_order_id": "35187218", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:18:29,741 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a04d3d79740582 for 80.00000000 SEI-USDT. +2023-09-18 11:18:29,884 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035909.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a04d3d79740582", "creation_timestamp": 1695035909.0, "exchange_order_id": "35187219", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:19:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a04d3d77170582. [clock=2023-09-18 11:19:24+00:00] +2023-09-18 11:19:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a04d3d79740582. [clock=2023-09-18 11:19:24+00:00] +2023-09-18 11:19:24,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245634605507051786906582264 amount: 80. +2023-09-18 11:19:24,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:19:24,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035964.0, "order_id": "x-XEKWYICXBSIUT605a04d3d77170582", "exchange_order_id": "35187218", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:19:24,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a04d3d77170582. +2023-09-18 11:19:24,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a05084df2d0582 for 80.00000000 SEI-USDT. +2023-09-18 11:19:24,406 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035964.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a05084df2d0582", "creation_timestamp": 1695035964.0, "exchange_order_id": "35187613", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:19:24,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695035964.0, "order_id": "x-XEKWYICXSSIUT605a04d3d79740582", "exchange_order_id": "35187219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:19:24,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a04d3d79740582. +2023-09-18 11:20:19,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a05084df2d0582. [clock=2023-09-18 11:20:19+00:00] +2023-09-18 11:20:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245805943275407901811275524 amount: 80. +2023-09-18 11:20:19,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249666148164329846037941160 amount: 80. +2023-09-18 11:20:19,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036019.0, "order_id": "x-XEKWYICXBSIUT605a05084df2d0582", "exchange_order_id": "35187613", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:20:19,058 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a05084df2d0582. +2023-09-18 11:20:19,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a053cb8b4b0582 for 80.00000000 SEI-USDT. +2023-09-18 11:20:19,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036019.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a053cb8b4b0582", "creation_timestamp": 1695036019.0, "exchange_order_id": "35187907", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:20:19,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a053cb87f60582 for 80.00000000 SEI-USDT. +2023-09-18 11:20:19,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036019.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a053cb87f60582", "creation_timestamp": 1695036019.0, "exchange_order_id": "35187908", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:20:29,051 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a053cb8b4b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:20:29,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:20:29+00:00] +2023-09-18 11:20:29,072 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036029.0, "order_id": "x-XEKWYICXSSIUT605a053cb8b4b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00999200"}]}, "exchange_trade_id": "4956044", "exchange_order_id": "35187907", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:20:29,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036029.0, "order_id": "x-XEKWYICXSSIUT605a053cb8b4b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35187907", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:20:29,088 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a053cb8b4b0582 completely filled. +2023-09-18 11:21:14,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a053cb87f60582. [clock=2023-09-18 11:21:14+00:00] +2023-09-18 11:21:14,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248952663187060577256504385 amount: 80. +2023-09-18 11:21:14,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:21:14,082 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036074.0, "order_id": "x-XEKWYICXBSIUT605a053cb87f60582", "exchange_order_id": "35187908", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:21:14,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a053cb87f60582. +2023-09-18 11:21:14,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a05712c3140582 for 80.00000000 SEI-USDT. +2023-09-18 11:21:14,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036074.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a05712c3140582", "creation_timestamp": 1695036074.0, "exchange_order_id": "35188254", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:22:09,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a05712c3140582. [clock=2023-09-18 11:22:09+00:00] +2023-09-18 11:22:09,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249208598110220680570473540 amount: 80. +2023-09-18 11:22:09,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:22:09,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036129.0, "order_id": "x-XEKWYICXBSIUT605a05712c3140582", "exchange_order_id": "35188254", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:22:09,086 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a05712c3140582. +2023-09-18 11:22:09,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a05a5a05ed0582 for 80.00000000 SEI-USDT. +2023-09-18 11:22:09,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036129.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a05a5a05ed0582", "creation_timestamp": 1695036129.0, "exchange_order_id": "35188712", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:23:04,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a05a5a05ed0582. [clock=2023-09-18 11:23:04+00:00] +2023-09-18 11:23:04,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248223134226163477183805667 amount: 80. +2023-09-18 11:23:04,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:23:04,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036184.0, "order_id": "x-XEKWYICXBSIUT605a05a5a05ed0582", "exchange_order_id": "35188712", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:23:04,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a05a5a05ed0582. +2023-09-18 11:23:04,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a05da17e8d0582 for 80.00000000 SEI-USDT. +2023-09-18 11:23:04,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036184.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a05da17e8d0582", "creation_timestamp": 1695036184.0, "exchange_order_id": "35189154", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:23:59,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a05da17e8d0582. [clock=2023-09-18 11:23:59+00:00] +2023-09-18 11:23:59,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246989309564026200787312023 amount: 80. +2023-09-18 11:23:59,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:23:59,486 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036239.0, "order_id": "x-XEKWYICXBSIUT605a05da17e8d0582", "exchange_order_id": "35189154", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:23:59,487 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a05da17e8d0582. +2023-09-18 11:23:59,732 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a060e93aa80582 for 80.00000000 SEI-USDT. +2023-09-18 11:23:59,795 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036239.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a060e93aa80582", "creation_timestamp": 1695036239.0, "exchange_order_id": "35189418", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:24:43,743 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a060e93aa80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:24:43,744 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:24:43+00:00] +2023-09-18 11:24:43,854 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036283.0, "order_id": "x-XEKWYICXBSIUT605a060e93aa80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4956227", "exchange_order_id": "35189418", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:24:43,909 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036283.0, "order_id": "x-XEKWYICXBSIUT605a060e93aa80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35189418", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:24:43,909 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a060e93aa80582 completely filled. +2023-09-18 11:24:54,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244338513655495148876472965 amount: 80. +2023-09-18 11:24:54,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248867604378776964582373184 amount: 80. +2023-09-18 11:24:54,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a064301d330582 for 80.00000000 SEI-USDT. +2023-09-18 11:24:54,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036294.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a064301d330582", "creation_timestamp": 1695036294.0, "exchange_order_id": "35190559", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:24:54,704 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a06430418c0582 for 80.00000000 SEI-USDT. +2023-09-18 11:24:54,769 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036294.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a06430418c0582", "creation_timestamp": 1695036294.0, "exchange_order_id": "35190566", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:25:49,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a064301d330582. [clock=2023-09-18 11:25:49+00:00] +2023-09-18 11:25:49,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a06430418c0582. [clock=2023-09-18 11:25:49+00:00] +2023-09-18 11:25:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244671120581281649113660010 amount: 80. +2023-09-18 11:25:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:25:49,045 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036349.0, "order_id": "x-XEKWYICXBSIUT605a064301d330582", "exchange_order_id": "35190559", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:25:49,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a064301d330582. +2023-09-18 11:25:49,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a06776efa30582 for 80.00000000 SEI-USDT. +2023-09-18 11:25:49,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036349.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a06776efa30582", "creation_timestamp": 1695036349.0, "exchange_order_id": "35190873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:25:49,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036349.0, "order_id": "x-XEKWYICXSSIUT605a06430418c0582", "exchange_order_id": "35190566", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:25:49,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a06430418c0582. +2023-09-18 11:26:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a06776efa30582. [clock=2023-09-18 11:26:44+00:00] +2023-09-18 11:26:44,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245945524312702687157669966 amount: 80. +2023-09-18 11:26:44,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249663631236279946151117755 amount: 80. +2023-09-18 11:26:44,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036404.0, "order_id": "x-XEKWYICXBSIUT605a06776efa30582", "exchange_order_id": "35190873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:26:44,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a06776efa30582. +2023-09-18 11:26:44,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a06abe2ea20582 for 80.00000000 SEI-USDT. +2023-09-18 11:26:44,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036404.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a06abe2ea20582", "creation_timestamp": 1695036404.0, "exchange_order_id": "35190999", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:26:44,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a06abe30d00582 for 80.00000000 SEI-USDT. +2023-09-18 11:26:44,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036404.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a06abe30d00582", "creation_timestamp": 1695036404.0, "exchange_order_id": "35191000", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:27:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a06abe2ea20582. [clock=2023-09-18 11:27:39+00:00] +2023-09-18 11:27:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a06abe30d00582. [clock=2023-09-18 11:27:39+00:00] +2023-09-18 11:27:39,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243735965774195573627436200 amount: 80. +2023-09-18 11:27:39,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:27:39,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036459.0, "order_id": "x-XEKWYICXBSIUT605a06abe2ea20582", "exchange_order_id": "35190999", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:27:39,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a06abe2ea20582. +2023-09-18 11:27:39,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a06e0563a80582 for 80.00000000 SEI-USDT. +2023-09-18 11:27:39,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036459.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a06e0563a80582", "creation_timestamp": 1695036459.0, "exchange_order_id": "35191199", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:27:39,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036459.0, "order_id": "x-XEKWYICXSSIUT605a06abe30d00582", "exchange_order_id": "35191000", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:27:39,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a06abe30d00582. +2023-09-18 11:28:34,095 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a06e0563a80582. [clock=2023-09-18 11:28:34+00:00] +2023-09-18 11:28:34,164 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243783718058955467434856124 amount: 80. +2023-09-18 11:28:34,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247536064977049868453684151 amount: 80. +2023-09-18 11:28:34,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036514.0, "order_id": "x-XEKWYICXBSIUT605a06e0563a80582", "exchange_order_id": "35191199", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:28:34,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a06e0563a80582. +2023-09-18 11:28:34,582 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0714ee2760582 for 80.00000000 SEI-USDT. +2023-09-18 11:28:34,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036514.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0714ee2760582", "creation_timestamp": 1695036514.0, "exchange_order_id": "35191652", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:28:34,628 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0714ee5ef0582 for 80.00000000 SEI-USDT. +2023-09-18 11:28:34,676 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036514.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a0714ee5ef0582", "creation_timestamp": 1695036514.0, "exchange_order_id": "35191653", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:29:10,665 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a0714ee5ef0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:29:10,667 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:29:10+00:00] +2023-09-18 11:29:10,810 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036550.0, "order_id": "x-XEKWYICXSSIUT605a0714ee5ef0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4956363", "exchange_order_id": "35191653", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:29:10,876 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036550.0, "order_id": "x-XEKWYICXSSIUT605a0714ee5ef0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35191653", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:29:10,876 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a0714ee5ef0582 completely filled. +2023-09-18 11:29:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0714ee2760582. [clock=2023-09-18 11:29:29+00:00] +2023-09-18 11:29:29,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244758061897710225945975075 amount: 80. +2023-09-18 11:29:29,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:29:29,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036569.0, "order_id": "x-XEKWYICXBSIUT605a0714ee2760582", "exchange_order_id": "35191652", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:29:29,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0714ee2760582. +2023-09-18 11:29:29,774 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a074944ad90582 for 80.00000000 SEI-USDT. +2023-09-18 11:29:29,848 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036569.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a074944ad90582", "creation_timestamp": 1695036569.0, "exchange_order_id": "35192313", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:30:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a074944ad90582. [clock=2023-09-18 11:30:24+00:00] +2023-09-18 11:30:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243957741507652046623905507 amount: 80. +2023-09-18 11:30:24,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:30:24,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036624.0, "order_id": "x-XEKWYICXBSIUT605a074944ad90582", "exchange_order_id": "35192313", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:30:24,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a074944ad90582. +2023-09-18 11:30:24,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a077db18e40582 for 80.00000000 SEI-USDT. +2023-09-18 11:30:24,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036624.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a077db18e40582", "creation_timestamp": 1695036624.0, "exchange_order_id": "35192473", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:31:19,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a077db18e40582. [clock=2023-09-18 11:31:19+00:00] +2023-09-18 11:31:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241810921134200259634863589 amount: 80. +2023-09-18 11:31:19,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:31:19,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036679.0, "order_id": "x-XEKWYICXBSIUT605a077db18e40582", "exchange_order_id": "35192473", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:31:19,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a077db18e40582. +2023-09-18 11:31:19,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a07b22520d0582 for 80.00000000 SEI-USDT. +2023-09-18 11:31:19,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036679.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a07b22520d0582", "creation_timestamp": 1695036679.0, "exchange_order_id": "35192895", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:32:14,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a07b22520d0582. [clock=2023-09-18 11:32:14+00:00] +2023-09-18 11:32:14,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240884804233696443701250207 amount: 80. +2023-09-18 11:32:14,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:32:14,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036734.0, "order_id": "x-XEKWYICXBSIUT605a07b22520d0582", "exchange_order_id": "35192895", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:32:14,081 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a07b22520d0582. +2023-09-18 11:32:14,082 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a07e698cad0582 for 80.00000000 SEI-USDT. +2023-09-18 11:32:14,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036734.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a07e698cad0582", "creation_timestamp": 1695036734.0, "exchange_order_id": "35193453", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:33:09,367 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a07e698cad0582. [clock=2023-09-18 11:33:09+00:00] +2023-09-18 11:33:09,487 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239781288948378904543075023 amount: 80. +2023-09-18 11:33:09,488 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:33:10,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036789.0, "order_id": "x-XEKWYICXBSIUT605a07e698cad0582", "exchange_order_id": "35193453", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:33:10,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a07e698cad0582. +2023-09-18 11:33:10,810 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a081b7fbf90582 for 80.00000000 SEI-USDT. +2023-09-18 11:33:10,845 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036790.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a081b7fbf90582", "creation_timestamp": 1695036789.0, "exchange_order_id": "35193890", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:34:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a081b7fbf90582. [clock=2023-09-18 11:34:04+00:00] +2023-09-18 11:34:04,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241954685336240174249829383 amount: 80. +2023-09-18 11:34:04,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:34:04,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036844.0, "order_id": "x-XEKWYICXBSIUT605a081b7fbf90582", "exchange_order_id": "35193890", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:34:04,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a081b7fbf90582. +2023-09-18 11:34:04,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a084f89b5f0582 for 80.00000000 SEI-USDT. +2023-09-18 11:34:04,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036844.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a084f89b5f0582", "creation_timestamp": 1695036844.0, "exchange_order_id": "35194212", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:34:59,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a084f89b5f0582. [clock=2023-09-18 11:34:59+00:00] +2023-09-18 11:34:59,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239327524955912661989979697 amount: 80. +2023-09-18 11:34:59,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:34:59,534 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036899.0, "order_id": "x-XEKWYICXBSIUT605a084f89b5f0582", "exchange_order_id": "35194212", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:34:59,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a084f89b5f0582. +2023-09-18 11:35:00,069 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0884007550582 for 80.00000000 SEI-USDT. +2023-09-18 11:35:00,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036900.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a0884007550582", "creation_timestamp": 1695036899.0, "exchange_order_id": "35194876", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:35:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0884007550582. [clock=2023-09-18 11:35:54+00:00] +2023-09-18 11:35:54,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240633736154461060079924767 amount: 80. +2023-09-18 11:35:54,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:35:54,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036954.0, "order_id": "x-XEKWYICXBSIUT605a0884007550582", "exchange_order_id": "35194876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:35:54,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0884007550582. +2023-09-18 11:35:54,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a08b86915a0582 for 80.00000000 SEI-USDT. +2023-09-18 11:35:54,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695036954.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a08b86915a0582", "creation_timestamp": 1695036954.0, "exchange_order_id": "35195172", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:36:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a08b86915a0582. [clock=2023-09-18 11:36:49+00:00] +2023-09-18 11:36:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240784250612217094085527694 amount: 80. +2023-09-18 11:36:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:36:49,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037009.0, "order_id": "x-XEKWYICXBSIUT605a08b86915a0582", "exchange_order_id": "35195172", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:36:49,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a08b86915a0582. +2023-09-18 11:36:49,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a08ecdbce00582 for 80.00000000 SEI-USDT. +2023-09-18 11:36:49,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037009.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a08ecdbce00582", "creation_timestamp": 1695037009.0, "exchange_order_id": "35195429", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:37:44,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a08ecdbce00582. [clock=2023-09-18 11:37:44+00:00] +2023-09-18 11:37:44,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240908019465224886290591268 amount: 80. +2023-09-18 11:37:44,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:37:44,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037064.0, "order_id": "x-XEKWYICXBSIUT605a08ecdbce00582", "exchange_order_id": "35195429", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:37:44,089 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a08ecdbce00582. +2023-09-18 11:37:44,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a09214f7300582 for 80.00000000 SEI-USDT. +2023-09-18 11:37:44,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037064.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a09214f7300582", "creation_timestamp": 1695037064.0, "exchange_order_id": "35195726", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:38:21,757 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a09214f7300582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:38:21,758 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:38:21+00:00] +2023-09-18 11:38:21,848 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037101.0, "order_id": "x-XEKWYICXBSIUT605a09214f7300582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4956777", "exchange_order_id": "35195726", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:38:21,893 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037101.0, "order_id": "x-XEKWYICXBSIUT605a09214f7300582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35195726", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:38:21,893 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a09214f7300582 completely filled. +2023-09-18 11:38:39,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237475470331665287741311109 amount: 80. +2023-09-18 11:38:39,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241211140964318422504527945 amount: 80. +2023-09-18 11:38:39,547 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0955d7cf80582 for 80.00000000 SEI-USDT. +2023-09-18 11:38:39,589 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037119.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a0955d7cf80582", "creation_timestamp": 1695037119.0, "exchange_order_id": "35196221", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:38:39,830 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0955d7fb10582 for 80.00000000 SEI-USDT. +2023-09-18 11:38:39,888 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037119.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a0955d7fb10582", "creation_timestamp": 1695037119.0, "exchange_order_id": "35196223", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:39:24,196 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a0955d7cf80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:39:24,198 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:39:24+00:00] +2023-09-18 11:39:24,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037164.0, "order_id": "x-XEKWYICXBSIUT605a0955d7cf80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4956846", "exchange_order_id": "35196221", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:39:24,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037164.0, "order_id": "x-XEKWYICXBSIUT605a0955d7cf80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35196221", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:39:24,355 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a0955d7cf80582 completely filled. +2023-09-18 11:39:34,582 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0955d7fb10582. [clock=2023-09-18 11:39:34+00:00] +2023-09-18 11:39:34,626 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233232429324683683014369302 amount: 80. +2023-09-18 11:39:34,627 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 11:39:34,904 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037174.0, "order_id": "x-XEKWYICXSSIUT605a0955d7fb10582", "exchange_order_id": "35196223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:39:34,905 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0955d7fb10582. +2023-09-18 11:39:35,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a098acc0a30582 for 80.00000000 SEI-USDT. +2023-09-18 11:39:35,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037175.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a098acc0a30582", "creation_timestamp": 1695037174.0, "exchange_order_id": "35196822", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:39:35,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a098acbdcd0582 for 80.00000000 SEI-USDT. +2023-09-18 11:39:35,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037175.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a098acbdcd0582", "creation_timestamp": 1695037174.0, "exchange_order_id": "35196821", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:40:12,506 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a098acc0a30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:40:12,508 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:40:12+00:00] +2023-09-18 11:40:12,543 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037212.0, "order_id": "x-XEKWYICXSSIUT605a098acc0a30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4956883", "exchange_order_id": "35196822", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:40:12,564 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037212.0, "order_id": "x-XEKWYICXSSIUT605a098acc0a30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35196822", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:40:12,565 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a098acc0a30582 completely filled. +2023-09-18 11:40:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a098acbdcd0582. [clock=2023-09-18 11:40:29+00:00] +2023-09-18 11:40:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236286721807356875293083030 amount: 80. +2023-09-18 11:40:29,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240549164942516092783109436 amount: 80. +2023-09-18 11:40:29,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037229.0, "order_id": "x-XEKWYICXBSIUT605a098acbdcd0582", "exchange_order_id": "35196821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:40:29,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a098acbdcd0582. +2023-09-18 11:40:29,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a09beab0e70582 for 80.00000000 SEI-USDT. +2023-09-18 11:40:29,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037229.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a09beab0e70582", "creation_timestamp": 1695037229.0, "exchange_order_id": "35197390", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:40:29,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a09beab5080582 for 80.00000000 SEI-USDT. +2023-09-18 11:40:29,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037229.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a09beab5080582", "creation_timestamp": 1695037229.0, "exchange_order_id": "35197391", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:40:44,595 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a09beab5080582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:40:44,596 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:40:44+00:00] +2023-09-18 11:40:44,626 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037244.0, "order_id": "x-XEKWYICXSSIUT605a09beab5080582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992000"}]}, "exchange_trade_id": "4956925", "exchange_order_id": "35197391", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:40:44,639 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037244.0, "order_id": "x-XEKWYICXSSIUT605a09beab5080582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35197391", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:40:44,640 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a09beab5080582 completely filled. +2023-09-18 11:41:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a09beab0e70582. [clock=2023-09-18 11:41:24+00:00] +2023-09-18 11:41:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238739386205425747901765181 amount: 80. +2023-09-18 11:41:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:41:24,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037284.0, "order_id": "x-XEKWYICXBSIUT605a09beab0e70582", "exchange_order_id": "35197390", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:41:24,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a09beab0e70582. +2023-09-18 11:41:24,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a09f31e2a90582 for 80.00000000 SEI-USDT. +2023-09-18 11:41:24,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037284.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a09f31e2a90582", "creation_timestamp": 1695037284.0, "exchange_order_id": "35197638", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:42:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a09f31e2a90582. [clock=2023-09-18 11:42:19+00:00] +2023-09-18 11:42:19,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237665251738423594162305166 amount: 80. +2023-09-18 11:42:19,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:42:19,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037339.0, "order_id": "x-XEKWYICXBSIUT605a09f31e2a90582", "exchange_order_id": "35197638", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:42:19,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a09f31e2a90582. +2023-09-18 11:42:19,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0a27942130582 for 80.00000000 SEI-USDT. +2023-09-18 11:42:19,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037339.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a0a27942130582", "creation_timestamp": 1695037339.0, "exchange_order_id": "35197866", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:43:14,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0a27942130582. [clock=2023-09-18 11:43:14+00:00] +2023-09-18 11:43:14,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238800384113058043395951307 amount: 80. +2023-09-18 11:43:14,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:43:14,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037394.0, "order_id": "x-XEKWYICXBSIUT605a0a27942130582", "exchange_order_id": "35197866", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:43:14,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0a27942130582. +2023-09-18 11:43:14,731 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0a5c1c5890582 for 80.00000000 SEI-USDT. +2023-09-18 11:43:14,766 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037394.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a0a5c1c5890582", "creation_timestamp": 1695037394.0, "exchange_order_id": "35198138", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:44:09,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0a5c1c5890582. [clock=2023-09-18 11:44:09+00:00] +2023-09-18 11:44:09,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236645523007015222211492467 amount: 80. +2023-09-18 11:44:09,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:44:09,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037449.0, "order_id": "x-XEKWYICXBSIUT605a0a5c1c5890582", "exchange_order_id": "35198138", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:44:09,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0a5c1c5890582. +2023-09-18 11:44:09,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0a9081ed60582 for 80.00000000 SEI-USDT. +2023-09-18 11:44:09,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037449.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a0a9081ed60582", "creation_timestamp": 1695037449.0, "exchange_order_id": "35198391", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:45:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0a9081ed60582. [clock=2023-09-18 11:45:04+00:00] +2023-09-18 11:45:04,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237870555692593917188328965 amount: 80. +2023-09-18 11:45:04,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:45:04,461 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037504.0, "order_id": "x-XEKWYICXBSIUT605a0a9081ed60582", "exchange_order_id": "35198391", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:45:04,461 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0a9081ed60582. +2023-09-18 11:45:04,774 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0ac4f3bc90582 for 80.00000000 SEI-USDT. +2023-09-18 11:45:04,813 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037504.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a0ac4f3bc90582", "creation_timestamp": 1695037504.0, "exchange_order_id": "35199125", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:45:59,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0ac4f3bc90582. [clock=2023-09-18 11:45:59+00:00] +2023-09-18 11:45:59,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237870555692593917188328965 amount: 80. +2023-09-18 11:45:59,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:45:59,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037559.0, "order_id": "x-XEKWYICXBSIUT605a0ac4f3bc90582", "exchange_order_id": "35199125", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:45:59,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0ac4f3bc90582. +2023-09-18 11:45:59,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0af961ce10582 for 80.00000000 SEI-USDT. +2023-09-18 11:45:59,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037559.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a0af961ce10582", "creation_timestamp": 1695037559.0, "exchange_order_id": "35199213", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:46:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0af961ce10582. [clock=2023-09-18 11:46:54+00:00] +2023-09-18 11:46:54,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239102651693107701449139198 amount: 80. +2023-09-18 11:46:54,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:46:54,044 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037614.0, "order_id": "x-XEKWYICXBSIUT605a0af961ce10582", "exchange_order_id": "35199213", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:46:54,044 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0af961ce10582. +2023-09-18 11:46:54,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0b2dd4c910582 for 80.00000000 SEI-USDT. +2023-09-18 11:46:54,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037614.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a0b2dd4c910582", "creation_timestamp": 1695037614.0, "exchange_order_id": "35199479", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:47:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0b2dd4c910582. [clock=2023-09-18 11:47:49+00:00] +2023-09-18 11:47:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239195346533085211294120003 amount: 80. +2023-09-18 11:47:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:47:49,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037669.0, "order_id": "x-XEKWYICXBSIUT605a0b2dd4c910582", "exchange_order_id": "35199479", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:47:49,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0b2dd4c910582. +2023-09-18 11:47:49,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0b6248ca90582 for 80.00000000 SEI-USDT. +2023-09-18 11:47:49,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037669.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a0b6248ca90582", "creation_timestamp": 1695037669.0, "exchange_order_id": "35199610", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:47:52,705 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 11:48:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0b6248ca90582. [clock=2023-09-18 11:48:44+00:00] +2023-09-18 11:48:44,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241476741490153541915338648 amount: 80. +2023-09-18 11:48:44,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:48:44,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037724.0, "order_id": "x-XEKWYICXBSIUT605a0b6248ca90582", "exchange_order_id": "35199610", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:48:44,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0b6248ca90582. +2023-09-18 11:48:44,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0b96c5b0a0582 for 80.00000000 SEI-USDT. +2023-09-18 11:48:44,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037724.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a0b96c5b0a0582", "creation_timestamp": 1695037724.0, "exchange_order_id": "35199977", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:49:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0b96c5b0a0582. [clock=2023-09-18 11:49:39+00:00] +2023-09-18 11:49:39,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241539474077826789815960163 amount: 80. +2023-09-18 11:49:39,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:49:39,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037779.0, "order_id": "x-XEKWYICXBSIUT605a0b96c5b0a0582", "exchange_order_id": "35199977", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:49:39,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0b96c5b0a0582. +2023-09-18 11:49:39,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0bcb31ca60582 for 80.00000000 SEI-USDT. +2023-09-18 11:49:39,505 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037779.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a0bcb31ca60582", "creation_timestamp": 1695037779.0, "exchange_order_id": "35200251", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:50:07,592 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a0bcb31ca60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:50:07,594 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:50:07+00:00] +2023-09-18 11:50:07,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037807.0, "order_id": "x-XEKWYICXBSIUT605a0bcb31ca60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4957253", "exchange_order_id": "35200251", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:50:07,636 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037807.0, "order_id": "x-XEKWYICXBSIUT605a0bcb31ca60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35200251", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:50:07,636 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a0bcb31ca60582 completely filled. +2023-09-18 11:50:34,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240259655749519135909456310 amount: 80. +2023-09-18 11:50:34,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243480470838681621967136914 amount: 80. +2023-09-18 11:50:34,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0bffa34d20582 for 80.00000000 SEI-USDT. +2023-09-18 11:50:34,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037834.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a0bffa34d20582", "creation_timestamp": 1695037834.0, "exchange_order_id": "35201112", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:50:34,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0bffa4fcd0582 for 80.00000000 SEI-USDT. +2023-09-18 11:50:34,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037834.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a0bffa4fcd0582", "creation_timestamp": 1695037834.0, "exchange_order_id": "35201113", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:51:14,626 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a0bffa4fcd0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 11:51:14,627 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 11:51:14+00:00] +2023-09-18 11:51:14,650 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037874.0, "order_id": "x-XEKWYICXSSIUT605a0bffa4fcd0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "4957348", "exchange_order_id": "35201113", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 11:51:14,664 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037874.0, "order_id": "x-XEKWYICXSSIUT605a0bffa4fcd0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35201113", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 11:51:14,665 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a0bffa4fcd0582 completely filled. +2023-09-18 11:51:29,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0bffa34d20582. [clock=2023-09-18 11:51:29+00:00] +2023-09-18 11:51:29,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241449765219396213838475973 amount: 80. +2023-09-18 11:51:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:51:29,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037889.0, "order_id": "x-XEKWYICXBSIUT605a0bffa34d20582", "exchange_order_id": "35201112", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:51:29,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0bffa34d20582. +2023-09-18 11:51:29,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0c34178d10582 for 80.00000000 SEI-USDT. +2023-09-18 11:51:29,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037889.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a0c34178d10582", "creation_timestamp": 1695037889.0, "exchange_order_id": "35201455", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:52:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0c34178d10582. [clock=2023-09-18 11:52:24+00:00] +2023-09-18 11:52:24,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242622464455605080674224245 amount: 80. +2023-09-18 11:52:24,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:52:24,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037944.0, "order_id": "x-XEKWYICXBSIUT605a0c34178d10582", "exchange_order_id": "35201455", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:52:24,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0c34178d10582. +2023-09-18 11:52:24,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0c688ba490582 for 80.00000000 SEI-USDT. +2023-09-18 11:52:24,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037944.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a0c688ba490582", "creation_timestamp": 1695037944.0, "exchange_order_id": "35201557", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:53:19,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0c688ba490582. [clock=2023-09-18 11:53:19+00:00] +2023-09-18 11:53:19,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242794665895583223198161284 amount: 80. +2023-09-18 11:53:19,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:53:19,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037999.0, "order_id": "x-XEKWYICXBSIUT605a0c688ba490582", "exchange_order_id": "35201557", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:53:19,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0c688ba490582. +2023-09-18 11:53:19,999 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0c9d0a2540582 for 80.00000000 SEI-USDT. +2023-09-18 11:53:20,047 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695037999.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a0c9d0a2540582", "creation_timestamp": 1695037999.0, "exchange_order_id": "35201778", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:54:14,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0c9d0a2540582. [clock=2023-09-18 11:54:14+00:00] +2023-09-18 11:54:14,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243917334788473130068090535 amount: 80. +2023-09-18 11:54:14,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:54:14,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038054.0, "order_id": "x-XEKWYICXBSIUT605a0c9d0a2540582", "exchange_order_id": "35201778", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:54:14,530 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0c9d0a2540582. +2023-09-18 11:54:14,714 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0cd17ac640582 for 80.00000000 SEI-USDT. +2023-09-18 11:54:14,770 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038054.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0cd17ac640582", "creation_timestamp": 1695038054.0, "exchange_order_id": "35202188", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:55:09,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0cd17ac640582. [clock=2023-09-18 11:55:09+00:00] +2023-09-18 11:55:09,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243820414700410079136135376 amount: 80. +2023-09-18 11:55:09,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:55:09,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038109.0, "order_id": "x-XEKWYICXBSIUT605a0cd17ac640582", "exchange_order_id": "35202188", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:55:09,091 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0cd17ac640582. +2023-09-18 11:55:09,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0d05e6b1d0582 for 80.00000000 SEI-USDT. +2023-09-18 11:55:09,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038109.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0d05e6b1d0582", "creation_timestamp": 1695038109.0, "exchange_order_id": "35202402", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:56:04,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0d05e6b1d0582. [clock=2023-09-18 11:56:04+00:00] +2023-09-18 11:56:04,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243868829858241802955564647 amount: 80. +2023-09-18 11:56:04,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:56:04,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0d3a73c860582 for 80.00000000 SEI-USDT. +2023-09-18 11:56:04,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038164.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0d3a73c860582", "creation_timestamp": 1695038164.0, "exchange_order_id": "35202648", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:56:04,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038164.0, "order_id": "x-XEKWYICXBSIUT605a0d05e6b1d0582", "exchange_order_id": "35202402", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:56:04,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0d05e6b1d0582. +2023-09-18 11:56:59,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0d3a73c860582. [clock=2023-09-18 11:56:59+00:00] +2023-09-18 11:56:59,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243868829858241802955564647 amount: 80. +2023-09-18 11:56:59,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:56:59,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038219.0, "order_id": "x-XEKWYICXBSIUT605a0d3a73c860582", "exchange_order_id": "35202648", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:56:59,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0d3a73c860582. +2023-09-18 11:56:59,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0d6ed6b630582 for 80.00000000 SEI-USDT. +2023-09-18 11:56:59,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038219.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0d6ed6b630582", "creation_timestamp": 1695038219.0, "exchange_order_id": "35202720", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:57:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0d6ed6b630582. [clock=2023-09-18 11:57:54+00:00] +2023-09-18 11:57:54,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244974235176623837080747622 amount: 80. +2023-09-18 11:57:54,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:57:54,060 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038274.0, "order_id": "x-XEKWYICXBSIUT605a0d6ed6b630582", "exchange_order_id": "35202720", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:57:54,060 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0d6ed6b630582. +2023-09-18 11:57:54,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0da3463300582 for 80.00000000 SEI-USDT. +2023-09-18 11:57:54,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038274.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a0da3463300582", "creation_timestamp": 1695038274.0, "exchange_order_id": "35202907", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:58:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0da3463300582. [clock=2023-09-18 11:58:49+00:00] +2023-09-18 11:58:49,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243900997490574701821942848 amount: 80. +2023-09-18 11:58:49,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:58:49,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038329.0, "order_id": "x-XEKWYICXBSIUT605a0da3463300582", "exchange_order_id": "35202907", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:58:49,585 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0da3463300582. +2023-09-18 11:58:49,921 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0dd7c57440582 for 80.00000000 SEI-USDT. +2023-09-18 11:58:49,949 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038329.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0dd7c57440582", "creation_timestamp": 1695038329.0, "exchange_order_id": "35202991", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 11:59:44,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0dd7c57440582. [clock=2023-09-18 11:59:44+00:00] +2023-09-18 11:59:44,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243916780458120526026191493 amount: 80. +2023-09-18 11:59:44,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 11:59:44,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038384.0, "order_id": "x-XEKWYICXBSIUT605a0dd7c57440582", "exchange_order_id": "35202991", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 11:59:44,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0dd7c57440582. +2023-09-18 11:59:44,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0e0c4eb840582 for 80.00000000 SEI-USDT. +2023-09-18 11:59:44,740 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038384.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0e0c4eb840582", "creation_timestamp": 1695038384.0, "exchange_order_id": "35203164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:00:39,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0e0c4eb840582. [clock=2023-09-18 12:00:39+00:00] +2023-09-18 12:00:39,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245982257753253943733322939 amount: 80. +2023-09-18 12:00:39,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:00:39,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038439.0, "order_id": "x-XEKWYICXBSIUT605a0e0c4eb840582", "exchange_order_id": "35203164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:00:39,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0e0c4eb840582. +2023-09-18 12:00:39,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0e409d3ca0582 for 80.00000000 SEI-USDT. +2023-09-18 12:00:39,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038439.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a0e409d3ca0582", "creation_timestamp": 1695038439.0, "exchange_order_id": "35203737", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:01:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0e409d3ca0582. [clock=2023-09-18 12:01:34+00:00] +2023-09-18 12:01:34,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245921800232811225454272141 amount: 80. +2023-09-18 12:01:34,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:01:34,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038494.0, "order_id": "x-XEKWYICXBSIUT605a0e409d3ca0582", "exchange_order_id": "35203737", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:01:34,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0e409d3ca0582. +2023-09-18 12:01:34,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0e75110f20582 for 80.00000000 SEI-USDT. +2023-09-18 12:01:34,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038494.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a0e75110f20582", "creation_timestamp": 1695038494.0, "exchange_order_id": "35204324", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:02:29,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0e75110f20582. [clock=2023-09-18 12:02:29+00:00] +2023-09-18 12:02:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245825166429541190738788719 amount: 80. +2023-09-18 12:02:29,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:02:29,048 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038549.0, "order_id": "x-XEKWYICXBSIUT605a0e75110f20582", "exchange_order_id": "35204324", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:02:29,048 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0e75110f20582. +2023-09-18 12:02:29,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0ea984a350582 for 80.00000000 SEI-USDT. +2023-09-18 12:02:29,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038549.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a0ea984a350582", "creation_timestamp": 1695038549.0, "exchange_order_id": "35205013", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:03:24,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0ea984a350582. [clock=2023-09-18 12:03:24+00:00] +2023-09-18 12:03:24,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 12:03:24,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:03:24,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038604.0, "order_id": "x-XEKWYICXBSIUT605a0ea984a350582", "exchange_order_id": "35205013", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:03:24,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0ea984a350582. +2023-09-18 12:03:24,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0ede0272b0582 for 80.00000000 SEI-USDT. +2023-09-18 12:03:24,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038604.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a0ede0272b0582", "creation_timestamp": 1695038604.0, "exchange_order_id": "35205220", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:03:34,332 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a0ede0272b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:03:34,334 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:03:34+00:00] +2023-09-18 12:03:34,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038614.0, "order_id": "x-XEKWYICXBSIUT605a0ede0272b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4957605", "exchange_order_id": "35205220", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:03:34,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038614.0, "order_id": "x-XEKWYICXBSIUT605a0ede0272b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35205220", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:03:34,408 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a0ede0272b0582 completely filled. +2023-09-18 12:04:19,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245890201769698557253501148 amount: 80. +2023-09-18 12:04:19,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248522490446410329124940471 amount: 80. +2023-09-18 12:04:19,556 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0f127c6b20582 for 80.00000000 SEI-USDT. +2023-09-18 12:04:19,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038659.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a0f127c6b20582", "creation_timestamp": 1695038659.0, "exchange_order_id": "35205798", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:04:19,950 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0f127c8850582 for 80.00000000 SEI-USDT. +2023-09-18 12:04:20,010 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038659.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a0f127c8850582", "creation_timestamp": 1695038659.0, "exchange_order_id": "35205801", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:04:59,531 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a0f127c8850582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:04:59,532 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:04:59+00:00] +2023-09-18 12:04:59,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038699.0, "order_id": "x-XEKWYICXSSIUT605a0f127c8850582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4957656", "exchange_order_id": "35205801", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:04:59,650 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038699.0, "order_id": "x-XEKWYICXSSIUT605a0f127c8850582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35205801", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:04:59,650 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a0f127c8850582 completely filled. +2023-09-18 12:05:14,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0f127c6b20582. [clock=2023-09-18 12:05:14+00:00] +2023-09-18 12:05:14,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-18 12:05:14,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:05:14,074 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038714.0, "order_id": "x-XEKWYICXBSIUT605a0f127c6b20582", "exchange_order_id": "35205798", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:05:14,074 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0f127c6b20582. +2023-09-18 12:05:14,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0f46e77910582 for 80.00000000 SEI-USDT. +2023-09-18 12:05:14,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038714.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a0f46e77910582", "creation_timestamp": 1695038714.0, "exchange_order_id": "35205994", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:05:38,207 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a0f46e77910582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:05:38,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:05:38+00:00] +2023-09-18 12:05:38,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038738.0, "order_id": "x-XEKWYICXBSIUT605a0f46e77910582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4957680", "exchange_order_id": "35205994", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:05:38,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038738.0, "order_id": "x-XEKWYICXBSIUT605a0f46e77910582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35205994", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:05:38,243 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a0f46e77910582 completely filled. +2023-09-18 12:06:09,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243512702224773109729407316 amount: 80. +2023-09-18 12:06:09,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246433902167028947388998948 amount: 80. +2023-09-18 12:06:09,071 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0f7b533030582 for 80.00000000 SEI-USDT. +2023-09-18 12:06:09,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038769.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605a0f7b533030582", "creation_timestamp": 1695038769.0, "exchange_order_id": "35206470", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:06:09,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0f7b52dd10582 for 80.00000000 SEI-USDT. +2023-09-18 12:06:09,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038769.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a0f7b52dd10582", "creation_timestamp": 1695038769.0, "exchange_order_id": "35206471", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:07:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0f7b52dd10582. [clock=2023-09-18 12:07:04+00:00] +2023-09-18 12:07:04,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0f7b533030582. [clock=2023-09-18 12:07:04+00:00] +2023-09-18 12:07:04,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241282564170207293190217115 amount: 80. +2023-09-18 12:07:04,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:07:04,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038824.0, "order_id": "x-XEKWYICXBSIUT605a0f7b52dd10582", "exchange_order_id": "35206471", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:07:04,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0f7b52dd10582. +2023-09-18 12:07:04,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038824.0, "order_id": "x-XEKWYICXSSIUT605a0f7b533030582", "exchange_order_id": "35206470", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:07:04,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0f7b533030582. +2023-09-18 12:07:04,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0fafc75340582 for 80.00000000 SEI-USDT. +2023-09-18 12:07:04,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038824.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a0fafc75340582", "creation_timestamp": 1695038824.0, "exchange_order_id": "35206836", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:07:59,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0fafc75340582. [clock=2023-09-18 12:07:59+00:00] +2023-09-18 12:07:59,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240048166472599613676691298 amount: 80. +2023-09-18 12:07:59,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243290908199589500708997538 amount: 80. +2023-09-18 12:07:59,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038879.0, "order_id": "x-XEKWYICXBSIUT605a0fafc75340582", "exchange_order_id": "35206836", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:07:59,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0fafc75340582. +2023-09-18 12:07:59,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a0fe43ee820582 for 80.00000000 SEI-USDT. +2023-09-18 12:07:59,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038879.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a0fe43ee820582", "creation_timestamp": 1695038879.0, "exchange_order_id": "35207228", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:07:59,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a0fe43f1990582 for 80.00000000 SEI-USDT. +2023-09-18 12:07:59,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038879.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a0fe43f1990582", "creation_timestamp": 1695038879.0, "exchange_order_id": "35207229", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:08:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a0fe43ee820582. [clock=2023-09-18 12:08:54+00:00] +2023-09-18 12:08:54,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a0fe43f1990582. [clock=2023-09-18 12:08:54+00:00] +2023-09-18 12:08:54,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240141688908631696975263340 amount: 80. +2023-09-18 12:08:54,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:08:54,738 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038934.0, "order_id": "x-XEKWYICXBSIUT605a0fe43ee820582", "exchange_order_id": "35207228", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:08:54,739 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a0fe43ee820582. +2023-09-18 12:08:54,899 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038934.0, "order_id": "x-XEKWYICXSSIUT605a0fe43f1990582", "exchange_order_id": "35207229", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:08:54,899 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a0fe43f1990582. +2023-09-18 12:08:55,045 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1018b507c0582 for 80.00000000 SEI-USDT. +2023-09-18 12:08:55,067 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038934.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a1018b507c0582", "creation_timestamp": 1695038934.0, "exchange_order_id": "35207528", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:09:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1018b507c0582. [clock=2023-09-18 12:09:49+00:00] +2023-09-18 12:09:49,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241358144226872758692190277 amount: 80. +2023-09-18 12:09:49,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244278602324588589449606193 amount: 80. +2023-09-18 12:09:49,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038989.0, "order_id": "x-XEKWYICXBSIUT605a1018b507c0582", "exchange_order_id": "35207528", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:09:49,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1018b507c0582. +2023-09-18 12:09:49,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a104d2565e0582 for 80.00000000 SEI-USDT. +2023-09-18 12:09:49,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038989.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a104d2565e0582", "creation_timestamp": 1695038989.0, "exchange_order_id": "35207859", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:09:49,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a104d289810582 for 80.00000000 SEI-USDT. +2023-09-18 12:09:49,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695038989.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a104d289810582", "creation_timestamp": 1695038989.0, "exchange_order_id": "35207860", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:10:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a104d2565e0582. [clock=2023-09-18 12:10:44+00:00] +2023-09-18 12:10:44,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a104d289810582. [clock=2023-09-18 12:10:44+00:00] +2023-09-18 12:10:44,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239106593576435347873903793 amount: 80. +2023-09-18 12:10:44,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:10:44,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039044.0, "order_id": "x-XEKWYICXBSIUT605a104d2565e0582", "exchange_order_id": "35207859", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:10:44,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a104d2565e0582. +2023-09-18 12:10:44,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039044.0, "order_id": "x-XEKWYICXSSIUT605a104d289810582", "exchange_order_id": "35207860", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:10:44,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a104d289810582. +2023-09-18 12:10:44,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1081960e70582 for 80.00000000 SEI-USDT. +2023-09-18 12:10:44,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039044.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1081960e70582", "creation_timestamp": 1695039044.0, "exchange_order_id": "35208354", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:11:39,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1081960e70582. [clock=2023-09-18 12:11:39+00:00] +2023-09-18 12:11:39,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237949232387444081861312628 amount: 80. +2023-09-18 12:11:39,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241121846090500730765683041 amount: 80. +2023-09-18 12:11:39,091 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039099.0, "order_id": "x-XEKWYICXBSIUT605a1081960e70582", "exchange_order_id": "35208354", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:11:39,091 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1081960e70582. +2023-09-18 12:11:39,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a10b609e7d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:11:39,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039099.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a10b609e7d0582", "creation_timestamp": 1695039099.0, "exchange_order_id": "35208948", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:11:39,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a10b609c010582 for 80.00000000 SEI-USDT. +2023-09-18 12:11:39,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039099.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a10b609c010582", "creation_timestamp": 1695039099.0, "exchange_order_id": "35208949", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:12:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a10b609c010582. [clock=2023-09-18 12:12:34+00:00] +2023-09-18 12:12:34,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a10b609e7d0582. [clock=2023-09-18 12:12:34+00:00] +2023-09-18 12:12:34,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236832201952124106981408549 amount: 80. +2023-09-18 12:12:34,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:12:34,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039154.0, "order_id": "x-XEKWYICXBSIUT605a10b609c010582", "exchange_order_id": "35208949", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:12:34,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a10b609c010582. +2023-09-18 12:12:34,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a10ea7db930582 for 80.00000000 SEI-USDT. +2023-09-18 12:12:34,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039154.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a10ea7db930582", "creation_timestamp": 1695039154.0, "exchange_order_id": "35209321", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:12:34,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039154.0, "order_id": "x-XEKWYICXSSIUT605a10b609e7d0582", "exchange_order_id": "35208948", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:12:34,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a10b609e7d0582. +2023-09-18 12:13:29,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a10ea7db930582. [clock=2023-09-18 12:13:29+00:00] +2023-09-18 12:13:29,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237926604494503906627433920 amount: 80. +2023-09-18 12:13:29,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241234333795423349236085430 amount: 80. +2023-09-18 12:13:29,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039209.0, "order_id": "x-XEKWYICXBSIUT605a10ea7db930582", "exchange_order_id": "35209321", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:13:29,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a10ea7db930582. +2023-09-18 12:13:29,790 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a111ef86990582 for 80.00000000 SEI-USDT. +2023-09-18 12:13:29,838 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039209.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a111ef86990582", "creation_timestamp": 1695039209.0, "exchange_order_id": "35209709", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:13:30,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a111efcaa30582 for 80.00000000 SEI-USDT. +2023-09-18 12:13:30,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039209.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a111efcaa30582", "creation_timestamp": 1695039209.0, "exchange_order_id": "35209710", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:14:24,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a111ef86990582. [clock=2023-09-18 12:14:24+00:00] +2023-09-18 12:14:24,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a111efcaa30582. [clock=2023-09-18 12:14:24+00:00] +2023-09-18 12:14:24,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239031590571633305880485599 amount: 80. +2023-09-18 12:14:24,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:14:24,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039264.0, "order_id": "x-XEKWYICXBSIUT605a111ef86990582", "exchange_order_id": "35209709", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:14:24,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a111ef86990582. +2023-09-18 12:14:24,946 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039264.0, "order_id": "x-XEKWYICXSSIUT605a111efcaa30582", "exchange_order_id": "35209710", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:14:24,946 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a111efcaa30582. +2023-09-18 12:14:25,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1153967210582 for 80.00000000 SEI-USDT. +2023-09-18 12:14:25,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039264.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1153967210582", "creation_timestamp": 1695039264.0, "exchange_order_id": "35209830", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:15:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1153967210582. [clock=2023-09-18 12:15:19+00:00] +2023-09-18 12:15:19,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239003362648042967538166136 amount: 80. +2023-09-18 12:15:19,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242414806352553487576508330 amount: 80. +2023-09-18 12:15:19,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039319.0, "order_id": "x-XEKWYICXBSIUT605a1153967210582", "exchange_order_id": "35209830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:15:19,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1153967210582. +2023-09-18 12:15:19,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1187d9c7c0582 for 80.00000000 SEI-USDT. +2023-09-18 12:15:19,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039319.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1187d9c7c0582", "creation_timestamp": 1695039319.0, "exchange_order_id": "35210064", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:15:19,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1187d9feb0582 for 80.00000000 SEI-USDT. +2023-09-18 12:15:19,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039319.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a1187d9feb0582", "creation_timestamp": 1695039319.0, "exchange_order_id": "35210065", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:16:14,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1187d9c7c0582. [clock=2023-09-18 12:16:14+00:00] +2023-09-18 12:16:14,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1187d9feb0582. [clock=2023-09-18 12:16:14+00:00] +2023-09-18 12:16:14,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239124920255042218449741871 amount: 80. +2023-09-18 12:16:14,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:16:14,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039374.0, "order_id": "x-XEKWYICXSSIUT605a1187d9feb0582", "exchange_order_id": "35210065", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:16:14,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1187d9feb0582. +2023-09-18 12:16:14,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039374.0, "order_id": "x-XEKWYICXBSIUT605a1187d9c7c0582", "exchange_order_id": "35210064", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:16:14,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1187d9c7c0582. +2023-09-18 12:16:14,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a11bc5282b0582 for 80.00000000 SEI-USDT. +2023-09-18 12:16:14,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039374.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a11bc5282b0582", "creation_timestamp": 1695039374.0, "exchange_order_id": "35210420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:17:09,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a11bc5282b0582. [clock=2023-09-18 12:17:09+00:00] +2023-09-18 12:17:09,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238142772581720342368360097 amount: 80. +2023-09-18 12:17:09,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241164583637623415873032729 amount: 80. +2023-09-18 12:17:09,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039429.0, "order_id": "x-XEKWYICXBSIUT605a11bc5282b0582", "exchange_order_id": "35210420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:17:09,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a11bc5282b0582. +2023-09-18 12:17:09,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a11f0c0e710582 for 80.00000000 SEI-USDT. +2023-09-18 12:17:09,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039429.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a11f0c0e710582", "creation_timestamp": 1695039429.0, "exchange_order_id": "35210570", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:17:09,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a11f0c0b5a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:17:09,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039429.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a11f0c0b5a0582", "creation_timestamp": 1695039429.0, "exchange_order_id": "35210571", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:17:52,721 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 12:18:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a11f0c0b5a0582. [clock=2023-09-18 12:18:04+00:00] +2023-09-18 12:18:04,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a11f0c0e710582. [clock=2023-09-18 12:18:04+00:00] +2023-09-18 12:18:04,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238374879841655151644861869 amount: 80. +2023-09-18 12:18:04,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:18:04,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039484.0, "order_id": "x-XEKWYICXBSIUT605a11f0c0b5a0582", "exchange_order_id": "35210571", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:18:04,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a11f0c0b5a0582. +2023-09-18 12:18:04,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039484.0, "order_id": "x-XEKWYICXSSIUT605a11f0c0e710582", "exchange_order_id": "35210570", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:18:04,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a11f0c0e710582. +2023-09-18 12:18:04,610 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a12253d12d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:18:04,638 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039484.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a12253d12d0582", "creation_timestamp": 1695039484.0, "exchange_order_id": "35211000", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:18:59,693 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a12253d12d0582. [clock=2023-09-18 12:18:59+00:00] +2023-09-18 12:18:59,766 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240337268148796915123690396 amount: 80. +2023-09-18 12:18:59,767 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243481862513952714201048755 amount: 80. +2023-09-18 12:19:00,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039539.0, "order_id": "x-XEKWYICXBSIUT605a12253d12d0582", "exchange_order_id": "35211000", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:19:00,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a12253d12d0582. +2023-09-18 12:19:00,521 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a125a5e6b30582 for 80.00000000 SEI-USDT. +2023-09-18 12:19:00,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039540.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a125a5e6b30582", "creation_timestamp": 1695039539.0, "exchange_order_id": "35211400", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:19:00,562 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a125a5ef060582 for 80.00000000 SEI-USDT. +2023-09-18 12:19:00,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039540.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a125a5ef060582", "creation_timestamp": 1695039539.0, "exchange_order_id": "35211401", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:19:54,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a125a5e6b30582. [clock=2023-09-18 12:19:54+00:00] +2023-09-18 12:19:54,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a125a5ef060582. [clock=2023-09-18 12:19:54+00:00] +2023-09-18 12:19:54,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239174241562737948018503106 amount: 80. +2023-09-18 12:19:54,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:19:54,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039594.0, "order_id": "x-XEKWYICXBSIUT605a125a5e6b30582", "exchange_order_id": "35211400", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:19:54,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a125a5e6b30582. +2023-09-18 12:19:54,792 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039594.0, "order_id": "x-XEKWYICXSSIUT605a125a5ef060582", "exchange_order_id": "35211401", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:19:54,793 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a125a5ef060582. +2023-09-18 12:19:54,795 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a128e502f80582 for 80.00000000 SEI-USDT. +2023-09-18 12:19:54,904 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039594.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a128e502f80582", "creation_timestamp": 1695039594.0, "exchange_order_id": "35211573", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:20:49,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a128e502f80582. [clock=2023-09-18 12:20:49+00:00] +2023-09-18 12:20:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239255375727501067225063397 amount: 80. +2023-09-18 12:20:49,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242350139151051830849373440 amount: 80. +2023-09-18 12:20:49,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039649.0, "order_id": "x-XEKWYICXBSIUT605a128e502f80582", "exchange_order_id": "35211573", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:20:49,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a128e502f80582. +2023-09-18 12:20:49,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a12c28f4460582 for 80.00000000 SEI-USDT. +2023-09-18 12:20:49,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039649.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a12c28f4460582", "creation_timestamp": 1695039649.0, "exchange_order_id": "35211894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:20:49,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a12c28f6e80582 for 80.00000000 SEI-USDT. +2023-09-18 12:20:49,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039649.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a12c28f6e80582", "creation_timestamp": 1695039649.0, "exchange_order_id": "35211895", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:21:43,618 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a12c28f6e80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:21:43,619 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:21:43+00:00] +2023-09-18 12:21:43,641 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039703.0, "order_id": "x-XEKWYICXSSIUT605a12c28f6e80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4958204", "exchange_order_id": "35211895", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:21:43,656 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039703.0, "order_id": "x-XEKWYICXSSIUT605a12c28f6e80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35211895", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:21:43,657 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a12c28f6e80582 completely filled. +2023-09-18 12:21:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a12c28f4460582. [clock=2023-09-18 12:21:44+00:00] +2023-09-18 12:21:44,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241453963506824270316150628 amount: 80. +2023-09-18 12:21:44,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:21:44,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039704.0, "order_id": "x-XEKWYICXBSIUT605a12c28f4460582", "exchange_order_id": "35211894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:21:44,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a12c28f4460582. +2023-09-18 12:21:44,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a12f702e270582 for 80.00000000 SEI-USDT. +2023-09-18 12:21:44,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039704.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a12f702e270582", "creation_timestamp": 1695039704.0, "exchange_order_id": "35212131", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:22:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a12f702e270582. [clock=2023-09-18 12:22:39+00:00] +2023-09-18 12:22:39,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240534075552568947666806988 amount: 80. +2023-09-18 12:22:39,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:22:39,052 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039759.0, "order_id": "x-XEKWYICXBSIUT605a12f702e270582", "exchange_order_id": "35212131", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:22:39,052 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a12f702e270582. +2023-09-18 12:22:39,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a132b77c520582 for 80.00000000 SEI-USDT. +2023-09-18 12:22:39,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039759.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a132b77c520582", "creation_timestamp": 1695039759.0, "exchange_order_id": "35212330", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:23:34,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a132b77c520582. [clock=2023-09-18 12:23:34+00:00] +2023-09-18 12:23:34,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245558549987418796700939272 amount: 80. +2023-09-18 12:23:34,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:23:34,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039814.0, "order_id": "x-XEKWYICXBSIUT605a132b77c520582", "exchange_order_id": "35212330", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:23:34,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a132b77c520582. +2023-09-18 12:23:34,437 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a135ff27000582 for 80.00000000 SEI-USDT. +2023-09-18 12:23:34,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039814.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a135ff27000582", "creation_timestamp": 1695039814.0, "exchange_order_id": "35213202", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:24:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a135ff27000582. [clock=2023-09-18 12:24:29+00:00] +2023-09-18 12:24:29,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246645145166756706409579058 amount: 80. +2023-09-18 12:24:29,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:24:29,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039869.0, "order_id": "x-XEKWYICXBSIUT605a135ff27000582", "exchange_order_id": "35213202", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:24:29,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a135ff27000582. +2023-09-18 12:24:29,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1394603850582 for 80.00000000 SEI-USDT. +2023-09-18 12:24:29,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039869.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a1394603850582", "creation_timestamp": 1695039869.0, "exchange_order_id": "35213501", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:24:55,850 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a1394603850582 amounting to 30.20000000/80.00000000 SEI has been filled. +2023-09-18 12:24:55,853 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 30.20 SEI-USDT binance at 0.12 [clock=2023-09-18 12:24:55+00:00] +2023-09-18 12:24:55,938 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039895.0, "order_id": "x-XEKWYICXBSIUT605a1394603850582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "30.20000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.03020000"}]}, "exchange_trade_id": "4958430", "exchange_order_id": "35213501", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:24:55,938 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a1394603850582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:24:55,949 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 49.80 SEI-USDT binance at 0.12 [clock=2023-09-18 12:24:55+00:00] +2023-09-18 12:24:56,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039895.0, "order_id": "x-XEKWYICXBSIUT605a1394603850582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "49.80000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.04980000"}]}, "exchange_trade_id": "4958431", "exchange_order_id": "35213501", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:24:56,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039895.0, "order_id": "x-XEKWYICXBSIUT605a1394603850582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35213501", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:24:56,129 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a1394603850582 completely filled. +2023-09-18 12:25:24,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245413650916836460108757490 amount: 80. +2023-09-18 12:25:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249552765086632625926972164 amount: 80. +2023-09-18 12:25:24,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a13c8d15400582 for 80.00000000 SEI-USDT. +2023-09-18 12:25:24,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039924.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a13c8d15400582", "creation_timestamp": 1695039924.0, "exchange_order_id": "35214266", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:25:24,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a13c8d1adf0582 for 80.00000000 SEI-USDT. +2023-09-18 12:25:24,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039924.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a13c8d1adf0582", "creation_timestamp": 1695039924.0, "exchange_order_id": "35214267", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:25:42,108 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a13c8d1adf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:25:42,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:25:42+00:00] +2023-09-18 12:25:42,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039942.0, "order_id": "x-XEKWYICXSSIUT605a13c8d1adf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00999200"}]}, "exchange_trade_id": "4958503", "exchange_order_id": "35214267", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:25:42,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039942.0, "order_id": "x-XEKWYICXSSIUT605a13c8d1adf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35214267", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:25:42,143 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a13c8d1adf0582 completely filled. +2023-09-18 12:26:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a13c8d15400582. [clock=2023-09-18 12:26:19+00:00] +2023-09-18 12:26:19,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246613382707594469089119266 amount: 80. +2023-09-18 12:26:19,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:26:19,047 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039979.0, "order_id": "x-XEKWYICXBSIUT605a13c8d15400582", "exchange_order_id": "35214266", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:26:19,047 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a13c8d15400582. +2023-09-18 12:26:19,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a13fd45dae0582 for 80.00000000 SEI-USDT. +2023-09-18 12:26:19,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695039979.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a13fd45dae0582", "creation_timestamp": 1695039979.0, "exchange_order_id": "35215133", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:27:14,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a13fd45dae0582. [clock=2023-09-18 12:27:14+00:00] +2023-09-18 12:27:14,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246492473031614325915196083 amount: 80. +2023-09-18 12:27:14,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:27:14,047 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040034.0, "order_id": "x-XEKWYICXBSIUT605a13fd45dae0582", "exchange_order_id": "35215133", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:27:14,048 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a13fd45dae0582. +2023-09-18 12:27:14,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1431b95580582 for 80.00000000 SEI-USDT. +2023-09-18 12:27:14,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040034.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a1431b95580582", "creation_timestamp": 1695040034.0, "exchange_order_id": "35215684", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:28:09,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1431b95580582. [clock=2023-09-18 12:28:09+00:00] +2023-09-18 12:28:09,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247582458529946097444555032 amount: 80. +2023-09-18 12:28:09,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:28:09,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040089.0, "order_id": "x-XEKWYICXBSIUT605a1431b95580582", "exchange_order_id": "35215684", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:28:09,624 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1431b95580582. +2023-09-18 12:28:09,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1466386e80582 for 80.00000000 SEI-USDT. +2023-09-18 12:28:09,676 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040089.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a1466386e80582", "creation_timestamp": 1695040089.0, "exchange_order_id": "35216352", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:29:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1466386e80582. [clock=2023-09-18 12:29:04+00:00] +2023-09-18 12:29:04,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246466498467324510261253568 amount: 80. +2023-09-18 12:29:04,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:29:04,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040144.0, "order_id": "x-XEKWYICXBSIUT605a1466386e80582", "exchange_order_id": "35216352", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:29:04,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1466386e80582. +2023-09-18 12:29:04,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a149aa81180582 for 80.00000000 SEI-USDT. +2023-09-18 12:29:04,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040144.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a149aa81180582", "creation_timestamp": 1695040144.0, "exchange_order_id": "35216873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:29:59,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a149aa81180582. [clock=2023-09-18 12:29:59+00:00] +2023-09-18 12:29:59,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247460501398990043476519658 amount: 80. +2023-09-18 12:29:59,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:29:59,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040199.0, "order_id": "x-XEKWYICXBSIUT605a149aa81180582", "exchange_order_id": "35216873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:29:59,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a149aa81180582. +2023-09-18 12:29:59,561 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a14cf1d9800582 for 80.00000000 SEI-USDT. +2023-09-18 12:29:59,610 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040199.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a14cf1d9800582", "creation_timestamp": 1695040199.0, "exchange_order_id": "35217123", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:30:54,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a14cf1d9800582. [clock=2023-09-18 12:30:54+00:00] +2023-09-18 12:30:54,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248642551702823842985649326 amount: 80. +2023-09-18 12:30:54,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:30:54,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040254.0, "order_id": "x-XEKWYICXBSIUT605a14cf1d9800582", "exchange_order_id": "35217123", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:30:54,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a14cf1d9800582. +2023-09-18 12:30:54,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1503888640582 for 80.00000000 SEI-USDT. +2023-09-18 12:30:54,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040254.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a1503888640582", "creation_timestamp": 1695040254.0, "exchange_order_id": "35217743", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:31:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1503888640582. [clock=2023-09-18 12:31:49+00:00] +2023-09-18 12:31:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247394214111954678081208337 amount: 80. +2023-09-18 12:31:49,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:31:49,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040309.0, "order_id": "x-XEKWYICXBSIUT605a1503888640582", "exchange_order_id": "35217743", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:31:49,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1503888640582. +2023-09-18 12:31:49,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1537fbd5a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:31:49,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040309.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a1537fbd5a0582", "creation_timestamp": 1695040309.0, "exchange_order_id": "35218521", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:32:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1537fbd5a0582. [clock=2023-09-18 12:32:44+00:00] +2023-09-18 12:32:44,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247371560748920693665170342 amount: 80. +2023-09-18 12:32:44,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:32:44,055 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040364.0, "order_id": "x-XEKWYICXBSIUT605a1537fbd5a0582", "exchange_order_id": "35218521", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:32:44,055 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1537fbd5a0582. +2023-09-18 12:32:44,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a156c708df0582 for 80.00000000 SEI-USDT. +2023-09-18 12:32:44,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040364.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a156c708df0582", "creation_timestamp": 1695040364.0, "exchange_order_id": "35219073", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:33:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a156c708df0582. [clock=2023-09-18 12:33:39+00:00] +2023-09-18 12:33:39,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246108357962435293571010820 amount: 80. +2023-09-18 12:33:39,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:33:39,648 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040419.0, "order_id": "x-XEKWYICXBSIUT605a156c708df0582", "exchange_order_id": "35219073", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:33:39,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a156c708df0582. +2023-09-18 12:33:39,872 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a15a0f35b50582 for 80.00000000 SEI-USDT. +2023-09-18 12:33:39,953 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040419.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a15a0f35b50582", "creation_timestamp": 1695040419.0, "exchange_order_id": "35219404", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:34:34,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a15a0f35b50582. [clock=2023-09-18 12:34:34+00:00] +2023-09-18 12:34:34,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250419035500052581928021609 amount: 80. +2023-09-18 12:34:34,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:34:34,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040474.0, "order_id": "x-XEKWYICXBSIUT605a15a0f35b50582", "exchange_order_id": "35219404", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:34:34,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a15a0f35b50582. +2023-09-18 12:34:34,873 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a15d57101a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:34:34,911 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040474.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a15d57101a0582", "creation_timestamp": 1695040474.0, "exchange_order_id": "35220111", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:35:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a15d57101a0582. [clock=2023-09-18 12:35:29+00:00] +2023-09-18 12:35:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253509709482662340013068706 amount: 80. +2023-09-18 12:35:29,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:35:29,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040529.0, "order_id": "x-XEKWYICXBSIUT605a15d57101a0582", "exchange_order_id": "35220111", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:35:29,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a15d57101a0582. +2023-09-18 12:35:29,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1609cb3ce0582 for 80.00000000 SEI-USDT. +2023-09-18 12:35:29,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040529.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a1609cb3ce0582", "creation_timestamp": 1695040529.0, "exchange_order_id": "35221120", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:36:24,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1609cb3ce0582. [clock=2023-09-18 12:36:24+00:00] +2023-09-18 12:36:24,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255994063159704547140311668 amount: 80. +2023-09-18 12:36:24,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:36:24,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040584.0, "order_id": "x-XEKWYICXBSIUT605a1609cb3ce0582", "exchange_order_id": "35221120", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:36:24,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1609cb3ce0582. +2023-09-18 12:36:24,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a163e3edc60582 for 80.00000000 SEI-USDT. +2023-09-18 12:36:24,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040584.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a163e3edc60582", "creation_timestamp": 1695040584.0, "exchange_order_id": "35222019", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:37:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a163e3edc60582. [clock=2023-09-18 12:37:19+00:00] +2023-09-18 12:37:19,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12560000 amount: 80. +2023-09-18 12:37:19,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:37:19,048 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040639.0, "order_id": "x-XEKWYICXBSIUT605a163e3edc60582", "exchange_order_id": "35222019", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:37:19,049 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a163e3edc60582. +2023-09-18 12:37:19,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1672b23c90582 for 80.00000000 SEI-USDT. +2023-09-18 12:37:19,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040639.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a1672b23c90582", "creation_timestamp": 1695040639.0, "exchange_order_id": "35222530", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:38:14,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1672b23c90582. [clock=2023-09-18 12:38:14+00:00] +2023-09-18 12:38:14,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12570000 amount: 80. +2023-09-18 12:38:14,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:38:14,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040694.0, "order_id": "x-XEKWYICXBSIUT605a1672b23c90582", "exchange_order_id": "35222530", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:38:14,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1672b23c90582. +2023-09-18 12:38:14,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a16a72e2810582 for 80.00000000 SEI-USDT. +2023-09-18 12:38:14,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040694.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a16a72e2810582", "creation_timestamp": 1695040694.0, "exchange_order_id": "35223017", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:39:07,306 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a16a72e2810582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:39:07,307 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:39:07+00:00] +2023-09-18 12:39:07,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040747.0, "order_id": "x-XEKWYICXBSIUT605a16a72e2810582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4959468", "exchange_order_id": "35223017", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:39:07,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040747.0, "order_id": "x-XEKWYICXBSIUT605a16a72e2810582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35223017", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:39:07,404 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a16a72e2810582 completely filled. +2023-09-18 12:39:09,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12560000 amount: 80. +2023-09-18 12:39:09,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261737372699774703551988214 amount: 80. +2023-09-18 12:39:09,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a16dba7f950582 for 80.00000000 SEI-USDT. +2023-09-18 12:39:09,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040749.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a16dba7f950582", "creation_timestamp": 1695040749.0, "exchange_order_id": "35223472", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:39:09,870 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a16dba83910582 for 80.00000000 SEI-USDT. +2023-09-18 12:39:09,905 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040749.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a16dba83910582", "creation_timestamp": 1695040749.0, "exchange_order_id": "35223473", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:39:20,879 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a16dba7f950582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:39:20,880 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:39:20+00:00] +2023-09-18 12:39:20,949 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040760.0, "order_id": "x-XEKWYICXBSIUT605a16dba7f950582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12560000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4959494", "exchange_order_id": "35223472", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:39:20,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040760.0, "order_id": "x-XEKWYICXBSIUT605a16dba7f950582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35223472", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:39:20,997 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a16dba7f950582 completely filled. +2023-09-18 12:40:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a16dba83910582. [clock=2023-09-18 12:40:04+00:00] +2023-09-18 12:40:04,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254476459468337052389498698 amount: 80. +2023-09-18 12:40:04,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259058059399676046494906634 amount: 80. +2023-09-18 12:40:04,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040804.0, "order_id": "x-XEKWYICXSSIUT605a16dba83910582", "exchange_order_id": "35223473", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:40:04,433 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a16dba83910582. +2023-09-18 12:40:04,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a17101bee30582 for 80.00000000 SEI-USDT. +2023-09-18 12:40:04,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040804.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a17101bee30582", "creation_timestamp": 1695040804.0, "exchange_order_id": "35223890", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:40:05,042 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a17101c1ed0582 for 80.00000000 SEI-USDT. +2023-09-18 12:40:05,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040805.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a17101c1ed0582", "creation_timestamp": 1695040804.0, "exchange_order_id": "35223901", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:40:59,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a17101bee30582. [clock=2023-09-18 12:40:59+00:00] +2023-09-18 12:40:59,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a17101c1ed0582. [clock=2023-09-18 12:40:59+00:00] +2023-09-18 12:40:59,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254393194503741346843619824 amount: 80. +2023-09-18 12:40:59,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259225108211113571233735042 amount: 80. +2023-09-18 12:40:59,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040859.0, "order_id": "x-XEKWYICXBSIUT605a17101bee30582", "exchange_order_id": "35223890", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:40:59,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a17101bee30582. +2023-09-18 12:40:59,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a17448304d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:40:59,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040859.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a17448304d0582", "creation_timestamp": 1695040859.0, "exchange_order_id": "35224189", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:40:59,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a174482bb20582 for 80.00000000 SEI-USDT. +2023-09-18 12:40:59,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040859.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a174482bb20582", "creation_timestamp": 1695040859.0, "exchange_order_id": "35224190", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:40:59,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040859.0, "order_id": "x-XEKWYICXSSIUT605a17101c1ed0582", "exchange_order_id": "35223901", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:40:59,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a17101c1ed0582. +2023-09-18 12:41:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a174482bb20582. [clock=2023-09-18 12:41:54+00:00] +2023-09-18 12:41:54,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a17448304d0582. [clock=2023-09-18 12:41:54+00:00] +2023-09-18 12:41:54,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253266933387704229649803436 amount: 80. +2023-09-18 12:41:54,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257994510616987633639458255 amount: 80. +2023-09-18 12:41:54,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040914.0, "order_id": "x-XEKWYICXBSIUT605a174482bb20582", "exchange_order_id": "35224190", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:41:54,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a174482bb20582. +2023-09-18 12:41:54,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1778f57f50582 for 80.00000000 SEI-USDT. +2023-09-18 12:41:54,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040914.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a1778f57f50582", "creation_timestamp": 1695040914.0, "exchange_order_id": "35224531", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:41:54,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040914.0, "order_id": "x-XEKWYICXSSIUT605a17448304d0582", "exchange_order_id": "35224189", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:41:54,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a17448304d0582. +2023-09-18 12:41:54,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1778f5aab0582 for 80.00000000 SEI-USDT. +2023-09-18 12:41:54,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040914.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a1778f5aab0582", "creation_timestamp": 1695040914.0, "exchange_order_id": "35224532", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:42:49,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1778f57f50582. [clock=2023-09-18 12:42:49+00:00] +2023-09-18 12:42:49,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1778f5aab0582. [clock=2023-09-18 12:42:49+00:00] +2023-09-18 12:42:49,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254475167246769805314354351 amount: 80. +2023-09-18 12:42:49,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258862053473739983186653751 amount: 80. +2023-09-18 12:42:49,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040969.0, "order_id": "x-XEKWYICXBSIUT605a1778f57f50582", "exchange_order_id": "35224531", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:42:49,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1778f57f50582. +2023-09-18 12:42:49,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040969.0, "order_id": "x-XEKWYICXSSIUT605a1778f5aab0582", "exchange_order_id": "35224532", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:42:49,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1778f5aab0582. +2023-09-18 12:42:49,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a17ad69a8a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:42:49,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040969.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a17ad69a8a0582", "creation_timestamp": 1695040969.0, "exchange_order_id": "35224833", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:42:49,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a17ad693c00582 for 80.00000000 SEI-USDT. +2023-09-18 12:42:49,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695040969.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a17ad693c00582", "creation_timestamp": 1695040969.0, "exchange_order_id": "35224834", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:43:42,934 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a17ad693c00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:43:42,936 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:43:42+00:00] +2023-09-18 12:43:43,037 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041022.0, "order_id": "x-XEKWYICXBSIUT605a17ad693c00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4959628", "exchange_order_id": "35224834", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:43:43,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041022.0, "order_id": "x-XEKWYICXBSIUT605a17ad693c00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35224834", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:43:43,088 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a17ad693c00582 completely filled. +2023-09-18 12:43:44,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a17ad69a8a0582. [clock=2023-09-18 12:43:44+00:00] +2023-09-18 12:43:44,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252106064408139159428043337 amount: 80. +2023-09-18 12:43:44,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256694010700192606195089917 amount: 80. +2023-09-18 12:43:44,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041024.0, "order_id": "x-XEKWYICXSSIUT605a17ad69a8a0582", "exchange_order_id": "35224833", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:43:44,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a17ad69a8a0582. +2023-09-18 12:43:44,599 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a17e1e8f720582 for 80.00000000 SEI-USDT. +2023-09-18 12:43:44,653 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041024.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a17e1e8f720582", "creation_timestamp": 1695041024.0, "exchange_order_id": "35225254", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:43:44,665 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a17e1e93650582 for 80.00000000 SEI-USDT. +2023-09-18 12:43:44,700 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041024.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a17e1e93650582", "creation_timestamp": 1695041024.0, "exchange_order_id": "35225255", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:44:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a17e1e8f720582. [clock=2023-09-18 12:44:39+00:00] +2023-09-18 12:44:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a17e1e93650582. [clock=2023-09-18 12:44:39+00:00] +2023-09-18 12:44:39,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252119865551572036478015527 amount: 80. +2023-09-18 12:44:39,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256603080258733607475971763 amount: 80. +2023-09-18 12:44:39,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041079.0, "order_id": "x-XEKWYICXBSIUT605a17e1e8f720582", "exchange_order_id": "35225254", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:44:39,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a17e1e8f720582. +2023-09-18 12:44:39,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041079.0, "order_id": "x-XEKWYICXSSIUT605a17e1e93650582", "exchange_order_id": "35225255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:44:39,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a17e1e93650582. +2023-09-18 12:44:39,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a181657c7f0582 for 80.00000000 SEI-USDT. +2023-09-18 12:44:39,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041079.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a181657c7f0582", "creation_timestamp": 1695041079.0, "exchange_order_id": "35225489", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:44:39,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1816582960582 for 80.00000000 SEI-USDT. +2023-09-18 12:44:39,486 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041079.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a1816582960582", "creation_timestamp": 1695041079.0, "exchange_order_id": "35225490", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:45:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a181657c7f0582. [clock=2023-09-18 12:45:34+00:00] +2023-09-18 12:45:34,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1816582960582. [clock=2023-09-18 12:45:34+00:00] +2023-09-18 12:45:34,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251050677099344564650941493 amount: 80. +2023-09-18 12:45:34,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255473284017065942093697271 amount: 80. +2023-09-18 12:45:34,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041134.0, "order_id": "x-XEKWYICXBSIUT605a181657c7f0582", "exchange_order_id": "35225489", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:45:34,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a181657c7f0582. +2023-09-18 12:45:34,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041134.0, "order_id": "x-XEKWYICXSSIUT605a1816582960582", "exchange_order_id": "35225490", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:45:34,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1816582960582. +2023-09-18 12:45:34,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a184ac582a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:45:34,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041134.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a184ac582a0582", "creation_timestamp": 1695041134.0, "exchange_order_id": "35225876", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:45:34,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a184ac54d80582 for 80.00000000 SEI-USDT. +2023-09-18 12:45:34,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041134.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a184ac54d80582", "creation_timestamp": 1695041134.0, "exchange_order_id": "35225877", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:46:29,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a184ac54d80582. [clock=2023-09-18 12:46:29+00:00] +2023-09-18 12:46:29,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a184ac582a0582. [clock=2023-09-18 12:46:29+00:00] +2023-09-18 12:46:29,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251294393020359908188573254 amount: 80. +2023-09-18 12:46:29,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255164072956499720835253468 amount: 80. +2023-09-18 12:46:29,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041189.0, "order_id": "x-XEKWYICXBSIUT605a184ac54d80582", "exchange_order_id": "35225877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:46:29,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a184ac54d80582. +2023-09-18 12:46:29,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a187f384d00582 for 80.00000000 SEI-USDT. +2023-09-18 12:46:29,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a187f384d00582", "creation_timestamp": 1695041189.0, "exchange_order_id": "35226218", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:46:29,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041189.0, "order_id": "x-XEKWYICXSSIUT605a184ac582a0582", "exchange_order_id": "35225876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:46:29,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a184ac582a0582. +2023-09-18 12:46:29,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a187f380800582 for 80.00000000 SEI-USDT. +2023-09-18 12:46:29,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a187f380800582", "creation_timestamp": 1695041189.0, "exchange_order_id": "35226219", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:47:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a187f380800582. [clock=2023-09-18 12:47:24+00:00] +2023-09-18 12:47:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a187f384d00582. [clock=2023-09-18 12:47:24+00:00] +2023-09-18 12:47:24,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251283455204446439147315355 amount: 80. +2023-09-18 12:47:24,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255138204298638074076020549 amount: 80. +2023-09-18 12:47:24,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041244.0, "order_id": "x-XEKWYICXBSIUT605a187f380800582", "exchange_order_id": "35226219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:47:24,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a187f380800582. +2023-09-18 12:47:24,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a18b3ac8570582 for 80.00000000 SEI-USDT. +2023-09-18 12:47:24,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041244.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a18b3ac8570582", "creation_timestamp": 1695041244.0, "exchange_order_id": "35226648", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:47:24,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a18b3acc2d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:47:24,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041244.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a18b3acc2d0582", "creation_timestamp": 1695041244.0, "exchange_order_id": "35226649", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:47:24,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041244.0, "order_id": "x-XEKWYICXSSIUT605a187f384d00582", "exchange_order_id": "35226218", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:47:24,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a187f384d00582. +2023-09-18 12:47:52,795 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 12:48:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a18b3ac8570582. [clock=2023-09-18 12:48:19+00:00] +2023-09-18 12:48:19,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a18b3acc2d0582. [clock=2023-09-18 12:48:19+00:00] +2023-09-18 12:48:19,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251498058768235412608206504 amount: 80. +2023-09-18 12:48:19,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254870893296963495483000604 amount: 80. +2023-09-18 12:48:19,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041299.0, "order_id": "x-XEKWYICXBSIUT605a18b3ac8570582", "exchange_order_id": "35226648", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:48:19,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a18b3ac8570582. +2023-09-18 12:48:19,922 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041299.0, "order_id": "x-XEKWYICXSSIUT605a18b3acc2d0582", "exchange_order_id": "35226649", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:48:19,923 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a18b3acc2d0582. +2023-09-18 12:48:19,927 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a18e82e3ab0582 for 80.00000000 SEI-USDT. +2023-09-18 12:48:19,988 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041299.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a18e82e3ab0582", "creation_timestamp": 1695041299.0, "exchange_order_id": "35226758", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:48:19,989 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a18e82e8060582 for 80.00000000 SEI-USDT. +2023-09-18 12:48:20,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041299.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a18e82e8060582", "creation_timestamp": 1695041299.0, "exchange_order_id": "35226759", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:48:22,658 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a18e82e8060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:48:22,659 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:48:22+00:00] +2023-09-18 12:48:22,740 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041302.0, "order_id": "x-XEKWYICXSSIUT605a18e82e8060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01003200"}]}, "exchange_trade_id": "4959738", "exchange_order_id": "35226759", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:48:22,819 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041302.0, "order_id": "x-XEKWYICXSSIUT605a18e82e8060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35226759", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:48:22,819 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a18e82e8060582 completely filled. +2023-09-18 12:49:14,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a18e82e3ab0582. [clock=2023-09-18 12:49:14+00:00] +2023-09-18 12:49:14,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253652336545603711040598382 amount: 80. +2023-09-18 12:49:14,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257041569036091044843641242 amount: 80. +2023-09-18 12:49:14,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041354.0, "order_id": "x-XEKWYICXBSIUT605a18e82e3ab0582", "exchange_order_id": "35226758", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:49:14,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a18e82e3ab0582. +2023-09-18 12:49:14,738 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a191cbd20b0582 for 80.00000000 SEI-USDT. +2023-09-18 12:49:14,793 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041354.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a191cbd20b0582", "creation_timestamp": 1695041354.0, "exchange_order_id": "35227073", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:49:14,793 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a191cbce650582 for 80.00000000 SEI-USDT. +2023-09-18 12:49:14,838 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041354.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a191cbce650582", "creation_timestamp": 1695041354.0, "exchange_order_id": "35227074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:49:31,472 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a191cbce650582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:49:31,485 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:49:31+00:00] +2023-09-18 12:49:31,570 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041371.0, "order_id": "x-XEKWYICXBSIUT605a191cbce650582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4959793", "exchange_order_id": "35227074", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:49:31,629 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041371.0, "order_id": "x-XEKWYICXBSIUT605a191cbce650582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35227074", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:49:31,630 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a191cbce650582 completely filled. +2023-09-18 12:50:09,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a191cbd20b0582. [clock=2023-09-18 12:50:09+00:00] +2023-09-18 12:50:09,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249041577563033919435424690 amount: 80. +2023-09-18 12:50:09,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252881217881683052303124072 amount: 80. +2023-09-18 12:50:09,091 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041409.0, "order_id": "x-XEKWYICXSSIUT605a191cbd20b0582", "exchange_order_id": "35227073", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:50:09,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a191cbd20b0582. +2023-09-18 12:50:09,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1951068520582 for 80.00000000 SEI-USDT. +2023-09-18 12:50:09,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041409.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a1951068520582", "creation_timestamp": 1695041409.0, "exchange_order_id": "35227723", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:50:09,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a195106b880582 for 80.00000000 SEI-USDT. +2023-09-18 12:50:09,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041409.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a195106b880582", "creation_timestamp": 1695041409.0, "exchange_order_id": "35227724", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:51:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1951068520582. [clock=2023-09-18 12:51:04+00:00] +2023-09-18 12:51:04,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a195106b880582. [clock=2023-09-18 12:51:04+00:00] +2023-09-18 12:51:04,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249286373888062867802853446 amount: 80. +2023-09-18 12:51:04,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252646072126064593819996091 amount: 80. +2023-09-18 12:51:04,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041464.0, "order_id": "x-XEKWYICXBSIUT605a1951068520582", "exchange_order_id": "35227723", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:51:04,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1951068520582. +2023-09-18 12:51:04,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a19857b0ed0582 for 80.00000000 SEI-USDT. +2023-09-18 12:51:04,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041464.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a19857b0ed0582", "creation_timestamp": 1695041464.0, "exchange_order_id": "35228327", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:51:04,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a19857ae9d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:51:04,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041464.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a19857ae9d0582", "creation_timestamp": 1695041464.0, "exchange_order_id": "35228328", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:51:04,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041464.0, "order_id": "x-XEKWYICXSSIUT605a195106b880582", "exchange_order_id": "35227724", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:51:04,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a195106b880582. +2023-09-18 12:51:59,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a19857ae9d0582. [clock=2023-09-18 12:51:59+00:00] +2023-09-18 12:51:59,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a19857b0ed0582. [clock=2023-09-18 12:51:59+00:00] +2023-09-18 12:51:59,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248160339290776723590469951 amount: 80. +2023-09-18 12:51:59,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251767138087480415183382313 amount: 80. +2023-09-18 12:51:59,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041519.0, "order_id": "x-XEKWYICXBSIUT605a19857ae9d0582", "exchange_order_id": "35228328", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:51:59,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a19857ae9d0582. +2023-09-18 12:51:59,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041519.0, "order_id": "x-XEKWYICXSSIUT605a19857b0ed0582", "exchange_order_id": "35228327", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:51:59,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a19857b0ed0582. +2023-09-18 12:51:59,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a19b9ee6550582 for 80.00000000 SEI-USDT. +2023-09-18 12:51:59,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041519.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a19b9ee6550582", "creation_timestamp": 1695041519.0, "exchange_order_id": "35228855", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:51:59,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a19b9ee49a0582 for 80.00000000 SEI-USDT. +2023-09-18 12:51:59,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041519.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a19b9ee49a0582", "creation_timestamp": 1695041519.0, "exchange_order_id": "35228856", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:52:03,471 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a19b9ee6550582 amounting to 40.00000000/80.00000000 SEI has been filled. +2023-09-18 12:52:03,472 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 40.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:52:03+00:00] +2023-09-18 12:52:03,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041523.0, "order_id": "x-XEKWYICXSSIUT605a19b9ee6550582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "40.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00500400"}]}, "exchange_trade_id": "4959922", "exchange_order_id": "35228855", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:52:03,551 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a19b9ee6550582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:52:03,554 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 40.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:52:03+00:00] +2023-09-18 12:52:03,573 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041523.0, "order_id": "x-XEKWYICXSSIUT605a19b9ee6550582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "40.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00500400"}]}, "exchange_trade_id": "4959923", "exchange_order_id": "35228855", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:52:03,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041523.0, "order_id": "x-XEKWYICXSSIUT605a19b9ee6550582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35228855", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:52:03,585 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a19b9ee6550582 completely filled. +2023-09-18 12:52:54,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a19b9ee49a0582. [clock=2023-09-18 12:52:54+00:00] +2023-09-18 12:52:54,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249270777130302544803545700 amount: 80. +2023-09-18 12:52:54,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252679274416962753932603520 amount: 80. +2023-09-18 12:52:54,051 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041574.0, "order_id": "x-XEKWYICXBSIUT605a19b9ee49a0582", "exchange_order_id": "35228856", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:52:54,051 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a19b9ee49a0582. +2023-09-18 12:52:54,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a19ee61e990582 for 80.00000000 SEI-USDT. +2023-09-18 12:52:54,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041574.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a19ee61e990582", "creation_timestamp": 1695041574.0, "exchange_order_id": "35229126", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:52:54,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a19ee61be20582 for 80.00000000 SEI-USDT. +2023-09-18 12:52:54,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041574.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a19ee61be20582", "creation_timestamp": 1695041574.0, "exchange_order_id": "35229128", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:52:55,577 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a19ee61be20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:52:55,578 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 12:52:55+00:00] +2023-09-18 12:52:55,602 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041575.0, "order_id": "x-XEKWYICXBSIUT605a19ee61be20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4960005", "exchange_order_id": "35229128", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:52:55,619 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041575.0, "order_id": "x-XEKWYICXBSIUT605a19ee61be20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35229128", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:52:55,620 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a19ee61be20582 completely filled. +2023-09-18 12:53:49,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a19ee61e990582. [clock=2023-09-18 12:53:49+00:00] +2023-09-18 12:53:49,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247941227541175749029641744 amount: 80. +2023-09-18 12:53:49,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251613506381189500970358256 amount: 80. +2023-09-18 12:53:49,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041629.0, "order_id": "x-XEKWYICXSSIUT605a19ee61e990582", "exchange_order_id": "35229126", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:53:49,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a19ee61e990582. +2023-09-18 12:53:49,818 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1a22ddadd0582 for 80.00000000 SEI-USDT. +2023-09-18 12:53:49,866 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041629.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a1a22ddadd0582", "creation_timestamp": 1695041629.0, "exchange_order_id": "35230019", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:53:49,867 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1a22dd7a70582 for 80.00000000 SEI-USDT. +2023-09-18 12:53:49,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041629.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a1a22dd7a70582", "creation_timestamp": 1695041629.0, "exchange_order_id": "35230018", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:53:55,863 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1a22ddadd0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:53:55,864 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:53:55+00:00] +2023-09-18 12:53:55,921 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041635.0, "order_id": "x-XEKWYICXSSIUT605a1a22ddadd0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "4960145", "exchange_order_id": "35230019", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:53:55,941 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041635.0, "order_id": "x-XEKWYICXSSIUT605a1a22ddadd0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35230019", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:53:55,941 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1a22ddadd0582 completely filled. +2023-09-18 12:54:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1a22dd7a70582. [clock=2023-09-18 12:54:44+00:00] +2023-09-18 12:54:44,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247905302520971705867154396 amount: 80. +2023-09-18 12:54:44,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251834889436483794132845604 amount: 80. +2023-09-18 12:54:44,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041684.0, "order_id": "x-XEKWYICXBSIUT605a1a22dd7a70582", "exchange_order_id": "35230018", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:54:44,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1a22dd7a70582. +2023-09-18 12:54:44,774 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1a57590d40582 for 80.00000000 SEI-USDT. +2023-09-18 12:54:44,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041684.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a1a57590d40582", "creation_timestamp": 1695041684.0, "exchange_order_id": "35230684", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:54:44,824 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1a5758ed10582 for 80.00000000 SEI-USDT. +2023-09-18 12:54:44,887 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041684.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a1a5758ed10582", "creation_timestamp": 1695041684.0, "exchange_order_id": "35230685", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:55:39,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1a5758ed10582. [clock=2023-09-18 12:55:39+00:00] +2023-09-18 12:55:39,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1a57590d40582. [clock=2023-09-18 12:55:39+00:00] +2023-09-18 12:55:39,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248167117370503764138456326 amount: 80. +2023-09-18 12:55:39,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251605550592269735861543674 amount: 80. +2023-09-18 12:55:39,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041739.0, "order_id": "x-XEKWYICXBSIUT605a1a5758ed10582", "exchange_order_id": "35230685", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:55:39,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1a5758ed10582. +2023-09-18 12:55:39,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1a8bbdedc0582 for 80.00000000 SEI-USDT. +2023-09-18 12:55:39,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041739.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a1a8bbdedc0582", "creation_timestamp": 1695041739.0, "exchange_order_id": "35230868", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:55:39,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041739.0, "order_id": "x-XEKWYICXSSIUT605a1a57590d40582", "exchange_order_id": "35230684", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:55:39,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1a57590d40582. +2023-09-18 12:55:39,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1a8bbdc980582 for 80.00000000 SEI-USDT. +2023-09-18 12:55:39,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041739.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a1a8bbdc980582", "creation_timestamp": 1695041739.0, "exchange_order_id": "35230869", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:56:34,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1a8bbdc980582. [clock=2023-09-18 12:56:34+00:00] +2023-09-18 12:56:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1a8bbdedc0582. [clock=2023-09-18 12:56:34+00:00] +2023-09-18 12:56:34,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248152432536768357550902181 amount: 80. +2023-09-18 12:56:34,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251629782261287392449097819 amount: 80. +2023-09-18 12:56:34,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041794.0, "order_id": "x-XEKWYICXBSIUT605a1a8bbdc980582", "exchange_order_id": "35230869", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:56:34,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1a8bbdc980582. +2023-09-18 12:56:34,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1ac03145c0582 for 80.00000000 SEI-USDT. +2023-09-18 12:56:34,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041794.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a1ac03145c0582", "creation_timestamp": 1695041794.0, "exchange_order_id": "35231159", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:56:34,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041794.0, "order_id": "x-XEKWYICXSSIUT605a1a8bbdedc0582", "exchange_order_id": "35230868", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:56:34,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1a8bbdedc0582. +2023-09-18 12:56:34,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1ac03106d0582 for 80.00000000 SEI-USDT. +2023-09-18 12:56:34,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041794.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a1ac03106d0582", "creation_timestamp": 1695041794.0, "exchange_order_id": "35231160", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:56:41,246 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1ac03145c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 12:56:41,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 12:56:41+00:00] +2023-09-18 12:56:41,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041801.0, "order_id": "x-XEKWYICXSSIUT605a1ac03145c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "4960245", "exchange_order_id": "35231159", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 12:56:41,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041801.0, "order_id": "x-XEKWYICXSSIUT605a1ac03145c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35231159", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 12:56:41,285 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1ac03145c0582 completely filled. +2023-09-18 12:57:29,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1ac03106d0582. [clock=2023-09-18 12:57:29+00:00] +2023-09-18 12:57:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249359426443895127947365617 amount: 80. +2023-09-18 12:57:29,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252621097146162275073920162 amount: 80. +2023-09-18 12:57:29,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041849.0, "order_id": "x-XEKWYICXBSIUT605a1ac03106d0582", "exchange_order_id": "35231160", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:57:29,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1ac03106d0582. +2023-09-18 12:57:29,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1af4a4af50582 for 80.00000000 SEI-USDT. +2023-09-18 12:57:29,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041849.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a1af4a4af50582", "creation_timestamp": 1695041849.0, "exchange_order_id": "35231419", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:57:29,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1af4a48e50582 for 80.00000000 SEI-USDT. +2023-09-18 12:57:29,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041849.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a1af4a48e50582", "creation_timestamp": 1695041849.0, "exchange_order_id": "35231420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:58:24,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1af4a48e50582. [clock=2023-09-18 12:58:24+00:00] +2023-09-18 12:58:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1af4a4af50582. [clock=2023-09-18 12:58:24+00:00] +2023-09-18 12:58:24,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248338786213069654239286920 amount: 80. +2023-09-18 12:58:24,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 12:58:24,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041904.0, "order_id": "x-XEKWYICXBSIUT605a1af4a48e50582", "exchange_order_id": "35231420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:58:24,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1af4a48e50582. +2023-09-18 12:58:24,404 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1b2923b630582 for 80.00000000 SEI-USDT. +2023-09-18 12:58:24,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041904.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a1b2923b630582", "creation_timestamp": 1695041904.0, "exchange_order_id": "35231538", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:58:24,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041904.0, "order_id": "x-XEKWYICXSSIUT605a1af4a4af50582", "exchange_order_id": "35231419", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:58:24,466 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1af4a4af50582. +2023-09-18 12:59:19,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1b2923b630582. [clock=2023-09-18 12:59:19+00:00] +2023-09-18 12:59:19,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249633906848177555359077201 amount: 80. +2023-09-18 12:59:19,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252349875314042870174102481 amount: 80. +2023-09-18 12:59:19,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041959.0, "order_id": "x-XEKWYICXBSIUT605a1b2923b630582", "exchange_order_id": "35231538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 12:59:19,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1b2923b630582. +2023-09-18 12:59:19,911 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1b5dc207c0582 for 80.00000000 SEI-USDT. +2023-09-18 12:59:19,951 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041959.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a1b5dc207c0582", "creation_timestamp": 1695041959.0, "exchange_order_id": "35231662", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 12:59:20,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1b5dc23f40582 for 80.00000000 SEI-USDT. +2023-09-18 12:59:20,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695041959.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a1b5dc23f40582", "creation_timestamp": 1695041959.0, "exchange_order_id": "35231663", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:00:00,031 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a1b5dc207c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:00:00,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:00:00+00:00] +2023-09-18 13:00:00,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042000.0, "order_id": "x-XEKWYICXBSIUT605a1b5dc207c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4960308", "exchange_order_id": "35231662", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:00:00,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042000.0, "order_id": "x-XEKWYICXBSIUT605a1b5dc207c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35231662", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:00:00,127 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a1b5dc207c0582 completely filled. +2023-09-18 13:00:14,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1b5dc23f40582. [clock=2023-09-18 13:00:14+00:00] +2023-09-18 13:00:14,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247403799180120344914154561 amount: 80. +2023-09-18 13:00:14,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250217360229495754407025453 amount: 80. +2023-09-18 13:00:14,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042014.0, "order_id": "x-XEKWYICXSSIUT605a1b5dc23f40582", "exchange_order_id": "35231663", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:00:14,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1b5dc23f40582. +2023-09-18 13:00:14,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1b91ffdc00582 for 80.00000000 SEI-USDT. +2023-09-18 13:00:14,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042014.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a1b91ffdc00582", "creation_timestamp": 1695042014.0, "exchange_order_id": "35232147", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:00:14,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1b920012d0582 for 80.00000000 SEI-USDT. +2023-09-18 13:00:14,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042014.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a1b920012d0582", "creation_timestamp": 1695042014.0, "exchange_order_id": "35232159", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:00:16,626 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1b920012d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:00:16,627 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:00:16+00:00] +2023-09-18 13:00:16,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042016.0, "order_id": "x-XEKWYICXSSIUT605a1b920012d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4960338", "exchange_order_id": "35232159", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:00:16,661 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042016.0, "order_id": "x-XEKWYICXSSIUT605a1b920012d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35232159", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:00:16,661 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1b920012d0582 completely filled. +2023-09-18 13:01:07,042 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a1b91ffdc00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:01:07,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:01:07+00:00] +2023-09-18 13:01:07,061 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042067.0, "order_id": "x-XEKWYICXBSIUT605a1b91ffdc00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4960373", "exchange_order_id": "35232147", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:01:07,072 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042067.0, "order_id": "x-XEKWYICXBSIUT605a1b91ffdc00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35232147", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:01:07,073 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a1b91ffdc00582 completely filled. +2023-09-18 13:01:09,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246016245055953765085862378 amount: 80. +2023-09-18 13:01:09,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249198784108619673287413190 amount: 80. +2023-09-18 13:01:09,080 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1bc67359b0582 for 80.00000000 SEI-USDT. +2023-09-18 13:01:09,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042069.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a1bc67359b0582", "creation_timestamp": 1695042069.0, "exchange_order_id": "35232783", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:01:09,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1bc6739080582 for 80.00000000 SEI-USDT. +2023-09-18 13:01:09,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042069.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a1bc6739080582", "creation_timestamp": 1695042069.0, "exchange_order_id": "35232784", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:02:04,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1bc67359b0582. [clock=2023-09-18 13:02:04+00:00] +2023-09-18 13:02:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1bc6739080582. [clock=2023-09-18 13:02:04+00:00] +2023-09-18 13:02:04,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246337978036363553225237297 amount: 80. +2023-09-18 13:02:04,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249120740104958418624593267 amount: 80. +2023-09-18 13:02:04,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042124.0, "order_id": "x-XEKWYICXBSIUT605a1bc67359b0582", "exchange_order_id": "35232783", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:02:04,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1bc67359b0582. +2023-09-18 13:02:04,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042124.0, "order_id": "x-XEKWYICXSSIUT605a1bc6739080582", "exchange_order_id": "35232784", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:02:04,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1bc6739080582. +2023-09-18 13:02:04,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1bfae77260582 for 80.00000000 SEI-USDT. +2023-09-18 13:02:04,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042124.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a1bfae77260582", "creation_timestamp": 1695042124.0, "exchange_order_id": "35233150", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:02:04,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1bfae755f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:02:04,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042124.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a1bfae755f0582", "creation_timestamp": 1695042124.0, "exchange_order_id": "35233151", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:02:59,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1bfae755f0582. [clock=2023-09-18 13:02:59+00:00] +2023-09-18 13:02:59,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1bfae77260582. [clock=2023-09-18 13:02:59+00:00] +2023-09-18 13:02:59,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246337978036363553225237297 amount: 80. +2023-09-18 13:02:59,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249120740104958418624593267 amount: 80. +2023-09-18 13:02:59,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042179.0, "order_id": "x-XEKWYICXBSIUT605a1bfae755f0582", "exchange_order_id": "35233151", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:02:59,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1bfae755f0582. +2023-09-18 13:02:59,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042179.0, "order_id": "x-XEKWYICXSSIUT605a1bfae77260582", "exchange_order_id": "35233150", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:02:59,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1bfae77260582. +2023-09-18 13:02:59,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1c2f5ed8a0582 for 80.00000000 SEI-USDT. +2023-09-18 13:02:59,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042179.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a1c2f5ed8a0582", "creation_timestamp": 1695042179.0, "exchange_order_id": "35233270", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:02:59,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1c2f5f09b0582 for 80.00000000 SEI-USDT. +2023-09-18 13:02:59,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042179.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a1c2f5f09b0582", "creation_timestamp": 1695042179.0, "exchange_order_id": "35233271", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:03:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1c2f5ed8a0582. [clock=2023-09-18 13:03:54+00:00] +2023-09-18 13:03:54,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1c2f5f09b0582. [clock=2023-09-18 13:03:54+00:00] +2023-09-18 13:03:54,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245546863078720495736950629 amount: 80. +2023-09-18 13:03:54,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247979894799444473936225787 amount: 80. +2023-09-18 13:03:54,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "order_id": "x-XEKWYICXBSIUT605a1c2f5ed8a0582", "exchange_order_id": "35233270", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:03:54,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1c2f5ed8a0582. +2023-09-18 13:03:54,851 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1c63d9b940582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:03:54,862 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:03:54+00:00] +2023-09-18 13:03:55,023 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "order_id": "x-XEKWYICXSSIUT605a1c63d9b940582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4960466", "exchange_order_id": "35233573", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:03:55,023 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1c63d9b940582 for 80.00000000 SEI-USDT. +2023-09-18 13:03:55,066 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a1c63d9b940582", "creation_timestamp": 1695042234.0, "exchange_order_id": "35233573", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:03:55,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "order_id": "x-XEKWYICXSSIUT605a1c2f5f09b0582", "exchange_order_id": "35233271", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:03:55,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1c2f5f09b0582. +2023-09-18 13:03:55,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1c63d975a0582 for 80.00000000 SEI-USDT. +2023-09-18 13:03:55,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a1c63d975a0582", "creation_timestamp": 1695042234.0, "exchange_order_id": "35233574", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:03:55,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042234.0, "order_id": "x-XEKWYICXSSIUT605a1c63d9b940582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35233573", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:03:55,218 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1c63d9b940582 completely filled. +2023-09-18 13:04:15,568 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a1c63d975a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:04:15,569 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:04:15+00:00] +2023-09-18 13:04:15,696 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042255.0, "order_id": "x-XEKWYICXBSIUT605a1c63d975a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4960521", "exchange_order_id": "35233574", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:04:15,776 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042255.0, "order_id": "x-XEKWYICXBSIUT605a1c63d975a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35233574", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:04:15,776 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a1c63d975a0582 completely filled. +2023-09-18 13:04:49,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239683700571046364942516679 amount: 80. +2023-09-18 13:04:49,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12440000 amount: 80. +2023-09-18 13:04:49,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1c986fbc60582 for 80.00000000 SEI-USDT. +2023-09-18 13:04:49,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042289.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1c986fbc60582", "creation_timestamp": 1695042289.0, "exchange_order_id": "35234663", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:04:49,806 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1c9879efb0582 for 80.00000000 SEI-USDT. +2023-09-18 13:04:49,833 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042289.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a1c9879efb0582", "creation_timestamp": 1695042289.0, "exchange_order_id": "35234665", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:05:44,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1c986fbc60582. [clock=2023-09-18 13:05:44+00:00] +2023-09-18 13:05:44,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a1c9879efb0582. [clock=2023-09-18 13:05:44+00:00] +2023-09-18 13:05:44,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238130568444775990962184590 amount: 80. +2023-09-18 13:05:44,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12420000 amount: 80. +2023-09-18 13:05:44,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "order_id": "x-XEKWYICXBSIUT605a1c986fbc60582", "exchange_order_id": "35234663", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:05:44,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1c986fbc60582. +2023-09-18 13:05:44,431 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1cccc0cae0582 for 80.00000000 SEI-USDT. +2023-09-18 13:05:44,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a1cccc0cae0582", "creation_timestamp": 1695042344.0, "exchange_order_id": "35235855", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:05:44,453 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1cccc09030582 for 80.00000000 SEI-USDT. +2023-09-18 13:05:44,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a1cccc09030582", "creation_timestamp": 1695042344.0, "exchange_order_id": "35235856", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:05:44,527 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1cccc0cae0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:05:44,529 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:05:44+00:00] +2023-09-18 13:05:44,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "order_id": "x-XEKWYICXSSIUT605a1cccc0cae0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4960826", "exchange_order_id": "35235855", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:05:44,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "order_id": "x-XEKWYICXSSIUT605a1c9879efb0582", "exchange_order_id": "35234665", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:05:44,561 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a1c9879efb0582. +2023-09-18 13:05:44,623 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042344.0, "order_id": "x-XEKWYICXSSIUT605a1cccc0cae0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35235855", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:05:44,624 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1cccc0cae0582 completely filled. +2023-09-18 13:06:39,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1cccc09030582. [clock=2023-09-18 13:06:39+00:00] +2023-09-18 13:06:39,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238114649607491520888741459 amount: 80. +2023-09-18 13:06:39,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12420000 amount: 80. +2023-09-18 13:06:39,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042399.0, "order_id": "x-XEKWYICXBSIUT605a1cccc09030582", "exchange_order_id": "35235856", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:06:39,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1cccc09030582. +2023-09-18 13:06:39,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1d012c1300582 for 80.00000000 SEI-USDT. +2023-09-18 13:06:39,577 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042399.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a1d012c1300582", "creation_timestamp": 1695042399.0, "exchange_order_id": "35236563", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:06:39,733 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a1d012c3520582 for 80.00000000 SEI-USDT. +2023-09-18 13:06:39,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042399.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a1d012c3520582", "creation_timestamp": 1695042399.0, "exchange_order_id": "35236565", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:06:55,104 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a1d012c3520582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:06:55,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:06:55+00:00] +2023-09-18 13:06:55,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042415.0, "order_id": "x-XEKWYICXSSIUT605a1d012c3520582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4960961", "exchange_order_id": "35236565", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:06:55,350 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042415.0, "order_id": "x-XEKWYICXSSIUT605a1d012c3520582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35236565", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:06:55,350 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a1d012c3520582 completely filled. +2023-09-18 13:07:35,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1d012c1300582. [clock=2023-09-18 13:07:35+00:00] +2023-09-18 13:07:35,343 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237100723178820336232144209 amount: 80. +2023-09-18 13:07:35,343 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:07:37,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042456.0, "order_id": "x-XEKWYICXBSIUT605a1d012c1300582", "exchange_order_id": "35236563", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:07:37,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1d012c1300582. +2023-09-18 13:07:37,665 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1d36e122d0582 for 80.00000000 SEI-USDT. +2023-09-18 13:07:37,706 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042457.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a1d36e122d0582", "creation_timestamp": 1695042455.0, "exchange_order_id": "35237907", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:08:30,357 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1d36e122d0582. [clock=2023-09-18 13:08:30+00:00] +2023-09-18 13:08:30,435 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237116609589223562964485012 amount: 80. +2023-09-18 13:08:30,436 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:08:30,882 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042510.0, "order_id": "x-XEKWYICXBSIUT605a1d36e122d0582", "exchange_order_id": "35237907", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:08:30,882 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1d36e122d0582. +2023-09-18 13:08:32,561 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1d6b6b7e40582 for 80.00000000 SEI-USDT. +2023-09-18 13:08:32,604 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042512.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a1d6b6b7e40582", "creation_timestamp": 1695042510.0, "exchange_order_id": "35238676", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:09:25,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1d6b6b7e40582. [clock=2023-09-18 13:09:25+00:00] +2023-09-18 13:09:25,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236886775113712312965859945 amount: 80. +2023-09-18 13:09:25,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:09:25,932 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042565.0, "order_id": "x-XEKWYICXBSIUT605a1d6b6b7e40582", "exchange_order_id": "35238676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:09:25,932 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1d6b6b7e40582. +2023-09-18 13:09:27,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1d9fac8f60582 for 80.00000000 SEI-USDT. +2023-09-18 13:09:27,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042567.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a1d9fac8f60582", "creation_timestamp": 1695042565.0, "exchange_order_id": "35239048", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:10:09,942 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:10:10,017 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042610.0, "order_id": "x-XEKWYICXBSIUT605a1d9fac8f60582", "exchange_order_id": "35239048", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:10:10,017 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1d9fac8f60582. +2023-09-18 13:10:52,607 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:10:52,617 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:10:52,818 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:10:52,903 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:10:53,013 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:10:53,616 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:10:53,622 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:10:53,630 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:10:54,221 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:10:54,224 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:10:54,991 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:11:00,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235209774516631179015965246 amount: 80. +2023-09-18 13:11:00,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:11:00,061 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1dfa119780582 for 80.00000000 SEI-USDT. +2023-09-18 13:11:00,076 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042660.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a1dfa119780582", "creation_timestamp": 1695042660.0, "exchange_order_id": "35239812", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:11:22,939 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:11:23,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042683.0, "order_id": "x-XEKWYICXBSIUT605a1dfa119780582", "exchange_order_id": "35239812", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:11:23,060 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1dfa119780582. +2023-09-18 13:12:25,799 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:12:25,803 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:12:25,952 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:12:26,021 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:12:26,084 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:12:26,272 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:12:26,286 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:12:26,445 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:12:26,584 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:12:27,160 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:12:27,909 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:12:35,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235312429022394418514049834 amount: 80. +2023-09-18 13:12:35,231 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:12:35,825 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1e54dffb70582 for 80.00000000 SEI-USDT. +2023-09-18 13:12:35,946 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042755.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a1e54dffb70582", "creation_timestamp": 1695042755.0, "exchange_order_id": "35240453", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:13:30,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1e54dffb70582. [clock=2023-09-18 13:13:30+00:00] +2023-09-18 13:13:30,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238731638116169274526865267 amount: 80. +2023-09-18 13:13:30,177 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:13:32,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042811.0, "order_id": "x-XEKWYICXBSIUT605a1e54dffb70582", "exchange_order_id": "35240453", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:13:32,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1e54dffb70582. +2023-09-18 13:13:32,859 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1e89468850582 for 80.00000000 SEI-USDT. +2023-09-18 13:13:32,917 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042812.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a1e89468850582", "creation_timestamp": 1695042810.0, "exchange_order_id": "35241036", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:14:25,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1e89468850582. [clock=2023-09-18 13:14:25+00:00] +2023-09-18 13:14:25,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239457719837917488464337969 amount: 80. +2023-09-18 13:14:25,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:14:25,890 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042865.0, "order_id": "x-XEKWYICXBSIUT605a1e89468850582", "exchange_order_id": "35241036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:14:25,891 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1e89468850582. +2023-09-18 13:14:27,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1ebdbf8340582 for 80.00000000 SEI-USDT. +2023-09-18 13:14:27,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042867.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1ebdbf8340582", "creation_timestamp": 1695042865.0, "exchange_order_id": "35241341", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:15:20,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1ebdbf8340582. [clock=2023-09-18 13:15:20+00:00] +2023-09-18 13:15:20,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240022537342626222861759493 amount: 80. +2023-09-18 13:15:20,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:15:20,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042920.0, "order_id": "x-XEKWYICXBSIUT605a1ebdbf8340582", "exchange_order_id": "35241341", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:15:20,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1ebdbf8340582. +2023-09-18 13:15:20,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1ef206a510582 for 80.00000000 SEI-USDT. +2023-09-18 13:15:20,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042920.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a1ef206a510582", "creation_timestamp": 1695042920.0, "exchange_order_id": "35241497", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:16:15,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1ef206a510582. [clock=2023-09-18 13:16:15+00:00] +2023-09-18 13:16:15,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241380041190387157791279630 amount: 80. +2023-09-18 13:16:15,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:16:15,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042975.0, "order_id": "x-XEKWYICXBSIUT605a1ef206a510582", "exchange_order_id": "35241497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:16:15,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1ef206a510582. +2023-09-18 13:16:15,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1f267cd010582 for 80.00000000 SEI-USDT. +2023-09-18 13:16:15,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695042975.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a1f267cd010582", "creation_timestamp": 1695042975.0, "exchange_order_id": "35241684", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:17:10,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1f267cd010582. [clock=2023-09-18 13:17:10+00:00] +2023-09-18 13:17:10,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239295035536136498668734211 amount: 80. +2023-09-18 13:17:10,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:17:10,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043030.0, "order_id": "x-XEKWYICXBSIUT605a1f267cd010582", "exchange_order_id": "35241684", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:17:10,582 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1f267cd010582. +2023-09-18 13:17:11,928 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1f5b1656a0582 for 80.00000000 SEI-USDT. +2023-09-18 13:17:11,958 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a1f5b1656a0582", "creation_timestamp": 1695043030.0, "exchange_order_id": "35241886", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:18:05,337 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1f5b1656a0582. [clock=2023-09-18 13:18:05+00:00] +2023-09-18 13:18:05,409 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237558462468559580950274213 amount: 80. +2023-09-18 13:18:05,444 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:18:06,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043085.0, "order_id": "x-XEKWYICXBSIUT605a1f5b1656a0582", "exchange_order_id": "35241886", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:18:06,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1f5b1656a0582. +2023-09-18 13:18:06,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1f8fca3150582 for 80.00000000 SEI-USDT. +2023-09-18 13:18:06,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043085.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a1f8fca3150582", "creation_timestamp": 1695043085.0, "exchange_order_id": "35242625", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:19:00,277 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1f8fca3150582. [clock=2023-09-18 13:19:00+00:00] +2023-09-18 13:19:00,334 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235929945574035653634120748 amount: 80. +2023-09-18 13:19:00,335 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:19:00,938 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043140.0, "order_id": "x-XEKWYICXBSIUT605a1f8fca3150582", "exchange_order_id": "35242625", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:19:00,938 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1f8fca3150582. +2023-09-18 13:19:02,553 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1fc4238360582 for 80.00000000 SEI-USDT. +2023-09-18 13:19:02,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043142.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a1fc4238360582", "creation_timestamp": 1695043140.0, "exchange_order_id": "35243165", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:19:55,153 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1fc4238360582. [clock=2023-09-18 13:19:55+00:00] +2023-09-18 13:19:55,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237151167322830144330135346 amount: 80. +2023-09-18 13:19:55,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:19:55,648 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043195.0, "order_id": "x-XEKWYICXBSIUT605a1fc4238360582", "exchange_order_id": "35243165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:19:55,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1fc4238360582. +2023-09-18 13:19:57,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a1ff873a490582 for 80.00000000 SEI-USDT. +2023-09-18 13:19:57,535 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043197.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a1ff873a490582", "creation_timestamp": 1695043195.0, "exchange_order_id": "35243750", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:20:50,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a1ff873a490582. [clock=2023-09-18 13:20:50+00:00] +2023-09-18 13:20:50,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237784015955327352114788338 amount: 80. +2023-09-18 13:20:50,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:20:50,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043250.0, "order_id": "x-XEKWYICXBSIUT605a1ff873a490582", "exchange_order_id": "35243750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:20:50,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a1ff873a490582. +2023-09-18 13:20:50,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a202cbcc2d0582 for 80.00000000 SEI-USDT. +2023-09-18 13:20:50,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043250.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a202cbcc2d0582", "creation_timestamp": 1695043250.0, "exchange_order_id": "35243880", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:21:45,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a202cbcc2d0582. [clock=2023-09-18 13:21:45+00:00] +2023-09-18 13:21:45,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238164369156998001291729919 amount: 80. +2023-09-18 13:21:45,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:21:45,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043305.0, "order_id": "x-XEKWYICXBSIUT605a202cbcc2d0582", "exchange_order_id": "35243880", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:21:45,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a202cbcc2d0582. +2023-09-18 13:21:45,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2061344280582 for 80.00000000 SEI-USDT. +2023-09-18 13:21:45,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043305.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a2061344280582", "creation_timestamp": 1695043305.0, "exchange_order_id": "35244055", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:22:40,633 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2061344280582. [clock=2023-09-18 13:22:40+00:00] +2023-09-18 13:22:40,756 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239508084052559471529301871 amount: 80. +2023-09-18 13:22:40,776 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:22:42,689 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043361.0, "order_id": "x-XEKWYICXBSIUT605a2061344280582", "exchange_order_id": "35244055", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:22:42,690 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2061344280582. +2023-09-18 13:22:44,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a20965e2320582 for 80.00000000 SEI-USDT. +2023-09-18 13:22:44,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043364.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a20965e2320582", "creation_timestamp": 1695043360.0, "exchange_order_id": "35244226", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:23:35,789 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a20965e2320582. [clock=2023-09-18 13:23:35+00:00] +2023-09-18 13:23:35,922 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237691642328045123838230520 amount: 80. +2023-09-18 13:23:35,936 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:23:37,449 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043416.0, "order_id": "x-XEKWYICXBSIUT605a20965e2320582", "exchange_order_id": "35244226", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:23:37,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a20965e2320582. +2023-09-18 13:23:39,546 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a20caf8d570582 for 80.00000000 SEI-USDT. +2023-09-18 13:23:39,605 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043418.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a20caf8d570582", "creation_timestamp": 1695043415.0, "exchange_order_id": "35244494", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:24:30,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a20caf8d570582. [clock=2023-09-18 13:24:30+00:00] +2023-09-18 13:24:30,134 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235164116623599191243691769 amount: 80. +2023-09-18 13:24:30,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:24:30,765 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043470.0, "order_id": "x-XEKWYICXBSIUT605a20caf8d570582", "exchange_order_id": "35244494", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:24:30,765 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a20caf8d570582. +2023-09-18 13:24:31,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a20fea90780582 for 80.00000000 SEI-USDT. +2023-09-18 13:24:31,461 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a20fea90780582", "creation_timestamp": 1695043470.0, "exchange_order_id": "35244962", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:25:25,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a20fea90780582. [clock=2023-09-18 13:25:25+00:00] +2023-09-18 13:25:25,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234088869519972608228629955 amount: 80. +2023-09-18 13:25:25,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:25:25,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043525.0, "order_id": "x-XEKWYICXBSIUT605a20fea90780582", "exchange_order_id": "35244962", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:25:25,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a20fea90780582. +2023-09-18 13:25:25,448 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2133116070582 for 80.00000000 SEI-USDT. +2023-09-18 13:25:25,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043525.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a2133116070582", "creation_timestamp": 1695043525.0, "exchange_order_id": "35245364", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:26:20,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2133116070582. [clock=2023-09-18 13:26:20+00:00] +2023-09-18 13:26:20,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234957474872913106857740057 amount: 80. +2023-09-18 13:26:20,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:26:20,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043580.0, "order_id": "x-XEKWYICXBSIUT605a2133116070582", "exchange_order_id": "35245364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:26:20,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2133116070582. +2023-09-18 13:26:20,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a21678699e0582 for 80.00000000 SEI-USDT. +2023-09-18 13:26:20,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043580.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a21678699e0582", "creation_timestamp": 1695043580.0, "exchange_order_id": "35245506", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:27:10,416 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:27:11,074 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043630.0, "order_id": "x-XEKWYICXBSIUT605a21678699e0582", "exchange_order_id": "35245506", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:27:11,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a21678699e0582. +2023-09-18 13:30:19,945 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:30:19,948 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:30:20,044 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:30:20,104 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:30:20,148 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:30:20,278 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:30:20,290 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:30:20,459 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:30:20,501 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:30:21,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:30:21,657 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:30:24,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231712671659272714097621799 amount: 80. +2023-09-18 13:30:24,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:30:24,062 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a22502563f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:30:24,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043824.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a22502563f0582", "creation_timestamp": 1695043824.0, "exchange_order_id": "35246646", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:31:19,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a22502563f0582. [clock=2023-09-18 13:31:19+00:00] +2023-09-18 13:31:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230485122415443783416347924 amount: 80. +2023-09-18 13:31:19,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:31:19,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043879.0, "order_id": "x-XEKWYICXBSIUT605a22502563f0582", "exchange_order_id": "35246646", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:31:19,079 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a22502563f0582. +2023-09-18 13:31:19,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a228499ac90582 for 80.00000000 SEI-USDT. +2023-09-18 13:31:19,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043879.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605a228499ac90582", "creation_timestamp": 1695043879.0, "exchange_order_id": "35247283", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:32:14,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a228499ac90582. [clock=2023-09-18 13:32:14+00:00] +2023-09-18 13:32:14,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231484872114453297568479936 amount: 80. +2023-09-18 13:32:14,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:32:14,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043934.0, "order_id": "x-XEKWYICXBSIUT605a228499ac90582", "exchange_order_id": "35247283", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:32:14,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a228499ac90582. +2023-09-18 13:32:14,091 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a22b90ce210582 for 80.00000000 SEI-USDT. +2023-09-18 13:32:14,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043934.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a22b90ce210582", "creation_timestamp": 1695043934.0, "exchange_order_id": "35247911", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:33:09,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a22b90ce210582. [clock=2023-09-18 13:33:09+00:00] +2023-09-18 13:33:09,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233290714972419923638604600 amount: 80. +2023-09-18 13:33:09,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:33:09,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043989.0, "order_id": "x-XEKWYICXBSIUT605a22b90ce210582", "exchange_order_id": "35247911", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:33:09,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a22b90ce210582. +2023-09-18 13:33:09,351 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a22ed852ae0582 for 80.00000000 SEI-USDT. +2023-09-18 13:33:09,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695043989.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a22ed852ae0582", "creation_timestamp": 1695043989.0, "exchange_order_id": "35248356", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:34:04,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a22ed852ae0582. [clock=2023-09-18 13:34:04+00:00] +2023-09-18 13:34:04,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233975901366077608507390218 amount: 80. +2023-09-18 13:34:04,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:34:04,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044044.0, "order_id": "x-XEKWYICXBSIUT605a22ed852ae0582", "exchange_order_id": "35248356", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:34:04,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a22ed852ae0582. +2023-09-18 13:34:04,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2321f672f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:34:04,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044044.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a2321f672f0582", "creation_timestamp": 1695044044.0, "exchange_order_id": "35248808", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:34:59,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2321f672f0582. [clock=2023-09-18 13:34:59+00:00] +2023-09-18 13:34:59,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234972237625744815112133896 amount: 80. +2023-09-18 13:34:59,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:34:59,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044099.0, "order_id": "x-XEKWYICXBSIUT605a2321f672f0582", "exchange_order_id": "35248808", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:34:59,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2321f672f0582. +2023-09-18 13:34:59,860 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a23567f45e0582 for 80.00000000 SEI-USDT. +2023-09-18 13:34:59,892 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044099.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a23567f45e0582", "creation_timestamp": 1695044099.0, "exchange_order_id": "35249140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:35:54,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a23567f45e0582. [clock=2023-09-18 13:35:54+00:00] +2023-09-18 13:35:54,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234307343282873085031050436 amount: 80. +2023-09-18 13:35:54,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:35:54,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044154.0, "order_id": "x-XEKWYICXBSIUT605a23567f45e0582", "exchange_order_id": "35249140", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:35:54,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a23567f45e0582. +2023-09-18 13:35:54,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a238add0430582 for 80.00000000 SEI-USDT. +2023-09-18 13:35:54,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044154.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a238add0430582", "creation_timestamp": 1695044154.0, "exchange_order_id": "35249319", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:36:49,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a238add0430582. [clock=2023-09-18 13:36:49+00:00] +2023-09-18 13:36:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236432426665908699507297237 amount: 80. +2023-09-18 13:36:49,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:36:49,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044209.0, "order_id": "x-XEKWYICXBSIUT605a238add0430582", "exchange_order_id": "35249319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:36:49,079 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a238add0430582. +2023-09-18 13:36:49,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a23bf5038f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:36:49,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044209.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a23bf5038f0582", "creation_timestamp": 1695044209.0, "exchange_order_id": "35249652", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:37:44,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a23bf5038f0582. [clock=2023-09-18 13:37:44+00:00] +2023-09-18 13:37:44,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237003343439673971551886729 amount: 80. +2023-09-18 13:37:44,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:37:44,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044264.0, "order_id": "x-XEKWYICXBSIUT605a23bf5038f0582", "exchange_order_id": "35249652", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:37:44,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a23bf5038f0582. +2023-09-18 13:37:44,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a23f3c5e570582 for 80.00000000 SEI-USDT. +2023-09-18 13:37:44,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044264.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a23f3c5e570582", "creation_timestamp": 1695044264.0, "exchange_order_id": "35249784", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:38:39,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a23f3c5e570582. [clock=2023-09-18 13:38:39+00:00] +2023-09-18 13:38:39,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237929029092421777020049457 amount: 80. +2023-09-18 13:38:39,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:38:39,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044319.0, "order_id": "x-XEKWYICXBSIUT605a23f3c5e570582", "exchange_order_id": "35249784", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:38:39,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a23f3c5e570582. +2023-09-18 13:38:39,708 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a242844b4f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:38:39,747 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044319.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a242844b4f0582", "creation_timestamp": 1695044319.0, "exchange_order_id": "35250052", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:39:34,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a242844b4f0582. [clock=2023-09-18 13:39:34+00:00] +2023-09-18 13:39:34,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237390767951521061275857439 amount: 80. +2023-09-18 13:39:34,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:39:34,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044374.0, "order_id": "x-XEKWYICXBSIUT605a242844b4f0582", "exchange_order_id": "35250052", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:39:34,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a242844b4f0582. +2023-09-18 13:39:34,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a245cb129a0582 for 80.00000000 SEI-USDT. +2023-09-18 13:39:34,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044374.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a245cb129a0582", "creation_timestamp": 1695044374.0, "exchange_order_id": "35250255", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:39:42,208 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:39:42,937 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044382.0, "order_id": "x-XEKWYICXBSIUT605a245cb129a0582", "exchange_order_id": "35250255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:39:42,938 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a245cb129a0582. +2023-09-18 13:40:39,796 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:40:39,800 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:40:39,940 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:40:40,000 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:40:40,002 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:40:40,414 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:40:40,423 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:40:40,425 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:40:40,426 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:40:41,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:40:41,996 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:40:43,601 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: unsupported operand type(s) for -: 'float' and 'decimal.Decimal' +Traceback (most recent call last): + File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper + return await c + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 143, in status_check_all + await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 0.1 + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 77, in strategy_status + st_status = self.strategy.format_status() + File "/home/hummingbot/scripts/bot_battle.py", line 175, in format_status + inventory_delta = (target_ratio - inventory_ratio) / inventory_ratio +TypeError: unsupported operand type(s) for -: 'float' and 'decimal.Decimal' +2023-09-18 13:40:54,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238344613905867989372951633 amount: 80. +2023-09-18 13:40:54,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:40:54,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a24a8fc1910582 for 80.00000000 SEI-USDT. +2023-09-18 13:40:54,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044454.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a24a8fc1910582", "creation_timestamp": 1695044454.0, "exchange_order_id": "35250859", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:40:54,238 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:40:54,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044454.0, "order_id": "x-XEKWYICXBSIUT605a24a8fc1910582", "exchange_order_id": "35250859", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:40:54,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a24a8fc1910582. +2023-09-18 13:43:09,544 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:43:12,974 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:43:12,988 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:43:13,008 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:14,014 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:14,365 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:43:14,955 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:43:15,205 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:43:15,206 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:16,350 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:16,575 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:43:17,154 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:43:18,062 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:18,639 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:43:19,485 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:20,168 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:43:21,341 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:43:22,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231763516693128642537464134 amount: 80. +2023-09-18 13:43:22,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:43:22,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a253635de00582 for 80.00000000 SEI-USDT. +2023-09-18 13:43:22,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044602.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a253635de00582", "creation_timestamp": 1695044602.0, "exchange_order_id": "35252172", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:43:26,736 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: unsupported operand type(s) for *: 'decimal.Decimal' and 'float' +Traceback (most recent call last): + File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper + return await c + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 148, in status_check_all + self.notify(await self.strategy_status()) + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 77, in strategy_status + st_status = self.strategy.format_status() + File "/home/hummingbot/scripts/bot_battle.py", line 176, in format_status + inventory_multiplier = inventory_delta * self.spread +TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float' +2023-09-18 13:44:17,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a253635de00582. [clock=2023-09-18 13:44:17+00:00] +2023-09-18 13:44:17,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229184054103599688961116739 amount: 80. +2023-09-18 13:44:17,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:44:17,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044657.0, "order_id": "x-XEKWYICXBSIUT605a253635de00582", "exchange_order_id": "35252172", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:44:17,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a253635de00582. +2023-09-18 13:44:17,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a256a92df70582 for 80.00000000 SEI-USDT. +2023-09-18 13:44:17,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044657.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605a256a92df70582", "creation_timestamp": 1695044657.0, "exchange_order_id": "35252545", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:45:12,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a256a92df70582. [clock=2023-09-18 13:45:12+00:00] +2023-09-18 13:45:12,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230215170608402864498295654 amount: 80. +2023-09-18 13:45:12,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:45:12,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044712.0, "order_id": "x-XEKWYICXBSIUT605a256a92df70582", "exchange_order_id": "35252545", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:45:12,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a256a92df70582. +2023-09-18 13:45:12,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a259f0a3710582 for 80.00000000 SEI-USDT. +2023-09-18 13:45:12,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044712.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605a259f0a3710582", "creation_timestamp": 1695044712.0, "exchange_order_id": "35252815", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:45:16,210 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:45:16,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044716.0, "order_id": "x-XEKWYICXBSIUT605a259f0a3710582", "exchange_order_id": "35252815", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:45:16,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a259f0a3710582. +2023-09-18 13:45:17,973 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:45:17,978 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:45:18,114 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:45:18,195 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:45:18,388 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:45:18,390 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:45:18,392 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:45:18,738 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:45:19,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:45:19,760 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:45:25,770 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: 'PMMhShiftedMidPriceDynamicSpread' object has no attribute 'inventory_multiplier' +Traceback (most recent call last): + File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper + return await c + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 148, in status_check_all + self.notify(await self.strategy_status()) + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 77, in strategy_status + st_status = self.strategy.format_status() + File "/home/hummingbot/scripts/bot_battle.py", line 184, in format_status + lines.extend(["", f" Current Inventory Ratio: {inventory_ratio:.4f} | Target Inventory Ratio: {target_ratio:.4f} | Inventory Price Shift: {self.inventory_multiplier:.4f}"]) +AttributeError: 'PMMhShiftedMidPriceDynamicSpread' object has no attribute 'inventory_multiplier' +2023-09-18 13:45:27,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232641333062878621574016024 amount: 80. +2023-09-18 13:45:27,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:45:27,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a25ad50a360582 for 80.00000000 SEI-USDT. +2023-09-18 13:45:27,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044727.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a25ad50a360582", "creation_timestamp": 1695044727.0, "exchange_order_id": "35252924", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:45:45,034 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:45:45,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044745.0, "order_id": "x-XEKWYICXBSIUT605a25ad50a360582", "exchange_order_id": "35252924", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:45:45,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a25ad50a360582. +2023-09-18 13:46:00,042 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:46:00,045 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:46:00,169 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:46:00,250 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:46:00,441 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:46:00,445 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:46:00,447 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:46:00,759 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:46:01,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:46:01,983 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:46:03,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229242411841407303493547800 amount: 80. +2023-09-18 13:46:03,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:46:03,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a25cfa57590582 for 80.00000000 SEI-USDT. +2023-09-18 13:46:03,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044763.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605a25cfa57590582", "creation_timestamp": 1695044763.0, "exchange_order_id": "35253110", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:46:58,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a25cfa57590582. [clock=2023-09-18 13:46:58+00:00] +2023-09-18 13:46:58,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231056082191453260594795863 amount: 80. +2023-09-18 13:46:58,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:46:58,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044818.0, "order_id": "x-XEKWYICXBSIUT605a25cfa57590582", "exchange_order_id": "35253110", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:46:58,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a25cfa57590582. +2023-09-18 13:46:58,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26041a41e0582 for 80.00000000 SEI-USDT. +2023-09-18 13:46:58,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044818.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a26041a41e0582", "creation_timestamp": 1695044818.0, "exchange_order_id": "35253424", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:47:53,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a26041a41e0582. [clock=2023-09-18 13:47:53+00:00] +2023-09-18 13:47:53,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231551722620995059840413308 amount: 80. +2023-09-18 13:47:53,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:47:53,334 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044873.0, "order_id": "x-XEKWYICXBSIUT605a26041a41e0582", "exchange_order_id": "35253424", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:47:53,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26041a41e0582. +2023-09-18 13:47:53,902 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26389f0940582 for 80.00000000 SEI-USDT. +2023-09-18 13:47:53,943 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044873.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a26389f0940582", "creation_timestamp": 1695044873.0, "exchange_order_id": "35253836", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:48:05,255 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:48:05,850 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044885.0, "order_id": "x-XEKWYICXBSIUT605a26389f0940582", "exchange_order_id": "35253836", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:48:05,850 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26389f0940582. +2023-09-18 13:48:14,909 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:48:14,929 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:48:15,725 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:48:16,010 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:48:16,020 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:48:16,268 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:48:17,353 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:48:17,683 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:48:17,891 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:48:18,062 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:48:18,244 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:48:19,508 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:48:22,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234311443331218039476749747 amount: 80. +2023-09-18 13:48:22,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:48:22,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26544aa360582 for 80.00000000 SEI-USDT. +2023-09-18 13:48:22,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044902.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a26544aa360582", "creation_timestamp": 1695044902.0, "exchange_order_id": "35254170", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:49:17,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a26544aa360582. [clock=2023-09-18 13:49:17+00:00] +2023-09-18 13:49:17,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232619432571387358197322861 amount: 80. +2023-09-18 13:49:17,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:49:17,673 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044957.0, "order_id": "x-XEKWYICXBSIUT605a26544aa360582", "exchange_order_id": "35254170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:49:17,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26544aa360582. +2023-09-18 13:49:18,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2688cc0540582 for 80.00000000 SEI-USDT. +2023-09-18 13:49:18,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695044958.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a2688cc0540582", "creation_timestamp": 1695044957.0, "exchange_order_id": "35254430", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:50:12,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2688cc0540582. [clock=2023-09-18 13:50:12+00:00] +2023-09-18 13:50:12,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234113335873568208688717632 amount: 80. +2023-09-18 13:50:12,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:50:12,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26bd1d1e30582 for 80.00000000 SEI-USDT. +2023-09-18 13:50:12,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045012.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a26bd1d1e30582", "creation_timestamp": 1695045012.0, "exchange_order_id": "35254838", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:50:12,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045012.0, "order_id": "x-XEKWYICXBSIUT605a2688cc0540582", "exchange_order_id": "35254430", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:50:12,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2688cc0540582. +2023-09-18 13:51:07,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a26bd1d1e30582. [clock=2023-09-18 13:51:07+00:00] +2023-09-18 13:51:07,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243990617378122548612012952 amount: 80. +2023-09-18 13:51:07,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:51:07,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045067.0, "order_id": "x-XEKWYICXBSIUT605a26bd1d1e30582", "exchange_order_id": "35254838", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:51:07,079 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26bd1d1e30582. +2023-09-18 13:51:07,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26f1903770582 for 80.00000000 SEI-USDT. +2023-09-18 13:51:07,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045067.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a26f1903770582", "creation_timestamp": 1695045067.0, "exchange_order_id": "35256167", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:51:09,240 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:51:09,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045069.0, "order_id": "x-XEKWYICXBSIUT605a26f1903770582", "exchange_order_id": "35256167", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:51:09,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26f1903770582. +2023-09-18 13:51:11,336 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:51:11,340 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:51:11,493 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:51:11,549 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:51:11,726 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:51:11,739 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:51:12,035 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:51:12,039 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:51:12,040 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:51:13,249 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:51:15,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243988715327084591490755491 amount: 80. +2023-09-18 13:51:15,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:51:15,064 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a26f931a590582 for 80.00000000 SEI-USDT. +2023-09-18 13:51:15,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045075.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a26f931a590582", "creation_timestamp": 1695045075.0, "exchange_order_id": "35256203", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:51:16,974 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: unsupported operand type(s) for *: 'float' and 'decimal.Decimal' +Traceback (most recent call last): + File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper + return await c + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 148, in status_check_all + self.notify(await self.strategy_status()) + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 77, in strategy_status + st_status = self.strategy.format_status() + File "/home/hummingbot/scripts/bot_battle.py", line 176, in format_status + inventory_multiplier = inventory_delta * Decimal(self.spread) +TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal' +2023-09-18 13:52:08,088 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 13:52:08,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045128.0, "order_id": "x-XEKWYICXBSIUT605a26f931a590582", "exchange_order_id": "35256203", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:52:08,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a26f931a590582. +2023-09-18 13:52:09,699 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 13:52:09,703 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 13:52:09,887 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:52:09,986 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:52:10,074 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:52:10,242 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 13:52:10,246 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 13:52:10,248 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 13:52:10,571 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 13:52:11,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 13:52:11,269 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 13:52:13,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243986981148256846166488366 amount: 80. +2023-09-18 13:52:13,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:52:13,076 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a273081c670582 for 80.00000000 SEI-USDT. +2023-09-18 13:52:13,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045133.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a273081c670582", "creation_timestamp": 1695045133.0, "exchange_order_id": "35257854", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:53:08,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a273081c670582. [clock=2023-09-18 13:53:08+00:00] +2023-09-18 13:53:08,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240900209546418967464185554 amount: 80. +2023-09-18 13:53:08,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:53:08,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045188.0, "order_id": "x-XEKWYICXBSIUT605a273081c670582", "exchange_order_id": "35257854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:53:08,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a273081c670582. +2023-09-18 13:53:08,590 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2764f98380582 for 80.00000000 SEI-USDT. +2023-09-18 13:53:08,618 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045188.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a2764f98380582", "creation_timestamp": 1695045188.0, "exchange_order_id": "35258386", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:54:03,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2764f98380582. [clock=2023-09-18 13:54:03+00:00] +2023-09-18 13:54:03,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247971118733856875419570231 amount: 80. +2023-09-18 13:54:03,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:54:03,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045243.0, "order_id": "x-XEKWYICXBSIUT605a2764f98380582", "exchange_order_id": "35258386", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:54:03,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2764f98380582. +2023-09-18 13:54:03,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2799826130582 for 80.00000000 SEI-USDT. +2023-09-18 13:54:03,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045243.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a2799826130582", "creation_timestamp": 1695045243.0, "exchange_order_id": "35261166", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:54:58,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2799826130582. [clock=2023-09-18 13:54:58+00:00] +2023-09-18 13:54:58,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249964411099540449736873670 amount: 80. +2023-09-18 13:54:58,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:54:58,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045298.0, "order_id": "x-XEKWYICXBSIUT605a2799826130582", "exchange_order_id": "35261166", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:54:58,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2799826130582. +2023-09-18 13:54:58,728 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a27cdf26f60582 for 80.00000000 SEI-USDT. +2023-09-18 13:54:58,782 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045298.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a27cdf26f60582", "creation_timestamp": 1695045298.0, "exchange_order_id": "35262447", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:55:53,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a27cdf26f60582. [clock=2023-09-18 13:55:53+00:00] +2023-09-18 13:55:53,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249972586890864006747671827 amount: 80. +2023-09-18 13:55:53,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:55:53,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045353.0, "order_id": "x-XEKWYICXBSIUT605a27cdf26f60582", "exchange_order_id": "35262447", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:55:53,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a27cdf26f60582. +2023-09-18 13:55:53,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2802505000582 for 80.00000000 SEI-USDT. +2023-09-18 13:55:53,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045353.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a2802505000582", "creation_timestamp": 1695045353.0, "exchange_order_id": "35262855", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:56:06,507 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a2802505000582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 13:56:06,508 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 13:56:06+00:00] +2023-09-18 13:56:06,541 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045366.0, "order_id": "x-XEKWYICXBSIUT605a2802505000582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4964173", "exchange_order_id": "35262855", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 13:56:06,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045366.0, "order_id": "x-XEKWYICXBSIUT605a2802505000582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35262855", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 13:56:06,557 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a2802505000582 completely filled. +2023-09-18 13:56:48,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247975299739059047040157066 amount: 80. +2023-09-18 13:56:48,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259128920297357368959842934 amount: 80. +2023-09-18 13:56:48,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2836c45b70582 for 80.00000000 SEI-USDT. +2023-09-18 13:56:48,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045408.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a2836c45b70582", "creation_timestamp": 1695045408.0, "exchange_order_id": "35263571", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:56:48,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2836c4c0c0582 for 80.00000000 SEI-USDT. +2023-09-18 13:56:48,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045408.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a2836c4c0c0582", "creation_timestamp": 1695045408.0, "exchange_order_id": "35263572", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:57:43,130 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2836c45b70582. [clock=2023-09-18 13:57:43+00:00] +2023-09-18 13:57:43,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2836c4c0c0582. [clock=2023-09-18 13:57:43+00:00] +2023-09-18 13:57:43,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250971379627635138011052964 amount: 80. +2023-09-18 13:57:43,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:57:43,866 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045463.0, "order_id": "x-XEKWYICXBSIUT605a2836c45b70582", "exchange_order_id": "35263571", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:57:43,866 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2836c45b70582. +2023-09-18 13:57:45,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045465.0, "order_id": "x-XEKWYICXSSIUT605a2836c4c0c0582", "exchange_order_id": "35263572", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:57:45,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2836c4c0c0582. +2023-09-18 13:57:46,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a286b604d90582 for 80.00000000 SEI-USDT. +2023-09-18 13:57:46,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045465.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a286b604d90582", "creation_timestamp": 1695045463.0, "exchange_order_id": "35264218", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:58:38,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a286b604d90582. [clock=2023-09-18 13:58:38+00:00] +2023-09-18 13:58:38,286 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249977357382107397462176752 amount: 80. +2023-09-18 13:58:38,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260662806595139352537823248 amount: 80. +2023-09-18 13:58:39,748 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045518.0, "order_id": "x-XEKWYICXBSIUT605a286b604d90582", "exchange_order_id": "35264218", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:58:39,748 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a286b604d90582. +2023-09-18 13:58:41,867 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a289fee35a0582 for 80.00000000 SEI-USDT. +2023-09-18 13:58:41,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a289fee35a0582", "creation_timestamp": 1695045518.0, "exchange_order_id": "35264555", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:58:42,584 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a289fee76f0582 for 80.00000000 SEI-USDT. +2023-09-18 13:58:42,622 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045522.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a289fee76f0582", "creation_timestamp": 1695045518.0, "exchange_order_id": "35264558", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 13:59:33,259 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a289fee35a0582. [clock=2023-09-18 13:59:33+00:00] +2023-09-18 13:59:33,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a289fee76f0582. [clock=2023-09-18 13:59:33+00:00] +2023-09-18 13:59:33,315 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246816504954032307310252348 amount: 80. +2023-09-18 13:59:33,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 13:59:34,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045573.0, "order_id": "x-XEKWYICXBSIUT605a289fee35a0582", "exchange_order_id": "35264555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:59:34,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a289fee35a0582. +2023-09-18 13:59:35,850 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045575.0, "order_id": "x-XEKWYICXSSIUT605a289fee76f0582", "exchange_order_id": "35264558", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 13:59:35,850 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a289fee76f0582. +2023-09-18 13:59:36,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a28d468f860582 for 80.00000000 SEI-USDT. +2023-09-18 13:59:36,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045575.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a28d468f860582", "creation_timestamp": 1695045573.0, "exchange_order_id": "35264842", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:00:28,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a28d468f860582. [clock=2023-09-18 14:00:28+00:00] +2023-09-18 14:00:28,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248312695041246730966066057 amount: 80. +2023-09-18 14:00:28,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257137007145455066633933943 amount: 80. +2023-09-18 14:00:28,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045628.0, "order_id": "x-XEKWYICXBSIUT605a28d468f860582", "exchange_order_id": "35264842", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:00:28,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a28d468f860582. +2023-09-18 14:00:28,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a29089baa20582 for 80.00000000 SEI-USDT. +2023-09-18 14:00:28,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045628.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a29089baa20582", "creation_timestamp": 1695045628.0, "exchange_order_id": "35265190", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:00:28,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a29089be9d0582 for 80.00000000 SEI-USDT. +2023-09-18 14:00:28,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045628.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a29089be9d0582", "creation_timestamp": 1695045628.0, "exchange_order_id": "35265191", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:01:23,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a29089baa20582. [clock=2023-09-18 14:01:23+00:00] +2023-09-18 14:01:23,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a29089be9d0582. [clock=2023-09-18 14:01:23+00:00] +2023-09-18 14:01:23,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253981076154243749211775432 amount: 80. +2023-09-18 14:01:23,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:01:23,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045683.0, "order_id": "x-XEKWYICXBSIUT605a29089baa20582", "exchange_order_id": "35265190", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:01:23,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a29089baa20582. +2023-09-18 14:01:23,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a293d18ec20582 for 80.00000000 SEI-USDT. +2023-09-18 14:01:23,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045683.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a293d18ec20582", "creation_timestamp": 1695045683.0, "exchange_order_id": "35266140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:01:23,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045683.0, "order_id": "x-XEKWYICXSSIUT605a29089be9d0582", "exchange_order_id": "35265191", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:01:23,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a29089be9d0582. +2023-09-18 14:02:05,186 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a293d18ec20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:02:05,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:02:04+00:00] +2023-09-18 14:02:05,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045724.0, "order_id": "x-XEKWYICXBSIUT605a293d18ec20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4964588", "exchange_order_id": "35266140", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:02:05,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045724.0, "order_id": "x-XEKWYICXBSIUT605a293d18ec20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35266140", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:02:05,335 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a293d18ec20582 completely filled. +2023-09-18 14:02:18,732 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252981726532020173204657450 amount: 80. +2023-09-18 14:02:18,734 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262588363410853573795342550 amount: 80. +2023-09-18 14:02:19,867 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a29722a2980582 for 80.00000000 SEI-USDT. +2023-09-18 14:02:19,954 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045739.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a29722a2980582", "creation_timestamp": 1695045738.0, "exchange_order_id": "35267054", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:02:21,035 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a29722a5d40582 for 80.00000000 SEI-USDT. +2023-09-18 14:02:21,076 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045740.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a29722a5d40582", "creation_timestamp": 1695045738.0, "exchange_order_id": "35267061", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:03:13,436 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a29722a2980582. [clock=2023-09-18 14:03:13+00:00] +2023-09-18 14:03:13,446 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a29722a5d40582. [clock=2023-09-18 14:03:13+00:00] +2023-09-18 14:03:13,519 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250749767048647608376123691 amount: 80. +2023-09-18 14:03:13,521 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258207902782027722023876309 amount: 80. +2023-09-18 14:03:13,977 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045793.0, "order_id": "x-XEKWYICXBSIUT605a29722a2980582", "exchange_order_id": "35267054", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:03:13,977 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a29722a2980582. +2023-09-18 14:03:16,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045796.0, "order_id": "x-XEKWYICXSSIUT605a29722a5d40582", "exchange_order_id": "35267061", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:03:16,478 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a29722a5d40582. +2023-09-18 14:03:16,478 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a29a66a4c90582 for 80.00000000 SEI-USDT. +2023-09-18 14:03:16,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a29a66a4c90582", "creation_timestamp": 1695045793.0, "exchange_order_id": "35267602", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:03:16,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a29a669ef60582 for 80.00000000 SEI-USDT. +2023-09-18 14:03:16,541 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a29a669ef60582", "creation_timestamp": 1695045793.0, "exchange_order_id": "35267603", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:04:08,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a29a669ef60582. [clock=2023-09-18 14:04:08+00:00] +2023-09-18 14:04:08,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a29a66a4c90582. [clock=2023-09-18 14:04:08+00:00] +2023-09-18 14:04:08,402 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249112443894959451210126083 amount: 80. +2023-09-18 14:04:08,403 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257649714346368664036620650 amount: 80. +2023-09-18 14:04:08,985 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045848.0, "order_id": "x-XEKWYICXBSIUT605a29a669ef60582", "exchange_order_id": "35267603", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:04:08,986 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a29a669ef60582. +2023-09-18 14:04:09,028 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045848.0, "order_id": "x-XEKWYICXSSIUT605a29a66a4c90582", "exchange_order_id": "35267602", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:04:09,028 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a29a66a4c90582. +2023-09-18 14:04:10,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a29dac46680582 for 80.00000000 SEI-USDT. +2023-09-18 14:04:10,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045849.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a29dac46680582", "creation_timestamp": 1695045848.0, "exchange_order_id": "35268589", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:04:10,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a29dac0edd0582 for 80.00000000 SEI-USDT. +2023-09-18 14:04:10,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045849.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a29dac0edd0582", "creation_timestamp": 1695045848.0, "exchange_order_id": "35268590", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:05:03,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a29dac0edd0582. [clock=2023-09-18 14:05:03+00:00] +2023-09-18 14:05:03,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a29dac46680582. [clock=2023-09-18 14:05:03+00:00] +2023-09-18 14:05:03,286 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249579328308883840276065940 amount: 80. +2023-09-18 14:05:03,294 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256736830086205530052429362 amount: 80. +2023-09-18 14:05:03,901 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045903.0, "order_id": "x-XEKWYICXBSIUT605a29dac0edd0582", "exchange_order_id": "35268590", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:05:03,901 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a29dac0edd0582. +2023-09-18 14:05:05,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045904.0, "order_id": "x-XEKWYICXSSIUT605a29dac46680582", "exchange_order_id": "35268589", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:05:05,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a29dac46680582. +2023-09-18 14:05:06,078 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2a0f19eec0582 for 80.00000000 SEI-USDT. +2023-09-18 14:05:06,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045905.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a2a0f19eec0582", "creation_timestamp": 1695045903.0, "exchange_order_id": "35268865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:05:06,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2a0f1a2040582 for 80.00000000 SEI-USDT. +2023-09-18 14:05:06,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045905.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a2a0f1a2040582", "creation_timestamp": 1695045903.0, "exchange_order_id": "35268866", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:05:58,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2a0f19eec0582. [clock=2023-09-18 14:05:58+00:00] +2023-09-18 14:05:58,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2a0f1a2040582. [clock=2023-09-18 14:05:58+00:00] +2023-09-18 14:05:58,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248898596258477442697264959 amount: 80. +2023-09-18 14:05:58,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256726940498259301302735041 amount: 80. +2023-09-18 14:05:58,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045958.0, "order_id": "x-XEKWYICXBSIUT605a2a0f19eec0582", "exchange_order_id": "35268865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:05:58,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2a0f19eec0582. +2023-09-18 14:05:58,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2a435bbb70582 for 80.00000000 SEI-USDT. +2023-09-18 14:05:58,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045958.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a2a435bbb70582", "creation_timestamp": 1695045958.0, "exchange_order_id": "35269096", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:05:58,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2a435b8fc0582 for 80.00000000 SEI-USDT. +2023-09-18 14:05:58,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045958.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a2a435b8fc0582", "creation_timestamp": 1695045958.0, "exchange_order_id": "35269097", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:05:58,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695045958.0, "order_id": "x-XEKWYICXSSIUT605a2a0f1a2040582", "exchange_order_id": "35268866", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:05:58,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2a0f1a2040582. +2023-09-18 14:06:53,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2a435b8fc0582. [clock=2023-09-18 14:06:53+00:00] +2023-09-18 14:06:53,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2a435bbb70582. [clock=2023-09-18 14:06:53+00:00] +2023-09-18 14:06:53,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247050504434429092541667389 amount: 80. +2023-09-18 14:06:53,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255804925305236875594718881 amount: 80. +2023-09-18 14:06:53,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046013.0, "order_id": "x-XEKWYICXBSIUT605a2a435b8fc0582", "exchange_order_id": "35269097", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:06:53,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2a435b8fc0582. +2023-09-18 14:06:53,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2a77d92750582 for 80.00000000 SEI-USDT. +2023-09-18 14:06:53,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046013.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a2a77d92750582", "creation_timestamp": 1695046013.0, "exchange_order_id": "35269702", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:06:53,549 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2a77d95710582 for 80.00000000 SEI-USDT. +2023-09-18 14:06:53,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046013.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a2a77d95710582", "creation_timestamp": 1695046013.0, "exchange_order_id": "35269703", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:06:53,591 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046013.0, "order_id": "x-XEKWYICXSSIUT605a2a435bbb70582", "exchange_order_id": "35269096", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:06:53,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2a435bbb70582. +2023-09-18 14:07:48,219 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2a77d92750582. [clock=2023-09-18 14:07:48+00:00] +2023-09-18 14:07:48,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2a77d95710582. [clock=2023-09-18 14:07:48+00:00] +2023-09-18 14:07:48,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242879122888896523611864438 amount: 80. +2023-09-18 14:07:48,279 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251680708346410795664023609 amount: 80. +2023-09-18 14:07:48,975 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046068.0, "order_id": "x-XEKWYICXBSIUT605a2a77d92750582", "exchange_order_id": "35269702", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:07:48,975 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2a77d92750582. +2023-09-18 14:07:50,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046069.0, "order_id": "x-XEKWYICXSSIUT605a2a77d95710582", "exchange_order_id": "35269703", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:07:50,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2a77d95710582. +2023-09-18 14:07:50,515 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2aac717230582 for 80.00000000 SEI-USDT. +2023-09-18 14:07:50,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046070.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a2aac717230582", "creation_timestamp": 1695046068.0, "exchange_order_id": "35270499", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:07:50,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2aac71a8e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:07:50,612 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046070.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a2aac71a8e0582", "creation_timestamp": 1695046068.0, "exchange_order_id": "35270498", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:08:43,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2aac717230582. [clock=2023-09-18 14:08:43+00:00] +2023-09-18 14:08:43,310 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2aac71a8e0582. [clock=2023-09-18 14:08:43+00:00] +2023-09-18 14:08:43,373 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237306443353840894035876106 amount: 80. +2023-09-18 14:08:43,374 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247319601000388200688575995 amount: 80. +2023-09-18 14:08:44,721 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046123.0, "order_id": "x-XEKWYICXBSIUT605a2aac717230582", "exchange_order_id": "35270499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:08:44,722 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2aac717230582. +2023-09-18 14:08:46,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046126.0, "order_id": "x-XEKWYICXSSIUT605a2aac71a8e0582", "exchange_order_id": "35270498", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:08:46,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2aac71a8e0582. +2023-09-18 14:08:46,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2ae0ffa3c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:08:46,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a2ae0ffa3c0582", "creation_timestamp": 1695046123.0, "exchange_order_id": "35272045", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:08:46,738 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2ae0fc5df0582 for 80.00000000 SEI-USDT. +2023-09-18 14:08:46,772 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a2ae0fc5df0582", "creation_timestamp": 1695046123.0, "exchange_order_id": "35272046", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:09:36,331 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a2ae0ffa3c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:09:36,333 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 14:09:36+00:00] +2023-09-18 14:09:36,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046176.0, "order_id": "x-XEKWYICXSSIUT605a2ae0ffa3c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4965413", "exchange_order_id": "35272045", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:09:36,656 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046176.0, "order_id": "x-XEKWYICXSSIUT605a2ae0ffa3c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35272045", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:09:36,656 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a2ae0ffa3c0582 completely filled. +2023-09-18 14:09:38,162 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2ae0fc5df0582. [clock=2023-09-18 14:09:38+00:00] +2023-09-18 14:09:38,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240750446681347307919481176 amount: 80. +2023-09-18 14:09:38,205 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251882530962064458480518824 amount: 80. +2023-09-18 14:09:39,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046178.0, "order_id": "x-XEKWYICXBSIUT605a2ae0fc5df0582", "exchange_order_id": "35272046", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:09:39,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2ae0fc5df0582. +2023-09-18 14:09:40,743 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2b154a0920582 for 80.00000000 SEI-USDT. +2023-09-18 14:09:40,832 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046180.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a2b154a0920582", "creation_timestamp": 1695046178.0, "exchange_order_id": "35273123", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:09:40,832 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2b1546da50582 for 80.00000000 SEI-USDT. +2023-09-18 14:09:40,869 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046180.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a2b1546da50582", "creation_timestamp": 1695046178.0, "exchange_order_id": "35273122", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:09:58,461 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:09:58,638 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046198.0, "order_id": "x-XEKWYICXBSIUT605a2b1546da50582", "exchange_order_id": "35273122", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:09:58,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2b1546da50582. +2023-09-18 14:09:58,851 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046198.0, "order_id": "x-XEKWYICXSSIUT605a2b154a0920582", "exchange_order_id": "35273123", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:09:58,851 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2b154a0920582. +2023-09-18 14:10:13,941 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:10:13,944 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:10:14,040 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:10:14,094 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:10:14,190 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:10:14,256 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:10:14,459 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:10:14,460 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:10:14,507 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:10:15,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:10:15,488 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:10:18,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241521921739054395689481200 amount: 80. +2023-09-18 14:10:18,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251877862053143786110518800 amount: 80. +2023-09-18 14:10:18,066 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2b3b3e8230582 for 80.00000000 SEI-USDT. +2023-09-18 14:10:18,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046218.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a2b3b3e8230582", "creation_timestamp": 1695046218.0, "exchange_order_id": "35273502", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:10:18,081 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2b3b3e3d70582 for 80.00000000 SEI-USDT. +2023-09-18 14:10:18,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046218.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a2b3b3e3d70582", "creation_timestamp": 1695046218.0, "exchange_order_id": "35273503", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:11:13,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2b3b3e3d70582. [clock=2023-09-18 14:11:13+00:00] +2023-09-18 14:11:13,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2b3b3e8230582. [clock=2023-09-18 14:11:13+00:00] +2023-09-18 14:11:13,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239599930050530237536828340 amount: 80. +2023-09-18 14:11:13,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:11:13,039 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046273.0, "order_id": "x-XEKWYICXBSIUT605a2b3b3e3d70582", "exchange_order_id": "35273503", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:11:13,040 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2b3b3e3d70582. +2023-09-18 14:11:13,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046273.0, "order_id": "x-XEKWYICXSSIUT605a2b3b3e8230582", "exchange_order_id": "35273502", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:11:13,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2b3b3e8230582. +2023-09-18 14:11:13,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2b6fb276b0582 for 80.00000000 SEI-USDT. +2023-09-18 14:11:13,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046273.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a2b6fb276b0582", "creation_timestamp": 1695046273.0, "exchange_order_id": "35274172", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:12:08,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2b6fb276b0582. [clock=2023-09-18 14:12:08+00:00] +2023-09-18 14:12:08,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238475949363942907229130004 amount: 80. +2023-09-18 14:12:08,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247389739652996996770869996 amount: 80. +2023-09-18 14:12:08,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046328.0, "order_id": "x-XEKWYICXBSIUT605a2b6fb276b0582", "exchange_order_id": "35274172", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:12:08,086 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2b6fb276b0582. +2023-09-18 14:12:08,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2ba425bea0582 for 80.00000000 SEI-USDT. +2023-09-18 14:12:08,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046328.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a2ba425bea0582", "creation_timestamp": 1695046328.0, "exchange_order_id": "35274705", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:12:08,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2ba425eee0582 for 80.00000000 SEI-USDT. +2023-09-18 14:12:08,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046328.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a2ba425eee0582", "creation_timestamp": 1695046328.0, "exchange_order_id": "35274709", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:13:03,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2ba425bea0582. [clock=2023-09-18 14:13:03+00:00] +2023-09-18 14:13:03,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2ba425eee0582. [clock=2023-09-18 14:13:03+00:00] +2023-09-18 14:13:03,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239712970091409608676332501 amount: 80. +2023-09-18 14:13:03,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:13:03,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046383.0, "order_id": "x-XEKWYICXBSIUT605a2ba425bea0582", "exchange_order_id": "35274705", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:13:03,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2ba425bea0582. +2023-09-18 14:13:03,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046383.0, "order_id": "x-XEKWYICXSSIUT605a2ba425eee0582", "exchange_order_id": "35274709", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:13:03,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2ba425eee0582. +2023-09-18 14:13:03,330 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2bd89bef90582 for 80.00000000 SEI-USDT. +2023-09-18 14:13:03,354 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046383.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a2bd89bef90582", "creation_timestamp": 1695046383.0, "exchange_order_id": "35275156", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:13:58,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2bd89bef90582. [clock=2023-09-18 14:13:58+00:00] +2023-09-18 14:13:58,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239328429795879008544691480 amount: 80. +2023-09-18 14:13:58,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249150214524029146316994159 amount: 80. +2023-09-18 14:13:58,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046438.0, "order_id": "x-XEKWYICXBSIUT605a2bd89bef90582", "exchange_order_id": "35275156", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:13:58,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2bd89bef90582. +2023-09-18 14:13:58,899 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2c0d1c19e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:13:58,987 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046438.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a2c0d1c19e0582", "creation_timestamp": 1695046438.0, "exchange_order_id": "35275431", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:13:58,989 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2c0d1c3b30582 for 80.00000000 SEI-USDT. +2023-09-18 14:13:59,062 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046438.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a2c0d1c3b30582", "creation_timestamp": 1695046438.0, "exchange_order_id": "35275432", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:14:53,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2c0d1c19e0582. [clock=2023-09-18 14:14:53+00:00] +2023-09-18 14:14:53,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2c0d1c3b30582. [clock=2023-09-18 14:14:53+00:00] +2023-09-18 14:14:53,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240203656629056511708131000 amount: 80. +2023-09-18 14:14:53,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:14:53,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046493.0, "order_id": "x-XEKWYICXBSIUT605a2c0d1c19e0582", "exchange_order_id": "35275431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:14:53,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2c0d1c19e0582. +2023-09-18 14:14:53,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046493.0, "order_id": "x-XEKWYICXSSIUT605a2c0d1c3b30582", "exchange_order_id": "35275432", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:14:53,630 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2c0d1c3b30582. +2023-09-18 14:14:53,739 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2c418b6cf0582 for 80.00000000 SEI-USDT. +2023-09-18 14:14:53,783 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046493.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a2c418b6cf0582", "creation_timestamp": 1695046493.0, "exchange_order_id": "35275654", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:15:48,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2c418b6cf0582. [clock=2023-09-18 14:15:48+00:00] +2023-09-18 14:15:48,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242182693603688669518540060 amount: 80. +2023-09-18 14:15:48,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249311637829408347375741316 amount: 80. +2023-09-18 14:15:48,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046548.0, "order_id": "x-XEKWYICXBSIUT605a2c418b6cf0582", "exchange_order_id": "35275654", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:15:48,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2c418b6cf0582. +2023-09-18 14:15:48,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2c75f5b170582 for 80.00000000 SEI-USDT. +2023-09-18 14:15:48,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046548.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a2c75f5b170582", "creation_timestamp": 1695046548.0, "exchange_order_id": "35276292", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:15:48,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2c75f56c50582 for 80.00000000 SEI-USDT. +2023-09-18 14:15:48,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046548.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a2c75f56c50582", "creation_timestamp": 1695046548.0, "exchange_order_id": "35276293", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:16:43,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2c75f56c50582. [clock=2023-09-18 14:16:43+00:00] +2023-09-18 14:16:43,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2c75f5b170582. [clock=2023-09-18 14:16:43+00:00] +2023-09-18 14:16:43,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243630913755279975969714798 amount: 80. +2023-09-18 14:16:43,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:16:43,081 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046603.0, "order_id": "x-XEKWYICXBSIUT605a2c75f56c50582", "exchange_order_id": "35276293", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:16:43,081 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2c75f56c50582. +2023-09-18 14:16:43,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046603.0, "order_id": "x-XEKWYICXSSIUT605a2c75f5b170582", "exchange_order_id": "35276292", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:16:43,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2c75f5b170582. +2023-09-18 14:16:43,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2caa68ac20582 for 80.00000000 SEI-USDT. +2023-09-18 14:16:43,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046603.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a2caa68ac20582", "creation_timestamp": 1695046603.0, "exchange_order_id": "35276785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:17:19,825 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:17:19,917 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046639.0, "order_id": "x-XEKWYICXBSIUT605a2caa68ac20582", "exchange_order_id": "35276785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:17:19,918 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2caa68ac20582. +2023-09-18 14:17:24,499 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:17:24,503 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:17:24,615 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:17:24,677 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:17:24,813 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:17:24,828 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:17:24,951 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:17:25,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:17:25,107 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:17:26,037 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:17:36,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246177106957753723572109345 amount: 80. +2023-09-18 14:17:36,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253378161071983374074700160 amount: 80. +2023-09-18 14:17:36,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2cdcfbbaf0582 for 80.00000000 SEI-USDT. +2023-09-18 14:17:36,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a2cdcfbbaf0582", "creation_timestamp": 1695046656.0, "exchange_order_id": "35277471", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:17:36,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2cdcfbf780582 for 80.00000000 SEI-USDT. +2023-09-18 14:17:36,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a2cdcfbf780582", "creation_timestamp": 1695046656.0, "exchange_order_id": "35277472", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:18:31,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2cdcfbbaf0582. [clock=2023-09-18 14:18:31+00:00] +2023-09-18 14:18:31,194 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2cdcfbf780582. [clock=2023-09-18 14:18:31+00:00] +2023-09-18 14:18:31,244 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245360624209197165521036330 amount: 80. +2023-09-18 14:18:31,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:18:31,988 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046711.0, "order_id": "x-XEKWYICXBSIUT605a2cdcfbbaf0582", "exchange_order_id": "35277471", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:18:32,001 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2cdcfbbaf0582. +2023-09-18 14:18:33,374 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046713.0, "order_id": "x-XEKWYICXSSIUT605a2cdcfbf780582", "exchange_order_id": "35277472", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:18:33,375 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2cdcfbf780582. +2023-09-18 14:18:33,614 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2d119f93e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:18:33,647 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046713.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a2d119f93e0582", "creation_timestamp": 1695046711.0, "exchange_order_id": "35278568", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:19:26,227 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2d119f93e0582. [clock=2023-09-18 14:19:26+00:00] +2023-09-18 14:19:26,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245101941058270170531918715 amount: 80. +2023-09-18 14:19:26,277 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251311790373236344668081285 amount: 80. +2023-09-18 14:19:26,892 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046766.0, "order_id": "x-XEKWYICXBSIUT605a2d119f93e0582", "exchange_order_id": "35278568", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:19:26,909 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2d119f93e0582. +2023-09-18 14:19:28,790 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2d461b2ca0582 for 80.00000000 SEI-USDT. +2023-09-18 14:19:28,853 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a2d461b2ca0582", "creation_timestamp": 1695046766.0, "exchange_order_id": "35278916", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:19:29,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2d461b6020582 for 80.00000000 SEI-USDT. +2023-09-18 14:19:29,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a2d461b6020582", "creation_timestamp": 1695046766.0, "exchange_order_id": "35278917", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:20:21,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2d461b2ca0582. [clock=2023-09-18 14:20:21+00:00] +2023-09-18 14:20:21,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2d461b6020582. [clock=2023-09-18 14:20:21+00:00] +2023-09-18 14:20:21,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246826267372221804231726916 amount: 80. +2023-09-18 14:20:21,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:20:21,072 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046821.0, "order_id": "x-XEKWYICXBSIUT605a2d461b2ca0582", "exchange_order_id": "35278916", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:20:21,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2d461b2ca0582. +2023-09-18 14:20:21,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2d7a5753e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:20:21,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a2d7a5753e0582", "creation_timestamp": 1695046821.0, "exchange_order_id": "35279259", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:20:21,494 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046821.0, "order_id": "x-XEKWYICXSSIUT605a2d461b6020582", "exchange_order_id": "35278917", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:20:21,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2d461b6020582. +2023-09-18 14:21:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2d7a5753e0582. [clock=2023-09-18 14:21:16+00:00] +2023-09-18 14:21:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249532736480404540834309578 amount: 80. +2023-09-18 14:21:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256851205917560694365690422 amount: 80. +2023-09-18 14:21:16,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046876.0, "order_id": "x-XEKWYICXBSIUT605a2d7a5753e0582", "exchange_order_id": "35279259", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:21:16,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2d7a5753e0582. +2023-09-18 14:21:16,607 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2daec3df50582 for 80.00000000 SEI-USDT. +2023-09-18 14:21:16,620 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a2daec3df50582", "creation_timestamp": 1695046876.0, "exchange_order_id": "35279849", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:21:16,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2daec3be60582 for 80.00000000 SEI-USDT. +2023-09-18 14:21:16,646 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a2daec3be60582", "creation_timestamp": 1695046876.0, "exchange_order_id": "35279850", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:21:25,376 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:21:25,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046885.0, "order_id": "x-XEKWYICXBSIUT605a2daec3be60582", "exchange_order_id": "35279850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:21:25,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2daec3be60582. +2023-09-18 14:21:25,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046885.0, "order_id": "x-XEKWYICXSSIUT605a2daec3df50582", "exchange_order_id": "35279849", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:21:25,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2daec3df50582. +2023-09-18 14:21:29,154 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:21:29,158 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:21:29,272 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:21:29,326 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:21:29,629 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:21:29,639 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:21:29,646 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:21:29,880 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:21:30,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:21:30,834 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:21:45,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254987320462388863887087747 amount: 80. +2023-09-18 14:21:45,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262990854692920210888779531 amount: 80. +2023-09-18 14:21:45,064 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2dca6aa680582 for 80.00000000 SEI-USDT. +2023-09-18 14:21:45,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046905.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a2dca6aa680582", "creation_timestamp": 1695046905.0, "exchange_order_id": "35280235", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:21:45,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2dca6ae9f0582 for 80.00000000 SEI-USDT. +2023-09-18 14:21:45,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046905.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a2dca6ae9f0582", "creation_timestamp": 1695046905.0, "exchange_order_id": "35280237", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:22:09,066 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a2dca6aa680582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:22:09,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:22:09+00:00] +2023-09-18 14:22:09,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046929.0, "order_id": "x-XEKWYICXBSIUT605a2dca6aa680582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4966154", "exchange_order_id": "35280235", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:22:09,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046929.0, "order_id": "x-XEKWYICXBSIUT605a2dca6aa680582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35280235", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:22:09,149 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a2dca6aa680582 completely filled. +2023-09-18 14:22:40,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2dca6ae9f0582. [clock=2023-09-18 14:22:40+00:00] +2023-09-18 14:22:40,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249989230044780590383020485 amount: 80. +2023-09-18 14:22:40,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258201650859353797816979515 amount: 80. +2023-09-18 14:22:40,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046960.0, "order_id": "x-XEKWYICXSSIUT605a2dca6ae9f0582", "exchange_order_id": "35280237", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:22:40,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2dca6ae9f0582. +2023-09-18 14:22:40,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2dfee5d360582 for 80.00000000 SEI-USDT. +2023-09-18 14:22:40,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046960.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a2dfee5d360582", "creation_timestamp": 1695046960.0, "exchange_order_id": "35281094", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:22:40,490 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2dfee62e10582 for 80.00000000 SEI-USDT. +2023-09-18 14:22:40,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695046960.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a2dfee62e10582", "creation_timestamp": 1695046960.0, "exchange_order_id": "35281096", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:23:23,212 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:23:23,591 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047003.0, "order_id": "x-XEKWYICXBSIUT605a2dfee5d360582", "exchange_order_id": "35281094", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:23:23,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2dfee5d360582. +2023-09-18 14:23:23,836 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047003.0, "order_id": "x-XEKWYICXSSIUT605a2dfee62e10582", "exchange_order_id": "35281096", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:23:23,836 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2dfee62e10582. +2023-09-18 14:23:26,638 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:23:26,641 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:23:27,114 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:23:27,579 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:23:27,956 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:23:28,183 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:23:28,943 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:23:29,195 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:23:30,691 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:23:30,907 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:23:31,184 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:23:31,185 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:23:32,665 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:23:41,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250734990646536466344831633 amount: 80. +2023-09-18 14:23:41,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257784977685588036404337039 amount: 80. +2023-09-18 14:23:41,830 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2e393990b0582 for 80.00000000 SEI-USDT. +2023-09-18 14:23:41,880 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a2e393990b0582", "creation_timestamp": 1695047021.0, "exchange_order_id": "35281499", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:23:41,881 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2e39392c30582 for 80.00000000 SEI-USDT. +2023-09-18 14:23:41,920 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a2e39392c30582", "creation_timestamp": 1695047021.0, "exchange_order_id": "35281500", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:24:12,615 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a2e393990b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:24:12,625 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:24:12+00:00] +2023-09-18 14:24:12,717 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047052.0, "order_id": "x-XEKWYICXSSIUT605a2e393990b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01005600"}]}, "exchange_trade_id": "4966345", "exchange_order_id": "35281499", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:24:12,975 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047052.0, "order_id": "x-XEKWYICXSSIUT605a2e393990b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35281499", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:24:12,975 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a2e393990b0582 completely filled. +2023-09-18 14:24:36,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2e39392c30582. [clock=2023-09-18 14:24:36+00:00] +2023-09-18 14:24:36,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259977422059092213507056542 amount: 80. +2023-09-18 14:24:36,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1270689949790388874492943458 amount: 80. +2023-09-18 14:24:36,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047076.0, "order_id": "x-XEKWYICXBSIUT605a2e39392c30582", "exchange_order_id": "35281500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:24:36,636 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2e39392c30582. +2023-09-18 14:24:38,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2e6da8f400582 for 80.00000000 SEI-USDT. +2023-09-18 14:24:38,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047078.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a2e6da8f400582", "creation_timestamp": 1695047076.0, "exchange_order_id": "35282948", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:24:39,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2e6da94390582 for 80.00000000 SEI-USDT. +2023-09-18 14:24:39,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047078.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXSSIUT605a2e6da94390582", "creation_timestamp": 1695047076.0, "exchange_order_id": "35282973", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:25:31,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2e6da8f400582. [clock=2023-09-18 14:25:31+00:00] +2023-09-18 14:25:31,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2e6da94390582. [clock=2023-09-18 14:25:31+00:00] +2023-09-18 14:25:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1261972171277036419717612977 amount: 80. +2023-09-18 14:25:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:25:31,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047131.0, "order_id": "x-XEKWYICXBSIUT605a2e6da8f400582", "exchange_order_id": "35282948", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:25:31,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2e6da8f400582. +2023-09-18 14:25:31,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2ea1f3edc0582 for 80.00000000 SEI-USDT. +2023-09-18 14:25:31,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXBSIUT605a2ea1f3edc0582", "creation_timestamp": 1695047131.0, "exchange_order_id": "35284952", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:25:31,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047131.0, "order_id": "x-XEKWYICXSSIUT605a2e6da94390582", "exchange_order_id": "35282973", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:25:31,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2e6da94390582. +2023-09-18 14:26:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2ea1f3edc0582. [clock=2023-09-18 14:26:26+00:00] +2023-09-18 14:26:26,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263975147034514920266064179 amount: 80. +2023-09-18 14:26:26,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1275234518147446612379484089 amount: 80. +2023-09-18 14:26:26,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047186.0, "order_id": "x-XEKWYICXBSIUT605a2ea1f3edc0582", "exchange_order_id": "35284952", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:26:26,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2ea1f3edc0582. +2023-09-18 14:26:26,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2ed6663210582 for 80.00000000 SEI-USDT. +2023-09-18 14:26:26,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12750000", "order_id": "x-XEKWYICXSSIUT605a2ed6663210582", "creation_timestamp": 1695047186.0, "exchange_order_id": "35286254", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:26:26,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2ed6660f50582 for 80.00000000 SEI-USDT. +2023-09-18 14:26:26,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a2ed6660f50582", "creation_timestamp": 1695047186.0, "exchange_order_id": "35286255", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:27:21,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2ed6660f50582. [clock=2023-09-18 14:27:21+00:00] +2023-09-18 14:27:21,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2ed6663210582. [clock=2023-09-18 14:27:21+00:00] +2023-09-18 14:27:21,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1267971022050263177969447993 amount: 80. +2023-09-18 14:27:21,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:27:22,784 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047241.0, "order_id": "x-XEKWYICXBSIUT605a2ed6660f50582", "exchange_order_id": "35286255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:27:22,784 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2ed6660f50582. +2023-09-18 14:27:24,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047244.0, "order_id": "x-XEKWYICXSSIUT605a2ed6663210582", "exchange_order_id": "35286254", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:27:24,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2ed6663210582. +2023-09-18 14:27:24,770 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2f0b08e5b0582 for 80.00000000 SEI-USDT. +2023-09-18 14:27:24,802 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047244.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXBSIUT605a2f0b08e5b0582", "creation_timestamp": 1695047241.0, "exchange_order_id": "35287884", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:28:16,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2f0b08e5b0582. [clock=2023-09-18 14:28:16+00:00] +2023-09-18 14:28:16,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1274955560897621185106335391 amount: 80. +2023-09-18 14:28:16,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1290098987323170079893664609 amount: 80. +2023-09-18 14:28:17,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047296.0, "order_id": "x-XEKWYICXBSIUT605a2f0b08e5b0582", "exchange_order_id": "35287884", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:28:17,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2f0b08e5b0582. +2023-09-18 14:28:17,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2f3f7dcab0582 for 80.00000000 SEI-USDT. +2023-09-18 14:28:17,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047297.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12740000", "order_id": "x-XEKWYICXBSIUT605a2f3f7dcab0582", "creation_timestamp": 1695047296.0, "exchange_order_id": "35290282", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:28:18,876 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2f3f7e0860582 for 80.00000000 SEI-USDT. +2023-09-18 14:28:18,924 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047298.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12900000", "order_id": "x-XEKWYICXSSIUT605a2f3f7e0860582", "creation_timestamp": 1695047296.0, "exchange_order_id": "35290300", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:28:33,375 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a2f3f7dcab0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:28:33,376 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:28:33+00:00] +2023-09-18 14:28:33,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047313.0, "order_id": "x-XEKWYICXBSIUT605a2f3f7dcab0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12740000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4967774", "exchange_order_id": "35290282", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:28:33,794 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047313.0, "order_id": "x-XEKWYICXBSIUT605a2f3f7dcab0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35290282", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:28:33,795 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a2f3f7dcab0582 completely filled. +2023-09-18 14:28:54,104 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:28:54,797 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047334.0, "order_id": "x-XEKWYICXSSIUT605a2f3f7e0860582", "exchange_order_id": "35290300", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:28:54,797 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2f3f7e0860582. +2023-09-18 14:28:57,551 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:28:57,569 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:28:58,312 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:28:58,460 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:28:58,854 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:28:58,874 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:28:58,898 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:28:59,299 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:28:59,602 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:29:00,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:29:00,736 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:29:02,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1268663329741188420367871539 amount: 80. +2023-09-18 14:29:02,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1285113109588065379644146842 amount: 80. +2023-09-18 14:29:02,624 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2f6b3ed520582 for 80.00000000 SEI-USDT. +2023-09-18 14:29:02,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047342.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12680000", "order_id": "x-XEKWYICXBSIUT605a2f6b3ed520582", "creation_timestamp": 1695047342.0, "exchange_order_id": "35291309", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:29:02,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2f6b3ef240582 for 80.00000000 SEI-USDT. +2023-09-18 14:29:02,756 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047342.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12850000", "order_id": "x-XEKWYICXSSIUT605a2f6b3ef240582", "creation_timestamp": 1695047342.0, "exchange_order_id": "35291310", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:29:57,343 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a2f6b3ed520582. [clock=2023-09-18 14:29:57+00:00] +2023-09-18 14:29:57,344 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2f6b3ef240582. [clock=2023-09-18 14:29:57+00:00] +2023-09-18 14:29:57,408 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12760000 amount: 80. +2023-09-18 14:29:57,410 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1296534878995836075892363006 amount: 80. +2023-09-18 14:29:57,963 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047397.0, "order_id": "x-XEKWYICXBSIUT605a2f6b3ed520582", "exchange_order_id": "35291309", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:29:57,964 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a2f6b3ed520582. +2023-09-18 14:30:00,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047400.0, "order_id": "x-XEKWYICXSSIUT605a2f6b3ef240582", "exchange_order_id": "35291310", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:30:00,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2f6b3ef240582. +2023-09-18 14:30:00,865 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2fa0004e30582 for 80.00000000 SEI-USDT. +2023-09-18 14:30:00,928 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047400.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12760000", "order_id": "x-XEKWYICXBSIUT605a2fa0004e30582", "creation_timestamp": 1695047397.0, "exchange_order_id": "35292943", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:30:00,929 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2fa00079b0582 for 80.00000000 SEI-USDT. +2023-09-18 14:30:01,017 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047400.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12960000", "order_id": "x-XEKWYICXSSIUT605a2fa00079b0582", "creation_timestamp": 1695047397.0, "exchange_order_id": "35292942", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:30:02,544 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a2fa0004e30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:30:02,545 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:30:02+00:00] +2023-09-18 14:30:02,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047402.0, "order_id": "x-XEKWYICXBSIUT605a2fa0004e30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12760000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4968060", "exchange_order_id": "35292943", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:30:02,639 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047402.0, "order_id": "x-XEKWYICXBSIUT605a2fa0004e30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.2080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35292943", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:30:02,639 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a2fa0004e30582 completely filled. +2023-09-18 14:30:52,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2fa00079b0582. [clock=2023-09-18 14:30:52+00:00] +2023-09-18 14:30:52,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12720000 amount: 80. +2023-09-18 14:30:52,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1291182051573524794626050740 amount: 80. +2023-09-18 14:30:52,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047452.0, "order_id": "x-XEKWYICXSSIUT605a2fa00079b0582", "exchange_order_id": "35292942", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:30:52,089 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2fa00079b0582. +2023-09-18 14:30:52,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a2fd41469a0582 for 80.00000000 SEI-USDT. +2023-09-18 14:30:52,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047452.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12910000", "order_id": "x-XEKWYICXSSIUT605a2fd41469a0582", "creation_timestamp": 1695047452.0, "exchange_order_id": "35294295", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:30:52,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a2fd4143af0582 for 80.00000000 SEI-USDT. +2023-09-18 14:30:52,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047452.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12720000", "order_id": "x-XEKWYICXBSIUT605a2fd4143af0582", "creation_timestamp": 1695047452.0, "exchange_order_id": "35294296", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:30:57,642 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a2fd4143af0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:30:57,643 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:30:57+00:00] +2023-09-18 14:30:57,664 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047457.0, "order_id": "x-XEKWYICXBSIUT605a2fd4143af0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12720000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4968223", "exchange_order_id": "35294296", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:30:57,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047457.0, "order_id": "x-XEKWYICXBSIUT605a2fd4143af0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35294296", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:30:57,678 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a2fd4143af0582 completely filled. +2023-09-18 14:31:47,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a2fd41469a0582. [clock=2023-09-18 14:31:47+00:00] +2023-09-18 14:31:47,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263066623719781706851304300 amount: 80. +2023-09-18 14:31:47,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1278334095168397349194264085 amount: 80. +2023-09-18 14:31:47,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047507.0, "order_id": "x-XEKWYICXSSIUT605a2fd41469a0582", "exchange_order_id": "35294295", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:31:47,085 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a2fd41469a0582. +2023-09-18 14:31:47,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a300887d930582 for 80.00000000 SEI-USDT. +2023-09-18 14:31:47,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047507.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12780000", "order_id": "x-XEKWYICXSSIUT605a300887d930582", "creation_timestamp": 1695047507.0, "exchange_order_id": "35295870", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:31:47,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a300887ab00582 for 80.00000000 SEI-USDT. +2023-09-18 14:31:47,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047507.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a300887ab00582", "creation_timestamp": 1695047507.0, "exchange_order_id": "35295871", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:32:42,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a300887ab00582. [clock=2023-09-18 14:32:42+00:00] +2023-09-18 14:32:42,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a300887d930582. [clock=2023-09-18 14:32:42+00:00] +2023-09-18 14:32:42,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258932104538682706642603668 amount: 80. +2023-09-18 14:32:42,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1273993496207139532616463192 amount: 80. +2023-09-18 14:32:42,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047562.0, "order_id": "x-XEKWYICXBSIUT605a300887ab00582", "exchange_order_id": "35295871", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:32:42,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a300887ab00582. +2023-09-18 14:32:42,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047562.0, "order_id": "x-XEKWYICXSSIUT605a300887d930582", "exchange_order_id": "35295870", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:32:42,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a300887d930582. +2023-09-18 14:32:42,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a303cfef7c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:32:42,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047562.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a303cfef7c0582", "creation_timestamp": 1695047562.0, "exchange_order_id": "35297124", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:32:42,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a303cff2760582 for 80.00000000 SEI-USDT. +2023-09-18 14:32:42,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047562.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12730000", "order_id": "x-XEKWYICXSSIUT605a303cff2760582", "creation_timestamp": 1695047562.0, "exchange_order_id": "35297125", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:33:37,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a303cfef7c0582. [clock=2023-09-18 14:33:37+00:00] +2023-09-18 14:33:37,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a303cff2760582. [clock=2023-09-18 14:33:37+00:00] +2023-09-18 14:33:37,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256912466434426544743925060 amount: 80. +2023-09-18 14:33:37,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1270621672781729758781306528 amount: 80. +2023-09-18 14:33:37,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047617.0, "order_id": "x-XEKWYICXBSIUT605a303cfef7c0582", "exchange_order_id": "35297124", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:33:37,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a303cfef7c0582. +2023-09-18 14:33:37,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047617.0, "order_id": "x-XEKWYICXSSIUT605a303cff2760582", "exchange_order_id": "35297125", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:33:37,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a303cff2760582. +2023-09-18 14:33:37,461 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a30717c9e20582 for 80.00000000 SEI-USDT. +2023-09-18 14:33:37,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047617.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXSSIUT605a30717c9e20582", "creation_timestamp": 1695047617.0, "exchange_order_id": "35297779", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:33:37,657 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a30717c7720582 for 80.00000000 SEI-USDT. +2023-09-18 14:33:37,689 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047617.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a30717c7720582", "creation_timestamp": 1695047617.0, "exchange_order_id": "35297780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:34:32,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a30717c7720582. [clock=2023-09-18 14:34:32+00:00] +2023-09-18 14:34:32,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a30717c9e20582. [clock=2023-09-18 14:34:32+00:00] +2023-09-18 14:34:32,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260145300439796049425484577 amount: 80. +2023-09-18 14:34:32,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1272145734997704537323663610 amount: 80. +2023-09-18 14:34:32,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047672.0, "order_id": "x-XEKWYICXBSIUT605a30717c7720582", "exchange_order_id": "35297780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:34:32,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a30717c7720582. +2023-09-18 14:34:32,882 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a30a5ea5e50582 for 80.00000000 SEI-USDT. +2023-09-18 14:34:32,977 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047672.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a30a5ea5e50582", "creation_timestamp": 1695047672.0, "exchange_order_id": "35298754", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:34:33,042 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047672.0, "order_id": "x-XEKWYICXSSIUT605a30717c9e20582", "exchange_order_id": "35297779", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:34:33,042 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a30717c9e20582. +2023-09-18 14:34:33,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a30a5ec3fc0582 for 80.00000000 SEI-USDT. +2023-09-18 14:34:33,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047673.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12720000", "order_id": "x-XEKWYICXSSIUT605a30a5ec3fc0582", "creation_timestamp": 1695047672.0, "exchange_order_id": "35298755", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:35:27,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a30a5ea5e50582. [clock=2023-09-18 14:35:27+00:00] +2023-09-18 14:35:27,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a30a5ec3fc0582. [clock=2023-09-18 14:35:27+00:00] +2023-09-18 14:35:27,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259370418191765216381053090 amount: 80. +2023-09-18 14:35:27,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1270034903532188635124514142 amount: 80. +2023-09-18 14:35:27,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047727.0, "order_id": "x-XEKWYICXBSIUT605a30a5ea5e50582", "exchange_order_id": "35298754", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:35:27,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a30a5ea5e50582. +2023-09-18 14:35:27,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a30da56a5c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:35:27,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047727.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a30da56a5c0582", "creation_timestamp": 1695047727.0, "exchange_order_id": "35299103", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:35:27,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a30da56d090582 for 80.00000000 SEI-USDT. +2023-09-18 14:35:27,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047727.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXSSIUT605a30da56d090582", "creation_timestamp": 1695047727.0, "exchange_order_id": "35299104", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:35:27,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047727.0, "order_id": "x-XEKWYICXSSIUT605a30a5ec3fc0582", "exchange_order_id": "35298755", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:35:27,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a30a5ec3fc0582. +2023-09-18 14:36:22,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a30da56a5c0582. [clock=2023-09-18 14:36:22+00:00] +2023-09-18 14:36:22,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a30da56d090582. [clock=2023-09-18 14:36:22+00:00] +2023-09-18 14:36:22,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257685161207528813172117337 amount: 80. +2023-09-18 14:36:22,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267155990766411259995665157 amount: 80. +2023-09-18 14:36:22,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047782.0, "order_id": "x-XEKWYICXBSIUT605a30da56a5c0582", "exchange_order_id": "35299103", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:36:22,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a30da56a5c0582. +2023-09-18 14:36:22,322 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047782.0, "order_id": "x-XEKWYICXSSIUT605a30da56d090582", "exchange_order_id": "35299104", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:36:22,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a30da56d090582. +2023-09-18 14:36:22,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a310eca33d0582 for 80.00000000 SEI-USDT. +2023-09-18 14:36:22,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047782.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a310eca33d0582", "creation_timestamp": 1695047782.0, "exchange_order_id": "35299785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:36:22,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a310eca6de0582 for 80.00000000 SEI-USDT. +2023-09-18 14:36:22,398 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047782.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605a310eca6de0582", "creation_timestamp": 1695047782.0, "exchange_order_id": "35299786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:37:17,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a310eca33d0582. [clock=2023-09-18 14:37:17+00:00] +2023-09-18 14:37:17,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a310eca6de0582. [clock=2023-09-18 14:37:17+00:00] +2023-09-18 14:37:17,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260571383377118461208624498 amount: 80. +2023-09-18 14:37:17,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1268976499828580985000350330 amount: 80. +2023-09-18 14:37:17,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047837.0, "order_id": "x-XEKWYICXBSIUT605a310eca33d0582", "exchange_order_id": "35299785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:37:17,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a310eca33d0582. +2023-09-18 14:37:17,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047837.0, "order_id": "x-XEKWYICXSSIUT605a310eca6de0582", "exchange_order_id": "35299786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:37:17,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a310eca6de0582. +2023-09-18 14:37:17,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a31433ef280582 for 80.00000000 SEI-USDT. +2023-09-18 14:37:17,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047837.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12680000", "order_id": "x-XEKWYICXSSIUT605a31433ef280582", "creation_timestamp": 1695047837.0, "exchange_order_id": "35300329", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:37:17,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a31433ec4c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:37:17,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047837.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a31433ec4c0582", "creation_timestamp": 1695047837.0, "exchange_order_id": "35300331", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:37:47,543 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a31433ef280582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:37:47,544 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:37:47+00:00] +2023-09-18 14:37:47,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047867.0, "order_id": "x-XEKWYICXSSIUT605a31433ef280582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12680000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01014400"}]}, "exchange_trade_id": "4968921", "exchange_order_id": "35300329", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:37:47,588 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047867.0, "order_id": "x-XEKWYICXSSIUT605a31433ef280582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35300329", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:37:47,589 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a31433ef280582 completely filled. +2023-09-18 14:38:12,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a31433ec4c0582. [clock=2023-09-18 14:38:12+00:00] +2023-09-18 14:38:12,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1265272235168460086608947829 amount: 80. +2023-09-18 14:38:12,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1276792097689133450991784134 amount: 80. +2023-09-18 14:38:12,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047892.0, "order_id": "x-XEKWYICXBSIUT605a31433ec4c0582", "exchange_order_id": "35300331", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:38:12,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a31433ec4c0582. +2023-09-18 14:38:12,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3177b8c4d0582 for 80.00000000 SEI-USDT. +2023-09-18 14:38:12,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047892.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXBSIUT605a3177b8c4d0582", "creation_timestamp": 1695047892.0, "exchange_order_id": "35301599", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:38:12,562 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3177b8f1d0582 for 80.00000000 SEI-USDT. +2023-09-18 14:38:12,605 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047892.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12760000", "order_id": "x-XEKWYICXSSIUT605a3177b8f1d0582", "creation_timestamp": 1695047892.0, "exchange_order_id": "35301600", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:39:07,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3177b8c4d0582. [clock=2023-09-18 14:39:07+00:00] +2023-09-18 14:39:07,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3177b8f1d0582. [clock=2023-09-18 14:39:07+00:00] +2023-09-18 14:39:07,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1264287426556822383985082217 amount: 80. +2023-09-18 14:39:07,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1273766654131935153680602251 amount: 80. +2023-09-18 14:39:07,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047947.0, "order_id": "x-XEKWYICXBSIUT605a3177b8c4d0582", "exchange_order_id": "35301599", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:39:07,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3177b8c4d0582. +2023-09-18 14:39:08,034 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a31ac32c1b0582 for 80.00000000 SEI-USDT. +2023-09-18 14:39:08,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047948.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXBSIUT605a31ac32c1b0582", "creation_timestamp": 1695047947.0, "exchange_order_id": "35302140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:39:08,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047948.0, "order_id": "x-XEKWYICXSSIUT605a3177b8f1d0582", "exchange_order_id": "35301600", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:39:08,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3177b8f1d0582. +2023-09-18 14:39:08,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a31ac3703f0582 for 80.00000000 SEI-USDT. +2023-09-18 14:39:08,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695047948.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12730000", "order_id": "x-XEKWYICXSSIUT605a31ac3703f0582", "creation_timestamp": 1695047947.0, "exchange_order_id": "35302141", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:40:02,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a31ac32c1b0582. [clock=2023-09-18 14:40:02+00:00] +2023-09-18 14:40:02,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a31ac3703f0582. [clock=2023-09-18 14:40:02+00:00] +2023-09-18 14:40:02,137 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1266359130952909246012227472 amount: 80. +2023-09-18 14:40:02,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1277842179460130661142343576 amount: 80. +2023-09-18 14:40:02,596 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048002.0, "order_id": "x-XEKWYICXBSIUT605a31ac32c1b0582", "exchange_order_id": "35302140", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:40:02,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a31ac32c1b0582. +2023-09-18 14:40:02,652 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048002.0, "order_id": "x-XEKWYICXSSIUT605a31ac3703f0582", "exchange_order_id": "35302141", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:40:02,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a31ac3703f0582. +2023-09-18 14:40:03,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a31e0b74510582 for 80.00000000 SEI-USDT. +2023-09-18 14:40:03,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048003.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXBSIUT605a31e0b74510582", "creation_timestamp": 1695048002.0, "exchange_order_id": "35302834", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:40:03,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a31e0b78750582 for 80.00000000 SEI-USDT. +2023-09-18 14:40:03,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048003.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12770000", "order_id": "x-XEKWYICXSSIUT605a31e0b78750582", "creation_timestamp": 1695048002.0, "exchange_order_id": "35302835", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:40:15,167 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:40:15,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048015.0, "order_id": "x-XEKWYICXBSIUT605a31e0b74510582", "exchange_order_id": "35302834", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:40:15,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a31e0b74510582. +2023-09-18 14:40:15,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048015.0, "order_id": "x-XEKWYICXSSIUT605a31e0b78750582", "exchange_order_id": "35302835", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:40:15,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a31e0b78750582. +2023-09-18 14:40:26,221 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:40:26,224 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:40:26,340 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:40:26,405 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:40:26,545 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:40:26,546 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:40:26,680 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:40:26,750 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:40:27,001 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:40:27,751 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:40:29,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260896766109416888362145539 amount: 80. +2023-09-18 14:40:29,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1271114372929029218231378857 amount: 80. +2023-09-18 14:40:29,071 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a31fa58af50582 for 80.00000000 SEI-USDT. +2023-09-18 14:40:29,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a31fa58af50582", "creation_timestamp": 1695048029.0, "exchange_order_id": "35303159", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:40:29,085 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a31fa58d780582 for 80.00000000 SEI-USDT. +2023-09-18 14:40:29,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12710000", "order_id": "x-XEKWYICXSSIUT605a31fa58d780582", "creation_timestamp": 1695048029.0, "exchange_order_id": "35303160", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:41:21,910 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a31fa58d780582 amounting to 75.00000000/80.00000000 SEI has been filled. +2023-09-18 14:41:21,912 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 75.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:41:21+00:00] +2023-09-18 14:41:21,941 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048081.0, "order_id": "x-XEKWYICXSSIUT605a31fa58d780582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12710000", "amount": "75.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00953250"}]}, "exchange_trade_id": "4969178", "exchange_order_id": "35303160", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:41:24,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a31fa58af50582. [clock=2023-09-18 14:41:24+00:00] +2023-09-18 14:41:24,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a31fa58d780582. [clock=2023-09-18 14:41:24+00:00] +2023-09-18 14:41:24,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1269011802822752533062024677 amount: 80. +2023-09-18 14:41:24,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1277666469742455181371064279 amount: 80. +2023-09-18 14:41:24,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048084.0, "order_id": "x-XEKWYICXBSIUT605a31fa58af50582", "exchange_order_id": "35303159", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:41:24,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a31fa58af50582. +2023-09-18 14:41:24,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048084.0, "order_id": "x-XEKWYICXSSIUT605a31fa58d780582", "exchange_order_id": "35303160", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:41:24,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a31fa58d780582. +2023-09-18 14:41:24,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a322ecceae0582 for 80.00000000 SEI-USDT. +2023-09-18 14:41:24,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048084.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXBSIUT605a322ecceae0582", "creation_timestamp": 1695048084.0, "exchange_order_id": "35303599", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:41:24,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a322ecd2670582 for 80.00000000 SEI-USDT. +2023-09-18 14:41:24,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048084.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12770000", "order_id": "x-XEKWYICXSSIUT605a322ecd2670582", "creation_timestamp": 1695048084.0, "exchange_order_id": "35303598", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:42:19,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a322ecceae0582. [clock=2023-09-18 14:42:19+00:00] +2023-09-18 14:42:19,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a322ecd2670582. [clock=2023-09-18 14:42:19+00:00] +2023-09-18 14:42:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1270239282783009484162498113 amount: 80. +2023-09-18 14:42:19,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1278157169722667029847721081 amount: 80. +2023-09-18 14:42:19,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048139.0, "order_id": "x-XEKWYICXBSIUT605a322ecceae0582", "exchange_order_id": "35303599", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:42:19,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a322ecceae0582. +2023-09-18 14:42:19,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048139.0, "order_id": "x-XEKWYICXSSIUT605a322ecd2670582", "exchange_order_id": "35303598", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:42:19,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a322ecd2670582. +2023-09-18 14:42:19,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a326340a320582 for 80.00000000 SEI-USDT. +2023-09-18 14:42:19,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048139.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXBSIUT605a326340a320582", "creation_timestamp": 1695048139.0, "exchange_order_id": "35304184", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:42:19,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a326340c9e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:42:19,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048139.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12780000", "order_id": "x-XEKWYICXSSIUT605a326340c9e0582", "creation_timestamp": 1695048139.0, "exchange_order_id": "35304185", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:43:14,390 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a326340a320582. [clock=2023-09-18 14:43:14+00:00] +2023-09-18 14:43:14,415 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a326340c9e0582. [clock=2023-09-18 14:43:14+00:00] +2023-09-18 14:43:14,474 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12760000 amount: 80. +2023-09-18 14:43:14,474 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1287271085142077059574606228 amount: 80. +2023-09-18 14:43:14,979 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048194.0, "order_id": "x-XEKWYICXBSIUT605a326340a320582", "exchange_order_id": "35304184", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:43:14,979 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a326340a320582. +2023-09-18 14:43:15,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a32982441e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:43:15,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048195.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12870000", "order_id": "x-XEKWYICXSSIUT605a32982441e0582", "creation_timestamp": 1695048194.0, "exchange_order_id": "35305210", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:43:15,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048195.0, "order_id": "x-XEKWYICXSSIUT605a326340c9e0582", "exchange_order_id": "35304185", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:43:15,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a326340c9e0582. +2023-09-18 14:43:15,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a32982425e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:43:15,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048195.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12760000", "order_id": "x-XEKWYICXBSIUT605a32982425e0582", "creation_timestamp": 1695048194.0, "exchange_order_id": "35305209", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:43:27,616 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a32982425e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:43:27,645 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:43:27+00:00] +2023-09-18 14:43:27,716 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048207.0, "order_id": "x-XEKWYICXBSIUT605a32982425e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12760000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4969341", "exchange_order_id": "35305209", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:43:27,774 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048207.0, "order_id": "x-XEKWYICXBSIUT605a32982425e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.2080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35305209", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:43:27,774 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a32982425e0582 completely filled. +2023-09-18 14:44:09,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a32982441e0582. [clock=2023-09-18 14:44:09+00:00] +2023-09-18 14:44:09,175 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1271272376779389878737697787 amount: 80. +2023-09-18 14:44:09,176 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1279900065957223276864196101 amount: 80. +2023-09-18 14:44:10,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048249.0, "order_id": "x-XEKWYICXSSIUT605a32982441e0582", "exchange_order_id": "35305210", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:44:10,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a32982441e0582. +2023-09-18 14:44:10,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a32cc4f1d70582 for 80.00000000 SEI-USDT. +2023-09-18 14:44:10,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048249.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12710000", "order_id": "x-XEKWYICXBSIUT605a32cc4f1d70582", "creation_timestamp": 1695048249.0, "exchange_order_id": "35305863", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:44:10,838 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a32cc4f4050582 for 80.00000000 SEI-USDT. +2023-09-18 14:44:10,873 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048250.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12790000", "order_id": "x-XEKWYICXSSIUT605a32cc4f4050582", "creation_timestamp": 1695048249.0, "exchange_order_id": "35305871", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:44:17,104 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:44:17,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048257.0, "order_id": "x-XEKWYICXBSIUT605a32cc4f1d70582", "exchange_order_id": "35305863", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:44:17,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a32cc4f1d70582. +2023-09-18 14:44:17,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048257.0, "order_id": "x-XEKWYICXSSIUT605a32cc4f4050582", "exchange_order_id": "35305871", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:44:17,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a32cc4f4050582. +2023-09-18 14:44:21,911 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:44:21,932 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:44:22,389 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:44:22,862 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:44:23,111 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:44:23,112 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:44:23,667 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:44:23,955 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:44:24,236 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:44:24,627 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:44:25,351 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:44:26,395 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:44:27,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262548321615526955536876808 amount: 80. +2023-09-18 14:44:27,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1272471291672075817944048014 amount: 80. +2023-09-18 14:44:27,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a32dd7bf830582 for 80.00000000 SEI-USDT. +2023-09-18 14:44:27,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048267.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12720000", "order_id": "x-XEKWYICXSSIUT605a32dd7bf830582", "creation_timestamp": 1695048267.0, "exchange_order_id": "35306242", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:44:27,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a32dd7bbf30582 for 80.00000000 SEI-USDT. +2023-09-18 14:44:27,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048267.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605a32dd7bbf30582", "creation_timestamp": 1695048267.0, "exchange_order_id": "35306244", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:44:31,183 - 1 - hummingbot.core.utils.async_utils - ERROR - Unhandled error in background task: 'PMMhShiftedMidPriceDynamicSpread' object has no attribute 'trend_price_shift' +Traceback (most recent call last): + File "/home/hummingbot/hummingbot/core/utils/async_utils.py", line 9, in safe_wrapper + return await c + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 143, in status_check_all + await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 0.1 + File "/home/hummingbot/hummingbot/client/command/status_command.py", line 77, in strategy_status + st_status = self.strategy.format_status() + File "/home/hummingbot/scripts/bot_battle.py", line 194, in format_status + lines.extend(["", f" Orig Price: {self.orig_price:.4f} | Trend Shift: {self.trend_price_shift:.4f} | Inventory Shift: {self.inventory_price_shift:.4f} | Reference Price: {self.reference_price:.4f}"]) +AttributeError: 'PMMhShiftedMidPriceDynamicSpread' object has no attribute 'trend_price_shift' +2023-09-18 14:44:41,776 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a32dd7bf830582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:44:41,777 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:44:41+00:00] +2023-09-18 14:44:41,893 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048281.0, "order_id": "x-XEKWYICXSSIUT605a32dd7bf830582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12720000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01017600"}]}, "exchange_trade_id": "4969469", "exchange_order_id": "35306242", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:44:41,923 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048281.0, "order_id": "x-XEKWYICXSSIUT605a32dd7bf830582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35306242", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:44:41,923 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a32dd7bf830582 completely filled. +2023-09-18 14:45:17,400 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:45:17,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048317.0, "order_id": "x-XEKWYICXBSIUT605a32dd7bbf30582", "exchange_order_id": "35306244", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:45:17,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a32dd7bbf30582. +2023-09-18 14:45:21,689 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:45:21,693 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:45:21,885 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:45:21,951 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:45:22,052 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:45:22,189 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:45:22,471 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:45:22,474 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:45:22,479 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:45:23,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:45:23,222 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:45:25,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263939285047279273576986718 amount: 80. +2023-09-18 14:45:25,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1274189363609373834605568236 amount: 80. +2023-09-18 14:45:25,076 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3314a276c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:45:25,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048325.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a3314a276c0582", "creation_timestamp": 1695048325.0, "exchange_order_id": "35307539", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:45:25,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3314a2af20582 for 80.00000000 SEI-USDT. +2023-09-18 14:45:25,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048325.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12740000", "order_id": "x-XEKWYICXSSIUT605a3314a2af20582", "creation_timestamp": 1695048325.0, "exchange_order_id": "35307538", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:45:56,688 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:45:56,782 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048356.0, "order_id": "x-XEKWYICXBSIUT605a3314a276c0582", "exchange_order_id": "35307539", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:45:56,783 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3314a276c0582. +2023-09-18 14:45:56,878 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048356.0, "order_id": "x-XEKWYICXSSIUT605a3314a2af20582", "exchange_order_id": "35307538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:45:56,878 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3314a2af20582. +2023-09-18 14:46:16,225 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:46:16,229 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:46:16,319 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:46:16,394 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:46:16,562 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:46:16,564 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:46:16,922 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:46:16,925 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:46:17,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:46:18,466 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:46:20,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262441112610908940324601754 amount: 80. +2023-09-18 14:46:20,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1271590130014271751302972300 amount: 80. +2023-09-18 14:46:20,081 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a33491664c0582 for 80.00000000 SEI-USDT. +2023-09-18 14:46:20,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048380.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605a33491664c0582", "creation_timestamp": 1695048380.0, "exchange_order_id": "35308050", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:46:20,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a334916ce70582 for 80.00000000 SEI-USDT. +2023-09-18 14:46:20,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048380.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12710000", "order_id": "x-XEKWYICXSSIUT605a334916ce70582", "creation_timestamp": 1695048380.0, "exchange_order_id": "35308051", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:46:35,302 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:46:35,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048395.0, "order_id": "x-XEKWYICXBSIUT605a33491664c0582", "exchange_order_id": "35308050", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:46:35,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a33491664c0582. +2023-09-18 14:46:35,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048395.0, "order_id": "x-XEKWYICXSSIUT605a334916ce70582", "exchange_order_id": "35308051", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:46:35,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a334916ce70582. +2023-09-18 14:47:21,500 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:47:23,298 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:47:23,302 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:47:23,387 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:47:23,566 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:47:23,944 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:47:23,950 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:47:23,953 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:47:24,018 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:47:24,177 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:47:26,002 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:47:33,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256348227745156131871921429 amount: 80. +2023-09-18 14:47:33,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267002755908254521101220043 amount: 80. +2023-09-18 14:47:33,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a338eb51e70582 for 80.00000000 SEI-USDT. +2023-09-18 14:47:33,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048453.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605a338eb51e70582", "creation_timestamp": 1695048453.0, "exchange_order_id": "35309268", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:47:33,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a338eb4d610582 for 80.00000000 SEI-USDT. +2023-09-18 14:47:33,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048453.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a338eb4d610582", "creation_timestamp": 1695048453.0, "exchange_order_id": "35309269", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:48:15,931 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - ERROR - Unexpected error while processing event 106. +Traceback (most recent call last): + File "hummingbot/core/pubsub.pyx", line 165, in hummingbot.core.pubsub.PubSub.c_trigger_event + typed_listener.c_call(arg) + File "hummingbot/core/event/event_listener.pyx", line 25, in hummingbot.core.event.event_listener.EventListener.c_call + self(arg) + File "/home/hummingbot/hummingbot/core/event/event_forwarder.py", line 24, in __call__ + self._to_function(self.current_event_tag, self.current_event_caller, arg) + File "/home/hummingbot/hummingbot/remote_iface/mqtt.py", line 462, in _send_mqtt_event + timestamp=int(timestamp), +ValueError: cannot convert float NaN to integer +2023-09-18 14:48:15,975 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": NaN, "order_id": "x-XEKWYICXBSIUT605a338eb4d610582", "exchange_order_id": "35309269", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:48:15,975 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a338eb4d610582. +2023-09-18 14:48:16,157 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - ERROR - Unexpected error while processing event 106. +Traceback (most recent call last): + File "hummingbot/core/pubsub.pyx", line 165, in hummingbot.core.pubsub.PubSub.c_trigger_event + typed_listener.c_call(arg) + File "hummingbot/core/event/event_listener.pyx", line 25, in hummingbot.core.event.event_listener.EventListener.c_call + self(arg) + File "/home/hummingbot/hummingbot/core/event/event_forwarder.py", line 24, in __call__ + self._to_function(self.current_event_tag, self.current_event_caller, arg) + File "/home/hummingbot/hummingbot/remote_iface/mqtt.py", line 462, in _send_mqtt_event + timestamp=int(timestamp), +ValueError: cannot convert float NaN to integer +2023-09-18 14:48:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": NaN, "order_id": "x-XEKWYICXSSIUT605a338eb51e70582", "exchange_order_id": "35309268", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:48:16,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a338eb51e70582. +2023-09-18 14:48:18,022 - 1 - hummingbot.client.hummingbot_application - INFO - MQTT Bridge disconnected +2023-09-18 14:52:53,561 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:52:53,565 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:52:53,746 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:52:53,810 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:52:53,945 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:52:53,946 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:52:54,006 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:52:54,273 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:52:54,274 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:52:56,363 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:53:01,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1261054673012228684070706861 amount: 80. +2023-09-18 14:53:01,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267503308743973081932564649 amount: 80. +2023-09-18 14:53:01,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a34c7829770582 for 80.00000000 SEI-USDT. +2023-09-18 14:53:01,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXBSIUT605a34c7829770582", "creation_timestamp": 1695048781.0, "exchange_order_id": "35312230", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:53:01,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a34c782f7d0582 for 80.00000000 SEI-USDT. +2023-09-18 14:53:01,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605a34c782f7d0582", "creation_timestamp": 1695048781.0, "exchange_order_id": "35312231", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:53:30,269 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a34c782f7d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:53:30,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:53:30+00:00] +2023-09-18 14:53:30,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048810.0, "order_id": "x-XEKWYICXSSIUT605a34c782f7d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12670000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01013600"}]}, "exchange_trade_id": "4970053", "exchange_order_id": "35312231", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:53:30,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048810.0, "order_id": "x-XEKWYICXSSIUT605a34c782f7d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35312231", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:53:30,661 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a34c782f7d0582 completely filled. +2023-09-18 14:53:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a34c7829770582. [clock=2023-09-18 14:53:56+00:00] +2023-09-18 14:53:56,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1265150383549553815530343016 amount: 80. +2023-09-18 14:53:56,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1270843960830960969713663214 amount: 80. +2023-09-18 14:53:56,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048836.0, "order_id": "x-XEKWYICXBSIUT605a34c7829770582", "exchange_order_id": "35312230", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:53:56,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a34c7829770582. +2023-09-18 14:53:56,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a34fbf955e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:53:56,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXBSIUT605a34fbf955e0582", "creation_timestamp": 1695048836.0, "exchange_order_id": "35312485", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:53:56,427 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a34fbf98200582 for 80.00000000 SEI-USDT. +2023-09-18 14:53:56,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXSSIUT605a34fbf98200582", "creation_timestamp": 1695048836.0, "exchange_order_id": "35312486", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:54:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a34fbf955e0582. [clock=2023-09-18 14:54:51+00:00] +2023-09-18 14:54:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a34fbf98200582. [clock=2023-09-18 14:54:51+00:00] +2023-09-18 14:54:51,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263916768898008497326500866 amount: 80. +2023-09-18 14:54:51,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:54:51,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048891.0, "order_id": "x-XEKWYICXBSIUT605a34fbf955e0582", "exchange_order_id": "35312485", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:54:51,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a34fbf955e0582. +2023-09-18 14:54:51,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3530719ac0582 for 80.00000000 SEI-USDT. +2023-09-18 14:54:51,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a3530719ac0582", "creation_timestamp": 1695048891.0, "exchange_order_id": "35312859", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:54:51,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048891.0, "order_id": "x-XEKWYICXSSIUT605a34fbf98200582", "exchange_order_id": "35312486", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:54:51,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a34fbf98200582. +2023-09-18 14:55:06,359 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-18 14:55:06,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048906.0, "order_id": "x-XEKWYICXBSIUT605a3530719ac0582", "exchange_order_id": "35312859", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:55:06,615 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3530719ac0582. +2023-09-18 14:55:14,427 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-18 14:55:14,432 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-18 14:55:14,559 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:55:14,615 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:55:14,748 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-18 14:55:14,801 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-18 14:55:14,936 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-18 14:55:14,986 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-18 14:55:15,033 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-18 14:55:15,974 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-18 14:55:26,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262157248341361107003953991 amount: 80. +2023-09-18 14:55:26,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266633699300387585656156361 amount: 80. +2023-09-18 14:55:26,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3551ca79a0582 for 80.00000000 SEI-USDT. +2023-09-18 14:55:26,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605a3551ca79a0582", "creation_timestamp": 1695048926.0, "exchange_order_id": "35313206", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:55:26,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3551cab510582 for 80.00000000 SEI-USDT. +2023-09-18 14:55:26,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605a3551cab510582", "creation_timestamp": 1695048926.0, "exchange_order_id": "35313207", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:55:26,444 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3551cab510582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:55:26,446 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:55:26+00:00] +2023-09-18 14:55:26,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048926.0, "order_id": "x-XEKWYICXSSIUT605a3551cab510582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12660000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01012800"}]}, "exchange_trade_id": "4970176", "exchange_order_id": "35313207", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:55:26,478 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048926.0, "order_id": "x-XEKWYICXSSIUT605a3551cab510582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35313207", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:55:26,478 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3551cab510582 completely filled. +2023-09-18 14:56:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3551ca79a0582. [clock=2023-09-18 14:56:21+00:00] +2023-09-18 14:56:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1265669259778081676662052001 amount: 80. +2023-09-18 14:56:21,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:56:21,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048981.0, "order_id": "x-XEKWYICXBSIUT605a3551ca79a0582", "exchange_order_id": "35313206", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:56:21,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3551ca79a0582. +2023-09-18 14:56:21,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a35863ef4e0582 for 80.00000000 SEI-USDT. +2023-09-18 14:56:21,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695048981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXBSIUT605a35863ef4e0582", "creation_timestamp": 1695048981.0, "exchange_order_id": "35313445", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:57:16,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a35863ef4e0582. [clock=2023-09-18 14:57:16+00:00] +2023-09-18 14:57:16,292 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1265705087014478098471278410 amount: 80. +2023-09-18 14:57:16,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:57:17,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049036.0, "order_id": "x-XEKWYICXBSIUT605a35863ef4e0582", "exchange_order_id": "35313445", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:57:17,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a35863ef4e0582. +2023-09-18 14:57:18,022 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a35baf76160582 for 80.00000000 SEI-USDT. +2023-09-18 14:57:18,063 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049037.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXBSIUT605a35baf76160582", "creation_timestamp": 1695049036.0, "exchange_order_id": "35313800", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:58:05,119 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a35baf76160582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 14:58:05,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 14:58:04+00:00] +2023-09-18 14:58:05,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049084.0, "order_id": "x-XEKWYICXBSIUT605a35baf76160582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12650000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4970237", "exchange_order_id": "35313800", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 14:58:05,340 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049085.0, "order_id": "x-XEKWYICXBSIUT605a35baf76160582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35313800", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 14:58:05,340 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a35baf76160582 completely filled. +2023-09-18 14:58:11,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1264152088728779159649490464 amount: 80. +2023-09-18 14:58:11,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267959777666271301309574924 amount: 80. +2023-09-18 14:58:11,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a35ef320c50582 for 80.00000000 SEI-USDT. +2023-09-18 14:58:11,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605a35ef320c50582", "creation_timestamp": 1695049091.0, "exchange_order_id": "35314196", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:58:11,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a35ef2fe0f0582 for 80.00000000 SEI-USDT. +2023-09-18 14:58:11,545 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXBSIUT605a35ef2fe0f0582", "creation_timestamp": 1695049091.0, "exchange_order_id": "35314197", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 14:59:06,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a35ef2fe0f0582. [clock=2023-09-18 14:59:06+00:00] +2023-09-18 14:59:06,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a35ef320c50582. [clock=2023-09-18 14:59:06+00:00] +2023-09-18 14:59:06,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263090430277002704900447791 amount: 80. +2023-09-18 14:59:06,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 14:59:06,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049146.0, "order_id": "x-XEKWYICXBSIUT605a35ef2fe0f0582", "exchange_order_id": "35314197", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:59:06,470 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a35ef2fe0f0582. +2023-09-18 14:59:07,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049147.0, "order_id": "x-XEKWYICXSSIUT605a35ef320c50582", "exchange_order_id": "35314196", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 14:59:07,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a35ef320c50582. +2023-09-18 14:59:07,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3623a0ef30582 for 80.00000000 SEI-USDT. +2023-09-18 14:59:07,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049147.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a3623a0ef30582", "creation_timestamp": 1695049146.0, "exchange_order_id": "35314582", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:00:01,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3623a0ef30582. [clock=2023-09-18 15:00:01+00:00] +2023-09-18 15:00:01,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1261742280693728685520286295 amount: 80. +2023-09-18 15:00:01,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266553761749856376653468380 amount: 80. +2023-09-18 15:00:01,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049201.0, "order_id": "x-XEKWYICXBSIUT605a3623a0ef30582", "exchange_order_id": "35314582", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:00:01,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3623a0ef30582. +2023-09-18 15:00:03,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a36582a8ec0582 for 80.00000000 SEI-USDT. +2023-09-18 15:00:03,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049202.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXBSIUT605a36582a8ec0582", "creation_timestamp": 1695049201.0, "exchange_order_id": "35315100", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:00:03,940 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a36582ab000582 for 80.00000000 SEI-USDT. +2023-09-18 15:00:04,013 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049203.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605a36582ab000582", "creation_timestamp": 1695049201.0, "exchange_order_id": "35315103", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:00:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a36582a8ec0582. [clock=2023-09-18 15:00:56+00:00] +2023-09-18 15:00:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a36582ab000582. [clock=2023-09-18 15:00:56+00:00] +2023-09-18 15:00:56,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1263152662172007712742114950 amount: 80. +2023-09-18 15:00:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:00:56,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049256.0, "order_id": "x-XEKWYICXBSIUT605a36582a8ec0582", "exchange_order_id": "35315100", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:00:56,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a36582a8ec0582. +2023-09-18 15:00:56,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049256.0, "order_id": "x-XEKWYICXSSIUT605a36582ab000582", "exchange_order_id": "35315103", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:00:56,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a36582ab000582. +2023-09-18 15:00:56,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a368c818840582 for 80.00000000 SEI-USDT. +2023-09-18 15:00:56,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a368c818840582", "creation_timestamp": 1695049256.0, "exchange_order_id": "35315502", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:01:12,046 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a368c818840582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:01:12,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:01:12+00:00] +2023-09-18 15:01:12,074 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049272.0, "order_id": "x-XEKWYICXBSIUT605a368c818840582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12630000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4970340", "exchange_order_id": "35315502", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:01:12,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049272.0, "order_id": "x-XEKWYICXBSIUT605a368c818840582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35315502", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:01:12,088 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a368c818840582 completely filled. +2023-09-18 15:01:51,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255037548990877179037737082 amount: 80. +2023-09-18 15:01:51,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261785829148873684080986376 amount: 80. +2023-09-18 15:01:51,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a36c0fb0ee0582 for 80.00000000 SEI-USDT. +2023-09-18 15:01:51,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a36c0fb0ee0582", "creation_timestamp": 1695049311.0, "exchange_order_id": "35316151", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:01:51,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a36c0fb52a0582 for 80.00000000 SEI-USDT. +2023-09-18 15:01:51,347 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a36c0fb52a0582", "creation_timestamp": 1695049311.0, "exchange_order_id": "35316154", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:01:56,865 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a36c0fb52a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:01:56,866 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:01:56+00:00] +2023-09-18 15:01:56,893 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049316.0, "order_id": "x-XEKWYICXSSIUT605a36c0fb52a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12610000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01008800"}]}, "exchange_trade_id": "4970422", "exchange_order_id": "35316154", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:01:56,907 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049316.0, "order_id": "x-XEKWYICXSSIUT605a36c0fb52a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35316154", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:01:56,908 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a36c0fb52a0582 completely filled. +2023-09-18 15:02:46,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a36c0fb0ee0582. [clock=2023-09-18 15:02:46+00:00] +2023-09-18 15:02:46,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256829456172395584606222550 amount: 80. +2023-09-18 15:02:46,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263417665118534424453537182 amount: 80. +2023-09-18 15:02:46,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049366.0, "order_id": "x-XEKWYICXBSIUT605a36c0fb0ee0582", "exchange_order_id": "35316151", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:02:46,383 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a36c0fb0ee0582. +2023-09-18 15:02:47,051 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a36f5794f70582 for 80.00000000 SEI-USDT. +2023-09-18 15:02:47,085 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a36f5794f70582", "creation_timestamp": 1695049366.0, "exchange_order_id": "35316709", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:02:47,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a36f5797b10582 for 80.00000000 SEI-USDT. +2023-09-18 15:02:47,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a36f5797b10582", "creation_timestamp": 1695049366.0, "exchange_order_id": "35316710", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:03:33,519 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a36f5797b10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:03:33,520 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:03:33+00:00] +2023-09-18 15:03:33,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049413.0, "order_id": "x-XEKWYICXSSIUT605a36f5797b10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12630000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01010400"}]}, "exchange_trade_id": "4970532", "exchange_order_id": "35316710", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:03:33,886 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049413.0, "order_id": "x-XEKWYICXSSIUT605a36f5797b10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35316710", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:03:33,887 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a36f5797b10582 completely filled. +2023-09-18 15:03:41,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a36f5794f70582. [clock=2023-09-18 15:03:41+00:00] +2023-09-18 15:03:41,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262279813386876835899289347 amount: 80. +2023-09-18 15:03:41,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:03:41,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049421.0, "order_id": "x-XEKWYICXBSIUT605a36f5794f70582", "exchange_order_id": "35316709", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:03:41,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a36f5794f70582. +2023-09-18 15:03:42,992 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3729ff80a0582 for 80.00000000 SEI-USDT. +2023-09-18 15:03:43,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049422.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605a3729ff80a0582", "creation_timestamp": 1695049421.0, "exchange_order_id": "35317397", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:04:35,880 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3729ff80a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:04:35,882 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:04:35+00:00] +2023-09-18 15:04:35,954 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049475.0, "order_id": "x-XEKWYICXBSIUT605a3729ff80a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12620000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4970559", "exchange_order_id": "35317397", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:04:36,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049475.0, "order_id": "x-XEKWYICXBSIUT605a3729ff80a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35317397", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:04:36,236 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3729ff80a0582 completely filled. +2023-09-18 15:04:36,578 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259435442128314786680174740 amount: 80. +2023-09-18 15:04:36,580 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265648563305313316051855248 amount: 80. +2023-09-18 15:04:37,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a375eda3270582 for 80.00000000 SEI-USDT. +2023-09-18 15:04:37,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049477.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a375eda3270582", "creation_timestamp": 1695049476.0, "exchange_order_id": "35317722", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:04:38,892 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a375eda6540582 for 80.00000000 SEI-USDT. +2023-09-18 15:04:38,935 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049478.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a375eda6540582", "creation_timestamp": 1695049476.0, "exchange_order_id": "35317735", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:05:06,747 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a375eda3270582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:05:06,749 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:05:06+00:00] +2023-09-18 15:05:06,803 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049506.0, "order_id": "x-XEKWYICXBSIUT605a375eda3270582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12590000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4970621", "exchange_order_id": "35317722", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:05:06,854 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049506.0, "order_id": "x-XEKWYICXBSIUT605a375eda3270582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35317722", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:05:06,855 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a375eda3270582 completely filled. +2023-09-18 15:05:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a375eda6540582. [clock=2023-09-18 15:05:31+00:00] +2023-09-18 15:05:31,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252719904609057901740053777 amount: 80. +2023-09-18 15:05:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259905257604412999356919599 amount: 80. +2023-09-18 15:05:31,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049531.0, "order_id": "x-XEKWYICXSSIUT605a375eda6540582", "exchange_order_id": "35317735", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:05:31,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a375eda6540582. +2023-09-18 15:05:31,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3792c43370582 for 80.00000000 SEI-USDT. +2023-09-18 15:05:31,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3792c43370582", "creation_timestamp": 1695049531.0, "exchange_order_id": "35318538", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:05:31,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3792c456f0582 for 80.00000000 SEI-USDT. +2023-09-18 15:05:31,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a3792c456f0582", "creation_timestamp": 1695049531.0, "exchange_order_id": "35318539", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:05:35,381 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3792c456f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:05:35,382 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:05:35+00:00] +2023-09-18 15:05:35,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049535.0, "order_id": "x-XEKWYICXSSIUT605a3792c456f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12590000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01007200"}]}, "exchange_trade_id": "4970667", "exchange_order_id": "35318539", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:05:35,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049535.0, "order_id": "x-XEKWYICXSSIUT605a3792c456f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35318539", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:05:35,423 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3792c456f0582 completely filled. +2023-09-18 15:06:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3792c43370582. [clock=2023-09-18 15:06:26+00:00] +2023-09-18 15:06:26,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258289996437044309909001272 amount: 80. +2023-09-18 15:06:26,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265079428884909153916217225 amount: 80. +2023-09-18 15:06:26,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049586.0, "order_id": "x-XEKWYICXBSIUT605a3792c43370582", "exchange_order_id": "35318538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:06:26,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3792c43370582. +2023-09-18 15:06:26,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a37c73bf070582 for 80.00000000 SEI-USDT. +2023-09-18 15:06:26,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a37c73bf070582", "creation_timestamp": 1695049586.0, "exchange_order_id": "35319011", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:06:26,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a37c73c2570582 for 80.00000000 SEI-USDT. +2023-09-18 15:06:26,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a37c73c2570582", "creation_timestamp": 1695049586.0, "exchange_order_id": "35319012", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:07:21,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a37c73bf070582. [clock=2023-09-18 15:07:21+00:00] +2023-09-18 15:07:21,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a37c73c2570582. [clock=2023-09-18 15:07:21+00:00] +2023-09-18 15:07:21,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256895268572988123765873980 amount: 80. +2023-09-18 15:07:21,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:07:21,631 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049641.0, "order_id": "x-XEKWYICXBSIUT605a37c73bf070582", "exchange_order_id": "35319011", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:07:21,632 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a37c73bf070582. +2023-09-18 15:07:22,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049642.0, "order_id": "x-XEKWYICXSSIUT605a37c73c2570582", "exchange_order_id": "35319012", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:07:22,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a37c73c2570582. +2023-09-18 15:07:23,400 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a37fbd574c0582 for 80.00000000 SEI-USDT. +2023-09-18 15:07:23,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049642.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a37fbd574c0582", "creation_timestamp": 1695049641.0, "exchange_order_id": "35319416", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:08:16,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a37fbd574c0582. [clock=2023-09-18 15:08:16+00:00] +2023-09-18 15:08:16,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257364408236882167210214410 amount: 80. +2023-09-18 15:08:16,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262510232039093932597185762 amount: 80. +2023-09-18 15:08:17,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049696.0, "order_id": "x-XEKWYICXBSIUT605a37fbd574c0582", "exchange_order_id": "35319416", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:08:17,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a37fbd574c0582. +2023-09-18 15:08:17,357 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3830538fc0582 for 80.00000000 SEI-USDT. +2023-09-18 15:08:17,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3830538fc0582", "creation_timestamp": 1695049696.0, "exchange_order_id": "35319544", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:08:18,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a383053ccd0582 for 80.00000000 SEI-USDT. +2023-09-18 15:08:18,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049698.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a383053ccd0582", "creation_timestamp": 1695049696.0, "exchange_order_id": "35319549", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:09:11,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3830538fc0582. [clock=2023-09-18 15:09:11+00:00] +2023-09-18 15:09:11,136 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a383053ccd0582. [clock=2023-09-18 15:09:11+00:00] +2023-09-18 15:09:11,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257563297139496980051825128 amount: 80. +2023-09-18 15:09:11,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:09:11,963 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049751.0, "order_id": "x-XEKWYICXBSIUT605a3830538fc0582", "exchange_order_id": "35319544", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:09:11,963 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3830538fc0582. +2023-09-18 15:09:13,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049752.0, "order_id": "x-XEKWYICXSSIUT605a383053ccd0582", "exchange_order_id": "35319549", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:09:13,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a383053ccd0582. +2023-09-18 15:09:13,676 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3864bb5370582 for 80.00000000 SEI-USDT. +2023-09-18 15:09:13,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049753.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3864bb5370582", "creation_timestamp": 1695049751.0, "exchange_order_id": "35319656", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:09:43,524 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3864bb5370582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:09:43,537 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:09:43+00:00] +2023-09-18 15:09:43,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049783.0, "order_id": "x-XEKWYICXBSIUT605a3864bb5370582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4970829", "exchange_order_id": "35319656", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:09:43,897 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049783.0, "order_id": "x-XEKWYICXBSIUT605a3864bb5370582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35319656", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:09:43,897 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3864bb5370582 completely filled. +2023-09-18 15:10:06,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249136855712013622901795470 amount: 80. +2023-09-18 15:10:06,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255758929780660774128978917 amount: 80. +2023-09-18 15:10:06,798 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a38993ec890582 for 80.00000000 SEI-USDT. +2023-09-18 15:10:06,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a38993ec890582", "creation_timestamp": 1695049806.0, "exchange_order_id": "35320980", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:10:06,822 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a38993f00e0582 for 80.00000000 SEI-USDT. +2023-09-18 15:10:06,843 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a38993f00e0582", "creation_timestamp": 1695049806.0, "exchange_order_id": "35320981", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:10:36,780 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a38993f00e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:10:36,781 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:10:36+00:00] +2023-09-18 15:10:36,804 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049836.0, "order_id": "x-XEKWYICXSSIUT605a38993f00e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004000"}]}, "exchange_trade_id": "4970938", "exchange_order_id": "35320981", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:10:36,863 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049836.0, "order_id": "x-XEKWYICXSSIUT605a38993f00e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35320981", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:10:36,864 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a38993f00e0582 completely filled. +2023-09-18 15:11:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a38993ec890582. [clock=2023-09-18 15:11:01+00:00] +2023-09-18 15:11:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249970599693321888215707315 amount: 80. +2023-09-18 15:11:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257922245207604215464346805 amount: 80. +2023-09-18 15:11:01,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049861.0, "order_id": "x-XEKWYICXBSIUT605a38993ec890582", "exchange_order_id": "35320980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:11:01,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a38993ec890582. +2023-09-18 15:11:01,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a38cd7b2980582 for 80.00000000 SEI-USDT. +2023-09-18 15:11:01,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a38cd7b2980582", "creation_timestamp": 1695049861.0, "exchange_order_id": "35321356", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:11:01,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a38cd7ae9c0582 for 80.00000000 SEI-USDT. +2023-09-18 15:11:01,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a38cd7ae9c0582", "creation_timestamp": 1695049861.0, "exchange_order_id": "35321357", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:11:44,697 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a38cd7b2980582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:11:44,698 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:11:44+00:00] +2023-09-18 15:11:44,726 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049904.0, "order_id": "x-XEKWYICXSSIUT605a38cd7b2980582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01005600"}]}, "exchange_trade_id": "4970965", "exchange_order_id": "35321356", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:11:44,738 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049904.0, "order_id": "x-XEKWYICXSSIUT605a38cd7b2980582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35321356", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:11:44,739 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a38cd7b2980582 completely filled. +2023-09-18 15:11:56,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a38cd7ae9c0582. [clock=2023-09-18 15:11:56+00:00] +2023-09-18 15:11:56,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254931340917370124362450134 amount: 80. +2023-09-18 15:11:56,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:11:56,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049916.0, "order_id": "x-XEKWYICXBSIUT605a38cd7ae9c0582", "exchange_order_id": "35321357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:11:56,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a38cd7ae9c0582. +2023-09-18 15:11:56,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3901f7a810582 for 80.00000000 SEI-USDT. +2023-09-18 15:11:56,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3901f7a810582", "creation_timestamp": 1695049916.0, "exchange_order_id": "35321714", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:12:51,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3901f7a810582. [clock=2023-09-18 15:12:51+00:00] +2023-09-18 15:12:51,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257220765122250738551296013 amount: 80. +2023-09-18 15:12:51,134 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:12:51,637 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049971.0, "order_id": "x-XEKWYICXBSIUT605a3901f7a810582", "exchange_order_id": "35321714", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:12:51,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3901f7a810582. +2023-09-18 15:12:52,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a39367efc40582 for 80.00000000 SEI-USDT. +2023-09-18 15:12:52,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695049972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a39367efc40582", "creation_timestamp": 1695049971.0, "exchange_order_id": "35322365", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:13:22,970 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a39367efc40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:13:22,971 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:13:22+00:00] +2023-09-18 15:13:23,053 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050002.0, "order_id": "x-XEKWYICXBSIUT605a39367efc40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971131", "exchange_order_id": "35322365", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:13:23,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050002.0, "order_id": "x-XEKWYICXBSIUT605a39367efc40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35322365", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:13:23,312 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a39367efc40582 completely filled. +2023-09-18 15:13:46,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255351223507495066865899784 amount: 80. +2023-09-18 15:13:46,150 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262133872382722036678968144 amount: 80. +2023-09-18 15:13:46,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a396af6b270582 for 80.00000000 SEI-USDT. +2023-09-18 15:13:46,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a396af6b270582", "creation_timestamp": 1695050026.0, "exchange_order_id": "35322513", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:13:47,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a396af6d9d0582 for 80.00000000 SEI-USDT. +2023-09-18 15:13:47,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a396af6d9d0582", "creation_timestamp": 1695050026.0, "exchange_order_id": "35322515", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:14:41,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a396af6b270582. [clock=2023-09-18 15:14:41+00:00] +2023-09-18 15:14:41,123 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a396af6d9d0582. [clock=2023-09-18 15:14:41+00:00] +2023-09-18 15:14:41,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257124081606169117684707518 amount: 80. +2023-09-18 15:14:41,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:14:43,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050082.0, "order_id": "x-XEKWYICXBSIUT605a396af6b270582", "exchange_order_id": "35322513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:14:43,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a396af6b270582. +2023-09-18 15:14:43,729 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050083.0, "order_id": "x-XEKWYICXSSIUT605a396af6d9d0582", "exchange_order_id": "35322515", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:14:43,734 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a396af6d9d0582. +2023-09-18 15:14:43,737 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a399f717590582 for 80.00000000 SEI-USDT. +2023-09-18 15:14:43,777 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050083.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a399f717590582", "creation_timestamp": 1695050081.0, "exchange_order_id": "35322835", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:15:36,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a399f717590582. [clock=2023-09-18 15:15:36+00:00] +2023-09-18 15:15:36,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258265336469782982823973797 amount: 80. +2023-09-18 15:15:36,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264592128366490702154268221 amount: 80. +2023-09-18 15:15:36,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050136.0, "order_id": "x-XEKWYICXBSIUT605a399f717590582", "exchange_order_id": "35322835", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:15:36,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a399f717590582. +2023-09-18 15:15:36,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a39d3ceece0582 for 80.00000000 SEI-USDT. +2023-09-18 15:15:36,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a39d3ceece0582", "creation_timestamp": 1695050136.0, "exchange_order_id": "35323550", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:15:36,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a39d3cf0a30582 for 80.00000000 SEI-USDT. +2023-09-18 15:15:36,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a39d3cf0a30582", "creation_timestamp": 1695050136.0, "exchange_order_id": "35323551", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:16:31,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a39d3ceece0582. [clock=2023-09-18 15:16:31+00:00] +2023-09-18 15:16:31,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a39d3cf0a30582. [clock=2023-09-18 15:16:31+00:00] +2023-09-18 15:16:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257122584027736663977638846 amount: 80. +2023-09-18 15:16:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:16:31,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050191.0, "order_id": "x-XEKWYICXBSIUT605a39d3ceece0582", "exchange_order_id": "35323550", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:16:31,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a39d3ceece0582. +2023-09-18 15:16:31,334 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050191.0, "order_id": "x-XEKWYICXSSIUT605a39d3cf0a30582", "exchange_order_id": "35323551", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:16:31,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a39d3cf0a30582. +2023-09-18 15:16:31,416 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3a083222d0582 for 80.00000000 SEI-USDT. +2023-09-18 15:16:31,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3a083222d0582", "creation_timestamp": 1695050191.0, "exchange_order_id": "35323744", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:17:26,716 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3a083222d0582. [clock=2023-09-18 15:17:26+00:00] +2023-09-18 15:17:26,776 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257015021622572893086940542 amount: 80. +2023-09-18 15:17:26,777 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262029250134152074866304860 amount: 80. +2023-09-18 15:17:27,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050246.0, "order_id": "x-XEKWYICXBSIUT605a3a083222d0582", "exchange_order_id": "35323744", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:17:27,335 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3a083222d0582. +2023-09-18 15:17:28,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3a3d5eade0582 for 80.00000000 SEI-USDT. +2023-09-18 15:17:28,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050248.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3a3d5eade0582", "creation_timestamp": 1695050246.0, "exchange_order_id": "35323979", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:17:28,556 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3a3d5edf10582 for 80.00000000 SEI-USDT. +2023-09-18 15:17:28,586 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050248.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a3a3d5edf10582", "creation_timestamp": 1695050246.0, "exchange_order_id": "35323980", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:18:21,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3a3d5eade0582. [clock=2023-09-18 15:18:21+00:00] +2023-09-18 15:18:21,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3a3d5edf10582. [clock=2023-09-18 15:18:21+00:00] +2023-09-18 15:18:21,326 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257103073765829550544452050 amount: 80. +2023-09-18 15:18:21,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:18:21,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050301.0, "order_id": "x-XEKWYICXBSIUT605a3a3d5eade0582", "exchange_order_id": "35323979", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:18:21,590 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3a3d5eade0582. +2023-09-18 15:18:22,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050302.0, "order_id": "x-XEKWYICXSSIUT605a3a3d5edf10582", "exchange_order_id": "35323980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:18:22,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3a3d5edf10582. +2023-09-18 15:18:22,845 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3a71648800582 for 80.00000000 SEI-USDT. +2023-09-18 15:18:22,871 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050302.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3a71648800582", "creation_timestamp": 1695050301.0, "exchange_order_id": "35324292", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:19:16,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3a71648800582. [clock=2023-09-18 15:19:16+00:00] +2023-09-18 15:19:16,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257405232963148079690468854 amount: 80. +2023-09-18 15:19:16,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261621770203212030440705438 amount: 80. +2023-09-18 15:19:16,788 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050356.0, "order_id": "x-XEKWYICXBSIUT605a3a71648800582", "exchange_order_id": "35324292", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:19:16,788 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3a71648800582. +2023-09-18 15:19:17,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3aa5abd800582 for 80.00000000 SEI-USDT. +2023-09-18 15:19:17,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3aa5abd800582", "creation_timestamp": 1695050356.0, "exchange_order_id": "35324429", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:19:17,716 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3aa5ac0a60582 for 80.00000000 SEI-USDT. +2023-09-18 15:19:17,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a3aa5ac0a60582", "creation_timestamp": 1695050356.0, "exchange_order_id": "35324430", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:19:53,866 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3aa5abd800582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:19:53,867 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:19:53+00:00] +2023-09-18 15:19:53,931 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050393.0, "order_id": "x-XEKWYICXBSIUT605a3aa5abd800582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971336", "exchange_order_id": "35324429", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:19:54,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050393.0, "order_id": "x-XEKWYICXBSIUT605a3aa5abd800582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35324429", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:19:54,381 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3aa5abd800582 completely filled. +2023-09-18 15:20:11,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3aa5ac0a60582. [clock=2023-09-18 15:20:11+00:00] +2023-09-18 15:20:11,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254707534991185436719348167 amount: 80. +2023-09-18 15:20:11,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258503053224724534097526993 amount: 80. +2023-09-18 15:20:11,135 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050411.0, "order_id": "x-XEKWYICXSSIUT605a3aa5ac0a60582", "exchange_order_id": "35324430", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:20:11,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3aa5ac0a60582. +2023-09-18 15:20:11,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ada160f80582 for 80.00000000 SEI-USDT. +2023-09-18 15:20:11,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3ada160f80582", "creation_timestamp": 1695050411.0, "exchange_order_id": "35324754", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:20:11,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ada164f20582 for 80.00000000 SEI-USDT. +2023-09-18 15:20:11,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a3ada164f20582", "creation_timestamp": 1695050411.0, "exchange_order_id": "35324755", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:20:17,118 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3ada164f20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:20:17,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:20:17+00:00] +2023-09-18 15:20:17,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050417.0, "order_id": "x-XEKWYICXSSIUT605a3ada164f20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12580000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01006400"}]}, "exchange_trade_id": "4971348", "exchange_order_id": "35324755", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:20:17,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050417.0, "order_id": "x-XEKWYICXSSIUT605a3ada164f20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35324755", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:20:17,152 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3ada164f20582 completely filled. +2023-09-18 15:21:06,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3ada160f80582. [clock=2023-09-18 15:21:06+00:00] +2023-09-18 15:21:06,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257490404776119652524741846 amount: 80. +2023-09-18 15:21:06,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261481979654659795564522695 amount: 80. +2023-09-18 15:21:06,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050466.0, "order_id": "x-XEKWYICXBSIUT605a3ada160f80582", "exchange_order_id": "35324754", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:21:06,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3ada160f80582. +2023-09-18 15:21:06,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3b0e7e3d80582 for 80.00000000 SEI-USDT. +2023-09-18 15:21:06,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a3b0e7e3d80582", "creation_timestamp": 1695050466.0, "exchange_order_id": "35325055", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:21:06,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3b0e7e7360582 for 80.00000000 SEI-USDT. +2023-09-18 15:21:06,444 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a3b0e7e7360582", "creation_timestamp": 1695050466.0, "exchange_order_id": "35325056", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:21:28,934 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3b0e7e3d80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:21:28,936 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:21:28+00:00] +2023-09-18 15:21:28,958 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050488.0, "order_id": "x-XEKWYICXBSIUT605a3b0e7e3d80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971361", "exchange_order_id": "35325055", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:21:28,972 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050488.0, "order_id": "x-XEKWYICXBSIUT605a3b0e7e3d80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35325055", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:21:28,972 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3b0e7e3d80582 completely filled. +2023-09-18 15:22:01,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3b0e7e7360582. [clock=2023-09-18 15:22:01+00:00] +2023-09-18 15:22:01,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254155748185555649072487315 amount: 80. +2023-09-18 15:22:01,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260809711754280148757050459 amount: 80. +2023-09-18 15:22:01,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050521.0, "order_id": "x-XEKWYICXSSIUT605a3b0e7e7360582", "exchange_order_id": "35325056", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:22:01,079 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3b0e7e7360582. +2023-09-18 15:22:01,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3b42f15860582 for 80.00000000 SEI-USDT. +2023-09-18 15:22:01,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3b42f15860582", "creation_timestamp": 1695050521.0, "exchange_order_id": "35325678", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:22:01,420 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3b42f1a3a0582 for 80.00000000 SEI-USDT. +2023-09-18 15:22:01,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a3b42f1a3a0582", "creation_timestamp": 1695050521.0, "exchange_order_id": "35325682", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:22:56,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3b42f15860582. [clock=2023-09-18 15:22:56+00:00] +2023-09-18 15:22:56,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3b42f1a3a0582. [clock=2023-09-18 15:22:56+00:00] +2023-09-18 15:22:56,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253427878298439846139424109 amount: 80. +2023-09-18 15:22:56,194 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260611138174180217385045222 amount: 80. +2023-09-18 15:22:56,930 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050576.0, "order_id": "x-XEKWYICXBSIUT605a3b42f15860582", "exchange_order_id": "35325678", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:22:56,943 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3b42f15860582. +2023-09-18 15:22:58,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050577.0, "order_id": "x-XEKWYICXSSIUT605a3b42f1a3a0582", "exchange_order_id": "35325682", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:22:58,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3b42f1a3a0582. +2023-09-18 15:22:58,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3b77890230582 for 80.00000000 SEI-USDT. +2023-09-18 15:22:58,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a3b77890230582", "creation_timestamp": 1695050576.0, "exchange_order_id": "35325943", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:22:58,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3b7786bd60582 for 80.00000000 SEI-USDT. +2023-09-18 15:22:58,591 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a3b7786bd60582", "creation_timestamp": 1695050576.0, "exchange_order_id": "35325942", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:23:51,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3b7786bd60582. [clock=2023-09-18 15:23:51+00:00] +2023-09-18 15:23:51,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3b77890230582. [clock=2023-09-18 15:23:51+00:00] +2023-09-18 15:23:51,296 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254000200514342815372816539 amount: 80. +2023-09-18 15:23:51,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259586174507019824173994296 amount: 80. +2023-09-18 15:23:51,690 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050631.0, "order_id": "x-XEKWYICXBSIUT605a3b7786bd60582", "exchange_order_id": "35325942", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:23:51,691 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3b7786bd60582. +2023-09-18 15:23:53,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050632.0, "order_id": "x-XEKWYICXSSIUT605a3b77890230582", "exchange_order_id": "35325943", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:23:53,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3b77890230582. +2023-09-18 15:23:54,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3bac139e50582 for 80.00000000 SEI-USDT. +2023-09-18 15:23:54,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3bac139e50582", "creation_timestamp": 1695050631.0, "exchange_order_id": "35326086", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:23:54,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3bac13e080582 for 80.00000000 SEI-USDT. +2023-09-18 15:23:54,340 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a3bac13e080582", "creation_timestamp": 1695050631.0, "exchange_order_id": "35326087", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:24:46,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3bac139e50582. [clock=2023-09-18 15:24:46+00:00] +2023-09-18 15:24:46,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3bac13e080582. [clock=2023-09-18 15:24:46+00:00] +2023-09-18 15:24:46,353 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254445047983403885102436962 amount: 80. +2023-09-18 15:24:46,354 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258789085585080183169081054 amount: 80. +2023-09-18 15:24:46,899 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050686.0, "order_id": "x-XEKWYICXBSIUT605a3bac139e50582", "exchange_order_id": "35326086", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:24:46,909 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3bac139e50582. +2023-09-18 15:24:48,064 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050687.0, "order_id": "x-XEKWYICXSSIUT605a3bac13e080582", "exchange_order_id": "35326087", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:24:48,065 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3bac13e080582. +2023-09-18 15:24:48,684 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3be09532c0582 for 80.00000000 SEI-USDT. +2023-09-18 15:24:48,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050688.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3be09532c0582", "creation_timestamp": 1695050686.0, "exchange_order_id": "35326169", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:24:48,732 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3be0956380582 for 80.00000000 SEI-USDT. +2023-09-18 15:24:48,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050688.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a3be0956380582", "creation_timestamp": 1695050686.0, "exchange_order_id": "35326170", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:25:14,878 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 15:25:21,348 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3be09532c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:25:21,349 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:25:21+00:00] +2023-09-18 15:25:21,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050721.0, "order_id": "x-XEKWYICXBSIUT605a3be09532c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971494", "exchange_order_id": "35326169", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:25:21,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050721.0, "order_id": "x-XEKWYICXBSIUT605a3be09532c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35326169", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:25:21,454 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3be09532c0582 completely filled. +2023-09-18 15:25:41,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3be0956380582. [clock=2023-09-18 15:25:41+00:00] +2023-09-18 15:25:41,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252837520589750748361310180 amount: 80. +2023-09-18 15:25:41,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257544857433950335722327495 amount: 80. +2023-09-18 15:25:41,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050741.0, "order_id": "x-XEKWYICXSSIUT605a3be0956380582", "exchange_order_id": "35326170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:25:41,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3be0956380582. +2023-09-18 15:25:41,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3c14c92e00582 for 80.00000000 SEI-USDT. +2023-09-18 15:25:41,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a3c14c92e00582", "creation_timestamp": 1695050741.0, "exchange_order_id": "35326781", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:25:41,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3c14c8fcd0582 for 80.00000000 SEI-USDT. +2023-09-18 15:25:41,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3c14c8fcd0582", "creation_timestamp": 1695050741.0, "exchange_order_id": "35326780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:26:36,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3c14c8fcd0582. [clock=2023-09-18 15:26:36+00:00] +2023-09-18 15:26:36,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3c14c92e00582. [clock=2023-09-18 15:26:36+00:00] +2023-09-18 15:26:36,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253991512244752823750342516 amount: 80. +2023-09-18 15:26:36,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258990420078114832471570772 amount: 80. +2023-09-18 15:26:36,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050796.0, "order_id": "x-XEKWYICXBSIUT605a3c14c8fcd0582", "exchange_order_id": "35326780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:26:36,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3c14c8fcd0582. +2023-09-18 15:26:36,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050796.0, "order_id": "x-XEKWYICXSSIUT605a3c14c92e00582", "exchange_order_id": "35326781", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:26:36,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3c14c92e00582. +2023-09-18 15:26:36,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3c493e9710582 for 80.00000000 SEI-USDT. +2023-09-18 15:26:36,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a3c493e9710582", "creation_timestamp": 1695050796.0, "exchange_order_id": "35327362", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:26:36,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3c493ed210582 for 80.00000000 SEI-USDT. +2023-09-18 15:26:36,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a3c493ed210582", "creation_timestamp": 1695050796.0, "exchange_order_id": "35327363", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:27:31,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3c493e9710582. [clock=2023-09-18 15:27:31+00:00] +2023-09-18 15:27:31,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3c493ed210582. [clock=2023-09-18 15:27:31+00:00] +2023-09-18 15:27:31,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251974987177421479083116915 amount: 80. +2023-09-18 15:27:31,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256525453814368373861142293 amount: 80. +2023-09-18 15:27:31,527 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050851.0, "order_id": "x-XEKWYICXBSIUT605a3c493e9710582", "exchange_order_id": "35327362", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:27:31,527 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3c493e9710582. +2023-09-18 15:27:32,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050852.0, "order_id": "x-XEKWYICXSSIUT605a3c493ed210582", "exchange_order_id": "35327363", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:27:32,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3c493ed210582. +2023-09-18 15:27:32,864 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3c7dbccf40582 for 80.00000000 SEI-USDT. +2023-09-18 15:27:32,930 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050852.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a3c7dbccf40582", "creation_timestamp": 1695050851.0, "exchange_order_id": "35327719", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:27:32,930 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3c7dbcf360582 for 80.00000000 SEI-USDT. +2023-09-18 15:27:32,973 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050852.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a3c7dbcf360582", "creation_timestamp": 1695050851.0, "exchange_order_id": "35327718", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:28:26,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3c7dbccf40582. [clock=2023-09-18 15:28:26+00:00] +2023-09-18 15:28:26,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3c7dbcf360582. [clock=2023-09-18 15:28:26+00:00] +2023-09-18 15:28:26,259 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252426373219810861282832807 amount: 80. +2023-09-18 15:28:26,260 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255965471830164152839858977 amount: 80. +2023-09-18 15:28:26,874 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050906.0, "order_id": "x-XEKWYICXBSIUT605a3c7dbccf40582", "exchange_order_id": "35327719", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:28:26,875 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3c7dbccf40582. +2023-09-18 15:28:27,787 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050907.0, "order_id": "x-XEKWYICXSSIUT605a3c7dbcf360582", "exchange_order_id": "35327718", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:28:27,788 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3c7dbcf360582. +2023-09-18 15:28:28,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3cb24f7f10582 for 80.00000000 SEI-USDT. +2023-09-18 15:28:28,340 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050907.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a3cb24f7f10582", "creation_timestamp": 1695050906.0, "exchange_order_id": "35327828", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:28:28,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3cb24d4620582 for 80.00000000 SEI-USDT. +2023-09-18 15:28:28,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050907.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3cb24d4620582", "creation_timestamp": 1695050906.0, "exchange_order_id": "35327829", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:28:42,944 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3cb24f7f10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:28:42,945 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:28:42+00:00] +2023-09-18 15:28:43,014 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050922.0, "order_id": "x-XEKWYICXSSIUT605a3cb24f7f10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004000"}]}, "exchange_trade_id": "4971567", "exchange_order_id": "35327828", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:28:43,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050922.0, "order_id": "x-XEKWYICXSSIUT605a3cb24f7f10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35327828", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:28:43,148 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3cb24f7f10582 completely filled. +2023-09-18 15:29:21,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3cb24d4620582. [clock=2023-09-18 15:29:21+00:00] +2023-09-18 15:29:21,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253935311584468495677207692 amount: 80. +2023-09-18 15:29:21,222 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257726234776070563337685833 amount: 80. +2023-09-18 15:29:21,834 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050961.0, "order_id": "x-XEKWYICXBSIUT605a3cb24d4620582", "exchange_order_id": "35327829", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:29:21,834 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3cb24d4620582. +2023-09-18 15:29:23,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ce6b7b790582 for 80.00000000 SEI-USDT. +2023-09-18 15:29:23,076 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050962.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a3ce6b7b790582", "creation_timestamp": 1695050961.0, "exchange_order_id": "35328074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:29:23,564 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ce6b7fc50582 for 80.00000000 SEI-USDT. +2023-09-18 15:29:23,613 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695050963.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a3ce6b7fc50582", "creation_timestamp": 1695050961.0, "exchange_order_id": "35328075", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:30:16,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3ce6b7b790582. [clock=2023-09-18 15:30:16+00:00] +2023-09-18 15:30:16,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3ce6b7fc50582. [clock=2023-09-18 15:30:16+00:00] +2023-09-18 15:30:16,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255171072598187844746390815 amount: 80. +2023-09-18 15:30:16,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258121483092309246433946994 amount: 80. +2023-09-18 15:30:16,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051016.0, "order_id": "x-XEKWYICXBSIUT605a3ce6b7b790582", "exchange_order_id": "35328074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:30:16,323 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3ce6b7b790582. +2023-09-18 15:30:16,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051016.0, "order_id": "x-XEKWYICXSSIUT605a3ce6b7fc50582", "exchange_order_id": "35328075", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:30:16,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3ce6b7fc50582. +2023-09-18 15:30:16,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3d1b1e6ca0582 for 80.00000000 SEI-USDT. +2023-09-18 15:30:16,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a3d1b1e6ca0582", "creation_timestamp": 1695051016.0, "exchange_order_id": "35328307", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:30:16,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3d1b1e9790582 for 80.00000000 SEI-USDT. +2023-09-18 15:30:16,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a3d1b1e9790582", "creation_timestamp": 1695051016.0, "exchange_order_id": "35328308", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:30:21,699 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3d1b1e6ca0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:30:21,700 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:30:21+00:00] +2023-09-18 15:30:21,723 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051021.0, "order_id": "x-XEKWYICXBSIUT605a3d1b1e6ca0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971612", "exchange_order_id": "35328307", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:30:21,735 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051021.0, "order_id": "x-XEKWYICXBSIUT605a3d1b1e6ca0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35328307", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:30:21,736 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3d1b1e6ca0582 completely filled. +2023-09-18 15:31:11,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3d1b1e9790582. [clock=2023-09-18 15:31:11+00:00] +2023-09-18 15:31:11,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252155492675868866944370017 amount: 80. +2023-09-18 15:31:11,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256669183892048986865784900 amount: 80. +2023-09-18 15:31:11,074 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051071.0, "order_id": "x-XEKWYICXSSIUT605a3d1b1e9790582", "exchange_order_id": "35328308", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:31:11,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3d1b1e9790582. +2023-09-18 15:31:11,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3d4f750e70582 for 80.00000000 SEI-USDT. +2023-09-18 15:31:11,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3d4f750e70582", "creation_timestamp": 1695051071.0, "exchange_order_id": "35328617", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:31:11,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3d4f753b00582 for 80.00000000 SEI-USDT. +2023-09-18 15:31:11,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a3d4f753b00582", "creation_timestamp": 1695051071.0, "exchange_order_id": "35328618", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:32:02,163 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3d4f753b00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:32:02,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:32:02+00:00] +2023-09-18 15:32:02,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051122.0, "order_id": "x-XEKWYICXSSIUT605a3d4f753b00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12560000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004800"}]}, "exchange_trade_id": "4971676", "exchange_order_id": "35328618", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:32:02,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051122.0, "order_id": "x-XEKWYICXSSIUT605a3d4f753b00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35328618", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:32:02,239 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3d4f753b00582 completely filled. +2023-09-18 15:32:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3d4f750e70582. [clock=2023-09-18 15:32:06+00:00] +2023-09-18 15:32:06,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254986870966934444162996333 amount: 80. +2023-09-18 15:32:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259020904584785143243370354 amount: 80. +2023-09-18 15:32:06,044 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051126.0, "order_id": "x-XEKWYICXBSIUT605a3d4f750e70582", "exchange_order_id": "35328617", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:32:06,045 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3d4f750e70582. +2023-09-18 15:32:06,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3d83e09750582 for 80.00000000 SEI-USDT. +2023-09-18 15:32:06,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3d83e09750582", "creation_timestamp": 1695051126.0, "exchange_order_id": "35329240", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:32:06,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3d83e0dac0582 for 80.00000000 SEI-USDT. +2023-09-18 15:32:06,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a3d83e0dac0582", "creation_timestamp": 1695051126.0, "exchange_order_id": "35329243", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:33:01,177 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3d83e09750582. [clock=2023-09-18 15:33:01+00:00] +2023-09-18 15:33:01,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3d83e0dac0582. [clock=2023-09-18 15:33:01+00:00] +2023-09-18 15:33:01,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256048937580843725564248679 amount: 80. +2023-09-18 15:33:01,230 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262084864165502700145336111 amount: 80. +2023-09-18 15:33:01,842 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051181.0, "order_id": "x-XEKWYICXBSIUT605a3d83e09750582", "exchange_order_id": "35329240", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:33:01,843 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3d83e09750582. +2023-09-18 15:33:03,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051182.0, "order_id": "x-XEKWYICXSSIUT605a3d83e0dac0582", "exchange_order_id": "35329243", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:33:03,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3d83e0dac0582. +2023-09-18 15:33:03,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3db888eb00582 for 80.00000000 SEI-USDT. +2023-09-18 15:33:03,402 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051183.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a3db888eb00582", "creation_timestamp": 1695051181.0, "exchange_order_id": "35329775", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:33:03,715 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3db888b030582 for 80.00000000 SEI-USDT. +2023-09-18 15:33:03,810 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051183.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a3db888b030582", "creation_timestamp": 1695051181.0, "exchange_order_id": "35329776", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:33:56,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3db888b030582. [clock=2023-09-18 15:33:56+00:00] +2023-09-18 15:33:56,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3db888eb00582. [clock=2023-09-18 15:33:56+00:00] +2023-09-18 15:33:56,253 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256260375357100096776797790 amount: 80. +2023-09-18 15:33:56,254 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260953269455110764034699484 amount: 80. +2023-09-18 15:33:57,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051236.0, "order_id": "x-XEKWYICXBSIUT605a3db888b030582", "exchange_order_id": "35329776", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:33:57,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3db888b030582. +2023-09-18 15:33:58,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051238.0, "order_id": "x-XEKWYICXSSIUT605a3db888eb00582", "exchange_order_id": "35329775", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:33:58,301 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3db888eb00582. +2023-09-18 15:33:58,679 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ded027a20582 for 80.00000000 SEI-USDT. +2023-09-18 15:33:58,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051238.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a3ded027a20582", "creation_timestamp": 1695051236.0, "exchange_order_id": "35330129", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:33:58,751 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ded022d30582 for 80.00000000 SEI-USDT. +2023-09-18 15:33:58,829 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051238.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a3ded022d30582", "creation_timestamp": 1695051236.0, "exchange_order_id": "35330130", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:34:50,590 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a3ded022d30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:34:50,609 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:34:50+00:00] +2023-09-18 15:34:50,710 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051290.0, "order_id": "x-XEKWYICXBSIUT605a3ded022d30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12560000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971760", "exchange_order_id": "35330130", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:34:51,011 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051290.0, "order_id": "x-XEKWYICXBSIUT605a3ded022d30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35330130", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:34:51,012 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a3ded022d30582 completely filled. +2023-09-18 15:34:51,734 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3ded027a20582. [clock=2023-09-18 15:34:51+00:00] +2023-09-18 15:34:51,775 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253160905916551408177650454 amount: 80. +2023-09-18 15:34:51,793 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258141124014817240131908154 amount: 80. +2023-09-18 15:34:52,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051291.0, "order_id": "x-XEKWYICXSSIUT605a3ded027a20582", "exchange_order_id": "35330129", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:34:52,581 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3ded027a20582. +2023-09-18 15:34:53,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3e21f96810582 for 80.00000000 SEI-USDT. +2023-09-18 15:34:53,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051293.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a3e21f96810582", "creation_timestamp": 1695051291.0, "exchange_order_id": "35330724", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:34:53,686 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3e21f9ade0582 for 80.00000000 SEI-USDT. +2023-09-18 15:34:53,715 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051293.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a3e21f9ade0582", "creation_timestamp": 1695051291.0, "exchange_order_id": "35330725", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:35:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3e21f96810582. [clock=2023-09-18 15:35:46+00:00] +2023-09-18 15:35:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3e21f9ade0582. [clock=2023-09-18 15:35:46+00:00] +2023-09-18 15:35:46,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251625362855236490010409774 amount: 80. +2023-09-18 15:35:46,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257349455343387161045423976 amount: 80. +2023-09-18 15:35:46,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051346.0, "order_id": "x-XEKWYICXBSIUT605a3e21f96810582", "exchange_order_id": "35330724", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:35:46,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3e21f96810582. +2023-09-18 15:35:46,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051346.0, "order_id": "x-XEKWYICXSSIUT605a3e21f9ade0582", "exchange_order_id": "35330725", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:35:46,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3e21f9ade0582. +2023-09-18 15:35:46,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3e55b375b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:35:46,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a3e55b375b0582", "creation_timestamp": 1695051346.0, "exchange_order_id": "35330956", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:35:46,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3e55b392d0582 for 80.00000000 SEI-USDT. +2023-09-18 15:35:46,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a3e55b392d0582", "creation_timestamp": 1695051346.0, "exchange_order_id": "35330957", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:36:41,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3e55b375b0582. [clock=2023-09-18 15:36:41+00:00] +2023-09-18 15:36:41,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3e55b392d0582. [clock=2023-09-18 15:36:41+00:00] +2023-09-18 15:36:41,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252154099978576621025473069 amount: 80. +2023-09-18 15:36:41,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256605790533525358531676660 amount: 80. +2023-09-18 15:36:41,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051401.0, "order_id": "x-XEKWYICXBSIUT605a3e55b375b0582", "exchange_order_id": "35330956", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:36:41,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3e55b375b0582. +2023-09-18 15:36:41,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051401.0, "order_id": "x-XEKWYICXSSIUT605a3e55b392d0582", "exchange_order_id": "35330957", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:36:41,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3e55b392d0582. +2023-09-18 15:36:41,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3e8a2e1e50582 for 80.00000000 SEI-USDT. +2023-09-18 15:36:41,509 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3e8a2e1e50582", "creation_timestamp": 1695051401.0, "exchange_order_id": "35331190", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:36:41,509 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3e8a2e4db0582 for 80.00000000 SEI-USDT. +2023-09-18 15:36:41,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a3e8a2e4db0582", "creation_timestamp": 1695051401.0, "exchange_order_id": "35331191", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:37:36,283 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3e8a2e1e50582. [clock=2023-09-18 15:37:36+00:00] +2023-09-18 15:37:36,284 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3e8a2e4db0582. [clock=2023-09-18 15:37:36+00:00] +2023-09-18 15:37:36,326 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249289926532400092847637743 amount: 80. +2023-09-18 15:37:36,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254457245947009980240664714 amount: 80. +2023-09-18 15:37:36,691 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051456.0, "order_id": "x-XEKWYICXBSIUT605a3e8a2e1e50582", "exchange_order_id": "35331190", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:37:36,691 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3e8a2e1e50582. +2023-09-18 15:37:37,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051457.0, "order_id": "x-XEKWYICXSSIUT605a3e8a2e4db0582", "exchange_order_id": "35331191", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:37:37,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3e8a2e4db0582. +2023-09-18 15:37:37,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ebee327b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:37:37,480 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051457.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a3ebee327b0582", "creation_timestamp": 1695051456.0, "exchange_order_id": "35331530", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:37:37,480 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ebee2f3d0582 for 80.00000000 SEI-USDT. +2023-09-18 15:37:37,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051457.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a3ebee2f3d0582", "creation_timestamp": 1695051456.0, "exchange_order_id": "35331529", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:38:14,900 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3ebee327b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:38:14,902 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:38:14+00:00] +2023-09-18 15:38:14,990 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051494.0, "order_id": "x-XEKWYICXSSIUT605a3ebee327b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01003200"}]}, "exchange_trade_id": "4971838", "exchange_order_id": "35331530", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:38:15,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051494.0, "order_id": "x-XEKWYICXSSIUT605a3ebee327b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35331530", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:38:15,134 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3ebee327b0582 completely filled. +2023-09-18 15:38:31,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3ebee2f3d0582. [clock=2023-09-18 15:38:31+00:00] +2023-09-18 15:38:31,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252288100315860491512328467 amount: 80. +2023-09-18 15:38:31,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257644991452703519070177845 amount: 80. +2023-09-18 15:38:31,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051511.0, "order_id": "x-XEKWYICXBSIUT605a3ebee2f3d0582", "exchange_order_id": "35331529", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:38:31,530 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3ebee2f3d0582. +2023-09-18 15:38:32,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ef332e870582 for 80.00000000 SEI-USDT. +2023-09-18 15:38:32,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051512.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3ef332e870582", "creation_timestamp": 1695051511.0, "exchange_order_id": "35331726", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:38:32,663 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ef3331720582 for 80.00000000 SEI-USDT. +2023-09-18 15:38:32,697 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051512.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a3ef3331720582", "creation_timestamp": 1695051511.0, "exchange_order_id": "35331728", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:39:26,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3ef332e870582. [clock=2023-09-18 15:39:26+00:00] +2023-09-18 15:39:26,291 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3ef3331720582. [clock=2023-09-18 15:39:26+00:00] +2023-09-18 15:39:26,321 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254987022122479130489186366 amount: 80. +2023-09-18 15:39:26,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259673761685478759042056032 amount: 80. +2023-09-18 15:39:26,703 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051566.0, "order_id": "x-XEKWYICXBSIUT605a3ef332e870582", "exchange_order_id": "35331726", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:39:26,703 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3ef332e870582. +2023-09-18 15:39:27,724 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051567.0, "order_id": "x-XEKWYICXSSIUT605a3ef3331720582", "exchange_order_id": "35331728", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:39:27,725 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3ef3331720582. +2023-09-18 15:39:28,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3f27c93cc0582 for 80.00000000 SEI-USDT. +2023-09-18 15:39:28,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051567.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a3f27c93cc0582", "creation_timestamp": 1695051566.0, "exchange_order_id": "35332157", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:39:28,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3f27c91d80582 for 80.00000000 SEI-USDT. +2023-09-18 15:39:28,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051567.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a3f27c91d80582", "creation_timestamp": 1695051566.0, "exchange_order_id": "35332156", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:40:21,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3f27c91d80582. [clock=2023-09-18 15:40:21+00:00] +2023-09-18 15:40:21,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3f27c93cc0582. [clock=2023-09-18 15:40:21+00:00] +2023-09-18 15:40:21,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252646026885818139119923670 amount: 80. +2023-09-18 15:40:21,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257992475111716180577325960 amount: 80. +2023-09-18 15:40:21,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051621.0, "order_id": "x-XEKWYICXBSIUT605a3f27c91d80582", "exchange_order_id": "35332156", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:40:21,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3f27c91d80582. +2023-09-18 15:40:21,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051621.0, "order_id": "x-XEKWYICXSSIUT605a3f27c93cc0582", "exchange_order_id": "35332157", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:40:21,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3f27c93cc0582. +2023-09-18 15:40:21,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3f5c02e6a0582 for 80.00000000 SEI-USDT. +2023-09-18 15:40:21,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a3f5c02e6a0582", "creation_timestamp": 1695051621.0, "exchange_order_id": "35332423", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:40:21,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3f5c02b300582 for 80.00000000 SEI-USDT. +2023-09-18 15:40:21,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3f5c02b300582", "creation_timestamp": 1695051621.0, "exchange_order_id": "35332422", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:41:16,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3f5c02b300582. [clock=2023-09-18 15:41:16+00:00] +2023-09-18 15:41:16,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3f5c02e6a0582. [clock=2023-09-18 15:41:16+00:00] +2023-09-18 15:41:16,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252740249978836227095470054 amount: 80. +2023-09-18 15:41:16,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256900260383480170386911275 amount: 80. +2023-09-18 15:41:16,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051676.0, "order_id": "x-XEKWYICXBSIUT605a3f5c02b300582", "exchange_order_id": "35332422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:41:16,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3f5c02b300582. +2023-09-18 15:41:16,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3f90813b80582 for 80.00000000 SEI-USDT. +2023-09-18 15:41:16,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a3f90813b80582", "creation_timestamp": 1695051676.0, "exchange_order_id": "35332629", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:41:16,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3f90810630582 for 80.00000000 SEI-USDT. +2023-09-18 15:41:16,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a3f90810630582", "creation_timestamp": 1695051676.0, "exchange_order_id": "35332630", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:41:16,532 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051676.0, "order_id": "x-XEKWYICXSSIUT605a3f5c02e6a0582", "exchange_order_id": "35332423", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:41:16,532 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3f5c02e6a0582. +2023-09-18 15:42:11,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3f90810630582. [clock=2023-09-18 15:42:11+00:00] +2023-09-18 15:42:11,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3f90813b80582. [clock=2023-09-18 15:42:11+00:00] +2023-09-18 15:42:11,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253020474167819647058965890 amount: 80. +2023-09-18 15:42:11,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256255567923762515650925925 amount: 80. +2023-09-18 15:42:11,942 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051731.0, "order_id": "x-XEKWYICXBSIUT605a3f90810630582", "exchange_order_id": "35332630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:42:11,942 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3f90810630582. +2023-09-18 15:42:11,989 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051731.0, "order_id": "x-XEKWYICXSSIUT605a3f90813b80582", "exchange_order_id": "35332629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:42:11,989 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3f90813b80582. +2023-09-18 15:42:13,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3fc4e8d6e0582 for 80.00000000 SEI-USDT. +2023-09-18 15:42:13,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051732.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a3fc4e8d6e0582", "creation_timestamp": 1695051731.0, "exchange_order_id": "35332743", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:42:13,799 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3fc4e8f850582 for 80.00000000 SEI-USDT. +2023-09-18 15:42:13,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051733.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a3fc4e8f850582", "creation_timestamp": 1695051731.0, "exchange_order_id": "35332744", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:42:58,825 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a3fc4e8f850582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:42:58,830 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:42:58+00:00] +2023-09-18 15:42:58,876 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051778.0, "order_id": "x-XEKWYICXSSIUT605a3fc4e8f850582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12560000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004800"}]}, "exchange_trade_id": "4971912", "exchange_order_id": "35332744", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:42:58,899 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051778.0, "order_id": "x-XEKWYICXSSIUT605a3fc4e8f850582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35332744", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:42:58,899 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a3fc4e8f850582 completely filled. +2023-09-18 15:43:06,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3fc4e8d6e0582. [clock=2023-09-18 15:43:06+00:00] +2023-09-18 15:43:06,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255829356793161942494392649 amount: 80. +2023-09-18 15:43:06,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260051120502058919399165344 amount: 80. +2023-09-18 15:43:06,865 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051786.0, "order_id": "x-XEKWYICXBSIUT605a3fc4e8d6e0582", "exchange_order_id": "35332743", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:43:06,866 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3fc4e8d6e0582. +2023-09-18 15:43:08,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a3ff983e510582 for 80.00000000 SEI-USDT. +2023-09-18 15:43:08,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051788.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a3ff983e510582", "creation_timestamp": 1695051786.0, "exchange_order_id": "35333151", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:43:08,609 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a3ff9842f90582 for 80.00000000 SEI-USDT. +2023-09-18 15:43:08,658 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051788.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a3ff9842f90582", "creation_timestamp": 1695051786.0, "exchange_order_id": "35333154", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:44:01,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a3ff983e510582. [clock=2023-09-18 15:44:01+00:00] +2023-09-18 15:44:01,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a3ff9842f90582. [clock=2023-09-18 15:44:01+00:00] +2023-09-18 15:44:01,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12580000 amount: 80. +2023-09-18 15:44:01,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 15:44:02,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051842.0, "order_id": "x-XEKWYICXBSIUT605a3ff983e510582", "exchange_order_id": "35333151", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:44:02,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a3ff983e510582. +2023-09-18 15:44:02,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a402de09910582 for 80.00000000 SEI-USDT. +2023-09-18 15:44:02,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051842.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a402de09910582", "creation_timestamp": 1695051841.0, "exchange_order_id": "35333616", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:44:02,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051842.0, "order_id": "x-XEKWYICXSSIUT605a3ff9842f90582", "exchange_order_id": "35333154", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:44:02,551 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a3ff9842f90582. +2023-09-18 15:44:45,319 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a402de09910582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:44:45,321 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:44:45+00:00] +2023-09-18 15:44:45,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051885.0, "order_id": "x-XEKWYICXBSIUT605a402de09910582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12580000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4971958", "exchange_order_id": "35333616", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:44:45,773 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051885.0, "order_id": "x-XEKWYICXBSIUT605a402de09910582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35333616", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:44:45,774 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a402de09910582 completely filled. +2023-09-18 15:44:56,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255979108096786439628866628 amount: 80. +2023-09-18 15:44:56,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261638751165965641531930126 amount: 80. +2023-09-18 15:44:56,498 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4062476ed0582 for 80.00000000 SEI-USDT. +2023-09-18 15:44:56,549 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a4062476ed0582", "creation_timestamp": 1695051896.0, "exchange_order_id": "35333799", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:44:57,888 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a40624795c0582 for 80.00000000 SEI-USDT. +2023-09-18 15:44:57,933 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051897.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a40624795c0582", "creation_timestamp": 1695051896.0, "exchange_order_id": "35333800", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:45:51,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4062476ed0582. [clock=2023-09-18 15:45:51+00:00] +2023-09-18 15:45:51,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a40624795c0582. [clock=2023-09-18 15:45:51+00:00] +2023-09-18 15:45:51,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256089254895773899900399337 amount: 80. +2023-09-18 15:45:51,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262491433893078486705412203 amount: 80. +2023-09-18 15:45:51,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051951.0, "order_id": "x-XEKWYICXBSIUT605a4062476ed0582", "exchange_order_id": "35333799", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:45:51,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4062476ed0582. +2023-09-18 15:45:51,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051951.0, "order_id": "x-XEKWYICXSSIUT605a40624795c0582", "exchange_order_id": "35333800", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:45:51,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a40624795c0582. +2023-09-18 15:45:51,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4096b06520582 for 80.00000000 SEI-USDT. +2023-09-18 15:45:51,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a4096b06520582", "creation_timestamp": 1695051951.0, "exchange_order_id": "35334404", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:45:51,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4096b09a70582 for 80.00000000 SEI-USDT. +2023-09-18 15:45:51,430 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695051951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a4096b09a70582", "creation_timestamp": 1695051951.0, "exchange_order_id": "35334405", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:46:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4096b06520582. [clock=2023-09-18 15:46:46+00:00] +2023-09-18 15:46:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4096b09a70582. [clock=2023-09-18 15:46:46+00:00] +2023-09-18 15:46:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254641109666566369093533709 amount: 80. +2023-09-18 15:46:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261621757628669884686797265 amount: 80. +2023-09-18 15:46:46,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052006.0, "order_id": "x-XEKWYICXBSIUT605a4096b06520582", "exchange_order_id": "35334404", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:46:46,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4096b06520582. +2023-09-18 15:46:46,527 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a40cb1db5c0582 for 80.00000000 SEI-USDT. +2023-09-18 15:46:46,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a40cb1db5c0582", "creation_timestamp": 1695052006.0, "exchange_order_id": "35334807", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:46:46,548 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a40cb1d7c20582 for 80.00000000 SEI-USDT. +2023-09-18 15:46:46,568 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a40cb1d7c20582", "creation_timestamp": 1695052006.0, "exchange_order_id": "35334808", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:46:46,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052006.0, "order_id": "x-XEKWYICXSSIUT605a4096b09a70582", "exchange_order_id": "35334405", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:46:46,582 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4096b09a70582. +2023-09-18 15:47:41,232 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a40cb1d7c20582. [clock=2023-09-18 15:47:41+00:00] +2023-09-18 15:47:41,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a40cb1db5c0582. [clock=2023-09-18 15:47:41+00:00] +2023-09-18 15:47:41,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254576965823307451722478497 amount: 80. +2023-09-18 15:47:41,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261860382404675325544142027 amount: 80. +2023-09-18 15:47:41,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052061.0, "order_id": "x-XEKWYICXBSIUT605a40cb1d7c20582", "exchange_order_id": "35334808", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:47:41,749 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a40cb1d7c20582. +2023-09-18 15:47:44,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a40ffd98ab0582 for 80.00000000 SEI-USDT. +2023-09-18 15:47:44,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052063.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a40ffd98ab0582", "creation_timestamp": 1695052061.0, "exchange_order_id": "35335213", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:47:44,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052063.0, "order_id": "x-XEKWYICXSSIUT605a40cb1db5c0582", "exchange_order_id": "35334807", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:47:44,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a40cb1db5c0582. +2023-09-18 15:47:44,495 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a40ffd96d90582 for 80.00000000 SEI-USDT. +2023-09-18 15:47:44,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052064.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a40ffd96d90582", "creation_timestamp": 1695052061.0, "exchange_order_id": "35335212", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:48:36,478 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a40ffd96d90582. [clock=2023-09-18 15:48:36+00:00] +2023-09-18 15:48:36,488 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a40ffd98ab0582. [clock=2023-09-18 15:48:36+00:00] +2023-09-18 15:48:36,540 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255797656516638747201954558 amount: 80. +2023-09-18 15:48:36,541 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262133608277118348885384304 amount: 80. +2023-09-18 15:48:37,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052116.0, "order_id": "x-XEKWYICXBSIUT605a40ffd96d90582", "exchange_order_id": "35335212", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:48:37,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a40ffd96d90582. +2023-09-18 15:48:38,674 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052118.0, "order_id": "x-XEKWYICXSSIUT605a40ffd98ab0582", "exchange_order_id": "35335213", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:48:38,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a40ffd98ab0582. +2023-09-18 15:48:39,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4134840100582 for 80.00000000 SEI-USDT. +2023-09-18 15:48:39,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052118.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a4134840100582", "creation_timestamp": 1695052116.0, "exchange_order_id": "35335582", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:48:39,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a41348445a0582 for 80.00000000 SEI-USDT. +2023-09-18 15:48:39,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052118.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a41348445a0582", "creation_timestamp": 1695052116.0, "exchange_order_id": "35335583", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:49:25,596 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4134840100582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:49:25,597 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:49:25+00:00] +2023-09-18 15:49:25,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052165.0, "order_id": "x-XEKWYICXBSIUT605a4134840100582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4972154", "exchange_order_id": "35335582", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:49:25,742 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052165.0, "order_id": "x-XEKWYICXBSIUT605a4134840100582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35335582", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:49:25,743 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4134840100582 completely filled. +2023-09-18 15:49:31,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a41348445a0582. [clock=2023-09-18 15:49:31+00:00] +2023-09-18 15:49:31,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252362243233270440840131684 amount: 80. +2023-09-18 15:49:31,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259138412353693597868913048 amount: 80. +2023-09-18 15:49:32,077 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052171.0, "order_id": "x-XEKWYICXSSIUT605a41348445a0582", "exchange_order_id": "35335583", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:49:32,077 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a41348445a0582. +2023-09-18 15:49:33,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4168aa34e0582 for 80.00000000 SEI-USDT. +2023-09-18 15:49:33,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052173.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4168aa34e0582", "creation_timestamp": 1695052171.0, "exchange_order_id": "35335914", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:49:34,049 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4168aa7290582 for 80.00000000 SEI-USDT. +2023-09-18 15:49:34,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052173.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a4168aa7290582", "creation_timestamp": 1695052171.0, "exchange_order_id": "35335916", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:50:26,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4168aa34e0582. [clock=2023-09-18 15:50:26+00:00] +2023-09-18 15:50:26,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4168aa7290582. [clock=2023-09-18 15:50:26+00:00] +2023-09-18 15:50:26,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252580963857716834918240865 amount: 80. +2023-09-18 15:50:26,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259037514274051748292649619 amount: 80. +2023-09-18 15:50:26,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052226.0, "order_id": "x-XEKWYICXBSIUT605a4168aa34e0582", "exchange_order_id": "35335914", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:50:26,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4168aa34e0582. +2023-09-18 15:50:26,380 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052226.0, "order_id": "x-XEKWYICXSSIUT605a4168aa7290582", "exchange_order_id": "35335916", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:50:26,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4168aa7290582. +2023-09-18 15:50:26,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a419cf625b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:50:26,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a419cf625b0582", "creation_timestamp": 1695052226.0, "exchange_order_id": "35336141", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:50:26,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a419cf65aa0582 for 80.00000000 SEI-USDT. +2023-09-18 15:50:26,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a419cf65aa0582", "creation_timestamp": 1695052226.0, "exchange_order_id": "35336142", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:51:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a419cf625b0582. [clock=2023-09-18 15:51:21+00:00] +2023-09-18 15:51:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a419cf65aa0582. [clock=2023-09-18 15:51:21+00:00] +2023-09-18 15:51:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252120424237360722284140475 amount: 80. +2023-09-18 15:51:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257137465883060946542339935 amount: 80. +2023-09-18 15:51:21,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052281.0, "order_id": "x-XEKWYICXBSIUT605a419cf625b0582", "exchange_order_id": "35336141", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:51:21,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a419cf625b0582. +2023-09-18 15:51:21,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052281.0, "order_id": "x-XEKWYICXSSIUT605a419cf65aa0582", "exchange_order_id": "35336142", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:51:21,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a419cf65aa0582. +2023-09-18 15:51:21,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a41d15f6300582 for 80.00000000 SEI-USDT. +2023-09-18 15:51:21,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a41d15f6300582", "creation_timestamp": 1695052281.0, "exchange_order_id": "35336403", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:51:21,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a41d15f8660582 for 80.00000000 SEI-USDT. +2023-09-18 15:51:21,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a41d15f8660582", "creation_timestamp": 1695052281.0, "exchange_order_id": "35336404", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:52:16,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a41d15f6300582. [clock=2023-09-18 15:52:16+00:00] +2023-09-18 15:52:16,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a41d15f8660582. [clock=2023-09-18 15:52:16+00:00] +2023-09-18 15:52:16,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252289344153623649943514554 amount: 80. +2023-09-18 15:52:16,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256857755498440782652466236 amount: 80. +2023-09-18 15:52:16,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052336.0, "order_id": "x-XEKWYICXBSIUT605a41d15f6300582", "exchange_order_id": "35336403", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:52:16,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a41d15f6300582. +2023-09-18 15:52:17,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052336.0, "order_id": "x-XEKWYICXSSIUT605a41d15f8660582", "exchange_order_id": "35336404", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:52:17,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a41d15f8660582. +2023-09-18 15:52:17,472 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4206005a60582 for 80.00000000 SEI-USDT. +2023-09-18 15:52:17,494 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052337.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a4206005a60582", "creation_timestamp": 1695052336.0, "exchange_order_id": "35336513", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:52:17,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4206003a40582 for 80.00000000 SEI-USDT. +2023-09-18 15:52:17,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052337.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4206003a40582", "creation_timestamp": 1695052336.0, "exchange_order_id": "35336512", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:53:11,177 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4206003a40582. [clock=2023-09-18 15:53:11+00:00] +2023-09-18 15:53:11,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4206005a60582. [clock=2023-09-18 15:53:11+00:00] +2023-09-18 15:53:11,219 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253668628798854313002440629 amount: 80. +2023-09-18 15:53:11,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257224309909197102016970912 amount: 80. +2023-09-18 15:53:11,601 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052391.0, "order_id": "x-XEKWYICXBSIUT605a4206003a40582", "exchange_order_id": "35336512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:53:11,610 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4206003a40582. +2023-09-18 15:53:12,014 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052391.0, "order_id": "x-XEKWYICXSSIUT605a4206005a60582", "exchange_order_id": "35336513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:53:12,014 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4206005a60582. +2023-09-18 15:53:12,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a423a783460582 for 80.00000000 SEI-USDT. +2023-09-18 15:53:12,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a423a783460582", "creation_timestamp": 1695052391.0, "exchange_order_id": "35336776", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:53:12,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a423a786060582 for 80.00000000 SEI-USDT. +2023-09-18 15:53:12,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052392.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a423a786060582", "creation_timestamp": 1695052391.0, "exchange_order_id": "35336777", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:53:43,690 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a423a786060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 15:53:43,693 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 15:53:43+00:00] +2023-09-18 15:53:43,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052423.0, "order_id": "x-XEKWYICXSSIUT605a423a786060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01005600"}]}, "exchange_trade_id": "4972264", "exchange_order_id": "35336777", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 15:53:43,893 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052423.0, "order_id": "x-XEKWYICXSSIUT605a423a786060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35336777", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 15:53:43,893 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a423a786060582 completely filled. +2023-09-18 15:54:06,244 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a423a783460582. [clock=2023-09-18 15:54:06+00:00] +2023-09-18 15:54:06,315 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258566420640397027948112463 amount: 80. +2023-09-18 15:54:06,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263412687276395035514642047 amount: 80. +2023-09-18 15:54:06,850 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052446.0, "order_id": "x-XEKWYICXBSIUT605a423a783460582", "exchange_order_id": "35336776", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:54:06,850 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a423a783460582. +2023-09-18 15:54:06,960 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a426f039170582 for 80.00000000 SEI-USDT. +2023-09-18 15:54:06,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a426f039170582", "creation_timestamp": 1695052446.0, "exchange_order_id": "35337357", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:54:07,938 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a426f0d97b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:54:07,972 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052447.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a426f0d97b0582", "creation_timestamp": 1695052446.0, "exchange_order_id": "35337366", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:55:01,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a426f039170582. [clock=2023-09-18 15:55:01+00:00] +2023-09-18 15:55:01,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a426f0d97b0582. [clock=2023-09-18 15:55:01+00:00] +2023-09-18 15:55:01,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259676805033429555363642649 amount: 80. +2023-09-18 15:55:01,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265192730202740497697543345 amount: 80. +2023-09-18 15:55:03,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052502.0, "order_id": "x-XEKWYICXBSIUT605a426f039170582", "exchange_order_id": "35337357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:55:03,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a426f039170582. +2023-09-18 15:55:03,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052503.0, "order_id": "x-XEKWYICXSSIUT605a426f0d97b0582", "exchange_order_id": "35337366", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:55:03,968 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a426f0d97b0582. +2023-09-18 15:55:03,969 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a42a3581630582 for 80.00000000 SEI-USDT. +2023-09-18 15:55:04,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052503.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a42a3581630582", "creation_timestamp": 1695052501.0, "exchange_order_id": "35337802", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:55:04,021 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a42a357e730582 for 80.00000000 SEI-USDT. +2023-09-18 15:55:04,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052503.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a42a357e730582", "creation_timestamp": 1695052501.0, "exchange_order_id": "35337801", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:55:14,961 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 15:55:56,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a42a357e730582. [clock=2023-09-18 15:55:56+00:00] +2023-09-18 15:55:56,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a42a3581630582. [clock=2023-09-18 15:55:56+00:00] +2023-09-18 15:55:56,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258321331545237530562582076 amount: 80. +2023-09-18 15:55:56,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263946071215929374528077290 amount: 80. +2023-09-18 15:55:56,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052556.0, "order_id": "x-XEKWYICXBSIUT605a42a357e730582", "exchange_order_id": "35337801", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:55:56,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a42a357e730582. +2023-09-18 15:55:56,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052556.0, "order_id": "x-XEKWYICXSSIUT605a42a3581630582", "exchange_order_id": "35337802", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:55:56,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a42a3581630582. +2023-09-18 15:55:56,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a42d7a93950582 for 80.00000000 SEI-USDT. +2023-09-18 15:55:56,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a42d7a93950582", "creation_timestamp": 1695052556.0, "exchange_order_id": "35338093", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:55:56,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a42d7a914b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:55:56,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a42d7a914b0582", "creation_timestamp": 1695052556.0, "exchange_order_id": "35338094", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:56:51,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a42d7a914b0582. [clock=2023-09-18 15:56:51+00:00] +2023-09-18 15:56:51,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a42d7a93950582. [clock=2023-09-18 15:56:51+00:00] +2023-09-18 15:56:51,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257473876052042658577098526 amount: 80. +2023-09-18 15:56:51,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261843556939182434779374697 amount: 80. +2023-09-18 15:56:51,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052611.0, "order_id": "x-XEKWYICXBSIUT605a42d7a914b0582", "exchange_order_id": "35338094", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:56:51,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a42d7a914b0582. +2023-09-18 15:56:51,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a430c223ba0582 for 80.00000000 SEI-USDT. +2023-09-18 15:56:51,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a430c223ba0582", "creation_timestamp": 1695052611.0, "exchange_order_id": "35338281", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:56:51,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052611.0, "order_id": "x-XEKWYICXSSIUT605a42d7a93950582", "exchange_order_id": "35338093", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:56:51,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a42d7a93950582. +2023-09-18 15:56:51,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a430c221850582 for 80.00000000 SEI-USDT. +2023-09-18 15:56:51,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a430c221850582", "creation_timestamp": 1695052611.0, "exchange_order_id": "35338282", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:57:46,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a430c221850582. [clock=2023-09-18 15:57:46+00:00] +2023-09-18 15:57:46,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a430c223ba0582. [clock=2023-09-18 15:57:46+00:00] +2023-09-18 15:57:46,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258591367692232515933990389 amount: 80. +2023-09-18 15:57:46,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261991715785668306569440033 amount: 80. +2023-09-18 15:57:47,701 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052666.0, "order_id": "x-XEKWYICXBSIUT605a430c221850582", "exchange_order_id": "35338282", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:57:47,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a430c221850582. +2023-09-18 15:57:48,860 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052668.0, "order_id": "x-XEKWYICXSSIUT605a430c223ba0582", "exchange_order_id": "35338281", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:57:48,860 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a430c223ba0582. +2023-09-18 15:57:49,348 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4340bd8f70582 for 80.00000000 SEI-USDT. +2023-09-18 15:57:49,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052668.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a4340bd8f70582", "creation_timestamp": 1695052666.0, "exchange_order_id": "35338456", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:57:49,438 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4340bd3d50582 for 80.00000000 SEI-USDT. +2023-09-18 15:57:49,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052668.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a4340bd3d50582", "creation_timestamp": 1695052666.0, "exchange_order_id": "35338457", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:58:41,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4340bd3d50582. [clock=2023-09-18 15:58:41+00:00] +2023-09-18 15:58:41,300 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4340bd8f70582. [clock=2023-09-18 15:58:41+00:00] +2023-09-18 15:58:41,361 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258756183897459892990202117 amount: 80. +2023-09-18 15:58:41,363 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262065934867437819678725913 amount: 80. +2023-09-18 15:58:42,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052721.0, "order_id": "x-XEKWYICXBSIUT605a4340bd3d50582", "exchange_order_id": "35338457", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:58:42,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4340bd3d50582. +2023-09-18 15:58:44,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052723.0, "order_id": "x-XEKWYICXSSIUT605a4340bd8f70582", "exchange_order_id": "35338456", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:58:44,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4340bd8f70582. +2023-09-18 15:58:44,581 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a43755189e0582 for 80.00000000 SEI-USDT. +2023-09-18 15:58:44,645 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052724.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a43755189e0582", "creation_timestamp": 1695052721.0, "exchange_order_id": "35338554", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:58:44,646 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a437551c3d0582 for 80.00000000 SEI-USDT. +2023-09-18 15:58:44,674 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052724.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a437551c3d0582", "creation_timestamp": 1695052721.0, "exchange_order_id": "35338555", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:59:36,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a43755189e0582. [clock=2023-09-18 15:59:36+00:00] +2023-09-18 15:59:36,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a437551c3d0582. [clock=2023-09-18 15:59:36+00:00] +2023-09-18 15:59:36,272 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259810471137462673670627755 amount: 80. +2023-09-18 15:59:36,273 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262386125501720894951301902 amount: 80. +2023-09-18 15:59:36,937 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052776.0, "order_id": "x-XEKWYICXBSIUT605a43755189e0582", "exchange_order_id": "35338554", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:59:36,937 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a43755189e0582. +2023-09-18 15:59:37,662 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052777.0, "order_id": "x-XEKWYICXSSIUT605a437551c3d0582", "exchange_order_id": "35338555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 15:59:37,662 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a437551c3d0582. +2023-09-18 15:59:37,833 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a43a9afb610582 for 80.00000000 SEI-USDT. +2023-09-18 15:59:37,869 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052777.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a43a9afb610582", "creation_timestamp": 1695052776.0, "exchange_order_id": "35338732", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 15:59:37,870 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a43a9af72b0582 for 80.00000000 SEI-USDT. +2023-09-18 15:59:37,903 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052777.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a43a9af72b0582", "creation_timestamp": 1695052776.0, "exchange_order_id": "35338731", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:00:11,947 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a43a9afb610582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:00:11,948 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:00:11+00:00] +2023-09-18 16:00:11,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052811.0, "order_id": "x-XEKWYICXSSIUT605a43a9afb610582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12620000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01009600"}]}, "exchange_trade_id": "4972402", "exchange_order_id": "35338732", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:00:11,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052811.0, "order_id": "x-XEKWYICXSSIUT605a43a9afb610582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35338732", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:00:11,982 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a43a9afb610582 completely filled. +2023-09-18 16:00:31,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a43a9af72b0582. [clock=2023-09-18 16:00:31+00:00] +2023-09-18 16:00:31,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12630000 amount: 80. +2023-09-18 16:00:31,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1269197539690459375456572848 amount: 80. +2023-09-18 16:00:31,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052831.0, "order_id": "x-XEKWYICXBSIUT605a43a9af72b0582", "exchange_order_id": "35338731", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:00:31,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a43a9af72b0582. +2023-09-18 16:00:31,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a43de138570582 for 80.00000000 SEI-USDT. +2023-09-18 16:00:31,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXBSIUT605a43de138570582", "creation_timestamp": 1695052831.0, "exchange_order_id": "35339104", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:00:31,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a43de13cf60582 for 80.00000000 SEI-USDT. +2023-09-18 16:00:31,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXSSIUT605a43de13cf60582", "creation_timestamp": 1695052831.0, "exchange_order_id": "35339105", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:00:35,075 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a43de138570582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:00:35,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:00:35+00:00] +2023-09-18 16:00:35,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052835.0, "order_id": "x-XEKWYICXBSIUT605a43de138570582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12630000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4972415", "exchange_order_id": "35339104", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:00:35,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052835.0, "order_id": "x-XEKWYICXBSIUT605a43de138570582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35339104", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:00:35,113 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a43de138570582 completely filled. +2023-09-18 16:01:26,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a43de13cf60582. [clock=2023-09-18 16:01:26+00:00] +2023-09-18 16:01:26,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259335653306666171807281206 amount: 80. +2023-09-18 16:01:26,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264856049216924565959353352 amount: 80. +2023-09-18 16:01:26,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052886.0, "order_id": "x-XEKWYICXSSIUT605a43de13cf60582", "exchange_order_id": "35339105", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:01:26,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a43de13cf60582. +2023-09-18 16:01:26,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a44127642c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:01:26,399 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a44127642c0582", "creation_timestamp": 1695052886.0, "exchange_order_id": "35339587", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:01:26,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4412767af0582 for 80.00000000 SEI-USDT. +2023-09-18 16:01:26,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a4412767af0582", "creation_timestamp": 1695052886.0, "exchange_order_id": "35339588", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:02:21,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a44127642c0582. [clock=2023-09-18 16:02:21+00:00] +2023-09-18 16:02:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4412767af0582. [clock=2023-09-18 16:02:21+00:00] +2023-09-18 16:02:21,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259485773089410266592299960 amount: 80. +2023-09-18 16:02:21,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263777835668694580358068055 amount: 80. +2023-09-18 16:02:21,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052941.0, "order_id": "x-XEKWYICXBSIUT605a44127642c0582", "exchange_order_id": "35339587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:02:21,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a44127642c0582. +2023-09-18 16:02:22,039 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052941.0, "order_id": "x-XEKWYICXSSIUT605a4412767af0582", "exchange_order_id": "35339588", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:02:22,039 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4412767af0582. +2023-09-18 16:02:22,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4446d5f3a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:02:22,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052942.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4446d5f3a0582", "creation_timestamp": 1695052941.0, "exchange_order_id": "35339794", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:02:22,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4446d5a230582 for 80.00000000 SEI-USDT. +2023-09-18 16:02:22,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052942.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a4446d5a230582", "creation_timestamp": 1695052941.0, "exchange_order_id": "35339793", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:03:05,292 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4446d5a230582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:03:05,306 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:03:05+00:00] +2023-09-18 16:03:05,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052985.0, "order_id": "x-XEKWYICXBSIUT605a4446d5a230582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12590000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4972492", "exchange_order_id": "35339793", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:03:05,526 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052985.0, "order_id": "x-XEKWYICXBSIUT605a4446d5a230582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35339793", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:03:05,527 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4446d5a230582 completely filled. +2023-09-18 16:03:16,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4446d5f3a0582. [clock=2023-09-18 16:03:16+00:00] +2023-09-18 16:03:16,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257715547342417510089339057 amount: 80. +2023-09-18 16:03:16,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262237246967703177311885449 amount: 80. +2023-09-18 16:03:16,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052996.0, "order_id": "x-XEKWYICXSSIUT605a4446d5f3a0582", "exchange_order_id": "35339794", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:03:16,576 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4446d5f3a0582. +2023-09-18 16:03:17,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a447b5a6760582 for 80.00000000 SEI-USDT. +2023-09-18 16:03:17,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052997.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a447b5a6760582", "creation_timestamp": 1695052996.0, "exchange_order_id": "35340182", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:03:17,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a447b5a9830582 for 80.00000000 SEI-USDT. +2023-09-18 16:03:17,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695052997.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a447b5a9830582", "creation_timestamp": 1695052996.0, "exchange_order_id": "35340183", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:04:11,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a447b5a6760582. [clock=2023-09-18 16:04:11+00:00] +2023-09-18 16:04:11,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a447b5a9830582. [clock=2023-09-18 16:04:11+00:00] +2023-09-18 16:04:11,292 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256060436540959738502476061 amount: 80. +2023-09-18 16:04:11,293 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260912589013804180412801743 amount: 80. +2023-09-18 16:04:12,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053051.0, "order_id": "x-XEKWYICXBSIUT605a447b5a6760582", "exchange_order_id": "35340182", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:04:12,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a447b5a6760582. +2023-09-18 16:04:12,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053051.0, "order_id": "x-XEKWYICXSSIUT605a447b5a9830582", "exchange_order_id": "35340183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:04:12,490 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a447b5a9830582. +2023-09-18 16:04:14,080 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a44aff6df70582 for 80.00000000 SEI-USDT. +2023-09-18 16:04:14,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053053.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a44aff6df70582", "creation_timestamp": 1695053051.0, "exchange_order_id": "35340653", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:04:14,343 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a44aff72030582 for 80.00000000 SEI-USDT. +2023-09-18 16:04:14,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053053.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a44aff72030582", "creation_timestamp": 1695053051.0, "exchange_order_id": "35340654", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:05:06,568 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a44aff6df70582. [clock=2023-09-18 16:05:06+00:00] +2023-09-18 16:05:06,569 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a44aff72030582. [clock=2023-09-18 16:05:06+00:00] +2023-09-18 16:05:06,623 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256492313701332722381680274 amount: 80. +2023-09-18 16:05:06,624 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260265887966688862914267951 amount: 80. +2023-09-18 16:05:07,079 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a44e4bb7350582 for 80.00000000 SEI-USDT. +2023-09-18 16:05:07,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053107.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a44e4bb7350582", "creation_timestamp": 1695053106.0, "exchange_order_id": "35340811", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:05:07,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a44e4bb98f0582 for 80.00000000 SEI-USDT. +2023-09-18 16:05:07,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053107.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a44e4bb98f0582", "creation_timestamp": 1695053106.0, "exchange_order_id": "35340812", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:05:07,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053107.0, "order_id": "x-XEKWYICXBSIUT605a44aff6df70582", "exchange_order_id": "35340653", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:05:07,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a44aff6df70582. +2023-09-18 16:05:07,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053107.0, "order_id": "x-XEKWYICXSSIUT605a44aff72030582", "exchange_order_id": "35340654", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:05:07,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a44aff72030582. +2023-09-18 16:06:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a44e4bb7350582. [clock=2023-09-18 16:06:01+00:00] +2023-09-18 16:06:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a44e4bb98f0582. [clock=2023-09-18 16:06:01+00:00] +2023-09-18 16:06:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255227141794343205003140435 amount: 80. +2023-09-18 16:06:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259664289992639962187148139 amount: 80. +2023-09-18 16:06:01,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053161.0, "order_id": "x-XEKWYICXBSIUT605a44e4bb7350582", "exchange_order_id": "35340811", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:06:01,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a44e4bb7350582. +2023-09-18 16:06:01,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053161.0, "order_id": "x-XEKWYICXSSIUT605a44e4bb98f0582", "exchange_order_id": "35340812", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:06:01,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a44e4bb98f0582. +2023-09-18 16:06:01,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a45189b53e0582 for 80.00000000 SEI-USDT. +2023-09-18 16:06:01,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a45189b53e0582", "creation_timestamp": 1695053161.0, "exchange_order_id": "35341154", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:06:01,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a45189b8f90582 for 80.00000000 SEI-USDT. +2023-09-18 16:06:01,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a45189b8f90582", "creation_timestamp": 1695053161.0, "exchange_order_id": "35341153", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:06:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a45189b53e0582. [clock=2023-09-18 16:06:56+00:00] +2023-09-18 16:06:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a45189b8f90582. [clock=2023-09-18 16:06:56+00:00] +2023-09-18 16:06:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253537143515370764441177784 amount: 80. +2023-09-18 16:06:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258986578124898790012323014 amount: 80. +2023-09-18 16:06:56,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053216.0, "order_id": "x-XEKWYICXBSIUT605a45189b53e0582", "exchange_order_id": "35341154", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:06:56,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a45189b53e0582. +2023-09-18 16:06:56,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a454d0efa70582 for 80.00000000 SEI-USDT. +2023-09-18 16:06:56,354 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a454d0efa70582", "creation_timestamp": 1695053216.0, "exchange_order_id": "35341780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:06:56,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053216.0, "order_id": "x-XEKWYICXSSIUT605a45189b8f90582", "exchange_order_id": "35341153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:06:56,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a45189b8f90582. +2023-09-18 16:06:56,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a454d0f2d00582 for 80.00000000 SEI-USDT. +2023-09-18 16:06:56,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a454d0f2d00582", "creation_timestamp": 1695053216.0, "exchange_order_id": "35341781", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:07:28,060 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a454d0f2d00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:07:28,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:07:27+00:00] +2023-09-18 16:07:28,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053247.0, "order_id": "x-XEKWYICXSSIUT605a454d0f2d00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12580000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01006400"}]}, "exchange_trade_id": "4972731", "exchange_order_id": "35341781", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:07:28,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053248.0, "order_id": "x-XEKWYICXSSIUT605a454d0f2d00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35341781", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:07:28,230 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a454d0f2d00582 completely filled. +2023-09-18 16:07:51,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a454d0efa70582. [clock=2023-09-18 16:07:51+00:00] +2023-09-18 16:07:51,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256501921807941279649286524 amount: 80. +2023-09-18 16:07:51,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263414786079212217340672088 amount: 80. +2023-09-18 16:07:51,526 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053271.0, "order_id": "x-XEKWYICXBSIUT605a454d0efa70582", "exchange_order_id": "35341780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:07:51,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a454d0efa70582. +2023-09-18 16:07:52,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a458190e780582 for 80.00000000 SEI-USDT. +2023-09-18 16:07:52,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053272.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a458190e780582", "creation_timestamp": 1695053271.0, "exchange_order_id": "35342417", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:07:52,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4581910660582 for 80.00000000 SEI-USDT. +2023-09-18 16:07:52,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053272.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4581910660582", "creation_timestamp": 1695053271.0, "exchange_order_id": "35342422", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:08:46,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a458190e780582. [clock=2023-09-18 16:08:46+00:00] +2023-09-18 16:08:46,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4581910660582. [clock=2023-09-18 16:08:46+00:00] +2023-09-18 16:08:46,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259009149344432132791178111 amount: 80. +2023-09-18 16:08:46,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265058145983838029101894582 amount: 80. +2023-09-18 16:08:46,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053326.0, "order_id": "x-XEKWYICXBSIUT605a458190e780582", "exchange_order_id": "35342417", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:08:46,495 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a458190e780582. +2023-09-18 16:08:47,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053327.0, "order_id": "x-XEKWYICXSSIUT605a4581910660582", "exchange_order_id": "35342422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:08:47,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4581910660582. +2023-09-18 16:08:48,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a45b621ac30582 for 80.00000000 SEI-USDT. +2023-09-18 16:08:48,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053327.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a45b621ac30582", "creation_timestamp": 1695053326.0, "exchange_order_id": "35342681", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:08:48,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a45b621def0582 for 80.00000000 SEI-USDT. +2023-09-18 16:08:48,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053327.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a45b621def0582", "creation_timestamp": 1695053326.0, "exchange_order_id": "35342682", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:09:41,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a45b621ac30582. [clock=2023-09-18 16:09:41+00:00] +2023-09-18 16:09:41,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a45b621def0582. [clock=2023-09-18 16:09:41+00:00] +2023-09-18 16:09:41,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257462936922475475526143199 amount: 80. +2023-09-18 16:09:41,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264021044096731789298433757 amount: 80. +2023-09-18 16:09:41,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053381.0, "order_id": "x-XEKWYICXBSIUT605a45b621ac30582", "exchange_order_id": "35342681", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:09:41,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a45b621ac30582. +2023-09-18 16:09:42,564 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053382.0, "order_id": "x-XEKWYICXSSIUT605a45b621def0582", "exchange_order_id": "35342682", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:09:42,565 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a45b621def0582. +2023-09-18 16:09:43,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a45ea7860b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:09:43,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053382.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a45ea7860b0582", "creation_timestamp": 1695053381.0, "exchange_order_id": "35342869", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:09:43,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a45ea783680582 for 80.00000000 SEI-USDT. +2023-09-18 16:09:43,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053382.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a45ea783680582", "creation_timestamp": 1695053381.0, "exchange_order_id": "35342870", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:10:36,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a45ea783680582. [clock=2023-09-18 16:10:36+00:00] +2023-09-18 16:10:36,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a45ea7860b0582. [clock=2023-09-18 16:10:36+00:00] +2023-09-18 16:10:36,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257925382056769062828177213 amount: 80. +2023-09-18 16:10:36,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263689442210558284345242681 amount: 80. +2023-09-18 16:10:36,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053436.0, "order_id": "x-XEKWYICXBSIUT605a45ea783680582", "exchange_order_id": "35342870", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:10:36,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a45ea783680582. +2023-09-18 16:10:36,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053436.0, "order_id": "x-XEKWYICXSSIUT605a45ea7860b0582", "exchange_order_id": "35342869", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:10:36,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a45ea7860b0582. +2023-09-18 16:10:36,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a461eefd140582 for 80.00000000 SEI-USDT. +2023-09-18 16:10:36,448 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a461eefd140582", "creation_timestamp": 1695053436.0, "exchange_order_id": "35343039", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:10:36,448 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a461eef95c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:10:36,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a461eef95c0582", "creation_timestamp": 1695053436.0, "exchange_order_id": "35343040", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:11:06,701 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a461eef95c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:11:06,703 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:11:06+00:00] +2023-09-18 16:11:06,727 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053466.0, "order_id": "x-XEKWYICXBSIUT605a461eef95c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4972822", "exchange_order_id": "35343040", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:11:06,740 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053466.0, "order_id": "x-XEKWYICXBSIUT605a461eef95c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35343040", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:11:06,740 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a461eef95c0582 completely filled. +2023-09-18 16:11:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a461eefd140582. [clock=2023-09-18 16:11:31+00:00] +2023-09-18 16:11:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251306363191511809679975503 amount: 80. +2023-09-18 16:11:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258824077807631512314948161 amount: 80. +2023-09-18 16:11:31,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053491.0, "order_id": "x-XEKWYICXSSIUT605a461eefd140582", "exchange_order_id": "35343039", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:11:31,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a461eefd140582. +2023-09-18 16:11:31,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a46535318c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:11:31,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a46535318c0582", "creation_timestamp": 1695053491.0, "exchange_order_id": "35343895", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:11:31,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a465352d5c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:11:31,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a465352d5c0582", "creation_timestamp": 1695053491.0, "exchange_order_id": "35343894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:12:26,269 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a465352d5c0582. [clock=2023-09-18 16:12:26+00:00] +2023-09-18 16:12:26,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a46535318c0582. [clock=2023-09-18 16:12:26+00:00] +2023-09-18 16:12:26,324 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247014685164971668283626885 amount: 80. +2023-09-18 16:12:26,326 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254181060964768003114770221 amount: 80. +2023-09-18 16:12:27,711 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053546.0, "order_id": "x-XEKWYICXBSIUT605a465352d5c0582", "exchange_order_id": "35343894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:12:27,711 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a465352d5c0582. +2023-09-18 16:12:29,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053548.0, "order_id": "x-XEKWYICXSSIUT605a46535318c0582", "exchange_order_id": "35343895", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:12:29,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a46535318c0582. +2023-09-18 16:12:29,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a468810b930582 for 80.00000000 SEI-USDT. +2023-09-18 16:12:29,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053549.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a468810b930582", "creation_timestamp": 1695053546.0, "exchange_order_id": "35344615", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:12:29,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4688107c90582 for 80.00000000 SEI-USDT. +2023-09-18 16:12:29,770 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053549.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a4688107c90582", "creation_timestamp": 1695053546.0, "exchange_order_id": "35344614", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:13:21,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4688107c90582. [clock=2023-09-18 16:13:21+00:00] +2023-09-18 16:13:21,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a468810b930582. [clock=2023-09-18 16:13:21+00:00] +2023-09-18 16:13:21,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246914234717878508302677232 amount: 80. +2023-09-18 16:13:21,191 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253007366832027286532234676 amount: 80. +2023-09-18 16:13:22,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053601.0, "order_id": "x-XEKWYICXBSIUT605a4688107c90582", "exchange_order_id": "35344614", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:13:22,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4688107c90582. +2023-09-18 16:13:23,924 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053603.0, "order_id": "x-XEKWYICXSSIUT605a468810b930582", "exchange_order_id": "35344615", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:13:23,925 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a468810b930582. +2023-09-18 16:13:24,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a46bc637690582 for 80.00000000 SEI-USDT. +2023-09-18 16:13:24,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053604.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a46bc637690582", "creation_timestamp": 1695053601.0, "exchange_order_id": "35345275", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:13:24,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a46bc6395b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:13:24,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053604.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a46bc6395b0582", "creation_timestamp": 1695053601.0, "exchange_order_id": "35345276", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:13:45,682 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a46bc6395b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:13:45,694 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:13:45+00:00] +2023-09-18 16:13:45,767 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053625.0, "order_id": "x-XEKWYICXSSIUT605a46bc6395b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01002400"}]}, "exchange_trade_id": "4973057", "exchange_order_id": "35345276", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:13:46,015 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053625.0, "order_id": "x-XEKWYICXSSIUT605a46bc6395b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35345276", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:13:46,015 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a46bc6395b0582 completely filled. +2023-09-18 16:14:16,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a46bc637690582. [clock=2023-09-18 16:14:16+00:00] +2023-09-18 16:14:16,336 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250941702878281260902947133 amount: 80. +2023-09-18 16:14:16,345 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256724805003855265984851662 amount: 80. +2023-09-18 16:14:16,767 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053656.0, "order_id": "x-XEKWYICXBSIUT605a46bc637690582", "exchange_order_id": "35345275", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:14:16,767 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a46bc637690582. +2023-09-18 16:14:17,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a46f0fcada0582 for 80.00000000 SEI-USDT. +2023-09-18 16:14:17,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a46f0fcada0582", "creation_timestamp": 1695053656.0, "exchange_order_id": "35345502", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:14:18,835 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a46f0fce2a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:14:18,889 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053658.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a46f0fce2a0582", "creation_timestamp": 1695053656.0, "exchange_order_id": "35345503", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:15:11,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a46f0fcada0582. [clock=2023-09-18 16:15:11+00:00] +2023-09-18 16:15:11,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a46f0fce2a0582. [clock=2023-09-18 16:15:11+00:00] +2023-09-18 16:15:11,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251029384015081766897924398 amount: 80. +2023-09-18 16:15:11,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256564090277643826721699172 amount: 80. +2023-09-18 16:15:11,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053711.0, "order_id": "x-XEKWYICXBSIUT605a46f0fcada0582", "exchange_order_id": "35345502", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:15:11,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a46f0fcada0582. +2023-09-18 16:15:11,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a47253bc740582 for 80.00000000 SEI-USDT. +2023-09-18 16:15:11,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a47253bc740582", "creation_timestamp": 1695053711.0, "exchange_order_id": "35345786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:15:11,540 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a47253ba090582 for 80.00000000 SEI-USDT. +2023-09-18 16:15:11,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a47253ba090582", "creation_timestamp": 1695053711.0, "exchange_order_id": "35345787", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:15:11,568 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053711.0, "order_id": "x-XEKWYICXSSIUT605a46f0fce2a0582", "exchange_order_id": "35345503", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:15:11,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a46f0fce2a0582. +2023-09-18 16:16:06,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a47253ba090582. [clock=2023-09-18 16:16:06+00:00] +2023-09-18 16:16:06,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a47253bc740582. [clock=2023-09-18 16:16:06+00:00] +2023-09-18 16:16:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249775774696517044563012793 amount: 80. +2023-09-18 16:16:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255120192777690510092221776 amount: 80. +2023-09-18 16:16:06,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053766.0, "order_id": "x-XEKWYICXBSIUT605a47253ba090582", "exchange_order_id": "35345787", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:16:06,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a47253ba090582. +2023-09-18 16:16:06,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053766.0, "order_id": "x-XEKWYICXSSIUT605a47253bc740582", "exchange_order_id": "35345786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:16:06,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a47253bc740582. +2023-09-18 16:16:06,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4759960a70582 for 80.00000000 SEI-USDT. +2023-09-18 16:16:06,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a4759960a70582", "creation_timestamp": 1695053766.0, "exchange_order_id": "35346114", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:16:06,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a47599642d0582 for 80.00000000 SEI-USDT. +2023-09-18 16:16:06,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a47599642d0582", "creation_timestamp": 1695053766.0, "exchange_order_id": "35346115", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:16:47,053 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4759960a70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:16:47,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 16:16:47+00:00] +2023-09-18 16:16:47,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053807.0, "order_id": "x-XEKWYICXBSIUT605a4759960a70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4973242", "exchange_order_id": "35346114", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:16:47,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053807.0, "order_id": "x-XEKWYICXBSIUT605a4759960a70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35346114", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:16:47,092 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4759960a70582 completely filled. +2023-09-18 16:17:01,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a47599642d0582. [clock=2023-09-18 16:17:01+00:00] +2023-09-18 16:17:01,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242329437798147392363463165 amount: 80. +2023-09-18 16:17:01,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250321460446603863244421196 amount: 80. +2023-09-18 16:17:01,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053821.0, "order_id": "x-XEKWYICXSSIUT605a47599642d0582", "exchange_order_id": "35346115", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:17:01,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a47599642d0582. +2023-09-18 16:17:01,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a478e127590582 for 80.00000000 SEI-USDT. +2023-09-18 16:17:01,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a478e127590582", "creation_timestamp": 1695053821.0, "exchange_order_id": "35346955", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:17:01,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a478e1239e0582 for 80.00000000 SEI-USDT. +2023-09-18 16:17:01,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a478e1239e0582", "creation_timestamp": 1695053821.0, "exchange_order_id": "35346956", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:17:56,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a478e1239e0582. [clock=2023-09-18 16:17:56+00:00] +2023-09-18 16:17:56,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a478e127590582. [clock=2023-09-18 16:17:56+00:00] +2023-09-18 16:17:56,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236048035823792111226645599 amount: 80. +2023-09-18 16:17:56,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245578568524052277769009617 amount: 80. +2023-09-18 16:17:56,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053876.0, "order_id": "x-XEKWYICXBSIUT605a478e1239e0582", "exchange_order_id": "35346956", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:17:56,533 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a478e1239e0582. +2023-09-18 16:17:57,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053876.0, "order_id": "x-XEKWYICXSSIUT605a478e127590582", "exchange_order_id": "35346955", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:17:57,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a478e127590582. +2023-09-18 16:17:57,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a47c29dfa10582 for 80.00000000 SEI-USDT. +2023-09-18 16:17:57,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a47c29dfa10582", "creation_timestamp": 1695053876.0, "exchange_order_id": "35348421", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:17:57,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a47c29dd8b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:17:57,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053877.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a47c29dd8b0582", "creation_timestamp": 1695053876.0, "exchange_order_id": "35348420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:18:41,165 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a47c29dfa10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:18:41,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 16:18:41+00:00] +2023-09-18 16:18:41,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053921.0, "order_id": "x-XEKWYICXSSIUT605a47c29dfa10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996000"}]}, "exchange_trade_id": "4973549", "exchange_order_id": "35348421", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:18:41,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053921.0, "order_id": "x-XEKWYICXSSIUT605a47c29dfa10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35348421", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:18:41,626 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a47c29dfa10582 completely filled. +2023-09-18 16:18:51,408 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a47c29dd8b0582. [clock=2023-09-18 16:18:51+00:00] +2023-09-18 16:18:51,461 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242770289059033537417556256 amount: 80. +2023-09-18 16:18:51,462 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252881294972484459286407384 amount: 80. +2023-09-18 16:18:52,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053931.0, "order_id": "x-XEKWYICXBSIUT605a47c29dd8b0582", "exchange_order_id": "35348420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:18:52,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a47c29dd8b0582. +2023-09-18 16:18:53,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a47f75c0e20582 for 80.00000000 SEI-USDT. +2023-09-18 16:18:53,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053933.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a47f75c0e20582", "creation_timestamp": 1695053931.0, "exchange_order_id": "35349244", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:18:54,056 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a47f75f3b90582 for 80.00000000 SEI-USDT. +2023-09-18 16:18:54,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053933.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a47f75f3b90582", "creation_timestamp": 1695053931.0, "exchange_order_id": "35349253", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:19:46,136 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a47f75c0e20582. [clock=2023-09-18 16:19:46+00:00] +2023-09-18 16:19:46,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a47f75f3b90582. [clock=2023-09-18 16:19:46+00:00] +2023-09-18 16:19:46,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244453795982628667640521412 amount: 80. +2023-09-18 16:19:46,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254836586490117691742669220 amount: 80. +2023-09-18 16:19:47,584 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053986.0, "order_id": "x-XEKWYICXBSIUT605a47f75c0e20582", "exchange_order_id": "35349244", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:19:47,586 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a47f75c0e20582. +2023-09-18 16:19:48,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053988.0, "order_id": "x-XEKWYICXSSIUT605a47f75f3b90582", "exchange_order_id": "35349253", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:19:48,997 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a47f75f3b90582. +2023-09-18 16:19:49,651 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a482b912190582 for 80.00000000 SEI-USDT. +2023-09-18 16:19:49,698 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053988.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a482b912190582", "creation_timestamp": 1695053986.0, "exchange_order_id": "35349827", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:19:49,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a482b9166e0582 for 80.00000000 SEI-USDT. +2023-09-18 16:19:49,773 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695053989.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a482b9166e0582", "creation_timestamp": 1695053986.0, "exchange_order_id": "35349828", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:20:41,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a482b912190582. [clock=2023-09-18 16:20:41+00:00] +2023-09-18 16:20:41,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a482b9166e0582. [clock=2023-09-18 16:20:41+00:00] +2023-09-18 16:20:41,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246334775748107252976671989 amount: 80. +2023-09-18 16:20:41,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255077713697653674683953105 amount: 80. +2023-09-18 16:20:41,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054041.0, "order_id": "x-XEKWYICXBSIUT605a482b912190582", "exchange_order_id": "35349827", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:20:41,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a482b912190582. +2023-09-18 16:20:41,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054041.0, "order_id": "x-XEKWYICXSSIUT605a482b9166e0582", "exchange_order_id": "35349828", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:20:41,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a482b9166e0582. +2023-09-18 16:20:41,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a485fe654b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:20:41,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a485fe654b0582", "creation_timestamp": 1695054041.0, "exchange_order_id": "35350037", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:20:41,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a485fe67f80582 for 80.00000000 SEI-USDT. +2023-09-18 16:20:41,350 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a485fe67f80582", "creation_timestamp": 1695054041.0, "exchange_order_id": "35350038", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:21:36,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a485fe654b0582. [clock=2023-09-18 16:21:36+00:00] +2023-09-18 16:21:36,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a485fe67f80582. [clock=2023-09-18 16:21:36+00:00] +2023-09-18 16:21:36,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244648010850061579177181594 amount: 80. +2023-09-18 16:21:36,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253823681556189713622777214 amount: 80. +2023-09-18 16:21:36,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054096.0, "order_id": "x-XEKWYICXBSIUT605a485fe654b0582", "exchange_order_id": "35350037", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:21:36,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a485fe654b0582. +2023-09-18 16:21:36,948 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054096.0, "order_id": "x-XEKWYICXSSIUT605a485fe67f80582", "exchange_order_id": "35350038", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:21:36,949 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a485fe67f80582. +2023-09-18 16:21:37,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a48946062d0582 for 80.00000000 SEI-USDT. +2023-09-18 16:21:37,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054097.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a48946062d0582", "creation_timestamp": 1695054096.0, "exchange_order_id": "35350504", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:21:37,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4894603630582 for 80.00000000 SEI-USDT. +2023-09-18 16:21:37,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054097.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a4894603630582", "creation_timestamp": 1695054096.0, "exchange_order_id": "35350505", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:22:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4894603630582. [clock=2023-09-18 16:22:31+00:00] +2023-09-18 16:22:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a48946062d0582. [clock=2023-09-18 16:22:31+00:00] +2023-09-18 16:22:31,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245519363761633557490449276 amount: 80. +2023-09-18 16:22:31,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253169564192088264975125045 amount: 80. +2023-09-18 16:22:31,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054151.0, "order_id": "x-XEKWYICXBSIUT605a4894603630582", "exchange_order_id": "35350505", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:22:31,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4894603630582. +2023-09-18 16:22:31,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054151.0, "order_id": "x-XEKWYICXSSIUT605a48946062d0582", "exchange_order_id": "35350504", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:22:31,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a48946062d0582. +2023-09-18 16:22:31,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a48c8c43ff0582 for 80.00000000 SEI-USDT. +2023-09-18 16:22:31,396 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a48c8c43ff0582", "creation_timestamp": 1695054151.0, "exchange_order_id": "35350937", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:22:31,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a48c8c67c50582 for 80.00000000 SEI-USDT. +2023-09-18 16:22:31,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a48c8c67c50582", "creation_timestamp": 1695054151.0, "exchange_order_id": "35350938", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:23:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a48c8c43ff0582. [clock=2023-09-18 16:23:26+00:00] +2023-09-18 16:23:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a48c8c67c50582. [clock=2023-09-18 16:23:26+00:00] +2023-09-18 16:23:26,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245132116535451937232665356 amount: 80. +2023-09-18 16:23:26,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253459640276669769785619475 amount: 80. +2023-09-18 16:23:26,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054206.0, "order_id": "x-XEKWYICXBSIUT605a48c8c43ff0582", "exchange_order_id": "35350937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:23:26,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a48c8c43ff0582. +2023-09-18 16:23:26,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054206.0, "order_id": "x-XEKWYICXSSIUT605a48c8c67c50582", "exchange_order_id": "35350938", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:23:26,465 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a48c8c67c50582. +2023-09-18 16:23:26,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a48fd34fd90582 for 80.00000000 SEI-USDT. +2023-09-18 16:23:26,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a48fd34fd90582", "creation_timestamp": 1695054206.0, "exchange_order_id": "35351349", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:23:26,573 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a48fd351de0582 for 80.00000000 SEI-USDT. +2023-09-18 16:23:26,604 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a48fd351de0582", "creation_timestamp": 1695054206.0, "exchange_order_id": "35351348", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:24:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a48fd34fd90582. [clock=2023-09-18 16:24:21+00:00] +2023-09-18 16:24:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a48fd351de0582. [clock=2023-09-18 16:24:21+00:00] +2023-09-18 16:24:21,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244593402800581052997943627 amount: 80. +2023-09-18 16:24:21,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251582658729014271277260545 amount: 80. +2023-09-18 16:24:21,652 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054261.0, "order_id": "x-XEKWYICXBSIUT605a48fd34fd90582", "exchange_order_id": "35351349", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:24:21,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a48fd34fd90582. +2023-09-18 16:24:22,043 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054262.0, "order_id": "x-XEKWYICXSSIUT605a48fd351de0582", "exchange_order_id": "35351348", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:24:22,044 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a48fd351de0582. +2023-09-18 16:24:22,045 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4931afdb20582 for 80.00000000 SEI-USDT. +2023-09-18 16:24:22,062 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054262.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a4931afdb20582", "creation_timestamp": 1695054261.0, "exchange_order_id": "35351792", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:24:22,062 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4931afa500582 for 80.00000000 SEI-USDT. +2023-09-18 16:24:22,085 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054262.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a4931afa500582", "creation_timestamp": 1695054261.0, "exchange_order_id": "35351794", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:25:14,980 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 16:25:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4931afa500582. [clock=2023-09-18 16:25:16+00:00] +2023-09-18 16:25:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4931afdb20582. [clock=2023-09-18 16:25:16+00:00] +2023-09-18 16:25:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244783650349392526597667513 amount: 80. +2023-09-18 16:25:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250741514599205644616868257 amount: 80. +2023-09-18 16:25:16,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054316.0, "order_id": "x-XEKWYICXBSIUT605a4931afa500582", "exchange_order_id": "35351794", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:25:16,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4931afa500582. +2023-09-18 16:25:16,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054316.0, "order_id": "x-XEKWYICXSSIUT605a4931afdb20582", "exchange_order_id": "35351792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:25:16,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4931afdb20582. +2023-09-18 16:25:16,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a496619ec00582 for 80.00000000 SEI-USDT. +2023-09-18 16:25:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a496619ec00582", "creation_timestamp": 1695054316.0, "exchange_order_id": "35351936", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:25:16,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a496619c180582 for 80.00000000 SEI-USDT. +2023-09-18 16:25:16,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a496619c180582", "creation_timestamp": 1695054316.0, "exchange_order_id": "35351937", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:26:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a496619c180582. [clock=2023-09-18 16:26:11+00:00] +2023-09-18 16:26:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a496619ec00582. [clock=2023-09-18 16:26:11+00:00] +2023-09-18 16:26:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245335168039821758101592170 amount: 80. +2023-09-18 16:26:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250484376620911914069662556 amount: 80. +2023-09-18 16:26:11,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054371.0, "order_id": "x-XEKWYICXBSIUT605a496619c180582", "exchange_order_id": "35351937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:26:11,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a496619c180582. +2023-09-18 16:26:11,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054371.0, "order_id": "x-XEKWYICXSSIUT605a496619ec00582", "exchange_order_id": "35351936", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:26:11,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a496619ec00582. +2023-09-18 16:26:11,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a499a8df8e0582 for 80.00000000 SEI-USDT. +2023-09-18 16:26:11,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a499a8df8e0582", "creation_timestamp": 1695054371.0, "exchange_order_id": "35352210", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:26:11,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a499a8db5b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:26:11,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a499a8db5b0582", "creation_timestamp": 1695054371.0, "exchange_order_id": "35352211", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:27:02,416 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a499a8df8e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:27:02,418 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 16:27:02+00:00] +2023-09-18 16:27:02,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054422.0, "order_id": "x-XEKWYICXSSIUT605a499a8df8e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4973831", "exchange_order_id": "35352210", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:27:02,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054422.0, "order_id": "x-XEKWYICXSSIUT605a499a8df8e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35352210", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:27:02,471 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a499a8df8e0582 completely filled. +2023-09-18 16:27:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a499a8db5b0582. [clock=2023-09-18 16:27:06+00:00] +2023-09-18 16:27:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249335374206313188709572326 amount: 80. +2023-09-18 16:27:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254682517022533531038156320 amount: 80. +2023-09-18 16:27:06,050 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054426.0, "order_id": "x-XEKWYICXBSIUT605a499a8db5b0582", "exchange_order_id": "35352211", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:27:06,051 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a499a8db5b0582. +2023-09-18 16:27:06,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a49cf00d6e0582 for 80.00000000 SEI-USDT. +2023-09-18 16:27:06,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a49cf00d6e0582", "creation_timestamp": 1695054426.0, "exchange_order_id": "35352556", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:27:06,326 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a49cf011540582 for 80.00000000 SEI-USDT. +2023-09-18 16:27:06,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a49cf011540582", "creation_timestamp": 1695054426.0, "exchange_order_id": "35352557", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:28:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a49cf00d6e0582. [clock=2023-09-18 16:28:01+00:00] +2023-09-18 16:28:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a49cf011540582. [clock=2023-09-18 16:28:01+00:00] +2023-09-18 16:28:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249166249364860105865255243 amount: 80. +2023-09-18 16:28:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 16:28:01,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054481.0, "order_id": "x-XEKWYICXBSIUT605a49cf00d6e0582", "exchange_order_id": "35352556", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:28:01,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a49cf00d6e0582. +2023-09-18 16:28:01,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054481.0, "order_id": "x-XEKWYICXSSIUT605a49cf011540582", "exchange_order_id": "35352557", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:28:01,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a49cf011540582. +2023-09-18 16:28:01,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4a0374e890582 for 80.00000000 SEI-USDT. +2023-09-18 16:28:01,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a4a0374e890582", "creation_timestamp": 1695054481.0, "exchange_order_id": "35353204", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:28:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4a0374e890582. [clock=2023-09-18 16:28:56+00:00] +2023-09-18 16:28:56,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249352670577546573998891775 amount: 80. +2023-09-18 16:28:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254550024038721377121441675 amount: 80. +2023-09-18 16:28:56,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054536.0, "order_id": "x-XEKWYICXBSIUT605a4a0374e890582", "exchange_order_id": "35353204", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:28:56,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4a0374e890582. +2023-09-18 16:28:56,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4a37e83050582 for 80.00000000 SEI-USDT. +2023-09-18 16:28:56,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a4a37e83050582", "creation_timestamp": 1695054536.0, "exchange_order_id": "35353567", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:28:56,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4a37e84e90582 for 80.00000000 SEI-USDT. +2023-09-18 16:28:56,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a4a37e84e90582", "creation_timestamp": 1695054536.0, "exchange_order_id": "35353568", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:29:27,408 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4a37e83050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:29:27,418 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 16:29:27+00:00] +2023-09-18 16:29:27,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054567.0, "order_id": "x-XEKWYICXBSIUT605a4a37e83050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4973904", "exchange_order_id": "35353567", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:29:27,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054567.0, "order_id": "x-XEKWYICXBSIUT605a4a37e83050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35353567", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:29:27,494 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4a37e83050582 completely filled. +2023-09-18 16:29:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4a37e84e90582. [clock=2023-09-18 16:29:51+00:00] +2023-09-18 16:29:51,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247705335906241419831401866 amount: 80. +2023-09-18 16:29:51,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252412074205688754533768073 amount: 80. +2023-09-18 16:29:51,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054591.0, "order_id": "x-XEKWYICXSSIUT605a4a37e84e90582", "exchange_order_id": "35353568", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:29:51,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4a37e84e90582. +2023-09-18 16:29:51,846 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4a6c63add0582 for 80.00000000 SEI-USDT. +2023-09-18 16:29:51,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a4a6c63add0582", "creation_timestamp": 1695054591.0, "exchange_order_id": "35353830", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:29:51,893 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4a6c63e3b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:29:51,952 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a4a6c63e3b0582", "creation_timestamp": 1695054591.0, "exchange_order_id": "35353831", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:30:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4a6c63add0582. [clock=2023-09-18 16:30:46+00:00] +2023-09-18 16:30:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4a6c63e3b0582. [clock=2023-09-18 16:30:46+00:00] +2023-09-18 16:30:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247810283446130093129699581 amount: 80. +2023-09-18 16:30:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252138018915198357395941603 amount: 80. +2023-09-18 16:30:46,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054646.0, "order_id": "x-XEKWYICXBSIUT605a4a6c63add0582", "exchange_order_id": "35353830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:30:46,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4a6c63add0582. +2023-09-18 16:30:46,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054646.0, "order_id": "x-XEKWYICXSSIUT605a4a6c63e3b0582", "exchange_order_id": "35353831", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:30:46,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4a6c63e3b0582. +2023-09-18 16:30:46,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4aa0d01850582 for 80.00000000 SEI-USDT. +2023-09-18 16:30:46,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a4aa0d01850582", "creation_timestamp": 1695054646.0, "exchange_order_id": "35354070", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:30:46,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4aa0cfe380582 for 80.00000000 SEI-USDT. +2023-09-18 16:30:46,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a4aa0cfe380582", "creation_timestamp": 1695054646.0, "exchange_order_id": "35354071", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:31:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4aa0cfe380582. [clock=2023-09-18 16:31:41+00:00] +2023-09-18 16:31:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4aa0d01850582. [clock=2023-09-18 16:31:41+00:00] +2023-09-18 16:31:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248144305714156441111279559 amount: 80. +2023-09-18 16:31:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252026482641361046575666827 amount: 80. +2023-09-18 16:31:41,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054701.0, "order_id": "x-XEKWYICXBSIUT605a4aa0cfe380582", "exchange_order_id": "35354071", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:31:41,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4aa0cfe380582. +2023-09-18 16:31:41,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054701.0, "order_id": "x-XEKWYICXSSIUT605a4aa0d01850582", "exchange_order_id": "35354070", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:31:41,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4aa0d01850582. +2023-09-18 16:31:41,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4ad543dd30582 for 80.00000000 SEI-USDT. +2023-09-18 16:31:41,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a4ad543dd30582", "creation_timestamp": 1695054701.0, "exchange_order_id": "35354237", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:31:41,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4ad543aee0582 for 80.00000000 SEI-USDT. +2023-09-18 16:31:41,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a4ad543aee0582", "creation_timestamp": 1695054701.0, "exchange_order_id": "35354238", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:32:36,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4ad543aee0582. [clock=2023-09-18 16:32:36+00:00] +2023-09-18 16:32:36,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4ad543dd30582. [clock=2023-09-18 16:32:36+00:00] +2023-09-18 16:32:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249334212375850441907658535 amount: 80. +2023-09-18 16:32:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252355517754134188548991678 amount: 80. +2023-09-18 16:32:36,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054756.0, "order_id": "x-XEKWYICXBSIUT605a4ad543aee0582", "exchange_order_id": "35354238", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:32:36,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4ad543aee0582. +2023-09-18 16:32:36,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054756.0, "order_id": "x-XEKWYICXSSIUT605a4ad543dd30582", "exchange_order_id": "35354237", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:32:36,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4ad543dd30582. +2023-09-18 16:32:36,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4b09b8a0a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:32:36,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a4b09b8a0a0582", "creation_timestamp": 1695054756.0, "exchange_order_id": "35354436", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:32:36,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4b09b8cf60582 for 80.00000000 SEI-USDT. +2023-09-18 16:32:36,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a4b09b8cf60582", "creation_timestamp": 1695054756.0, "exchange_order_id": "35354437", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:32:44,578 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a4b09b8cf60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:32:44,579 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:32:44+00:00] +2023-09-18 16:32:44,601 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054764.0, "order_id": "x-XEKWYICXSSIUT605a4b09b8cf60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4973993", "exchange_order_id": "35354437", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:32:44,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054764.0, "order_id": "x-XEKWYICXSSIUT605a4b09b8cf60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35354437", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:32:44,615 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a4b09b8cf60582 completely filled. +2023-09-18 16:33:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4b09b8a0a0582. [clock=2023-09-18 16:33:31+00:00] +2023-09-18 16:33:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-18 16:33:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257152727970887109448113974 amount: 80. +2023-09-18 16:33:31,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054811.0, "order_id": "x-XEKWYICXBSIUT605a4b09b8a0a0582", "exchange_order_id": "35354436", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:33:31,057 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4b09b8a0a0582. +2023-09-18 16:33:31,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4b3e2b7680582 for 80.00000000 SEI-USDT. +2023-09-18 16:33:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a4b3e2b7680582", "creation_timestamp": 1695054811.0, "exchange_order_id": "35354830", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:33:31,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4b3e2b3b30582 for 80.00000000 SEI-USDT. +2023-09-18 16:33:31,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a4b3e2b3b30582", "creation_timestamp": 1695054811.0, "exchange_order_id": "35354831", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:33:31,461 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4b3e2b3b30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:33:31,463 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:33:31+00:00] +2023-09-18 16:33:31,486 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054811.0, "order_id": "x-XEKWYICXBSIUT605a4b3e2b3b30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974007", "exchange_order_id": "35354831", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:33:31,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054811.0, "order_id": "x-XEKWYICXBSIUT605a4b3e2b3b30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35354831", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:33:31,500 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4b3e2b3b30582 completely filled. +2023-09-18 16:34:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4b3e2b7680582. [clock=2023-09-18 16:34:26+00:00] +2023-09-18 16:34:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250543289923272220291742462 amount: 80. +2023-09-18 16:34:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254362910052478407898848928 amount: 80. +2023-09-18 16:34:26,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054866.0, "order_id": "x-XEKWYICXSSIUT605a4b3e2b7680582", "exchange_order_id": "35354830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:34:26,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4b3e2b7680582. +2023-09-18 16:34:26,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4b729eb070582 for 80.00000000 SEI-USDT. +2023-09-18 16:34:26,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a4b729eb070582", "creation_timestamp": 1695054866.0, "exchange_order_id": "35355189", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:34:26,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4b729ee400582 for 80.00000000 SEI-USDT. +2023-09-18 16:34:26,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a4b729ee400582", "creation_timestamp": 1695054866.0, "exchange_order_id": "35355190", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:35:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4b729eb070582. [clock=2023-09-18 16:35:21+00:00] +2023-09-18 16:35:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4b729ee400582. [clock=2023-09-18 16:35:21+00:00] +2023-09-18 16:35:21,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251744983928739067297081176 amount: 80. +2023-09-18 16:35:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255234606905510290341935256 amount: 80. +2023-09-18 16:35:21,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054921.0, "order_id": "x-XEKWYICXBSIUT605a4b729eb070582", "exchange_order_id": "35355189", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:35:21,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4b729eb070582. +2023-09-18 16:35:21,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054921.0, "order_id": "x-XEKWYICXSSIUT605a4b729ee400582", "exchange_order_id": "35355190", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:35:21,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4b729ee400582. +2023-09-18 16:35:21,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4ba712b1a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:35:21,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a4ba712b1a0582", "creation_timestamp": 1695054921.0, "exchange_order_id": "35355504", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:35:21,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4ba7129030582 for 80.00000000 SEI-USDT. +2023-09-18 16:35:21,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a4ba7129030582", "creation_timestamp": 1695054921.0, "exchange_order_id": "35355505", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:36:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4ba7129030582. [clock=2023-09-18 16:36:16+00:00] +2023-09-18 16:36:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4ba712b1a0582. [clock=2023-09-18 16:36:16+00:00] +2023-09-18 16:36:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252861403036944728106818489 amount: 80. +2023-09-18 16:36:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257133444509018435517804183 amount: 80. +2023-09-18 16:36:16,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054976.0, "order_id": "x-XEKWYICXBSIUT605a4ba7129030582", "exchange_order_id": "35355505", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:36:16,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4ba7129030582. +2023-09-18 16:36:16,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054976.0, "order_id": "x-XEKWYICXSSIUT605a4ba712b1a0582", "exchange_order_id": "35355504", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:36:16,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4ba712b1a0582. +2023-09-18 16:36:16,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4bdb864d80582 for 80.00000000 SEI-USDT. +2023-09-18 16:36:16,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4bdb864d80582", "creation_timestamp": 1695054976.0, "exchange_order_id": "35355921", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:36:16,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4bdb868d30582 for 80.00000000 SEI-USDT. +2023-09-18 16:36:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695054976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a4bdb868d30582", "creation_timestamp": 1695054976.0, "exchange_order_id": "35355922", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:37:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4bdb864d80582. [clock=2023-09-18 16:37:11+00:00] +2023-09-18 16:37:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4bdb868d30582. [clock=2023-09-18 16:37:11+00:00] +2023-09-18 16:37:11,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252875059395820727083577092 amount: 80. +2023-09-18 16:37:11,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256716071361602856454018870 amount: 80. +2023-09-18 16:37:11,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055031.0, "order_id": "x-XEKWYICXBSIUT605a4bdb864d80582", "exchange_order_id": "35355921", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:37:11,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4bdb864d80582. +2023-09-18 16:37:11,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055031.0, "order_id": "x-XEKWYICXSSIUT605a4bdb868d30582", "exchange_order_id": "35355922", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:37:11,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4bdb868d30582. +2023-09-18 16:37:11,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4c0ffa4630582 for 80.00000000 SEI-USDT. +2023-09-18 16:37:11,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a4c0ffa4630582", "creation_timestamp": 1695055031.0, "exchange_order_id": "35356229", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:37:11,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4c0ffa0ad0582 for 80.00000000 SEI-USDT. +2023-09-18 16:37:11,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4c0ffa0ad0582", "creation_timestamp": 1695055031.0, "exchange_order_id": "35356230", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:38:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4c0ffa0ad0582. [clock=2023-09-18 16:38:06+00:00] +2023-09-18 16:38:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4c0ffa4630582. [clock=2023-09-18 16:38:06+00:00] +2023-09-18 16:38:06,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252487366727719011975755585 amount: 80. +2023-09-18 16:38:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256514782419205452816926707 amount: 80. +2023-09-18 16:38:06,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055086.0, "order_id": "x-XEKWYICXBSIUT605a4c0ffa0ad0582", "exchange_order_id": "35356230", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:38:06,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4c0ffa0ad0582. +2023-09-18 16:38:06,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055086.0, "order_id": "x-XEKWYICXSSIUT605a4c0ffa4630582", "exchange_order_id": "35356229", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:38:06,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4c0ffa4630582. +2023-09-18 16:38:06,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4c44721480582 for 80.00000000 SEI-USDT. +2023-09-18 16:38:06,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4c44721480582", "creation_timestamp": 1695055086.0, "exchange_order_id": "35356462", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:38:06,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4c44724000582 for 80.00000000 SEI-USDT. +2023-09-18 16:38:06,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a4c44724000582", "creation_timestamp": 1695055086.0, "exchange_order_id": "35356463", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:38:14,553 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4c44721480582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:38:14,554 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:38:14+00:00] +2023-09-18 16:38:14,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055094.0, "order_id": "x-XEKWYICXBSIUT605a4c44721480582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974151", "exchange_order_id": "35356462", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:38:14,593 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055094.0, "order_id": "x-XEKWYICXBSIUT605a4c44721480582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35356462", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:38:14,594 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4c44721480582 completely filled. +2023-09-18 16:39:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4c44724000582. [clock=2023-09-18 16:39:01+00:00] +2023-09-18 16:39:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251939192470012168111904036 amount: 80. +2023-09-18 16:39:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257966365071911068131000530 amount: 80. +2023-09-18 16:39:01,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055141.0, "order_id": "x-XEKWYICXSSIUT605a4c44724000582", "exchange_order_id": "35356463", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:39:01,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4c44724000582. +2023-09-18 16:39:01,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4c78e20cc0582 for 80.00000000 SEI-USDT. +2023-09-18 16:39:01,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a4c78e20cc0582", "creation_timestamp": 1695055141.0, "exchange_order_id": "35356854", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:39:01,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4c78e1bd60582 for 80.00000000 SEI-USDT. +2023-09-18 16:39:01,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a4c78e1bd60582", "creation_timestamp": 1695055141.0, "exchange_order_id": "35356853", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:39:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4c78e1bd60582. [clock=2023-09-18 16:39:56+00:00] +2023-09-18 16:39:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4c78e20cc0582. [clock=2023-09-18 16:39:56+00:00] +2023-09-18 16:39:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252175606905055001917985534 amount: 80. +2023-09-18 16:39:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256861786329768680721097079 amount: 80. +2023-09-18 16:39:56,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055196.0, "order_id": "x-XEKWYICXBSIUT605a4c78e1bd60582", "exchange_order_id": "35356853", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:39:56,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4c78e1bd60582. +2023-09-18 16:39:56,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055196.0, "order_id": "x-XEKWYICXSSIUT605a4c78e20cc0582", "exchange_order_id": "35356854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:39:56,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4c78e20cc0582. +2023-09-18 16:39:56,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4cad552650582 for 80.00000000 SEI-USDT. +2023-09-18 16:39:56,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a4cad552650582", "creation_timestamp": 1695055196.0, "exchange_order_id": "35357025", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:39:56,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4cad555520582 for 80.00000000 SEI-USDT. +2023-09-18 16:39:56,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a4cad555520582", "creation_timestamp": 1695055196.0, "exchange_order_id": "35357026", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:40:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4cad552650582. [clock=2023-09-18 16:40:51+00:00] +2023-09-18 16:40:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4cad555520582. [clock=2023-09-18 16:40:51+00:00] +2023-09-18 16:40:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250636712339190242213790978 amount: 80. +2023-09-18 16:40:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255613842474050524816985478 amount: 80. +2023-09-18 16:40:51,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055251.0, "order_id": "x-XEKWYICXBSIUT605a4cad552650582", "exchange_order_id": "35357025", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:40:51,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4cad552650582. +2023-09-18 16:40:51,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055251.0, "order_id": "x-XEKWYICXSSIUT605a4cad555520582", "exchange_order_id": "35357026", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:40:51,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4cad555520582. +2023-09-18 16:40:51,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4ce1c94280582 for 80.00000000 SEI-USDT. +2023-09-18 16:40:51,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a4ce1c94280582", "creation_timestamp": 1695055251.0, "exchange_order_id": "35357356", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:40:51,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4ce1c980a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:40:51,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a4ce1c980a0582", "creation_timestamp": 1695055251.0, "exchange_order_id": "35357357", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:41:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4ce1c94280582. [clock=2023-09-18 16:41:46+00:00] +2023-09-18 16:41:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4ce1c980a0582. [clock=2023-09-18 16:41:46+00:00] +2023-09-18 16:41:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250940702946377967966098094 amount: 80. +2023-09-18 16:41:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254811033698465248072426649 amount: 80. +2023-09-18 16:41:46,091 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055306.0, "order_id": "x-XEKWYICXBSIUT605a4ce1c94280582", "exchange_order_id": "35357356", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:41:46,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4ce1c94280582. +2023-09-18 16:41:46,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4d163cd200582 for 80.00000000 SEI-USDT. +2023-09-18 16:41:46,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a4d163cd200582", "creation_timestamp": 1695055306.0, "exchange_order_id": "35357555", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:41:46,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4d163cef50582 for 80.00000000 SEI-USDT. +2023-09-18 16:41:46,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a4d163cef50582", "creation_timestamp": 1695055306.0, "exchange_order_id": "35357556", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:41:46,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055306.0, "order_id": "x-XEKWYICXSSIUT605a4ce1c980a0582", "exchange_order_id": "35357357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:41:46,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4ce1c980a0582. +2023-09-18 16:41:55,486 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a4d163cef50582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:41:55,487 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:41:55+00:00] +2023-09-18 16:41:55,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055315.0, "order_id": "x-XEKWYICXSSIUT605a4d163cef50582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12540000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01003200"}]}, "exchange_trade_id": "4974238", "exchange_order_id": "35357556", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:41:55,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055315.0, "order_id": "x-XEKWYICXSSIUT605a4d163cef50582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35357556", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:41:55,523 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a4d163cef50582 completely filled. +2023-09-18 16:42:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4d163cd200582. [clock=2023-09-18 16:42:41+00:00] +2023-09-18 16:42:41,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12560000 amount: 80. +2023-09-18 16:42:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263867340238102839839966966 amount: 80. +2023-09-18 16:42:41,053 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055361.0, "order_id": "x-XEKWYICXBSIUT605a4d163cd200582", "exchange_order_id": "35357555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:42:41,054 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4d163cd200582. +2023-09-18 16:42:41,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4d4ab083b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:42:41,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4d4ab083b0582", "creation_timestamp": 1695055361.0, "exchange_order_id": "35358336", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:42:41,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4d4ab05e40582 for 80.00000000 SEI-USDT. +2023-09-18 16:42:41,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a4d4ab05e40582", "creation_timestamp": 1695055361.0, "exchange_order_id": "35358337", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:43:36,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4d4ab05e40582. [clock=2023-09-18 16:43:36+00:00] +2023-09-18 16:43:36,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4d4ab083b0582. [clock=2023-09-18 16:43:36+00:00] +2023-09-18 16:43:36,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12560000 amount: 80. +2023-09-18 16:43:36,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262960838597072462556909002 amount: 80. +2023-09-18 16:43:36,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055416.0, "order_id": "x-XEKWYICXBSIUT605a4d4ab05e40582", "exchange_order_id": "35358337", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:43:36,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4d4ab05e40582. +2023-09-18 16:43:36,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055416.0, "order_id": "x-XEKWYICXSSIUT605a4d4ab083b0582", "exchange_order_id": "35358336", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:43:36,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4d4ab083b0582. +2023-09-18 16:43:36,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4d7f275ba0582 for 80.00000000 SEI-USDT. +2023-09-18 16:43:36,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a4d7f275ba0582", "creation_timestamp": 1695055416.0, "exchange_order_id": "35358481", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:43:36,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4d7f272590582 for 80.00000000 SEI-USDT. +2023-09-18 16:43:36,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a4d7f272590582", "creation_timestamp": 1695055416.0, "exchange_order_id": "35358480", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:44:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4d7f272590582. [clock=2023-09-18 16:44:31+00:00] +2023-09-18 16:44:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4d7f275ba0582. [clock=2023-09-18 16:44:31+00:00] +2023-09-18 16:44:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12560000 amount: 80. +2023-09-18 16:44:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262070570960879946067472363 amount: 80. +2023-09-18 16:44:31,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055471.0, "order_id": "x-XEKWYICXBSIUT605a4d7f272590582", "exchange_order_id": "35358480", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:44:31,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4d7f272590582. +2023-09-18 16:44:31,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055471.0, "order_id": "x-XEKWYICXSSIUT605a4d7f275ba0582", "exchange_order_id": "35358481", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:44:31,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4d7f275ba0582. +2023-09-18 16:44:31,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4db3980690582 for 80.00000000 SEI-USDT. +2023-09-18 16:44:31,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605a4db3980690582", "creation_timestamp": 1695055471.0, "exchange_order_id": "35358766", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:44:31,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4db3983780582 for 80.00000000 SEI-USDT. +2023-09-18 16:44:31,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a4db3983780582", "creation_timestamp": 1695055471.0, "exchange_order_id": "35358767", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:45:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4db3980690582. [clock=2023-09-18 16:45:26+00:00] +2023-09-18 16:45:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4db3983780582. [clock=2023-09-18 16:45:26+00:00] +2023-09-18 16:45:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12570000 amount: 80. +2023-09-18 16:45:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261723683103168392768141887 amount: 80. +2023-09-18 16:45:26,055 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055526.0, "order_id": "x-XEKWYICXBSIUT605a4db3980690582", "exchange_order_id": "35358766", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:45:26,055 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4db3980690582. +2023-09-18 16:45:26,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4de80bbe90582 for 80.00000000 SEI-USDT. +2023-09-18 16:45:26,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a4de80bbe90582", "creation_timestamp": 1695055526.0, "exchange_order_id": "35359048", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:45:26,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055526.0, "order_id": "x-XEKWYICXSSIUT605a4db3983780582", "exchange_order_id": "35358767", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:45:26,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4db3983780582. +2023-09-18 16:45:26,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4de80be910582 for 80.00000000 SEI-USDT. +2023-09-18 16:45:26,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a4de80be910582", "creation_timestamp": 1695055526.0, "exchange_order_id": "35359049", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:46:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4de80bbe90582. [clock=2023-09-18 16:46:21+00:00] +2023-09-18 16:46:21,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4de80be910582. [clock=2023-09-18 16:46:21+00:00] +2023-09-18 16:46:21,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12580000 amount: 80. +2023-09-18 16:46:21,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262986008644708972837251677 amount: 80. +2023-09-18 16:46:21,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055581.0, "order_id": "x-XEKWYICXBSIUT605a4de80bbe90582", "exchange_order_id": "35359048", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:46:21,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4de80bbe90582. +2023-09-18 16:46:21,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4e1c8341c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:46:21,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a4e1c8341c0582", "creation_timestamp": 1695055581.0, "exchange_order_id": "35359444", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:46:21,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055581.0, "order_id": "x-XEKWYICXSSIUT605a4de80be910582", "exchange_order_id": "35359049", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:46:21,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4de80be910582. +2023-09-18 16:46:21,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4e1c82ff20582 for 80.00000000 SEI-USDT. +2023-09-18 16:46:21,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a4e1c82ff20582", "creation_timestamp": 1695055581.0, "exchange_order_id": "35359445", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:47:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4e1c82ff20582. [clock=2023-09-18 16:47:16+00:00] +2023-09-18 16:47:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4e1c8341c0582. [clock=2023-09-18 16:47:16+00:00] +2023-09-18 16:47:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12590000 amount: 80. +2023-09-18 16:47:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263726348373473286390719654 amount: 80. +2023-09-18 16:47:16,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055636.0, "order_id": "x-XEKWYICXBSIUT605a4e1c82ff20582", "exchange_order_id": "35359445", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:47:16,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4e1c82ff20582. +2023-09-18 16:47:16,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055636.0, "order_id": "x-XEKWYICXSSIUT605a4e1c8341c0582", "exchange_order_id": "35359444", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:47:16,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4e1c8341c0582. +2023-09-18 16:47:16,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4e50f38520582 for 80.00000000 SEI-USDT. +2023-09-18 16:47:16,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4e50f38520582", "creation_timestamp": 1695055636.0, "exchange_order_id": "35359587", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:47:16,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4e50f35fc0582 for 80.00000000 SEI-USDT. +2023-09-18 16:47:16,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a4e50f35fc0582", "creation_timestamp": 1695055636.0, "exchange_order_id": "35359588", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:48:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4e50f35fc0582. [clock=2023-09-18 16:48:11+00:00] +2023-09-18 16:48:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4e50f38520582. [clock=2023-09-18 16:48:11+00:00] +2023-09-18 16:48:11,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12600000 amount: 80. +2023-09-18 16:48:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264333089559022019147070859 amount: 80. +2023-09-18 16:48:11,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055691.0, "order_id": "x-XEKWYICXBSIUT605a4e50f35fc0582", "exchange_order_id": "35359588", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:48:11,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4e50f35fc0582. +2023-09-18 16:48:11,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055691.0, "order_id": "x-XEKWYICXSSIUT605a4e50f38520582", "exchange_order_id": "35359587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:48:11,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4e50f38520582. +2023-09-18 16:48:11,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4e85673440582 for 80.00000000 SEI-USDT. +2023-09-18 16:48:11,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a4e85673440582", "creation_timestamp": 1695055691.0, "exchange_order_id": "35359786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:48:11,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4e856706b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:48:11,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a4e856706b0582", "creation_timestamp": 1695055691.0, "exchange_order_id": "35359787", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:48:34,042 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4e856706b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:48:34,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:48:34+00:00] +2023-09-18 16:48:34,066 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055714.0, "order_id": "x-XEKWYICXBSIUT605a4e856706b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12600000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974398", "exchange_order_id": "35359787", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:48:34,078 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055714.0, "order_id": "x-XEKWYICXBSIUT605a4e856706b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35359787", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:48:34,079 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4e856706b0582 completely filled. +2023-09-18 16:49:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4e85673440582. [clock=2023-09-18 16:49:06+00:00] +2023-09-18 16:49:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12580000 amount: 80. +2023-09-18 16:49:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261699148424516059928190544 amount: 80. +2023-09-18 16:49:06,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055746.0, "order_id": "x-XEKWYICXSSIUT605a4e85673440582", "exchange_order_id": "35359786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:49:06,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4e85673440582. +2023-09-18 16:49:06,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4eb9da97b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:49:06,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a4eb9da97b0582", "creation_timestamp": 1695055746.0, "exchange_order_id": "35360058", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:49:06,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4eb9dabea0582 for 80.00000000 SEI-USDT. +2023-09-18 16:49:06,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a4eb9dabea0582", "creation_timestamp": 1695055746.0, "exchange_order_id": "35360059", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:50:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4eb9da97b0582. [clock=2023-09-18 16:50:01+00:00] +2023-09-18 16:50:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4eb9dabea0582. [clock=2023-09-18 16:50:01+00:00] +2023-09-18 16:50:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12580000 amount: 80. +2023-09-18 16:50:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261700273738465675763872424 amount: 80. +2023-09-18 16:50:01,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055801.0, "order_id": "x-XEKWYICXBSIUT605a4eb9da97b0582", "exchange_order_id": "35360058", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:50:01,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4eb9da97b0582. +2023-09-18 16:50:01,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055801.0, "order_id": "x-XEKWYICXSSIUT605a4eb9dabea0582", "exchange_order_id": "35360059", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:50:01,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4eb9dabea0582. +2023-09-18 16:50:01,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4eee4f33b0582 for 80.00000000 SEI-USDT. +2023-09-18 16:50:01,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a4eee4f33b0582", "creation_timestamp": 1695055801.0, "exchange_order_id": "35360150", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:50:01,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4eee4efaa0582 for 80.00000000 SEI-USDT. +2023-09-18 16:50:01,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a4eee4efaa0582", "creation_timestamp": 1695055801.0, "exchange_order_id": "35360151", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:50:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4eee4efaa0582. [clock=2023-09-18 16:50:56+00:00] +2023-09-18 16:50:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4eee4f33b0582. [clock=2023-09-18 16:50:56+00:00] +2023-09-18 16:50:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12590000 amount: 80. +2023-09-18 16:50:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263432695883036420348030423 amount: 80. +2023-09-18 16:50:56,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055856.0, "order_id": "x-XEKWYICXBSIUT605a4eee4efaa0582", "exchange_order_id": "35360151", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:50:56,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4eee4efaa0582. +2023-09-18 16:50:56,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055856.0, "order_id": "x-XEKWYICXSSIUT605a4eee4f33b0582", "exchange_order_id": "35360150", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:50:56,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4eee4f33b0582. +2023-09-18 16:50:56,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4f22c26880582 for 80.00000000 SEI-USDT. +2023-09-18 16:50:56,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4f22c26880582", "creation_timestamp": 1695055856.0, "exchange_order_id": "35360450", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:50:56,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4f22c23a80582 for 80.00000000 SEI-USDT. +2023-09-18 16:50:56,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a4f22c23a80582", "creation_timestamp": 1695055856.0, "exchange_order_id": "35360451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:51:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4f22c23a80582. [clock=2023-09-18 16:51:51+00:00] +2023-09-18 16:51:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4f22c26880582. [clock=2023-09-18 16:51:51+00:00] +2023-09-18 16:51:51,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12590000 amount: 80. +2023-09-18 16:51:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263223893865092979089243297 amount: 80. +2023-09-18 16:51:51,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055911.0, "order_id": "x-XEKWYICXBSIUT605a4f22c23a80582", "exchange_order_id": "35360451", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:51:51,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4f22c23a80582. +2023-09-18 16:51:51,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055911.0, "order_id": "x-XEKWYICXSSIUT605a4f22c26880582", "exchange_order_id": "35360450", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:51:51,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4f22c26880582. +2023-09-18 16:51:51,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4f57361060582 for 80.00000000 SEI-USDT. +2023-09-18 16:51:51,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a4f57361060582", "creation_timestamp": 1695055911.0, "exchange_order_id": "35360763", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:51:51,336 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4f57363f40582 for 80.00000000 SEI-USDT. +2023-09-18 16:51:51,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4f57363f40582", "creation_timestamp": 1695055911.0, "exchange_order_id": "35360764", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:52:23,373 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a4f57361060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:52:23,374 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:52:23+00:00] +2023-09-18 16:52:23,398 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055943.0, "order_id": "x-XEKWYICXBSIUT605a4f57361060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12590000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974484", "exchange_order_id": "35360763", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:52:23,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055943.0, "order_id": "x-XEKWYICXBSIUT605a4f57361060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35360763", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:52:23,417 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a4f57361060582 completely filled. +2023-09-18 16:52:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4f57363f40582. [clock=2023-09-18 16:52:46+00:00] +2023-09-18 16:52:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257956241326861419804245672 amount: 80. +2023-09-18 16:52:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261443812202922329691829820 amount: 80. +2023-09-18 16:52:46,091 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055966.0, "order_id": "x-XEKWYICXSSIUT605a4f57363f40582", "exchange_order_id": "35360764", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:52:46,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4f57363f40582. +2023-09-18 16:52:46,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4f8ba961c0582 for 80.00000000 SEI-USDT. +2023-09-18 16:52:46,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a4f8ba961c0582", "creation_timestamp": 1695055966.0, "exchange_order_id": "35361024", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:52:46,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4f8ba98ab0582 for 80.00000000 SEI-USDT. +2023-09-18 16:52:46,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a4f8ba98ab0582", "creation_timestamp": 1695055966.0, "exchange_order_id": "35361025", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:53:18,641 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a4f8ba98ab0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:53:18,642 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:53:18+00:00] +2023-09-18 16:53:18,681 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055998.0, "order_id": "x-XEKWYICXSSIUT605a4f8ba98ab0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12610000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01008800"}]}, "exchange_trade_id": "4974510", "exchange_order_id": "35361025", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:53:18,704 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695055998.0, "order_id": "x-XEKWYICXSSIUT605a4f8ba98ab0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35361025", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:53:18,705 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a4f8ba98ab0582 completely filled. +2023-09-18 16:53:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4f8ba961c0582. [clock=2023-09-18 16:53:41+00:00] +2023-09-18 16:53:41,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12600000 amount: 80. +2023-09-18 16:53:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263936444511731974660125828 amount: 80. +2023-09-18 16:53:41,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056021.0, "order_id": "x-XEKWYICXBSIUT605a4f8ba961c0582", "exchange_order_id": "35361024", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:53:41,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4f8ba961c0582. +2023-09-18 16:53:41,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4fc01d4dc0582 for 80.00000000 SEI-USDT. +2023-09-18 16:53:41,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a4fc01d4dc0582", "creation_timestamp": 1695056021.0, "exchange_order_id": "35361327", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:53:41,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4fc01d7ae0582 for 80.00000000 SEI-USDT. +2023-09-18 16:53:41,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a4fc01d7ae0582", "creation_timestamp": 1695056021.0, "exchange_order_id": "35361328", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:54:36,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4fc01d4dc0582. [clock=2023-09-18 16:54:36+00:00] +2023-09-18 16:54:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4fc01d7ae0582. [clock=2023-09-18 16:54:36+00:00] +2023-09-18 16:54:36,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259445852097380176103853200 amount: 80. +2023-09-18 16:54:36,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262742490247939898562614511 amount: 80. +2023-09-18 16:54:36,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056076.0, "order_id": "x-XEKWYICXSSIUT605a4fc01d7ae0582", "exchange_order_id": "35361328", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:54:36,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4fc01d7ae0582. +2023-09-18 16:54:36,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a4ff494f870582 for 80.00000000 SEI-USDT. +2023-09-18 16:54:36,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a4ff494f870582", "creation_timestamp": 1695056076.0, "exchange_order_id": "35361496", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:54:36,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056076.0, "order_id": "x-XEKWYICXBSIUT605a4fc01d4dc0582", "exchange_order_id": "35361327", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:54:36,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4fc01d4dc0582. +2023-09-18 16:54:36,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a4ff494bd70582 for 80.00000000 SEI-USDT. +2023-09-18 16:54:36,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a4ff494bd70582", "creation_timestamp": 1695056076.0, "exchange_order_id": "35361497", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:55:14,992 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 16:55:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a4ff494bd70582. [clock=2023-09-18 16:55:31+00:00] +2023-09-18 16:55:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a4ff494f870582. [clock=2023-09-18 16:55:31+00:00] +2023-09-18 16:55:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12610000 amount: 80. +2023-09-18 16:55:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265539456913310842753210572 amount: 80. +2023-09-18 16:55:31,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056131.0, "order_id": "x-XEKWYICXBSIUT605a4ff494bd70582", "exchange_order_id": "35361497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:55:31,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a4ff494bd70582. +2023-09-18 16:55:31,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056131.0, "order_id": "x-XEKWYICXSSIUT605a4ff494f870582", "exchange_order_id": "35361496", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:55:31,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a4ff494f870582. +2023-09-18 16:55:31,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a50290536a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:55:31,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a50290536a0582", "creation_timestamp": 1695056131.0, "exchange_order_id": "35361645", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:55:31,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5029050140582 for 80.00000000 SEI-USDT. +2023-09-18 16:55:31,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXBSIUT605a5029050140582", "creation_timestamp": 1695056131.0, "exchange_order_id": "35361646", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:56:01,596 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5029050140582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:56:01,597 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:56:01+00:00] +2023-09-18 16:56:01,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056161.0, "order_id": "x-XEKWYICXBSIUT605a5029050140582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12610000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974537", "exchange_order_id": "35361646", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:56:01,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056161.0, "order_id": "x-XEKWYICXBSIUT605a5029050140582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35361646", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:56:01,628 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5029050140582 completely filled. +2023-09-18 16:56:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a50290536a0582. [clock=2023-09-18 16:56:26+00:00] +2023-09-18 16:56:26,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259956093936421465247201496 amount: 80. +2023-09-18 16:56:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263654690691624361201713704 amount: 80. +2023-09-18 16:56:26,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056186.0, "order_id": "x-XEKWYICXSSIUT605a50290536a0582", "exchange_order_id": "35361645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:56:26,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a50290536a0582. +2023-09-18 16:56:26,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a505d7831a0582 for 80.00000000 SEI-USDT. +2023-09-18 16:56:26,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a505d7831a0582", "creation_timestamp": 1695056186.0, "exchange_order_id": "35361864", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:56:26,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a505d7851f0582 for 80.00000000 SEI-USDT. +2023-09-18 16:56:26,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a505d7851f0582", "creation_timestamp": 1695056186.0, "exchange_order_id": "35361865", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:57:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a505d7831a0582. [clock=2023-09-18 16:57:21+00:00] +2023-09-18 16:57:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a505d7851f0582. [clock=2023-09-18 16:57:21+00:00] +2023-09-18 16:57:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259188902618194233495729379 amount: 80. +2023-09-18 16:57:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262062899958744984654794705 amount: 80. +2023-09-18 16:57:21,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056241.0, "order_id": "x-XEKWYICXBSIUT605a505d7831a0582", "exchange_order_id": "35361864", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:57:21,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a505d7831a0582. +2023-09-18 16:57:21,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056241.0, "order_id": "x-XEKWYICXSSIUT605a505d7851f0582", "exchange_order_id": "35361865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:57:21,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a505d7851f0582. +2023-09-18 16:57:21,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5091ec6c90582 for 80.00000000 SEI-USDT. +2023-09-18 16:57:21,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a5091ec6c90582", "creation_timestamp": 1695056241.0, "exchange_order_id": "35362001", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:57:21,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5091ec4b40582 for 80.00000000 SEI-USDT. +2023-09-18 16:57:21,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a5091ec4b40582", "creation_timestamp": 1695056241.0, "exchange_order_id": "35362002", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:58:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5091ec4b40582. [clock=2023-09-18 16:58:16+00:00] +2023-09-18 16:58:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5091ec6c90582. [clock=2023-09-18 16:58:16+00:00] +2023-09-18 16:58:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12610000 amount: 80. +2023-09-18 16:58:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265345417720151962959208587 amount: 80. +2023-09-18 16:58:16,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056296.0, "order_id": "x-XEKWYICXBSIUT605a5091ec4b40582", "exchange_order_id": "35362002", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:58:16,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5091ec4b40582. +2023-09-18 16:58:16,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a50c66031d0582 for 80.00000000 SEI-USDT. +2023-09-18 16:58:16,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXBSIUT605a50c66031d0582", "creation_timestamp": 1695056296.0, "exchange_order_id": "35362164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:58:16,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056296.0, "order_id": "x-XEKWYICXSSIUT605a5091ec6c90582", "exchange_order_id": "35362001", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:58:16,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5091ec6c90582. +2023-09-18 16:58:16,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a50c66050f0582 for 80.00000000 SEI-USDT. +2023-09-18 16:58:16,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605a50c66050f0582", "creation_timestamp": 1695056296.0, "exchange_order_id": "35362165", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:58:51,074 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a50c66031d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 16:58:51,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 16:58:51+00:00] +2023-09-18 16:58:51,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056331.0, "order_id": "x-XEKWYICXBSIUT605a50c66031d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12610000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974575", "exchange_order_id": "35362164", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 16:58:51,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056331.0, "order_id": "x-XEKWYICXBSIUT605a50c66031d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35362164", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 16:58:51,107 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a50c66031d0582 completely filled. +2023-09-18 16:59:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a50c66050f0582. [clock=2023-09-18 16:59:11+00:00] +2023-09-18 16:59:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258625358625129073408311060 amount: 80. +2023-09-18 16:59:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261802813191813314581726151 amount: 80. +2023-09-18 16:59:11,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056351.0, "order_id": "x-XEKWYICXSSIUT605a50c66050f0582", "exchange_order_id": "35362165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 16:59:11,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a50c66050f0582. +2023-09-18 16:59:11,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a50fad57910582 for 80.00000000 SEI-USDT. +2023-09-18 16:59:11,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a50fad57910582", "creation_timestamp": 1695056351.0, "exchange_order_id": "35362421", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 16:59:11,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a50fad64750582 for 80.00000000 SEI-USDT. +2023-09-18 16:59:11,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605a50fad64750582", "creation_timestamp": 1695056351.0, "exchange_order_id": "35362422", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:00:06,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a50fad57910582. [clock=2023-09-18 17:00:06+00:00] +2023-09-18 17:00:06,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a50fad64750582. [clock=2023-09-18 17:00:06+00:00] +2023-09-18 17:00:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257462916292152910594291366 amount: 80. +2023-09-18 17:00:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260451854275318566893449908 amount: 80. +2023-09-18 17:00:06,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056406.0, "order_id": "x-XEKWYICXBSIUT605a50fad57910582", "exchange_order_id": "35362421", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:00:06,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a50fad57910582. +2023-09-18 17:00:06,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056406.0, "order_id": "x-XEKWYICXSSIUT605a50fad64750582", "exchange_order_id": "35362422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:00:06,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a50fad64750582. +2023-09-18 17:00:06,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a512f495a80582 for 80.00000000 SEI-USDT. +2023-09-18 17:00:06,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a512f495a80582", "creation_timestamp": 1695056406.0, "exchange_order_id": "35362641", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:00:06,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a512f4936f0582 for 80.00000000 SEI-USDT. +2023-09-18 17:00:06,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a512f4936f0582", "creation_timestamp": 1695056406.0, "exchange_order_id": "35362642", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:00:10,228 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a512f495a80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:00:10,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:00:10+00:00] +2023-09-18 17:00:10,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056410.0, "order_id": "x-XEKWYICXSSIUT605a512f495a80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12600000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01008000"}]}, "exchange_trade_id": "4974604", "exchange_order_id": "35362641", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:00:10,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056410.0, "order_id": "x-XEKWYICXSSIUT605a512f495a80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35362641", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:00:10,267 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a512f495a80582 completely filled. +2023-09-18 17:01:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a512f4936f0582. [clock=2023-09-18 17:01:01+00:00] +2023-09-18 17:01:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259702426603251946218843140 amount: 80. +2023-09-18 17:01:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264026709118211180187880148 amount: 80. +2023-09-18 17:01:01,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056461.0, "order_id": "x-XEKWYICXBSIUT605a512f4936f0582", "exchange_order_id": "35362642", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:01:01,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a512f4936f0582. +2023-09-18 17:01:01,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5163bcbab0582 for 80.00000000 SEI-USDT. +2023-09-18 17:01:01,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a5163bcbab0582", "creation_timestamp": 1695056461.0, "exchange_order_id": "35362937", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:01:01,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5163bc7320582 for 80.00000000 SEI-USDT. +2023-09-18 17:01:01,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605a5163bc7320582", "creation_timestamp": 1695056461.0, "exchange_order_id": "35362938", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:01:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5163bc7320582. [clock=2023-09-18 17:01:56+00:00] +2023-09-18 17:01:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5163bcbab0582. [clock=2023-09-18 17:01:56+00:00] +2023-09-18 17:01:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260988897609887586812241290 amount: 80. +2023-09-18 17:01:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264354379593724908712415145 amount: 80. +2023-09-18 17:01:56,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056516.0, "order_id": "x-XEKWYICXBSIUT605a5163bc7320582", "exchange_order_id": "35362938", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:01:56,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5163bc7320582. +2023-09-18 17:01:56,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056516.0, "order_id": "x-XEKWYICXSSIUT605a5163bcbab0582", "exchange_order_id": "35362937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:01:56,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5163bcbab0582. +2023-09-18 17:01:56,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a51982f6940582 for 80.00000000 SEI-USDT. +2023-09-18 17:01:56,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a51982f6940582", "creation_timestamp": 1695056516.0, "exchange_order_id": "35363222", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:01:56,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a51982f9050582 for 80.00000000 SEI-USDT. +2023-09-18 17:01:56,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a51982f9050582", "creation_timestamp": 1695056516.0, "exchange_order_id": "35363223", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:02:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a51982f6940582. [clock=2023-09-18 17:02:51+00:00] +2023-09-18 17:02:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a51982f9050582. [clock=2023-09-18 17:02:51+00:00] +2023-09-18 17:02:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260811859364027621117345318 amount: 80. +2023-09-18 17:02:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264763863127038415240430391 amount: 80. +2023-09-18 17:02:51,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056571.0, "order_id": "x-XEKWYICXBSIUT605a51982f6940582", "exchange_order_id": "35363222", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:02:51,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a51982f6940582. +2023-09-18 17:02:51,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056571.0, "order_id": "x-XEKWYICXSSIUT605a51982f9050582", "exchange_order_id": "35363223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:02:51,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a51982f9050582. +2023-09-18 17:02:51,343 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a51cca35f10582 for 80.00000000 SEI-USDT. +2023-09-18 17:02:51,354 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a51cca35f10582", "creation_timestamp": 1695056571.0, "exchange_order_id": "35363378", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:02:51,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a51cca335d0582 for 80.00000000 SEI-USDT. +2023-09-18 17:02:51,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a51cca335d0582", "creation_timestamp": 1695056571.0, "exchange_order_id": "35363379", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:03:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a51cca335d0582. [clock=2023-09-18 17:03:46+00:00] +2023-09-18 17:03:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a51cca35f10582. [clock=2023-09-18 17:03:46+00:00] +2023-09-18 17:03:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260717677873110979670338830 amount: 80. +2023-09-18 17:03:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264457948822662634987528240 amount: 80. +2023-09-18 17:03:46,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056626.0, "order_id": "x-XEKWYICXBSIUT605a51cca335d0582", "exchange_order_id": "35363379", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:03:46,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a51cca335d0582. +2023-09-18 17:03:46,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5201164ff0582 for 80.00000000 SEI-USDT. +2023-09-18 17:03:46,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605a5201164ff0582", "creation_timestamp": 1695056626.0, "exchange_order_id": "35363546", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:03:46,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056626.0, "order_id": "x-XEKWYICXSSIUT605a51cca35f10582", "exchange_order_id": "35363378", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:03:46,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a51cca35f10582. +2023-09-18 17:03:46,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5201168400582 for 80.00000000 SEI-USDT. +2023-09-18 17:03:46,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605a5201168400582", "creation_timestamp": 1695056626.0, "exchange_order_id": "35363547", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:04:11,804 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5201164ff0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:04:11,805 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:04:11+00:00] +2023-09-18 17:04:11,825 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056651.0, "order_id": "x-XEKWYICXBSIUT605a5201164ff0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12600000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4974746", "exchange_order_id": "35363546", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:04:11,839 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056651.0, "order_id": "x-XEKWYICXBSIUT605a5201164ff0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35363546", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:04:11,839 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5201164ff0582 completely filled. +2023-09-18 17:04:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5201168400582. [clock=2023-09-18 17:04:41+00:00] +2023-09-18 17:04:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255906621010334173755928716 amount: 80. +2023-09-18 17:04:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260811868523521405795098484 amount: 80. +2023-09-18 17:04:41,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056681.0, "order_id": "x-XEKWYICXSSIUT605a5201168400582", "exchange_order_id": "35363547", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:04:41,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5201168400582. +2023-09-18 17:04:41,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a52358a3630582 for 80.00000000 SEI-USDT. +2023-09-18 17:04:41,327 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a52358a3630582", "creation_timestamp": 1695056681.0, "exchange_order_id": "35364019", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:04:41,330 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a52358a6f00582 for 80.00000000 SEI-USDT. +2023-09-18 17:04:41,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605a52358a6f00582", "creation_timestamp": 1695056681.0, "exchange_order_id": "35364020", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:05:24,500 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a52358a6f00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:05:24,502 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:05:24+00:00] +2023-09-18 17:05:24,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056724.0, "order_id": "x-XEKWYICXSSIUT605a52358a6f00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12600000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01008000"}]}, "exchange_trade_id": "4974785", "exchange_order_id": "35364020", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:05:24,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056724.0, "order_id": "x-XEKWYICXSSIUT605a52358a6f00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35364020", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:05:24,547 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a52358a6f00582 completely filled. +2023-09-18 17:05:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a52358a3630582. [clock=2023-09-18 17:05:36+00:00] +2023-09-18 17:05:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257867045833183485722870538 amount: 80. +2023-09-18 17:05:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263015231738344164107447830 amount: 80. +2023-09-18 17:05:36,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056736.0, "order_id": "x-XEKWYICXBSIUT605a52358a3630582", "exchange_order_id": "35364019", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:05:36,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a52358a3630582. +2023-09-18 17:05:36,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5269fdc840582 for 80.00000000 SEI-USDT. +2023-09-18 17:05:36,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a5269fdc840582", "creation_timestamp": 1695056736.0, "exchange_order_id": "35364232", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:05:36,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5269fdfcb0582 for 80.00000000 SEI-USDT. +2023-09-18 17:05:36,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a5269fdfcb0582", "creation_timestamp": 1695056736.0, "exchange_order_id": "35364233", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:06:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5269fdc840582. [clock=2023-09-18 17:06:31+00:00] +2023-09-18 17:06:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5269fdfcb0582. [clock=2023-09-18 17:06:31+00:00] +2023-09-18 17:06:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257610581259857436873654670 amount: 80. +2023-09-18 17:06:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263466208772521320906523482 amount: 80. +2023-09-18 17:06:31,061 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056791.0, "order_id": "x-XEKWYICXBSIUT605a5269fdc840582", "exchange_order_id": "35364232", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:06:31,061 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5269fdc840582. +2023-09-18 17:06:31,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a529e719050582 for 80.00000000 SEI-USDT. +2023-09-18 17:06:31,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a529e719050582", "creation_timestamp": 1695056791.0, "exchange_order_id": "35364692", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:06:31,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a529e71bf40582 for 80.00000000 SEI-USDT. +2023-09-18 17:06:31,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605a529e71bf40582", "creation_timestamp": 1695056791.0, "exchange_order_id": "35364693", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:06:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056791.0, "order_id": "x-XEKWYICXSSIUT605a5269fdfcb0582", "exchange_order_id": "35364233", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:06:31,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5269fdfcb0582. +2023-09-18 17:07:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a529e719050582. [clock=2023-09-18 17:07:26+00:00] +2023-09-18 17:07:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a529e71bf40582. [clock=2023-09-18 17:07:26+00:00] +2023-09-18 17:07:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257564346858325951023790730 amount: 80. +2023-09-18 17:07:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262787788078291093043470781 amount: 80. +2023-09-18 17:07:26,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056846.0, "order_id": "x-XEKWYICXBSIUT605a529e719050582", "exchange_order_id": "35364692", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:07:26,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a529e719050582. +2023-09-18 17:07:26,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056846.0, "order_id": "x-XEKWYICXSSIUT605a529e71bf40582", "exchange_order_id": "35364693", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:07:26,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a529e71bf40582. +2023-09-18 17:07:26,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a52d2e597a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:07:26,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605a52d2e597a0582", "creation_timestamp": 1695056846.0, "exchange_order_id": "35364795", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:07:26,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a52d2e5c130582 for 80.00000000 SEI-USDT. +2023-09-18 17:07:26,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a52d2e5c130582", "creation_timestamp": 1695056846.0, "exchange_order_id": "35364796", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:08:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a52d2e597a0582. [clock=2023-09-18 17:08:21+00:00] +2023-09-18 17:08:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a52d2e5c130582. [clock=2023-09-18 17:08:21+00:00] +2023-09-18 17:08:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258105673013363422894183623 amount: 80. +2023-09-18 17:08:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262168223401355704143341025 amount: 80. +2023-09-18 17:08:21,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056901.0, "order_id": "x-XEKWYICXBSIUT605a52d2e597a0582", "exchange_order_id": "35364795", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:08:21,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a52d2e597a0582. +2023-09-18 17:08:21,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056901.0, "order_id": "x-XEKWYICXSSIUT605a52d2e5c130582", "exchange_order_id": "35364796", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:08:21,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a52d2e5c130582. +2023-09-18 17:08:21,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a53075960e0582 for 80.00000000 SEI-USDT. +2023-09-18 17:08:21,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605a53075960e0582", "creation_timestamp": 1695056901.0, "exchange_order_id": "35365194", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:08:21,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a530759b1a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:08:21,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605a530759b1a0582", "creation_timestamp": 1695056901.0, "exchange_order_id": "35365195", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:09:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a53075960e0582. [clock=2023-09-18 17:09:16+00:00] +2023-09-18 17:09:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a530759b1a0582. [clock=2023-09-18 17:09:16+00:00] +2023-09-18 17:09:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255991147965160384926724835 amount: 80. +2023-09-18 17:09:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259813897431257411750340172 amount: 80. +2023-09-18 17:09:16,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056956.0, "order_id": "x-XEKWYICXBSIUT605a53075960e0582", "exchange_order_id": "35365194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:09:16,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a53075960e0582. +2023-09-18 17:09:16,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a533bccde20582 for 80.00000000 SEI-USDT. +2023-09-18 17:09:16,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605a533bccde20582", "creation_timestamp": 1695056956.0, "exchange_order_id": "35365442", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:09:16,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a533bcd17d0582 for 80.00000000 SEI-USDT. +2023-09-18 17:09:16,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a533bcd17d0582", "creation_timestamp": 1695056956.0, "exchange_order_id": "35365443", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:09:16,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695056956.0, "order_id": "x-XEKWYICXSSIUT605a530759b1a0582", "exchange_order_id": "35365195", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:09:16,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a530759b1a0582. +2023-09-18 17:10:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a533bccde20582. [clock=2023-09-18 17:10:11+00:00] +2023-09-18 17:10:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a533bcd17d0582. [clock=2023-09-18 17:10:11+00:00] +2023-09-18 17:10:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250276892984761500760333132 amount: 80. +2023-09-18 17:10:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12560000 amount: 80. +2023-09-18 17:10:11,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057011.0, "order_id": "x-XEKWYICXBSIUT605a533bccde20582", "exchange_order_id": "35365442", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:10:11,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a533bccde20582. +2023-09-18 17:10:11,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057011.0, "order_id": "x-XEKWYICXSSIUT605a533bcd17d0582", "exchange_order_id": "35365443", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:10:11,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a533bcd17d0582. +2023-09-18 17:10:11,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a537040ff00582 for 80.00000000 SEI-USDT. +2023-09-18 17:10:11,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a537040ff00582", "creation_timestamp": 1695057011.0, "exchange_order_id": "35366166", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:10:11,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a537040d750582 for 80.00000000 SEI-USDT. +2023-09-18 17:10:11,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a537040d750582", "creation_timestamp": 1695057011.0, "exchange_order_id": "35366167", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:11:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a537040d750582. [clock=2023-09-18 17:11:06+00:00] +2023-09-18 17:11:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a537040ff00582. [clock=2023-09-18 17:11:06+00:00] +2023-09-18 17:11:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249355695279060500224052379 amount: 80. +2023-09-18 17:11:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12550000 amount: 80. +2023-09-18 17:11:06,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "order_id": "x-XEKWYICXBSIUT605a537040d750582", "exchange_order_id": "35366167", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:11:06,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a537040d750582. +2023-09-18 17:11:06,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "order_id": "x-XEKWYICXSSIUT605a537040ff00582", "exchange_order_id": "35366166", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:11:06,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a537040ff00582. +2023-09-18 17:11:06,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a53a4b4cf80582 for 80.00000000 SEI-USDT. +2023-09-18 17:11:06,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a53a4b4cf80582", "creation_timestamp": 1695057066.0, "exchange_order_id": "35366439", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:11:06,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a53a4b49de0582 for 80.00000000 SEI-USDT. +2023-09-18 17:11:06,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a53a4b49de0582", "creation_timestamp": 1695057066.0, "exchange_order_id": "35366438", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:11:06,339 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a53a4b4cf80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:11:06,340 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:11:06+00:00] +2023-09-18 17:11:06,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "order_id": "x-XEKWYICXSSIUT605a53a4b4cf80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004000"}]}, "exchange_trade_id": "4974947", "exchange_order_id": "35366439", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:11:06,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057066.0, "order_id": "x-XEKWYICXSSIUT605a53a4b4cf80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35366439", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:11:06,373 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a53a4b4cf80582 completely filled. +2023-09-18 17:12:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a53a4b49de0582. [clock=2023-09-18 17:12:01+00:00] +2023-09-18 17:12:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248750223190300002149452984 amount: 80. +2023-09-18 17:12:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255037730058900202391523848 amount: 80. +2023-09-18 17:12:01,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057121.0, "order_id": "x-XEKWYICXBSIUT605a53a4b49de0582", "exchange_order_id": "35366438", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:12:01,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a53a4b49de0582. +2023-09-18 17:12:01,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a53d9283790582 for 80.00000000 SEI-USDT. +2023-09-18 17:12:01,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a53d9283790582", "creation_timestamp": 1695057121.0, "exchange_order_id": "35366716", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:12:01,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a53d92877a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:12:01,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a53d92877a0582", "creation_timestamp": 1695057121.0, "exchange_order_id": "35366718", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:12:15,223 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a53d92877a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:12:15,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:12:15+00:00] +2023-09-18 17:12:15,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057135.0, "order_id": "x-XEKWYICXSSIUT605a53d92877a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004000"}]}, "exchange_trade_id": "4974986", "exchange_order_id": "35366718", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:12:15,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057135.0, "order_id": "x-XEKWYICXSSIUT605a53d92877a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35366718", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:12:15,259 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a53d92877a0582 completely filled. +2023-09-18 17:12:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a53d9283790582. [clock=2023-09-18 17:12:56+00:00] +2023-09-18 17:12:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252725147344339913846454046 amount: 80. +2023-09-18 17:12:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258292580414260778962076752 amount: 80. +2023-09-18 17:12:56,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057176.0, "order_id": "x-XEKWYICXBSIUT605a53d9283790582", "exchange_order_id": "35366716", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:12:56,057 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a53d9283790582. +2023-09-18 17:12:56,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a540d9c00a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:12:56,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a540d9c00a0582", "creation_timestamp": 1695057176.0, "exchange_order_id": "35366872", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:12:56,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a540d9bce70582 for 80.00000000 SEI-USDT. +2023-09-18 17:12:56,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a540d9bce70582", "creation_timestamp": 1695057176.0, "exchange_order_id": "35366873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:13:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a540d9bce70582. [clock=2023-09-18 17:13:51+00:00] +2023-09-18 17:13:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a540d9c00a0582. [clock=2023-09-18 17:13:51+00:00] +2023-09-18 17:13:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252957662981875668418798677 amount: 80. +2023-09-18 17:13:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257954615829034523021030535 amount: 80. +2023-09-18 17:13:51,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057231.0, "order_id": "x-XEKWYICXBSIUT605a540d9bce70582", "exchange_order_id": "35366873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:13:51,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a540d9bce70582. +2023-09-18 17:13:51,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057231.0, "order_id": "x-XEKWYICXSSIUT605a540d9c00a0582", "exchange_order_id": "35366872", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:13:51,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a540d9c00a0582. +2023-09-18 17:13:51,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5442100b70582 for 80.00000000 SEI-USDT. +2023-09-18 17:13:51,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a5442100b70582", "creation_timestamp": 1695057231.0, "exchange_order_id": "35366946", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:13:51,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a54420fd1b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:13:51,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a54420fd1b0582", "creation_timestamp": 1695057231.0, "exchange_order_id": "35366947", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:14:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a54420fd1b0582. [clock=2023-09-18 17:14:46+00:00] +2023-09-18 17:14:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5442100b70582. [clock=2023-09-18 17:14:46+00:00] +2023-09-18 17:14:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254349645293455061152308352 amount: 80. +2023-09-18 17:14:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259423516287393029961161770 amount: 80. +2023-09-18 17:14:46,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057286.0, "order_id": "x-XEKWYICXBSIUT605a54420fd1b0582", "exchange_order_id": "35366947", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:14:46,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a54420fd1b0582. +2023-09-18 17:14:46,329 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057286.0, "order_id": "x-XEKWYICXSSIUT605a5442100b70582", "exchange_order_id": "35366946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:14:46,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5442100b70582. +2023-09-18 17:14:46,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5476834810582 for 80.00000000 SEI-USDT. +2023-09-18 17:14:46,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605a5476834810582", "creation_timestamp": 1695057286.0, "exchange_order_id": "35367286", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:14:46,343 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a54768329f0582 for 80.00000000 SEI-USDT. +2023-09-18 17:14:46,352 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605a54768329f0582", "creation_timestamp": 1695057286.0, "exchange_order_id": "35367287", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:15:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a54768329f0582. [clock=2023-09-18 17:15:41+00:00] +2023-09-18 17:15:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5476834810582. [clock=2023-09-18 17:15:41+00:00] +2023-09-18 17:15:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253302704334741779100743737 amount: 80. +2023-09-18 17:15:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258430906072854335130632867 amount: 80. +2023-09-18 17:15:41,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057341.0, "order_id": "x-XEKWYICXBSIUT605a54768329f0582", "exchange_order_id": "35367287", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:15:41,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a54768329f0582. +2023-09-18 17:15:41,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057341.0, "order_id": "x-XEKWYICXSSIUT605a5476834810582", "exchange_order_id": "35367286", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:15:41,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5476834810582. +2023-09-18 17:15:41,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a54aaf75780582 for 80.00000000 SEI-USDT. +2023-09-18 17:15:41,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a54aaf75780582", "creation_timestamp": 1695057341.0, "exchange_order_id": "35367905", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:15:41,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a54aaf77750582 for 80.00000000 SEI-USDT. +2023-09-18 17:15:41,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a54aaf77750582", "creation_timestamp": 1695057341.0, "exchange_order_id": "35367906", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:16:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a54aaf75780582. [clock=2023-09-18 17:16:36+00:00] +2023-09-18 17:16:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a54aaf77750582. [clock=2023-09-18 17:16:36+00:00] +2023-09-18 17:16:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253417674996395639868885262 amount: 80. +2023-09-18 17:16:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258591904288145975305207420 amount: 80. +2023-09-18 17:16:36,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057396.0, "order_id": "x-XEKWYICXBSIUT605a54aaf75780582", "exchange_order_id": "35367905", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:16:36,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a54aaf75780582. +2023-09-18 17:16:36,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057396.0, "order_id": "x-XEKWYICXSSIUT605a54aaf77750582", "exchange_order_id": "35367906", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:16:36,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a54aaf77750582. +2023-09-18 17:16:36,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a54df6ae670582 for 80.00000000 SEI-USDT. +2023-09-18 17:16:36,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a54df6ae670582", "creation_timestamp": 1695057396.0, "exchange_order_id": "35368184", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:16:36,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a54df6b1fc0582 for 80.00000000 SEI-USDT. +2023-09-18 17:16:36,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a54df6b1fc0582", "creation_timestamp": 1695057396.0, "exchange_order_id": "35368185", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:17:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a54df6ae670582. [clock=2023-09-18 17:17:31+00:00] +2023-09-18 17:17:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a54df6b1fc0582. [clock=2023-09-18 17:17:31+00:00] +2023-09-18 17:17:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252070816669797742906448674 amount: 80. +2023-09-18 17:17:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257279037674357543041666719 amount: 80. +2023-09-18 17:17:31,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057451.0, "order_id": "x-XEKWYICXBSIUT605a54df6ae670582", "exchange_order_id": "35368184", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:17:31,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a54df6ae670582. +2023-09-18 17:17:31,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057451.0, "order_id": "x-XEKWYICXSSIUT605a54df6b1fc0582", "exchange_order_id": "35368185", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:17:31,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a54df6b1fc0582. +2023-09-18 17:17:31,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5513deb290582 for 80.00000000 SEI-USDT. +2023-09-18 17:17:31,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a5513deb290582", "creation_timestamp": 1695057451.0, "exchange_order_id": "35368401", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:17:31,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5513de75b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:17:31,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a5513de75b0582", "creation_timestamp": 1695057451.0, "exchange_order_id": "35368402", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:18:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5513de75b0582. [clock=2023-09-18 17:18:26+00:00] +2023-09-18 17:18:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5513deb290582. [clock=2023-09-18 17:18:26+00:00] +2023-09-18 17:18:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249545951668632814211452087 amount: 80. +2023-09-18 17:18:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254260375951100311186726261 amount: 80. +2023-09-18 17:18:26,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057506.0, "order_id": "x-XEKWYICXBSIUT605a5513de75b0582", "exchange_order_id": "35368402", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:18:26,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5513de75b0582. +2023-09-18 17:18:26,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057506.0, "order_id": "x-XEKWYICXSSIUT605a5513deb290582", "exchange_order_id": "35368401", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:18:26,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5513deb290582. +2023-09-18 17:18:26,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a554852b800582 for 80.00000000 SEI-USDT. +2023-09-18 17:18:26,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a554852b800582", "creation_timestamp": 1695057506.0, "exchange_order_id": "35368812", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:18:26,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a554852ecc0582 for 80.00000000 SEI-USDT. +2023-09-18 17:18:26,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a554852ecc0582", "creation_timestamp": 1695057506.0, "exchange_order_id": "35368813", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:19:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a554852b800582. [clock=2023-09-18 17:19:21+00:00] +2023-09-18 17:19:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a554852ecc0582. [clock=2023-09-18 17:19:21+00:00] +2023-09-18 17:19:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250314091972527638619939152 amount: 80. +2023-09-18 17:19:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12540000 amount: 80. +2023-09-18 17:19:21,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057561.0, "order_id": "x-XEKWYICXBSIUT605a554852b800582", "exchange_order_id": "35368812", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:19:21,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a554852b800582. +2023-09-18 17:19:21,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057561.0, "order_id": "x-XEKWYICXSSIUT605a554852ecc0582", "exchange_order_id": "35368813", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:19:21,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a554852ecc0582. +2023-09-18 17:19:21,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a557cc63f50582 for 80.00000000 SEI-USDT. +2023-09-18 17:19:21,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a557cc63f50582", "creation_timestamp": 1695057561.0, "exchange_order_id": "35369135", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:19:21,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a557cc677b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:19:21,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a557cc677b0582", "creation_timestamp": 1695057561.0, "exchange_order_id": "35369136", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:20:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a557cc63f50582. [clock=2023-09-18 17:20:16+00:00] +2023-09-18 17:20:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a557cc677b0582. [clock=2023-09-18 17:20:16+00:00] +2023-09-18 17:20:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249531492804534138358799061 amount: 80. +2023-09-18 17:20:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12530000 amount: 80. +2023-09-18 17:20:16,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057616.0, "order_id": "x-XEKWYICXBSIUT605a557cc63f50582", "exchange_order_id": "35369135", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:20:16,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a557cc63f50582. +2023-09-18 17:20:16,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057616.0, "order_id": "x-XEKWYICXSSIUT605a557cc677b0582", "exchange_order_id": "35369136", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:20:16,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a557cc677b0582. +2023-09-18 17:20:16,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a55b13a0100582 for 80.00000000 SEI-USDT. +2023-09-18 17:20:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a55b13a0100582", "creation_timestamp": 1695057616.0, "exchange_order_id": "35369881", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:20:16,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a55b13a20f0582 for 80.00000000 SEI-USDT. +2023-09-18 17:20:16,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a55b13a20f0582", "creation_timestamp": 1695057616.0, "exchange_order_id": "35369882", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:21:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a55b13a0100582. [clock=2023-09-18 17:21:11+00:00] +2023-09-18 17:21:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a55b13a20f0582. [clock=2023-09-18 17:21:11+00:00] +2023-09-18 17:21:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247833742613259067345801980 amount: 80. +2023-09-18 17:21:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252157379834170620881950575 amount: 80. +2023-09-18 17:21:11,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057671.0, "order_id": "x-XEKWYICXBSIUT605a55b13a0100582", "exchange_order_id": "35369881", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:21:11,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a55b13a0100582. +2023-09-18 17:21:11,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057671.0, "order_id": "x-XEKWYICXSSIUT605a55b13a20f0582", "exchange_order_id": "35369882", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:21:11,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a55b13a20f0582. +2023-09-18 17:21:11,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a55e5adc9c0582 for 80.00000000 SEI-USDT. +2023-09-18 17:21:11,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a55e5adc9c0582", "creation_timestamp": 1695057671.0, "exchange_order_id": "35370587", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:21:11,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a55e5ae0020582 for 80.00000000 SEI-USDT. +2023-09-18 17:21:11,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a55e5ae0020582", "creation_timestamp": 1695057671.0, "exchange_order_id": "35370588", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:21:38,295 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a55e5ae0020582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:21:38,296 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 17:21:38+00:00] +2023-09-18 17:21:38,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057698.0, "order_id": "x-XEKWYICXSSIUT605a55e5ae0020582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4975321", "exchange_order_id": "35370588", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:21:38,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057698.0, "order_id": "x-XEKWYICXSSIUT605a55e5ae0020582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35370588", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:21:38,332 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a55e5ae0020582 completely filled. +2023-09-18 17:22:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a55e5adc9c0582. [clock=2023-09-18 17:22:06+00:00] +2023-09-18 17:22:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249781637228244210315434507 amount: 80. +2023-09-18 17:22:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254184145267412248318788333 amount: 80. +2023-09-18 17:22:06,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057726.0, "order_id": "x-XEKWYICXBSIUT605a55e5adc9c0582", "exchange_order_id": "35370587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:22:06,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a55e5adc9c0582. +2023-09-18 17:22:06,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a561a213330582 for 80.00000000 SEI-USDT. +2023-09-18 17:22:06,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a561a213330582", "creation_timestamp": 1695057726.0, "exchange_order_id": "35370931", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:22:06,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a561a2160b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:22:06,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a561a2160b0582", "creation_timestamp": 1695057726.0, "exchange_order_id": "35370932", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:23:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a561a213330582. [clock=2023-09-18 17:23:01+00:00] +2023-09-18 17:23:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a561a2160b0582. [clock=2023-09-18 17:23:01+00:00] +2023-09-18 17:23:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246385080590981005474638190 amount: 80. +2023-09-18 17:23:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:23:01,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057781.0, "order_id": "x-XEKWYICXBSIUT605a561a213330582", "exchange_order_id": "35370931", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:23:01,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a561a213330582. +2023-09-18 17:23:01,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057781.0, "order_id": "x-XEKWYICXSSIUT605a561a2160b0582", "exchange_order_id": "35370932", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:23:01,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a561a2160b0582. +2023-09-18 17:23:01,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a564e952ec0582 for 80.00000000 SEI-USDT. +2023-09-18 17:23:01,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a564e952ec0582", "creation_timestamp": 1695057781.0, "exchange_order_id": "35371409", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:23:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a564e952ec0582. [clock=2023-09-18 17:23:56+00:00] +2023-09-18 17:23:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245769001606948926936868660 amount: 80. +2023-09-18 17:23:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250894180787732412750955202 amount: 80. +2023-09-18 17:23:56,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057836.0, "order_id": "x-XEKWYICXBSIUT605a564e952ec0582", "exchange_order_id": "35371409", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:23:56,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a564e952ec0582. +2023-09-18 17:23:56,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a568308f5b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:23:56,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a568308f5b0582", "creation_timestamp": 1695057836.0, "exchange_order_id": "35371784", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:23:56,330 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a568308b980582 for 80.00000000 SEI-USDT. +2023-09-18 17:23:56,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a568308b980582", "creation_timestamp": 1695057836.0, "exchange_order_id": "35371785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:24:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a568308b980582. [clock=2023-09-18 17:24:51+00:00] +2023-09-18 17:24:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a568308f5b0582. [clock=2023-09-18 17:24:51+00:00] +2023-09-18 17:24:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245069925666701666196374646 amount: 80. +2023-09-18 17:24:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:24:51,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057891.0, "order_id": "x-XEKWYICXBSIUT605a568308b980582", "exchange_order_id": "35371785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:24:51,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a568308b980582. +2023-09-18 17:24:51,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057891.0, "order_id": "x-XEKWYICXSSIUT605a568308f5b0582", "exchange_order_id": "35371784", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:24:51,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a568308f5b0582. +2023-09-18 17:24:51,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a56b77d1b10582 for 80.00000000 SEI-USDT. +2023-09-18 17:24:51,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a56b77d1b10582", "creation_timestamp": 1695057891.0, "exchange_order_id": "35372173", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:25:15,014 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 17:25:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a56b77d1b10582. [clock=2023-09-18 17:25:46+00:00] +2023-09-18 17:25:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243646121666275585330718045 amount: 80. +2023-09-18 17:25:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248967461233042904263817325 amount: 80. +2023-09-18 17:25:46,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057946.0, "order_id": "x-XEKWYICXBSIUT605a56b77d1b10582", "exchange_order_id": "35372173", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:25:46,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a56b77d1b10582. +2023-09-18 17:25:46,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a56ebf01560582 for 80.00000000 SEI-USDT. +2023-09-18 17:25:46,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a56ebf01560582", "creation_timestamp": 1695057946.0, "exchange_order_id": "35373036", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:25:46,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a56ebf03ae0582 for 80.00000000 SEI-USDT. +2023-09-18 17:25:46,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a56ebf03ae0582", "creation_timestamp": 1695057946.0, "exchange_order_id": "35373037", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:26:29,470 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a56ebf03ae0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:26:29,471 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:26:29+00:00] +2023-09-18 17:26:29,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057989.0, "order_id": "x-XEKWYICXSSIUT605a56ebf03ae0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4975489", "exchange_order_id": "35373037", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:26:29,508 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695057989.0, "order_id": "x-XEKWYICXSSIUT605a56ebf03ae0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35373037", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:26:29,509 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a56ebf03ae0582 completely filled. +2023-09-18 17:26:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a56ebf01560582. [clock=2023-09-18 17:26:41+00:00] +2023-09-18 17:26:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247072092660794004852473963 amount: 80. +2023-09-18 17:26:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:26:41,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058001.0, "order_id": "x-XEKWYICXBSIUT605a56ebf01560582", "exchange_order_id": "35373036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:26:41,089 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a56ebf01560582. +2023-09-18 17:26:41,348 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a572063ee10582 for 80.00000000 SEI-USDT. +2023-09-18 17:26:41,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a572063ee10582", "creation_timestamp": 1695058001.0, "exchange_order_id": "35373478", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:26:56,202 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a572063ee10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:26:56,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:26:56+00:00] +2023-09-18 17:26:56,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058016.0, "order_id": "x-XEKWYICXBSIUT605a572063ee10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4975553", "exchange_order_id": "35373478", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:26:56,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058016.0, "order_id": "x-XEKWYICXBSIUT605a572063ee10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35373478", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:26:56,242 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a572063ee10582 completely filled. +2023-09-18 17:27:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237043865096043854209326318 amount: 80. +2023-09-18 17:27:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246485169357128371616419926 amount: 80. +2023-09-18 17:27:36,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5754d76c30582 for 80.00000000 SEI-USDT. +2023-09-18 17:27:36,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a5754d76c30582", "creation_timestamp": 1695058056.0, "exchange_order_id": "35374705", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:27:36,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5754d7e960582 for 80.00000000 SEI-USDT. +2023-09-18 17:27:36,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605a5754d7e960582", "creation_timestamp": 1695058056.0, "exchange_order_id": "35374706", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:28:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5754d76c30582. [clock=2023-09-18 17:28:31+00:00] +2023-09-18 17:28:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5754d7e960582. [clock=2023-09-18 17:28:31+00:00] +2023-09-18 17:28:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234279331555426317369773616 amount: 80. +2023-09-18 17:28:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:28:31,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058111.0, "order_id": "x-XEKWYICXBSIUT605a5754d76c30582", "exchange_order_id": "35374705", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:28:31,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5754d76c30582. +2023-09-18 17:28:31,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a57894baa50582 for 80.00000000 SEI-USDT. +2023-09-18 17:28:31,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a57894baa50582", "creation_timestamp": 1695058111.0, "exchange_order_id": "35376469", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:28:31,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058111.0, "order_id": "x-XEKWYICXSSIUT605a5754d7e960582", "exchange_order_id": "35374706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:28:31,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5754d7e960582. +2023-09-18 17:29:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a57894baa50582. [clock=2023-09-18 17:29:26+00:00] +2023-09-18 17:29:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234936309223942402553158893 amount: 80. +2023-09-18 17:29:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244557238584227011948735005 amount: 80. +2023-09-18 17:29:26,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058166.0, "order_id": "x-XEKWYICXBSIUT605a57894baa50582", "exchange_order_id": "35376469", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:29:26,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a57894baa50582. +2023-09-18 17:29:26,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a57bdc03300582 for 80.00000000 SEI-USDT. +2023-09-18 17:29:26,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a57bdc03300582", "creation_timestamp": 1695058166.0, "exchange_order_id": "35377459", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:29:26,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a57bdbff490582 for 80.00000000 SEI-USDT. +2023-09-18 17:29:26,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a57bdbff490582", "creation_timestamp": 1695058166.0, "exchange_order_id": "35377460", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:30:21,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a57bdbff490582. [clock=2023-09-18 17:30:21+00:00] +2023-09-18 17:30:21,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a57bdc03300582. [clock=2023-09-18 17:30:21+00:00] +2023-09-18 17:30:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236441186557525105189754451 amount: 80. +2023-09-18 17:30:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:30:21,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058221.0, "order_id": "x-XEKWYICXBSIUT605a57bdbff490582", "exchange_order_id": "35377460", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:30:21,303 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a57bdbff490582. +2023-09-18 17:30:21,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a57f2344d80582 for 80.00000000 SEI-USDT. +2023-09-18 17:30:21,374 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a57f2344d80582", "creation_timestamp": 1695058221.0, "exchange_order_id": "35378116", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:30:21,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058221.0, "order_id": "x-XEKWYICXSSIUT605a57bdc03300582", "exchange_order_id": "35377459", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:30:21,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a57bdc03300582. +2023-09-18 17:31:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a57f2344d80582. [clock=2023-09-18 17:31:16+00:00] +2023-09-18 17:31:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231446127412295681954435569 amount: 80. +2023-09-18 17:31:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241841153278579645042559567 amount: 80. +2023-09-18 17:31:16,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058276.0, "order_id": "x-XEKWYICXBSIUT605a57f2344d80582", "exchange_order_id": "35378116", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:31:16,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a57f2344d80582. +2023-09-18 17:31:16,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5826a6b650582 for 80.00000000 SEI-USDT. +2023-09-18 17:31:16,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a5826a6b650582", "creation_timestamp": 1695058276.0, "exchange_order_id": "35379280", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:31:16,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5826a68120582 for 80.00000000 SEI-USDT. +2023-09-18 17:31:16,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a5826a68120582", "creation_timestamp": 1695058276.0, "exchange_order_id": "35379279", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:32:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5826a68120582. [clock=2023-09-18 17:32:11+00:00] +2023-09-18 17:32:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5826a6b650582. [clock=2023-09-18 17:32:11+00:00] +2023-09-18 17:32:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227597111920322028552819524 amount: 80. +2023-09-18 17:32:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:32:11,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058331.0, "order_id": "x-XEKWYICXBSIUT605a5826a68120582", "exchange_order_id": "35379279", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:32:11,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5826a68120582. +2023-09-18 17:32:11,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058331.0, "order_id": "x-XEKWYICXSSIUT605a5826a6b650582", "exchange_order_id": "35379280", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:32:11,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5826a6b650582. +2023-09-18 17:32:11,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a585b1a9fa0582 for 80.00000000 SEI-USDT. +2023-09-18 17:32:11,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605a585b1a9fa0582", "creation_timestamp": 1695058331.0, "exchange_order_id": "35380302", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:33:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a585b1a9fa0582. [clock=2023-09-18 17:33:06+00:00] +2023-09-18 17:33:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227952484797200876104803081 amount: 80. +2023-09-18 17:33:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237557280897143149501084819 amount: 80. +2023-09-18 17:33:06,061 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058386.0, "order_id": "x-XEKWYICXBSIUT605a585b1a9fa0582", "exchange_order_id": "35380302", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:33:06,061 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a585b1a9fa0582. +2023-09-18 17:33:06,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a588f8e2fc0582 for 80.00000000 SEI-USDT. +2023-09-18 17:33:06,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605a588f8e2fc0582", "creation_timestamp": 1695058386.0, "exchange_order_id": "35381079", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:33:06,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a588f8e5cf0582 for 80.00000000 SEI-USDT. +2023-09-18 17:33:06,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a588f8e5cf0582", "creation_timestamp": 1695058386.0, "exchange_order_id": "35381080", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:34:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a588f8e2fc0582. [clock=2023-09-18 17:34:01+00:00] +2023-09-18 17:34:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a588f8e5cf0582. [clock=2023-09-18 17:34:01+00:00] +2023-09-18 17:34:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226107474887614119004268882 amount: 80. +2023-09-18 17:34:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:34:01,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058441.0, "order_id": "x-XEKWYICXBSIUT605a588f8e2fc0582", "exchange_order_id": "35381079", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:34:01,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a588f8e2fc0582. +2023-09-18 17:34:01,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058441.0, "order_id": "x-XEKWYICXSSIUT605a588f8e5cf0582", "exchange_order_id": "35381080", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:34:01,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a588f8e5cf0582. +2023-09-18 17:34:01,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a58c4020630582 for 80.00000000 SEI-USDT. +2023-09-18 17:34:01,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605a58c4020630582", "creation_timestamp": 1695058441.0, "exchange_order_id": "35381873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:34:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a58c4020630582. [clock=2023-09-18 17:34:56+00:00] +2023-09-18 17:34:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1224542055144194167322105039 amount: 80. +2023-09-18 17:34:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236381627464763006900406295 amount: 80. +2023-09-18 17:34:56,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058496.0, "order_id": "x-XEKWYICXBSIUT605a58c4020630582", "exchange_order_id": "35381873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:34:56,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a58c4020630582. +2023-09-18 17:34:56,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a58f8757bb0582 for 80.00000000 SEI-USDT. +2023-09-18 17:34:56,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605a58f8757bb0582", "creation_timestamp": 1695058496.0, "exchange_order_id": "35383116", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:34:56,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a58f8755090582 for 80.00000000 SEI-USDT. +2023-09-18 17:34:56,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605a58f8755090582", "creation_timestamp": 1695058496.0, "exchange_order_id": "35383117", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:35:25,033 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a58f8757bb0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:35:25,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:35:25+00:00] +2023-09-18 17:35:25,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058525.0, "order_id": "x-XEKWYICXSSIUT605a58f8757bb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12360000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00988800"}]}, "exchange_trade_id": "4976609", "exchange_order_id": "35383116", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:35:25,069 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058525.0, "order_id": "x-XEKWYICXSSIUT605a58f8757bb0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35383116", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:35:25,070 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a58f8757bb0582 completely filled. +2023-09-18 17:35:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a58f8755090582. [clock=2023-09-18 17:35:51+00:00] +2023-09-18 17:35:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226356995117033071160085942 amount: 80. +2023-09-18 17:35:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:35:51,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058551.0, "order_id": "x-XEKWYICXBSIUT605a58f8755090582", "exchange_order_id": "35383117", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:35:51,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a58f8755090582. +2023-09-18 17:35:51,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a592ce96de0582 for 80.00000000 SEI-USDT. +2023-09-18 17:35:51,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605a592ce96de0582", "creation_timestamp": 1695058551.0, "exchange_order_id": "35384177", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:36:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a592ce96de0582. [clock=2023-09-18 17:36:46+00:00] +2023-09-18 17:36:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1224416677547275533519672016 amount: 80. +2023-09-18 17:36:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:36:46,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058606.0, "order_id": "x-XEKWYICXBSIUT605a592ce96de0582", "exchange_order_id": "35384177", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:36:46,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a592ce96de0582. +2023-09-18 17:36:46,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a59615cd860582 for 80.00000000 SEI-USDT. +2023-09-18 17:36:46,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605a59615cd860582", "creation_timestamp": 1695058606.0, "exchange_order_id": "35385013", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:37:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a59615cd860582. [clock=2023-09-18 17:37:41+00:00] +2023-09-18 17:37:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233577292460511716346896640 amount: 80. +2023-09-18 17:37:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:37:41,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058661.0, "order_id": "x-XEKWYICXBSIUT605a59615cd860582", "exchange_order_id": "35385013", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:37:41,090 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a59615cd860582. +2023-09-18 17:37:41,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5995d0ccc0582 for 80.00000000 SEI-USDT. +2023-09-18 17:37:41,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a5995d0ccc0582", "creation_timestamp": 1695058661.0, "exchange_order_id": "35385887", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:38:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5995d0ccc0582. [clock=2023-09-18 17:38:36+00:00] +2023-09-18 17:38:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238263738028323952085182944 amount: 80. +2023-09-18 17:38:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:38:36,089 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058716.0, "order_id": "x-XEKWYICXBSIUT605a5995d0ccc0582", "exchange_order_id": "35385887", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:38:36,089 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5995d0ccc0582. +2023-09-18 17:38:36,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a59ca4466a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:38:36,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a59ca4466a0582", "creation_timestamp": 1695058716.0, "exchange_order_id": "35387005", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:39:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a59ca4466a0582. [clock=2023-09-18 17:39:31+00:00] +2023-09-18 17:39:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 17:39:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:39:31,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058771.0, "order_id": "x-XEKWYICXBSIUT605a59ca4466a0582", "exchange_order_id": "35387005", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:39:31,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a59ca4466a0582. +2023-09-18 17:39:31,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a59feb7f970582 for 80.00000000 SEI-USDT. +2023-09-18 17:39:31,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a59feb7f970582", "creation_timestamp": 1695058771.0, "exchange_order_id": "35387894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:39:41,421 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a59feb7f970582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:39:41,422 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:39:41+00:00] +2023-09-18 17:39:41,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058781.0, "order_id": "x-XEKWYICXBSIUT605a59feb7f970582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4976931", "exchange_order_id": "35387894", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:39:41,459 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058781.0, "order_id": "x-XEKWYICXBSIUT605a59feb7f970582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35387894", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:39:41,459 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a59feb7f970582 completely filled. +2023-09-18 17:40:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238842683047513119482727405 amount: 80. +2023-09-18 17:40:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249175240988527972905650031 amount: 80. +2023-09-18 17:40:26,078 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5a332b92a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:40:26,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a5a332b92a0582", "creation_timestamp": 1695058826.0, "exchange_order_id": "35388355", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:40:26,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5a332bd5e0582 for 80.00000000 SEI-USDT. +2023-09-18 17:40:26,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a5a332bd5e0582", "creation_timestamp": 1695058826.0, "exchange_order_id": "35388357", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:41:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5a332b92a0582. [clock=2023-09-18 17:41:21+00:00] +2023-09-18 17:41:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5a332bd5e0582. [clock=2023-09-18 17:41:21+00:00] +2023-09-18 17:41:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240551695634729048718838899 amount: 80. +2023-09-18 17:41:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 17:41:21,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058881.0, "order_id": "x-XEKWYICXBSIUT605a5a332b92a0582", "exchange_order_id": "35388355", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:41:21,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5a332b92a0582. +2023-09-18 17:41:21,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5a679fd240582 for 80.00000000 SEI-USDT. +2023-09-18 17:41:21,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5a679fd240582", "creation_timestamp": 1695058881.0, "exchange_order_id": "35388912", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:41:21,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058881.0, "order_id": "x-XEKWYICXSSIUT605a5a332bd5e0582", "exchange_order_id": "35388357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:41:21,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5a332bd5e0582. +2023-09-18 17:41:45,130 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5a679fd240582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:41:45,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:41:45+00:00] +2023-09-18 17:41:45,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058905.0, "order_id": "x-XEKWYICXBSIUT605a5a679fd240582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4977039", "exchange_order_id": "35388912", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:41:45,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058905.0, "order_id": "x-XEKWYICXBSIUT605a5a679fd240582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35388912", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:41:45,186 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5a679fd240582 completely filled. +2023-09-18 17:42:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239510090293196297412533413 amount: 80. +2023-09-18 17:42:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247311186668565285822014445 amount: 80. +2023-09-18 17:42:16,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5a9c143ea0582 for 80.00000000 SEI-USDT. +2023-09-18 17:42:16,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a5a9c143ea0582", "creation_timestamp": 1695058936.0, "exchange_order_id": "35389537", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:42:16,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5a9c147ec0582 for 80.00000000 SEI-USDT. +2023-09-18 17:42:16,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a5a9c147ec0582", "creation_timestamp": 1695058936.0, "exchange_order_id": "35389538", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:43:11,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5a9c143ea0582. [clock=2023-09-18 17:43:11+00:00] +2023-09-18 17:43:11,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5a9c147ec0582. [clock=2023-09-18 17:43:11+00:00] +2023-09-18 17:43:11,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241946421610044439438881664 amount: 80. +2023-09-18 17:43:11,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249723578993268388380479494 amount: 80. +2023-09-18 17:43:11,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058991.0, "order_id": "x-XEKWYICXBSIUT605a5a9c143ea0582", "exchange_order_id": "35389537", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:43:11,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5a9c143ea0582. +2023-09-18 17:43:11,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058991.0, "order_id": "x-XEKWYICXSSIUT605a5a9c147ec0582", "exchange_order_id": "35389538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:43:11,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5a9c147ec0582. +2023-09-18 17:43:11,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5ad0900ce0582 for 80.00000000 SEI-USDT. +2023-09-18 17:43:11,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a5ad0900ce0582", "creation_timestamp": 1695058991.0, "exchange_order_id": "35390091", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:43:11,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5ad08fe0c0582 for 80.00000000 SEI-USDT. +2023-09-18 17:43:11,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695058991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a5ad08fe0c0582", "creation_timestamp": 1695058991.0, "exchange_order_id": "35390092", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:44:06,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5ad08fe0c0582. [clock=2023-09-18 17:44:06+00:00] +2023-09-18 17:44:06,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5ad0900ce0582. [clock=2023-09-18 17:44:06+00:00] +2023-09-18 17:44:06,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243385891909427234349744873 amount: 80. +2023-09-18 17:44:06,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250469013178815242779869141 amount: 80. +2023-09-18 17:44:06,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059046.0, "order_id": "x-XEKWYICXBSIUT605a5ad08fe0c0582", "exchange_order_id": "35390092", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:44:06,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5ad08fe0c0582. +2023-09-18 17:44:06,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059046.0, "order_id": "x-XEKWYICXSSIUT605a5ad0900ce0582", "exchange_order_id": "35390091", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:44:06,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5ad0900ce0582. +2023-09-18 17:44:06,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5b05054730582 for 80.00000000 SEI-USDT. +2023-09-18 17:44:06,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a5b05054730582", "creation_timestamp": 1695059046.0, "exchange_order_id": "35390536", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:44:06,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5b050512b0582 for 80.00000000 SEI-USDT. +2023-09-18 17:44:06,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a5b050512b0582", "creation_timestamp": 1695059046.0, "exchange_order_id": "35390537", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:45:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5b050512b0582. [clock=2023-09-18 17:45:01+00:00] +2023-09-18 17:45:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5b05054730582. [clock=2023-09-18 17:45:01+00:00] +2023-09-18 17:45:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243002959310039347610512850 amount: 80. +2023-09-18 17:45:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250759723056297258851775910 amount: 80. +2023-09-18 17:45:01,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059101.0, "order_id": "x-XEKWYICXBSIUT605a5b050512b0582", "exchange_order_id": "35390537", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:45:01,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5b050512b0582. +2023-09-18 17:45:01,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5b396f11e0582 for 80.00000000 SEI-USDT. +2023-09-18 17:45:01,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a5b396f11e0582", "creation_timestamp": 1695059101.0, "exchange_order_id": "35390653", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:45:01,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059101.0, "order_id": "x-XEKWYICXSSIUT605a5b05054730582", "exchange_order_id": "35390536", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:45:01,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5b05054730582. +2023-09-18 17:45:01,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5b396ed2e0582 for 80.00000000 SEI-USDT. +2023-09-18 17:45:01,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a5b396ed2e0582", "creation_timestamp": 1695059101.0, "exchange_order_id": "35390654", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:45:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5b396ed2e0582. [clock=2023-09-18 17:45:56+00:00] +2023-09-18 17:45:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5b396f11e0582. [clock=2023-09-18 17:45:56+00:00] +2023-09-18 17:45:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243255108847798113097521266 amount: 80. +2023-09-18 17:45:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250618978614742783648327697 amount: 80. +2023-09-18 17:45:56,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059156.0, "order_id": "x-XEKWYICXBSIUT605a5b396ed2e0582", "exchange_order_id": "35390654", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:45:56,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5b396ed2e0582. +2023-09-18 17:45:56,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059156.0, "order_id": "x-XEKWYICXSSIUT605a5b396f11e0582", "exchange_order_id": "35390653", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:45:56,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5b396f11e0582. +2023-09-18 17:45:56,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5b6de28510582 for 80.00000000 SEI-USDT. +2023-09-18 17:45:56,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a5b6de28510582", "creation_timestamp": 1695059156.0, "exchange_order_id": "35391194", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:45:56,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5b6de25d60582 for 80.00000000 SEI-USDT. +2023-09-18 17:45:56,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a5b6de25d60582", "creation_timestamp": 1695059156.0, "exchange_order_id": "35391195", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:46:37,412 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5b6de25d60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:46:37,413 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:46:37+00:00] +2023-09-18 17:46:37,436 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059197.0, "order_id": "x-XEKWYICXBSIUT605a5b6de25d60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4977237", "exchange_order_id": "35391195", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:46:37,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059197.0, "order_id": "x-XEKWYICXBSIUT605a5b6de25d60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35391195", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:46:37,451 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5b6de25d60582 completely filled. +2023-09-18 17:46:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5b6de28510582. [clock=2023-09-18 17:46:51+00:00] +2023-09-18 17:46:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240968597411372415686081370 amount: 80. +2023-09-18 17:46:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248028990825501800369409055 amount: 80. +2023-09-18 17:46:51,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059211.0, "order_id": "x-XEKWYICXSSIUT605a5b6de28510582", "exchange_order_id": "35391194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:46:51,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5b6de28510582. +2023-09-18 17:46:51,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5ba2562a90582 for 80.00000000 SEI-USDT. +2023-09-18 17:46:51,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5ba2562a90582", "creation_timestamp": 1695059211.0, "exchange_order_id": "35391738", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:46:51,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5ba2565410582 for 80.00000000 SEI-USDT. +2023-09-18 17:46:51,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a5ba2565410582", "creation_timestamp": 1695059211.0, "exchange_order_id": "35391739", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:47:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5ba2562a90582. [clock=2023-09-18 17:47:46+00:00] +2023-09-18 17:47:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5ba2565410582. [clock=2023-09-18 17:47:46+00:00] +2023-09-18 17:47:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241421679269310962159937421 amount: 80. +2023-09-18 17:47:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246911630787796494978610963 amount: 80. +2023-09-18 17:47:46,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059266.0, "order_id": "x-XEKWYICXBSIUT605a5ba2562a90582", "exchange_order_id": "35391738", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:47:46,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5ba2562a90582. +2023-09-18 17:47:46,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059266.0, "order_id": "x-XEKWYICXSSIUT605a5ba2565410582", "exchange_order_id": "35391739", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:47:46,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5ba2565410582. +2023-09-18 17:47:46,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5bd6c9b1c0582 for 80.00000000 SEI-USDT. +2023-09-18 17:47:46,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a5bd6c9b1c0582", "creation_timestamp": 1695059266.0, "exchange_order_id": "35392119", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:47:46,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5bd6c9d780582 for 80.00000000 SEI-USDT. +2023-09-18 17:47:46,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605a5bd6c9d780582", "creation_timestamp": 1695059266.0, "exchange_order_id": "35392118", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:48:41,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5bd6c9b1c0582. [clock=2023-09-18 17:48:41+00:00] +2023-09-18 17:48:41,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5bd6c9d780582. [clock=2023-09-18 17:48:41+00:00] +2023-09-18 17:48:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242764267272214857469575434 amount: 80. +2023-09-18 17:48:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247701781392583660340543790 amount: 80. +2023-09-18 17:48:41,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059321.0, "order_id": "x-XEKWYICXBSIUT605a5bd6c9b1c0582", "exchange_order_id": "35392119", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:48:41,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5bd6c9b1c0582. +2023-09-18 17:48:41,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059321.0, "order_id": "x-XEKWYICXSSIUT605a5bd6c9d780582", "exchange_order_id": "35392118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:48:41,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5bd6c9d780582. +2023-09-18 17:48:41,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5c0b3f5b40582 for 80.00000000 SEI-USDT. +2023-09-18 17:48:41,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a5c0b3f5b40582", "creation_timestamp": 1695059321.0, "exchange_order_id": "35392398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:48:41,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5c0b3fa170582 for 80.00000000 SEI-USDT. +2023-09-18 17:48:41,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a5c0b3fa170582", "creation_timestamp": 1695059321.0, "exchange_order_id": "35392399", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:49:23,424 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a5c0b3fa170582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:49:23,425 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:49:23+00:00] +2023-09-18 17:49:23,447 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059363.0, "order_id": "x-XEKWYICXSSIUT605a5c0b3fa170582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4977316", "exchange_order_id": "35392399", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:49:23,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059363.0, "order_id": "x-XEKWYICXSSIUT605a5c0b3fa170582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35392399", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:49:23,462 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a5c0b3fa170582 completely filled. +2023-09-18 17:49:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5c0b3f5b40582. [clock=2023-09-18 17:49:36+00:00] +2023-09-18 17:49:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245503897789349365765287230 amount: 80. +2023-09-18 17:49:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251202979869747112587701560 amount: 80. +2023-09-18 17:49:36,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059376.0, "order_id": "x-XEKWYICXBSIUT605a5c0b3f5b40582", "exchange_order_id": "35392398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:49:36,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5c0b3f5b40582. +2023-09-18 17:49:36,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5c3fb1aa50582 for 80.00000000 SEI-USDT. +2023-09-18 17:49:36,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a5c3fb1aa50582", "creation_timestamp": 1695059376.0, "exchange_order_id": "35392891", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:49:36,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5c3fb17b90582 for 80.00000000 SEI-USDT. +2023-09-18 17:49:36,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a5c3fb17b90582", "creation_timestamp": 1695059376.0, "exchange_order_id": "35392892", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:50:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5c3fb17b90582. [clock=2023-09-18 17:50:31+00:00] +2023-09-18 17:50:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5c3fb1aa50582. [clock=2023-09-18 17:50:31+00:00] +2023-09-18 17:50:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244424490272280566106845720 amount: 80. +2023-09-18 17:50:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249519390981051716848789980 amount: 80. +2023-09-18 17:50:31,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059431.0, "order_id": "x-XEKWYICXBSIUT605a5c3fb17b90582", "exchange_order_id": "35392892", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:50:31,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5c3fb17b90582. +2023-09-18 17:50:31,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059431.0, "order_id": "x-XEKWYICXSSIUT605a5c3fb1aa50582", "exchange_order_id": "35392891", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:50:31,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5c3fb1aa50582. +2023-09-18 17:50:31,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5c742581d0582 for 80.00000000 SEI-USDT. +2023-09-18 17:50:31,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a5c742581d0582", "creation_timestamp": 1695059431.0, "exchange_order_id": "35393484", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:50:31,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5c74254f80582 for 80.00000000 SEI-USDT. +2023-09-18 17:50:31,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a5c74254f80582", "creation_timestamp": 1695059431.0, "exchange_order_id": "35393485", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:51:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5c74254f80582. [clock=2023-09-18 17:51:26+00:00] +2023-09-18 17:51:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5c742581d0582. [clock=2023-09-18 17:51:26+00:00] +2023-09-18 17:51:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244384308871179913701153824 amount: 80. +2023-09-18 17:51:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249533064383310707102294270 amount: 80. +2023-09-18 17:51:26,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059486.0, "order_id": "x-XEKWYICXBSIUT605a5c74254f80582", "exchange_order_id": "35393485", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:51:26,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5c74254f80582. +2023-09-18 17:51:26,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5ca8991840582 for 80.00000000 SEI-USDT. +2023-09-18 17:51:26,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a5ca8991840582", "creation_timestamp": 1695059486.0, "exchange_order_id": "35393792", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:51:26,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5ca898f2e0582 for 80.00000000 SEI-USDT. +2023-09-18 17:51:26,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a5ca898f2e0582", "creation_timestamp": 1695059486.0, "exchange_order_id": "35393793", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:51:26,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059486.0, "order_id": "x-XEKWYICXSSIUT605a5c742581d0582", "exchange_order_id": "35393484", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:51:26,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5c742581d0582. +2023-09-18 17:52:07,157 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5ca898f2e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:52:07,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:52:07+00:00] +2023-09-18 17:52:07,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059527.0, "order_id": "x-XEKWYICXBSIUT605a5ca898f2e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4977399", "exchange_order_id": "35393793", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:52:07,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059527.0, "order_id": "x-XEKWYICXBSIUT605a5ca898f2e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35393793", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:52:07,193 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5ca898f2e0582 completely filled. +2023-09-18 17:52:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5ca8991840582. [clock=2023-09-18 17:52:21+00:00] +2023-09-18 17:52:21,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242141217796367474547729625 amount: 80. +2023-09-18 17:52:21,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247999615064629477760274729 amount: 80. +2023-09-18 17:52:21,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059541.0, "order_id": "x-XEKWYICXSSIUT605a5ca8991840582", "exchange_order_id": "35393792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:52:21,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5ca8991840582. +2023-09-18 17:52:21,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5cdd0f15c0582 for 80.00000000 SEI-USDT. +2023-09-18 17:52:21,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a5cdd0f15c0582", "creation_timestamp": 1695059541.0, "exchange_order_id": "35394458", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:52:21,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5cdd0f5720582 for 80.00000000 SEI-USDT. +2023-09-18 17:52:21,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a5cdd0f5720582", "creation_timestamp": 1695059541.0, "exchange_order_id": "35394459", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:53:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5cdd0f15c0582. [clock=2023-09-18 17:53:16+00:00] +2023-09-18 17:53:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5cdd0f5720582. [clock=2023-09-18 17:53:16+00:00] +2023-09-18 17:53:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243663416201148382975836825 amount: 80. +2023-09-18 17:53:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249403672195308273591340725 amount: 80. +2023-09-18 17:53:16,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059596.0, "order_id": "x-XEKWYICXBSIUT605a5cdd0f15c0582", "exchange_order_id": "35394458", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:53:16,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5cdd0f15c0582. +2023-09-18 17:53:16,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5d11803780582 for 80.00000000 SEI-USDT. +2023-09-18 17:53:16,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a5d11803780582", "creation_timestamp": 1695059596.0, "exchange_order_id": "35394885", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:53:16,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059596.0, "order_id": "x-XEKWYICXSSIUT605a5cdd0f5720582", "exchange_order_id": "35394459", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:53:16,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5cdd0f5720582. +2023-09-18 17:53:16,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5d11806410582 for 80.00000000 SEI-USDT. +2023-09-18 17:53:16,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a5d11806410582", "creation_timestamp": 1695059596.0, "exchange_order_id": "35394886", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:54:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5d11803780582. [clock=2023-09-18 17:54:11+00:00] +2023-09-18 17:54:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5d11806410582. [clock=2023-09-18 17:54:11+00:00] +2023-09-18 17:54:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243960176341510468488997624 amount: 80. +2023-09-18 17:54:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248423596194308241969722480 amount: 80. +2023-09-18 17:54:11,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059651.0, "order_id": "x-XEKWYICXBSIUT605a5d11803780582", "exchange_order_id": "35394885", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:54:11,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5d11803780582. +2023-09-18 17:54:11,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059651.0, "order_id": "x-XEKWYICXSSIUT605a5d11806410582", "exchange_order_id": "35394886", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:54:11,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5d11806410582. +2023-09-18 17:54:11,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5d45f46c60582 for 80.00000000 SEI-USDT. +2023-09-18 17:54:11,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a5d45f46c60582", "creation_timestamp": 1695059651.0, "exchange_order_id": "35395067", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:54:11,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5d45f43760582 for 80.00000000 SEI-USDT. +2023-09-18 17:54:11,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a5d45f43760582", "creation_timestamp": 1695059651.0, "exchange_order_id": "35395068", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:55:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5d45f43760582. [clock=2023-09-18 17:55:06+00:00] +2023-09-18 17:55:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5d45f46c60582. [clock=2023-09-18 17:55:06+00:00] +2023-09-18 17:55:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244191571309839166248535182 amount: 80. +2023-09-18 17:55:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247662382118885543101022734 amount: 80. +2023-09-18 17:55:06,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059706.0, "order_id": "x-XEKWYICXBSIUT605a5d45f43760582", "exchange_order_id": "35395068", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:55:06,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5d45f43760582. +2023-09-18 17:55:06,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059706.0, "order_id": "x-XEKWYICXSSIUT605a5d45f46c60582", "exchange_order_id": "35395067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:55:06,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5d45f46c60582. +2023-09-18 17:55:06,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5d7a67dd20582 for 80.00000000 SEI-USDT. +2023-09-18 17:55:06,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a5d7a67dd20582", "creation_timestamp": 1695059706.0, "exchange_order_id": "35395173", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:55:06,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5d7a680270582 for 80.00000000 SEI-USDT. +2023-09-18 17:55:06,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a5d7a680270582", "creation_timestamp": 1695059706.0, "exchange_order_id": "35395174", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:55:15,026 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 17:55:57,926 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5d7a67dd20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:55:57,927 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:55:57+00:00] +2023-09-18 17:55:57,952 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059757.0, "order_id": "x-XEKWYICXBSIUT605a5d7a67dd20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4977496", "exchange_order_id": "35395173", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:55:57,966 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059757.0, "order_id": "x-XEKWYICXBSIUT605a5d7a67dd20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35395173", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:55:57,966 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5d7a67dd20582 completely filled. +2023-09-18 17:56:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5d7a680270582. [clock=2023-09-18 17:56:01+00:00] +2023-09-18 17:56:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240817183693077319973428297 amount: 80. +2023-09-18 17:56:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245618510571623154997415361 amount: 80. +2023-09-18 17:56:01,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059761.0, "order_id": "x-XEKWYICXSSIUT605a5d7a680270582", "exchange_order_id": "35395174", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:56:01,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5d7a680270582. +2023-09-18 17:56:01,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5daedb7f00582 for 80.00000000 SEI-USDT. +2023-09-18 17:56:01,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5daedb7f00582", "creation_timestamp": 1695059761.0, "exchange_order_id": "35395499", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:56:01,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5daedbb0a0582 for 80.00000000 SEI-USDT. +2023-09-18 17:56:01,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5daedbb0a0582", "creation_timestamp": 1695059761.0, "exchange_order_id": "35395500", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:56:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5daedb7f00582. [clock=2023-09-18 17:56:56+00:00] +2023-09-18 17:56:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5daedbb0a0582. [clock=2023-09-18 17:56:56+00:00] +2023-09-18 17:56:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238519303532511275298318409 amount: 80. +2023-09-18 17:56:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244921242783991301260610503 amount: 80. +2023-09-18 17:56:56,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059816.0, "order_id": "x-XEKWYICXBSIUT605a5daedb7f00582", "exchange_order_id": "35395499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:56:56,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5daedb7f00582. +2023-09-18 17:56:56,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5de34f73d0582 for 80.00000000 SEI-USDT. +2023-09-18 17:56:56,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a5de34f73d0582", "creation_timestamp": 1695059816.0, "exchange_order_id": "35396359", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:56:56,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059816.0, "order_id": "x-XEKWYICXSSIUT605a5daedbb0a0582", "exchange_order_id": "35395500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:56:56,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5daedbb0a0582. +2023-09-18 17:56:56,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5de34fa490582 for 80.00000000 SEI-USDT. +2023-09-18 17:56:56,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a5de34fa490582", "creation_timestamp": 1695059816.0, "exchange_order_id": "35396360", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:57:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5de34f73d0582. [clock=2023-09-18 17:57:51+00:00] +2023-09-18 17:57:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5de34fa490582. [clock=2023-09-18 17:57:51+00:00] +2023-09-18 17:57:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236722559897643933202440542 amount: 80. +2023-09-18 17:57:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242363003391335405310282211 amount: 80. +2023-09-18 17:57:51,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059871.0, "order_id": "x-XEKWYICXBSIUT605a5de34f73d0582", "exchange_order_id": "35396359", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:57:51,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5de34f73d0582. +2023-09-18 17:57:51,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5e17c37050582 for 80.00000000 SEI-USDT. +2023-09-18 17:57:51,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a5e17c37050582", "creation_timestamp": 1695059871.0, "exchange_order_id": "35396729", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:57:51,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059871.0, "order_id": "x-XEKWYICXSSIUT605a5de34fa490582", "exchange_order_id": "35396360", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:57:51,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5de34fa490582. +2023-09-18 17:57:51,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5e17c3a810582 for 80.00000000 SEI-USDT. +2023-09-18 17:57:51,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a5e17c3a810582", "creation_timestamp": 1695059871.0, "exchange_order_id": "35396730", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:58:21,029 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a5e17c3a810582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 17:58:21,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 17:58:21+00:00] +2023-09-18 17:58:21,067 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059901.0, "order_id": "x-XEKWYICXSSIUT605a5e17c3a810582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4977639", "exchange_order_id": "35396730", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 17:58:21,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059901.0, "order_id": "x-XEKWYICXSSIUT605a5e17c3a810582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35396730", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 17:58:21,086 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a5e17c3a810582 completely filled. +2023-09-18 17:58:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5e17c37050582. [clock=2023-09-18 17:58:46+00:00] +2023-09-18 17:58:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239407565412859354399463925 amount: 80. +2023-09-18 17:58:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245654255378420427628902574 amount: 80. +2023-09-18 17:58:46,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059926.0, "order_id": "x-XEKWYICXBSIUT605a5e17c37050582", "exchange_order_id": "35396729", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:58:46,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5e17c37050582. +2023-09-18 17:58:46,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5e4c36e1f0582 for 80.00000000 SEI-USDT. +2023-09-18 17:58:46,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5e4c36e1f0582", "creation_timestamp": 1695059926.0, "exchange_order_id": "35397318", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:58:46,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5e4c36aac0582 for 80.00000000 SEI-USDT. +2023-09-18 17:58:46,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a5e4c36aac0582", "creation_timestamp": 1695059926.0, "exchange_order_id": "35397319", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:59:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5e4c36aac0582. [clock=2023-09-18 17:59:41+00:00] +2023-09-18 17:59:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5e4c36e1f0582. [clock=2023-09-18 17:59:41+00:00] +2023-09-18 17:59:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239427892858529480053416126 amount: 80. +2023-09-18 17:59:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245620298821949068262080000 amount: 80. +2023-09-18 17:59:41,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059981.0, "order_id": "x-XEKWYICXBSIUT605a5e4c36aac0582", "exchange_order_id": "35397319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:59:41,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5e4c36aac0582. +2023-09-18 17:59:41,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059981.0, "order_id": "x-XEKWYICXSSIUT605a5e4c36e1f0582", "exchange_order_id": "35397318", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 17:59:41,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5e4c36e1f0582. +2023-09-18 17:59:41,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5e80aaf760582 for 80.00000000 SEI-USDT. +2023-09-18 17:59:41,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5e80aaf760582", "creation_timestamp": 1695059981.0, "exchange_order_id": "35397573", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 17:59:41,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5e80aac430582 for 80.00000000 SEI-USDT. +2023-09-18 17:59:41,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695059981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a5e80aac430582", "creation_timestamp": 1695059981.0, "exchange_order_id": "35397574", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:00:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5e80aac430582. [clock=2023-09-18 18:00:36+00:00] +2023-09-18 18:00:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5e80aaf760582. [clock=2023-09-18 18:00:36+00:00] +2023-09-18 18:00:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240091390155306132034962997 amount: 80. +2023-09-18 18:00:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245571598033527368432515341 amount: 80. +2023-09-18 18:00:36,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060036.0, "order_id": "x-XEKWYICXBSIUT605a5e80aac430582", "exchange_order_id": "35397574", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:00:36,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5e80aac430582. +2023-09-18 18:00:36,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060036.0, "order_id": "x-XEKWYICXSSIUT605a5e80aaf760582", "exchange_order_id": "35397573", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:00:36,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5e80aaf760582. +2023-09-18 18:00:36,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5eb51fdb40582 for 80.00000000 SEI-USDT. +2023-09-18 18:00:36,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5eb51fdb40582", "creation_timestamp": 1695060036.0, "exchange_order_id": "35398036", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:00:36,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5eb51fa200582 for 80.00000000 SEI-USDT. +2023-09-18 18:00:36,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5eb51fa200582", "creation_timestamp": 1695060036.0, "exchange_order_id": "35398037", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:01:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5eb51fa200582. [clock=2023-09-18 18:01:31+00:00] +2023-09-18 18:01:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5eb51fdb40582. [clock=2023-09-18 18:01:31+00:00] +2023-09-18 18:01:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237787126606190231337348414 amount: 80. +2023-09-18 18:01:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242565433201627525838760182 amount: 80. +2023-09-18 18:01:31,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060091.0, "order_id": "x-XEKWYICXBSIUT605a5eb51fa200582", "exchange_order_id": "35398037", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:01:31,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5eb51fa200582. +2023-09-18 18:01:31,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060091.0, "order_id": "x-XEKWYICXSSIUT605a5eb51fdb40582", "exchange_order_id": "35398036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:01:31,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5eb51fdb40582. +2023-09-18 18:01:31,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5ee9921000582 for 80.00000000 SEI-USDT. +2023-09-18 18:01:31,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a5ee9921000582", "creation_timestamp": 1695060091.0, "exchange_order_id": "35398559", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:01:31,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5ee991d930582 for 80.00000000 SEI-USDT. +2023-09-18 18:01:31,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a5ee991d930582", "creation_timestamp": 1695060091.0, "exchange_order_id": "35398560", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:02:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5ee991d930582. [clock=2023-09-18 18:02:26+00:00] +2023-09-18 18:02:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5ee9921000582. [clock=2023-09-18 18:02:26+00:00] +2023-09-18 18:02:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238907981892192850690847536 amount: 80. +2023-09-18 18:02:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243813521819944452888788498 amount: 80. +2023-09-18 18:02:26,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060146.0, "order_id": "x-XEKWYICXBSIUT605a5ee991d930582", "exchange_order_id": "35398560", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:02:26,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5ee991d930582. +2023-09-18 18:02:26,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060146.0, "order_id": "x-XEKWYICXSSIUT605a5ee9921000582", "exchange_order_id": "35398559", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:02:26,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5ee9921000582. +2023-09-18 18:02:26,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5f1e05c9b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:02:26,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a5f1e05c9b0582", "creation_timestamp": 1695060146.0, "exchange_order_id": "35399150", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:02:26,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5f1e060290582 for 80.00000000 SEI-USDT. +2023-09-18 18:02:26,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a5f1e060290582", "creation_timestamp": 1695060146.0, "exchange_order_id": "35399151", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:03:09,768 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a5f1e060290582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:03:09,769 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:03:09+00:00] +2023-09-18 18:03:09,792 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060189.0, "order_id": "x-XEKWYICXSSIUT605a5f1e060290582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "4977761", "exchange_order_id": "35399151", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:03:09,804 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060189.0, "order_id": "x-XEKWYICXSSIUT605a5f1e060290582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35399151", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:03:09,804 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a5f1e060290582 completely filled. +2023-09-18 18:03:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5f1e05c9b0582. [clock=2023-09-18 18:03:21+00:00] +2023-09-18 18:03:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240968526391279490188801248 amount: 80. +2023-09-18 18:03:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245821416120040796302440829 amount: 80. +2023-09-18 18:03:21,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060201.0, "order_id": "x-XEKWYICXBSIUT605a5f1e05c9b0582", "exchange_order_id": "35399150", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:03:21,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5f1e05c9b0582. +2023-09-18 18:03:21,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5f52797e40582 for 80.00000000 SEI-USDT. +2023-09-18 18:03:21,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5f52797e40582", "creation_timestamp": 1695060201.0, "exchange_order_id": "35399641", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:03:21,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5f52795a80582 for 80.00000000 SEI-USDT. +2023-09-18 18:03:21,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5f52795a80582", "creation_timestamp": 1695060201.0, "exchange_order_id": "35399642", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:04:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5f52795a80582. [clock=2023-09-18 18:04:16+00:00] +2023-09-18 18:04:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5f52797e40582. [clock=2023-09-18 18:04:16+00:00] +2023-09-18 18:04:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240801772019005661583044138 amount: 80. +2023-09-18 18:04:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245245368582284964004493442 amount: 80. +2023-09-18 18:04:16,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060256.0, "order_id": "x-XEKWYICXBSIUT605a5f52795a80582", "exchange_order_id": "35399642", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:04:16,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5f52795a80582. +2023-09-18 18:04:16,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060256.0, "order_id": "x-XEKWYICXSSIUT605a5f52797e40582", "exchange_order_id": "35399641", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:04:16,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5f52797e40582. +2023-09-18 18:04:16,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5f86eea0e0582 for 80.00000000 SEI-USDT. +2023-09-18 18:04:16,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a5f86eea0e0582", "creation_timestamp": 1695060256.0, "exchange_order_id": "35399998", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:04:16,486 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5f86ee62b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:04:16,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a5f86ee62b0582", "creation_timestamp": 1695060256.0, "exchange_order_id": "35399999", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:05:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5f86ee62b0582. [clock=2023-09-18 18:05:11+00:00] +2023-09-18 18:05:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5f86eea0e0582. [clock=2023-09-18 18:05:11+00:00] +2023-09-18 18:05:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242154677764637505964844692 amount: 80. +2023-09-18 18:05:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246130131352565988918692774 amount: 80. +2023-09-18 18:05:11,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060311.0, "order_id": "x-XEKWYICXBSIUT605a5f86ee62b0582", "exchange_order_id": "35399999", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:05:11,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5f86ee62b0582. +2023-09-18 18:05:11,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060311.0, "order_id": "x-XEKWYICXSSIUT605a5f86eea0e0582", "exchange_order_id": "35399998", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:05:11,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5f86eea0e0582. +2023-09-18 18:05:11,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5fbb6162a0582 for 80.00000000 SEI-USDT. +2023-09-18 18:05:11,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a5fbb6162a0582", "creation_timestamp": 1695060311.0, "exchange_order_id": "35400298", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:05:11,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5fbb61ac00582 for 80.00000000 SEI-USDT. +2023-09-18 18:05:11,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605a5fbb61ac00582", "creation_timestamp": 1695060311.0, "exchange_order_id": "35400299", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:05:39,142 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a5fbb6162a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:05:39,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:05:39+00:00] +2023-09-18 18:05:39,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060339.0, "order_id": "x-XEKWYICXBSIUT605a5fbb6162a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4977831", "exchange_order_id": "35400298", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:05:39,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060339.0, "order_id": "x-XEKWYICXBSIUT605a5fbb6162a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35400298", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:05:39,197 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a5fbb6162a0582 completely filled. +2023-09-18 18:06:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5fbb61ac00582. [clock=2023-09-18 18:06:06+00:00] +2023-09-18 18:06:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238234621536046348161800745 amount: 80. +2023-09-18 18:06:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242358499981636108771653421 amount: 80. +2023-09-18 18:06:06,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060366.0, "order_id": "x-XEKWYICXSSIUT605a5fbb61ac00582", "exchange_order_id": "35400299", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:06:06,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5fbb61ac00582. +2023-09-18 18:06:06,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a5fefd4b050582 for 80.00000000 SEI-USDT. +2023-09-18 18:06:06,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a5fefd4b050582", "creation_timestamp": 1695060366.0, "exchange_order_id": "35400781", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:06:06,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a5fefd4dd00582 for 80.00000000 SEI-USDT. +2023-09-18 18:06:06,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a5fefd4dd00582", "creation_timestamp": 1695060366.0, "exchange_order_id": "35400782", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:07:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a5fefd4b050582. [clock=2023-09-18 18:07:01+00:00] +2023-09-18 18:07:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a5fefd4dd00582. [clock=2023-09-18 18:07:01+00:00] +2023-09-18 18:07:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237734022769720959542814388 amount: 80. +2023-09-18 18:07:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242527412009063429620940147 amount: 80. +2023-09-18 18:07:01,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060421.0, "order_id": "x-XEKWYICXBSIUT605a5fefd4b050582", "exchange_order_id": "35400781", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:07:01,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a5fefd4b050582. +2023-09-18 18:07:01,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060421.0, "order_id": "x-XEKWYICXSSIUT605a5fefd4dd00582", "exchange_order_id": "35400782", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:07:01,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a5fefd4dd00582. +2023-09-18 18:07:01,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a602448b970582 for 80.00000000 SEI-USDT. +2023-09-18 18:07:01,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a602448b970582", "creation_timestamp": 1695060421.0, "exchange_order_id": "35400916", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:07:01,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a602448e790582 for 80.00000000 SEI-USDT. +2023-09-18 18:07:01,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a602448e790582", "creation_timestamp": 1695060421.0, "exchange_order_id": "35400917", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:07:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a602448b970582. [clock=2023-09-18 18:07:56+00:00] +2023-09-18 18:07:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a602448e790582. [clock=2023-09-18 18:07:56+00:00] +2023-09-18 18:07:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237922440086536337077317637 amount: 80. +2023-09-18 18:07:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242317330273447575077191887 amount: 80. +2023-09-18 18:07:56,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060476.0, "order_id": "x-XEKWYICXBSIUT605a602448b970582", "exchange_order_id": "35400916", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:07:56,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a602448b970582. +2023-09-18 18:07:56,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060476.0, "order_id": "x-XEKWYICXSSIUT605a602448e790582", "exchange_order_id": "35400917", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:07:56,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a602448e790582. +2023-09-18 18:07:56,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6058bc5c40582 for 80.00000000 SEI-USDT. +2023-09-18 18:07:56,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a6058bc5c40582", "creation_timestamp": 1695060476.0, "exchange_order_id": "35401295", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:07:56,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6058bc3af0582 for 80.00000000 SEI-USDT. +2023-09-18 18:07:56,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6058bc3af0582", "creation_timestamp": 1695060476.0, "exchange_order_id": "35401296", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:08:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6058bc3af0582. [clock=2023-09-18 18:08:51+00:00] +2023-09-18 18:08:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6058bc5c40582. [clock=2023-09-18 18:08:51+00:00] +2023-09-18 18:08:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239403250568034799724819315 amount: 80. +2023-09-18 18:08:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243489303568922034307817981 amount: 80. +2023-09-18 18:08:51,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060531.0, "order_id": "x-XEKWYICXBSIUT605a6058bc3af0582", "exchange_order_id": "35401296", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:08:51,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6058bc3af0582. +2023-09-18 18:08:51,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060531.0, "order_id": "x-XEKWYICXSSIUT605a6058bc5c40582", "exchange_order_id": "35401295", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:08:51,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6058bc5c40582. +2023-09-18 18:08:51,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a608d30a170582 for 80.00000000 SEI-USDT. +2023-09-18 18:08:51,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a608d30a170582", "creation_timestamp": 1695060531.0, "exchange_order_id": "35401556", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:08:51,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a608d30fd00582 for 80.00000000 SEI-USDT. +2023-09-18 18:08:51,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a608d30fd00582", "creation_timestamp": 1695060531.0, "exchange_order_id": "35401557", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:09:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a608d30a170582. [clock=2023-09-18 18:09:46+00:00] +2023-09-18 18:09:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a608d30fd00582. [clock=2023-09-18 18:09:46+00:00] +2023-09-18 18:09:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240770541802135180622984363 amount: 80. +2023-09-18 18:09:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244616253072477640192691633 amount: 80. +2023-09-18 18:09:46,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060586.0, "order_id": "x-XEKWYICXBSIUT605a608d30a170582", "exchange_order_id": "35401556", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:09:46,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a608d30a170582. +2023-09-18 18:09:46,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060586.0, "order_id": "x-XEKWYICXSSIUT605a608d30fd00582", "exchange_order_id": "35401557", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:09:46,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a608d30fd00582. +2023-09-18 18:09:46,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a60c1a3b570582 for 80.00000000 SEI-USDT. +2023-09-18 18:09:46,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a60c1a3b570582", "creation_timestamp": 1695060586.0, "exchange_order_id": "35401790", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:09:46,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a60c1a37260582 for 80.00000000 SEI-USDT. +2023-09-18 18:09:46,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a60c1a37260582", "creation_timestamp": 1695060586.0, "exchange_order_id": "35401791", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:10:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a60c1a37260582. [clock=2023-09-18 18:10:41+00:00] +2023-09-18 18:10:41,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a60c1a3b570582. [clock=2023-09-18 18:10:41+00:00] +2023-09-18 18:10:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241057376390112742751813175 amount: 80. +2023-09-18 18:10:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244713145356529322088334189 amount: 80. +2023-09-18 18:10:41,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060641.0, "order_id": "x-XEKWYICXBSIUT605a60c1a37260582", "exchange_order_id": "35401791", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:10:41,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a60c1a37260582. +2023-09-18 18:10:41,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060641.0, "order_id": "x-XEKWYICXSSIUT605a60c1a3b570582", "exchange_order_id": "35401790", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:10:41,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a60c1a3b570582. +2023-09-18 18:10:41,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a60f6182eb0582 for 80.00000000 SEI-USDT. +2023-09-18 18:10:41,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060641.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a60f6182eb0582", "creation_timestamp": 1695060641.0, "exchange_order_id": "35401864", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:10:41,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a60f617ece0582 for 80.00000000 SEI-USDT. +2023-09-18 18:10:41,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060641.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a60f617ece0582", "creation_timestamp": 1695060641.0, "exchange_order_id": "35401865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:11:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a60f617ece0582. [clock=2023-09-18 18:11:36+00:00] +2023-09-18 18:11:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a60f6182eb0582. [clock=2023-09-18 18:11:36+00:00] +2023-09-18 18:11:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240839465993381917373870524 amount: 80. +2023-09-18 18:11:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244351453981975093919454548 amount: 80. +2023-09-18 18:11:36,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060696.0, "order_id": "x-XEKWYICXBSIUT605a60f617ece0582", "exchange_order_id": "35401865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:11:36,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a60f617ece0582. +2023-09-18 18:11:36,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060696.0, "order_id": "x-XEKWYICXSSIUT605a60f6182eb0582", "exchange_order_id": "35401864", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:11:36,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a60f6182eb0582. +2023-09-18 18:11:36,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a612a8af280582 for 80.00000000 SEI-USDT. +2023-09-18 18:11:36,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a612a8af280582", "creation_timestamp": 1695060696.0, "exchange_order_id": "35401944", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:11:36,367 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a612a8b1930582 for 80.00000000 SEI-USDT. +2023-09-18 18:11:36,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a612a8b1930582", "creation_timestamp": 1695060696.0, "exchange_order_id": "35401945", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:12:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a612a8af280582. [clock=2023-09-18 18:12:31+00:00] +2023-09-18 18:12:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a612a8b1930582. [clock=2023-09-18 18:12:31+00:00] +2023-09-18 18:12:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241097507696580448094265978 amount: 80. +2023-09-18 18:12:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243828763026178225000076628 amount: 80. +2023-09-18 18:12:31,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060751.0, "order_id": "x-XEKWYICXBSIUT605a612a8af280582", "exchange_order_id": "35401944", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:12:31,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a612a8af280582. +2023-09-18 18:12:31,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060751.0, "order_id": "x-XEKWYICXSSIUT605a612a8b1930582", "exchange_order_id": "35401945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:12:31,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a612a8b1930582. +2023-09-18 18:12:31,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a615efef090582 for 80.00000000 SEI-USDT. +2023-09-18 18:12:31,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a615efef090582", "creation_timestamp": 1695060751.0, "exchange_order_id": "35402091", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:12:31,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a615eff2200582 for 80.00000000 SEI-USDT. +2023-09-18 18:12:31,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a615eff2200582", "creation_timestamp": 1695060751.0, "exchange_order_id": "35402092", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:13:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a615efef090582. [clock=2023-09-18 18:13:26+00:00] +2023-09-18 18:13:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a615eff2200582. [clock=2023-09-18 18:13:26+00:00] +2023-09-18 18:13:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241298149413412035758472797 amount: 80. +2023-09-18 18:13:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243422283147621363204629089 amount: 80. +2023-09-18 18:13:26,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060806.0, "order_id": "x-XEKWYICXBSIUT605a615efef090582", "exchange_order_id": "35402091", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:13:26,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a615efef090582. +2023-09-18 18:13:26,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a619372ac80582 for 80.00000000 SEI-USDT. +2023-09-18 18:13:26,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a619372ac80582", "creation_timestamp": 1695060806.0, "exchange_order_id": "35402250", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:13:26,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060806.0, "order_id": "x-XEKWYICXSSIUT605a615eff2200582", "exchange_order_id": "35402092", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:13:26,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a615eff2200582. +2023-09-18 18:13:26,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a619372d090582 for 80.00000000 SEI-USDT. +2023-09-18 18:13:26,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a619372d090582", "creation_timestamp": 1695060806.0, "exchange_order_id": "35402251", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:14:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a619372ac80582. [clock=2023-09-18 18:14:21+00:00] +2023-09-18 18:14:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a619372d090582. [clock=2023-09-18 18:14:21+00:00] +2023-09-18 18:14:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240234360263798467587432695 amount: 80. +2023-09-18 18:14:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242551518799879898772548282 amount: 80. +2023-09-18 18:14:21,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060861.0, "order_id": "x-XEKWYICXBSIUT605a619372ac80582", "exchange_order_id": "35402250", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:14:21,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a619372ac80582. +2023-09-18 18:14:21,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a61c7e65f60582 for 80.00000000 SEI-USDT. +2023-09-18 18:14:21,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a61c7e65f60582", "creation_timestamp": 1695060861.0, "exchange_order_id": "35402628", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:14:21,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a61c7e63ad0582 for 80.00000000 SEI-USDT. +2023-09-18 18:14:21,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a61c7e63ad0582", "creation_timestamp": 1695060861.0, "exchange_order_id": "35402629", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:14:21,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060861.0, "order_id": "x-XEKWYICXSSIUT605a619372d090582", "exchange_order_id": "35402251", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:14:21,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a619372d090582. +2023-09-18 18:14:25,074 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a61c7e65f60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:14:25,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:14:25+00:00] +2023-09-18 18:14:25,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060865.0, "order_id": "x-XEKWYICXSSIUT605a61c7e65f60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4978082", "exchange_order_id": "35402628", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:14:25,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060865.0, "order_id": "x-XEKWYICXSSIUT605a61c7e65f60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35402628", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:14:25,125 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a61c7e65f60582 completely filled. +2023-09-18 18:15:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a61c7e63ad0582. [clock=2023-09-18 18:15:16+00:00] +2023-09-18 18:15:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242676580266831589964513659 amount: 80. +2023-09-18 18:15:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246184613555128805118468197 amount: 80. +2023-09-18 18:15:16,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060916.0, "order_id": "x-XEKWYICXBSIUT605a61c7e63ad0582", "exchange_order_id": "35402629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:15:16,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a61c7e63ad0582. +2023-09-18 18:15:16,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a61fc5b9fd0582 for 80.00000000 SEI-USDT. +2023-09-18 18:15:16,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605a61fc5b9fd0582", "creation_timestamp": 1695060916.0, "exchange_order_id": "35403102", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:15:16,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a61fc5b0280582 for 80.00000000 SEI-USDT. +2023-09-18 18:15:16,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a61fc5b0280582", "creation_timestamp": 1695060916.0, "exchange_order_id": "35403103", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:16:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a61fc5b0280582. [clock=2023-09-18 18:16:11+00:00] +2023-09-18 18:16:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a61fc5b9fd0582. [clock=2023-09-18 18:16:11+00:00] +2023-09-18 18:16:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241078427361278026753244788 amount: 80. +2023-09-18 18:16:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244845907006666753447830272 amount: 80. +2023-09-18 18:16:11,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060971.0, "order_id": "x-XEKWYICXBSIUT605a61fc5b0280582", "exchange_order_id": "35403103", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:16:11,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a61fc5b0280582. +2023-09-18 18:16:11,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060971.0, "order_id": "x-XEKWYICXSSIUT605a61fc5b9fd0582", "exchange_order_id": "35403102", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:16:11,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a61fc5b9fd0582. +2023-09-18 18:16:11,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6230cde9e0582 for 80.00000000 SEI-USDT. +2023-09-18 18:16:11,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a6230cde9e0582", "creation_timestamp": 1695060971.0, "exchange_order_id": "35403512", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:16:11,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6230cdb820582 for 80.00000000 SEI-USDT. +2023-09-18 18:16:11,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695060971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a6230cdb820582", "creation_timestamp": 1695060971.0, "exchange_order_id": "35403513", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:17:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6230cdb820582. [clock=2023-09-18 18:17:06+00:00] +2023-09-18 18:17:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6230cde9e0582. [clock=2023-09-18 18:17:06+00:00] +2023-09-18 18:17:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241283897597862830403225038 amount: 80. +2023-09-18 18:17:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244213656480679040121560996 amount: 80. +2023-09-18 18:17:06,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061026.0, "order_id": "x-XEKWYICXBSIUT605a6230cdb820582", "exchange_order_id": "35403513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:17:06,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6230cdb820582. +2023-09-18 18:17:06,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061026.0, "order_id": "x-XEKWYICXSSIUT605a6230cde9e0582", "exchange_order_id": "35403512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:17:06,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6230cde9e0582. +2023-09-18 18:17:06,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a626541f720582 for 80.00000000 SEI-USDT. +2023-09-18 18:17:06,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a626541f720582", "creation_timestamp": 1695061026.0, "exchange_order_id": "35403796", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:17:06,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a626541b880582 for 80.00000000 SEI-USDT. +2023-09-18 18:17:06,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a626541b880582", "creation_timestamp": 1695061026.0, "exchange_order_id": "35403795", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:18:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a626541b880582. [clock=2023-09-18 18:18:01+00:00] +2023-09-18 18:18:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a626541f720582. [clock=2023-09-18 18:18:01+00:00] +2023-09-18 18:18:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242369981580657534498132843 amount: 80. +2023-09-18 18:18:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245968022565149358529381082 amount: 80. +2023-09-18 18:18:01,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061081.0, "order_id": "x-XEKWYICXBSIUT605a626541b880582", "exchange_order_id": "35403795", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:18:01,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a626541b880582. +2023-09-18 18:18:01,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061081.0, "order_id": "x-XEKWYICXSSIUT605a626541f720582", "exchange_order_id": "35403796", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:18:01,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a626541f720582. +2023-09-18 18:18:01,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6299b58590582 for 80.00000000 SEI-USDT. +2023-09-18 18:18:01,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a6299b58590582", "creation_timestamp": 1695061081.0, "exchange_order_id": "35404065", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:18:01,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6299b5a8b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:18:01,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a6299b5a8b0582", "creation_timestamp": 1695061081.0, "exchange_order_id": "35404066", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:18:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6299b58590582. [clock=2023-09-18 18:18:56+00:00] +2023-09-18 18:18:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6299b5a8b0582. [clock=2023-09-18 18:18:56+00:00] +2023-09-18 18:18:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241393192712668832814701433 amount: 80. +2023-09-18 18:18:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244856053446598537688673911 amount: 80. +2023-09-18 18:18:56,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061136.0, "order_id": "x-XEKWYICXBSIUT605a6299b58590582", "exchange_order_id": "35404065", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:18:56,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6299b58590582. +2023-09-18 18:18:56,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a62ce29a500582 for 80.00000000 SEI-USDT. +2023-09-18 18:18:56,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a62ce29a500582", "creation_timestamp": 1695061136.0, "exchange_order_id": "35404364", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:18:56,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a62ce2956d0582 for 80.00000000 SEI-USDT. +2023-09-18 18:18:56,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a62ce2956d0582", "creation_timestamp": 1695061136.0, "exchange_order_id": "35404365", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:18:56,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061136.0, "order_id": "x-XEKWYICXSSIUT605a6299b5a8b0582", "exchange_order_id": "35404066", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:18:56,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6299b5a8b0582. +2023-09-18 18:19:51,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a62ce2956d0582. [clock=2023-09-18 18:19:51+00:00] +2023-09-18 18:19:51,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a62ce29a500582. [clock=2023-09-18 18:19:51+00:00] +2023-09-18 18:19:51,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241528667747085298472109351 amount: 80. +2023-09-18 18:19:51,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244221463185501092742967392 amount: 80. +2023-09-18 18:19:51,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061191.0, "order_id": "x-XEKWYICXBSIUT605a62ce2956d0582", "exchange_order_id": "35404365", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:19:51,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a62ce2956d0582. +2023-09-18 18:19:51,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061191.0, "order_id": "x-XEKWYICXSSIUT605a62ce29a500582", "exchange_order_id": "35404364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:19:51,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a62ce29a500582. +2023-09-18 18:19:51,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a63029fced0582 for 80.00000000 SEI-USDT. +2023-09-18 18:19:51,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a63029fced0582", "creation_timestamp": 1695061191.0, "exchange_order_id": "35404554", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:19:51,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a63029fa790582 for 80.00000000 SEI-USDT. +2023-09-18 18:19:51,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a63029fa790582", "creation_timestamp": 1695061191.0, "exchange_order_id": "35404555", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:20:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a63029fa790582. [clock=2023-09-18 18:20:46+00:00] +2023-09-18 18:20:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a63029fced0582. [clock=2023-09-18 18:20:46+00:00] +2023-09-18 18:20:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240260224168374736547080757 amount: 80. +2023-09-18 18:20:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243020698473564913778592115 amount: 80. +2023-09-18 18:20:46,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061246.0, "order_id": "x-XEKWYICXBSIUT605a63029fa790582", "exchange_order_id": "35404555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:20:46,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a63029fa790582. +2023-09-18 18:20:46,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061246.0, "order_id": "x-XEKWYICXSSIUT605a63029fced0582", "exchange_order_id": "35404554", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:20:46,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a63029fced0582. +2023-09-18 18:20:46,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6337107ee0582 for 80.00000000 SEI-USDT. +2023-09-18 18:20:46,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a6337107ee0582", "creation_timestamp": 1695061246.0, "exchange_order_id": "35404765", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:20:46,301 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a633710bc50582 for 80.00000000 SEI-USDT. +2023-09-18 18:20:46,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a633710bc50582", "creation_timestamp": 1695061246.0, "exchange_order_id": "35404764", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:21:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6337107ee0582. [clock=2023-09-18 18:21:41+00:00] +2023-09-18 18:21:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a633710bc50582. [clock=2023-09-18 18:21:41+00:00] +2023-09-18 18:21:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239837054173494469237467821 amount: 80. +2023-09-18 18:21:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243690785934640779272013572 amount: 80. +2023-09-18 18:21:41,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061301.0, "order_id": "x-XEKWYICXBSIUT605a6337107ee0582", "exchange_order_id": "35404765", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:21:41,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6337107ee0582. +2023-09-18 18:21:41,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061301.0, "order_id": "x-XEKWYICXSSIUT605a633710bc50582", "exchange_order_id": "35404764", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:21:41,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a633710bc50582. +2023-09-18 18:21:41,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a636b8473c0582 for 80.00000000 SEI-USDT. +2023-09-18 18:21:41,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061301.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a636b8473c0582", "creation_timestamp": 1695061301.0, "exchange_order_id": "35404994", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:21:41,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a636b8445e0582 for 80.00000000 SEI-USDT. +2023-09-18 18:21:41,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061301.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a636b8445e0582", "creation_timestamp": 1695061301.0, "exchange_order_id": "35404993", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:22:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a636b8445e0582. [clock=2023-09-18 18:22:36+00:00] +2023-09-18 18:22:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a636b8473c0582. [clock=2023-09-18 18:22:36+00:00] +2023-09-18 18:22:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240095731665411059694085906 amount: 80. +2023-09-18 18:22:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243092668924313432346712582 amount: 80. +2023-09-18 18:22:36,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061356.0, "order_id": "x-XEKWYICXBSIUT605a636b8445e0582", "exchange_order_id": "35404993", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:22:36,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a636b8445e0582. +2023-09-18 18:22:36,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061356.0, "order_id": "x-XEKWYICXSSIUT605a636b8473c0582", "exchange_order_id": "35404994", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:22:36,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a636b8473c0582. +2023-09-18 18:22:36,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a639ff81770582 for 80.00000000 SEI-USDT. +2023-09-18 18:22:36,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a639ff81770582", "creation_timestamp": 1695061356.0, "exchange_order_id": "35405081", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:22:36,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a639ff7e1f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:22:36,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a639ff7e1f0582", "creation_timestamp": 1695061356.0, "exchange_order_id": "35405082", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:23:12,978 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a639ff7e1f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:23:12,978 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:23:12+00:00] +2023-09-18 18:23:13,001 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061392.0, "order_id": "x-XEKWYICXBSIUT605a639ff7e1f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4978282", "exchange_order_id": "35405082", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:23:13,013 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061392.0, "order_id": "x-XEKWYICXBSIUT605a639ff7e1f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35405082", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:23:13,014 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a639ff7e1f0582 completely filled. +2023-09-18 18:23:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a639ff81770582. [clock=2023-09-18 18:23:31+00:00] +2023-09-18 18:23:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238395124712397174050541108 amount: 80. +2023-09-18 18:23:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241910578843867042488748000 amount: 80. +2023-09-18 18:23:31,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061411.0, "order_id": "x-XEKWYICXSSIUT605a639ff81770582", "exchange_order_id": "35405081", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:23:31,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a639ff81770582. +2023-09-18 18:23:31,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a63d46bf410582 for 80.00000000 SEI-USDT. +2023-09-18 18:23:31,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a63d46bf410582", "creation_timestamp": 1695061411.0, "exchange_order_id": "35405404", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:23:31,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a63d46bc1f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:23:31,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a63d46bc1f0582", "creation_timestamp": 1695061411.0, "exchange_order_id": "35405405", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:24:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a63d46bc1f0582. [clock=2023-09-18 18:24:26+00:00] +2023-09-18 18:24:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a63d46bf410582. [clock=2023-09-18 18:24:26+00:00] +2023-09-18 18:24:26,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238752324804707054562726106 amount: 80. +2023-09-18 18:24:26,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241486493173566569297177574 amount: 80. +2023-09-18 18:24:26,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061466.0, "order_id": "x-XEKWYICXBSIUT605a63d46bc1f0582", "exchange_order_id": "35405405", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:24:26,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a63d46bc1f0582. +2023-09-18 18:24:26,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061466.0, "order_id": "x-XEKWYICXSSIUT605a63d46bf410582", "exchange_order_id": "35405404", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:24:26,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a63d46bf410582. +2023-09-18 18:24:26,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6408e3c0a0582 for 80.00000000 SEI-USDT. +2023-09-18 18:24:26,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a6408e3c0a0582", "creation_timestamp": 1695061466.0, "exchange_order_id": "35405528", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:24:26,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6408e3f690582 for 80.00000000 SEI-USDT. +2023-09-18 18:24:26,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a6408e3f690582", "creation_timestamp": 1695061466.0, "exchange_order_id": "35405529", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:25:15,042 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 18:25:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6408e3c0a0582. [clock=2023-09-18 18:25:21+00:00] +2023-09-18 18:25:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6408e3f690582. [clock=2023-09-18 18:25:21+00:00] +2023-09-18 18:25:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237626466380953449882052100 amount: 80. +2023-09-18 18:25:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240936102791578907437668162 amount: 80. +2023-09-18 18:25:21,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061521.0, "order_id": "x-XEKWYICXBSIUT605a6408e3c0a0582", "exchange_order_id": "35405528", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:25:21,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6408e3c0a0582. +2023-09-18 18:25:21,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a643d537a30582 for 80.00000000 SEI-USDT. +2023-09-18 18:25:21,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a643d537a30582", "creation_timestamp": 1695061521.0, "exchange_order_id": "35405761", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:25:21,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a643d53c250582 for 80.00000000 SEI-USDT. +2023-09-18 18:25:21,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a643d53c250582", "creation_timestamp": 1695061521.0, "exchange_order_id": "35405762", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:25:21,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061521.0, "order_id": "x-XEKWYICXSSIUT605a6408e3f690582", "exchange_order_id": "35405529", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:25:21,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6408e3f690582. +2023-09-18 18:26:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a643d537a30582. [clock=2023-09-18 18:26:16+00:00] +2023-09-18 18:26:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a643d53c250582. [clock=2023-09-18 18:26:16+00:00] +2023-09-18 18:26:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236426824802970596713690480 amount: 80. +2023-09-18 18:26:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239518432177187644406267896 amount: 80. +2023-09-18 18:26:16,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061576.0, "order_id": "x-XEKWYICXBSIUT605a643d537a30582", "exchange_order_id": "35405761", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:26:16,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a643d537a30582. +2023-09-18 18:26:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061576.0, "order_id": "x-XEKWYICXSSIUT605a643d53c250582", "exchange_order_id": "35405762", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:26:16,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a643d53c250582. +2023-09-18 18:26:16,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6471c6eea0582 for 80.00000000 SEI-USDT. +2023-09-18 18:26:16,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061576.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6471c6eea0582", "creation_timestamp": 1695061576.0, "exchange_order_id": "35406232", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:26:16,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6471c71030582 for 80.00000000 SEI-USDT. +2023-09-18 18:26:16,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061576.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a6471c71030582", "creation_timestamp": 1695061576.0, "exchange_order_id": "35406233", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:26:49,981 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a6471c71030582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:26:49,983 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:26:49+00:00] +2023-09-18 18:26:50,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061609.0, "order_id": "x-XEKWYICXSSIUT605a6471c71030582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4978350", "exchange_order_id": "35406233", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:26:50,040 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061609.0, "order_id": "x-XEKWYICXSSIUT605a6471c71030582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35406233", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:26:50,041 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a6471c71030582 completely filled. +2023-09-18 18:27:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6471c6eea0582. [clock=2023-09-18 18:27:11+00:00] +2023-09-18 18:27:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237807438626859933471038987 amount: 80. +2023-09-18 18:27:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240733174333850044318797126 amount: 80. +2023-09-18 18:27:11,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061631.0, "order_id": "x-XEKWYICXBSIUT605a6471c6eea0582", "exchange_order_id": "35406232", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:27:11,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6471c6eea0582. +2023-09-18 18:27:11,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a64a63acff0582 for 80.00000000 SEI-USDT. +2023-09-18 18:27:11,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a64a63acff0582", "creation_timestamp": 1695061631.0, "exchange_order_id": "35406531", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:27:11,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a64a63a9ac0582 for 80.00000000 SEI-USDT. +2023-09-18 18:27:11,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a64a63a9ac0582", "creation_timestamp": 1695061631.0, "exchange_order_id": "35406532", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:28:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a64a63a9ac0582. [clock=2023-09-18 18:28:06+00:00] +2023-09-18 18:28:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a64a63acff0582. [clock=2023-09-18 18:28:06+00:00] +2023-09-18 18:28:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234584547741847870865772926 amount: 80. +2023-09-18 18:28:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 18:28:06,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061686.0, "order_id": "x-XEKWYICXBSIUT605a64a63a9ac0582", "exchange_order_id": "35406532", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:28:06,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a64a63a9ac0582. +2023-09-18 18:28:06,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061686.0, "order_id": "x-XEKWYICXSSIUT605a64a63acff0582", "exchange_order_id": "35406531", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:28:06,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a64a63acff0582. +2023-09-18 18:28:06,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a64daae7800582 for 80.00000000 SEI-USDT. +2023-09-18 18:28:06,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a64daae7800582", "creation_timestamp": 1695061686.0, "exchange_order_id": "35406970", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:28:06,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a64daae9a80582 for 80.00000000 SEI-USDT. +2023-09-18 18:28:06,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a64daae9a80582", "creation_timestamp": 1695061686.0, "exchange_order_id": "35406971", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:29:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a64daae7800582. [clock=2023-09-18 18:29:01+00:00] +2023-09-18 18:29:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a64daae9a80582. [clock=2023-09-18 18:29:01+00:00] +2023-09-18 18:29:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234099449985152060864221516 amount: 80. +2023-09-18 18:29:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238075294118411427905174598 amount: 80. +2023-09-18 18:29:01,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061741.0, "order_id": "x-XEKWYICXBSIUT605a64daae7800582", "exchange_order_id": "35406970", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:29:01,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a64daae7800582. +2023-09-18 18:29:01,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061741.0, "order_id": "x-XEKWYICXSSIUT605a64daae9a80582", "exchange_order_id": "35406971", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:29:01,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a64daae9a80582. +2023-09-18 18:29:01,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a650f224680582 for 80.00000000 SEI-USDT. +2023-09-18 18:29:01,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a650f224680582", "creation_timestamp": 1695061741.0, "exchange_order_id": "35407101", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:29:01,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a650f2221f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:29:01,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a650f2221f0582", "creation_timestamp": 1695061741.0, "exchange_order_id": "35407104", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:29:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a650f2221f0582. [clock=2023-09-18 18:29:56+00:00] +2023-09-18 18:29:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a650f224680582. [clock=2023-09-18 18:29:56+00:00] +2023-09-18 18:29:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232774586487245825568324999 amount: 80. +2023-09-18 18:29:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237195313127127213371049305 amount: 80. +2023-09-18 18:29:56,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061796.0, "order_id": "x-XEKWYICXBSIUT605a650f2221f0582", "exchange_order_id": "35407104", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:29:56,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a650f2221f0582. +2023-09-18 18:29:56,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061796.0, "order_id": "x-XEKWYICXSSIUT605a650f224680582", "exchange_order_id": "35407101", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:29:56,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a650f224680582. +2023-09-18 18:29:56,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6543965680582 for 80.00000000 SEI-USDT. +2023-09-18 18:29:56,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a6543965680582", "creation_timestamp": 1695061796.0, "exchange_order_id": "35407508", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:29:56,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a65439611e0582 for 80.00000000 SEI-USDT. +2023-09-18 18:29:56,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a65439611e0582", "creation_timestamp": 1695061796.0, "exchange_order_id": "35407509", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:30:01,946 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a6543965680582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:30:01,947 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:30:01+00:00] +2023-09-18 18:30:01,977 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061801.0, "order_id": "x-XEKWYICXSSIUT605a6543965680582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00989600"}]}, "exchange_trade_id": "4978433", "exchange_order_id": "35407508", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:30:01,990 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061801.0, "order_id": "x-XEKWYICXSSIUT605a6543965680582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35407508", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:30:01,990 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a6543965680582 completely filled. +2023-09-18 18:30:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a65439611e0582. [clock=2023-09-18 18:30:51+00:00] +2023-09-18 18:30:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235923954536896210014297164 amount: 80. +2023-09-18 18:30:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240034505317405892394186905 amount: 80. +2023-09-18 18:30:51,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061851.0, "order_id": "x-XEKWYICXBSIUT605a65439611e0582", "exchange_order_id": "35407509", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:30:51,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a65439611e0582. +2023-09-18 18:30:51,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a657809c2f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:30:51,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a657809c2f0582", "creation_timestamp": 1695061851.0, "exchange_order_id": "35407712", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:30:51,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a657809eff0582 for 80.00000000 SEI-USDT. +2023-09-18 18:30:51,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a657809eff0582", "creation_timestamp": 1695061851.0, "exchange_order_id": "35407713", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:31:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a657809c2f0582. [clock=2023-09-18 18:31:46+00:00] +2023-09-18 18:31:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a657809eff0582. [clock=2023-09-18 18:31:46+00:00] +2023-09-18 18:31:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234991551980580075501905043 amount: 80. +2023-09-18 18:31:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 18:31:46,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061906.0, "order_id": "x-XEKWYICXBSIUT605a657809c2f0582", "exchange_order_id": "35407712", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:31:46,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a657809c2f0582. +2023-09-18 18:31:46,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061906.0, "order_id": "x-XEKWYICXSSIUT605a657809eff0582", "exchange_order_id": "35407713", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:31:46,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a657809eff0582. +2023-09-18 18:31:46,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a65ac7d5bf0582 for 80.00000000 SEI-USDT. +2023-09-18 18:31:46,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061906.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a65ac7d5bf0582", "creation_timestamp": 1695061906.0, "exchange_order_id": "35407890", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:32:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a65ac7d5bf0582. [clock=2023-09-18 18:32:41+00:00] +2023-09-18 18:32:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235019087511674890889622475 amount: 80. +2023-09-18 18:32:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238171393576594802718620749 amount: 80. +2023-09-18 18:32:41,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061961.0, "order_id": "x-XEKWYICXBSIUT605a65ac7d5bf0582", "exchange_order_id": "35407890", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:32:41,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a65ac7d5bf0582. +2023-09-18 18:32:41,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a65e0f109f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:32:41,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a65e0f109f0582", "creation_timestamp": 1695061961.0, "exchange_order_id": "35408363", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:32:41,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a65e0f14220582 for 80.00000000 SEI-USDT. +2023-09-18 18:32:41,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695061961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a65e0f14220582", "creation_timestamp": 1695061961.0, "exchange_order_id": "35408364", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:33:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a65e0f109f0582. [clock=2023-09-18 18:33:36+00:00] +2023-09-18 18:33:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a65e0f14220582. [clock=2023-09-18 18:33:36+00:00] +2023-09-18 18:33:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236112159105108894000249310 amount: 80. +2023-09-18 18:33:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 18:33:36,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062016.0, "order_id": "x-XEKWYICXBSIUT605a65e0f109f0582", "exchange_order_id": "35408363", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:33:36,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a65e0f109f0582. +2023-09-18 18:33:36,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062016.0, "order_id": "x-XEKWYICXSSIUT605a65e0f14220582", "exchange_order_id": "35408364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:33:36,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a65e0f14220582. +2023-09-18 18:33:36,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a661564d090582 for 80.00000000 SEI-USDT. +2023-09-18 18:33:36,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a661564d090582", "creation_timestamp": 1695062016.0, "exchange_order_id": "35408617", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:34:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a661564d090582. [clock=2023-09-18 18:34:31+00:00] +2023-09-18 18:34:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237713468524622010960813418 amount: 80. +2023-09-18 18:34:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241474697729899384398702862 amount: 80. +2023-09-18 18:34:31,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062071.0, "order_id": "x-XEKWYICXBSIUT605a661564d090582", "exchange_order_id": "35408617", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:34:31,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a661564d090582. +2023-09-18 18:34:31,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6649d85c30582 for 80.00000000 SEI-USDT. +2023-09-18 18:34:31,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6649d85c30582", "creation_timestamp": 1695062071.0, "exchange_order_id": "35409011", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:34:31,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6649d87e40582 for 80.00000000 SEI-USDT. +2023-09-18 18:34:31,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a6649d87e40582", "creation_timestamp": 1695062071.0, "exchange_order_id": "35409012", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:35:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6649d85c30582. [clock=2023-09-18 18:35:26+00:00] +2023-09-18 18:35:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6649d87e40582. [clock=2023-09-18 18:35:26+00:00] +2023-09-18 18:35:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238777078934572787904434475 amount: 80. +2023-09-18 18:35:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 18:35:26,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062126.0, "order_id": "x-XEKWYICXBSIUT605a6649d85c30582", "exchange_order_id": "35409011", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:35:26,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6649d85c30582. +2023-09-18 18:35:26,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a667e4c5cb0582 for 80.00000000 SEI-USDT. +2023-09-18 18:35:26,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a667e4c5cb0582", "creation_timestamp": 1695062126.0, "exchange_order_id": "35409183", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:35:26,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062126.0, "order_id": "x-XEKWYICXSSIUT605a6649d87e40582", "exchange_order_id": "35409012", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:35:26,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6649d87e40582. +2023-09-18 18:36:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a667e4c5cb0582. [clock=2023-09-18 18:36:21+00:00] +2023-09-18 18:36:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 18:36:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243631161726306437405387606 amount: 80. +2023-09-18 18:36:21,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062181.0, "order_id": "x-XEKWYICXBSIUT605a667e4c5cb0582", "exchange_order_id": "35409183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:36:21,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a667e4c5cb0582. +2023-09-18 18:36:21,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a66b2c03c00582 for 80.00000000 SEI-USDT. +2023-09-18 18:36:21,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a66b2c03c00582", "creation_timestamp": 1695062181.0, "exchange_order_id": "35409390", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:36:21,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a66b2c07ee0582 for 80.00000000 SEI-USDT. +2023-09-18 18:36:21,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a66b2c07ee0582", "creation_timestamp": 1695062181.0, "exchange_order_id": "35409391", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:37:16,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a66b2c03c00582. [clock=2023-09-18 18:37:16+00:00] +2023-09-18 18:37:16,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a66b2c07ee0582. [clock=2023-09-18 18:37:16+00:00] +2023-09-18 18:37:16,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239979565675528711054156020 amount: 80. +2023-09-18 18:37:16,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 18:37:16,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062236.0, "order_id": "x-XEKWYICXBSIUT605a66b2c03c00582", "exchange_order_id": "35409390", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:37:16,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a66b2c03c00582. +2023-09-18 18:37:16,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062236.0, "order_id": "x-XEKWYICXSSIUT605a66b2c07ee0582", "exchange_order_id": "35409391", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:37:16,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a66b2c07ee0582. +2023-09-18 18:37:16,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a66e73af330582 for 80.00000000 SEI-USDT. +2023-09-18 18:37:16,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a66e73af330582", "creation_timestamp": 1695062236.0, "exchange_order_id": "35409480", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:38:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a66e73af330582. [clock=2023-09-18 18:38:11+00:00] +2023-09-18 18:38:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 18:38:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242757654166651881883844200 amount: 80. +2023-09-18 18:38:11,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062291.0, "order_id": "x-XEKWYICXBSIUT605a66e73af330582", "exchange_order_id": "35409480", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:38:11,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a66e73af330582. +2023-09-18 18:38:11,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a671ba75f40582 for 80.00000000 SEI-USDT. +2023-09-18 18:38:11,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a671ba75f40582", "creation_timestamp": 1695062291.0, "exchange_order_id": "35409695", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:38:11,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a671ba78670582 for 80.00000000 SEI-USDT. +2023-09-18 18:38:11,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a671ba78670582", "creation_timestamp": 1695062291.0, "exchange_order_id": "35409696", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:39:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a671ba75f40582. [clock=2023-09-18 18:39:06+00:00] +2023-09-18 18:39:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a671ba78670582. [clock=2023-09-18 18:39:06+00:00] +2023-09-18 18:39:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 18:39:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 18:39:06,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062346.0, "order_id": "x-XEKWYICXBSIUT605a671ba75f40582", "exchange_order_id": "35409695", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:39:06,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a671ba75f40582. +2023-09-18 18:39:06,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062346.0, "order_id": "x-XEKWYICXSSIUT605a671ba78670582", "exchange_order_id": "35409696", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:39:06,343 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a671ba78670582. +2023-09-18 18:39:06,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a67501b8260582 for 80.00000000 SEI-USDT. +2023-09-18 18:39:06,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a67501b8260582", "creation_timestamp": 1695062346.0, "exchange_order_id": "35409808", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:39:15,743 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a67501b8260582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:39:15,744 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:39:15+00:00] +2023-09-18 18:39:15,771 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062355.0, "order_id": "x-XEKWYICXBSIUT605a67501b8260582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4978582", "exchange_order_id": "35409808", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:39:15,783 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062355.0, "order_id": "x-XEKWYICXBSIUT605a67501b8260582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35409808", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:39:15,784 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a67501b8260582 completely filled. +2023-09-18 18:40:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238147613910849967900754314 amount: 80. +2023-09-18 18:40:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241514172307378621692717702 amount: 80. +2023-09-18 18:40:01,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a67848e9ca0582 for 80.00000000 SEI-USDT. +2023-09-18 18:40:01,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a67848e9ca0582", "creation_timestamp": 1695062401.0, "exchange_order_id": "35410048", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:40:01,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a67848ee6b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:40:01,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a67848ee6b0582", "creation_timestamp": 1695062401.0, "exchange_order_id": "35410049", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:40:20,334 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a67848e9ca0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:40:20,334 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:40:20+00:00] +2023-09-18 18:40:20,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062420.0, "order_id": "x-XEKWYICXBSIUT605a67848e9ca0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4978635", "exchange_order_id": "35410048", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:40:20,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062420.0, "order_id": "x-XEKWYICXBSIUT605a67848e9ca0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35410048", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:40:20,372 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a67848e9ca0582 completely filled. +2023-09-18 18:40:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a67848ee6b0582. [clock=2023-09-18 18:40:56+00:00] +2023-09-18 18:40:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236837933982583565128061617 amount: 80. +2023-09-18 18:40:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240119899301455839184972274 amount: 80. +2023-09-18 18:40:56,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062456.0, "order_id": "x-XEKWYICXSSIUT605a67848ee6b0582", "exchange_order_id": "35410049", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:40:56,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a67848ee6b0582. +2023-09-18 18:40:56,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a67b902de80582 for 80.00000000 SEI-USDT. +2023-09-18 18:40:56,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a67b902de80582", "creation_timestamp": 1695062456.0, "exchange_order_id": "35410499", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:40:56,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a67b9028e30582 for 80.00000000 SEI-USDT. +2023-09-18 18:40:56,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a67b9028e30582", "creation_timestamp": 1695062456.0, "exchange_order_id": "35410500", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:41:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a67b9028e30582. [clock=2023-09-18 18:41:51+00:00] +2023-09-18 18:41:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a67b902de80582. [clock=2023-09-18 18:41:51+00:00] +2023-09-18 18:41:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236860805808214309608449305 amount: 80. +2023-09-18 18:41:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240079809412486580599215699 amount: 80. +2023-09-18 18:41:51,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062511.0, "order_id": "x-XEKWYICXBSIUT605a67b9028e30582", "exchange_order_id": "35410500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:41:51,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a67b9028e30582. +2023-09-18 18:41:51,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a67ed767880582 for 80.00000000 SEI-USDT. +2023-09-18 18:41:51,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a67ed767880582", "creation_timestamp": 1695062511.0, "exchange_order_id": "35410994", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:41:51,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a67ed769c30582 for 80.00000000 SEI-USDT. +2023-09-18 18:41:51,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a67ed769c30582", "creation_timestamp": 1695062511.0, "exchange_order_id": "35410995", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:41:51,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062511.0, "order_id": "x-XEKWYICXSSIUT605a67b902de80582", "exchange_order_id": "35410499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:41:51,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a67b902de80582. +2023-09-18 18:42:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a67ed767880582. [clock=2023-09-18 18:42:46+00:00] +2023-09-18 18:42:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a67ed769c30582. [clock=2023-09-18 18:42:46+00:00] +2023-09-18 18:42:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236598145634309651403713898 amount: 80. +2023-09-18 18:42:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239770024161253518450355302 amount: 80. +2023-09-18 18:42:46,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062566.0, "order_id": "x-XEKWYICXBSIUT605a67ed767880582", "exchange_order_id": "35410994", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:42:46,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a67ed767880582. +2023-09-18 18:42:46,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6821ea60b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:42:46,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a6821ea60b0582", "creation_timestamp": 1695062566.0, "exchange_order_id": "35411351", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:42:46,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062566.0, "order_id": "x-XEKWYICXSSIUT605a67ed769c30582", "exchange_order_id": "35410995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:42:46,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a67ed769c30582. +2023-09-18 18:42:46,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6821ea2fa0582 for 80.00000000 SEI-USDT. +2023-09-18 18:42:46,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6821ea2fa0582", "creation_timestamp": 1695062566.0, "exchange_order_id": "35411352", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:43:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6821ea2fa0582. [clock=2023-09-18 18:43:41+00:00] +2023-09-18 18:43:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6821ea60b0582. [clock=2023-09-18 18:43:41+00:00] +2023-09-18 18:43:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235063076324489551356762993 amount: 80. +2023-09-18 18:43:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238862945882945729666946147 amount: 80. +2023-09-18 18:43:41,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062621.0, "order_id": "x-XEKWYICXBSIUT605a6821ea2fa0582", "exchange_order_id": "35411352", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:43:41,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6821ea2fa0582. +2023-09-18 18:43:41,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a68565e22f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:43:41,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a68565e22f0582", "creation_timestamp": 1695062621.0, "exchange_order_id": "35411778", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:43:41,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a68565dfba0582 for 80.00000000 SEI-USDT. +2023-09-18 18:43:41,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a68565dfba0582", "creation_timestamp": 1695062621.0, "exchange_order_id": "35411779", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:43:41,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062621.0, "order_id": "x-XEKWYICXSSIUT605a6821ea60b0582", "exchange_order_id": "35411351", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:43:41,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6821ea60b0582. +2023-09-18 18:44:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a68565dfba0582. [clock=2023-09-18 18:44:36+00:00] +2023-09-18 18:44:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a68565e22f0582. [clock=2023-09-18 18:44:36+00:00] +2023-09-18 18:44:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235154350418858257201144577 amount: 80. +2023-09-18 18:44:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238776458259737437780056035 amount: 80. +2023-09-18 18:44:36,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062676.0, "order_id": "x-XEKWYICXBSIUT605a68565dfba0582", "exchange_order_id": "35411779", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:44:36,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a68565dfba0582. +2023-09-18 18:44:36,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a688ad1eb70582 for 80.00000000 SEI-USDT. +2023-09-18 18:44:36,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a688ad1eb70582", "creation_timestamp": 1695062676.0, "exchange_order_id": "35412104", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:44:36,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062676.0, "order_id": "x-XEKWYICXSSIUT605a68565e22f0582", "exchange_order_id": "35411778", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:44:36,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a68565e22f0582. +2023-09-18 18:44:36,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a688ad222c0582 for 80.00000000 SEI-USDT. +2023-09-18 18:44:36,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a688ad222c0582", "creation_timestamp": 1695062676.0, "exchange_order_id": "35412105", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:45:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a688ad1eb70582. [clock=2023-09-18 18:45:31+00:00] +2023-09-18 18:45:31,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a688ad222c0582. [clock=2023-09-18 18:45:31+00:00] +2023-09-18 18:45:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233109082319730594562401386 amount: 80. +2023-09-18 18:45:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12370000 amount: 80. +2023-09-18 18:45:31,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062731.0, "order_id": "x-XEKWYICXBSIUT605a688ad1eb70582", "exchange_order_id": "35412104", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:45:31,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a688ad1eb70582. +2023-09-18 18:45:31,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a68bf45ad70582 for 80.00000000 SEI-USDT. +2023-09-18 18:45:31,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a68bf45ad70582", "creation_timestamp": 1695062731.0, "exchange_order_id": "35412491", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:45:31,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a68bf45d890582 for 80.00000000 SEI-USDT. +2023-09-18 18:45:31,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a68bf45d890582", "creation_timestamp": 1695062731.0, "exchange_order_id": "35412492", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:45:31,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062731.0, "order_id": "x-XEKWYICXSSIUT605a688ad222c0582", "exchange_order_id": "35412105", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:45:31,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a688ad222c0582. +2023-09-18 18:45:42,326 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a68bf45d890582 amounting to 79.90000000/80.00000000 SEI has been filled. +2023-09-18 18:45:42,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 79.90 SEI-USDT binance at 0.12 [clock=2023-09-18 18:45:42+00:00] +2023-09-18 18:45:42,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062742.0, "order_id": "x-XEKWYICXSSIUT605a68bf45d890582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "79.90000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00988363"}]}, "exchange_trade_id": "4978790", "exchange_order_id": "35412492", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:45:42,431 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a68bf45d890582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:45:42,432 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 0.10 SEI-USDT binance at 0.12 [clock=2023-09-18 18:45:42+00:00] +2023-09-18 18:45:42,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062742.0, "order_id": "x-XEKWYICXSSIUT605a68bf45d890582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "0.10000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00001237"}]}, "exchange_trade_id": "4978791", "exchange_order_id": "35412492", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:45:42,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062742.0, "order_id": "x-XEKWYICXSSIUT605a68bf45d890582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35412492", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:45:42,464 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a68bf45d890582 completely filled. +2023-09-18 18:46:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a68bf45ad70582. [clock=2023-09-18 18:46:26+00:00] +2023-09-18 18:46:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235595463081025147436742564 amount: 80. +2023-09-18 18:46:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239492091905153266907034336 amount: 80. +2023-09-18 18:46:26,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062786.0, "order_id": "x-XEKWYICXBSIUT605a68bf45ad70582", "exchange_order_id": "35412491", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:46:26,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a68bf45ad70582. +2023-09-18 18:46:26,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a68f3b94240582 for 80.00000000 SEI-USDT. +2023-09-18 18:46:26,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a68f3b94240582", "creation_timestamp": 1695062786.0, "exchange_order_id": "35412725", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:46:26,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a68f3b90550582 for 80.00000000 SEI-USDT. +2023-09-18 18:46:26,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a68f3b90550582", "creation_timestamp": 1695062786.0, "exchange_order_id": "35412726", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:47:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a68f3b90550582. [clock=2023-09-18 18:47:21+00:00] +2023-09-18 18:47:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a68f3b94240582. [clock=2023-09-18 18:47:21+00:00] +2023-09-18 18:47:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237143978337560481279229157 amount: 80. +2023-09-18 18:47:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240174971680759543896418087 amount: 80. +2023-09-18 18:47:21,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062841.0, "order_id": "x-XEKWYICXBSIUT605a68f3b90550582", "exchange_order_id": "35412726", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:47:21,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a68f3b90550582. +2023-09-18 18:47:21,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062841.0, "order_id": "x-XEKWYICXSSIUT605a68f3b94240582", "exchange_order_id": "35412725", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:47:21,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a68f3b94240582. +2023-09-18 18:47:21,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a69282d5970582 for 80.00000000 SEI-USDT. +2023-09-18 18:47:21,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a69282d5970582", "creation_timestamp": 1695062841.0, "exchange_order_id": "35412981", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:47:21,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a69282d7ee0582 for 80.00000000 SEI-USDT. +2023-09-18 18:47:21,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a69282d7ee0582", "creation_timestamp": 1695062841.0, "exchange_order_id": "35412980", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:47:56,027 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a69282d5970582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:47:56,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:47:56+00:00] +2023-09-18 18:47:56,056 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062876.0, "order_id": "x-XEKWYICXBSIUT605a69282d5970582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4978847", "exchange_order_id": "35412981", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:47:56,068 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062876.0, "order_id": "x-XEKWYICXBSIUT605a69282d5970582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35412981", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:47:56,068 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a69282d5970582 completely filled. +2023-09-18 18:48:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a69282d7ee0582. [clock=2023-09-18 18:48:16+00:00] +2023-09-18 18:48:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235646749206808064834086035 amount: 80. +2023-09-18 18:48:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238521647538624402420617851 amount: 80. +2023-09-18 18:48:16,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062896.0, "order_id": "x-XEKWYICXSSIUT605a69282d7ee0582", "exchange_order_id": "35412980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:48:16,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a69282d7ee0582. +2023-09-18 18:48:16,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a695ca09d70582 for 80.00000000 SEI-USDT. +2023-09-18 18:48:16,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a695ca09d70582", "creation_timestamp": 1695062896.0, "exchange_order_id": "35413387", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:48:16,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a695ca0d380582 for 80.00000000 SEI-USDT. +2023-09-18 18:48:16,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a695ca0d380582", "creation_timestamp": 1695062896.0, "exchange_order_id": "35413388", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:49:11,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a695ca09d70582. [clock=2023-09-18 18:49:11+00:00] +2023-09-18 18:49:11,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a695ca0d380582. [clock=2023-09-18 18:49:11+00:00] +2023-09-18 18:49:11,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235703883580473046824665767 amount: 80. +2023-09-18 18:49:11,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238458462692416808170672054 amount: 80. +2023-09-18 18:49:11,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062951.0, "order_id": "x-XEKWYICXBSIUT605a695ca09d70582", "exchange_order_id": "35413387", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:49:11,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a695ca09d70582. +2023-09-18 18:49:11,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a699138f200582 for 80.00000000 SEI-USDT. +2023-09-18 18:49:11,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a699138f200582", "creation_timestamp": 1695062951.0, "exchange_order_id": "35413632", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:49:11,375 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062951.0, "order_id": "x-XEKWYICXSSIUT605a695ca0d380582", "exchange_order_id": "35413388", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:49:11,376 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a695ca0d380582. +2023-09-18 18:49:11,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a69913923e0582 for 80.00000000 SEI-USDT. +2023-09-18 18:49:11,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695062951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a69913923e0582", "creation_timestamp": 1695062951.0, "exchange_order_id": "35413633", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:50:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a699138f200582. [clock=2023-09-18 18:50:06+00:00] +2023-09-18 18:50:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a69913923e0582. [clock=2023-09-18 18:50:06+00:00] +2023-09-18 18:50:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236094486870837906378038564 amount: 80. +2023-09-18 18:50:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238754077841727658555683979 amount: 80. +2023-09-18 18:50:06,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063006.0, "order_id": "x-XEKWYICXBSIUT605a699138f200582", "exchange_order_id": "35413632", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:50:06,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a699138f200582. +2023-09-18 18:50:06,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063006.0, "order_id": "x-XEKWYICXSSIUT605a69913923e0582", "exchange_order_id": "35413633", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:50:06,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a69913923e0582. +2023-09-18 18:50:06,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a69c5884900582 for 80.00000000 SEI-USDT. +2023-09-18 18:50:06,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a69c5884900582", "creation_timestamp": 1695063006.0, "exchange_order_id": "35413907", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:50:06,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a69c5886890582 for 80.00000000 SEI-USDT. +2023-09-18 18:50:06,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a69c5886890582", "creation_timestamp": 1695063006.0, "exchange_order_id": "35413908", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:50:23,269 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a69c5886890582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:50:23,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:50:23+00:00] +2023-09-18 18:50:23,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063023.0, "order_id": "x-XEKWYICXSSIUT605a69c5886890582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4978918", "exchange_order_id": "35413908", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:50:23,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063023.0, "order_id": "x-XEKWYICXSSIUT605a69c5886890582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35413908", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:50:23,326 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a69c5886890582 completely filled. +2023-09-18 18:51:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a69c5884900582. [clock=2023-09-18 18:51:01+00:00] +2023-09-18 18:51:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234896886874039831060610411 amount: 80. +2023-09-18 18:51:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238223198426351685179924679 amount: 80. +2023-09-18 18:51:01,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063061.0, "order_id": "x-XEKWYICXBSIUT605a69c5884900582", "exchange_order_id": "35413907", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:51:01,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a69c5884900582. +2023-09-18 18:51:01,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a69f9fc14b0582 for 80.00000000 SEI-USDT. +2023-09-18 18:51:01,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a69f9fc14b0582", "creation_timestamp": 1695063061.0, "exchange_order_id": "35414173", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:51:01,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a69f9fbca10582 for 80.00000000 SEI-USDT. +2023-09-18 18:51:01,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a69f9fbca10582", "creation_timestamp": 1695063061.0, "exchange_order_id": "35414174", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:51:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a69f9fbca10582. [clock=2023-09-18 18:51:56+00:00] +2023-09-18 18:51:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a69f9fc14b0582. [clock=2023-09-18 18:51:56+00:00] +2023-09-18 18:51:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234921498118281140821818043 amount: 80. +2023-09-18 18:51:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238175027888558054970200637 amount: 80. +2023-09-18 18:51:56,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063116.0, "order_id": "x-XEKWYICXBSIUT605a69f9fbca10582", "exchange_order_id": "35414174", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:51:56,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a69f9fbca10582. +2023-09-18 18:51:56,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6a2e6f8b30582 for 80.00000000 SEI-USDT. +2023-09-18 18:51:56,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a6a2e6f8b30582", "creation_timestamp": 1695063116.0, "exchange_order_id": "35414346", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:51:56,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6a2e6fba20582 for 80.00000000 SEI-USDT. +2023-09-18 18:51:56,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a6a2e6fba20582", "creation_timestamp": 1695063116.0, "exchange_order_id": "35414347", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:51:56,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063116.0, "order_id": "x-XEKWYICXSSIUT605a69f9fc14b0582", "exchange_order_id": "35414173", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:51:56,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a69f9fc14b0582. +2023-09-18 18:52:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6a2e6f8b30582. [clock=2023-09-18 18:52:51+00:00] +2023-09-18 18:52:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6a2e6fba20582. [clock=2023-09-18 18:52:51+00:00] +2023-09-18 18:52:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234688653278914648218784781 amount: 80. +2023-09-18 18:52:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237887512960130217851464677 amount: 80. +2023-09-18 18:52:51,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063171.0, "order_id": "x-XEKWYICXBSIUT605a6a2e6f8b30582", "exchange_order_id": "35414346", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:52:51,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6a2e6f8b30582. +2023-09-18 18:52:51,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063171.0, "order_id": "x-XEKWYICXSSIUT605a6a2e6fba20582", "exchange_order_id": "35414347", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:52:51,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6a2e6fba20582. +2023-09-18 18:52:51,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6a62e3bf40582 for 80.00000000 SEI-USDT. +2023-09-18 18:52:51,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a6a62e3bf40582", "creation_timestamp": 1695063171.0, "exchange_order_id": "35414499", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:52:51,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6a62e38710582 for 80.00000000 SEI-USDT. +2023-09-18 18:52:51,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a6a62e38710582", "creation_timestamp": 1695063171.0, "exchange_order_id": "35414500", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:53:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6a62e38710582. [clock=2023-09-18 18:53:46+00:00] +2023-09-18 18:53:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6a62e3bf40582. [clock=2023-09-18 18:53:46+00:00] +2023-09-18 18:53:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234980198286350125604889753 amount: 80. +2023-09-18 18:53:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237468071566329144274938169 amount: 80. +2023-09-18 18:53:46,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063226.0, "order_id": "x-XEKWYICXBSIUT605a6a62e38710582", "exchange_order_id": "35414500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:53:46,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6a62e38710582. +2023-09-18 18:53:46,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063226.0, "order_id": "x-XEKWYICXSSIUT605a6a62e3bf40582", "exchange_order_id": "35414499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:53:46,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6a62e3bf40582. +2023-09-18 18:53:46,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6a97574bd0582 for 80.00000000 SEI-USDT. +2023-09-18 18:53:46,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a6a97574bd0582", "creation_timestamp": 1695063226.0, "exchange_order_id": "35414627", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:53:46,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6a97572910582 for 80.00000000 SEI-USDT. +2023-09-18 18:53:46,327 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a6a97572910582", "creation_timestamp": 1695063226.0, "exchange_order_id": "35414628", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:54:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6a97572910582. [clock=2023-09-18 18:54:41+00:00] +2023-09-18 18:54:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6a97574bd0582. [clock=2023-09-18 18:54:41+00:00] +2023-09-18 18:54:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233423784374299141074412992 amount: 80. +2023-09-18 18:54:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237211271489303125467771924 amount: 80. +2023-09-18 18:54:41,075 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063281.0, "order_id": "x-XEKWYICXBSIUT605a6a97572910582", "exchange_order_id": "35414628", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:54:41,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6a97572910582. +2023-09-18 18:54:41,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6acbcacd50582 for 80.00000000 SEI-USDT. +2023-09-18 18:54:41,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a6acbcacd50582", "creation_timestamp": 1695063281.0, "exchange_order_id": "35414796", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:54:41,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063281.0, "order_id": "x-XEKWYICXSSIUT605a6a97574bd0582", "exchange_order_id": "35414627", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:54:41,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6a97574bd0582. +2023-09-18 18:54:41,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6acbcb01f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:54:41,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a6acbcb01f0582", "creation_timestamp": 1695063281.0, "exchange_order_id": "35414797", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:55:15,055 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 18:55:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6acbcacd50582. [clock=2023-09-18 18:55:36+00:00] +2023-09-18 18:55:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6acbcb01f0582. [clock=2023-09-18 18:55:36+00:00] +2023-09-18 18:55:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233774660171313272833308226 amount: 80. +2023-09-18 18:55:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236720316461691096192206526 amount: 80. +2023-09-18 18:55:36,123 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063336.0, "order_id": "x-XEKWYICXBSIUT605a6acbcacd50582", "exchange_order_id": "35414796", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:55:36,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6acbcacd50582. +2023-09-18 18:55:36,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063336.0, "order_id": "x-XEKWYICXSSIUT605a6acbcb01f0582", "exchange_order_id": "35414797", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:55:36,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6acbcb01f0582. +2023-09-18 18:55:36,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6b003fa350582 for 80.00000000 SEI-USDT. +2023-09-18 18:55:36,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605a6b003fa350582", "creation_timestamp": 1695063336.0, "exchange_order_id": "35414915", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:55:36,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6b003f71a0582 for 80.00000000 SEI-USDT. +2023-09-18 18:55:36,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a6b003f71a0582", "creation_timestamp": 1695063336.0, "exchange_order_id": "35414916", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:56:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6b003f71a0582. [clock=2023-09-18 18:56:31+00:00] +2023-09-18 18:56:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6b003fa350582. [clock=2023-09-18 18:56:31+00:00] +2023-09-18 18:56:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234047070573460680475744461 amount: 80. +2023-09-18 18:56:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236338034685536420632233239 amount: 80. +2023-09-18 18:56:31,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063391.0, "order_id": "x-XEKWYICXBSIUT605a6b003f71a0582", "exchange_order_id": "35414916", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:56:31,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6b003f71a0582. +2023-09-18 18:56:31,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6b34b2a580582 for 80.00000000 SEI-USDT. +2023-09-18 18:56:31,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605a6b34b2a580582", "creation_timestamp": 1695063391.0, "exchange_order_id": "35415041", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:56:31,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6b34b269d0582 for 80.00000000 SEI-USDT. +2023-09-18 18:56:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a6b34b269d0582", "creation_timestamp": 1695063391.0, "exchange_order_id": "35415040", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:56:31,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063391.0, "order_id": "x-XEKWYICXSSIUT605a6b003fa350582", "exchange_order_id": "35414915", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:56:31,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6b003fa350582. +2023-09-18 18:57:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6b34b269d0582. [clock=2023-09-18 18:57:26+00:00] +2023-09-18 18:57:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6b34b2a580582. [clock=2023-09-18 18:57:26+00:00] +2023-09-18 18:57:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232982284308997315770410508 amount: 80. +2023-09-18 18:57:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235428959413500821400220553 amount: 80. +2023-09-18 18:57:26,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063446.0, "order_id": "x-XEKWYICXBSIUT605a6b34b269d0582", "exchange_order_id": "35415040", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:57:26,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6b34b269d0582. +2023-09-18 18:57:26,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6b692629d0582 for 80.00000000 SEI-USDT. +2023-09-18 18:57:26,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6b692629d0582", "creation_timestamp": 1695063446.0, "exchange_order_id": "35415298", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:57:26,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063446.0, "order_id": "x-XEKWYICXSSIUT605a6b34b2a580582", "exchange_order_id": "35415041", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:57:26,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6b34b2a580582. +2023-09-18 18:57:26,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6b69264e60582 for 80.00000000 SEI-USDT. +2023-09-18 18:57:26,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605a6b69264e60582", "creation_timestamp": 1695063446.0, "exchange_order_id": "35415299", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:58:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6b692629d0582. [clock=2023-09-18 18:58:21+00:00] +2023-09-18 18:58:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6b69264e60582. [clock=2023-09-18 18:58:21+00:00] +2023-09-18 18:58:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232612919076728987909391253 amount: 80. +2023-09-18 18:58:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12350000 amount: 80. +2023-09-18 18:58:21,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063501.0, "order_id": "x-XEKWYICXBSIUT605a6b692629d0582", "exchange_order_id": "35415298", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:58:21,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6b692629d0582. +2023-09-18 18:58:21,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063501.0, "order_id": "x-XEKWYICXSSIUT605a6b69264e60582", "exchange_order_id": "35415299", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:58:21,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6b69264e60582. +2023-09-18 18:58:21,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6b9d99d310582 for 80.00000000 SEI-USDT. +2023-09-18 18:58:21,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6b9d99d310582", "creation_timestamp": 1695063501.0, "exchange_order_id": "35415630", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:58:21,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6b9d99f110582 for 80.00000000 SEI-USDT. +2023-09-18 18:58:21,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605a6b9d99f110582", "creation_timestamp": 1695063501.0, "exchange_order_id": "35415631", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:58:33,444 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a6b9d99f110582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:58:33,445 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:58:33+00:00] +2023-09-18 18:58:33,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063513.0, "order_id": "x-XEKWYICXSSIUT605a6b9d99f110582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12350000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00988000"}]}, "exchange_trade_id": "4979035", "exchange_order_id": "35415631", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:58:33,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063513.0, "order_id": "x-XEKWYICXSSIUT605a6b9d99f110582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35415631", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:58:33,490 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a6b9d99f110582 completely filled. +2023-09-18 18:59:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6b9d99d310582. [clock=2023-09-18 18:59:16+00:00] +2023-09-18 18:59:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232744998586869498139365539 amount: 80. +2023-09-18 18:59:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12350000 amount: 80. +2023-09-18 18:59:16,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063556.0, "order_id": "x-XEKWYICXBSIUT605a6b9d99d310582", "exchange_order_id": "35415630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 18:59:16,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6b9d99d310582. +2023-09-18 18:59:16,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6bd20dc4f0582 for 80.00000000 SEI-USDT. +2023-09-18 18:59:16,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605a6bd20dc4f0582", "creation_timestamp": 1695063556.0, "exchange_order_id": "35415864", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:59:16,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6bd20d9110582 for 80.00000000 SEI-USDT. +2023-09-18 18:59:16,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6bd20d9110582", "creation_timestamp": 1695063556.0, "exchange_order_id": "35415863", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 18:59:34,846 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a6bd20dc4f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 18:59:34,847 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 18:59:34+00:00] +2023-09-18 18:59:34,873 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063574.0, "order_id": "x-XEKWYICXSSIUT605a6bd20dc4f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12350000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00988000"}]}, "exchange_trade_id": "4979046", "exchange_order_id": "35415864", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 18:59:34,884 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063574.0, "order_id": "x-XEKWYICXSSIUT605a6bd20dc4f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35415864", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 18:59:34,884 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a6bd20dc4f0582 completely filled. +2023-09-18 19:00:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6bd20d9110582. [clock=2023-09-18 19:00:11+00:00] +2023-09-18 19:00:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232452128729617746820649103 amount: 80. +2023-09-18 19:00:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:00:11,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063611.0, "order_id": "x-XEKWYICXBSIUT605a6bd20d9110582", "exchange_order_id": "35415863", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:00:11,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6bd20d9110582. +2023-09-18 19:00:11,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6c06811d70582 for 80.00000000 SEI-USDT. +2023-09-18 19:00:11,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6c06811d70582", "creation_timestamp": 1695063611.0, "exchange_order_id": "35416101", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:01:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6c06811d70582. [clock=2023-09-18 19:01:06+00:00] +2023-09-18 19:01:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231330130192468128180142713 amount: 80. +2023-09-18 19:01:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:01:06,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063666.0, "order_id": "x-XEKWYICXBSIUT605a6c06811d70582", "exchange_order_id": "35416101", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:01:06,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6c06811d70582. +2023-09-18 19:01:06,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6c3af4a150582 for 80.00000000 SEI-USDT. +2023-09-18 19:01:06,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063666.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605a6c3af4a150582", "creation_timestamp": 1695063666.0, "exchange_order_id": "35416616", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:02:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6c3af4a150582. [clock=2023-09-18 19:02:01+00:00] +2023-09-18 19:02:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229979954824050621242845771 amount: 80. +2023-09-18 19:02:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:02:01,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063721.0, "order_id": "x-XEKWYICXBSIUT605a6c3af4a150582", "exchange_order_id": "35416616", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:02:01,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6c3af4a150582. +2023-09-18 19:02:01,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6c6f68c4b0582 for 80.00000000 SEI-USDT. +2023-09-18 19:02:01,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605a6c6f68c4b0582", "creation_timestamp": 1695063721.0, "exchange_order_id": "35416996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:02:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6c6f68c4b0582. [clock=2023-09-18 19:02:56+00:00] +2023-09-18 19:02:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232438137645708208607411995 amount: 80. +2023-09-18 19:02:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:02:56,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063776.0, "order_id": "x-XEKWYICXBSIUT605a6c6f68c4b0582", "exchange_order_id": "35416996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:02:56,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6c6f68c4b0582. +2023-09-18 19:02:56,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6ca3dc45b0582 for 80.00000000 SEI-USDT. +2023-09-18 19:02:56,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6ca3dc45b0582", "creation_timestamp": 1695063776.0, "exchange_order_id": "35417307", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:03:50,952 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a6ca3dc45b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:03:50,953 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:03:50+00:00] +2023-09-18 19:03:50,978 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063830.0, "order_id": "x-XEKWYICXBSIUT605a6ca3dc45b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4979255", "exchange_order_id": "35417307", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:03:50,991 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063830.0, "order_id": "x-XEKWYICXBSIUT605a6ca3dc45b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35417307", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:03:50,992 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a6ca3dc45b0582 completely filled. +2023-09-18 19:03:51,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230817896889100649057432974 amount: 80. +2023-09-18 19:03:51,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234841057342433473903317599 amount: 80. +2023-09-18 19:03:51,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6cd8590b80582 for 80.00000000 SEI-USDT. +2023-09-18 19:03:51,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605a6cd8590b80582", "creation_timestamp": 1695063831.0, "exchange_order_id": "35417700", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:03:51,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6cd8594470582 for 80.00000000 SEI-USDT. +2023-09-18 19:03:51,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605a6cd8594470582", "creation_timestamp": 1695063831.0, "exchange_order_id": "35417704", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:04:46,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6cd8590b80582. [clock=2023-09-18 19:04:46+00:00] +2023-09-18 19:04:46,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6cd8594470582. [clock=2023-09-18 19:04:46+00:00] +2023-09-18 19:04:46,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232039267808494118024271937 amount: 80. +2023-09-18 19:04:46,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:04:46,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063886.0, "order_id": "x-XEKWYICXBSIUT605a6cd8590b80582", "exchange_order_id": "35417700", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:04:46,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6cd8590b80582. +2023-09-18 19:04:46,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063886.0, "order_id": "x-XEKWYICXSSIUT605a6cd8594470582", "exchange_order_id": "35417704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:04:46,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6cd8594470582. +2023-09-18 19:04:46,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6d0ccb4860582 for 80.00000000 SEI-USDT. +2023-09-18 19:04:46,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605a6d0ccb4860582", "creation_timestamp": 1695063886.0, "exchange_order_id": "35417939", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:05:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6d0ccb4860582. [clock=2023-09-18 19:05:41+00:00] +2023-09-18 19:05:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233169236538697901799079931 amount: 80. +2023-09-18 19:05:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237600072659498231805311833 amount: 80. +2023-09-18 19:05:41,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063941.0, "order_id": "x-XEKWYICXBSIUT605a6d0ccb4860582", "exchange_order_id": "35417939", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:05:41,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6d0ccb4860582. +2023-09-18 19:05:41,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6d41377900582 for 80.00000000 SEI-USDT. +2023-09-18 19:05:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a6d41377900582", "creation_timestamp": 1695063941.0, "exchange_order_id": "35418162", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:05:41,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6d4137ab00582 for 80.00000000 SEI-USDT. +2023-09-18 19:05:41,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a6d4137ab00582", "creation_timestamp": 1695063941.0, "exchange_order_id": "35418163", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:06:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6d41377900582. [clock=2023-09-18 19:06:36+00:00] +2023-09-18 19:06:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6d4137ab00582. [clock=2023-09-18 19:06:36+00:00] +2023-09-18 19:06:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234535452750614234223330812 amount: 80. +2023-09-18 19:06:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:06:36,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063996.0, "order_id": "x-XEKWYICXBSIUT605a6d41377900582", "exchange_order_id": "35418162", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:06:36,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6d41377900582. +2023-09-18 19:06:36,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063996.0, "order_id": "x-XEKWYICXSSIUT605a6d4137ab00582", "exchange_order_id": "35418163", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:06:36,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6d4137ab00582. +2023-09-18 19:06:36,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6d75acb7a0582 for 80.00000000 SEI-USDT. +2023-09-18 19:06:36,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695063996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a6d75acb7a0582", "creation_timestamp": 1695063996.0, "exchange_order_id": "35418361", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:07:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6d75acb7a0582. [clock=2023-09-18 19:07:31+00:00] +2023-09-18 19:07:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235859965084974486792730620 amount: 80. +2023-09-18 19:07:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240248327938784963814611109 amount: 80. +2023-09-18 19:07:31,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064051.0, "order_id": "x-XEKWYICXBSIUT605a6d75acb7a0582", "exchange_order_id": "35418361", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:07:31,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6d75acb7a0582. +2023-09-18 19:07:31,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6daa1f48b0582 for 80.00000000 SEI-USDT. +2023-09-18 19:07:31,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a6daa1f48b0582", "creation_timestamp": 1695064051.0, "exchange_order_id": "35418645", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:07:31,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6daa1f6ee0582 for 80.00000000 SEI-USDT. +2023-09-18 19:07:31,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a6daa1f6ee0582", "creation_timestamp": 1695064051.0, "exchange_order_id": "35418646", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:08:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6daa1f48b0582. [clock=2023-09-18 19:08:26+00:00] +2023-09-18 19:08:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6daa1f6ee0582. [clock=2023-09-18 19:08:26+00:00] +2023-09-18 19:08:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12370000 amount: 80. +2023-09-18 19:08:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:08:26,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064106.0, "order_id": "x-XEKWYICXBSIUT605a6daa1f48b0582", "exchange_order_id": "35418645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:08:26,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6daa1f48b0582. +2023-09-18 19:08:26,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064106.0, "order_id": "x-XEKWYICXSSIUT605a6daa1f6ee0582", "exchange_order_id": "35418646", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:08:26,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6daa1f6ee0582. +2023-09-18 19:08:26,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6dde92ff90582 for 80.00000000 SEI-USDT. +2023-09-18 19:08:26,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6dde92ff90582", "creation_timestamp": 1695064106.0, "exchange_order_id": "35419141", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:09:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6dde92ff90582. [clock=2023-09-18 19:09:21+00:00] +2023-09-18 19:09:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12370000 amount: 80. +2023-09-18 19:09:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240255455194911988550361223 amount: 80. +2023-09-18 19:09:21,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064161.0, "order_id": "x-XEKWYICXBSIUT605a6dde92ff90582", "exchange_order_id": "35419141", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:09:21,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6dde92ff90582. +2023-09-18 19:09:21,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6e1306ec10582 for 80.00000000 SEI-USDT. +2023-09-18 19:09:21,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a6e1306ec10582", "creation_timestamp": 1695064161.0, "exchange_order_id": "35419287", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:09:21,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6e1306ac90582 for 80.00000000 SEI-USDT. +2023-09-18 19:09:21,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6e1306ac90582", "creation_timestamp": 1695064161.0, "exchange_order_id": "35419288", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:10:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6e1306ac90582. [clock=2023-09-18 19:10:16+00:00] +2023-09-18 19:10:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6e1306ec10582. [clock=2023-09-18 19:10:16+00:00] +2023-09-18 19:10:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12370000 amount: 80. +2023-09-18 19:10:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:10:16,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064216.0, "order_id": "x-XEKWYICXBSIUT605a6e1306ac90582", "exchange_order_id": "35419288", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:10:16,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6e1306ac90582. +2023-09-18 19:10:16,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6e477a75f0582 for 80.00000000 SEI-USDT. +2023-09-18 19:10:16,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6e477a75f0582", "creation_timestamp": 1695064216.0, "exchange_order_id": "35419369", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:10:16,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064216.0, "order_id": "x-XEKWYICXSSIUT605a6e1306ec10582", "exchange_order_id": "35419287", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:10:16,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6e1306ec10582. +2023-09-18 19:11:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6e477a75f0582. [clock=2023-09-18 19:11:11+00:00] +2023-09-18 19:11:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236904823593083819754124294 amount: 80. +2023-09-18 19:11:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239272649431739038473433622 amount: 80. +2023-09-18 19:11:11,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064271.0, "order_id": "x-XEKWYICXBSIUT605a6e477a75f0582", "exchange_order_id": "35419369", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:11:11,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6e477a75f0582. +2023-09-18 19:11:11,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6e7bee4100582 for 80.00000000 SEI-USDT. +2023-09-18 19:11:11,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a6e7bee4100582", "creation_timestamp": 1695064271.0, "exchange_order_id": "35419431", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:11:11,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6e7bee0e50582 for 80.00000000 SEI-USDT. +2023-09-18 19:11:11,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6e7bee0e50582", "creation_timestamp": 1695064271.0, "exchange_order_id": "35419432", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:12:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6e7bee0e50582. [clock=2023-09-18 19:12:06+00:00] +2023-09-18 19:12:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6e7bee4100582. [clock=2023-09-18 19:12:06+00:00] +2023-09-18 19:12:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236884017960094930953977240 amount: 80. +2023-09-18 19:12:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:12:06,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064326.0, "order_id": "x-XEKWYICXBSIUT605a6e7bee0e50582", "exchange_order_id": "35419432", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:12:06,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6e7bee0e50582. +2023-09-18 19:12:06,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064326.0, "order_id": "x-XEKWYICXSSIUT605a6e7bee4100582", "exchange_order_id": "35419431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:12:06,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6e7bee4100582. +2023-09-18 19:12:06,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6eb06211d0582 for 80.00000000 SEI-USDT. +2023-09-18 19:12:06,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6eb06211d0582", "creation_timestamp": 1695064326.0, "exchange_order_id": "35419728", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:13:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6eb06211d0582. [clock=2023-09-18 19:13:01+00:00] +2023-09-18 19:13:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236884017960094930953977240 amount: 80. +2023-09-18 19:13:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239764157445070679502770658 amount: 80. +2023-09-18 19:13:01,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064381.0, "order_id": "x-XEKWYICXBSIUT605a6eb06211d0582", "exchange_order_id": "35419728", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:13:01,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6eb06211d0582. +2023-09-18 19:13:01,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6ee4d5e5e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:13:01,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6ee4d5e5e0582", "creation_timestamp": 1695064381.0, "exchange_order_id": "35419816", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:13:01,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6ee4d62490582 for 80.00000000 SEI-USDT. +2023-09-18 19:13:01,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a6ee4d62490582", "creation_timestamp": 1695064381.0, "exchange_order_id": "35419817", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:13:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6ee4d5e5e0582. [clock=2023-09-18 19:13:56+00:00] +2023-09-18 19:13:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6ee4d62490582. [clock=2023-09-18 19:13:56+00:00] +2023-09-18 19:13:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 19:13:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:13:56,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064436.0, "order_id": "x-XEKWYICXBSIUT605a6ee4d5e5e0582", "exchange_order_id": "35419816", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:13:56,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6ee4d5e5e0582. +2023-09-18 19:13:56,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064436.0, "order_id": "x-XEKWYICXSSIUT605a6ee4d62490582", "exchange_order_id": "35419817", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:13:56,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6ee4d62490582. +2023-09-18 19:13:56,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6f194980f0582 for 80.00000000 SEI-USDT. +2023-09-18 19:13:56,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a6f194980f0582", "creation_timestamp": 1695064436.0, "exchange_order_id": "35420000", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:14:41,549 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a6f194980f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:14:41,550 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:14:41+00:00] +2023-09-18 19:14:41,597 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064481.0, "order_id": "x-XEKWYICXBSIUT605a6f194980f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4979488", "exchange_order_id": "35420000", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:14:41,611 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064481.0, "order_id": "x-XEKWYICXBSIUT605a6f194980f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35420000", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:14:41,611 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a6f194980f0582 completely filled. +2023-09-18 19:14:51,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237809883745420225046028837 amount: 80. +2023-09-18 19:14:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240738523742813211503002827 amount: 80. +2023-09-18 19:14:51,092 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6f4dbcb180582 for 80.00000000 SEI-USDT. +2023-09-18 19:14:51,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6f4dbcb180582", "creation_timestamp": 1695064491.0, "exchange_order_id": "35420209", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:14:51,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6f4dbcf8b0582 for 80.00000000 SEI-USDT. +2023-09-18 19:14:51,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a6f4dbcf8b0582", "creation_timestamp": 1695064491.0, "exchange_order_id": "35420210", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:15:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6f4dbcb180582. [clock=2023-09-18 19:15:46+00:00] +2023-09-18 19:15:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6f4dbcf8b0582. [clock=2023-09-18 19:15:46+00:00] +2023-09-18 19:15:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237852338846573484734785713 amount: 80. +2023-09-18 19:15:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240129649406069240341742577 amount: 80. +2023-09-18 19:15:46,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064546.0, "order_id": "x-XEKWYICXBSIUT605a6f4dbcb180582", "exchange_order_id": "35420209", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:15:46,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6f4dbcb180582. +2023-09-18 19:15:46,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064546.0, "order_id": "x-XEKWYICXSSIUT605a6f4dbcf8b0582", "exchange_order_id": "35420210", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:15:46,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6f4dbcf8b0582. +2023-09-18 19:15:46,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6f823122c0582 for 80.00000000 SEI-USDT. +2023-09-18 19:15:46,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a6f823122c0582", "creation_timestamp": 1695064546.0, "exchange_order_id": "35420347", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:15:46,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6f8230fa20582 for 80.00000000 SEI-USDT. +2023-09-18 19:15:46,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6f8230fa20582", "creation_timestamp": 1695064546.0, "exchange_order_id": "35420348", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:16:41,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6f8230fa20582. [clock=2023-09-18 19:16:41+00:00] +2023-09-18 19:16:41,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6f823122c0582. [clock=2023-09-18 19:16:41+00:00] +2023-09-18 19:16:41,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237885277670382344290929927 amount: 80. +2023-09-18 19:16:41,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239656204346352523734434063 amount: 80. +2023-09-18 19:16:41,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064601.0, "order_id": "x-XEKWYICXBSIUT605a6f8230fa20582", "exchange_order_id": "35420348", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:16:41,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6f8230fa20582. +2023-09-18 19:16:41,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6fb6acdb30582 for 80.00000000 SEI-USDT. +2023-09-18 19:16:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a6fb6acdb30582", "creation_timestamp": 1695064601.0, "exchange_order_id": "35420438", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:16:41,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064601.0, "order_id": "x-XEKWYICXSSIUT605a6f823122c0582", "exchange_order_id": "35420347", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:16:41,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6f823122c0582. +2023-09-18 19:16:41,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6fb6aca460582 for 80.00000000 SEI-USDT. +2023-09-18 19:16:41,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a6fb6aca460582", "creation_timestamp": 1695064601.0, "exchange_order_id": "35420439", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:17:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6fb6aca460582. [clock=2023-09-18 19:17:36+00:00] +2023-09-18 19:17:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6fb6acdb30582. [clock=2023-09-18 19:17:36+00:00] +2023-09-18 19:17:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236910919286782656533937198 amount: 80. +2023-09-18 19:17:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238287003801517757261871358 amount: 80. +2023-09-18 19:17:36,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064656.0, "order_id": "x-XEKWYICXBSIUT605a6fb6aca460582", "exchange_order_id": "35420439", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:17:36,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6fb6aca460582. +2023-09-18 19:17:36,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064656.0, "order_id": "x-XEKWYICXSSIUT605a6fb6acdb30582", "exchange_order_id": "35420438", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:17:36,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6fb6acdb30582. +2023-09-18 19:17:36,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a6feb187670582 for 80.00000000 SEI-USDT. +2023-09-18 19:17:36,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a6feb187670582", "creation_timestamp": 1695064656.0, "exchange_order_id": "35420615", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:17:36,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a6feb18b4e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:17:36,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a6feb18b4e0582", "creation_timestamp": 1695064656.0, "exchange_order_id": "35420616", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:18:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a6feb187670582. [clock=2023-09-18 19:18:31+00:00] +2023-09-18 19:18:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a6feb18b4e0582. [clock=2023-09-18 19:18:31+00:00] +2023-09-18 19:18:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235581360471098334826921251 amount: 80. +2023-09-18 19:18:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237317888942583565275253449 amount: 80. +2023-09-18 19:18:31,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064711.0, "order_id": "x-XEKWYICXBSIUT605a6feb187670582", "exchange_order_id": "35420615", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:18:31,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a6feb187670582. +2023-09-18 19:18:31,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064711.0, "order_id": "x-XEKWYICXSSIUT605a6feb18b4e0582", "exchange_order_id": "35420616", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:18:31,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a6feb18b4e0582. +2023-09-18 19:18:31,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a701f8c5310582 for 80.00000000 SEI-USDT. +2023-09-18 19:18:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a701f8c5310582", "creation_timestamp": 1695064711.0, "exchange_order_id": "35420865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:18:31,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a701f8c8c20582 for 80.00000000 SEI-USDT. +2023-09-18 19:18:31,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a701f8c8c20582", "creation_timestamp": 1695064711.0, "exchange_order_id": "35420866", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:19:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a701f8c5310582. [clock=2023-09-18 19:19:26+00:00] +2023-09-18 19:19:26,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a701f8c8c20582. [clock=2023-09-18 19:19:26+00:00] +2023-09-18 19:19:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235674639919968214460900088 amount: 80. +2023-09-18 19:19:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237025164243760753694727098 amount: 80. +2023-09-18 19:19:26,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064766.0, "order_id": "x-XEKWYICXBSIUT605a701f8c5310582", "exchange_order_id": "35420865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:19:26,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a701f8c5310582. +2023-09-18 19:19:26,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7054007140582 for 80.00000000 SEI-USDT. +2023-09-18 19:19:26,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a7054007140582", "creation_timestamp": 1695064766.0, "exchange_order_id": "35420936", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:19:26,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064766.0, "order_id": "x-XEKWYICXSSIUT605a701f8c8c20582", "exchange_order_id": "35420866", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:19:26,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a701f8c8c20582. +2023-09-18 19:19:26,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7054003710582 for 80.00000000 SEI-USDT. +2023-09-18 19:19:26,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a7054003710582", "creation_timestamp": 1695064766.0, "exchange_order_id": "35420937", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:20:21,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7054003710582. [clock=2023-09-18 19:20:21+00:00] +2023-09-18 19:20:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7054007140582. [clock=2023-09-18 19:20:21+00:00] +2023-09-18 19:20:21,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233370709477319731145500367 amount: 80. +2023-09-18 19:20:21,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12360000 amount: 80. +2023-09-18 19:20:21,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064821.0, "order_id": "x-XEKWYICXBSIUT605a7054003710582", "exchange_order_id": "35420937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:20:21,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7054003710582. +2023-09-18 19:20:21,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064821.0, "order_id": "x-XEKWYICXSSIUT605a7054007140582", "exchange_order_id": "35420936", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:20:21,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7054007140582. +2023-09-18 19:20:21,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a708876fab0582 for 80.00000000 SEI-USDT. +2023-09-18 19:20:21,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a708876fab0582", "creation_timestamp": 1695064821.0, "exchange_order_id": "35421144", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:20:21,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a70887731a0582 for 80.00000000 SEI-USDT. +2023-09-18 19:20:21,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605a70887731a0582", "creation_timestamp": 1695064821.0, "exchange_order_id": "35421145", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:21:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a708876fab0582. [clock=2023-09-18 19:21:16+00:00] +2023-09-18 19:21:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a70887731a0582. [clock=2023-09-18 19:21:16+00:00] +2023-09-18 19:21:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233733036693730059409857443 amount: 80. +2023-09-18 19:21:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12360000 amount: 80. +2023-09-18 19:21:16,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064876.0, "order_id": "x-XEKWYICXBSIUT605a708876fab0582", "exchange_order_id": "35421144", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:21:16,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a708876fab0582. +2023-09-18 19:21:16,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a70bce7bf70582 for 80.00000000 SEI-USDT. +2023-09-18 19:21:16,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605a70bce7bf70582", "creation_timestamp": 1695064876.0, "exchange_order_id": "35421263", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:21:16,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064876.0, "order_id": "x-XEKWYICXSSIUT605a70887731a0582", "exchange_order_id": "35421145", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:21:16,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a70887731a0582. +2023-09-18 19:21:16,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a70bce787e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:21:16,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a70bce787e0582", "creation_timestamp": 1695064876.0, "exchange_order_id": "35421264", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:21:23,062 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a70bce7bf70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:21:23,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:21:23+00:00] +2023-09-18 19:21:23,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064883.0, "order_id": "x-XEKWYICXSSIUT605a70bce7bf70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12360000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00988800"}]}, "exchange_trade_id": "4979576", "exchange_order_id": "35421263", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:21:23,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064883.0, "order_id": "x-XEKWYICXSSIUT605a70bce7bf70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35421263", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:21:23,102 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a70bce7bf70582 completely filled. +2023-09-18 19:22:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a70bce787e0582. [clock=2023-09-18 19:22:11+00:00] +2023-09-18 19:22:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235716256383448126066415994 amount: 80. +2023-09-18 19:22:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238773294773283231163048633 amount: 80. +2023-09-18 19:22:11,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064931.0, "order_id": "x-XEKWYICXBSIUT605a70bce787e0582", "exchange_order_id": "35421264", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:22:11,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a70bce787e0582. +2023-09-18 19:22:11,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a70f15af340582 for 80.00000000 SEI-USDT. +2023-09-18 19:22:11,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a70f15af340582", "creation_timestamp": 1695064931.0, "exchange_order_id": "35421465", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:22:11,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a70f15b3ce0582 for 80.00000000 SEI-USDT. +2023-09-18 19:22:11,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a70f15b3ce0582", "creation_timestamp": 1695064931.0, "exchange_order_id": "35421466", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:23:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a70f15af340582. [clock=2023-09-18 19:23:06+00:00] +2023-09-18 19:23:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a70f15b3ce0582. [clock=2023-09-18 19:23:06+00:00] +2023-09-18 19:23:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235534421769677809392885691 amount: 80. +2023-09-18 19:23:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:23:06,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064986.0, "order_id": "x-XEKWYICXBSIUT605a70f15af340582", "exchange_order_id": "35421465", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:23:06,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a70f15af340582. +2023-09-18 19:23:06,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7125ceeae0582 for 80.00000000 SEI-USDT. +2023-09-18 19:23:06,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a7125ceeae0582", "creation_timestamp": 1695064986.0, "exchange_order_id": "35421562", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:23:06,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695064986.0, "order_id": "x-XEKWYICXSSIUT605a70f15b3ce0582", "exchange_order_id": "35421466", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:23:06,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a70f15b3ce0582. +2023-09-18 19:24:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7125ceeae0582. [clock=2023-09-18 19:24:01+00:00] +2023-09-18 19:24:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235752849230113022208946544 amount: 80. +2023-09-18 19:24:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238797419044615440425845934 amount: 80. +2023-09-18 19:24:01,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065041.0, "order_id": "x-XEKWYICXBSIUT605a7125ceeae0582", "exchange_order_id": "35421562", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:24:01,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7125ceeae0582. +2023-09-18 19:24:01,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a715a42bde0582 for 80.00000000 SEI-USDT. +2023-09-18 19:24:01,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a715a42bde0582", "creation_timestamp": 1695065041.0, "exchange_order_id": "35421652", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:24:01,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a715a42ed40582 for 80.00000000 SEI-USDT. +2023-09-18 19:24:01,263 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a715a42ed40582", "creation_timestamp": 1695065041.0, "exchange_order_id": "35421653", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:24:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a715a42bde0582. [clock=2023-09-18 19:24:56+00:00] +2023-09-18 19:24:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a715a42ed40582. [clock=2023-09-18 19:24:56+00:00] +2023-09-18 19:24:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235808020315837386879591358 amount: 80. +2023-09-18 19:24:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:24:56,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065096.0, "order_id": "x-XEKWYICXBSIUT605a715a42bde0582", "exchange_order_id": "35421652", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:24:56,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a715a42bde0582. +2023-09-18 19:24:56,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065096.0, "order_id": "x-XEKWYICXSSIUT605a715a42ed40582", "exchange_order_id": "35421653", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:24:56,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a715a42ed40582. +2023-09-18 19:24:56,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a718eb62a90582 for 80.00000000 SEI-USDT. +2023-09-18 19:24:56,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a718eb62a90582", "creation_timestamp": 1695065096.0, "exchange_order_id": "35421726", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:25:15,076 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 19:25:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a718eb62a90582. [clock=2023-09-18 19:25:51+00:00] +2023-09-18 19:25:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235850832904820334417766385 amount: 80. +2023-09-18 19:25:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237691859780284882961668050 amount: 80. +2023-09-18 19:25:51,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065151.0, "order_id": "x-XEKWYICXBSIUT605a718eb62a90582", "exchange_order_id": "35421726", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:25:51,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a718eb62a90582. +2023-09-18 19:25:51,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a71c32a3d60582 for 80.00000000 SEI-USDT. +2023-09-18 19:25:51,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a71c32a3d60582", "creation_timestamp": 1695065151.0, "exchange_order_id": "35421864", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:25:51,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a71c32a72b0582 for 80.00000000 SEI-USDT. +2023-09-18 19:25:51,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a71c32a72b0582", "creation_timestamp": 1695065151.0, "exchange_order_id": "35421865", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:26:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a71c32a3d60582. [clock=2023-09-18 19:26:46+00:00] +2023-09-18 19:26:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a71c32a72b0582. [clock=2023-09-18 19:26:46+00:00] +2023-09-18 19:26:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235884072150148729034580800 amount: 80. +2023-09-18 19:26:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:26:46,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065206.0, "order_id": "x-XEKWYICXBSIUT605a71c32a3d60582", "exchange_order_id": "35421864", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:26:46,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a71c32a3d60582. +2023-09-18 19:26:46,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065206.0, "order_id": "x-XEKWYICXSSIUT605a71c32a72b0582", "exchange_order_id": "35421865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:26:46,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a71c32a72b0582. +2023-09-18 19:26:46,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a71f79d8470582 for 80.00000000 SEI-USDT. +2023-09-18 19:26:46,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a71f79d8470582", "creation_timestamp": 1695065206.0, "exchange_order_id": "35421966", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:27:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a71f79d8470582. [clock=2023-09-18 19:27:41+00:00] +2023-09-18 19:27:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235909888943661622707908888 amount: 80. +2023-09-18 19:27:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237023322141823023402811356 amount: 80. +2023-09-18 19:27:41,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065261.0, "order_id": "x-XEKWYICXBSIUT605a71f79d8470582", "exchange_order_id": "35421966", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:27:41,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a71f79d8470582. +2023-09-18 19:27:41,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a722c1139e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:27:41,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605a722c1139e0582", "creation_timestamp": 1695065261.0, "exchange_order_id": "35422047", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:27:41,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a722c10ff00582 for 80.00000000 SEI-USDT. +2023-09-18 19:27:41,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a722c10ff00582", "creation_timestamp": 1695065261.0, "exchange_order_id": "35422048", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:27:58,487 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a722c1139e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:27:58,488 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:27:58+00:00] +2023-09-18 19:27:58,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065278.0, "order_id": "x-XEKWYICXSSIUT605a722c1139e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00989600"}]}, "exchange_trade_id": "4979634", "exchange_order_id": "35422047", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:27:58,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065278.0, "order_id": "x-XEKWYICXSSIUT605a722c1139e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35422047", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:27:58,537 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a722c1139e0582 completely filled. +2023-09-18 19:28:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a722c10ff00582. [clock=2023-09-18 19:28:36+00:00] +2023-09-18 19:28:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:28:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:28:36,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065316.0, "order_id": "x-XEKWYICXBSIUT605a722c10ff00582", "exchange_order_id": "35422048", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:28:36,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a722c10ff00582. +2023-09-18 19:28:36,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7260850cf0582 for 80.00000000 SEI-USDT. +2023-09-18 19:28:36,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7260850cf0582", "creation_timestamp": 1695065316.0, "exchange_order_id": "35422202", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:29:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7260850cf0582. [clock=2023-09-18 19:29:31+00:00] +2023-09-18 19:29:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:29:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:29:31,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065371.0, "order_id": "x-XEKWYICXBSIUT605a7260850cf0582", "exchange_order_id": "35422202", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:29:31,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7260850cf0582. +2023-09-18 19:29:31,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7294f88d00582 for 80.00000000 SEI-USDT. +2023-09-18 19:29:31,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7294f88d00582", "creation_timestamp": 1695065371.0, "exchange_order_id": "35422487", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:30:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7294f88d00582. [clock=2023-09-18 19:30:26+00:00] +2023-09-18 19:30:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:30:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:30:26,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065426.0, "order_id": "x-XEKWYICXBSIUT605a7294f88d00582", "exchange_order_id": "35422487", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:30:26,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7294f88d00582. +2023-09-18 19:30:26,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a72c96c4210582 for 80.00000000 SEI-USDT. +2023-09-18 19:30:26,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a72c96c4210582", "creation_timestamp": 1695065426.0, "exchange_order_id": "35422598", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:31:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a72c96c4210582. [clock=2023-09-18 19:31:21+00:00] +2023-09-18 19:31:21,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:31:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:31:21,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065481.0, "order_id": "x-XEKWYICXBSIUT605a72c96c4210582", "exchange_order_id": "35422598", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:31:21,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a72c96c4210582. +2023-09-18 19:31:21,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a72fde01a00582 for 80.00000000 SEI-USDT. +2023-09-18 19:31:21,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a72fde01a00582", "creation_timestamp": 1695065481.0, "exchange_order_id": "35422673", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:32:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a72fde01a00582. [clock=2023-09-18 19:32:16+00:00] +2023-09-18 19:32:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:32:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:32:16,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065536.0, "order_id": "x-XEKWYICXBSIUT605a72fde01a00582", "exchange_order_id": "35422673", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:32:16,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a72fde01a00582. +2023-09-18 19:32:16,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7332547600582 for 80.00000000 SEI-USDT. +2023-09-18 19:32:16,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7332547600582", "creation_timestamp": 1695065536.0, "exchange_order_id": "35422762", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:33:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7332547600582. [clock=2023-09-18 19:33:11+00:00] +2023-09-18 19:33:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:33:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:33:11,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065591.0, "order_id": "x-XEKWYICXBSIUT605a7332547600582", "exchange_order_id": "35422762", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:33:11,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7332547600582. +2023-09-18 19:33:11,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7366c784a0582 for 80.00000000 SEI-USDT. +2023-09-18 19:33:11,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7366c784a0582", "creation_timestamp": 1695065591.0, "exchange_order_id": "35422851", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:34:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7366c784a0582. [clock=2023-09-18 19:34:06+00:00] +2023-09-18 19:34:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-18 19:34:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:34:06,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065646.0, "order_id": "x-XEKWYICXBSIUT605a7366c784a0582", "exchange_order_id": "35422851", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:34:06,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7366c784a0582. +2023-09-18 19:34:06,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a739b3c1650582 for 80.00000000 SEI-USDT. +2023-09-18 19:34:06,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a739b3c1650582", "creation_timestamp": 1695065646.0, "exchange_order_id": "35422911", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:35:01,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a739b3c1650582. [clock=2023-09-18 19:35:01+00:00] +2023-09-18 19:35:01,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 19:35:01,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:35:01,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065701.0, "order_id": "x-XEKWYICXBSIUT605a739b3c1650582", "exchange_order_id": "35422911", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:35:01,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a739b3c1650582. +2023-09-18 19:35:01,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a73cfb86630582 for 80.00000000 SEI-USDT. +2023-09-18 19:35:01,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a73cfb86630582", "creation_timestamp": 1695065701.0, "exchange_order_id": "35423330", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:35:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a73cfb86630582. [clock=2023-09-18 19:35:56+00:00] +2023-09-18 19:35:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 19:35:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:35:56,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065756.0, "order_id": "x-XEKWYICXBSIUT605a73cfb86630582", "exchange_order_id": "35423330", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:35:56,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a73cfb86630582. +2023-09-18 19:35:56,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a740422f970582 for 80.00000000 SEI-USDT. +2023-09-18 19:35:56,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a740422f970582", "creation_timestamp": 1695065756.0, "exchange_order_id": "35423463", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:36:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a740422f970582. [clock=2023-09-18 19:36:51+00:00] +2023-09-18 19:36:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 19:36:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:36:51,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065811.0, "order_id": "x-XEKWYICXBSIUT605a740422f970582", "exchange_order_id": "35423463", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:36:51,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a740422f970582. +2023-09-18 19:36:51,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a743896bcf0582 for 80.00000000 SEI-USDT. +2023-09-18 19:36:51,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a743896bcf0582", "creation_timestamp": 1695065811.0, "exchange_order_id": "35423589", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:37:43,875 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a743896bcf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:37:43,877 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:37:43+00:00] +2023-09-18 19:37:43,898 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065863.0, "order_id": "x-XEKWYICXBSIUT605a743896bcf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4979706", "exchange_order_id": "35423589", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:37:43,909 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065863.0, "order_id": "x-XEKWYICXBSIUT605a743896bcf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35423589", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:37:43,909 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a743896bcf0582 completely filled. +2023-09-18 19:37:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 19:37:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12390000 amount: 80. +2023-09-18 19:37:46,089 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a746d0a0db0582 for 80.00000000 SEI-USDT. +2023-09-18 19:37:46,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a746d0a0db0582", "creation_timestamp": 1695065866.0, "exchange_order_id": "35423714", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:37:46,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a746d0a6090582 for 80.00000000 SEI-USDT. +2023-09-18 19:37:46,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a746d0a6090582", "creation_timestamp": 1695065866.0, "exchange_order_id": "35423715", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:38:33,766 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a746d0a6090582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:38:33,767 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:38:33+00:00] +2023-09-18 19:38:33,796 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065913.0, "order_id": "x-XEKWYICXSSIUT605a746d0a6090582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4979727", "exchange_order_id": "35423715", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:38:33,811 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065913.0, "order_id": "x-XEKWYICXSSIUT605a746d0a6090582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35423715", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:38:33,812 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a746d0a6090582 completely filled. +2023-09-18 19:38:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a746d0a0db0582. [clock=2023-09-18 19:38:41+00:00] +2023-09-18 19:38:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 19:38:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:38:41,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065921.0, "order_id": "x-XEKWYICXBSIUT605a746d0a0db0582", "exchange_order_id": "35423714", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:38:41,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a746d0a0db0582. +2023-09-18 19:38:41,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a74a17e5870582 for 80.00000000 SEI-USDT. +2023-09-18 19:38:41,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a74a17e5870582", "creation_timestamp": 1695065921.0, "exchange_order_id": "35424035", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:39:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a74a17e5870582. [clock=2023-09-18 19:39:36+00:00] +2023-09-18 19:39:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 19:39:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:39:36,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065976.0, "order_id": "x-XEKWYICXBSIUT605a74a17e5870582", "exchange_order_id": "35424035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:39:36,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a74a17e5870582. +2023-09-18 19:39:36,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a74d5f19750582 for 80.00000000 SEI-USDT. +2023-09-18 19:39:36,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695065976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a74d5f19750582", "creation_timestamp": 1695065976.0, "exchange_order_id": "35424392", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:40:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a74d5f19750582. [clock=2023-09-18 19:40:31+00:00] +2023-09-18 19:40:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 19:40:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:40:31,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066031.0, "order_id": "x-XEKWYICXBSIUT605a74d5f19750582", "exchange_order_id": "35424392", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:40:31,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a74d5f19750582. +2023-09-18 19:40:31,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a750a65b630582 for 80.00000000 SEI-USDT. +2023-09-18 19:40:31,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a750a65b630582", "creation_timestamp": 1695066031.0, "exchange_order_id": "35424715", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:41:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a750a65b630582. [clock=2023-09-18 19:41:26+00:00] +2023-09-18 19:41:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 19:41:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:41:26,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066086.0, "order_id": "x-XEKWYICXBSIUT605a750a65b630582", "exchange_order_id": "35424715", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:41:26,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a750a65b630582. +2023-09-18 19:41:26,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a753ed9d5d0582 for 80.00000000 SEI-USDT. +2023-09-18 19:41:26,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a753ed9d5d0582", "creation_timestamp": 1695066086.0, "exchange_order_id": "35424905", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:42:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a753ed9d5d0582. [clock=2023-09-18 19:42:21+00:00] +2023-09-18 19:42:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 19:42:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:42:21,075 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066141.0, "order_id": "x-XEKWYICXBSIUT605a753ed9d5d0582", "exchange_order_id": "35424905", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:42:21,075 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a753ed9d5d0582. +2023-09-18 19:42:21,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a75734d1ec0582 for 80.00000000 SEI-USDT. +2023-09-18 19:42:21,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a75734d1ec0582", "creation_timestamp": 1695066141.0, "exchange_order_id": "35425154", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:43:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a75734d1ec0582. [clock=2023-09-18 19:43:16+00:00] +2023-09-18 19:43:16,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-18 19:43:16,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:43:16,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066196.0, "order_id": "x-XEKWYICXBSIUT605a75734d1ec0582", "exchange_order_id": "35425154", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:43:16,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a75734d1ec0582. +2023-09-18 19:43:16,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a75a7c31360582 for 80.00000000 SEI-USDT. +2023-09-18 19:43:16,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a75a7c31360582", "creation_timestamp": 1695066196.0, "exchange_order_id": "35425626", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:44:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a75a7c31360582. [clock=2023-09-18 19:44:11+00:00] +2023-09-18 19:44:11,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-18 19:44:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:44:11,135 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066251.0, "order_id": "x-XEKWYICXBSIUT605a75a7c31360582", "exchange_order_id": "35425626", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:44:11,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a75a7c31360582. +2023-09-18 19:44:11,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a75dc344110582 for 80.00000000 SEI-USDT. +2023-09-18 19:44:11,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a75dc344110582", "creation_timestamp": 1695066251.0, "exchange_order_id": "35425715", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:45:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a75dc344110582. [clock=2023-09-18 19:45:06+00:00] +2023-09-18 19:45:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-18 19:45:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:45:06,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066306.0, "order_id": "x-XEKWYICXBSIUT605a75dc344110582", "exchange_order_id": "35425715", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:45:06,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a75dc344110582. +2023-09-18 19:45:06,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7610a87fd0582 for 80.00000000 SEI-USDT. +2023-09-18 19:45:06,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a7610a87fd0582", "creation_timestamp": 1695066306.0, "exchange_order_id": "35425991", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:46:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7610a87fd0582. [clock=2023-09-18 19:46:01+00:00] +2023-09-18 19:46:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-18 19:46:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:46:01,099 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066361.0, "order_id": "x-XEKWYICXBSIUT605a7610a87fd0582", "exchange_order_id": "35425991", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:46:01,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7610a87fd0582. +2023-09-18 19:46:01,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a76451c0720582 for 80.00000000 SEI-USDT. +2023-09-18 19:46:01,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a76451c0720582", "creation_timestamp": 1695066361.0, "exchange_order_id": "35426534", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:46:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a76451c0720582. [clock=2023-09-18 19:46:56+00:00] +2023-09-18 19:46:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-18 19:46:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:46:56,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066416.0, "order_id": "x-XEKWYICXBSIUT605a76451c0720582", "exchange_order_id": "35426534", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:46:56,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a76451c0720582. +2023-09-18 19:46:56,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a76798fb430582 for 80.00000000 SEI-USDT. +2023-09-18 19:46:56,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a76798fb430582", "creation_timestamp": 1695066416.0, "exchange_order_id": "35426923", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:47:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a76798fb430582. [clock=2023-09-18 19:47:51+00:00] +2023-09-18 19:47:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-18 19:47:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:47:51,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066471.0, "order_id": "x-XEKWYICXBSIUT605a76798fb430582", "exchange_order_id": "35426923", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:47:51,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a76798fb430582. +2023-09-18 19:47:51,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a76ae0388d0582 for 80.00000000 SEI-USDT. +2023-09-18 19:47:51,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a76ae0388d0582", "creation_timestamp": 1695066471.0, "exchange_order_id": "35427052", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:48:02,971 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a76ae0388d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 19:48:02,972 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 19:48:02+00:00] +2023-09-18 19:48:02,997 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066482.0, "order_id": "x-XEKWYICXBSIUT605a76ae0388d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980046", "exchange_order_id": "35427052", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 19:48:03,012 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066482.0, "order_id": "x-XEKWYICXBSIUT605a76ae0388d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35427052", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 19:48:03,012 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a76ae0388d0582 completely filled. +2023-09-18 19:48:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241789256084175530251904175 amount: 80. +2023-09-18 19:48:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245292239329426907187336297 amount: 80. +2023-09-18 19:48:46,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a76e276ef60582 for 80.00000000 SEI-USDT. +2023-09-18 19:48:46,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a76e276ef60582", "creation_timestamp": 1695066526.0, "exchange_order_id": "35427530", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:48:46,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a76e2773590582 for 80.00000000 SEI-USDT. +2023-09-18 19:48:46,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605a76e2773590582", "creation_timestamp": 1695066526.0, "exchange_order_id": "35427531", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:49:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a76e276ef60582. [clock=2023-09-18 19:49:41+00:00] +2023-09-18 19:49:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a76e2773590582. [clock=2023-09-18 19:49:41+00:00] +2023-09-18 19:49:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240836795546607464719847219 amount: 80. +2023-09-18 19:49:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:49:41,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066581.0, "order_id": "x-XEKWYICXBSIUT605a76e276ef60582", "exchange_order_id": "35427530", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:49:41,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a76e276ef60582. +2023-09-18 19:49:41,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066581.0, "order_id": "x-XEKWYICXSSIUT605a76e2773590582", "exchange_order_id": "35427531", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:49:41,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a76e2773590582. +2023-09-18 19:49:41,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7716ec61c0582 for 80.00000000 SEI-USDT. +2023-09-18 19:49:41,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a7716ec61c0582", "creation_timestamp": 1695066581.0, "exchange_order_id": "35427703", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:50:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7716ec61c0582. [clock=2023-09-18 19:50:36+00:00] +2023-09-18 19:50:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240842236441419137799410235 amount: 80. +2023-09-18 19:50:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243477557269040742018036191 amount: 80. +2023-09-18 19:50:36,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066636.0, "order_id": "x-XEKWYICXBSIUT605a7716ec61c0582", "exchange_order_id": "35427703", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:50:36,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7716ec61c0582. +2023-09-18 19:50:36,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a774b5e7b50582 for 80.00000000 SEI-USDT. +2023-09-18 19:50:36,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a774b5e7b50582", "creation_timestamp": 1695066636.0, "exchange_order_id": "35427975", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:50:36,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a774b5e9ab0582 for 80.00000000 SEI-USDT. +2023-09-18 19:50:36,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a774b5e9ab0582", "creation_timestamp": 1695066636.0, "exchange_order_id": "35427976", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:51:31,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a774b5e7b50582. [clock=2023-09-18 19:51:31+00:00] +2023-09-18 19:51:31,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a774b5e9ab0582. [clock=2023-09-18 19:51:31+00:00] +2023-09-18 19:51:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240771313667322329107934216 amount: 80. +2023-09-18 19:51:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:51:31,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066691.0, "order_id": "x-XEKWYICXBSIUT605a774b5e7b50582", "exchange_order_id": "35427975", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:51:31,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a774b5e7b50582. +2023-09-18 19:51:31,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066691.0, "order_id": "x-XEKWYICXSSIUT605a774b5e9ab0582", "exchange_order_id": "35427976", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:51:31,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a774b5e9ab0582. +2023-09-18 19:51:31,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a777fd39b90582 for 80.00000000 SEI-USDT. +2023-09-18 19:51:31,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a777fd39b90582", "creation_timestamp": 1695066691.0, "exchange_order_id": "35428144", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:52:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a777fd39b90582. [clock=2023-09-18 19:52:26+00:00] +2023-09-18 19:52:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240604162623113903247207743 amount: 80. +2023-09-18 19:52:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243121723933344539114857741 amount: 80. +2023-09-18 19:52:26,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066746.0, "order_id": "x-XEKWYICXBSIUT605a777fd39b90582", "exchange_order_id": "35428144", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:52:26,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a777fd39b90582. +2023-09-18 19:52:26,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a77b4460d70582 for 80.00000000 SEI-USDT. +2023-09-18 19:52:26,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a77b4460d70582", "creation_timestamp": 1695066746.0, "exchange_order_id": "35428328", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:52:26,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a77b4463d40582 for 80.00000000 SEI-USDT. +2023-09-18 19:52:26,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a77b4463d40582", "creation_timestamp": 1695066746.0, "exchange_order_id": "35428329", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:53:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a77b4460d70582. [clock=2023-09-18 19:53:21+00:00] +2023-09-18 19:53:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a77b4463d40582. [clock=2023-09-18 19:53:21+00:00] +2023-09-18 19:53:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239692535182018695929714017 amount: 80. +2023-09-18 19:53:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:53:21,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066801.0, "order_id": "x-XEKWYICXBSIUT605a77b4460d70582", "exchange_order_id": "35428328", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:53:21,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a77b4460d70582. +2023-09-18 19:53:21,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066801.0, "order_id": "x-XEKWYICXSSIUT605a77b4463d40582", "exchange_order_id": "35428329", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:53:21,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a77b4463d40582. +2023-09-18 19:53:21,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a77e8ba4b90582 for 80.00000000 SEI-USDT. +2023-09-18 19:53:21,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a77e8ba4b90582", "creation_timestamp": 1695066801.0, "exchange_order_id": "35428555", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:54:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a77e8ba4b90582. [clock=2023-09-18 19:54:16+00:00] +2023-09-18 19:54:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239483513027446106399672969 amount: 80. +2023-09-18 19:54:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241524772026166655766760747 amount: 80. +2023-09-18 19:54:16,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066856.0, "order_id": "x-XEKWYICXBSIUT605a77e8ba4b90582", "exchange_order_id": "35428555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:54:16,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a77e8ba4b90582. +2023-09-18 19:54:16,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a781d2de9d0582 for 80.00000000 SEI-USDT. +2023-09-18 19:54:16,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a781d2de9d0582", "creation_timestamp": 1695066856.0, "exchange_order_id": "35428773", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:54:16,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a781d2db1e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:54:16,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a781d2db1e0582", "creation_timestamp": 1695066856.0, "exchange_order_id": "35428774", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:55:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a781d2db1e0582. [clock=2023-09-18 19:55:11+00:00] +2023-09-18 19:55:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a781d2de9d0582. [clock=2023-09-18 19:55:11+00:00] +2023-09-18 19:55:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239715863727431217242982506 amount: 80. +2023-09-18 19:55:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:55:11,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066911.0, "order_id": "x-XEKWYICXBSIUT605a781d2db1e0582", "exchange_order_id": "35428774", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:55:11,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a781d2db1e0582. +2023-09-18 19:55:11,352 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066911.0, "order_id": "x-XEKWYICXSSIUT605a781d2de9d0582", "exchange_order_id": "35428773", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:55:11,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a781d2de9d0582. +2023-09-18 19:55:11,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7851a1a020582 for 80.00000000 SEI-USDT. +2023-09-18 19:55:11,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7851a1a020582", "creation_timestamp": 1695066911.0, "exchange_order_id": "35428918", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:55:15,091 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 19:56:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7851a1a020582. [clock=2023-09-18 19:56:06+00:00] +2023-09-18 19:56:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239779119401642984102296189 amount: 80. +2023-09-18 19:56:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241416214514967864669288211 amount: 80. +2023-09-18 19:56:06,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066966.0, "order_id": "x-XEKWYICXBSIUT605a7851a1a020582", "exchange_order_id": "35428918", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:56:06,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7851a1a020582. +2023-09-18 19:56:06,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a78861604a0582 for 80.00000000 SEI-USDT. +2023-09-18 19:56:06,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a78861604a0582", "creation_timestamp": 1695066966.0, "exchange_order_id": "35429050", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:56:06,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a78861642e0582 for 80.00000000 SEI-USDT. +2023-09-18 19:56:06,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695066966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a78861642e0582", "creation_timestamp": 1695066966.0, "exchange_order_id": "35429051", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:57:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a78861604a0582. [clock=2023-09-18 19:57:01+00:00] +2023-09-18 19:57:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a78861642e0582. [clock=2023-09-18 19:57:01+00:00] +2023-09-18 19:57:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239779119401642984102296189 amount: 80. +2023-09-18 19:57:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:57:01,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067021.0, "order_id": "x-XEKWYICXBSIUT605a78861604a0582", "exchange_order_id": "35429050", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:57:01,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a78861604a0582. +2023-09-18 19:57:01,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067021.0, "order_id": "x-XEKWYICXSSIUT605a78861642e0582", "exchange_order_id": "35429051", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:57:01,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a78861642e0582. +2023-09-18 19:57:01,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a78ba892470582 for 80.00000000 SEI-USDT. +2023-09-18 19:57:01,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a78ba892470582", "creation_timestamp": 1695067021.0, "exchange_order_id": "35429183", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:57:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a78ba892470582. [clock=2023-09-18 19:57:56+00:00] +2023-09-18 19:57:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239527605189974216614743151 amount: 80. +2023-09-18 19:57:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241468760559384973739826798 amount: 80. +2023-09-18 19:57:56,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067076.0, "order_id": "x-XEKWYICXBSIUT605a78ba892470582", "exchange_order_id": "35429183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:57:56,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a78ba892470582. +2023-09-18 19:57:56,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a78eefc9b10582 for 80.00000000 SEI-USDT. +2023-09-18 19:57:56,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a78eefc9b10582", "creation_timestamp": 1695067076.0, "exchange_order_id": "35429322", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:57:56,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a78eefc6b20582 for 80.00000000 SEI-USDT. +2023-09-18 19:57:56,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a78eefc6b20582", "creation_timestamp": 1695067076.0, "exchange_order_id": "35429323", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:58:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a78eefc6b20582. [clock=2023-09-18 19:58:51+00:00] +2023-09-18 19:58:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a78eefc9b10582. [clock=2023-09-18 19:58:51+00:00] +2023-09-18 19:58:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238632967961928066140071169 amount: 80. +2023-09-18 19:58:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 19:58:51,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067131.0, "order_id": "x-XEKWYICXBSIUT605a78eefc6b20582", "exchange_order_id": "35429323", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:58:51,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a78eefc6b20582. +2023-09-18 19:58:51,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7923706590582 for 80.00000000 SEI-USDT. +2023-09-18 19:58:51,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a7923706590582", "creation_timestamp": 1695067131.0, "exchange_order_id": "35429567", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:58:51,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067131.0, "order_id": "x-XEKWYICXSSIUT605a78eefc9b10582", "exchange_order_id": "35429322", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:58:51,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a78eefc9b10582. +2023-09-18 19:59:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7923706590582. [clock=2023-09-18 19:59:46+00:00] +2023-09-18 19:59:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239805046890825252328167612 amount: 80. +2023-09-18 19:59:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241645113420284221172048300 amount: 80. +2023-09-18 19:59:46,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067186.0, "order_id": "x-XEKWYICXBSIUT605a7923706590582", "exchange_order_id": "35429567", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 19:59:46,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7923706590582. +2023-09-18 19:59:46,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7957e48220582 for 80.00000000 SEI-USDT. +2023-09-18 19:59:46,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a7957e48220582", "creation_timestamp": 1695067186.0, "exchange_order_id": "35429756", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 19:59:46,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7957e45970582 for 80.00000000 SEI-USDT. +2023-09-18 19:59:46,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7957e45970582", "creation_timestamp": 1695067186.0, "exchange_order_id": "35429757", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:00:30,100 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7957e48220582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:00:30,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:00:30+00:00] +2023-09-18 20:00:30,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067230.0, "order_id": "x-XEKWYICXSSIUT605a7957e48220582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "4980162", "exchange_order_id": "35429756", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:00:30,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067230.0, "order_id": "x-XEKWYICXSSIUT605a7957e48220582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35429756", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:00:30,143 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7957e48220582 completely filled. +2023-09-18 20:00:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7957e45970582. [clock=2023-09-18 20:00:41+00:00] +2023-09-18 20:00:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240993434477000443638665899 amount: 80. +2023-09-18 20:00:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:00:41,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067241.0, "order_id": "x-XEKWYICXBSIUT605a7957e45970582", "exchange_order_id": "35429757", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:00:41,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7957e45970582. +2023-09-18 20:00:41,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a798c57c8e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:00:41,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a798c57c8e0582", "creation_timestamp": 1695067241.0, "exchange_order_id": "35430093", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:01:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a798c57c8e0582. [clock=2023-09-18 20:01:36+00:00] +2023-09-18 20:01:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240991680448754401740055257 amount: 80. +2023-09-18 20:01:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:01:36,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067296.0, "order_id": "x-XEKWYICXBSIUT605a798c57c8e0582", "exchange_order_id": "35430093", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:01:36,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a798c57c8e0582. +2023-09-18 20:01:36,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a79c0cba240582 for 80.00000000 SEI-USDT. +2023-09-18 20:01:36,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a79c0cba240582", "creation_timestamp": 1695067296.0, "exchange_order_id": "35430260", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:02:00,110 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a79c0cba240582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:02:00,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:02:00+00:00] +2023-09-18 20:02:00,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067320.0, "order_id": "x-XEKWYICXBSIUT605a79c0cba240582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980208", "exchange_order_id": "35430260", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:02:00,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067320.0, "order_id": "x-XEKWYICXBSIUT605a79c0cba240582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35430260", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:02:00,146 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a79c0cba240582 completely filled. +2023-09-18 20:02:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238211552252764673570446125 amount: 80. +2023-09-18 20:02:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240782243533444716686995978 amount: 80. +2023-09-18 20:02:31,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a79f53f7e00582 for 80.00000000 SEI-USDT. +2023-09-18 20:02:31,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a79f53f7e00582", "creation_timestamp": 1695067351.0, "exchange_order_id": "35430695", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:02:31,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a79f53fd410582 for 80.00000000 SEI-USDT. +2023-09-18 20:02:31,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a79f53fd410582", "creation_timestamp": 1695067351.0, "exchange_order_id": "35430697", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:02:37,553 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a79f53f7e00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:02:37,554 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:02:37+00:00] +2023-09-18 20:02:37,592 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067357.0, "order_id": "x-XEKWYICXBSIUT605a79f53f7e00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980232", "exchange_order_id": "35430695", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:02:37,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067357.0, "order_id": "x-XEKWYICXBSIUT605a79f53f7e00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35430695", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:02:37,616 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a79f53f7e00582 completely filled. +2023-09-18 20:03:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a79f53fd410582. [clock=2023-09-18 20:03:26+00:00] +2023-09-18 20:03:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235787158103075260140115293 amount: 80. +2023-09-18 20:03:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12390000 amount: 80. +2023-09-18 20:03:26,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067406.0, "order_id": "x-XEKWYICXSSIUT605a79f53fd410582", "exchange_order_id": "35430697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:03:26,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a79f53fd410582. +2023-09-18 20:03:26,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7a29b366e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:03:26,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7a29b366e0582", "creation_timestamp": 1695067406.0, "exchange_order_id": "35431125", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:03:26,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7a29b33ce0582 for 80.00000000 SEI-USDT. +2023-09-18 20:03:26,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a7a29b33ce0582", "creation_timestamp": 1695067406.0, "exchange_order_id": "35431126", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:03:42,069 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7a29b366e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:03:42,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:03:42+00:00] +2023-09-18 20:03:42,091 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067422.0, "order_id": "x-XEKWYICXSSIUT605a7a29b366e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4980285", "exchange_order_id": "35431125", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:03:42,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067422.0, "order_id": "x-XEKWYICXSSIUT605a7a29b366e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35431125", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:03:42,103 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7a29b366e0582 completely filled. +2023-09-18 20:04:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7a29b33ce0582. [clock=2023-09-18 20:04:21+00:00] +2023-09-18 20:04:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238178141449395218823054009 amount: 80. +2023-09-18 20:04:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241578658083632407372312075 amount: 80. +2023-09-18 20:04:21,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067461.0, "order_id": "x-XEKWYICXBSIUT605a7a29b33ce0582", "exchange_order_id": "35431126", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:04:21,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7a29b33ce0582. +2023-09-18 20:04:21,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7a5e26ec00582 for 80.00000000 SEI-USDT. +2023-09-18 20:04:21,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a7a5e26ec00582", "creation_timestamp": 1695067461.0, "exchange_order_id": "35431423", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:04:21,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7a5e26b1a0582 for 80.00000000 SEI-USDT. +2023-09-18 20:04:21,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a7a5e26b1a0582", "creation_timestamp": 1695067461.0, "exchange_order_id": "35431424", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:04:50,704 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a7a5e26b1a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:04:50,705 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:04:50+00:00] +2023-09-18 20:04:50,727 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067490.0, "order_id": "x-XEKWYICXBSIUT605a7a5e26b1a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980310", "exchange_order_id": "35431424", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:04:50,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067490.0, "order_id": "x-XEKWYICXBSIUT605a7a5e26b1a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35431424", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:04:50,739 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a7a5e26b1a0582 completely filled. +2023-09-18 20:05:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7a5e26ec00582. [clock=2023-09-18 20:05:16+00:00] +2023-09-18 20:05:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236723900077109947880882775 amount: 80. +2023-09-18 20:05:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239886170079129433990043655 amount: 80. +2023-09-18 20:05:16,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067516.0, "order_id": "x-XEKWYICXSSIUT605a7a5e26ec00582", "exchange_order_id": "35431423", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:05:16,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7a5e26ec00582. +2023-09-18 20:05:16,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7a929ac4d0582 for 80.00000000 SEI-USDT. +2023-09-18 20:05:16,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7a929ac4d0582", "creation_timestamp": 1695067516.0, "exchange_order_id": "35431584", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:05:16,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7a929a9820582 for 80.00000000 SEI-USDT. +2023-09-18 20:05:16,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7a929a9820582", "creation_timestamp": 1695067516.0, "exchange_order_id": "35431585", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:06:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7a929a9820582. [clock=2023-09-18 20:06:11+00:00] +2023-09-18 20:06:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7a929ac4d0582. [clock=2023-09-18 20:06:11+00:00] +2023-09-18 20:06:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237007960956570589082736788 amount: 80. +2023-09-18 20:06:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239467370481264750937378788 amount: 80. +2023-09-18 20:06:11,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067571.0, "order_id": "x-XEKWYICXBSIUT605a7a929a9820582", "exchange_order_id": "35431585", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:06:11,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7a929a9820582. +2023-09-18 20:06:11,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067571.0, "order_id": "x-XEKWYICXSSIUT605a7a929ac4d0582", "exchange_order_id": "35431584", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:06:11,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7a929ac4d0582. +2023-09-18 20:06:11,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7ac70e8560582 for 80.00000000 SEI-USDT. +2023-09-18 20:06:11,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7ac70e8560582", "creation_timestamp": 1695067571.0, "exchange_order_id": "35431675", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:06:11,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7ac70eb840582 for 80.00000000 SEI-USDT. +2023-09-18 20:06:11,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7ac70eb840582", "creation_timestamp": 1695067571.0, "exchange_order_id": "35431676", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:07:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7ac70e8560582. [clock=2023-09-18 20:07:06+00:00] +2023-09-18 20:07:06,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7ac70eb840582. [clock=2023-09-18 20:07:06+00:00] +2023-09-18 20:07:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235228456367365326478499527 amount: 80. +2023-09-18 20:07:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 20:07:06,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067626.0, "order_id": "x-XEKWYICXBSIUT605a7ac70e8560582", "exchange_order_id": "35431675", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:07:06,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7ac70e8560582. +2023-09-18 20:07:06,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067626.0, "order_id": "x-XEKWYICXSSIUT605a7ac70eb840582", "exchange_order_id": "35431676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:07:06,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7ac70eb840582. +2023-09-18 20:07:06,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7afb828f40582 for 80.00000000 SEI-USDT. +2023-09-18 20:07:06,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605a7afb828f40582", "creation_timestamp": 1695067626.0, "exchange_order_id": "35431780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:07:06,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7afb82c9f0582 for 80.00000000 SEI-USDT. +2023-09-18 20:07:06,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a7afb82c9f0582", "creation_timestamp": 1695067626.0, "exchange_order_id": "35431781", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:07:28,839 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7afb82c9f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:07:28,841 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:07:28+00:00] +2023-09-18 20:07:28,863 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067648.0, "order_id": "x-XEKWYICXSSIUT605a7afb82c9f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4980344", "exchange_order_id": "35431781", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:07:28,876 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067648.0, "order_id": "x-XEKWYICXSSIUT605a7afb82c9f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35431781", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:07:28,877 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7afb82c9f0582 completely filled. +2023-09-18 20:08:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7afb828f40582. [clock=2023-09-18 20:08:01+00:00] +2023-09-18 20:08:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237260360097086861454827915 amount: 80. +2023-09-18 20:08:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240360079513870023850699286 amount: 80. +2023-09-18 20:08:01,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067681.0, "order_id": "x-XEKWYICXBSIUT605a7afb828f40582", "exchange_order_id": "35431780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:08:01,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7afb828f40582. +2023-09-18 20:08:01,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7b2ff62440582 for 80.00000000 SEI-USDT. +2023-09-18 20:08:01,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7b2ff62440582", "creation_timestamp": 1695067681.0, "exchange_order_id": "35431926", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:08:01,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7b2ff648c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:08:01,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a7b2ff648c0582", "creation_timestamp": 1695067681.0, "exchange_order_id": "35431927", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:08:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7b2ff62440582. [clock=2023-09-18 20:08:56+00:00] +2023-09-18 20:08:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7b2ff648c0582. [clock=2023-09-18 20:08:56+00:00] +2023-09-18 20:08:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237424739208640724830662145 amount: 80. +2023-09-18 20:08:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:08:56,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067736.0, "order_id": "x-XEKWYICXBSIUT605a7b2ff62440582", "exchange_order_id": "35431926", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:08:56,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7b2ff62440582. +2023-09-18 20:08:56,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067736.0, "order_id": "x-XEKWYICXSSIUT605a7b2ff648c0582", "exchange_order_id": "35431927", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:08:56,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7b2ff648c0582. +2023-09-18 20:08:56,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7b6469c410582 for 80.00000000 SEI-USDT. +2023-09-18 20:08:56,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7b6469c410582", "creation_timestamp": 1695067736.0, "exchange_order_id": "35432077", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:09:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7b6469c410582. [clock=2023-09-18 20:09:51+00:00] +2023-09-18 20:09:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237552714441251530526979328 amount: 80. +2023-09-18 20:09:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239427368706351700390284144 amount: 80. +2023-09-18 20:09:51,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "order_id": "x-XEKWYICXBSIUT605a7b6469c410582", "exchange_order_id": "35432077", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:09:51,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7b6469c410582. +2023-09-18 20:09:51,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7b98dd72c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:09:51,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7b98dd72c0582", "creation_timestamp": 1695067791.0, "exchange_order_id": "35432218", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:09:51,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7b98dd4b80582 for 80.00000000 SEI-USDT. +2023-09-18 20:09:51,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7b98dd4b80582", "creation_timestamp": 1695067791.0, "exchange_order_id": "35432217", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:09:51,288 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7b98dd72c0582 amounting to 30.40000000/80.00000000 SEI has been filled. +2023-09-18 20:09:51,289 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 30.40 SEI-USDT binance at 0.12 [clock=2023-09-18 20:09:51+00:00] +2023-09-18 20:09:51,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "order_id": "x-XEKWYICXSSIUT605a7b98dd72c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "30.40000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00376656"}]}, "exchange_trade_id": "4980360", "exchange_order_id": "35432218", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:09:51,308 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7b98dd72c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:09:51,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 49.60 SEI-USDT binance at 0.12 [clock=2023-09-18 20:09:51+00:00] +2023-09-18 20:09:51,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "order_id": "x-XEKWYICXSSIUT605a7b98dd72c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "49.60000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00614544"}]}, "exchange_trade_id": "4980361", "exchange_order_id": "35432218", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:09:51,354 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067791.0, "order_id": "x-XEKWYICXSSIUT605a7b98dd72c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35432218", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:09:51,355 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7b98dd72c0582 completely filled. +2023-09-18 20:10:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7b98dd4b80582. [clock=2023-09-18 20:10:46+00:00] +2023-09-18 20:10:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237700916803642298284686073 amount: 80. +2023-09-18 20:10:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:10:46,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067846.0, "order_id": "x-XEKWYICXBSIUT605a7b98dd4b80582", "exchange_order_id": "35432217", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:10:46,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7b98dd4b80582. +2023-09-18 20:10:46,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7bcd50c5e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:10:46,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7bcd50c5e0582", "creation_timestamp": 1695067846.0, "exchange_order_id": "35432417", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:11:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7bcd50c5e0582. [clock=2023-09-18 20:11:41+00:00] +2023-09-18 20:11:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237691889078879645428175319 amount: 80. +2023-09-18 20:11:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:11:41,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067901.0, "order_id": "x-XEKWYICXBSIUT605a7bcd50c5e0582", "exchange_order_id": "35432417", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:11:41,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7bcd50c5e0582. +2023-09-18 20:11:41,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7c01c59c00582 for 80.00000000 SEI-USDT. +2023-09-18 20:11:41,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7c01c59c00582", "creation_timestamp": 1695067901.0, "exchange_order_id": "35432495", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:12:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7c01c59c00582. [clock=2023-09-18 20:12:36+00:00] +2023-09-18 20:12:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237760600998295071123070277 amount: 80. +2023-09-18 20:12:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:12:36,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067956.0, "order_id": "x-XEKWYICXBSIUT605a7c01c59c00582", "exchange_order_id": "35432495", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:12:36,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7c01c59c00582. +2023-09-18 20:12:36,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7c36384dc0582 for 80.00000000 SEI-USDT. +2023-09-18 20:12:36,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695067956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7c36384dc0582", "creation_timestamp": 1695067956.0, "exchange_order_id": "35432581", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:13:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7c36384dc0582. [clock=2023-09-18 20:13:31+00:00] +2023-09-18 20:13:31,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237813947682275760498158752 amount: 80. +2023-09-18 20:13:31,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:13:31,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068011.0, "order_id": "x-XEKWYICXBSIUT605a7c36384dc0582", "exchange_order_id": "35432581", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:13:31,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7c36384dc0582. +2023-09-18 20:13:31,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7c6ab1c160582 for 80.00000000 SEI-USDT. +2023-09-18 20:13:31,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7c6ab1c160582", "creation_timestamp": 1695068011.0, "exchange_order_id": "35432755", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:14:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7c6ab1c160582. [clock=2023-09-18 20:14:26+00:00] +2023-09-18 20:14:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237855381515987483691004456 amount: 80. +2023-09-18 20:14:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:14:26,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068066.0, "order_id": "x-XEKWYICXBSIUT605a7c6ab1c160582", "exchange_order_id": "35432755", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:14:26,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7c6ab1c160582. +2023-09-18 20:14:26,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7c9f1fe6c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:14:26,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7c9f1fe6c0582", "creation_timestamp": 1695068066.0, "exchange_order_id": "35432850", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:15:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7c9f1fe6c0582. [clock=2023-09-18 20:15:21+00:00] +2023-09-18 20:15:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 20:15:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:15:21,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068121.0, "order_id": "x-XEKWYICXBSIUT605a7c9f1fe6c0582", "exchange_order_id": "35432850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:15:21,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7c9f1fe6c0582. +2023-09-18 20:15:21,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7cd393d7f0582 for 80.00000000 SEI-USDT. +2023-09-18 20:15:21,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a7cd393d7f0582", "creation_timestamp": 1695068121.0, "exchange_order_id": "35432976", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:16:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7cd393d7f0582. [clock=2023-09-18 20:16:16+00:00] +2023-09-18 20:16:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 20:16:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:16:16,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068176.0, "order_id": "x-XEKWYICXBSIUT605a7cd393d7f0582", "exchange_order_id": "35432976", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:16:16,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7cd393d7f0582. +2023-09-18 20:16:16,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7d08078e00582 for 80.00000000 SEI-USDT. +2023-09-18 20:16:16,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7d08078e00582", "creation_timestamp": 1695068176.0, "exchange_order_id": "35433300", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:17:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7d08078e00582. [clock=2023-09-18 20:17:11+00:00] +2023-09-18 20:17:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 20:17:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:17:11,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068231.0, "order_id": "x-XEKWYICXBSIUT605a7d08078e00582", "exchange_order_id": "35433300", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:17:11,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7d08078e00582. +2023-09-18 20:17:11,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7d3c7b2db0582 for 80.00000000 SEI-USDT. +2023-09-18 20:17:11,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7d3c7b2db0582", "creation_timestamp": 1695068231.0, "exchange_order_id": "35433548", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:18:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7d3c7b2db0582. [clock=2023-09-18 20:18:06+00:00] +2023-09-18 20:18:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 20:18:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:18:06,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068286.0, "order_id": "x-XEKWYICXBSIUT605a7d3c7b2db0582", "exchange_order_id": "35433548", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:18:06,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7d3c7b2db0582. +2023-09-18 20:18:06,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7d70ef43b0582 for 80.00000000 SEI-USDT. +2023-09-18 20:18:06,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7d70ef43b0582", "creation_timestamp": 1695068286.0, "exchange_order_id": "35433659", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:19:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7d70ef43b0582. [clock=2023-09-18 20:19:01+00:00] +2023-09-18 20:19:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 20:19:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:19:01,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068341.0, "order_id": "x-XEKWYICXBSIUT605a7d70ef43b0582", "exchange_order_id": "35433659", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:19:01,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7d70ef43b0582. +2023-09-18 20:19:01,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7da5625e10582 for 80.00000000 SEI-USDT. +2023-09-18 20:19:01,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7da5625e10582", "creation_timestamp": 1695068341.0, "exchange_order_id": "35433824", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:19:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7da5625e10582. [clock=2023-09-18 20:19:56+00:00] +2023-09-18 20:19:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 20:19:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:19:56,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068396.0, "order_id": "x-XEKWYICXBSIUT605a7da5625e10582", "exchange_order_id": "35433824", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:19:56,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7da5625e10582. +2023-09-18 20:19:56,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7dd9d6b210582 for 80.00000000 SEI-USDT. +2023-09-18 20:19:56,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a7dd9d6b210582", "creation_timestamp": 1695068396.0, "exchange_order_id": "35433982", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:20:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7dd9d6b210582. [clock=2023-09-18 20:20:51+00:00] +2023-09-18 20:20:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:20:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:20:51,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068451.0, "order_id": "x-XEKWYICXBSIUT605a7dd9d6b210582", "exchange_order_id": "35433982", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:20:51,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7dd9d6b210582. +2023-09-18 20:20:51,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7e0e4a0cd0582 for 80.00000000 SEI-USDT. +2023-09-18 20:20:51,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a7e0e4a0cd0582", "creation_timestamp": 1695068451.0, "exchange_order_id": "35434267", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:20:52,017 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a7e0e4a0cd0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:20:52,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:20:52+00:00] +2023-09-18 20:20:52,042 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068452.0, "order_id": "x-XEKWYICXBSIUT605a7e0e4a0cd0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980448", "exchange_order_id": "35434267", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:20:52,058 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068452.0, "order_id": "x-XEKWYICXBSIUT605a7e0e4a0cd0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35434267", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:20:52,059 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a7e0e4a0cd0582 completely filled. +2023-09-18 20:21:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:21:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241927386031067779159297478 amount: 80. +2023-09-18 20:21:46,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7e42bd7290582 for 80.00000000 SEI-USDT. +2023-09-18 20:21:46,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a7e42bd7290582", "creation_timestamp": 1695068506.0, "exchange_order_id": "35434469", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:21:46,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7e42bd9f50582 for 80.00000000 SEI-USDT. +2023-09-18 20:21:46,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a7e42bd9f50582", "creation_timestamp": 1695068506.0, "exchange_order_id": "35434470", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:22:07,351 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a7e42bd7290582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:22:07,352 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:22:07+00:00] +2023-09-18 20:22:07,375 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068527.0, "order_id": "x-XEKWYICXBSIUT605a7e42bd7290582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980462", "exchange_order_id": "35434469", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:22:07,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068527.0, "order_id": "x-XEKWYICXBSIUT605a7e42bd7290582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35434469", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:22:07,388 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a7e42bd7290582 completely filled. +2023-09-18 20:22:41,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7e42bd9f50582. [clock=2023-09-18 20:22:41+00:00] +2023-09-18 20:22:41,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238775961533083325245404003 amount: 80. +2023-09-18 20:22:41,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240386486562614783432352986 amount: 80. +2023-09-18 20:22:41,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068561.0, "order_id": "x-XEKWYICXSSIUT605a7e42bd9f50582", "exchange_order_id": "35434470", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:22:41,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7e42bd9f50582. +2023-09-18 20:22:41,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7e77388810582 for 80.00000000 SEI-USDT. +2023-09-18 20:22:41,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a7e77388810582", "creation_timestamp": 1695068561.0, "exchange_order_id": "35434643", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:22:41,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7e7738c050582 for 80.00000000 SEI-USDT. +2023-09-18 20:22:41,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a7e7738c050582", "creation_timestamp": 1695068561.0, "exchange_order_id": "35434644", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:23:19,707 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7e7738c050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:23:19,708 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:23:19+00:00] +2023-09-18 20:23:19,738 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068599.0, "order_id": "x-XEKWYICXSSIUT605a7e7738c050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992000"}]}, "exchange_trade_id": "4980471", "exchange_order_id": "35434644", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:23:19,754 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068599.0, "order_id": "x-XEKWYICXSSIUT605a7e7738c050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35434644", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:23:19,755 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7e7738c050582 completely filled. +2023-09-18 20:23:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7e77388810582. [clock=2023-09-18 20:23:36+00:00] +2023-09-18 20:23:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:23:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242027305753849464542267911 amount: 80. +2023-09-18 20:23:36,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068616.0, "order_id": "x-XEKWYICXBSIUT605a7e77388810582", "exchange_order_id": "35434643", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:23:36,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7e77388810582. +2023-09-18 20:23:36,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7eaba58c40582 for 80.00000000 SEI-USDT. +2023-09-18 20:23:36,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a7eaba58c40582", "creation_timestamp": 1695068616.0, "exchange_order_id": "35434837", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:23:36,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7eaba5bcc0582 for 80.00000000 SEI-USDT. +2023-09-18 20:23:36,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a7eaba5bcc0582", "creation_timestamp": 1695068616.0, "exchange_order_id": "35434838", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:24:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7eaba58c40582. [clock=2023-09-18 20:24:31+00:00] +2023-09-18 20:24:31,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7eaba5bcc0582. [clock=2023-09-18 20:24:31+00:00] +2023-09-18 20:24:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:24:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:24:31,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068671.0, "order_id": "x-XEKWYICXBSIUT605a7eaba58c40582", "exchange_order_id": "35434837", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:24:31,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7eaba58c40582. +2023-09-18 20:24:31,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7ee01a0830582 for 80.00000000 SEI-USDT. +2023-09-18 20:24:31,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a7ee01a0830582", "creation_timestamp": 1695068671.0, "exchange_order_id": "35434975", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:24:31,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068671.0, "order_id": "x-XEKWYICXSSIUT605a7eaba5bcc0582", "exchange_order_id": "35434838", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:24:31,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7eaba5bcc0582. +2023-09-18 20:25:00,280 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a7ee01a0830582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:25:00,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:25:00+00:00] +2023-09-18 20:25:00,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068700.0, "order_id": "x-XEKWYICXBSIUT605a7ee01a0830582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980481", "exchange_order_id": "35434975", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:25:00,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068700.0, "order_id": "x-XEKWYICXBSIUT605a7ee01a0830582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35434975", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:25:00,316 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a7ee01a0830582 completely filled. +2023-09-18 20:25:15,117 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 20:25:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236835424740691500449814828 amount: 80. +2023-09-18 20:25:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239997800946999535385505002 amount: 80. +2023-09-18 20:25:26,099 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7f148c8ff0582 for 80.00000000 SEI-USDT. +2023-09-18 20:25:26,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7f148c8ff0582", "creation_timestamp": 1695068726.0, "exchange_order_id": "35435305", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:25:26,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7f148cfa40582 for 80.00000000 SEI-USDT. +2023-09-18 20:25:26,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7f148cfa40582", "creation_timestamp": 1695068726.0, "exchange_order_id": "35435306", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:26:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7f148c8ff0582. [clock=2023-09-18 20:26:21+00:00] +2023-09-18 20:26:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7f148cfa40582. [clock=2023-09-18 20:26:21+00:00] +2023-09-18 20:26:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236824742765451453417295769 amount: 80. +2023-09-18 20:26:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239951187393221915450648917 amount: 80. +2023-09-18 20:26:21,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068781.0, "order_id": "x-XEKWYICXBSIUT605a7f148c8ff0582", "exchange_order_id": "35435305", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:26:21,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7f148c8ff0582. +2023-09-18 20:26:21,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068781.0, "order_id": "x-XEKWYICXSSIUT605a7f148cfa40582", "exchange_order_id": "35435306", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:26:21,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7f148cfa40582. +2023-09-18 20:26:21,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7f49010500582 for 80.00000000 SEI-USDT. +2023-09-18 20:26:21,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7f49010500582", "creation_timestamp": 1695068781.0, "exchange_order_id": "35435466", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:26:21,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7f4900dac0582 for 80.00000000 SEI-USDT. +2023-09-18 20:26:21,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a7f4900dac0582", "creation_timestamp": 1695068781.0, "exchange_order_id": "35435467", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:27:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7f4900dac0582. [clock=2023-09-18 20:27:16+00:00] +2023-09-18 20:27:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7f49010500582. [clock=2023-09-18 20:27:16+00:00] +2023-09-18 20:27:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237269699111127131510956810 amount: 80. +2023-09-18 20:27:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239699605351874174592348754 amount: 80. +2023-09-18 20:27:16,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068836.0, "order_id": "x-XEKWYICXBSIUT605a7f4900dac0582", "exchange_order_id": "35435467", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:27:16,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7f4900dac0582. +2023-09-18 20:27:16,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068836.0, "order_id": "x-XEKWYICXSSIUT605a7f49010500582", "exchange_order_id": "35435466", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:27:16,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7f49010500582. +2023-09-18 20:27:16,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7f7d74a0e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:27:16,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7f7d74a0e0582", "creation_timestamp": 1695068836.0, "exchange_order_id": "35435606", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:27:16,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7f7d7461e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:27:16,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7f7d7461e0582", "creation_timestamp": 1695068836.0, "exchange_order_id": "35435605", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:28:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7f7d7461e0582. [clock=2023-09-18 20:28:11+00:00] +2023-09-18 20:28:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7f7d74a0e0582. [clock=2023-09-18 20:28:11+00:00] +2023-09-18 20:28:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237432086420903226149364092 amount: 80. +2023-09-18 20:28:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239321849170636335258830164 amount: 80. +2023-09-18 20:28:11,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068891.0, "order_id": "x-XEKWYICXBSIUT605a7f7d7461e0582", "exchange_order_id": "35435605", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:28:11,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7f7d7461e0582. +2023-09-18 20:28:11,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068891.0, "order_id": "x-XEKWYICXSSIUT605a7f7d74a0e0582", "exchange_order_id": "35435606", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:28:11,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7f7d74a0e0582. +2023-09-18 20:28:11,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7fb1e84320582 for 80.00000000 SEI-USDT. +2023-09-18 20:28:11,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a7fb1e84320582", "creation_timestamp": 1695068891.0, "exchange_order_id": "35435744", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:28:11,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7fb1e86a10582 for 80.00000000 SEI-USDT. +2023-09-18 20:28:11,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a7fb1e86a10582", "creation_timestamp": 1695068891.0, "exchange_order_id": "35435745", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:28:13,544 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a7fb1e86a10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:28:13,545 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:28:13+00:00] +2023-09-18 20:28:13,570 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068893.0, "order_id": "x-XEKWYICXSSIUT605a7fb1e86a10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4980519", "exchange_order_id": "35435745", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:28:13,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068893.0, "order_id": "x-XEKWYICXSSIUT605a7fb1e86a10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35435745", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:28:13,582 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a7fb1e86a10582 completely filled. +2023-09-18 20:29:06,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7fb1e84320582. [clock=2023-09-18 20:29:06+00:00] +2023-09-18 20:29:06,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238707141718293835666098219 amount: 80. +2023-09-18 20:29:06,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240178222074197650995798013 amount: 80. +2023-09-18 20:29:06,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068946.0, "order_id": "x-XEKWYICXBSIUT605a7fb1e84320582", "exchange_order_id": "35435744", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:29:06,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7fb1e84320582. +2023-09-18 20:29:06,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a7fe666c070582 for 80.00000000 SEI-USDT. +2023-09-18 20:29:06,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a7fe666c070582", "creation_timestamp": 1695068946.0, "exchange_order_id": "35436018", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:29:06,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a7fe666fc10582 for 80.00000000 SEI-USDT. +2023-09-18 20:29:06,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695068946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a7fe666fc10582", "creation_timestamp": 1695068946.0, "exchange_order_id": "35436019", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:30:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a7fe666c070582. [clock=2023-09-18 20:30:01+00:00] +2023-09-18 20:30:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a7fe666fc10582. [clock=2023-09-18 20:30:01+00:00] +2023-09-18 20:30:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238869415088316418623087410 amount: 80. +2023-09-18 20:30:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:30:01,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069001.0, "order_id": "x-XEKWYICXSSIUT605a7fe666fc10582", "exchange_order_id": "35436019", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:30:01,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a7fe666fc10582. +2023-09-18 20:30:01,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069001.0, "order_id": "x-XEKWYICXBSIUT605a7fe666c070582", "exchange_order_id": "35436018", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:30:01,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a7fe666c070582. +2023-09-18 20:30:01,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a801acfc000582 for 80.00000000 SEI-USDT. +2023-09-18 20:30:01,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a801acfc000582", "creation_timestamp": 1695069001.0, "exchange_order_id": "35436133", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:30:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a801acfc000582. [clock=2023-09-18 20:30:56+00:00] +2023-09-18 20:30:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238898557937326136039198718 amount: 80. +2023-09-18 20:30:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240560478816132309974000552 amount: 80. +2023-09-18 20:30:56,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069056.0, "order_id": "x-XEKWYICXBSIUT605a801acfc000582", "exchange_order_id": "35436133", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:30:56,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a801acfc000582. +2023-09-18 20:30:56,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a804f4340b0582 for 80.00000000 SEI-USDT. +2023-09-18 20:30:56,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a804f4340b0582", "creation_timestamp": 1695069056.0, "exchange_order_id": "35436213", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:30:56,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a804f437cf0582 for 80.00000000 SEI-USDT. +2023-09-18 20:30:56,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a804f437cf0582", "creation_timestamp": 1695069056.0, "exchange_order_id": "35436214", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:30:58,115 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a804f437cf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:30:58,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:30:58+00:00] +2023-09-18 20:30:58,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069058.0, "order_id": "x-XEKWYICXSSIUT605a804f437cf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992000"}]}, "exchange_trade_id": "4980533", "exchange_order_id": "35436214", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:30:58,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069058.0, "order_id": "x-XEKWYICXSSIUT605a804f437cf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35436214", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:30:58,158 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a804f437cf0582 completely filled. +2023-09-18 20:31:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a804f4340b0582. [clock=2023-09-18 20:31:51+00:00] +2023-09-18 20:31:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:31:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:31:51,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069111.0, "order_id": "x-XEKWYICXBSIUT605a804f4340b0582", "exchange_order_id": "35436213", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:31:51,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a804f4340b0582. +2023-09-18 20:31:51,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8083b6f1c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:31:51,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a8083b6f1c0582", "creation_timestamp": 1695069111.0, "exchange_order_id": "35436387", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:32:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8083b6f1c0582. [clock=2023-09-18 20:32:46+00:00] +2023-09-18 20:32:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:32:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:32:46,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069166.0, "order_id": "x-XEKWYICXBSIUT605a8083b6f1c0582", "exchange_order_id": "35436387", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:32:46,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8083b6f1c0582. +2023-09-18 20:32:46,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a80b82ab100582 for 80.00000000 SEI-USDT. +2023-09-18 20:32:46,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a80b82ab100582", "creation_timestamp": 1695069166.0, "exchange_order_id": "35436546", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:33:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a80b82ab100582. [clock=2023-09-18 20:33:41+00:00] +2023-09-18 20:33:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239962910890679126212406597 amount: 80. +2023-09-18 20:33:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:33:41,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069221.0, "order_id": "x-XEKWYICXBSIUT605a80b82ab100582", "exchange_order_id": "35436546", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:33:41,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a80b82ab100582. +2023-09-18 20:33:41,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a80ec9ebdd0582 for 80.00000000 SEI-USDT. +2023-09-18 20:33:41,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a80ec9ebdd0582", "creation_timestamp": 1695069221.0, "exchange_order_id": "35436706", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:34:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a80ec9ebdd0582. [clock=2023-09-18 20:34:36+00:00] +2023-09-18 20:34:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:34:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:34:36,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069276.0, "order_id": "x-XEKWYICXBSIUT605a80ec9ebdd0582", "exchange_order_id": "35436706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:34:36,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a80ec9ebdd0582. +2023-09-18 20:34:36,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8121124010582 for 80.00000000 SEI-USDT. +2023-09-18 20:34:36,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a8121124010582", "creation_timestamp": 1695069276.0, "exchange_order_id": "35436793", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:35:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8121124010582. [clock=2023-09-18 20:35:31+00:00] +2023-09-18 20:35:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:35:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:35:31,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069331.0, "order_id": "x-XEKWYICXBSIUT605a8121124010582", "exchange_order_id": "35436793", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:35:31,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8121124010582. +2023-09-18 20:35:31,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8155862db0582 for 80.00000000 SEI-USDT. +2023-09-18 20:35:31,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8155862db0582", "creation_timestamp": 1695069331.0, "exchange_order_id": "35436950", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:36:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8155862db0582. [clock=2023-09-18 20:36:26+00:00] +2023-09-18 20:36:26,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:36:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:36:26,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069386.0, "order_id": "x-XEKWYICXBSIUT605a8155862db0582", "exchange_order_id": "35436950", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:36:26,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8155862db0582. +2023-09-18 20:36:26,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8189f975a0582 for 80.00000000 SEI-USDT. +2023-09-18 20:36:26,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8189f975a0582", "creation_timestamp": 1695069386.0, "exchange_order_id": "35437047", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:37:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8189f975a0582. [clock=2023-09-18 20:37:21+00:00] +2023-09-18 20:37:21,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:37:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:37:21,101 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069441.0, "order_id": "x-XEKWYICXBSIUT605a8189f975a0582", "exchange_order_id": "35437047", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:37:21,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8189f975a0582. +2023-09-18 20:37:21,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a81be6d2ae0582 for 80.00000000 SEI-USDT. +2023-09-18 20:37:21,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a81be6d2ae0582", "creation_timestamp": 1695069441.0, "exchange_order_id": "35437211", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:38:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a81be6d2ae0582. [clock=2023-09-18 20:38:16+00:00] +2023-09-18 20:38:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:38:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:38:16,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069496.0, "order_id": "x-XEKWYICXBSIUT605a81be6d2ae0582", "exchange_order_id": "35437211", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:38:16,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a81be6d2ae0582. +2023-09-18 20:38:16,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a81f2e13660582 for 80.00000000 SEI-USDT. +2023-09-18 20:38:16,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a81f2e13660582", "creation_timestamp": 1695069496.0, "exchange_order_id": "35437380", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:39:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a81f2e13660582. [clock=2023-09-18 20:39:11+00:00] +2023-09-18 20:39:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240983239497935721436839912 amount: 80. +2023-09-18 20:39:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:39:11,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069551.0, "order_id": "x-XEKWYICXBSIUT605a81f2e13660582", "exchange_order_id": "35437380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:39:11,065 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a81f2e13660582. +2023-09-18 20:39:11,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a822754b080582 for 80.00000000 SEI-USDT. +2023-09-18 20:39:11,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a822754b080582", "creation_timestamp": 1695069551.0, "exchange_order_id": "35437540", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:39:42,666 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a822754b080582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:39:42,667 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:39:42+00:00] +2023-09-18 20:39:42,693 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069582.0, "order_id": "x-XEKWYICXBSIUT605a822754b080582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980663", "exchange_order_id": "35437540", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:39:42,709 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069582.0, "order_id": "x-XEKWYICXBSIUT605a822754b080582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35437540", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:39:42,710 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a822754b080582 completely filled. +2023-09-18 20:40:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237279605538653487348223369 amount: 80. +2023-09-18 20:40:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239823718522668576416221332 amount: 80. +2023-09-18 20:40:06,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a825bc86120582 for 80.00000000 SEI-USDT. +2023-09-18 20:40:06,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a825bc86120582", "creation_timestamp": 1695069606.0, "exchange_order_id": "35437965", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:40:06,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a825bc88400582 for 80.00000000 SEI-USDT. +2023-09-18 20:40:06,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a825bc88400582", "creation_timestamp": 1695069606.0, "exchange_order_id": "35437966", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:40:10,792 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a825bc88400582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:40:10,793 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:40:10+00:00] +2023-09-18 20:40:10,817 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069610.0, "order_id": "x-XEKWYICXSSIUT605a825bc88400582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4980699", "exchange_order_id": "35437966", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:40:10,836 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069610.0, "order_id": "x-XEKWYICXSSIUT605a825bc88400582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35437966", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:40:10,836 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a825bc88400582 completely filled. +2023-09-18 20:41:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a825bc86120582. [clock=2023-09-18 20:41:01+00:00] +2023-09-18 20:41:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238537724007494574400350764 amount: 80. +2023-09-18 20:41:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:41:01,062 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069661.0, "order_id": "x-XEKWYICXBSIUT605a825bc86120582", "exchange_order_id": "35437965", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:41:01,063 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a825bc86120582. +2023-09-18 20:41:01,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a82903c5b00582 for 80.00000000 SEI-USDT. +2023-09-18 20:41:01,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a82903c5b00582", "creation_timestamp": 1695069661.0, "exchange_order_id": "35438079", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:41:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a82903c5b00582. [clock=2023-09-18 20:41:56+00:00] +2023-09-18 20:41:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239830253330671177589206546 amount: 80. +2023-09-18 20:41:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:41:56,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069716.0, "order_id": "x-XEKWYICXBSIUT605a82903c5b00582", "exchange_order_id": "35438079", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:41:56,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a82903c5b00582. +2023-09-18 20:41:56,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a82c4afe300582 for 80.00000000 SEI-USDT. +2023-09-18 20:41:56,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a82c4afe300582", "creation_timestamp": 1695069716.0, "exchange_order_id": "35438221", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:42:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a82c4afe300582. [clock=2023-09-18 20:42:51+00:00] +2023-09-18 20:42:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:42:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:42:51,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069771.0, "order_id": "x-XEKWYICXBSIUT605a82c4afe300582", "exchange_order_id": "35438221", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:42:51,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a82c4afe300582. +2023-09-18 20:42:51,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a82f923d690582 for 80.00000000 SEI-USDT. +2023-09-18 20:42:51,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a82f923d690582", "creation_timestamp": 1695069771.0, "exchange_order_id": "35438354", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:43:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a82f923d690582. [clock=2023-09-18 20:43:46+00:00] +2023-09-18 20:43:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 20:43:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:43:46,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069826.0, "order_id": "x-XEKWYICXBSIUT605a82f923d690582", "exchange_order_id": "35438354", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:43:46,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a82f923d690582. +2023-09-18 20:43:46,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a832d97a2f0582 for 80.00000000 SEI-USDT. +2023-09-18 20:43:46,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a832d97a2f0582", "creation_timestamp": 1695069826.0, "exchange_order_id": "35438456", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:43:49,728 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a832d97a2f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:43:49,729 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:43:49+00:00] +2023-09-18 20:43:49,765 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069829.0, "order_id": "x-XEKWYICXBSIUT605a832d97a2f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980775", "exchange_order_id": "35438456", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:43:49,785 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069829.0, "order_id": "x-XEKWYICXBSIUT605a832d97a2f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35438456", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:43:49,785 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a832d97a2f0582 completely filled. +2023-09-18 20:44:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239578055683022308840239630 amount: 80. +2023-09-18 20:44:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242149977051567781760184060 amount: 80. +2023-09-18 20:44:41,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a83620acbe0582 for 80.00000000 SEI-USDT. +2023-09-18 20:44:41,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a83620acbe0582", "creation_timestamp": 1695069881.0, "exchange_order_id": "35438711", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:44:41,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a83620af100582 for 80.00000000 SEI-USDT. +2023-09-18 20:44:41,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a83620af100582", "creation_timestamp": 1695069881.0, "exchange_order_id": "35438712", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:45:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a83620acbe0582. [clock=2023-09-18 20:45:36+00:00] +2023-09-18 20:45:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a83620af100582. [clock=2023-09-18 20:45:36+00:00] +2023-09-18 20:45:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240798498709802264153046073 amount: 80. +2023-09-18 20:45:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:45:36,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069936.0, "order_id": "x-XEKWYICXBSIUT605a83620acbe0582", "exchange_order_id": "35438711", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:45:36,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a83620acbe0582. +2023-09-18 20:45:36,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a83967f3f60582 for 80.00000000 SEI-USDT. +2023-09-18 20:45:36,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a83967f3f60582", "creation_timestamp": 1695069936.0, "exchange_order_id": "35438889", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:45:36,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069936.0, "order_id": "x-XEKWYICXSSIUT605a83620af100582", "exchange_order_id": "35438712", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:45:36,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a83620af100582. +2023-09-18 20:46:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a83967f3f60582. [clock=2023-09-18 20:46:31+00:00] +2023-09-18 20:46:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241971830542041660938140131 amount: 80. +2023-09-18 20:46:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244450826986427426660210920 amount: 80. +2023-09-18 20:46:31,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069991.0, "order_id": "x-XEKWYICXBSIUT605a83967f3f60582", "exchange_order_id": "35438889", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:46:31,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a83967f3f60582. +2023-09-18 20:46:31,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a83caf32a70582 for 80.00000000 SEI-USDT. +2023-09-18 20:46:31,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605a83caf32a70582", "creation_timestamp": 1695069991.0, "exchange_order_id": "35439081", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:46:31,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a83caf2ea00582 for 80.00000000 SEI-USDT. +2023-09-18 20:46:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695069991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a83caf2ea00582", "creation_timestamp": 1695069991.0, "exchange_order_id": "35439080", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:47:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a83caf2ea00582. [clock=2023-09-18 20:47:26+00:00] +2023-09-18 20:47:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a83caf32a70582. [clock=2023-09-18 20:47:26+00:00] +2023-09-18 20:47:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-18 20:47:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:47:26,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070046.0, "order_id": "x-XEKWYICXBSIUT605a83caf2ea00582", "exchange_order_id": "35439080", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:47:26,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a83caf2ea00582. +2023-09-18 20:47:26,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a83ff66d3b0582 for 80.00000000 SEI-USDT. +2023-09-18 20:47:26,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a83ff66d3b0582", "creation_timestamp": 1695070046.0, "exchange_order_id": "35439215", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:47:26,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070046.0, "order_id": "x-XEKWYICXSSIUT605a83caf32a70582", "exchange_order_id": "35439081", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:47:26,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a83caf32a70582. +2023-09-18 20:48:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a83ff66d3b0582. [clock=2023-09-18 20:48:21+00:00] +2023-09-18 20:48:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241977031385183692605439443 amount: 80. +2023-09-18 20:48:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243995329857535286428472488 amount: 80. +2023-09-18 20:48:21,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070101.0, "order_id": "x-XEKWYICXBSIUT605a83ff66d3b0582", "exchange_order_id": "35439215", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:48:21,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a83ff66d3b0582. +2023-09-18 20:48:21,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8433dabb90582 for 80.00000000 SEI-USDT. +2023-09-18 20:48:21,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605a8433dabb90582", "creation_timestamp": 1695070101.0, "exchange_order_id": "35439333", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:48:21,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8433da7e40582 for 80.00000000 SEI-USDT. +2023-09-18 20:48:21,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8433da7e40582", "creation_timestamp": 1695070101.0, "exchange_order_id": "35439334", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:49:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8433da7e40582. [clock=2023-09-18 20:49:16+00:00] +2023-09-18 20:49:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8433dabb90582. [clock=2023-09-18 20:49:16+00:00] +2023-09-18 20:49:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240976225364656855176446795 amount: 80. +2023-09-18 20:49:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:49:16,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070156.0, "order_id": "x-XEKWYICXBSIUT605a8433da7e40582", "exchange_order_id": "35439334", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:49:16,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8433da7e40582. +2023-09-18 20:49:16,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070156.0, "order_id": "x-XEKWYICXSSIUT605a8433dabb90582", "exchange_order_id": "35439333", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:49:16,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8433dabb90582. +2023-09-18 20:49:16,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a84684e3b00582 for 80.00000000 SEI-USDT. +2023-09-18 20:49:16,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a84684e3b00582", "creation_timestamp": 1695070156.0, "exchange_order_id": "35439551", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:50:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a84684e3b00582. [clock=2023-09-18 20:50:11+00:00] +2023-09-18 20:50:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240704086001305354191269055 amount: 80. +2023-09-18 20:50:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242847212655812546868229385 amount: 80. +2023-09-18 20:50:11,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070211.0, "order_id": "x-XEKWYICXBSIUT605a84684e3b00582", "exchange_order_id": "35439551", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:50:11,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a84684e3b00582. +2023-09-18 20:50:11,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a849cc1a9c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:50:11,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a849cc1a9c0582", "creation_timestamp": 1695070211.0, "exchange_order_id": "35439634", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:50:11,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a849cc1df80582 for 80.00000000 SEI-USDT. +2023-09-18 20:50:11,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a849cc1df80582", "creation_timestamp": 1695070211.0, "exchange_order_id": "35439635", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:51:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a849cc1a9c0582. [clock=2023-09-18 20:51:06+00:00] +2023-09-18 20:51:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a849cc1df80582. [clock=2023-09-18 20:51:06+00:00] +2023-09-18 20:51:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240769962881213809514427024 amount: 80. +2023-09-18 20:51:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:51:06,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070266.0, "order_id": "x-XEKWYICXBSIUT605a849cc1a9c0582", "exchange_order_id": "35439634", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:51:06,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a849cc1a9c0582. +2023-09-18 20:51:06,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a84d13582b0582 for 80.00000000 SEI-USDT. +2023-09-18 20:51:06,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a84d13582b0582", "creation_timestamp": 1695070266.0, "exchange_order_id": "35439707", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:51:06,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070266.0, "order_id": "x-XEKWYICXSSIUT605a849cc1df80582", "exchange_order_id": "35439635", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:51:06,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a849cc1df80582. +2023-09-18 20:52:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a84d13582b0582. [clock=2023-09-18 20:52:01+00:00] +2023-09-18 20:52:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240769962881213809514427024 amount: 80. +2023-09-18 20:52:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242436607798204283935582984 amount: 80. +2023-09-18 20:52:01,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070321.0, "order_id": "x-XEKWYICXBSIUT605a84d13582b0582", "exchange_order_id": "35439707", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:52:01,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a84d13582b0582. +2023-09-18 20:52:01,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8505a90e70582 for 80.00000000 SEI-USDT. +2023-09-18 20:52:01,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a8505a90e70582", "creation_timestamp": 1695070321.0, "exchange_order_id": "35439863", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:52:01,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8505a94960582 for 80.00000000 SEI-USDT. +2023-09-18 20:52:01,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a8505a94960582", "creation_timestamp": 1695070321.0, "exchange_order_id": "35439866", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:52:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8505a90e70582. [clock=2023-09-18 20:52:56+00:00] +2023-09-18 20:52:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8505a94960582. [clock=2023-09-18 20:52:56+00:00] +2023-09-18 20:52:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240821153753355183275590401 amount: 80. +2023-09-18 20:52:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:52:56,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070376.0, "order_id": "x-XEKWYICXBSIUT605a8505a90e70582", "exchange_order_id": "35439863", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:52:56,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8505a90e70582. +2023-09-18 20:52:56,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a853a1d07e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:52:56,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a853a1d07e0582", "creation_timestamp": 1695070376.0, "exchange_order_id": "35439970", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:52:56,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070376.0, "order_id": "x-XEKWYICXSSIUT605a8505a94960582", "exchange_order_id": "35439866", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:52:56,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8505a94960582. +2023-09-18 20:53:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a853a1d07e0582. [clock=2023-09-18 20:53:51+00:00] +2023-09-18 20:53:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240768778826293784351949672 amount: 80. +2023-09-18 20:53:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242443991056098524587555580 amount: 80. +2023-09-18 20:53:51,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070431.0, "order_id": "x-XEKWYICXBSIUT605a853a1d07e0582", "exchange_order_id": "35439970", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:53:51,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a853a1d07e0582. +2023-09-18 20:53:51,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a856e90a040582 for 80.00000000 SEI-USDT. +2023-09-18 20:53:51,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a856e90a040582", "creation_timestamp": 1695070431.0, "exchange_order_id": "35440171", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:53:51,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a856e90da80582 for 80.00000000 SEI-USDT. +2023-09-18 20:53:51,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a856e90da80582", "creation_timestamp": 1695070431.0, "exchange_order_id": "35440172", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:54:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a856e90a040582. [clock=2023-09-18 20:54:46+00:00] +2023-09-18 20:54:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a856e90da80582. [clock=2023-09-18 20:54:46+00:00] +2023-09-18 20:54:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 20:54:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:54:46,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070486.0, "order_id": "x-XEKWYICXBSIUT605a856e90a040582", "exchange_order_id": "35440171", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:54:46,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a856e90a040582. +2023-09-18 20:54:46,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070486.0, "order_id": "x-XEKWYICXSSIUT605a856e90da80582", "exchange_order_id": "35440172", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:54:46,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a856e90da80582. +2023-09-18 20:54:46,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a85a30485e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:54:46,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a85a30485e0582", "creation_timestamp": 1695070486.0, "exchange_order_id": "35440258", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:55:15,136 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 20:55:28,240 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a85a30485e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:55:28,241 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:55:28+00:00] +2023-09-18 20:55:28,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070528.0, "order_id": "x-XEKWYICXBSIUT605a85a30485e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980901", "exchange_order_id": "35440258", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:55:28,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070528.0, "order_id": "x-XEKWYICXBSIUT605a85a30485e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35440258", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:55:28,315 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a85a30485e0582 completely filled. +2023-09-18 20:55:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239011612720416104630294717 amount: 80. +2023-09-18 20:55:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241877396564996803999505949 amount: 80. +2023-09-18 20:55:41,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a85d7783bd0582 for 80.00000000 SEI-USDT. +2023-09-18 20:55:41,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a85d7783bd0582", "creation_timestamp": 1695070541.0, "exchange_order_id": "35440437", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:55:41,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a85d77877e0582 for 80.00000000 SEI-USDT. +2023-09-18 20:55:41,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a85d77877e0582", "creation_timestamp": 1695070541.0, "exchange_order_id": "35440438", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:55:50,635 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a85d77877e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 20:55:50,636 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 20:55:50+00:00] +2023-09-18 20:55:50,659 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070550.0, "order_id": "x-XEKWYICXSSIUT605a85d77877e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "4980911", "exchange_order_id": "35440438", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 20:55:50,674 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070550.0, "order_id": "x-XEKWYICXSSIUT605a85d77877e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35440438", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 20:55:50,674 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a85d77877e0582 completely filled. +2023-09-18 20:56:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a85d7783bd0582. [clock=2023-09-18 20:56:36+00:00] +2023-09-18 20:56:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240669095443101133253108789 amount: 80. +2023-09-18 20:56:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242898647103021504301297205 amount: 80. +2023-09-18 20:56:36,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070596.0, "order_id": "x-XEKWYICXBSIUT605a85d7783bd0582", "exchange_order_id": "35440437", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:56:36,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a85d7783bd0582. +2023-09-18 20:56:36,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a860bebdc70582 for 80.00000000 SEI-USDT. +2023-09-18 20:56:36,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a860bebdc70582", "creation_timestamp": 1695070596.0, "exchange_order_id": "35440554", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:56:36,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a860bebac00582 for 80.00000000 SEI-USDT. +2023-09-18 20:56:36,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a860bebac00582", "creation_timestamp": 1695070596.0, "exchange_order_id": "35440555", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:57:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a860bebac00582. [clock=2023-09-18 20:57:31+00:00] +2023-09-18 20:57:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a860bebdc70582. [clock=2023-09-18 20:57:31+00:00] +2023-09-18 20:57:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240742601973124406079888649 amount: 80. +2023-09-18 20:57:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:57:31,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070651.0, "order_id": "x-XEKWYICXBSIUT605a860bebac00582", "exchange_order_id": "35440555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:57:31,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a860bebac00582. +2023-09-18 20:57:31,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a86405fca90582 for 80.00000000 SEI-USDT. +2023-09-18 20:57:31,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a86405fca90582", "creation_timestamp": 1695070651.0, "exchange_order_id": "35440652", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:57:31,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070651.0, "order_id": "x-XEKWYICXSSIUT605a860bebdc70582", "exchange_order_id": "35440554", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:57:31,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a860bebdc70582. +2023-09-18 20:58:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a86405fca90582. [clock=2023-09-18 20:58:26+00:00] +2023-09-18 20:58:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239800039643843964533802643 amount: 80. +2023-09-18 20:58:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241147357751168923503551919 amount: 80. +2023-09-18 20:58:26,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070706.0, "order_id": "x-XEKWYICXBSIUT605a86405fca90582", "exchange_order_id": "35440652", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:58:26,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a86405fca90582. +2023-09-18 20:58:26,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8674d3da70582 for 80.00000000 SEI-USDT. +2023-09-18 20:58:26,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a8674d3da70582", "creation_timestamp": 1695070706.0, "exchange_order_id": "35440837", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:58:26,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8674d3a610582 for 80.00000000 SEI-USDT. +2023-09-18 20:58:26,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a8674d3a610582", "creation_timestamp": 1695070706.0, "exchange_order_id": "35440838", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 20:59:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8674d3a610582. [clock=2023-09-18 20:59:21+00:00] +2023-09-18 20:59:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8674d3da70582. [clock=2023-09-18 20:59:21+00:00] +2023-09-18 20:59:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238565941996826663015814026 amount: 80. +2023-09-18 20:59:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 20:59:21,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070761.0, "order_id": "x-XEKWYICXBSIUT605a8674d3a610582", "exchange_order_id": "35440838", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:59:21,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8674d3a610582. +2023-09-18 20:59:21,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070761.0, "order_id": "x-XEKWYICXSSIUT605a8674d3da70582", "exchange_order_id": "35440837", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 20:59:21,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8674d3da70582. +2023-09-18 20:59:21,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a86a94751c0582 for 80.00000000 SEI-USDT. +2023-09-18 20:59:21,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a86a94751c0582", "creation_timestamp": 1695070761.0, "exchange_order_id": "35441196", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:00:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a86a94751c0582. [clock=2023-09-18 21:00:16+00:00] +2023-09-18 21:00:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238662565416610355024207136 amount: 80. +2023-09-18 21:00:16,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12400000 amount: 80. +2023-09-18 21:00:16,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070816.0, "order_id": "x-XEKWYICXBSIUT605a86a94751c0582", "exchange_order_id": "35441196", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:00:16,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a86a94751c0582. +2023-09-18 21:00:16,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a86ddbc2670582 for 80.00000000 SEI-USDT. +2023-09-18 21:00:16,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a86ddbc2670582", "creation_timestamp": 1695070816.0, "exchange_order_id": "35441285", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:00:16,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a86ddbbe990582 for 80.00000000 SEI-USDT. +2023-09-18 21:00:16,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a86ddbbe990582", "creation_timestamp": 1695070816.0, "exchange_order_id": "35441286", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:01:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a86ddbbe990582. [clock=2023-09-18 21:01:11+00:00] +2023-09-18 21:01:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a86ddbc2670582. [clock=2023-09-18 21:01:11+00:00] +2023-09-18 21:01:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238080407681242679664995102 amount: 80. +2023-09-18 21:01:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:01:11,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070871.0, "order_id": "x-XEKWYICXBSIUT605a86ddbbe990582", "exchange_order_id": "35441286", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:01:11,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a86ddbbe990582. +2023-09-18 21:01:11,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070871.0, "order_id": "x-XEKWYICXSSIUT605a86ddbc2670582", "exchange_order_id": "35441285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:01:11,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a86ddbc2670582. +2023-09-18 21:01:11,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a87122e7a80582 for 80.00000000 SEI-USDT. +2023-09-18 21:01:11,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a87122e7a80582", "creation_timestamp": 1695070871.0, "exchange_order_id": "35441364", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:02:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a87122e7a80582. [clock=2023-09-18 21:02:06+00:00] +2023-09-18 21:02:06,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238671050630969099768241177 amount: 80. +2023-09-18 21:02:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240329420224977882990082369 amount: 80. +2023-09-18 21:02:06,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070926.0, "order_id": "x-XEKWYICXBSIUT605a87122e7a80582", "exchange_order_id": "35441364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:02:06,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a87122e7a80582. +2023-09-18 21:02:06,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8746a20230582 for 80.00000000 SEI-USDT. +2023-09-18 21:02:06,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a8746a20230582", "creation_timestamp": 1695070926.0, "exchange_order_id": "35441444", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:02:06,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8746a2b560582 for 80.00000000 SEI-USDT. +2023-09-18 21:02:06,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605a8746a2b560582", "creation_timestamp": 1695070926.0, "exchange_order_id": "35441446", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:02:20,138 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a8746a2b560582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:02:20,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:02:20+00:00] +2023-09-18 21:02:20,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070940.0, "order_id": "x-XEKWYICXSSIUT605a8746a2b560582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992000"}]}, "exchange_trade_id": "4980971", "exchange_order_id": "35441446", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:02:20,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070940.0, "order_id": "x-XEKWYICXSSIUT605a8746a2b560582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35441446", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:02:20,185 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a8746a2b560582 completely filled. +2023-09-18 21:03:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8746a20230582. [clock=2023-09-18 21:03:01+00:00] +2023-09-18 21:03:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238774582260233147087379522 amount: 80. +2023-09-18 21:03:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:03:01,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070981.0, "order_id": "x-XEKWYICXBSIUT605a8746a20230582", "exchange_order_id": "35441444", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:03:01,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8746a20230582. +2023-09-18 21:03:01,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a877b1627b0582 for 80.00000000 SEI-USDT. +2023-09-18 21:03:01,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695070981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a877b1627b0582", "creation_timestamp": 1695070981.0, "exchange_order_id": "35441757", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:03:20,549 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a877b1627b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:03:20,550 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:03:20+00:00] +2023-09-18 21:03:20,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071000.0, "order_id": "x-XEKWYICXBSIUT605a877b1627b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4980998", "exchange_order_id": "35441757", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:03:20,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071000.0, "order_id": "x-XEKWYICXBSIUT605a877b1627b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35441757", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:03:20,585 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a877b1627b0582 completely filled. +2023-09-18 21:03:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234610869995112975124758142 amount: 80. +2023-09-18 21:03:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238418256872470977471709423 amount: 80. +2023-09-18 21:03:56,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a87af896890582 for 80.00000000 SEI-USDT. +2023-09-18 21:03:56,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a87af896890582", "creation_timestamp": 1695071036.0, "exchange_order_id": "35442082", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:03:56,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a87af89bb30582 for 80.00000000 SEI-USDT. +2023-09-18 21:03:56,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a87af89bb30582", "creation_timestamp": 1695071036.0, "exchange_order_id": "35442083", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:04:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a87af896890582. [clock=2023-09-18 21:04:51+00:00] +2023-09-18 21:04:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a87af89bb30582. [clock=2023-09-18 21:04:51+00:00] +2023-09-18 21:04:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233306008290030404546262083 amount: 80. +2023-09-18 21:04:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:04:51,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071091.0, "order_id": "x-XEKWYICXBSIUT605a87af896890582", "exchange_order_id": "35442082", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:04:51,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a87af896890582. +2023-09-18 21:04:51,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071091.0, "order_id": "x-XEKWYICXSSIUT605a87af89bb30582", "exchange_order_id": "35442083", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:04:51,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a87af89bb30582. +2023-09-18 21:04:51,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a87e3fd63c0582 for 80.00000000 SEI-USDT. +2023-09-18 21:04:51,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605a87e3fd63c0582", "creation_timestamp": 1695071091.0, "exchange_order_id": "35442742", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:05:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a87e3fd63c0582. [clock=2023-09-18 21:05:46+00:00] +2023-09-18 21:05:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234736315421605435615169282 amount: 80. +2023-09-18 21:05:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238743314384715263817884176 amount: 80. +2023-09-18 21:05:46,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071146.0, "order_id": "x-XEKWYICXBSIUT605a87e3fd63c0582", "exchange_order_id": "35442742", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:05:46,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a87e3fd63c0582. +2023-09-18 21:05:46,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a88187107f0582 for 80.00000000 SEI-USDT. +2023-09-18 21:05:46,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605a88187107f0582", "creation_timestamp": 1695071146.0, "exchange_order_id": "35442854", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:05:46,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8818713120582 for 80.00000000 SEI-USDT. +2023-09-18 21:05:46,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a8818713120582", "creation_timestamp": 1695071146.0, "exchange_order_id": "35442855", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:06:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a88187107f0582. [clock=2023-09-18 21:06:41+00:00] +2023-09-18 21:06:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8818713120582. [clock=2023-09-18 21:06:41+00:00] +2023-09-18 21:06:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236016704039521758259533541 amount: 80. +2023-09-18 21:06:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:06:41,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071201.0, "order_id": "x-XEKWYICXBSIUT605a88187107f0582", "exchange_order_id": "35442854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:06:41,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a88187107f0582. +2023-09-18 21:06:41,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071201.0, "order_id": "x-XEKWYICXSSIUT605a8818713120582", "exchange_order_id": "35442855", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:06:41,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8818713120582. +2023-09-18 21:06:41,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a884ce5d9b0582 for 80.00000000 SEI-USDT. +2023-09-18 21:06:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a884ce5d9b0582", "creation_timestamp": 1695071201.0, "exchange_order_id": "35442978", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:07:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a884ce5d9b0582. [clock=2023-09-18 21:07:36+00:00] +2023-09-18 21:07:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236235218540591902416688276 amount: 80. +2023-09-18 21:07:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238660593412991021916301188 amount: 80. +2023-09-18 21:07:36,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071256.0, "order_id": "x-XEKWYICXBSIUT605a884ce5d9b0582", "exchange_order_id": "35442978", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:07:36,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a884ce5d9b0582. +2023-09-18 21:07:36,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a888158e2a0582 for 80.00000000 SEI-USDT. +2023-09-18 21:07:36,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a888158e2a0582", "creation_timestamp": 1695071256.0, "exchange_order_id": "35443073", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:07:36,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a888158ae00582 for 80.00000000 SEI-USDT. +2023-09-18 21:07:36,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a888158ae00582", "creation_timestamp": 1695071256.0, "exchange_order_id": "35443074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:08:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a888158ae00582. [clock=2023-09-18 21:08:31+00:00] +2023-09-18 21:08:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a888158e2a0582. [clock=2023-09-18 21:08:31+00:00] +2023-09-18 21:08:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236510942656715595192473398 amount: 80. +2023-09-18 21:08:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:08:31,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071311.0, "order_id": "x-XEKWYICXBSIUT605a888158ae00582", "exchange_order_id": "35443074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:08:31,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a888158ae00582. +2023-09-18 21:08:31,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071311.0, "order_id": "x-XEKWYICXSSIUT605a888158e2a0582", "exchange_order_id": "35443073", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:08:31,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a888158e2a0582. +2023-09-18 21:08:31,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a88b5cd3250582 for 80.00000000 SEI-USDT. +2023-09-18 21:08:31,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a88b5cd3250582", "creation_timestamp": 1695071311.0, "exchange_order_id": "35443153", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:09:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a88b5cd3250582. [clock=2023-09-18 21:09:26+00:00] +2023-09-18 21:09:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236619764715236862238370803 amount: 80. +2023-09-18 21:09:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238489054860809070737909179 amount: 80. +2023-09-18 21:09:26,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071366.0, "order_id": "x-XEKWYICXBSIUT605a88b5cd3250582", "exchange_order_id": "35443153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:09:26,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a88b5cd3250582. +2023-09-18 21:09:26,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a88ea4013d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:09:26,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a88ea4013d0582", "creation_timestamp": 1695071366.0, "exchange_order_id": "35443360", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:09:26,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a88ea404d20582 for 80.00000000 SEI-USDT. +2023-09-18 21:09:26,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a88ea404d20582", "creation_timestamp": 1695071366.0, "exchange_order_id": "35443361", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:10:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a88ea4013d0582. [clock=2023-09-18 21:10:21+00:00] +2023-09-18 21:10:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a88ea404d20582. [clock=2023-09-18 21:10:21+00:00] +2023-09-18 21:10:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236704347744311423280147557 amount: 80. +2023-09-18 21:10:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:10:21,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071421.0, "order_id": "x-XEKWYICXBSIUT605a88ea4013d0582", "exchange_order_id": "35443360", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:10:21,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a88ea4013d0582. +2023-09-18 21:10:21,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071421.0, "order_id": "x-XEKWYICXSSIUT605a88ea404d20582", "exchange_order_id": "35443361", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:10:21,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a88ea404d20582. +2023-09-18 21:10:21,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a891eb3ef70582 for 80.00000000 SEI-USDT. +2023-09-18 21:10:21,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a891eb3ef70582", "creation_timestamp": 1695071421.0, "exchange_order_id": "35443428", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:11:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a891eb3ef70582. [clock=2023-09-18 21:11:16+00:00] +2023-09-18 21:11:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237615491950024863297007052 amount: 80. +2023-09-18 21:11:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239933338432445308071919160 amount: 80. +2023-09-18 21:11:16,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071476.0, "order_id": "x-XEKWYICXBSIUT605a891eb3ef70582", "exchange_order_id": "35443428", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:11:16,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a891eb3ef70582. +2023-09-18 21:11:16,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a89532786c0582 for 80.00000000 SEI-USDT. +2023-09-18 21:11:16,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a89532786c0582", "creation_timestamp": 1695071476.0, "exchange_order_id": "35443605", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:11:16,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a895327b9c0582 for 80.00000000 SEI-USDT. +2023-09-18 21:11:16,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a895327b9c0582", "creation_timestamp": 1695071476.0, "exchange_order_id": "35443606", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:12:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a89532786c0582. [clock=2023-09-18 21:12:11+00:00] +2023-09-18 21:12:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a895327b9c0582. [clock=2023-09-18 21:12:11+00:00] +2023-09-18 21:12:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237700913286574799746966778 amount: 80. +2023-09-18 21:12:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:12:11,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071531.0, "order_id": "x-XEKWYICXBSIUT605a89532786c0582", "exchange_order_id": "35443605", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:12:11,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a89532786c0582. +2023-09-18 21:12:11,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071531.0, "order_id": "x-XEKWYICXSSIUT605a895327b9c0582", "exchange_order_id": "35443606", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:12:11,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a895327b9c0582. +2023-09-18 21:12:11,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a89879bcce0582 for 80.00000000 SEI-USDT. +2023-09-18 21:12:11,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a89879bcce0582", "creation_timestamp": 1695071531.0, "exchange_order_id": "35443819", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:13:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a89879bcce0582. [clock=2023-09-18 21:13:06+00:00] +2023-09-18 21:13:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237681265598252087442704239 amount: 80. +2023-09-18 21:13:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239602056649487828072161840 amount: 80. +2023-09-18 21:13:06,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071586.0, "order_id": "x-XEKWYICXBSIUT605a89879bcce0582", "exchange_order_id": "35443819", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:13:06,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a89879bcce0582. +2023-09-18 21:13:06,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a89bc0f4500582 for 80.00000000 SEI-USDT. +2023-09-18 21:13:06,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605a89bc0f4500582", "creation_timestamp": 1695071586.0, "exchange_order_id": "35443903", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:13:06,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a89bc0f10d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:13:06,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a89bc0f10d0582", "creation_timestamp": 1695071586.0, "exchange_order_id": "35443904", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:13:23,131 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a89bc0f4500582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:13:23,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:13:23+00:00] +2023-09-18 21:13:23,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071603.0, "order_id": "x-XEKWYICXSSIUT605a89bc0f4500582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4981204", "exchange_order_id": "35443903", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:13:23,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071603.0, "order_id": "x-XEKWYICXSSIUT605a89bc0f4500582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35443903", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:13:23,164 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a89bc0f4500582 completely filled. +2023-09-18 21:14:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a89bc0f10d0582. [clock=2023-09-18 21:14:01+00:00] +2023-09-18 21:14:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237832743254183342850631297 amount: 80. +2023-09-18 21:14:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:14:01,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071641.0, "order_id": "x-XEKWYICXBSIUT605a89bc0f10d0582", "exchange_order_id": "35443904", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:14:01,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a89bc0f10d0582. +2023-09-18 21:14:01,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a89f082a580582 for 80.00000000 SEI-USDT. +2023-09-18 21:14:01,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071641.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605a89f082a580582", "creation_timestamp": 1695071641.0, "exchange_order_id": "35444090", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:14:29,441 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a89f082a580582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:14:29,442 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:14:29+00:00] +2023-09-18 21:14:29,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071669.0, "order_id": "x-XEKWYICXBSIUT605a89f082a580582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981255", "exchange_order_id": "35444090", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:14:29,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071669.0, "order_id": "x-XEKWYICXBSIUT605a89f082a580582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35444090", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:14:29,472 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a89f082a580582 completely filled. +2023-09-18 21:14:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236291071860492772557332115 amount: 80. +2023-09-18 21:14:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238970797825674619202983491 amount: 80. +2023-09-18 21:14:56,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8a24f64790582 for 80.00000000 SEI-USDT. +2023-09-18 21:14:56,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605a8a24f64790582", "creation_timestamp": 1695071696.0, "exchange_order_id": "35444365", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:14:56,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8a24f67ee0582 for 80.00000000 SEI-USDT. +2023-09-18 21:14:56,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605a8a24f67ee0582", "creation_timestamp": 1695071696.0, "exchange_order_id": "35444366", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:15:43,427 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a8a24f67ee0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:15:43,428 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:15:43+00:00] +2023-09-18 21:15:43,449 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071743.0, "order_id": "x-XEKWYICXSSIUT605a8a24f67ee0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4981265", "exchange_order_id": "35444366", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:15:43,461 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071743.0, "order_id": "x-XEKWYICXSSIUT605a8a24f67ee0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35444366", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:15:43,461 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a8a24f67ee0582 completely filled. +2023-09-18 21:15:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8a24f64790582. [clock=2023-09-18 21:15:51+00:00] +2023-09-18 21:15:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 21:15:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:15:51,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071751.0, "order_id": "x-XEKWYICXBSIUT605a8a24f64790582", "exchange_order_id": "35444365", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:15:51,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8a24f64790582. +2023-09-18 21:15:51,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8a596a50b0582 for 80.00000000 SEI-USDT. +2023-09-18 21:15:51,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a8a596a50b0582", "creation_timestamp": 1695071751.0, "exchange_order_id": "35444602", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:16:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8a596a50b0582. [clock=2023-09-18 21:16:46+00:00] +2023-09-18 21:16:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-18 21:16:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:16:46,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071806.0, "order_id": "x-XEKWYICXBSIUT605a8a596a50b0582", "exchange_order_id": "35444602", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:16:46,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8a596a50b0582. +2023-09-18 21:16:46,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8a8dddf6e0582 for 80.00000000 SEI-USDT. +2023-09-18 21:16:46,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605a8a8dddf6e0582", "creation_timestamp": 1695071806.0, "exchange_order_id": "35444717", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:17:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8a8dddf6e0582. [clock=2023-09-18 21:17:41+00:00] +2023-09-18 21:17:41,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-18 21:17:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:17:41,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071861.0, "order_id": "x-XEKWYICXBSIUT605a8a8dddf6e0582", "exchange_order_id": "35444717", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:17:41,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8a8dddf6e0582. +2023-09-18 21:17:41,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8ac25161b0582 for 80.00000000 SEI-USDT. +2023-09-18 21:17:41,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a8ac25161b0582", "creation_timestamp": 1695071861.0, "exchange_order_id": "35445069", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:18:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8ac25161b0582. [clock=2023-09-18 21:18:36+00:00] +2023-09-18 21:18:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-18 21:18:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:18:36,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071916.0, "order_id": "x-XEKWYICXBSIUT605a8ac25161b0582", "exchange_order_id": "35445069", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:18:36,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8ac25161b0582. +2023-09-18 21:18:36,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8af6c5a6d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:18:36,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a8af6c5a6d0582", "creation_timestamp": 1695071916.0, "exchange_order_id": "35445304", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:19:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8af6c5a6d0582. [clock=2023-09-18 21:19:31+00:00] +2023-09-18 21:19:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:19:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:19:31,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071971.0, "order_id": "x-XEKWYICXBSIUT605a8af6c5a6d0582", "exchange_order_id": "35445304", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:19:31,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8af6c5a6d0582. +2023-09-18 21:19:31,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8b2b396840582 for 80.00000000 SEI-USDT. +2023-09-18 21:19:31,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695071971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8b2b396840582", "creation_timestamp": 1695071971.0, "exchange_order_id": "35445611", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:20:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8b2b396840582. [clock=2023-09-18 21:20:26+00:00] +2023-09-18 21:20:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:20:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:20:26,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072026.0, "order_id": "x-XEKWYICXBSIUT605a8b2b396840582", "exchange_order_id": "35445611", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:20:26,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8b2b396840582. +2023-09-18 21:20:26,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8b5fad1400582 for 80.00000000 SEI-USDT. +2023-09-18 21:20:26,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8b5fad1400582", "creation_timestamp": 1695072026.0, "exchange_order_id": "35445734", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:21:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8b5fad1400582. [clock=2023-09-18 21:21:21+00:00] +2023-09-18 21:21:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:21:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:21:21,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072081.0, "order_id": "x-XEKWYICXBSIUT605a8b5fad1400582", "exchange_order_id": "35445734", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:21:21,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8b5fad1400582. +2023-09-18 21:21:21,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8b9420b1f0582 for 80.00000000 SEI-USDT. +2023-09-18 21:21:21,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8b9420b1f0582", "creation_timestamp": 1695072081.0, "exchange_order_id": "35445860", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:21:38,132 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a8b9420b1f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:21:38,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:21:38+00:00] +2023-09-18 21:21:38,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072098.0, "order_id": "x-XEKWYICXBSIUT605a8b9420b1f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981353", "exchange_order_id": "35445860", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:21:38,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072098.0, "order_id": "x-XEKWYICXBSIUT605a8b9420b1f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35445860", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:21:38,180 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a8b9420b1f0582 completely filled. +2023-09-18 21:22:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239989588789628712935262321 amount: 80. +2023-09-18 21:22:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242018027646907565700376045 amount: 80. +2023-09-18 21:22:16,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8bc89416e0582 for 80.00000000 SEI-USDT. +2023-09-18 21:22:16,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a8bc89416e0582", "creation_timestamp": 1695072136.0, "exchange_order_id": "35446113", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:22:16,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8bc8943ec0582 for 80.00000000 SEI-USDT. +2023-09-18 21:22:16,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605a8bc8943ec0582", "creation_timestamp": 1695072136.0, "exchange_order_id": "35446114", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:23:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8bc89416e0582. [clock=2023-09-18 21:23:11+00:00] +2023-09-18 21:23:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8bc8943ec0582. [clock=2023-09-18 21:23:11+00:00] +2023-09-18 21:23:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239772980962355821224889535 amount: 80. +2023-09-18 21:23:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:23:11,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072191.0, "order_id": "x-XEKWYICXBSIUT605a8bc89416e0582", "exchange_order_id": "35446113", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:23:11,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8bc89416e0582. +2023-09-18 21:23:11,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072191.0, "order_id": "x-XEKWYICXSSIUT605a8bc8943ec0582", "exchange_order_id": "35446114", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:23:11,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8bc8943ec0582. +2023-09-18 21:23:11,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8bfd085810582 for 80.00000000 SEI-USDT. +2023-09-18 21:23:11,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a8bfd085810582", "creation_timestamp": 1695072191.0, "exchange_order_id": "35446191", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:24:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8bfd085810582. [clock=2023-09-18 21:24:06+00:00] +2023-09-18 21:24:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239823545386675215066170130 amount: 80. +2023-09-18 21:24:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241454754461567941258214800 amount: 80. +2023-09-18 21:24:06,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072246.0, "order_id": "x-XEKWYICXBSIUT605a8bfd085810582", "exchange_order_id": "35446191", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:24:06,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8bfd085810582. +2023-09-18 21:24:06,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8c317bbbb0582 for 80.00000000 SEI-USDT. +2023-09-18 21:24:06,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605a8c317bbbb0582", "creation_timestamp": 1695072246.0, "exchange_order_id": "35446274", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:24:06,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8c317bef20582 for 80.00000000 SEI-USDT. +2023-09-18 21:24:06,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605a8c317bef20582", "creation_timestamp": 1695072246.0, "exchange_order_id": "35446276", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:25:00,528 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a8c317bef20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:25:00,529 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:25:00+00:00] +2023-09-18 21:25:00,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072300.0, "order_id": "x-XEKWYICXSSIUT605a8c317bef20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "4981373", "exchange_order_id": "35446276", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:25:00,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072300.0, "order_id": "x-XEKWYICXSSIUT605a8c317bef20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35446276", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:25:00,560 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a8c317bef20582 completely filled. +2023-09-18 21:25:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8c317bbbb0582. [clock=2023-09-18 21:25:01+00:00] +2023-09-18 21:25:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240989344815336774112071050 amount: 80. +2023-09-18 21:25:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:25:01,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072301.0, "order_id": "x-XEKWYICXBSIUT605a8c317bbbb0582", "exchange_order_id": "35446274", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:25:01,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8c317bbbb0582. +2023-09-18 21:25:01,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8c65ef9f10582 for 80.00000000 SEI-USDT. +2023-09-18 21:25:01,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072301.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a8c65ef9f10582", "creation_timestamp": 1695072301.0, "exchange_order_id": "35446442", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:25:15,163 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 21:25:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8c65ef9f10582. [clock=2023-09-18 21:25:56+00:00] +2023-09-18 21:25:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:25:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:25:56,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072356.0, "order_id": "x-XEKWYICXBSIUT605a8c65ef9f10582", "exchange_order_id": "35446442", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:25:56,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8c65ef9f10582. +2023-09-18 21:25:56,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8c9a6377e0582 for 80.00000000 SEI-USDT. +2023-09-18 21:25:56,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8c9a6377e0582", "creation_timestamp": 1695072356.0, "exchange_order_id": "35446569", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:26:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8c9a6377e0582. [clock=2023-09-18 21:26:51+00:00] +2023-09-18 21:26:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:26:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:26:51,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072411.0, "order_id": "x-XEKWYICXBSIUT605a8c9a6377e0582", "exchange_order_id": "35446569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:26:51,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8c9a6377e0582. +2023-09-18 21:26:51,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8cced73e20582 for 80.00000000 SEI-USDT. +2023-09-18 21:26:51,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8cced73e20582", "creation_timestamp": 1695072411.0, "exchange_order_id": "35446665", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:27:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8cced73e20582. [clock=2023-09-18 21:27:46+00:00] +2023-09-18 21:27:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12410000 amount: 80. +2023-09-18 21:27:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:27:46,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072466.0, "order_id": "x-XEKWYICXBSIUT605a8cced73e20582", "exchange_order_id": "35446665", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:27:46,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8cced73e20582. +2023-09-18 21:27:46,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8d034af5a0582 for 80.00000000 SEI-USDT. +2023-09-18 21:27:46,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605a8d034af5a0582", "creation_timestamp": 1695072466.0, "exchange_order_id": "35446872", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:28:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8d034af5a0582. [clock=2023-09-18 21:28:41+00:00] +2023-09-18 21:28:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-18 21:28:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:28:41,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072521.0, "order_id": "x-XEKWYICXBSIUT605a8d034af5a0582", "exchange_order_id": "35446872", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:28:41,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8d034af5a0582. +2023-09-18 21:28:41,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8d37bf0120582 for 80.00000000 SEI-USDT. +2023-09-18 21:28:41,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a8d37bf0120582", "creation_timestamp": 1695072521.0, "exchange_order_id": "35447132", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:29:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8d37bf0120582. [clock=2023-09-18 21:29:36+00:00] +2023-09-18 21:29:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-18 21:29:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:29:36,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072576.0, "order_id": "x-XEKWYICXBSIUT605a8d37bf0120582", "exchange_order_id": "35447132", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:29:36,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8d37bf0120582. +2023-09-18 21:29:36,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8d6c324ce0582 for 80.00000000 SEI-USDT. +2023-09-18 21:29:36,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072576.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a8d6c324ce0582", "creation_timestamp": 1695072576.0, "exchange_order_id": "35447474", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:30:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8d6c324ce0582. [clock=2023-09-18 21:30:31+00:00] +2023-09-18 21:30:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-18 21:30:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:30:31,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072631.0, "order_id": "x-XEKWYICXBSIUT605a8d6c324ce0582", "exchange_order_id": "35447474", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:30:31,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8d6c324ce0582. +2023-09-18 21:30:31,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8da0a65bf0582 for 80.00000000 SEI-USDT. +2023-09-18 21:30:31,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a8da0a65bf0582", "creation_timestamp": 1695072631.0, "exchange_order_id": "35447641", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:31:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8da0a65bf0582. [clock=2023-09-18 21:31:26+00:00] +2023-09-18 21:31:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-18 21:31:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:31:26,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072686.0, "order_id": "x-XEKWYICXBSIUT605a8da0a65bf0582", "exchange_order_id": "35447641", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:31:26,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8da0a65bf0582. +2023-09-18 21:31:26,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8dd519e180582 for 80.00000000 SEI-USDT. +2023-09-18 21:31:26,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a8dd519e180582", "creation_timestamp": 1695072686.0, "exchange_order_id": "35448295", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:32:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8dd519e180582. [clock=2023-09-18 21:32:21+00:00] +2023-09-18 21:32:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-18 21:32:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:32:21,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072741.0, "order_id": "x-XEKWYICXBSIUT605a8dd519e180582", "exchange_order_id": "35448295", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:32:21,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8dd519e180582. +2023-09-18 21:32:21,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8e098d8a90582 for 80.00000000 SEI-USDT. +2023-09-18 21:32:21,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a8e098d8a90582", "creation_timestamp": 1695072741.0, "exchange_order_id": "35448773", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:33:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8e098d8a90582. [clock=2023-09-18 21:33:16+00:00] +2023-09-18 21:33:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-18 21:33:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:33:16,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072796.0, "order_id": "x-XEKWYICXBSIUT605a8e098d8a90582", "exchange_order_id": "35448773", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:33:16,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8e098d8a90582. +2023-09-18 21:33:16,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8e3e017a70582 for 80.00000000 SEI-USDT. +2023-09-18 21:33:16,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a8e3e017a70582", "creation_timestamp": 1695072796.0, "exchange_order_id": "35448881", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:34:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8e3e017a70582. [clock=2023-09-18 21:34:11+00:00] +2023-09-18 21:34:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-18 21:34:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:34:11,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072851.0, "order_id": "x-XEKWYICXBSIUT605a8e3e017a70582", "exchange_order_id": "35448881", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:34:11,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8e3e017a70582. +2023-09-18 21:34:11,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8e7274c7d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:34:11,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a8e7274c7d0582", "creation_timestamp": 1695072851.0, "exchange_order_id": "35448982", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:35:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8e7274c7d0582. [clock=2023-09-18 21:35:06+00:00] +2023-09-18 21:35:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 21:35:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:35:06,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072906.0, "order_id": "x-XEKWYICXBSIUT605a8e7274c7d0582", "exchange_order_id": "35448982", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:35:06,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8e7274c7d0582. +2023-09-18 21:35:06,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8ea6e99400582 for 80.00000000 SEI-USDT. +2023-09-18 21:35:06,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072906.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a8ea6e99400582", "creation_timestamp": 1695072906.0, "exchange_order_id": "35449232", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:36:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8ea6e99400582. [clock=2023-09-18 21:36:01+00:00] +2023-09-18 21:36:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 21:36:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:36:01,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072961.0, "order_id": "x-XEKWYICXBSIUT605a8ea6e99400582", "exchange_order_id": "35449232", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:36:01,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8ea6e99400582. +2023-09-18 21:36:01,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8edb5ca080582 for 80.00000000 SEI-USDT. +2023-09-18 21:36:01,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695072961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a8edb5ca080582", "creation_timestamp": 1695072961.0, "exchange_order_id": "35449363", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:36:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8edb5ca080582. [clock=2023-09-18 21:36:56+00:00] +2023-09-18 21:36:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 21:36:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:36:56,109 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073016.0, "order_id": "x-XEKWYICXBSIUT605a8edb5ca080582", "exchange_order_id": "35449363", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:36:56,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8edb5ca080582. +2023-09-18 21:36:56,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8f0fd05820582 for 80.00000000 SEI-USDT. +2023-09-18 21:36:56,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a8f0fd05820582", "creation_timestamp": 1695073016.0, "exchange_order_id": "35449528", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:37:44,334 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a8f0fd05820582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:37:44,335 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:37:44+00:00] +2023-09-18 21:37:44,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073064.0, "order_id": "x-XEKWYICXBSIUT605a8f0fd05820582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981644", "exchange_order_id": "35449528", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:37:44,369 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073064.0, "order_id": "x-XEKWYICXBSIUT605a8f0fd05820582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35449528", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:37:44,369 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a8f0fd05820582 completely filled. +2023-09-18 21:37:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 21:37:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252344349740440433793437553 amount: 80. +2023-09-18 21:37:51,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8f44439df0582 for 80.00000000 SEI-USDT. +2023-09-18 21:37:51,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a8f44439df0582", "creation_timestamp": 1695073071.0, "exchange_order_id": "35449705", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:37:51,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8f4443deb0582 for 80.00000000 SEI-USDT. +2023-09-18 21:37:51,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a8f4443deb0582", "creation_timestamp": 1695073071.0, "exchange_order_id": "35449706", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:38:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8f44439df0582. [clock=2023-09-18 21:38:46+00:00] +2023-09-18 21:38:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8f4443deb0582. [clock=2023-09-18 21:38:46+00:00] +2023-09-18 21:38:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-18 21:38:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:38:46,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073126.0, "order_id": "x-XEKWYICXBSIUT605a8f44439df0582", "exchange_order_id": "35449705", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:38:46,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8f44439df0582. +2023-09-18 21:38:46,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8f78b7c1e0582 for 80.00000000 SEI-USDT. +2023-09-18 21:38:46,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a8f78b7c1e0582", "creation_timestamp": 1695073126.0, "exchange_order_id": "35449847", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:38:46,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073126.0, "order_id": "x-XEKWYICXSSIUT605a8f4443deb0582", "exchange_order_id": "35449706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:38:46,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8f4443deb0582. +2023-09-18 21:39:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8f78b7c1e0582. [clock=2023-09-18 21:39:41+00:00] +2023-09-18 21:39:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-18 21:39:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252338856485539670007587970 amount: 80. +2023-09-18 21:39:41,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073181.0, "order_id": "x-XEKWYICXBSIUT605a8f78b7c1e0582", "exchange_order_id": "35449847", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:39:41,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8f78b7c1e0582. +2023-09-18 21:39:41,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8fad2ba050582 for 80.00000000 SEI-USDT. +2023-09-18 21:39:41,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a8fad2ba050582", "creation_timestamp": 1695073181.0, "exchange_order_id": "35449926", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:39:41,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a8fad2bd400582 for 80.00000000 SEI-USDT. +2023-09-18 21:39:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a8fad2bd400582", "creation_timestamp": 1695073181.0, "exchange_order_id": "35449927", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:40:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8fad2ba050582. [clock=2023-09-18 21:40:36+00:00] +2023-09-18 21:40:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a8fad2bd400582. [clock=2023-09-18 21:40:36+00:00] +2023-09-18 21:40:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-18 21:40:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 21:40:36,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073236.0, "order_id": "x-XEKWYICXBSIUT605a8fad2ba050582", "exchange_order_id": "35449926", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:40:36,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8fad2ba050582. +2023-09-18 21:40:36,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a8fe19f16b0582 for 80.00000000 SEI-USDT. +2023-09-18 21:40:36,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a8fe19f16b0582", "creation_timestamp": 1695073236.0, "exchange_order_id": "35450235", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:40:36,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073236.0, "order_id": "x-XEKWYICXSSIUT605a8fad2bd400582", "exchange_order_id": "35449927", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:40:36,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a8fad2bd400582. +2023-09-18 21:41:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a8fe19f16b0582. [clock=2023-09-18 21:41:31+00:00] +2023-09-18 21:41:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-18 21:41:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254997073793558964141832183 amount: 80. +2023-09-18 21:41:31,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073291.0, "order_id": "x-XEKWYICXBSIUT605a8fe19f16b0582", "exchange_order_id": "35450235", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:41:31,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a8fe19f16b0582. +2023-09-18 21:41:31,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a901612d000582 for 80.00000000 SEI-USDT. +2023-09-18 21:41:31,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a901612d000582", "creation_timestamp": 1695073291.0, "exchange_order_id": "35450484", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:41:31,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9016130710582 for 80.00000000 SEI-USDT. +2023-09-18 21:41:31,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a9016130710582", "creation_timestamp": 1695073291.0, "exchange_order_id": "35450483", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:42:15,949 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a901612d000582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:42:15,950 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 21:42:15+00:00] +2023-09-18 21:42:15,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073335.0, "order_id": "x-XEKWYICXBSIUT605a901612d000582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981781", "exchange_order_id": "35450484", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:42:15,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073335.0, "order_id": "x-XEKWYICXBSIUT605a901612d000582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35450484", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:42:15,981 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a901612d000582 completely filled. +2023-09-18 21:42:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9016130710582. [clock=2023-09-18 21:42:26+00:00] +2023-09-18 21:42:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-18 21:42:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257573805555815649320238489 amount: 80. +2023-09-18 21:42:26,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073346.0, "order_id": "x-XEKWYICXSSIUT605a9016130710582", "exchange_order_id": "35450483", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:42:26,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9016130710582. +2023-09-18 21:42:26,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a904a868970582 for 80.00000000 SEI-USDT. +2023-09-18 21:42:26,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605a904a868970582", "creation_timestamp": 1695073346.0, "exchange_order_id": "35450900", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:42:26,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a904a86b730582 for 80.00000000 SEI-USDT. +2023-09-18 21:42:26,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605a904a86b730582", "creation_timestamp": 1695073346.0, "exchange_order_id": "35450901", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:43:15,689 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a904a868970582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:43:15,690 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 21:43:15+00:00] +2023-09-18 21:43:15,711 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073395.0, "order_id": "x-XEKWYICXBSIUT605a904a868970582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981881", "exchange_order_id": "35450900", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:43:15,721 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073395.0, "order_id": "x-XEKWYICXBSIUT605a904a868970582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35450900", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:43:15,722 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a904a868970582 completely filled. +2023-09-18 21:43:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a904a86b730582. [clock=2023-09-18 21:43:21+00:00] +2023-09-18 21:43:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 21:43:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258039337884129642736528611 amount: 80. +2023-09-18 21:43:21,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073401.0, "order_id": "x-XEKWYICXSSIUT605a904a86b730582", "exchange_order_id": "35450901", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:43:21,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a904a86b730582. +2023-09-18 21:43:21,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a907efa7310582 for 80.00000000 SEI-USDT. +2023-09-18 21:43:21,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a907efa7310582", "creation_timestamp": 1695073401.0, "exchange_order_id": "35451458", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:43:21,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a907efaa170582 for 80.00000000 SEI-USDT. +2023-09-18 21:43:21,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605a907efaa170582", "creation_timestamp": 1695073401.0, "exchange_order_id": "35451459", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:43:57,006 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a907efa7310582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:43:57,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 21:43:57+00:00] +2023-09-18 21:43:57,038 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073437.0, "order_id": "x-XEKWYICXBSIUT605a907efa7310582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981918", "exchange_order_id": "35451458", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:43:57,054 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073437.0, "order_id": "x-XEKWYICXBSIUT605a907efa7310582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35451458", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:43:57,054 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a907efa7310582 completely filled. +2023-09-18 21:44:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a907efaa170582. [clock=2023-09-18 21:44:16+00:00] +2023-09-18 21:44:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-18 21:44:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255989281540520275546007610 amount: 80. +2023-09-18 21:44:16,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073456.0, "order_id": "x-XEKWYICXSSIUT605a907efaa170582", "exchange_order_id": "35451459", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:44:16,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a907efaa170582. +2023-09-18 21:44:16,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a90b36ea750582 for 80.00000000 SEI-USDT. +2023-09-18 21:44:16,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a90b36ea750582", "creation_timestamp": 1695073456.0, "exchange_order_id": "35451687", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:44:16,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a90b36eedc0582 for 80.00000000 SEI-USDT. +2023-09-18 21:44:16,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a90b36eedc0582", "creation_timestamp": 1695073456.0, "exchange_order_id": "35451688", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:44:32,721 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a90b36ea750582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:44:32,722 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 21:44:32+00:00] +2023-09-18 21:44:32,745 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073472.0, "order_id": "x-XEKWYICXBSIUT605a90b36ea750582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4981950", "exchange_order_id": "35451687", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:44:32,759 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073472.0, "order_id": "x-XEKWYICXBSIUT605a90b36ea750582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35451687", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:44:32,759 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a90b36ea750582 completely filled. +2023-09-18 21:45:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a90b36eedc0582. [clock=2023-09-18 21:45:11+00:00] +2023-09-18 21:45:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248749926309150837626834593 amount: 80. +2023-09-18 21:45:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254100871955017512008555641 amount: 80. +2023-09-18 21:45:11,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073511.0, "order_id": "x-XEKWYICXSSIUT605a90b36eedc0582", "exchange_order_id": "35451688", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:45:11,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a90b36eedc0582. +2023-09-18 21:45:11,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a90e7e218d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:45:11,406 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a90e7e218d0582", "creation_timestamp": 1695073511.0, "exchange_order_id": "35452053", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:45:11,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a90e7e24ea0582 for 80.00000000 SEI-USDT. +2023-09-18 21:45:11,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a90e7e24ea0582", "creation_timestamp": 1695073511.0, "exchange_order_id": "35452054", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:46:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a90e7e218d0582. [clock=2023-09-18 21:46:06+00:00] +2023-09-18 21:46:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a90e7e24ea0582. [clock=2023-09-18 21:46:06+00:00] +2023-09-18 21:46:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248792115790409042103380285 amount: 80. +2023-09-18 21:46:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253620009438583829316031063 amount: 80. +2023-09-18 21:46:06,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073566.0, "order_id": "x-XEKWYICXBSIUT605a90e7e218d0582", "exchange_order_id": "35452053", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:46:06,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a90e7e218d0582. +2023-09-18 21:46:06,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073566.0, "order_id": "x-XEKWYICXSSIUT605a90e7e24ea0582", "exchange_order_id": "35452054", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:46:06,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a90e7e24ea0582. +2023-09-18 21:46:06,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a911c561ce0582 for 80.00000000 SEI-USDT. +2023-09-18 21:46:06,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a911c561ce0582", "creation_timestamp": 1695073566.0, "exchange_order_id": "35452202", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:46:06,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a911c55eb00582 for 80.00000000 SEI-USDT. +2023-09-18 21:46:06,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a911c55eb00582", "creation_timestamp": 1695073566.0, "exchange_order_id": "35452203", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:47:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a911c55eb00582. [clock=2023-09-18 21:47:01+00:00] +2023-09-18 21:47:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a911c561ce0582. [clock=2023-09-18 21:47:01+00:00] +2023-09-18 21:47:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248792115790409042103380285 amount: 80. +2023-09-18 21:47:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253620009438583829316031063 amount: 80. +2023-09-18 21:47:01,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073621.0, "order_id": "x-XEKWYICXBSIUT605a911c55eb00582", "exchange_order_id": "35452203", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:47:01,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a911c55eb00582. +2023-09-18 21:47:01,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073621.0, "order_id": "x-XEKWYICXSSIUT605a911c561ce0582", "exchange_order_id": "35452202", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:47:01,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a911c561ce0582. +2023-09-18 21:47:01,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9150c9e600582 for 80.00000000 SEI-USDT. +2023-09-18 21:47:01,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a9150c9e600582", "creation_timestamp": 1695073621.0, "exchange_order_id": "35452305", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:47:01,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9150c9c650582 for 80.00000000 SEI-USDT. +2023-09-18 21:47:01,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9150c9c650582", "creation_timestamp": 1695073621.0, "exchange_order_id": "35452306", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:47:56,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9150c9c650582. [clock=2023-09-18 21:47:56+00:00] +2023-09-18 21:47:56,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9150c9e600582. [clock=2023-09-18 21:47:56+00:00] +2023-09-18 21:47:56,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249282835400121417208074943 amount: 80. +2023-09-18 21:47:56,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253037726396399644165465317 amount: 80. +2023-09-18 21:47:56,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073676.0, "order_id": "x-XEKWYICXBSIUT605a9150c9c650582", "exchange_order_id": "35452306", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:47:56,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9150c9c650582. +2023-09-18 21:47:56,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073676.0, "order_id": "x-XEKWYICXSSIUT605a9150c9e600582", "exchange_order_id": "35452305", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:47:56,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9150c9e600582. +2023-09-18 21:47:56,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a91853f1f40582 for 80.00000000 SEI-USDT. +2023-09-18 21:47:56,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a91853f1f40582", "creation_timestamp": 1695073676.0, "exchange_order_id": "35452399", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:47:56,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a91853f0050582 for 80.00000000 SEI-USDT. +2023-09-18 21:47:56,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a91853f0050582", "creation_timestamp": 1695073676.0, "exchange_order_id": "35452398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:48:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a91853f0050582. [clock=2023-09-18 21:48:51+00:00] +2023-09-18 21:48:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a91853f1f40582. [clock=2023-09-18 21:48:51+00:00] +2023-09-18 21:48:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249664475131090885102005578 amount: 80. +2023-09-18 21:48:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252584862780224676049719829 amount: 80. +2023-09-18 21:48:51,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073731.0, "order_id": "x-XEKWYICXBSIUT605a91853f0050582", "exchange_order_id": "35452398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:48:51,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a91853f0050582. +2023-09-18 21:48:51,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073731.0, "order_id": "x-XEKWYICXSSIUT605a91853f1f40582", "exchange_order_id": "35452399", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:48:51,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a91853f1f40582. +2023-09-18 21:48:51,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a91b9b14230582 for 80.00000000 SEI-USDT. +2023-09-18 21:48:51,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a91b9b14230582", "creation_timestamp": 1695073731.0, "exchange_order_id": "35452486", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:48:51,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a91b9b11d90582 for 80.00000000 SEI-USDT. +2023-09-18 21:48:51,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a91b9b11d90582", "creation_timestamp": 1695073731.0, "exchange_order_id": "35452487", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:49:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a91b9b11d90582. [clock=2023-09-18 21:49:46+00:00] +2023-09-18 21:49:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a91b9b14230582. [clock=2023-09-18 21:49:46+00:00] +2023-09-18 21:49:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249961287231104740860993891 amount: 80. +2023-09-18 21:49:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252232649556822874886153925 amount: 80. +2023-09-18 21:49:46,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073786.0, "order_id": "x-XEKWYICXBSIUT605a91b9b11d90582", "exchange_order_id": "35452487", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:49:46,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a91b9b11d90582. +2023-09-18 21:49:46,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073786.0, "order_id": "x-XEKWYICXSSIUT605a91b9b14230582", "exchange_order_id": "35452486", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:49:46,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a91b9b14230582. +2023-09-18 21:49:46,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a91ee24ab00582 for 80.00000000 SEI-USDT. +2023-09-18 21:49:46,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a91ee24ab00582", "creation_timestamp": 1695073786.0, "exchange_order_id": "35452595", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:49:46,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a91ee24cdb0582 for 80.00000000 SEI-USDT. +2023-09-18 21:49:46,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a91ee24cdb0582", "creation_timestamp": 1695073786.0, "exchange_order_id": "35452596", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:50:34,462 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a91ee24cdb0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:50:34,464 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 21:50:34+00:00] +2023-09-18 21:50:34,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073834.0, "order_id": "x-XEKWYICXSSIUT605a91ee24cdb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "4982017", "exchange_order_id": "35452596", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:50:34,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073834.0, "order_id": "x-XEKWYICXSSIUT605a91ee24cdb0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35452596", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:50:34,499 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a91ee24cdb0582 completely filled. +2023-09-18 21:50:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a91ee24ab00582. [clock=2023-09-18 21:50:41+00:00] +2023-09-18 21:50:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250345452555683252672670533 amount: 80. +2023-09-18 21:50:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252629513909221837121539225 amount: 80. +2023-09-18 21:50:41,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073841.0, "order_id": "x-XEKWYICXBSIUT605a91ee24ab00582", "exchange_order_id": "35452595", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:50:41,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a91ee24ab00582. +2023-09-18 21:50:41,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a922298bdf0582 for 80.00000000 SEI-USDT. +2023-09-18 21:50:41,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a922298bdf0582", "creation_timestamp": 1695073841.0, "exchange_order_id": "35452778", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:50:41,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9222991330582 for 80.00000000 SEI-USDT. +2023-09-18 21:50:41,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a9222991330582", "creation_timestamp": 1695073841.0, "exchange_order_id": "35452779", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:51:25,084 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a922298bdf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:51:25,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:51:25+00:00] +2023-09-18 21:51:25,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073885.0, "order_id": "x-XEKWYICXBSIUT605a922298bdf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982045", "exchange_order_id": "35452778", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:51:25,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073885.0, "order_id": "x-XEKWYICXBSIUT605a922298bdf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35452778", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:51:25,160 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a922298bdf0582 completely filled. +2023-09-18 21:51:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9222991330582. [clock=2023-09-18 21:51:36+00:00] +2023-09-18 21:51:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247411634073197084275929999 amount: 80. +2023-09-18 21:51:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250223228352251245405765183 amount: 80. +2023-09-18 21:51:36,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073896.0, "order_id": "x-XEKWYICXSSIUT605a9222991330582", "exchange_order_id": "35452779", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:51:36,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9222991330582. +2023-09-18 21:51:36,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a92570c4bb0582 for 80.00000000 SEI-USDT. +2023-09-18 21:51:36,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a92570c4bb0582", "creation_timestamp": 1695073896.0, "exchange_order_id": "35453084", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:51:36,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a92570c2480582 for 80.00000000 SEI-USDT. +2023-09-18 21:51:36,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a92570c2480582", "creation_timestamp": 1695073896.0, "exchange_order_id": "35453085", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:52:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a92570c2480582. [clock=2023-09-18 21:52:31+00:00] +2023-09-18 21:52:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a92570c4bb0582. [clock=2023-09-18 21:52:31+00:00] +2023-09-18 21:52:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247765436634416134599620475 amount: 80. +2023-09-18 21:52:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12500000 amount: 80. +2023-09-18 21:52:31,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073951.0, "order_id": "x-XEKWYICXBSIUT605a92570c2480582", "exchange_order_id": "35453085", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:52:31,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a92570c2480582. +2023-09-18 21:52:31,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a928b802610582 for 80.00000000 SEI-USDT. +2023-09-18 21:52:31,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a928b802610582", "creation_timestamp": 1695073951.0, "exchange_order_id": "35453198", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:52:31,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073951.0, "order_id": "x-XEKWYICXSSIUT605a92570c4bb0582", "exchange_order_id": "35453084", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:52:31,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a92570c4bb0582. +2023-09-18 21:52:31,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a928b806970582 for 80.00000000 SEI-USDT. +2023-09-18 21:52:31,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a928b806970582", "creation_timestamp": 1695073951.0, "exchange_order_id": "35453199", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:53:05,968 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a928b802610582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:53:05,970 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:53:05+00:00] +2023-09-18 21:53:05,991 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073985.0, "order_id": "x-XEKWYICXBSIUT605a928b802610582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982128", "exchange_order_id": "35453198", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:53:06,001 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695073985.0, "order_id": "x-XEKWYICXBSIUT605a928b802610582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35453198", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:53:06,002 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a928b802610582 completely filled. +2023-09-18 21:53:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a928b806970582. [clock=2023-09-18 21:53:26+00:00] +2023-09-18 21:53:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242192496319044178750683247 amount: 80. +2023-09-18 21:53:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 21:53:26,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074006.0, "order_id": "x-XEKWYICXSSIUT605a928b806970582", "exchange_order_id": "35453199", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:53:26,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a928b806970582. +2023-09-18 21:53:26,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a92bff39db0582 for 80.00000000 SEI-USDT. +2023-09-18 21:53:26,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a92bff39db0582", "creation_timestamp": 1695074006.0, "exchange_order_id": "35454031", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:53:26,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a92bff3c350582 for 80.00000000 SEI-USDT. +2023-09-18 21:53:26,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a92bff3c350582", "creation_timestamp": 1695074006.0, "exchange_order_id": "35454032", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:54:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a92bff39db0582. [clock=2023-09-18 21:54:21+00:00] +2023-09-18 21:54:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a92bff3c350582. [clock=2023-09-18 21:54:21+00:00] +2023-09-18 21:54:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240744495215163949705331936 amount: 80. +2023-09-18 21:54:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-18 21:54:21,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074061.0, "order_id": "x-XEKWYICXBSIUT605a92bff39db0582", "exchange_order_id": "35454031", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:54:21,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a92bff39db0582. +2023-09-18 21:54:21,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a92f4674f60582 for 80.00000000 SEI-USDT. +2023-09-18 21:54:21,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605a92f4674f60582", "creation_timestamp": 1695074061.0, "exchange_order_id": "35454422", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:54:21,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a92f4677390582 for 80.00000000 SEI-USDT. +2023-09-18 21:54:21,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a92f4677390582", "creation_timestamp": 1695074061.0, "exchange_order_id": "35454423", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:54:21,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074061.0, "order_id": "x-XEKWYICXSSIUT605a92bff3c350582", "exchange_order_id": "35454032", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:54:21,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a92bff3c350582. +2023-09-18 21:54:28,909 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a92f4677390582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:54:28,910 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:54:28+00:00] +2023-09-18 21:54:28,930 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074068.0, "order_id": "x-XEKWYICXSSIUT605a92f4677390582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4982213", "exchange_order_id": "35454423", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:54:28,940 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074068.0, "order_id": "x-XEKWYICXSSIUT605a92f4677390582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35454423", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:54:28,941 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a92f4677390582 completely filled. +2023-09-18 21:55:15,182 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 21:55:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a92f4674f60582. [clock=2023-09-18 21:55:16+00:00] +2023-09-18 21:55:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243278932668984609741246247 amount: 80. +2023-09-18 21:55:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 21:55:16,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074116.0, "order_id": "x-XEKWYICXBSIUT605a92f4674f60582", "exchange_order_id": "35454422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:55:16,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a92f4674f60582. +2023-09-18 21:55:16,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9328db35d0582 for 80.00000000 SEI-USDT. +2023-09-18 21:55:16,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9328db35d0582", "creation_timestamp": 1695074116.0, "exchange_order_id": "35454714", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:55:16,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9328db0ce0582 for 80.00000000 SEI-USDT. +2023-09-18 21:55:16,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a9328db0ce0582", "creation_timestamp": 1695074116.0, "exchange_order_id": "35454715", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:55:42,962 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9328db35d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:55:42,963 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:55:42+00:00] +2023-09-18 21:55:42,985 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074142.0, "order_id": "x-XEKWYICXSSIUT605a9328db35d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4982233", "exchange_order_id": "35454714", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:55:42,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074142.0, "order_id": "x-XEKWYICXSSIUT605a9328db35d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35454714", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:55:42,999 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9328db35d0582 completely filled. +2023-09-18 21:56:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9328db0ce0582. [clock=2023-09-18 21:56:11+00:00] +2023-09-18 21:56:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242908763885086716352054551 amount: 80. +2023-09-18 21:56:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-18 21:56:11,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074171.0, "order_id": "x-XEKWYICXBSIUT605a9328db0ce0582", "exchange_order_id": "35454715", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:56:11,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9328db0ce0582. +2023-09-18 21:56:11,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a935d4ed070582 for 80.00000000 SEI-USDT. +2023-09-18 21:56:11,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605a935d4ed070582", "creation_timestamp": 1695074171.0, "exchange_order_id": "35455024", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:56:11,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a935d4f0e30582 for 80.00000000 SEI-USDT. +2023-09-18 21:56:11,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a935d4f0e30582", "creation_timestamp": 1695074171.0, "exchange_order_id": "35455026", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:56:31,886 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a935d4f0e30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:56:31,887 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:56:31+00:00] +2023-09-18 21:56:31,905 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074191.0, "order_id": "x-XEKWYICXSSIUT605a935d4f0e30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4982263", "exchange_order_id": "35455026", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:56:31,917 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074191.0, "order_id": "x-XEKWYICXSSIUT605a935d4f0e30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35455026", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:56:31,918 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a935d4f0e30582 completely filled. +2023-09-18 21:57:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a935d4ed070582. [clock=2023-09-18 21:57:06+00:00] +2023-09-18 21:57:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246256075245663706714247220 amount: 80. +2023-09-18 21:57:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250536438497478049447423831 amount: 80. +2023-09-18 21:57:06,120 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074226.0, "order_id": "x-XEKWYICXBSIUT605a935d4ed070582", "exchange_order_id": "35455024", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:57:06,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a935d4ed070582. +2023-09-18 21:57:06,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9391c26a90582 for 80.00000000 SEI-USDT. +2023-09-18 21:57:06,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9391c26a90582", "creation_timestamp": 1695074226.0, "exchange_order_id": "35455498", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:57:06,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9391c29050582 for 80.00000000 SEI-USDT. +2023-09-18 21:57:06,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9391c29050582", "creation_timestamp": 1695074226.0, "exchange_order_id": "35455499", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:58:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9391c26a90582. [clock=2023-09-18 21:58:01+00:00] +2023-09-18 21:58:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9391c29050582. [clock=2023-09-18 21:58:01+00:00] +2023-09-18 21:58:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243439048561012153613716733 amount: 80. +2023-09-18 21:58:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 21:58:01,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074281.0, "order_id": "x-XEKWYICXBSIUT605a9391c26a90582", "exchange_order_id": "35455498", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:58:01,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9391c26a90582. +2023-09-18 21:58:01,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074281.0, "order_id": "x-XEKWYICXSSIUT605a9391c29050582", "exchange_order_id": "35455499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:58:01,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9391c29050582. +2023-09-18 21:58:01,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a93c636be20582 for 80.00000000 SEI-USDT. +2023-09-18 21:58:01,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a93c636be20582", "creation_timestamp": 1695074281.0, "exchange_order_id": "35455775", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:58:01,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a93c6369cb0582 for 80.00000000 SEI-USDT. +2023-09-18 21:58:01,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605a93c6369cb0582", "creation_timestamp": 1695074281.0, "exchange_order_id": "35455774", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:58:48,189 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a93c636be20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:58:48,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:58:48+00:00] +2023-09-18 21:58:48,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074328.0, "order_id": "x-XEKWYICXSSIUT605a93c636be20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4982359", "exchange_order_id": "35455775", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:58:48,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074328.0, "order_id": "x-XEKWYICXSSIUT605a93c636be20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35455775", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:58:48,223 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a93c636be20582 completely filled. +2023-09-18 21:58:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a93c6369cb0582. [clock=2023-09-18 21:58:56+00:00] +2023-09-18 21:58:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244084684858226598989830703 amount: 80. +2023-09-18 21:58:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248076520920487903204888929 amount: 80. +2023-09-18 21:58:56,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074336.0, "order_id": "x-XEKWYICXBSIUT605a93c6369cb0582", "exchange_order_id": "35455774", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:58:56,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a93c6369cb0582. +2023-09-18 21:58:56,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a93faaa1a50582 for 80.00000000 SEI-USDT. +2023-09-18 21:58:56,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a93faaa1a50582", "creation_timestamp": 1695074336.0, "exchange_order_id": "35456014", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:58:56,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a93faaa4e30582 for 80.00000000 SEI-USDT. +2023-09-18 21:58:56,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a93faaa4e30582", "creation_timestamp": 1695074336.0, "exchange_order_id": "35456015", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:59:10,191 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a93faaa4e30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 21:59:10,192 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 21:59:10+00:00] +2023-09-18 21:59:10,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074350.0, "order_id": "x-XEKWYICXSSIUT605a93faaa4e30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4982366", "exchange_order_id": "35456015", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 21:59:10,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074350.0, "order_id": "x-XEKWYICXSSIUT605a93faaa4e30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35456015", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 21:59:10,227 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a93faaa4e30582 completely filled. +2023-09-18 21:59:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a93faaa1a50582. [clock=2023-09-18 21:59:51+00:00] +2023-09-18 21:59:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246347598440937483689782878 amount: 80. +2023-09-18 21:59:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250122027309354088999176550 amount: 80. +2023-09-18 21:59:51,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074391.0, "order_id": "x-XEKWYICXBSIUT605a93faaa1a50582", "exchange_order_id": "35456014", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 21:59:51,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a93faaa1a50582. +2023-09-18 21:59:51,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a942f1dce50582 for 80.00000000 SEI-USDT. +2023-09-18 21:59:51,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a942f1dce50582", "creation_timestamp": 1695074391.0, "exchange_order_id": "35456205", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 21:59:51,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a942f1deed0582 for 80.00000000 SEI-USDT. +2023-09-18 21:59:51,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a942f1deed0582", "creation_timestamp": 1695074391.0, "exchange_order_id": "35456206", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:00:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a942f1dce50582. [clock=2023-09-18 22:00:46+00:00] +2023-09-18 22:00:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a942f1deed0582. [clock=2023-09-18 22:00:46+00:00] +2023-09-18 22:00:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247492525890764070213963197 amount: 80. +2023-09-18 22:00:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:00:46,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074446.0, "order_id": "x-XEKWYICXBSIUT605a942f1dce50582", "exchange_order_id": "35456205", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:00:46,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a942f1dce50582. +2023-09-18 22:00:46,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074446.0, "order_id": "x-XEKWYICXSSIUT605a942f1deed0582", "exchange_order_id": "35456206", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:00:46,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a942f1deed0582. +2023-09-18 22:00:46,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a946391c3a0582 for 80.00000000 SEI-USDT. +2023-09-18 22:00:46,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a946391c3a0582", "creation_timestamp": 1695074446.0, "exchange_order_id": "35456525", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:00:57,782 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a946391c3a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:00:57,783 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:00:57+00:00] +2023-09-18 22:00:57,814 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074457.0, "order_id": "x-XEKWYICXBSIUT605a946391c3a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982426", "exchange_order_id": "35456525", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:00:57,832 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074457.0, "order_id": "x-XEKWYICXBSIUT605a946391c3a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35456525", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:00:57,832 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a946391c3a0582 completely filled. +2023-09-18 22:01:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248469081846493266652859025 amount: 80. +2023-09-18 22:01:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251940479252778766761669045 amount: 80. +2023-09-18 22:01:41,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a949804ee10582 for 80.00000000 SEI-USDT. +2023-09-18 22:01:41,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a949804ee10582", "creation_timestamp": 1695074501.0, "exchange_order_id": "35457114", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:01:41,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9498051ab0582 for 80.00000000 SEI-USDT. +2023-09-18 22:01:41,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a9498051ab0582", "creation_timestamp": 1695074501.0, "exchange_order_id": "35457115", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:02:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a949804ee10582. [clock=2023-09-18 22:02:36+00:00] +2023-09-18 22:02:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9498051ab0582. [clock=2023-09-18 22:02:36+00:00] +2023-09-18 22:02:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248586936004513829063362196 amount: 80. +2023-09-18 22:02:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251286332669209889438983166 amount: 80. +2023-09-18 22:02:36,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074556.0, "order_id": "x-XEKWYICXBSIUT605a949804ee10582", "exchange_order_id": "35457114", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:02:36,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a949804ee10582. +2023-09-18 22:02:36,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a94cc790760582 for 80.00000000 SEI-USDT. +2023-09-18 22:02:36,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a94cc790760582", "creation_timestamp": 1695074556.0, "exchange_order_id": "35457215", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:02:36,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a94cc792a30582 for 80.00000000 SEI-USDT. +2023-09-18 22:02:36,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a94cc792a30582", "creation_timestamp": 1695074556.0, "exchange_order_id": "35457216", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:02:36,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074556.0, "order_id": "x-XEKWYICXSSIUT605a9498051ab0582", "exchange_order_id": "35457115", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:02:36,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9498051ab0582. +2023-09-18 22:03:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a94cc790760582. [clock=2023-09-18 22:03:31+00:00] +2023-09-18 22:03:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a94cc792a30582. [clock=2023-09-18 22:03:31+00:00] +2023-09-18 22:03:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248825348961461652266873986 amount: 80. +2023-09-18 22:03:31,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251442104859971541236156500 amount: 80. +2023-09-18 22:03:31,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074611.0, "order_id": "x-XEKWYICXBSIUT605a94cc790760582", "exchange_order_id": "35457215", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:03:31,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a94cc790760582. +2023-09-18 22:03:31,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074611.0, "order_id": "x-XEKWYICXSSIUT605a94cc792a30582", "exchange_order_id": "35457216", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:03:31,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a94cc792a30582. +2023-09-18 22:03:31,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9500edc3c0582 for 80.00000000 SEI-USDT. +2023-09-18 22:03:31,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a9500edc3c0582", "creation_timestamp": 1695074611.0, "exchange_order_id": "35457320", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:03:31,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9500ed9e30582 for 80.00000000 SEI-USDT. +2023-09-18 22:03:31,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9500ed9e30582", "creation_timestamp": 1695074611.0, "exchange_order_id": "35457319", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:04:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9500ed9e30582. [clock=2023-09-18 22:04:26+00:00] +2023-09-18 22:04:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9500edc3c0582. [clock=2023-09-18 22:04:26+00:00] +2023-09-18 22:04:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249864215030246413518740405 amount: 80. +2023-09-18 22:04:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251900688564181094368589705 amount: 80. +2023-09-18 22:04:26,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074666.0, "order_id": "x-XEKWYICXBSIUT605a9500ed9e30582", "exchange_order_id": "35457319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:04:26,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9500ed9e30582. +2023-09-18 22:04:26,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a95356089c0582 for 80.00000000 SEI-USDT. +2023-09-18 22:04:26,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074666.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a95356089c0582", "creation_timestamp": 1695074666.0, "exchange_order_id": "35457523", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:04:26,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074666.0, "order_id": "x-XEKWYICXSSIUT605a9500edc3c0582", "exchange_order_id": "35457320", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:04:26,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9500edc3c0582. +2023-09-18 22:04:26,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a953560bcf0582 for 80.00000000 SEI-USDT. +2023-09-18 22:04:26,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074666.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a953560bcf0582", "creation_timestamp": 1695074666.0, "exchange_order_id": "35457524", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:05:21,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a95356089c0582. [clock=2023-09-18 22:05:21+00:00] +2023-09-18 22:05:21,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a953560bcf0582. [clock=2023-09-18 22:05:21+00:00] +2023-09-18 22:05:21,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249616597197081212171628923 amount: 80. +2023-09-18 22:05:21,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252386807004105356005403371 amount: 80. +2023-09-18 22:05:21,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074721.0, "order_id": "x-XEKWYICXBSIUT605a95356089c0582", "exchange_order_id": "35457523", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:05:21,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a95356089c0582. +2023-09-18 22:05:21,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074721.0, "order_id": "x-XEKWYICXSSIUT605a953560bcf0582", "exchange_order_id": "35457524", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:05:21,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a953560bcf0582. +2023-09-18 22:05:21,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9569daa380582 for 80.00000000 SEI-USDT. +2023-09-18 22:05:21,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a9569daa380582", "creation_timestamp": 1695074721.0, "exchange_order_id": "35457708", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:05:21,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9569da6f70582 for 80.00000000 SEI-USDT. +2023-09-18 22:05:21,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a9569da6f70582", "creation_timestamp": 1695074721.0, "exchange_order_id": "35457709", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:06:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9569da6f70582. [clock=2023-09-18 22:06:16+00:00] +2023-09-18 22:06:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9569daa380582. [clock=2023-09-18 22:06:16+00:00] +2023-09-18 22:06:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249701963255058097538736538 amount: 80. +2023-09-18 22:06:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251856187451381688090764410 amount: 80. +2023-09-18 22:06:16,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074776.0, "order_id": "x-XEKWYICXBSIUT605a9569da6f70582", "exchange_order_id": "35457709", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:06:16,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9569da6f70582. +2023-09-18 22:06:16,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074776.0, "order_id": "x-XEKWYICXSSIUT605a9569daa380582", "exchange_order_id": "35457708", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:06:16,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9569daa380582. +2023-09-18 22:06:16,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a959e483910582 for 80.00000000 SEI-USDT. +2023-09-18 22:06:16,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a959e483910582", "creation_timestamp": 1695074776.0, "exchange_order_id": "35457808", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:06:16,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a959e480260582 for 80.00000000 SEI-USDT. +2023-09-18 22:06:16,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a959e480260582", "creation_timestamp": 1695074776.0, "exchange_order_id": "35457809", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:07:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a959e480260582. [clock=2023-09-18 22:07:11+00:00] +2023-09-18 22:07:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a959e483910582. [clock=2023-09-18 22:07:11+00:00] +2023-09-18 22:07:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249768293713895208720759461 amount: 80. +2023-09-18 22:07:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251443569483750275893876072 amount: 80. +2023-09-18 22:07:11,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074831.0, "order_id": "x-XEKWYICXBSIUT605a959e480260582", "exchange_order_id": "35457809", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:07:11,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a959e480260582. +2023-09-18 22:07:11,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074831.0, "order_id": "x-XEKWYICXSSIUT605a959e483910582", "exchange_order_id": "35457808", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:07:11,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a959e483910582. +2023-09-18 22:07:11,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a95d2bbc520582 for 80.00000000 SEI-USDT. +2023-09-18 22:07:11,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a95d2bbc520582", "creation_timestamp": 1695074831.0, "exchange_order_id": "35457896", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:07:11,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a95d2bb9b60582 for 80.00000000 SEI-USDT. +2023-09-18 22:07:11,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a95d2bb9b60582", "creation_timestamp": 1695074831.0, "exchange_order_id": "35457897", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:08:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a95d2bb9b60582. [clock=2023-09-18 22:08:06+00:00] +2023-09-18 22:08:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a95d2bbc520582. [clock=2023-09-18 22:08:06+00:00] +2023-09-18 22:08:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249819844533246089556732688 amount: 80. +2023-09-18 22:08:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251122696496820639571817528 amount: 80. +2023-09-18 22:08:06,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074886.0, "order_id": "x-XEKWYICXBSIUT605a95d2bb9b60582", "exchange_order_id": "35457897", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:08:06,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a95d2bb9b60582. +2023-09-18 22:08:06,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074886.0, "order_id": "x-XEKWYICXSSIUT605a95d2bbc520582", "exchange_order_id": "35457896", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:08:06,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a95d2bbc520582. +2023-09-18 22:08:06,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a96072fd340582 for 80.00000000 SEI-USDT. +2023-09-18 22:08:06,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a96072fd340582", "creation_timestamp": 1695074886.0, "exchange_order_id": "35458001", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:08:06,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a96072fa650582 for 80.00000000 SEI-USDT. +2023-09-18 22:08:06,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a96072fa650582", "creation_timestamp": 1695074886.0, "exchange_order_id": "35458002", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:09:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a96072fa650582. [clock=2023-09-18 22:09:01+00:00] +2023-09-18 22:09:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a96072fd340582. [clock=2023-09-18 22:09:01+00:00] +2023-09-18 22:09:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249907896724200170005930590 amount: 80. +2023-09-18 22:09:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251876754867781509688146602 amount: 80. +2023-09-18 22:09:01,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074941.0, "order_id": "x-XEKWYICXBSIUT605a96072fa650582", "exchange_order_id": "35458002", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:09:01,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a96072fa650582. +2023-09-18 22:09:01,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074941.0, "order_id": "x-XEKWYICXSSIUT605a96072fd340582", "exchange_order_id": "35458001", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:09:01,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a96072fd340582. +2023-09-18 22:09:01,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a963ba37300582 for 80.00000000 SEI-USDT. +2023-09-18 22:09:01,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a963ba37300582", "creation_timestamp": 1695074941.0, "exchange_order_id": "35458090", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:09:01,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a963ba34c10582 for 80.00000000 SEI-USDT. +2023-09-18 22:09:01,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a963ba34c10582", "creation_timestamp": 1695074941.0, "exchange_order_id": "35458091", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:09:03,506 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a963ba37300582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:09:03,507 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 22:09:03+00:00] +2023-09-18 22:09:03,527 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074943.0, "order_id": "x-XEKWYICXSSIUT605a963ba37300582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "4982546", "exchange_order_id": "35458090", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:09:03,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074943.0, "order_id": "x-XEKWYICXSSIUT605a963ba37300582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35458090", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:09:03,538 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a963ba37300582 completely filled. +2023-09-18 22:09:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a963ba34c10582. [clock=2023-09-18 22:09:56+00:00] +2023-09-18 22:09:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249760013227777088149741336 amount: 80. +2023-09-18 22:09:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252627548440794190082471492 amount: 80. +2023-09-18 22:09:56,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074996.0, "order_id": "x-XEKWYICXBSIUT605a963ba34c10582", "exchange_order_id": "35458091", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:09:56,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a963ba34c10582. +2023-09-18 22:09:56,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9670172e30582 for 80.00000000 SEI-USDT. +2023-09-18 22:09:56,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a9670172e30582", "creation_timestamp": 1695074996.0, "exchange_order_id": "35458298", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:09:56,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a96701763b0582 for 80.00000000 SEI-USDT. +2023-09-18 22:09:56,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695074996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a96701763b0582", "creation_timestamp": 1695074996.0, "exchange_order_id": "35458299", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:10:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9670172e30582. [clock=2023-09-18 22:10:51+00:00] +2023-09-18 22:10:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a96701763b0582. [clock=2023-09-18 22:10:51+00:00] +2023-09-18 22:10:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249813561489625616667012906 amount: 80. +2023-09-18 22:10:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:10:51,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075051.0, "order_id": "x-XEKWYICXBSIUT605a9670172e30582", "exchange_order_id": "35458298", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:10:51,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9670172e30582. +2023-09-18 22:10:51,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075051.0, "order_id": "x-XEKWYICXSSIUT605a96701763b0582", "exchange_order_id": "35458299", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:10:51,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a96701763b0582. +2023-09-18 22:10:51,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a96a48a95d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:10:51,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a96a48a95d0582", "creation_timestamp": 1695075051.0, "exchange_order_id": "35458380", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:11:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a96a48a95d0582. [clock=2023-09-18 22:11:46+00:00] +2023-09-18 22:11:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249799146983681134875276079 amount: 80. +2023-09-18 22:11:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252200760028065411788610051 amount: 80. +2023-09-18 22:11:46,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075106.0, "order_id": "x-XEKWYICXBSIUT605a96a48a95d0582", "exchange_order_id": "35458380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:11:46,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a96a48a95d0582. +2023-09-18 22:11:46,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a96d8feb9a0582 for 80.00000000 SEI-USDT. +2023-09-18 22:11:46,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a96d8feb9a0582", "creation_timestamp": 1695075106.0, "exchange_order_id": "35458534", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:11:46,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a96d8fef9d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:11:46,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a96d8fef9d0582", "creation_timestamp": 1695075106.0, "exchange_order_id": "35458535", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:12:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a96d8feb9a0582. [clock=2023-09-18 22:12:41+00:00] +2023-09-18 22:12:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a96d8fef9d0582. [clock=2023-09-18 22:12:41+00:00] +2023-09-18 22:12:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-18 22:12:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:12:41,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075161.0, "order_id": "x-XEKWYICXBSIUT605a96d8feb9a0582", "exchange_order_id": "35458534", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:12:41,118 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a96d8feb9a0582. +2023-09-18 22:12:41,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075161.0, "order_id": "x-XEKWYICXSSIUT605a96d8fef9d0582", "exchange_order_id": "35458535", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:12:41,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a96d8fef9d0582. +2023-09-18 22:12:41,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a970d7203b0582 for 80.00000000 SEI-USDT. +2023-09-18 22:12:41,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a970d7203b0582", "creation_timestamp": 1695075161.0, "exchange_order_id": "35458728", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:12:56,109 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a970d7203b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:12:56,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:12:56+00:00] +2023-09-18 22:12:56,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075176.0, "order_id": "x-XEKWYICXBSIUT605a970d7203b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982634", "exchange_order_id": "35458728", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:12:56,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075176.0, "order_id": "x-XEKWYICXBSIUT605a970d7203b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35458728", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:12:56,154 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a970d7203b0582 completely filled. +2023-09-18 22:13:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248220167499954286243650702 amount: 80. +2023-09-18 22:13:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250709797405963890332467154 amount: 80. +2023-09-18 22:13:36,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9741e54910582 for 80.00000000 SEI-USDT. +2023-09-18 22:13:36,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9741e54910582", "creation_timestamp": 1695075216.0, "exchange_order_id": "35458851", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:13:36,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9741e58050582 for 80.00000000 SEI-USDT. +2023-09-18 22:13:36,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9741e58050582", "creation_timestamp": 1695075216.0, "exchange_order_id": "35458853", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:13:59,771 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9741e58050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:13:59,773 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:13:59+00:00] +2023-09-18 22:13:59,803 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075239.0, "order_id": "x-XEKWYICXSSIUT605a9741e58050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4982643", "exchange_order_id": "35458853", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:13:59,819 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075239.0, "order_id": "x-XEKWYICXSSIUT605a9741e58050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35458853", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:13:59,819 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9741e58050582 completely filled. +2023-09-18 22:14:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9741e54910582. [clock=2023-09-18 22:14:31+00:00] +2023-09-18 22:14:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249784680443805969261092260 amount: 80. +2023-09-18 22:14:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252240845563450304319527217 amount: 80. +2023-09-18 22:14:31,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075271.0, "order_id": "x-XEKWYICXBSIUT605a9741e54910582", "exchange_order_id": "35458851", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:14:31,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9741e54910582. +2023-09-18 22:14:31,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a977659f0e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:14:31,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a977659f0e0582", "creation_timestamp": 1695075271.0, "exchange_order_id": "35458959", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:14:31,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a97765a18f0582 for 80.00000000 SEI-USDT. +2023-09-18 22:14:31,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a97765a18f0582", "creation_timestamp": 1695075271.0, "exchange_order_id": "35458960", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:15:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a977659f0e0582. [clock=2023-09-18 22:15:26+00:00] +2023-09-18 22:15:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a97765a18f0582. [clock=2023-09-18 22:15:26+00:00] +2023-09-18 22:15:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-18 22:15:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:15:26,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075326.0, "order_id": "x-XEKWYICXBSIUT605a977659f0e0582", "exchange_order_id": "35458959", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:15:26,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a977659f0e0582. +2023-09-18 22:15:26,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a97aacd2790582 for 80.00000000 SEI-USDT. +2023-09-18 22:15:26,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a97aacd2790582", "creation_timestamp": 1695075326.0, "exchange_order_id": "35459107", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:15:26,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075326.0, "order_id": "x-XEKWYICXSSIUT605a97765a18f0582", "exchange_order_id": "35458960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:15:26,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a97765a18f0582. +2023-09-18 22:16:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a97aacd2790582. [clock=2023-09-18 22:16:21+00:00] +2023-09-18 22:16:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-18 22:16:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252026319767393634882087277 amount: 80. +2023-09-18 22:16:21,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075381.0, "order_id": "x-XEKWYICXBSIUT605a97aacd2790582", "exchange_order_id": "35459107", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:16:21,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a97aacd2790582. +2023-09-18 22:16:21,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a97df411ef0582 for 80.00000000 SEI-USDT. +2023-09-18 22:16:21,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a97df411ef0582", "creation_timestamp": 1695075381.0, "exchange_order_id": "35459200", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:16:21,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a97df415590582 for 80.00000000 SEI-USDT. +2023-09-18 22:16:21,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a97df415590582", "creation_timestamp": 1695075381.0, "exchange_order_id": "35459201", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:16:59,743 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a97df411ef0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:16:59,744 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:16:59+00:00] +2023-09-18 22:16:59,774 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075419.0, "order_id": "x-XEKWYICXBSIUT605a97df411ef0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982679", "exchange_order_id": "35459200", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:16:59,790 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075419.0, "order_id": "x-XEKWYICXBSIUT605a97df411ef0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35459200", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:16:59,790 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a97df411ef0582 completely filled. +2023-09-18 22:17:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a97df415590582. [clock=2023-09-18 22:17:16+00:00] +2023-09-18 22:17:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248548663082165946965758843 amount: 80. +2023-09-18 22:17:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250624495663567607547458745 amount: 80. +2023-09-18 22:17:16,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075436.0, "order_id": "x-XEKWYICXSSIUT605a97df415590582", "exchange_order_id": "35459201", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:17:16,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a97df415590582. +2023-09-18 22:17:16,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9813b4da30582 for 80.00000000 SEI-USDT. +2023-09-18 22:17:16,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9813b4da30582", "creation_timestamp": 1695075436.0, "exchange_order_id": "35459352", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:17:16,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9813b518d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:17:16,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9813b518d0582", "creation_timestamp": 1695075436.0, "exchange_order_id": "35459353", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:17:48,195 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9813b518d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:17:48,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:17:48+00:00] +2023-09-18 22:17:48,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075468.0, "order_id": "x-XEKWYICXSSIUT605a9813b518d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4982687", "exchange_order_id": "35459353", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:17:48,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075468.0, "order_id": "x-XEKWYICXSSIUT605a9813b518d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35459353", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:17:48,230 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9813b518d0582 completely filled. +2023-09-18 22:18:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9813b4da30582. [clock=2023-09-18 22:18:11+00:00] +2023-09-18 22:18:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250930092245059286374212743 amount: 80. +2023-09-18 22:18:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253733328964793225900461087 amount: 80. +2023-09-18 22:18:11,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075491.0, "order_id": "x-XEKWYICXBSIUT605a9813b4da30582", "exchange_order_id": "35459352", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:18:11,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9813b4da30582. +2023-09-18 22:18:11,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9848286bb0582 for 80.00000000 SEI-USDT. +2023-09-18 22:18:11,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605a9848286bb0582", "creation_timestamp": 1695075491.0, "exchange_order_id": "35459574", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:18:11,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9848289310582 for 80.00000000 SEI-USDT. +2023-09-18 22:18:11,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a9848289310582", "creation_timestamp": 1695075491.0, "exchange_order_id": "35459575", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:19:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9848286bb0582. [clock=2023-09-18 22:19:06+00:00] +2023-09-18 22:19:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9848289310582. [clock=2023-09-18 22:19:06+00:00] +2023-09-18 22:19:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249945500701835757043741381 amount: 80. +2023-09-18 22:19:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:19:06,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075546.0, "order_id": "x-XEKWYICXBSIUT605a9848286bb0582", "exchange_order_id": "35459574", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:19:06,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9848286bb0582. +2023-09-18 22:19:06,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075546.0, "order_id": "x-XEKWYICXSSIUT605a9848289310582", "exchange_order_id": "35459575", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:19:06,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9848289310582. +2023-09-18 22:19:06,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a987c9c4920582 for 80.00000000 SEI-USDT. +2023-09-18 22:19:06,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a987c9c4920582", "creation_timestamp": 1695075546.0, "exchange_order_id": "35459719", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:20:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a987c9c4920582. [clock=2023-09-18 22:20:01+00:00] +2023-09-18 22:20:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249945693738496426808802647 amount: 80. +2023-09-18 22:20:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252123730905738292106923411 amount: 80. +2023-09-18 22:20:01,118 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075601.0, "order_id": "x-XEKWYICXBSIUT605a987c9c4920582", "exchange_order_id": "35459719", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:20:01,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a987c9c4920582. +2023-09-18 22:20:01,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a98b10fef20582 for 80.00000000 SEI-USDT. +2023-09-18 22:20:01,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a98b10fef20582", "creation_timestamp": 1695075601.0, "exchange_order_id": "35459873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:20:01,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a98b1101a80582 for 80.00000000 SEI-USDT. +2023-09-18 22:20:01,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605a98b1101a80582", "creation_timestamp": 1695075601.0, "exchange_order_id": "35459875", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:20:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a98b10fef20582. [clock=2023-09-18 22:20:56+00:00] +2023-09-18 22:20:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a98b1101a80582. [clock=2023-09-18 22:20:56+00:00] +2023-09-18 22:20:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249957892458923284589532393 amount: 80. +2023-09-18 22:20:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:20:56,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075656.0, "order_id": "x-XEKWYICXBSIUT605a98b10fef20582", "exchange_order_id": "35459873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:20:56,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a98b10fef20582. +2023-09-18 22:20:56,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075656.0, "order_id": "x-XEKWYICXSSIUT605a98b1101a80582", "exchange_order_id": "35459875", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:20:56,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a98b1101a80582. +2023-09-18 22:20:56,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a98e583c2c0582 for 80.00000000 SEI-USDT. +2023-09-18 22:20:56,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a98e583c2c0582", "creation_timestamp": 1695075656.0, "exchange_order_id": "35460014", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:21:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a98e583c2c0582. [clock=2023-09-18 22:21:51+00:00] +2023-09-18 22:21:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 22:21:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255554646194320931117124551 amount: 80. +2023-09-18 22:21:51,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075711.0, "order_id": "x-XEKWYICXBSIUT605a98e583c2c0582", "exchange_order_id": "35460014", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:21:51,120 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a98e583c2c0582. +2023-09-18 22:21:51,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9919f781f0582 for 80.00000000 SEI-USDT. +2023-09-18 22:21:51,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a9919f781f0582", "creation_timestamp": 1695075711.0, "exchange_order_id": "35460312", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:21:51,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9919f74c80582 for 80.00000000 SEI-USDT. +2023-09-18 22:21:51,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a9919f74c80582", "creation_timestamp": 1695075711.0, "exchange_order_id": "35460313", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:22:03,541 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a9919f74c80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:22:03,542 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 22:22:03+00:00] +2023-09-18 22:22:03,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075723.0, "order_id": "x-XEKWYICXBSIUT605a9919f74c80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982776", "exchange_order_id": "35460313", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:22:03,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075723.0, "order_id": "x-XEKWYICXBSIUT605a9919f74c80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35460313", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:22:03,573 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a9919f74c80582 completely filled. +2023-09-18 22:22:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9919f781f0582. [clock=2023-09-18 22:22:46+00:00] +2023-09-18 22:22:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 22:22:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256124081797464564812193631 amount: 80. +2023-09-18 22:22:46,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075766.0, "order_id": "x-XEKWYICXSSIUT605a9919f781f0582", "exchange_order_id": "35460312", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:22:46,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9919f781f0582. +2023-09-18 22:22:46,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a994e6b83e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:22:46,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a994e6b83e0582", "creation_timestamp": 1695075766.0, "exchange_order_id": "35460528", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:22:46,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a994e6bd5e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:22:46,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605a994e6bd5e0582", "creation_timestamp": 1695075766.0, "exchange_order_id": "35460529", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:23:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a994e6b83e0582. [clock=2023-09-18 22:23:41+00:00] +2023-09-18 22:23:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a994e6bd5e0582. [clock=2023-09-18 22:23:41+00:00] +2023-09-18 22:23:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 22:23:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255206863393521078062745029 amount: 80. +2023-09-18 22:23:41,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075821.0, "order_id": "x-XEKWYICXBSIUT605a994e6b83e0582", "exchange_order_id": "35460528", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:23:41,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a994e6b83e0582. +2023-09-18 22:23:41,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075821.0, "order_id": "x-XEKWYICXSSIUT605a994e6bd5e0582", "exchange_order_id": "35460529", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:23:41,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a994e6bd5e0582. +2023-09-18 22:23:41,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9982df1600582 for 80.00000000 SEI-USDT. +2023-09-18 22:23:41,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a9982df1600582", "creation_timestamp": 1695075821.0, "exchange_order_id": "35460633", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:23:41,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9982df3890582 for 80.00000000 SEI-USDT. +2023-09-18 22:23:41,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605a9982df3890582", "creation_timestamp": 1695075821.0, "exchange_order_id": "35460634", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:24:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9982df1600582. [clock=2023-09-18 22:24:36+00:00] +2023-09-18 22:24:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9982df3890582. [clock=2023-09-18 22:24:36+00:00] +2023-09-18 22:24:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251644816716432494612981550 amount: 80. +2023-09-18 22:24:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254277723823262759255724128 amount: 80. +2023-09-18 22:24:36,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075876.0, "order_id": "x-XEKWYICXBSIUT605a9982df1600582", "exchange_order_id": "35460633", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:24:36,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9982df1600582. +2023-09-18 22:24:36,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075876.0, "order_id": "x-XEKWYICXSSIUT605a9982df3890582", "exchange_order_id": "35460634", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:24:36,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9982df3890582. +2023-09-18 22:24:36,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a99b752a800582 for 80.00000000 SEI-USDT. +2023-09-18 22:24:36,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a99b752a800582", "creation_timestamp": 1695075876.0, "exchange_order_id": "35460704", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:24:36,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a99b7528b20582 for 80.00000000 SEI-USDT. +2023-09-18 22:24:36,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a99b7528b20582", "creation_timestamp": 1695075876.0, "exchange_order_id": "35460705", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:25:15,212 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 22:25:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a99b7528b20582. [clock=2023-09-18 22:25:31+00:00] +2023-09-18 22:25:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a99b752a800582. [clock=2023-09-18 22:25:31+00:00] +2023-09-18 22:25:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 22:25:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254597978402506345223101208 amount: 80. +2023-09-18 22:25:31,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075931.0, "order_id": "x-XEKWYICXBSIUT605a99b7528b20582", "exchange_order_id": "35460705", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:25:31,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a99b7528b20582. +2023-09-18 22:25:31,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075931.0, "order_id": "x-XEKWYICXSSIUT605a99b752a800582", "exchange_order_id": "35460704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:25:31,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a99b752a800582. +2023-09-18 22:25:31,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a99ebc67f30582 for 80.00000000 SEI-USDT. +2023-09-18 22:25:31,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a99ebc67f30582", "creation_timestamp": 1695075931.0, "exchange_order_id": "35460780", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:25:31,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a99ebc65e40582 for 80.00000000 SEI-USDT. +2023-09-18 22:25:31,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a99ebc65e40582", "creation_timestamp": 1695075931.0, "exchange_order_id": "35460779", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:26:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a99ebc65e40582. [clock=2023-09-18 22:26:26+00:00] +2023-09-18 22:26:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a99ebc67f30582. [clock=2023-09-18 22:26:26+00:00] +2023-09-18 22:26:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-18 22:26:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254020349774210310630481829 amount: 80. +2023-09-18 22:26:26,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075986.0, "order_id": "x-XEKWYICXBSIUT605a99ebc65e40582", "exchange_order_id": "35460779", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:26:26,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a99ebc65e40582. +2023-09-18 22:26:26,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9a203a57e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:26:26,414 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605a9a203a57e0582", "creation_timestamp": 1695075986.0, "exchange_order_id": "35460861", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:26:26,426 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075986.0, "order_id": "x-XEKWYICXSSIUT605a99ebc67f30582", "exchange_order_id": "35460780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:26:26,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a99ebc67f30582. +2023-09-18 22:26:26,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9a203a31b0582 for 80.00000000 SEI-USDT. +2023-09-18 22:26:26,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695075986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605a9a203a31b0582", "creation_timestamp": 1695075986.0, "exchange_order_id": "35460862", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:27:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9a203a31b0582. [clock=2023-09-18 22:27:21+00:00] +2023-09-18 22:27:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9a203a57e0582. [clock=2023-09-18 22:27:21+00:00] +2023-09-18 22:27:21,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251632598944486176849608564 amount: 80. +2023-09-18 22:27:21,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253538008214127604500584464 amount: 80. +2023-09-18 22:27:21,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076041.0, "order_id": "x-XEKWYICXBSIUT605a9a203a31b0582", "exchange_order_id": "35460862", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:27:21,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9a203a31b0582. +2023-09-18 22:27:21,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9a54af61b0582 for 80.00000000 SEI-USDT. +2023-09-18 22:27:21,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605a9a54af61b0582", "creation_timestamp": 1695076041.0, "exchange_order_id": "35460948", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:27:21,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9a54af9c80582 for 80.00000000 SEI-USDT. +2023-09-18 22:27:21,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605a9a54af9c80582", "creation_timestamp": 1695076041.0, "exchange_order_id": "35460949", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:27:21,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076041.0, "order_id": "x-XEKWYICXSSIUT605a9a203a57e0582", "exchange_order_id": "35460861", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:27:21,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9a203a57e0582. +2023-09-18 22:27:31,842 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a9a54af61b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:27:31,843 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-18 22:27:31+00:00] +2023-09-18 22:27:31,865 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076051.0, "order_id": "x-XEKWYICXBSIUT605a9a54af61b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4982826", "exchange_order_id": "35460948", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:27:31,878 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076051.0, "order_id": "x-XEKWYICXBSIUT605a9a54af61b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35460948", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:27:31,878 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a9a54af61b0582 completely filled. +2023-09-18 22:28:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9a54af9c80582. [clock=2023-09-18 22:28:16+00:00] +2023-09-18 22:28:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246038881650962076938322834 amount: 80. +2023-09-18 22:28:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12500000 amount: 80. +2023-09-18 22:28:16,123 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076096.0, "order_id": "x-XEKWYICXSSIUT605a9a54af9c80582", "exchange_order_id": "35460949", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:28:16,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9a54af9c80582. +2023-09-18 22:28:16,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9a89217dc0582 for 80.00000000 SEI-USDT. +2023-09-18 22:28:16,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9a89217dc0582", "creation_timestamp": 1695076096.0, "exchange_order_id": "35461644", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:28:16,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9a8921a3b0582 for 80.00000000 SEI-USDT. +2023-09-18 22:28:16,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9a8921a3b0582", "creation_timestamp": 1695076096.0, "exchange_order_id": "35461645", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:29:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9a89217dc0582. [clock=2023-09-18 22:29:11+00:00] +2023-09-18 22:29:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9a8921a3b0582. [clock=2023-09-18 22:29:11+00:00] +2023-09-18 22:29:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247538257851249421272421123 amount: 80. +2023-09-18 22:29:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250968159262199849862357840 amount: 80. +2023-09-18 22:29:11,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076151.0, "order_id": "x-XEKWYICXBSIUT605a9a89217dc0582", "exchange_order_id": "35461644", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:29:11,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9a89217dc0582. +2023-09-18 22:29:11,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076151.0, "order_id": "x-XEKWYICXSSIUT605a9a8921a3b0582", "exchange_order_id": "35461645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:29:11,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9a8921a3b0582. +2023-09-18 22:29:11,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9abd95cdf0582 for 80.00000000 SEI-USDT. +2023-09-18 22:29:11,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a9abd95cdf0582", "creation_timestamp": 1695076151.0, "exchange_order_id": "35461760", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:29:11,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9abd960000582 for 80.00000000 SEI-USDT. +2023-09-18 22:29:11,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9abd960000582", "creation_timestamp": 1695076151.0, "exchange_order_id": "35461759", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:29:52,283 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9abd960000582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:29:52,284 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:29:52+00:00] +2023-09-18 22:29:52,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076192.0, "order_id": "x-XEKWYICXSSIUT605a9abd960000582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000000"}]}, "exchange_trade_id": "4982938", "exchange_order_id": "35461759", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:29:52,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076192.0, "order_id": "x-XEKWYICXSSIUT605a9abd960000582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35461759", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:29:52,323 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9abd960000582 completely filled. +2023-09-18 22:30:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9abd95cdf0582. [clock=2023-09-18 22:30:06+00:00] +2023-09-18 22:30:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249136377390278938651592849 amount: 80. +2023-09-18 22:30:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251806680133314066198295945 amount: 80. +2023-09-18 22:30:06,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076206.0, "order_id": "x-XEKWYICXBSIUT605a9abd95cdf0582", "exchange_order_id": "35461760", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:30:06,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9abd95cdf0582. +2023-09-18 22:30:06,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9af2090ca0582 for 80.00000000 SEI-USDT. +2023-09-18 22:30:06,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605a9af2090ca0582", "creation_timestamp": 1695076206.0, "exchange_order_id": "35461952", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:30:06,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9af2093d40582 for 80.00000000 SEI-USDT. +2023-09-18 22:30:06,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605a9af2093d40582", "creation_timestamp": 1695076206.0, "exchange_order_id": "35461953", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:31:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9af2090ca0582. [clock=2023-09-18 22:31:01+00:00] +2023-09-18 22:31:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9af2093d40582. [clock=2023-09-18 22:31:01+00:00] +2023-09-18 22:31:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248136690948850947123731288 amount: 80. +2023-09-18 22:31:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250804856643045741245421386 amount: 80. +2023-09-18 22:31:01,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076261.0, "order_id": "x-XEKWYICXBSIUT605a9af2090ca0582", "exchange_order_id": "35461952", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:31:01,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9af2090ca0582. +2023-09-18 22:31:01,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9b267cfb60582 for 80.00000000 SEI-USDT. +2023-09-18 22:31:01,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9b267cfb60582", "creation_timestamp": 1695076261.0, "exchange_order_id": "35462132", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:31:01,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076261.0, "order_id": "x-XEKWYICXSSIUT605a9af2093d40582", "exchange_order_id": "35461953", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:31:01,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9af2093d40582. +2023-09-18 22:31:01,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9b267cc520582 for 80.00000000 SEI-USDT. +2023-09-18 22:31:01,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9b267cc520582", "creation_timestamp": 1695076261.0, "exchange_order_id": "35462133", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:31:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9b267cc520582. [clock=2023-09-18 22:31:56+00:00] +2023-09-18 22:31:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9b267cfb60582. [clock=2023-09-18 22:31:56+00:00] +2023-09-18 22:31:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248113292151250545155914623 amount: 80. +2023-09-18 22:31:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250854945830724493891198485 amount: 80. +2023-09-18 22:31:56,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076316.0, "order_id": "x-XEKWYICXBSIUT605a9b267cc520582", "exchange_order_id": "35462133", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:31:56,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9b267cc520582. +2023-09-18 22:31:56,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9b5af0f1e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:31:56,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9b5af0f1e0582", "creation_timestamp": 1695076316.0, "exchange_order_id": "35462209", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:31:56,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076316.0, "order_id": "x-XEKWYICXSSIUT605a9b267cfb60582", "exchange_order_id": "35462132", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:31:56,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9b267cfb60582. +2023-09-18 22:31:56,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9b5af0c5d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:31:56,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9b5af0c5d0582", "creation_timestamp": 1695076316.0, "exchange_order_id": "35462210", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:32:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9b5af0c5d0582. [clock=2023-09-18 22:32:51+00:00] +2023-09-18 22:32:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9b5af0f1e0582. [clock=2023-09-18 22:32:51+00:00] +2023-09-18 22:32:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248094798922321284386307388 amount: 80. +2023-09-18 22:32:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250893610350105385290020388 amount: 80. +2023-09-18 22:32:51,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076371.0, "order_id": "x-XEKWYICXBSIUT605a9b5af0c5d0582", "exchange_order_id": "35462210", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:32:51,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9b5af0c5d0582. +2023-09-18 22:32:51,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076371.0, "order_id": "x-XEKWYICXSSIUT605a9b5af0f1e0582", "exchange_order_id": "35462209", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:32:51,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9b5af0f1e0582. +2023-09-18 22:32:51,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9b8f649250582 for 80.00000000 SEI-USDT. +2023-09-18 22:32:51,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9b8f649250582", "creation_timestamp": 1695076371.0, "exchange_order_id": "35462363", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:32:51,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9b8f64afd0582 for 80.00000000 SEI-USDT. +2023-09-18 22:32:51,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605a9b8f64afd0582", "creation_timestamp": 1695076371.0, "exchange_order_id": "35462365", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:33:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9b8f649250582. [clock=2023-09-18 22:33:46+00:00] +2023-09-18 22:33:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9b8f64afd0582. [clock=2023-09-18 22:33:46+00:00] +2023-09-18 22:33:46,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246032314609123689283708217 amount: 80. +2023-09-18 22:33:46,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12490000 amount: 80. +2023-09-18 22:33:46,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076426.0, "order_id": "x-XEKWYICXBSIUT605a9b8f649250582", "exchange_order_id": "35462363", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:33:46,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9b8f649250582. +2023-09-18 22:33:46,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076426.0, "order_id": "x-XEKWYICXSSIUT605a9b8f64afd0582", "exchange_order_id": "35462365", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:33:46,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9b8f64afd0582. +2023-09-18 22:33:46,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9bc3da1430582 for 80.00000000 SEI-USDT. +2023-09-18 22:33:46,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605a9bc3da1430582", "creation_timestamp": 1695076426.0, "exchange_order_id": "35462578", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:33:46,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9bc3d9f230582 for 80.00000000 SEI-USDT. +2023-09-18 22:33:46,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9bc3d9f230582", "creation_timestamp": 1695076426.0, "exchange_order_id": "35462579", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:34:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9bc3d9f230582. [clock=2023-09-18 22:34:41+00:00] +2023-09-18 22:34:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9bc3da1430582. [clock=2023-09-18 22:34:41+00:00] +2023-09-18 22:34:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244985058231479991685149773 amount: 80. +2023-09-18 22:34:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 22:34:41,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076481.0, "order_id": "x-XEKWYICXBSIUT605a9bc3d9f230582", "exchange_order_id": "35462579", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:34:41,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9bc3d9f230582. +2023-09-18 22:34:41,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076481.0, "order_id": "x-XEKWYICXSSIUT605a9bc3da1430582", "exchange_order_id": "35462578", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:34:41,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9bc3da1430582. +2023-09-18 22:34:41,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9bf84c0b20582 for 80.00000000 SEI-USDT. +2023-09-18 22:34:41,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a9bf84c0b20582", "creation_timestamp": 1695076481.0, "exchange_order_id": "35462849", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:34:41,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9bf84c3800582 for 80.00000000 SEI-USDT. +2023-09-18 22:34:41,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9bf84c3800582", "creation_timestamp": 1695076481.0, "exchange_order_id": "35462852", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:35:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9bf84c0b20582. [clock=2023-09-18 22:35:36+00:00] +2023-09-18 22:35:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9bf84c3800582. [clock=2023-09-18 22:35:36+00:00] +2023-09-18 22:35:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245433148307242371679785361 amount: 80. +2023-09-18 22:35:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 22:35:36,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076536.0, "order_id": "x-XEKWYICXBSIUT605a9bf84c0b20582", "exchange_order_id": "35462849", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:35:36,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9bf84c0b20582. +2023-09-18 22:35:36,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9c2cc01f90582 for 80.00000000 SEI-USDT. +2023-09-18 22:35:36,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a9c2cc01f90582", "creation_timestamp": 1695076536.0, "exchange_order_id": "35462932", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:35:36,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9c2cc03e00582 for 80.00000000 SEI-USDT. +2023-09-18 22:35:36,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9c2cc03e00582", "creation_timestamp": 1695076536.0, "exchange_order_id": "35462933", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:35:36,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076536.0, "order_id": "x-XEKWYICXSSIUT605a9bf84c3800582", "exchange_order_id": "35462852", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:35:36,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9bf84c3800582. +2023-09-18 22:36:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9c2cc01f90582. [clock=2023-09-18 22:36:31+00:00] +2023-09-18 22:36:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9c2cc03e00582. [clock=2023-09-18 22:36:31+00:00] +2023-09-18 22:36:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245781350574800459440930802 amount: 80. +2023-09-18 22:36:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 22:36:31,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076591.0, "order_id": "x-XEKWYICXBSIUT605a9c2cc01f90582", "exchange_order_id": "35462932", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:36:31,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9c2cc01f90582. +2023-09-18 22:36:31,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9c61335fa0582 for 80.00000000 SEI-USDT. +2023-09-18 22:36:31,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9c61335fa0582", "creation_timestamp": 1695076591.0, "exchange_order_id": "35463022", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:36:31,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9c61333760582 for 80.00000000 SEI-USDT. +2023-09-18 22:36:31,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a9c61333760582", "creation_timestamp": 1695076591.0, "exchange_order_id": "35463021", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:36:31,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076591.0, "order_id": "x-XEKWYICXSSIUT605a9c2cc03e00582", "exchange_order_id": "35462933", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:36:31,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9c2cc03e00582. +2023-09-18 22:37:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9c61333760582. [clock=2023-09-18 22:37:26+00:00] +2023-09-18 22:37:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9c61335fa0582. [clock=2023-09-18 22:37:26+00:00] +2023-09-18 22:37:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246052169441690568381120489 amount: 80. +2023-09-18 22:37:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 22:37:26,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076646.0, "order_id": "x-XEKWYICXBSIUT605a9c61333760582", "exchange_order_id": "35463021", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:37:26,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9c61333760582. +2023-09-18 22:37:26,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9c95a78ae0582 for 80.00000000 SEI-USDT. +2023-09-18 22:37:26,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9c95a78ae0582", "creation_timestamp": 1695076646.0, "exchange_order_id": "35463104", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:37:26,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076646.0, "order_id": "x-XEKWYICXSSIUT605a9c61335fa0582", "exchange_order_id": "35463022", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:37:26,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9c61335fa0582. +2023-09-18 22:37:26,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9c95a76bb0582 for 80.00000000 SEI-USDT. +2023-09-18 22:37:26,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9c95a76bb0582", "creation_timestamp": 1695076646.0, "exchange_order_id": "35463103", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:38:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9c95a76bb0582. [clock=2023-09-18 22:38:21+00:00] +2023-09-18 22:38:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9c95a78ae0582. [clock=2023-09-18 22:38:21+00:00] +2023-09-18 22:38:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246262803232062500670296764 amount: 80. +2023-09-18 22:38:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 22:38:21,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076701.0, "order_id": "x-XEKWYICXBSIUT605a9c95a76bb0582", "exchange_order_id": "35463103", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:38:21,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9c95a76bb0582. +2023-09-18 22:38:21,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076701.0, "order_id": "x-XEKWYICXSSIUT605a9c95a78ae0582", "exchange_order_id": "35463104", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:38:21,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9c95a78ae0582. +2023-09-18 22:38:21,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9cca1b0590582 for 80.00000000 SEI-USDT. +2023-09-18 22:38:21,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9cca1b0590582", "creation_timestamp": 1695076701.0, "exchange_order_id": "35463198", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:38:21,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9cca1b29f0582 for 80.00000000 SEI-USDT. +2023-09-18 22:38:21,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9cca1b29f0582", "creation_timestamp": 1695076701.0, "exchange_order_id": "35463199", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:39:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9cca1b0590582. [clock=2023-09-18 22:39:16+00:00] +2023-09-18 22:39:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9cca1b29f0582. [clock=2023-09-18 22:39:16+00:00] +2023-09-18 22:39:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244669648240590028460973150 amount: 80. +2023-09-18 22:39:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-18 22:39:16,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076756.0, "order_id": "x-XEKWYICXBSIUT605a9cca1b0590582", "exchange_order_id": "35463198", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:39:16,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9cca1b0590582. +2023-09-18 22:39:16,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076756.0, "order_id": "x-XEKWYICXSSIUT605a9cca1b29f0582", "exchange_order_id": "35463199", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:39:16,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9cca1b29f0582. +2023-09-18 22:39:16,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9cfe8eb0e0582 for 80.00000000 SEI-USDT. +2023-09-18 22:39:16,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a9cfe8eb0e0582", "creation_timestamp": 1695076756.0, "exchange_order_id": "35463322", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:39:16,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9cfe8e8f10582 for 80.00000000 SEI-USDT. +2023-09-18 22:39:16,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a9cfe8e8f10582", "creation_timestamp": 1695076756.0, "exchange_order_id": "35463323", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:40:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9cfe8e8f10582. [clock=2023-09-18 22:40:11+00:00] +2023-09-18 22:40:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9cfe8eb0e0582. [clock=2023-09-18 22:40:11+00:00] +2023-09-18 22:40:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244965492280170451759227739 amount: 80. +2023-09-18 22:40:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-18 22:40:11,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076811.0, "order_id": "x-XEKWYICXBSIUT605a9cfe8e8f10582", "exchange_order_id": "35463323", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:40:11,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9cfe8e8f10582. +2023-09-18 22:40:11,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076811.0, "order_id": "x-XEKWYICXSSIUT605a9cfe8eb0e0582", "exchange_order_id": "35463322", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:40:11,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9cfe8eb0e0582. +2023-09-18 22:40:11,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9d3302df30582 for 80.00000000 SEI-USDT. +2023-09-18 22:40:11,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a9d3302df30582", "creation_timestamp": 1695076811.0, "exchange_order_id": "35463468", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:40:11,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9d3302a6d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:40:11,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a9d3302a6d0582", "creation_timestamp": 1695076811.0, "exchange_order_id": "35463467", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:40:43,526 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9d3302df30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:40:43,527 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:40:43+00:00] +2023-09-18 22:40:43,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076843.0, "order_id": "x-XEKWYICXSSIUT605a9d3302df30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4983080", "exchange_order_id": "35463468", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:40:43,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076843.0, "order_id": "x-XEKWYICXSSIUT605a9d3302df30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35463468", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:40:43,577 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9d3302df30582 completely filled. +2023-09-18 22:41:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9d3302a6d0582. [clock=2023-09-18 22:41:06+00:00] +2023-09-18 22:41:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246533248748869325151301664 amount: 80. +2023-09-18 22:41:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248155427942580990928805048 amount: 80. +2023-09-18 22:41:06,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076866.0, "order_id": "x-XEKWYICXBSIUT605a9d3302a6d0582", "exchange_order_id": "35463467", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:41:06,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9d3302a6d0582. +2023-09-18 22:41:06,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9d6775cc90582 for 80.00000000 SEI-USDT. +2023-09-18 22:41:06,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9d6775cc90582", "creation_timestamp": 1695076866.0, "exchange_order_id": "35463688", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:41:06,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9d67759b30582 for 80.00000000 SEI-USDT. +2023-09-18 22:41:06,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9d67759b30582", "creation_timestamp": 1695076866.0, "exchange_order_id": "35463689", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:41:38,222 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9d6775cc90582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:41:38,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:41:38+00:00] +2023-09-18 22:41:38,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076898.0, "order_id": "x-XEKWYICXSSIUT605a9d6775cc90582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4983104", "exchange_order_id": "35463688", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:41:38,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076898.0, "order_id": "x-XEKWYICXSSIUT605a9d6775cc90582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35463688", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:41:38,259 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9d6775cc90582 completely filled. +2023-09-18 22:42:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9d67759b30582. [clock=2023-09-18 22:42:01+00:00] +2023-09-18 22:42:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247878038610868807287399716 amount: 80. +2023-09-18 22:42:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:42:01,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076921.0, "order_id": "x-XEKWYICXBSIUT605a9d67759b30582", "exchange_order_id": "35463689", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:42:01,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9d67759b30582. +2023-09-18 22:42:01,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9d9be9b2f0582 for 80.00000000 SEI-USDT. +2023-09-18 22:42:01,123 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a9d9be9b2f0582", "creation_timestamp": 1695076921.0, "exchange_order_id": "35463876", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:42:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9d9be9b2f0582. [clock=2023-09-18 22:42:56+00:00] +2023-09-18 22:42:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247905279725478775728849988 amount: 80. +2023-09-18 22:42:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:42:56,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076976.0, "order_id": "x-XEKWYICXBSIUT605a9d9be9b2f0582", "exchange_order_id": "35463876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:42:56,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9d9be9b2f0582. +2023-09-18 22:42:56,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9dd05d1d60582 for 80.00000000 SEI-USDT. +2023-09-18 22:42:56,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695076976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605a9dd05d1d60582", "creation_timestamp": 1695076976.0, "exchange_order_id": "35463954", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:43:22,012 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a9dd05d1d60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:43:22,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:43:22+00:00] +2023-09-18 22:43:22,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077002.0, "order_id": "x-XEKWYICXBSIUT605a9dd05d1d60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4983152", "exchange_order_id": "35463954", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:43:22,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077002.0, "order_id": "x-XEKWYICXBSIUT605a9dd05d1d60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35463954", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:43:22,059 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a9dd05d1d60582 completely filled. +2023-09-18 22:43:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246480261520246183828284276 amount: 80. +2023-09-18 22:43:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248532100287967644296660648 amount: 80. +2023-09-18 22:43:51,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9e04d14c50582 for 80.00000000 SEI-USDT. +2023-09-18 22:43:51,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9e04d14c50582", "creation_timestamp": 1695077031.0, "exchange_order_id": "35464182", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:43:51,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9e04d17490582 for 80.00000000 SEI-USDT. +2023-09-18 22:43:51,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9e04d17490582", "creation_timestamp": 1695077031.0, "exchange_order_id": "35464183", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:44:21,594 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605a9e04d14c50582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:44:21,595 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:44:21+00:00] +2023-09-18 22:44:21,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077061.0, "order_id": "x-XEKWYICXBSIUT605a9e04d14c50582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4983182", "exchange_order_id": "35464182", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:44:21,625 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077061.0, "order_id": "x-XEKWYICXBSIUT605a9e04d14c50582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35464182", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:44:21,625 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605a9e04d14c50582 completely filled. +2023-09-18 22:44:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9e04d17490582. [clock=2023-09-18 22:44:46+00:00] +2023-09-18 22:44:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244958521810327053674733762 amount: 80. +2023-09-18 22:44:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247885725073673419281744260 amount: 80. +2023-09-18 22:44:46,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077086.0, "order_id": "x-XEKWYICXSSIUT605a9e04d17490582", "exchange_order_id": "35464183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:44:46,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9e04d17490582. +2023-09-18 22:44:46,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9e39459f40582 for 80.00000000 SEI-USDT. +2023-09-18 22:44:46,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a9e39459f40582", "creation_timestamp": 1695077086.0, "exchange_order_id": "35464468", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:44:46,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9e39456740582 for 80.00000000 SEI-USDT. +2023-09-18 22:44:46,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605a9e39456740582", "creation_timestamp": 1695077086.0, "exchange_order_id": "35464469", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:44:46,726 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9e39459f40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:44:46,728 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:44:46+00:00] +2023-09-18 22:44:46,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077086.0, "order_id": "x-XEKWYICXSSIUT605a9e39459f40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4983198", "exchange_order_id": "35464468", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:44:46,764 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077086.0, "order_id": "x-XEKWYICXSSIUT605a9e39459f40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35464468", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:44:46,765 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9e39459f40582 completely filled. +2023-09-18 22:45:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9e39456740582. [clock=2023-09-18 22:45:41+00:00] +2023-09-18 22:45:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246423329517462222979769242 amount: 80. +2023-09-18 22:45:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248702126594204284140280289 amount: 80. +2023-09-18 22:45:41,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077141.0, "order_id": "x-XEKWYICXBSIUT605a9e39456740582", "exchange_order_id": "35464469", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:45:41,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9e39456740582. +2023-09-18 22:45:41,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9e6db84220582 for 80.00000000 SEI-USDT. +2023-09-18 22:45:41,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9e6db84220582", "creation_timestamp": 1695077141.0, "exchange_order_id": "35464728", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:45:41,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9e6db86ff0582 for 80.00000000 SEI-USDT. +2023-09-18 22:45:41,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9e6db86ff0582", "creation_timestamp": 1695077141.0, "exchange_order_id": "35464729", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:46:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9e6db84220582. [clock=2023-09-18 22:46:36+00:00] +2023-09-18 22:46:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9e6db86ff0582. [clock=2023-09-18 22:46:36+00:00] +2023-09-18 22:46:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246743089732793124917160390 amount: 80. +2023-09-18 22:46:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:46:36,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077196.0, "order_id": "x-XEKWYICXBSIUT605a9e6db84220582", "exchange_order_id": "35464728", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:46:36,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9e6db84220582. +2023-09-18 22:46:36,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077196.0, "order_id": "x-XEKWYICXSSIUT605a9e6db86ff0582", "exchange_order_id": "35464729", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:46:36,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9e6db86ff0582. +2023-09-18 22:46:36,395 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9ea22caba0582 for 80.00000000 SEI-USDT. +2023-09-18 22:46:36,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9ea22caba0582", "creation_timestamp": 1695077196.0, "exchange_order_id": "35464875", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:47:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9ea22caba0582. [clock=2023-09-18 22:47:31+00:00] +2023-09-18 22:47:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246800336755646643463591803 amount: 80. +2023-09-18 22:47:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248696382649959401393339947 amount: 80. +2023-09-18 22:47:31,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077251.0, "order_id": "x-XEKWYICXBSIUT605a9ea22caba0582", "exchange_order_id": "35464875", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:47:31,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9ea22caba0582. +2023-09-18 22:47:31,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9ed6a03d70582 for 80.00000000 SEI-USDT. +2023-09-18 22:47:31,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605a9ed6a03d70582", "creation_timestamp": 1695077251.0, "exchange_order_id": "35464955", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:47:31,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9ed6a016a0582 for 80.00000000 SEI-USDT. +2023-09-18 22:47:31,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9ed6a016a0582", "creation_timestamp": 1695077251.0, "exchange_order_id": "35464956", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:48:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9ed6a016a0582. [clock=2023-09-18 22:48:26+00:00] +2023-09-18 22:48:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605a9ed6a03d70582. [clock=2023-09-18 22:48:26+00:00] +2023-09-18 22:48:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246844800645019248538281454 amount: 80. +2023-09-18 22:48:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:48:26,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077306.0, "order_id": "x-XEKWYICXBSIUT605a9ed6a016a0582", "exchange_order_id": "35464956", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:48:26,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9ed6a016a0582. +2023-09-18 22:48:26,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077306.0, "order_id": "x-XEKWYICXSSIUT605a9ed6a03d70582", "exchange_order_id": "35464955", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:48:26,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605a9ed6a03d70582. +2023-09-18 22:48:26,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9f0b1401d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:48:26,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605a9f0b1401d0582", "creation_timestamp": 1695077306.0, "exchange_order_id": "35465040", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:49:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9f0b1401d0582. [clock=2023-09-18 22:49:21+00:00] +2023-09-18 22:49:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245620145517159068238491846 amount: 80. +2023-09-18 22:49:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247285154932882450622469261 amount: 80. +2023-09-18 22:49:21,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077361.0, "order_id": "x-XEKWYICXBSIUT605a9f0b1401d0582", "exchange_order_id": "35465040", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:49:21,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9f0b1401d0582. +2023-09-18 22:49:21,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9f3f881b50582 for 80.00000000 SEI-USDT. +2023-09-18 22:49:21,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605a9f3f881b50582", "creation_timestamp": 1695077361.0, "exchange_order_id": "35465278", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:49:21,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605a9f3f885060582 for 80.00000000 SEI-USDT. +2023-09-18 22:49:21,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605a9f3f885060582", "creation_timestamp": 1695077361.0, "exchange_order_id": "35465279", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:49:40,804 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605a9f3f885060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:49:40,805 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:49:40+00:00] +2023-09-18 22:49:40,828 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077380.0, "order_id": "x-XEKWYICXSSIUT605a9f3f885060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "4983243", "exchange_order_id": "35465279", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:49:40,841 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077380.0, "order_id": "x-XEKWYICXSSIUT605a9f3f885060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35465279", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:49:40,841 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605a9f3f885060582 completely filled. +2023-09-18 22:50:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9f3f881b50582. [clock=2023-09-18 22:50:16+00:00] +2023-09-18 22:50:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 22:50:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:50:16,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077416.0, "order_id": "x-XEKWYICXBSIUT605a9f3f881b50582", "exchange_order_id": "35465278", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:50:16,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9f3f881b50582. +2023-09-18 22:50:16,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9f73fb79d0582 for 80.00000000 SEI-USDT. +2023-09-18 22:50:16,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9f73fb79d0582", "creation_timestamp": 1695077416.0, "exchange_order_id": "35465531", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:51:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9f73fb79d0582. [clock=2023-09-18 22:51:11+00:00] +2023-09-18 22:51:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 22:51:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:51:11,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077471.0, "order_id": "x-XEKWYICXBSIUT605a9f73fb79d0582", "exchange_order_id": "35465531", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:51:11,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9f73fb79d0582. +2023-09-18 22:51:11,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9fa86fa260582 for 80.00000000 SEI-USDT. +2023-09-18 22:51:11,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9fa86fa260582", "creation_timestamp": 1695077471.0, "exchange_order_id": "35465624", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:52:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9fa86fa260582. [clock=2023-09-18 22:52:06+00:00] +2023-09-18 22:52:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-18 22:52:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:52:06,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077526.0, "order_id": "x-XEKWYICXBSIUT605a9fa86fa260582", "exchange_order_id": "35465624", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:52:06,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9fa86fa260582. +2023-09-18 22:52:06,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605a9fdce2d780582 for 80.00000000 SEI-USDT. +2023-09-18 22:52:06,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605a9fdce2d780582", "creation_timestamp": 1695077526.0, "exchange_order_id": "35465720", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:53:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605a9fdce2d780582. [clock=2023-09-18 22:53:01+00:00] +2023-09-18 22:53:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-18 22:53:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:53:01,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077581.0, "order_id": "x-XEKWYICXBSIUT605a9fdce2d780582", "exchange_order_id": "35465720", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:53:01,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605a9fdce2d780582. +2023-09-18 22:53:01,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa011564990582 for 80.00000000 SEI-USDT. +2023-09-18 22:53:01,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605aa011564990582", "creation_timestamp": 1695077581.0, "exchange_order_id": "35465973", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:53:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa011564990582. [clock=2023-09-18 22:53:56+00:00] +2023-09-18 22:53:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-18 22:53:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:53:56,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077636.0, "order_id": "x-XEKWYICXBSIUT605aa011564990582", "exchange_order_id": "35465973", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:53:56,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa011564990582. +2023-09-18 22:53:56,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa045ca4900582 for 80.00000000 SEI-USDT. +2023-09-18 22:53:56,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605aa045ca4900582", "creation_timestamp": 1695077636.0, "exchange_order_id": "35466050", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:54:45,360 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aa045ca4900582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 22:54:45,361 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 22:54:45+00:00] +2023-09-18 22:54:45,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077685.0, "order_id": "x-XEKWYICXBSIUT605aa045ca4900582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4983294", "exchange_order_id": "35466050", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 22:54:45,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077685.0, "order_id": "x-XEKWYICXBSIUT605aa045ca4900582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35466050", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 22:54:45,392 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aa045ca4900582 completely filled. +2023-09-18 22:54:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247836311291702532759275572 amount: 80. +2023-09-18 22:54:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250064780719108112938837837 amount: 80. +2023-09-18 22:54:51,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa07a3dcab0582 for 80.00000000 SEI-USDT. +2023-09-18 22:54:51,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa07a3dcab0582", "creation_timestamp": 1695077691.0, "exchange_order_id": "35466176", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:54:51,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa07a3dfa20582 for 80.00000000 SEI-USDT. +2023-09-18 22:54:51,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605aa07a3dfa20582", "creation_timestamp": 1695077691.0, "exchange_order_id": "35466177", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:55:15,234 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 22:55:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa07a3dcab0582. [clock=2023-09-18 22:55:46+00:00] +2023-09-18 22:55:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa07a3dfa20582. [clock=2023-09-18 22:55:46+00:00] +2023-09-18 22:55:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247872972780547170248106901 amount: 80. +2023-09-18 22:55:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:55:46,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077746.0, "order_id": "x-XEKWYICXBSIUT605aa07a3dcab0582", "exchange_order_id": "35466176", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:55:46,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa07a3dcab0582. +2023-09-18 22:55:46,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa0aeb23550582 for 80.00000000 SEI-USDT. +2023-09-18 22:55:46,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa0aeb23550582", "creation_timestamp": 1695077746.0, "exchange_order_id": "35466262", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:55:46,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077746.0, "order_id": "x-XEKWYICXSSIUT605aa07a3dfa20582", "exchange_order_id": "35466177", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:55:46,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa07a3dfa20582. +2023-09-18 22:56:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa0aeb23550582. [clock=2023-09-18 22:56:41+00:00] +2023-09-18 22:56:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248739591050797476596046521 amount: 80. +2023-09-18 22:56:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251274959097685719769348250 amount: 80. +2023-09-18 22:56:41,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077801.0, "order_id": "x-XEKWYICXBSIUT605aa0aeb23550582", "exchange_order_id": "35466262", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:56:41,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa0aeb23550582. +2023-09-18 22:56:41,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa0e325b110582 for 80.00000000 SEI-USDT. +2023-09-18 22:56:41,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605aa0e325b110582", "creation_timestamp": 1695077801.0, "exchange_order_id": "35466482", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:56:41,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa0e3258840582 for 80.00000000 SEI-USDT. +2023-09-18 22:56:41,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605aa0e3258840582", "creation_timestamp": 1695077801.0, "exchange_order_id": "35466483", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:57:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa0e3258840582. [clock=2023-09-18 22:57:36+00:00] +2023-09-18 22:57:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa0e325b110582. [clock=2023-09-18 22:57:36+00:00] +2023-09-18 22:57:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248942761002137278923308991 amount: 80. +2023-09-18 22:57:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:57:36,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077856.0, "order_id": "x-XEKWYICXBSIUT605aa0e3258840582", "exchange_order_id": "35466483", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:57:36,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa0e3258840582. +2023-09-18 22:57:36,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077856.0, "order_id": "x-XEKWYICXSSIUT605aa0e325b110582", "exchange_order_id": "35466482", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:57:36,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa0e325b110582. +2023-09-18 22:57:36,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa117997570582 for 80.00000000 SEI-USDT. +2023-09-18 22:57:36,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605aa117997570582", "creation_timestamp": 1695077856.0, "exchange_order_id": "35466556", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:58:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa117997570582. [clock=2023-09-18 22:58:31+00:00] +2023-09-18 22:58:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247449145512229318625441234 amount: 80. +2023-09-18 22:58:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250168597377418437761373556 amount: 80. +2023-09-18 22:58:31,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077911.0, "order_id": "x-XEKWYICXBSIUT605aa117997570582", "exchange_order_id": "35466556", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:58:31,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa117997570582. +2023-09-18 22:58:31,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa14c0d1ee0582 for 80.00000000 SEI-USDT. +2023-09-18 22:58:31,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa14c0d1ee0582", "creation_timestamp": 1695077911.0, "exchange_order_id": "35466837", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:58:31,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa14c0d4700582 for 80.00000000 SEI-USDT. +2023-09-18 22:58:31,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605aa14c0d4700582", "creation_timestamp": 1695077911.0, "exchange_order_id": "35466838", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 22:59:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa14c0d1ee0582. [clock=2023-09-18 22:59:26+00:00] +2023-09-18 22:59:26,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa14c0d4700582. [clock=2023-09-18 22:59:26+00:00] +2023-09-18 22:59:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248782854367482275338702604 amount: 80. +2023-09-18 22:59:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 22:59:26,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077966.0, "order_id": "x-XEKWYICXBSIUT605aa14c0d1ee0582", "exchange_order_id": "35466837", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:59:26,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa14c0d1ee0582. +2023-09-18 22:59:26,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077966.0, "order_id": "x-XEKWYICXSSIUT605aa14c0d4700582", "exchange_order_id": "35466838", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 22:59:26,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa14c0d4700582. +2023-09-18 22:59:26,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa18080fd10582 for 80.00000000 SEI-USDT. +2023-09-18 22:59:26,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695077966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605aa18080fd10582", "creation_timestamp": 1695077966.0, "exchange_order_id": "35466944", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:00:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa18080fd10582. [clock=2023-09-18 23:00:21+00:00] +2023-09-18 23:00:21,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247831215943555618663755075 amount: 80. +2023-09-18 23:00:21,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249474794156510007729606754 amount: 80. +2023-09-18 23:00:21,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078021.0, "order_id": "x-XEKWYICXBSIUT605aa18080fd10582", "exchange_order_id": "35466944", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:00:21,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa18080fd10582. +2023-09-18 23:00:21,431 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa1b4f95f30582 for 80.00000000 SEI-USDT. +2023-09-18 23:00:21,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa1b4f95f30582", "creation_timestamp": 1695078021.0, "exchange_order_id": "35467111", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:00:21,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa1b4f99270582 for 80.00000000 SEI-USDT. +2023-09-18 23:00:21,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605aa1b4f99270582", "creation_timestamp": 1695078021.0, "exchange_order_id": "35467112", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:01:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa1b4f95f30582. [clock=2023-09-18 23:01:16+00:00] +2023-09-18 23:01:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa1b4f99270582. [clock=2023-09-18 23:01:16+00:00] +2023-09-18 23:01:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247443321013477613446519292 amount: 80. +2023-09-18 23:01:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:01:16,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078076.0, "order_id": "x-XEKWYICXBSIUT605aa1b4f95f30582", "exchange_order_id": "35467111", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:01:16,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa1b4f95f30582. +2023-09-18 23:01:16,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078076.0, "order_id": "x-XEKWYICXSSIUT605aa1b4f99270582", "exchange_order_id": "35467112", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:01:16,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa1b4f99270582. +2023-09-18 23:01:16,376 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa1e96867c0582 for 80.00000000 SEI-USDT. +2023-09-18 23:01:16,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa1e96867c0582", "creation_timestamp": 1695078076.0, "exchange_order_id": "35467329", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:02:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa1e96867c0582. [clock=2023-09-18 23:02:11+00:00] +2023-09-18 23:02:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247567173820605490913954622 amount: 80. +2023-09-18 23:02:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249484331497562084236905860 amount: 80. +2023-09-18 23:02:11,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078131.0, "order_id": "x-XEKWYICXBSIUT605aa1e96867c0582", "exchange_order_id": "35467329", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:02:11,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa1e96867c0582. +2023-09-18 23:02:11,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa21ddbfd30582 for 80.00000000 SEI-USDT. +2023-09-18 23:02:11,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa21ddbfd30582", "creation_timestamp": 1695078131.0, "exchange_order_id": "35467422", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:02:11,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa21ddc2350582 for 80.00000000 SEI-USDT. +2023-09-18 23:02:11,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605aa21ddc2350582", "creation_timestamp": 1695078131.0, "exchange_order_id": "35467423", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:03:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa21ddbfd30582. [clock=2023-09-18 23:03:06+00:00] +2023-09-18 23:03:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa21ddc2350582. [clock=2023-09-18 23:03:06+00:00] +2023-09-18 23:03:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247663445965555813544168384 amount: 80. +2023-09-18 23:03:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:03:06,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078186.0, "order_id": "x-XEKWYICXBSIUT605aa21ddbfd30582", "exchange_order_id": "35467422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:03:06,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa21ddbfd30582. +2023-09-18 23:03:06,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078186.0, "order_id": "x-XEKWYICXSSIUT605aa21ddc2350582", "exchange_order_id": "35467423", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:03:06,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa21ddc2350582. +2023-09-18 23:03:06,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa2524ffaa0582 for 80.00000000 SEI-USDT. +2023-09-18 23:03:06,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aa2524ffaa0582", "creation_timestamp": 1695078186.0, "exchange_order_id": "35467565", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:04:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa2524ffaa0582. [clock=2023-09-18 23:04:01+00:00] +2023-09-18 23:04:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245647801485854063811741184 amount: 80. +2023-09-18 23:04:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-18 23:04:01,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078241.0, "order_id": "x-XEKWYICXBSIUT605aa2524ffaa0582", "exchange_order_id": "35467565", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:04:01,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa2524ffaa0582. +2023-09-18 23:04:01,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa286c3b6f0582 for 80.00000000 SEI-USDT. +2023-09-18 23:04:01,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa286c3b6f0582", "creation_timestamp": 1695078241.0, "exchange_order_id": "35467785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:04:01,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa286c3eec0582 for 80.00000000 SEI-USDT. +2023-09-18 23:04:01,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605aa286c3eec0582", "creation_timestamp": 1695078241.0, "exchange_order_id": "35467786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:04:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa286c3b6f0582. [clock=2023-09-18 23:04:56+00:00] +2023-09-18 23:04:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa286c3eec0582. [clock=2023-09-18 23:04:56+00:00] +2023-09-18 23:04:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245948517926343998575564356 amount: 80. +2023-09-18 23:04:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:04:56,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078296.0, "order_id": "x-XEKWYICXBSIUT605aa286c3b6f0582", "exchange_order_id": "35467785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:04:56,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa286c3b6f0582. +2023-09-18 23:04:56,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078296.0, "order_id": "x-XEKWYICXSSIUT605aa286c3eec0582", "exchange_order_id": "35467786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:04:56,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa286c3eec0582. +2023-09-18 23:04:56,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa2bb3705e0582 for 80.00000000 SEI-USDT. +2023-09-18 23:04:56,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa2bb3705e0582", "creation_timestamp": 1695078296.0, "exchange_order_id": "35468118", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:05:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa2bb3705e0582. [clock=2023-09-18 23:05:51+00:00] +2023-09-18 23:05:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243765310935356562586544051 amount: 80. +2023-09-18 23:05:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-18 23:05:51,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078351.0, "order_id": "x-XEKWYICXBSIUT605aa2bb3705e0582", "exchange_order_id": "35468118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:05:51,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa2bb3705e0582. +2023-09-18 23:05:51,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa2efaab2f0582 for 80.00000000 SEI-USDT. +2023-09-18 23:05:51,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605aa2efaab2f0582", "creation_timestamp": 1695078351.0, "exchange_order_id": "35468481", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:05:51,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa2efaae4e0582 for 80.00000000 SEI-USDT. +2023-09-18 23:05:51,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605aa2efaae4e0582", "creation_timestamp": 1695078351.0, "exchange_order_id": "35468482", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:06:25,958 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aa2efaae4e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:06:25,959 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:06:25+00:00] +2023-09-18 23:06:25,979 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078385.0, "order_id": "x-XEKWYICXSSIUT605aa2efaae4e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "4983466", "exchange_order_id": "35468482", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:06:25,991 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078385.0, "order_id": "x-XEKWYICXSSIUT605aa2efaae4e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35468482", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:06:25,991 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aa2efaae4e0582 completely filled. +2023-09-18 23:06:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa2efaab2f0582. [clock=2023-09-18 23:06:46+00:00] +2023-09-18 23:06:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245196441284972558798561632 amount: 80. +2023-09-18 23:06:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:06:46,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078406.0, "order_id": "x-XEKWYICXBSIUT605aa2efaab2f0582", "exchange_order_id": "35468481", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:06:46,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa2efaab2f0582. +2023-09-18 23:06:46,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa3241e95b0582 for 80.00000000 SEI-USDT. +2023-09-18 23:06:46,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa3241e95b0582", "creation_timestamp": 1695078406.0, "exchange_order_id": "35468706", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:07:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa3241e95b0582. [clock=2023-09-18 23:07:41+00:00] +2023-09-18 23:07:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245375061437596164143138722 amount: 80. +2023-09-18 23:07:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:07:41,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078461.0, "order_id": "x-XEKWYICXBSIUT605aa3241e95b0582", "exchange_order_id": "35468706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:07:41,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa3241e95b0582. +2023-09-18 23:07:41,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa358925010582 for 80.00000000 SEI-USDT. +2023-09-18 23:07:41,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa358925010582", "creation_timestamp": 1695078461.0, "exchange_order_id": "35468767", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:08:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa358925010582. [clock=2023-09-18 23:08:36+00:00] +2023-09-18 23:08:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245513981279387594978442900 amount: 80. +2023-09-18 23:08:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:08:36,119 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078516.0, "order_id": "x-XEKWYICXBSIUT605aa358925010582", "exchange_order_id": "35468767", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:08:36,119 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa358925010582. +2023-09-18 23:08:36,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa38d066a30582 for 80.00000000 SEI-USDT. +2023-09-18 23:08:36,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa38d066a30582", "creation_timestamp": 1695078516.0, "exchange_order_id": "35468844", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:09:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa38d066a30582. [clock=2023-09-18 23:09:31+00:00] +2023-09-18 23:09:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245906381607155703306658118 amount: 80. +2023-09-18 23:09:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:09:31,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078571.0, "order_id": "x-XEKWYICXBSIUT605aa38d066a30582", "exchange_order_id": "35468844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:09:31,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa38d066a30582. +2023-09-18 23:09:31,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa3c179c920582 for 80.00000000 SEI-USDT. +2023-09-18 23:09:31,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa3c179c920582", "creation_timestamp": 1695078571.0, "exchange_order_id": "35469009", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:10:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa3c179c920582. [clock=2023-09-18 23:10:26+00:00] +2023-09-18 23:10:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245927248964682359251699746 amount: 80. +2023-09-18 23:10:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:10:26,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078626.0, "order_id": "x-XEKWYICXBSIUT605aa3c179c920582", "exchange_order_id": "35469009", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:10:26,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa3c179c920582. +2023-09-18 23:10:26,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa3f5ee3050582 for 80.00000000 SEI-USDT. +2023-09-18 23:10:26,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605aa3f5ee3050582", "creation_timestamp": 1695078626.0, "exchange_order_id": "35469085", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:11:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa3f5ee3050582. [clock=2023-09-18 23:11:21+00:00] +2023-09-18 23:11:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244704134050794873941336270 amount: 80. +2023-09-18 23:11:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:11:21,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078681.0, "order_id": "x-XEKWYICXBSIUT605aa3f5ee3050582", "exchange_order_id": "35469085", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:11:21,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa3f5ee3050582. +2023-09-18 23:11:21,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa42a613e90582 for 80.00000000 SEI-USDT. +2023-09-18 23:11:21,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605aa42a613e90582", "creation_timestamp": 1695078681.0, "exchange_order_id": "35469170", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:12:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa42a613e90582. [clock=2023-09-18 23:12:16+00:00] +2023-09-18 23:12:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242842344386859704474628120 amount: 80. +2023-09-18 23:12:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:12:16,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078736.0, "order_id": "x-XEKWYICXBSIUT605aa42a613e90582", "exchange_order_id": "35469170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:12:16,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa42a613e90582. +2023-09-18 23:12:16,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa45ed4dac0582 for 80.00000000 SEI-USDT. +2023-09-18 23:12:16,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605aa45ed4dac0582", "creation_timestamp": 1695078736.0, "exchange_order_id": "35469437", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:13:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa45ed4dac0582. [clock=2023-09-18 23:13:11+00:00] +2023-09-18 23:13:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243099786223504147734398155 amount: 80. +2023-09-18 23:13:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:13:11,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078791.0, "order_id": "x-XEKWYICXBSIUT605aa45ed4dac0582", "exchange_order_id": "35469437", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:13:11,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa45ed4dac0582. +2023-09-18 23:13:11,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa4934872c0582 for 80.00000000 SEI-USDT. +2023-09-18 23:13:11,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605aa4934872c0582", "creation_timestamp": 1695078791.0, "exchange_order_id": "35469533", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:13:45,141 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aa4934872c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:13:45,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:13:45+00:00] +2023-09-18 23:13:45,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078825.0, "order_id": "x-XEKWYICXBSIUT605aa4934872c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4983629", "exchange_order_id": "35469533", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:13:45,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078825.0, "order_id": "x-XEKWYICXBSIUT605aa4934872c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35469533", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:13:45,174 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aa4934872c0582 completely filled. +2023-09-18 23:14:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241420605775346142443818290 amount: 80. +2023-09-18 23:14:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12440000 amount: 80. +2023-09-18 23:14:06,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa4c7bc5190582 for 80.00000000 SEI-USDT. +2023-09-18 23:14:06,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605aa4c7bc5190582", "creation_timestamp": 1695078846.0, "exchange_order_id": "35469898", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:14:06,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa4c7bc24b0582 for 80.00000000 SEI-USDT. +2023-09-18 23:14:06,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605aa4c7bc24b0582", "creation_timestamp": 1695078846.0, "exchange_order_id": "35469899", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:14:53,868 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aa4c7bc5190582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:14:53,869 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:14:53+00:00] +2023-09-18 23:14:53,897 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078893.0, "order_id": "x-XEKWYICXSSIUT605aa4c7bc5190582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00995200"}]}, "exchange_trade_id": "4983678", "exchange_order_id": "35469898", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:14:53,914 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078893.0, "order_id": "x-XEKWYICXSSIUT605aa4c7bc5190582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35469898", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:14:53,915 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aa4c7bc5190582 completely filled. +2023-09-18 23:15:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa4c7bc24b0582. [clock=2023-09-18 23:15:01+00:00] +2023-09-18 23:15:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241679758146757239393706935 amount: 80. +2023-09-18 23:15:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:15:01,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078901.0, "order_id": "x-XEKWYICXBSIUT605aa4c7bc24b0582", "exchange_order_id": "35469899", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:15:01,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa4c7bc24b0582. +2023-09-18 23:15:01,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa4fc306f50582 for 80.00000000 SEI-USDT. +2023-09-18 23:15:01,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605aa4fc306f50582", "creation_timestamp": 1695078901.0, "exchange_order_id": "35470044", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:15:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa4fc306f50582. [clock=2023-09-18 23:15:56+00:00] +2023-09-18 23:15:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240273115644060155571640030 amount: 80. +2023-09-18 23:15:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:15:56,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078956.0, "order_id": "x-XEKWYICXBSIUT605aa4fc306f50582", "exchange_order_id": "35470044", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:15:56,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa4fc306f50582. +2023-09-18 23:15:56,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa530a40440582 for 80.00000000 SEI-USDT. +2023-09-18 23:15:56,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695078956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605aa530a40440582", "creation_timestamp": 1695078956.0, "exchange_order_id": "35470256", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:16:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa530a40440582. [clock=2023-09-18 23:16:51+00:00] +2023-09-18 23:16:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240657256207210614301486708 amount: 80. +2023-09-18 23:16:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:16:51,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079011.0, "order_id": "x-XEKWYICXBSIUT605aa530a40440582", "exchange_order_id": "35470256", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:16:51,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa530a40440582. +2023-09-18 23:16:51,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa565178da0582 for 80.00000000 SEI-USDT. +2023-09-18 23:16:51,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605aa565178da0582", "creation_timestamp": 1695079011.0, "exchange_order_id": "35470338", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:17:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa565178da0582. [clock=2023-09-18 23:17:46+00:00] +2023-09-18 23:17:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242411993508160946557742492 amount: 80. +2023-09-18 23:17:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:17:46,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079066.0, "order_id": "x-XEKWYICXBSIUT605aa565178da0582", "exchange_order_id": "35470338", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:17:46,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa565178da0582. +2023-09-18 23:17:46,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa5998b7960582 for 80.00000000 SEI-USDT. +2023-09-18 23:17:46,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605aa5998b7960582", "creation_timestamp": 1695079066.0, "exchange_order_id": "35470570", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:18:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa5998b7960582. [clock=2023-09-18 23:18:41+00:00] +2023-09-18 23:18:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241395663686330969688612485 amount: 80. +2023-09-18 23:18:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:18:41,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079121.0, "order_id": "x-XEKWYICXBSIUT605aa5998b7960582", "exchange_order_id": "35470570", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:18:41,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa5998b7960582. +2023-09-18 23:18:41,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa5cdff02b0582 for 80.00000000 SEI-USDT. +2023-09-18 23:18:41,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605aa5cdff02b0582", "creation_timestamp": 1695079121.0, "exchange_order_id": "35470745", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:19:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa5cdff02b0582. [clock=2023-09-18 23:19:36+00:00] +2023-09-18 23:19:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241530242572681008746898698 amount: 80. +2023-09-18 23:19:36,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:19:36,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079176.0, "order_id": "x-XEKWYICXBSIUT605aa5cdff02b0582", "exchange_order_id": "35470745", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:19:36,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa5cdff02b0582. +2023-09-18 23:19:36,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa602739600582 for 80.00000000 SEI-USDT. +2023-09-18 23:19:36,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605aa602739600582", "creation_timestamp": 1695079176.0, "exchange_order_id": "35470873", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:20:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa602739600582. [clock=2023-09-18 23:20:31+00:00] +2023-09-18 23:20:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241487338752475719638953710 amount: 80. +2023-09-18 23:20:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:20:31,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079231.0, "order_id": "x-XEKWYICXBSIUT605aa602739600582", "exchange_order_id": "35470873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:20:31,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa602739600582. +2023-09-18 23:20:31,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa636e6af80582 for 80.00000000 SEI-USDT. +2023-09-18 23:20:31,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605aa636e6af80582", "creation_timestamp": 1695079231.0, "exchange_order_id": "35471077", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:20:50,066 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aa636e6af80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:20:50,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:20:50+00:00] +2023-09-18 23:20:50,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079250.0, "order_id": "x-XEKWYICXBSIUT605aa636e6af80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4983760", "exchange_order_id": "35471077", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:20:50,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079250.0, "order_id": "x-XEKWYICXBSIUT605aa636e6af80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35471077", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:20:50,100 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aa636e6af80582 completely filled. +2023-09-18 23:21:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237477534977359545740824545 amount: 80. +2023-09-18 23:21:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241503555385615759724545514 amount: 80. +2023-09-18 23:21:26,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa66b5a31d0582 for 80.00000000 SEI-USDT. +2023-09-18 23:21:26,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aa66b5a31d0582", "creation_timestamp": 1695079286.0, "exchange_order_id": "35471626", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:21:26,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa66b5a6740582 for 80.00000000 SEI-USDT. +2023-09-18 23:21:26,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aa66b5a6740582", "creation_timestamp": 1695079286.0, "exchange_order_id": "35471627", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:22:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa66b5a31d0582. [clock=2023-09-18 23:22:21+00:00] +2023-09-18 23:22:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa66b5a6740582. [clock=2023-09-18 23:22:21+00:00] +2023-09-18 23:22:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238038915973708796471207798 amount: 80. +2023-09-18 23:22:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:22:21,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079341.0, "order_id": "x-XEKWYICXBSIUT605aa66b5a31d0582", "exchange_order_id": "35471626", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:22:21,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa66b5a31d0582. +2023-09-18 23:22:21,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079341.0, "order_id": "x-XEKWYICXSSIUT605aa66b5a6740582", "exchange_order_id": "35471627", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:22:21,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa66b5a6740582. +2023-09-18 23:22:21,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa69fce07d0582 for 80.00000000 SEI-USDT. +2023-09-18 23:22:21,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa69fce07d0582", "creation_timestamp": 1695079341.0, "exchange_order_id": "35471751", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:23:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa69fce07d0582. [clock=2023-09-18 23:23:16+00:00] +2023-09-18 23:23:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238474881868871079433705561 amount: 80. +2023-09-18 23:23:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12410000 amount: 80. +2023-09-18 23:23:16,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079396.0, "order_id": "x-XEKWYICXBSIUT605aa69fce07d0582", "exchange_order_id": "35471751", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:23:16,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa69fce07d0582. +2023-09-18 23:23:16,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa6d441ed60582 for 80.00000000 SEI-USDT. +2023-09-18 23:23:16,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aa6d441ed60582", "creation_timestamp": 1695079396.0, "exchange_order_id": "35471913", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:23:16,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa6d441bc90582 for 80.00000000 SEI-USDT. +2023-09-18 23:23:16,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa6d441bc90582", "creation_timestamp": 1695079396.0, "exchange_order_id": "35471912", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:24:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa6d441bc90582. [clock=2023-09-18 23:24:11+00:00] +2023-09-18 23:24:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa6d441ed60582. [clock=2023-09-18 23:24:11+00:00] +2023-09-18 23:24:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237814856122679092806556835 amount: 80. +2023-09-18 23:24:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:24:11,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079451.0, "order_id": "x-XEKWYICXBSIUT605aa6d441bc90582", "exchange_order_id": "35471912", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:24:11,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa6d441bc90582. +2023-09-18 23:24:11,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079451.0, "order_id": "x-XEKWYICXSSIUT605aa6d441ed60582", "exchange_order_id": "35471913", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:24:11,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa6d441ed60582. +2023-09-18 23:24:11,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa708b5b150582 for 80.00000000 SEI-USDT. +2023-09-18 23:24:11,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aa708b5b150582", "creation_timestamp": 1695079451.0, "exchange_order_id": "35472109", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:25:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa708b5b150582. [clock=2023-09-18 23:25:06+00:00] +2023-09-18 23:25:06,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236754103079222904681019754 amount: 80. +2023-09-18 23:25:06,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12390000 amount: 80. +2023-09-18 23:25:06,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079506.0, "order_id": "x-XEKWYICXBSIUT605aa708b5b150582", "exchange_order_id": "35472109", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:25:06,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa708b5b150582. +2023-09-18 23:25:06,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa73d2ef910582 for 80.00000000 SEI-USDT. +2023-09-18 23:25:06,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aa73d2ef910582", "creation_timestamp": 1695079506.0, "exchange_order_id": "35472389", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:25:06,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa73d2ec670582 for 80.00000000 SEI-USDT. +2023-09-18 23:25:06,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aa73d2ec670582", "creation_timestamp": 1695079506.0, "exchange_order_id": "35472388", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:25:15,258 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 23:26:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa73d2ec670582. [clock=2023-09-18 23:26:01+00:00] +2023-09-18 23:26:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa73d2ef910582. [clock=2023-09-18 23:26:01+00:00] +2023-09-18 23:26:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235501019774116760112332767 amount: 80. +2023-09-18 23:26:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:26:01,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079561.0, "order_id": "x-XEKWYICXBSIUT605aa73d2ec670582", "exchange_order_id": "35472388", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:26:01,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa73d2ec670582. +2023-09-18 23:26:01,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079561.0, "order_id": "x-XEKWYICXSSIUT605aa73d2ef910582", "exchange_order_id": "35472389", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:26:01,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa73d2ef910582. +2023-09-18 23:26:01,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa7719d4810582 for 80.00000000 SEI-USDT. +2023-09-18 23:26:01,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aa7719d4810582", "creation_timestamp": 1695079561.0, "exchange_order_id": "35472775", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:26:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa7719d4810582. [clock=2023-09-18 23:26:56+00:00] +2023-09-18 23:26:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236639148518760886869238817 amount: 80. +2023-09-18 23:26:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240409604190133610877424261 amount: 80. +2023-09-18 23:26:56,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079616.0, "order_id": "x-XEKWYICXBSIUT605aa7719d4810582", "exchange_order_id": "35472775", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:26:56,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa7719d4810582. +2023-09-18 23:26:56,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa7a61119c0582 for 80.00000000 SEI-USDT. +2023-09-18 23:26:56,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aa7a61119c0582", "creation_timestamp": 1695079616.0, "exchange_order_id": "35473242", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:26:56,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa7a610fb50582 for 80.00000000 SEI-USDT. +2023-09-18 23:26:56,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aa7a610fb50582", "creation_timestamp": 1695079616.0, "exchange_order_id": "35473243", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:27:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa7a610fb50582. [clock=2023-09-18 23:27:51+00:00] +2023-09-18 23:27:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa7a61119c0582. [clock=2023-09-18 23:27:51+00:00] +2023-09-18 23:27:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238143039802171750796358355 amount: 80. +2023-09-18 23:27:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:27:51,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079671.0, "order_id": "x-XEKWYICXBSIUT605aa7a610fb50582", "exchange_order_id": "35473243", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:27:51,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa7a610fb50582. +2023-09-18 23:27:51,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079671.0, "order_id": "x-XEKWYICXSSIUT605aa7a61119c0582", "exchange_order_id": "35473242", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:27:51,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa7a61119c0582. +2023-09-18 23:27:51,214 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa7da84ee90582 for 80.00000000 SEI-USDT. +2023-09-18 23:27:51,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa7da84ee90582", "creation_timestamp": 1695079671.0, "exchange_order_id": "35473331", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:28:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa7da84ee90582. [clock=2023-09-18 23:28:46+00:00] +2023-09-18 23:28:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238333787735959923252905374 amount: 80. +2023-09-18 23:28:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241133473569242196420576432 amount: 80. +2023-09-18 23:28:46,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079726.0, "order_id": "x-XEKWYICXBSIUT605aa7da84ee90582", "exchange_order_id": "35473331", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:28:46,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa7da84ee90582. +2023-09-18 23:28:46,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa80ef84e10582 for 80.00000000 SEI-USDT. +2023-09-18 23:28:46,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aa80ef84e10582", "creation_timestamp": 1695079726.0, "exchange_order_id": "35473676", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:28:46,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa80ef82a50582 for 80.00000000 SEI-USDT. +2023-09-18 23:28:46,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa80ef82a50582", "creation_timestamp": 1695079726.0, "exchange_order_id": "35473677", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:29:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa80ef82a50582. [clock=2023-09-18 23:29:41+00:00] +2023-09-18 23:29:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa80ef84e10582. [clock=2023-09-18 23:29:41+00:00] +2023-09-18 23:29:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238482023874987190026944703 amount: 80. +2023-09-18 23:29:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:29:41,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079781.0, "order_id": "x-XEKWYICXBSIUT605aa80ef82a50582", "exchange_order_id": "35473677", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:29:41,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa80ef82a50582. +2023-09-18 23:29:41,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079781.0, "order_id": "x-XEKWYICXSSIUT605aa80ef84e10582", "exchange_order_id": "35473676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:29:41,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa80ef84e10582. +2023-09-18 23:29:41,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa8436c08e0582 for 80.00000000 SEI-USDT. +2023-09-18 23:29:41,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa8436c08e0582", "creation_timestamp": 1695079781.0, "exchange_order_id": "35473870", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:30:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa8436c08e0582. [clock=2023-09-18 23:30:36+00:00] +2023-09-18 23:30:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238597244007639445344662732 amount: 80. +2023-09-18 23:30:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240290485258658342906713066 amount: 80. +2023-09-18 23:30:36,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079836.0, "order_id": "x-XEKWYICXBSIUT605aa8436c08e0582", "exchange_order_id": "35473870", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:30:36,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa8436c08e0582. +2023-09-18 23:30:36,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa877e03580582 for 80.00000000 SEI-USDT. +2023-09-18 23:30:36,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa877e03580582", "creation_timestamp": 1695079836.0, "exchange_order_id": "35474134", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:30:36,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa877e06000582 for 80.00000000 SEI-USDT. +2023-09-18 23:30:36,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aa877e06000582", "creation_timestamp": 1695079836.0, "exchange_order_id": "35474135", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:31:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa877e03580582. [clock=2023-09-18 23:31:31+00:00] +2023-09-18 23:31:31,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa877e06000582. [clock=2023-09-18 23:31:31+00:00] +2023-09-18 23:31:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238686814505928769192990882 amount: 80. +2023-09-18 23:31:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:31:31,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079891.0, "order_id": "x-XEKWYICXBSIUT605aa877e03580582", "exchange_order_id": "35474134", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:31:31,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa877e03580582. +2023-09-18 23:31:31,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079891.0, "order_id": "x-XEKWYICXSSIUT605aa877e06000582", "exchange_order_id": "35474135", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:31:31,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa877e06000582. +2023-09-18 23:31:31,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa8ac53ca50582 for 80.00000000 SEI-USDT. +2023-09-18 23:31:31,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa8ac53ca50582", "creation_timestamp": 1695079891.0, "exchange_order_id": "35474209", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:32:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa8ac53ca50582. [clock=2023-09-18 23:32:26+00:00] +2023-09-18 23:32:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238756453126683090692848575 amount: 80. +2023-09-18 23:32:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12400000 amount: 80. +2023-09-18 23:32:26,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079946.0, "order_id": "x-XEKWYICXBSIUT605aa8ac53ca50582", "exchange_order_id": "35474209", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:32:26,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa8ac53ca50582. +2023-09-18 23:32:26,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa8e0c78330582 for 80.00000000 SEI-USDT. +2023-09-18 23:32:26,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aa8e0c78330582", "creation_timestamp": 1695079946.0, "exchange_order_id": "35474278", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:32:26,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa8e0c74f10582 for 80.00000000 SEI-USDT. +2023-09-18 23:32:26,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695079946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa8e0c74f10582", "creation_timestamp": 1695079946.0, "exchange_order_id": "35474279", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:33:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa8e0c74f10582. [clock=2023-09-18 23:33:21+00:00] +2023-09-18 23:33:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa8e0c78330582. [clock=2023-09-18 23:33:21+00:00] +2023-09-18 23:33:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238084067961328321652165520 amount: 80. +2023-09-18 23:33:21,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:33:21,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080001.0, "order_id": "x-XEKWYICXBSIUT605aa8e0c74f10582", "exchange_order_id": "35474279", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:33:21,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa8e0c74f10582. +2023-09-18 23:33:21,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080001.0, "order_id": "x-XEKWYICXSSIUT605aa8e0c78330582", "exchange_order_id": "35474278", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:33:21,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa8e0c78330582. +2023-09-18 23:33:21,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa9153bb790582 for 80.00000000 SEI-USDT. +2023-09-18 23:33:21,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa9153bb790582", "creation_timestamp": 1695080001.0, "exchange_order_id": "35474355", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:34:16,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa9153bb790582. [clock=2023-09-18 23:34:16+00:00] +2023-09-18 23:34:16,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239729201136120545970039828 amount: 80. +2023-09-18 23:34:16,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12410000 amount: 80. +2023-09-18 23:34:16,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080056.0, "order_id": "x-XEKWYICXBSIUT605aa9153bb790582", "exchange_order_id": "35474355", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:34:16,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa9153bb790582. +2023-09-18 23:34:16,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa949b1bf70582 for 80.00000000 SEI-USDT. +2023-09-18 23:34:16,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605aa949b1bf70582", "creation_timestamp": 1695080056.0, "exchange_order_id": "35474543", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:34:16,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa949b1f580582 for 80.00000000 SEI-USDT. +2023-09-18 23:34:16,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aa949b1f580582", "creation_timestamp": 1695080056.0, "exchange_order_id": "35474542", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:35:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa949b1bf70582. [clock=2023-09-18 23:35:11+00:00] +2023-09-18 23:35:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa949b1f580582. [clock=2023-09-18 23:35:11+00:00] +2023-09-18 23:35:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239789331475971249091251641 amount: 80. +2023-09-18 23:35:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:35:11,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080111.0, "order_id": "x-XEKWYICXBSIUT605aa949b1bf70582", "exchange_order_id": "35474543", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:35:11,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa949b1bf70582. +2023-09-18 23:35:11,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa97e229a50582 for 80.00000000 SEI-USDT. +2023-09-18 23:35:11,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605aa97e229a50582", "creation_timestamp": 1695080111.0, "exchange_order_id": "35474622", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:35:11,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080111.0, "order_id": "x-XEKWYICXSSIUT605aa949b1f580582", "exchange_order_id": "35474542", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:35:11,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa949b1f580582. +2023-09-18 23:36:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa97e229a50582. [clock=2023-09-18 23:36:06+00:00] +2023-09-18 23:36:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238836297737322823707234078 amount: 80. +2023-09-18 23:36:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12400000 amount: 80. +2023-09-18 23:36:06,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080166.0, "order_id": "x-XEKWYICXBSIUT605aa97e229a50582", "exchange_order_id": "35474622", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:36:06,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa97e229a50582. +2023-09-18 23:36:06,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa9b2965960582 for 80.00000000 SEI-USDT. +2023-09-18 23:36:06,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aa9b2965960582", "creation_timestamp": 1695080166.0, "exchange_order_id": "35474819", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:36:06,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa9b2967e30582 for 80.00000000 SEI-USDT. +2023-09-18 23:36:06,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aa9b2967e30582", "creation_timestamp": 1695080166.0, "exchange_order_id": "35474820", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:36:46,719 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aa9b2965960582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:36:46,720 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:36:46+00:00] +2023-09-18 23:36:46,741 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080206.0, "order_id": "x-XEKWYICXBSIUT605aa9b2965960582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984001", "exchange_order_id": "35474819", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:36:46,752 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080206.0, "order_id": "x-XEKWYICXBSIUT605aa9b2965960582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35474819", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:36:46,753 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aa9b2965960582 completely filled. +2023-09-18 23:37:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aa9b2967e30582. [clock=2023-09-18 23:37:01+00:00] +2023-09-18 23:37:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235529677421568565934202262 amount: 80. +2023-09-18 23:37:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 23:37:01,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080221.0, "order_id": "x-XEKWYICXSSIUT605aa9b2967e30582", "exchange_order_id": "35474820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:37:01,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aa9b2967e30582. +2023-09-18 23:37:01,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aa9e70a0240582 for 80.00000000 SEI-USDT. +2023-09-18 23:37:01,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aa9e70a0240582", "creation_timestamp": 1695080221.0, "exchange_order_id": "35475256", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:37:01,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aa9e709de10582 for 80.00000000 SEI-USDT. +2023-09-18 23:37:01,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aa9e709de10582", "creation_timestamp": 1695080221.0, "exchange_order_id": "35475257", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:37:21,256 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aa9e70a0240582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:37:21,258 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:37:21+00:00] +2023-09-18 23:37:21,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080241.0, "order_id": "x-XEKWYICXSSIUT605aa9e70a0240582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984021", "exchange_order_id": "35475256", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:37:21,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080241.0, "order_id": "x-XEKWYICXSSIUT605aa9e70a0240582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35475256", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:37:21,309 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aa9e70a0240582 completely filled. +2023-09-18 23:37:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aa9e709de10582. [clock=2023-09-18 23:37:56+00:00] +2023-09-18 23:37:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236017051969729825491785591 amount: 80. +2023-09-18 23:37:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 23:37:56,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080276.0, "order_id": "x-XEKWYICXBSIUT605aa9e709de10582", "exchange_order_id": "35475257", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:37:56,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aa9e709de10582. +2023-09-18 23:37:56,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaa1b7dce10582 for 80.00000000 SEI-USDT. +2023-09-18 23:37:56,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aaa1b7dce10582", "creation_timestamp": 1695080276.0, "exchange_order_id": "35475385", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:37:56,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaa1b7dafc0582 for 80.00000000 SEI-USDT. +2023-09-18 23:37:56,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aaa1b7dafc0582", "creation_timestamp": 1695080276.0, "exchange_order_id": "35475386", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:38:01,271 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aaa1b7dce10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:38:01,272 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:38:01+00:00] +2023-09-18 23:38:01,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080281.0, "order_id": "x-XEKWYICXSSIUT605aaa1b7dce10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984031", "exchange_order_id": "35475385", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:38:01,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080281.0, "order_id": "x-XEKWYICXSSIUT605aaa1b7dce10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35475385", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:38:01,309 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aaa1b7dce10582 completely filled. +2023-09-18 23:38:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaa1b7dafc0582. [clock=2023-09-18 23:38:51+00:00] +2023-09-18 23:38:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237359633455444305886492419 amount: 80. +2023-09-18 23:38:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:38:51,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080331.0, "order_id": "x-XEKWYICXBSIUT605aaa1b7dafc0582", "exchange_order_id": "35475386", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:38:51,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaa1b7dafc0582. +2023-09-18 23:38:51,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaa4ff16160582 for 80.00000000 SEI-USDT. +2023-09-18 23:38:51,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aaa4ff16160582", "creation_timestamp": 1695080331.0, "exchange_order_id": "35475686", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:39:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaa4ff16160582. [clock=2023-09-18 23:39:46+00:00] +2023-09-18 23:39:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235803425621748622762947840 amount: 80. +2023-09-18 23:39:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:39:46,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080386.0, "order_id": "x-XEKWYICXBSIUT605aaa4ff16160582", "exchange_order_id": "35475686", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:39:46,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaa4ff16160582. +2023-09-18 23:39:46,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaa84650220582 for 80.00000000 SEI-USDT. +2023-09-18 23:39:46,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aaa84650220582", "creation_timestamp": 1695080386.0, "exchange_order_id": "35475805", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:40:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaa84650220582. [clock=2023-09-18 23:40:41+00:00] +2023-09-18 23:40:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237558986391687849494174583 amount: 80. +2023-09-18 23:40:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:40:41,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080441.0, "order_id": "x-XEKWYICXBSIUT605aaa84650220582", "exchange_order_id": "35475805", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:40:41,113 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaa84650220582. +2023-09-18 23:40:41,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaab8d8bbd0582 for 80.00000000 SEI-USDT. +2023-09-18 23:40:41,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aaab8d8bbd0582", "creation_timestamp": 1695080441.0, "exchange_order_id": "35475943", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:41:35,972 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aaab8d8bbd0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:41:35,973 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:41:35+00:00] +2023-09-18 23:41:35,994 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080495.0, "order_id": "x-XEKWYICXBSIUT605aaab8d8bbd0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984103", "exchange_order_id": "35475943", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:41:36,005 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080495.0, "order_id": "x-XEKWYICXBSIUT605aaab8d8bbd0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35475943", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:41:36,006 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aaab8d8bbd0582 completely filled. +2023-09-18 23:41:36,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237463387539344566031823221 amount: 80. +2023-09-18 23:41:36,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239364649155729205334800977 amount: 80. +2023-09-18 23:41:36,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaaed585720582 for 80.00000000 SEI-USDT. +2023-09-18 23:41:36,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aaaed585720582", "creation_timestamp": 1695080496.0, "exchange_order_id": "35476268", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:41:36,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaaed5888c0582 for 80.00000000 SEI-USDT. +2023-09-18 23:41:36,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aaaed5888c0582", "creation_timestamp": 1695080496.0, "exchange_order_id": "35476269", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:41:37,268 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aaaed585720582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:41:37,269 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:41:37+00:00] +2023-09-18 23:41:37,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080497.0, "order_id": "x-XEKWYICXBSIUT605aaaed585720582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984124", "exchange_order_id": "35476268", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:41:37,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080497.0, "order_id": "x-XEKWYICXBSIUT605aaaed585720582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35476268", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:41:37,323 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aaaed585720582 completely filled. +2023-09-18 23:42:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aaaed5888c0582. [clock=2023-09-18 23:42:31+00:00] +2023-09-18 23:42:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237232650025674816810719855 amount: 80. +2023-09-18 23:42:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239229806160480320940429891 amount: 80. +2023-09-18 23:42:31,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080551.0, "order_id": "x-XEKWYICXSSIUT605aaaed5888c0582", "exchange_order_id": "35476269", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:42:31,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aaaed5888c0582. +2023-09-18 23:42:31,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aab21c06e40582 for 80.00000000 SEI-USDT. +2023-09-18 23:42:31,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aab21c06e40582", "creation_timestamp": 1695080551.0, "exchange_order_id": "35476479", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:42:31,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aab21c04860582 for 80.00000000 SEI-USDT. +2023-09-18 23:42:31,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aab21c04860582", "creation_timestamp": 1695080551.0, "exchange_order_id": "35476480", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:43:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aab21c04860582. [clock=2023-09-18 23:43:26+00:00] +2023-09-18 23:43:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aab21c06e40582. [clock=2023-09-18 23:43:26+00:00] +2023-09-18 23:43:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237403228236444749590351794 amount: 80. +2023-09-18 23:43:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12390000 amount: 80. +2023-09-18 23:43:26,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080606.0, "order_id": "x-XEKWYICXBSIUT605aab21c04860582", "exchange_order_id": "35476480", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:43:26,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aab21c04860582. +2023-09-18 23:43:26,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aab56345da0582 for 80.00000000 SEI-USDT. +2023-09-18 23:43:26,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aab56345da0582", "creation_timestamp": 1695080606.0, "exchange_order_id": "35476672", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:43:26,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080606.0, "order_id": "x-XEKWYICXSSIUT605aab21c06e40582", "exchange_order_id": "35476479", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:43:26,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aab21c06e40582. +2023-09-18 23:43:26,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aab56348fc0582 for 80.00000000 SEI-USDT. +2023-09-18 23:43:26,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aab56348fc0582", "creation_timestamp": 1695080606.0, "exchange_order_id": "35476673", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:44:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aab56345da0582. [clock=2023-09-18 23:44:21+00:00] +2023-09-18 23:44:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aab56348fc0582. [clock=2023-09-18 23:44:21+00:00] +2023-09-18 23:44:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236337111355757900460426163 amount: 80. +2023-09-18 23:44:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238062444495504934248614697 amount: 80. +2023-09-18 23:44:21,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080661.0, "order_id": "x-XEKWYICXBSIUT605aab56345da0582", "exchange_order_id": "35476672", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:44:21,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aab56345da0582. +2023-09-18 23:44:21,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080661.0, "order_id": "x-XEKWYICXSSIUT605aab56348fc0582", "exchange_order_id": "35476673", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:44:21,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aab56348fc0582. +2023-09-18 23:44:21,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aab8aa7d5d0582 for 80.00000000 SEI-USDT. +2023-09-18 23:44:21,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aab8aa7d5d0582", "creation_timestamp": 1695080661.0, "exchange_order_id": "35476909", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:44:21,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aab8aa7f430582 for 80.00000000 SEI-USDT. +2023-09-18 23:44:21,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aab8aa7f430582", "creation_timestamp": 1695080661.0, "exchange_order_id": "35476910", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:45:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aab8aa7d5d0582. [clock=2023-09-18 23:45:16+00:00] +2023-09-18 23:45:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aab8aa7f430582. [clock=2023-09-18 23:45:16+00:00] +2023-09-18 23:45:16,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234647345744519671510476715 amount: 80. +2023-09-18 23:45:16,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12370000 amount: 80. +2023-09-18 23:45:16,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080716.0, "order_id": "x-XEKWYICXBSIUT605aab8aa7d5d0582", "exchange_order_id": "35476909", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:45:16,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aab8aa7d5d0582. +2023-09-18 23:45:16,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aabbf1c58e0582 for 80.00000000 SEI-USDT. +2023-09-18 23:45:16,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605aabbf1c58e0582", "creation_timestamp": 1695080716.0, "exchange_order_id": "35477096", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:45:16,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aabbf1c9170582 for 80.00000000 SEI-USDT. +2023-09-18 23:45:16,402 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605aabbf1c9170582", "creation_timestamp": 1695080716.0, "exchange_order_id": "35477097", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:45:16,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080716.0, "order_id": "x-XEKWYICXSSIUT605aab8aa7f430582", "exchange_order_id": "35476910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:45:16,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aab8aa7f430582. +2023-09-18 23:45:22,329 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aabbf1c9170582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:45:22,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:45:22+00:00] +2023-09-18 23:45:22,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080722.0, "order_id": "x-XEKWYICXSSIUT605aabbf1c9170582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00989600"}]}, "exchange_trade_id": "4984195", "exchange_order_id": "35477097", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:45:22,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080722.0, "order_id": "x-XEKWYICXSSIUT605aabbf1c9170582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35477097", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:45:22,370 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aabbf1c9170582 completely filled. +2023-09-18 23:46:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aabbf1c58e0582. [clock=2023-09-18 23:46:11+00:00] +2023-09-18 23:46:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236094825606601302251593486 amount: 80. +2023-09-18 23:46:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 23:46:11,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080771.0, "order_id": "x-XEKWYICXBSIUT605aabbf1c58e0582", "exchange_order_id": "35477096", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:46:11,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aabbf1c58e0582. +2023-09-18 23:46:11,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aabf38f3330582 for 80.00000000 SEI-USDT. +2023-09-18 23:46:11,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aabf38f3330582", "creation_timestamp": 1695080771.0, "exchange_order_id": "35477386", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:46:11,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aabf38f52d0582 for 80.00000000 SEI-USDT. +2023-09-18 23:46:11,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aabf38f52d0582", "creation_timestamp": 1695080771.0, "exchange_order_id": "35477387", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:47:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aabf38f3330582. [clock=2023-09-18 23:47:06+00:00] +2023-09-18 23:47:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aabf38f52d0582. [clock=2023-09-18 23:47:06+00:00] +2023-09-18 23:47:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236295912373969339875201554 amount: 80. +2023-09-18 23:47:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:47:06,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080826.0, "order_id": "x-XEKWYICXBSIUT605aabf38f3330582", "exchange_order_id": "35477386", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:47:06,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aabf38f3330582. +2023-09-18 23:47:06,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080826.0, "order_id": "x-XEKWYICXSSIUT605aabf38f52d0582", "exchange_order_id": "35477387", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:47:06,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aabf38f52d0582. +2023-09-18 23:47:06,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aac2802ade0582 for 80.00000000 SEI-USDT. +2023-09-18 23:47:06,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aac2802ade0582", "creation_timestamp": 1695080826.0, "exchange_order_id": "35477484", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:48:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aac2802ade0582. [clock=2023-09-18 23:48:01+00:00] +2023-09-18 23:48:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236295912373969339875201554 amount: 80. +2023-09-18 23:48:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-18 23:48:01,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080881.0, "order_id": "x-XEKWYICXBSIUT605aac2802ade0582", "exchange_order_id": "35477484", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:48:01,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aac2802ade0582. +2023-09-18 23:48:01,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aac5c765820582 for 80.00000000 SEI-USDT. +2023-09-18 23:48:01,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aac5c765820582", "creation_timestamp": 1695080881.0, "exchange_order_id": "35477563", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:48:01,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aac5c767dc0582 for 80.00000000 SEI-USDT. +2023-09-18 23:48:01,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aac5c767dc0582", "creation_timestamp": 1695080881.0, "exchange_order_id": "35477564", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:48:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aac5c765820582. [clock=2023-09-18 23:48:56+00:00] +2023-09-18 23:48:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aac5c767dc0582. [clock=2023-09-18 23:48:56+00:00] +2023-09-18 23:48:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235452841057542148368888580 amount: 80. +2023-09-18 23:48:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:48:56,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080936.0, "order_id": "x-XEKWYICXBSIUT605aac5c765820582", "exchange_order_id": "35477563", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:48:56,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aac5c765820582. +2023-09-18 23:48:56,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080936.0, "order_id": "x-XEKWYICXSSIUT605aac5c767dc0582", "exchange_order_id": "35477564", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:48:56,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aac5c767dc0582. +2023-09-18 23:48:56,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aac90ea9df0582 for 80.00000000 SEI-USDT. +2023-09-18 23:48:56,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aac90ea9df0582", "creation_timestamp": 1695080936.0, "exchange_order_id": "35477686", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:49:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aac90ea9df0582. [clock=2023-09-18 23:49:51+00:00] +2023-09-18 23:49:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236744759087594635690596557 amount: 80. +2023-09-18 23:49:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239277781799744395371006457 amount: 80. +2023-09-18 23:49:51,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080991.0, "order_id": "x-XEKWYICXBSIUT605aac90ea9df0582", "exchange_order_id": "35477686", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:49:51,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aac90ea9df0582. +2023-09-18 23:49:51,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aacc55df5b0582 for 80.00000000 SEI-USDT. +2023-09-18 23:49:51,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aacc55df5b0582", "creation_timestamp": 1695080991.0, "exchange_order_id": "35477840", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:49:51,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aacc55e1650582 for 80.00000000 SEI-USDT. +2023-09-18 23:49:51,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695080991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aacc55e1650582", "creation_timestamp": 1695080991.0, "exchange_order_id": "35477841", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:50:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aacc55df5b0582. [clock=2023-09-18 23:50:46+00:00] +2023-09-18 23:50:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aacc55e1650582. [clock=2023-09-18 23:50:46+00:00] +2023-09-18 23:50:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236459293800580150504061858 amount: 80. +2023-09-18 23:50:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:50:46,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081046.0, "order_id": "x-XEKWYICXBSIUT605aacc55df5b0582", "exchange_order_id": "35477840", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:50:46,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aacc55df5b0582. +2023-09-18 23:50:46,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aacf9d1ede0582 for 80.00000000 SEI-USDT. +2023-09-18 23:50:46,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aacf9d1ede0582", "creation_timestamp": 1695081046.0, "exchange_order_id": "35477917", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:50:46,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081046.0, "order_id": "x-XEKWYICXSSIUT605aacc55e1650582", "exchange_order_id": "35477841", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:50:46,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aacc55e1650582. +2023-09-18 23:51:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aacf9d1ede0582. [clock=2023-09-18 23:51:41+00:00] +2023-09-18 23:51:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236579623062089809436122332 amount: 80. +2023-09-18 23:51:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238631453936835330151220257 amount: 80. +2023-09-18 23:51:41,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081101.0, "order_id": "x-XEKWYICXBSIUT605aacf9d1ede0582", "exchange_order_id": "35477917", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:51:41,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aacf9d1ede0582. +2023-09-18 23:51:41,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aad2e456970582 for 80.00000000 SEI-USDT. +2023-09-18 23:51:41,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aad2e456970582", "creation_timestamp": 1695081101.0, "exchange_order_id": "35477996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:51:41,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aad2e459f00582 for 80.00000000 SEI-USDT. +2023-09-18 23:51:41,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aad2e459f00582", "creation_timestamp": 1695081101.0, "exchange_order_id": "35477997", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:52:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aad2e456970582. [clock=2023-09-18 23:52:36+00:00] +2023-09-18 23:52:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aad2e459f00582. [clock=2023-09-18 23:52:36+00:00] +2023-09-18 23:52:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235673408647248499438849408 amount: 80. +2023-09-18 23:52:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:52:36,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081156.0, "order_id": "x-XEKWYICXBSIUT605aad2e456970582", "exchange_order_id": "35477996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:52:36,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aad2e456970582. +2023-09-18 23:52:36,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081156.0, "order_id": "x-XEKWYICXSSIUT605aad2e459f00582", "exchange_order_id": "35477997", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:52:36,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aad2e459f00582. +2023-09-18 23:52:36,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aad62b9ad60582 for 80.00000000 SEI-USDT. +2023-09-18 23:52:36,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aad62b9ad60582", "creation_timestamp": 1695081156.0, "exchange_order_id": "35478152", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:53:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aad62b9ad60582. [clock=2023-09-18 23:53:31+00:00] +2023-09-18 23:53:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236600561405662150172908823 amount: 80. +2023-09-18 23:53:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239027967107825981844814822 amount: 80. +2023-09-18 23:53:31,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081211.0, "order_id": "x-XEKWYICXBSIUT605aad62b9ad60582", "exchange_order_id": "35478152", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:53:31,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aad62b9ad60582. +2023-09-18 23:53:31,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aad972ce530582 for 80.00000000 SEI-USDT. +2023-09-18 23:53:31,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aad972ce530582", "creation_timestamp": 1695081211.0, "exchange_order_id": "35478268", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:53:31,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aad972d0600582 for 80.00000000 SEI-USDT. +2023-09-18 23:53:31,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aad972d0600582", "creation_timestamp": 1695081211.0, "exchange_order_id": "35478269", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:54:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aad972ce530582. [clock=2023-09-18 23:54:26+00:00] +2023-09-18 23:54:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aad972d0600582. [clock=2023-09-18 23:54:26+00:00] +2023-09-18 23:54:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236689308674871022257090628 amount: 80. +2023-09-18 23:54:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:54:26,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081266.0, "order_id": "x-XEKWYICXBSIUT605aad972ce530582", "exchange_order_id": "35478268", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:54:26,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aad972ce530582. +2023-09-18 23:54:26,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081266.0, "order_id": "x-XEKWYICXSSIUT605aad972d0600582", "exchange_order_id": "35478269", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:54:26,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aad972d0600582. +2023-09-18 23:54:26,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aadcba10630582 for 80.00000000 SEI-USDT. +2023-09-18 23:54:26,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aadcba10630582", "creation_timestamp": 1695081266.0, "exchange_order_id": "35478361", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:55:15,277 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-18 23:55:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aadcba10630582. [clock=2023-09-18 23:55:21+00:00] +2023-09-18 23:55:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236758441952415306238840423 amount: 80. +2023-09-18 23:55:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238226490810547153305328103 amount: 80. +2023-09-18 23:55:21,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081321.0, "order_id": "x-XEKWYICXBSIUT605aadcba10630582", "exchange_order_id": "35478361", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:55:21,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aadcba10630582. +2023-09-18 23:55:21,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aae0014d600582 for 80.00000000 SEI-USDT. +2023-09-18 23:55:21,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aae0014d600582", "creation_timestamp": 1695081321.0, "exchange_order_id": "35478423", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:55:21,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aae0014ad90582 for 80.00000000 SEI-USDT. +2023-09-18 23:55:21,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aae0014ad90582", "creation_timestamp": 1695081321.0, "exchange_order_id": "35478424", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:56:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aae0014ad90582. [clock=2023-09-18 23:56:16+00:00] +2023-09-18 23:56:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aae0014d600582. [clock=2023-09-18 23:56:16+00:00] +2023-09-18 23:56:16,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236812176424588102125254033 amount: 80. +2023-09-18 23:56:16,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-18 23:56:16,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081376.0, "order_id": "x-XEKWYICXBSIUT605aae0014ad90582", "exchange_order_id": "35478424", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:56:16,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aae0014ad90582. +2023-09-18 23:56:16,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aae348966f0582 for 80.00000000 SEI-USDT. +2023-09-18 23:56:16,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aae348966f0582", "creation_timestamp": 1695081376.0, "exchange_order_id": "35478509", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:56:16,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081376.0, "order_id": "x-XEKWYICXSSIUT605aae0014d600582", "exchange_order_id": "35478423", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:56:16,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aae0014d600582. +2023-09-18 23:57:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aae348966f0582. [clock=2023-09-18 23:57:11+00:00] +2023-09-18 23:57:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237936366757159177545724585 amount: 80. +2023-09-18 23:57:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239343337607059831575521276 amount: 80. +2023-09-18 23:57:11,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081431.0, "order_id": "x-XEKWYICXBSIUT605aae348966f0582", "exchange_order_id": "35478509", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:57:11,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aae348966f0582. +2023-09-18 23:57:11,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aae68fbeff0582 for 80.00000000 SEI-USDT. +2023-09-18 23:57:11,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aae68fbeff0582", "creation_timestamp": 1695081431.0, "exchange_order_id": "35478707", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:57:11,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aae68fc2110582 for 80.00000000 SEI-USDT. +2023-09-18 23:57:11,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aae68fc2110582", "creation_timestamp": 1695081431.0, "exchange_order_id": "35478708", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:57:49,779 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aae68fbeff0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-18 23:57:49,780 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-18 23:57:49+00:00] +2023-09-18 23:57:49,801 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081469.0, "order_id": "x-XEKWYICXBSIUT605aae68fbeff0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984404", "exchange_order_id": "35478707", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-18 23:57:49,812 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081469.0, "order_id": "x-XEKWYICXBSIUT605aae68fbeff0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35478707", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-18 23:57:49,813 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aae68fbeff0582 completely filled. +2023-09-18 23:58:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aae68fc2110582. [clock=2023-09-18 23:58:06+00:00] +2023-09-18 23:58:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236436287021932897386765279 amount: 80. +2023-09-18 23:58:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238567934330781032377544973 amount: 80. +2023-09-18 23:58:06,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081486.0, "order_id": "x-XEKWYICXSSIUT605aae68fc2110582", "exchange_order_id": "35478708", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:58:06,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aae68fc2110582. +2023-09-18 23:58:06,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aae9d6fa2c0582 for 80.00000000 SEI-USDT. +2023-09-18 23:58:06,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aae9d6fa2c0582", "creation_timestamp": 1695081486.0, "exchange_order_id": "35478960", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:58:06,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aae9d6fc7e0582 for 80.00000000 SEI-USDT. +2023-09-18 23:58:06,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aae9d6fc7e0582", "creation_timestamp": 1695081486.0, "exchange_order_id": "35478961", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:59:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aae9d6fa2c0582. [clock=2023-09-18 23:59:01+00:00] +2023-09-18 23:59:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aae9d6fc7e0582. [clock=2023-09-18 23:59:01+00:00] +2023-09-18 23:59:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236436589148438824167672555 amount: 80. +2023-09-18 23:59:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238568236978160666967294936 amount: 80. +2023-09-18 23:59:01,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081541.0, "order_id": "x-XEKWYICXBSIUT605aae9d6fa2c0582", "exchange_order_id": "35478960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:59:01,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aae9d6fa2c0582. +2023-09-18 23:59:01,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaed1e3fe60582 for 80.00000000 SEI-USDT. +2023-09-18 23:59:01,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aaed1e3fe60582", "creation_timestamp": 1695081541.0, "exchange_order_id": "35479124", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:59:01,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081541.0, "order_id": "x-XEKWYICXSSIUT605aae9d6fc7e0582", "exchange_order_id": "35478961", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:59:01,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aae9d6fc7e0582. +2023-09-18 23:59:01,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaed1e3c950582 for 80.00000000 SEI-USDT. +2023-09-18 23:59:01,263 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aaed1e3c950582", "creation_timestamp": 1695081541.0, "exchange_order_id": "35479125", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:59:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaed1e3c950582. [clock=2023-09-18 23:59:56+00:00] +2023-09-18 23:59:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aaed1e3fe60582. [clock=2023-09-18 23:59:56+00:00] +2023-09-18 23:59:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236561872744113401586938617 amount: 80. +2023-09-18 23:59:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238219671484757571023258277 amount: 80. +2023-09-18 23:59:56,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081596.0, "order_id": "x-XEKWYICXBSIUT605aaed1e3c950582", "exchange_order_id": "35479125", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:59:56,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaed1e3c950582. +2023-09-18 23:59:56,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081596.0, "order_id": "x-XEKWYICXSSIUT605aaed1e3fe60582", "exchange_order_id": "35479124", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-18 23:59:56,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aaed1e3fe60582. +2023-09-18 23:59:56,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaf0656fda0582 for 80.00000000 SEI-USDT. +2023-09-18 23:59:56,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aaf0656fda0582", "creation_timestamp": 1695081596.0, "exchange_order_id": "35479219", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-18 23:59:56,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaf06572910582 for 80.00000000 SEI-USDT. +2023-09-18 23:59:56,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aaf06572910582", "creation_timestamp": 1695081596.0, "exchange_order_id": "35479218", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:00:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaf0656fda0582. [clock=2023-09-19 00:00:51+00:00] +2023-09-19 00:00:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aaf06572910582. [clock=2023-09-19 00:00:51+00:00] +2023-09-19 00:00:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236726277016607746133779451 amount: 80. +2023-09-19 00:00:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238681467716880733493461939 amount: 80. +2023-09-19 00:00:51,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081651.0, "order_id": "x-XEKWYICXBSIUT605aaf0656fda0582", "exchange_order_id": "35479219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:00:51,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaf0656fda0582. +2023-09-19 00:00:51,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081651.0, "order_id": "x-XEKWYICXSSIUT605aaf06572910582", "exchange_order_id": "35479218", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:00:51,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aaf06572910582. +2023-09-19 00:00:51,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaf3acb5be0582 for 80.00000000 SEI-USDT. +2023-09-19 00:00:51,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aaf3acb5be0582", "creation_timestamp": 1695081651.0, "exchange_order_id": "35479464", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:00:51,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaf3acb9430582 for 80.00000000 SEI-USDT. +2023-09-19 00:00:51,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aaf3acb9430582", "creation_timestamp": 1695081651.0, "exchange_order_id": "35479465", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:01:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaf3acb5be0582. [clock=2023-09-19 00:01:46+00:00] +2023-09-19 00:01:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aaf3acb9430582. [clock=2023-09-19 00:01:46+00:00] +2023-09-19 00:01:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236597902810227824418215459 amount: 80. +2023-09-19 00:01:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238119411545292470491224613 amount: 80. +2023-09-19 00:01:46,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081706.0, "order_id": "x-XEKWYICXBSIUT605aaf3acb5be0582", "exchange_order_id": "35479464", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:01:46,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaf3acb5be0582. +2023-09-19 00:01:46,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aaf6f3f02b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:01:46,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aaf6f3f02b0582", "creation_timestamp": 1695081706.0, "exchange_order_id": "35479625", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:01:46,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081706.0, "order_id": "x-XEKWYICXSSIUT605aaf3acb9430582", "exchange_order_id": "35479465", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:01:46,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aaf3acb9430582. +2023-09-19 00:01:46,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aaf6f3edf30582 for 80.00000000 SEI-USDT. +2023-09-19 00:01:46,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aaf6f3edf30582", "creation_timestamp": 1695081706.0, "exchange_order_id": "35479626", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:01:55,138 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aaf6f3f02b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:01:55,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:01:55+00:00] +2023-09-19 00:01:55,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081715.0, "order_id": "x-XEKWYICXSSIUT605aaf6f3f02b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984487", "exchange_order_id": "35479625", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:01:55,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081715.0, "order_id": "x-XEKWYICXSSIUT605aaf6f3f02b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35479625", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:01:55,189 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aaf6f3f02b0582 completely filled. +2023-09-19 00:02:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aaf6f3edf30582. [clock=2023-09-19 00:02:41+00:00] +2023-09-19 00:02:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236957862946091649439514262 amount: 80. +2023-09-19 00:02:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238659170947607461692540018 amount: 80. +2023-09-19 00:02:41,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081761.0, "order_id": "x-XEKWYICXBSIUT605aaf6f3edf30582", "exchange_order_id": "35479626", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:02:41,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aaf6f3edf30582. +2023-09-19 00:02:41,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aafa3b2a770582 for 80.00000000 SEI-USDT. +2023-09-19 00:02:41,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605aafa3b2a770582", "creation_timestamp": 1695081761.0, "exchange_order_id": "35479916", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:02:41,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aafa3b26cb0582 for 80.00000000 SEI-USDT. +2023-09-19 00:02:41,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aafa3b26cb0582", "creation_timestamp": 1695081761.0, "exchange_order_id": "35479917", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:02:43,163 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aafa3b2a770582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:02:43,164 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:02:43+00:00] +2023-09-19 00:02:43,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081763.0, "order_id": "x-XEKWYICXSSIUT605aafa3b2a770582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984499", "exchange_order_id": "35479916", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:02:43,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081763.0, "order_id": "x-XEKWYICXSSIUT605aafa3b2a770582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35479916", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:02:43,203 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aafa3b2a770582 completely filled. +2023-09-19 00:03:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aafa3b26cb0582. [clock=2023-09-19 00:03:36+00:00] +2023-09-19 00:03:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-19 00:03:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:03:36,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081816.0, "order_id": "x-XEKWYICXBSIUT605aafa3b26cb0582", "exchange_order_id": "35479917", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:03:36,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aafa3b26cb0582. +2023-09-19 00:03:36,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aafd8261cc0582 for 80.00000000 SEI-USDT. +2023-09-19 00:03:36,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aafd8261cc0582", "creation_timestamp": 1695081816.0, "exchange_order_id": "35480249", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:04:28,591 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aafd8261cc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:04:28,592 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:04:28+00:00] +2023-09-19 00:04:28,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081868.0, "order_id": "x-XEKWYICXBSIUT605aafd8261cc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984539", "exchange_order_id": "35480249", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:04:28,627 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081868.0, "order_id": "x-XEKWYICXBSIUT605aafd8261cc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35480249", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:04:28,628 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aafd8261cc0582 completely filled. +2023-09-19 00:04:31,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-19 00:04:31,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241044722044699664156871671 amount: 80. +2023-09-19 00:04:31,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab00ca30220582 for 80.00000000 SEI-USDT. +2023-09-19 00:04:31,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605ab00ca30220582", "creation_timestamp": 1695081871.0, "exchange_order_id": "35480607", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:04:31,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab00ca33400582 for 80.00000000 SEI-USDT. +2023-09-19 00:04:31,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605ab00ca33400582", "creation_timestamp": 1695081871.0, "exchange_order_id": "35480608", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:05:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab00ca30220582. [clock=2023-09-19 00:05:26+00:00] +2023-09-19 00:05:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab00ca33400582. [clock=2023-09-19 00:05:26+00:00] +2023-09-19 00:05:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-19 00:05:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:05:26,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081926.0, "order_id": "x-XEKWYICXBSIUT605ab00ca30220582", "exchange_order_id": "35480607", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:05:26,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab00ca30220582. +2023-09-19 00:05:26,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081926.0, "order_id": "x-XEKWYICXSSIUT605ab00ca33400582", "exchange_order_id": "35480608", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:05:26,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab00ca33400582. +2023-09-19 00:05:26,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab0410dc630582 for 80.00000000 SEI-USDT. +2023-09-19 00:05:26,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605ab0410dc630582", "creation_timestamp": 1695081926.0, "exchange_order_id": "35480717", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:06:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab0410dc630582. [clock=2023-09-19 00:06:21+00:00] +2023-09-19 00:06:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-19 00:06:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242270508105326933212224534 amount: 80. +2023-09-19 00:06:21,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081981.0, "order_id": "x-XEKWYICXBSIUT605ab0410dc630582", "exchange_order_id": "35480717", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:06:21,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab0410dc630582. +2023-09-19 00:06:21,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab075816330582 for 80.00000000 SEI-USDT. +2023-09-19 00:06:21,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605ab075816330582", "creation_timestamp": 1695081981.0, "exchange_order_id": "35480877", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:06:21,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab075819b50582 for 80.00000000 SEI-USDT. +2023-09-19 00:06:21,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695081981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605ab075819b50582", "creation_timestamp": 1695081981.0, "exchange_order_id": "35480879", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:07:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab075816330582. [clock=2023-09-19 00:07:16+00:00] +2023-09-19 00:07:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab075819b50582. [clock=2023-09-19 00:07:16+00:00] +2023-09-19 00:07:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-19 00:07:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:07:16,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082036.0, "order_id": "x-XEKWYICXBSIUT605ab075816330582", "exchange_order_id": "35480877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:07:16,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab075816330582. +2023-09-19 00:07:16,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab0a9f51b60582 for 80.00000000 SEI-USDT. +2023-09-19 00:07:16,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605ab0a9f51b60582", "creation_timestamp": 1695082036.0, "exchange_order_id": "35481007", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:07:16,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082036.0, "order_id": "x-XEKWYICXSSIUT605ab075819b50582", "exchange_order_id": "35480879", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:07:16,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab075819b50582. +2023-09-19 00:08:11,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab0a9f51b60582. [clock=2023-09-19 00:08:11+00:00] +2023-09-19 00:08:11,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-19 00:08:11,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243606539903546088149554228 amount: 80. +2023-09-19 00:08:11,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082091.0, "order_id": "x-XEKWYICXBSIUT605ab0a9f51b60582", "exchange_order_id": "35481007", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:08:11,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab0a9f51b60582. +2023-09-19 00:08:11,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab0de75bc30582 for 80.00000000 SEI-USDT. +2023-09-19 00:08:11,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605ab0de75bc30582", "creation_timestamp": 1695082091.0, "exchange_order_id": "35481175", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:08:11,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab0de75dc10582 for 80.00000000 SEI-USDT. +2023-09-19 00:08:11,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605ab0de75dc10582", "creation_timestamp": 1695082091.0, "exchange_order_id": "35481176", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:09:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab0de75bc30582. [clock=2023-09-19 00:09:06+00:00] +2023-09-19 00:09:06,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab0de75dc10582. [clock=2023-09-19 00:09:06+00:00] +2023-09-19 00:09:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-19 00:09:06,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:09:06,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082146.0, "order_id": "x-XEKWYICXBSIUT605ab0de75bc30582", "exchange_order_id": "35481175", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:09:06,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab0de75bc30582. +2023-09-19 00:09:06,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082146.0, "order_id": "x-XEKWYICXSSIUT605ab0de75dc10582", "exchange_order_id": "35481176", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:09:06,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab0de75dc10582. +2023-09-19 00:09:06,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab112dd1b60582 for 80.00000000 SEI-USDT. +2023-09-19 00:09:06,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605ab112dd1b60582", "creation_timestamp": 1695082146.0, "exchange_order_id": "35481258", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:09:06,887 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ab112dd1b60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:09:06,889 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:09:06+00:00] +2023-09-19 00:09:06,912 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082146.0, "order_id": "x-XEKWYICXBSIUT605ab112dd1b60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984614", "exchange_order_id": "35481258", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:09:06,993 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082146.0, "order_id": "x-XEKWYICXBSIUT605ab112dd1b60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35481258", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:09:06,994 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ab112dd1b60582 completely filled. +2023-09-19 00:10:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238798382477285047009284764 amount: 80. +2023-09-19 00:10:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241505442690256435682218084 amount: 80. +2023-09-19 00:10:01,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab1474ff990582 for 80.00000000 SEI-USDT. +2023-09-19 00:10:01,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605ab1474ff990582", "creation_timestamp": 1695082201.0, "exchange_order_id": "35481416", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:10:01,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab147504260582 for 80.00000000 SEI-USDT. +2023-09-19 00:10:01,571 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605ab147504260582", "creation_timestamp": 1695082201.0, "exchange_order_id": "35481417", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:10:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab1474ff990582. [clock=2023-09-19 00:10:56+00:00] +2023-09-19 00:10:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab147504260582. [clock=2023-09-19 00:10:56+00:00] +2023-09-19 00:10:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238283384543377409425625408 amount: 80. +2023-09-19 00:10:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241724737787654202554736070 amount: 80. +2023-09-19 00:10:56,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082256.0, "order_id": "x-XEKWYICXBSIUT605ab1474ff990582", "exchange_order_id": "35481416", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:10:56,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab1474ff990582. +2023-09-19 00:10:56,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab17bc4c340582 for 80.00000000 SEI-USDT. +2023-09-19 00:10:56,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605ab17bc4c340582", "creation_timestamp": 1695082256.0, "exchange_order_id": "35481630", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:10:56,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab17bc485c0582 for 80.00000000 SEI-USDT. +2023-09-19 00:10:56,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605ab17bc485c0582", "creation_timestamp": 1695082256.0, "exchange_order_id": "35481631", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:10:56,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082256.0, "order_id": "x-XEKWYICXSSIUT605ab147504260582", "exchange_order_id": "35481417", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:10:56,616 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab147504260582. +2023-09-19 00:11:51,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab17bc485c0582. [clock=2023-09-19 00:11:51+00:00] +2023-09-19 00:11:51,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab17bc4c340582. [clock=2023-09-19 00:11:51+00:00] +2023-09-19 00:11:51,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237443314182010621658552719 amount: 80. +2023-09-19 00:11:51,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240117280708367617261347293 amount: 80. +2023-09-19 00:11:51,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082311.0, "order_id": "x-XEKWYICXBSIUT605ab17bc485c0582", "exchange_order_id": "35481631", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:11:51,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab17bc485c0582. +2023-09-19 00:11:51,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082311.0, "order_id": "x-XEKWYICXSSIUT605ab17bc4c340582", "exchange_order_id": "35481630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:11:51,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab17bc4c340582. +2023-09-19 00:11:51,515 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab1b04b84f0582 for 80.00000000 SEI-USDT. +2023-09-19 00:11:51,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605ab1b04b84f0582", "creation_timestamp": 1695082311.0, "exchange_order_id": "35481855", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:11:51,530 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab1b04b56b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:11:51,541 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605ab1b04b56b0582", "creation_timestamp": 1695082311.0, "exchange_order_id": "35481856", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:12:32,257 - 1 - hummingbot.client.hummingbot_application - INFO - stop command initiated. +2023-09-19 00:12:32,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082352.0, "order_id": "x-XEKWYICXBSIUT605ab1b04b56b0582", "exchange_order_id": "35481856", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:12:32,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab1b04b56b0582. +2023-09-19 00:12:32,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082352.0, "order_id": "x-XEKWYICXSSIUT605ab1b04b84f0582", "exchange_order_id": "35481855", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:12:32,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab1b04b84f0582. +2023-09-19 00:12:42,476 - 1 - hummingbot.client.hummingbot_application - INFO - Creating the clock with tick size: 1.0 +2023-09-19 00:12:42,479 - 1 - hummingbot.client.hummingbot_application - INFO - start command initiated. +2023-09-19 00:12:42,605 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-19 00:12:42,704 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-19 00:12:42,843 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Successfully obtained listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO +2023-09-19 00:12:42,851 - 1 - hummingbot.core.data_type.order_book_tracker - INFO - Initialized order book for SEI-USDT. 1/1 completed. +2023-09-19 00:12:43,000 - 1 - hummingbot.strategy.script_strategy_base - WARNING - binance is not ready. Please wait... +2023-09-19 00:12:43,045 - 1 - hummingbot.data_feed.candles_feed.binance_spot_candles.binance_spot_candles - INFO - Subscribed to public klines... +2023-09-19 00:12:43,047 - 1 - hummingbot.connector.exchange.binance.binance_api_order_book_data_source.BinanceAPIOrderBookDataSource - INFO - Subscribed to public order book and trade channels... +2023-09-19 00:12:44,004 - 1 - hummingbot.core.rate_oracle.rate_oracle - INFO - Network status has changed to NetworkStatus.CONNECTED. Starting networking... +2023-09-19 00:13:01,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233370370491356578977796054 amount: 80. +2023-09-19 00:13:01,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12380000 amount: 80. +2023-09-19 00:13:01,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab1f2fd4e90582 for 80.00000000 SEI-USDT. +2023-09-19 00:13:01,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605ab1f2fd4e90582", "creation_timestamp": 1695082381.0, "exchange_order_id": "35482178", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:13:01,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab1f2fd7330582 for 80.00000000 SEI-USDT. +2023-09-19 00:13:01,374 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605ab1f2fd7330582", "creation_timestamp": 1695082381.0, "exchange_order_id": "35482179", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:13:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab1f2fd4e90582. [clock=2023-09-19 00:13:56+00:00] +2023-09-19 00:13:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab1f2fd7330582. [clock=2023-09-19 00:13:56+00:00] +2023-09-19 00:13:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231794403080211883951676015 amount: 80. +2023-09-19 00:13:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237571112475969841812818699 amount: 80. +2023-09-19 00:13:56,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "order_id": "x-XEKWYICXBSIUT605ab1f2fd4e90582", "exchange_order_id": "35482178", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:13:56,047 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab1f2fd4e90582. +2023-09-19 00:13:56,354 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ab1f2fd7330582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:13:56,355 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:13:56+00:00] +2023-09-19 00:13:56,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "order_id": "x-XEKWYICXSSIUT605ab1f2fd7330582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984713", "exchange_order_id": "35482179", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:13:56,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "order_id": "x-XEKWYICXSSIUT605ab1f2fd7330582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35482179", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:13:56,483 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ab1f2fd7330582 completely filled. +2023-09-19 00:13:56,690 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - WARNING - Failed to cancel order x-XEKWYICXSSIUT605ab1f2fd7330582 (order not found) +2023-09-19 00:13:56,771 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab2276c2ee0582 for 80.00000000 SEI-USDT. +2023-09-19 00:13:56,785 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ab2276c2ee0582", "creation_timestamp": 1695082436.0, "exchange_order_id": "35482431", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:13:56,786 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab2276c5fc0582 for 80.00000000 SEI-USDT. +2023-09-19 00:13:56,797 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ab2276c5fc0582", "creation_timestamp": 1695082436.0, "exchange_order_id": "35482432", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:13:56,798 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ab2276c5fc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:13:56,799 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:13:56+00:00] +2023-09-19 00:13:56,820 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "order_id": "x-XEKWYICXSSIUT605ab2276c5fc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "4984721", "exchange_order_id": "35482432", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:13:56,899 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082436.0, "order_id": "x-XEKWYICXSSIUT605ab2276c5fc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35482432", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:13:56,899 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ab2276c5fc0582 completely filled. +2023-09-19 00:14:51,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab2276c2ee0582. [clock=2023-09-19 00:14:51+00:00] +2023-09-19 00:14:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235862742048555717848461403 amount: 80. +2023-09-19 00:14:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:14:51,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082491.0, "order_id": "x-XEKWYICXBSIUT605ab2276c2ee0582", "exchange_order_id": "35482431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:14:51,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab2276c2ee0582. +2023-09-19 00:14:51,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab25be1dbc0582 for 80.00000000 SEI-USDT. +2023-09-19 00:14:51,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605ab25be1dbc0582", "creation_timestamp": 1695082491.0, "exchange_order_id": "35482674", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:15:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab25be1dbc0582. [clock=2023-09-19 00:15:46+00:00] +2023-09-19 00:15:46,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235117131320322609648104685 amount: 80. +2023-09-19 00:15:46,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:15:46,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082546.0, "order_id": "x-XEKWYICXBSIUT605ab25be1dbc0582", "exchange_order_id": "35482674", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:15:46,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab25be1dbc0582. +2023-09-19 00:15:46,398 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab290572bc0582 for 80.00000000 SEI-USDT. +2023-09-19 00:15:46,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605ab290572bc0582", "creation_timestamp": 1695082546.0, "exchange_order_id": "35482871", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:16:07,856 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ab290572bc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:16:07,857 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:16:07+00:00] +2023-09-19 00:16:07,878 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082567.0, "order_id": "x-XEKWYICXBSIUT605ab290572bc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12350000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4984772", "exchange_order_id": "35482871", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:16:07,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082567.0, "order_id": "x-XEKWYICXBSIUT605ab290572bc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35482871", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:16:07,891 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ab290572bc0582 completely filled. +2023-09-19 00:16:41,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231488496332409332490267724 amount: 80. +2023-09-19 00:16:41,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237100549630305121047709096 amount: 80. +2023-09-19 00:16:41,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab2c4d2b0f0582 for 80.00000000 SEI-USDT. +2023-09-19 00:16:41,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ab2c4d2b0f0582", "creation_timestamp": 1695082601.0, "exchange_order_id": "35483353", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:16:41,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab2c4d30790582 for 80.00000000 SEI-USDT. +2023-09-19 00:16:41,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ab2c4d30790582", "creation_timestamp": 1695082601.0, "exchange_order_id": "35483357", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:17:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab2c4d2b0f0582. [clock=2023-09-19 00:17:36+00:00] +2023-09-19 00:17:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab2c4d30790582. [clock=2023-09-19 00:17:36+00:00] +2023-09-19 00:17:36,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232269764273524255255599734 amount: 80. +2023-09-19 00:17:36,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:17:36,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082656.0, "order_id": "x-XEKWYICXBSIUT605ab2c4d2b0f0582", "exchange_order_id": "35483353", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:17:36,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab2c4d2b0f0582. +2023-09-19 00:17:36,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082656.0, "order_id": "x-XEKWYICXSSIUT605ab2c4d30790582", "exchange_order_id": "35483357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:17:36,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab2c4d30790582. +2023-09-19 00:17:36,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab2f93dc950582 for 80.00000000 SEI-USDT. +2023-09-19 00:17:36,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ab2f93dc950582", "creation_timestamp": 1695082656.0, "exchange_order_id": "35483622", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:18:31,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab2f93dc950582. [clock=2023-09-19 00:18:31+00:00] +2023-09-19 00:18:31,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228643821330479516333692547 amount: 80. +2023-09-19 00:18:31,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234008723770540341407840365 amount: 80. +2023-09-19 00:18:31,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082711.0, "order_id": "x-XEKWYICXBSIUT605ab2f93dc950582", "exchange_order_id": "35483622", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:18:31,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab2f93dc950582. +2023-09-19 00:18:31,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab32dba6a10582 for 80.00000000 SEI-USDT. +2023-09-19 00:18:31,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab32dba6a10582", "creation_timestamp": 1695082711.0, "exchange_order_id": "35484258", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:18:31,552 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab32dbaa2b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:18:31,564 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ab32dbaa2b0582", "creation_timestamp": 1695082711.0, "exchange_order_id": "35484259", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:19:26,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab32dba6a10582. [clock=2023-09-19 00:19:26+00:00] +2023-09-19 00:19:26,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab32dbaa2b0582. [clock=2023-09-19 00:19:26+00:00] +2023-09-19 00:19:26,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230389128410358956637907089 amount: 80. +2023-09-19 00:19:26,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:19:26,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082766.0, "order_id": "x-XEKWYICXBSIUT605ab32dba6a10582", "exchange_order_id": "35484258", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:19:26,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab32dba6a10582. +2023-09-19 00:19:26,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082766.0, "order_id": "x-XEKWYICXSSIUT605ab32dbaa2b0582", "exchange_order_id": "35484259", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:19:26,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab32dbaa2b0582. +2023-09-19 00:19:26,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab3624065d0582 for 80.00000000 SEI-USDT. +2023-09-19 00:19:26,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ab3624065d0582", "creation_timestamp": 1695082766.0, "exchange_order_id": "35484569", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:20:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab3624065d0582. [clock=2023-09-19 00:20:21+00:00] +2023-09-19 00:20:21,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228276786127993956167070337 amount: 80. +2023-09-19 00:20:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232632272969515338336703783 amount: 80. +2023-09-19 00:20:21,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082821.0, "order_id": "x-XEKWYICXBSIUT605ab3624065d0582", "exchange_order_id": "35484569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:20:21,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab3624065d0582. +2023-09-19 00:20:21,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab396957590582 for 80.00000000 SEI-USDT. +2023-09-19 00:20:21,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab396957590582", "creation_timestamp": 1695082821.0, "exchange_order_id": "35484865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:20:21,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab39695aa50582 for 80.00000000 SEI-USDT. +2023-09-19 00:20:21,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ab39695aa50582", "creation_timestamp": 1695082821.0, "exchange_order_id": "35484866", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:21:06,201 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ab396957590582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:21:06,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:21:06+00:00] +2023-09-19 00:21:06,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082866.0, "order_id": "x-XEKWYICXBSIUT605ab396957590582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12280000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4985069", "exchange_order_id": "35484865", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:21:06,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082866.0, "order_id": "x-XEKWYICXBSIUT605ab396957590582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35484865", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:21:06,254 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ab396957590582 completely filled. +2023-09-19 00:21:16,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab39695aa50582. [clock=2023-09-19 00:21:16+00:00] +2023-09-19 00:21:16,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220440777549327601577523487 amount: 80. +2023-09-19 00:21:16,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232202005419438142921808673 amount: 80. +2023-09-19 00:21:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082876.0, "order_id": "x-XEKWYICXSSIUT605ab39695aa50582", "exchange_order_id": "35484866", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:21:16,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab39695aa50582. +2023-09-19 00:21:16,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab3cb19c2d0582 for 80.00000000 SEI-USDT. +2023-09-19 00:21:16,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ab3cb19c2d0582", "creation_timestamp": 1695082876.0, "exchange_order_id": "35485821", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:21:16,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab3cb19f0f0582 for 80.00000000 SEI-USDT. +2023-09-19 00:21:16,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ab3cb19f0f0582", "creation_timestamp": 1695082876.0, "exchange_order_id": "35485820", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:22:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab3cb19c2d0582. [clock=2023-09-19 00:22:11+00:00] +2023-09-19 00:22:11,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab3cb19f0f0582. [clock=2023-09-19 00:22:11+00:00] +2023-09-19 00:22:11,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222351271918349559711951103 amount: 80. +2023-09-19 00:22:11,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231495945029491134269851357 amount: 80. +2023-09-19 00:22:11,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082931.0, "order_id": "x-XEKWYICXBSIUT605ab3cb19c2d0582", "exchange_order_id": "35485821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:22:11,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab3cb19c2d0582. +2023-09-19 00:22:11,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082931.0, "order_id": "x-XEKWYICXSSIUT605ab3cb19f0f0582", "exchange_order_id": "35485820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:22:11,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab3cb19f0f0582. +2023-09-19 00:22:11,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab3ff7d7590582 for 80.00000000 SEI-USDT. +2023-09-19 00:22:11,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ab3ff7d7590582", "creation_timestamp": 1695082931.0, "exchange_order_id": "35486484", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:22:11,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab3ff7da920582 for 80.00000000 SEI-USDT. +2023-09-19 00:22:11,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ab3ff7da920582", "creation_timestamp": 1695082931.0, "exchange_order_id": "35486483", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:23:06,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab3ff7d7590582. [clock=2023-09-19 00:23:06+00:00] +2023-09-19 00:23:06,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab3ff7da920582. [clock=2023-09-19 00:23:06+00:00] +2023-09-19 00:23:06,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1224824836162899051421569222 amount: 80. +2023-09-19 00:23:06,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231945833377567696487785856 amount: 80. +2023-09-19 00:23:06,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082986.0, "order_id": "x-XEKWYICXBSIUT605ab3ff7d7590582", "exchange_order_id": "35486484", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:23:06,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab3ff7d7590582. +2023-09-19 00:23:06,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082986.0, "order_id": "x-XEKWYICXSSIUT605ab3ff7da920582", "exchange_order_id": "35486483", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:23:06,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab3ff7da920582. +2023-09-19 00:23:06,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab433f86770582 for 80.00000000 SEI-USDT. +2023-09-19 00:23:06,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605ab433f86770582", "creation_timestamp": 1695082986.0, "exchange_order_id": "35486687", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:23:06,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab433f89830582 for 80.00000000 SEI-USDT. +2023-09-19 00:23:06,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ab433f89830582", "creation_timestamp": 1695082986.0, "exchange_order_id": "35486685", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:23:09,800 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ab433f89830582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:23:09,802 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:23:09+00:00] +2023-09-19 00:23:09,822 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082989.0, "order_id": "x-XEKWYICXSSIUT605ab433f89830582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00984800"}]}, "exchange_trade_id": "4985198", "exchange_order_id": "35486685", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:23:09,834 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695082989.0, "order_id": "x-XEKWYICXSSIUT605ab433f89830582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35486685", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:23:09,834 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ab433f89830582 completely filled. +2023-09-19 00:24:01,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab433f86770582. [clock=2023-09-19 00:24:01+00:00] +2023-09-19 00:24:01,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229268592020811864641767562 amount: 80. +2023-09-19 00:24:01,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238635992325430408348388952 amount: 80. +2023-09-19 00:24:01,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083041.0, "order_id": "x-XEKWYICXBSIUT605ab433f86770582", "exchange_order_id": "35486687", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:24:01,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab433f86770582. +2023-09-19 00:24:01,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab4686ac380582 for 80.00000000 SEI-USDT. +2023-09-19 00:24:01,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ab4686ac380582", "creation_timestamp": 1695083041.0, "exchange_order_id": "35487164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:24:01,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab4686af970582 for 80.00000000 SEI-USDT. +2023-09-19 00:24:01,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605ab4686af970582", "creation_timestamp": 1695083041.0, "exchange_order_id": "35487173", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:24:56,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab4686ac380582. [clock=2023-09-19 00:24:56+00:00] +2023-09-19 00:24:56,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab4686af970582. [clock=2023-09-19 00:24:56+00:00] +2023-09-19 00:24:56,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230222467013439978571808289 amount: 80. +2023-09-19 00:24:56,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:24:56,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083096.0, "order_id": "x-XEKWYICXBSIUT605ab4686ac380582", "exchange_order_id": "35487164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:24:56,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab4686ac380582. +2023-09-19 00:24:56,537 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083096.0, "order_id": "x-XEKWYICXSSIUT605ab4686af970582", "exchange_order_id": "35487173", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:24:56,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab4686af970582. +2023-09-19 00:24:56,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab49ce5d0a0582 for 80.00000000 SEI-USDT. +2023-09-19 00:24:56,549 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ab49ce5d0a0582", "creation_timestamp": 1695083096.0, "exchange_order_id": "35487850", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:25:51,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab49ce5d0a0582. [clock=2023-09-19 00:25:51+00:00] +2023-09-19 00:25:51,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226826696150921021323102505 amount: 80. +2023-09-19 00:25:51,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240425637360689323660155611 amount: 80. +2023-09-19 00:25:51,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083151.0, "order_id": "x-XEKWYICXBSIUT605ab49ce5d0a0582", "exchange_order_id": "35487850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:25:51,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab49ce5d0a0582. +2023-09-19 00:25:51,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab4d15b9e10582 for 80.00000000 SEI-USDT. +2023-09-19 00:25:51,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ab4d15b9e10582", "creation_timestamp": 1695083151.0, "exchange_order_id": "35488432", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:25:51,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab4d15bd3f0582 for 80.00000000 SEI-USDT. +2023-09-19 00:25:51,406 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605ab4d15bd3f0582", "creation_timestamp": 1695083151.0, "exchange_order_id": "35488433", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:26:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab4d15b9e10582. [clock=2023-09-19 00:26:46+00:00] +2023-09-19 00:26:46,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab4d15bd3f0582. [clock=2023-09-19 00:26:46+00:00] +2023-09-19 00:26:46,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227146968330509873409530291 amount: 80. +2023-09-19 00:26:46,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:26:46,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083206.0, "order_id": "x-XEKWYICXBSIUT605ab4d15b9e10582", "exchange_order_id": "35488432", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:26:46,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab4d15b9e10582. +2023-09-19 00:26:46,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083206.0, "order_id": "x-XEKWYICXSSIUT605ab4d15bd3f0582", "exchange_order_id": "35488433", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:26:46,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab4d15bd3f0582. +2023-09-19 00:26:46,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab505c577e0582 for 80.00000000 SEI-USDT. +2023-09-19 00:26:46,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ab505c577e0582", "creation_timestamp": 1695083206.0, "exchange_order_id": "35488769", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:27:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab505c577e0582. [clock=2023-09-19 00:27:41+00:00] +2023-09-19 00:27:41,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226221788294723359931997492 amount: 80. +2023-09-19 00:27:41,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235293910712791164726491984 amount: 80. +2023-09-19 00:27:41,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083261.0, "order_id": "x-XEKWYICXBSIUT605ab505c577e0582", "exchange_order_id": "35488769", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:27:41,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab505c577e0582. +2023-09-19 00:27:41,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab53a336850582 for 80.00000000 SEI-USDT. +2023-09-19 00:27:41,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ab53a336850582", "creation_timestamp": 1695083261.0, "exchange_order_id": "35489273", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:27:41,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab53a339b40582 for 80.00000000 SEI-USDT. +2023-09-19 00:27:41,352 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ab53a339b40582", "creation_timestamp": 1695083261.0, "exchange_order_id": "35489274", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:28:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab53a336850582. [clock=2023-09-19 00:28:36+00:00] +2023-09-19 00:28:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab53a339b40582. [clock=2023-09-19 00:28:36+00:00] +2023-09-19 00:28:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1223899118271577144620680856 amount: 80. +2023-09-19 00:28:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:28:36,038 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083316.0, "order_id": "x-XEKWYICXBSIUT605ab53a336850582", "exchange_order_id": "35489273", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:28:36,038 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab53a336850582. +2023-09-19 00:28:36,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083316.0, "order_id": "x-XEKWYICXSSIUT605ab53a339b40582", "exchange_order_id": "35489274", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:28:36,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab53a339b40582. +2023-09-19 00:28:36,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab56ea75830582 for 80.00000000 SEI-USDT. +2023-09-19 00:28:36,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ab56ea75830582", "creation_timestamp": 1695083316.0, "exchange_order_id": "35489618", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:29:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab56ea75830582. [clock=2023-09-19 00:29:31+00:00] +2023-09-19 00:29:31,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228378466003856538212957638 amount: 80. +2023-09-19 00:29:31,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235845207232819601957155689 amount: 80. +2023-09-19 00:29:31,117 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083371.0, "order_id": "x-XEKWYICXBSIUT605ab56ea75830582", "exchange_order_id": "35489618", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:29:31,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab56ea75830582. +2023-09-19 00:29:31,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab5a31aa5d0582 for 80.00000000 SEI-USDT. +2023-09-19 00:29:31,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab5a31aa5d0582", "creation_timestamp": 1695083371.0, "exchange_order_id": "35489875", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:29:31,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab5a31ac9b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:29:31,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ab5a31ac9b0582", "creation_timestamp": 1695083371.0, "exchange_order_id": "35489874", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:30:26,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab5a31aa5d0582. [clock=2023-09-19 00:30:26+00:00] +2023-09-19 00:30:26,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab5a31ac9b0582. [clock=2023-09-19 00:30:26+00:00] +2023-09-19 00:30:26,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226502254002129656838196316 amount: 80. +2023-09-19 00:30:26,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:30:26,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083426.0, "order_id": "x-XEKWYICXBSIUT605ab5a31aa5d0582", "exchange_order_id": "35489875", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:30:26,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab5a31aa5d0582. +2023-09-19 00:30:26,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083426.0, "order_id": "x-XEKWYICXSSIUT605ab5a31ac9b0582", "exchange_order_id": "35489874", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:30:26,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab5a31ac9b0582. +2023-09-19 00:30:26,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab5d796d430582 for 80.00000000 SEI-USDT. +2023-09-19 00:30:26,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ab5d796d430582", "creation_timestamp": 1695083426.0, "exchange_order_id": "35490112", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:31:21,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab5d796d430582. [clock=2023-09-19 00:31:21+00:00] +2023-09-19 00:31:21,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227059347512988160442913475 amount: 80. +2023-09-19 00:31:21,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233306591599252655580292467 amount: 80. +2023-09-19 00:31:21,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083481.0, "order_id": "x-XEKWYICXBSIUT605ab5d796d430582", "exchange_order_id": "35490112", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:31:21,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab5d796d430582. +2023-09-19 00:31:21,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab60c098690582 for 80.00000000 SEI-USDT. +2023-09-19 00:31:21,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ab60c098690582", "creation_timestamp": 1695083481.0, "exchange_order_id": "35490216", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:31:21,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab60c09c670582 for 80.00000000 SEI-USDT. +2023-09-19 00:31:21,448 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ab60c09c670582", "creation_timestamp": 1695083481.0, "exchange_order_id": "35490217", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:32:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab60c098690582. [clock=2023-09-19 00:32:16+00:00] +2023-09-19 00:32:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab60c09c670582. [clock=2023-09-19 00:32:16+00:00] +2023-09-19 00:32:16,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228677267674669786211528052 amount: 80. +2023-09-19 00:32:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:32:16,045 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083536.0, "order_id": "x-XEKWYICXBSIUT605ab60c098690582", "exchange_order_id": "35490216", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:32:16,045 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab60c098690582. +2023-09-19 00:32:16,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab6407648b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:32:16,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab6407648b0582", "creation_timestamp": 1695083536.0, "exchange_order_id": "35490398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:32:16,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083536.0, "order_id": "x-XEKWYICXSSIUT605ab60c09c670582", "exchange_order_id": "35490217", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:32:16,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab60c09c670582. +2023-09-19 00:33:11,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab6407648b0582. [clock=2023-09-19 00:33:11+00:00] +2023-09-19 00:33:11,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228971618156229849380840271 amount: 80. +2023-09-19 00:33:11,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233423665384833197076865785 amount: 80. +2023-09-19 00:33:11,073 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083591.0, "order_id": "x-XEKWYICXBSIUT605ab6407648b0582", "exchange_order_id": "35490398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:33:11,074 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab6407648b0582. +2023-09-19 00:33:11,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab674f25660582 for 80.00000000 SEI-USDT. +2023-09-19 00:33:11,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab674f25660582", "creation_timestamp": 1695083591.0, "exchange_order_id": "35490613", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:33:11,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab674f28fd0582 for 80.00000000 SEI-USDT. +2023-09-19 00:33:11,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ab674f28fd0582", "creation_timestamp": 1695083591.0, "exchange_order_id": "35490614", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:34:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab674f25660582. [clock=2023-09-19 00:34:06+00:00] +2023-09-19 00:34:06,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab674f28fd0582. [clock=2023-09-19 00:34:06+00:00] +2023-09-19 00:34:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229345875560693202707564948 amount: 80. +2023-09-19 00:34:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:34:06,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083646.0, "order_id": "x-XEKWYICXBSIUT605ab674f25660582", "exchange_order_id": "35490613", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:34:06,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab674f25660582. +2023-09-19 00:34:06,135 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083646.0, "order_id": "x-XEKWYICXSSIUT605ab674f28fd0582", "exchange_order_id": "35490614", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:34:06,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab674f28fd0582. +2023-09-19 00:34:06,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab6a95e0a90582 for 80.00000000 SEI-USDT. +2023-09-19 00:34:06,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ab6a95e0a90582", "creation_timestamp": 1695083646.0, "exchange_order_id": "35490704", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:35:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab6a95e0a90582. [clock=2023-09-19 00:35:01+00:00] +2023-09-19 00:35:01,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229176866434435430578005821 amount: 80. +2023-09-19 00:35:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234615466561829703936589880 amount: 80. +2023-09-19 00:35:01,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083701.0, "order_id": "x-XEKWYICXBSIUT605ab6a95e0a90582", "exchange_order_id": "35490704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:35:01,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab6a95e0a90582. +2023-09-19 00:35:01,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab6ddd16ad0582 for 80.00000000 SEI-USDT. +2023-09-19 00:35:01,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ab6ddd16ad0582", "creation_timestamp": 1695083701.0, "exchange_order_id": "35490985", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:35:01,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab6ddd18840582 for 80.00000000 SEI-USDT. +2023-09-19 00:35:01,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ab6ddd18840582", "creation_timestamp": 1695083701.0, "exchange_order_id": "35490986", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:35:56,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab6ddd16ad0582. [clock=2023-09-19 00:35:56+00:00] +2023-09-19 00:35:56,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab6ddd18840582. [clock=2023-09-19 00:35:56+00:00] +2023-09-19 00:35:56,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228573900618648635700466510 amount: 80. +2023-09-19 00:35:56,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:35:56,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083756.0, "order_id": "x-XEKWYICXBSIUT605ab6ddd16ad0582", "exchange_order_id": "35490985", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:35:56,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab6ddd16ad0582. +2023-09-19 00:35:56,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083756.0, "order_id": "x-XEKWYICXSSIUT605ab6ddd18840582", "exchange_order_id": "35490986", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:35:56,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab6ddd18840582. +2023-09-19 00:35:56,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab712557270582 for 80.00000000 SEI-USDT. +2023-09-19 00:35:56,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab712557270582", "creation_timestamp": 1695083756.0, "exchange_order_id": "35491188", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:36:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab712557270582. [clock=2023-09-19 00:36:51+00:00] +2023-09-19 00:36:51,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226291661002459042713281837 amount: 80. +2023-09-19 00:36:51,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232420566540983406310367994 amount: 80. +2023-09-19 00:36:51,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083811.0, "order_id": "x-XEKWYICXBSIUT605ab712557270582", "exchange_order_id": "35491188", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:36:51,047 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab712557270582. +2023-09-19 00:36:51,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab746b954d0582 for 80.00000000 SEI-USDT. +2023-09-19 00:36:51,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ab746b954d0582", "creation_timestamp": 1695083811.0, "exchange_order_id": "35491426", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:36:51,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab746b918c0582 for 80.00000000 SEI-USDT. +2023-09-19 00:36:51,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ab746b918c0582", "creation_timestamp": 1695083811.0, "exchange_order_id": "35491427", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:37:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab746b918c0582. [clock=2023-09-19 00:37:46+00:00] +2023-09-19 00:37:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab746b954d0582. [clock=2023-09-19 00:37:46+00:00] +2023-09-19 00:37:46,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227703071886051810294496747 amount: 80. +2023-09-19 00:37:46,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:37:46,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083866.0, "order_id": "x-XEKWYICXBSIUT605ab746b918c0582", "exchange_order_id": "35491427", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:37:46,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab746b918c0582. +2023-09-19 00:37:46,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083866.0, "order_id": "x-XEKWYICXSSIUT605ab746b954d0582", "exchange_order_id": "35491426", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:37:46,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab746b954d0582. +2023-09-19 00:37:46,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab77b306f90582 for 80.00000000 SEI-USDT. +2023-09-19 00:37:46,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ab77b306f90582", "creation_timestamp": 1695083866.0, "exchange_order_id": "35491792", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:38:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab77b306f90582. [clock=2023-09-19 00:38:41+00:00] +2023-09-19 00:38:41,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227499364869162541894792588 amount: 80. +2023-09-19 00:38:41,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234297281337685983611939564 amount: 80. +2023-09-19 00:38:41,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083921.0, "order_id": "x-XEKWYICXBSIUT605ab77b306f90582", "exchange_order_id": "35491792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:38:41,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab77b306f90582. +2023-09-19 00:38:41,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab7afa02330582 for 80.00000000 SEI-USDT. +2023-09-19 00:38:41,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ab7afa02330582", "creation_timestamp": 1695083921.0, "exchange_order_id": "35492097", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:38:41,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab7afa05910582 for 80.00000000 SEI-USDT. +2023-09-19 00:38:41,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ab7afa05910582", "creation_timestamp": 1695083921.0, "exchange_order_id": "35492098", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:39:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab7afa02330582. [clock=2023-09-19 00:39:36+00:00] +2023-09-19 00:39:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab7afa05910582. [clock=2023-09-19 00:39:36+00:00] +2023-09-19 00:39:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228833030080578553900700432 amount: 80. +2023-09-19 00:39:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:39:36,038 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083976.0, "order_id": "x-XEKWYICXBSIUT605ab7afa02330582", "exchange_order_id": "35492097", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:39:36,038 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab7afa02330582. +2023-09-19 00:39:36,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083976.0, "order_id": "x-XEKWYICXSSIUT605ab7afa05910582", "exchange_order_id": "35492098", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:39:36,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab7afa05910582. +2023-09-19 00:39:36,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab7e4142140582 for 80.00000000 SEI-USDT. +2023-09-19 00:39:36,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695083976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab7e4142140582", "creation_timestamp": 1695083976.0, "exchange_order_id": "35492243", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:40:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab7e4142140582. [clock=2023-09-19 00:40:31+00:00] +2023-09-19 00:40:31,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228093421842870438923549227 amount: 80. +2023-09-19 00:40:31,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232203236019240561687503669 amount: 80. +2023-09-19 00:40:31,063 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084031.0, "order_id": "x-XEKWYICXBSIUT605ab7e4142140582", "exchange_order_id": "35492243", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:40:31,064 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab7e4142140582. +2023-09-19 00:40:31,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab8188d51b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:40:31,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ab8188d51b0582", "creation_timestamp": 1695084031.0, "exchange_order_id": "35492343", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:40:31,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab8188d2c10582 for 80.00000000 SEI-USDT. +2023-09-19 00:40:31,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab8188d2c10582", "creation_timestamp": 1695084031.0, "exchange_order_id": "35492344", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:41:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab8188d2c10582. [clock=2023-09-19 00:41:26+00:00] +2023-09-19 00:41:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab8188d51b0582. [clock=2023-09-19 00:41:26+00:00] +2023-09-19 00:41:26,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226740314212907268233645594 amount: 80. +2023-09-19 00:41:26,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:41:26,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084086.0, "order_id": "x-XEKWYICXBSIUT605ab8188d2c10582", "exchange_order_id": "35492344", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:41:26,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab8188d2c10582. +2023-09-19 00:41:26,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084086.0, "order_id": "x-XEKWYICXSSIUT605ab8188d51b0582", "exchange_order_id": "35492343", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:41:26,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab8188d51b0582. +2023-09-19 00:41:26,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab84cfff7b0582 for 80.00000000 SEI-USDT. +2023-09-19 00:41:26,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ab84cfff7b0582", "creation_timestamp": 1695084086.0, "exchange_order_id": "35492529", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:42:21,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab84cfff7b0582. [clock=2023-09-19 00:42:21+00:00] +2023-09-19 00:42:21,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228020107182504341574624451 amount: 80. +2023-09-19 00:42:21,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231179686417297001699879049 amount: 80. +2023-09-19 00:42:21,114 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084141.0, "order_id": "x-XEKWYICXBSIUT605ab84cfff7b0582", "exchange_order_id": "35492529", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:42:21,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab84cfff7b0582. +2023-09-19 00:42:21,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab881815760582 for 80.00000000 SEI-USDT. +2023-09-19 00:42:21,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab881815760582", "creation_timestamp": 1695084141.0, "exchange_order_id": "35492704", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:42:21,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab881818760582 for 80.00000000 SEI-USDT. +2023-09-19 00:42:21,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ab881818760582", "creation_timestamp": 1695084141.0, "exchange_order_id": "35492705", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:42:42,861 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 00:43:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab881815760582. [clock=2023-09-19 00:43:16+00:00] +2023-09-19 00:43:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab881818760582. [clock=2023-09-19 00:43:16+00:00] +2023-09-19 00:43:16,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228237875399363980926522830 amount: 80. +2023-09-19 00:43:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:43:16,039 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084196.0, "order_id": "x-XEKWYICXBSIUT605ab881815760582", "exchange_order_id": "35492704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:43:16,040 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab881815760582. +2023-09-19 00:43:16,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084196.0, "order_id": "x-XEKWYICXSSIUT605ab881818760582", "exchange_order_id": "35492705", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:43:16,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab881818760582. +2023-09-19 00:43:16,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab8b5e30550582 for 80.00000000 SEI-USDT. +2023-09-19 00:43:16,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ab8b5e30550582", "creation_timestamp": 1695084196.0, "exchange_order_id": "35492878", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:44:09,010 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ab8b5e30550582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:44:09,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:44:08+00:00] +2023-09-19 00:44:09,035 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084248.0, "order_id": "x-XEKWYICXBSIUT605ab8b5e30550582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12280000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4985802", "exchange_order_id": "35492878", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:44:09,046 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084249.0, "order_id": "x-XEKWYICXBSIUT605ab8b5e30550582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35492878", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:44:09,047 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ab8b5e30550582 completely filled. +2023-09-19 00:44:11,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225215621628835764704284923 amount: 80. +2023-09-19 00:44:11,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227121638300634763595338341 amount: 80. +2023-09-19 00:44:11,100 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab8ea5634c0582 for 80.00000000 SEI-USDT. +2023-09-19 00:44:11,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ab8ea5634c0582", "creation_timestamp": 1695084251.0, "exchange_order_id": "35493357", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:44:11,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab8ea566680582 for 80.00000000 SEI-USDT. +2023-09-19 00:44:11,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ab8ea566680582", "creation_timestamp": 1695084251.0, "exchange_order_id": "35493358", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:44:54,736 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ab8ea566680582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 00:44:54,737 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 00:44:54+00:00] +2023-09-19 00:44:54,761 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084294.0, "order_id": "x-XEKWYICXSSIUT605ab8ea566680582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12270000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00981600"}]}, "exchange_trade_id": "4985863", "exchange_order_id": "35493358", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 00:44:54,773 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084294.0, "order_id": "x-XEKWYICXSSIUT605ab8ea566680582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35493358", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 00:44:54,774 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ab8ea566680582 completely filled. +2023-09-19 00:45:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab8ea5634c0582. [clock=2023-09-19 00:45:06+00:00] +2023-09-19 00:45:06,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1223456520538026391860358219 amount: 80. +2023-09-19 00:45:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227529985378225017434401299 amount: 80. +2023-09-19 00:45:06,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084306.0, "order_id": "x-XEKWYICXBSIUT605ab8ea5634c0582", "exchange_order_id": "35493357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:45:06,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab8ea5634c0582. +2023-09-19 00:45:06,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab91ecaad00582 for 80.00000000 SEI-USDT. +2023-09-19 00:45:06,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ab91ecaad00582", "creation_timestamp": 1695084306.0, "exchange_order_id": "35493728", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:45:06,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab91ecad110582 for 80.00000000 SEI-USDT. +2023-09-19 00:45:06,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ab91ecad110582", "creation_timestamp": 1695084306.0, "exchange_order_id": "35493727", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:46:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab91ecaad00582. [clock=2023-09-19 00:46:01+00:00] +2023-09-19 00:46:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab91ecad110582. [clock=2023-09-19 00:46:01+00:00] +2023-09-19 00:46:01,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218676586483533934278660751 amount: 80. +2023-09-19 00:46:01,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:46:01,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084361.0, "order_id": "x-XEKWYICXBSIUT605ab91ecaad00582", "exchange_order_id": "35493728", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:46:01,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab91ecaad00582. +2023-09-19 00:46:01,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084361.0, "order_id": "x-XEKWYICXSSIUT605ab91ecad110582", "exchange_order_id": "35493727", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:46:01,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab91ecad110582. +2023-09-19 00:46:01,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab9533e1e90582 for 80.00000000 SEI-USDT. +2023-09-19 00:46:01,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ab9533e1e90582", "creation_timestamp": 1695084361.0, "exchange_order_id": "35494529", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:46:56,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab9533e1e90582. [clock=2023-09-19 00:46:56+00:00] +2023-09-19 00:46:56,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216477338198806582066136832 amount: 80. +2023-09-19 00:46:56,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225313264113525779891695516 amount: 80. +2023-09-19 00:46:56,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084416.0, "order_id": "x-XEKWYICXBSIUT605ab9533e1e90582", "exchange_order_id": "35494529", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:46:56,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab9533e1e90582. +2023-09-19 00:46:56,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab987b89e10582 for 80.00000000 SEI-USDT. +2023-09-19 00:46:56,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ab987b89e10582", "creation_timestamp": 1695084416.0, "exchange_order_id": "35495035", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:46:56,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab987b85e80582 for 80.00000000 SEI-USDT. +2023-09-19 00:46:56,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605ab987b85e80582", "creation_timestamp": 1695084416.0, "exchange_order_id": "35495036", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:47:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab987b85e80582. [clock=2023-09-19 00:47:51+00:00] +2023-09-19 00:47:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab987b89e10582. [clock=2023-09-19 00:47:51+00:00] +2023-09-19 00:47:51,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1214319399871744713107498966 amount: 80. +2023-09-19 00:47:51,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:47:51,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084471.0, "order_id": "x-XEKWYICXBSIUT605ab987b85e80582", "exchange_order_id": "35495036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:47:51,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab987b85e80582. +2023-09-19 00:47:51,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab9bc259ab0582 for 80.00000000 SEI-USDT. +2023-09-19 00:47:51,352 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12140000", "order_id": "x-XEKWYICXBSIUT605ab9bc259ab0582", "creation_timestamp": 1695084471.0, "exchange_order_id": "35495526", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:47:51,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084471.0, "order_id": "x-XEKWYICXSSIUT605ab987b89e10582", "exchange_order_id": "35495035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:47:51,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab987b89e10582. +2023-09-19 00:48:46,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab9bc259ab0582. [clock=2023-09-19 00:48:46+00:00] +2023-09-19 00:48:46,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1213802584536834026631711824 amount: 80. +2023-09-19 00:48:46,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225316227965929402521861734 amount: 80. +2023-09-19 00:48:46,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084526.0, "order_id": "x-XEKWYICXBSIUT605ab9bc259ab0582", "exchange_order_id": "35495526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:48:46,084 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab9bc259ab0582. +2023-09-19 00:48:46,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ab9f0a439c0582 for 80.00000000 SEI-USDT. +2023-09-19 00:48:46,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12130000", "order_id": "x-XEKWYICXBSIUT605ab9f0a439c0582", "creation_timestamp": 1695084526.0, "exchange_order_id": "35496806", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:48:46,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ab9f0a46320582 for 80.00000000 SEI-USDT. +2023-09-19 00:48:46,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ab9f0a46320582", "creation_timestamp": 1695084526.0, "exchange_order_id": "35496807", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:49:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ab9f0a439c0582. [clock=2023-09-19 00:49:41+00:00] +2023-09-19 00:49:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ab9f0a46320582. [clock=2023-09-19 00:49:41+00:00] +2023-09-19 00:49:41,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1215019633024990119212865901 amount: 80. +2023-09-19 00:49:41,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:49:41,125 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084581.0, "order_id": "x-XEKWYICXBSIUT605ab9f0a439c0582", "exchange_order_id": "35496806", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:49:41,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ab9f0a439c0582. +2023-09-19 00:49:41,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084581.0, "order_id": "x-XEKWYICXSSIUT605ab9f0a46320582", "exchange_order_id": "35496807", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:49:41,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ab9f0a46320582. +2023-09-19 00:49:41,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aba250d0670582 for 80.00000000 SEI-USDT. +2023-09-19 00:49:41,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12150000", "order_id": "x-XEKWYICXBSIUT605aba250d0670582", "creation_timestamp": 1695084581.0, "exchange_order_id": "35497099", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:50:36,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aba250d0670582. [clock=2023-09-19 00:50:36+00:00] +2023-09-19 00:50:36,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220119770779859950560680383 amount: 80. +2023-09-19 00:50:36,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232175888696665094988943113 amount: 80. +2023-09-19 00:50:36,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084636.0, "order_id": "x-XEKWYICXBSIUT605aba250d0670582", "exchange_order_id": "35497099", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:50:36,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aba250d0670582. +2023-09-19 00:50:36,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aba598b4050582 for 80.00000000 SEI-USDT. +2023-09-19 00:50:36,391 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605aba598b4050582", "creation_timestamp": 1695084636.0, "exchange_order_id": "35497593", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:50:36,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aba598b7220582 for 80.00000000 SEI-USDT. +2023-09-19 00:50:36,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605aba598b7220582", "creation_timestamp": 1695084636.0, "exchange_order_id": "35497594", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:51:31,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aba598b4050582. [clock=2023-09-19 00:51:31+00:00] +2023-09-19 00:51:31,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aba598b7220582. [clock=2023-09-19 00:51:31+00:00] +2023-09-19 00:51:31,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220762550128594815570843948 amount: 80. +2023-09-19 00:51:31,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:51:31,111 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084691.0, "order_id": "x-XEKWYICXBSIUT605aba598b4050582", "exchange_order_id": "35497593", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:51:31,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aba598b4050582. +2023-09-19 00:51:31,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aba8e05e720582 for 80.00000000 SEI-USDT. +2023-09-19 00:51:31,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605aba8e05e720582", "creation_timestamp": 1695084691.0, "exchange_order_id": "35498105", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:51:31,467 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084691.0, "order_id": "x-XEKWYICXSSIUT605aba598b7220582", "exchange_order_id": "35497594", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:51:31,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aba598b7220582. +2023-09-19 00:52:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aba8e05e720582. [clock=2023-09-19 00:52:26+00:00] +2023-09-19 00:52:26,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221261910437970200785936164 amount: 80. +2023-09-19 00:52:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1228547724053584999588881238 amount: 80. +2023-09-19 00:52:26,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084746.0, "order_id": "x-XEKWYICXBSIUT605aba8e05e720582", "exchange_order_id": "35498105", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:52:26,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aba8e05e720582. +2023-09-19 00:52:26,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abac26878f0582 for 80.00000000 SEI-USDT. +2023-09-19 00:52:26,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605abac26878f0582", "creation_timestamp": 1695084746.0, "exchange_order_id": "35498181", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:52:26,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abac268afb0582 for 80.00000000 SEI-USDT. +2023-09-19 00:52:26,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXSSIUT605abac268afb0582", "creation_timestamp": 1695084746.0, "exchange_order_id": "35498182", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:53:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abac26878f0582. [clock=2023-09-19 00:53:21+00:00] +2023-09-19 00:53:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abac268afb0582. [clock=2023-09-19 00:53:21+00:00] +2023-09-19 00:53:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219907783402325603533397085 amount: 80. +2023-09-19 00:53:21,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:53:21,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084801.0, "order_id": "x-XEKWYICXBSIUT605abac26878f0582", "exchange_order_id": "35498181", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:53:21,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abac26878f0582. +2023-09-19 00:53:21,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084801.0, "order_id": "x-XEKWYICXSSIUT605abac268afb0582", "exchange_order_id": "35498182", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:53:21,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abac268afb0582. +2023-09-19 00:53:21,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abaf6dc0530582 for 80.00000000 SEI-USDT. +2023-09-19 00:53:21,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abaf6dc0530582", "creation_timestamp": 1695084801.0, "exchange_order_id": "35498551", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:54:16,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abaf6dc0530582. [clock=2023-09-19 00:54:16+00:00] +2023-09-19 00:54:16,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221655947340940557548627525 amount: 80. +2023-09-19 00:54:16,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229141405384666028378880183 amount: 80. +2023-09-19 00:54:16,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084856.0, "order_id": "x-XEKWYICXBSIUT605abaf6dc0530582", "exchange_order_id": "35498551", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:54:16,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abaf6dc0530582. +2023-09-19 00:54:16,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abb2b572dc0582 for 80.00000000 SEI-USDT. +2023-09-19 00:54:16,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605abb2b572dc0582", "creation_timestamp": 1695084856.0, "exchange_order_id": "35498790", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:54:16,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abb2b575790582 for 80.00000000 SEI-USDT. +2023-09-19 00:54:16,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605abb2b575790582", "creation_timestamp": 1695084856.0, "exchange_order_id": "35498791", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:55:11,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abb2b572dc0582. [clock=2023-09-19 00:55:11+00:00] +2023-09-19 00:55:11,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abb2b575790582. [clock=2023-09-19 00:55:11+00:00] +2023-09-19 00:55:11,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221476529219166601651212541 amount: 80. +2023-09-19 00:55:11,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:55:11,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084911.0, "order_id": "x-XEKWYICXBSIUT605abb2b572dc0582", "exchange_order_id": "35498790", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:55:11,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abb2b572dc0582. +2023-09-19 00:55:11,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084911.0, "order_id": "x-XEKWYICXSSIUT605abb2b575790582", "exchange_order_id": "35498791", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:55:11,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abb2b575790582. +2023-09-19 00:55:11,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abb5fcdbff0582 for 80.00000000 SEI-USDT. +2023-09-19 00:55:11,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605abb5fcdbff0582", "creation_timestamp": 1695084911.0, "exchange_order_id": "35499030", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:56:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abb5fcdbff0582. [clock=2023-09-19 00:56:06+00:00] +2023-09-19 00:56:06,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220817151473577355788795076 amount: 80. +2023-09-19 00:56:06,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226014142244367744458202431 amount: 80. +2023-09-19 00:56:06,073 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084966.0, "order_id": "x-XEKWYICXBSIUT605abb5fcdbff0582", "exchange_order_id": "35499030", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:56:06,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abb5fcdbff0582. +2023-09-19 00:56:06,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abb943f8400582 for 80.00000000 SEI-USDT. +2023-09-19 00:56:06,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605abb943f8400582", "creation_timestamp": 1695084966.0, "exchange_order_id": "35499179", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:56:06,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abb943fc120582 for 80.00000000 SEI-USDT. +2023-09-19 00:56:06,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695084966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605abb943fc120582", "creation_timestamp": 1695084966.0, "exchange_order_id": "35499180", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:57:01,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abb943f8400582. [clock=2023-09-19 00:57:01+00:00] +2023-09-19 00:57:01,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abb943fc120582. [clock=2023-09-19 00:57:01+00:00] +2023-09-19 00:57:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219943489141998083643676160 amount: 80. +2023-09-19 00:57:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:57:01,053 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085021.0, "order_id": "x-XEKWYICXBSIUT605abb943f8400582", "exchange_order_id": "35499179", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:57:01,053 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abb943f8400582. +2023-09-19 00:57:01,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abbc8ad3a00582 for 80.00000000 SEI-USDT. +2023-09-19 00:57:01,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abbc8ad3a00582", "creation_timestamp": 1695085021.0, "exchange_order_id": "35499327", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:57:01,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085021.0, "order_id": "x-XEKWYICXSSIUT605abb943fc120582", "exchange_order_id": "35499180", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:57:01,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abb943fc120582. +2023-09-19 00:57:56,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abbc8ad3a00582. [clock=2023-09-19 00:57:56+00:00] +2023-09-19 00:57:56,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220401807816421263785702986 amount: 80. +2023-09-19 00:57:56,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226177020927095752228395606 amount: 80. +2023-09-19 00:57:56,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085076.0, "order_id": "x-XEKWYICXBSIUT605abbc8ad3a00582", "exchange_order_id": "35499327", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:57:56,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abbc8ad3a00582. +2023-09-19 00:57:56,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abbfd2fa2d0582 for 80.00000000 SEI-USDT. +2023-09-19 00:57:56,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605abbfd2fa2d0582", "creation_timestamp": 1695085076.0, "exchange_order_id": "35499469", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:57:56,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abbfd2f7e30582 for 80.00000000 SEI-USDT. +2023-09-19 00:57:56,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605abbfd2f7e30582", "creation_timestamp": 1695085076.0, "exchange_order_id": "35499470", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:58:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abbfd2f7e30582. [clock=2023-09-19 00:58:51+00:00] +2023-09-19 00:58:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abbfd2fa2d0582. [clock=2023-09-19 00:58:51+00:00] +2023-09-19 00:58:51,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220757758216909797079820768 amount: 80. +2023-09-19 00:58:51,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 00:58:51,039 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085131.0, "order_id": "x-XEKWYICXBSIUT605abbfd2f7e30582", "exchange_order_id": "35499470", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:58:51,040 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abbfd2f7e30582. +2023-09-19 00:58:51,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085131.0, "order_id": "x-XEKWYICXSSIUT605abbfd2fa2d0582", "exchange_order_id": "35499469", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:58:51,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abbfd2fa2d0582. +2023-09-19 00:58:51,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abc3192a950582 for 80.00000000 SEI-USDT. +2023-09-19 00:58:51,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605abc3192a950582", "creation_timestamp": 1695085131.0, "exchange_order_id": "35499612", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:59:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abc3192a950582. [clock=2023-09-19 00:59:46+00:00] +2023-09-19 00:59:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218129631784649987928367588 amount: 80. +2023-09-19 00:59:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1222728556500389762510770434 amount: 80. +2023-09-19 00:59:46,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085186.0, "order_id": "x-XEKWYICXBSIUT605abc3192a950582", "exchange_order_id": "35499612", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 00:59:46,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abc3192a950582. +2023-09-19 00:59:46,575 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abc66069d50582 for 80.00000000 SEI-USDT. +2023-09-19 00:59:46,629 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605abc66069d50582", "creation_timestamp": 1695085186.0, "exchange_order_id": "35499812", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 00:59:46,719 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abc6606dda0582 for 80.00000000 SEI-USDT. +2023-09-19 00:59:46,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXSSIUT605abc6606dda0582", "creation_timestamp": 1695085186.0, "exchange_order_id": "35499814", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:00:41,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abc66069d50582. [clock=2023-09-19 01:00:41+00:00] +2023-09-19 01:00:41,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abc6606dda0582. [clock=2023-09-19 01:00:41+00:00] +2023-09-19 01:00:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216535142018414222843969322 amount: 80. +2023-09-19 01:00:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:00:41,054 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085241.0, "order_id": "x-XEKWYICXBSIUT605abc66069d50582", "exchange_order_id": "35499812", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:00:41,054 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abc66069d50582. +2023-09-19 01:00:41,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085241.0, "order_id": "x-XEKWYICXSSIUT605abc6606dda0582", "exchange_order_id": "35499814", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:00:41,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abc6606dda0582. +2023-09-19 01:00:41,351 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abc9a7c27a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:00:41,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605abc9a7c27a0582", "creation_timestamp": 1695085241.0, "exchange_order_id": "35500172", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:01:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abc9a7c27a0582. [clock=2023-09-19 01:01:36+00:00] +2023-09-19 01:01:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1215612480334954343652653584 amount: 80. +2023-09-19 01:01:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1221040308591825600026217073 amount: 80. +2023-09-19 01:01:36,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085296.0, "order_id": "x-XEKWYICXBSIUT605abc9a7c27a0582", "exchange_order_id": "35500172", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:01:36,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abc9a7c27a0582. +2023-09-19 01:01:36,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abcceedac30582 for 80.00000000 SEI-USDT. +2023-09-19 01:01:36,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12150000", "order_id": "x-XEKWYICXBSIUT605abcceedac30582", "creation_timestamp": 1695085296.0, "exchange_order_id": "35500402", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:01:36,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abcceede310582 for 80.00000000 SEI-USDT. +2023-09-19 01:01:36,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXSSIUT605abcceede310582", "creation_timestamp": 1695085296.0, "exchange_order_id": "35500403", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:02:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abcceedac30582. [clock=2023-09-19 01:02:31+00:00] +2023-09-19 01:02:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abcceede310582. [clock=2023-09-19 01:02:31+00:00] +2023-09-19 01:02:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1213439306161182389933423169 amount: 80. +2023-09-19 01:02:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:02:31,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085351.0, "order_id": "x-XEKWYICXBSIUT605abcceedac30582", "exchange_order_id": "35500402", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:02:31,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abcceedac30582. +2023-09-19 01:02:31,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085351.0, "order_id": "x-XEKWYICXSSIUT605abcceede310582", "exchange_order_id": "35500403", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:02:31,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abcceede310582. +2023-09-19 01:02:31,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abd03619cd0582 for 80.00000000 SEI-USDT. +2023-09-19 01:02:31,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12130000", "order_id": "x-XEKWYICXBSIUT605abd03619cd0582", "creation_timestamp": 1695085351.0, "exchange_order_id": "35501003", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:03:26,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abd03619cd0582. [clock=2023-09-19 01:03:26+00:00] +2023-09-19 01:03:26,248 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1214454249601790703820633066 amount: 80. +2023-09-19 01:03:26,249 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1220136826202879262946036797 amount: 80. +2023-09-19 01:03:26,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085406.0, "order_id": "x-XEKWYICXBSIUT605abd03619cd0582", "exchange_order_id": "35501003", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:03:26,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abd03619cd0582. +2023-09-19 01:03:26,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abd380e32c0582 for 80.00000000 SEI-USDT. +2023-09-19 01:03:26,527 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXSSIUT605abd380e32c0582", "creation_timestamp": 1695085406.0, "exchange_order_id": "35501363", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:03:26,528 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abd380dfb70582 for 80.00000000 SEI-USDT. +2023-09-19 01:03:26,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12140000", "order_id": "x-XEKWYICXBSIUT605abd380dfb70582", "creation_timestamp": 1695085406.0, "exchange_order_id": "35501364", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:04:21,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abd380dfb70582. [clock=2023-09-19 01:04:21+00:00] +2023-09-19 01:04:21,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abd380e32c0582. [clock=2023-09-19 01:04:21+00:00] +2023-09-19 01:04:21,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1215242773097394937210033740 amount: 80. +2023-09-19 01:04:21,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:04:21,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085461.0, "order_id": "x-XEKWYICXBSIUT605abd380dfb70582", "exchange_order_id": "35501364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:04:21,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abd380dfb70582. +2023-09-19 01:04:21,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085461.0, "order_id": "x-XEKWYICXSSIUT605abd380e32c0582", "exchange_order_id": "35501363", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:04:21,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abd380e32c0582. +2023-09-19 01:04:21,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abd6c524c30582 for 80.00000000 SEI-USDT. +2023-09-19 01:04:21,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12150000", "order_id": "x-XEKWYICXBSIUT605abd6c524c30582", "creation_timestamp": 1695085461.0, "exchange_order_id": "35501576", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:04:28,529 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605abd6c524c30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:04:28,531 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:04:28+00:00] +2023-09-19 01:04:28,571 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085468.0, "order_id": "x-XEKWYICXBSIUT605abd6c524c30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12150000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4986710", "exchange_order_id": "35501576", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:04:28,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085468.0, "order_id": "x-XEKWYICXBSIUT605abd6c524c30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35501576", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:04:28,652 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605abd6c524c30582 completely filled. +2023-09-19 01:05:16,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1207756679701226435475585706 amount: 80. +2023-09-19 01:05:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1216367695274009740712883896 amount: 80. +2023-09-19 01:05:16,115 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abda0be3160582 for 80.00000000 SEI-USDT. +2023-09-19 01:05:16,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12070000", "order_id": "x-XEKWYICXBSIUT605abda0be3160582", "creation_timestamp": 1695085516.0, "exchange_order_id": "35502875", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:05:16,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abda0be69b0582 for 80.00000000 SEI-USDT. +2023-09-19 01:05:16,374 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXSSIUT605abda0be69b0582", "creation_timestamp": 1695085516.0, "exchange_order_id": "35502878", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:06:11,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abda0be3160582. [clock=2023-09-19 01:06:11+00:00] +2023-09-19 01:06:11,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abda0be69b0582. [clock=2023-09-19 01:06:11+00:00] +2023-09-19 01:06:11,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1207099526892029887680784632 amount: 80. +2023-09-19 01:06:11,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1216622530782118644306180538 amount: 80. +2023-09-19 01:06:11,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085571.0, "order_id": "x-XEKWYICXBSIUT605abda0be3160582", "exchange_order_id": "35502875", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:06:11,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abda0be3160582. +2023-09-19 01:06:11,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085571.0, "order_id": "x-XEKWYICXSSIUT605abda0be69b0582", "exchange_order_id": "35502878", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:06:11,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abda0be69b0582. +2023-09-19 01:06:11,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abdd54301f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:06:11,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXSSIUT605abdd54301f0582", "creation_timestamp": 1695085571.0, "exchange_order_id": "35503253", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:06:11,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abdd542dc20582 for 80.00000000 SEI-USDT. +2023-09-19 01:06:11,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12070000", "order_id": "x-XEKWYICXBSIUT605abdd542dc20582", "creation_timestamp": 1695085571.0, "exchange_order_id": "35503252", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:07:06,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abdd542dc20582. [clock=2023-09-19 01:07:06+00:00] +2023-09-19 01:07:06,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abdd54301f0582. [clock=2023-09-19 01:07:06+00:00] +2023-09-19 01:07:06,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1203502473195630200969721284 amount: 80. +2023-09-19 01:07:06,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1215223870066846055444627956 amount: 80. +2023-09-19 01:07:06,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085626.0, "order_id": "x-XEKWYICXBSIUT605abdd542dc20582", "exchange_order_id": "35503252", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:07:06,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abdd542dc20582. +2023-09-19 01:07:06,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085626.0, "order_id": "x-XEKWYICXSSIUT605abdd54301f0582", "exchange_order_id": "35503253", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:07:06,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abdd54301f0582. +2023-09-19 01:07:06,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abe09ae65c0582 for 80.00000000 SEI-USDT. +2023-09-19 01:07:06,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12030000", "order_id": "x-XEKWYICXBSIUT605abe09ae65c0582", "creation_timestamp": 1695085626.0, "exchange_order_id": "35504333", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:07:06,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abe09ae9960582 for 80.00000000 SEI-USDT. +2023-09-19 01:07:06,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12150000", "order_id": "x-XEKWYICXSSIUT605abe09ae9960582", "creation_timestamp": 1695085626.0, "exchange_order_id": "35504332", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:07:39,465 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605abe09ae9960582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:07:39,466 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:07:39+00:00] +2023-09-19 01:07:39,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085659.0, "order_id": "x-XEKWYICXSSIUT605abe09ae9960582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12150000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00972000"}]}, "exchange_trade_id": "4987141", "exchange_order_id": "35504332", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:07:39,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085659.0, "order_id": "x-XEKWYICXSSIUT605abe09ae9960582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35504332", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:07:39,500 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605abe09ae9960582 completely filled. +2023-09-19 01:08:01,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abe09ae65c0582. [clock=2023-09-19 01:08:01+00:00] +2023-09-19 01:08:01,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1214239163389499980221867791 amount: 80. +2023-09-19 01:08:01,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232735742456600948982593367 amount: 80. +2023-09-19 01:08:01,052 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085681.0, "order_id": "x-XEKWYICXBSIUT605abe09ae65c0582", "exchange_order_id": "35504333", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:08:01,053 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abe09ae65c0582. +2023-09-19 01:08:01,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abe3e1a9280582 for 80.00000000 SEI-USDT. +2023-09-19 01:08:01,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12140000", "order_id": "x-XEKWYICXBSIUT605abe3e1a9280582", "creation_timestamp": 1695085681.0, "exchange_order_id": "35505224", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:08:01,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abe3e1ad810582 for 80.00000000 SEI-USDT. +2023-09-19 01:08:01,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605abe3e1ad810582", "creation_timestamp": 1695085681.0, "exchange_order_id": "35505227", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:08:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abe3e1a9280582. [clock=2023-09-19 01:08:56+00:00] +2023-09-19 01:08:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abe3e1ad810582. [clock=2023-09-19 01:08:56+00:00] +2023-09-19 01:08:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216296884110227318389235855 amount: 80. +2023-09-19 01:08:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:08:56,042 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085736.0, "order_id": "x-XEKWYICXBSIUT605abe3e1a9280582", "exchange_order_id": "35505224", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:08:56,042 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abe3e1a9280582. +2023-09-19 01:08:56,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abe728bf980582 for 80.00000000 SEI-USDT. +2023-09-19 01:08:56,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605abe728bf980582", "creation_timestamp": 1695085736.0, "exchange_order_id": "35505592", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:08:56,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085736.0, "order_id": "x-XEKWYICXSSIUT605abe3e1ad810582", "exchange_order_id": "35505227", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:08:56,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abe3e1ad810582. +2023-09-19 01:09:51,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abe728bf980582. [clock=2023-09-19 01:09:51+00:00] +2023-09-19 01:09:51,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220445308003920944074212027 amount: 80. +2023-09-19 01:09:51,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234747685251698931818482510 amount: 80. +2023-09-19 01:09:51,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085791.0, "order_id": "x-XEKWYICXBSIUT605abe728bf980582", "exchange_order_id": "35505592", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:09:51,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abe728bf980582. +2023-09-19 01:09:51,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abea7102870582 for 80.00000000 SEI-USDT. +2023-09-19 01:09:51,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605abea7102870582", "creation_timestamp": 1695085791.0, "exchange_order_id": "35506170", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:09:51,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abea70ff830582 for 80.00000000 SEI-USDT. +2023-09-19 01:09:51,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605abea70ff830582", "creation_timestamp": 1695085791.0, "exchange_order_id": "35506171", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:10:46,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abea70ff830582. [clock=2023-09-19 01:10:46+00:00] +2023-09-19 01:10:46,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abea7102870582. [clock=2023-09-19 01:10:46+00:00] +2023-09-19 01:10:46,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219526494641695288161898657 amount: 80. +2023-09-19 01:10:46,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:10:46,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085846.0, "order_id": "x-XEKWYICXBSIUT605abea70ff830582", "exchange_order_id": "35506171", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:10:46,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abea70ff830582. +2023-09-19 01:10:46,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085846.0, "order_id": "x-XEKWYICXSSIUT605abea7102870582", "exchange_order_id": "35506170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:10:46,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abea7102870582. +2023-09-19 01:10:46,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abedb7eb600582 for 80.00000000 SEI-USDT. +2023-09-19 01:10:46,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abedb7eb600582", "creation_timestamp": 1695085846.0, "exchange_order_id": "35506380", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:11:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abedb7eb600582. [clock=2023-09-19 01:11:41+00:00] +2023-09-19 01:11:41,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219048158314960959748356949 amount: 80. +2023-09-19 01:11:41,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229666822303700932106011637 amount: 80. +2023-09-19 01:11:41,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085901.0, "order_id": "x-XEKWYICXBSIUT605abedb7eb600582", "exchange_order_id": "35506380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:11:41,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abedb7eb600582. +2023-09-19 01:11:41,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abf0fe6dd00582 for 80.00000000 SEI-USDT. +2023-09-19 01:11:41,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abf0fe6dd00582", "creation_timestamp": 1695085901.0, "exchange_order_id": "35506496", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:11:41,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abf0fe70140582 for 80.00000000 SEI-USDT. +2023-09-19 01:11:41,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605abf0fe70140582", "creation_timestamp": 1695085901.0, "exchange_order_id": "35506497", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:12:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abf0fe6dd00582. [clock=2023-09-19 01:12:36+00:00] +2023-09-19 01:12:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abf0fe70140582. [clock=2023-09-19 01:12:36+00:00] +2023-09-19 01:12:36,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219262737142915906998798780 amount: 80. +2023-09-19 01:12:36,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:12:36,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085956.0, "order_id": "x-XEKWYICXBSIUT605abf0fe6dd00582", "exchange_order_id": "35506496", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:12:36,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abf0fe6dd00582. +2023-09-19 01:12:36,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085956.0, "order_id": "x-XEKWYICXSSIUT605abf0fe70140582", "exchange_order_id": "35506497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:12:36,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abf0fe70140582. +2023-09-19 01:12:36,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abf445a9e20582 for 80.00000000 SEI-USDT. +2023-09-19 01:12:36,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695085956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abf445a9e20582", "creation_timestamp": 1695085956.0, "exchange_order_id": "35506606", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:12:42,869 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 01:13:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abf445a9e20582. [clock=2023-09-19 01:13:31+00:00] +2023-09-19 01:13:31,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218428891798671766605279454 amount: 80. +2023-09-19 01:13:31,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224838236861753927001252076 amount: 80. +2023-09-19 01:13:31,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086011.0, "order_id": "x-XEKWYICXBSIUT605abf445a9e20582", "exchange_order_id": "35506606", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:13:31,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abf445a9e20582. +2023-09-19 01:13:31,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abf78ce5d80582 for 80.00000000 SEI-USDT. +2023-09-19 01:13:31,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605abf78ce5d80582", "creation_timestamp": 1695086011.0, "exchange_order_id": "35506730", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:13:31,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abf78ce2700582 for 80.00000000 SEI-USDT. +2023-09-19 01:13:31,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605abf78ce2700582", "creation_timestamp": 1695086011.0, "exchange_order_id": "35506731", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:14:26,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abf78ce2700582. [clock=2023-09-19 01:14:26+00:00] +2023-09-19 01:14:26,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abf78ce5d80582. [clock=2023-09-19 01:14:26+00:00] +2023-09-19 01:14:26,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219556999103166590985799469 amount: 80. +2023-09-19 01:14:26,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:14:26,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086066.0, "order_id": "x-XEKWYICXBSIUT605abf78ce2700582", "exchange_order_id": "35506731", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:14:26,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abf78ce2700582. +2023-09-19 01:14:26,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086066.0, "order_id": "x-XEKWYICXSSIUT605abf78ce5d80582", "exchange_order_id": "35506730", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:14:26,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abf78ce5d80582. +2023-09-19 01:14:26,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abfad4d0e90582 for 80.00000000 SEI-USDT. +2023-09-19 01:14:26,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605abfad4d0e90582", "creation_timestamp": 1695086066.0, "exchange_order_id": "35506847", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:15:21,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abfad4d0e90582. [clock=2023-09-19 01:15:21+00:00] +2023-09-19 01:15:21,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218391553921464314083956847 amount: 80. +2023-09-19 01:15:21,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224245255059933664071690970 amount: 80. +2023-09-19 01:15:21,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086121.0, "order_id": "x-XEKWYICXBSIUT605abfad4d0e90582", "exchange_order_id": "35506847", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:15:21,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abfad4d0e90582. +2023-09-19 01:15:21,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605abfe1c5e710582 for 80.00000000 SEI-USDT. +2023-09-19 01:15:21,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605abfe1c5e710582", "creation_timestamp": 1695086121.0, "exchange_order_id": "35507036", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:15:21,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605abfe1c618e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:15:21,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605abfe1c618e0582", "creation_timestamp": 1695086121.0, "exchange_order_id": "35507037", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:16:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605abfe1c5e710582. [clock=2023-09-19 01:16:16+00:00] +2023-09-19 01:16:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605abfe1c618e0582. [clock=2023-09-19 01:16:16+00:00] +2023-09-19 01:16:16,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12210000 amount: 80. +2023-09-19 01:16:16,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 01:16:16,058 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086176.0, "order_id": "x-XEKWYICXBSIUT605abfe1c5e710582", "exchange_order_id": "35507036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:16:16,059 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605abfe1c5e710582. +2023-09-19 01:16:16,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac0162d6c40582 for 80.00000000 SEI-USDT. +2023-09-19 01:16:16,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac0162d6c40582", "creation_timestamp": 1695086176.0, "exchange_order_id": "35507527", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:16:16,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086176.0, "order_id": "x-XEKWYICXSSIUT605abfe1c618e0582", "exchange_order_id": "35507037", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:16:16,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605abfe1c618e0582. +2023-09-19 01:17:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac0162d6c40582. [clock=2023-09-19 01:17:11+00:00] +2023-09-19 01:17:11,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12220000 amount: 80. +2023-09-19 01:17:11,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227179353018059982835695584 amount: 80. +2023-09-19 01:17:11,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086231.0, "order_id": "x-XEKWYICXBSIUT605ac0162d6c40582", "exchange_order_id": "35507527", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:17:11,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac0162d6c40582. +2023-09-19 01:17:11,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac04a9cfdb0582 for 80.00000000 SEI-USDT. +2023-09-19 01:17:11,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac04a9cfdb0582", "creation_timestamp": 1695086231.0, "exchange_order_id": "35507736", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:17:11,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac04a9d3810582 for 80.00000000 SEI-USDT. +2023-09-19 01:17:11,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac04a9d3810582", "creation_timestamp": 1695086231.0, "exchange_order_id": "35507737", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:17:23,798 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ac04a9cfdb0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:17:23,799 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:17:23+00:00] +2023-09-19 01:17:23,819 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086243.0, "order_id": "x-XEKWYICXBSIUT605ac04a9cfdb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12220000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4987417", "exchange_order_id": "35507736", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:17:23,833 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086243.0, "order_id": "x-XEKWYICXBSIUT605ac04a9cfdb0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35507736", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:17:23,833 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ac04a9cfdb0582 completely filled. +2023-09-19 01:18:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac04a9d3810582. [clock=2023-09-19 01:18:06+00:00] +2023-09-19 01:18:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222828391165979295566866076 amount: 80. +2023-09-19 01:18:06,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227647568443700300702622166 amount: 80. +2023-09-19 01:18:06,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086286.0, "order_id": "x-XEKWYICXSSIUT605ac04a9d3810582", "exchange_order_id": "35507737", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:18:06,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac04a9d3810582. +2023-09-19 01:18:06,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac07f110c40582 for 80.00000000 SEI-USDT. +2023-09-19 01:18:06,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac07f110c40582", "creation_timestamp": 1695086286.0, "exchange_order_id": "35507996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:18:06,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac07f115e10582 for 80.00000000 SEI-USDT. +2023-09-19 01:18:06,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac07f115e10582", "creation_timestamp": 1695086286.0, "exchange_order_id": "35507997", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:18:32,823 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ac07f110c40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:18:32,824 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:18:32+00:00] +2023-09-19 01:18:32,846 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086312.0, "order_id": "x-XEKWYICXBSIUT605ac07f110c40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12220000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4987447", "exchange_order_id": "35507996", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:18:32,859 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086312.0, "order_id": "x-XEKWYICXBSIUT605ac07f110c40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35507996", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:18:32,860 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ac07f110c40582 completely filled. +2023-09-19 01:19:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac07f115e10582. [clock=2023-09-19 01:19:01+00:00] +2023-09-19 01:19:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220660009975639805088327637 amount: 80. +2023-09-19 01:19:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226589368622849463977235581 amount: 80. +2023-09-19 01:19:01,399 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086341.0, "order_id": "x-XEKWYICXSSIUT605ac07f115e10582", "exchange_order_id": "35507997", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:19:01,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac07f115e10582. +2023-09-19 01:19:01,627 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac0b384f0f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:19:01,653 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac0b384f0f0582", "creation_timestamp": 1695086341.0, "exchange_order_id": "35508389", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:19:01,750 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac0b38536a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:19:01,775 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac0b38536a0582", "creation_timestamp": 1695086341.0, "exchange_order_id": "35508390", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:19:56,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac0b384f0f0582. [clock=2023-09-19 01:19:56+00:00] +2023-09-19 01:19:56,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac0b38536a0582. [clock=2023-09-19 01:19:56+00:00] +2023-09-19 01:19:56,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222002581635435683417336069 amount: 80. +2023-09-19 01:19:56,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227728011862731900540165976 amount: 80. +2023-09-19 01:19:56,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086396.0, "order_id": "x-XEKWYICXBSIUT605ac0b384f0f0582", "exchange_order_id": "35508389", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:19:56,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac0b384f0f0582. +2023-09-19 01:19:56,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086396.0, "order_id": "x-XEKWYICXSSIUT605ac0b38536a0582", "exchange_order_id": "35508390", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:19:56,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac0b38536a0582. +2023-09-19 01:19:56,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac0e80a6040582 for 80.00000000 SEI-USDT. +2023-09-19 01:19:56,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac0e80a6040582", "creation_timestamp": 1695086396.0, "exchange_order_id": "35508583", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:19:56,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac0e80a85a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:19:56,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac0e80a85a0582", "creation_timestamp": 1695086396.0, "exchange_order_id": "35508584", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:20:51,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac0e80a6040582. [clock=2023-09-19 01:20:51+00:00] +2023-09-19 01:20:51,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac0e80a85a0582. [clock=2023-09-19 01:20:51+00:00] +2023-09-19 01:20:51,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222224064148368453933068567 amount: 80. +2023-09-19 01:20:51,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226675666207028843966078927 amount: 80. +2023-09-19 01:20:51,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086451.0, "order_id": "x-XEKWYICXBSIUT605ac0e80a6040582", "exchange_order_id": "35508583", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:20:51,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac0e80a6040582. +2023-09-19 01:20:51,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086451.0, "order_id": "x-XEKWYICXSSIUT605ac0e80a85a0582", "exchange_order_id": "35508584", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:20:51,404 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac0e80a85a0582. +2023-09-19 01:20:51,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac11c7b0500582 for 80.00000000 SEI-USDT. +2023-09-19 01:20:51,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac11c7b0500582", "creation_timestamp": 1695086451.0, "exchange_order_id": "35508688", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:20:51,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac11c7ada40582 for 80.00000000 SEI-USDT. +2023-09-19 01:20:51,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac11c7ada40582", "creation_timestamp": 1695086451.0, "exchange_order_id": "35508689", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:21:46,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac11c7ada40582. [clock=2023-09-19 01:21:46+00:00] +2023-09-19 01:21:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac11c7b0500582. [clock=2023-09-19 01:21:46+00:00] +2023-09-19 01:21:46,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12230000 amount: 80. +2023-09-19 01:21:46,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1228369169100277699650371741 amount: 80. +2023-09-19 01:21:46,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086506.0, "order_id": "x-XEKWYICXBSIUT605ac11c7ada40582", "exchange_order_id": "35508689", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:21:46,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac11c7ada40582. +2023-09-19 01:21:46,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086506.0, "order_id": "x-XEKWYICXSSIUT605ac11c7b0500582", "exchange_order_id": "35508688", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:21:46,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac11c7b0500582. +2023-09-19 01:21:46,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac150e39480582 for 80.00000000 SEI-USDT. +2023-09-19 01:21:46,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXSSIUT605ac150e39480582", "creation_timestamp": 1695086506.0, "exchange_order_id": "35508805", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:21:46,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac150e34e50582 for 80.00000000 SEI-USDT. +2023-09-19 01:21:46,391 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ac150e34e50582", "creation_timestamp": 1695086506.0, "exchange_order_id": "35508806", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:22:32,822 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ac150e34e50582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:22:32,823 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:22:32+00:00] +2023-09-19 01:22:32,846 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086552.0, "order_id": "x-XEKWYICXBSIUT605ac150e34e50582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12230000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4987498", "exchange_order_id": "35508806", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:22:32,858 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086552.0, "order_id": "x-XEKWYICXBSIUT605ac150e34e50582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35508806", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:22:32,859 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ac150e34e50582 completely filled. +2023-09-19 01:22:41,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac150e39480582. [clock=2023-09-19 01:22:41+00:00] +2023-09-19 01:22:41,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12240000 amount: 80. +2023-09-19 01:22:41,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229012610578614512113541890 amount: 80. +2023-09-19 01:22:41,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086561.0, "order_id": "x-XEKWYICXSSIUT605ac150e39480582", "exchange_order_id": "35508805", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:22:41,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac150e39480582. +2023-09-19 01:22:41,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac18560a6f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:22:41,391 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ac18560a6f0582", "creation_timestamp": 1695086561.0, "exchange_order_id": "35509241", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:22:41,393 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac185606e80582 for 80.00000000 SEI-USDT. +2023-09-19 01:22:41,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605ac185606e80582", "creation_timestamp": 1695086561.0, "exchange_order_id": "35509242", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:23:05,509 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ac185606e80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:23:05,510 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:23:05+00:00] +2023-09-19 01:23:05,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086585.0, "order_id": "x-XEKWYICXBSIUT605ac185606e80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12240000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4987507", "exchange_order_id": "35509242", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:23:05,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086585.0, "order_id": "x-XEKWYICXBSIUT605ac185606e80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35509242", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:23:05,599 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ac185606e80582 completely filled. +2023-09-19 01:23:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac18560a6f0582. [clock=2023-09-19 01:23:36+00:00] +2023-09-19 01:23:36,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222862136573904524237540540 amount: 80. +2023-09-19 01:23:36,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227606410377928032572506184 amount: 80. +2023-09-19 01:23:36,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086616.0, "order_id": "x-XEKWYICXSSIUT605ac18560a6f0582", "exchange_order_id": "35509241", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:23:36,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac18560a6f0582. +2023-09-19 01:23:36,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac1b9cbd260582 for 80.00000000 SEI-USDT. +2023-09-19 01:23:36,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac1b9cbd260582", "creation_timestamp": 1695086616.0, "exchange_order_id": "35509464", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:23:36,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac1b9cc0cb0582 for 80.00000000 SEI-USDT. +2023-09-19 01:23:36,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac1b9cc0cb0582", "creation_timestamp": 1695086616.0, "exchange_order_id": "35509465", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:24:31,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac1b9cbd260582. [clock=2023-09-19 01:24:31+00:00] +2023-09-19 01:24:31,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac1b9cc0cb0582. [clock=2023-09-19 01:24:31+00:00] +2023-09-19 01:24:31,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222894315314383098845730576 amount: 80. +2023-09-19 01:24:31,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226582813142454489577649264 amount: 80. +2023-09-19 01:24:31,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086671.0, "order_id": "x-XEKWYICXBSIUT605ac1b9cbd260582", "exchange_order_id": "35509464", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:24:31,433 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac1b9cbd260582. +2023-09-19 01:24:33,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086672.0, "order_id": "x-XEKWYICXSSIUT605ac1b9cc0cb0582", "exchange_order_id": "35509465", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:24:33,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac1b9cc0cb0582. +2023-09-19 01:24:33,786 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac1ee636e10582 for 80.00000000 SEI-USDT. +2023-09-19 01:24:33,835 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086673.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac1ee636e10582", "creation_timestamp": 1695086671.0, "exchange_order_id": "35509612", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:24:33,854 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac1ee6398e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:24:33,908 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086673.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac1ee6398e0582", "creation_timestamp": 1695086671.0, "exchange_order_id": "35509611", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:25:26,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac1ee636e10582. [clock=2023-09-19 01:25:26+00:00] +2023-09-19 01:25:26,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac1ee6398e0582. [clock=2023-09-19 01:25:26+00:00] +2023-09-19 01:25:26,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220320074608894754935806253 amount: 80. +2023-09-19 01:25:26,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224048885666673742549929086 amount: 80. +2023-09-19 01:25:26,060 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086726.0, "order_id": "x-XEKWYICXBSIUT605ac1ee636e10582", "exchange_order_id": "35509612", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:25:26,060 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac1ee636e10582. +2023-09-19 01:25:26,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086726.0, "order_id": "x-XEKWYICXSSIUT605ac1ee6398e0582", "exchange_order_id": "35509611", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:25:26,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac1ee6398e0582. +2023-09-19 01:25:26,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac222b1fee0582 for 80.00000000 SEI-USDT. +2023-09-19 01:25:26,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac222b1fee0582", "creation_timestamp": 1695086726.0, "exchange_order_id": "35509843", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:25:26,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac222b1ba70582 for 80.00000000 SEI-USDT. +2023-09-19 01:25:26,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac222b1ba70582", "creation_timestamp": 1695086726.0, "exchange_order_id": "35509844", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:26:21,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac222b1ba70582. [clock=2023-09-19 01:26:21+00:00] +2023-09-19 01:26:21,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac222b1fee0582. [clock=2023-09-19 01:26:21+00:00] +2023-09-19 01:26:21,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219464735138842343873224671 amount: 80. +2023-09-19 01:26:21,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224094416517305423736774973 amount: 80. +2023-09-19 01:26:21,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086781.0, "order_id": "x-XEKWYICXBSIUT605ac222b1ba70582", "exchange_order_id": "35509844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:26:21,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac222b1ba70582. +2023-09-19 01:26:21,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086781.0, "order_id": "x-XEKWYICXSSIUT605ac222b1fee0582", "exchange_order_id": "35509843", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:26:21,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac222b1fee0582. +2023-09-19 01:26:21,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac25732eae0582 for 80.00000000 SEI-USDT. +2023-09-19 01:26:21,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac25732eae0582", "creation_timestamp": 1695086781.0, "exchange_order_id": "35510074", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:26:21,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac25732b950582 for 80.00000000 SEI-USDT. +2023-09-19 01:26:21,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ac25732b950582", "creation_timestamp": 1695086781.0, "exchange_order_id": "35510075", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:27:16,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac25732b950582. [clock=2023-09-19 01:27:16+00:00] +2023-09-19 01:27:16,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac25732eae0582. [clock=2023-09-19 01:27:16+00:00] +2023-09-19 01:27:16,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220028053479172889007879502 amount: 80. +2023-09-19 01:27:16,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223629061133131998301679654 amount: 80. +2023-09-19 01:27:16,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086836.0, "order_id": "x-XEKWYICXBSIUT605ac25732b950582", "exchange_order_id": "35510075", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:27:16,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac25732b950582. +2023-09-19 01:27:16,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086836.0, "order_id": "x-XEKWYICXSSIUT605ac25732eae0582", "exchange_order_id": "35510074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:27:16,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac25732eae0582. +2023-09-19 01:27:16,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac28ba8c300582 for 80.00000000 SEI-USDT. +2023-09-19 01:27:16,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac28ba8c300582", "creation_timestamp": 1695086836.0, "exchange_order_id": "35510153", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:27:16,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac28ba90c70582 for 80.00000000 SEI-USDT. +2023-09-19 01:27:16,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac28ba90c70582", "creation_timestamp": 1695086836.0, "exchange_order_id": "35510154", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:27:52,579 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ac28ba90c70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:27:52,592 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:27:52+00:00] +2023-09-19 01:27:52,677 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086872.0, "order_id": "x-XEKWYICXSSIUT605ac28ba90c70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12230000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00978400"}]}, "exchange_trade_id": "4987572", "exchange_order_id": "35510154", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:27:52,957 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086872.0, "order_id": "x-XEKWYICXSSIUT605ac28ba90c70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35510154", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:27:52,958 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ac28ba90c70582 completely filled. +2023-09-19 01:28:11,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac28ba8c300582. [clock=2023-09-19 01:28:11+00:00] +2023-09-19 01:28:11,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221706671323921777483725217 amount: 80. +2023-09-19 01:28:11,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225373664484100599089869891 amount: 80. +2023-09-19 01:28:11,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086891.0, "order_id": "x-XEKWYICXBSIUT605ac28ba8c300582", "exchange_order_id": "35510153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:28:11,565 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac28ba8c300582. +2023-09-19 01:28:11,685 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac2c020cf70582 for 80.00000000 SEI-USDT. +2023-09-19 01:28:11,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac2c020cf70582", "creation_timestamp": 1695086891.0, "exchange_order_id": "35510326", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:28:12,673 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac2c020ffa0582 for 80.00000000 SEI-USDT. +2023-09-19 01:28:12,704 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086892.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ac2c020ffa0582", "creation_timestamp": 1695086891.0, "exchange_order_id": "35510328", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:28:52,492 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ac2c020ffa0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:28:52,493 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:28:52+00:00] +2023-09-19 01:28:52,540 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086932.0, "order_id": "x-XEKWYICXSSIUT605ac2c020ffa0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12250000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00980000"}]}, "exchange_trade_id": "4987623", "exchange_order_id": "35510328", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:28:52,680 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086932.0, "order_id": "x-XEKWYICXSSIUT605ac2c020ffa0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35510328", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:28:52,681 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ac2c020ffa0582 completely filled. +2023-09-19 01:29:06,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac2c020cf70582. [clock=2023-09-19 01:29:06+00:00] +2023-09-19 01:29:06,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1223415434845461936117329199 amount: 80. +2023-09-19 01:29:06,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227997317096119830678723750 amount: 80. +2023-09-19 01:29:06,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086946.0, "order_id": "x-XEKWYICXBSIUT605ac2c020cf70582", "exchange_order_id": "35510326", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:29:06,524 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac2c020cf70582. +2023-09-19 01:29:07,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac2f49e32e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:29:07,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ac2f49e32e0582", "creation_timestamp": 1695086946.0, "exchange_order_id": "35510735", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:29:08,054 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac2f49e75c0582 for 80.00000000 SEI-USDT. +2023-09-19 01:29:08,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695086947.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac2f49e75c0582", "creation_timestamp": 1695086946.0, "exchange_order_id": "35510736", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:30:01,191 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac2f49e32e0582. [clock=2023-09-19 01:30:01+00:00] +2023-09-19 01:30:01,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac2f49e75c0582. [clock=2023-09-19 01:30:01+00:00] +2023-09-19 01:30:01,250 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222703952325638787975617652 amount: 80. +2023-09-19 01:30:01,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1228401523793312133271209016 amount: 80. +2023-09-19 01:30:02,066 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087001.0, "order_id": "x-XEKWYICXBSIUT605ac2f49e32e0582", "exchange_order_id": "35510735", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:30:02,067 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac2f49e32e0582. +2023-09-19 01:30:03,640 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087003.0, "order_id": "x-XEKWYICXSSIUT605ac2f49e75c0582", "exchange_order_id": "35510736", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:30:03,641 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac2f49e75c0582. +2023-09-19 01:30:04,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac3292b0460582 for 80.00000000 SEI-USDT. +2023-09-19 01:30:04,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087003.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac3292b0460582", "creation_timestamp": 1695087001.0, "exchange_order_id": "35510930", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:30:04,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac3292f42b0582 for 80.00000000 SEI-USDT. +2023-09-19 01:30:04,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087003.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXSSIUT605ac3292f42b0582", "creation_timestamp": 1695087001.0, "exchange_order_id": "35510931", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:30:56,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac3292b0460582. [clock=2023-09-19 01:30:56+00:00] +2023-09-19 01:30:56,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac3292f42b0582. [clock=2023-09-19 01:30:56+00:00] +2023-09-19 01:30:56,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221274269303959415873246238 amount: 80. +2023-09-19 01:30:56,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226815941936626371345822520 amount: 80. +2023-09-19 01:30:56,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087056.0, "order_id": "x-XEKWYICXBSIUT605ac3292b0460582", "exchange_order_id": "35510930", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:30:56,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac3292b0460582. +2023-09-19 01:30:56,535 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087056.0, "order_id": "x-XEKWYICXSSIUT605ac3292f42b0582", "exchange_order_id": "35510931", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:30:56,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac3292f42b0582. +2023-09-19 01:30:56,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac35d6d9110582 for 80.00000000 SEI-USDT. +2023-09-19 01:30:56,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac35d6d9110582", "creation_timestamp": 1695087056.0, "exchange_order_id": "35511317", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:30:56,551 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac35d6d6060582 for 80.00000000 SEI-USDT. +2023-09-19 01:30:56,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac35d6d6060582", "creation_timestamp": 1695087056.0, "exchange_order_id": "35511319", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:31:51,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac35d6d6060582. [clock=2023-09-19 01:31:51+00:00] +2023-09-19 01:31:51,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac35d6d9110582. [clock=2023-09-19 01:31:51+00:00] +2023-09-19 01:31:51,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221676894003923033219249805 amount: 80. +2023-09-19 01:31:51,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227096225820688315770712133 amount: 80. +2023-09-19 01:31:51,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087111.0, "order_id": "x-XEKWYICXBSIUT605ac35d6d6060582", "exchange_order_id": "35511319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:31:51,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac35d6d6060582. +2023-09-19 01:31:51,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087111.0, "order_id": "x-XEKWYICXSSIUT605ac35d6d9110582", "exchange_order_id": "35511317", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:31:51,398 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac35d6d9110582. +2023-09-19 01:31:51,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac391e78cd0582 for 80.00000000 SEI-USDT. +2023-09-19 01:31:51,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac391e78cd0582", "creation_timestamp": 1695087111.0, "exchange_order_id": "35511663", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:31:51,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac391e7b370582 for 80.00000000 SEI-USDT. +2023-09-19 01:31:51,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac391e7b370582", "creation_timestamp": 1695087111.0, "exchange_order_id": "35511664", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:32:46,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac391e78cd0582. [clock=2023-09-19 01:32:46+00:00] +2023-09-19 01:32:46,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac391e7b370582. [clock=2023-09-19 01:32:46+00:00] +2023-09-19 01:32:46,339 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221699604936220663836027476 amount: 80. +2023-09-19 01:32:46,340 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227025999458370559493954029 amount: 80. +2023-09-19 01:32:46,777 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087166.0, "order_id": "x-XEKWYICXBSIUT605ac391e78cd0582", "exchange_order_id": "35511663", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:32:46,777 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac391e78cd0582. +2023-09-19 01:32:48,716 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087168.0, "order_id": "x-XEKWYICXSSIUT605ac391e7b370582", "exchange_order_id": "35511664", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:32:48,717 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac391e7b370582. +2023-09-19 01:32:50,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac3c69c0dc0582 for 80.00000000 SEI-USDT. +2023-09-19 01:32:50,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087168.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac3c69c0dc0582", "creation_timestamp": 1695087166.0, "exchange_order_id": "35512022", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:32:50,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac3c69bcf40582 for 80.00000000 SEI-USDT. +2023-09-19 01:32:50,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087169.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac3c69bcf40582", "creation_timestamp": 1695087166.0, "exchange_order_id": "35512023", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:33:41,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac3c69bcf40582. [clock=2023-09-19 01:33:41+00:00] +2023-09-19 01:33:41,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac3c69c0dc0582. [clock=2023-09-19 01:33:41+00:00] +2023-09-19 01:33:41,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222988210492706346739091129 amount: 80. +2023-09-19 01:33:41,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227133323419743230296951799 amount: 80. +2023-09-19 01:33:41,806 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087221.0, "order_id": "x-XEKWYICXBSIUT605ac3c69bcf40582", "exchange_order_id": "35512023", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:33:41,807 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac3c69bcf40582. +2023-09-19 01:33:42,838 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087222.0, "order_id": "x-XEKWYICXSSIUT605ac3c69c0dc0582", "exchange_order_id": "35512022", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:33:42,839 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac3c69c0dc0582. +2023-09-19 01:33:43,448 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac3faf6b180582 for 80.00000000 SEI-USDT. +2023-09-19 01:33:43,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087222.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac3faf6b180582", "creation_timestamp": 1695087221.0, "exchange_order_id": "35512256", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:33:43,478 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac3faf90110582 for 80.00000000 SEI-USDT. +2023-09-19 01:33:43,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087222.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ac3faf90110582", "creation_timestamp": 1695087221.0, "exchange_order_id": "35512255", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:34:36,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac3faf6b180582. [clock=2023-09-19 01:34:36+00:00] +2023-09-19 01:34:36,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac3faf90110582. [clock=2023-09-19 01:34:36+00:00] +2023-09-19 01:34:36,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1222002031131037291629955737 amount: 80. +2023-09-19 01:34:36,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226087563297174350514760619 amount: 80. +2023-09-19 01:34:36,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087276.0, "order_id": "x-XEKWYICXBSIUT605ac3faf6b180582", "exchange_order_id": "35512256", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:34:36,756 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac3faf6b180582. +2023-09-19 01:34:37,735 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087277.0, "order_id": "x-XEKWYICXSSIUT605ac3faf90110582", "exchange_order_id": "35512255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:34:37,736 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac3faf90110582. +2023-09-19 01:34:38,035 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac42f5e6780582 for 80.00000000 SEI-USDT. +2023-09-19 01:34:38,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087277.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac42f5e6780582", "creation_timestamp": 1695087276.0, "exchange_order_id": "35512573", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:34:38,066 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac42f5e9300582 for 80.00000000 SEI-USDT. +2023-09-19 01:34:38,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087277.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac42f5e9300582", "creation_timestamp": 1695087276.0, "exchange_order_id": "35512572", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:35:31,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac42f5e6780582. [clock=2023-09-19 01:35:31+00:00] +2023-09-19 01:35:31,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac42f5e9300582. [clock=2023-09-19 01:35:31+00:00] +2023-09-19 01:35:31,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220931095050434236467507348 amount: 80. +2023-09-19 01:35:31,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226952755226830905815294500 amount: 80. +2023-09-19 01:35:31,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087331.0, "order_id": "x-XEKWYICXBSIUT605ac42f5e6780582", "exchange_order_id": "35512573", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:35:31,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac42f5e6780582. +2023-09-19 01:35:31,399 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087331.0, "order_id": "x-XEKWYICXSSIUT605ac42f5e9300582", "exchange_order_id": "35512572", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:35:31,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac42f5e9300582. +2023-09-19 01:35:31,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac463b0aad0582 for 80.00000000 SEI-USDT. +2023-09-19 01:35:31,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac463b0aad0582", "creation_timestamp": 1695087331.0, "exchange_order_id": "35512748", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:35:31,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac463b089e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:35:31,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac463b089e0582", "creation_timestamp": 1695087331.0, "exchange_order_id": "35512749", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:36:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac463b089e0582. [clock=2023-09-19 01:36:26+00:00] +2023-09-19 01:36:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac463b0aad0582. [clock=2023-09-19 01:36:26+00:00] +2023-09-19 01:36:26,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221391268186132497670897366 amount: 80. +2023-09-19 01:36:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226073980750602753152469841 amount: 80. +2023-09-19 01:36:26,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087386.0, "order_id": "x-XEKWYICXBSIUT605ac463b089e0582", "exchange_order_id": "35512749", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:36:26,131 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac463b089e0582. +2023-09-19 01:36:26,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087386.0, "order_id": "x-XEKWYICXSSIUT605ac463b0aad0582", "exchange_order_id": "35512748", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:36:26,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac463b0aad0582. +2023-09-19 01:36:26,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac4981bbc20582 for 80.00000000 SEI-USDT. +2023-09-19 01:36:26,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac4981bbc20582", "creation_timestamp": 1695087386.0, "exchange_order_id": "35512820", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:36:26,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac4981b9c40582 for 80.00000000 SEI-USDT. +2023-09-19 01:36:26,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac4981b9c40582", "creation_timestamp": 1695087386.0, "exchange_order_id": "35512821", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:36:27,629 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ac4981b9c40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:36:27,630 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:36:27+00:00] +2023-09-19 01:36:27,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087387.0, "order_id": "x-XEKWYICXBSIUT605ac4981b9c40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12210000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4987848", "exchange_order_id": "35512821", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:36:27,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087387.0, "order_id": "x-XEKWYICXBSIUT605ac4981b9c40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35512821", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:36:27,668 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ac4981b9c40582 completely filled. +2023-09-19 01:37:21,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac4981bbc20582. [clock=2023-09-19 01:37:21+00:00] +2023-09-19 01:37:21,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216011277871280252308269284 amount: 80. +2023-09-19 01:37:21,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223346966546271597141958659 amount: 80. +2023-09-19 01:37:21,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087441.0, "order_id": "x-XEKWYICXSSIUT605ac4981bbc20582", "exchange_order_id": "35512820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:37:21,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac4981bbc20582. +2023-09-19 01:37:22,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac4cc9b8c10582 for 80.00000000 SEI-USDT. +2023-09-19 01:37:22,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087442.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605ac4cc9b8c10582", "creation_timestamp": 1695087441.0, "exchange_order_id": "35513379", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:37:22,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac4cc9bbe90582 for 80.00000000 SEI-USDT. +2023-09-19 01:37:22,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087442.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac4cc9bbe90582", "creation_timestamp": 1695087441.0, "exchange_order_id": "35513380", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:38:16,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac4cc9b8c10582. [clock=2023-09-19 01:38:16+00:00] +2023-09-19 01:38:16,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac4cc9bbe90582. [clock=2023-09-19 01:38:16+00:00] +2023-09-19 01:38:16,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1210890388429596945829210461 amount: 80. +2023-09-19 01:38:16,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12190000 amount: 80. +2023-09-19 01:38:16,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087496.0, "order_id": "x-XEKWYICXBSIUT605ac4cc9b8c10582", "exchange_order_id": "35513379", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:38:16,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac4cc9b8c10582. +2023-09-19 01:38:16,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087496.0, "order_id": "x-XEKWYICXSSIUT605ac4cc9bbe90582", "exchange_order_id": "35513380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:38:16,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac4cc9bbe90582. +2023-09-19 01:38:18,724 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac50124fcb0582 for 80.00000000 SEI-USDT. +2023-09-19 01:38:18,816 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087497.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12100000", "order_id": "x-XEKWYICXBSIUT605ac50124fcb0582", "creation_timestamp": 1695087496.0, "exchange_order_id": "35513909", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:38:19,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac5012537b0582 for 80.00000000 SEI-USDT. +2023-09-19 01:38:19,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087498.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXSSIUT605ac5012537b0582", "creation_timestamp": 1695087496.0, "exchange_order_id": "35513911", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:39:11,768 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac50124fcb0582. [clock=2023-09-19 01:39:11+00:00] +2023-09-19 01:39:11,769 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac5012537b0582. [clock=2023-09-19 01:39:11+00:00] +2023-09-19 01:39:11,861 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1212475742459948604568461844 amount: 80. +2023-09-19 01:39:11,869 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12190000 amount: 80. +2023-09-19 01:39:12,596 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087552.0, "order_id": "x-XEKWYICXBSIUT605ac50124fcb0582", "exchange_order_id": "35513909", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:39:12,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac50124fcb0582. +2023-09-19 01:39:13,583 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ac5012537b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:39:13,601 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:39:13+00:00] +2023-09-19 01:39:13,713 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087553.0, "order_id": "x-XEKWYICXSSIUT605ac5012537b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12190000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00975200"}]}, "exchange_trade_id": "4987937", "exchange_order_id": "35513911", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:39:13,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087553.0, "order_id": "x-XEKWYICXSSIUT605ac5012537b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35513911", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:39:13,998 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ac5012537b0582 completely filled. +2023-09-19 01:39:13,999 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - WARNING - Failed to cancel order x-XEKWYICXSSIUT605ac5012537b0582 (order not found) +2023-09-19 01:39:14,537 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac536474c80582 for 80.00000000 SEI-USDT. +2023-09-19 01:39:14,636 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087554.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXSSIUT605ac536474c80582", "creation_timestamp": 1695087551.0, "exchange_order_id": "35514054", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:39:14,637 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac536470ea0582 for 80.00000000 SEI-USDT. +2023-09-19 01:39:14,676 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087554.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12120000", "order_id": "x-XEKWYICXBSIUT605ac536470ea0582", "creation_timestamp": 1695087551.0, "exchange_order_id": "35514055", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:39:14,679 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ac536474c80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 01:39:14,681 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 01:39:14+00:00] +2023-09-19 01:39:14,788 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087554.0, "order_id": "x-XEKWYICXSSIUT605ac536474c80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12190000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00975200"}]}, "exchange_trade_id": "4987945", "exchange_order_id": "35514054", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 01:39:15,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087554.0, "order_id": "x-XEKWYICXSSIUT605ac536474c80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.7520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35514054", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 01:39:15,212 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ac536474c80582 completely filled. +2023-09-19 01:40:06,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac536470ea0582. [clock=2023-09-19 01:40:06+00:00] +2023-09-19 01:40:06,267 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1217997612192730422162503381 amount: 80. +2023-09-19 01:40:06,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223515403657063630153035822 amount: 80. +2023-09-19 01:40:06,950 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087606.0, "order_id": "x-XEKWYICXBSIUT605ac536470ea0582", "exchange_order_id": "35514055", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:40:06,951 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac536470ea0582. +2023-09-19 01:40:07,487 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac56a2b44f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:40:07,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087607.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12170000", "order_id": "x-XEKWYICXBSIUT605ac56a2b44f0582", "creation_timestamp": 1695087606.0, "exchange_order_id": "35514260", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:40:08,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac56a2b83b0582 for 80.00000000 SEI-USDT. +2023-09-19 01:40:08,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087607.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac56a2b83b0582", "creation_timestamp": 1695087606.0, "exchange_order_id": "35514261", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:41:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac56a2b44f0582. [clock=2023-09-19 01:41:01+00:00] +2023-09-19 01:41:01,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac56a2b83b0582. [clock=2023-09-19 01:41:01+00:00] +2023-09-19 01:41:01,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216246715953737266458333261 amount: 80. +2023-09-19 01:41:01,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1222875248168589770259624697 amount: 80. +2023-09-19 01:41:01,127 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087661.0, "order_id": "x-XEKWYICXBSIUT605ac56a2b44f0582", "exchange_order_id": "35514260", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:41:01,127 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac56a2b44f0582. +2023-09-19 01:41:01,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac59e62a8e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:41:01,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605ac59e62a8e0582", "creation_timestamp": 1695087661.0, "exchange_order_id": "35514430", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:41:01,393 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac59e62da40582 for 80.00000000 SEI-USDT. +2023-09-19 01:41:01,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXSSIUT605ac59e62da40582", "creation_timestamp": 1695087661.0, "exchange_order_id": "35514431", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:41:01,556 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087661.0, "order_id": "x-XEKWYICXSSIUT605ac56a2b83b0582", "exchange_order_id": "35514261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:41:01,557 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac56a2b83b0582. +2023-09-19 01:41:56,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac59e62a8e0582. [clock=2023-09-19 01:41:56+00:00] +2023-09-19 01:41:56,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac59e62da40582. [clock=2023-09-19 01:41:56+00:00] +2023-09-19 01:41:56,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218010395706016075779127001 amount: 80. +2023-09-19 01:41:56,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224280541972975834910357563 amount: 80. +2023-09-19 01:41:56,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087716.0, "order_id": "x-XEKWYICXBSIUT605ac59e62a8e0582", "exchange_order_id": "35514430", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:41:56,335 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac59e62a8e0582. +2023-09-19 01:41:56,501 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087716.0, "order_id": "x-XEKWYICXSSIUT605ac59e62da40582", "exchange_order_id": "35514431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:41:56,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac59e62da40582. +2023-09-19 01:41:56,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac5d3033250582 for 80.00000000 SEI-USDT. +2023-09-19 01:41:56,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac5d3033250582", "creation_timestamp": 1695087716.0, "exchange_order_id": "35514576", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:41:56,574 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac5d3030df0582 for 80.00000000 SEI-USDT. +2023-09-19 01:41:56,592 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac5d3030df0582", "creation_timestamp": 1695087716.0, "exchange_order_id": "35514577", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:42:43,196 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 01:42:51,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac5d3030df0582. [clock=2023-09-19 01:42:51+00:00] +2023-09-19 01:42:51,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac5d3033250582. [clock=2023-09-19 01:42:51+00:00] +2023-09-19 01:42:51,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216760242504564238548061179 amount: 80. +2023-09-19 01:42:51,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1222747259184814560302246671 amount: 80. +2023-09-19 01:42:51,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087771.0, "order_id": "x-XEKWYICXBSIUT605ac5d3030df0582", "exchange_order_id": "35514577", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:42:51,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac5d3030df0582. +2023-09-19 01:42:52,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087772.0, "order_id": "x-XEKWYICXSSIUT605ac5d3033250582", "exchange_order_id": "35514576", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:42:52,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac5d3033250582. +2023-09-19 01:42:52,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac6076b4ab0582 for 80.00000000 SEI-USDT. +2023-09-19 01:42:52,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087772.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXSSIUT605ac6076b4ab0582", "creation_timestamp": 1695087771.0, "exchange_order_id": "35514737", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:42:52,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac6076b18a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:42:52,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087772.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605ac6076b18a0582", "creation_timestamp": 1695087771.0, "exchange_order_id": "35514738", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:43:46,314 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac6076b18a0582. [clock=2023-09-19 01:43:46+00:00] +2023-09-19 01:43:46,315 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac6076b4ab0582. [clock=2023-09-19 01:43:46+00:00] +2023-09-19 01:43:46,364 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218039584414361758498876034 amount: 80. +2023-09-19 01:43:46,365 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224923848569721677870567188 amount: 80. +2023-09-19 01:43:48,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087828.0, "order_id": "x-XEKWYICXBSIUT605ac6076b18a0582", "exchange_order_id": "35514738", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:43:48,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac6076b18a0582. +2023-09-19 01:43:48,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087828.0, "order_id": "x-XEKWYICXSSIUT605ac6076b4ab0582", "exchange_order_id": "35514737", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:43:48,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac6076b4ab0582. +2023-09-19 01:43:48,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac63c0eab40582 for 80.00000000 SEI-USDT. +2023-09-19 01:43:48,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087828.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac63c0eab40582", "creation_timestamp": 1695087826.0, "exchange_order_id": "35514940", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:43:48,752 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac63c10eb60582 for 80.00000000 SEI-USDT. +2023-09-19 01:43:48,808 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087828.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac63c10eb60582", "creation_timestamp": 1695087826.0, "exchange_order_id": "35514939", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:44:41,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac63c0eab40582. [clock=2023-09-19 01:44:41+00:00] +2023-09-19 01:44:41,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac63c10eb60582. [clock=2023-09-19 01:44:41+00:00] +2023-09-19 01:44:41,256 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218475305958739888743962386 amount: 80. +2023-09-19 01:44:41,257 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223828287414766251216405211 amount: 80. +2023-09-19 01:44:42,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087881.0, "order_id": "x-XEKWYICXBSIUT605ac63c0eab40582", "exchange_order_id": "35514940", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:44:42,071 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac63c0eab40582. +2023-09-19 01:44:44,017 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087883.0, "order_id": "x-XEKWYICXSSIUT605ac63c10eb60582", "exchange_order_id": "35514939", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:44:44,026 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac63c10eb60582. +2023-09-19 01:44:44,598 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac670685840582 for 80.00000000 SEI-USDT. +2023-09-19 01:44:44,663 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087883.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac670685840582", "creation_timestamp": 1695087881.0, "exchange_order_id": "35515036", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:44:44,665 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac670681770582 for 80.00000000 SEI-USDT. +2023-09-19 01:44:44,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087884.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac670681770582", "creation_timestamp": 1695087881.0, "exchange_order_id": "35515035", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:45:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac670681770582. [clock=2023-09-19 01:45:36+00:00] +2023-09-19 01:45:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac670685840582. [clock=2023-09-19 01:45:36+00:00] +2023-09-19 01:45:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1217498622591349208537005951 amount: 80. +2023-09-19 01:45:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1222769816607330369445357155 amount: 80. +2023-09-19 01:45:36,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087936.0, "order_id": "x-XEKWYICXBSIUT605ac670681770582", "exchange_order_id": "35515035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:45:36,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac670681770582. +2023-09-19 01:45:36,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087936.0, "order_id": "x-XEKWYICXSSIUT605ac670685840582", "exchange_order_id": "35515036", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:45:36,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac670685840582. +2023-09-19 01:45:36,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac6a4a1d9c0582 for 80.00000000 SEI-USDT. +2023-09-19 01:45:36,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXSSIUT605ac6a4a1d9c0582", "creation_timestamp": 1695087936.0, "exchange_order_id": "35515194", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:45:36,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac6a4a1ae00582 for 80.00000000 SEI-USDT. +2023-09-19 01:45:36,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12170000", "order_id": "x-XEKWYICXBSIUT605ac6a4a1ae00582", "creation_timestamp": 1695087936.0, "exchange_order_id": "35515195", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:46:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac6a4a1ae00582. [clock=2023-09-19 01:46:31+00:00] +2023-09-19 01:46:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac6a4a1d9c0582. [clock=2023-09-19 01:46:31+00:00] +2023-09-19 01:46:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1216503545256618320843975512 amount: 80. +2023-09-19 01:46:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223447972280118025758323799 amount: 80. +2023-09-19 01:46:31,051 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087991.0, "order_id": "x-XEKWYICXBSIUT605ac6a4a1ae00582", "exchange_order_id": "35515195", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:46:31,051 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac6a4a1ae00582. +2023-09-19 01:46:31,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087991.0, "order_id": "x-XEKWYICXSSIUT605ac6a4a1d9c0582", "exchange_order_id": "35515194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:46:31,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac6a4a1d9c0582. +2023-09-19 01:46:31,218 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac6d9151900582 for 80.00000000 SEI-USDT. +2023-09-19 01:46:31,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac6d9151900582", "creation_timestamp": 1695087991.0, "exchange_order_id": "35515525", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:46:31,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac6d914f4e0582 for 80.00000000 SEI-USDT. +2023-09-19 01:46:31,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695087991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12160000", "order_id": "x-XEKWYICXBSIUT605ac6d914f4e0582", "creation_timestamp": 1695087991.0, "exchange_order_id": "35515526", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:47:26,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac6d914f4e0582. [clock=2023-09-19 01:47:26+00:00] +2023-09-19 01:47:26,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac6d9151900582. [clock=2023-09-19 01:47:26+00:00] +2023-09-19 01:47:26,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218462085053267523576099609 amount: 80. +2023-09-19 01:47:26,218 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223864131306168404308594565 amount: 80. +2023-09-19 01:47:26,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088046.0, "order_id": "x-XEKWYICXBSIUT605ac6d914f4e0582", "exchange_order_id": "35515526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:47:26,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac6d914f4e0582. +2023-09-19 01:47:27,383 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088047.0, "order_id": "x-XEKWYICXSSIUT605ac6d9151900582", "exchange_order_id": "35515525", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:47:27,383 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac6d9151900582. +2023-09-19 01:47:27,684 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac70db9ca40582 for 80.00000000 SEI-USDT. +2023-09-19 01:47:27,723 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088047.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac70db9ca40582", "creation_timestamp": 1695088046.0, "exchange_order_id": "35515823", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:47:27,723 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac70db9f860582 for 80.00000000 SEI-USDT. +2023-09-19 01:47:27,756 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088047.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac70db9f860582", "creation_timestamp": 1695088046.0, "exchange_order_id": "35515822", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:48:21,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac70db9ca40582. [clock=2023-09-19 01:48:21+00:00] +2023-09-19 01:48:21,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac70db9f860582. [clock=2023-09-19 01:48:21+00:00] +2023-09-19 01:48:21,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1217214758088401360724169297 amount: 80. +2023-09-19 01:48:21,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224256095971281296098004587 amount: 80. +2023-09-19 01:48:21,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088101.0, "order_id": "x-XEKWYICXBSIUT605ac70db9ca40582", "exchange_order_id": "35515823", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:48:21,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac70db9ca40582. +2023-09-19 01:48:22,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088102.0, "order_id": "x-XEKWYICXSSIUT605ac70db9f860582", "exchange_order_id": "35515822", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:48:22,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac70db9f860582. +2023-09-19 01:48:22,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac7421e6550582 for 80.00000000 SEI-USDT. +2023-09-19 01:48:22,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088102.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac7421e6550582", "creation_timestamp": 1695088101.0, "exchange_order_id": "35516039", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:48:22,513 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac7421e4510582 for 80.00000000 SEI-USDT. +2023-09-19 01:48:22,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088102.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12170000", "order_id": "x-XEKWYICXBSIUT605ac7421e4510582", "creation_timestamp": 1695088101.0, "exchange_order_id": "35516038", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:49:16,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac7421e4510582. [clock=2023-09-19 01:49:16+00:00] +2023-09-19 01:49:16,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac7421e6550582. [clock=2023-09-19 01:49:16+00:00] +2023-09-19 01:49:16,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218612039617120063460051440 amount: 80. +2023-09-19 01:49:16,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224091400629401256726542521 amount: 80. +2023-09-19 01:49:17,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088156.0, "order_id": "x-XEKWYICXBSIUT605ac7421e4510582", "exchange_order_id": "35516038", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:49:17,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac7421e4510582. +2023-09-19 01:49:18,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088157.0, "order_id": "x-XEKWYICXSSIUT605ac7421e6550582", "exchange_order_id": "35516039", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:49:18,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac7421e6550582. +2023-09-19 01:49:18,997 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac776b2f400582 for 80.00000000 SEI-USDT. +2023-09-19 01:49:19,055 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088158.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac776b2f400582", "creation_timestamp": 1695088156.0, "exchange_order_id": "35516179", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:49:19,056 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac776b04910582 for 80.00000000 SEI-USDT. +2023-09-19 01:49:19,092 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088158.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac776b04910582", "creation_timestamp": 1695088156.0, "exchange_order_id": "35516180", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:50:11,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac776b04910582. [clock=2023-09-19 01:50:11+00:00] +2023-09-19 01:50:11,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac776b2f400582. [clock=2023-09-19 01:50:11+00:00] +2023-09-19 01:50:11,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218920428189632077500306507 amount: 80. +2023-09-19 01:50:11,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223181103285827665185760608 amount: 80. +2023-09-19 01:50:11,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088211.0, "order_id": "x-XEKWYICXBSIUT605ac776b04910582", "exchange_order_id": "35516180", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:50:11,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac776b04910582. +2023-09-19 01:50:11,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088211.0, "order_id": "x-XEKWYICXSSIUT605ac776b2f400582", "exchange_order_id": "35516179", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:50:11,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac776b2f400582. +2023-09-19 01:50:11,465 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac7aaf408f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:50:11,478 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac7aaf408f0582", "creation_timestamp": 1695088211.0, "exchange_order_id": "35516261", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:50:11,481 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac7aaf42700582 for 80.00000000 SEI-USDT. +2023-09-19 01:50:11,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac7aaf42700582", "creation_timestamp": 1695088211.0, "exchange_order_id": "35516262", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:51:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac7aaf408f0582. [clock=2023-09-19 01:51:06+00:00] +2023-09-19 01:51:06,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac7aaf42700582. [clock=2023-09-19 01:51:06+00:00] +2023-09-19 01:51:06,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219160670585917256165768180 amount: 80. +2023-09-19 01:51:06,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1222473895335844791119628820 amount: 80. +2023-09-19 01:51:06,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088266.0, "order_id": "x-XEKWYICXBSIUT605ac7aaf408f0582", "exchange_order_id": "35516261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:51:06,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac7aaf408f0582. +2023-09-19 01:51:06,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac7df600790582 for 80.00000000 SEI-USDT. +2023-09-19 01:51:06,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ac7df600790582", "creation_timestamp": 1695088266.0, "exchange_order_id": "35516370", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:51:06,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac7df603040582 for 80.00000000 SEI-USDT. +2023-09-19 01:51:06,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXSSIUT605ac7df603040582", "creation_timestamp": 1695088266.0, "exchange_order_id": "35516369", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:51:06,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088266.0, "order_id": "x-XEKWYICXSSIUT605ac7aaf42700582", "exchange_order_id": "35516262", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:51:06,550 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac7aaf42700582. +2023-09-19 01:52:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac7df600790582. [clock=2023-09-19 01:52:01+00:00] +2023-09-19 01:52:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac7df603040582. [clock=2023-09-19 01:52:01+00:00] +2023-09-19 01:52:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219370492043268103165895386 amount: 80. +2023-09-19 01:52:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223794719882019247847616812 amount: 80. +2023-09-19 01:52:01,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088321.0, "order_id": "x-XEKWYICXBSIUT605ac7df600790582", "exchange_order_id": "35516370", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:52:01,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac7df600790582. +2023-09-19 01:52:01,148 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088321.0, "order_id": "x-XEKWYICXSSIUT605ac7df603040582", "exchange_order_id": "35516369", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:52:01,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac7df603040582. +2023-09-19 01:52:01,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac813cb7030582 for 80.00000000 SEI-USDT. +2023-09-19 01:52:01,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ac813cb7030582", "creation_timestamp": 1695088321.0, "exchange_order_id": "35516525", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:52:01,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac813cba400582 for 80.00000000 SEI-USDT. +2023-09-19 01:52:01,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac813cba400582", "creation_timestamp": 1695088321.0, "exchange_order_id": "35516526", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:52:56,475 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac813cb7030582. [clock=2023-09-19 01:52:56+00:00] +2023-09-19 01:52:56,485 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac813cba400582. [clock=2023-09-19 01:52:56+00:00] +2023-09-19 01:52:56,527 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218839073410940212665702422 amount: 80. +2023-09-19 01:52:56,537 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223393773682191365385662815 amount: 80. +2023-09-19 01:52:57,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088376.0, "order_id": "x-XEKWYICXBSIUT605ac813cb7030582", "exchange_order_id": "35516525", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:52:57,529 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac813cb7030582. +2023-09-19 01:52:58,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088378.0, "order_id": "x-XEKWYICXSSIUT605ac813cba400582", "exchange_order_id": "35516526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:52:58,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac813cba400582. +2023-09-19 01:52:58,580 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac848be7730582 for 80.00000000 SEI-USDT. +2023-09-19 01:52:58,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088378.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac848be7730582", "creation_timestamp": 1695088376.0, "exchange_order_id": "35516851", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:52:58,609 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac848be1700582 for 80.00000000 SEI-USDT. +2023-09-19 01:52:58,639 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088378.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ac848be1700582", "creation_timestamp": 1695088376.0, "exchange_order_id": "35516852", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:53:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac848be1700582. [clock=2023-09-19 01:53:51+00:00] +2023-09-19 01:53:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac848be7730582. [clock=2023-09-19 01:53:51+00:00] +2023-09-19 01:53:51,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219261071723577471811431299 amount: 80. +2023-09-19 01:53:51,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1223913725889920619402333637 amount: 80. +2023-09-19 01:53:51,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088431.0, "order_id": "x-XEKWYICXBSIUT605ac848be1700582", "exchange_order_id": "35516852", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:53:51,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac848be1700582. +2023-09-19 01:53:52,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088432.0, "order_id": "x-XEKWYICXSSIUT605ac848be7730582", "exchange_order_id": "35516851", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:53:52,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac848be7730582. +2023-09-19 01:53:52,636 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac87cbfd650582 for 80.00000000 SEI-USDT. +2023-09-19 01:53:52,676 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088432.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ac87cbfd650582", "creation_timestamp": 1695088431.0, "exchange_order_id": "35516945", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:53:52,676 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac87cc01700582 for 80.00000000 SEI-USDT. +2023-09-19 01:53:52,699 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088432.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXSSIUT605ac87cc01700582", "creation_timestamp": 1695088431.0, "exchange_order_id": "35516946", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:54:46,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac87cbfd650582. [clock=2023-09-19 01:54:46+00:00] +2023-09-19 01:54:46,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac87cc01700582. [clock=2023-09-19 01:54:46+00:00] +2023-09-19 01:54:46,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220248066186928703730582384 amount: 80. +2023-09-19 01:54:46,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1224982422996999329511027730 amount: 80. +2023-09-19 01:54:46,556 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088486.0, "order_id": "x-XEKWYICXBSIUT605ac87cbfd650582", "exchange_order_id": "35516945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:54:46,556 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac87cbfd650582. +2023-09-19 01:54:47,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088487.0, "order_id": "x-XEKWYICXSSIUT605ac87cc01700582", "exchange_order_id": "35516946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:54:47,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac87cc01700582. +2023-09-19 01:54:47,904 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac8b14627d0582 for 80.00000000 SEI-USDT. +2023-09-19 01:54:47,959 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088487.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXSSIUT605ac8b14627d0582", "creation_timestamp": 1695088486.0, "exchange_order_id": "35517194", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:54:47,960 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac8b14608a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:54:48,016 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088487.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac8b14608a0582", "creation_timestamp": 1695088486.0, "exchange_order_id": "35517193", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:55:41,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac8b14608a0582. [clock=2023-09-19 01:55:41+00:00] +2023-09-19 01:55:41,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac8b14627d0582. [clock=2023-09-19 01:55:41+00:00] +2023-09-19 01:55:41,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220635967103962338754855360 amount: 80. +2023-09-19 01:55:41,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225428535910067339846084110 amount: 80. +2023-09-19 01:55:41,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088541.0, "order_id": "x-XEKWYICXBSIUT605ac8b14608a0582", "exchange_order_id": "35517193", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:55:41,376 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac8b14608a0582. +2023-09-19 01:55:41,612 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac8e5c74450582 for 80.00000000 SEI-USDT. +2023-09-19 01:55:41,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ac8e5c74450582", "creation_timestamp": 1695088541.0, "exchange_order_id": "35517260", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:55:41,768 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088541.0, "order_id": "x-XEKWYICXSSIUT605ac8b14627d0582", "exchange_order_id": "35517194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:55:41,769 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac8b14627d0582. +2023-09-19 01:55:41,770 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac8e5c721f0582 for 80.00000000 SEI-USDT. +2023-09-19 01:55:41,787 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ac8e5c721f0582", "creation_timestamp": 1695088541.0, "exchange_order_id": "35517261", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:56:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac8e5c721f0582. [clock=2023-09-19 01:56:36+00:00] +2023-09-19 01:56:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac8e5c74450582. [clock=2023-09-19 01:56:36+00:00] +2023-09-19 01:56:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1221677242592336624305191200 amount: 80. +2023-09-19 01:56:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226521067594255657036647929 amount: 80. +2023-09-19 01:56:36,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088596.0, "order_id": "x-XEKWYICXBSIUT605ac8e5c721f0582", "exchange_order_id": "35517261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:56:36,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac8e5c721f0582. +2023-09-19 01:56:36,355 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac91a0e6520582 for 80.00000000 SEI-USDT. +2023-09-19 01:56:36,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12210000", "order_id": "x-XEKWYICXBSIUT605ac91a0e6520582", "creation_timestamp": 1695088596.0, "exchange_order_id": "35517469", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:56:36,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088596.0, "order_id": "x-XEKWYICXSSIUT605ac8e5c74450582", "exchange_order_id": "35517260", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:56:36,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac8e5c74450582. +2023-09-19 01:56:36,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac91a0e9e70582 for 80.00000000 SEI-USDT. +2023-09-19 01:56:36,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ac91a0e9e70582", "creation_timestamp": 1695088596.0, "exchange_order_id": "35517470", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:57:31,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac91a0e6520582. [clock=2023-09-19 01:57:31+00:00] +2023-09-19 01:57:31,230 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac91a0e9e70582. [clock=2023-09-19 01:57:31+00:00] +2023-09-19 01:57:31,291 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12220000 amount: 80. +2023-09-19 01:57:31,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229326530044427885656664378 amount: 80. +2023-09-19 01:57:31,983 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088651.0, "order_id": "x-XEKWYICXBSIUT605ac91a0e6520582", "exchange_order_id": "35517469", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:57:31,984 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac91a0e6520582. +2023-09-19 01:57:33,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088653.0, "order_id": "x-XEKWYICXSSIUT605ac91a0e9e70582", "exchange_order_id": "35517470", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:57:33,516 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac91a0e9e70582. +2023-09-19 01:57:34,404 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac94ec84920582 for 80.00000000 SEI-USDT. +2023-09-19 01:57:34,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088653.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ac94ec84920582", "creation_timestamp": 1695088651.0, "exchange_order_id": "35517881", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:57:34,466 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac94ec82470582 for 80.00000000 SEI-USDT. +2023-09-19 01:57:34,512 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088653.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12220000", "order_id": "x-XEKWYICXBSIUT605ac94ec82470582", "creation_timestamp": 1695088651.0, "exchange_order_id": "35517880", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:58:26,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac94ec82470582. [clock=2023-09-19 01:58:26+00:00] +2023-09-19 01:58:26,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac94ec84920582. [clock=2023-09-19 01:58:26+00:00] +2023-09-19 01:58:26,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12230000 amount: 80. +2023-09-19 01:58:26,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230117157832905255715886305 amount: 80. +2023-09-19 01:58:26,987 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088706.0, "order_id": "x-XEKWYICXBSIUT605ac94ec82470582", "exchange_order_id": "35517880", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:58:26,987 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac94ec82470582. +2023-09-19 01:58:28,509 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088708.0, "order_id": "x-XEKWYICXSSIUT605ac94ec84920582", "exchange_order_id": "35517881", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:58:28,509 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac94ec84920582. +2023-09-19 01:58:28,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac98336d9a0582 for 80.00000000 SEI-USDT. +2023-09-19 01:58:28,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088708.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ac98336d9a0582", "creation_timestamp": 1695088706.0, "exchange_order_id": "35518259", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:58:28,548 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac983370c50582 for 80.00000000 SEI-USDT. +2023-09-19 01:58:28,589 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088708.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ac983370c50582", "creation_timestamp": 1695088706.0, "exchange_order_id": "35518258", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:59:21,175 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac98336d9a0582. [clock=2023-09-19 01:59:21+00:00] +2023-09-19 01:59:21,176 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac983370c50582. [clock=2023-09-19 01:59:21+00:00] +2023-09-19 01:59:21,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12240000 amount: 80. +2023-09-19 01:59:21,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230959241803424353651975555 amount: 80. +2023-09-19 01:59:21,589 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088761.0, "order_id": "x-XEKWYICXBSIUT605ac98336d9a0582", "exchange_order_id": "35518259", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:59:21,589 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac98336d9a0582. +2023-09-19 01:59:22,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088762.0, "order_id": "x-XEKWYICXSSIUT605ac983370c50582", "exchange_order_id": "35518258", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 01:59:22,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac983370c50582. +2023-09-19 01:59:22,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac9b797db70582 for 80.00000000 SEI-USDT. +2023-09-19 01:59:22,540 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088762.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605ac9b797db70582", "creation_timestamp": 1695088761.0, "exchange_order_id": "35518516", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 01:59:22,541 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac9b79800b0582 for 80.00000000 SEI-USDT. +2023-09-19 01:59:22,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088762.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ac9b79800b0582", "creation_timestamp": 1695088761.0, "exchange_order_id": "35518515", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:00:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac9b797db70582. [clock=2023-09-19 02:00:16+00:00] +2023-09-19 02:00:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac9b79800b0582. [clock=2023-09-19 02:00:16+00:00] +2023-09-19 02:00:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12250000 amount: 80. +2023-09-19 02:00:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231831747339691342989856845 amount: 80. +2023-09-19 02:00:16,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088816.0, "order_id": "x-XEKWYICXBSIUT605ac9b797db70582", "exchange_order_id": "35518516", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:00:16,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac9b797db70582. +2023-09-19 02:00:16,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088816.0, "order_id": "x-XEKWYICXSSIUT605ac9b79800b0582", "exchange_order_id": "35518515", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:00:16,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac9b79800b0582. +2023-09-19 02:00:16,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ac9ebdd6460582 for 80.00000000 SEI-USDT. +2023-09-19 02:00:16,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ac9ebdd6460582", "creation_timestamp": 1695088816.0, "exchange_order_id": "35518903", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:00:16,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ac9ebdd3390582 for 80.00000000 SEI-USDT. +2023-09-19 02:00:16,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ac9ebdd3390582", "creation_timestamp": 1695088816.0, "exchange_order_id": "35518902", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:01:11,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ac9ebdd3390582. [clock=2023-09-19 02:01:11+00:00] +2023-09-19 02:01:11,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ac9ebdd6460582. [clock=2023-09-19 02:01:11+00:00] +2023-09-19 02:01:11,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12250000 amount: 80. +2023-09-19 02:01:11,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230310866321614832998161010 amount: 80. +2023-09-19 02:01:11,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088871.0, "order_id": "x-XEKWYICXBSIUT605ac9ebdd3390582", "exchange_order_id": "35518902", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:01:11,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ac9ebdd3390582. +2023-09-19 02:01:11,396 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088871.0, "order_id": "x-XEKWYICXSSIUT605ac9ebdd6460582", "exchange_order_id": "35518903", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:01:11,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ac9ebdd6460582. +2023-09-19 02:01:11,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aca205caf80582 for 80.00000000 SEI-USDT. +2023-09-19 02:01:11,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605aca205caf80582", "creation_timestamp": 1695088871.0, "exchange_order_id": "35519251", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:01:11,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aca205c7a60582 for 80.00000000 SEI-USDT. +2023-09-19 02:01:11,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605aca205c7a60582", "creation_timestamp": 1695088871.0, "exchange_order_id": "35519252", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:02:06,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aca205c7a60582. [clock=2023-09-19 02:02:06+00:00] +2023-09-19 02:02:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aca205caf80582. [clock=2023-09-19 02:02:06+00:00] +2023-09-19 02:02:06,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12260000 amount: 80. +2023-09-19 02:02:06,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232339823455605027340076189 amount: 80. +2023-09-19 02:02:06,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088926.0, "order_id": "x-XEKWYICXBSIUT605aca205c7a60582", "exchange_order_id": "35519252", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:02:06,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aca205c7a60582. +2023-09-19 02:02:06,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088926.0, "order_id": "x-XEKWYICXSSIUT605aca205caf80582", "exchange_order_id": "35519251", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:02:06,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aca205caf80582. +2023-09-19 02:02:06,661 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aca54ca9580582 for 80.00000000 SEI-USDT. +2023-09-19 02:02:06,683 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605aca54ca9580582", "creation_timestamp": 1695088926.0, "exchange_order_id": "35519681", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:02:06,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aca54ca7460582 for 80.00000000 SEI-USDT. +2023-09-19 02:02:06,725 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605aca54ca7460582", "creation_timestamp": 1695088926.0, "exchange_order_id": "35519680", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:03:01,151 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aca54ca7460582. [clock=2023-09-19 02:03:01+00:00] +2023-09-19 02:03:01,153 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aca54ca9580582. [clock=2023-09-19 02:03:01+00:00] +2023-09-19 02:03:01,244 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12270000 amount: 80. +2023-09-19 02:03:01,246 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234761291621153513431003910 amount: 80. +2023-09-19 02:03:02,073 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088981.0, "order_id": "x-XEKWYICXBSIUT605aca54ca7460582", "exchange_order_id": "35519680", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:03:02,074 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aca54ca7460582. +2023-09-19 02:03:03,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088983.0, "order_id": "x-XEKWYICXSSIUT605aca54ca9580582", "exchange_order_id": "35519681", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:03:03,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aca54ca9580582. +2023-09-19 02:03:03,924 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aca8973ba70582 for 80.00000000 SEI-USDT. +2023-09-19 02:03:03,975 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088983.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605aca8973ba70582", "creation_timestamp": 1695088981.0, "exchange_order_id": "35519995", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:03:03,976 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aca89702c50582 for 80.00000000 SEI-USDT. +2023-09-19 02:03:04,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695088983.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605aca89702c50582", "creation_timestamp": 1695088981.0, "exchange_order_id": "35519996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:03:56,170 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aca89702c50582. [clock=2023-09-19 02:03:56+00:00] +2023-09-19 02:03:56,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aca8973ba70582. [clock=2023-09-19 02:03:56+00:00] +2023-09-19 02:03:56,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12280000 amount: 80. +2023-09-19 02:03:56,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235454940292478503565823862 amount: 80. +2023-09-19 02:03:57,097 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089036.0, "order_id": "x-XEKWYICXBSIUT605aca89702c50582", "exchange_order_id": "35519996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:03:57,098 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aca89702c50582. +2023-09-19 02:03:58,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089038.0, "order_id": "x-XEKWYICXSSIUT605aca8973ba70582", "exchange_order_id": "35519995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:03:58,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aca8973ba70582. +2023-09-19 02:03:58,697 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acabde17340582 for 80.00000000 SEI-USDT. +2023-09-19 02:03:58,733 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089038.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acabde17340582", "creation_timestamp": 1695089036.0, "exchange_order_id": "35520303", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:03:58,748 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acabde13600582 for 80.00000000 SEI-USDT. +2023-09-19 02:03:58,824 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089038.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605acabde13600582", "creation_timestamp": 1695089036.0, "exchange_order_id": "35520304", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:04:51,152 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acabde13600582. [clock=2023-09-19 02:04:51+00:00] +2023-09-19 02:04:51,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acabde17340582. [clock=2023-09-19 02:04:51+00:00] +2023-09-19 02:04:51,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12280000 amount: 80. +2023-09-19 02:04:51,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235210696168173098835310615 amount: 80. +2023-09-19 02:04:51,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089091.0, "order_id": "x-XEKWYICXBSIUT605acabde13600582", "exchange_order_id": "35520304", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:04:51,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acabde13600582. +2023-09-19 02:04:52,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089092.0, "order_id": "x-XEKWYICXSSIUT605acabde17340582", "exchange_order_id": "35520303", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:04:52,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acabde17340582. +2023-09-19 02:04:52,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acaf24e48a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:04:52,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089092.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605acaf24e48a0582", "creation_timestamp": 1695089091.0, "exchange_order_id": "35520507", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:04:52,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acaf2508a60582 for 80.00000000 SEI-USDT. +2023-09-19 02:04:52,623 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089092.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acaf2508a60582", "creation_timestamp": 1695089091.0, "exchange_order_id": "35520506", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:05:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acaf24e48a0582. [clock=2023-09-19 02:05:46+00:00] +2023-09-19 02:05:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acaf2508a60582. [clock=2023-09-19 02:05:46+00:00] +2023-09-19 02:05:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12280000 amount: 80. +2023-09-19 02:05:46,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233605962281487441914954440 amount: 80. +2023-09-19 02:05:46,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089146.0, "order_id": "x-XEKWYICXBSIUT605acaf24e48a0582", "exchange_order_id": "35520507", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:05:46,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acaf24e48a0582. +2023-09-19 02:05:46,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089146.0, "order_id": "x-XEKWYICXSSIUT605acaf2508a60582", "exchange_order_id": "35520506", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:05:46,453 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acaf2508a60582. +2023-09-19 02:05:46,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acb269338a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:05:46,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605acb269338a0582", "creation_timestamp": 1695089146.0, "exchange_order_id": "35520659", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:05:46,551 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acb269376c0582 for 80.00000000 SEI-USDT. +2023-09-19 02:05:46,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605acb269376c0582", "creation_timestamp": 1695089146.0, "exchange_order_id": "35520658", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:06:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acb269338a0582. [clock=2023-09-19 02:06:41+00:00] +2023-09-19 02:06:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acb269376c0582. [clock=2023-09-19 02:06:41+00:00] +2023-09-19 02:06:41,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12300000 amount: 80. +2023-09-19 02:06:41,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235782698975365366047024802 amount: 80. +2023-09-19 02:06:41,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089201.0, "order_id": "x-XEKWYICXBSIUT605acb269338a0582", "exchange_order_id": "35520659", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:06:41,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acb269338a0582. +2023-09-19 02:06:41,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acb5b075be0582 for 80.00000000 SEI-USDT. +2023-09-19 02:06:41,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605acb5b075be0582", "creation_timestamp": 1695089201.0, "exchange_order_id": "35521044", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:06:41,377 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089201.0, "order_id": "x-XEKWYICXSSIUT605acb269376c0582", "exchange_order_id": "35520658", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:06:41,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acb269376c0582. +2023-09-19 02:06:41,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acb5b078ab0582 for 80.00000000 SEI-USDT. +2023-09-19 02:06:41,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acb5b078ab0582", "creation_timestamp": 1695089201.0, "exchange_order_id": "35521045", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:07:36,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acb5b075be0582. [clock=2023-09-19 02:07:36+00:00] +2023-09-19 02:07:36,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acb5b078ab0582. [clock=2023-09-19 02:07:36+00:00] +2023-09-19 02:07:36,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 02:07:36,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238330550541265772152548422 amount: 80. +2023-09-19 02:07:37,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089256.0, "order_id": "x-XEKWYICXBSIUT605acb5b075be0582", "exchange_order_id": "35521044", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:07:37,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acb5b075be0582. +2023-09-19 02:07:38,571 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089258.0, "order_id": "x-XEKWYICXSSIUT605acb5b078ab0582", "exchange_order_id": "35521045", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:07:38,571 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acb5b078ab0582. +2023-09-19 02:07:38,924 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acb8f8e9d10582 for 80.00000000 SEI-USDT. +2023-09-19 02:07:38,976 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089258.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605acb8f8e9d10582", "creation_timestamp": 1695089256.0, "exchange_order_id": "35521359", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:07:38,977 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acb8f90cf10582 for 80.00000000 SEI-USDT. +2023-09-19 02:07:39,018 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089258.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605acb8f90cf10582", "creation_timestamp": 1695089256.0, "exchange_order_id": "35521358", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:08:31,324 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acb8f8e9d10582. [clock=2023-09-19 02:08:31+00:00] +2023-09-19 02:08:31,325 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acb8f90cf10582. [clock=2023-09-19 02:08:31+00:00] +2023-09-19 02:08:31,377 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 02:08:31,378 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236698473857102035505119555 amount: 80. +2023-09-19 02:08:31,949 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089311.0, "order_id": "x-XEKWYICXBSIUT605acb8f8e9d10582", "exchange_order_id": "35521359", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:08:31,949 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acb8f8e9d10582. +2023-09-19 02:08:33,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089313.0, "order_id": "x-XEKWYICXSSIUT605acb8f90cf10582", "exchange_order_id": "35521358", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:08:33,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acb8f90cf10582. +2023-09-19 02:08:34,044 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acbc446f000582 for 80.00000000 SEI-USDT. +2023-09-19 02:08:34,098 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089313.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605acbc446f000582", "creation_timestamp": 1695089311.0, "exchange_order_id": "35521450", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:08:34,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acbc446c360582 for 80.00000000 SEI-USDT. +2023-09-19 02:08:34,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089313.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605acbc446c360582", "creation_timestamp": 1695089311.0, "exchange_order_id": "35521451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:08:39,345 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acbc446c360582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:08:39,358 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:08:39+00:00] +2023-09-19 02:08:39,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089319.0, "order_id": "x-XEKWYICXBSIUT605acbc446c360582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988463", "exchange_order_id": "35521451", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:08:39,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089319.0, "order_id": "x-XEKWYICXBSIUT605acbc446c360582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35521451", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:08:39,428 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acbc446c360582 completely filled. +2023-09-19 02:09:26,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acbc446f000582. [clock=2023-09-19 02:09:26+00:00] +2023-09-19 02:09:26,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:09:26,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237095471985822406550110703 amount: 80. +2023-09-19 02:09:26,778 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089366.0, "order_id": "x-XEKWYICXSSIUT605acbc446f000582", "exchange_order_id": "35521450", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:09:26,778 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acbc446f000582. +2023-09-19 02:09:27,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acbf8942120582 for 80.00000000 SEI-USDT. +2023-09-19 02:09:27,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acbf8942120582", "creation_timestamp": 1695089366.0, "exchange_order_id": "35521800", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:09:27,772 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acbf8945570582 for 80.00000000 SEI-USDT. +2023-09-19 02:09:27,806 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605acbf8945570582", "creation_timestamp": 1695089366.0, "exchange_order_id": "35521801", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:09:36,753 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acbf8942120582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:09:36,754 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:09:36+00:00] +2023-09-19 02:09:36,852 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089376.0, "order_id": "x-XEKWYICXBSIUT605acbf8942120582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988488", "exchange_order_id": "35521800", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:09:36,923 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089376.0, "order_id": "x-XEKWYICXBSIUT605acbf8942120582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35521800", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:09:36,923 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acbf8942120582 completely filled. +2023-09-19 02:10:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acbf8945570582. [clock=2023-09-19 02:10:21+00:00] +2023-09-19 02:10:21,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:10:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235616779874236345204338277 amount: 80. +2023-09-19 02:10:21,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089421.0, "order_id": "x-XEKWYICXSSIUT605acbf8945570582", "exchange_order_id": "35521801", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:10:21,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acbf8945570582. +2023-09-19 02:10:21,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acc2cd62ef0582 for 80.00000000 SEI-USDT. +2023-09-19 02:10:21,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acc2cd62ef0582", "creation_timestamp": 1695089421.0, "exchange_order_id": "35522061", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:10:21,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acc2cd5fd50582 for 80.00000000 SEI-USDT. +2023-09-19 02:10:21,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acc2cd5fd50582", "creation_timestamp": 1695089421.0, "exchange_order_id": "35522062", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:10:38,824 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acc2cd5fd50582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:10:38,825 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:10:38+00:00] +2023-09-19 02:10:38,867 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089438.0, "order_id": "x-XEKWYICXBSIUT605acc2cd5fd50582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988501", "exchange_order_id": "35522062", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:10:38,886 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089438.0, "order_id": "x-XEKWYICXBSIUT605acc2cd5fd50582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35522062", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:10:38,886 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acc2cd5fd50582 completely filled. +2023-09-19 02:11:16,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acc2cd62ef0582. [clock=2023-09-19 02:11:16+00:00] +2023-09-19 02:11:16,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231916952158419903286632133 amount: 80. +2023-09-19 02:11:16,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234544591429180766930762587 amount: 80. +2023-09-19 02:11:16,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089476.0, "order_id": "x-XEKWYICXSSIUT605acc2cd62ef0582", "exchange_order_id": "35522061", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:11:16,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acc2cd62ef0582. +2023-09-19 02:11:16,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acc61567020582 for 80.00000000 SEI-USDT. +2023-09-19 02:11:16,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605acc61567020582", "creation_timestamp": 1695089476.0, "exchange_order_id": "35522456", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:11:16,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acc61564d30582 for 80.00000000 SEI-USDT. +2023-09-19 02:11:16,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605acc61564d30582", "creation_timestamp": 1695089476.0, "exchange_order_id": "35522457", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:12:11,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acc61564d30582. [clock=2023-09-19 02:12:11+00:00] +2023-09-19 02:12:11,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acc61567020582. [clock=2023-09-19 02:12:11+00:00] +2023-09-19 02:12:11,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230899915691216493093726820 amount: 80. +2023-09-19 02:12:11,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234056492511494306262060742 amount: 80. +2023-09-19 02:12:11,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089531.0, "order_id": "x-XEKWYICXBSIUT605acc61564d30582", "exchange_order_id": "35522457", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:12:11,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acc61564d30582. +2023-09-19 02:12:11,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089531.0, "order_id": "x-XEKWYICXSSIUT605acc61567020582", "exchange_order_id": "35522456", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:12:11,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acc61567020582. +2023-09-19 02:12:11,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acc95c862a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:12:11,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605acc95c862a0582", "creation_timestamp": 1695089531.0, "exchange_order_id": "35522803", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:12:11,478 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acc95c893f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:12:11,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605acc95c893f0582", "creation_timestamp": 1695089531.0, "exchange_order_id": "35522804", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:12:43,786 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 02:13:06,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acc95c862a0582. [clock=2023-09-19 02:13:06+00:00] +2023-09-19 02:13:06,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acc95c893f0582. [clock=2023-09-19 02:13:06+00:00] +2023-09-19 02:13:06,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230923001398002851900404310 amount: 80. +2023-09-19 02:13:06,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233377463378030806522746708 amount: 80. +2023-09-19 02:13:07,061 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089586.0, "order_id": "x-XEKWYICXBSIUT605acc95c862a0582", "exchange_order_id": "35522803", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:13:07,061 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acc95c862a0582. +2023-09-19 02:13:08,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089587.0, "order_id": "x-XEKWYICXSSIUT605acc95c893f0582", "exchange_order_id": "35522804", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:13:08,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acc95c893f0582. +2023-09-19 02:13:08,668 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605accca559850582 for 80.00000000 SEI-USDT. +2023-09-19 02:13:08,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089588.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605accca559850582", "creation_timestamp": 1695089586.0, "exchange_order_id": "35522981", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:13:08,751 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605accca55d7f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:13:08,795 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089588.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605accca55d7f0582", "creation_timestamp": 1695089586.0, "exchange_order_id": "35522980", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:14:01,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605accca559850582. [clock=2023-09-19 02:14:01+00:00] +2023-09-19 02:14:01,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605accca55d7f0582. [clock=2023-09-19 02:14:01+00:00] +2023-09-19 02:14:01,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230851238470624900518042465 amount: 80. +2023-09-19 02:14:01,289 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235530149063356694616567699 amount: 80. +2023-09-19 02:14:02,025 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089641.0, "order_id": "x-XEKWYICXBSIUT605accca559850582", "exchange_order_id": "35522981", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:14:02,026 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605accca559850582. +2023-09-19 02:14:03,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089643.0, "order_id": "x-XEKWYICXSSIUT605accca55d7f0582", "exchange_order_id": "35522980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:14:03,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605accca55d7f0582. +2023-09-19 02:14:03,696 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605accfee7b580582 for 80.00000000 SEI-USDT. +2023-09-19 02:14:03,725 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605accfee7b580582", "creation_timestamp": 1695089641.0, "exchange_order_id": "35523301", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:14:03,727 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605accfee77420582 for 80.00000000 SEI-USDT. +2023-09-19 02:14:03,770 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605accfee77420582", "creation_timestamp": 1695089641.0, "exchange_order_id": "35523302", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:14:56,250 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605accfee77420582. [clock=2023-09-19 02:14:56+00:00] +2023-09-19 02:14:56,251 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605accfee7b580582. [clock=2023-09-19 02:14:56+00:00] +2023-09-19 02:14:56,294 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228187725248478916788171241 amount: 80. +2023-09-19 02:14:56,295 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232932779627194252792056355 amount: 80. +2023-09-19 02:14:56,689 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089696.0, "order_id": "x-XEKWYICXBSIUT605accfee77420582", "exchange_order_id": "35523302", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:14:56,689 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605accfee77420582. +2023-09-19 02:14:57,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089697.0, "order_id": "x-XEKWYICXSSIUT605accfee7b580582", "exchange_order_id": "35523301", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:14:57,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605accfee7b580582. +2023-09-19 02:14:57,678 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acd335fff50582 for 80.00000000 SEI-USDT. +2023-09-19 02:14:57,721 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605acd335fff50582", "creation_timestamp": 1695089696.0, "exchange_order_id": "35523539", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:14:57,723 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acd335ca730582 for 80.00000000 SEI-USDT. +2023-09-19 02:14:57,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605acd335ca730582", "creation_timestamp": 1695089696.0, "exchange_order_id": "35523538", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:15:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acd335ca730582. [clock=2023-09-19 02:15:51+00:00] +2023-09-19 02:15:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acd335fff50582. [clock=2023-09-19 02:15:51+00:00] +2023-09-19 02:15:51,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228591400049073840444074489 amount: 80. +2023-09-19 02:15:51,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232281626791026477757457769 amount: 80. +2023-09-19 02:15:51,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089751.0, "order_id": "x-XEKWYICXBSIUT605acd335ca730582", "exchange_order_id": "35523538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:15:51,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acd335ca730582. +2023-09-19 02:15:51,360 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089751.0, "order_id": "x-XEKWYICXSSIUT605acd335fff50582", "exchange_order_id": "35523539", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:15:51,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acd335fff50582. +2023-09-19 02:15:51,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acd678c81d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:15:51,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605acd678c81d0582", "creation_timestamp": 1695089751.0, "exchange_order_id": "35523679", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:15:51,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acd678ca5f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:15:51,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605acd678ca5f0582", "creation_timestamp": 1695089751.0, "exchange_order_id": "35523680", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:16:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acd678c81d0582. [clock=2023-09-19 02:16:46+00:00] +2023-09-19 02:16:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acd678ca5f0582. [clock=2023-09-19 02:16:46+00:00] +2023-09-19 02:16:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227979937014954782954229085 amount: 80. +2023-09-19 02:16:46,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231962445617975165694263201 amount: 80. +2023-09-19 02:16:46,135 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089806.0, "order_id": "x-XEKWYICXBSIUT605acd678c81d0582", "exchange_order_id": "35523679", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:16:46,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acd678c81d0582. +2023-09-19 02:16:46,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089806.0, "order_id": "x-XEKWYICXSSIUT605acd678ca5f0582", "exchange_order_id": "35523680", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:16:46,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acd678ca5f0582. +2023-09-19 02:16:46,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acd9c006300582 for 80.00000000 SEI-USDT. +2023-09-19 02:16:46,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605acd9c006300582", "creation_timestamp": 1695089806.0, "exchange_order_id": "35523757", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:16:46,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acd9c009200582 for 80.00000000 SEI-USDT. +2023-09-19 02:16:46,369 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605acd9c009200582", "creation_timestamp": 1695089806.0, "exchange_order_id": "35523758", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:16:53,285 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605acd9c009200582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:16:53,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:16:53+00:00] +2023-09-19 02:16:53,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089813.0, "order_id": "x-XEKWYICXSSIUT605acd9c009200582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00984800"}]}, "exchange_trade_id": "4988647", "exchange_order_id": "35523758", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:16:53,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089813.0, "order_id": "x-XEKWYICXSSIUT605acd9c009200582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35523758", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:16:53,322 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605acd9c009200582 completely filled. +2023-09-19 02:17:41,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acd9c006300582. [clock=2023-09-19 02:17:41+00:00] +2023-09-19 02:17:41,251 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:17:41,253 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236512671208973975279480007 amount: 80. +2023-09-19 02:17:42,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089862.0, "order_id": "x-XEKWYICXBSIUT605acd9c006300582", "exchange_order_id": "35523757", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:17:42,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acd9c006300582. +2023-09-19 02:17:42,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acdd0ad7270582 for 80.00000000 SEI-USDT. +2023-09-19 02:17:42,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089862.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acdd0ad7270582", "creation_timestamp": 1695089861.0, "exchange_order_id": "35523987", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:17:42,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acdd0adaa40582 for 80.00000000 SEI-USDT. +2023-09-19 02:17:42,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089862.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605acdd0adaa40582", "creation_timestamp": 1695089861.0, "exchange_order_id": "35523986", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:17:50,496 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acdd0ad7270582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:17:50,497 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:17:50+00:00] +2023-09-19 02:17:50,593 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089870.0, "order_id": "x-XEKWYICXBSIUT605acdd0ad7270582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988670", "exchange_order_id": "35523987", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:17:50,673 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089870.0, "order_id": "x-XEKWYICXBSIUT605acdd0ad7270582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35523987", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:17:50,673 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acdd0ad7270582 completely filled. +2023-09-19 02:18:36,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acdd0adaa40582. [clock=2023-09-19 02:18:36+00:00] +2023-09-19 02:18:36,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230895927826003384027039993 amount: 80. +2023-09-19 02:18:36,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234170770765913975750358967 amount: 80. +2023-09-19 02:18:38,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089917.0, "order_id": "x-XEKWYICXSSIUT605acdd0adaa40582", "exchange_order_id": "35523986", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:18:38,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acdd0adaa40582. +2023-09-19 02:18:38,600 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ace052a52d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:18:38,666 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089918.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ace052a52d0582", "creation_timestamp": 1695089916.0, "exchange_order_id": "35524130", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:18:38,666 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ace052a93e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:18:38,703 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089918.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ace052a93e0582", "creation_timestamp": 1695089916.0, "exchange_order_id": "35524129", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:19:31,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ace052a52d0582. [clock=2023-09-19 02:19:31+00:00] +2023-09-19 02:19:31,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ace052a93e0582. [clock=2023-09-19 02:19:31+00:00] +2023-09-19 02:19:31,331 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230919944870125144609296154 amount: 80. +2023-09-19 02:19:31,344 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233466341879673248967464548 amount: 80. +2023-09-19 02:19:31,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089971.0, "order_id": "x-XEKWYICXBSIUT605ace052a52d0582", "exchange_order_id": "35524130", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:19:31,747 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ace052a52d0582. +2023-09-19 02:19:32,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089972.0, "order_id": "x-XEKWYICXSSIUT605ace052a93e0582", "exchange_order_id": "35524129", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:19:32,622 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ace052a93e0582. +2023-09-19 02:19:32,624 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ace39ab7ff0582 for 80.00000000 SEI-USDT. +2023-09-19 02:19:32,647 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ace39ab7ff0582", "creation_timestamp": 1695089971.0, "exchange_order_id": "35524223", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:19:32,832 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ace39ab54e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:19:32,861 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695089972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ace39ab54e0582", "creation_timestamp": 1695089971.0, "exchange_order_id": "35524222", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:20:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ace39ab54e0582. [clock=2023-09-19 02:20:26+00:00] +2023-09-19 02:20:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ace39ab7ff0582. [clock=2023-09-19 02:20:26+00:00] +2023-09-19 02:20:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230937962035180768665481750 amount: 80. +2023-09-19 02:20:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232918066895110195471213326 amount: 80. +2023-09-19 02:20:26,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090026.0, "order_id": "x-XEKWYICXBSIUT605ace39ab54e0582", "exchange_order_id": "35524222", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:20:26,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ace39ab54e0582. +2023-09-19 02:20:26,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090026.0, "order_id": "x-XEKWYICXSSIUT605ace39ab7ff0582", "exchange_order_id": "35524223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:20:26,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ace39ab7ff0582. +2023-09-19 02:20:26,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ace6dcf55c0582 for 80.00000000 SEI-USDT. +2023-09-19 02:20:26,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ace6dcf55c0582", "creation_timestamp": 1695090026.0, "exchange_order_id": "35524309", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:20:26,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ace6dcf2400582 for 80.00000000 SEI-USDT. +2023-09-19 02:20:26,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ace6dcf2400582", "creation_timestamp": 1695090026.0, "exchange_order_id": "35524310", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:21:21,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ace6dcf2400582. [clock=2023-09-19 02:21:21+00:00] +2023-09-19 02:21:21,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ace6dcf55c0582. [clock=2023-09-19 02:21:21+00:00] +2023-09-19 02:21:21,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230951885656434602480042152 amount: 80. +2023-09-19 02:21:21,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232491709414283268619470746 amount: 80. +2023-09-19 02:21:21,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090081.0, "order_id": "x-XEKWYICXBSIUT605ace6dcf2400582", "exchange_order_id": "35524310", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:21:21,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ace6dcf2400582. +2023-09-19 02:21:21,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acea2529890582 for 80.00000000 SEI-USDT. +2023-09-19 02:21:21,460 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605acea2529890582", "creation_timestamp": 1695090081.0, "exchange_order_id": "35524452", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:21:21,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acea25274f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:21:21,584 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605acea25274f0582", "creation_timestamp": 1695090081.0, "exchange_order_id": "35524453", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:21:21,606 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090081.0, "order_id": "x-XEKWYICXSSIUT605ace6dcf55c0582", "exchange_order_id": "35524309", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:21:21,607 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ace6dcf55c0582. +2023-09-19 02:21:51,760 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605acea2529890582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:21:51,761 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:21:51+00:00] +2023-09-19 02:21:51,786 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090111.0, "order_id": "x-XEKWYICXSSIUT605acea2529890582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00985600"}]}, "exchange_trade_id": "4988687", "exchange_order_id": "35524452", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:21:51,803 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090111.0, "order_id": "x-XEKWYICXSSIUT605acea2529890582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35524452", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:21:51,803 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605acea2529890582 completely filled. +2023-09-19 02:22:16,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acea25274f0582. [clock=2023-09-19 02:22:16+00:00] +2023-09-19 02:22:16,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:22:16,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233283270940818437727074824 amount: 80. +2023-09-19 02:22:16,908 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090136.0, "order_id": "x-XEKWYICXBSIUT605acea25274f0582", "exchange_order_id": "35524453", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:22:16,908 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acea25274f0582. +2023-09-19 02:22:17,785 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aced6e05730582 for 80.00000000 SEI-USDT. +2023-09-19 02:22:17,814 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090137.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605aced6e05730582", "creation_timestamp": 1695090136.0, "exchange_order_id": "35524649", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:22:18,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aced6e08bf0582 for 80.00000000 SEI-USDT. +2023-09-19 02:22:18,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090137.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605aced6e08bf0582", "creation_timestamp": 1695090136.0, "exchange_order_id": "35524651", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:23:11,251 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aced6e05730582. [clock=2023-09-19 02:23:11+00:00] +2023-09-19 02:23:11,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aced6e08bf0582. [clock=2023-09-19 02:23:11+00:00] +2023-09-19 02:23:11,298 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:23:11,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12330000 amount: 80. +2023-09-19 02:23:11,851 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090191.0, "order_id": "x-XEKWYICXBSIUT605aced6e05730582", "exchange_order_id": "35524649", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:23:11,851 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aced6e05730582. +2023-09-19 02:23:13,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090193.0, "order_id": "x-XEKWYICXSSIUT605aced6e08bf0582", "exchange_order_id": "35524651", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:23:13,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aced6e08bf0582. +2023-09-19 02:23:13,921 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acf0b734910582 for 80.00000000 SEI-USDT. +2023-09-19 02:23:13,968 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090193.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acf0b734910582", "creation_timestamp": 1695090191.0, "exchange_order_id": "35524796", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:23:13,994 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acf0b737880582 for 80.00000000 SEI-USDT. +2023-09-19 02:23:14,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090193.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605acf0b737880582", "creation_timestamp": 1695090191.0, "exchange_order_id": "35524797", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:23:37,024 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605acf0b737880582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:23:37,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:23:36+00:00] +2023-09-19 02:23:37,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090216.0, "order_id": "x-XEKWYICXSSIUT605acf0b737880582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00986400"}]}, "exchange_trade_id": "4988696", "exchange_order_id": "35524797", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:23:37,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090216.0, "order_id": "x-XEKWYICXSSIUT605acf0b737880582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35524797", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:23:37,246 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605acf0b737880582 completely filled. +2023-09-19 02:24:06,191 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acf0b734910582. [clock=2023-09-19 02:24:06+00:00] +2023-09-19 02:24:06,259 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 02:24:06,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235166171611604514463581669 amount: 80. +2023-09-19 02:24:07,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090246.0, "order_id": "x-XEKWYICXBSIUT605acf0b734910582", "exchange_order_id": "35524796", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:24:07,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acf0b734910582. +2023-09-19 02:24:07,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acf3fd9a4e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:24:07,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acf3fd9a4e0582", "creation_timestamp": 1695090246.0, "exchange_order_id": "35525074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:24:08,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acf3fd9ee80582 for 80.00000000 SEI-USDT. +2023-09-19 02:24:08,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090248.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acf3fd9ee80582", "creation_timestamp": 1695090246.0, "exchange_order_id": "35525075", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:25:01,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acf3fd9a4e0582. [clock=2023-09-19 02:25:01+00:00] +2023-09-19 02:25:01,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acf3fd9ee80582. [clock=2023-09-19 02:25:01+00:00] +2023-09-19 02:25:01,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12330000 amount: 80. +2023-09-19 02:25:01,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236168741556094453192854056 amount: 80. +2023-09-19 02:25:01,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090301.0, "order_id": "x-XEKWYICXBSIUT605acf3fd9a4e0582", "exchange_order_id": "35525074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:25:01,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acf3fd9a4e0582. +2023-09-19 02:25:02,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090302.0, "order_id": "x-XEKWYICXSSIUT605acf3fd9ee80582", "exchange_order_id": "35525075", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:25:02,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acf3fd9ee80582. +2023-09-19 02:25:02,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acf74314210582 for 80.00000000 SEI-USDT. +2023-09-19 02:25:02,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090302.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605acf74314210582", "creation_timestamp": 1695090301.0, "exchange_order_id": "35525189", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:25:02,540 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acf743117a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:25:02,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090302.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605acf743117a0582", "creation_timestamp": 1695090301.0, "exchange_order_id": "35525188", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:25:16,997 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acf743117a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:25:16,998 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:25:16+00:00] +2023-09-19 02:25:17,019 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090316.0, "order_id": "x-XEKWYICXBSIUT605acf743117a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988724", "exchange_order_id": "35525188", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:25:17,032 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090316.0, "order_id": "x-XEKWYICXBSIUT605acf743117a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35525188", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:25:17,033 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acf743117a0582 completely filled. +2023-09-19 02:25:56,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acf74314210582. [clock=2023-09-19 02:25:56+00:00] +2023-09-19 02:25:56,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12330000 amount: 80. +2023-09-19 02:25:56,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235249302108293336600381078 amount: 80. +2023-09-19 02:25:56,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090356.0, "order_id": "x-XEKWYICXSSIUT605acf74314210582", "exchange_order_id": "35525189", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:25:56,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acf74314210582. +2023-09-19 02:25:56,400 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acfa8921c10582 for 80.00000000 SEI-USDT. +2023-09-19 02:25:56,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605acfa8921c10582", "creation_timestamp": 1695090356.0, "exchange_order_id": "35525290", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:25:56,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acfa89251d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:25:56,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acfa89251d0582", "creation_timestamp": 1695090356.0, "exchange_order_id": "35525291", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:26:21,729 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605acfa8921c10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:26:21,731 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:26:21+00:00] +2023-09-19 02:26:21,759 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090381.0, "order_id": "x-XEKWYICXBSIUT605acfa8921c10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4988734", "exchange_order_id": "35525290", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:26:21,777 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090381.0, "order_id": "x-XEKWYICXBSIUT605acfa8921c10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35525290", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:26:21,778 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605acfa8921c10582 completely filled. +2023-09-19 02:26:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acfa89251d0582. [clock=2023-09-19 02:26:51+00:00] +2023-09-19 02:26:51,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232912333387587452099150136 amount: 80. +2023-09-19 02:26:51,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235657393813497249408305152 amount: 80. +2023-09-19 02:26:51,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090411.0, "order_id": "x-XEKWYICXSSIUT605acfa89251d0582", "exchange_order_id": "35525291", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:26:51,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acfa89251d0582. +2023-09-19 02:26:51,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605acfdcf92560582 for 80.00000000 SEI-USDT. +2023-09-19 02:26:51,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605acfdcf92560582", "creation_timestamp": 1695090411.0, "exchange_order_id": "35525384", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:26:51,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605acfdcf94910582 for 80.00000000 SEI-USDT. +2023-09-19 02:26:51,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605acfdcf94910582", "creation_timestamp": 1695090411.0, "exchange_order_id": "35525385", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:27:46,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605acfdcf92560582. [clock=2023-09-19 02:27:46+00:00] +2023-09-19 02:27:46,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605acfdcf94910582. [clock=2023-09-19 02:27:46+00:00] +2023-09-19 02:27:46,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231316369827935649742426239 amount: 80. +2023-09-19 02:27:46,177 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235674897605477224701084264 amount: 80. +2023-09-19 02:27:46,765 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090466.0, "order_id": "x-XEKWYICXBSIUT605acfdcf92560582", "exchange_order_id": "35525384", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:27:46,765 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605acfdcf92560582. +2023-09-19 02:27:48,010 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090467.0, "order_id": "x-XEKWYICXSSIUT605acfdcf94910582", "exchange_order_id": "35525385", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:27:48,010 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605acfdcf94910582. +2023-09-19 02:27:48,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad011947980582 for 80.00000000 SEI-USDT. +2023-09-19 02:27:48,587 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090468.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ad011947980582", "creation_timestamp": 1695090466.0, "exchange_order_id": "35525769", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:27:48,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad011943260582 for 80.00000000 SEI-USDT. +2023-09-19 02:27:48,631 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090468.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad011943260582", "creation_timestamp": 1695090466.0, "exchange_order_id": "35525768", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:28:41,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad011943260582. [clock=2023-09-19 02:28:41+00:00] +2023-09-19 02:28:41,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad011947980582. [clock=2023-09-19 02:28:41+00:00] +2023-09-19 02:28:41,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230691735236233989764209706 amount: 80. +2023-09-19 02:28:41,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234078649504496018379125750 amount: 80. +2023-09-19 02:28:42,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090521.0, "order_id": "x-XEKWYICXBSIUT605ad011943260582", "exchange_order_id": "35525768", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:28:42,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad011943260582. +2023-09-19 02:28:43,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090522.0, "order_id": "x-XEKWYICXSSIUT605ad011947980582", "exchange_order_id": "35525769", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:28:43,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad011947980582. +2023-09-19 02:28:43,550 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad046114cd0582 for 80.00000000 SEI-USDT. +2023-09-19 02:28:43,619 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090523.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad046114cd0582", "creation_timestamp": 1695090521.0, "exchange_order_id": "35525959", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:28:43,679 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad046117ff0582 for 80.00000000 SEI-USDT. +2023-09-19 02:28:43,758 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090523.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad046117ff0582", "creation_timestamp": 1695090521.0, "exchange_order_id": "35525960", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:29:36,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad046114cd0582. [clock=2023-09-19 02:29:36+00:00] +2023-09-19 02:29:36,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad046117ff0582. [clock=2023-09-19 02:29:36+00:00] +2023-09-19 02:29:36,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230983091219937080726048191 amount: 80. +2023-09-19 02:29:36,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233617176057003985852753607 amount: 80. +2023-09-19 02:29:36,594 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090576.0, "order_id": "x-XEKWYICXBSIUT605ad046114cd0582", "exchange_order_id": "35525959", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:29:36,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad046114cd0582. +2023-09-19 02:29:37,620 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090577.0, "order_id": "x-XEKWYICXSSIUT605ad046117ff0582", "exchange_order_id": "35525960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:29:37,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad046117ff0582. +2023-09-19 02:29:37,938 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad07a6c9b80582 for 80.00000000 SEI-USDT. +2023-09-19 02:29:37,957 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090577.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad07a6c9b80582", "creation_timestamp": 1695090576.0, "exchange_order_id": "35526075", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:29:37,960 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad07a6c6db0582 for 80.00000000 SEI-USDT. +2023-09-19 02:29:37,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090577.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad07a6c6db0582", "creation_timestamp": 1695090576.0, "exchange_order_id": "35526074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:30:31,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad07a6c6db0582. [clock=2023-09-19 02:30:31+00:00] +2023-09-19 02:30:31,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad07a6c9b80582. [clock=2023-09-19 02:30:31+00:00] +2023-09-19 02:30:31,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231209130796372661414627578 amount: 80. +2023-09-19 02:30:31,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233257752570228963788921582 amount: 80. +2023-09-19 02:30:31,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090631.0, "order_id": "x-XEKWYICXBSIUT605ad07a6c6db0582", "exchange_order_id": "35526074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:30:31,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad07a6c6db0582. +2023-09-19 02:30:31,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090631.0, "order_id": "x-XEKWYICXSSIUT605ad07a6c9b80582", "exchange_order_id": "35526075", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:30:31,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad07a6c9b80582. +2023-09-19 02:30:31,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad0aed2b8f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:30:31,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad0aed2b8f0582", "creation_timestamp": 1695090631.0, "exchange_order_id": "35526285", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:30:31,422 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad0aed2f1e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:30:31,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad0aed2f1e0582", "creation_timestamp": 1695090631.0, "exchange_order_id": "35526286", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:31:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad0aed2b8f0582. [clock=2023-09-19 02:31:26+00:00] +2023-09-19 02:31:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad0aed2f1e0582. [clock=2023-09-19 02:31:26+00:00] +2023-09-19 02:31:26,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230600991537116441994997575 amount: 80. +2023-09-19 02:31:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233059213732838075504408652 amount: 80. +2023-09-19 02:31:26,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090686.0, "order_id": "x-XEKWYICXBSIUT605ad0aed2b8f0582", "exchange_order_id": "35526285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:31:26,139 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad0aed2b8f0582. +2023-09-19 02:31:26,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090686.0, "order_id": "x-XEKWYICXSSIUT605ad0aed2f1e0582", "exchange_order_id": "35526286", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:31:26,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad0aed2f1e0582. +2023-09-19 02:31:26,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad0e33c14f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:31:26,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad0e33c14f0582", "creation_timestamp": 1695090686.0, "exchange_order_id": "35526375", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:31:26,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad0e33c4980582 for 80.00000000 SEI-USDT. +2023-09-19 02:31:26,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad0e33c4980582", "creation_timestamp": 1695090686.0, "exchange_order_id": "35526374", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:32:21,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad0e33c14f0582. [clock=2023-09-19 02:32:21+00:00] +2023-09-19 02:32:21,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad0e33c4980582. [clock=2023-09-19 02:32:21+00:00] +2023-09-19 02:32:21,392 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229912735241289901191954072 amount: 80. +2023-09-19 02:32:21,393 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12320000 amount: 80. +2023-09-19 02:32:22,785 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090742.0, "order_id": "x-XEKWYICXBSIUT605ad0e33c14f0582", "exchange_order_id": "35526375", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:32:22,785 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad0e33c14f0582. +2023-09-19 02:32:24,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad1180bb7a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:32:24,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090743.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad1180bb7a0582", "creation_timestamp": 1695090741.0, "exchange_order_id": "35526565", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:32:24,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090744.0, "order_id": "x-XEKWYICXSSIUT605ad0e33c4980582", "exchange_order_id": "35526374", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:32:24,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad0e33c4980582. +2023-09-19 02:32:24,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad1180b7c70582 for 80.00000000 SEI-USDT. +2023-09-19 02:32:24,587 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090744.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad1180b7c70582", "creation_timestamp": 1695090741.0, "exchange_order_id": "35526566", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:33:16,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad1180b7c70582. [clock=2023-09-19 02:33:16+00:00] +2023-09-19 02:33:16,284 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad1180bb7a0582. [clock=2023-09-19 02:33:16+00:00] +2023-09-19 02:33:16,349 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230154631631621427476925772 amount: 80. +2023-09-19 02:33:16,351 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12320000 amount: 80. +2023-09-19 02:33:16,906 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090796.0, "order_id": "x-XEKWYICXBSIUT605ad1180b7c70582", "exchange_order_id": "35526566", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:33:16,907 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad1180b7c70582. +2023-09-19 02:33:18,931 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090797.0, "order_id": "x-XEKWYICXSSIUT605ad1180bb7a0582", "exchange_order_id": "35526565", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:33:18,932 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad1180bb7a0582. +2023-09-19 02:33:19,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad14c751e10582 for 80.00000000 SEI-USDT. +2023-09-19 02:33:19,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090797.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad14c751e10582", "creation_timestamp": 1695090796.0, "exchange_order_id": "35526643", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:33:19,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad14c74dd70582 for 80.00000000 SEI-USDT. +2023-09-19 02:33:19,414 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090799.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad14c74dd70582", "creation_timestamp": 1695090796.0, "exchange_order_id": "35526644", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:34:11,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad14c74dd70582. [clock=2023-09-19 02:34:11+00:00] +2023-09-19 02:34:11,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad14c751e10582. [clock=2023-09-19 02:34:11+00:00] +2023-09-19 02:34:11,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229572740594565811062889823 amount: 80. +2023-09-19 02:34:11,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12320000 amount: 80. +2023-09-19 02:34:11,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090851.0, "order_id": "x-XEKWYICXBSIUT605ad14c74dd70582", "exchange_order_id": "35526644", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:34:11,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad14c74dd70582. +2023-09-19 02:34:11,460 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090851.0, "order_id": "x-XEKWYICXSSIUT605ad14c751e10582", "exchange_order_id": "35526643", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:34:11,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad14c751e10582. +2023-09-19 02:34:12,093 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad180b76360582 for 80.00000000 SEI-USDT. +2023-09-19 02:34:12,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad180b76360582", "creation_timestamp": 1695090851.0, "exchange_order_id": "35526820", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:34:12,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad180b78f10582 for 80.00000000 SEI-USDT. +2023-09-19 02:34:12,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad180b78f10582", "creation_timestamp": 1695090851.0, "exchange_order_id": "35526821", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:35:06,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad180b76360582. [clock=2023-09-19 02:35:06+00:00] +2023-09-19 02:35:06,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad180b78f10582. [clock=2023-09-19 02:35:06+00:00] +2023-09-19 02:35:06,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229889847054651274549388307 amount: 80. +2023-09-19 02:35:06,197 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12320000 amount: 80. +2023-09-19 02:35:06,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090906.0, "order_id": "x-XEKWYICXBSIUT605ad180b76360582", "exchange_order_id": "35526820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:35:06,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad180b76360582. +2023-09-19 02:35:07,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090907.0, "order_id": "x-XEKWYICXSSIUT605ad180b78f10582", "exchange_order_id": "35526821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:35:07,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad180b78f10582. +2023-09-19 02:35:07,830 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad1b53718d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:35:07,910 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090907.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad1b53718d0582", "creation_timestamp": 1695090906.0, "exchange_order_id": "35526993", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:35:08,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad1b536ea90582 for 80.00000000 SEI-USDT. +2023-09-19 02:35:08,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090907.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad1b536ea90582", "creation_timestamp": 1695090906.0, "exchange_order_id": "35526994", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:36:01,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad1b536ea90582. [clock=2023-09-19 02:36:01+00:00] +2023-09-19 02:36:01,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad1b53718d0582. [clock=2023-09-19 02:36:01+00:00] +2023-09-19 02:36:01,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229889847054651274549388307 amount: 80. +2023-09-19 02:36:01,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12320000 amount: 80. +2023-09-19 02:36:01,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090961.0, "order_id": "x-XEKWYICXBSIUT605ad1b536ea90582", "exchange_order_id": "35526994", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:36:01,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad1b536ea90582. +2023-09-19 02:36:01,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090961.0, "order_id": "x-XEKWYICXSSIUT605ad1b53718d0582", "exchange_order_id": "35526993", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:36:01,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad1b53718d0582. +2023-09-19 02:36:01,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad1e99153c0582 for 80.00000000 SEI-USDT. +2023-09-19 02:36:01,436 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad1e99153c0582", "creation_timestamp": 1695090961.0, "exchange_order_id": "35527206", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:36:01,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad1e9912040582 for 80.00000000 SEI-USDT. +2023-09-19 02:36:01,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad1e9912040582", "creation_timestamp": 1695090961.0, "exchange_order_id": "35527207", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:36:30,302 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad1e99153c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:36:30,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:36:30+00:00] +2023-09-19 02:36:30,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090990.0, "order_id": "x-XEKWYICXSSIUT605ad1e99153c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00985600"}]}, "exchange_trade_id": "4988902", "exchange_order_id": "35527206", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:36:30,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695090990.0, "order_id": "x-XEKWYICXSSIUT605ad1e99153c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35527206", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:36:30,363 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad1e99153c0582 completely filled. +2023-09-19 02:36:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad1e9912040582. [clock=2023-09-19 02:36:56+00:00] +2023-09-19 02:36:56,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231073930204851732216660986 amount: 80. +2023-09-19 02:36:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233408059392409299879018701 amount: 80. +2023-09-19 02:36:56,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091016.0, "order_id": "x-XEKWYICXBSIUT605ad1e9912040582", "exchange_order_id": "35527207", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:36:56,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad1e9912040582. +2023-09-19 02:36:56,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad21df24740582 for 80.00000000 SEI-USDT. +2023-09-19 02:36:56,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad21df24740582", "creation_timestamp": 1695091016.0, "exchange_order_id": "35527315", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:36:56,398 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad21df26a00582 for 80.00000000 SEI-USDT. +2023-09-19 02:36:56,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad21df26a00582", "creation_timestamp": 1695091016.0, "exchange_order_id": "35527316", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:37:27,992 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad21df26a00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:37:27,993 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:37:27+00:00] +2023-09-19 02:37:28,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091047.0, "order_id": "x-XEKWYICXSSIUT605ad21df26a00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00986400"}]}, "exchange_trade_id": "4988950", "exchange_order_id": "35527316", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:37:28,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091047.0, "order_id": "x-XEKWYICXSSIUT605ad21df26a00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35527316", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:37:28,330 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad21df26a00582 completely filled. +2023-09-19 02:37:51,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad21df24740582. [clock=2023-09-19 02:37:51+00:00] +2023-09-19 02:37:51,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230807971400027894824946615 amount: 80. +2023-09-19 02:37:51,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234847985692962179240440356 amount: 80. +2023-09-19 02:37:51,884 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091071.0, "order_id": "x-XEKWYICXBSIUT605ad21df24740582", "exchange_order_id": "35527315", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:37:51,897 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad21df24740582. +2023-09-19 02:37:53,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad2529b60f0582 for 80.00000000 SEI-USDT. +2023-09-19 02:37:53,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091072.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad2529b60f0582", "creation_timestamp": 1695091071.0, "exchange_order_id": "35527770", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:37:53,797 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad2529b9910582 for 80.00000000 SEI-USDT. +2023-09-19 02:37:53,865 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091073.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad2529b9910582", "creation_timestamp": 1695091071.0, "exchange_order_id": "35527771", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:38:46,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad2529b60f0582. [clock=2023-09-19 02:38:46+00:00] +2023-09-19 02:38:46,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad2529b9910582. [clock=2023-09-19 02:38:46+00:00] +2023-09-19 02:38:46,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232261233596139762252714511 amount: 80. +2023-09-19 02:38:46,273 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236517257643550279461392425 amount: 80. +2023-09-19 02:38:46,957 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091126.0, "order_id": "x-XEKWYICXBSIUT605ad2529b60f0582", "exchange_order_id": "35527770", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:38:46,958 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad2529b60f0582. +2023-09-19 02:38:48,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091127.0, "order_id": "x-XEKWYICXSSIUT605ad2529b9910582", "exchange_order_id": "35527771", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:38:48,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad2529b9910582. +2023-09-19 02:38:48,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad287189c40582 for 80.00000000 SEI-USDT. +2023-09-19 02:38:48,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091128.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad287189c40582", "creation_timestamp": 1695091126.0, "exchange_order_id": "35527941", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:38:48,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad287186110582 for 80.00000000 SEI-USDT. +2023-09-19 02:38:48,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091128.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad287186110582", "creation_timestamp": 1695091126.0, "exchange_order_id": "35527940", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:39:41,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad287186110582. [clock=2023-09-19 02:39:41+00:00] +2023-09-19 02:39:41,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad287189c40582. [clock=2023-09-19 02:39:41+00:00] +2023-09-19 02:39:41,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232037462786732483183534374 amount: 80. +2023-09-19 02:39:41,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237574297935256486682006720 amount: 80. +2023-09-19 02:39:41,461 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091181.0, "order_id": "x-XEKWYICXBSIUT605ad287186110582", "exchange_order_id": "35527940", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:39:41,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad287186110582. +2023-09-19 02:39:42,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091182.0, "order_id": "x-XEKWYICXSSIUT605ad287189c40582", "exchange_order_id": "35527941", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:39:42,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad287189c40582. +2023-09-19 02:39:42,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad2bb6c8ee0582 for 80.00000000 SEI-USDT. +2023-09-19 02:39:42,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091182.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad2bb6c8ee0582", "creation_timestamp": 1695091181.0, "exchange_order_id": "35528289", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:39:42,500 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad2bb6cc070582 for 80.00000000 SEI-USDT. +2023-09-19 02:39:42,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091182.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ad2bb6cc070582", "creation_timestamp": 1695091181.0, "exchange_order_id": "35528288", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:40:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad2bb6c8ee0582. [clock=2023-09-19 02:40:36+00:00] +2023-09-19 02:40:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad2bb6cc070582. [clock=2023-09-19 02:40:36+00:00] +2023-09-19 02:40:36,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232251946095944947027428958 amount: 80. +2023-09-19 02:40:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236556973465816642723179181 amount: 80. +2023-09-19 02:40:36,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091236.0, "order_id": "x-XEKWYICXBSIUT605ad2bb6c8ee0582", "exchange_order_id": "35528289", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:40:36,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad2bb6c8ee0582. +2023-09-19 02:40:36,371 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091236.0, "order_id": "x-XEKWYICXSSIUT605ad2bb6cc070582", "exchange_order_id": "35528288", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:40:36,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad2bb6cc070582. +2023-09-19 02:40:36,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad2efc19180582 for 80.00000000 SEI-USDT. +2023-09-19 02:40:36,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad2efc19180582", "creation_timestamp": 1695091236.0, "exchange_order_id": "35528361", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:40:36,470 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad2efc1ba50582 for 80.00000000 SEI-USDT. +2023-09-19 02:40:36,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad2efc1ba50582", "creation_timestamp": 1695091236.0, "exchange_order_id": "35528362", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:41:29,885 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ad2efc19180582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:41:29,886 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:41:29+00:00] +2023-09-19 02:41:29,908 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091289.0, "order_id": "x-XEKWYICXBSIUT605ad2efc19180582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4989049", "exchange_order_id": "35528361", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:41:29,921 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091289.0, "order_id": "x-XEKWYICXBSIUT605ad2efc19180582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35528361", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:41:29,921 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ad2efc19180582 completely filled. +2023-09-19 02:41:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad2efc1ba50582. [clock=2023-09-19 02:41:31+00:00] +2023-09-19 02:41:31,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228878672081926446625042109 amount: 80. +2023-09-19 02:41:31,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233331090890713725034751971 amount: 80. +2023-09-19 02:41:31,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091291.0, "order_id": "x-XEKWYICXSSIUT605ad2efc1ba50582", "exchange_order_id": "35528362", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:41:31,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad2efc1ba50582. +2023-09-19 02:41:31,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad3243508c0582 for 80.00000000 SEI-USDT. +2023-09-19 02:41:31,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ad3243508c0582", "creation_timestamp": 1695091291.0, "exchange_order_id": "35528637", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:41:31,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad3243556d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:41:31,371 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad3243556d0582", "creation_timestamp": 1695091291.0, "exchange_order_id": "35528638", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:42:26,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad3243508c0582. [clock=2023-09-19 02:42:26+00:00] +2023-09-19 02:42:26,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad3243556d0582. [clock=2023-09-19 02:42:26+00:00] +2023-09-19 02:42:26,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228478328810080337659441579 amount: 80. +2023-09-19 02:42:26,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232806779420641581180200830 amount: 80. +2023-09-19 02:42:26,587 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091346.0, "order_id": "x-XEKWYICXBSIUT605ad3243508c0582", "exchange_order_id": "35528637", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:42:26,587 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad3243508c0582. +2023-09-19 02:42:27,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091347.0, "order_id": "x-XEKWYICXSSIUT605ad3243556d0582", "exchange_order_id": "35528638", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:42:27,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad3243556d0582. +2023-09-19 02:42:27,693 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad358c13860582 for 80.00000000 SEI-USDT. +2023-09-19 02:42:27,731 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091347.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ad358c13860582", "creation_timestamp": 1695091346.0, "exchange_order_id": "35528740", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:42:27,732 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad358c172e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:42:27,756 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091347.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad358c172e0582", "creation_timestamp": 1695091346.0, "exchange_order_id": "35528741", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:42:44,276 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 02:43:21,211 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad358c13860582. [clock=2023-09-19 02:43:21+00:00] +2023-09-19 02:43:21,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad358c172e0582. [clock=2023-09-19 02:43:21+00:00] +2023-09-19 02:43:21,300 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229038620180273427576072063 amount: 80. +2023-09-19 02:43:21,301 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232405410252906136128220703 amount: 80. +2023-09-19 02:43:21,825 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091401.0, "order_id": "x-XEKWYICXBSIUT605ad358c13860582", "exchange_order_id": "35528740", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:43:21,826 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad358c13860582. +2023-09-19 02:43:23,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091403.0, "order_id": "x-XEKWYICXSSIUT605ad358c172e0582", "exchange_order_id": "35528741", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:43:23,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad358c172e0582. +2023-09-19 02:43:24,040 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad38d622b80582 for 80.00000000 SEI-USDT. +2023-09-19 02:43:24,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091403.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad38d622b80582", "creation_timestamp": 1695091401.0, "exchange_order_id": "35528822", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:43:24,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad38d61ed70582 for 80.00000000 SEI-USDT. +2023-09-19 02:43:24,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091403.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad38d61ed70582", "creation_timestamp": 1695091401.0, "exchange_order_id": "35528823", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:44:16,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad38d61ed70582. [clock=2023-09-19 02:44:16+00:00] +2023-09-19 02:44:16,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad38d622b80582. [clock=2023-09-19 02:44:16+00:00] +2023-09-19 02:44:16,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229506481503723102381100211 amount: 80. +2023-09-19 02:44:16,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232987756280806285835578085 amount: 80. +2023-09-19 02:44:16,760 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091456.0, "order_id": "x-XEKWYICXBSIUT605ad38d61ed70582", "exchange_order_id": "35528823", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:44:16,760 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad38d61ed70582. +2023-09-19 02:44:16,802 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091456.0, "order_id": "x-XEKWYICXSSIUT605ad38d622b80582", "exchange_order_id": "35528822", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:44:16,803 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad38d622b80582. +2023-09-19 02:44:17,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad3c1d685d0582 for 80.00000000 SEI-USDT. +2023-09-19 02:44:17,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad3c1d685d0582", "creation_timestamp": 1695091456.0, "exchange_order_id": "35528906", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:44:19,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad3c1d6bc30582 for 80.00000000 SEI-USDT. +2023-09-19 02:44:19,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091458.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad3c1d6bc30582", "creation_timestamp": 1695091456.0, "exchange_order_id": "35528908", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:44:26,551 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad3c1d6bc30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:44:26,564 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:44:26+00:00] +2023-09-19 02:44:26,634 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091466.0, "order_id": "x-XEKWYICXSSIUT605ad3c1d6bc30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00985600"}]}, "exchange_trade_id": "4989079", "exchange_order_id": "35528908", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:44:26,883 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091466.0, "order_id": "x-XEKWYICXSSIUT605ad3c1d6bc30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35528908", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:44:26,883 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad3c1d6bc30582 completely filled. +2023-09-19 02:45:11,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad3c1d685d0582. [clock=2023-09-19 02:45:11+00:00] +2023-09-19 02:45:11,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229831383381021412794316897 amount: 80. +2023-09-19 02:45:11,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233403785520615847840197206 amount: 80. +2023-09-19 02:45:11,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091511.0, "order_id": "x-XEKWYICXBSIUT605ad3c1d685d0582", "exchange_order_id": "35528906", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:45:11,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad3c1d685d0582. +2023-09-19 02:45:11,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad3f60ab720582 for 80.00000000 SEI-USDT. +2023-09-19 02:45:11,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ad3f60ab720582", "creation_timestamp": 1695091511.0, "exchange_order_id": "35528995", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:45:11,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad3f60aec80582 for 80.00000000 SEI-USDT. +2023-09-19 02:45:11,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad3f60aec80582", "creation_timestamp": 1695091511.0, "exchange_order_id": "35528996", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:46:06,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad3f60ab720582. [clock=2023-09-19 02:46:06+00:00] +2023-09-19 02:46:06,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad3f60aec80582. [clock=2023-09-19 02:46:06+00:00] +2023-09-19 02:46:06,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230091230767729277300646706 amount: 80. +2023-09-19 02:46:06,095 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232869456148876708897167738 amount: 80. +2023-09-19 02:46:06,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091566.0, "order_id": "x-XEKWYICXBSIUT605ad3f60ab720582", "exchange_order_id": "35528995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:46:06,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad3f60ab720582. +2023-09-19 02:46:06,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091566.0, "order_id": "x-XEKWYICXSSIUT605ad3f60aec80582", "exchange_order_id": "35528996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:46:06,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad3f60aec80582. +2023-09-19 02:46:06,657 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad42a8ad920582 for 80.00000000 SEI-USDT. +2023-09-19 02:46:06,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ad42a8ad920582", "creation_timestamp": 1695091566.0, "exchange_order_id": "35529132", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:46:06,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad42a8aa400582 for 80.00000000 SEI-USDT. +2023-09-19 02:46:06,681 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad42a8aa400582", "creation_timestamp": 1695091566.0, "exchange_order_id": "35529133", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:46:52,899 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ad42a8aa400582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:46:52,900 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:46:52+00:00] +2023-09-19 02:46:52,921 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091612.0, "order_id": "x-XEKWYICXBSIUT605ad42a8aa400582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12300000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4989136", "exchange_order_id": "35529133", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:46:52,987 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091612.0, "order_id": "x-XEKWYICXBSIUT605ad42a8aa400582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35529133", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:46:52,988 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ad42a8aa400582 completely filled. +2023-09-19 02:47:01,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad42a8ad920582. [clock=2023-09-19 02:47:01+00:00] +2023-09-19 02:47:01,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225357504791141207611083395 amount: 80. +2023-09-19 02:47:01,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12310000 amount: 80. +2023-09-19 02:47:01,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091621.0, "order_id": "x-XEKWYICXSSIUT605ad42a8ad920582", "exchange_order_id": "35529132", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:47:01,145 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad42a8ad920582. +2023-09-19 02:47:01,378 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad45ef58080582 for 80.00000000 SEI-USDT. +2023-09-19 02:47:01,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ad45ef58080582", "creation_timestamp": 1695091621.0, "exchange_order_id": "35529384", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:47:01,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad45ef563b0582 for 80.00000000 SEI-USDT. +2023-09-19 02:47:01,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ad45ef563b0582", "creation_timestamp": 1695091621.0, "exchange_order_id": "35529383", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:47:42,903 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad45ef58080582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:47:42,904 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:47:42+00:00] +2023-09-19 02:47:42,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091662.0, "order_id": "x-XEKWYICXSSIUT605ad45ef58080582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00984800"}]}, "exchange_trade_id": "4989172", "exchange_order_id": "35529384", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:47:43,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091662.0, "order_id": "x-XEKWYICXSSIUT605ad45ef58080582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35529384", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:47:43,192 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad45ef58080582 completely filled. +2023-09-19 02:47:56,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad45ef563b0582. [clock=2023-09-19 02:47:56+00:00] +2023-09-19 02:47:56,219 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230121977955296091118117801 amount: 80. +2023-09-19 02:47:56,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236240208834343625182695231 amount: 80. +2023-09-19 02:47:56,934 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091676.0, "order_id": "x-XEKWYICXBSIUT605ad45ef563b0582", "exchange_order_id": "35529383", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:47:56,935 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad45ef563b0582. +2023-09-19 02:47:58,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad49390ad00582 for 80.00000000 SEI-USDT. +2023-09-19 02:47:58,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091677.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad49390ad00582", "creation_timestamp": 1695091676.0, "exchange_order_id": "35529816", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:47:58,976 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad49390df10582 for 80.00000000 SEI-USDT. +2023-09-19 02:47:59,038 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091678.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad49390df10582", "creation_timestamp": 1695091676.0, "exchange_order_id": "35529821", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:48:51,234 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad49390ad00582. [clock=2023-09-19 02:48:51+00:00] +2023-09-19 02:48:51,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad49390df10582. [clock=2023-09-19 02:48:51+00:00] +2023-09-19 02:48:51,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231630740967899386675381203 amount: 80. +2023-09-19 02:48:51,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237503372699815697999344795 amount: 80. +2023-09-19 02:48:51,823 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091731.0, "order_id": "x-XEKWYICXBSIUT605ad49390ad00582", "exchange_order_id": "35529816", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:48:51,823 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad49390ad00582. +2023-09-19 02:48:53,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091733.0, "order_id": "x-XEKWYICXSSIUT605ad49390df10582", "exchange_order_id": "35529821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:48:53,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad49390df10582. +2023-09-19 02:48:53,774 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad4c8134010582 for 80.00000000 SEI-USDT. +2023-09-19 02:48:53,797 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091733.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad4c8134010582", "creation_timestamp": 1695091731.0, "exchange_order_id": "35530034", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:48:53,827 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad4c8137090582 for 80.00000000 SEI-USDT. +2023-09-19 02:48:53,846 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091733.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ad4c8137090582", "creation_timestamp": 1695091731.0, "exchange_order_id": "35530035", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:49:46,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad4c8134010582. [clock=2023-09-19 02:49:46+00:00] +2023-09-19 02:49:46,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad4c8137090582. [clock=2023-09-19 02:49:46+00:00] +2023-09-19 02:49:46,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232020771454199627260059547 amount: 80. +2023-09-19 02:49:46,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237697637202428673580629141 amount: 80. +2023-09-19 02:49:46,679 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091786.0, "order_id": "x-XEKWYICXBSIUT605ad4c8134010582", "exchange_order_id": "35530034", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:49:46,679 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad4c8134010582. +2023-09-19 02:49:47,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091787.0, "order_id": "x-XEKWYICXSSIUT605ad4c8137090582", "exchange_order_id": "35530035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:49:47,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad4c8137090582. +2023-09-19 02:49:47,936 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad4fc5f1c30582 for 80.00000000 SEI-USDT. +2023-09-19 02:49:47,968 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091787.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ad4fc5f1c30582", "creation_timestamp": 1695091786.0, "exchange_order_id": "35530128", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:49:47,970 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad4fc5ee7e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:49:47,999 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091787.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad4fc5ee7e0582", "creation_timestamp": 1695091786.0, "exchange_order_id": "35530127", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:50:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad4fc5ee7e0582. [clock=2023-09-19 02:50:41+00:00] +2023-09-19 02:50:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad4fc5f1c30582. [clock=2023-09-19 02:50:41+00:00] +2023-09-19 02:50:41,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231516378947940687231132971 amount: 80. +2023-09-19 02:50:41,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237045496051201020237100228 amount: 80. +2023-09-19 02:50:41,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091841.0, "order_id": "x-XEKWYICXBSIUT605ad4fc5ee7e0582", "exchange_order_id": "35530127", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:50:41,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad4fc5ee7e0582. +2023-09-19 02:50:41,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad530bffe00582 for 80.00000000 SEI-USDT. +2023-09-19 02:50:41,460 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ad530bffe00582", "creation_timestamp": 1695091841.0, "exchange_order_id": "35530392", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:50:41,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091841.0, "order_id": "x-XEKWYICXSSIUT605ad4fc5f1c30582", "exchange_order_id": "35530128", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:50:41,490 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad4fc5f1c30582. +2023-09-19 02:50:41,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad530bfcbe0582 for 80.00000000 SEI-USDT. +2023-09-19 02:50:41,505 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad530bfcbe0582", "creation_timestamp": 1695091841.0, "exchange_order_id": "35530393", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:51:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad530bfcbe0582. [clock=2023-09-19 02:51:36+00:00] +2023-09-19 02:51:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad530bffe00582. [clock=2023-09-19 02:51:36+00:00] +2023-09-19 02:51:36,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231547673670210010698522054 amount: 80. +2023-09-19 02:51:36,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236960245197834029837509406 amount: 80. +2023-09-19 02:51:36,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091896.0, "order_id": "x-XEKWYICXBSIUT605ad530bfcbe0582", "exchange_order_id": "35530393", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:51:36,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad530bfcbe0582. +2023-09-19 02:51:36,459 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091896.0, "order_id": "x-XEKWYICXSSIUT605ad530bffe00582", "exchange_order_id": "35530392", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:51:36,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad530bffe00582. +2023-09-19 02:51:36,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad5652eaa30582 for 80.00000000 SEI-USDT. +2023-09-19 02:51:36,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad5652eaa30582", "creation_timestamp": 1695091896.0, "exchange_order_id": "35530686", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:51:36,486 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad5652ee1e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:51:36,512 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad5652ee1e0582", "creation_timestamp": 1695091896.0, "exchange_order_id": "35530685", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:52:31,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad5652eaa30582. [clock=2023-09-19 02:52:31+00:00] +2023-09-19 02:52:31,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad5652ee1e0582. [clock=2023-09-19 02:52:31+00:00] +2023-09-19 02:52:31,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233136456821673070059654284 amount: 80. +2023-09-19 02:52:31,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237346188143512758418868908 amount: 80. +2023-09-19 02:52:31,653 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091951.0, "order_id": "x-XEKWYICXBSIUT605ad5652eaa30582", "exchange_order_id": "35530686", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:52:31,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad5652eaa30582. +2023-09-19 02:52:32,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091952.0, "order_id": "x-XEKWYICXSSIUT605ad5652ee1e0582", "exchange_order_id": "35530685", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:52:32,371 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad5652ee1e0582. +2023-09-19 02:52:32,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad599c45bf0582 for 80.00000000 SEI-USDT. +2023-09-19 02:52:32,593 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091952.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605ad599c45bf0582", "creation_timestamp": 1695091951.0, "exchange_order_id": "35530837", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:52:32,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad599c42670582 for 80.00000000 SEI-USDT. +2023-09-19 02:52:32,626 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091952.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605ad599c42670582", "creation_timestamp": 1695091951.0, "exchange_order_id": "35530838", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:52:44,732 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ad599c42670582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:52:44,733 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:52:44+00:00] +2023-09-19 02:52:44,773 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091964.0, "order_id": "x-XEKWYICXBSIUT605ad599c42670582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4989356", "exchange_order_id": "35530838", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:52:45,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695091964.0, "order_id": "x-XEKWYICXBSIUT605ad599c42670582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35530838", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:52:45,285 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ad599c42670582 completely filled. +2023-09-19 02:53:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad599c45bf0582. [clock=2023-09-19 02:53:26+00:00] +2023-09-19 02:53:26,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230927611705002835553846777 amount: 80. +2023-09-19 02:53:26,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236176213781359120826122667 amount: 80. +2023-09-19 02:53:26,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092006.0, "order_id": "x-XEKWYICXSSIUT605ad599c45bf0582", "exchange_order_id": "35530837", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:53:26,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad599c45bf0582. +2023-09-19 02:53:27,086 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad5ce1ebd50582 for 80.00000000 SEI-USDT. +2023-09-19 02:53:27,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad5ce1ebd50582", "creation_timestamp": 1695092006.0, "exchange_order_id": "35531109", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:53:27,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad5ce1edd80582 for 80.00000000 SEI-USDT. +2023-09-19 02:53:27,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092007.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad5ce1edd80582", "creation_timestamp": 1695092006.0, "exchange_order_id": "35531110", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:54:21,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad5ce1ebd50582. [clock=2023-09-19 02:54:21+00:00] +2023-09-19 02:54:21,307 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad5ce1edd80582. [clock=2023-09-19 02:54:21+00:00] +2023-09-19 02:54:21,375 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231389145248247818411500542 amount: 80. +2023-09-19 02:54:21,375 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235470988077197404000911444 amount: 80. +2023-09-19 02:54:22,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092061.0, "order_id": "x-XEKWYICXBSIUT605ad5ce1ebd50582", "exchange_order_id": "35531109", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:54:22,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad5ce1ebd50582. +2023-09-19 02:54:24,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092063.0, "order_id": "x-XEKWYICXSSIUT605ad5ce1edd80582", "exchange_order_id": "35531110", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:54:24,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad5ce1edd80582. +2023-09-19 02:54:24,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad602e0d3b0582 for 80.00000000 SEI-USDT. +2023-09-19 02:54:24,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092064.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad602e0d3b0582", "creation_timestamp": 1695092061.0, "exchange_order_id": "35531206", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:54:24,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad602e10e10582 for 80.00000000 SEI-USDT. +2023-09-19 02:54:24,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092064.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ad602e10e10582", "creation_timestamp": 1695092061.0, "exchange_order_id": "35531207", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:55:16,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad602e0d3b0582. [clock=2023-09-19 02:55:16+00:00] +2023-09-19 02:55:16,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad602e10e10582. [clock=2023-09-19 02:55:16+00:00] +2023-09-19 02:55:16,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231747230243257432418905568 amount: 80. +2023-09-19 02:55:16,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234921750881497072307129758 amount: 80. +2023-09-19 02:55:16,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092116.0, "order_id": "x-XEKWYICXBSIUT605ad602e0d3b0582", "exchange_order_id": "35531206", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:55:16,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad602e0d3b0582. +2023-09-19 02:55:16,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092116.0, "order_id": "x-XEKWYICXSSIUT605ad602e10e10582", "exchange_order_id": "35531207", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:55:16,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad602e10e10582. +2023-09-19 02:55:16,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad6370df0a0582 for 80.00000000 SEI-USDT. +2023-09-19 02:55:16,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad6370df0a0582", "creation_timestamp": 1695092116.0, "exchange_order_id": "35531348", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:55:16,441 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad6370dcd90582 for 80.00000000 SEI-USDT. +2023-09-19 02:55:16,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad6370dcd90582", "creation_timestamp": 1695092116.0, "exchange_order_id": "35531349", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:56:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad6370dcd90582. [clock=2023-09-19 02:56:11+00:00] +2023-09-19 02:56:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad6370df0a0582. [clock=2023-09-19 02:56:11+00:00] +2023-09-19 02:56:11,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231151811671039357557048168 amount: 80. +2023-09-19 02:56:11,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235596871408214114922473070 amount: 80. +2023-09-19 02:56:11,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092171.0, "order_id": "x-XEKWYICXBSIUT605ad6370dcd90582", "exchange_order_id": "35531349", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:56:11,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad6370dcd90582. +2023-09-19 02:56:11,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092171.0, "order_id": "x-XEKWYICXSSIUT605ad6370df0a0582", "exchange_order_id": "35531348", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:56:11,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad6370df0a0582. +2023-09-19 02:56:11,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad66b75b830582 for 80.00000000 SEI-USDT. +2023-09-19 02:56:11,443 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad66b75b830582", "creation_timestamp": 1695092171.0, "exchange_order_id": "35531608", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:56:11,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad66b75e780582 for 80.00000000 SEI-USDT. +2023-09-19 02:56:11,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ad66b75e780582", "creation_timestamp": 1695092171.0, "exchange_order_id": "35531609", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:57:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad66b75b830582. [clock=2023-09-19 02:57:06+00:00] +2023-09-19 02:57:06,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad66b75e780582. [clock=2023-09-19 02:57:06+00:00] +2023-09-19 02:57:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231562634121005232240415669 amount: 80. +2023-09-19 02:57:06,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235019669616708506939089090 amount: 80. +2023-09-19 02:57:06,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092226.0, "order_id": "x-XEKWYICXBSIUT605ad66b75b830582", "exchange_order_id": "35531608", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:57:06,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad66b75b830582. +2023-09-19 02:57:06,598 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad69feda780582 for 80.00000000 SEI-USDT. +2023-09-19 02:57:06,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ad69feda780582", "creation_timestamp": 1695092226.0, "exchange_order_id": "35531742", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:57:06,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092226.0, "order_id": "x-XEKWYICXSSIUT605ad66b75e780582", "exchange_order_id": "35531609", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:57:06,630 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad66b75e780582. +2023-09-19 02:57:06,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad69fe964e0582 for 80.00000000 SEI-USDT. +2023-09-19 02:57:06,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad69fe964e0582", "creation_timestamp": 1695092226.0, "exchange_order_id": "35531741", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:58:01,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad69fe964e0582. [clock=2023-09-19 02:58:01+00:00] +2023-09-19 02:58:01,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad69feda780582. [clock=2023-09-19 02:58:01+00:00] +2023-09-19 02:58:01,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230563799867865730835516711 amount: 80. +2023-09-19 02:58:01,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234018031604042887712050089 amount: 80. +2023-09-19 02:58:01,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092281.0, "order_id": "x-XEKWYICXBSIUT605ad69fe964e0582", "exchange_order_id": "35531741", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:58:01,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad69fe964e0582. +2023-09-19 02:58:02,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092282.0, "order_id": "x-XEKWYICXSSIUT605ad69feda780582", "exchange_order_id": "35531742", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:58:02,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad69feda780582. +2023-09-19 02:58:02,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad6d4717ca0582 for 80.00000000 SEI-USDT. +2023-09-19 02:58:02,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092282.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad6d4717ca0582", "creation_timestamp": 1695092281.0, "exchange_order_id": "35531955", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:58:02,509 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad6d46e5710582 for 80.00000000 SEI-USDT. +2023-09-19 02:58:02,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092282.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad6d46e5710582", "creation_timestamp": 1695092281.0, "exchange_order_id": "35531958", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:58:20,222 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad6d4717ca0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 02:58:20,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 02:58:19+00:00] +2023-09-19 02:58:20,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092299.0, "order_id": "x-XEKWYICXSSIUT605ad6d4717ca0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12340000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00987200"}]}, "exchange_trade_id": "4989451", "exchange_order_id": "35531955", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 02:58:20,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092300.0, "order_id": "x-XEKWYICXSSIUT605ad6d4717ca0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35531955", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 02:58:20,472 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad6d4717ca0582 completely filled. +2023-09-19 02:58:56,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad6d46e5710582. [clock=2023-09-19 02:58:56+00:00] +2023-09-19 02:58:56,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231458891958372810872608456 amount: 80. +2023-09-19 02:58:56,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236372932398833915854709450 amount: 80. +2023-09-19 02:58:56,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092336.0, "order_id": "x-XEKWYICXBSIUT605ad6d46e5710582", "exchange_order_id": "35531958", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:58:56,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad6d46e5710582. +2023-09-19 02:58:58,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad708d49f30582 for 80.00000000 SEI-USDT. +2023-09-19 02:58:58,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092338.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad708d49f30582", "creation_timestamp": 1695092336.0, "exchange_order_id": "35532269", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:58:58,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad708d47160582 for 80.00000000 SEI-USDT. +2023-09-19 02:58:58,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092338.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad708d47160582", "creation_timestamp": 1695092336.0, "exchange_order_id": "35532268", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:59:51,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad708d47160582. [clock=2023-09-19 02:59:51+00:00] +2023-09-19 02:59:51,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad708d49f30582. [clock=2023-09-19 02:59:51+00:00] +2023-09-19 02:59:51,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231927401194920108328692572 amount: 80. +2023-09-19 02:59:51,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236859278282721832261594621 amount: 80. +2023-09-19 02:59:53,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092392.0, "order_id": "x-XEKWYICXBSIUT605ad708d47160582", "exchange_order_id": "35532268", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:59:53,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad708d47160582. +2023-09-19 02:59:53,698 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092393.0, "order_id": "x-XEKWYICXSSIUT605ad708d49f30582", "exchange_order_id": "35532269", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 02:59:53,699 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad708d49f30582. +2023-09-19 02:59:53,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad73d67ad20582 for 80.00000000 SEI-USDT. +2023-09-19 02:59:53,752 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad73d67ad20582", "creation_timestamp": 1695092391.0, "exchange_order_id": "35532492", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 02:59:53,752 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad73d67e110582 for 80.00000000 SEI-USDT. +2023-09-19 02:59:53,812 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad73d67e110582", "creation_timestamp": 1695092391.0, "exchange_order_id": "35532493", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:00:46,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad73d67ad20582. [clock=2023-09-19 03:00:46+00:00] +2023-09-19 03:00:46,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad73d67e110582. [clock=2023-09-19 03:00:46+00:00] +2023-09-19 03:00:46,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230797096321458945553897346 amount: 80. +2023-09-19 03:00:46,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234630888916175487821170616 amount: 80. +2023-09-19 03:00:46,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092446.0, "order_id": "x-XEKWYICXBSIUT605ad73d67ad20582", "exchange_order_id": "35532492", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:00:46,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad73d67ad20582. +2023-09-19 03:00:46,591 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092446.0, "order_id": "x-XEKWYICXSSIUT605ad73d67e110582", "exchange_order_id": "35532493", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:00:46,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad73d67e110582. +2023-09-19 03:00:46,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad771d9fd20582 for 80.00000000 SEI-USDT. +2023-09-19 03:00:46,610 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad771d9fd20582", "creation_timestamp": 1695092446.0, "exchange_order_id": "35532671", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:00:46,611 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad771d9dd60582 for 80.00000000 SEI-USDT. +2023-09-19 03:00:46,623 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ad771d9dd60582", "creation_timestamp": 1695092446.0, "exchange_order_id": "35532672", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:01:34,764 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad771d9fd20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:01:34,766 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:01:34+00:00] +2023-09-19 03:01:34,806 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092494.0, "order_id": "x-XEKWYICXSSIUT605ad771d9fd20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12340000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00987200"}]}, "exchange_trade_id": "4989553", "exchange_order_id": "35532671", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:01:34,828 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092494.0, "order_id": "x-XEKWYICXSSIUT605ad771d9fd20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35532671", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:01:34,828 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad771d9fd20582 completely filled. +2023-09-19 03:01:41,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad771d9dd60582. [clock=2023-09-19 03:01:41+00:00] +2023-09-19 03:01:41,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232618627956949734261177381 amount: 80. +2023-09-19 03:01:41,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236714480793610457348416830 amount: 80. +2023-09-19 03:01:41,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092501.0, "order_id": "x-XEKWYICXBSIUT605ad771d9dd60582", "exchange_order_id": "35532672", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:01:41,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad771d9dd60582. +2023-09-19 03:01:41,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad7a6331430582 for 80.00000000 SEI-USDT. +2023-09-19 03:01:41,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad7a6331430582", "creation_timestamp": 1695092501.0, "exchange_order_id": "35533009", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:01:41,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad7a6334850582 for 80.00000000 SEI-USDT. +2023-09-19 03:01:41,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad7a6334850582", "creation_timestamp": 1695092501.0, "exchange_order_id": "35533010", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:02:36,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad7a6331430582. [clock=2023-09-19 03:02:36+00:00] +2023-09-19 03:02:36,328 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad7a6334850582. [clock=2023-09-19 03:02:36+00:00] +2023-09-19 03:02:36,389 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232006827859716714063774513 amount: 80. +2023-09-19 03:02:36,391 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236305910574763684104362275 amount: 80. +2023-09-19 03:02:36,845 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092556.0, "order_id": "x-XEKWYICXBSIUT605ad7a6331430582", "exchange_order_id": "35533009", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:02:36,846 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad7a6331430582. +2023-09-19 03:02:38,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092558.0, "order_id": "x-XEKWYICXSSIUT605ad7a6334850582", "exchange_order_id": "35533010", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:02:38,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad7a6334850582. +2023-09-19 03:02:38,603 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad7daf66910582 for 80.00000000 SEI-USDT. +2023-09-19 03:02:38,636 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092558.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ad7daf66910582", "creation_timestamp": 1695092556.0, "exchange_order_id": "35533170", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:02:38,637 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad7daf621e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:02:38,656 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092558.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad7daf621e0582", "creation_timestamp": 1695092556.0, "exchange_order_id": "35533171", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:03:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad7daf621e0582. [clock=2023-09-19 03:03:31+00:00] +2023-09-19 03:03:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad7daf66910582. [clock=2023-09-19 03:03:31+00:00] +2023-09-19 03:03:31,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232227884852681285214716923 amount: 80. +2023-09-19 03:03:31,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235570919646184244103424991 amount: 80. +2023-09-19 03:03:31,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092611.0, "order_id": "x-XEKWYICXBSIUT605ad7daf621e0582", "exchange_order_id": "35533171", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:03:31,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad7daf621e0582. +2023-09-19 03:03:32,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092611.0, "order_id": "x-XEKWYICXSSIUT605ad7daf66910582", "exchange_order_id": "35533170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:03:32,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad7daf66910582. +2023-09-19 03:03:32,798 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad80f1cb670582 for 80.00000000 SEI-USDT. +2023-09-19 03:03:32,829 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092612.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad80f1cb670582", "creation_timestamp": 1695092611.0, "exchange_order_id": "35533244", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:03:32,831 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad80f1ce550582 for 80.00000000 SEI-USDT. +2023-09-19 03:03:32,856 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092612.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ad80f1ce550582", "creation_timestamp": 1695092611.0, "exchange_order_id": "35533243", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:04:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad80f1cb670582. [clock=2023-09-19 03:04:26+00:00] +2023-09-19 03:04:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad80f1ce550582. [clock=2023-09-19 03:04:26+00:00] +2023-09-19 03:04:26,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232399678980554348301329857 amount: 80. +2023-09-19 03:04:26,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234999395987662259884743257 amount: 80. +2023-09-19 03:04:26,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092666.0, "order_id": "x-XEKWYICXBSIUT605ad80f1cb670582", "exchange_order_id": "35533244", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:04:26,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad80f1cb670582. +2023-09-19 03:04:27,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092666.0, "order_id": "x-XEKWYICXSSIUT605ad80f1ce550582", "exchange_order_id": "35533243", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:04:27,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad80f1ce550582. +2023-09-19 03:04:27,618 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad843900150582 for 80.00000000 SEI-USDT. +2023-09-19 03:04:27,657 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad843900150582", "creation_timestamp": 1695092666.0, "exchange_order_id": "35533342", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:04:27,658 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad8439022a0582 for 80.00000000 SEI-USDT. +2023-09-19 03:04:27,711 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad8439022a0582", "creation_timestamp": 1695092666.0, "exchange_order_id": "35533343", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:05:21,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad843900150582. [clock=2023-09-19 03:05:21+00:00] +2023-09-19 03:05:21,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad8439022a0582. [clock=2023-09-19 03:05:21+00:00] +2023-09-19 03:05:21,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232533212494506863786465970 amount: 80. +2023-09-19 03:05:21,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234554959830392412695820570 amount: 80. +2023-09-19 03:05:21,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092721.0, "order_id": "x-XEKWYICXBSIUT605ad843900150582", "exchange_order_id": "35533342", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:05:21,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad843900150582. +2023-09-19 03:05:21,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092721.0, "order_id": "x-XEKWYICXSSIUT605ad8439022a0582", "exchange_order_id": "35533343", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:05:21,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad8439022a0582. +2023-09-19 03:05:21,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad878029cc0582 for 80.00000000 SEI-USDT. +2023-09-19 03:05:21,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad878029cc0582", "creation_timestamp": 1695092721.0, "exchange_order_id": "35533433", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:05:21,516 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad878027bc0582 for 80.00000000 SEI-USDT. +2023-09-19 03:05:21,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad878027bc0582", "creation_timestamp": 1695092721.0, "exchange_order_id": "35533432", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:06:16,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad878027bc0582. [clock=2023-09-19 03:06:16+00:00] +2023-09-19 03:06:16,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad878029cc0582. [clock=2023-09-19 03:06:16+00:00] +2023-09-19 03:06:16,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232637020993689176227766935 amount: 80. +2023-09-19 03:06:16,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234209337016462879044038937 amount: 80. +2023-09-19 03:06:16,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092776.0, "order_id": "x-XEKWYICXBSIUT605ad878027bc0582", "exchange_order_id": "35533432", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:06:16,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad878027bc0582. +2023-09-19 03:06:16,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad8ac711030582 for 80.00000000 SEI-USDT. +2023-09-19 03:06:16,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad8ac711030582", "creation_timestamp": 1695092776.0, "exchange_order_id": "35533517", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:06:16,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092776.0, "order_id": "x-XEKWYICXSSIUT605ad878029cc0582", "exchange_order_id": "35533433", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:06:16,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad878029cc0582. +2023-09-19 03:06:16,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad8ac714ee0582 for 80.00000000 SEI-USDT. +2023-09-19 03:06:16,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad8ac714ee0582", "creation_timestamp": 1695092776.0, "exchange_order_id": "35533518", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:07:11,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad8ac711030582. [clock=2023-09-19 03:07:11+00:00] +2023-09-19 03:07:11,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad8ac714ee0582. [clock=2023-09-19 03:07:11+00:00] +2023-09-19 03:07:11,138 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232717730145607965473296328 amount: 80. +2023-09-19 03:07:11,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12340000 amount: 80. +2023-09-19 03:07:11,543 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092831.0, "order_id": "x-XEKWYICXBSIUT605ad8ac711030582", "exchange_order_id": "35533517", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:07:11,543 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad8ac711030582. +2023-09-19 03:07:12,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092831.0, "order_id": "x-XEKWYICXSSIUT605ad8ac714ee0582", "exchange_order_id": "35533518", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:07:12,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad8ac714ee0582. +2023-09-19 03:07:13,673 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad8e0fb9d50582 for 80.00000000 SEI-USDT. +2023-09-19 03:07:13,715 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092832.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ad8e0fb9d50582", "creation_timestamp": 1695092831.0, "exchange_order_id": "35533661", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:07:13,718 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad8e0fb6f30582 for 80.00000000 SEI-USDT. +2023-09-19 03:07:13,773 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092832.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605ad8e0fb6f30582", "creation_timestamp": 1695092831.0, "exchange_order_id": "35533660", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:08:06,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad8e0fb6f30582. [clock=2023-09-19 03:08:06+00:00] +2023-09-19 03:08:06,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad8e0fb9d50582. [clock=2023-09-19 03:08:06+00:00] +2023-09-19 03:08:06,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231313120241926453383552795 amount: 80. +2023-09-19 03:08:06,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233128470859288867751385265 amount: 80. +2023-09-19 03:08:07,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092886.0, "order_id": "x-XEKWYICXBSIUT605ad8e0fb6f30582", "exchange_order_id": "35533660", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:08:07,088 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad8e0fb6f30582. +2023-09-19 03:08:07,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092886.0, "order_id": "x-XEKWYICXSSIUT605ad8e0fb9d50582", "exchange_order_id": "35533661", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:08:07,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad8e0fb9d50582. +2023-09-19 03:08:08,662 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad9157e8120582 for 80.00000000 SEI-USDT. +2023-09-19 03:08:08,708 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092888.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad9157e8120582", "creation_timestamp": 1695092886.0, "exchange_order_id": "35533877", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:08:08,906 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad9157aedb0582 for 80.00000000 SEI-USDT. +2023-09-19 03:08:08,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092888.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad9157aedb0582", "creation_timestamp": 1695092886.0, "exchange_order_id": "35533878", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:09:01,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad9157aedb0582. [clock=2023-09-19 03:09:01+00:00] +2023-09-19 03:09:01,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad9157e8120582. [clock=2023-09-19 03:09:01+00:00] +2023-09-19 03:09:01,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231313376871630379892454683 amount: 80. +2023-09-19 03:09:01,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233128727867347319845938007 amount: 80. +2023-09-19 03:09:01,889 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092941.0, "order_id": "x-XEKWYICXBSIUT605ad9157aedb0582", "exchange_order_id": "35533878", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:09:01,889 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad9157aedb0582. +2023-09-19 03:09:03,910 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092943.0, "order_id": "x-XEKWYICXSSIUT605ad9157e8120582", "exchange_order_id": "35533877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:09:03,911 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad9157e8120582. +2023-09-19 03:09:04,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad949fd8060582 for 80.00000000 SEI-USDT. +2023-09-19 03:09:04,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092943.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad949fd8060582", "creation_timestamp": 1695092941.0, "exchange_order_id": "35533968", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:09:04,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad949fd51c0582 for 80.00000000 SEI-USDT. +2023-09-19 03:09:04,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092944.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad949fd51c0582", "creation_timestamp": 1695092941.0, "exchange_order_id": "35533969", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:09:56,374 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad949fd51c0582. [clock=2023-09-19 03:09:56+00:00] +2023-09-19 03:09:56,387 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad949fd8060582. [clock=2023-09-19 03:09:56+00:00] +2023-09-19 03:09:56,425 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231466007378989295246834610 amount: 80. +2023-09-19 03:09:56,426 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12330000 amount: 80. +2023-09-19 03:09:56,875 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092996.0, "order_id": "x-XEKWYICXBSIUT605ad949fd51c0582", "exchange_order_id": "35533969", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:09:56,875 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad949fd51c0582. +2023-09-19 03:09:57,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092997.0, "order_id": "x-XEKWYICXSSIUT605ad949fd8060582", "exchange_order_id": "35533968", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:09:57,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad949fd8060582. +2023-09-19 03:09:57,692 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad97e9ca2d0582 for 80.00000000 SEI-USDT. +2023-09-19 03:09:57,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092997.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad97e9ca2d0582", "creation_timestamp": 1695092996.0, "exchange_order_id": "35534054", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:09:57,722 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad97e9cf3c0582 for 80.00000000 SEI-USDT. +2023-09-19 03:09:57,745 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695092997.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad97e9cf3c0582", "creation_timestamp": 1695092996.0, "exchange_order_id": "35534055", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:10:51,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad97e9ca2d0582. [clock=2023-09-19 03:10:51+00:00] +2023-09-19 03:10:51,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad97e9cf3c0582. [clock=2023-09-19 03:10:51+00:00] +2023-09-19 03:10:51,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231584701198283583312140382 amount: 80. +2023-09-19 03:10:51,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12330000 amount: 80. +2023-09-19 03:10:51,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093051.0, "order_id": "x-XEKWYICXBSIUT605ad97e9ca2d0582", "exchange_order_id": "35534054", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:10:51,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad97e9ca2d0582. +2023-09-19 03:10:51,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad9b2bc8650582 for 80.00000000 SEI-USDT. +2023-09-19 03:10:51,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ad9b2bc8650582", "creation_timestamp": 1695093051.0, "exchange_order_id": "35534140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:10:51,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093051.0, "order_id": "x-XEKWYICXSSIUT605ad97e9cf3c0582", "exchange_order_id": "35534055", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:10:51,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad97e9cf3c0582. +2023-09-19 03:10:51,447 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad9b2bcb940582 for 80.00000000 SEI-USDT. +2023-09-19 03:10:51,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ad9b2bcb940582", "creation_timestamp": 1695093051.0, "exchange_order_id": "35534141", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:11:05,658 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ad9b2bc8650582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:11:05,659 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:11:05+00:00] +2023-09-19 03:11:05,682 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093065.0, "order_id": "x-XEKWYICXBSIUT605ad9b2bc8650582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4989670", "exchange_order_id": "35534140", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:11:05,694 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093065.0, "order_id": "x-XEKWYICXBSIUT605ad9b2bc8650582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35534140", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:11:05,695 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ad9b2bc8650582 completely filled. +2023-09-19 03:11:46,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ad9b2bcb940582. [clock=2023-09-19 03:11:46+00:00] +2023-09-19 03:11:46,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1223620042037720251423396907 amount: 80. +2023-09-19 03:11:46,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12290000 amount: 80. +2023-09-19 03:11:46,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093106.0, "order_id": "x-XEKWYICXSSIUT605ad9b2bcb940582", "exchange_order_id": "35534141", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:11:46,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ad9b2bcb940582. +2023-09-19 03:11:46,632 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ad9e72a5250582 for 80.00000000 SEI-USDT. +2023-09-19 03:11:46,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12230000", "order_id": "x-XEKWYICXBSIUT605ad9e72a5250582", "creation_timestamp": 1695093106.0, "exchange_order_id": "35534792", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:11:46,717 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ad9e72a7390582 for 80.00000000 SEI-USDT. +2023-09-19 03:11:46,729 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ad9e72a7390582", "creation_timestamp": 1695093106.0, "exchange_order_id": "35534793", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:12:35,195 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ad9e72a7390582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:12:35,198 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:12:34+00:00] +2023-09-19 03:12:35,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093154.0, "order_id": "x-XEKWYICXSSIUT605ad9e72a7390582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12290000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00983200"}]}, "exchange_trade_id": "4989731", "exchange_order_id": "35534793", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:12:35,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093155.0, "order_id": "x-XEKWYICXSSIUT605ad9e72a7390582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35534793", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:12:35,442 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ad9e72a7390582 completely filled. +2023-09-19 03:12:41,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ad9e72a5250582. [clock=2023-09-19 03:12:41+00:00] +2023-09-19 03:12:41,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225209037300495734818121168 amount: 80. +2023-09-19 03:12:41,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230436135126019688230872539 amount: 80. +2023-09-19 03:12:41,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093161.0, "order_id": "x-XEKWYICXBSIUT605ad9e72a5250582", "exchange_order_id": "35534792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:12:41,513 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ad9e72a5250582. +2023-09-19 03:12:42,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ada1bb2a9b0582 for 80.00000000 SEI-USDT. +2023-09-19 03:12:42,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ada1bb2a9b0582", "creation_timestamp": 1695093161.0, "exchange_order_id": "35534975", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:12:42,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ada1bb2e0e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:12:42,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093162.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ada1bb2e0e0582", "creation_timestamp": 1695093161.0, "exchange_order_id": "35534979", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:12:44,889 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 03:13:05,869 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ada1bb2e0e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:13:05,870 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:13:05+00:00] +2023-09-19 03:13:05,924 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093185.0, "order_id": "x-XEKWYICXSSIUT605ada1bb2e0e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12300000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00984000"}]}, "exchange_trade_id": "4989750", "exchange_order_id": "35534979", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:13:06,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093185.0, "order_id": "x-XEKWYICXSSIUT605ada1bb2e0e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35534979", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:13:06,390 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ada1bb2e0e0582 completely filled. +2023-09-19 03:13:36,123 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ada1bb2a9b0582. [clock=2023-09-19 03:13:36+00:00] +2023-09-19 03:13:36,194 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228948992217572577978887783 amount: 80. +2023-09-19 03:13:36,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235246243504758594871001067 amount: 80. +2023-09-19 03:13:36,900 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093216.0, "order_id": "x-XEKWYICXBSIUT605ada1bb2a9b0582", "exchange_order_id": "35534975", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:13:36,901 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ada1bb2a9b0582. +2023-09-19 03:13:38,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ada50333b30582 for 80.00000000 SEI-USDT. +2023-09-19 03:13:38,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093217.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ada50333b30582", "creation_timestamp": 1695093216.0, "exchange_order_id": "35535077", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:13:38,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ada50337930582 for 80.00000000 SEI-USDT. +2023-09-19 03:13:38,729 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093218.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ada50337930582", "creation_timestamp": 1695093216.0, "exchange_order_id": "35535083", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:14:31,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ada50333b30582. [clock=2023-09-19 03:14:31+00:00] +2023-09-19 03:14:31,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ada50337930582. [clock=2023-09-19 03:14:31+00:00] +2023-09-19 03:14:31,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230378770314661392827284462 amount: 80. +2023-09-19 03:14:31,218 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:14:32,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093271.0, "order_id": "x-XEKWYICXBSIUT605ada50333b30582", "exchange_order_id": "35535077", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:14:32,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ada50333b30582. +2023-09-19 03:14:32,932 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093272.0, "order_id": "x-XEKWYICXSSIUT605ada50337930582", "exchange_order_id": "35535083", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:14:32,933 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ada50337930582. +2023-09-19 03:14:33,062 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ada84aca8d0582 for 80.00000000 SEI-USDT. +2023-09-19 03:14:33,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093272.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ada84aca8d0582", "creation_timestamp": 1695093271.0, "exchange_order_id": "35535317", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:15:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ada84aca8d0582. [clock=2023-09-19 03:15:26+00:00] +2023-09-19 03:15:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229131720969402815830051951 amount: 80. +2023-09-19 03:15:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234670221356122888656704386 amount: 80. +2023-09-19 03:15:26,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093326.0, "order_id": "x-XEKWYICXBSIUT605ada84aca8d0582", "exchange_order_id": "35535317", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:15:26,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ada84aca8d0582. +2023-09-19 03:15:26,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adab8ef6da0582 for 80.00000000 SEI-USDT. +2023-09-19 03:15:26,558 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605adab8ef6da0582", "creation_timestamp": 1695093326.0, "exchange_order_id": "35535450", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:15:26,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adab8ef4a30582 for 80.00000000 SEI-USDT. +2023-09-19 03:15:26,573 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605adab8ef4a30582", "creation_timestamp": 1695093326.0, "exchange_order_id": "35535451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:16:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adab8ef4a30582. [clock=2023-09-19 03:16:21+00:00] +2023-09-19 03:16:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adab8ef6da0582. [clock=2023-09-19 03:16:21+00:00] +2023-09-19 03:16:21,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227331943089289672746466285 amount: 80. +2023-09-19 03:16:21,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:16:21,139 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093381.0, "order_id": "x-XEKWYICXBSIUT605adab8ef4a30582", "exchange_order_id": "35535451", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:16:21,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adab8ef4a30582. +2023-09-19 03:16:21,348 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adaed62ff40582 for 80.00000000 SEI-USDT. +2023-09-19 03:16:21,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605adaed62ff40582", "creation_timestamp": 1695093381.0, "exchange_order_id": "35535690", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:16:21,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093381.0, "order_id": "x-XEKWYICXSSIUT605adab8ef6da0582", "exchange_order_id": "35535450", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:16:21,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adab8ef6da0582. +2023-09-19 03:17:16,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adaed62ff40582. [clock=2023-09-19 03:17:16+00:00] +2023-09-19 03:17:16,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227703900361400717843136157 amount: 80. +2023-09-19 03:17:16,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232400224837961434102439681 amount: 80. +2023-09-19 03:17:16,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093436.0, "order_id": "x-XEKWYICXBSIUT605adaed62ff40582", "exchange_order_id": "35535690", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:17:16,498 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adaed62ff40582. +2023-09-19 03:17:17,678 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adb21e9b6f0582 for 80.00000000 SEI-USDT. +2023-09-19 03:17:17,713 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093437.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605adb21e9b6f0582", "creation_timestamp": 1695093436.0, "exchange_order_id": "35535777", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:17:17,714 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adb21e997e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:17:17,734 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093437.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605adb21e997e0582", "creation_timestamp": 1695093436.0, "exchange_order_id": "35535776", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:18:11,269 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adb21e997e0582. [clock=2023-09-19 03:18:11+00:00] +2023-09-19 03:18:11,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adb21e9b6f0582. [clock=2023-09-19 03:18:11+00:00] +2023-09-19 03:18:11,348 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227992447957807809152466263 amount: 80. +2023-09-19 03:18:11,349 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:18:11,870 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093491.0, "order_id": "x-XEKWYICXBSIUT605adb21e997e0582", "exchange_order_id": "35535776", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:18:11,870 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adb21e997e0582. +2023-09-19 03:18:11,911 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093491.0, "order_id": "x-XEKWYICXSSIUT605adb21e9b6f0582", "exchange_order_id": "35535777", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:18:11,912 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adb21e9b6f0582. +2023-09-19 03:18:13,799 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adb569b7d20582 for 80.00000000 SEI-USDT. +2023-09-19 03:18:13,839 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093492.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605adb569b7d20582", "creation_timestamp": 1695093491.0, "exchange_order_id": "35535977", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:19:06,403 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adb569b7d20582. [clock=2023-09-19 03:19:06+00:00] +2023-09-19 03:19:06,467 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227977738768556759951361591 amount: 80. +2023-09-19 03:19:06,470 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231682994561767270191145347 amount: 80. +2023-09-19 03:19:07,638 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093547.0, "order_id": "x-XEKWYICXBSIUT605adb569b7d20582", "exchange_order_id": "35535977", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:19:07,639 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adb569b7d20582. +2023-09-19 03:19:07,641 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adb8b2cd5b0582 for 80.00000000 SEI-USDT. +2023-09-19 03:19:07,669 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093547.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605adb8b2cd5b0582", "creation_timestamp": 1695093546.0, "exchange_order_id": "35536098", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:19:07,854 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adb8b2ca130582 for 80.00000000 SEI-USDT. +2023-09-19 03:19:07,887 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093547.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605adb8b2ca130582", "creation_timestamp": 1695093546.0, "exchange_order_id": "35536099", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:20:01,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adb8b2ca130582. [clock=2023-09-19 03:20:01+00:00] +2023-09-19 03:20:01,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adb8b2cd5b0582. [clock=2023-09-19 03:20:01+00:00] +2023-09-19 03:20:01,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228234207092956738411259618 amount: 80. +2023-09-19 03:20:01,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:20:01,574 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093601.0, "order_id": "x-XEKWYICXBSIUT605adb8b2ca130582", "exchange_order_id": "35536099", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:20:01,575 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adb8b2ca130582. +2023-09-19 03:20:02,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093602.0, "order_id": "x-XEKWYICXSSIUT605adb8b2cd5b0582", "exchange_order_id": "35536098", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:20:02,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adb8b2cd5b0582. +2023-09-19 03:20:02,694 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adbbf3bca70582 for 80.00000000 SEI-USDT. +2023-09-19 03:20:02,726 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093602.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adbbf3bca70582", "creation_timestamp": 1695093601.0, "exchange_order_id": "35536233", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:20:56,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adbbf3bca70582. [clock=2023-09-19 03:20:56+00:00] +2023-09-19 03:20:56,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226844730546270134890801103 amount: 80. +2023-09-19 03:20:56,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231701037045836976556115045 amount: 80. +2023-09-19 03:20:56,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093656.0, "order_id": "x-XEKWYICXBSIUT605adbbf3bca70582", "exchange_order_id": "35536233", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:20:56,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adbbf3bca70582. +2023-09-19 03:20:56,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adbf3ab9910582 for 80.00000000 SEI-USDT. +2023-09-19 03:20:56,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605adbf3ab9910582", "creation_timestamp": 1695093656.0, "exchange_order_id": "35536470", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:20:56,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adbf3ab6010582 for 80.00000000 SEI-USDT. +2023-09-19 03:20:56,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605adbf3ab6010582", "creation_timestamp": 1695093656.0, "exchange_order_id": "35536471", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:21:51,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adbf3ab6010582. [clock=2023-09-19 03:21:51+00:00] +2023-09-19 03:21:51,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adbf3ab9910582. [clock=2023-09-19 03:21:51+00:00] +2023-09-19 03:21:51,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226530234930834735754577345 amount: 80. +2023-09-19 03:21:51,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:21:51,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093711.0, "order_id": "x-XEKWYICXBSIUT605adbf3ab6010582", "exchange_order_id": "35536471", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:21:51,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adbf3ab6010582. +2023-09-19 03:21:51,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093711.0, "order_id": "x-XEKWYICXSSIUT605adbf3ab9910582", "exchange_order_id": "35536470", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:21:51,437 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adbf3ab9910582. +2023-09-19 03:21:51,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adc282ab9e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:21:51,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605adc282ab9e0582", "creation_timestamp": 1695093711.0, "exchange_order_id": "35536568", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:22:46,230 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adc282ab9e0582. [clock=2023-09-19 03:22:46+00:00] +2023-09-19 03:22:46,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226857409832773183895584987 amount: 80. +2023-09-19 03:22:46,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230661071127034748220312091 amount: 80. +2023-09-19 03:22:47,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093766.0, "order_id": "x-XEKWYICXBSIUT605adc282ab9e0582", "exchange_order_id": "35536568", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:22:47,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adc282ab9e0582. +2023-09-19 03:22:49,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adc5cd60980582 for 80.00000000 SEI-USDT. +2023-09-19 03:22:49,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605adc5cd60980582", "creation_timestamp": 1695093766.0, "exchange_order_id": "35536675", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:22:49,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adc5cd662c0582 for 80.00000000 SEI-USDT. +2023-09-19 03:22:49,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605adc5cd662c0582", "creation_timestamp": 1695093766.0, "exchange_order_id": "35536676", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:23:41,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adc5cd60980582. [clock=2023-09-19 03:23:41+00:00] +2023-09-19 03:23:41,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adc5cd662c0582. [clock=2023-09-19 03:23:41+00:00] +2023-09-19 03:23:41,192 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227111657898785873720858945 amount: 80. +2023-09-19 03:23:41,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:23:42,031 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093821.0, "order_id": "x-XEKWYICXBSIUT605adc5cd60980582", "exchange_order_id": "35536675", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:23:42,031 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adc5cd60980582. +2023-09-19 03:23:43,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093822.0, "order_id": "x-XEKWYICXSSIUT605adc5cd662c0582", "exchange_order_id": "35536676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:23:43,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adc5cd662c0582. +2023-09-19 03:23:43,844 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adc912fe090582 for 80.00000000 SEI-USDT. +2023-09-19 03:23:43,964 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093823.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605adc912fe090582", "creation_timestamp": 1695093821.0, "exchange_order_id": "35536746", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:24:36,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adc912fe090582. [clock=2023-09-19 03:24:36+00:00] +2023-09-19 03:24:36,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228424935565394695509327361 amount: 80. +2023-09-19 03:24:36,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231591286760584510081385858 amount: 80. +2023-09-19 03:24:37,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093876.0, "order_id": "x-XEKWYICXBSIUT605adc912fe090582", "exchange_order_id": "35536746", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:24:37,117 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adc912fe090582. +2023-09-19 03:24:38,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adcc5a9b310582 for 80.00000000 SEI-USDT. +2023-09-19 03:24:38,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093877.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adcc5a9b310582", "creation_timestamp": 1695093876.0, "exchange_order_id": "35536901", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:24:38,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adcc5abfa60582 for 80.00000000 SEI-USDT. +2023-09-19 03:24:38,665 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093878.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605adcc5abfa60582", "creation_timestamp": 1695093876.0, "exchange_order_id": "35536902", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:25:31,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adcc5a9b310582. [clock=2023-09-19 03:25:31+00:00] +2023-09-19 03:25:31,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adcc5abfa60582. [clock=2023-09-19 03:25:31+00:00] +2023-09-19 03:25:31,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228552767766042709884266329 amount: 80. +2023-09-19 03:25:31,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:25:31,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093931.0, "order_id": "x-XEKWYICXBSIUT605adcc5a9b310582", "exchange_order_id": "35536901", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:25:31,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adcc5a9b310582. +2023-09-19 03:25:31,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adcf9f7d370582 for 80.00000000 SEI-USDT. +2023-09-19 03:25:31,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adcf9f7d370582", "creation_timestamp": 1695093931.0, "exchange_order_id": "35537010", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:25:31,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093931.0, "order_id": "x-XEKWYICXSSIUT605adcc5abfa60582", "exchange_order_id": "35536902", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:25:31,490 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adcc5abfa60582. +2023-09-19 03:26:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adcf9f7d370582. [clock=2023-09-19 03:26:26+00:00] +2023-09-19 03:26:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227652590091794220032307712 amount: 80. +2023-09-19 03:26:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229565858675466812003567670 amount: 80. +2023-09-19 03:26:26,137 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093986.0, "order_id": "x-XEKWYICXBSIUT605adcf9f7d370582", "exchange_order_id": "35537010", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:26:26,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adcf9f7d370582. +2023-09-19 03:26:26,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605add2e5c6790582 for 80.00000000 SEI-USDT. +2023-09-19 03:26:26,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605add2e5c6790582", "creation_timestamp": 1695093986.0, "exchange_order_id": "35537163", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:26:26,357 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605add2e5c4370582 for 80.00000000 SEI-USDT. +2023-09-19 03:26:26,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695093986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605add2e5c4370582", "creation_timestamp": 1695093986.0, "exchange_order_id": "35537164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:26:48,786 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605add2e5c6790582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:26:48,788 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:26:48+00:00] +2023-09-19 03:26:48,808 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094008.0, "order_id": "x-XEKWYICXSSIUT605add2e5c6790582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12290000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00983200"}]}, "exchange_trade_id": "4989928", "exchange_order_id": "35537163", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:26:48,819 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094008.0, "order_id": "x-XEKWYICXSSIUT605add2e5c6790582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35537163", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:26:48,820 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605add2e5c6790582 completely filled. +2023-09-19 03:27:21,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605add2e5c4370582. [clock=2023-09-19 03:27:21+00:00] +2023-09-19 03:27:21,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227881356168453400697363311 amount: 80. +2023-09-19 03:27:21,197 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:27:21,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094041.0, "order_id": "x-XEKWYICXBSIUT605add2e5c4370582", "exchange_order_id": "35537164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:27:21,555 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605add2e5c4370582. +2023-09-19 03:27:22,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605add62fbb540582 for 80.00000000 SEI-USDT. +2023-09-19 03:27:22,116 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094042.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605add62fbb540582", "creation_timestamp": 1695094041.0, "exchange_order_id": "35537254", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:28:16,307 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605add62fbb540582. [clock=2023-09-19 03:28:16+00:00] +2023-09-19 03:28:16,375 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227907795679956441280779718 amount: 80. +2023-09-19 03:28:16,377 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:28:17,694 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094097.0, "order_id": "x-XEKWYICXBSIUT605add62fbb540582", "exchange_order_id": "35537254", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:28:17,694 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605add62fbb540582. +2023-09-19 03:28:18,036 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605add979b4440582 for 80.00000000 SEI-USDT. +2023-09-19 03:28:18,090 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094097.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605add979b4440582", "creation_timestamp": 1695094096.0, "exchange_order_id": "35537339", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:29:11,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605add979b4440582. [clock=2023-09-19 03:29:11+00:00] +2023-09-19 03:29:11,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227928330425981446404160829 amount: 80. +2023-09-19 03:29:11,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:29:11,734 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094151.0, "order_id": "x-XEKWYICXBSIUT605add979b4440582", "exchange_order_id": "35537339", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:29:11,734 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605add979b4440582. +2023-09-19 03:29:13,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605addcbf7f100582 for 80.00000000 SEI-USDT. +2023-09-19 03:29:13,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094152.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605addcbf7f100582", "creation_timestamp": 1695094151.0, "exchange_order_id": "35537429", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:30:06,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605addcbf7f100582. [clock=2023-09-19 03:30:06+00:00] +2023-09-19 03:30:06,248 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227944284158571761825232038 amount: 80. +2023-09-19 03:30:06,249 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:30:06,688 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094206.0, "order_id": "x-XEKWYICXBSIUT605addcbf7f100582", "exchange_order_id": "35537429", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:30:06,688 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605addcbf7f100582. +2023-09-19 03:30:06,926 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ade00637ff0582 for 80.00000000 SEI-USDT. +2023-09-19 03:30:06,954 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ade00637ff0582", "creation_timestamp": 1695094206.0, "exchange_order_id": "35537505", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:31:01,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ade00637ff0582. [clock=2023-09-19 03:31:01+00:00] +2023-09-19 03:31:01,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227944284158571761825232038 amount: 80. +2023-09-19 03:31:01,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:31:01,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094261.0, "order_id": "x-XEKWYICXBSIUT605ade00637ff0582", "exchange_order_id": "35537505", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:31:01,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ade00637ff0582. +2023-09-19 03:31:01,422 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ade34b20df0582 for 80.00000000 SEI-USDT. +2023-09-19 03:31:01,436 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ade34b20df0582", "creation_timestamp": 1695094261.0, "exchange_order_id": "35537584", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:31:56,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ade34b20df0582. [clock=2023-09-19 03:31:56+00:00] +2023-09-19 03:31:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227956681887425394906361780 amount: 80. +2023-09-19 03:31:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:31:56,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094316.0, "order_id": "x-XEKWYICXBSIUT605ade34b20df0582", "exchange_order_id": "35537584", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:31:56,107 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ade34b20df0582. +2023-09-19 03:31:56,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ade6913ce80582 for 80.00000000 SEI-USDT. +2023-09-19 03:31:56,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ade6913ce80582", "creation_timestamp": 1695094316.0, "exchange_order_id": "35537672", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:32:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ade6913ce80582. [clock=2023-09-19 03:32:51+00:00] +2023-09-19 03:32:51,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12290000 amount: 80. +2023-09-19 03:32:51,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:32:51,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094371.0, "order_id": "x-XEKWYICXBSIUT605ade6913ce80582", "exchange_order_id": "35537672", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:32:51,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ade6913ce80582. +2023-09-19 03:32:51,959 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ade9d931320582 for 80.00000000 SEI-USDT. +2023-09-19 03:32:51,995 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ade9d931320582", "creation_timestamp": 1695094371.0, "exchange_order_id": "35537880", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:33:46,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ade9d931320582. [clock=2023-09-19 03:33:46+00:00] +2023-09-19 03:33:46,321 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12300000 amount: 80. +2023-09-19 03:33:46,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:33:46,771 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094426.0, "order_id": "x-XEKWYICXBSIUT605ade9d931320582", "exchange_order_id": "35537880", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:33:46,771 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ade9d931320582. +2023-09-19 03:33:48,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aded2445d60582 for 80.00000000 SEI-USDT. +2023-09-19 03:33:48,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094427.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605aded2445d60582", "creation_timestamp": 1695094426.0, "exchange_order_id": "35538181", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:34:11,584 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aded2445d60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:34:11,585 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:34:11+00:00] +2023-09-19 03:34:11,657 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094451.0, "order_id": "x-XEKWYICXBSIUT605aded2445d60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12300000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4989966", "exchange_order_id": "35538181", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:34:11,870 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094451.0, "order_id": "x-XEKWYICXBSIUT605aded2445d60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35538181", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:34:11,870 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aded2445d60582 completely filled. +2023-09-19 03:34:41,258 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228620402190768951874993531 amount: 80. +2023-09-19 03:34:41,260 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230661035952372560566484328 amount: 80. +2023-09-19 03:34:42,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adf06a8fbb0582 for 80.00000000 SEI-USDT. +2023-09-19 03:34:42,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094482.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adf06a8fbb0582", "creation_timestamp": 1695094481.0, "exchange_order_id": "35538336", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:34:43,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adf06a93830582 for 80.00000000 SEI-USDT. +2023-09-19 03:34:43,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094483.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605adf06a93830582", "creation_timestamp": 1695094481.0, "exchange_order_id": "35538338", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:35:36,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adf06a8fbb0582. [clock=2023-09-19 03:35:36+00:00] +2023-09-19 03:35:36,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adf06a93830582. [clock=2023-09-19 03:35:36+00:00] +2023-09-19 03:35:36,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228909099721889301224576162 amount: 80. +2023-09-19 03:35:36,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:35:36,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094536.0, "order_id": "x-XEKWYICXBSIUT605adf06a8fbb0582", "exchange_order_id": "35538336", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:35:36,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adf06a8fbb0582. +2023-09-19 03:35:36,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094536.0, "order_id": "x-XEKWYICXSSIUT605adf06a93830582", "exchange_order_id": "35538338", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:35:36,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adf06a93830582. +2023-09-19 03:35:36,437 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adf3af1ab60582 for 80.00000000 SEI-USDT. +2023-09-19 03:35:36,448 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adf3af1ab60582", "creation_timestamp": 1695094536.0, "exchange_order_id": "35538428", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:36:31,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adf3af1ab60582. [clock=2023-09-19 03:36:31+00:00] +2023-09-19 03:36:31,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228929503806368899769361636 amount: 80. +2023-09-19 03:36:31,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231027478881740086693938382 amount: 80. +2023-09-19 03:36:31,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094591.0, "order_id": "x-XEKWYICXBSIUT605adf3af1ab60582", "exchange_order_id": "35538428", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:36:31,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adf3af1ab60582. +2023-09-19 03:36:31,367 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adf6f5855c0582 for 80.00000000 SEI-USDT. +2023-09-19 03:36:31,380 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adf6f5855c0582", "creation_timestamp": 1695094591.0, "exchange_order_id": "35538506", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:36:31,383 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adf6f5876b0582 for 80.00000000 SEI-USDT. +2023-09-19 03:36:31,398 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605adf6f5876b0582", "creation_timestamp": 1695094591.0, "exchange_order_id": "35538507", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:37:26,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adf6f5855c0582. [clock=2023-09-19 03:37:26+00:00] +2023-09-19 03:37:26,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adf6f5876b0582. [clock=2023-09-19 03:37:26+00:00] +2023-09-19 03:37:26,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228945293039274676282875088 amount: 80. +2023-09-19 03:37:26,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:37:26,629 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094646.0, "order_id": "x-XEKWYICXBSIUT605adf6f5855c0582", "exchange_order_id": "35538506", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:37:26,630 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adf6f5855c0582. +2023-09-19 03:37:27,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094647.0, "order_id": "x-XEKWYICXSSIUT605adf6f5876b0582", "exchange_order_id": "35538507", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:37:27,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adf6f5876b0582. +2023-09-19 03:37:27,303 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adfa3e77eb0582 for 80.00000000 SEI-USDT. +2023-09-19 03:37:27,334 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094647.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adfa3e77eb0582", "creation_timestamp": 1695094646.0, "exchange_order_id": "35538603", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:38:21,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adfa3e77eb0582. [clock=2023-09-19 03:38:21+00:00] +2023-09-19 03:38:21,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228957524794112915856955743 amount: 80. +2023-09-19 03:38:21,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230226271306272340270983937 amount: 80. +2023-09-19 03:38:21,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094701.0, "order_id": "x-XEKWYICXBSIUT605adfa3e77eb0582", "exchange_order_id": "35538603", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:38:21,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adfa3e77eb0582. +2023-09-19 03:38:22,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605adfd8435b00582 for 80.00000000 SEI-USDT. +2023-09-19 03:38:22,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605adfd8435b00582", "creation_timestamp": 1695094701.0, "exchange_order_id": "35538704", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:38:22,416 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605adfd8438000582 for 80.00000000 SEI-USDT. +2023-09-19 03:38:22,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094702.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605adfd8438000582", "creation_timestamp": 1695094701.0, "exchange_order_id": "35538706", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:39:16,256 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605adfd8435b00582. [clock=2023-09-19 03:39:16+00:00] +2023-09-19 03:39:16,265 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605adfd8438000582. [clock=2023-09-19 03:39:16+00:00] +2023-09-19 03:39:16,338 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228967008886883034851814014 amount: 80. +2023-09-19 03:39:16,351 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:39:17,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094756.0, "order_id": "x-XEKWYICXBSIUT605adfd8435b00582", "exchange_order_id": "35538704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:39:17,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605adfd8435b00582. +2023-09-19 03:39:19,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094758.0, "order_id": "x-XEKWYICXSSIUT605adfd8438000582", "exchange_order_id": "35538706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:39:19,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605adfd8438000582. +2023-09-19 03:39:19,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae00d01de10582 for 80.00000000 SEI-USDT. +2023-09-19 03:39:19,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094758.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ae00d01de10582", "creation_timestamp": 1695094756.0, "exchange_order_id": "35538817", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:39:48,446 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae00d01de10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:39:48,447 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:39:48+00:00] +2023-09-19 03:39:48,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094788.0, "order_id": "x-XEKWYICXBSIUT605ae00d01de10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12280000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990019", "exchange_order_id": "35538817", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:39:48,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094788.0, "order_id": "x-XEKWYICXBSIUT605ae00d01de10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35538817", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:39:48,583 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae00d01de10582 completely filled. +2023-09-19 03:40:11,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226955889310107493504703054 amount: 80. +2023-09-19 03:40:11,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229452467466589962638687916 amount: 80. +2023-09-19 03:40:11,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae0412dc470582 for 80.00000000 SEI-USDT. +2023-09-19 03:40:11,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ae0412dc470582", "creation_timestamp": 1695094811.0, "exchange_order_id": "35539117", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:40:11,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae0412d93d0582 for 80.00000000 SEI-USDT. +2023-09-19 03:40:11,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae0412d93d0582", "creation_timestamp": 1695094811.0, "exchange_order_id": "35539118", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:41:06,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae0412d93d0582. [clock=2023-09-19 03:41:06+00:00] +2023-09-19 03:41:06,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae0412dc470582. [clock=2023-09-19 03:41:06+00:00] +2023-09-19 03:41:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227188271269319907747228916 amount: 80. +2023-09-19 03:41:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229129983054216524969072026 amount: 80. +2023-09-19 03:41:06,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094866.0, "order_id": "x-XEKWYICXBSIUT605ae0412d93d0582", "exchange_order_id": "35539118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:41:06,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae0412d93d0582. +2023-09-19 03:41:06,521 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae075995e00582 for 80.00000000 SEI-USDT. +2023-09-19 03:41:06,535 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ae075995e00582", "creation_timestamp": 1695094866.0, "exchange_order_id": "35539213", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:41:06,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae075992b60582 for 80.00000000 SEI-USDT. +2023-09-19 03:41:06,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae075992b60582", "creation_timestamp": 1695094866.0, "exchange_order_id": "35539214", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:41:06,561 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094866.0, "order_id": "x-XEKWYICXSSIUT605ae0412dc470582", "exchange_order_id": "35539117", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:41:06,562 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae0412dc470582. +2023-09-19 03:42:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae075992b60582. [clock=2023-09-19 03:42:01+00:00] +2023-09-19 03:42:01,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae075995e00582. [clock=2023-09-19 03:42:01+00:00] +2023-09-19 03:42:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227229254112390588582684418 amount: 80. +2023-09-19 03:42:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230281217487804063776917450 amount: 80. +2023-09-19 03:42:01,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094921.0, "order_id": "x-XEKWYICXBSIUT605ae075992b60582", "exchange_order_id": "35539214", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:42:01,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae075992b60582. +2023-09-19 03:42:01,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094921.0, "order_id": "x-XEKWYICXSSIUT605ae075995e00582", "exchange_order_id": "35539213", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:42:01,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae075995e00582. +2023-09-19 03:42:01,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae0aa0cb380582 for 80.00000000 SEI-USDT. +2023-09-19 03:42:01,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae0aa0cb380582", "creation_timestamp": 1695094921.0, "exchange_order_id": "35539303", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:42:01,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae0aa0c9060582 for 80.00000000 SEI-USDT. +2023-09-19 03:42:01,383 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae0aa0c9060582", "creation_timestamp": 1695094921.0, "exchange_order_id": "35539302", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:42:45,346 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 03:42:56,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae0aa0c9060582. [clock=2023-09-19 03:42:56+00:00] +2023-09-19 03:42:56,293 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae0aa0cb380582. [clock=2023-09-19 03:42:56+00:00] +2023-09-19 03:42:56,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226766757516447918427019432 amount: 80. +2023-09-19 03:42:56,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230253538675130825094918990 amount: 80. +2023-09-19 03:42:56,805 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094976.0, "order_id": "x-XEKWYICXBSIUT605ae0aa0c9060582", "exchange_order_id": "35539302", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:42:56,805 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae0aa0c9060582. +2023-09-19 03:42:57,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094977.0, "order_id": "x-XEKWYICXSSIUT605ae0aa0cb380582", "exchange_order_id": "35539303", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:42:57,648 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae0aa0cb380582. +2023-09-19 03:42:58,078 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae0dec9f560582 for 80.00000000 SEI-USDT. +2023-09-19 03:42:58,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094977.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae0dec9f560582", "creation_timestamp": 1695094976.0, "exchange_order_id": "35539386", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:42:58,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae0dec9b4f0582 for 80.00000000 SEI-USDT. +2023-09-19 03:42:58,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695094977.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae0dec9b4f0582", "creation_timestamp": 1695094976.0, "exchange_order_id": "35539387", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:43:38,392 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae0dec9b4f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:43:38,407 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:43:38+00:00] +2023-09-19 03:43:38,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095018.0, "order_id": "x-XEKWYICXBSIUT605ae0dec9b4f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12260000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990106", "exchange_order_id": "35539387", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:43:38,760 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095018.0, "order_id": "x-XEKWYICXBSIUT605ae0dec9b4f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35539387", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:43:38,760 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae0dec9b4f0582 completely filled. +2023-09-19 03:43:51,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae0dec9f560582. [clock=2023-09-19 03:43:51+00:00] +2023-09-19 03:43:51,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219092545761299209200118405 amount: 80. +2023-09-19 03:43:51,277 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226234156521848684413075719 amount: 80. +2023-09-19 03:43:51,875 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095031.0, "order_id": "x-XEKWYICXSSIUT605ae0dec9f560582", "exchange_order_id": "35539386", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:43:51,876 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae0dec9f560582. +2023-09-19 03:43:53,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae113326980582 for 80.00000000 SEI-USDT. +2023-09-19 03:43:53,602 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095033.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ae113326980582", "creation_timestamp": 1695095031.0, "exchange_order_id": "35540310", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:43:53,926 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae11332a960582 for 80.00000000 SEI-USDT. +2023-09-19 03:43:53,984 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095033.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ae11332a960582", "creation_timestamp": 1695095031.0, "exchange_order_id": "35540314", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:44:46,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae113326980582. [clock=2023-09-19 03:44:46+00:00] +2023-09-19 03:44:46,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae11332a960582. [clock=2023-09-19 03:44:46+00:00] +2023-09-19 03:44:46,192 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1218495833272872049526658577 amount: 80. +2023-09-19 03:44:46,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225154843910391371117488476 amount: 80. +2023-09-19 03:44:46,856 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095086.0, "order_id": "x-XEKWYICXBSIUT605ae113326980582", "exchange_order_id": "35540310", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:44:46,856 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae113326980582. +2023-09-19 03:44:48,054 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095087.0, "order_id": "x-XEKWYICXSSIUT605ae11332a960582", "exchange_order_id": "35540314", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:44:48,054 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae11332a960582. +2023-09-19 03:44:48,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae147970950582 for 80.00000000 SEI-USDT. +2023-09-19 03:44:48,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095088.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12180000", "order_id": "x-XEKWYICXBSIUT605ae147970950582", "creation_timestamp": 1695095086.0, "exchange_order_id": "35540619", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:44:48,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae147974910582 for 80.00000000 SEI-USDT. +2023-09-19 03:44:48,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095088.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ae147974910582", "creation_timestamp": 1695095086.0, "exchange_order_id": "35540620", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:44:49,429 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ae147974910582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:44:49,430 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:44:49+00:00] +2023-09-19 03:44:49,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095089.0, "order_id": "x-XEKWYICXSSIUT605ae147974910582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12250000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00980000"}]}, "exchange_trade_id": "4990164", "exchange_order_id": "35540620", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:44:49,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095089.0, "order_id": "x-XEKWYICXSSIUT605ae147974910582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35540620", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:44:49,624 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ae147974910582 completely filled. +2023-09-19 03:45:41,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae147970950582. [clock=2023-09-19 03:45:41+00:00] +2023-09-19 03:45:41,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1219632564871467388631450574 amount: 80. +2023-09-19 03:45:41,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1227037396411273088705490308 amount: 80. +2023-09-19 03:45:41,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095141.0, "order_id": "x-XEKWYICXBSIUT605ae147970950582", "exchange_order_id": "35540619", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:45:41,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae147970950582. +2023-09-19 03:45:41,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae17be86d10582 for 80.00000000 SEI-USDT. +2023-09-19 03:45:41,418 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12190000", "order_id": "x-XEKWYICXBSIUT605ae17be86d10582", "creation_timestamp": 1695095141.0, "exchange_order_id": "35540959", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:45:41,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae17be8a2c0582 for 80.00000000 SEI-USDT. +2023-09-19 03:45:41,429 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXSSIUT605ae17be8a2c0582", "creation_timestamp": 1695095141.0, "exchange_order_id": "35540960", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:46:36,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae17be86d10582. [clock=2023-09-19 03:46:36+00:00] +2023-09-19 03:46:36,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae17be8a2c0582. [clock=2023-09-19 03:46:36+00:00] +2023-09-19 03:46:36,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220824712109482640643532672 amount: 80. +2023-09-19 03:46:36,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1226585768667529441579512148 amount: 80. +2023-09-19 03:46:36,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095196.0, "order_id": "x-XEKWYICXBSIUT605ae17be86d10582", "exchange_order_id": "35540959", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:46:36,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae17be86d10582. +2023-09-19 03:46:36,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095196.0, "order_id": "x-XEKWYICXSSIUT605ae17be8a2c0582", "exchange_order_id": "35540960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:46:36,465 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae17be8a2c0582. +2023-09-19 03:46:36,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae1b0594ba0582 for 80.00000000 SEI-USDT. +2023-09-19 03:46:36,488 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ae1b0594ba0582", "creation_timestamp": 1695095196.0, "exchange_order_id": "35541313", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:46:36,488 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae1b0597c90582 for 80.00000000 SEI-USDT. +2023-09-19 03:46:36,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXSSIUT605ae1b0597c90582", "creation_timestamp": 1695095196.0, "exchange_order_id": "35541314", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:47:31,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae1b0594ba0582. [clock=2023-09-19 03:47:31+00:00] +2023-09-19 03:47:31,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae1b0597c90582. [clock=2023-09-19 03:47:31+00:00] +2023-09-19 03:47:31,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1220126940394924226277580581 amount: 80. +2023-09-19 03:47:31,286 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1225471452318036081683656525 amount: 80. +2023-09-19 03:47:32,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095251.0, "order_id": "x-XEKWYICXBSIUT605ae1b0594ba0582", "exchange_order_id": "35541313", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:47:32,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae1b0594ba0582. +2023-09-19 03:47:32,741 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095252.0, "order_id": "x-XEKWYICXSSIUT605ae1b0597c90582", "exchange_order_id": "35541314", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:47:32,742 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae1b0597c90582. +2023-09-19 03:47:32,926 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae1e503f690582 for 80.00000000 SEI-USDT. +2023-09-19 03:47:32,955 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095252.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXSSIUT605ae1e503f690582", "creation_timestamp": 1695095251.0, "exchange_order_id": "35541502", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:47:32,957 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae1e50079e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:47:32,989 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095252.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12200000", "order_id": "x-XEKWYICXBSIUT605ae1e50079e0582", "creation_timestamp": 1695095251.0, "exchange_order_id": "35541503", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:48:23,295 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ae1e503f690582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 03:48:23,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 03:48:23+00:00] +2023-09-19 03:48:23,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095303.0, "order_id": "x-XEKWYICXSSIUT605ae1e503f690582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12250000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00980000"}]}, "exchange_trade_id": "4990249", "exchange_order_id": "35541502", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 03:48:23,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095303.0, "order_id": "x-XEKWYICXSSIUT605ae1e503f690582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35541502", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 03:48:23,554 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ae1e503f690582 completely filled. +2023-09-19 03:48:26,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae1e50079e0582. [clock=2023-09-19 03:48:26+00:00] +2023-09-19 03:48:26,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1224770720966741789969280117 amount: 80. +2023-09-19 03:48:26,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230916180912752026633893540 amount: 80. +2023-09-19 03:48:26,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095306.0, "order_id": "x-XEKWYICXBSIUT605ae1e50079e0582", "exchange_order_id": "35541503", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:48:26,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae1e50079e0582. +2023-09-19 03:48:27,335 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae2194bdd00582 for 80.00000000 SEI-USDT. +2023-09-19 03:48:27,371 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095307.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605ae2194bdd00582", "creation_timestamp": 1695095306.0, "exchange_order_id": "35541863", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:48:27,704 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae2194c1130582 for 80.00000000 SEI-USDT. +2023-09-19 03:48:27,730 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095307.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae2194c1130582", "creation_timestamp": 1695095306.0, "exchange_order_id": "35541865", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:49:21,329 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae2194bdd00582. [clock=2023-09-19 03:49:21+00:00] +2023-09-19 03:49:21,346 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae2194c1130582. [clock=2023-09-19 03:49:21+00:00] +2023-09-19 03:49:21,420 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225317957366140226782875280 amount: 80. +2023-09-19 03:49:21,422 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:49:23,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095362.0, "order_id": "x-XEKWYICXBSIUT605ae2194bdd00582", "exchange_order_id": "35541863", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:49:23,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae2194bdd00582. +2023-09-19 03:49:23,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095363.0, "order_id": "x-XEKWYICXSSIUT605ae2194c1130582", "exchange_order_id": "35541865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:49:23,610 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae2194c1130582. +2023-09-19 03:49:23,611 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae24e0c23f0582 for 80.00000000 SEI-USDT. +2023-09-19 03:49:23,666 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095363.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ae24e0c23f0582", "creation_timestamp": 1695095361.0, "exchange_order_id": "35541969", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:50:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae24e0c23f0582. [clock=2023-09-19 03:50:16+00:00] +2023-09-19 03:50:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226672700940345885707688321 amount: 80. +2023-09-19 03:50:16,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232793919879787566163278773 amount: 80. +2023-09-19 03:50:16,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095416.0, "order_id": "x-XEKWYICXBSIUT605ae24e0c23f0582", "exchange_order_id": "35541969", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:50:16,203 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae24e0c23f0582. +2023-09-19 03:50:16,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae2821d5180582 for 80.00000000 SEI-USDT. +2023-09-19 03:50:16,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae2821d5180582", "creation_timestamp": 1695095416.0, "exchange_order_id": "35542193", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:50:16,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae2821d6f70582 for 80.00000000 SEI-USDT. +2023-09-19 03:50:16,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ae2821d6f70582", "creation_timestamp": 1695095416.0, "exchange_order_id": "35542194", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:51:11,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae2821d5180582. [clock=2023-09-19 03:51:11+00:00] +2023-09-19 03:51:11,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae2821d6f70582. [clock=2023-09-19 03:51:11+00:00] +2023-09-19 03:51:11,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226746056796940184008634823 amount: 80. +2023-09-19 03:51:11,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:51:11,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095471.0, "order_id": "x-XEKWYICXBSIUT605ae2821d5180582", "exchange_order_id": "35542193", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:51:11,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae2821d5180582. +2023-09-19 03:51:11,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095471.0, "order_id": "x-XEKWYICXSSIUT605ae2821d6f70582", "exchange_order_id": "35542194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:51:11,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae2821d6f70582. +2023-09-19 03:51:11,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae2b6a46290582 for 80.00000000 SEI-USDT. +2023-09-19 03:51:11,467 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae2b6a46290582", "creation_timestamp": 1695095471.0, "exchange_order_id": "35542291", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:52:06,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae2b6a46290582. [clock=2023-09-19 03:52:06+00:00] +2023-09-19 03:52:06,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226281412833735491629091482 amount: 80. +2023-09-19 03:52:06,095 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231096884326149968913490292 amount: 80. +2023-09-19 03:52:06,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095526.0, "order_id": "x-XEKWYICXBSIUT605ae2b6a46290582", "exchange_order_id": "35542291", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:52:06,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae2b6a46290582. +2023-09-19 03:52:06,361 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae2eb179b60582 for 80.00000000 SEI-USDT. +2023-09-19 03:52:06,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae2eb179b60582", "creation_timestamp": 1695095526.0, "exchange_order_id": "35542376", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:52:06,642 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae2eb17eda0582 for 80.00000000 SEI-USDT. +2023-09-19 03:52:06,672 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ae2eb17eda0582", "creation_timestamp": 1695095526.0, "exchange_order_id": "35542377", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:53:01,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae2eb179b60582. [clock=2023-09-19 03:53:01+00:00] +2023-09-19 03:53:01,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae2eb17eda0582. [clock=2023-09-19 03:53:01+00:00] +2023-09-19 03:53:01,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226281412833734875962648460 amount: 80. +2023-09-19 03:53:01,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:53:01,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095581.0, "order_id": "x-XEKWYICXBSIUT605ae2eb179b60582", "exchange_order_id": "35542376", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:53:01,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae2eb179b60582. +2023-09-19 03:53:02,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095582.0, "order_id": "x-XEKWYICXSSIUT605ae2eb17eda0582", "exchange_order_id": "35542377", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:53:02,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae2eb17eda0582. +2023-09-19 03:53:02,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae31f9855a0582 for 80.00000000 SEI-USDT. +2023-09-19 03:53:02,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095582.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae31f9855a0582", "creation_timestamp": 1695095581.0, "exchange_order_id": "35542654", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:53:56,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae31f9855a0582. [clock=2023-09-19 03:53:56+00:00] +2023-09-19 03:53:56,254 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226441703350840139549498574 amount: 80. +2023-09-19 03:53:56,255 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230185925950929537691476525 amount: 80. +2023-09-19 03:53:57,043 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095636.0, "order_id": "x-XEKWYICXBSIUT605ae31f9855a0582", "exchange_order_id": "35542654", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:53:57,044 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae31f9855a0582. +2023-09-19 03:53:58,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae354263de0582 for 80.00000000 SEI-USDT. +2023-09-19 03:53:58,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095638.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae354263de0582", "creation_timestamp": 1695095636.0, "exchange_order_id": "35542792", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:53:58,841 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae354296c60582 for 80.00000000 SEI-USDT. +2023-09-19 03:53:58,900 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095638.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae354296c60582", "creation_timestamp": 1695095636.0, "exchange_order_id": "35542793", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:54:51,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae354263de0582. [clock=2023-09-19 03:54:51+00:00] +2023-09-19 03:54:51,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae354296c60582. [clock=2023-09-19 03:54:51+00:00] +2023-09-19 03:54:51,267 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226781541947780482876834432 amount: 80. +2023-09-19 03:54:51,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:54:51,940 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095691.0, "order_id": "x-XEKWYICXBSIUT605ae354263de0582", "exchange_order_id": "35542792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:54:51,941 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae354263de0582. +2023-09-19 03:54:53,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095692.0, "order_id": "x-XEKWYICXSSIUT605ae354296c60582", "exchange_order_id": "35542793", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:54:53,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae354296c60582. +2023-09-19 03:54:53,968 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae3889d07d0582 for 80.00000000 SEI-USDT. +2023-09-19 03:54:53,990 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095693.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae3889d07d0582", "creation_timestamp": 1695095691.0, "exchange_order_id": "35542865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:55:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae3889d07d0582. [clock=2023-09-19 03:55:46+00:00] +2023-09-19 03:55:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226357620079515150793069320 amount: 80. +2023-09-19 03:55:46,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230600638314941890522374318 amount: 80. +2023-09-19 03:55:46,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095746.0, "order_id": "x-XEKWYICXBSIUT605ae3889d07d0582", "exchange_order_id": "35542865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:55:46,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae3889d07d0582. +2023-09-19 03:55:46,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae3bcd3c450582 for 80.00000000 SEI-USDT. +2023-09-19 03:55:46,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae3bcd3c450582", "creation_timestamp": 1695095746.0, "exchange_order_id": "35542951", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:55:46,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae3bcd3a360582 for 80.00000000 SEI-USDT. +2023-09-19 03:55:46,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae3bcd3a360582", "creation_timestamp": 1695095746.0, "exchange_order_id": "35542952", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:56:41,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae3bcd3a360582. [clock=2023-09-19 03:56:41+00:00] +2023-09-19 03:56:41,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae3bcd3c450582. [clock=2023-09-19 03:56:41+00:00] +2023-09-19 03:56:41,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226500839948948152398124714 amount: 80. +2023-09-19 03:56:41,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:56:41,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095801.0, "order_id": "x-XEKWYICXBSIUT605ae3bcd3a360582", "exchange_order_id": "35542952", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:56:41,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae3bcd3a360582. +2023-09-19 03:56:41,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095801.0, "order_id": "x-XEKWYICXSSIUT605ae3bcd3c450582", "exchange_order_id": "35542951", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:56:41,547 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae3bcd3c450582. +2023-09-19 03:56:41,549 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae3f15f3600582 for 80.00000000 SEI-USDT. +2023-09-19 03:56:41,568 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae3f15f3600582", "creation_timestamp": 1695095801.0, "exchange_order_id": "35543062", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:57:36,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae3f15f3600582. [clock=2023-09-19 03:57:36+00:00] +2023-09-19 03:57:36,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226443290709828550598722624 amount: 80. +2023-09-19 03:57:36,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230121916784765292214085770 amount: 80. +2023-09-19 03:57:36,972 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095856.0, "order_id": "x-XEKWYICXBSIUT605ae3f15f3600582", "exchange_order_id": "35543062", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:57:36,973 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae3f15f3600582. +2023-09-19 03:57:37,634 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae425ebc230582 for 80.00000000 SEI-USDT. +2023-09-19 03:57:37,662 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095857.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae425ebc230582", "creation_timestamp": 1695095856.0, "exchange_order_id": "35543145", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:57:37,966 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae425ec0600582 for 80.00000000 SEI-USDT. +2023-09-19 03:57:37,997 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095857.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae425ec0600582", "creation_timestamp": 1695095856.0, "exchange_order_id": "35543146", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:58:31,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae425ebc230582. [clock=2023-09-19 03:58:31+00:00] +2023-09-19 03:58:31,170 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae425ec0600582. [clock=2023-09-19 03:58:31+00:00] +2023-09-19 03:58:31,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226825015149234330953576054 amount: 80. +2023-09-19 03:58:31,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 03:58:31,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095911.0, "order_id": "x-XEKWYICXBSIUT605ae425ebc230582", "exchange_order_id": "35543145", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:58:31,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae425ebc230582. +2023-09-19 03:58:32,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095912.0, "order_id": "x-XEKWYICXSSIUT605ae425ec0600582", "exchange_order_id": "35543146", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:58:32,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae425ec0600582. +2023-09-19 03:58:32,646 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae45a5d0d60582 for 80.00000000 SEI-USDT. +2023-09-19 03:58:32,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095912.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae45a5d0d60582", "creation_timestamp": 1695095911.0, "exchange_order_id": "35543257", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:59:26,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae45a5d0d60582. [clock=2023-09-19 03:59:26+00:00] +2023-09-19 03:59:26,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12290000 amount: 80. +2023-09-19 03:59:26,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233353826267695813277581018 amount: 80. +2023-09-19 03:59:27,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095967.0, "order_id": "x-XEKWYICXBSIUT605ae45a5d0d60582", "exchange_order_id": "35543257", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 03:59:27,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae45a5d0d60582. +2023-09-19 03:59:29,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae48ed13060582 for 80.00000000 SEI-USDT. +2023-09-19 03:59:29,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095968.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae48ed13060582", "creation_timestamp": 1695095966.0, "exchange_order_id": "35543637", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 03:59:29,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae48ed170e0582 for 80.00000000 SEI-USDT. +2023-09-19 03:59:29,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695095968.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae48ed170e0582", "creation_timestamp": 1695095966.0, "exchange_order_id": "35543638", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:00:01,504 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae48ed13060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:00:01,505 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:00:01+00:00] +2023-09-19 04:00:01,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096001.0, "order_id": "x-XEKWYICXBSIUT605ae48ed13060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12290000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990420", "exchange_order_id": "35543637", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:00:01,700 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096001.0, "order_id": "x-XEKWYICXBSIUT605ae48ed13060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8320000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35543637", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:00:01,700 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae48ed13060582 completely filled. +2023-09-19 04:00:21,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae48ed170e0582. [clock=2023-09-19 04:00:21+00:00] +2023-09-19 04:00:21,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12290000 amount: 80. +2023-09-19 04:00:21,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234586096274857082172850581 amount: 80. +2023-09-19 04:00:21,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096021.0, "order_id": "x-XEKWYICXSSIUT605ae48ed170e0582", "exchange_order_id": "35543638", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:00:21,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae48ed170e0582. +2023-09-19 04:00:21,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae4c31e2bf0582 for 80.00000000 SEI-USDT. +2023-09-19 04:00:21,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ae4c31e2bf0582", "creation_timestamp": 1695096021.0, "exchange_order_id": "35543944", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:00:21,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae4c31df580582 for 80.00000000 SEI-USDT. +2023-09-19 04:00:21,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae4c31df580582", "creation_timestamp": 1695096021.0, "exchange_order_id": "35543945", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:01:16,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae4c31df580582. [clock=2023-09-19 04:01:16+00:00] +2023-09-19 04:01:16,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae4c31e2bf0582. [clock=2023-09-19 04:01:16+00:00] +2023-09-19 04:01:16,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12300000 amount: 80. +2023-09-19 04:01:16,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234346861040405304454474678 amount: 80. +2023-09-19 04:01:16,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096076.0, "order_id": "x-XEKWYICXBSIUT605ae4c31df580582", "exchange_order_id": "35543945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:01:16,209 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae4c31df580582. +2023-09-19 04:01:16,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096076.0, "order_id": "x-XEKWYICXSSIUT605ae4c31e2bf0582", "exchange_order_id": "35543944", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:01:16,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae4c31e2bf0582. +2023-09-19 04:01:16,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae4f795aca0582 for 80.00000000 SEI-USDT. +2023-09-19 04:01:16,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ae4f795aca0582", "creation_timestamp": 1695096076.0, "exchange_order_id": "35544145", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:01:16,578 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae4f795e070582 for 80.00000000 SEI-USDT. +2023-09-19 04:01:16,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ae4f795e070582", "creation_timestamp": 1695096076.0, "exchange_order_id": "35544146", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:01:25,278 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae4f795aca0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:01:25,279 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:01:25+00:00] +2023-09-19 04:01:25,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096085.0, "order_id": "x-XEKWYICXBSIUT605ae4f795aca0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12300000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990472", "exchange_order_id": "35544145", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:01:25,369 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096085.0, "order_id": "x-XEKWYICXBSIUT605ae4f795aca0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35544145", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:01:25,369 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae4f795aca0582 completely filled. +2023-09-19 04:02:11,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae4f795e070582. [clock=2023-09-19 04:02:11+00:00] +2023-09-19 04:02:11,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227996767136925633618349972 amount: 80. +2023-09-19 04:02:11,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232376885898986065831358209 amount: 80. +2023-09-19 04:02:11,932 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096131.0, "order_id": "x-XEKWYICXSSIUT605ae4f795e070582", "exchange_order_id": "35544146", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:02:11,933 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae4f795e070582. +2023-09-19 04:02:12,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae52c18a4e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:02:12,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae52c18a4e0582", "creation_timestamp": 1695096131.0, "exchange_order_id": "35544563", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:02:14,063 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae52c18db30582 for 80.00000000 SEI-USDT. +2023-09-19 04:02:14,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096133.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605ae52c18db30582", "creation_timestamp": 1695096131.0, "exchange_order_id": "35544569", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:03:06,488 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae52c18a4e0582. [clock=2023-09-19 04:03:06+00:00] +2023-09-19 04:03:06,489 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae52c18db30582. [clock=2023-09-19 04:03:06+00:00] +2023-09-19 04:03:06,576 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227221269556695697802493248 amount: 80. +2023-09-19 04:03:06,577 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230624528396584872056439048 amount: 80. +2023-09-19 04:03:07,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096186.0, "order_id": "x-XEKWYICXBSIUT605ae52c18a4e0582", "exchange_order_id": "35544563", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:03:07,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae52c18a4e0582. +2023-09-19 04:03:09,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096189.0, "order_id": "x-XEKWYICXSSIUT605ae52c18db30582", "exchange_order_id": "35544569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:03:09,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae52c18db30582. +2023-09-19 04:03:09,767 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae560fa23b0582 for 80.00000000 SEI-USDT. +2023-09-19 04:03:09,824 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae560fa23b0582", "creation_timestamp": 1695096186.0, "exchange_order_id": "35544777", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:03:09,825 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae560fa5660582 for 80.00000000 SEI-USDT. +2023-09-19 04:03:09,892 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae560fa5660582", "creation_timestamp": 1695096186.0, "exchange_order_id": "35544778", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:04:01,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae560fa23b0582. [clock=2023-09-19 04:04:01+00:00] +2023-09-19 04:04:01,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae560fa5660582. [clock=2023-09-19 04:04:01+00:00] +2023-09-19 04:04:01,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226480412051336829621949362 amount: 80. +2023-09-19 04:04:01,345 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230997712585566966712490126 amount: 80. +2023-09-19 04:04:01,960 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096241.0, "order_id": "x-XEKWYICXBSIUT605ae560fa23b0582", "exchange_order_id": "35544777", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:04:01,961 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae560fa23b0582. +2023-09-19 04:04:03,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096243.0, "order_id": "x-XEKWYICXSSIUT605ae560fa5660582", "exchange_order_id": "35544778", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:04:03,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae560fa5660582. +2023-09-19 04:04:03,561 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae595354340582 for 80.00000000 SEI-USDT. +2023-09-19 04:04:03,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096243.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae595354340582", "creation_timestamp": 1695096241.0, "exchange_order_id": "35544865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:04:03,601 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae595358c30582 for 80.00000000 SEI-USDT. +2023-09-19 04:04:03,623 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096243.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae595358c30582", "creation_timestamp": 1695096241.0, "exchange_order_id": "35544864", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:04:56,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae595354340582. [clock=2023-09-19 04:04:56+00:00] +2023-09-19 04:04:56,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae595358c30582. [clock=2023-09-19 04:04:56+00:00] +2023-09-19 04:04:56,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226818338077917477913607664 amount: 80. +2023-09-19 04:04:56,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230331324446773404287698538 amount: 80. +2023-09-19 04:04:56,846 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096296.0, "order_id": "x-XEKWYICXBSIUT605ae595354340582", "exchange_order_id": "35544865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:04:56,846 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae595354340582. +2023-09-19 04:04:57,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae5c96dde20582 for 80.00000000 SEI-USDT. +2023-09-19 04:04:57,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096297.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae5c96dde20582", "creation_timestamp": 1695096296.0, "exchange_order_id": "35544944", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:04:57,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096297.0, "order_id": "x-XEKWYICXSSIUT605ae595358c30582", "exchange_order_id": "35544864", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:04:57,438 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae595358c30582. +2023-09-19 04:04:57,524 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae5c96e1560582 for 80.00000000 SEI-USDT. +2023-09-19 04:04:57,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096297.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae5c96e1560582", "creation_timestamp": 1695096296.0, "exchange_order_id": "35544945", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:05:51,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae5c96dde20582. [clock=2023-09-19 04:05:51+00:00] +2023-09-19 04:05:51,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae5c96e1560582. [clock=2023-09-19 04:05:51+00:00] +2023-09-19 04:05:51,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225707977097774066123019592 amount: 80. +2023-09-19 04:05:51,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1229548970099964712759650667 amount: 80. +2023-09-19 04:05:51,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096351.0, "order_id": "x-XEKWYICXBSIUT605ae5c96dde20582", "exchange_order_id": "35544944", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:05:51,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae5c96dde20582. +2023-09-19 04:05:51,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096351.0, "order_id": "x-XEKWYICXSSIUT605ae5c96e1560582", "exchange_order_id": "35544945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:05:51,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae5c96e1560582. +2023-09-19 04:05:51,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae5fdcfa060582 for 80.00000000 SEI-USDT. +2023-09-19 04:05:51,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXSSIUT605ae5fdcfa060582", "creation_timestamp": 1695096351.0, "exchange_order_id": "35545224", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:05:51,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae5fdcf7b90582 for 80.00000000 SEI-USDT. +2023-09-19 04:05:51,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605ae5fdcf7b90582", "creation_timestamp": 1695096351.0, "exchange_order_id": "35545225", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:06:46,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae5fdcf7b90582. [clock=2023-09-19 04:06:46+00:00] +2023-09-19 04:06:46,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae5fdcfa060582. [clock=2023-09-19 04:06:46+00:00] +2023-09-19 04:06:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226402149189645295071791650 amount: 80. +2023-09-19 04:06:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231369381262056037122730376 amount: 80. +2023-09-19 04:06:46,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096406.0, "order_id": "x-XEKWYICXBSIUT605ae5fdcf7b90582", "exchange_order_id": "35545225", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:06:46,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae5fdcf7b90582. +2023-09-19 04:06:46,605 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096406.0, "order_id": "x-XEKWYICXSSIUT605ae5fdcfa060582", "exchange_order_id": "35545224", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:06:46,605 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae5fdcfa060582. +2023-09-19 04:06:46,607 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae63241dd10582 for 80.00000000 SEI-USDT. +2023-09-19 04:06:46,620 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae63241dd10582", "creation_timestamp": 1695096406.0, "exchange_order_id": "35545559", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:06:46,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae632421520582 for 80.00000000 SEI-USDT. +2023-09-19 04:06:46,632 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ae632421520582", "creation_timestamp": 1695096406.0, "exchange_order_id": "35545560", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:07:41,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae63241dd10582. [clock=2023-09-19 04:07:41+00:00] +2023-09-19 04:07:41,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae632421520582. [clock=2023-09-19 04:07:41+00:00] +2023-09-19 04:07:41,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226398657747124409423882214 amount: 80. +2023-09-19 04:07:41,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231373975265590326055097902 amount: 80. +2023-09-19 04:07:41,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096461.0, "order_id": "x-XEKWYICXBSIUT605ae63241dd10582", "exchange_order_id": "35545559", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:07:41,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae63241dd10582. +2023-09-19 04:07:42,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096462.0, "order_id": "x-XEKWYICXSSIUT605ae632421520582", "exchange_order_id": "35545560", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:07:42,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae632421520582. +2023-09-19 04:07:42,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae666cb5e50582 for 80.00000000 SEI-USDT. +2023-09-19 04:07:42,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096462.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ae666cb5e50582", "creation_timestamp": 1695096461.0, "exchange_order_id": "35545651", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:07:42,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae666cb3b60582 for 80.00000000 SEI-USDT. +2023-09-19 04:07:42,501 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096462.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae666cb3b60582", "creation_timestamp": 1695096461.0, "exchange_order_id": "35545650", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:08:36,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae666cb3b60582. [clock=2023-09-19 04:08:36+00:00] +2023-09-19 04:08:36,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae666cb5e50582. [clock=2023-09-19 04:08:36+00:00] +2023-09-19 04:08:36,192 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226864150245981307950155029 amount: 80. +2023-09-19 04:08:36,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1231843595147424070935818387 amount: 80. +2023-09-19 04:08:37,044 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096516.0, "order_id": "x-XEKWYICXBSIUT605ae666cb3b60582", "exchange_order_id": "35545650", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:08:37,044 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae666cb3b60582. +2023-09-19 04:08:38,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096518.0, "order_id": "x-XEKWYICXSSIUT605ae666cb5e50582", "exchange_order_id": "35545651", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:08:38,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae666cb5e50582. +2023-09-19 04:08:38,905 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae69b5784c0582 for 80.00000000 SEI-USDT. +2023-09-19 04:08:38,951 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096518.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605ae69b5784c0582", "creation_timestamp": 1695096516.0, "exchange_order_id": "35545750", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:08:38,952 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae69b57a460582 for 80.00000000 SEI-USDT. +2023-09-19 04:08:39,037 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096518.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXSSIUT605ae69b57a460582", "creation_timestamp": 1695096516.0, "exchange_order_id": "35545749", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:09:31,339 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae69b5784c0582. [clock=2023-09-19 04:09:31+00:00] +2023-09-19 04:09:31,340 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae69b57a460582. [clock=2023-09-19 04:09:31+00:00] +2023-09-19 04:09:31,408 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227116975792602814052700563 amount: 80. +2023-09-19 04:09:31,410 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1230988929377322530375276637 amount: 80. +2023-09-19 04:09:32,115 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096571.0, "order_id": "x-XEKWYICXBSIUT605ae69b5784c0582", "exchange_order_id": "35545750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:09:32,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae69b5784c0582. +2023-09-19 04:09:33,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae6cffba480582 for 80.00000000 SEI-USDT. +2023-09-19 04:09:33,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096573.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXSSIUT605ae6cffba480582", "creation_timestamp": 1695096571.0, "exchange_order_id": "35545842", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:09:33,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096573.0, "order_id": "x-XEKWYICXSSIUT605ae69b57a460582", "exchange_order_id": "35545749", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:09:33,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae69b57a460582. +2023-09-19 04:09:33,556 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae6cffb6850582 for 80.00000000 SEI-USDT. +2023-09-19 04:09:33,594 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096573.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae6cffb6850582", "creation_timestamp": 1695096571.0, "exchange_order_id": "35545843", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:09:40,625 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605ae6cffba480582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:09:40,637 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:09:40+00:00] +2023-09-19 04:09:40,769 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096580.0, "order_id": "x-XEKWYICXSSIUT605ae6cffba480582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12300000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00984000"}]}, "exchange_trade_id": "4990625", "exchange_order_id": "35545842", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:09:41,051 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096580.0, "order_id": "x-XEKWYICXSSIUT605ae6cffba480582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35545842", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:09:41,052 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605ae6cffba480582 completely filled. +2023-09-19 04:10:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae6cffb6850582. [clock=2023-09-19 04:10:26+00:00] +2023-09-19 04:10:26,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 04:10:26,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238795213425527239206652033 amount: 80. +2023-09-19 04:10:26,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096626.0, "order_id": "x-XEKWYICXBSIUT605ae6cffb6850582", "exchange_order_id": "35545843", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:10:26,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae6cffb6850582. +2023-09-19 04:10:26,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae7040fadf0582 for 80.00000000 SEI-USDT. +2023-09-19 04:10:26,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605ae7040fadf0582", "creation_timestamp": 1695096626.0, "exchange_order_id": "35546255", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:10:26,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae7040f7610582 for 80.00000000 SEI-USDT. +2023-09-19 04:10:26,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ae7040f7610582", "creation_timestamp": 1695096626.0, "exchange_order_id": "35546256", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:11:05,062 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae7040f7610582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:11:05,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:11:05+00:00] +2023-09-19 04:11:05,084 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096665.0, "order_id": "x-XEKWYICXBSIUT605ae7040f7610582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990669", "exchange_order_id": "35546256", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:11:05,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096665.0, "order_id": "x-XEKWYICXBSIUT605ae7040f7610582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35546256", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:11:05,097 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae7040f7610582 completely filled. +2023-09-19 04:11:21,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae7040fadf0582. [clock=2023-09-19 04:11:21+00:00] +2023-09-19 04:11:21,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 04:11:21,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236572125854682790691943567 amount: 80. +2023-09-19 04:11:21,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096681.0, "order_id": "x-XEKWYICXSSIUT605ae7040fadf0582", "exchange_order_id": "35546255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:11:21,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae7040fadf0582. +2023-09-19 04:11:21,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae7388d8800582 for 80.00000000 SEI-USDT. +2023-09-19 04:11:21,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ae7388d8800582", "creation_timestamp": 1695096681.0, "exchange_order_id": "35546416", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:11:21,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae7388db7a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:11:21,360 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ae7388db7a0582", "creation_timestamp": 1695096681.0, "exchange_order_id": "35546417", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:12:16,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae7388d8800582. [clock=2023-09-19 04:12:16+00:00] +2023-09-19 04:12:16,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae7388db7a0582. [clock=2023-09-19 04:12:16+00:00] +2023-09-19 04:12:16,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 04:12:16,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236344554759600622967587753 amount: 80. +2023-09-19 04:12:16,810 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096736.0, "order_id": "x-XEKWYICXBSIUT605ae7388d8800582", "exchange_order_id": "35546416", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:12:16,810 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae7388d8800582. +2023-09-19 04:12:17,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096737.0, "order_id": "x-XEKWYICXSSIUT605ae7388db7a0582", "exchange_order_id": "35546417", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:12:17,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae7388db7a0582. +2023-09-19 04:12:17,780 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae76d107820582 for 80.00000000 SEI-USDT. +2023-09-19 04:12:17,814 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096737.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ae76d107820582", "creation_timestamp": 1695096736.0, "exchange_order_id": "35546499", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:12:17,814 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae76d109700582 for 80.00000000 SEI-USDT. +2023-09-19 04:12:17,837 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096737.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ae76d109700582", "creation_timestamp": 1695096736.0, "exchange_order_id": "35546501", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:12:46,391 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 04:13:11,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae76d107820582. [clock=2023-09-19 04:13:11+00:00] +2023-09-19 04:13:11,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae76d109700582. [clock=2023-09-19 04:13:11+00:00] +2023-09-19 04:13:11,356 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12310000 amount: 80. +2023-09-19 04:13:11,366 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236171094765293223640042371 amount: 80. +2023-09-19 04:13:12,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096791.0, "order_id": "x-XEKWYICXBSIUT605ae76d107820582", "exchange_order_id": "35546499", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:13:12,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae76d107820582. +2023-09-19 04:13:13,830 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096793.0, "order_id": "x-XEKWYICXSSIUT605ae76d109700582", "exchange_order_id": "35546501", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:13:13,830 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae76d109700582. +2023-09-19 04:13:13,846 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae7a1bfd050582 for 80.00000000 SEI-USDT. +2023-09-19 04:13:13,895 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096793.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605ae7a1bfd050582", "creation_timestamp": 1695096791.0, "exchange_order_id": "35546819", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:13:14,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae7a1bf9550582 for 80.00000000 SEI-USDT. +2023-09-19 04:13:14,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096793.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605ae7a1bf9550582", "creation_timestamp": 1695096791.0, "exchange_order_id": "35546820", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:14:00,983 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605ae7a1bf9550582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:14:00,998 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:14:00+00:00] +2023-09-19 04:14:01,073 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096840.0, "order_id": "x-XEKWYICXBSIUT605ae7a1bf9550582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12310000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4990727", "exchange_order_id": "35546820", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:14:01,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096840.0, "order_id": "x-XEKWYICXBSIUT605ae7a1bf9550582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8480000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35546820", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:14:01,472 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605ae7a1bf9550582 completely filled. +2023-09-19 04:14:06,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae7a1bfd050582. [clock=2023-09-19 04:14:06+00:00] +2023-09-19 04:14:06,260 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229740489099300987359438065 amount: 80. +2023-09-19 04:14:06,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234034214554767955223899321 amount: 80. +2023-09-19 04:14:07,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096846.0, "order_id": "x-XEKWYICXSSIUT605ae7a1bfd050582", "exchange_order_id": "35546819", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:14:07,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae7a1bfd050582. +2023-09-19 04:14:07,758 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae7d61c2fb0582 for 80.00000000 SEI-USDT. +2023-09-19 04:14:07,793 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096847.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae7d61c2fb0582", "creation_timestamp": 1695096846.0, "exchange_order_id": "35546926", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:14:07,861 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae7d61c68d0582 for 80.00000000 SEI-USDT. +2023-09-19 04:14:07,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096847.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ae7d61c68d0582", "creation_timestamp": 1695096846.0, "exchange_order_id": "35546927", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:15:01,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae7d61c2fb0582. [clock=2023-09-19 04:15:01+00:00] +2023-09-19 04:15:01,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae7d61c68d0582. [clock=2023-09-19 04:15:01+00:00] +2023-09-19 04:15:01,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228088609358990365440703227 amount: 80. +2023-09-19 04:15:01,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234603294513574511297462597 amount: 80. +2023-09-19 04:15:01,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096901.0, "order_id": "x-XEKWYICXBSIUT605ae7d61c2fb0582", "exchange_order_id": "35546926", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:15:01,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae7d61c2fb0582. +2023-09-19 04:15:03,028 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096902.0, "order_id": "x-XEKWYICXSSIUT605ae7d61c68d0582", "exchange_order_id": "35546927", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:15:03,028 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae7d61c68d0582. +2023-09-19 04:15:03,355 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae80a745750582 for 80.00000000 SEI-USDT. +2023-09-19 04:15:03,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096903.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ae80a745750582", "creation_timestamp": 1695096901.0, "exchange_order_id": "35547118", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:15:03,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae80a721cf0582 for 80.00000000 SEI-USDT. +2023-09-19 04:15:03,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096903.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ae80a721cf0582", "creation_timestamp": 1695096901.0, "exchange_order_id": "35547119", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:15:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae80a721cf0582. [clock=2023-09-19 04:15:56+00:00] +2023-09-19 04:15:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae80a745750582. [clock=2023-09-19 04:15:56+00:00] +2023-09-19 04:15:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227459896529161223747638562 amount: 80. +2023-09-19 04:15:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233639321353211280532962286 amount: 80. +2023-09-19 04:15:56,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096956.0, "order_id": "x-XEKWYICXBSIUT605ae80a721cf0582", "exchange_order_id": "35547119", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:15:56,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae80a721cf0582. +2023-09-19 04:15:56,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096956.0, "order_id": "x-XEKWYICXSSIUT605ae80a745750582", "exchange_order_id": "35547118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:15:56,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae80a745750582. +2023-09-19 04:15:56,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae83ec63ac0582 for 80.00000000 SEI-USDT. +2023-09-19 04:15:56,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae83ec63ac0582", "creation_timestamp": 1695096956.0, "exchange_order_id": "35547285", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:15:56,521 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae83ec67ea0582 for 80.00000000 SEI-USDT. +2023-09-19 04:15:56,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695096956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae83ec67ea0582", "creation_timestamp": 1695096956.0, "exchange_order_id": "35547286", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:16:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae83ec63ac0582. [clock=2023-09-19 04:16:51+00:00] +2023-09-19 04:16:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae83ec67ea0582. [clock=2023-09-19 04:16:51+00:00] +2023-09-19 04:16:51,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228067419679943948264506499 amount: 80. +2023-09-19 04:16:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233982723938152698747190471 amount: 80. +2023-09-19 04:16:51,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097011.0, "order_id": "x-XEKWYICXBSIUT605ae83ec63ac0582", "exchange_order_id": "35547285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:16:51,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae83ec63ac0582. +2023-09-19 04:16:51,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae8733a5e30582 for 80.00000000 SEI-USDT. +2023-09-19 04:16:51,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae8733a5e30582", "creation_timestamp": 1695097011.0, "exchange_order_id": "35547445", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:16:51,536 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae8733a3230582 for 80.00000000 SEI-USDT. +2023-09-19 04:16:51,552 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ae8733a3230582", "creation_timestamp": 1695097011.0, "exchange_order_id": "35547447", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:16:51,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097011.0, "order_id": "x-XEKWYICXSSIUT605ae83ec67ea0582", "exchange_order_id": "35547286", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:16:51,564 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae83ec67ea0582. +2023-09-19 04:17:46,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae8733a3230582. [clock=2023-09-19 04:17:46+00:00] +2023-09-19 04:17:46,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae8733a5e30582. [clock=2023-09-19 04:17:46+00:00] +2023-09-19 04:17:46,194 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1228189230029165874212522747 amount: 80. +2023-09-19 04:17:46,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233901249550071337302872117 amount: 80. +2023-09-19 04:17:46,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097066.0, "order_id": "x-XEKWYICXBSIUT605ae8733a3230582", "exchange_order_id": "35547447", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:17:46,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae8733a3230582. +2023-09-19 04:17:47,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097067.0, "order_id": "x-XEKWYICXSSIUT605ae8733a5e30582", "exchange_order_id": "35547445", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:17:47,323 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae8733a5e30582. +2023-09-19 04:17:47,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae8a7d89b30582 for 80.00000000 SEI-USDT. +2023-09-19 04:17:47,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097067.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12280000", "order_id": "x-XEKWYICXBSIUT605ae8a7d89b30582", "creation_timestamp": 1695097066.0, "exchange_order_id": "35547518", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:17:47,517 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae8a7d8ccc0582 for 80.00000000 SEI-USDT. +2023-09-19 04:17:47,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097067.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae8a7d8ccc0582", "creation_timestamp": 1695097066.0, "exchange_order_id": "35547517", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:18:41,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae8a7d89b30582. [clock=2023-09-19 04:18:41+00:00] +2023-09-19 04:18:41,123 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae8a7d8ccc0582. [clock=2023-09-19 04:18:41+00:00] +2023-09-19 04:18:41,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227619625315866679483767353 amount: 80. +2023-09-19 04:18:41,162 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233175049047489656437151495 amount: 80. +2023-09-19 04:18:41,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097121.0, "order_id": "x-XEKWYICXBSIUT605ae8a7d89b30582", "exchange_order_id": "35547518", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:18:41,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae8a7d89b30582. +2023-09-19 04:18:43,026 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097122.0, "order_id": "x-XEKWYICXSSIUT605ae8a7d8ccc0582", "exchange_order_id": "35547517", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:18:43,026 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae8a7d8ccc0582. +2023-09-19 04:18:43,028 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae8dc443660582 for 80.00000000 SEI-USDT. +2023-09-19 04:18:43,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097122.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605ae8dc443660582", "creation_timestamp": 1695097121.0, "exchange_order_id": "35547694", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:18:43,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae8dc4459b0582 for 80.00000000 SEI-USDT. +2023-09-19 04:18:43,135 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097122.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae8dc4459b0582", "creation_timestamp": 1695097121.0, "exchange_order_id": "35547695", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:19:36,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae8dc443660582. [clock=2023-09-19 04:19:36+00:00] +2023-09-19 04:19:36,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae8dc4459b0582. [clock=2023-09-19 04:19:36+00:00] +2023-09-19 04:19:36,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229157095723510400621159687 amount: 80. +2023-09-19 04:19:36,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235701086109761339621819381 amount: 80. +2023-09-19 04:19:37,074 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097176.0, "order_id": "x-XEKWYICXBSIUT605ae8dc443660582", "exchange_order_id": "35547694", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:19:37,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae8dc443660582. +2023-09-19 04:19:38,106 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097177.0, "order_id": "x-XEKWYICXSSIUT605ae8dc4459b0582", "exchange_order_id": "35547695", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:19:38,106 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae8dc4459b0582. +2023-09-19 04:19:38,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae910ca60a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:19:38,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097178.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae910ca60a0582", "creation_timestamp": 1695097176.0, "exchange_order_id": "35547858", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:19:38,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae910ca93a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:19:38,588 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097178.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605ae910ca93a0582", "creation_timestamp": 1695097176.0, "exchange_order_id": "35547859", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:20:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae910ca60a0582. [clock=2023-09-19 04:20:31+00:00] +2023-09-19 04:20:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae910ca93a0582. [clock=2023-09-19 04:20:31+00:00] +2023-09-19 04:20:31,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229256051274798497781308006 amount: 80. +2023-09-19 04:20:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234347363121166464603319528 amount: 80. +2023-09-19 04:20:31,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097231.0, "order_id": "x-XEKWYICXBSIUT605ae910ca60a0582", "exchange_order_id": "35547858", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:20:31,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae910ca60a0582. +2023-09-19 04:20:31,369 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097231.0, "order_id": "x-XEKWYICXSSIUT605ae910ca93a0582", "exchange_order_id": "35547859", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:20:31,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae910ca93a0582. +2023-09-19 04:20:31,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae94508fb60582 for 80.00000000 SEI-USDT. +2023-09-19 04:20:31,384 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605ae94508fb60582", "creation_timestamp": 1695097231.0, "exchange_order_id": "35547995", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:20:31,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae94508bc80582 for 80.00000000 SEI-USDT. +2023-09-19 04:20:31,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae94508bc80582", "creation_timestamp": 1695097231.0, "exchange_order_id": "35547996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:21:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae94508bc80582. [clock=2023-09-19 04:21:26+00:00] +2023-09-19 04:21:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae94508fb60582. [clock=2023-09-19 04:21:26+00:00] +2023-09-19 04:21:26,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229643859493791541110738869 amount: 80. +2023-09-19 04:21:26,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233603195908762029013776951 amount: 80. +2023-09-19 04:21:26,347 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097286.0, "order_id": "x-XEKWYICXBSIUT605ae94508bc80582", "exchange_order_id": "35547996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:21:26,348 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae94508bc80582. +2023-09-19 04:21:26,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097286.0, "order_id": "x-XEKWYICXSSIUT605ae94508fb60582", "exchange_order_id": "35547995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:21:26,572 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae94508fb60582. +2023-09-19 04:21:26,574 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae979827400582 for 80.00000000 SEI-USDT. +2023-09-19 04:21:26,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae979827400582", "creation_timestamp": 1695097286.0, "exchange_order_id": "35548185", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:21:26,599 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae979823930582 for 80.00000000 SEI-USDT. +2023-09-19 04:21:26,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae979823930582", "creation_timestamp": 1695097286.0, "exchange_order_id": "35548186", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:22:21,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae979823930582. [clock=2023-09-19 04:22:21+00:00] +2023-09-19 04:22:21,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae979827400582. [clock=2023-09-19 04:22:21+00:00] +2023-09-19 04:22:21,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229945383778383983892365751 amount: 80. +2023-09-19 04:22:21,263 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233024521170804740286229643 amount: 80. +2023-09-19 04:22:22,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097341.0, "order_id": "x-XEKWYICXBSIUT605ae979823930582", "exchange_order_id": "35548186", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:22:22,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae979823930582. +2023-09-19 04:22:23,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097343.0, "order_id": "x-XEKWYICXSSIUT605ae979827400582", "exchange_order_id": "35548185", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:22:23,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae979827400582. +2023-09-19 04:22:23,788 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae9ae2bc830582 for 80.00000000 SEI-USDT. +2023-09-19 04:22:23,808 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097343.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605ae9ae2bc830582", "creation_timestamp": 1695097341.0, "exchange_order_id": "35548270", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:22:23,808 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae9ae2c0460582 for 80.00000000 SEI-USDT. +2023-09-19 04:22:23,829 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097343.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae9ae2c0460582", "creation_timestamp": 1695097341.0, "exchange_order_id": "35548269", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:23:16,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae9ae2bc830582. [clock=2023-09-19 04:23:16+00:00] +2023-09-19 04:23:16,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae9ae2c0460582. [clock=2023-09-19 04:23:16+00:00] +2023-09-19 04:23:16,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230159858681885445028494541 amount: 80. +2023-09-19 04:23:16,130 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233417438819860400978019587 amount: 80. +2023-09-19 04:23:16,567 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097396.0, "order_id": "x-XEKWYICXBSIUT605ae9ae2bc830582", "exchange_order_id": "35548270", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:23:16,567 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae9ae2bc830582. +2023-09-19 04:23:17,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097397.0, "order_id": "x-XEKWYICXSSIUT605ae9ae2c0460582", "exchange_order_id": "35548269", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:23:17,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae9ae2c0460582. +2023-09-19 04:23:17,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605ae9e27f51d0582 for 80.00000000 SEI-USDT. +2023-09-19 04:23:17,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097397.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605ae9e27f51d0582", "creation_timestamp": 1695097396.0, "exchange_order_id": "35548367", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:23:17,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605ae9e27f1de0582 for 80.00000000 SEI-USDT. +2023-09-19 04:23:17,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097397.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605ae9e27f1de0582", "creation_timestamp": 1695097396.0, "exchange_order_id": "35548366", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:24:11,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605ae9e27f1de0582. [clock=2023-09-19 04:24:11+00:00] +2023-09-19 04:24:11,098 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605ae9e27f51d0582. [clock=2023-09-19 04:24:11+00:00] +2023-09-19 04:24:11,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229543680706859521374207845 amount: 80. +2023-09-19 04:24:11,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233189986250223652901634130 amount: 80. +2023-09-19 04:24:11,845 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097451.0, "order_id": "x-XEKWYICXBSIUT605ae9e27f1de0582", "exchange_order_id": "35548366", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:24:11,846 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605ae9e27f1de0582. +2023-09-19 04:24:11,887 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097451.0, "order_id": "x-XEKWYICXSSIUT605ae9e27f51d0582", "exchange_order_id": "35548367", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:24:11,887 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605ae9e27f51d0582. +2023-09-19 04:24:13,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aea16f26640582 for 80.00000000 SEI-USDT. +2023-09-19 04:24:13,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097452.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605aea16f26640582", "creation_timestamp": 1695097451.0, "exchange_order_id": "35548764", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:24:13,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aea16f294f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:24:13,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097452.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605aea16f294f0582", "creation_timestamp": 1695097451.0, "exchange_order_id": "35548763", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:25:06,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aea16f26640582. [clock=2023-09-19 04:25:06+00:00] +2023-09-19 04:25:06,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aea16f294f0582. [clock=2023-09-19 04:25:06+00:00] +2023-09-19 04:25:06,377 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229867397181232218144950246 amount: 80. +2023-09-19 04:25:06,378 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232703224845857961483814729 amount: 80. +2023-09-19 04:25:07,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097506.0, "order_id": "x-XEKWYICXBSIUT605aea16f26640582", "exchange_order_id": "35548764", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:25:07,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aea16f26640582. +2023-09-19 04:25:08,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097508.0, "order_id": "x-XEKWYICXSSIUT605aea16f294f0582", "exchange_order_id": "35548763", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:25:08,546 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aea16f294f0582. +2023-09-19 04:25:08,694 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aea4ba379c0582 for 80.00000000 SEI-USDT. +2023-09-19 04:25:08,710 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097508.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605aea4ba379c0582", "creation_timestamp": 1695097506.0, "exchange_order_id": "35548942", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:25:08,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aea4ba338a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:25:08,731 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097508.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605aea4ba338a0582", "creation_timestamp": 1695097506.0, "exchange_order_id": "35548943", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:26:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aea4ba338a0582. [clock=2023-09-19 04:26:01+00:00] +2023-09-19 04:26:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aea4ba379c0582. [clock=2023-09-19 04:26:01+00:00] +2023-09-19 04:26:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229867397181232218144950246 amount: 80. +2023-09-19 04:26:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232703224845857961483814729 amount: 80. +2023-09-19 04:26:01,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097561.0, "order_id": "x-XEKWYICXBSIUT605aea4ba338a0582", "exchange_order_id": "35548943", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:26:01,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aea4ba338a0582. +2023-09-19 04:26:01,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097561.0, "order_id": "x-XEKWYICXSSIUT605aea4ba379c0582", "exchange_order_id": "35548942", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:26:01,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aea4ba379c0582. +2023-09-19 04:26:01,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aea7fbfcb80582 for 80.00000000 SEI-USDT. +2023-09-19 04:26:01,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605aea7fbfcb80582", "creation_timestamp": 1695097561.0, "exchange_order_id": "35549077", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:26:01,580 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aea7fbfa140582 for 80.00000000 SEI-USDT. +2023-09-19 04:26:01,593 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605aea7fbfa140582", "creation_timestamp": 1695097561.0, "exchange_order_id": "35549076", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:26:56,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aea7fbfa140582. [clock=2023-09-19 04:26:56+00:00] +2023-09-19 04:26:56,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aea7fbfcb80582. [clock=2023-09-19 04:26:56+00:00] +2023-09-19 04:26:56,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230012658785069356242549696 amount: 80. +2023-09-19 04:26:56,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233328015175391153277647138 amount: 80. +2023-09-19 04:26:56,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097616.0, "order_id": "x-XEKWYICXBSIUT605aea7fbfa140582", "exchange_order_id": "35549076", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:26:56,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aea7fbfa140582. +2023-09-19 04:26:56,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097616.0, "order_id": "x-XEKWYICXSSIUT605aea7fbfcb80582", "exchange_order_id": "35549077", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:26:56,484 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aea7fbfcb80582. +2023-09-19 04:26:56,486 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeab4475750582 for 80.00000000 SEI-USDT. +2023-09-19 04:26:56,507 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605aeab4475750582", "creation_timestamp": 1695097616.0, "exchange_order_id": "35549174", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:26:56,567 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeab4471ce0582 for 80.00000000 SEI-USDT. +2023-09-19 04:26:56,589 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605aeab4471ce0582", "creation_timestamp": 1695097616.0, "exchange_order_id": "35549173", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:27:51,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aeab4471ce0582. [clock=2023-09-19 04:27:51+00:00] +2023-09-19 04:27:51,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aeab4475750582. [clock=2023-09-19 04:27:51+00:00] +2023-09-19 04:27:51,241 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230232209512280656290549506 amount: 80. +2023-09-19 04:27:51,242 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1232810508139177449956309126 amount: 80. +2023-09-19 04:27:51,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097671.0, "order_id": "x-XEKWYICXBSIUT605aeab4471ce0582", "exchange_order_id": "35549173", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:27:51,558 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aeab4471ce0582. +2023-09-19 04:27:52,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097672.0, "order_id": "x-XEKWYICXSSIUT605aeab4475750582", "exchange_order_id": "35549174", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:27:52,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aeab4475750582. +2023-09-19 04:27:52,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeae8dd1ec0582 for 80.00000000 SEI-USDT. +2023-09-19 04:27:52,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097672.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605aeae8dd1ec0582", "creation_timestamp": 1695097671.0, "exchange_order_id": "35549575", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:27:52,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeae8e23940582 for 80.00000000 SEI-USDT. +2023-09-19 04:27:52,414 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097672.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXSSIUT605aeae8e23940582", "creation_timestamp": 1695097671.0, "exchange_order_id": "35549574", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:28:18,203 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aeae8e23940582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:28:18,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:28:18+00:00] +2023-09-19 04:28:18,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097698.0, "order_id": "x-XEKWYICXSSIUT605aeae8e23940582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12320000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00985600"}]}, "exchange_trade_id": "4990981", "exchange_order_id": "35549574", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:28:18,392 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097698.0, "order_id": "x-XEKWYICXSSIUT605aeae8e23940582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35549574", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:28:18,392 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aeae8e23940582 completely filled. +2023-09-19 04:28:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aeae8dd1ec0582. [clock=2023-09-19 04:28:46+00:00] +2023-09-19 04:28:46,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12320000 amount: 80. +2023-09-19 04:28:46,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235657836661874393721560313 amount: 80. +2023-09-19 04:28:46,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097726.0, "order_id": "x-XEKWYICXBSIUT605aeae8dd1ec0582", "exchange_order_id": "35549575", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:28:46,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aeae8dd1ec0582. +2023-09-19 04:28:47,003 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeb1d1f8300582 for 80.00000000 SEI-USDT. +2023-09-19 04:28:47,034 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605aeb1d1f8300582", "creation_timestamp": 1695097726.0, "exchange_order_id": "35549965", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:28:47,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeb1d1fa930582 for 80.00000000 SEI-USDT. +2023-09-19 04:28:47,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097727.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605aeb1d1fa930582", "creation_timestamp": 1695097726.0, "exchange_order_id": "35549966", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:29:41,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aeb1d1f8300582. [clock=2023-09-19 04:29:41+00:00] +2023-09-19 04:29:41,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aeb1d1fa930582. [clock=2023-09-19 04:29:41+00:00] +2023-09-19 04:29:41,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12330000 amount: 80. +2023-09-19 04:29:41,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235846282478380372516923238 amount: 80. +2023-09-19 04:29:42,619 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097782.0, "order_id": "x-XEKWYICXBSIUT605aeb1d1f8300582", "exchange_order_id": "35549965", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:29:42,620 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aeb1d1f8300582. +2023-09-19 04:29:44,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097783.0, "order_id": "x-XEKWYICXSSIUT605aeb1d1fa930582", "exchange_order_id": "35549966", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:29:44,095 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aeb1d1fa930582. +2023-09-19 04:29:44,597 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeb51b50850582 for 80.00000000 SEI-USDT. +2023-09-19 04:29:44,641 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097784.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aeb51b50850582", "creation_timestamp": 1695097781.0, "exchange_order_id": "35550169", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:29:44,642 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aeb51b50850582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:29:44,659 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:29:44+00:00] +2023-09-19 04:29:44,738 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097784.0, "order_id": "x-XEKWYICXBSIUT605aeb51b50850582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4991012", "exchange_order_id": "35550169", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:29:44,739 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeb51b53200582 for 80.00000000 SEI-USDT. +2023-09-19 04:29:44,776 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097784.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605aeb51b53200582", "creation_timestamp": 1695097781.0, "exchange_order_id": "35550170", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:29:44,914 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097784.0, "order_id": "x-XEKWYICXBSIUT605aeb51b50850582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35550169", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:29:44,915 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aeb51b50850582 completely filled. +2023-09-19 04:30:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aeb51b53200582. [clock=2023-09-19 04:30:36+00:00] +2023-09-19 04:30:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12330000 amount: 80. +2023-09-19 04:30:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235020008733678122346419471 amount: 80. +2023-09-19 04:30:36,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097836.0, "order_id": "x-XEKWYICXSSIUT605aeb51b53200582", "exchange_order_id": "35550170", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:30:36,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aeb51b53200582. +2023-09-19 04:30:36,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeb8603c890582 for 80.00000000 SEI-USDT. +2023-09-19 04:30:36,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605aeb8603c890582", "creation_timestamp": 1695097836.0, "exchange_order_id": "35550266", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:30:36,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeb86037ad0582 for 80.00000000 SEI-USDT. +2023-09-19 04:30:36,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aeb86037ad0582", "creation_timestamp": 1695097836.0, "exchange_order_id": "35550267", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:30:41,447 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aeb86037ad0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:30:41,448 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:30:41+00:00] +2023-09-19 04:30:41,488 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097841.0, "order_id": "x-XEKWYICXBSIUT605aeb86037ad0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4991028", "exchange_order_id": "35550267", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:30:41,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097841.0, "order_id": "x-XEKWYICXBSIUT605aeb86037ad0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35550267", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:30:41,511 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aeb86037ad0582 completely filled. +2023-09-19 04:31:31,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aeb8603c890582. [clock=2023-09-19 04:31:31+00:00] +2023-09-19 04:31:31,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233889040835524491151542142 amount: 80. +2023-09-19 04:31:31,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237335528675528723937032102 amount: 80. +2023-09-19 04:31:31,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097891.0, "order_id": "x-XEKWYICXSSIUT605aeb8603c890582", "exchange_order_id": "35550266", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:31:31,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aeb8603c890582. +2023-09-19 04:31:31,648 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aebba84d2b0582 for 80.00000000 SEI-USDT. +2023-09-19 04:31:31,660 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aebba84d2b0582", "creation_timestamp": 1695097891.0, "exchange_order_id": "35550515", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:31:31,663 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aebba84f3d0582 for 80.00000000 SEI-USDT. +2023-09-19 04:31:31,675 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605aebba84f3d0582", "creation_timestamp": 1695097891.0, "exchange_order_id": "35550516", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:32:26,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aebba84d2b0582. [clock=2023-09-19 04:32:26+00:00] +2023-09-19 04:32:26,150 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aebba84f3d0582. [clock=2023-09-19 04:32:26+00:00] +2023-09-19 04:32:26,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233913572799598973088246106 amount: 80. +2023-09-19 04:32:26,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236593396052813713474415482 amount: 80. +2023-09-19 04:32:26,509 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097946.0, "order_id": "x-XEKWYICXBSIUT605aebba84d2b0582", "exchange_order_id": "35550515", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:32:26,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aebba84d2b0582. +2023-09-19 04:32:27,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097947.0, "order_id": "x-XEKWYICXSSIUT605aebba84f3d0582", "exchange_order_id": "35550516", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:32:27,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aebba84f3d0582. +2023-09-19 04:32:27,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aebef14b340582 for 80.00000000 SEI-USDT. +2023-09-19 04:32:27,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097947.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605aebef14b340582", "creation_timestamp": 1695097946.0, "exchange_order_id": "35550602", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:32:27,555 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aebef146570582 for 80.00000000 SEI-USDT. +2023-09-19 04:32:27,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695097947.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aebef146570582", "creation_timestamp": 1695097946.0, "exchange_order_id": "35550603", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:33:21,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aebef146570582. [clock=2023-09-19 04:33:21+00:00] +2023-09-19 04:33:21,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aebef14b340582. [clock=2023-09-19 04:33:21+00:00] +2023-09-19 04:33:21,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233896574007652203013203747 amount: 80. +2023-09-19 04:33:21,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1237093746148643923471856934 amount: 80. +2023-09-19 04:33:21,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098001.0, "order_id": "x-XEKWYICXBSIUT605aebef146570582", "exchange_order_id": "35550603", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:33:21,544 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aebef146570582. +2023-09-19 04:33:22,868 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098002.0, "order_id": "x-XEKWYICXSSIUT605aebef14b340582", "exchange_order_id": "35550602", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:33:22,868 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aebef14b340582. +2023-09-19 04:33:23,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aec237bc490582 for 80.00000000 SEI-USDT. +2023-09-19 04:33:23,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098002.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aec237bc490582", "creation_timestamp": 1695098001.0, "exchange_order_id": "35550785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:33:23,457 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aec237bfa40582 for 80.00000000 SEI-USDT. +2023-09-19 04:33:23,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098002.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605aec237bfa40582", "creation_timestamp": 1695098001.0, "exchange_order_id": "35550786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:34:16,267 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aec237bc490582. [clock=2023-09-19 04:34:16+00:00] +2023-09-19 04:34:16,277 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aec237bfa40582. [clock=2023-09-19 04:34:16+00:00] +2023-09-19 04:34:16,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233919980432538921178685258 amount: 80. +2023-09-19 04:34:16,333 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236403986317803854334611868 amount: 80. +2023-09-19 04:34:17,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098056.0, "order_id": "x-XEKWYICXBSIUT605aec237bc490582", "exchange_order_id": "35550785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:34:17,105 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aec237bc490582. +2023-09-19 04:34:17,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098056.0, "order_id": "x-XEKWYICXSSIUT605aec237bfa40582", "exchange_order_id": "35550786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:34:17,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aec237bfa40582. +2023-09-19 04:34:18,415 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aec581d9af0582 for 80.00000000 SEI-USDT. +2023-09-19 04:34:18,460 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098058.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aec581d9af0582", "creation_timestamp": 1695098056.0, "exchange_order_id": "35550935", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:34:18,881 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aec581fd730582 for 80.00000000 SEI-USDT. +2023-09-19 04:34:18,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098058.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605aec581fd730582", "creation_timestamp": 1695098056.0, "exchange_order_id": "35550936", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:35:11,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aec581d9af0582. [clock=2023-09-19 04:35:11+00:00] +2023-09-19 04:35:11,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aec581fd730582. [clock=2023-09-19 04:35:11+00:00] +2023-09-19 04:35:11,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231943970843727666265391445 amount: 80. +2023-09-19 04:35:11,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234738426896592146977410489 amount: 80. +2023-09-19 04:35:11,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098111.0, "order_id": "x-XEKWYICXBSIUT605aec581d9af0582", "exchange_order_id": "35550935", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:35:11,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aec581d9af0582. +2023-09-19 04:35:11,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098111.0, "order_id": "x-XEKWYICXSSIUT605aec581fd730582", "exchange_order_id": "35550936", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:35:11,585 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aec581fd730582. +2023-09-19 04:35:11,586 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aec8c717d40582 for 80.00000000 SEI-USDT. +2023-09-19 04:35:11,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605aec8c717d40582", "creation_timestamp": 1695098111.0, "exchange_order_id": "35551121", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:35:11,599 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aec8c715150582 for 80.00000000 SEI-USDT. +2023-09-19 04:35:11,610 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605aec8c715150582", "creation_timestamp": 1695098111.0, "exchange_order_id": "35551122", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:36:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aec8c715150582. [clock=2023-09-19 04:36:06+00:00] +2023-09-19 04:36:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aec8c717d40582. [clock=2023-09-19 04:36:06+00:00] +2023-09-19 04:36:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231016758846597513087832465 amount: 80. +2023-09-19 04:36:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1234919680104783089222862220 amount: 80. +2023-09-19 04:36:06,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098166.0, "order_id": "x-XEKWYICXBSIUT605aec8c715150582", "exchange_order_id": "35551122", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:36:06,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aec8c715150582. +2023-09-19 04:36:06,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098166.0, "order_id": "x-XEKWYICXSSIUT605aec8c717d40582", "exchange_order_id": "35551121", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:36:06,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aec8c717d40582. +2023-09-19 04:36:06,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aecc0b8c6f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:36:06,375 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXSSIUT605aecc0b8c6f0582", "creation_timestamp": 1695098166.0, "exchange_order_id": "35551474", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:36:06,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aecc0b8a6b0582 for 80.00000000 SEI-USDT. +2023-09-19 04:36:06,389 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605aecc0b8a6b0582", "creation_timestamp": 1695098166.0, "exchange_order_id": "35551473", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:36:11,593 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aecc0b8c6f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:36:11,594 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:36:11+00:00] +2023-09-19 04:36:11,619 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098171.0, "order_id": "x-XEKWYICXSSIUT605aecc0b8c6f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12340000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00987200"}]}, "exchange_trade_id": "4991139", "exchange_order_id": "35551474", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:36:11,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098171.0, "order_id": "x-XEKWYICXSSIUT605aecc0b8c6f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8720000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35551474", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:36:11,636 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aecc0b8c6f0582 completely filled. +2023-09-19 04:37:01,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aecc0b8a6b0582. [clock=2023-09-19 04:37:01+00:00] +2023-09-19 04:37:01,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12350000 amount: 80. +2023-09-19 04:37:01,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242755750154864826653479537 amount: 80. +2023-09-19 04:37:01,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098221.0, "order_id": "x-XEKWYICXBSIUT605aecc0b8a6b0582", "exchange_order_id": "35551473", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:37:01,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aecc0b8a6b0582. +2023-09-19 04:37:01,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aecf5301480582 for 80.00000000 SEI-USDT. +2023-09-19 04:37:01,436 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aecf5301480582", "creation_timestamp": 1695098221.0, "exchange_order_id": "35551798", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:37:01,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aecf5303a20582 for 80.00000000 SEI-USDT. +2023-09-19 04:37:01,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605aecf5303a20582", "creation_timestamp": 1695098221.0, "exchange_order_id": "35551799", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:37:56,834 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aecf5301480582. [clock=2023-09-19 04:37:56+00:00] +2023-09-19 04:37:56,835 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aecf5303a20582. [clock=2023-09-19 04:37:56+00:00] +2023-09-19 04:37:56,902 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12350000 amount: 80. +2023-09-19 04:37:56,903 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241027658395200992016246698 amount: 80. +2023-09-19 04:37:57,640 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098277.0, "order_id": "x-XEKWYICXBSIUT605aecf5301480582", "exchange_order_id": "35551798", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:37:57,641 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aecf5301480582. +2023-09-19 04:37:58,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098278.0, "order_id": "x-XEKWYICXSSIUT605aecf5303a20582", "exchange_order_id": "35551799", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:37:58,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aecf5303a20582. +2023-09-19 04:37:58,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aed2a77ced0582 for 80.00000000 SEI-USDT. +2023-09-19 04:37:58,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098278.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aed2a77ced0582", "creation_timestamp": 1695098276.0, "exchange_order_id": "35552181", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:37:58,656 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aed2a779a60582 for 80.00000000 SEI-USDT. +2023-09-19 04:37:58,684 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098278.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aed2a779a60582", "creation_timestamp": 1695098276.0, "exchange_order_id": "35552182", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:38:51,327 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aed2a779a60582. [clock=2023-09-19 04:38:51+00:00] +2023-09-19 04:38:51,342 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aed2a77ced0582. [clock=2023-09-19 04:38:51+00:00] +2023-09-19 04:38:51,410 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-19 04:38:51,412 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240690555079976984373712363 amount: 80. +2023-09-19 04:38:51,701 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098331.0, "order_id": "x-XEKWYICXBSIUT605aed2a779a60582", "exchange_order_id": "35552182", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:38:51,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aed2a779a60582. +2023-09-19 04:38:52,907 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098332.0, "order_id": "x-XEKWYICXSSIUT605aed2a77ced0582", "exchange_order_id": "35552181", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:38:52,908 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aed2a77ced0582. +2023-09-19 04:38:52,909 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aed5e73ba30582 for 80.00000000 SEI-USDT. +2023-09-19 04:38:52,943 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098332.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aed5e73ba30582", "creation_timestamp": 1695098331.0, "exchange_order_id": "35552436", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:38:52,943 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aed5e7373f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:38:52,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098332.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aed5e7373f0582", "creation_timestamp": 1695098331.0, "exchange_order_id": "35552437", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:39:46,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aed5e7373f0582. [clock=2023-09-19 04:39:46+00:00] +2023-09-19 04:39:46,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aed5e73ba30582. [clock=2023-09-19 04:39:46+00:00] +2023-09-19 04:39:46,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12370000 amount: 80. +2023-09-19 04:39:46,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241573225867905050306262920 amount: 80. +2023-09-19 04:39:46,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098386.0, "order_id": "x-XEKWYICXBSIUT605aed5e7373f0582", "exchange_order_id": "35552437", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:39:46,522 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aed5e7373f0582. +2023-09-19 04:39:47,805 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098387.0, "order_id": "x-XEKWYICXSSIUT605aed5e73ba30582", "exchange_order_id": "35552436", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:39:47,805 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aed5e73ba30582. +2023-09-19 04:39:47,808 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aed92af7860582 for 80.00000000 SEI-USDT. +2023-09-19 04:39:47,835 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098387.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605aed92af7860582", "creation_timestamp": 1695098386.0, "exchange_order_id": "35552685", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:39:47,836 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aed92afad80582 for 80.00000000 SEI-USDT. +2023-09-19 04:39:47,863 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098387.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aed92afad80582", "creation_timestamp": 1695098386.0, "exchange_order_id": "35552686", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:40:25,729 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aed92af7860582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:40:25,731 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:40:25+00:00] +2023-09-19 04:40:25,752 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098425.0, "order_id": "x-XEKWYICXBSIUT605aed92af7860582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4991251", "exchange_order_id": "35552685", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:40:25,765 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098425.0, "order_id": "x-XEKWYICXBSIUT605aed92af7860582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35552685", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:40:25,765 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aed92af7860582 completely filled. +2023-09-19 04:40:41,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aed92afad80582. [clock=2023-09-19 04:40:41+00:00] +2023-09-19 04:40:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235890001465305441835471489 amount: 80. +2023-09-19 04:40:41,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239211780220427550931047363 amount: 80. +2023-09-19 04:40:41,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098441.0, "order_id": "x-XEKWYICXSSIUT605aed92afad80582", "exchange_order_id": "35552686", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:40:41,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aed92afad80582. +2023-09-19 04:40:41,358 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aedc6fd2da0582 for 80.00000000 SEI-USDT. +2023-09-19 04:40:41,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aedc6fd2da0582", "creation_timestamp": 1695098441.0, "exchange_order_id": "35552972", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:40:41,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aedc6fcf5e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:40:41,383 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605aedc6fcf5e0582", "creation_timestamp": 1695098441.0, "exchange_order_id": "35552973", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:41:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aedc6fcf5e0582. [clock=2023-09-19 04:41:36+00:00] +2023-09-19 04:41:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aedc6fd2da0582. [clock=2023-09-19 04:41:36+00:00] +2023-09-19 04:41:36,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236878102309197312594824271 amount: 80. +2023-09-19 04:41:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240573834960711848279200101 amount: 80. +2023-09-19 04:41:36,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098496.0, "order_id": "x-XEKWYICXBSIUT605aedc6fcf5e0582", "exchange_order_id": "35552973", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:41:36,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aedc6fcf5e0582. +2023-09-19 04:41:36,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098496.0, "order_id": "x-XEKWYICXSSIUT605aedc6fd2da0582", "exchange_order_id": "35552972", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:41:36,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aedc6fd2da0582. +2023-09-19 04:41:36,415 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aedfb6f3be0582 for 80.00000000 SEI-USDT. +2023-09-19 04:41:36,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aedfb6f3be0582", "creation_timestamp": 1695098496.0, "exchange_order_id": "35553203", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:41:36,495 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aedfb6f74e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:41:36,520 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aedfb6f74e0582", "creation_timestamp": 1695098496.0, "exchange_order_id": "35553202", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:42:31,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aedfb6f3be0582. [clock=2023-09-19 04:42:31+00:00] +2023-09-19 04:42:31,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aedfb6f74e0582. [clock=2023-09-19 04:42:31+00:00] +2023-09-19 04:42:31,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236837895735006844610076829 amount: 80. +2023-09-19 04:42:31,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241690930015759850250312406 amount: 80. +2023-09-19 04:42:31,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098551.0, "order_id": "x-XEKWYICXBSIUT605aedfb6f3be0582", "exchange_order_id": "35553203", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:42:31,635 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aedfb6f3be0582. +2023-09-19 04:42:32,958 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098552.0, "order_id": "x-XEKWYICXSSIUT605aedfb6f74e0582", "exchange_order_id": "35553202", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:42:32,958 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aedfb6f74e0582. +2023-09-19 04:42:33,522 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aee300209e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:42:33,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098553.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aee300209e0582", "creation_timestamp": 1695098551.0, "exchange_order_id": "35553360", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:42:33,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aee3001c0a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:42:33,605 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098553.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aee3001c0a0582", "creation_timestamp": 1695098551.0, "exchange_order_id": "35553361", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:42:46,932 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 04:43:26,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aee3001c0a0582. [clock=2023-09-19 04:43:26+00:00] +2023-09-19 04:43:26,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aee300209e0582. [clock=2023-09-19 04:43:26+00:00] +2023-09-19 04:43:26,248 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236836597808894965617220944 amount: 80. +2023-09-19 04:43:26,262 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241727445997726299738995126 amount: 80. +2023-09-19 04:43:27,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098606.0, "order_id": "x-XEKWYICXBSIUT605aee3001c0a0582", "exchange_order_id": "35553361", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:43:27,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aee3001c0a0582. +2023-09-19 04:43:28,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098608.0, "order_id": "x-XEKWYICXSSIUT605aee300209e0582", "exchange_order_id": "35553360", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:43:28,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aee300209e0582. +2023-09-19 04:43:28,947 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aee6491a070582 for 80.00000000 SEI-USDT. +2023-09-19 04:43:28,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098608.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aee6491a070582", "creation_timestamp": 1695098606.0, "exchange_order_id": "35553484", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:43:28,998 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aee64917210582 for 80.00000000 SEI-USDT. +2023-09-19 04:43:29,041 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098608.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aee64917210582", "creation_timestamp": 1695098606.0, "exchange_order_id": "35553485", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:44:21,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aee64917210582. [clock=2023-09-19 04:44:21+00:00] +2023-09-19 04:44:21,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aee6491a070582. [clock=2023-09-19 04:44:21+00:00] +2023-09-19 04:44:21,339 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236835829549066501978916193 amount: 80. +2023-09-19 04:44:21,340 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241749052932518555880231770 amount: 80. +2023-09-19 04:44:23,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098663.0, "order_id": "x-XEKWYICXBSIUT605aee64917210582", "exchange_order_id": "35553485", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:44:23,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aee64917210582. +2023-09-19 04:44:24,663 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aee99186010582 for 80.00000000 SEI-USDT. +2023-09-19 04:44:24,705 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098663.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aee99186010582", "creation_timestamp": 1695098661.0, "exchange_order_id": "35553904", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:44:24,728 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098663.0, "order_id": "x-XEKWYICXSSIUT605aee6491a070582", "exchange_order_id": "35553484", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:44:24,729 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aee6491a070582. +2023-09-19 04:44:24,730 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aee991aa4e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:44:24,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098663.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605aee991aa4e0582", "creation_timestamp": 1695098661.0, "exchange_order_id": "35553905", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:45:16,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aee99186010582. [clock=2023-09-19 04:45:16+00:00] +2023-09-19 04:45:16,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aee991aa4e0582. [clock=2023-09-19 04:45:16+00:00] +2023-09-19 04:45:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233922859064463262053593194 amount: 80. +2023-09-19 04:45:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240828787009460319296031188 amount: 80. +2023-09-19 04:45:16,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098716.0, "order_id": "x-XEKWYICXBSIUT605aee99186010582", "exchange_order_id": "35553904", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:45:16,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aee99186010582. +2023-09-19 04:45:16,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098716.0, "order_id": "x-XEKWYICXSSIUT605aee991aa4e0582", "exchange_order_id": "35553905", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:45:16,635 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aee991aa4e0582. +2023-09-19 04:45:16,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aeecd3eed00582 for 80.00000000 SEI-USDT. +2023-09-19 04:45:16,655 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605aeecd3eed00582", "creation_timestamp": 1695098716.0, "exchange_order_id": "35554110", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:45:16,655 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aeecd3f1f10582 for 80.00000000 SEI-USDT. +2023-09-19 04:45:16,671 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605aeecd3f1f10582", "creation_timestamp": 1695098716.0, "exchange_order_id": "35554109", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:46:11,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aeecd3eed00582. [clock=2023-09-19 04:46:11+00:00] +2023-09-19 04:46:11,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aeecd3f1f10582. [clock=2023-09-19 04:46:11+00:00] +2023-09-19 04:46:11,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234606868194636662419840232 amount: 80. +2023-09-19 04:46:11,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239977783034431313373580731 amount: 80. +2023-09-19 04:46:11,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098771.0, "order_id": "x-XEKWYICXBSIUT605aeecd3eed00582", "exchange_order_id": "35554110", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:46:11,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aeecd3eed00582. +2023-09-19 04:46:11,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098771.0, "order_id": "x-XEKWYICXSSIUT605aeecd3f1f10582", "exchange_order_id": "35554109", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:46:11,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aeecd3f1f10582. +2023-09-19 04:46:11,473 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aef01c00b60582 for 80.00000000 SEI-USDT. +2023-09-19 04:46:11,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605aef01c00b60582", "creation_timestamp": 1695098771.0, "exchange_order_id": "35554267", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:46:11,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aef01c04830582 for 80.00000000 SEI-USDT. +2023-09-19 04:46:11,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605aef01c04830582", "creation_timestamp": 1695098771.0, "exchange_order_id": "35554268", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:47:03,124 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aef01c04830582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:47:03,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:47:03+00:00] +2023-09-19 04:47:03,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098823.0, "order_id": "x-XEKWYICXSSIUT605aef01c04830582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "4991423", "exchange_order_id": "35554268", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:47:03,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098823.0, "order_id": "x-XEKWYICXSSIUT605aef01c04830582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35554268", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:47:03,157 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aef01c04830582 completely filled. +2023-09-19 04:47:06,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aef01c00b60582. [clock=2023-09-19 04:47:06+00:00] +2023-09-19 04:47:06,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12380000 amount: 80. +2023-09-19 04:47:06,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245778967774110537937178743 amount: 80. +2023-09-19 04:47:06,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098826.0, "order_id": "x-XEKWYICXBSIUT605aef01c00b60582", "exchange_order_id": "35554267", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:47:06,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aef01c00b60582. +2023-09-19 04:47:07,035 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aef362ca590582 for 80.00000000 SEI-USDT. +2023-09-19 04:47:07,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aef362ca590582", "creation_timestamp": 1695098826.0, "exchange_order_id": "35554637", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:47:07,343 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aef362cd900582 for 80.00000000 SEI-USDT. +2023-09-19 04:47:07,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098827.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605aef362cd900582", "creation_timestamp": 1695098826.0, "exchange_order_id": "35554638", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:48:01,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aef362ca590582. [clock=2023-09-19 04:48:01+00:00] +2023-09-19 04:48:01,130 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aef362cd900582. [clock=2023-09-19 04:48:01+00:00] +2023-09-19 04:48:01,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-19 04:48:01,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247969769825695541029647460 amount: 80. +2023-09-19 04:48:02,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aef6abb85f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:48:02,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098882.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605aef6abb85f0582", "creation_timestamp": 1695098881.0, "exchange_order_id": "35554897", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:48:02,578 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aef6abbab20582 for 80.00000000 SEI-USDT. +2023-09-19 04:48:02,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098882.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605aef6abbab20582", "creation_timestamp": 1695098881.0, "exchange_order_id": "35554898", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:48:02,629 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098882.0, "order_id": "x-XEKWYICXBSIUT605aef362ca590582", "exchange_order_id": "35554637", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:48:02,629 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aef362ca590582. +2023-09-19 04:48:02,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098882.0, "order_id": "x-XEKWYICXSSIUT605aef362cd900582", "exchange_order_id": "35554638", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:48:02,650 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aef362cd900582. +2023-09-19 04:48:14,080 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605aef6abb85f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:48:14,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:48:14+00:00] +2023-09-19 04:48:14,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098894.0, "order_id": "x-XEKWYICXBSIUT605aef6abb85f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4991478", "exchange_order_id": "35554897", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:48:14,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098894.0, "order_id": "x-XEKWYICXBSIUT605aef6abb85f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35554897", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:48:14,254 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605aef6abb85f0582 completely filled. +2023-09-19 04:48:56,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aef6abbab20582. [clock=2023-09-19 04:48:56+00:00] +2023-09-19 04:48:56,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238737706958017812583973085 amount: 80. +2023-09-19 04:48:56,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246377594363267109183677729 amount: 80. +2023-09-19 04:48:56,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098936.0, "order_id": "x-XEKWYICXSSIUT605aef6abbab20582", "exchange_order_id": "35554898", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:48:56,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aef6abbab20582. +2023-09-19 04:48:57,823 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aef9f307100582 for 80.00000000 SEI-USDT. +2023-09-19 04:48:57,881 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098937.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605aef9f307100582", "creation_timestamp": 1695098936.0, "exchange_order_id": "35555504", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:48:58,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aef9f30a0a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:48:58,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098937.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605aef9f30a0a0582", "creation_timestamp": 1695098936.0, "exchange_order_id": "35555507", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:49:51,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aef9f307100582. [clock=2023-09-19 04:49:51+00:00] +2023-09-19 04:49:51,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aef9f30a0a0582. [clock=2023-09-19 04:49:51+00:00] +2023-09-19 04:49:51,294 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236185226522998135593955486 amount: 80. +2023-09-19 04:49:51,295 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243230263122976334957366374 amount: 80. +2023-09-19 04:49:51,964 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098991.0, "order_id": "x-XEKWYICXBSIUT605aef9f307100582", "exchange_order_id": "35555504", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:49:51,964 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aef9f307100582. +2023-09-19 04:49:53,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098993.0, "order_id": "x-XEKWYICXSSIUT605aef9f30a0a0582", "exchange_order_id": "35555507", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:49:53,610 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aef9f30a0a0582. +2023-09-19 04:49:53,613 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aefd3c3aa00582 for 80.00000000 SEI-USDT. +2023-09-19 04:49:53,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098993.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605aefd3c3aa00582", "creation_timestamp": 1695098991.0, "exchange_order_id": "35555899", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:49:53,670 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aefd3c3e6e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:49:53,731 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695098993.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605aefd3c3e6e0582", "creation_timestamp": 1695098991.0, "exchange_order_id": "35555900", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:50:46,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aefd3c3aa00582. [clock=2023-09-19 04:50:46+00:00] +2023-09-19 04:50:46,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aefd3c3e6e0582. [clock=2023-09-19 04:50:46+00:00] +2023-09-19 04:50:46,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235368802007481498151423479 amount: 80. +2023-09-19 04:50:46,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241954458790460809384870482 amount: 80. +2023-09-19 04:50:46,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099046.0, "order_id": "x-XEKWYICXBSIUT605aefd3c3aa00582", "exchange_order_id": "35555899", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:50:46,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aefd3c3aa00582. +2023-09-19 04:50:46,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af007ff16d0582 for 80.00000000 SEI-USDT. +2023-09-19 04:50:46,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605af007ff16d0582", "creation_timestamp": 1695099046.0, "exchange_order_id": "35556232", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:50:46,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099046.0, "order_id": "x-XEKWYICXSSIUT605aefd3c3e6e0582", "exchange_order_id": "35555900", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:50:46,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aefd3c3e6e0582. +2023-09-19 04:50:46,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af007ff4390582 for 80.00000000 SEI-USDT. +2023-09-19 04:50:46,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605af007ff4390582", "creation_timestamp": 1695099046.0, "exchange_order_id": "35556233", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:51:41,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af007ff16d0582. [clock=2023-09-19 04:51:41+00:00] +2023-09-19 04:51:41,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af007ff4390582. [clock=2023-09-19 04:51:41+00:00] +2023-09-19 04:51:41,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236576812816733237345307181 amount: 80. +2023-09-19 04:51:41,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243676423076059827710086505 amount: 80. +2023-09-19 04:51:41,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099101.0, "order_id": "x-XEKWYICXBSIUT605af007ff16d0582", "exchange_order_id": "35556232", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:51:41,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af007ff16d0582. +2023-09-19 04:51:41,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099101.0, "order_id": "x-XEKWYICXSSIUT605af007ff4390582", "exchange_order_id": "35556233", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:51:41,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af007ff4390582. +2023-09-19 04:51:41,398 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af03c795860582 for 80.00000000 SEI-USDT. +2023-09-19 04:51:41,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605af03c795860582", "creation_timestamp": 1695099101.0, "exchange_order_id": "35556419", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:51:41,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af03c793580582 for 80.00000000 SEI-USDT. +2023-09-19 04:51:41,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605af03c793580582", "creation_timestamp": 1695099101.0, "exchange_order_id": "35556420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:52:36,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af03c793580582. [clock=2023-09-19 04:52:36+00:00] +2023-09-19 04:52:36,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af03c795860582. [clock=2023-09-19 04:52:36+00:00] +2023-09-19 04:52:36,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237114792792833448672007198 amount: 80. +2023-09-19 04:52:36,296 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242635592360198995142119994 amount: 80. +2023-09-19 04:52:37,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099156.0, "order_id": "x-XEKWYICXBSIUT605af03c793580582", "exchange_order_id": "35556420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:52:37,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af03c793580582. +2023-09-19 04:52:38,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099158.0, "order_id": "x-XEKWYICXSSIUT605af03c795860582", "exchange_order_id": "35556419", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:52:38,512 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af03c795860582. +2023-09-19 04:52:38,791 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af0711c2b20582 for 80.00000000 SEI-USDT. +2023-09-19 04:52:38,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099158.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605af0711c2b20582", "creation_timestamp": 1695099156.0, "exchange_order_id": "35556821", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:52:38,821 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af0711fb110582 for 80.00000000 SEI-USDT. +2023-09-19 04:52:38,841 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099158.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605af0711fb110582", "creation_timestamp": 1695099156.0, "exchange_order_id": "35556820", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:53:31,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af0711c2b20582. [clock=2023-09-19 04:53:31+00:00] +2023-09-19 04:53:31,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af0711fb110582. [clock=2023-09-19 04:53:31+00:00] +2023-09-19 04:53:31,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236582449057459648886090091 amount: 80. +2023-09-19 04:53:31,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242850712126465318373216571 amount: 80. +2023-09-19 04:53:31,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099211.0, "order_id": "x-XEKWYICXBSIUT605af0711c2b20582", "exchange_order_id": "35556821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:53:31,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af0711c2b20582. +2023-09-19 04:53:32,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099212.0, "order_id": "x-XEKWYICXSSIUT605af0711fb110582", "exchange_order_id": "35556820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:53:32,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af0711fb110582. +2023-09-19 04:53:32,641 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af0a56855e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:53:32,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099212.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605af0a56855e0582", "creation_timestamp": 1695099211.0, "exchange_order_id": "35556971", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:53:32,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af0a56cb3f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:53:32,699 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099212.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605af0a56cb3f0582", "creation_timestamp": 1695099211.0, "exchange_order_id": "35556972", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:53:41,043 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605af0a56cb3f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:53:41,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:53:40+00:00] +2023-09-19 04:53:41,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099220.0, "order_id": "x-XEKWYICXSSIUT605af0a56cb3f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4991747", "exchange_order_id": "35556972", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:53:41,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099221.0, "order_id": "x-XEKWYICXSSIUT605af0a56cb3f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35556972", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:53:41,333 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605af0a56cb3f0582 completely filled. +2023-09-19 04:54:26,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af0a56855e0582. [clock=2023-09-19 04:54:26+00:00] +2023-09-19 04:54:26,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-19 04:54:26,142 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256055185797191741390205162 amount: 80. +2023-09-19 04:54:26,612 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099266.0, "order_id": "x-XEKWYICXBSIUT605af0a56855e0582", "exchange_order_id": "35556971", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:54:26,612 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af0a56855e0582. +2023-09-19 04:54:27,528 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af0d9e11780582 for 80.00000000 SEI-USDT. +2023-09-19 04:54:27,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099267.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605af0d9e11780582", "creation_timestamp": 1695099266.0, "exchange_order_id": "35558091", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:54:27,942 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af0d9e144a0582 for 80.00000000 SEI-USDT. +2023-09-19 04:54:27,992 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099267.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605af0d9e144a0582", "creation_timestamp": 1695099266.0, "exchange_order_id": "35558093", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:55:21,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af0d9e11780582. [clock=2023-09-19 04:55:21+00:00] +2023-09-19 04:55:21,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af0d9e144a0582. [clock=2023-09-19 04:55:21+00:00] +2023-09-19 04:55:21,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12460000 amount: 80. +2023-09-19 04:55:21,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257632185657095939185391557 amount: 80. +2023-09-19 04:55:21,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099321.0, "order_id": "x-XEKWYICXBSIUT605af0d9e11780582", "exchange_order_id": "35558091", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:55:21,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af0d9e11780582. +2023-09-19 04:55:21,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099321.0, "order_id": "x-XEKWYICXSSIUT605af0d9e144a0582", "exchange_order_id": "35558093", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:55:21,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af0d9e144a0582. +2023-09-19 04:55:21,605 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af10e4b2f60582 for 80.00000000 SEI-USDT. +2023-09-19 04:55:21,627 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605af10e4b2f60582", "creation_timestamp": 1695099321.0, "exchange_order_id": "35558722", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:55:21,627 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af10e4b0050582 for 80.00000000 SEI-USDT. +2023-09-19 04:55:21,646 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af10e4b0050582", "creation_timestamp": 1695099321.0, "exchange_order_id": "35558723", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:55:32,597 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605af10e4b0050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 04:55:32,598 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 04:55:32+00:00] +2023-09-19 04:55:32,623 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099332.0, "order_id": "x-XEKWYICXBSIUT605af10e4b0050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4991899", "exchange_order_id": "35558723", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 04:55:32,637 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099332.0, "order_id": "x-XEKWYICXBSIUT605af10e4b0050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35558723", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 04:55:32,637 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605af10e4b0050582 completely filled. +2023-09-19 04:56:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af10e4b2f60582. [clock=2023-09-19 04:56:16+00:00] +2023-09-19 04:56:16,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244661645483269019435691637 amount: 80. +2023-09-19 04:56:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254002762976666360664027749 amount: 80. +2023-09-19 04:56:16,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099376.0, "order_id": "x-XEKWYICXSSIUT605af10e4b2f60582", "exchange_order_id": "35558722", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:56:16,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af10e4b2f60582. +2023-09-19 04:56:16,400 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af142aaf520582 for 80.00000000 SEI-USDT. +2023-09-19 04:56:16,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605af142aaf520582", "creation_timestamp": 1695099376.0, "exchange_order_id": "35559046", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:56:16,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af142ab2b70582 for 80.00000000 SEI-USDT. +2023-09-19 04:56:16,532 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605af142ab2b70582", "creation_timestamp": 1695099376.0, "exchange_order_id": "35559047", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:57:11,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af142aaf520582. [clock=2023-09-19 04:57:11+00:00] +2023-09-19 04:57:11,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af142ab2b70582. [clock=2023-09-19 04:57:11+00:00] +2023-09-19 04:57:11,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244741287791108685063552413 amount: 80. +2023-09-19 04:57:11,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252001012523923258055299361 amount: 80. +2023-09-19 04:57:11,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099431.0, "order_id": "x-XEKWYICXBSIUT605af142aaf520582", "exchange_order_id": "35559046", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:57:11,481 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af142aaf520582. +2023-09-19 04:57:12,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099432.0, "order_id": "x-XEKWYICXSSIUT605af142ab2b70582", "exchange_order_id": "35559047", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:57:12,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af142ab2b70582. +2023-09-19 04:57:12,918 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af17732d840582 for 80.00000000 SEI-USDT. +2023-09-19 04:57:13,012 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099432.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605af17732d840582", "creation_timestamp": 1695099431.0, "exchange_order_id": "35559146", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:57:13,013 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af177330d10582 for 80.00000000 SEI-USDT. +2023-09-19 04:57:13,070 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099432.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605af177330d10582", "creation_timestamp": 1695099431.0, "exchange_order_id": "35559147", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:58:06,254 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af17732d840582. [clock=2023-09-19 04:58:06+00:00] +2023-09-19 04:58:06,256 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af177330d10582. [clock=2023-09-19 04:58:06+00:00] +2023-09-19 04:58:06,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245768876668038424465338987 amount: 80. +2023-09-19 04:58:06,340 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252280045393611528373473033 amount: 80. +2023-09-19 04:58:07,094 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099486.0, "order_id": "x-XEKWYICXBSIUT605af17732d840582", "exchange_order_id": "35559146", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:58:07,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af17732d840582. +2023-09-19 04:58:07,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099486.0, "order_id": "x-XEKWYICXSSIUT605af177330d10582", "exchange_order_id": "35559147", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:58:07,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af177330d10582. +2023-09-19 04:58:08,423 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af1abe06330582 for 80.00000000 SEI-USDT. +2023-09-19 04:58:08,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099488.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605af1abe06330582", "creation_timestamp": 1695099486.0, "exchange_order_id": "35559461", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:58:08,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af1abe09f10582 for 80.00000000 SEI-USDT. +2023-09-19 04:58:08,548 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099488.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605af1abe09f10582", "creation_timestamp": 1695099486.0, "exchange_order_id": "35559462", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:59:01,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af1abe06330582. [clock=2023-09-19 04:59:01+00:00] +2023-09-19 04:59:01,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af1abe09f10582. [clock=2023-09-19 04:59:01+00:00] +2023-09-19 04:59:01,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246684454838617240277511137 amount: 80. +2023-09-19 04:59:01,249 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255423411828017148785584117 amount: 80. +2023-09-19 04:59:02,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099541.0, "order_id": "x-XEKWYICXBSIUT605af1abe06330582", "exchange_order_id": "35559461", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:59:02,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af1abe06330582. +2023-09-19 04:59:03,687 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099543.0, "order_id": "x-XEKWYICXSSIUT605af1abe09f10582", "exchange_order_id": "35559462", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:59:03,688 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af1abe09f10582. +2023-09-19 04:59:03,999 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af1e03e30e0582 for 80.00000000 SEI-USDT. +2023-09-19 04:59:04,031 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099543.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af1e03e30e0582", "creation_timestamp": 1695099541.0, "exchange_order_id": "35559723", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:59:04,031 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af1e03de140582 for 80.00000000 SEI-USDT. +2023-09-19 04:59:04,056 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099543.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af1e03de140582", "creation_timestamp": 1695099541.0, "exchange_order_id": "35559722", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:59:56,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af1e03de140582. [clock=2023-09-19 04:59:56+00:00] +2023-09-19 04:59:56,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af1e03e30e0582. [clock=2023-09-19 04:59:56+00:00] +2023-09-19 04:59:56,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245629063932540312615762833 amount: 80. +2023-09-19 04:59:56,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255772787922147329614662739 amount: 80. +2023-09-19 04:59:56,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099596.0, "order_id": "x-XEKWYICXBSIUT605af1e03de140582", "exchange_order_id": "35559722", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:59:56,615 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af1e03de140582. +2023-09-19 04:59:57,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099597.0, "order_id": "x-XEKWYICXSSIUT605af1e03e30e0582", "exchange_order_id": "35559723", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 04:59:57,336 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af1e03e30e0582. +2023-09-19 04:59:57,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af2149b33f0582 for 80.00000000 SEI-USDT. +2023-09-19 04:59:57,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099597.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af2149b33f0582", "creation_timestamp": 1695099596.0, "exchange_order_id": "35559986", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 04:59:57,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af2149b0380582 for 80.00000000 SEI-USDT. +2023-09-19 04:59:57,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099597.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605af2149b0380582", "creation_timestamp": 1695099596.0, "exchange_order_id": "35559985", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:00:51,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af2149b0380582. [clock=2023-09-19 05:00:51+00:00] +2023-09-19 05:00:51,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af2149b33f0582. [clock=2023-09-19 05:00:51+00:00] +2023-09-19 05:00:51,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245716602753488088152216029 amount: 80. +2023-09-19 05:00:51,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253599587561905678245488082 amount: 80. +2023-09-19 05:00:51,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099651.0, "order_id": "x-XEKWYICXBSIUT605af2149b0380582", "exchange_order_id": "35559985", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:00:51,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af2149b0380582. +2023-09-19 05:00:51,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099651.0, "order_id": "x-XEKWYICXSSIUT605af2149b33f0582", "exchange_order_id": "35559986", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:00:51,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af2149b33f0582. +2023-09-19 05:00:51,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af248f0c6e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:00:51,426 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605af248f0c6e0582", "creation_timestamp": 1695099651.0, "exchange_order_id": "35560407", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:00:51,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af248f08de0582 for 80.00000000 SEI-USDT. +2023-09-19 05:00:51,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605af248f08de0582", "creation_timestamp": 1695099651.0, "exchange_order_id": "35560408", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:01:45,878 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605af248f08de0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:01:45,879 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:01:45+00:00] +2023-09-19 05:01:45,906 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099705.0, "order_id": "x-XEKWYICXBSIUT605af248f08de0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4992109", "exchange_order_id": "35560408", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:01:45,921 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099705.0, "order_id": "x-XEKWYICXBSIUT605af248f08de0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35560408", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:01:45,922 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605af248f08de0582 completely filled. +2023-09-19 05:01:46,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af248f0c6e0582. [clock=2023-09-19 05:01:46+00:00] +2023-09-19 05:01:46,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239925550482525242632720014 amount: 80. +2023-09-19 05:01:46,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248253152300152211631170188 amount: 80. +2023-09-19 05:01:46,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099706.0, "order_id": "x-XEKWYICXSSIUT605af248f0c6e0582", "exchange_order_id": "35560407", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:01:46,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af248f0c6e0582. +2023-09-19 05:01:46,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af27d72a8d0582 for 80.00000000 SEI-USDT. +2023-09-19 05:01:46,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605af27d72a8d0582", "creation_timestamp": 1695099706.0, "exchange_order_id": "35560829", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:01:46,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af27d72d520582 for 80.00000000 SEI-USDT. +2023-09-19 05:01:46,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af27d72d520582", "creation_timestamp": 1695099706.0, "exchange_order_id": "35560830", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:02:41,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af27d72a8d0582. [clock=2023-09-19 05:02:41+00:00] +2023-09-19 05:02:41,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af27d72d520582. [clock=2023-09-19 05:02:41+00:00] +2023-09-19 05:02:41,366 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236612477107143628192026970 amount: 80. +2023-09-19 05:02:41,383 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246173490799997353976000183 amount: 80. +2023-09-19 05:02:42,007 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099761.0, "order_id": "x-XEKWYICXBSIUT605af27d72a8d0582", "exchange_order_id": "35560829", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:02:42,008 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af27d72a8d0582. +2023-09-19 05:02:43,863 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099763.0, "order_id": "x-XEKWYICXSSIUT605af27d72d520582", "exchange_order_id": "35560830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:02:43,864 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af27d72d520582. +2023-09-19 05:02:44,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af2b22dc4b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:02:44,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099763.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605af2b22dc4b0582", "creation_timestamp": 1695099761.0, "exchange_order_id": "35561227", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:02:44,357 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af2b22d8e10582 for 80.00000000 SEI-USDT. +2023-09-19 05:02:44,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099763.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605af2b22d8e10582", "creation_timestamp": 1695099761.0, "exchange_order_id": "35561228", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:03:36,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af2b22d8e10582. [clock=2023-09-19 05:03:36+00:00] +2023-09-19 05:03:36,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af2b22dc4b0582. [clock=2023-09-19 05:03:36+00:00] +2023-09-19 05:03:36,267 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235895994544934310292048268 amount: 80. +2023-09-19 05:03:36,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244440455694435319896294634 amount: 80. +2023-09-19 05:03:37,096 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099816.0, "order_id": "x-XEKWYICXBSIUT605af2b22d8e10582", "exchange_order_id": "35561228", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:03:37,097 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af2b22d8e10582. +2023-09-19 05:03:39,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099818.0, "order_id": "x-XEKWYICXSSIUT605af2b22dc4b0582", "exchange_order_id": "35561227", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:03:39,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af2b22dc4b0582. +2023-09-19 05:03:39,693 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af2e6856aa0582 for 80.00000000 SEI-USDT. +2023-09-19 05:03:39,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099819.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605af2e6856aa0582", "creation_timestamp": 1695099816.0, "exchange_order_id": "35561741", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:03:39,746 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af2e6854030582 for 80.00000000 SEI-USDT. +2023-09-19 05:03:39,787 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099819.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605af2e6854030582", "creation_timestamp": 1695099816.0, "exchange_order_id": "35561742", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:04:31,249 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af2e6854030582. [clock=2023-09-19 05:04:31+00:00] +2023-09-19 05:04:31,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af2e6856aa0582. [clock=2023-09-19 05:04:31+00:00] +2023-09-19 05:04:31,375 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237564000378660569062768296 amount: 80. +2023-09-19 05:04:31,377 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244208189645910604338901620 amount: 80. +2023-09-19 05:04:32,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099871.0, "order_id": "x-XEKWYICXBSIUT605af2e6854030582", "exchange_order_id": "35561742", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:04:32,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af2e6854030582. +2023-09-19 05:04:33,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099873.0, "order_id": "x-XEKWYICXSSIUT605af2e6856aa0582", "exchange_order_id": "35561741", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:04:33,763 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af2e6856aa0582. +2023-09-19 05:04:33,765 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af31b136550582 for 80.00000000 SEI-USDT. +2023-09-19 05:04:33,798 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099873.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605af31b136550582", "creation_timestamp": 1695099871.0, "exchange_order_id": "35561923", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:04:33,807 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af31b138a80582 for 80.00000000 SEI-USDT. +2023-09-19 05:04:33,833 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099873.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605af31b138a80582", "creation_timestamp": 1695099871.0, "exchange_order_id": "35561922", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:05:26,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af31b136550582. [clock=2023-09-19 05:05:26+00:00] +2023-09-19 05:05:26,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af31b138a80582. [clock=2023-09-19 05:05:26+00:00] +2023-09-19 05:05:26,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237436783117281329213279338 amount: 80. +2023-09-19 05:05:26,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243717196424252535009454932 amount: 80. +2023-09-19 05:05:26,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099926.0, "order_id": "x-XEKWYICXBSIUT605af31b136550582", "exchange_order_id": "35561923", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:05:26,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af31b136550582. +2023-09-19 05:05:26,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099926.0, "order_id": "x-XEKWYICXSSIUT605af31b138a80582", "exchange_order_id": "35561922", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:05:26,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af31b138a80582. +2023-09-19 05:05:26,466 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af34f3ef250582 for 80.00000000 SEI-USDT. +2023-09-19 05:05:26,480 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605af34f3ef250582", "creation_timestamp": 1695099926.0, "exchange_order_id": "35562211", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:05:26,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af34f3f1cc0582 for 80.00000000 SEI-USDT. +2023-09-19 05:05:26,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605af34f3f1cc0582", "creation_timestamp": 1695099926.0, "exchange_order_id": "35562210", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:06:06,731 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605af34f3f1cc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:06:06,732 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:06:06+00:00] +2023-09-19 05:06:06,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099966.0, "order_id": "x-XEKWYICXSSIUT605af34f3f1cc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "4992232", "exchange_order_id": "35562210", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:06:06,776 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099966.0, "order_id": "x-XEKWYICXSSIUT605af34f3f1cc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35562210", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:06:06,777 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605af34f3f1cc0582 completely filled. +2023-09-19 05:06:21,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af34f3ef250582. [clock=2023-09-19 05:06:21+00:00] +2023-09-19 05:06:21,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238704978775684276659542131 amount: 80. +2023-09-19 05:06:21,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244700194688665441984764325 amount: 80. +2023-09-19 05:06:21,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099981.0, "order_id": "x-XEKWYICXBSIUT605af34f3ef250582", "exchange_order_id": "35562211", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:06:21,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af34f3ef250582. +2023-09-19 05:06:21,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af383b80810582 for 80.00000000 SEI-USDT. +2023-09-19 05:06:21,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605af383b80810582", "creation_timestamp": 1695099981.0, "exchange_order_id": "35562520", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:06:21,464 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af383b7cab0582 for 80.00000000 SEI-USDT. +2023-09-19 05:06:21,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695099981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605af383b7cab0582", "creation_timestamp": 1695099981.0, "exchange_order_id": "35562521", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:07:16,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af383b7cab0582. [clock=2023-09-19 05:07:16+00:00] +2023-09-19 05:07:16,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af383b80810582. [clock=2023-09-19 05:07:16+00:00] +2023-09-19 05:07:16,346 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238827140897520394502942616 amount: 80. +2023-09-19 05:07:16,356 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244600089240595316816380120 amount: 80. +2023-09-19 05:07:17,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100036.0, "order_id": "x-XEKWYICXBSIUT605af383b7cab0582", "exchange_order_id": "35562521", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:07:17,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af383b7cab0582. +2023-09-19 05:07:18,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100038.0, "order_id": "x-XEKWYICXSSIUT605af383b80810582", "exchange_order_id": "35562520", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:07:18,775 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af383b80810582. +2023-09-19 05:07:18,787 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af3b8697d50582 for 80.00000000 SEI-USDT. +2023-09-19 05:07:18,834 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100038.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605af3b8697d50582", "creation_timestamp": 1695100036.0, "exchange_order_id": "35562676", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:07:18,834 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af3b869c740582 for 80.00000000 SEI-USDT. +2023-09-19 05:07:18,871 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100038.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605af3b869c740582", "creation_timestamp": 1695100036.0, "exchange_order_id": "35562675", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:08:11,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af3b8697d50582. [clock=2023-09-19 05:08:11+00:00] +2023-09-19 05:08:11,191 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af3b869c740582. [clock=2023-09-19 05:08:11+00:00] +2023-09-19 05:08:11,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240327865764894600161130983 amount: 80. +2023-09-19 05:08:11,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245929773615819239843252411 amount: 80. +2023-09-19 05:08:13,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100091.0, "order_id": "x-XEKWYICXBSIUT605af3b8697d50582", "exchange_order_id": "35562676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:08:13,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af3b8697d50582. +2023-09-19 05:08:13,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100091.0, "order_id": "x-XEKWYICXSSIUT605af3b869c740582", "exchange_order_id": "35562675", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:08:13,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af3b869c740582. +2023-09-19 05:08:14,336 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af3ecc8f740582 for 80.00000000 SEI-USDT. +2023-09-19 05:08:14,380 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100094.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605af3ecc8f740582", "creation_timestamp": 1695100091.0, "exchange_order_id": "35562847", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:08:14,932 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af3eccb5560582 for 80.00000000 SEI-USDT. +2023-09-19 05:08:14,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100094.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605af3eccb5560582", "creation_timestamp": 1695100091.0, "exchange_order_id": "35562849", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:09:06,315 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af3ecc8f740582. [clock=2023-09-19 05:09:06+00:00] +2023-09-19 05:09:06,325 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af3eccb5560582. [clock=2023-09-19 05:09:06+00:00] +2023-09-19 05:09:06,399 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240920860693428668640701308 amount: 80. +2023-09-19 05:09:06,400 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245277796770244283135457158 amount: 80. +2023-09-19 05:09:07,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100146.0, "order_id": "x-XEKWYICXBSIUT605af3ecc8f740582", "exchange_order_id": "35562847", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:09:07,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af3ecc8f740582. +2023-09-19 05:09:08,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100147.0, "order_id": "x-XEKWYICXSSIUT605af3eccb5560582", "exchange_order_id": "35562849", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:09:08,129 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af3eccb5560582. +2023-09-19 05:09:08,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af4215bd210582 for 80.00000000 SEI-USDT. +2023-09-19 05:09:08,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100148.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605af4215bd210582", "creation_timestamp": 1695100146.0, "exchange_order_id": "35562929", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:09:08,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af4216009b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:09:08,512 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100148.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605af4216009b0582", "creation_timestamp": 1695100146.0, "exchange_order_id": "35562930", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:10:01,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af4215bd210582. [clock=2023-09-19 05:10:01+00:00] +2023-09-19 05:10:01,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af4216009b0582. [clock=2023-09-19 05:10:01+00:00] +2023-09-19 05:10:01,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240920860693428668640701308 amount: 80. +2023-09-19 05:10:01,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245277796770244283135457158 amount: 80. +2023-09-19 05:10:01,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100201.0, "order_id": "x-XEKWYICXBSIUT605af4215bd210582", "exchange_order_id": "35562929", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:10:01,622 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af4215bd210582. +2023-09-19 05:10:02,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100202.0, "order_id": "x-XEKWYICXSSIUT605af4216009b0582", "exchange_order_id": "35562930", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:10:02,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af4216009b0582. +2023-09-19 05:10:02,692 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af4558ce0f0582 for 80.00000000 SEI-USDT. +2023-09-19 05:10:02,719 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100202.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605af4558ce0f0582", "creation_timestamp": 1695100201.0, "exchange_order_id": "35563018", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:10:02,745 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af4558d1380582 for 80.00000000 SEI-USDT. +2023-09-19 05:10:02,787 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100202.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605af4558d1380582", "creation_timestamp": 1695100201.0, "exchange_order_id": "35563019", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:10:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af4558ce0f0582. [clock=2023-09-19 05:10:56+00:00] +2023-09-19 05:10:56,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af4558d1380582. [clock=2023-09-19 05:10:56+00:00] +2023-09-19 05:10:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240853044064885355338715138 amount: 80. +2023-09-19 05:10:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245352090498647865654809958 amount: 80. +2023-09-19 05:10:56,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100256.0, "order_id": "x-XEKWYICXBSIUT605af4558ce0f0582", "exchange_order_id": "35563018", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:10:56,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af4558ce0f0582. +2023-09-19 05:10:56,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af489e76d10582 for 80.00000000 SEI-USDT. +2023-09-19 05:10:56,444 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605af489e76d10582", "creation_timestamp": 1695100256.0, "exchange_order_id": "35563094", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:10:56,459 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100256.0, "order_id": "x-XEKWYICXSSIUT605af4558d1380582", "exchange_order_id": "35563019", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:10:56,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af4558d1380582. +2023-09-19 05:10:56,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af489e74320582 for 80.00000000 SEI-USDT. +2023-09-19 05:10:56,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605af489e74320582", "creation_timestamp": 1695100256.0, "exchange_order_id": "35563095", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:11:01,448 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605af489e76d10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:11:01,449 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:11:01+00:00] +2023-09-19 05:11:01,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100261.0, "order_id": "x-XEKWYICXSSIUT605af489e76d10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996000"}]}, "exchange_trade_id": "4992306", "exchange_order_id": "35563094", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:11:01,486 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100261.0, "order_id": "x-XEKWYICXSSIUT605af489e76d10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35563094", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:11:01,487 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605af489e76d10582 completely filled. +2023-09-19 05:11:51,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af489e74320582. [clock=2023-09-19 05:11:51+00:00] +2023-09-19 05:11:51,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243606205688076904008583892 amount: 80. +2023-09-19 05:11:51,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249332368326710388133133774 amount: 80. +2023-09-19 05:11:51,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100311.0, "order_id": "x-XEKWYICXBSIUT605af489e74320582", "exchange_order_id": "35563095", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:11:51,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af489e74320582. +2023-09-19 05:11:51,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af4be69e750582 for 80.00000000 SEI-USDT. +2023-09-19 05:11:51,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af4be69e750582", "creation_timestamp": 1695100311.0, "exchange_order_id": "35563420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:11:51,586 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af4be6a27c0582 for 80.00000000 SEI-USDT. +2023-09-19 05:11:51,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605af4be6a27c0582", "creation_timestamp": 1695100311.0, "exchange_order_id": "35563421", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:12:46,922 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af4be69e750582. [clock=2023-09-19 05:12:46+00:00] +2023-09-19 05:12:46,923 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af4be6a27c0582. [clock=2023-09-19 05:12:46+00:00] +2023-09-19 05:12:47,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243089778424988031603311073 amount: 80. +2023-09-19 05:12:47,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248656796977663638592655317 amount: 80. +2023-09-19 05:12:47,622 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100366.0, "order_id": "x-XEKWYICXBSIUT605af4be69e750582", "exchange_order_id": "35563420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:12:47,622 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af4be69e750582. +2023-09-19 05:12:48,824 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100368.0, "order_id": "x-XEKWYICXSSIUT605af4be6a27c0582", "exchange_order_id": "35563421", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:12:48,824 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af4be6a27c0582. +2023-09-19 05:12:49,129 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 05:12:49,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af4f3c0f7e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:12:49,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100368.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af4f3c0f7e0582", "creation_timestamp": 1695100366.0, "exchange_order_id": "35563526", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:12:49,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af4f3c09bf0582 for 80.00000000 SEI-USDT. +2023-09-19 05:12:49,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100368.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af4f3c09bf0582", "creation_timestamp": 1695100366.0, "exchange_order_id": "35563527", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:13:41,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af4f3c09bf0582. [clock=2023-09-19 05:13:41+00:00] +2023-09-19 05:13:41,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af4f3c0f7e0582. [clock=2023-09-19 05:13:41+00:00] +2023-09-19 05:13:41,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243945265570026540809025273 amount: 80. +2023-09-19 05:13:41,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248272511995945416903872329 amount: 80. +2023-09-19 05:13:42,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100421.0, "order_id": "x-XEKWYICXBSIUT605af4f3c09bf0582", "exchange_order_id": "35563527", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:13:42,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af4f3c09bf0582. +2023-09-19 05:13:42,936 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100422.0, "order_id": "x-XEKWYICXSSIUT605af4f3c0f7e0582", "exchange_order_id": "35563526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:13:42,936 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af4f3c0f7e0582. +2023-09-19 05:13:43,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af527703c60582 for 80.00000000 SEI-USDT. +2023-09-19 05:13:43,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100423.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af527703c60582", "creation_timestamp": 1695100421.0, "exchange_order_id": "35563783", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:13:43,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af527700bd0582 for 80.00000000 SEI-USDT. +2023-09-19 05:13:43,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100423.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af527700bd0582", "creation_timestamp": 1695100421.0, "exchange_order_id": "35563782", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:14:36,151 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af527700bd0582. [clock=2023-09-19 05:14:36+00:00] +2023-09-19 05:14:36,152 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af527703c60582. [clock=2023-09-19 05:14:36+00:00] +2023-09-19 05:14:36,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243908740223947465662882632 amount: 80. +2023-09-19 05:14:36,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248385722296306756442130646 amount: 80. +2023-09-19 05:14:36,658 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100476.0, "order_id": "x-XEKWYICXBSIUT605af527700bd0582", "exchange_order_id": "35563782", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:14:36,659 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af527700bd0582. +2023-09-19 05:14:37,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100477.0, "order_id": "x-XEKWYICXSSIUT605af527703c60582", "exchange_order_id": "35563783", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:14:37,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af527703c60582. +2023-09-19 05:14:37,802 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af55be2aaf0582 for 80.00000000 SEI-USDT. +2023-09-19 05:14:37,833 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100477.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af55be2aaf0582", "creation_timestamp": 1695100476.0, "exchange_order_id": "35563893", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:14:37,833 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af55be269e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:14:37,883 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100477.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af55be269e0582", "creation_timestamp": 1695100476.0, "exchange_order_id": "35563894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:15:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af55be269e0582. [clock=2023-09-19 05:15:31+00:00] +2023-09-19 05:15:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af55be2aaf0582. [clock=2023-09-19 05:15:31+00:00] +2023-09-19 05:15:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243652814745862857315799166 amount: 80. +2023-09-19 05:15:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248245424069945400262621772 amount: 80. +2023-09-19 05:15:31,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100531.0, "order_id": "x-XEKWYICXBSIUT605af55be269e0582", "exchange_order_id": "35563894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:15:31,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af55be269e0582. +2023-09-19 05:15:31,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100531.0, "order_id": "x-XEKWYICXSSIUT605af55be2aaf0582", "exchange_order_id": "35563893", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:15:31,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af55be2aaf0582. +2023-09-19 05:15:31,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af5902969d0582 for 80.00000000 SEI-USDT. +2023-09-19 05:15:31,403 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af5902969d0582", "creation_timestamp": 1695100531.0, "exchange_order_id": "35563984", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:15:31,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af590298c40582 for 80.00000000 SEI-USDT. +2023-09-19 05:15:31,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af590298c40582", "creation_timestamp": 1695100531.0, "exchange_order_id": "35563983", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:16:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af5902969d0582. [clock=2023-09-19 05:16:26+00:00] +2023-09-19 05:16:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af590298c40582. [clock=2023-09-19 05:16:26+00:00] +2023-09-19 05:16:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244697705024739497561579028 amount: 80. +2023-09-19 05:16:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249137344740083348343573168 amount: 80. +2023-09-19 05:16:26,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100586.0, "order_id": "x-XEKWYICXBSIUT605af5902969d0582", "exchange_order_id": "35563984", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:16:26,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af5902969d0582. +2023-09-19 05:16:26,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100586.0, "order_id": "x-XEKWYICXSSIUT605af590298c40582", "exchange_order_id": "35563983", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:16:26,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af590298c40582. +2023-09-19 05:16:26,395 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af5c49d77d0582 for 80.00000000 SEI-USDT. +2023-09-19 05:16:26,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605af5c49d77d0582", "creation_timestamp": 1695100586.0, "exchange_order_id": "35564127", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:16:26,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af5c49d4e00582 for 80.00000000 SEI-USDT. +2023-09-19 05:16:26,426 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605af5c49d4e00582", "creation_timestamp": 1695100586.0, "exchange_order_id": "35564128", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:17:21,475 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af5c49d4e00582. [clock=2023-09-19 05:17:21+00:00] +2023-09-19 05:17:21,478 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af5c49d77d0582. [clock=2023-09-19 05:17:21+00:00] +2023-09-19 05:17:21,547 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243833143757326617526637670 amount: 80. +2023-09-19 05:17:21,557 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250128112543130893789126717 amount: 80. +2023-09-19 05:17:22,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100641.0, "order_id": "x-XEKWYICXBSIUT605af5c49d4e00582", "exchange_order_id": "35564128", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:17:22,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af5c49d4e00582. +2023-09-19 05:17:23,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100642.0, "order_id": "x-XEKWYICXSSIUT605af5c49d77d0582", "exchange_order_id": "35564127", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:17:23,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af5c49d77d0582. +2023-09-19 05:17:23,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af5f993d420582 for 80.00000000 SEI-USDT. +2023-09-19 05:17:23,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605af5f993d420582", "creation_timestamp": 1695100641.0, "exchange_order_id": "35564586", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:17:23,397 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af5f99447c0582 for 80.00000000 SEI-USDT. +2023-09-19 05:17:23,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605af5f99447c0582", "creation_timestamp": 1695100641.0, "exchange_order_id": "35564587", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:18:16,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af5f993d420582. [clock=2023-09-19 05:18:16+00:00] +2023-09-19 05:18:16,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af5f99447c0582. [clock=2023-09-19 05:18:16+00:00] +2023-09-19 05:18:16,170 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242933658458986955397405966 amount: 80. +2023-09-19 05:18:16,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248936693782997316079662810 amount: 80. +2023-09-19 05:18:16,548 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100696.0, "order_id": "x-XEKWYICXBSIUT605af5f993d420582", "exchange_order_id": "35564586", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:18:16,548 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af5f993d420582. +2023-09-19 05:18:16,567 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100696.0, "order_id": "x-XEKWYICXSSIUT605af5f99447c0582", "exchange_order_id": "35564587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:18:16,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af5f99447c0582. +2023-09-19 05:18:17,718 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af62dac2540582 for 80.00000000 SEI-USDT. +2023-09-19 05:18:17,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605af62dac2540582", "creation_timestamp": 1695100696.0, "exchange_order_id": "35564826", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:18:17,932 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af62dac5840582 for 80.00000000 SEI-USDT. +2023-09-19 05:18:17,972 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605af62dac5840582", "creation_timestamp": 1695100696.0, "exchange_order_id": "35564827", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:19:11,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af62dac2540582. [clock=2023-09-19 05:19:11+00:00] +2023-09-19 05:19:11,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af62dac5840582. [clock=2023-09-19 05:19:11+00:00] +2023-09-19 05:19:11,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241066013569096875530732146 amount: 80. +2023-09-19 05:19:11,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246846191100679982377264620 amount: 80. +2023-09-19 05:19:12,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100752.0, "order_id": "x-XEKWYICXBSIUT605af62dac2540582", "exchange_order_id": "35564826", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:19:12,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af62dac2540582. +2023-09-19 05:19:12,484 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100752.0, "order_id": "x-XEKWYICXSSIUT605af62dac5840582", "exchange_order_id": "35564827", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:19:12,484 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af62dac5840582. +2023-09-19 05:19:12,487 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af6621997e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:19:12,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100752.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605af6621997e0582", "creation_timestamp": 1695100751.0, "exchange_order_id": "35565137", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:19:12,514 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af66219c280582 for 80.00000000 SEI-USDT. +2023-09-19 05:19:12,540 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100752.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605af66219c280582", "creation_timestamp": 1695100751.0, "exchange_order_id": "35565138", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:19:57,472 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605af66219c280582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:19:57,485 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:19:57+00:00] +2023-09-19 05:19:57,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100797.0, "order_id": "x-XEKWYICXSSIUT605af66219c280582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "4992588", "exchange_order_id": "35565138", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:19:57,999 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100797.0, "order_id": "x-XEKWYICXSSIUT605af66219c280582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35565138", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:19:57,999 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605af66219c280582 completely filled. +2023-09-19 05:20:06,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af6621997e0582. [clock=2023-09-19 05:20:06+00:00] +2023-09-19 05:20:06,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245741104422669869543491577 amount: 80. +2023-09-19 05:20:06,150 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252222549808221849704868833 amount: 80. +2023-09-19 05:20:06,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100806.0, "order_id": "x-XEKWYICXBSIUT605af6621997e0582", "exchange_order_id": "35565137", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:20:06,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af6621997e0582. +2023-09-19 05:20:06,684 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af6968b7ee0582 for 80.00000000 SEI-USDT. +2023-09-19 05:20:06,718 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605af6968b7ee0582", "creation_timestamp": 1695100806.0, "exchange_order_id": "35565710", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:20:07,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af6968bd610582 for 80.00000000 SEI-USDT. +2023-09-19 05:20:07,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100807.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605af6968bd610582", "creation_timestamp": 1695100806.0, "exchange_order_id": "35565713", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:21:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af6968b7ee0582. [clock=2023-09-19 05:21:01+00:00] +2023-09-19 05:21:01,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af6968bd610582. [clock=2023-09-19 05:21:01+00:00] +2023-09-19 05:21:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-19 05:21:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258198203709669505095377710 amount: 80. +2023-09-19 05:21:01,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100861.0, "order_id": "x-XEKWYICXBSIUT605af6968b7ee0582", "exchange_order_id": "35565710", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:21:01,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af6968b7ee0582. +2023-09-19 05:21:01,399 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100861.0, "order_id": "x-XEKWYICXSSIUT605af6968bd610582", "exchange_order_id": "35565713", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:21:01,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af6968bd610582. +2023-09-19 05:21:01,404 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af6cae03180582 for 80.00000000 SEI-USDT. +2023-09-19 05:21:01,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605af6cae03180582", "creation_timestamp": 1695100861.0, "exchange_order_id": "35565955", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:21:01,420 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af6cae064a0582 for 80.00000000 SEI-USDT. +2023-09-19 05:21:01,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605af6cae064a0582", "creation_timestamp": 1695100861.0, "exchange_order_id": "35565956", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:21:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af6cae03180582. [clock=2023-09-19 05:21:56+00:00] +2023-09-19 05:21:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af6cae064a0582. [clock=2023-09-19 05:21:56+00:00] +2023-09-19 05:21:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-19 05:21:56,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259546548193392221074299575 amount: 80. +2023-09-19 05:21:56,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100916.0, "order_id": "x-XEKWYICXBSIUT605af6cae03180582", "exchange_order_id": "35565955", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:21:56,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af6cae03180582. +2023-09-19 05:21:56,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100916.0, "order_id": "x-XEKWYICXSSIUT605af6cae064a0582", "exchange_order_id": "35565956", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:21:56,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af6cae064a0582. +2023-09-19 05:21:56,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af6ff53a470582 for 80.00000000 SEI-USDT. +2023-09-19 05:21:56,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605af6ff53a470582", "creation_timestamp": 1695100916.0, "exchange_order_id": "35566184", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:21:56,425 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af6ff535e40582 for 80.00000000 SEI-USDT. +2023-09-19 05:21:56,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605af6ff535e40582", "creation_timestamp": 1695100916.0, "exchange_order_id": "35566183", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:22:12,521 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605af6ff535e40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:22:12,522 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:22:12+00:00] +2023-09-19 05:22:12,580 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100932.0, "order_id": "x-XEKWYICXBSIUT605af6ff535e40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4992663", "exchange_order_id": "35566183", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:22:12,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100932.0, "order_id": "x-XEKWYICXBSIUT605af6ff535e40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35566183", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:22:12,678 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605af6ff535e40582 completely filled. +2023-09-19 05:22:51,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af6ff53a470582. [clock=2023-09-19 05:22:51+00:00] +2023-09-19 05:22:51,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246810815739544207377486861 amount: 80. +2023-09-19 05:22:51,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254921381798157310496400941 amount: 80. +2023-09-19 05:22:51,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100971.0, "order_id": "x-XEKWYICXSSIUT605af6ff53a470582", "exchange_order_id": "35566184", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:22:51,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af6ff53a470582. +2023-09-19 05:22:51,967 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af733d843b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:22:51,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af733d843b0582", "creation_timestamp": 1695100971.0, "exchange_order_id": "35566315", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:22:52,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af733da7660582 for 80.00000000 SEI-USDT. +2023-09-19 05:22:52,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695100972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605af733da7660582", "creation_timestamp": 1695100971.0, "exchange_order_id": "35566316", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:23:46,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af733d843b0582. [clock=2023-09-19 05:23:46+00:00] +2023-09-19 05:23:46,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af733da7660582. [clock=2023-09-19 05:23:46+00:00] +2023-09-19 05:23:46,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246918079808111427069091269 amount: 80. +2023-09-19 05:23:46,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254337655698780324496497000 amount: 80. +2023-09-19 05:23:46,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101026.0, "order_id": "x-XEKWYICXBSIUT605af733d843b0582", "exchange_order_id": "35566315", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:23:46,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af733d843b0582. +2023-09-19 05:23:47,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af76853ed30582 for 80.00000000 SEI-USDT. +2023-09-19 05:23:47,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101027.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605af76853ed30582", "creation_timestamp": 1695101026.0, "exchange_order_id": "35566481", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:23:47,422 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af76853b530582 for 80.00000000 SEI-USDT. +2023-09-19 05:23:47,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101027.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af76853b530582", "creation_timestamp": 1695101026.0, "exchange_order_id": "35566482", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:23:47,484 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101027.0, "order_id": "x-XEKWYICXSSIUT605af733da7660582", "exchange_order_id": "35566316", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:23:47,484 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af733da7660582. +2023-09-19 05:24:41,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af76853b530582. [clock=2023-09-19 05:24:41+00:00] +2023-09-19 05:24:41,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af76853ed30582. [clock=2023-09-19 05:24:41+00:00] +2023-09-19 05:24:41,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 05:24:41,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257345480492103929674554622 amount: 80. +2023-09-19 05:24:41,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101081.0, "order_id": "x-XEKWYICXBSIUT605af76853b530582", "exchange_order_id": "35566482", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:24:41,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af76853b530582. +2023-09-19 05:24:42,627 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af79cca50a0582 for 80.00000000 SEI-USDT. +2023-09-19 05:24:42,663 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101082.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605af79cca50a0582", "creation_timestamp": 1695101081.0, "exchange_order_id": "35566909", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:24:42,721 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101082.0, "order_id": "x-XEKWYICXSSIUT605af76853ed30582", "exchange_order_id": "35566481", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:24:42,722 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af76853ed30582. +2023-09-19 05:24:42,732 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af79cca32a0582 for 80.00000000 SEI-USDT. +2023-09-19 05:24:42,796 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101082.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605af79cca32a0582", "creation_timestamp": 1695101081.0, "exchange_order_id": "35566910", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:25:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af79cca32a0582. [clock=2023-09-19 05:25:36+00:00] +2023-09-19 05:25:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af79cca50a0582. [clock=2023-09-19 05:25:36+00:00] +2023-09-19 05:25:36,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 05:25:36,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261012736393967459408334287 amount: 80. +2023-09-19 05:25:36,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101136.0, "order_id": "x-XEKWYICXBSIUT605af79cca32a0582", "exchange_order_id": "35566910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:25:36,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af79cca32a0582. +2023-09-19 05:25:36,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101136.0, "order_id": "x-XEKWYICXSSIUT605af79cca50a0582", "exchange_order_id": "35566909", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:25:36,625 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af79cca50a0582. +2023-09-19 05:25:36,628 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af7d1281860582 for 80.00000000 SEI-USDT. +2023-09-19 05:25:36,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605af7d1281860582", "creation_timestamp": 1695101136.0, "exchange_order_id": "35567328", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:25:36,733 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af7d12853b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:25:36,752 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605af7d12853b0582", "creation_timestamp": 1695101136.0, "exchange_order_id": "35567329", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:25:53,208 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605af7d1281860582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:25:53,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 05:25:53+00:00] +2023-09-19 05:25:53,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101153.0, "order_id": "x-XEKWYICXBSIUT605af7d1281860582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4992793", "exchange_order_id": "35567328", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:25:53,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101153.0, "order_id": "x-XEKWYICXBSIUT605af7d1281860582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35567328", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:25:53,243 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605af7d1281860582 completely filled. +2023-09-19 05:26:31,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af7d12853b0582. [clock=2023-09-19 05:26:31+00:00] +2023-09-19 05:26:31,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252666782626954072889007222 amount: 80. +2023-09-19 05:26:31,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261469615214024855598025108 amount: 80. +2023-09-19 05:26:31,218 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101191.0, "order_id": "x-XEKWYICXSSIUT605af7d12853b0582", "exchange_order_id": "35567329", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:26:31,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af7d12853b0582. +2023-09-19 05:26:31,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af8059f1b10582 for 80.00000000 SEI-USDT. +2023-09-19 05:26:31,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605af8059f1b10582", "creation_timestamp": 1695101191.0, "exchange_order_id": "35567920", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:26:31,584 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af8059f5840582 for 80.00000000 SEI-USDT. +2023-09-19 05:26:31,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605af8059f5840582", "creation_timestamp": 1695101191.0, "exchange_order_id": "35567921", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:26:33,920 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605af8059f1b10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:26:33,921 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 05:26:33+00:00] +2023-09-19 05:26:33,948 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101193.0, "order_id": "x-XEKWYICXBSIUT605af8059f1b10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4992869", "exchange_order_id": "35567920", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:26:33,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101193.0, "order_id": "x-XEKWYICXBSIUT605af8059f1b10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35567920", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:26:33,968 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605af8059f1b10582 completely filled. +2023-09-19 05:27:26,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af8059f5840582. [clock=2023-09-19 05:27:26+00:00] +2023-09-19 05:27:26,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246908035210037255723694781 amount: 80. +2023-09-19 05:27:26,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255963346624612007811297455 amount: 80. +2023-09-19 05:27:26,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101246.0, "order_id": "x-XEKWYICXSSIUT605af8059f5840582", "exchange_order_id": "35567921", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:27:26,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af8059f5840582. +2023-09-19 05:27:27,546 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af83a23da30582 for 80.00000000 SEI-USDT. +2023-09-19 05:27:27,580 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101247.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af83a23da30582", "creation_timestamp": 1695101246.0, "exchange_order_id": "35568699", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:27:27,907 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af83a241040582 for 80.00000000 SEI-USDT. +2023-09-19 05:27:27,938 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101247.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af83a241040582", "creation_timestamp": 1695101246.0, "exchange_order_id": "35568700", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:28:21,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af83a23da30582. [clock=2023-09-19 05:28:21+00:00] +2023-09-19 05:28:21,130 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af83a241040582. [clock=2023-09-19 05:28:21+00:00] +2023-09-19 05:28:21,173 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250009875059153701504982197 amount: 80. +2023-09-19 05:28:21,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257059072046473167752475877 amount: 80. +2023-09-19 05:28:21,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101301.0, "order_id": "x-XEKWYICXBSIUT605af83a23da30582", "exchange_order_id": "35568699", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:28:21,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af83a23da30582. +2023-09-19 05:28:22,863 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101302.0, "order_id": "x-XEKWYICXSSIUT605af83a241040582", "exchange_order_id": "35568700", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:28:22,864 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af83a241040582. +2023-09-19 05:28:23,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af86ea34b00582 for 80.00000000 SEI-USDT. +2023-09-19 05:28:23,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101302.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605af86ea34b00582", "creation_timestamp": 1695101301.0, "exchange_order_id": "35568878", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:28:23,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af86ea31b80582 for 80.00000000 SEI-USDT. +2023-09-19 05:28:23,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101302.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605af86ea31b80582", "creation_timestamp": 1695101301.0, "exchange_order_id": "35568879", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:29:16,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af86ea31b80582. [clock=2023-09-19 05:29:16+00:00] +2023-09-19 05:29:16,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af86ea34b00582. [clock=2023-09-19 05:29:16+00:00] +2023-09-19 05:29:16,173 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246367571392919545912857595 amount: 80. +2023-09-19 05:29:16,175 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255546958800455158025277119 amount: 80. +2023-09-19 05:29:16,573 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101356.0, "order_id": "x-XEKWYICXBSIUT605af86ea31b80582", "exchange_order_id": "35568879", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:29:16,573 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af86ea31b80582. +2023-09-19 05:29:17,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101357.0, "order_id": "x-XEKWYICXSSIUT605af86ea34b00582", "exchange_order_id": "35568878", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:29:17,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af86ea34b00582. +2023-09-19 05:29:17,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af8a3172780582 for 80.00000000 SEI-USDT. +2023-09-19 05:29:17,570 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af8a3172780582", "creation_timestamp": 1695101356.0, "exchange_order_id": "35569463", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:29:17,920 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af8a316e7e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:29:18,002 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af8a316e7e0582", "creation_timestamp": 1695101356.0, "exchange_order_id": "35569462", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:30:11,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af8a316e7e0582. [clock=2023-09-19 05:30:11+00:00] +2023-09-19 05:30:11,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af8a3172780582. [clock=2023-09-19 05:30:11+00:00] +2023-09-19 05:30:11,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248331093908144398238444792 amount: 80. +2023-09-19 05:30:11,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256337195043580492663932934 amount: 80. +2023-09-19 05:30:11,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101411.0, "order_id": "x-XEKWYICXBSIUT605af8a316e7e0582", "exchange_order_id": "35569462", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:30:11,441 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af8a316e7e0582. +2023-09-19 05:30:11,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101411.0, "order_id": "x-XEKWYICXSSIUT605af8a3172780582", "exchange_order_id": "35569463", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:30:11,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af8a3172780582. +2023-09-19 05:30:11,658 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af8d76ded30582 for 80.00000000 SEI-USDT. +2023-09-19 05:30:11,672 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605af8d76ded30582", "creation_timestamp": 1695101411.0, "exchange_order_id": "35569958", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:30:11,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af8d76e26f0582 for 80.00000000 SEI-USDT. +2023-09-19 05:30:11,685 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605af8d76e26f0582", "creation_timestamp": 1695101411.0, "exchange_order_id": "35569959", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:31:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af8d76ded30582. [clock=2023-09-19 05:31:06+00:00] +2023-09-19 05:31:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af8d76e26f0582. [clock=2023-09-19 05:31:06+00:00] +2023-09-19 05:31:06,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246386795923316843368428249 amount: 80. +2023-09-19 05:31:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255443754487588819876682057 amount: 80. +2023-09-19 05:31:06,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101466.0, "order_id": "x-XEKWYICXBSIUT605af8d76ded30582", "exchange_order_id": "35569958", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:31:06,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af8d76ded30582. +2023-09-19 05:31:06,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af90bd902f0582 for 80.00000000 SEI-USDT. +2023-09-19 05:31:06,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af90bd902f0582", "creation_timestamp": 1695101466.0, "exchange_order_id": "35570260", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:31:06,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101466.0, "order_id": "x-XEKWYICXSSIUT605af8d76e26f0582", "exchange_order_id": "35569959", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:31:06,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af8d76e26f0582. +2023-09-19 05:31:06,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af90bd8cae0582 for 80.00000000 SEI-USDT. +2023-09-19 05:31:06,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af90bd8cae0582", "creation_timestamp": 1695101466.0, "exchange_order_id": "35570261", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:32:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af90bd8cae0582. [clock=2023-09-19 05:32:01+00:00] +2023-09-19 05:32:01,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af90bd902f0582. [clock=2023-09-19 05:32:01+00:00] +2023-09-19 05:32:01,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246388460092252529286371483 amount: 80. +2023-09-19 05:32:01,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255445430749326791372808549 amount: 80. +2023-09-19 05:32:01,414 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101521.0, "order_id": "x-XEKWYICXBSIUT605af90bd8cae0582", "exchange_order_id": "35570261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:32:01,415 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af90bd8cae0582. +2023-09-19 05:32:01,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101521.0, "order_id": "x-XEKWYICXSSIUT605af90bd902f0582", "exchange_order_id": "35570260", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:32:01,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af90bd902f0582. +2023-09-19 05:32:01,719 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af9404ec3a0582 for 80.00000000 SEI-USDT. +2023-09-19 05:32:01,745 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605af9404ec3a0582", "creation_timestamp": 1695101521.0, "exchange_order_id": "35570454", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:32:01,746 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af9404e8e40582 for 80.00000000 SEI-USDT. +2023-09-19 05:32:01,780 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af9404e8e40582", "creation_timestamp": 1695101521.0, "exchange_order_id": "35570455", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:32:56,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af9404e8e40582. [clock=2023-09-19 05:32:56+00:00] +2023-09-19 05:32:56,176 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af9404ec3a0582. [clock=2023-09-19 05:32:56+00:00] +2023-09-19 05:32:56,264 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246197124788708255870649047 amount: 80. +2023-09-19 05:32:56,266 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254353918941697832376310609 amount: 80. +2023-09-19 05:32:57,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101576.0, "order_id": "x-XEKWYICXBSIUT605af9404e8e40582", "exchange_order_id": "35570455", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:32:57,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af9404e8e40582. +2023-09-19 05:32:58,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101578.0, "order_id": "x-XEKWYICXSSIUT605af9404ec3a0582", "exchange_order_id": "35570454", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:32:58,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af9404ec3a0582. +2023-09-19 05:32:58,590 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af974fc6530582 for 80.00000000 SEI-USDT. +2023-09-19 05:32:58,619 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605af974fc6530582", "creation_timestamp": 1695101576.0, "exchange_order_id": "35570734", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:32:58,620 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af974fc0a60582 for 80.00000000 SEI-USDT. +2023-09-19 05:32:58,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605af974fc0a60582", "creation_timestamp": 1695101576.0, "exchange_order_id": "35570735", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:33:51,312 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af974fc0a60582. [clock=2023-09-19 05:33:51+00:00] +2023-09-19 05:33:51,323 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af974fc6530582. [clock=2023-09-19 05:33:51+00:00] +2023-09-19 05:33:51,394 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244960837418678706076131441 amount: 80. +2023-09-19 05:33:51,419 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253518850949899263924711554 amount: 80. +2023-09-19 05:33:52,015 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101631.0, "order_id": "x-XEKWYICXBSIUT605af974fc0a60582", "exchange_order_id": "35570735", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:33:52,016 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af974fc0a60582. +2023-09-19 05:33:53,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101633.0, "order_id": "x-XEKWYICXSSIUT605af974fc6530582", "exchange_order_id": "35570734", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:33:53,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af974fc6530582. +2023-09-19 05:33:53,656 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af9a9954440582 for 80.00000000 SEI-USDT. +2023-09-19 05:33:53,695 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605af9a9954440582", "creation_timestamp": 1695101631.0, "exchange_order_id": "35571125", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:33:53,695 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af9a995a750582 for 80.00000000 SEI-USDT. +2023-09-19 05:33:53,720 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605af9a995a750582", "creation_timestamp": 1695101631.0, "exchange_order_id": "35571126", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:34:46,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af9a9954440582. [clock=2023-09-19 05:34:46+00:00] +2023-09-19 05:34:46,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af9a995a750582. [clock=2023-09-19 05:34:46+00:00] +2023-09-19 05:34:46,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240303745808028790603782609 amount: 80. +2023-09-19 05:34:46,134 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250280996944210340661004307 amount: 80. +2023-09-19 05:34:46,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101686.0, "order_id": "x-XEKWYICXBSIUT605af9a9954440582", "exchange_order_id": "35571125", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:34:46,547 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af9a9954440582. +2023-09-19 05:34:47,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101687.0, "order_id": "x-XEKWYICXSSIUT605af9a995a750582", "exchange_order_id": "35571126", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:34:47,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af9a995a750582. +2023-09-19 05:34:47,582 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605af9ddc35740582 for 80.00000000 SEI-USDT. +2023-09-19 05:34:47,613 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101687.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605af9ddc35740582", "creation_timestamp": 1695101686.0, "exchange_order_id": "35571601", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:34:47,615 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605af9ddc39810582 for 80.00000000 SEI-USDT. +2023-09-19 05:34:47,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101687.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605af9ddc39810582", "creation_timestamp": 1695101686.0, "exchange_order_id": "35571602", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:35:41,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605af9ddc35740582. [clock=2023-09-19 05:35:41+00:00] +2023-09-19 05:35:41,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605af9ddc39810582. [clock=2023-09-19 05:35:41+00:00] +2023-09-19 05:35:41,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230872543152739989437251501 amount: 80. +2023-09-19 05:35:41,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-19 05:35:41,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101741.0, "order_id": "x-XEKWYICXBSIUT605af9ddc35740582", "exchange_order_id": "35571601", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:35:41,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605af9ddc35740582. +2023-09-19 05:35:41,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101741.0, "order_id": "x-XEKWYICXSSIUT605af9ddc39810582", "exchange_order_id": "35571602", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:35:41,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605af9ddc39810582. +2023-09-19 05:35:41,503 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afa12276520582 for 80.00000000 SEI-USDT. +2023-09-19 05:35:41,516 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605afa12276520582", "creation_timestamp": 1695101741.0, "exchange_order_id": "35572639", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:35:41,516 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afa12273490582 for 80.00000000 SEI-USDT. +2023-09-19 05:35:41,532 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605afa12273490582", "creation_timestamp": 1695101741.0, "exchange_order_id": "35572638", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:36:36,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afa12273490582. [clock=2023-09-19 05:36:36+00:00] +2023-09-19 05:36:36,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afa12276520582. [clock=2023-09-19 05:36:36+00:00] +2023-09-19 05:36:36,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229726025763359379388024511 amount: 80. +2023-09-19 05:36:36,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 05:36:36,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101796.0, "order_id": "x-XEKWYICXBSIUT605afa12273490582", "exchange_order_id": "35572638", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:36:36,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afa12273490582. +2023-09-19 05:36:36,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101796.0, "order_id": "x-XEKWYICXSSIUT605afa12276520582", "exchange_order_id": "35572639", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:36:36,584 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afa12276520582. +2023-09-19 05:36:36,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afa469f3d20582 for 80.00000000 SEI-USDT. +2023-09-19 05:36:36,599 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605afa469f3d20582", "creation_timestamp": 1695101796.0, "exchange_order_id": "35573083", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:36:36,599 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afa469f62a0582 for 80.00000000 SEI-USDT. +2023-09-19 05:36:36,610 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605afa469f62a0582", "creation_timestamp": 1695101796.0, "exchange_order_id": "35573082", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:37:31,197 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afa469f3d20582. [clock=2023-09-19 05:37:31+00:00] +2023-09-19 05:37:31,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afa469f62a0582. [clock=2023-09-19 05:37:31+00:00] +2023-09-19 05:37:31,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232893091988901166380786976 amount: 80. +2023-09-19 05:37:31,286 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 05:37:32,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101851.0, "order_id": "x-XEKWYICXBSIUT605afa469f3d20582", "exchange_order_id": "35573083", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:37:32,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afa469f3d20582. +2023-09-19 05:37:33,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101853.0, "order_id": "x-XEKWYICXSSIUT605afa469f62a0582", "exchange_order_id": "35573082", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:37:33,563 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afa469f62a0582. +2023-09-19 05:37:34,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afa7b43d000582 for 80.00000000 SEI-USDT. +2023-09-19 05:37:34,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101853.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605afa7b43d000582", "creation_timestamp": 1695101851.0, "exchange_order_id": "35573247", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:37:34,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afa7b473fb0582 for 80.00000000 SEI-USDT. +2023-09-19 05:37:34,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101853.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605afa7b473fb0582", "creation_timestamp": 1695101851.0, "exchange_order_id": "35573246", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:38:26,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afa7b43d000582. [clock=2023-09-19 05:38:26+00:00] +2023-09-19 05:38:26,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afa7b473fb0582. [clock=2023-09-19 05:38:26+00:00] +2023-09-19 05:38:26,359 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234370044341574428580605634 amount: 80. +2023-09-19 05:38:26,361 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 05:38:27,020 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101906.0, "order_id": "x-XEKWYICXBSIUT605afa7b43d000582", "exchange_order_id": "35573247", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:38:27,021 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afa7b43d000582. +2023-09-19 05:38:29,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101909.0, "order_id": "x-XEKWYICXSSIUT605afa7b473fb0582", "exchange_order_id": "35573246", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:38:29,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afa7b473fb0582. +2023-09-19 05:38:29,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afaafc9ed00582 for 80.00000000 SEI-USDT. +2023-09-19 05:38:29,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101909.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605afaafc9ed00582", "creation_timestamp": 1695101906.0, "exchange_order_id": "35573434", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:38:29,692 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afaafc9b120582 for 80.00000000 SEI-USDT. +2023-09-19 05:38:29,721 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101909.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605afaafc9b120582", "creation_timestamp": 1695101906.0, "exchange_order_id": "35573433", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:38:36,982 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605afaafc9ed00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:38:36,984 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:38:36+00:00] +2023-09-19 05:38:37,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101916.0, "order_id": "x-XEKWYICXSSIUT605afaafc9ed00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996000"}]}, "exchange_trade_id": "4993307", "exchange_order_id": "35573434", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:38:37,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101916.0, "order_id": "x-XEKWYICXSSIUT605afaafc9ed00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35573434", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:38:37,490 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605afaafc9ed00582 completely filled. +2023-09-19 05:39:21,230 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afaafc9b120582. [clock=2023-09-19 05:39:21+00:00] +2023-09-19 05:39:21,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240618097795982004220299972 amount: 80. +2023-09-19 05:39:21,319 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248935081689237719637576313 amount: 80. +2023-09-19 05:39:22,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101961.0, "order_id": "x-XEKWYICXBSIUT605afaafc9b120582", "exchange_order_id": "35573433", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:39:22,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afaafc9b120582. +2023-09-19 05:39:23,677 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afae4333970582 for 80.00000000 SEI-USDT. +2023-09-19 05:39:23,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101963.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605afae4333970582", "creation_timestamp": 1695101961.0, "exchange_order_id": "35573696", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:39:24,002 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afae4338a40582 for 80.00000000 SEI-USDT. +2023-09-19 05:39:24,044 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695101963.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605afae4338a40582", "creation_timestamp": 1695101961.0, "exchange_order_id": "35573697", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:40:16,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afae4333970582. [clock=2023-09-19 05:40:16+00:00] +2023-09-19 05:40:16,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afae4338a40582. [clock=2023-09-19 05:40:16+00:00] +2023-09-19 05:40:16,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241810752729619055856356721 amount: 80. +2023-09-19 05:40:16,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248280917174025782930727909 amount: 80. +2023-09-19 05:40:16,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "order_id": "x-XEKWYICXBSIUT605afae4333970582", "exchange_order_id": "35573696", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:40:16,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afae4333970582. +2023-09-19 05:40:16,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "order_id": "x-XEKWYICXSSIUT605afae4338a40582", "exchange_order_id": "35573697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:40:16,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afae4338a40582. +2023-09-19 05:40:16,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afb18701920582 for 80.00000000 SEI-USDT. +2023-09-19 05:40:16,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605afb18701920582", "creation_timestamp": 1695102016.0, "exchange_order_id": "35574012", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:40:16,607 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605afb18704580582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:40:16,608 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:40:16+00:00] +2023-09-19 05:40:16,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "order_id": "x-XEKWYICXSSIUT605afb18704580582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00999200"}]}, "exchange_trade_id": "4993386", "exchange_order_id": "35574013", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:40:16,629 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afb18704580582 for 80.00000000 SEI-USDT. +2023-09-19 05:40:16,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605afb18704580582", "creation_timestamp": 1695102016.0, "exchange_order_id": "35574013", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:40:16,656 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102016.0, "order_id": "x-XEKWYICXSSIUT605afb18704580582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35574013", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:40:16,656 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605afb18704580582 completely filled. +2023-09-19 05:41:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afb18701920582. [clock=2023-09-19 05:41:11+00:00] +2023-09-19 05:41:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246843810070279104860230563 amount: 80. +2023-09-19 05:41:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256212443671429719943596647 amount: 80. +2023-09-19 05:41:11,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102071.0, "order_id": "x-XEKWYICXBSIUT605afb18701920582", "exchange_order_id": "35574012", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:41:11,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afb18701920582. +2023-09-19 05:41:11,373 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afb4cd20240582 for 80.00000000 SEI-USDT. +2023-09-19 05:41:11,388 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605afb4cd20240582", "creation_timestamp": 1695102071.0, "exchange_order_id": "35574500", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:41:11,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afb4cd1dd10582 for 80.00000000 SEI-USDT. +2023-09-19 05:41:11,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605afb4cd1dd10582", "creation_timestamp": 1695102071.0, "exchange_order_id": "35574501", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:42:06,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afb4cd1dd10582. [clock=2023-09-19 05:42:06+00:00] +2023-09-19 05:42:06,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afb4cd20240582. [clock=2023-09-19 05:42:06+00:00] +2023-09-19 05:42:06,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246678121119936599962134675 amount: 80. +2023-09-19 05:42:06,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256801428017595430841799725 amount: 80. +2023-09-19 05:42:06,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102126.0, "order_id": "x-XEKWYICXBSIUT605afb4cd1dd10582", "exchange_order_id": "35574501", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:42:06,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afb4cd1dd10582. +2023-09-19 05:42:06,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102126.0, "order_id": "x-XEKWYICXSSIUT605afb4cd20240582", "exchange_order_id": "35574500", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:42:06,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afb4cd20240582. +2023-09-19 05:42:06,760 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afb815328b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:42:06,791 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605afb815328b0582", "creation_timestamp": 1695102126.0, "exchange_order_id": "35574719", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:42:06,791 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afb81536250582 for 80.00000000 SEI-USDT. +2023-09-19 05:42:06,824 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605afb81536250582", "creation_timestamp": 1695102126.0, "exchange_order_id": "35574718", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:42:50,465 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 05:43:01,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afb815328b0582. [clock=2023-09-19 05:43:01+00:00] +2023-09-19 05:43:01,256 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afb81536250582. [clock=2023-09-19 05:43:01+00:00] +2023-09-19 05:43:01,329 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245780457562555787396215088 amount: 80. +2023-09-19 05:43:01,331 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257021477263783292895479836 amount: 80. +2023-09-19 05:43:01,988 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102181.0, "order_id": "x-XEKWYICXBSIUT605afb815328b0582", "exchange_order_id": "35574719", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:43:01,988 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afb815328b0582. +2023-09-19 05:43:04,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102183.0, "order_id": "x-XEKWYICXSSIUT605afb81536250582", "exchange_order_id": "35574718", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:43:04,057 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afb81536250582. +2023-09-19 05:43:04,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afbb6056db0582 for 80.00000000 SEI-USDT. +2023-09-19 05:43:04,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102184.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605afbb6056db0582", "creation_timestamp": 1695102181.0, "exchange_order_id": "35574947", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:43:04,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afbb60525e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:43:04,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102184.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605afbb60525e0582", "creation_timestamp": 1695102181.0, "exchange_order_id": "35574948", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:43:56,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afbb60525e0582. [clock=2023-09-19 05:43:56+00:00] +2023-09-19 05:43:56,205 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afbb6056db0582. [clock=2023-09-19 05:43:56+00:00] +2023-09-19 05:43:56,286 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245304185396923194185780633 amount: 80. +2023-09-19 05:43:56,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255150313554273172728194723 amount: 80. +2023-09-19 05:43:56,999 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102236.0, "order_id": "x-XEKWYICXBSIUT605afbb60525e0582", "exchange_order_id": "35574948", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:43:56,999 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afbb60525e0582. +2023-09-19 05:43:57,766 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102237.0, "order_id": "x-XEKWYICXSSIUT605afbb6056db0582", "exchange_order_id": "35574947", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:43:57,767 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afbb6056db0582. +2023-09-19 05:43:58,046 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afbea6e3ca0582 for 80.00000000 SEI-USDT. +2023-09-19 05:43:58,093 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102237.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605afbea6e3ca0582", "creation_timestamp": 1695102236.0, "exchange_order_id": "35575080", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:43:58,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afbea6e0f10582 for 80.00000000 SEI-USDT. +2023-09-19 05:43:58,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102237.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605afbea6e0f10582", "creation_timestamp": 1695102236.0, "exchange_order_id": "35575081", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:44:51,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afbea6e0f10582. [clock=2023-09-19 05:44:51+00:00] +2023-09-19 05:44:51,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afbea6e3ca0582. [clock=2023-09-19 05:44:51+00:00] +2023-09-19 05:44:51,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246707861942994390107850414 amount: 80. +2023-09-19 05:44:51,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255483365660433608882876640 amount: 80. +2023-09-19 05:44:51,566 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102291.0, "order_id": "x-XEKWYICXBSIUT605afbea6e0f10582", "exchange_order_id": "35575081", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:44:51,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afbea6e0f10582. +2023-09-19 05:44:52,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102292.0, "order_id": "x-XEKWYICXSSIUT605afbea6e3ca0582", "exchange_order_id": "35575080", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:44:52,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afbea6e3ca0582. +2023-09-19 05:44:52,908 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afc1ec2f440582 for 80.00000000 SEI-USDT. +2023-09-19 05:44:52,949 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102292.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605afc1ec2f440582", "creation_timestamp": 1695102291.0, "exchange_order_id": "35575243", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:44:52,950 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afc1ec320f0582 for 80.00000000 SEI-USDT. +2023-09-19 05:44:52,979 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102292.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605afc1ec320f0582", "creation_timestamp": 1695102291.0, "exchange_order_id": "35575244", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:45:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afc1ec2f440582. [clock=2023-09-19 05:45:46+00:00] +2023-09-19 05:45:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afc1ec320f0582. [clock=2023-09-19 05:45:46+00:00] +2023-09-19 05:45:46,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247438734651867092779926130 amount: 80. +2023-09-19 05:45:46,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254262790638854932129958500 amount: 80. +2023-09-19 05:45:46,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102346.0, "order_id": "x-XEKWYICXBSIUT605afc1ec2f440582", "exchange_order_id": "35575243", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:45:46,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afc1ec2f440582. +2023-09-19 05:45:46,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afc53149a80582 for 80.00000000 SEI-USDT. +2023-09-19 05:45:46,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605afc53149a80582", "creation_timestamp": 1695102346.0, "exchange_order_id": "35575422", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:45:46,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afc53145da0582 for 80.00000000 SEI-USDT. +2023-09-19 05:45:46,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605afc53145da0582", "creation_timestamp": 1695102346.0, "exchange_order_id": "35575423", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:45:46,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102346.0, "order_id": "x-XEKWYICXSSIUT605afc1ec320f0582", "exchange_order_id": "35575244", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:45:46,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afc1ec320f0582. +2023-09-19 05:46:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afc53145da0582. [clock=2023-09-19 05:46:41+00:00] +2023-09-19 05:46:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afc53149a80582. [clock=2023-09-19 05:46:41+00:00] +2023-09-19 05:46:41,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248910228369847196334206630 amount: 80. +2023-09-19 05:46:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256192732546316094247804800 amount: 80. +2023-09-19 05:46:41,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102401.0, "order_id": "x-XEKWYICXBSIUT605afc53145da0582", "exchange_order_id": "35575423", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:46:41,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afc53145da0582. +2023-09-19 05:46:41,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afc87886160582 for 80.00000000 SEI-USDT. +2023-09-19 05:46:41,404 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605afc87886160582", "creation_timestamp": 1695102401.0, "exchange_order_id": "35575790", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:46:41,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afc8788a300582 for 80.00000000 SEI-USDT. +2023-09-19 05:46:41,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605afc8788a300582", "creation_timestamp": 1695102401.0, "exchange_order_id": "35575791", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:46:41,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102401.0, "order_id": "x-XEKWYICXSSIUT605afc53149a80582", "exchange_order_id": "35575422", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:46:41,433 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afc53149a80582. +2023-09-19 05:47:36,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afc87886160582. [clock=2023-09-19 05:47:36+00:00] +2023-09-19 05:47:36,246 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afc8788a300582. [clock=2023-09-19 05:47:36+00:00] +2023-09-19 05:47:36,335 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250124128570686130762574045 amount: 80. +2023-09-19 05:47:36,337 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256657250938178497530939841 amount: 80. +2023-09-19 05:47:37,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102456.0, "order_id": "x-XEKWYICXBSIUT605afc87886160582", "exchange_order_id": "35575790", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:47:37,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afc87886160582. +2023-09-19 05:47:37,889 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102457.0, "order_id": "x-XEKWYICXSSIUT605afc8788a300582", "exchange_order_id": "35575791", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:47:37,889 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afc8788a300582. +2023-09-19 05:47:38,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afcbc492cc0582 for 80.00000000 SEI-USDT. +2023-09-19 05:47:38,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102457.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605afcbc492cc0582", "creation_timestamp": 1695102456.0, "exchange_order_id": "35576072", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:47:38,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afcbc4973e0582 for 80.00000000 SEI-USDT. +2023-09-19 05:47:38,263 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102457.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605afcbc4973e0582", "creation_timestamp": 1695102456.0, "exchange_order_id": "35576071", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:48:31,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afcbc492cc0582. [clock=2023-09-19 05:48:31+00:00] +2023-09-19 05:48:31,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afcbc4973e0582. [clock=2023-09-19 05:48:31+00:00] +2023-09-19 05:48:31,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250540563567965642314508346 amount: 80. +2023-09-19 05:48:31,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255620623817773535589243014 amount: 80. +2023-09-19 05:48:31,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102511.0, "order_id": "x-XEKWYICXBSIUT605afcbc492cc0582", "exchange_order_id": "35576072", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:48:31,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afcbc492cc0582. +2023-09-19 05:48:32,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102512.0, "order_id": "x-XEKWYICXSSIUT605afcbc4973e0582", "exchange_order_id": "35576071", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:48:32,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afcbc4973e0582. +2023-09-19 05:48:32,536 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afcf086ede0582 for 80.00000000 SEI-USDT. +2023-09-19 05:48:32,566 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102512.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605afcf086ede0582", "creation_timestamp": 1695102511.0, "exchange_order_id": "35576175", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:48:32,567 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afcf086b520582 for 80.00000000 SEI-USDT. +2023-09-19 05:48:32,597 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102512.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605afcf086b520582", "creation_timestamp": 1695102511.0, "exchange_order_id": "35576174", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:49:26,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afcf086b520582. [clock=2023-09-19 05:49:26+00:00] +2023-09-19 05:49:26,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afcf086ede0582. [clock=2023-09-19 05:49:26+00:00] +2023-09-19 05:49:26,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250052550272349838950465371 amount: 80. +2023-09-19 05:49:26,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255980418470058932129570429 amount: 80. +2023-09-19 05:49:26,956 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102566.0, "order_id": "x-XEKWYICXBSIUT605afcf086b520582", "exchange_order_id": "35576174", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:49:26,957 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afcf086b520582. +2023-09-19 05:49:27,645 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102567.0, "order_id": "x-XEKWYICXSSIUT605afcf086ede0582", "exchange_order_id": "35576175", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:49:27,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afcf086ede0582. +2023-09-19 05:49:27,936 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afd24f6dfb0582 for 80.00000000 SEI-USDT. +2023-09-19 05:49:27,965 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102567.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605afd24f6dfb0582", "creation_timestamp": 1695102566.0, "exchange_order_id": "35576387", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:49:27,978 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afd24fa2310582 for 80.00000000 SEI-USDT. +2023-09-19 05:49:28,002 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102567.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605afd24fa2310582", "creation_timestamp": 1695102566.0, "exchange_order_id": "35576388", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:50:21,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afd24f6dfb0582. [clock=2023-09-19 05:50:21+00:00] +2023-09-19 05:50:21,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afd24fa2310582. [clock=2023-09-19 05:50:21+00:00] +2023-09-19 05:50:21,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-19 05:50:21,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258838950121027326163047831 amount: 80. +2023-09-19 05:50:21,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102621.0, "order_id": "x-XEKWYICXBSIUT605afd24f6dfb0582", "exchange_order_id": "35576387", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:50:21,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afd24f6dfb0582. +2023-09-19 05:50:21,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afd59595960582 for 80.00000000 SEI-USDT. +2023-09-19 05:50:21,402 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605afd59595960582", "creation_timestamp": 1695102621.0, "exchange_order_id": "35576597", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:50:21,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afd59599730582 for 80.00000000 SEI-USDT. +2023-09-19 05:50:21,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605afd59599730582", "creation_timestamp": 1695102621.0, "exchange_order_id": "35576598", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:50:21,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102621.0, "order_id": "x-XEKWYICXSSIUT605afd24fa2310582", "exchange_order_id": "35576388", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:50:21,427 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afd24fa2310582. +2023-09-19 05:51:16,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afd59595960582. [clock=2023-09-19 05:51:16+00:00] +2023-09-19 05:51:16,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afd59599730582. [clock=2023-09-19 05:51:16+00:00] +2023-09-19 05:51:16,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-19 05:51:16,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257539263076833687808957362 amount: 80. +2023-09-19 05:51:16,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102676.0, "order_id": "x-XEKWYICXBSIUT605afd59595960582", "exchange_order_id": "35576597", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:51:16,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afd59595960582. +2023-09-19 05:51:16,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102676.0, "order_id": "x-XEKWYICXSSIUT605afd59599730582", "exchange_order_id": "35576598", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:51:16,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afd59599730582. +2023-09-19 05:51:16,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afd8dd18260582 for 80.00000000 SEI-USDT. +2023-09-19 05:51:16,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605afd8dd18260582", "creation_timestamp": 1695102676.0, "exchange_order_id": "35576705", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:51:16,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afd8dd1a650582 for 80.00000000 SEI-USDT. +2023-09-19 05:51:16,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605afd8dd1a650582", "creation_timestamp": 1695102676.0, "exchange_order_id": "35576706", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:51:28,523 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605afd8dd18260582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:51:28,524 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 05:51:28+00:00] +2023-09-19 05:51:28,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102688.0, "order_id": "x-XEKWYICXBSIUT605afd8dd18260582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4993623", "exchange_order_id": "35576705", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:51:28,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102688.0, "order_id": "x-XEKWYICXBSIUT605afd8dd18260582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35576705", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:51:28,558 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605afd8dd18260582 completely filled. +2023-09-19 05:52:11,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afd8dd1a650582. [clock=2023-09-19 05:52:11+00:00] +2023-09-19 05:52:11,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250289963306896780699660663 amount: 80. +2023-09-19 05:52:11,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254461711975054217869221177 amount: 80. +2023-09-19 05:52:11,568 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102731.0, "order_id": "x-XEKWYICXSSIUT605afd8dd1a650582", "exchange_order_id": "35576706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:52:11,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afd8dd1a650582. +2023-09-19 05:52:11,570 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afdc25c7820582 for 80.00000000 SEI-USDT. +2023-09-19 05:52:11,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605afdc25c7820582", "creation_timestamp": 1695102731.0, "exchange_order_id": "35576978", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:52:12,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afdc25c9b40582 for 80.00000000 SEI-USDT. +2023-09-19 05:52:12,498 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102732.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605afdc25c9b40582", "creation_timestamp": 1695102731.0, "exchange_order_id": "35576981", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:53:06,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afdc25c7820582. [clock=2023-09-19 05:53:06+00:00] +2023-09-19 05:53:06,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afdc25c9b40582. [clock=2023-09-19 05:53:06+00:00] +2023-09-19 05:53:06,273 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248034203406892995068460001 amount: 80. +2023-09-19 05:53:06,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253254337757264482666259143 amount: 80. +2023-09-19 05:53:07,052 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102786.0, "order_id": "x-XEKWYICXBSIUT605afdc25c7820582", "exchange_order_id": "35576978", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:53:07,052 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afdc25c7820582. +2023-09-19 05:53:08,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102788.0, "order_id": "x-XEKWYICXSSIUT605afdc25c9b40582", "exchange_order_id": "35576981", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:53:08,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afdc25c9b40582. +2023-09-19 05:53:09,673 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afdf6f02cf0582 for 80.00000000 SEI-USDT. +2023-09-19 05:53:09,713 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102788.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605afdf6f02cf0582", "creation_timestamp": 1695102786.0, "exchange_order_id": "35577195", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:53:09,713 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afdf6f06310582 for 80.00000000 SEI-USDT. +2023-09-19 05:53:09,767 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102788.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605afdf6f06310582", "creation_timestamp": 1695102786.0, "exchange_order_id": "35577196", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:54:01,138 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afdf6f02cf0582. [clock=2023-09-19 05:54:01+00:00] +2023-09-19 05:54:01,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afdf6f06310582. [clock=2023-09-19 05:54:01+00:00] +2023-09-19 05:54:01,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248035238580361876834168828 amount: 80. +2023-09-19 05:54:01,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253255377260538243778810754 amount: 80. +2023-09-19 05:54:02,034 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102841.0, "order_id": "x-XEKWYICXBSIUT605afdf6f02cf0582", "exchange_order_id": "35577195", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:54:02,035 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afdf6f02cf0582. +2023-09-19 05:54:03,584 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102843.0, "order_id": "x-XEKWYICXSSIUT605afdf6f06310582", "exchange_order_id": "35577196", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:54:03,584 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afdf6f06310582. +2023-09-19 05:54:04,008 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afe2b552750582 for 80.00000000 SEI-USDT. +2023-09-19 05:54:04,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102843.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605afe2b552750582", "creation_timestamp": 1695102841.0, "exchange_order_id": "35577279", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:54:04,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afe2b54e180582 for 80.00000000 SEI-USDT. +2023-09-19 05:54:04,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102843.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605afe2b54e180582", "creation_timestamp": 1695102841.0, "exchange_order_id": "35577280", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:54:56,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afe2b54e180582. [clock=2023-09-19 05:54:56+00:00] +2023-09-19 05:54:56,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afe2b552750582. [clock=2023-09-19 05:54:56+00:00] +2023-09-19 05:54:56,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248693942195371934725013149 amount: 80. +2023-09-19 05:54:56,289 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252754305927268672345168919 amount: 80. +2023-09-19 05:54:57,038 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102896.0, "order_id": "x-XEKWYICXBSIUT605afe2b54e180582", "exchange_order_id": "35577280", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:54:57,038 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afe2b54e180582. +2023-09-19 05:54:58,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102898.0, "order_id": "x-XEKWYICXSSIUT605afe2b552750582", "exchange_order_id": "35577279", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:54:58,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afe2b552750582. +2023-09-19 05:54:58,953 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afe5fdb4bd0582 for 80.00000000 SEI-USDT. +2023-09-19 05:54:58,999 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102898.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605afe5fdb4bd0582", "creation_timestamp": 1695102896.0, "exchange_order_id": "35577448", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:54:58,999 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afe5fdb95b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:54:59,069 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102898.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605afe5fdb95b0582", "creation_timestamp": 1695102896.0, "exchange_order_id": "35577449", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:55:51,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afe5fdb4bd0582. [clock=2023-09-19 05:55:51+00:00] +2023-09-19 05:55:51,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afe5fdb95b0582. [clock=2023-09-19 05:55:51+00:00] +2023-09-19 05:55:51,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247276565275707407680954638 amount: 80. +2023-09-19 05:55:51,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251544419954867969006547409 amount: 80. +2023-09-19 05:55:51,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102951.0, "order_id": "x-XEKWYICXBSIUT605afe5fdb4bd0582", "exchange_order_id": "35577448", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:55:51,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afe5fdb4bd0582. +2023-09-19 05:55:51,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afe94175a10582 for 80.00000000 SEI-USDT. +2023-09-19 05:55:51,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605afe94175a10582", "creation_timestamp": 1695102951.0, "exchange_order_id": "35577667", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:55:51,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102951.0, "order_id": "x-XEKWYICXSSIUT605afe5fdb95b0582", "exchange_order_id": "35577449", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:55:51,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afe5fdb95b0582. +2023-09-19 05:55:51,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afe94177e50582 for 80.00000000 SEI-USDT. +2023-09-19 05:55:51,459 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695102951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605afe94177e50582", "creation_timestamp": 1695102951.0, "exchange_order_id": "35577668", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:56:46,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afe94175a10582. [clock=2023-09-19 05:56:46+00:00] +2023-09-19 05:56:46,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afe94177e50582. [clock=2023-09-19 05:56:46+00:00] +2023-09-19 05:56:46,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247564672645836145262644221 amount: 80. +2023-09-19 05:56:46,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251992465131155033340047145 amount: 80. +2023-09-19 05:56:46,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103006.0, "order_id": "x-XEKWYICXBSIUT605afe94175a10582", "exchange_order_id": "35577667", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:56:46,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afe94175a10582. +2023-09-19 05:56:46,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afec88369c0582 for 80.00000000 SEI-USDT. +2023-09-19 05:56:46,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605afec88369c0582", "creation_timestamp": 1695103006.0, "exchange_order_id": "35577772", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:56:46,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103006.0, "order_id": "x-XEKWYICXSSIUT605afe94177e50582", "exchange_order_id": "35577668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:56:46,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afe94177e50582. +2023-09-19 05:56:46,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afec8838eb0582 for 80.00000000 SEI-USDT. +2023-09-19 05:56:46,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605afec8838eb0582", "creation_timestamp": 1695103006.0, "exchange_order_id": "35577773", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:57:41,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afec88369c0582. [clock=2023-09-19 05:57:41+00:00] +2023-09-19 05:57:41,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afec8838eb0582. [clock=2023-09-19 05:57:41+00:00] +2023-09-19 05:57:41,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247176810110930846211399578 amount: 80. +2023-09-19 05:57:41,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251733138027150998959683132 amount: 80. +2023-09-19 05:57:41,976 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103061.0, "order_id": "x-XEKWYICXBSIUT605afec88369c0582", "exchange_order_id": "35577772", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:57:41,976 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afec88369c0582. +2023-09-19 05:57:43,502 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103063.0, "order_id": "x-XEKWYICXSSIUT605afec8838eb0582", "exchange_order_id": "35577773", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:57:43,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afec8838eb0582. +2023-09-19 05:57:44,025 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605afefd1fdee0582 for 80.00000000 SEI-USDT. +2023-09-19 05:57:44,081 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103063.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605afefd1fdee0582", "creation_timestamp": 1695103061.0, "exchange_order_id": "35577909", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:57:44,082 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605afefd201c70582 for 80.00000000 SEI-USDT. +2023-09-19 05:57:44,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103063.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605afefd201c70582", "creation_timestamp": 1695103061.0, "exchange_order_id": "35577910", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:58:36,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605afefd1fdee0582. [clock=2023-09-19 05:58:36+00:00] +2023-09-19 05:58:36,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605afefd201c70582. [clock=2023-09-19 05:58:36+00:00] +2023-09-19 05:58:36,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247498930972733985015158344 amount: 80. +2023-09-19 05:58:36,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252151024871487981496158664 amount: 80. +2023-09-19 05:58:37,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103116.0, "order_id": "x-XEKWYICXBSIUT605afefd1fdee0582", "exchange_order_id": "35577909", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:58:37,081 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605afefd1fdee0582. +2023-09-19 05:58:38,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103118.0, "order_id": "x-XEKWYICXSSIUT605afefd201c70582", "exchange_order_id": "35577910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:58:38,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605afefd201c70582. +2023-09-19 05:58:38,619 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aff319ff9b0582 for 80.00000000 SEI-USDT. +2023-09-19 05:58:38,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103118.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605aff319ff9b0582", "creation_timestamp": 1695103116.0, "exchange_order_id": "35577999", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:58:38,644 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aff31a33570582 for 80.00000000 SEI-USDT. +2023-09-19 05:58:38,669 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103118.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605aff31a33570582", "creation_timestamp": 1695103116.0, "exchange_order_id": "35577998", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:59:31,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aff319ff9b0582. [clock=2023-09-19 05:59:31+00:00] +2023-09-19 05:59:31,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aff31a33570582. [clock=2023-09-19 05:59:31+00:00] +2023-09-19 05:59:31,122 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243141556783573129817781544 amount: 80. +2023-09-19 05:59:31,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248482268654649514461666332 amount: 80. +2023-09-19 05:59:31,598 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103171.0, "order_id": "x-XEKWYICXBSIUT605aff319ff9b0582", "exchange_order_id": "35577999", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:59:31,598 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aff319ff9b0582. +2023-09-19 05:59:32,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103172.0, "order_id": "x-XEKWYICXSSIUT605aff31a33570582", "exchange_order_id": "35577998", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 05:59:32,327 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aff31a33570582. +2023-09-19 05:59:32,602 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aff65f77720582 for 80.00000000 SEI-USDT. +2023-09-19 05:59:32,637 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103172.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605aff65f77720582", "creation_timestamp": 1695103171.0, "exchange_order_id": "35578428", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:59:32,639 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aff65f7aa80582 for 80.00000000 SEI-USDT. +2023-09-19 05:59:32,667 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103172.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605aff65f7aa80582", "creation_timestamp": 1695103171.0, "exchange_order_id": "35578429", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 05:59:36,219 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605aff65f7aa80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 05:59:36,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 05:59:36+00:00] +2023-09-19 05:59:36,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103176.0, "order_id": "x-XEKWYICXSSIUT605aff65f7aa80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4993740", "exchange_order_id": "35578429", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 05:59:36,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103176.0, "order_id": "x-XEKWYICXSSIUT605aff65f7aa80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35578429", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 05:59:36,471 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605aff65f7aa80582 completely filled. +2023-09-19 06:00:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aff65f77720582. [clock=2023-09-19 06:00:26+00:00] +2023-09-19 06:00:26,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243520589882661415435985030 amount: 80. +2023-09-19 06:00:26,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249650125351350174744134565 amount: 80. +2023-09-19 06:00:26,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103226.0, "order_id": "x-XEKWYICXBSIUT605aff65f77720582", "exchange_order_id": "35578428", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:00:26,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aff65f77720582. +2023-09-19 06:00:26,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605aff9a567c80582 for 80.00000000 SEI-USDT. +2023-09-19 06:00:26,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605aff9a567c80582", "creation_timestamp": 1695103226.0, "exchange_order_id": "35578939", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:00:26,416 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605aff9a564530582 for 80.00000000 SEI-USDT. +2023-09-19 06:00:26,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605aff9a564530582", "creation_timestamp": 1695103226.0, "exchange_order_id": "35578938", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:01:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605aff9a564530582. [clock=2023-09-19 06:01:21+00:00] +2023-09-19 06:01:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605aff9a567c80582. [clock=2023-09-19 06:01:21+00:00] +2023-09-19 06:01:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245291493120099101733176705 amount: 80. +2023-09-19 06:01:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250063085557907635537160288 amount: 80. +2023-09-19 06:01:21,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103281.0, "order_id": "x-XEKWYICXBSIUT605aff9a564530582", "exchange_order_id": "35578938", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:01:21,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605aff9a564530582. +2023-09-19 06:01:21,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605affcec52a60582 for 80.00000000 SEI-USDT. +2023-09-19 06:01:21,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605affcec52a60582", "creation_timestamp": 1695103281.0, "exchange_order_id": "35579097", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:01:21,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605affcec4ea70582 for 80.00000000 SEI-USDT. +2023-09-19 06:01:21,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605affcec4ea70582", "creation_timestamp": 1695103281.0, "exchange_order_id": "35579098", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:01:21,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103281.0, "order_id": "x-XEKWYICXSSIUT605aff9a567c80582", "exchange_order_id": "35578939", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:01:21,489 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605aff9a567c80582. +2023-09-19 06:02:16,344 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605affcec4ea70582. [clock=2023-09-19 06:02:16+00:00] +2023-09-19 06:02:16,346 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605affcec52a60582. [clock=2023-09-19 06:02:16+00:00] +2023-09-19 06:02:16,410 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245763088671285086897334380 amount: 80. +2023-09-19 06:02:16,412 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250336002162310644819810714 amount: 80. +2023-09-19 06:02:17,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103336.0, "order_id": "x-XEKWYICXBSIUT605affcec4ea70582", "exchange_order_id": "35579098", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:02:17,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605affcec4ea70582. +2023-09-19 06:02:18,957 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103338.0, "order_id": "x-XEKWYICXSSIUT605affcec52a60582", "exchange_order_id": "35579097", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:02:18,957 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605affcec52a60582. +2023-09-19 06:02:19,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0003977950582 for 80.00000000 SEI-USDT. +2023-09-19 06:02:19,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103339.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0003977950582", "creation_timestamp": 1695103336.0, "exchange_order_id": "35579184", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:02:19,379 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0003973730582 for 80.00000000 SEI-USDT. +2023-09-19 06:02:19,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103339.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0003973730582", "creation_timestamp": 1695103336.0, "exchange_order_id": "35579183", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:03:11,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0003973730582. [clock=2023-09-19 06:03:11+00:00] +2023-09-19 06:03:11,318 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0003977950582. [clock=2023-09-19 06:03:11+00:00] +2023-09-19 06:03:11,409 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246779088471590544394311059 amount: 80. +2023-09-19 06:03:11,411 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252064456598697014762799075 amount: 80. +2023-09-19 06:03:12,073 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103391.0, "order_id": "x-XEKWYICXBSIUT605b0003973730582", "exchange_order_id": "35579183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:03:12,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0003973730582. +2023-09-19 06:03:13,936 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103393.0, "order_id": "x-XEKWYICXSSIUT605b0003977950582", "exchange_order_id": "35579184", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:03:13,937 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0003977950582. +2023-09-19 06:03:14,094 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b00380abc30582 for 80.00000000 SEI-USDT. +2023-09-19 06:03:14,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b00380abc30582", "creation_timestamp": 1695103391.0, "exchange_order_id": "35579383", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:03:14,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b00380e2a60582 for 80.00000000 SEI-USDT. +2023-09-19 06:03:14,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b00380e2a60582", "creation_timestamp": 1695103391.0, "exchange_order_id": "35579384", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:04:06,839 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b00380abc30582. [clock=2023-09-19 06:04:06+00:00] +2023-09-19 06:04:06,840 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b00380e2a60582. [clock=2023-09-19 06:04:06+00:00] +2023-09-19 06:04:06,927 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246430845348966750767131395 amount: 80. +2023-09-19 06:04:06,929 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251654880088445497270896683 amount: 80. +2023-09-19 06:04:07,562 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103447.0, "order_id": "x-XEKWYICXBSIUT605b00380abc30582", "exchange_order_id": "35579383", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:04:07,571 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b00380abc30582. +2023-09-19 06:04:07,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103447.0, "order_id": "x-XEKWYICXSSIUT605b00380e2a60582", "exchange_order_id": "35579384", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:04:07,644 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b00380e2a60582. +2023-09-19 06:04:08,932 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b006cfce3a0582 for 80.00000000 SEI-USDT. +2023-09-19 06:04:08,984 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103448.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b006cfce3a0582", "creation_timestamp": 1695103446.0, "exchange_order_id": "35579513", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:04:08,985 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b006cfd2d80582 for 80.00000000 SEI-USDT. +2023-09-19 06:04:09,036 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103448.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b006cfd2d80582", "creation_timestamp": 1695103446.0, "exchange_order_id": "35579512", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:05:01,138 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b006cfce3a0582. [clock=2023-09-19 06:05:01+00:00] +2023-09-19 06:05:01,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b006cfd2d80582. [clock=2023-09-19 06:05:01+00:00] +2023-09-19 06:05:01,218 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242623915461033224270602073 amount: 80. +2023-09-19 06:05:01,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250066082013433833395129699 amount: 80. +2023-09-19 06:05:01,871 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103501.0, "order_id": "x-XEKWYICXBSIUT605b006cfce3a0582", "exchange_order_id": "35579513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:05:01,871 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b006cfce3a0582. +2023-09-19 06:05:02,956 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103502.0, "order_id": "x-XEKWYICXSSIUT605b006cfd2d80582", "exchange_order_id": "35579512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:05:02,956 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b006cfd2d80582. +2023-09-19 06:05:03,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b00a0c5c0c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:05:03,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103503.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b00a0c5c0c0582", "creation_timestamp": 1695103501.0, "exchange_order_id": "35579831", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:05:03,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b00a0c383e0582 for 80.00000000 SEI-USDT. +2023-09-19 06:05:03,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103503.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b00a0c383e0582", "creation_timestamp": 1695103501.0, "exchange_order_id": "35579830", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:05:56,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b00a0c383e0582. [clock=2023-09-19 06:05:56+00:00] +2023-09-19 06:05:56,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b00a0c5c0c0582. [clock=2023-09-19 06:05:56+00:00] +2023-09-19 06:05:56,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244340378846029357977786299 amount: 80. +2023-09-19 06:05:56,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252349792882338006578654230 amount: 80. +2023-09-19 06:05:56,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103556.0, "order_id": "x-XEKWYICXBSIUT605b00a0c383e0582", "exchange_order_id": "35579830", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:05:56,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b00a0c383e0582. +2023-09-19 06:05:56,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103556.0, "order_id": "x-XEKWYICXSSIUT605b00a0c5c0c0582", "exchange_order_id": "35579831", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:05:56,385 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b00a0c5c0c0582. +2023-09-19 06:05:56,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b00d50a11d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:05:56,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b00d50a11d0582", "creation_timestamp": 1695103556.0, "exchange_order_id": "35579993", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:05:56,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b00d509df60582 for 80.00000000 SEI-USDT. +2023-09-19 06:05:56,496 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b00d509df60582", "creation_timestamp": 1695103556.0, "exchange_order_id": "35579994", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:06:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b00d509df60582. [clock=2023-09-19 06:06:51+00:00] +2023-09-19 06:06:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b00d50a11d0582. [clock=2023-09-19 06:06:51+00:00] +2023-09-19 06:06:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244285834422193184502181712 amount: 80. +2023-09-19 06:06:51,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251629508880425361961828806 amount: 80. +2023-09-19 06:06:51,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103611.0, "order_id": "x-XEKWYICXBSIUT605b00d509df60582", "exchange_order_id": "35579994", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:06:51,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b00d509df60582. +2023-09-19 06:06:51,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103611.0, "order_id": "x-XEKWYICXSSIUT605b00d50a11d0582", "exchange_order_id": "35579993", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:06:51,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b00d50a11d0582. +2023-09-19 06:06:51,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b01097a8010582 for 80.00000000 SEI-USDT. +2023-09-19 06:06:51,465 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b01097a8010582", "creation_timestamp": 1695103611.0, "exchange_order_id": "35580064", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:06:51,465 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b01097aad40582 for 80.00000000 SEI-USDT. +2023-09-19 06:06:51,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b01097aad40582", "creation_timestamp": 1695103611.0, "exchange_order_id": "35580065", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:07:46,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b01097a8010582. [clock=2023-09-19 06:07:46+00:00] +2023-09-19 06:07:46,150 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b01097aad40582. [clock=2023-09-19 06:07:46+00:00] +2023-09-19 06:07:46,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241347199973770596600238276 amount: 80. +2023-09-19 06:07:46,200 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249276982326515229645411100 amount: 80. +2023-09-19 06:07:46,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103666.0, "order_id": "x-XEKWYICXBSIUT605b01097a8010582", "exchange_order_id": "35580064", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:07:46,547 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b01097a8010582. +2023-09-19 06:07:47,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103667.0, "order_id": "x-XEKWYICXSSIUT605b01097aad40582", "exchange_order_id": "35580065", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:07:47,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b01097aad40582. +2023-09-19 06:07:47,431 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b013e1a3cf0582 for 80.00000000 SEI-USDT. +2023-09-19 06:07:47,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b013e1a3cf0582", "creation_timestamp": 1695103666.0, "exchange_order_id": "35580330", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:07:47,751 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b013e1a0200582 for 80.00000000 SEI-USDT. +2023-09-19 06:07:47,777 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b013e1a0200582", "creation_timestamp": 1695103666.0, "exchange_order_id": "35580331", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:08:41,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b013e1a0200582. [clock=2023-09-19 06:08:41+00:00] +2023-09-19 06:08:41,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b013e1a3cf0582. [clock=2023-09-19 06:08:41+00:00] +2023-09-19 06:08:41,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241698734641915350451635002 amount: 80. +2023-09-19 06:08:41,198 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248976991433378136675814286 amount: 80. +2023-09-19 06:08:41,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103721.0, "order_id": "x-XEKWYICXBSIUT605b013e1a0200582", "exchange_order_id": "35580331", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:08:41,629 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b013e1a0200582. +2023-09-19 06:08:42,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103722.0, "order_id": "x-XEKWYICXSSIUT605b013e1a3cf0582", "exchange_order_id": "35580330", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:08:42,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b013e1a3cf0582. +2023-09-19 06:08:42,857 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b01728d6040582 for 80.00000000 SEI-USDT. +2023-09-19 06:08:42,901 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103722.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b01728d6040582", "creation_timestamp": 1695103721.0, "exchange_order_id": "35580513", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:08:42,903 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b01728d21d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:08:42,962 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103722.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b01728d21d0582", "creation_timestamp": 1695103721.0, "exchange_order_id": "35580512", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:09:36,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b01728d21d0582. [clock=2023-09-19 06:09:36+00:00] +2023-09-19 06:09:36,153 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b01728d6040582. [clock=2023-09-19 06:09:36+00:00] +2023-09-19 06:09:36,209 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240049963882045723087808553 amount: 80. +2023-09-19 06:09:36,211 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247929663600977591135204683 amount: 80. +2023-09-19 06:09:36,953 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103776.0, "order_id": "x-XEKWYICXBSIUT605b01728d21d0582", "exchange_order_id": "35580512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:09:36,954 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b01728d21d0582. +2023-09-19 06:09:38,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103778.0, "order_id": "x-XEKWYICXSSIUT605b01728d6040582", "exchange_order_id": "35580513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:09:38,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b01728d6040582. +2023-09-19 06:09:39,056 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b01a7045b90582 for 80.00000000 SEI-USDT. +2023-09-19 06:09:39,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103778.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b01a7045b90582", "creation_timestamp": 1695103776.0, "exchange_order_id": "35580877", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:09:39,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b01a7041500582 for 80.00000000 SEI-USDT. +2023-09-19 06:09:39,133 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103778.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b01a7041500582", "creation_timestamp": 1695103776.0, "exchange_order_id": "35580876", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:10:31,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b01a7041500582. [clock=2023-09-19 06:10:31+00:00] +2023-09-19 06:10:31,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b01a7045b90582. [clock=2023-09-19 06:10:31+00:00] +2023-09-19 06:10:31,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240153829258366422496112683 amount: 80. +2023-09-19 06:10:31,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246278673553711359099327809 amount: 80. +2023-09-19 06:10:31,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103831.0, "order_id": "x-XEKWYICXBSIUT605b01a7041500582", "exchange_order_id": "35580876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:10:31,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b01a7041500582. +2023-09-19 06:10:31,646 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103831.0, "order_id": "x-XEKWYICXSSIUT605b01a7045b90582", "exchange_order_id": "35580877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:10:31,647 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b01a7045b90582. +2023-09-19 06:10:31,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b01db502f50582 for 80.00000000 SEI-USDT. +2023-09-19 06:10:31,662 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b01db502f50582", "creation_timestamp": 1695103831.0, "exchange_order_id": "35581236", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:10:31,715 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b01db506650582 for 80.00000000 SEI-USDT. +2023-09-19 06:10:31,728 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b01db506650582", "creation_timestamp": 1695103831.0, "exchange_order_id": "35581235", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:11:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b01db502f50582. [clock=2023-09-19 06:11:26+00:00] +2023-09-19 06:11:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b01db506650582. [clock=2023-09-19 06:11:26+00:00] +2023-09-19 06:11:26,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238356497519778455795592835 amount: 80. +2023-09-19 06:11:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245955325341298432175980503 amount: 80. +2023-09-19 06:11:26,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103886.0, "order_id": "x-XEKWYICXBSIUT605b01db502f50582", "exchange_order_id": "35581236", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:11:26,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b01db502f50582. +2023-09-19 06:11:26,383 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b020fbd1d10582 for 80.00000000 SEI-USDT. +2023-09-19 06:11:26,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b020fbd1d10582", "creation_timestamp": 1695103886.0, "exchange_order_id": "35581643", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:11:26,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103886.0, "order_id": "x-XEKWYICXSSIUT605b01db506650582", "exchange_order_id": "35581235", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:11:26,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b01db506650582. +2023-09-19 06:11:26,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b020fbd3f50582 for 80.00000000 SEI-USDT. +2023-09-19 06:11:26,421 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b020fbd3f50582", "creation_timestamp": 1695103886.0, "exchange_order_id": "35581644", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:12:21,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b020fbd1d10582. [clock=2023-09-19 06:12:21+00:00] +2023-09-19 06:12:21,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b020fbd3f50582. [clock=2023-09-19 06:12:21+00:00] +2023-09-19 06:12:21,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234654221583197223711172714 amount: 80. +2023-09-19 06:12:21,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12430000 amount: 80. +2023-09-19 06:12:22,002 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103941.0, "order_id": "x-XEKWYICXBSIUT605b020fbd1d10582", "exchange_order_id": "35581643", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:12:22,010 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b020fbd1d10582. +2023-09-19 06:12:23,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103943.0, "order_id": "x-XEKWYICXSSIUT605b020fbd3f50582", "exchange_order_id": "35581644", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:12:23,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b020fbd3f50582. +2023-09-19 06:12:23,512 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b02446fc7f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:12:23,568 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103943.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b02446fc7f0582", "creation_timestamp": 1695103941.0, "exchange_order_id": "35582195", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:12:23,579 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b02446fe500582 for 80.00000000 SEI-USDT. +2023-09-19 06:12:23,611 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103943.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b02446fe500582", "creation_timestamp": 1695103941.0, "exchange_order_id": "35582196", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:12:42,641 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b02446fe500582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 06:12:42,642 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 06:12:42+00:00] +2023-09-19 06:12:42,708 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103962.0, "order_id": "x-XEKWYICXSSIUT605b02446fe500582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "4994044", "exchange_order_id": "35582196", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 06:12:42,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103962.0, "order_id": "x-XEKWYICXSSIUT605b02446fe500582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35582196", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 06:12:42,981 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b02446fe500582 completely filled. +2023-09-19 06:12:50,668 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 06:13:16,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b02446fc7f0582. [clock=2023-09-19 06:13:16+00:00] +2023-09-19 06:13:16,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239301079625074192170615023 amount: 80. +2023-09-19 06:13:16,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246304162259027729071317601 amount: 80. +2023-09-19 06:13:16,579 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103996.0, "order_id": "x-XEKWYICXBSIUT605b02446fc7f0582", "exchange_order_id": "35582195", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:13:16,579 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b02446fc7f0582. +2023-09-19 06:13:17,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0278c28910582 for 80.00000000 SEI-USDT. +2023-09-19 06:13:17,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b0278c28910582", "creation_timestamp": 1695103996.0, "exchange_order_id": "35582441", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:13:17,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0278c2a750582 for 80.00000000 SEI-USDT. +2023-09-19 06:13:17,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695103997.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b0278c2a750582", "creation_timestamp": 1695103996.0, "exchange_order_id": "35582442", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:14:11,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0278c28910582. [clock=2023-09-19 06:14:11+00:00] +2023-09-19 06:14:11,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0278c2a750582. [clock=2023-09-19 06:14:11+00:00] +2023-09-19 06:14:11,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239703981743619228156109221 amount: 80. +2023-09-19 06:14:11,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247123499246778077797218893 amount: 80. +2023-09-19 06:14:11,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104051.0, "order_id": "x-XEKWYICXBSIUT605b0278c28910582", "exchange_order_id": "35582441", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:14:11,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0278c28910582. +2023-09-19 06:14:11,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104051.0, "order_id": "x-XEKWYICXSSIUT605b0278c2a750582", "exchange_order_id": "35582442", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:14:11,545 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0278c2a750582. +2023-09-19 06:14:12,720 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b02ad3d4940582 for 80.00000000 SEI-USDT. +2023-09-19 06:14:12,775 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104052.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b02ad3d4940582", "creation_timestamp": 1695104051.0, "exchange_order_id": "35582544", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:14:12,777 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b02ad3d2a60582 for 80.00000000 SEI-USDT. +2023-09-19 06:14:12,844 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104052.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b02ad3d2a60582", "creation_timestamp": 1695104051.0, "exchange_order_id": "35582543", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:15:06,310 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b02ad3d2a60582. [clock=2023-09-19 06:15:06+00:00] +2023-09-19 06:15:06,311 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b02ad3d4940582. [clock=2023-09-19 06:15:06+00:00] +2023-09-19 06:15:06,392 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243559193806711080504060618 amount: 80. +2023-09-19 06:15:06,402 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251068957276846354205811474 amount: 80. +2023-09-19 06:15:07,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104106.0, "order_id": "x-XEKWYICXBSIUT605b02ad3d2a60582", "exchange_order_id": "35582543", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:15:07,059 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b02ad3d2a60582. +2023-09-19 06:15:08,766 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104108.0, "order_id": "x-XEKWYICXSSIUT605b02ad3d4940582", "exchange_order_id": "35582544", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:15:08,766 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b02ad3d4940582. +2023-09-19 06:15:08,860 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b02e1e92fd0582 for 80.00000000 SEI-USDT. +2023-09-19 06:15:08,883 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104108.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b02e1e92fd0582", "creation_timestamp": 1695104106.0, "exchange_order_id": "35582976", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:15:08,885 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b02e1e986c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:15:08,906 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104108.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b02e1e986c0582", "creation_timestamp": 1695104106.0, "exchange_order_id": "35582975", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:16:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b02e1e92fd0582. [clock=2023-09-19 06:16:01+00:00] +2023-09-19 06:16:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b02e1e986c0582. [clock=2023-09-19 06:16:01+00:00] +2023-09-19 06:16:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243555214257266803033618547 amount: 80. +2023-09-19 06:16:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251064953695192598432693401 amount: 80. +2023-09-19 06:16:01,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104161.0, "order_id": "x-XEKWYICXBSIUT605b02e1e92fd0582", "exchange_order_id": "35582976", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:16:01,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b02e1e92fd0582. +2023-09-19 06:16:01,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104161.0, "order_id": "x-XEKWYICXSSIUT605b02e1e986c0582", "exchange_order_id": "35582975", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:16:01,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b02e1e986c0582. +2023-09-19 06:16:01,380 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0316001640582 for 80.00000000 SEI-USDT. +2023-09-19 06:16:01,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b0316001640582", "creation_timestamp": 1695104161.0, "exchange_order_id": "35583069", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:16:01,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0316004600582 for 80.00000000 SEI-USDT. +2023-09-19 06:16:01,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b0316004600582", "creation_timestamp": 1695104161.0, "exchange_order_id": "35583068", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:16:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0316001640582. [clock=2023-09-19 06:16:56+00:00] +2023-09-19 06:16:56,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0316004600582. [clock=2023-09-19 06:16:56+00:00] +2023-09-19 06:16:56,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245181225891400815766859476 amount: 80. +2023-09-19 06:16:56,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253246056878193769666035660 amount: 80. +2023-09-19 06:16:56,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104216.0, "order_id": "x-XEKWYICXBSIUT605b0316001640582", "exchange_order_id": "35583069", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:16:56,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0316001640582. +2023-09-19 06:16:56,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104216.0, "order_id": "x-XEKWYICXSSIUT605b0316004600582", "exchange_order_id": "35583068", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:16:56,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0316004600582. +2023-09-19 06:16:56,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b034a7a0a20582 for 80.00000000 SEI-USDT. +2023-09-19 06:16:56,496 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b034a7a0a20582", "creation_timestamp": 1695104216.0, "exchange_order_id": "35583293", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:16:56,587 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b034a79d520582 for 80.00000000 SEI-USDT. +2023-09-19 06:16:56,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b034a79d520582", "creation_timestamp": 1695104216.0, "exchange_order_id": "35583294", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:17:51,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b034a79d520582. [clock=2023-09-19 06:17:51+00:00] +2023-09-19 06:17:51,231 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b034a7a0a20582. [clock=2023-09-19 06:17:51+00:00] +2023-09-19 06:17:51,306 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242123288847831306121892909 amount: 80. +2023-09-19 06:17:51,307 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250614576838195305549277183 amount: 80. +2023-09-19 06:17:52,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104271.0, "order_id": "x-XEKWYICXBSIUT605b034a79d520582", "exchange_order_id": "35583294", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:17:52,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b034a79d520582. +2023-09-19 06:17:54,110 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104273.0, "order_id": "x-XEKWYICXSSIUT605b034a7a0a20582", "exchange_order_id": "35583293", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:17:54,110 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b034a7a0a20582. +2023-09-19 06:17:54,654 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b037f2d76e0582 for 80.00000000 SEI-USDT. +2023-09-19 06:17:54,701 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104274.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b037f2d76e0582", "creation_timestamp": 1695104271.0, "exchange_order_id": "35583644", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:17:54,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b037f2d3720582 for 80.00000000 SEI-USDT. +2023-09-19 06:17:54,742 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104274.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b037f2d3720582", "creation_timestamp": 1695104271.0, "exchange_order_id": "35583645", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:18:46,279 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b037f2d3720582. [clock=2023-09-19 06:18:46+00:00] +2023-09-19 06:18:46,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b037f2d76e0582. [clock=2023-09-19 06:18:46+00:00] +2023-09-19 06:18:46,356 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242021408835119462428925055 amount: 80. +2023-09-19 06:18:46,356 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249740357704838763801096638 amount: 80. +2023-09-19 06:18:47,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104326.0, "order_id": "x-XEKWYICXBSIUT605b037f2d3720582", "exchange_order_id": "35583645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:18:47,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b037f2d3720582. +2023-09-19 06:18:48,919 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104328.0, "order_id": "x-XEKWYICXSSIUT605b037f2d76e0582", "exchange_order_id": "35583644", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:18:48,919 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b037f2d76e0582. +2023-09-19 06:18:48,922 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b03b3ad0f60582 for 80.00000000 SEI-USDT. +2023-09-19 06:18:48,951 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104328.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b03b3ad0f60582", "creation_timestamp": 1695104326.0, "exchange_order_id": "35583893", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:18:49,022 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b03b3aced00582 for 80.00000000 SEI-USDT. +2023-09-19 06:18:49,053 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104328.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b03b3aced00582", "creation_timestamp": 1695104326.0, "exchange_order_id": "35583894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:19:41,123 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b03b3aced00582. [clock=2023-09-19 06:19:41+00:00] +2023-09-19 06:19:41,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b03b3ad0f60582. [clock=2023-09-19 06:19:41+00:00] +2023-09-19 06:19:41,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244652436971917212838279705 amount: 80. +2023-09-19 06:19:41,173 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252885704983524903469521219 amount: 80. +2023-09-19 06:19:41,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104381.0, "order_id": "x-XEKWYICXBSIUT605b03b3aced00582", "exchange_order_id": "35583894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:19:41,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b03b3aced00582. +2023-09-19 06:19:42,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104382.0, "order_id": "x-XEKWYICXSSIUT605b03b3ad0f60582", "exchange_order_id": "35583893", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:19:42,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b03b3ad0f60582. +2023-09-19 06:19:42,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b03e7f414e0582 for 80.00000000 SEI-USDT. +2023-09-19 06:19:42,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104382.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b03e7f414e0582", "creation_timestamp": 1695104381.0, "exchange_order_id": "35584237", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:19:42,515 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b03e7f3e350582 for 80.00000000 SEI-USDT. +2023-09-19 06:19:42,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104382.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b03e7f3e350582", "creation_timestamp": 1695104381.0, "exchange_order_id": "35584238", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:20:36,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b03e7f3e350582. [clock=2023-09-19 06:20:36+00:00] +2023-09-19 06:20:36,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b03e7f414e0582. [clock=2023-09-19 06:20:36+00:00] +2023-09-19 06:20:36,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246227360592831591519060551 amount: 80. +2023-09-19 06:20:36,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254606746448477732763425827 amount: 80. +2023-09-19 06:20:36,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104436.0, "order_id": "x-XEKWYICXBSIUT605b03e7f3e350582", "exchange_order_id": "35584238", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:20:36,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b03e7f3e350582. +2023-09-19 06:20:36,402 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104436.0, "order_id": "x-XEKWYICXSSIUT605b03e7f414e0582", "exchange_order_id": "35584237", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:20:36,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b03e7f414e0582. +2023-09-19 06:20:36,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b041c4545c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:20:36,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b041c4545c0582", "creation_timestamp": 1695104436.0, "exchange_order_id": "35584501", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:20:36,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b041c452050582 for 80.00000000 SEI-USDT. +2023-09-19 06:20:36,429 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b041c452050582", "creation_timestamp": 1695104436.0, "exchange_order_id": "35584502", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:21:31,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b041c452050582. [clock=2023-09-19 06:21:31+00:00] +2023-09-19 06:21:31,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b041c4545c0582. [clock=2023-09-19 06:21:31+00:00] +2023-09-19 06:21:31,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244961671850888658915559352 amount: 80. +2023-09-19 06:21:31,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252587427102480247129222034 amount: 80. +2023-09-19 06:21:31,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104491.0, "order_id": "x-XEKWYICXBSIUT605b041c452050582", "exchange_order_id": "35584502", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:21:31,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b041c452050582. +2023-09-19 06:21:31,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0450c44b20582 for 80.00000000 SEI-USDT. +2023-09-19 06:21:31,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0450c44b20582", "creation_timestamp": 1695104491.0, "exchange_order_id": "35584749", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:21:31,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0450c47ae0582 for 80.00000000 SEI-USDT. +2023-09-19 06:21:31,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b0450c47ae0582", "creation_timestamp": 1695104491.0, "exchange_order_id": "35584750", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:21:31,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104491.0, "order_id": "x-XEKWYICXSSIUT605b041c4545c0582", "exchange_order_id": "35584501", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:21:31,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b041c4545c0582. +2023-09-19 06:22:26,405 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0450c44b20582. [clock=2023-09-19 06:22:26+00:00] +2023-09-19 06:22:26,410 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0450c47ae0582. [clock=2023-09-19 06:22:26+00:00] +2023-09-19 06:22:26,454 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244748446904733755683242260 amount: 80. +2023-09-19 06:22:26,455 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251794198073157695396576814 amount: 80. +2023-09-19 06:22:26,869 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104546.0, "order_id": "x-XEKWYICXBSIUT605b0450c44b20582", "exchange_order_id": "35584749", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:22:26,869 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0450c44b20582. +2023-09-19 06:22:27,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104547.0, "order_id": "x-XEKWYICXSSIUT605b0450c47ae0582", "exchange_order_id": "35584750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:22:27,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0450c47ae0582. +2023-09-19 06:22:27,847 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b048593dd80582 for 80.00000000 SEI-USDT. +2023-09-19 06:22:27,885 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104547.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b048593dd80582", "creation_timestamp": 1695104546.0, "exchange_order_id": "35584968", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:22:27,887 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0485940250582 for 80.00000000 SEI-USDT. +2023-09-19 06:22:27,908 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104547.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b0485940250582", "creation_timestamp": 1695104546.0, "exchange_order_id": "35584967", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:23:21,501 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b048593dd80582. [clock=2023-09-19 06:23:21+00:00] +2023-09-19 06:23:21,515 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0485940250582. [clock=2023-09-19 06:23:21+00:00] +2023-09-19 06:23:21,582 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245249400552954632362607367 amount: 80. +2023-09-19 06:23:21,596 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250728188916054431732308521 amount: 80. +2023-09-19 06:23:22,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104602.0, "order_id": "x-XEKWYICXBSIUT605b048593dd80582", "exchange_order_id": "35584968", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:23:22,490 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b048593dd80582. +2023-09-19 06:23:23,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104603.0, "order_id": "x-XEKWYICXSSIUT605b0485940250582", "exchange_order_id": "35584967", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:23:23,763 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0485940250582. +2023-09-19 06:23:24,420 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b04ba2a8400582 for 80.00000000 SEI-USDT. +2023-09-19 06:23:24,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104603.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b04ba2a8400582", "creation_timestamp": 1695104601.0, "exchange_order_id": "35585080", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:23:24,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b04ba2a2d30582 for 80.00000000 SEI-USDT. +2023-09-19 06:23:24,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104603.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b04ba2a2d30582", "creation_timestamp": 1695104601.0, "exchange_order_id": "35585079", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:24:16,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b04ba2a2d30582. [clock=2023-09-19 06:24:16+00:00] +2023-09-19 06:24:16,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b04ba2a8400582. [clock=2023-09-19 06:24:16+00:00] +2023-09-19 06:24:16,367 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245638790759023437107753565 amount: 80. +2023-09-19 06:24:16,368 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249899320293574896198232515 amount: 80. +2023-09-19 06:24:16,917 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104656.0, "order_id": "x-XEKWYICXBSIUT605b04ba2a2d30582", "exchange_order_id": "35585079", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:24:16,930 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b04ba2a2d30582. +2023-09-19 06:24:16,967 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104656.0, "order_id": "x-XEKWYICXSSIUT605b04ba2a8400582", "exchange_order_id": "35585080", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:24:16,967 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b04ba2a8400582. +2023-09-19 06:24:19,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b04ee664b20582 for 80.00000000 SEI-USDT. +2023-09-19 06:24:19,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104659.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b04ee664b20582", "creation_timestamp": 1695104656.0, "exchange_order_id": "35585260", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:24:19,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b04ee667e50582 for 80.00000000 SEI-USDT. +2023-09-19 06:24:19,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104659.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b04ee667e50582", "creation_timestamp": 1695104656.0, "exchange_order_id": "35585261", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:25:11,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b04ee664b20582. [clock=2023-09-19 06:25:11+00:00] +2023-09-19 06:25:11,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b04ee667e50582. [clock=2023-09-19 06:25:11+00:00] +2023-09-19 06:25:11,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245888186390741088230540962 amount: 80. +2023-09-19 06:25:11,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250064006724017730697286448 amount: 80. +2023-09-19 06:25:11,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104711.0, "order_id": "x-XEKWYICXBSIUT605b04ee664b20582", "exchange_order_id": "35585260", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:25:11,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b04ee664b20582. +2023-09-19 06:25:11,637 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104711.0, "order_id": "x-XEKWYICXSSIUT605b04ee667e50582", "exchange_order_id": "35585261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:25:11,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b04ee667e50582. +2023-09-19 06:25:11,642 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0522b30f20582 for 80.00000000 SEI-USDT. +2023-09-19 06:25:11,655 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0522b30f20582", "creation_timestamp": 1695104711.0, "exchange_order_id": "35585457", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:25:11,656 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0522b2e710582 for 80.00000000 SEI-USDT. +2023-09-19 06:25:11,669 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0522b2e710582", "creation_timestamp": 1695104711.0, "exchange_order_id": "35585456", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:26:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0522b2e710582. [clock=2023-09-19 06:26:06+00:00] +2023-09-19 06:26:06,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0522b30f20582. [clock=2023-09-19 06:26:06+00:00] +2023-09-19 06:26:06,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245648604552675429887281759 amount: 80. +2023-09-19 06:26:06,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249763052098242421141565311 amount: 80. +2023-09-19 06:26:06,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104766.0, "order_id": "x-XEKWYICXBSIUT605b0522b2e710582", "exchange_order_id": "35585456", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:26:06,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0522b2e710582. +2023-09-19 06:26:06,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104766.0, "order_id": "x-XEKWYICXSSIUT605b0522b30f20582", "exchange_order_id": "35585457", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:26:06,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0522b30f20582. +2023-09-19 06:26:06,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b055701a7d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:26:06,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b055701a7d0582", "creation_timestamp": 1695104766.0, "exchange_order_id": "35585539", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:26:06,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0557016920582 for 80.00000000 SEI-USDT. +2023-09-19 06:26:06,472 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0557016920582", "creation_timestamp": 1695104766.0, "exchange_order_id": "35585538", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:27:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0557016920582. [clock=2023-09-19 06:27:01+00:00] +2023-09-19 06:27:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b055701a7d0582. [clock=2023-09-19 06:27:01+00:00] +2023-09-19 06:27:01,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245648604552675429887281759 amount: 80. +2023-09-19 06:27:01,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249763052098242421141565311 amount: 80. +2023-09-19 06:27:01,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104821.0, "order_id": "x-XEKWYICXBSIUT605b0557016920582", "exchange_order_id": "35585538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:27:01,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0557016920582. +2023-09-19 06:27:01,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b058b6c6ff0582 for 80.00000000 SEI-USDT. +2023-09-19 06:27:01,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b058b6c6ff0582", "creation_timestamp": 1695104821.0, "exchange_order_id": "35585687", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:27:01,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104821.0, "order_id": "x-XEKWYICXSSIUT605b055701a7d0582", "exchange_order_id": "35585539", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:27:01,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b055701a7d0582. +2023-09-19 06:27:01,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b058b6c9120582 for 80.00000000 SEI-USDT. +2023-09-19 06:27:01,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b058b6c9120582", "creation_timestamp": 1695104821.0, "exchange_order_id": "35585688", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:27:56,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b058b6c6ff0582. [clock=2023-09-19 06:27:56+00:00] +2023-09-19 06:27:56,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b058b6c9120582. [clock=2023-09-19 06:27:56+00:00] +2023-09-19 06:27:56,153 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245855605189065637163339729 amount: 80. +2023-09-19 06:27:56,154 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250165067546993013788489219 amount: 80. +2023-09-19 06:27:56,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104876.0, "order_id": "x-XEKWYICXBSIUT605b058b6c6ff0582", "exchange_order_id": "35585687", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:27:56,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b058b6c6ff0582. +2023-09-19 06:27:58,805 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104878.0, "order_id": "x-XEKWYICXSSIUT605b058b6c9120582", "exchange_order_id": "35585688", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:27:58,805 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b058b6c9120582. +2023-09-19 06:27:59,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b05c00108e0582 for 80.00000000 SEI-USDT. +2023-09-19 06:27:59,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104878.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b05c00108e0582", "creation_timestamp": 1695104876.0, "exchange_order_id": "35585844", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:27:59,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b05c000e9f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:27:59,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104878.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b05c000e9f0582", "creation_timestamp": 1695104876.0, "exchange_order_id": "35585843", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:28:51,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b05c000e9f0582. [clock=2023-09-19 06:28:51+00:00] +2023-09-19 06:28:51,205 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b05c00108e0582. [clock=2023-09-19 06:28:51+00:00] +2023-09-19 06:28:51,283 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247109475082794517812443379 amount: 80. +2023-09-19 06:28:51,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250463363478955353710123371 amount: 80. +2023-09-19 06:28:52,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104931.0, "order_id": "x-XEKWYICXBSIUT605b05c000e9f0582", "exchange_order_id": "35585843", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:28:52,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b05c000e9f0582. +2023-09-19 06:28:54,534 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104934.0, "order_id": "x-XEKWYICXSSIUT605b05c00108e0582", "exchange_order_id": "35585844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:28:54,534 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b05c00108e0582. +2023-09-19 06:28:55,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b05f496f7a0582 for 80.00000000 SEI-USDT. +2023-09-19 06:28:55,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104934.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b05f496f7a0582", "creation_timestamp": 1695104931.0, "exchange_order_id": "35586042", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:28:55,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b05f494a410582 for 80.00000000 SEI-USDT. +2023-09-19 06:28:55,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104934.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b05f494a410582", "creation_timestamp": 1695104931.0, "exchange_order_id": "35586043", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:29:46,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b05f494a410582. [clock=2023-09-19 06:29:46+00:00] +2023-09-19 06:29:46,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b05f496f7a0582. [clock=2023-09-19 06:29:46+00:00] +2023-09-19 06:29:46,360 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248240520635308613826442054 amount: 80. +2023-09-19 06:29:46,361 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251961846891111300932182610 amount: 80. +2023-09-19 06:29:46,981 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104986.0, "order_id": "x-XEKWYICXBSIUT605b05f494a410582", "exchange_order_id": "35586043", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:29:46,981 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b05f494a410582. +2023-09-19 06:29:48,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104988.0, "order_id": "x-XEKWYICXSSIUT605b05f496f7a0582", "exchange_order_id": "35586042", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:29:48,328 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b05f496f7a0582. +2023-09-19 06:29:48,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b06291b3170582 for 80.00000000 SEI-USDT. +2023-09-19 06:29:48,541 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104988.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b06291b3170582", "creation_timestamp": 1695104986.0, "exchange_order_id": "35586217", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:29:48,543 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b06291af260582 for 80.00000000 SEI-USDT. +2023-09-19 06:29:48,571 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695104988.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b06291af260582", "creation_timestamp": 1695104986.0, "exchange_order_id": "35586216", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:30:41,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b06291af260582. [clock=2023-09-19 06:30:41+00:00] +2023-09-19 06:30:41,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b06291b3170582. [clock=2023-09-19 06:30:41+00:00] +2023-09-19 06:30:41,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249389853528084599050594124 amount: 80. +2023-09-19 06:30:41,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253397242545306304771525462 amount: 80. +2023-09-19 06:30:41,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105041.0, "order_id": "x-XEKWYICXBSIUT605b06291af260582", "exchange_order_id": "35586216", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:30:41,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b06291af260582. +2023-09-19 06:30:41,540 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b065d561960582 for 80.00000000 SEI-USDT. +2023-09-19 06:30:41,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b065d561960582", "creation_timestamp": 1695105041.0, "exchange_order_id": "35586451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:30:41,570 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105041.0, "order_id": "x-XEKWYICXSSIUT605b06291b3170582", "exchange_order_id": "35586217", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:30:41,570 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b06291b3170582. +2023-09-19 06:30:41,571 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b065d5638a0582 for 80.00000000 SEI-USDT. +2023-09-19 06:30:41,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b065d5638a0582", "creation_timestamp": 1695105041.0, "exchange_order_id": "35586452", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:31:36,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b065d561960582. [clock=2023-09-19 06:31:36+00:00] +2023-09-19 06:31:36,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b065d5638a0582. [clock=2023-09-19 06:31:36+00:00] +2023-09-19 06:31:36,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 06:31:36,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254655940196930769294542553 amount: 80. +2023-09-19 06:31:36,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105096.0, "order_id": "x-XEKWYICXBSIUT605b065d561960582", "exchange_order_id": "35586451", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:31:36,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b065d561960582. +2023-09-19 06:31:36,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105096.0, "order_id": "x-XEKWYICXSSIUT605b065d5638a0582", "exchange_order_id": "35586452", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:31:36,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b065d5638a0582. +2023-09-19 06:31:36,505 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0691c0d300582 for 80.00000000 SEI-USDT. +2023-09-19 06:31:36,525 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b0691c0d300582", "creation_timestamp": 1695105096.0, "exchange_order_id": "35586676", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:31:36,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0691c09e60582 for 80.00000000 SEI-USDT. +2023-09-19 06:31:36,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0691c09e60582", "creation_timestamp": 1695105096.0, "exchange_order_id": "35586677", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:32:06,198 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b0691c09e60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 06:32:06,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 06:32:06+00:00] +2023-09-19 06:32:06,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105126.0, "order_id": "x-XEKWYICXBSIUT605b0691c09e60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4994387", "exchange_order_id": "35586677", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 06:32:06,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105126.0, "order_id": "x-XEKWYICXBSIUT605b0691c09e60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35586677", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 06:32:06,235 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b0691c09e60582 completely filled. +2023-09-19 06:32:31,251 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0691c0d300582. [clock=2023-09-19 06:32:31+00:00] +2023-09-19 06:32:31,331 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248402138875043827922650187 amount: 80. +2023-09-19 06:32:31,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253474930465569036496048805 amount: 80. +2023-09-19 06:32:33,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105152.0, "order_id": "x-XEKWYICXSSIUT605b0691c0d300582", "exchange_order_id": "35586676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:32:33,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0691c0d300582. +2023-09-19 06:32:33,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b06c66f5c20582 for 80.00000000 SEI-USDT. +2023-09-19 06:32:33,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105153.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b06c66f5c20582", "creation_timestamp": 1695105151.0, "exchange_order_id": "35586821", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:32:33,552 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b06c66f1ac0582 for 80.00000000 SEI-USDT. +2023-09-19 06:32:33,577 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105153.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b06c66f1ac0582", "creation_timestamp": 1695105151.0, "exchange_order_id": "35586820", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:33:26,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b06c66f1ac0582. [clock=2023-09-19 06:33:26+00:00] +2023-09-19 06:33:26,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b06c66f5c20582. [clock=2023-09-19 06:33:26+00:00] +2023-09-19 06:33:26,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-19 06:33:26,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256133253016493528324256176 amount: 80. +2023-09-19 06:33:27,007 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105206.0, "order_id": "x-XEKWYICXBSIUT605b06c66f1ac0582", "exchange_order_id": "35586820", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:33:27,007 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b06c66f1ac0582. +2023-09-19 06:33:28,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105207.0, "order_id": "x-XEKWYICXSSIUT605b06c66f5c20582", "exchange_order_id": "35586821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:33:28,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b06c66f5c20582. +2023-09-19 06:33:28,865 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b06fad6b4e0582 for 80.00000000 SEI-USDT. +2023-09-19 06:33:28,895 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105208.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b06fad6b4e0582", "creation_timestamp": 1695105206.0, "exchange_order_id": "35587034", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:33:28,896 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b06fad674c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:33:28,933 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105208.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b06fad674c0582", "creation_timestamp": 1695105206.0, "exchange_order_id": "35587033", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:34:21,203 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b06fad674c0582. [clock=2023-09-19 06:34:21+00:00] +2023-09-19 06:34:21,204 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b06fad6b4e0582. [clock=2023-09-19 06:34:21+00:00] +2023-09-19 06:34:21,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-19 06:34:21,253 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254990782416894268813759184 amount: 80. +2023-09-19 06:34:21,711 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105261.0, "order_id": "x-XEKWYICXBSIUT605b06fad674c0582", "exchange_order_id": "35587033", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:34:21,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b06fad674c0582. +2023-09-19 06:34:22,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105262.0, "order_id": "x-XEKWYICXSSIUT605b06fad6b4e0582", "exchange_order_id": "35587034", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:34:22,336 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b06fad6b4e0582. +2023-09-19 06:34:22,617 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b072f433fe0582 for 80.00000000 SEI-USDT. +2023-09-19 06:34:22,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105262.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b072f433fe0582", "creation_timestamp": 1695105261.0, "exchange_order_id": "35587115", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:34:22,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b072f437890582 for 80.00000000 SEI-USDT. +2023-09-19 06:34:22,672 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105262.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b072f437890582", "creation_timestamp": 1695105261.0, "exchange_order_id": "35587114", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:35:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b072f433fe0582. [clock=2023-09-19 06:35:16+00:00] +2023-09-19 06:35:16,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b072f437890582. [clock=2023-09-19 06:35:16+00:00] +2023-09-19 06:35:16,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-19 06:35:16,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259133009260854176278672694 amount: 80. +2023-09-19 06:35:16,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105316.0, "order_id": "x-XEKWYICXBSIUT605b072f433fe0582", "exchange_order_id": "35587115", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:35:16,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b072f433fe0582. +2023-09-19 06:35:16,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0763824da0582 for 80.00000000 SEI-USDT. +2023-09-19 06:35:16,426 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b0763824da0582", "creation_timestamp": 1695105316.0, "exchange_order_id": "35587474", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:35:16,440 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105316.0, "order_id": "x-XEKWYICXSSIUT605b072f437890582", "exchange_order_id": "35587114", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:35:16,440 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b072f437890582. +2023-09-19 06:35:16,441 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0763827ed0582 for 80.00000000 SEI-USDT. +2023-09-19 06:35:16,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b0763827ed0582", "creation_timestamp": 1695105316.0, "exchange_order_id": "35587475", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:35:19,645 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b0763824da0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 06:35:19,646 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 06:35:19+00:00] +2023-09-19 06:35:19,669 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105319.0, "order_id": "x-XEKWYICXBSIUT605b0763824da0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4994486", "exchange_order_id": "35587474", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 06:35:19,681 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105319.0, "order_id": "x-XEKWYICXBSIUT605b0763824da0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35587474", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 06:35:19,681 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b0763824da0582 completely filled. +2023-09-19 06:36:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0763827ed0582. [clock=2023-09-19 06:36:11+00:00] +2023-09-19 06:36:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251794588043549587206049892 amount: 80. +2023-09-19 06:36:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257135134972339823882224518 amount: 80. +2023-09-19 06:36:11,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105371.0, "order_id": "x-XEKWYICXSSIUT605b0763827ed0582", "exchange_order_id": "35587475", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:36:11,204 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0763827ed0582. +2023-09-19 06:36:11,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0797f23e50582 for 80.00000000 SEI-USDT. +2023-09-19 06:36:11,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b0797f23e50582", "creation_timestamp": 1695105371.0, "exchange_order_id": "35587906", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:36:11,482 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0797f27900582 for 80.00000000 SEI-USDT. +2023-09-19 06:36:11,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b0797f27900582", "creation_timestamp": 1695105371.0, "exchange_order_id": "35587908", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:37:06,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0797f23e50582. [clock=2023-09-19 06:37:06+00:00] +2023-09-19 06:37:06,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0797f27900582. [clock=2023-09-19 06:37:06+00:00] +2023-09-19 06:37:06,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251842038192608151232745952 amount: 80. +2023-09-19 06:37:06,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255993986198600693618264572 amount: 80. +2023-09-19 06:37:06,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105426.0, "order_id": "x-XEKWYICXBSIUT605b0797f23e50582", "exchange_order_id": "35587906", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:37:06,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0797f23e50582. +2023-09-19 06:37:06,569 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105426.0, "order_id": "x-XEKWYICXSSIUT605b0797f27900582", "exchange_order_id": "35587908", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:37:06,570 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0797f27900582. +2023-09-19 06:37:06,573 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b07cc800040582 for 80.00000000 SEI-USDT. +2023-09-19 06:37:06,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b07cc800040582", "creation_timestamp": 1695105426.0, "exchange_order_id": "35588067", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:37:06,586 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b07cc7fc420582 for 80.00000000 SEI-USDT. +2023-09-19 06:37:06,597 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b07cc7fc420582", "creation_timestamp": 1695105426.0, "exchange_order_id": "35588068", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:38:01,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b07cc7fc420582. [clock=2023-09-19 06:38:01+00:00] +2023-09-19 06:38:01,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b07cc800040582. [clock=2023-09-19 06:38:01+00:00] +2023-09-19 06:38:01,162 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252798409021920771649195727 amount: 80. +2023-09-19 06:38:01,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258068644235505528015214043 amount: 80. +2023-09-19 06:38:01,566 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105481.0, "order_id": "x-XEKWYICXBSIUT605b07cc7fc420582", "exchange_order_id": "35588068", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:38:01,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b07cc7fc420582. +2023-09-19 06:38:02,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105482.0, "order_id": "x-XEKWYICXSSIUT605b07cc800040582", "exchange_order_id": "35588067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:38:02,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b07cc800040582. +2023-09-19 06:38:02,407 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0800fe6870582 for 80.00000000 SEI-USDT. +2023-09-19 06:38:02,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105482.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b0800fe6870582", "creation_timestamp": 1695105481.0, "exchange_order_id": "35588443", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:38:02,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0800fe3830582 for 80.00000000 SEI-USDT. +2023-09-19 06:38:02,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105482.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b0800fe3830582", "creation_timestamp": 1695105481.0, "exchange_order_id": "35588444", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:38:56,303 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0800fe3830582. [clock=2023-09-19 06:38:56+00:00] +2023-09-19 06:38:56,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0800fe6870582. [clock=2023-09-19 06:38:56+00:00] +2023-09-19 06:38:56,380 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251843473873353619346954656 amount: 80. +2023-09-19 06:38:56,381 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255937507593937971020484803 amount: 80. +2023-09-19 06:38:57,347 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105537.0, "order_id": "x-XEKWYICXBSIUT605b0800fe3830582", "exchange_order_id": "35588444", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:38:57,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0800fe3830582. +2023-09-19 06:38:58,890 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105538.0, "order_id": "x-XEKWYICXSSIUT605b0800fe6870582", "exchange_order_id": "35588443", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:38:58,890 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0800fe6870582. +2023-09-19 06:38:59,102 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0835a55b00582 for 80.00000000 SEI-USDT. +2023-09-19 06:38:59,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105538.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b0835a55b00582", "creation_timestamp": 1695105536.0, "exchange_order_id": "35588723", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:38:59,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0835a53340582 for 80.00000000 SEI-USDT. +2023-09-19 06:38:59,574 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105539.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b0835a53340582", "creation_timestamp": 1695105536.0, "exchange_order_id": "35588722", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:39:51,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0835a53340582. [clock=2023-09-19 06:39:51+00:00] +2023-09-19 06:39:51,205 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0835a55b00582. [clock=2023-09-19 06:39:51+00:00] +2023-09-19 06:39:51,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249754392630189068765857463 amount: 80. +2023-09-19 06:39:51,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255160576619700360536231966 amount: 80. +2023-09-19 06:39:51,977 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105591.0, "order_id": "x-XEKWYICXBSIUT605b0835a53340582", "exchange_order_id": "35588722", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:39:51,977 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0835a53340582. +2023-09-19 06:39:53,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105593.0, "order_id": "x-XEKWYICXSSIUT605b0835a55b00582", "exchange_order_id": "35588723", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:39:53,303 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0835a55b00582. +2023-09-19 06:39:53,860 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0869fe2250582 for 80.00000000 SEI-USDT. +2023-09-19 06:39:53,933 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105593.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b0869fe2250582", "creation_timestamp": 1695105591.0, "exchange_order_id": "35588920", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:39:53,934 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0869fdfaa0582 for 80.00000000 SEI-USDT. +2023-09-19 06:39:54,006 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105593.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b0869fdfaa0582", "creation_timestamp": 1695105591.0, "exchange_order_id": "35588919", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:40:46,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0869fdfaa0582. [clock=2023-09-19 06:40:46+00:00] +2023-09-19 06:40:46,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0869fe2250582. [clock=2023-09-19 06:40:46+00:00] +2023-09-19 06:40:46,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251198665448900586737525931 amount: 80. +2023-09-19 06:40:46,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256516688129785695725067729 amount: 80. +2023-09-19 06:40:46,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105646.0, "order_id": "x-XEKWYICXBSIUT605b0869fdfaa0582", "exchange_order_id": "35588919", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:40:46,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0869fdfaa0582. +2023-09-19 06:40:46,656 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b089e466d70582 for 80.00000000 SEI-USDT. +2023-09-19 06:40:46,675 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b089e466d70582", "creation_timestamp": 1695105646.0, "exchange_order_id": "35589125", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:40:46,677 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b089e46a150582 for 80.00000000 SEI-USDT. +2023-09-19 06:40:46,694 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b089e46a150582", "creation_timestamp": 1695105646.0, "exchange_order_id": "35589126", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:40:46,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105646.0, "order_id": "x-XEKWYICXSSIUT605b0869fe2250582", "exchange_order_id": "35588920", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:40:46,762 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0869fe2250582. +2023-09-19 06:41:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b089e466d70582. [clock=2023-09-19 06:41:41+00:00] +2023-09-19 06:41:41,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b089e46a150582. [clock=2023-09-19 06:41:41+00:00] +2023-09-19 06:41:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249929524239682013121662109 amount: 80. +2023-09-19 06:41:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256038164667967336511955073 amount: 80. +2023-09-19 06:41:41,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105701.0, "order_id": "x-XEKWYICXBSIUT605b089e466d70582", "exchange_order_id": "35589125", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:41:41,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b089e466d70582. +2023-09-19 06:41:41,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b08d2a8eeb0582 for 80.00000000 SEI-USDT. +2023-09-19 06:41:41,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b08d2a8eeb0582", "creation_timestamp": 1695105701.0, "exchange_order_id": "35589398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:41:41,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105701.0, "order_id": "x-XEKWYICXSSIUT605b089e46a150582", "exchange_order_id": "35589126", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:41:41,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b089e46a150582. +2023-09-19 06:41:41,549 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b08d2a92720582 for 80.00000000 SEI-USDT. +2023-09-19 06:41:41,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b08d2a92720582", "creation_timestamp": 1695105701.0, "exchange_order_id": "35589399", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:42:36,373 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b08d2a8eeb0582. [clock=2023-09-19 06:42:36+00:00] +2023-09-19 06:42:36,374 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b08d2a92720582. [clock=2023-09-19 06:42:36+00:00] +2023-09-19 06:42:36,409 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249693453152972390977719740 amount: 80. +2023-09-19 06:42:36,422 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255554029923277083366734446 amount: 80. +2023-09-19 06:42:36,851 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105756.0, "order_id": "x-XEKWYICXBSIUT605b08d2a8eeb0582", "exchange_order_id": "35589398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:42:36,851 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b08d2a8eeb0582. +2023-09-19 06:42:37,592 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105757.0, "order_id": "x-XEKWYICXSSIUT605b08d2a92720582", "exchange_order_id": "35589399", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:42:37,593 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b08d2a92720582. +2023-09-19 06:42:37,888 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b09077e1370582 for 80.00000000 SEI-USDT. +2023-09-19 06:42:37,909 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105757.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b09077e1370582", "creation_timestamp": 1695105756.0, "exchange_order_id": "35589536", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:42:37,911 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b09077e3e20582 for 80.00000000 SEI-USDT. +2023-09-19 06:42:37,937 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105757.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b09077e3e20582", "creation_timestamp": 1695105756.0, "exchange_order_id": "35589537", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:42:51,085 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 06:43:31,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b09077e1370582. [clock=2023-09-19 06:43:31+00:00] +2023-09-19 06:43:31,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b09077e3e20582. [clock=2023-09-19 06:43:31+00:00] +2023-09-19 06:43:31,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250864405829866952927488143 amount: 80. +2023-09-19 06:43:31,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256290698940389418387127544 amount: 80. +2023-09-19 06:43:31,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105811.0, "order_id": "x-XEKWYICXBSIUT605b09077e1370582", "exchange_order_id": "35589536", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:43:31,576 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b09077e1370582. +2023-09-19 06:43:32,083 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105811.0, "order_id": "x-XEKWYICXSSIUT605b09077e3e20582", "exchange_order_id": "35589537", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:43:32,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b09077e3e20582. +2023-09-19 06:43:32,376 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b093b98b550582 for 80.00000000 SEI-USDT. +2023-09-19 06:43:32,409 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105812.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b093b98b550582", "creation_timestamp": 1695105811.0, "exchange_order_id": "35589874", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:43:32,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b093b98d450582 for 80.00000000 SEI-USDT. +2023-09-19 06:43:32,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105812.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b093b98d450582", "creation_timestamp": 1695105811.0, "exchange_order_id": "35589875", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:44:26,214 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b093b98b550582. [clock=2023-09-19 06:44:26+00:00] +2023-09-19 06:44:26,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b093b98d450582. [clock=2023-09-19 06:44:26+00:00] +2023-09-19 06:44:26,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247815205775014603478125964 amount: 80. +2023-09-19 06:44:26,291 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254003624883547072432798646 amount: 80. +2023-09-19 06:44:27,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105866.0, "order_id": "x-XEKWYICXBSIUT605b093b98b550582", "exchange_order_id": "35589874", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:44:27,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b093b98b550582. +2023-09-19 06:44:28,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105868.0, "order_id": "x-XEKWYICXSSIUT605b093b98d450582", "exchange_order_id": "35589875", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:44:28,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b093b98d450582. +2023-09-19 06:44:29,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b097045a520582 for 80.00000000 SEI-USDT. +2023-09-19 06:44:29,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105868.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b097045a520582", "creation_timestamp": 1695105866.0, "exchange_order_id": "35590225", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:44:29,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b097045d8f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:44:29,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105868.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b097045d8f0582", "creation_timestamp": 1695105866.0, "exchange_order_id": "35590226", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:45:21,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b097045a520582. [clock=2023-09-19 06:45:21+00:00] +2023-09-19 06:45:21,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b097045d8f0582. [clock=2023-09-19 06:45:21+00:00] +2023-09-19 06:45:21,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246407918271033049832537693 amount: 80. +2023-09-19 06:45:21,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252328707958477293516105867 amount: 80. +2023-09-19 06:45:21,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105921.0, "order_id": "x-XEKWYICXBSIUT605b097045a520582", "exchange_order_id": "35590225", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:45:21,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b097045a520582. +2023-09-19 06:45:21,467 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105921.0, "order_id": "x-XEKWYICXSSIUT605b097045d8f0582", "exchange_order_id": "35590226", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:45:21,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b097045d8f0582. +2023-09-19 06:45:21,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b09a48674d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:45:21,480 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b09a48674d0582", "creation_timestamp": 1695105921.0, "exchange_order_id": "35590629", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:45:21,481 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b09a486ab40582 for 80.00000000 SEI-USDT. +2023-09-19 06:45:21,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b09a486ab40582", "creation_timestamp": 1695105921.0, "exchange_order_id": "35590630", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:46:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b09a48674d0582. [clock=2023-09-19 06:46:16+00:00] +2023-09-19 06:46:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b09a486ab40582. [clock=2023-09-19 06:46:16+00:00] +2023-09-19 06:46:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246327713155653521715385982 amount: 80. +2023-09-19 06:46:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251798880620368526641932068 amount: 80. +2023-09-19 06:46:16,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105976.0, "order_id": "x-XEKWYICXBSIUT605b09a48674d0582", "exchange_order_id": "35590629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:46:16,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b09a48674d0582. +2023-09-19 06:46:16,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105976.0, "order_id": "x-XEKWYICXSSIUT605b09a486ab40582", "exchange_order_id": "35590630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:46:16,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b09a486ab40582. +2023-09-19 06:46:16,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b09d8ebc190582 for 80.00000000 SEI-USDT. +2023-09-19 06:46:16,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b09d8ebc190582", "creation_timestamp": 1695105976.0, "exchange_order_id": "35590715", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:46:16,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b09d8eb88c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:46:16,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695105976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b09d8eb88c0582", "creation_timestamp": 1695105976.0, "exchange_order_id": "35590716", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:47:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b09d8eb88c0582. [clock=2023-09-19 06:47:11+00:00] +2023-09-19 06:47:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b09d8ebc190582. [clock=2023-09-19 06:47:11+00:00] +2023-09-19 06:47:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247041348537546573576977906 amount: 80. +2023-09-19 06:47:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252158356245575272712702364 amount: 80. +2023-09-19 06:47:11,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106031.0, "order_id": "x-XEKWYICXBSIUT605b09d8eb88c0582", "exchange_order_id": "35590716", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:47:11,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b09d8eb88c0582. +2023-09-19 06:47:11,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0a0d5f8b50582 for 80.00000000 SEI-USDT. +2023-09-19 06:47:11,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b0a0d5f8b50582", "creation_timestamp": 1695106031.0, "exchange_order_id": "35590805", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:47:11,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0a0d5f5220582 for 80.00000000 SEI-USDT. +2023-09-19 06:47:11,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b0a0d5f5220582", "creation_timestamp": 1695106031.0, "exchange_order_id": "35590806", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:47:11,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106031.0, "order_id": "x-XEKWYICXSSIUT605b09d8ebc190582", "exchange_order_id": "35590715", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:47:11,301 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b09d8ebc190582. +2023-09-19 06:48:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0a0d5f5220582. [clock=2023-09-19 06:48:06+00:00] +2023-09-19 06:48:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0a0d5f8b50582. [clock=2023-09-19 06:48:06+00:00] +2023-09-19 06:48:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246879109127406472712507673 amount: 80. +2023-09-19 06:48:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251724953612423298957571837 amount: 80. +2023-09-19 06:48:06,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106086.0, "order_id": "x-XEKWYICXBSIUT605b0a0d5f5220582", "exchange_order_id": "35590806", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:48:06,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0a0d5f5220582. +2023-09-19 06:48:06,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106086.0, "order_id": "x-XEKWYICXSSIUT605b0a0d5f8b50582", "exchange_order_id": "35590805", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:48:06,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0a0d5f8b50582. +2023-09-19 06:48:06,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0a41d2e890582 for 80.00000000 SEI-USDT. +2023-09-19 06:48:06,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0a41d2e890582", "creation_timestamp": 1695106086.0, "exchange_order_id": "35590878", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:48:06,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0a41d33170582 for 80.00000000 SEI-USDT. +2023-09-19 06:48:06,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b0a41d33170582", "creation_timestamp": 1695106086.0, "exchange_order_id": "35590879", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:49:01,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0a41d2e890582. [clock=2023-09-19 06:49:01+00:00] +2023-09-19 06:49:01,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0a41d33170582. [clock=2023-09-19 06:49:01+00:00] +2023-09-19 06:49:01,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246879109127406472712507673 amount: 80. +2023-09-19 06:49:01,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251724953612423298957571837 amount: 80. +2023-09-19 06:49:01,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106141.0, "order_id": "x-XEKWYICXBSIUT605b0a41d2e890582", "exchange_order_id": "35590878", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:49:01,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0a41d2e890582. +2023-09-19 06:49:01,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0a7652a940582 for 80.00000000 SEI-USDT. +2023-09-19 06:49:01,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b0a7652a940582", "creation_timestamp": 1695106141.0, "exchange_order_id": "35590973", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:49:01,625 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0a76527b40582 for 80.00000000 SEI-USDT. +2023-09-19 06:49:01,653 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0a76527b40582", "creation_timestamp": 1695106141.0, "exchange_order_id": "35590974", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:49:01,683 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106141.0, "order_id": "x-XEKWYICXSSIUT605b0a41d33170582", "exchange_order_id": "35590879", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:49:01,683 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0a41d33170582. +2023-09-19 06:49:26,786 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b0a7652a940582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 06:49:26,800 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 06:49:26+00:00] +2023-09-19 06:49:26,874 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106166.0, "order_id": "x-XEKWYICXSSIUT605b0a7652a940582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "4994854", "exchange_order_id": "35590973", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 06:49:27,107 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106166.0, "order_id": "x-XEKWYICXSSIUT605b0a7652a940582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35590973", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 06:49:27,108 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b0a7652a940582 completely filled. +2023-09-19 06:49:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0a76527b40582. [clock=2023-09-19 06:49:56+00:00] +2023-09-19 06:49:56,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247830296458684967406789558 amount: 80. +2023-09-19 06:49:56,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252709654384450511283752212 amount: 80. +2023-09-19 06:49:56,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106196.0, "order_id": "x-XEKWYICXBSIUT605b0a76527b40582", "exchange_order_id": "35590974", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:49:56,522 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0a76527b40582. +2023-09-19 06:49:56,966 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0aaac5fd60582 for 80.00000000 SEI-USDT. +2023-09-19 06:49:57,016 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b0aaac5fd60582", "creation_timestamp": 1695106196.0, "exchange_order_id": "35591118", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:49:57,018 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0aaac85de0582 for 80.00000000 SEI-USDT. +2023-09-19 06:49:57,066 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b0aaac85de0582", "creation_timestamp": 1695106196.0, "exchange_order_id": "35591119", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:50:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0aaac5fd60582. [clock=2023-09-19 06:50:51+00:00] +2023-09-19 06:50:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0aaac85de0582. [clock=2023-09-19 06:50:51+00:00] +2023-09-19 06:50:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246453135909637924901478897 amount: 80. +2023-09-19 06:50:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251357220049267579754371114 amount: 80. +2023-09-19 06:50:51,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106251.0, "order_id": "x-XEKWYICXBSIUT605b0aaac5fd60582", "exchange_order_id": "35591118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:50:51,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0aaac5fd60582. +2023-09-19 06:50:51,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0adf2e7da0582 for 80.00000000 SEI-USDT. +2023-09-19 06:50:51,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0adf2e7da0582", "creation_timestamp": 1695106251.0, "exchange_order_id": "35591451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:50:51,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106251.0, "order_id": "x-XEKWYICXSSIUT605b0aaac85de0582", "exchange_order_id": "35591119", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:50:51,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0aaac85de0582. +2023-09-19 06:50:51,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0adf2eb0d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:50:51,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b0adf2eb0d0582", "creation_timestamp": 1695106251.0, "exchange_order_id": "35591452", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:51:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0adf2e7da0582. [clock=2023-09-19 06:51:46+00:00] +2023-09-19 06:51:46,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0adf2eb0d0582. [clock=2023-09-19 06:51:46+00:00] +2023-09-19 06:51:46,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244218879037882386268785126 amount: 80. +2023-09-19 06:51:46,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250255039794513291836274791 amount: 80. +2023-09-19 06:51:46,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106306.0, "order_id": "x-XEKWYICXBSIUT605b0adf2e7da0582", "exchange_order_id": "35591451", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:51:46,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0adf2e7da0582. +2023-09-19 06:51:46,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0b13a79660582 for 80.00000000 SEI-USDT. +2023-09-19 06:51:46,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0b13a79660582", "creation_timestamp": 1695106306.0, "exchange_order_id": "35591964", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:51:46,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106306.0, "order_id": "x-XEKWYICXSSIUT605b0adf2eb0d0582", "exchange_order_id": "35591452", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:51:46,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0adf2eb0d0582. +2023-09-19 06:51:46,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0b13a7c5b0582 for 80.00000000 SEI-USDT. +2023-09-19 06:51:46,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0b13a7c5b0582", "creation_timestamp": 1695106306.0, "exchange_order_id": "35591965", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:52:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0b13a79660582. [clock=2023-09-19 06:52:41+00:00] +2023-09-19 06:52:41,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0b13a7c5b0582. [clock=2023-09-19 06:52:41+00:00] +2023-09-19 06:52:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245059795496702014509858731 amount: 80. +2023-09-19 06:52:41,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249755229183737833794108493 amount: 80. +2023-09-19 06:52:41,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106361.0, "order_id": "x-XEKWYICXBSIUT605b0b13a79660582", "exchange_order_id": "35591964", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:52:41,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0b13a79660582. +2023-09-19 06:52:41,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0b4816bcb0582 for 80.00000000 SEI-USDT. +2023-09-19 06:52:41,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0b4816bcb0582", "creation_timestamp": 1695106361.0, "exchange_order_id": "35592068", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:52:41,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106361.0, "order_id": "x-XEKWYICXSSIUT605b0b13a7c5b0582", "exchange_order_id": "35591965", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:52:41,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0b13a7c5b0582. +2023-09-19 06:52:41,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0b4816e410582 for 80.00000000 SEI-USDT. +2023-09-19 06:52:41,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0b4816e410582", "creation_timestamp": 1695106361.0, "exchange_order_id": "35592069", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:53:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0b4816bcb0582. [clock=2023-09-19 06:53:36+00:00] +2023-09-19 06:53:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0b4816e410582. [clock=2023-09-19 06:53:36+00:00] +2023-09-19 06:53:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242787967989800448760507265 amount: 80. +2023-09-19 06:53:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-19 06:53:36,150 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106416.0, "order_id": "x-XEKWYICXBSIUT605b0b4816bcb0582", "exchange_order_id": "35592068", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:53:36,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0b4816bcb0582. +2023-09-19 06:53:36,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106416.0, "order_id": "x-XEKWYICXSSIUT605b0b4816e410582", "exchange_order_id": "35592069", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:53:36,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0b4816e410582. +2023-09-19 06:53:36,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0b7c897db0582 for 80.00000000 SEI-USDT. +2023-09-19 06:53:36,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b0b7c897db0582", "creation_timestamp": 1695106416.0, "exchange_order_id": "35592338", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:53:36,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0b7c89b320582 for 80.00000000 SEI-USDT. +2023-09-19 06:53:36,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b0b7c89b320582", "creation_timestamp": 1695106416.0, "exchange_order_id": "35592339", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:54:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0b7c897db0582. [clock=2023-09-19 06:54:31+00:00] +2023-09-19 06:54:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0b7c89b320582. [clock=2023-09-19 06:54:31+00:00] +2023-09-19 06:54:31,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244412941946622344649810037 amount: 80. +2023-09-19 06:54:31,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249033271234334589078762932 amount: 80. +2023-09-19 06:54:31,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106471.0, "order_id": "x-XEKWYICXBSIUT605b0b7c897db0582", "exchange_order_id": "35592338", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:54:31,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0b7c897db0582. +2023-09-19 06:54:31,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0bb0fe12f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:54:31,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0bb0fe12f0582", "creation_timestamp": 1695106471.0, "exchange_order_id": "35592433", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:54:31,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106471.0, "order_id": "x-XEKWYICXSSIUT605b0b7c89b320582", "exchange_order_id": "35592339", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:54:31,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0b7c89b320582. +2023-09-19 06:54:31,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0bb0fe30f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:54:31,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0bb0fe30f0582", "creation_timestamp": 1695106471.0, "exchange_order_id": "35592434", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:55:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0bb0fe12f0582. [clock=2023-09-19 06:55:26+00:00] +2023-09-19 06:55:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0bb0fe30f0582. [clock=2023-09-19 06:55:26+00:00] +2023-09-19 06:55:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245986166675154802208275064 amount: 80. +2023-09-19 06:55:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249582815485404531680741036 amount: 80. +2023-09-19 06:55:26,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106526.0, "order_id": "x-XEKWYICXBSIUT605b0bb0fe12f0582", "exchange_order_id": "35592433", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:55:26,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0bb0fe12f0582. +2023-09-19 06:55:26,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106526.0, "order_id": "x-XEKWYICXSSIUT605b0bb0fe30f0582", "exchange_order_id": "35592434", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:55:26,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0bb0fe30f0582. +2023-09-19 06:55:26,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0be5710b50582 for 80.00000000 SEI-USDT. +2023-09-19 06:55:26,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0be5710b50582", "creation_timestamp": 1695106526.0, "exchange_order_id": "35592596", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:55:26,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0be570e5f0582 for 80.00000000 SEI-USDT. +2023-09-19 06:55:26,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0be570e5f0582", "creation_timestamp": 1695106526.0, "exchange_order_id": "35592597", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:56:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0be570e5f0582. [clock=2023-09-19 06:56:21+00:00] +2023-09-19 06:56:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0be5710b50582. [clock=2023-09-19 06:56:21+00:00] +2023-09-19 06:56:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246433096999931050779313243 amount: 80. +2023-09-19 06:56:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249230596685348775481983433 amount: 80. +2023-09-19 06:56:21,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106581.0, "order_id": "x-XEKWYICXBSIUT605b0be570e5f0582", "exchange_order_id": "35592597", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:56:21,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0be570e5f0582. +2023-09-19 06:56:21,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106581.0, "order_id": "x-XEKWYICXSSIUT605b0be5710b50582", "exchange_order_id": "35592596", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:56:21,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0be5710b50582. +2023-09-19 06:56:21,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0c19e4f8d0582 for 80.00000000 SEI-USDT. +2023-09-19 06:56:21,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0c19e4f8d0582", "creation_timestamp": 1695106581.0, "exchange_order_id": "35592806", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:56:21,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0c19e51b30582 for 80.00000000 SEI-USDT. +2023-09-19 06:56:21,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0c19e51b30582", "creation_timestamp": 1695106581.0, "exchange_order_id": "35592807", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:57:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0c19e4f8d0582. [clock=2023-09-19 06:57:16+00:00] +2023-09-19 06:57:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0c19e51b30582. [clock=2023-09-19 06:57:16+00:00] +2023-09-19 06:57:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246781274320459223126096699 amount: 80. +2023-09-19 06:57:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12490000 amount: 80. +2023-09-19 06:57:16,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106636.0, "order_id": "x-XEKWYICXBSIUT605b0c19e4f8d0582", "exchange_order_id": "35592806", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:57:16,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0c19e4f8d0582. +2023-09-19 06:57:16,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106636.0, "order_id": "x-XEKWYICXSSIUT605b0c19e51b30582", "exchange_order_id": "35592807", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:57:16,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0c19e51b30582. +2023-09-19 06:57:16,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0c4e58a640582 for 80.00000000 SEI-USDT. +2023-09-19 06:57:16,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0c4e58a640582", "creation_timestamp": 1695106636.0, "exchange_order_id": "35592910", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:57:16,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0c4e58d770582 for 80.00000000 SEI-USDT. +2023-09-19 06:57:16,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0c4e58d770582", "creation_timestamp": 1695106636.0, "exchange_order_id": "35592909", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:58:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0c4e58a640582. [clock=2023-09-19 06:58:11+00:00] +2023-09-19 06:58:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0c4e58d770582. [clock=2023-09-19 06:58:11+00:00] +2023-09-19 06:58:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247052088128739119029377728 amount: 80. +2023-09-19 06:58:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12490000 amount: 80. +2023-09-19 06:58:11,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106691.0, "order_id": "x-XEKWYICXBSIUT605b0c4e58a640582", "exchange_order_id": "35592910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:58:11,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0c4e58a640582. +2023-09-19 06:58:11,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106691.0, "order_id": "x-XEKWYICXSSIUT605b0c4e58d770582", "exchange_order_id": "35592909", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:58:11,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0c4e58d770582. +2023-09-19 06:58:11,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0c82cc3710582 for 80.00000000 SEI-USDT. +2023-09-19 06:58:11,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b0c82cc3710582", "creation_timestamp": 1695106691.0, "exchange_order_id": "35592988", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:58:11,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0c82cc87c0582 for 80.00000000 SEI-USDT. +2023-09-19 06:58:11,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0c82cc87c0582", "creation_timestamp": 1695106691.0, "exchange_order_id": "35592989", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:58:19,518 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b0c82cc87c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 06:58:19,519 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 06:58:19+00:00] +2023-09-19 06:58:19,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106699.0, "order_id": "x-XEKWYICXSSIUT605b0c82cc87c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12490000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00999200"}]}, "exchange_trade_id": "4995015", "exchange_order_id": "35592989", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 06:58:19,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106699.0, "order_id": "x-XEKWYICXSSIUT605b0c82cc87c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9920000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35592989", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 06:58:19,555 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b0c82cc87c0582 completely filled. +2023-09-19 06:59:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0c82cc3710582. [clock=2023-09-19 06:59:06+00:00] +2023-09-19 06:59:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249237985898551293872702475 amount: 80. +2023-09-19 06:59:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252284657508755400584181301 amount: 80. +2023-09-19 06:59:06,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106746.0, "order_id": "x-XEKWYICXBSIUT605b0c82cc3710582", "exchange_order_id": "35592988", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 06:59:06,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0c82cc3710582. +2023-09-19 06:59:06,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0cb7401b10582 for 80.00000000 SEI-USDT. +2023-09-19 06:59:06,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b0cb7401b10582", "creation_timestamp": 1695106746.0, "exchange_order_id": "35593274", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 06:59:06,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0cb7406180582 for 80.00000000 SEI-USDT. +2023-09-19 06:59:06,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b0cb7406180582", "creation_timestamp": 1695106746.0, "exchange_order_id": "35593275", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:00:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0cb7401b10582. [clock=2023-09-19 07:00:01+00:00] +2023-09-19 07:00:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0cb7406180582. [clock=2023-09-19 07:00:01+00:00] +2023-09-19 07:00:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250284529813784712706077087 amount: 80. +2023-09-19 07:00:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255555439461696558469028875 amount: 80. +2023-09-19 07:00:01,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106801.0, "order_id": "x-XEKWYICXBSIUT605b0cb7401b10582", "exchange_order_id": "35593274", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:00:01,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0cb7401b10582. +2023-09-19 07:00:01,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106801.0, "order_id": "x-XEKWYICXSSIUT605b0cb7406180582", "exchange_order_id": "35593275", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:00:01,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0cb7406180582. +2023-09-19 07:00:01,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0cebb37570582 for 80.00000000 SEI-USDT. +2023-09-19 07:00:01,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0cebb37570582", "creation_timestamp": 1695106801.0, "exchange_order_id": "35593477", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:00:01,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0cebb3ac30582 for 80.00000000 SEI-USDT. +2023-09-19 07:00:01,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b0cebb3ac30582", "creation_timestamp": 1695106801.0, "exchange_order_id": "35593478", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:00:56,017 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0cebb37570582. [clock=2023-09-19 07:00:56+00:00] +2023-09-19 07:00:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0cebb3ac30582. [clock=2023-09-19 07:00:56+00:00] +2023-09-19 07:00:56,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250139751475231992983187995 amount: 80. +2023-09-19 07:00:56,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256465133259140768152476510 amount: 80. +2023-09-19 07:00:56,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106856.0, "order_id": "x-XEKWYICXBSIUT605b0cebb37570582", "exchange_order_id": "35593477", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:00:56,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0cebb37570582. +2023-09-19 07:00:56,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106856.0, "order_id": "x-XEKWYICXSSIUT605b0cebb3ac30582", "exchange_order_id": "35593478", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:00:56,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0cebb3ac30582. +2023-09-19 07:00:56,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0d202b8040582 for 80.00000000 SEI-USDT. +2023-09-19 07:00:56,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b0d202b8040582", "creation_timestamp": 1695106856.0, "exchange_order_id": "35593958", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:00:56,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0d202b4bd0582 for 80.00000000 SEI-USDT. +2023-09-19 07:00:56,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0d202b4bd0582", "creation_timestamp": 1695106856.0, "exchange_order_id": "35593959", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:01:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0d202b4bd0582. [clock=2023-09-19 07:01:51+00:00] +2023-09-19 07:01:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0d202b8040582. [clock=2023-09-19 07:01:51+00:00] +2023-09-19 07:01:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 07:01:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259056392347785327066309378 amount: 80. +2023-09-19 07:01:51,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "order_id": "x-XEKWYICXBSIUT605b0d202b4bd0582", "exchange_order_id": "35593959", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:01:51,151 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0d202b4bd0582. +2023-09-19 07:01:51,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "order_id": "x-XEKWYICXSSIUT605b0d202b8040582", "exchange_order_id": "35593958", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:01:51,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0d202b8040582. +2023-09-19 07:01:51,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0d549afce0582 for 80.00000000 SEI-USDT. +2023-09-19 07:01:51,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b0d549afce0582", "creation_timestamp": 1695106911.0, "exchange_order_id": "35594084", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:01:51,296 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b0d549afce0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:01:51,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 07:01:51+00:00] +2023-09-19 07:01:51,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "order_id": "x-XEKWYICXBSIUT605b0d549afce0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4995096", "exchange_order_id": "35594084", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:01:51,383 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "order_id": "x-XEKWYICXBSIUT605b0d549afce0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35594084", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:01:51,383 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b0d549afce0582 completely filled. +2023-09-19 07:01:51,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0d549b1ab0582 for 80.00000000 SEI-USDT. +2023-09-19 07:01:51,395 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b0d549b1ab0582", "creation_timestamp": 1695106911.0, "exchange_order_id": "35594086", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:02:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0d549b1ab0582. [clock=2023-09-19 07:02:46+00:00] +2023-09-19 07:02:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249877373760924048445876436 amount: 80. +2023-09-19 07:02:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254564888093095115841309788 amount: 80. +2023-09-19 07:02:46,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106966.0, "order_id": "x-XEKWYICXSSIUT605b0d549b1ab0582", "exchange_order_id": "35594086", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:02:46,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0d549b1ab0582. +2023-09-19 07:02:46,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0d890e7fb0582 for 80.00000000 SEI-USDT. +2023-09-19 07:02:46,380 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b0d890e7fb0582", "creation_timestamp": 1695106966.0, "exchange_order_id": "35594185", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:02:46,381 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0d890ea010582 for 80.00000000 SEI-USDT. +2023-09-19 07:02:46,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695106966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b0d890ea010582", "creation_timestamp": 1695106966.0, "exchange_order_id": "35594186", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:03:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0d890e7fb0582. [clock=2023-09-19 07:03:41+00:00] +2023-09-19 07:03:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0d890ea010582. [clock=2023-09-19 07:03:41+00:00] +2023-09-19 07:03:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250127916519051174370069083 amount: 80. +2023-09-19 07:03:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253772972896128591064360796 amount: 80. +2023-09-19 07:03:41,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107021.0, "order_id": "x-XEKWYICXBSIUT605b0d890e7fb0582", "exchange_order_id": "35594185", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:03:41,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0d890e7fb0582. +2023-09-19 07:03:41,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0dbd836460582 for 80.00000000 SEI-USDT. +2023-09-19 07:03:41,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b0dbd836460582", "creation_timestamp": 1695107021.0, "exchange_order_id": "35594270", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:03:41,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0dbd833400582 for 80.00000000 SEI-USDT. +2023-09-19 07:03:41,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0dbd833400582", "creation_timestamp": 1695107021.0, "exchange_order_id": "35594271", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:03:41,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107021.0, "order_id": "x-XEKWYICXSSIUT605b0d890ea010582", "exchange_order_id": "35594186", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:03:41,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0d890ea010582. +2023-09-19 07:04:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0dbd833400582. [clock=2023-09-19 07:04:36+00:00] +2023-09-19 07:04:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0dbd836460582. [clock=2023-09-19 07:04:36+00:00] +2023-09-19 07:04:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250321928353959877873200587 amount: 80. +2023-09-19 07:04:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253156493863967609826393487 amount: 80. +2023-09-19 07:04:36,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107076.0, "order_id": "x-XEKWYICXBSIUT605b0dbd833400582", "exchange_order_id": "35594271", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:04:36,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0dbd833400582. +2023-09-19 07:04:36,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107076.0, "order_id": "x-XEKWYICXSSIUT605b0dbd836460582", "exchange_order_id": "35594270", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:04:36,422 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0dbd836460582. +2023-09-19 07:04:36,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0df1f65ad0582 for 80.00000000 SEI-USDT. +2023-09-19 07:04:36,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b0df1f65ad0582", "creation_timestamp": 1695107076.0, "exchange_order_id": "35594351", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:04:36,472 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0df1f62710582 for 80.00000000 SEI-USDT. +2023-09-19 07:04:36,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0df1f62710582", "creation_timestamp": 1695107076.0, "exchange_order_id": "35594350", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:05:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0df1f62710582. [clock=2023-09-19 07:05:31+00:00] +2023-09-19 07:05:31,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0df1f65ad0582. [clock=2023-09-19 07:05:31+00:00] +2023-09-19 07:05:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250472741301447240688576821 amount: 80. +2023-09-19 07:05:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252677114015787750110849098 amount: 80. +2023-09-19 07:05:31,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107131.0, "order_id": "x-XEKWYICXBSIUT605b0df1f62710582", "exchange_order_id": "35594350", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:05:31,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0df1f62710582. +2023-09-19 07:05:31,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107131.0, "order_id": "x-XEKWYICXSSIUT605b0df1f65ad0582", "exchange_order_id": "35594351", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:05:31,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0df1f65ad0582. +2023-09-19 07:05:31,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0e266a93c0582 for 80.00000000 SEI-USDT. +2023-09-19 07:05:31,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b0e266a93c0582", "creation_timestamp": 1695107131.0, "exchange_order_id": "35594436", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:05:31,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0e266ac480582 for 80.00000000 SEI-USDT. +2023-09-19 07:05:31,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b0e266ac480582", "creation_timestamp": 1695107131.0, "exchange_order_id": "35594437", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:06:07,673 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b0e266a93c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:06:07,674 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:06:07+00:00] +2023-09-19 07:06:07,698 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107167.0, "order_id": "x-XEKWYICXBSIUT605b0e266a93c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4995127", "exchange_order_id": "35594436", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:06:07,710 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107167.0, "order_id": "x-XEKWYICXBSIUT605b0e266a93c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35594436", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:06:07,711 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b0e266a93c0582 completely filled. +2023-09-19 07:06:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0e266ac480582. [clock=2023-09-19 07:06:26+00:00] +2023-09-19 07:06:26,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244690736446012419953639937 amount: 80. +2023-09-19 07:06:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249484899634138815377718375 amount: 80. +2023-09-19 07:06:26,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107186.0, "order_id": "x-XEKWYICXSSIUT605b0e266ac480582", "exchange_order_id": "35594437", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:06:26,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0e266ac480582. +2023-09-19 07:06:26,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0e5add7320582 for 80.00000000 SEI-USDT. +2023-09-19 07:06:26,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0e5add7320582", "creation_timestamp": 1695107186.0, "exchange_order_id": "35594876", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:06:26,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0e5adda280582 for 80.00000000 SEI-USDT. +2023-09-19 07:06:26,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0e5adda280582", "creation_timestamp": 1695107186.0, "exchange_order_id": "35594877", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:07:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0e5add7320582. [clock=2023-09-19 07:07:21+00:00] +2023-09-19 07:07:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0e5adda280582. [clock=2023-09-19 07:07:21+00:00] +2023-09-19 07:07:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243237905971512211622383984 amount: 80. +2023-09-19 07:07:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12490000 amount: 80. +2023-09-19 07:07:21,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107241.0, "order_id": "x-XEKWYICXBSIUT605b0e5add7320582", "exchange_order_id": "35594876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:07:21,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0e5add7320582. +2023-09-19 07:07:21,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0e8f519e20582 for 80.00000000 SEI-USDT. +2023-09-19 07:07:21,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0e8f519e20582", "creation_timestamp": 1695107241.0, "exchange_order_id": "35595120", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:07:21,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0e8f516b90582 for 80.00000000 SEI-USDT. +2023-09-19 07:07:21,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b0e8f516b90582", "creation_timestamp": 1695107241.0, "exchange_order_id": "35595121", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:07:21,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107241.0, "order_id": "x-XEKWYICXSSIUT605b0e5adda280582", "exchange_order_id": "35594877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:07:21,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0e5adda280582. +2023-09-19 07:08:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0e8f516b90582. [clock=2023-09-19 07:08:16+00:00] +2023-09-19 07:08:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0e8f519e20582. [clock=2023-09-19 07:08:16+00:00] +2023-09-19 07:08:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243401087042304664627167432 amount: 80. +2023-09-19 07:08:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12490000 amount: 80. +2023-09-19 07:08:16,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107296.0, "order_id": "x-XEKWYICXBSIUT605b0e8f516b90582", "exchange_order_id": "35595121", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:08:16,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0e8f516b90582. +2023-09-19 07:08:16,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107296.0, "order_id": "x-XEKWYICXSSIUT605b0e8f519e20582", "exchange_order_id": "35595120", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:08:16,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0e8f519e20582. +2023-09-19 07:08:16,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0ec3c50390582 for 80.00000000 SEI-USDT. +2023-09-19 07:08:16,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b0ec3c50390582", "creation_timestamp": 1695107296.0, "exchange_order_id": "35595293", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:08:16,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0ec3c52c00582 for 80.00000000 SEI-USDT. +2023-09-19 07:08:16,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0ec3c52c00582", "creation_timestamp": 1695107296.0, "exchange_order_id": "35595294", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:09:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0ec3c50390582. [clock=2023-09-19 07:09:11+00:00] +2023-09-19 07:09:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0ec3c52c00582. [clock=2023-09-19 07:09:11+00:00] +2023-09-19 07:09:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245296739131253627786790640 amount: 80. +2023-09-19 07:09:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249609677227600952440619108 amount: 80. +2023-09-19 07:09:11,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107351.0, "order_id": "x-XEKWYICXBSIUT605b0ec3c50390582", "exchange_order_id": "35595293", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:09:11,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0ec3c50390582. +2023-09-19 07:09:11,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0ef8391120582 for 80.00000000 SEI-USDT. +2023-09-19 07:09:11,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b0ef8391120582", "creation_timestamp": 1695107351.0, "exchange_order_id": "35595379", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:09:11,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107351.0, "order_id": "x-XEKWYICXSSIUT605b0ec3c52c00582", "exchange_order_id": "35595294", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:09:11,303 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0ec3c52c00582. +2023-09-19 07:09:11,355 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0ef838d290582 for 80.00000000 SEI-USDT. +2023-09-19 07:09:11,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b0ef838d290582", "creation_timestamp": 1695107351.0, "exchange_order_id": "35595380", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:10:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0ef838d290582. [clock=2023-09-19 07:10:06+00:00] +2023-09-19 07:10:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0ef8391120582. [clock=2023-09-19 07:10:06+00:00] +2023-09-19 07:10:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244012270952721884200903695 amount: 80. +2023-09-19 07:10:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250206019571619661951874426 amount: 80. +2023-09-19 07:10:06,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107406.0, "order_id": "x-XEKWYICXBSIUT605b0ef838d290582", "exchange_order_id": "35595380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:10:06,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0ef838d290582. +2023-09-19 07:10:06,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107406.0, "order_id": "x-XEKWYICXSSIUT605b0ef8391120582", "exchange_order_id": "35595379", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:10:06,367 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0ef8391120582. +2023-09-19 07:10:06,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0f2cacb3a0582 for 80.00000000 SEI-USDT. +2023-09-19 07:10:06,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0f2cacb3a0582", "creation_timestamp": 1695107406.0, "exchange_order_id": "35595841", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:10:06,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0f2cacec60582 for 80.00000000 SEI-USDT. +2023-09-19 07:10:06,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0f2cacec60582", "creation_timestamp": 1695107406.0, "exchange_order_id": "35595842", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:11:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0f2cacb3a0582. [clock=2023-09-19 07:11:01+00:00] +2023-09-19 07:11:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0f2cacec60582. [clock=2023-09-19 07:11:01+00:00] +2023-09-19 07:11:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244012270952721884200903695 amount: 80. +2023-09-19 07:11:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250206019571619661951874426 amount: 80. +2023-09-19 07:11:01,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107461.0, "order_id": "x-XEKWYICXBSIUT605b0f2cacb3a0582", "exchange_order_id": "35595841", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:11:01,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0f2cacb3a0582. +2023-09-19 07:11:01,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107461.0, "order_id": "x-XEKWYICXSSIUT605b0f2cacec60582", "exchange_order_id": "35595842", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:11:01,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0f2cacec60582. +2023-09-19 07:11:01,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0f61208fc0582 for 80.00000000 SEI-USDT. +2023-09-19 07:11:01,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0f61208fc0582", "creation_timestamp": 1695107461.0, "exchange_order_id": "35595927", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:11:01,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0f612064f0582 for 80.00000000 SEI-USDT. +2023-09-19 07:11:01,381 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0f612064f0582", "creation_timestamp": 1695107461.0, "exchange_order_id": "35595928", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:11:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0f612064f0582. [clock=2023-09-19 07:11:56+00:00] +2023-09-19 07:11:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0f61208fc0582. [clock=2023-09-19 07:11:56+00:00] +2023-09-19 07:11:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244183479632873619291109649 amount: 80. +2023-09-19 07:11:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250111339516445072022012417 amount: 80. +2023-09-19 07:11:56,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107516.0, "order_id": "x-XEKWYICXBSIUT605b0f612064f0582", "exchange_order_id": "35595928", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:11:56,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0f612064f0582. +2023-09-19 07:11:56,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107516.0, "order_id": "x-XEKWYICXSSIUT605b0f61208fc0582", "exchange_order_id": "35595927", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:11:56,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0f61208fc0582. +2023-09-19 07:11:56,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0f95946f70582 for 80.00000000 SEI-USDT. +2023-09-19 07:11:56,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0f95946f70582", "creation_timestamp": 1695107516.0, "exchange_order_id": "35596156", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:11:56,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0f95943a60582 for 80.00000000 SEI-USDT. +2023-09-19 07:11:56,449 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0f95943a60582", "creation_timestamp": 1695107516.0, "exchange_order_id": "35596157", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:12:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0f95943a60582. [clock=2023-09-19 07:12:51+00:00] +2023-09-19 07:12:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0f95946f70582. [clock=2023-09-19 07:12:51+00:00] +2023-09-19 07:12:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244316647890930075011941511 amount: 80. +2023-09-19 07:12:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250037694138154560423334971 amount: 80. +2023-09-19 07:12:51,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107571.0, "order_id": "x-XEKWYICXBSIUT605b0f95943a60582", "exchange_order_id": "35596157", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:12:51,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0f95943a60582. +2023-09-19 07:12:51,209 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 07:12:51,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0fca080fc0582 for 80.00000000 SEI-USDT. +2023-09-19 07:12:51,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0fca080fc0582", "creation_timestamp": 1695107571.0, "exchange_order_id": "35596256", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:12:51,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0fca07d770582 for 80.00000000 SEI-USDT. +2023-09-19 07:12:51,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b0fca07d770582", "creation_timestamp": 1695107571.0, "exchange_order_id": "35596257", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:12:51,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107571.0, "order_id": "x-XEKWYICXSSIUT605b0f95946f70582", "exchange_order_id": "35596156", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:12:51,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0f95946f70582. +2023-09-19 07:13:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0fca07d770582. [clock=2023-09-19 07:13:46+00:00] +2023-09-19 07:13:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0fca080fc0582. [clock=2023-09-19 07:13:46+00:00] +2023-09-19 07:13:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246536407257585307169102614 amount: 80. +2023-09-19 07:13:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250988198217496483111023064 amount: 80. +2023-09-19 07:13:46,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107626.0, "order_id": "x-XEKWYICXBSIUT605b0fca07d770582", "exchange_order_id": "35596257", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:13:46,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0fca07d770582. +2023-09-19 07:13:46,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b0ffe7b6880582 for 80.00000000 SEI-USDT. +2023-09-19 07:13:46,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b0ffe7b6880582", "creation_timestamp": 1695107626.0, "exchange_order_id": "35596371", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:13:46,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107626.0, "order_id": "x-XEKWYICXSSIUT605b0fca080fc0582", "exchange_order_id": "35596256", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:13:46,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0fca080fc0582. +2023-09-19 07:13:46,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b0ffe7b9f50582 for 80.00000000 SEI-USDT. +2023-09-19 07:13:46,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b0ffe7b9f50582", "creation_timestamp": 1695107626.0, "exchange_order_id": "35596372", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:14:41,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b0ffe7b6880582. [clock=2023-09-19 07:14:41+00:00] +2023-09-19 07:14:41,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b0ffe7b9f50582. [clock=2023-09-19 07:14:41+00:00] +2023-09-19 07:14:41,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244111510961616749189269202 amount: 80. +2023-09-19 07:14:41,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248679905041863081132925974 amount: 80. +2023-09-19 07:14:41,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107681.0, "order_id": "x-XEKWYICXBSIUT605b0ffe7b6880582", "exchange_order_id": "35596371", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:14:41,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b0ffe7b6880582. +2023-09-19 07:14:41,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1032f10bb0582 for 80.00000000 SEI-USDT. +2023-09-19 07:14:41,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b1032f10bb0582", "creation_timestamp": 1695107681.0, "exchange_order_id": "35596595", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:14:41,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107681.0, "order_id": "x-XEKWYICXSSIUT605b0ffe7b9f50582", "exchange_order_id": "35596372", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:14:41,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b0ffe7b9f50582. +2023-09-19 07:14:41,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1032f13390582 for 80.00000000 SEI-USDT. +2023-09-19 07:14:41,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b1032f13390582", "creation_timestamp": 1695107681.0, "exchange_order_id": "35596596", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:15:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1032f10bb0582. [clock=2023-09-19 07:15:36+00:00] +2023-09-19 07:15:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1032f13390582. [clock=2023-09-19 07:15:36+00:00] +2023-09-19 07:15:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239022182223840240892820029 amount: 80. +2023-09-19 07:15:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 07:15:36,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107736.0, "order_id": "x-XEKWYICXBSIUT605b1032f10bb0582", "exchange_order_id": "35596595", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:15:36,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1032f10bb0582. +2023-09-19 07:15:36,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107736.0, "order_id": "x-XEKWYICXSSIUT605b1032f13390582", "exchange_order_id": "35596596", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:15:36,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1032f13390582. +2023-09-19 07:15:36,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1067634270582 for 80.00000000 SEI-USDT. +2023-09-19 07:15:36,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b1067634270582", "creation_timestamp": 1695107736.0, "exchange_order_id": "35596945", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:15:36,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1067637180582 for 80.00000000 SEI-USDT. +2023-09-19 07:15:36,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b1067637180582", "creation_timestamp": 1695107736.0, "exchange_order_id": "35596946", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:16:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1067634270582. [clock=2023-09-19 07:16:31+00:00] +2023-09-19 07:16:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1067637180582. [clock=2023-09-19 07:16:31+00:00] +2023-09-19 07:16:31,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238458430853114909781529920 amount: 80. +2023-09-19 07:16:31,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 07:16:31,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107791.0, "order_id": "x-XEKWYICXBSIUT605b1067634270582", "exchange_order_id": "35596945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:16:31,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1067634270582. +2023-09-19 07:16:31,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107791.0, "order_id": "x-XEKWYICXSSIUT605b1067637180582", "exchange_order_id": "35596946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:16:31,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1067637180582. +2023-09-19 07:16:31,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b109bdb57c0582 for 80.00000000 SEI-USDT. +2023-09-19 07:16:31,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b109bdb57c0582", "creation_timestamp": 1695107791.0, "exchange_order_id": "35597177", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:16:31,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b109bdb7670582 for 80.00000000 SEI-USDT. +2023-09-19 07:16:31,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b109bdb7670582", "creation_timestamp": 1695107791.0, "exchange_order_id": "35597178", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:17:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b109bdb57c0582. [clock=2023-09-19 07:17:26+00:00] +2023-09-19 07:17:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b109bdb7670582. [clock=2023-09-19 07:17:26+00:00] +2023-09-19 07:17:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238792821937713058219600856 amount: 80. +2023-09-19 07:17:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 07:17:26,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107846.0, "order_id": "x-XEKWYICXBSIUT605b109bdb57c0582", "exchange_order_id": "35597177", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:17:26,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b109bdb57c0582. +2023-09-19 07:17:26,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107846.0, "order_id": "x-XEKWYICXSSIUT605b109bdb7670582", "exchange_order_id": "35597178", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:17:26,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b109bdb7670582. +2023-09-19 07:17:26,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b10d04a8990582 for 80.00000000 SEI-USDT. +2023-09-19 07:17:26,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b10d04a8990582", "creation_timestamp": 1695107846.0, "exchange_order_id": "35597358", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:17:26,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b10d04ac220582 for 80.00000000 SEI-USDT. +2023-09-19 07:17:26,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b10d04ac220582", "creation_timestamp": 1695107846.0, "exchange_order_id": "35597359", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:18:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b10d04a8990582. [clock=2023-09-19 07:18:21+00:00] +2023-09-19 07:18:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b10d04ac220582. [clock=2023-09-19 07:18:21+00:00] +2023-09-19 07:18:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239055568886010508467107295 amount: 80. +2023-09-19 07:18:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12450000 amount: 80. +2023-09-19 07:18:21,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107901.0, "order_id": "x-XEKWYICXBSIUT605b10d04a8990582", "exchange_order_id": "35597358", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:18:21,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b10d04a8990582. +2023-09-19 07:18:21,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107901.0, "order_id": "x-XEKWYICXSSIUT605b10d04ac220582", "exchange_order_id": "35597359", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:18:21,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b10d04ac220582. +2023-09-19 07:18:21,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1104bee4a0582 for 80.00000000 SEI-USDT. +2023-09-19 07:18:21,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b1104bee4a0582", "creation_timestamp": 1695107901.0, "exchange_order_id": "35597524", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:18:21,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1104beaba0582 for 80.00000000 SEI-USDT. +2023-09-19 07:18:21,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b1104beaba0582", "creation_timestamp": 1695107901.0, "exchange_order_id": "35597525", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:19:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1104beaba0582. [clock=2023-09-19 07:19:16+00:00] +2023-09-19 07:19:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1104bee4a0582. [clock=2023-09-19 07:19:16+00:00] +2023-09-19 07:19:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237366819974200209467808560 amount: 80. +2023-09-19 07:19:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12440000 amount: 80. +2023-09-19 07:19:16,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107956.0, "order_id": "x-XEKWYICXBSIUT605b1104beaba0582", "exchange_order_id": "35597525", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:19:16,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1104beaba0582. +2023-09-19 07:19:16,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1139323380582 for 80.00000000 SEI-USDT. +2023-09-19 07:19:16,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b1139323380582", "creation_timestamp": 1695107956.0, "exchange_order_id": "35597750", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:19:16,473 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107956.0, "order_id": "x-XEKWYICXSSIUT605b1104bee4a0582", "exchange_order_id": "35597524", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:19:16,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1104bee4a0582. +2023-09-19 07:19:16,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1139325f10582 for 80.00000000 SEI-USDT. +2023-09-19 07:19:16,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695107956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b1139325f10582", "creation_timestamp": 1695107956.0, "exchange_order_id": "35597751", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:20:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1139323380582. [clock=2023-09-19 07:20:11+00:00] +2023-09-19 07:20:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1139325f10582. [clock=2023-09-19 07:20:11+00:00] +2023-09-19 07:20:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237621829138983586521933721 amount: 80. +2023-09-19 07:20:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12430000 amount: 80. +2023-09-19 07:20:11,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108011.0, "order_id": "x-XEKWYICXBSIUT605b1139323380582", "exchange_order_id": "35597750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:20:11,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1139323380582. +2023-09-19 07:20:11,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108011.0, "order_id": "x-XEKWYICXSSIUT605b1139325f10582", "exchange_order_id": "35597751", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:20:11,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1139325f10582. +2023-09-19 07:20:11,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b116da59710582 for 80.00000000 SEI-USDT. +2023-09-19 07:20:11,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b116da59710582", "creation_timestamp": 1695108011.0, "exchange_order_id": "35597976", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:20:11,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b116da5d810582 for 80.00000000 SEI-USDT. +2023-09-19 07:20:11,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b116da5d810582", "creation_timestamp": 1695108011.0, "exchange_order_id": "35597977", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:21:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b116da59710582. [clock=2023-09-19 07:21:06+00:00] +2023-09-19 07:21:06,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b116da5d810582. [clock=2023-09-19 07:21:06+00:00] +2023-09-19 07:21:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237698663502788011078674655 amount: 80. +2023-09-19 07:21:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12430000 amount: 80. +2023-09-19 07:21:06,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108066.0, "order_id": "x-XEKWYICXBSIUT605b116da59710582", "exchange_order_id": "35597976", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:21:06,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b116da59710582. +2023-09-19 07:21:06,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108066.0, "order_id": "x-XEKWYICXSSIUT605b116da5d810582", "exchange_order_id": "35597977", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:21:06,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b116da5d810582. +2023-09-19 07:21:06,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b11a219c830582 for 80.00000000 SEI-USDT. +2023-09-19 07:21:06,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b11a219c830582", "creation_timestamp": 1695108066.0, "exchange_order_id": "35598061", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:21:06,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b11a219f4b0582 for 80.00000000 SEI-USDT. +2023-09-19 07:21:06,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b11a219f4b0582", "creation_timestamp": 1695108066.0, "exchange_order_id": "35598062", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:22:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b11a219c830582. [clock=2023-09-19 07:22:01+00:00] +2023-09-19 07:22:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b11a219f4b0582. [clock=2023-09-19 07:22:01+00:00] +2023-09-19 07:22:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237698663502788011078674655 amount: 80. +2023-09-19 07:22:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12430000 amount: 80. +2023-09-19 07:22:01,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108121.0, "order_id": "x-XEKWYICXBSIUT605b11a219c830582", "exchange_order_id": "35598061", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:22:01,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b11a219c830582. +2023-09-19 07:22:01,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108121.0, "order_id": "x-XEKWYICXSSIUT605b11a219f4b0582", "exchange_order_id": "35598062", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:22:01,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b11a219f4b0582. +2023-09-19 07:22:01,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b11d68d6f10582 for 80.00000000 SEI-USDT. +2023-09-19 07:22:01,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b11d68d6f10582", "creation_timestamp": 1695108121.0, "exchange_order_id": "35598160", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:22:01,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b11d68dab20582 for 80.00000000 SEI-USDT. +2023-09-19 07:22:01,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b11d68dab20582", "creation_timestamp": 1695108121.0, "exchange_order_id": "35598161", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:22:48,154 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b11d68dab20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:22:48,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:22:48+00:00] +2023-09-19 07:22:48,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108168.0, "order_id": "x-XEKWYICXSSIUT605b11d68dab20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "4995540", "exchange_order_id": "35598161", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:22:48,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108168.0, "order_id": "x-XEKWYICXSSIUT605b11d68dab20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35598161", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:22:48,198 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b11d68dab20582 completely filled. +2023-09-19 07:22:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b11d68d6f10582. [clock=2023-09-19 07:22:56+00:00] +2023-09-19 07:22:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239275689467213557894593824 amount: 80. +2023-09-19 07:22:56,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243615497871159342355488318 amount: 80. +2023-09-19 07:22:56,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108176.0, "order_id": "x-XEKWYICXBSIUT605b11d68d6f10582", "exchange_order_id": "35598160", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:22:56,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b11d68d6f10582. +2023-09-19 07:22:56,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b120b01ac60582 for 80.00000000 SEI-USDT. +2023-09-19 07:22:56,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b120b01ac60582", "creation_timestamp": 1695108176.0, "exchange_order_id": "35598416", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:22:56,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b120b01dfe0582 for 80.00000000 SEI-USDT. +2023-09-19 07:22:56,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b120b01dfe0582", "creation_timestamp": 1695108176.0, "exchange_order_id": "35598417", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:23:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b120b01ac60582. [clock=2023-09-19 07:23:51+00:00] +2023-09-19 07:23:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b120b01dfe0582. [clock=2023-09-19 07:23:51+00:00] +2023-09-19 07:23:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235787299318592254848328696 amount: 80. +2023-09-19 07:23:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12420000 amount: 80. +2023-09-19 07:23:51,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108231.0, "order_id": "x-XEKWYICXBSIUT605b120b01ac60582", "exchange_order_id": "35598416", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:23:51,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b120b01ac60582. +2023-09-19 07:23:51,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108231.0, "order_id": "x-XEKWYICXSSIUT605b120b01dfe0582", "exchange_order_id": "35598417", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:23:51,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b120b01dfe0582. +2023-09-19 07:23:51,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b123f74cee0582 for 80.00000000 SEI-USDT. +2023-09-19 07:23:51,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b123f74cee0582", "creation_timestamp": 1695108231.0, "exchange_order_id": "35598632", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:23:51,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b123f751300582 for 80.00000000 SEI-USDT. +2023-09-19 07:23:51,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b123f751300582", "creation_timestamp": 1695108231.0, "exchange_order_id": "35598633", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:24:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b123f74cee0582. [clock=2023-09-19 07:24:46+00:00] +2023-09-19 07:24:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b123f751300582. [clock=2023-09-19 07:24:46+00:00] +2023-09-19 07:24:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235912721637244366151582456 amount: 80. +2023-09-19 07:24:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12420000 amount: 80. +2023-09-19 07:24:46,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108286.0, "order_id": "x-XEKWYICXBSIUT605b123f74cee0582", "exchange_order_id": "35598632", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:24:46,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b123f74cee0582. +2023-09-19 07:24:46,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108286.0, "order_id": "x-XEKWYICXSSIUT605b123f751300582", "exchange_order_id": "35598633", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:24:46,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b123f751300582. +2023-09-19 07:24:46,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1273e8a060582 for 80.00000000 SEI-USDT. +2023-09-19 07:24:46,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b1273e8a060582", "creation_timestamp": 1695108286.0, "exchange_order_id": "35598730", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:24:46,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1273e870c0582 for 80.00000000 SEI-USDT. +2023-09-19 07:24:46,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b1273e870c0582", "creation_timestamp": 1695108286.0, "exchange_order_id": "35598731", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:25:03,263 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b1273e8a060582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:25:03,263 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:25:03+00:00] +2023-09-19 07:25:03,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108303.0, "order_id": "x-XEKWYICXSSIUT605b1273e8a060582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "4995596", "exchange_order_id": "35598730", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:25:03,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108303.0, "order_id": "x-XEKWYICXSSIUT605b1273e8a060582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35598730", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:25:03,302 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b1273e8a060582 completely filled. +2023-09-19 07:25:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1273e870c0582. [clock=2023-09-19 07:25:41+00:00] +2023-09-19 07:25:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240611372078057940055383508 amount: 80. +2023-09-19 07:25:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245976726202293070236197498 amount: 80. +2023-09-19 07:25:41,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108341.0, "order_id": "x-XEKWYICXBSIUT605b1273e870c0582", "exchange_order_id": "35598731", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:25:41,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1273e870c0582. +2023-09-19 07:25:41,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b12a85c9eb0582 for 80.00000000 SEI-USDT. +2023-09-19 07:25:41,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b12a85c9eb0582", "creation_timestamp": 1695108341.0, "exchange_order_id": "35599012", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:25:41,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b12a85c6f50582 for 80.00000000 SEI-USDT. +2023-09-19 07:25:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b12a85c6f50582", "creation_timestamp": 1695108341.0, "exchange_order_id": "35599013", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:26:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b12a85c6f50582. [clock=2023-09-19 07:26:36+00:00] +2023-09-19 07:26:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b12a85c9eb0582. [clock=2023-09-19 07:26:36+00:00] +2023-09-19 07:26:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241209980664837985830893403 amount: 80. +2023-09-19 07:26:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246245141488866083401321737 amount: 80. +2023-09-19 07:26:36,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108396.0, "order_id": "x-XEKWYICXBSIUT605b12a85c6f50582", "exchange_order_id": "35599013", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:26:36,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b12a85c6f50582. +2023-09-19 07:26:36,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108396.0, "order_id": "x-XEKWYICXSSIUT605b12a85c9eb0582", "exchange_order_id": "35599012", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:26:36,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b12a85c9eb0582. +2023-09-19 07:26:36,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b12dcd0b210582 for 80.00000000 SEI-USDT. +2023-09-19 07:26:36,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b12dcd0b210582", "creation_timestamp": 1695108396.0, "exchange_order_id": "35599274", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:26:36,323 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b12dcd07390582 for 80.00000000 SEI-USDT. +2023-09-19 07:26:36,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b12dcd07390582", "creation_timestamp": 1695108396.0, "exchange_order_id": "35599275", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:27:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b12dcd07390582. [clock=2023-09-19 07:27:31+00:00] +2023-09-19 07:27:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b12dcd0b210582. [clock=2023-09-19 07:27:31+00:00] +2023-09-19 07:27:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242606913300861238728190041 amount: 80. +2023-09-19 07:27:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246525790673280972482819250 amount: 80. +2023-09-19 07:27:31,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108451.0, "order_id": "x-XEKWYICXBSIUT605b12dcd07390582", "exchange_order_id": "35599275", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:27:31,153 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b12dcd07390582. +2023-09-19 07:27:31,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108451.0, "order_id": "x-XEKWYICXSSIUT605b12dcd0b210582", "exchange_order_id": "35599274", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:27:31,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b12dcd0b210582. +2023-09-19 07:27:31,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b131143c650582 for 80.00000000 SEI-USDT. +2023-09-19 07:27:31,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b131143c650582", "creation_timestamp": 1695108451.0, "exchange_order_id": "35599532", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:27:31,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1311439040582 for 80.00000000 SEI-USDT. +2023-09-19 07:27:31,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b1311439040582", "creation_timestamp": 1695108451.0, "exchange_order_id": "35599533", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:28:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1311439040582. [clock=2023-09-19 07:28:26+00:00] +2023-09-19 07:28:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b131143c650582. [clock=2023-09-19 07:28:26+00:00] +2023-09-19 07:28:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241916982871041951269305180 amount: 80. +2023-09-19 07:28:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244962239150646083815507779 amount: 80. +2023-09-19 07:28:26,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108506.0, "order_id": "x-XEKWYICXBSIUT605b1311439040582", "exchange_order_id": "35599533", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:28:26,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1311439040582. +2023-09-19 07:28:26,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108506.0, "order_id": "x-XEKWYICXSSIUT605b131143c650582", "exchange_order_id": "35599532", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:28:26,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b131143c650582. +2023-09-19 07:28:26,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1345b7acf0582 for 80.00000000 SEI-USDT. +2023-09-19 07:28:26,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b1345b7acf0582", "creation_timestamp": 1695108506.0, "exchange_order_id": "35599670", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:28:26,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1345b77810582 for 80.00000000 SEI-USDT. +2023-09-19 07:28:26,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b1345b77810582", "creation_timestamp": 1695108506.0, "exchange_order_id": "35599672", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:29:06,385 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b1345b7acf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:29:06,387 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:29:06+00:00] +2023-09-19 07:29:06,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108546.0, "order_id": "x-XEKWYICXSSIUT605b1345b7acf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00995200"}]}, "exchange_trade_id": "4995675", "exchange_order_id": "35599670", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:29:06,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108546.0, "order_id": "x-XEKWYICXSSIUT605b1345b7acf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35599670", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:29:06,420 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b1345b7acf0582 completely filled. +2023-09-19 07:29:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1345b77810582. [clock=2023-09-19 07:29:21+00:00] +2023-09-19 07:29:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242402642162649618793251766 amount: 80. +2023-09-19 07:29:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244771455667712122329828887 amount: 80. +2023-09-19 07:29:21,145 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108561.0, "order_id": "x-XEKWYICXBSIUT605b1345b77810582", "exchange_order_id": "35599672", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:29:21,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1345b77810582. +2023-09-19 07:29:21,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b137a2b6a90582 for 80.00000000 SEI-USDT. +2023-09-19 07:29:21,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b137a2b6a90582", "creation_timestamp": 1695108561.0, "exchange_order_id": "35599762", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:29:21,303 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b137a2b2ac0582 for 80.00000000 SEI-USDT. +2023-09-19 07:29:21,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b137a2b2ac0582", "creation_timestamp": 1695108561.0, "exchange_order_id": "35599763", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:29:50,123 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b137a2b6a90582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:29:50,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:29:50+00:00] +2023-09-19 07:29:50,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108590.0, "order_id": "x-XEKWYICXSSIUT605b137a2b6a90582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00995200"}]}, "exchange_trade_id": "4995685", "exchange_order_id": "35599762", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:29:50,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108590.0, "order_id": "x-XEKWYICXSSIUT605b137a2b6a90582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35599762", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:29:50,162 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b137a2b6a90582 completely filled. +2023-09-19 07:30:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b137a2b2ac0582. [clock=2023-09-19 07:30:16+00:00] +2023-09-19 07:30:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242725684070228096310005251 amount: 80. +2023-09-19 07:30:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244568183295198770634673145 amount: 80. +2023-09-19 07:30:16,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108616.0, "order_id": "x-XEKWYICXBSIUT605b137a2b2ac0582", "exchange_order_id": "35599763", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:30:16,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b137a2b2ac0582. +2023-09-19 07:30:16,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b13ae9efdc0582 for 80.00000000 SEI-USDT. +2023-09-19 07:30:16,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b13ae9efdc0582", "creation_timestamp": 1695108616.0, "exchange_order_id": "35599875", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:30:16,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b13ae9ec860582 for 80.00000000 SEI-USDT. +2023-09-19 07:30:16,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b13ae9ec860582", "creation_timestamp": 1695108616.0, "exchange_order_id": "35599876", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:30:19,227 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b13ae9efdc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:30:19,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:30:19+00:00] +2023-09-19 07:30:19,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108619.0, "order_id": "x-XEKWYICXSSIUT605b13ae9efdc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00995200"}]}, "exchange_trade_id": "4995702", "exchange_order_id": "35599875", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:30:19,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108619.0, "order_id": "x-XEKWYICXSSIUT605b13ae9efdc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35599875", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:30:19,276 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b13ae9efdc0582 completely filled. +2023-09-19 07:31:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b13ae9ec860582. [clock=2023-09-19 07:31:11+00:00] +2023-09-19 07:31:11,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243894849891157740558724318 amount: 80. +2023-09-19 07:31:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:31:11,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108671.0, "order_id": "x-XEKWYICXBSIUT605b13ae9ec860582", "exchange_order_id": "35599876", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:31:11,128 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b13ae9ec860582. +2023-09-19 07:31:11,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b13e3126830582 for 80.00000000 SEI-USDT. +2023-09-19 07:31:11,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b13e3126830582", "creation_timestamp": 1695108671.0, "exchange_order_id": "35600042", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:32:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b13e3126830582. [clock=2023-09-19 07:32:06+00:00] +2023-09-19 07:32:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243878446974994612138675306 amount: 80. +2023-09-19 07:32:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:32:06,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108726.0, "order_id": "x-XEKWYICXBSIUT605b13e3126830582", "exchange_order_id": "35600042", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:32:06,121 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b13e3126830582. +2023-09-19 07:32:06,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1417862770582 for 80.00000000 SEI-USDT. +2023-09-19 07:32:06,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b1417862770582", "creation_timestamp": 1695108726.0, "exchange_order_id": "35600122", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:33:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1417862770582. [clock=2023-09-19 07:33:01+00:00] +2023-09-19 07:33:01,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 07:33:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:33:01,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108781.0, "order_id": "x-XEKWYICXBSIUT605b1417862770582", "exchange_order_id": "35600122", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:33:01,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1417862770582. +2023-09-19 07:33:01,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b144bf9cc90582 for 80.00000000 SEI-USDT. +2023-09-19 07:33:01,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b144bf9cc90582", "creation_timestamp": 1695108781.0, "exchange_order_id": "35600587", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:33:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b144bf9cc90582. [clock=2023-09-19 07:33:56+00:00] +2023-09-19 07:33:56,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-19 07:33:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:33:56,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108836.0, "order_id": "x-XEKWYICXBSIUT605b144bf9cc90582", "exchange_order_id": "35600587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:33:56,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b144bf9cc90582. +2023-09-19 07:33:56,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b14806dcc70582 for 80.00000000 SEI-USDT. +2023-09-19 07:33:56,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b14806dcc70582", "creation_timestamp": 1695108836.0, "exchange_order_id": "35600765", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:34:21,987 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b14806dcc70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:34:21,988 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:34:21+00:00] +2023-09-19 07:34:22,009 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108861.0, "order_id": "x-XEKWYICXBSIUT605b14806dcc70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4995835", "exchange_order_id": "35600765", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:34:22,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108861.0, "order_id": "x-XEKWYICXBSIUT605b14806dcc70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35600765", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:34:22,022 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b14806dcc70582 completely filled. +2023-09-19 07:34:51,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243672514739875057529887498 amount: 80. +2023-09-19 07:34:51,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248600477237595362104503832 amount: 80. +2023-09-19 07:34:51,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b14b5051890582 for 80.00000000 SEI-USDT. +2023-09-19 07:34:51,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b14b5051890582", "creation_timestamp": 1695108891.0, "exchange_order_id": "35600917", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:34:51,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b14b5054680582 for 80.00000000 SEI-USDT. +2023-09-19 07:34:51,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b14b5054680582", "creation_timestamp": 1695108891.0, "exchange_order_id": "35600918", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:35:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b14b5051890582. [clock=2023-09-19 07:35:46+00:00] +2023-09-19 07:35:46,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b14b5054680582. [clock=2023-09-19 07:35:46+00:00] +2023-09-19 07:35:46,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243746277363194897220657552 amount: 80. +2023-09-19 07:35:46,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:35:46,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108946.0, "order_id": "x-XEKWYICXBSIUT605b14b5051890582", "exchange_order_id": "35600917", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:35:46,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b14b5051890582. +2023-09-19 07:35:46,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b14e9562e70582 for 80.00000000 SEI-USDT. +2023-09-19 07:35:46,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b14e9562e70582", "creation_timestamp": 1695108946.0, "exchange_order_id": "35600998", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:35:46,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695108946.0, "order_id": "x-XEKWYICXSSIUT605b14b5054680582", "exchange_order_id": "35600918", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:35:46,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b14b5054680582. +2023-09-19 07:36:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b14e9562e70582. [clock=2023-09-19 07:36:41+00:00] +2023-09-19 07:36:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242234356103404747100266304 amount: 80. +2023-09-19 07:36:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246324780556990984066361983 amount: 80. +2023-09-19 07:36:41,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109001.0, "order_id": "x-XEKWYICXBSIUT605b14e9562e70582", "exchange_order_id": "35600998", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:36:41,133 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b14e9562e70582. +2023-09-19 07:36:41,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b151dc94320582 for 80.00000000 SEI-USDT. +2023-09-19 07:36:41,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b151dc94320582", "creation_timestamp": 1695109001.0, "exchange_order_id": "35601233", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:36:41,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b151dc91580582 for 80.00000000 SEI-USDT. +2023-09-19 07:36:41,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b151dc91580582", "creation_timestamp": 1695109001.0, "exchange_order_id": "35601234", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:37:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b151dc91580582. [clock=2023-09-19 07:37:36+00:00] +2023-09-19 07:37:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b151dc94320582. [clock=2023-09-19 07:37:36+00:00] +2023-09-19 07:37:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242243077112295254300461164 amount: 80. +2023-09-19 07:37:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:37:36,141 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109056.0, "order_id": "x-XEKWYICXBSIUT605b151dc91580582", "exchange_order_id": "35601234", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:37:36,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b151dc91580582. +2023-09-19 07:37:36,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109056.0, "order_id": "x-XEKWYICXSSIUT605b151dc94320582", "exchange_order_id": "35601233", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:37:36,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b151dc94320582. +2023-09-19 07:37:36,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b15523cae20582 for 80.00000000 SEI-USDT. +2023-09-19 07:37:36,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b15523cae20582", "creation_timestamp": 1695109056.0, "exchange_order_id": "35601309", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:38:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b15523cae20582. [clock=2023-09-19 07:38:31+00:00] +2023-09-19 07:38:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240902258689317886993097005 amount: 80. +2023-09-19 07:38:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244912208898320458659634061 amount: 80. +2023-09-19 07:38:31,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109111.0, "order_id": "x-XEKWYICXBSIUT605b15523cae20582", "exchange_order_id": "35601309", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:38:31,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b15523cae20582. +2023-09-19 07:38:31,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1586b04920582 for 80.00000000 SEI-USDT. +2023-09-19 07:38:31,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b1586b04920582", "creation_timestamp": 1695109111.0, "exchange_order_id": "35601508", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:38:31,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1586b07340582 for 80.00000000 SEI-USDT. +2023-09-19 07:38:31,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b1586b07340582", "creation_timestamp": 1695109111.0, "exchange_order_id": "35601509", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:39:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1586b04920582. [clock=2023-09-19 07:39:26+00:00] +2023-09-19 07:39:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1586b07340582. [clock=2023-09-19 07:39:26+00:00] +2023-09-19 07:39:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239366347496835207800781395 amount: 80. +2023-09-19 07:39:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:39:26,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109166.0, "order_id": "x-XEKWYICXBSIUT605b1586b04920582", "exchange_order_id": "35601508", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:39:26,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1586b04920582. +2023-09-19 07:39:26,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109166.0, "order_id": "x-XEKWYICXSSIUT605b1586b07340582", "exchange_order_id": "35601509", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:39:26,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1586b07340582. +2023-09-19 07:39:26,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b15bb24ee70582 for 80.00000000 SEI-USDT. +2023-09-19 07:39:26,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b15bb24ee70582", "creation_timestamp": 1695109166.0, "exchange_order_id": "35601593", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:40:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b15bb24ee70582. [clock=2023-09-19 07:40:21+00:00] +2023-09-19 07:40:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241085583239891822144363181 amount: 80. +2023-09-19 07:40:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245487860517420844456807071 amount: 80. +2023-09-19 07:40:21,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109221.0, "order_id": "x-XEKWYICXBSIUT605b15bb24ee70582", "exchange_order_id": "35601593", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:40:21,134 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b15bb24ee70582. +2023-09-19 07:40:21,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b15ef97c9e0582 for 80.00000000 SEI-USDT. +2023-09-19 07:40:21,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b15ef97c9e0582", "creation_timestamp": 1695109221.0, "exchange_order_id": "35601669", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:40:21,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b15ef97ee90582 for 80.00000000 SEI-USDT. +2023-09-19 07:40:21,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b15ef97ee90582", "creation_timestamp": 1695109221.0, "exchange_order_id": "35601670", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:41:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b15ef97c9e0582. [clock=2023-09-19 07:41:16+00:00] +2023-09-19 07:41:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b15ef97ee90582. [clock=2023-09-19 07:41:16+00:00] +2023-09-19 07:41:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241289267170307061158107567 amount: 80. +2023-09-19 07:41:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:41:16,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109276.0, "order_id": "x-XEKWYICXBSIUT605b15ef97c9e0582", "exchange_order_id": "35601669", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:41:16,201 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b15ef97c9e0582. +2023-09-19 07:41:16,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109276.0, "order_id": "x-XEKWYICXSSIUT605b15ef97ee90582", "exchange_order_id": "35601670", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:41:16,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b15ef97ee90582. +2023-09-19 07:41:16,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b16240bffd0582 for 80.00000000 SEI-USDT. +2023-09-19 07:41:16,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b16240bffd0582", "creation_timestamp": 1695109276.0, "exchange_order_id": "35601766", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:42:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b16240bffd0582. [clock=2023-09-19 07:42:11+00:00] +2023-09-19 07:42:11,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238266228790604690475439466 amount: 80. +2023-09-19 07:42:11,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242655207626274790659893096 amount: 80. +2023-09-19 07:42:11,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109331.0, "order_id": "x-XEKWYICXBSIUT605b16240bffd0582", "exchange_order_id": "35601766", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:42:11,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b16240bffd0582. +2023-09-19 07:42:11,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b16588490a0582 for 80.00000000 SEI-USDT. +2023-09-19 07:42:11,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b16588490a0582", "creation_timestamp": 1695109331.0, "exchange_order_id": "35602023", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:42:11,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b165884c950582 for 80.00000000 SEI-USDT. +2023-09-19 07:42:11,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b165884c950582", "creation_timestamp": 1695109331.0, "exchange_order_id": "35602024", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:42:51,236 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 07:43:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b16588490a0582. [clock=2023-09-19 07:43:06+00:00] +2023-09-19 07:43:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b165884c950582. [clock=2023-09-19 07:43:06+00:00] +2023-09-19 07:43:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237182279372086792738860130 amount: 80. +2023-09-19 07:43:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:43:06,149 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109386.0, "order_id": "x-XEKWYICXBSIUT605b16588490a0582", "exchange_order_id": "35602023", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:43:06,149 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b16588490a0582. +2023-09-19 07:43:06,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109386.0, "order_id": "x-XEKWYICXSSIUT605b165884c950582", "exchange_order_id": "35602024", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:43:06,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b165884c950582. +2023-09-19 07:43:06,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b168cf34490582 for 80.00000000 SEI-USDT. +2023-09-19 07:43:06,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b168cf34490582", "creation_timestamp": 1695109386.0, "exchange_order_id": "35602588", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:44:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b168cf34490582. [clock=2023-09-19 07:44:01+00:00] +2023-09-19 07:44:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237184940744234785876351211 amount: 80. +2023-09-19 07:44:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241705515469920376492472916 amount: 80. +2023-09-19 07:44:01,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109441.0, "order_id": "x-XEKWYICXBSIUT605b168cf34490582", "exchange_order_id": "35602588", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:44:01,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b168cf34490582. +2023-09-19 07:44:01,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b16c166b040582 for 80.00000000 SEI-USDT. +2023-09-19 07:44:01,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b16c166b040582", "creation_timestamp": 1695109441.0, "exchange_order_id": "35602676", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:44:01,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b16c166e6f0582 for 80.00000000 SEI-USDT. +2023-09-19 07:44:01,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b16c166e6f0582", "creation_timestamp": 1695109441.0, "exchange_order_id": "35602677", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:44:24,039 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b16c166e6f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:44:24,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:44:24+00:00] +2023-09-19 07:44:24,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109464.0, "order_id": "x-XEKWYICXSSIUT605b16c166e6f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "4995974", "exchange_order_id": "35602677", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:44:24,080 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109464.0, "order_id": "x-XEKWYICXSSIUT605b16c166e6f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35602677", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:44:24,080 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b16c166e6f0582 completely filled. +2023-09-19 07:44:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b16c166b040582. [clock=2023-09-19 07:44:56+00:00] +2023-09-19 07:44:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237595106574784881409507955 amount: 80. +2023-09-19 07:44:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:44:56,131 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109496.0, "order_id": "x-XEKWYICXBSIUT605b16c166b040582", "exchange_order_id": "35602676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:44:56,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b16c166b040582. +2023-09-19 07:44:56,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b16f5dae890582 for 80.00000000 SEI-USDT. +2023-09-19 07:44:56,214 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b16f5dae890582", "creation_timestamp": 1695109496.0, "exchange_order_id": "35602828", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:45:51,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b16f5dae890582. [clock=2023-09-19 07:45:51+00:00] +2023-09-19 07:45:51,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240288637347234939755840956 amount: 80. +2023-09-19 07:45:51,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:45:51,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109551.0, "order_id": "x-XEKWYICXBSIUT605b16f5dae890582", "exchange_order_id": "35602828", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:45:51,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b16f5dae890582. +2023-09-19 07:45:51,402 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b172a58af60582 for 80.00000000 SEI-USDT. +2023-09-19 07:45:51,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b172a58af60582", "creation_timestamp": 1695109551.0, "exchange_order_id": "35602980", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:46:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b172a58af60582. [clock=2023-09-19 07:46:46+00:00] +2023-09-19 07:46:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240447399653028716670119300 amount: 80. +2023-09-19 07:46:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:46:46,132 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109606.0, "order_id": "x-XEKWYICXBSIUT605b172a58af60582", "exchange_order_id": "35602980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:46:46,132 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b172a58af60582. +2023-09-19 07:46:46,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b175ec1e5b0582 for 80.00000000 SEI-USDT. +2023-09-19 07:46:46,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b175ec1e5b0582", "creation_timestamp": 1695109606.0, "exchange_order_id": "35603063", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:47:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b175ec1e5b0582. [clock=2023-09-19 07:47:41+00:00] +2023-09-19 07:47:41,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-19 07:47:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:47:41,126 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109661.0, "order_id": "x-XEKWYICXBSIUT605b175ec1e5b0582", "exchange_order_id": "35603063", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:47:41,126 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b175ec1e5b0582. +2023-09-19 07:47:41,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1793358e90582 for 80.00000000 SEI-USDT. +2023-09-19 07:47:41,207 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b1793358e90582", "creation_timestamp": 1695109661.0, "exchange_order_id": "35603272", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:48:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1793358e90582. [clock=2023-09-19 07:48:36+00:00] +2023-09-19 07:48:36,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 07:48:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:48:36,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109716.0, "order_id": "x-XEKWYICXBSIUT605b1793358e90582", "exchange_order_id": "35603272", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:48:36,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1793358e90582. +2023-09-19 07:48:36,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b17c7a935b0582 for 80.00000000 SEI-USDT. +2023-09-19 07:48:36,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b17c7a935b0582", "creation_timestamp": 1695109716.0, "exchange_order_id": "35603612", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:49:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b17c7a935b0582. [clock=2023-09-19 07:49:31+00:00] +2023-09-19 07:49:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 07:49:31,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:49:31,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109771.0, "order_id": "x-XEKWYICXBSIUT605b17c7a935b0582", "exchange_order_id": "35603612", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:49:31,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b17c7a935b0582. +2023-09-19 07:49:31,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b17fc1df4a0582 for 80.00000000 SEI-USDT. +2023-09-19 07:49:31,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b17fc1df4a0582", "creation_timestamp": 1695109771.0, "exchange_order_id": "35603849", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:50:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b17fc1df4a0582. [clock=2023-09-19 07:50:26+00:00] +2023-09-19 07:50:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 07:50:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:50:26,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109826.0, "order_id": "x-XEKWYICXBSIUT605b17fc1df4a0582", "exchange_order_id": "35603849", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:50:26,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b17fc1df4a0582. +2023-09-19 07:50:26,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b183090f360582 for 80.00000000 SEI-USDT. +2023-09-19 07:50:26,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b183090f360582", "creation_timestamp": 1695109826.0, "exchange_order_id": "35603935", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:50:43,748 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b183090f360582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 07:50:43,748 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 07:50:43+00:00] +2023-09-19 07:50:43,786 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109843.0, "order_id": "x-XEKWYICXBSIUT605b183090f360582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996116", "exchange_order_id": "35603935", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 07:50:43,812 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109843.0, "order_id": "x-XEKWYICXBSIUT605b183090f360582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35603935", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 07:50:43,813 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b183090f360582 completely filled. +2023-09-19 07:51:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236065076876760451658349939 amount: 80. +2023-09-19 07:51:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246480652349036664313840599 amount: 80. +2023-09-19 07:51:21,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1865047550582 for 80.00000000 SEI-USDT. +2023-09-19 07:51:21,152 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b1865047550582", "creation_timestamp": 1695109881.0, "exchange_order_id": "35604745", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:51:21,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b186504cec0582 for 80.00000000 SEI-USDT. +2023-09-19 07:51:21,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b186504cec0582", "creation_timestamp": 1695109881.0, "exchange_order_id": "35604746", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:52:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1865047550582. [clock=2023-09-19 07:52:16+00:00] +2023-09-19 07:52:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b186504cec0582. [clock=2023-09-19 07:52:16+00:00] +2023-09-19 07:52:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236723305372135138128009518 amount: 80. +2023-09-19 07:52:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:52:16,104 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109936.0, "order_id": "x-XEKWYICXBSIUT605b1865047550582", "exchange_order_id": "35604745", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:52:16,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1865047550582. +2023-09-19 07:52:16,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1899788490582 for 80.00000000 SEI-USDT. +2023-09-19 07:52:16,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b1899788490582", "creation_timestamp": 1695109936.0, "exchange_order_id": "35604855", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:52:16,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109936.0, "order_id": "x-XEKWYICXSSIUT605b186504cec0582", "exchange_order_id": "35604746", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:52:16,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b186504cec0582. +2023-09-19 07:53:11,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1899788490582. [clock=2023-09-19 07:53:11+00:00] +2023-09-19 07:53:11,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236727491241591924704126516 amount: 80. +2023-09-19 07:53:11,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243893579678415510702301069 amount: 80. +2023-09-19 07:53:11,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109991.0, "order_id": "x-XEKWYICXBSIUT605b1899788490582", "exchange_order_id": "35604855", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:53:11,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1899788490582. +2023-09-19 07:53:11,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b18cdee4e90582 for 80.00000000 SEI-USDT. +2023-09-19 07:53:11,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b18cdee4e90582", "creation_timestamp": 1695109991.0, "exchange_order_id": "35604955", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:53:11,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b18cdee7000582 for 80.00000000 SEI-USDT. +2023-09-19 07:53:11,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695109991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b18cdee7000582", "creation_timestamp": 1695109991.0, "exchange_order_id": "35604956", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:54:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b18cdee4e90582. [clock=2023-09-19 07:54:06+00:00] +2023-09-19 07:54:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b18cdee7000582. [clock=2023-09-19 07:54:06+00:00] +2023-09-19 07:54:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237440907400403043510223311 amount: 80. +2023-09-19 07:54:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:54:06,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110046.0, "order_id": "x-XEKWYICXBSIUT605b18cdee4e90582", "exchange_order_id": "35604955", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:54:06,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b18cdee4e90582. +2023-09-19 07:54:06,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110046.0, "order_id": "x-XEKWYICXSSIUT605b18cdee7000582", "exchange_order_id": "35604956", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:54:06,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b18cdee7000582. +2023-09-19 07:54:06,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1902601ff0582 for 80.00000000 SEI-USDT. +2023-09-19 07:54:06,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b1902601ff0582", "creation_timestamp": 1695110046.0, "exchange_order_id": "35605140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:55:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1902601ff0582. [clock=2023-09-19 07:55:01+00:00] +2023-09-19 07:55:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237440907400403043510223311 amount: 80. +2023-09-19 07:55:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243876256166309584481644608 amount: 80. +2023-09-19 07:55:01,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110101.0, "order_id": "x-XEKWYICXBSIUT605b1902601ff0582", "exchange_order_id": "35605140", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:55:01,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1902601ff0582. +2023-09-19 07:55:01,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1936d3b0f0582 for 80.00000000 SEI-USDT. +2023-09-19 07:55:01,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b1936d3b0f0582", "creation_timestamp": 1695110101.0, "exchange_order_id": "35605274", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:55:01,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1936d3e0b0582 for 80.00000000 SEI-USDT. +2023-09-19 07:55:01,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b1936d3e0b0582", "creation_timestamp": 1695110101.0, "exchange_order_id": "35605275", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:55:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1936d3b0f0582. [clock=2023-09-19 07:55:56+00:00] +2023-09-19 07:55:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1936d3e0b0582. [clock=2023-09-19 07:55:56+00:00] +2023-09-19 07:55:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237230680366164867487119780 amount: 80. +2023-09-19 07:55:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:55:56,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110156.0, "order_id": "x-XEKWYICXBSIUT605b1936d3b0f0582", "exchange_order_id": "35605274", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:55:56,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1936d3b0f0582. +2023-09-19 07:55:56,222 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110156.0, "order_id": "x-XEKWYICXSSIUT605b1936d3e0b0582", "exchange_order_id": "35605275", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:55:56,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1936d3e0b0582. +2023-09-19 07:55:56,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b196b479620582 for 80.00000000 SEI-USDT. +2023-09-19 07:55:56,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b196b479620582", "creation_timestamp": 1695110156.0, "exchange_order_id": "35605562", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:56:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b196b479620582. [clock=2023-09-19 07:56:51+00:00] +2023-09-19 07:56:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237624741025417917315777334 amount: 80. +2023-09-19 07:56:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242383714886710836843725345 amount: 80. +2023-09-19 07:56:51,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110211.0, "order_id": "x-XEKWYICXBSIUT605b196b479620582", "exchange_order_id": "35605562", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:56:51,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b196b479620582. +2023-09-19 07:56:51,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b199fbb7e60582 for 80.00000000 SEI-USDT. +2023-09-19 07:56:51,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b199fbb7e60582", "creation_timestamp": 1695110211.0, "exchange_order_id": "35605697", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:56:51,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b199fbb4050582 for 80.00000000 SEI-USDT. +2023-09-19 07:56:51,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b199fbb4050582", "creation_timestamp": 1695110211.0, "exchange_order_id": "35605698", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:57:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b199fbb4050582. [clock=2023-09-19 07:57:46+00:00] +2023-09-19 07:57:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b199fbb7e60582. [clock=2023-09-19 07:57:46+00:00] +2023-09-19 07:57:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236931748658888026032711488 amount: 80. +2023-09-19 07:57:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:57:46,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110266.0, "order_id": "x-XEKWYICXBSIUT605b199fbb4050582", "exchange_order_id": "35605698", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:57:46,137 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b199fbb4050582. +2023-09-19 07:57:46,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110266.0, "order_id": "x-XEKWYICXSSIUT605b199fbb7e60582", "exchange_order_id": "35605697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:57:46,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b199fbb7e60582. +2023-09-19 07:57:46,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b19d42f2470582 for 80.00000000 SEI-USDT. +2023-09-19 07:57:46,228 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b19d42f2470582", "creation_timestamp": 1695110266.0, "exchange_order_id": "35605910", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:58:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b19d42f2470582. [clock=2023-09-19 07:58:41+00:00] +2023-09-19 07:58:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237847461536819371480886149 amount: 80. +2023-09-19 07:58:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241837838611254217523972986 amount: 80. +2023-09-19 07:58:41,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110321.0, "order_id": "x-XEKWYICXBSIUT605b19d42f2470582", "exchange_order_id": "35605910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:58:41,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b19d42f2470582. +2023-09-19 07:58:41,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1a08a2ec50582 for 80.00000000 SEI-USDT. +2023-09-19 07:58:41,219 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b1a08a2ec50582", "creation_timestamp": 1695110321.0, "exchange_order_id": "35606144", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:58:41,221 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1a08a2ab10582 for 80.00000000 SEI-USDT. +2023-09-19 07:58:41,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b1a08a2ab10582", "creation_timestamp": 1695110321.0, "exchange_order_id": "35606145", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 07:59:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1a08a2ab10582. [clock=2023-09-19 07:59:36+00:00] +2023-09-19 07:59:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1a08a2ec50582. [clock=2023-09-19 07:59:36+00:00] +2023-09-19 07:59:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237104411277660204158160059 amount: 80. +2023-09-19 07:59:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 07:59:36,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110376.0, "order_id": "x-XEKWYICXBSIUT605b1a08a2ab10582", "exchange_order_id": "35606145", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:59:36,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1a08a2ab10582. +2023-09-19 07:59:36,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110376.0, "order_id": "x-XEKWYICXSSIUT605b1a08a2ec50582", "exchange_order_id": "35606144", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 07:59:36,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1a08a2ec50582. +2023-09-19 07:59:36,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1a3d16bfc0582 for 80.00000000 SEI-USDT. +2023-09-19 07:59:36,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b1a3d16bfc0582", "creation_timestamp": 1695110376.0, "exchange_order_id": "35606298", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:00:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1a3d16bfc0582. [clock=2023-09-19 08:00:31+00:00] +2023-09-19 08:00:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239581526721652224871542401 amount: 80. +2023-09-19 08:00:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245333900226219446480365997 amount: 80. +2023-09-19 08:00:31,143 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110431.0, "order_id": "x-XEKWYICXBSIUT605b1a3d16bfc0582", "exchange_order_id": "35606298", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:00:31,144 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1a3d16bfc0582. +2023-09-19 08:00:31,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1a7189e1f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:00:31,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b1a7189e1f0582", "creation_timestamp": 1695110431.0, "exchange_order_id": "35606668", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:00:31,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1a718a0610582 for 80.00000000 SEI-USDT. +2023-09-19 08:00:31,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b1a718a0610582", "creation_timestamp": 1695110431.0, "exchange_order_id": "35606669", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:01:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1a7189e1f0582. [clock=2023-09-19 08:01:26+00:00] +2023-09-19 08:01:26,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1a718a0610582. [clock=2023-09-19 08:01:26+00:00] +2023-09-19 08:01:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-19 08:01:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 08:01:26,156 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110486.0, "order_id": "x-XEKWYICXBSIUT605b1a7189e1f0582", "exchange_order_id": "35606668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:01:26,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1a7189e1f0582. +2023-09-19 08:01:26,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1aa5fe74a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:01:26,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b1aa5fe74a0582", "creation_timestamp": 1695110486.0, "exchange_order_id": "35607111", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:01:26,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110486.0, "order_id": "x-XEKWYICXSSIUT605b1a718a0610582", "exchange_order_id": "35606669", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:01:26,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1a718a0610582. +2023-09-19 08:01:28,455 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b1aa5fe74a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:01:28,456 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:01:28+00:00] +2023-09-19 08:01:28,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110488.0, "order_id": "x-XEKWYICXBSIUT605b1aa5fe74a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996503", "exchange_order_id": "35607111", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:01:28,496 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110488.0, "order_id": "x-XEKWYICXBSIUT605b1aa5fe74a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35607111", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:01:28,496 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b1aa5fe74a0582 completely filled. +2023-09-19 08:02:21,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239621790819388710875360161 amount: 80. +2023-09-19 08:02:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246180272600182905472417529 amount: 80. +2023-09-19 08:02:21,116 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ada711af0582 for 80.00000000 SEI-USDT. +2023-09-19 08:02:21,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b1ada711af0582", "creation_timestamp": 1695110541.0, "exchange_order_id": "35607383", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:02:21,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ada716350582 for 80.00000000 SEI-USDT. +2023-09-19 08:02:21,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b1ada716350582", "creation_timestamp": 1695110541.0, "exchange_order_id": "35607384", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:03:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ada711af0582. [clock=2023-09-19 08:03:16+00:00] +2023-09-19 08:03:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ada716350582. [clock=2023-09-19 08:03:16+00:00] +2023-09-19 08:03:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240068523354563596905858892 amount: 80. +2023-09-19 08:03:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246030995494289652661464649 amount: 80. +2023-09-19 08:03:16,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110596.0, "order_id": "x-XEKWYICXBSIUT605b1ada711af0582", "exchange_order_id": "35607383", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:03:16,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ada711af0582. +2023-09-19 08:03:16,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110596.0, "order_id": "x-XEKWYICXSSIUT605b1ada716350582", "exchange_order_id": "35607384", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:03:16,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ada716350582. +2023-09-19 08:03:16,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1b0ee58490582 for 80.00000000 SEI-USDT. +2023-09-19 08:03:16,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b1b0ee58490582", "creation_timestamp": 1695110596.0, "exchange_order_id": "35607590", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:03:16,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1b0ee55bf0582 for 80.00000000 SEI-USDT. +2023-09-19 08:03:16,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b1b0ee55bf0582", "creation_timestamp": 1695110596.0, "exchange_order_id": "35607591", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:04:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1b0ee55bf0582. [clock=2023-09-19 08:04:11+00:00] +2023-09-19 08:04:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1b0ee58490582. [clock=2023-09-19 08:04:11+00:00] +2023-09-19 08:04:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240665001846167632078842692 amount: 80. +2023-09-19 08:04:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248152563541418561399715944 amount: 80. +2023-09-19 08:04:11,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110651.0, "order_id": "x-XEKWYICXBSIUT605b1b0ee55bf0582", "exchange_order_id": "35607591", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:04:11,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1b0ee55bf0582. +2023-09-19 08:04:11,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110651.0, "order_id": "x-XEKWYICXSSIUT605b1b0ee58490582", "exchange_order_id": "35607590", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:04:11,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1b0ee58490582. +2023-09-19 08:04:11,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1b43593bc0582 for 80.00000000 SEI-USDT. +2023-09-19 08:04:11,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b1b43593bc0582", "creation_timestamp": 1695110651.0, "exchange_order_id": "35607750", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:04:11,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1b435977e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:04:11,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b1b435977e0582", "creation_timestamp": 1695110651.0, "exchange_order_id": "35607751", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:05:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1b43593bc0582. [clock=2023-09-19 08:05:06+00:00] +2023-09-19 08:05:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1b435977e0582. [clock=2023-09-19 08:05:06+00:00] +2023-09-19 08:05:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241124350581578913036542264 amount: 80. +2023-09-19 08:05:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248056707252809873831329842 amount: 80. +2023-09-19 08:05:06,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110706.0, "order_id": "x-XEKWYICXBSIUT605b1b43593bc0582", "exchange_order_id": "35607750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:05:06,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1b43593bc0582. +2023-09-19 08:05:06,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1b77cd4be0582 for 80.00000000 SEI-USDT. +2023-09-19 08:05:06,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b1b77cd4be0582", "creation_timestamp": 1695110706.0, "exchange_order_id": "35607856", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:05:06,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1b77cd2200582 for 80.00000000 SEI-USDT. +2023-09-19 08:05:06,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b1b77cd2200582", "creation_timestamp": 1695110706.0, "exchange_order_id": "35607857", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:05:06,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110706.0, "order_id": "x-XEKWYICXSSIUT605b1b435977e0582", "exchange_order_id": "35607751", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:05:06,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1b435977e0582. +2023-09-19 08:06:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1b77cd2200582. [clock=2023-09-19 08:06:01+00:00] +2023-09-19 08:06:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1b77cd4be0582. [clock=2023-09-19 08:06:01+00:00] +2023-09-19 08:06:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242314598122870876065064905 amount: 80. +2023-09-19 08:06:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250364316501361946340772781 amount: 80. +2023-09-19 08:06:01,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110761.0, "order_id": "x-XEKWYICXBSIUT605b1b77cd2200582", "exchange_order_id": "35607857", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:06:01,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1b77cd2200582. +2023-09-19 08:06:01,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110761.0, "order_id": "x-XEKWYICXSSIUT605b1b77cd4be0582", "exchange_order_id": "35607856", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:06:01,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1b77cd4be0582. +2023-09-19 08:06:01,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1bac411b20582 for 80.00000000 SEI-USDT. +2023-09-19 08:06:01,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b1bac411b20582", "creation_timestamp": 1695110761.0, "exchange_order_id": "35608056", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:06:01,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1bac40dba0582 for 80.00000000 SEI-USDT. +2023-09-19 08:06:01,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b1bac40dba0582", "creation_timestamp": 1695110761.0, "exchange_order_id": "35608057", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:06:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1bac40dba0582. [clock=2023-09-19 08:06:56+00:00] +2023-09-19 08:06:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1bac411b20582. [clock=2023-09-19 08:06:56+00:00] +2023-09-19 08:06:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243467122834052033274552772 amount: 80. +2023-09-19 08:06:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249729314775015753852618058 amount: 80. +2023-09-19 08:06:56,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110816.0, "order_id": "x-XEKWYICXBSIUT605b1bac40dba0582", "exchange_order_id": "35608057", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:06:56,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1bac40dba0582. +2023-09-19 08:06:56,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1be0b46210582 for 80.00000000 SEI-USDT. +2023-09-19 08:06:56,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b1be0b46210582", "creation_timestamp": 1695110816.0, "exchange_order_id": "35608332", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:06:56,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1be0b49d70582 for 80.00000000 SEI-USDT. +2023-09-19 08:06:56,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b1be0b49d70582", "creation_timestamp": 1695110816.0, "exchange_order_id": "35608331", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:06:56,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110816.0, "order_id": "x-XEKWYICXSSIUT605b1bac411b20582", "exchange_order_id": "35608056", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:06:56,423 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1bac411b20582. +2023-09-19 08:07:51,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1be0b46210582. [clock=2023-09-19 08:07:51+00:00] +2023-09-19 08:07:51,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1be0b49d70582. [clock=2023-09-19 08:07:51+00:00] +2023-09-19 08:07:51,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243745188332188795534259174 amount: 80. +2023-09-19 08:07:51,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249725064062501337089560854 amount: 80. +2023-09-19 08:07:51,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110871.0, "order_id": "x-XEKWYICXBSIUT605b1be0b46210582", "exchange_order_id": "35608332", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:07:51,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1be0b46210582. +2023-09-19 08:07:51,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1c15303090582 for 80.00000000 SEI-USDT. +2023-09-19 08:07:51,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b1c15303090582", "creation_timestamp": 1695110871.0, "exchange_order_id": "35608642", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:07:51,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110871.0, "order_id": "x-XEKWYICXSSIUT605b1be0b49d70582", "exchange_order_id": "35608331", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:07:51,338 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1be0b49d70582. +2023-09-19 08:07:51,339 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1c153064f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:07:51,350 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b1c153064f0582", "creation_timestamp": 1695110871.0, "exchange_order_id": "35608643", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:08:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1c15303090582. [clock=2023-09-19 08:08:46+00:00] +2023-09-19 08:08:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1c153064f0582. [clock=2023-09-19 08:08:46+00:00] +2023-09-19 08:08:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243802697895612424058736546 amount: 80. +2023-09-19 08:08:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248451443968671716794979208 amount: 80. +2023-09-19 08:08:46,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110926.0, "order_id": "x-XEKWYICXBSIUT605b1c15303090582", "exchange_order_id": "35608642", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:08:46,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1c15303090582. +2023-09-19 08:08:46,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110926.0, "order_id": "x-XEKWYICXSSIUT605b1c153064f0582", "exchange_order_id": "35608643", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:08:46,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1c153064f0582. +2023-09-19 08:08:46,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1c499c41b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:08:46,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b1c499c41b0582", "creation_timestamp": 1695110926.0, "exchange_order_id": "35608827", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:08:46,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1c499bf330582 for 80.00000000 SEI-USDT. +2023-09-19 08:08:46,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b1c499bf330582", "creation_timestamp": 1695110926.0, "exchange_order_id": "35608828", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:09:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1c499bf330582. [clock=2023-09-19 08:09:41+00:00] +2023-09-19 08:09:41,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1c499c41b0582. [clock=2023-09-19 08:09:41+00:00] +2023-09-19 08:09:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244809786437485860406464971 amount: 80. +2023-09-19 08:09:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249293782669500912306774438 amount: 80. +2023-09-19 08:09:41,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110981.0, "order_id": "x-XEKWYICXBSIUT605b1c499bf330582", "exchange_order_id": "35608828", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:09:41,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1c499bf330582. +2023-09-19 08:09:41,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110981.0, "order_id": "x-XEKWYICXSSIUT605b1c499c41b0582", "exchange_order_id": "35608827", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:09:41,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1c499c41b0582. +2023-09-19 08:09:41,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1c7e102390582 for 80.00000000 SEI-USDT. +2023-09-19 08:09:41,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b1c7e102390582", "creation_timestamp": 1695110981.0, "exchange_order_id": "35609086", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:09:41,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1c7e0ff120582 for 80.00000000 SEI-USDT. +2023-09-19 08:09:41,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695110981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b1c7e0ff120582", "creation_timestamp": 1695110981.0, "exchange_order_id": "35609087", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:10:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1c7e0ff120582. [clock=2023-09-19 08:10:36+00:00] +2023-09-19 08:10:36,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1c7e102390582. [clock=2023-09-19 08:10:36+00:00] +2023-09-19 08:10:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12460000 amount: 80. +2023-09-19 08:10:36,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251861126976353638085154425 amount: 80. +2023-09-19 08:10:36,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111036.0, "order_id": "x-XEKWYICXBSIUT605b1c7e0ff120582", "exchange_order_id": "35609087", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:10:36,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1c7e0ff120582. +2023-09-19 08:10:36,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1cb28420c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:10:36,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b1cb28420c0582", "creation_timestamp": 1695111036.0, "exchange_order_id": "35609289", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:10:36,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111036.0, "order_id": "x-XEKWYICXSSIUT605b1c7e102390582", "exchange_order_id": "35609086", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:10:36,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1c7e102390582. +2023-09-19 08:10:36,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1cb283e740582 for 80.00000000 SEI-USDT. +2023-09-19 08:10:36,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111036.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b1cb283e740582", "creation_timestamp": 1695111036.0, "exchange_order_id": "35609290", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:10:38,131 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b1cb283e740582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:10:38,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:10:38+00:00] +2023-09-19 08:10:38,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111038.0, "order_id": "x-XEKWYICXBSIUT605b1cb283e740582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996638", "exchange_order_id": "35609290", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:10:38,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111038.0, "order_id": "x-XEKWYICXBSIUT605b1cb283e740582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35609290", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:10:38,182 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b1cb283e740582 completely filled. +2023-09-19 08:11:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1cb28420c0582. [clock=2023-09-19 08:11:31+00:00] +2023-09-19 08:11:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-19 08:11:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249182223027122568353419641 amount: 80. +2023-09-19 08:11:31,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111091.0, "order_id": "x-XEKWYICXSSIUT605b1cb28420c0582", "exchange_order_id": "35609289", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:11:31,142 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1cb28420c0582. +2023-09-19 08:11:31,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ce6f71090582 for 80.00000000 SEI-USDT. +2023-09-19 08:11:31,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b1ce6f71090582", "creation_timestamp": 1695111091.0, "exchange_order_id": "35609477", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:11:31,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ce6f6dba0582 for 80.00000000 SEI-USDT. +2023-09-19 08:11:31,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111091.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b1ce6f6dba0582", "creation_timestamp": 1695111091.0, "exchange_order_id": "35609478", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:12:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ce6f6dba0582. [clock=2023-09-19 08:12:26+00:00] +2023-09-19 08:12:26,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ce6f71090582. [clock=2023-09-19 08:12:26+00:00] +2023-09-19 08:12:26,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-19 08:12:26,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248252589522736198430790114 amount: 80. +2023-09-19 08:12:26,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111146.0, "order_id": "x-XEKWYICXBSIUT605b1ce6f6dba0582", "exchange_order_id": "35609478", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:12:26,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ce6f6dba0582. +2023-09-19 08:12:26,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111146.0, "order_id": "x-XEKWYICXSSIUT605b1ce6f71090582", "exchange_order_id": "35609477", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:12:26,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ce6f71090582. +2023-09-19 08:12:26,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1d1b6c83c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:12:26,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b1d1b6c83c0582", "creation_timestamp": 1695111146.0, "exchange_order_id": "35609557", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:12:26,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1d1b6cc5f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:12:26,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b1d1b6cc5f0582", "creation_timestamp": 1695111146.0, "exchange_order_id": "35609558", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:12:40,439 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b1d1b6c83c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:12:40,440 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:12:40+00:00] +2023-09-19 08:12:40,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111160.0, "order_id": "x-XEKWYICXBSIUT605b1d1b6c83c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996678", "exchange_order_id": "35609557", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:12:40,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111160.0, "order_id": "x-XEKWYICXBSIUT605b1d1b6c83c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35609557", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:12:40,477 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b1d1b6c83c0582 completely filled. +2023-09-19 08:12:51,261 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 08:13:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1d1b6cc5f0582. [clock=2023-09-19 08:13:21+00:00] +2023-09-19 08:13:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12450000 amount: 80. +2023-09-19 08:13:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250338240768161432106775726 amount: 80. +2023-09-19 08:13:21,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111201.0, "order_id": "x-XEKWYICXSSIUT605b1d1b6cc5f0582", "exchange_order_id": "35609558", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:13:21,148 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1d1b6cc5f0582. +2023-09-19 08:13:21,215 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1d4fde5c70582 for 80.00000000 SEI-USDT. +2023-09-19 08:13:21,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b1d4fde5c70582", "creation_timestamp": 1695111201.0, "exchange_order_id": "35609849", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:13:21,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1d4fde8de0582 for 80.00000000 SEI-USDT. +2023-09-19 08:13:21,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b1d4fde8de0582", "creation_timestamp": 1695111201.0, "exchange_order_id": "35609850", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:14:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1d4fde5c70582. [clock=2023-09-19 08:14:16+00:00] +2023-09-19 08:14:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1d4fde8de0582. [clock=2023-09-19 08:14:16+00:00] +2023-09-19 08:14:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12460000 amount: 80. +2023-09-19 08:14:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251078306351112091633039453 amount: 80. +2023-09-19 08:14:16,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111256.0, "order_id": "x-XEKWYICXBSIUT605b1d4fde5c70582", "exchange_order_id": "35609849", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:14:16,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1d4fde5c70582. +2023-09-19 08:14:16,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1d84523a30582 for 80.00000000 SEI-USDT. +2023-09-19 08:14:16,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b1d84523a30582", "creation_timestamp": 1695111256.0, "exchange_order_id": "35610034", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:14:16,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111256.0, "order_id": "x-XEKWYICXSSIUT605b1d4fde8de0582", "exchange_order_id": "35609850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:14:16,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1d4fde8de0582. +2023-09-19 08:14:16,328 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1d84526f20582 for 80.00000000 SEI-USDT. +2023-09-19 08:14:16,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b1d84526f20582", "creation_timestamp": 1695111256.0, "exchange_order_id": "35610035", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:14:47,286 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b1d84523a30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:14:47,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:14:47+00:00] +2023-09-19 08:14:47,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111287.0, "order_id": "x-XEKWYICXBSIUT605b1d84523a30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996776", "exchange_order_id": "35610034", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:14:47,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111287.0, "order_id": "x-XEKWYICXBSIUT605b1d84523a30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35610034", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:14:47,325 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b1d84523a30582 completely filled. +2023-09-19 08:15:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1d84526f20582. [clock=2023-09-19 08:15:11+00:00] +2023-09-19 08:15:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245785679485987427324583611 amount: 80. +2023-09-19 08:15:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251465537765093622517605853 amount: 80. +2023-09-19 08:15:11,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111311.0, "order_id": "x-XEKWYICXSSIUT605b1d84526f20582", "exchange_order_id": "35610035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:15:11,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1d84526f20582. +2023-09-19 08:15:11,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1db8c5d7a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:15:11,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b1db8c5d7a0582", "creation_timestamp": 1695111311.0, "exchange_order_id": "35610354", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:15:11,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1db8c5f9a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:15:11,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b1db8c5f9a0582", "creation_timestamp": 1695111311.0, "exchange_order_id": "35610356", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:16:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1db8c5d7a0582. [clock=2023-09-19 08:16:06+00:00] +2023-09-19 08:16:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1db8c5f9a0582. [clock=2023-09-19 08:16:06+00:00] +2023-09-19 08:16:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246801077734724419133491998 amount: 80. +2023-09-19 08:16:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252083288426181403450375248 amount: 80. +2023-09-19 08:16:06,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111366.0, "order_id": "x-XEKWYICXBSIUT605b1db8c5d7a0582", "exchange_order_id": "35610354", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:16:06,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1db8c5d7a0582. +2023-09-19 08:16:06,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111366.0, "order_id": "x-XEKWYICXSSIUT605b1db8c5f9a0582", "exchange_order_id": "35610356", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:16:06,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1db8c5f9a0582. +2023-09-19 08:16:06,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ded39c840582 for 80.00000000 SEI-USDT. +2023-09-19 08:16:06,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b1ded39c840582", "creation_timestamp": 1695111366.0, "exchange_order_id": "35610566", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:16:06,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ded3993e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:16:06,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111366.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b1ded3993e0582", "creation_timestamp": 1695111366.0, "exchange_order_id": "35610567", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:17:01,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ded3993e0582. [clock=2023-09-19 08:17:01+00:00] +2023-09-19 08:17:01,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ded39c840582. [clock=2023-09-19 08:17:01+00:00] +2023-09-19 08:17:01,098 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249667980049960730136995173 amount: 80. +2023-09-19 08:17:01,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258297955962753649814831592 amount: 80. +2023-09-19 08:17:01,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111421.0, "order_id": "x-XEKWYICXBSIUT605b1ded3993e0582", "exchange_order_id": "35610567", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:17:01,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ded3993e0582. +2023-09-19 08:17:01,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111421.0, "order_id": "x-XEKWYICXSSIUT605b1ded39c840582", "exchange_order_id": "35610566", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:17:01,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ded39c840582. +2023-09-19 08:17:01,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1e21bffe80582 for 80.00000000 SEI-USDT. +2023-09-19 08:17:01,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b1e21bffe80582", "creation_timestamp": 1695111421.0, "exchange_order_id": "35611165", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:17:01,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1e21bfbf90582 for 80.00000000 SEI-USDT. +2023-09-19 08:17:01,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1e21bfbf90582", "creation_timestamp": 1695111421.0, "exchange_order_id": "35611166", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:17:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1e21bfbf90582. [clock=2023-09-19 08:17:56+00:00] +2023-09-19 08:17:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1e21bffe80582. [clock=2023-09-19 08:17:56+00:00] +2023-09-19 08:17:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250695511726107577516487288 amount: 80. +2023-09-19 08:17:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258519118328576186512458168 amount: 80. +2023-09-19 08:17:56,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111476.0, "order_id": "x-XEKWYICXBSIUT605b1e21bfbf90582", "exchange_order_id": "35611166", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:17:56,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1e21bfbf90582. +2023-09-19 08:17:56,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1e56212e80582 for 80.00000000 SEI-USDT. +2023-09-19 08:17:56,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b1e56212e80582", "creation_timestamp": 1695111476.0, "exchange_order_id": "35611478", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:17:56,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111476.0, "order_id": "x-XEKWYICXSSIUT605b1e21bffe80582", "exchange_order_id": "35611165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:17:56,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1e21bffe80582. +2023-09-19 08:17:56,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1e56215530582 for 80.00000000 SEI-USDT. +2023-09-19 08:17:56,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b1e56215530582", "creation_timestamp": 1695111476.0, "exchange_order_id": "35611479", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:18:22,282 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b1e56212e80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:18:22,283 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:18:22+00:00] +2023-09-19 08:18:22,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111502.0, "order_id": "x-XEKWYICXBSIUT605b1e56212e80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4996964", "exchange_order_id": "35611478", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:18:22,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111502.0, "order_id": "x-XEKWYICXBSIUT605b1e56212e80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35611478", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:18:22,320 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b1e56212e80582 completely filled. +2023-09-19 08:18:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1e56215530582. [clock=2023-09-19 08:18:51+00:00] +2023-09-19 08:18:51,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249652049465484297767536455 amount: 80. +2023-09-19 08:18:51,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259083083541214497031428552 amount: 80. +2023-09-19 08:18:51,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111531.0, "order_id": "x-XEKWYICXSSIUT605b1e56215530582", "exchange_order_id": "35611479", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:18:51,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1e56215530582. +2023-09-19 08:18:51,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1e8a9bff10582 for 80.00000000 SEI-USDT. +2023-09-19 08:18:51,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b1e8a9bff10582", "creation_timestamp": 1695111531.0, "exchange_order_id": "35611946", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:18:51,327 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1e8a9bc690582 for 80.00000000 SEI-USDT. +2023-09-19 08:18:51,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1e8a9bc690582", "creation_timestamp": 1695111531.0, "exchange_order_id": "35611947", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:19:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1e8a9bc690582. [clock=2023-09-19 08:19:46+00:00] +2023-09-19 08:19:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1e8a9bff10582. [clock=2023-09-19 08:19:46+00:00] +2023-09-19 08:19:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249794978561321670615400119 amount: 80. +2023-09-19 08:19:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258235208372271636714934141 amount: 80. +2023-09-19 08:19:46,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111586.0, "order_id": "x-XEKWYICXBSIUT605b1e8a9bc690582", "exchange_order_id": "35611947", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:19:46,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1e8a9bc690582. +2023-09-19 08:19:46,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ebf08b3b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:19:46,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1ebf08b3b0582", "creation_timestamp": 1695111586.0, "exchange_order_id": "35612163", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:19:46,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ebf08d7e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:19:46,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b1ebf08d7e0582", "creation_timestamp": 1695111586.0, "exchange_order_id": "35612164", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:19:46,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111586.0, "order_id": "x-XEKWYICXSSIUT605b1e8a9bff10582", "exchange_order_id": "35611946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:19:46,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1e8a9bff10582. +2023-09-19 08:20:41,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ebf08b3b0582. [clock=2023-09-19 08:20:41+00:00] +2023-09-19 08:20:41,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ebf08d7e0582. [clock=2023-09-19 08:20:41+00:00] +2023-09-19 08:20:41,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249904148985479784704199853 amount: 80. +2023-09-19 08:20:41,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257586154725338155518191779 amount: 80. +2023-09-19 08:20:41,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111641.0, "order_id": "x-XEKWYICXBSIUT605b1ebf08b3b0582", "exchange_order_id": "35612163", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:20:41,347 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ebf08b3b0582. +2023-09-19 08:20:41,415 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ef3a4fc10582 for 80.00000000 SEI-USDT. +2023-09-19 08:20:41,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111641.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1ef3a4fc10582", "creation_timestamp": 1695111641.0, "exchange_order_id": "35612270", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:20:41,460 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111641.0, "order_id": "x-XEKWYICXSSIUT605b1ebf08d7e0582", "exchange_order_id": "35612164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:20:41,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ebf08d7e0582. +2023-09-19 08:20:41,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ef3a536d0582 for 80.00000000 SEI-USDT. +2023-09-19 08:20:41,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111641.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b1ef3a536d0582", "creation_timestamp": 1695111641.0, "exchange_order_id": "35612271", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:21:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ef3a4fc10582. [clock=2023-09-19 08:21:36+00:00] +2023-09-19 08:21:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ef3a536d0582. [clock=2023-09-19 08:21:36+00:00] +2023-09-19 08:21:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249990472447608764654215452 amount: 80. +2023-09-19 08:21:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257072045054922593882885806 amount: 80. +2023-09-19 08:21:36,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111696.0, "order_id": "x-XEKWYICXBSIUT605b1ef3a4fc10582", "exchange_order_id": "35612270", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:21:36,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ef3a4fc10582. +2023-09-19 08:21:36,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111696.0, "order_id": "x-XEKWYICXSSIUT605b1ef3a536d0582", "exchange_order_id": "35612271", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:21:36,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ef3a536d0582. +2023-09-19 08:21:36,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1f27f0c9a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:21:36,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b1f27f0c9a0582", "creation_timestamp": 1695111696.0, "exchange_order_id": "35612345", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:21:36,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1f27f08820582 for 80.00000000 SEI-USDT. +2023-09-19 08:21:36,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111696.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1f27f08820582", "creation_timestamp": 1695111696.0, "exchange_order_id": "35612346", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:22:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1f27f08820582. [clock=2023-09-19 08:22:31+00:00] +2023-09-19 08:22:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1f27f0c9a0582. [clock=2023-09-19 08:22:31+00:00] +2023-09-19 08:22:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251215908342068559631529178 amount: 80. +2023-09-19 08:22:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256725729530839358603466794 amount: 80. +2023-09-19 08:22:31,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111751.0, "order_id": "x-XEKWYICXBSIUT605b1f27f08820582", "exchange_order_id": "35612346", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:22:31,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1f27f08820582. +2023-09-19 08:22:31,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1f5c63bd30582 for 80.00000000 SEI-USDT. +2023-09-19 08:22:31,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b1f5c63bd30582", "creation_timestamp": 1695111751.0, "exchange_order_id": "35612552", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:22:31,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111751.0, "order_id": "x-XEKWYICXSSIUT605b1f27f0c9a0582", "exchange_order_id": "35612345", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:22:31,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1f27f0c9a0582. +2023-09-19 08:22:31,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1f5c63fbf0582 for 80.00000000 SEI-USDT. +2023-09-19 08:22:31,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b1f5c63fbf0582", "creation_timestamp": 1695111751.0, "exchange_order_id": "35612553", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:23:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1f5c63bd30582. [clock=2023-09-19 08:23:26+00:00] +2023-09-19 08:23:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1f5c63fbf0582. [clock=2023-09-19 08:23:26+00:00] +2023-09-19 08:23:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247961460434212580779039333 amount: 80. +2023-09-19 08:23:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254215893641471912698233458 amount: 80. +2023-09-19 08:23:26,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111806.0, "order_id": "x-XEKWYICXBSIUT605b1f5c63bd30582", "exchange_order_id": "35612552", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:23:26,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1f5c63bd30582. +2023-09-19 08:23:26,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111806.0, "order_id": "x-XEKWYICXSSIUT605b1f5c63fbf0582", "exchange_order_id": "35612553", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:23:26,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1f5c63fbf0582. +2023-09-19 08:23:26,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1f90d7a310582 for 80.00000000 SEI-USDT. +2023-09-19 08:23:26,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b1f90d7a310582", "creation_timestamp": 1695111806.0, "exchange_order_id": "35612697", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:23:26,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1f90d782c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:23:26,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b1f90d782c0582", "creation_timestamp": 1695111806.0, "exchange_order_id": "35612698", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:24:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1f90d782c0582. [clock=2023-09-19 08:24:21+00:00] +2023-09-19 08:24:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1f90d7a310582. [clock=2023-09-19 08:24:21+00:00] +2023-09-19 08:24:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251183915337385216843923660 amount: 80. +2023-09-19 08:24:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256917129964591066104167417 amount: 80. +2023-09-19 08:24:21,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111861.0, "order_id": "x-XEKWYICXBSIUT605b1f90d782c0582", "exchange_order_id": "35612698", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:24:21,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1f90d782c0582. +2023-09-19 08:24:21,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111861.0, "order_id": "x-XEKWYICXSSIUT605b1f90d7a310582", "exchange_order_id": "35612697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:24:21,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1f90d7a310582. +2023-09-19 08:24:21,245 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1fc54bd9a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:24:21,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b1fc54bd9a0582", "creation_timestamp": 1695111861.0, "exchange_order_id": "35612902", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:24:21,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1fc54ba140582 for 80.00000000 SEI-USDT. +2023-09-19 08:24:21,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b1fc54ba140582", "creation_timestamp": 1695111861.0, "exchange_order_id": "35612903", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:25:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1fc54ba140582. [clock=2023-09-19 08:25:16+00:00] +2023-09-19 08:25:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1fc54bd9a0582. [clock=2023-09-19 08:25:16+00:00] +2023-09-19 08:25:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249203832628641056140794565 amount: 80. +2023-09-19 08:25:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254770317746512241506353249 amount: 80. +2023-09-19 08:25:16,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111916.0, "order_id": "x-XEKWYICXBSIUT605b1fc54ba140582", "exchange_order_id": "35612903", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:25:16,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1fc54ba140582. +2023-09-19 08:25:16,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111916.0, "order_id": "x-XEKWYICXSSIUT605b1fc54bd9a0582", "exchange_order_id": "35612902", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:25:16,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1fc54bd9a0582. +2023-09-19 08:25:16,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b1ff9bf4720582 for 80.00000000 SEI-USDT. +2023-09-19 08:25:16,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b1ff9bf4720582", "creation_timestamp": 1695111916.0, "exchange_order_id": "35613155", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:25:16,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b1ff9bf72d0582 for 80.00000000 SEI-USDT. +2023-09-19 08:25:16,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b1ff9bf72d0582", "creation_timestamp": 1695111916.0, "exchange_order_id": "35613156", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:26:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b1ff9bf4720582. [clock=2023-09-19 08:26:11+00:00] +2023-09-19 08:26:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b1ff9bf72d0582. [clock=2023-09-19 08:26:11+00:00] +2023-09-19 08:26:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249518879096337711949209624 amount: 80. +2023-09-19 08:26:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254956212679974961632031059 amount: 80. +2023-09-19 08:26:11,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111971.0, "order_id": "x-XEKWYICXBSIUT605b1ff9bf4720582", "exchange_order_id": "35613155", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:26:11,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b1ff9bf4720582. +2023-09-19 08:26:11,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111971.0, "order_id": "x-XEKWYICXSSIUT605b1ff9bf72d0582", "exchange_order_id": "35613156", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:26:11,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b1ff9bf72d0582. +2023-09-19 08:26:11,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b202e32c160582 for 80.00000000 SEI-USDT. +2023-09-19 08:26:11,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b202e32c160582", "creation_timestamp": 1695111971.0, "exchange_order_id": "35613451", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:26:11,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b202e32efe0582 for 80.00000000 SEI-USDT. +2023-09-19 08:26:11,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695111971.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b202e32efe0582", "creation_timestamp": 1695111971.0, "exchange_order_id": "35613452", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:27:06,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b202e32c160582. [clock=2023-09-19 08:27:06+00:00] +2023-09-19 08:27:06,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b202e32efe0582. [clock=2023-09-19 08:27:06+00:00] +2023-09-19 08:27:06,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250070435390759952144365014 amount: 80. +2023-09-19 08:27:06,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254299294709570699011977688 amount: 80. +2023-09-19 08:27:06,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112026.0, "order_id": "x-XEKWYICXBSIUT605b202e32c160582", "exchange_order_id": "35613451", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:27:06,227 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b202e32c160582. +2023-09-19 08:27:06,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2062b051a0582 for 80.00000000 SEI-USDT. +2023-09-19 08:27:06,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b2062b051a0582", "creation_timestamp": 1695112026.0, "exchange_order_id": "35613607", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:27:06,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2062b092e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:27:06,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b2062b092e0582", "creation_timestamp": 1695112026.0, "exchange_order_id": "35613608", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:27:06,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112026.0, "order_id": "x-XEKWYICXSSIUT605b202e32efe0582", "exchange_order_id": "35613452", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:27:06,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b202e32efe0582. +2023-09-19 08:28:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2062b051a0582. [clock=2023-09-19 08:28:01+00:00] +2023-09-19 08:28:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2062b092e0582. [clock=2023-09-19 08:28:01+00:00] +2023-09-19 08:28:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249056579023193694477844746 amount: 80. +2023-09-19 08:28:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255506713396291687697938254 amount: 80. +2023-09-19 08:28:01,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112081.0, "order_id": "x-XEKWYICXBSIUT605b2062b051a0582", "exchange_order_id": "35613607", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:28:01,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2062b051a0582. +2023-09-19 08:28:01,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112081.0, "order_id": "x-XEKWYICXSSIUT605b2062b092e0582", "exchange_order_id": "35613608", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:28:01,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2062b092e0582. +2023-09-19 08:28:01,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b20971a70b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:28:01,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b20971a70b0582", "creation_timestamp": 1695112081.0, "exchange_order_id": "35613844", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:28:01,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b20971a3c70582 for 80.00000000 SEI-USDT. +2023-09-19 08:28:01,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b20971a3c70582", "creation_timestamp": 1695112081.0, "exchange_order_id": "35613845", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:28:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b20971a3c70582. [clock=2023-09-19 08:28:56+00:00] +2023-09-19 08:28:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b20971a70b0582. [clock=2023-09-19 08:28:56+00:00] +2023-09-19 08:28:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249710948394917329605367148 amount: 80. +2023-09-19 08:28:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254727469443683769406440272 amount: 80. +2023-09-19 08:28:56,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112136.0, "order_id": "x-XEKWYICXBSIUT605b20971a3c70582", "exchange_order_id": "35613845", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:28:56,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b20971a3c70582. +2023-09-19 08:28:56,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112136.0, "order_id": "x-XEKWYICXSSIUT605b20971a70b0582", "exchange_order_id": "35613844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:28:56,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b20971a70b0582. +2023-09-19 08:28:56,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b20cb8e4ae0582 for 80.00000000 SEI-USDT. +2023-09-19 08:28:56,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b20cb8e4ae0582", "creation_timestamp": 1695112136.0, "exchange_order_id": "35614009", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:28:56,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b20cb8e9890582 for 80.00000000 SEI-USDT. +2023-09-19 08:28:56,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b20cb8e9890582", "creation_timestamp": 1695112136.0, "exchange_order_id": "35614010", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:29:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b20cb8e4ae0582. [clock=2023-09-19 08:29:51+00:00] +2023-09-19 08:29:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b20cb8e9890582. [clock=2023-09-19 08:29:51+00:00] +2023-09-19 08:29:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247536419230621552416807645 amount: 80. +2023-09-19 08:29:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253655943595945831527698482 amount: 80. +2023-09-19 08:29:51,159 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112191.0, "order_id": "x-XEKWYICXBSIUT605b20cb8e4ae0582", "exchange_order_id": "35614009", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:29:51,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b20cb8e4ae0582. +2023-09-19 08:29:51,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112191.0, "order_id": "x-XEKWYICXSSIUT605b20cb8e9890582", "exchange_order_id": "35614010", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:29:51,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b20cb8e9890582. +2023-09-19 08:29:51,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b210001cc60582 for 80.00000000 SEI-USDT. +2023-09-19 08:29:51,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b210001cc60582", "creation_timestamp": 1695112191.0, "exchange_order_id": "35614226", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:29:51,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b210001fc90582 for 80.00000000 SEI-USDT. +2023-09-19 08:29:51,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b210001fc90582", "creation_timestamp": 1695112191.0, "exchange_order_id": "35614227", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:30:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b210001cc60582. [clock=2023-09-19 08:30:46+00:00] +2023-09-19 08:30:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b210001fc90582. [clock=2023-09-19 08:30:46+00:00] +2023-09-19 08:30:46,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245657477176521868698656780 amount: 80. +2023-09-19 08:30:46,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252387102925113503530459676 amount: 80. +2023-09-19 08:30:46,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112246.0, "order_id": "x-XEKWYICXBSIUT605b210001cc60582", "exchange_order_id": "35614226", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:30:46,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b210001cc60582. +2023-09-19 08:30:46,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112246.0, "order_id": "x-XEKWYICXSSIUT605b210001fc90582", "exchange_order_id": "35614227", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:30:46,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b210001fc90582. +2023-09-19 08:30:46,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2134770610582 for 80.00000000 SEI-USDT. +2023-09-19 08:30:46,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b2134770610582", "creation_timestamp": 1695112246.0, "exchange_order_id": "35614498", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:30:46,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2134773ca0582 for 80.00000000 SEI-USDT. +2023-09-19 08:30:46,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112246.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b2134773ca0582", "creation_timestamp": 1695112246.0, "exchange_order_id": "35614497", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:31:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2134770610582. [clock=2023-09-19 08:31:41+00:00] +2023-09-19 08:31:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2134773ca0582. [clock=2023-09-19 08:31:41+00:00] +2023-09-19 08:31:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244479888612276525427741650 amount: 80. +2023-09-19 08:31:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250821499725687751058966896 amount: 80. +2023-09-19 08:31:41,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112301.0, "order_id": "x-XEKWYICXBSIUT605b2134770610582", "exchange_order_id": "35614498", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:31:41,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2134770610582. +2023-09-19 08:31:41,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112301.0, "order_id": "x-XEKWYICXSSIUT605b2134773ca0582", "exchange_order_id": "35614497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:31:41,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2134773ca0582. +2023-09-19 08:31:41,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2168e96ee0582 for 80.00000000 SEI-USDT. +2023-09-19 08:31:41,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112301.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b2168e96ee0582", "creation_timestamp": 1695112301.0, "exchange_order_id": "35614672", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:31:41,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2168e99d30582 for 80.00000000 SEI-USDT. +2023-09-19 08:31:41,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112301.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b2168e99d30582", "creation_timestamp": 1695112301.0, "exchange_order_id": "35614673", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:32:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2168e96ee0582. [clock=2023-09-19 08:32:36+00:00] +2023-09-19 08:32:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2168e99d30582. [clock=2023-09-19 08:32:36+00:00] +2023-09-19 08:32:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235886231426314245364483569 amount: 80. +2023-09-19 08:32:36,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-19 08:32:36,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112356.0, "order_id": "x-XEKWYICXBSIUT605b2168e96ee0582", "exchange_order_id": "35614672", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:32:36,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2168e96ee0582. +2023-09-19 08:32:36,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112356.0, "order_id": "x-XEKWYICXSSIUT605b2168e99d30582", "exchange_order_id": "35614673", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:32:36,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2168e99d30582. +2023-09-19 08:32:36,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b219d5dfb00582 for 80.00000000 SEI-USDT. +2023-09-19 08:32:36,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b219d5dfb00582", "creation_timestamp": 1695112356.0, "exchange_order_id": "35615165", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:32:36,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b219d5dc4e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:32:36,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b219d5dc4e0582", "creation_timestamp": 1695112356.0, "exchange_order_id": "35615166", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:33:31,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b219d5dc4e0582. [clock=2023-09-19 08:33:31+00:00] +2023-09-19 08:33:31,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b219d5dfb00582. [clock=2023-09-19 08:33:31+00:00] +2023-09-19 08:33:31,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234883893817787680404866865 amount: 80. +2023-09-19 08:33:31,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 08:33:31,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112411.0, "order_id": "x-XEKWYICXBSIUT605b219d5dc4e0582", "exchange_order_id": "35615166", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:33:31,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b219d5dc4e0582. +2023-09-19 08:33:31,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b21d1fc6a40582 for 80.00000000 SEI-USDT. +2023-09-19 08:33:31,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b21d1fc6a40582", "creation_timestamp": 1695112411.0, "exchange_order_id": "35615537", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:33:31,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112411.0, "order_id": "x-XEKWYICXSSIUT605b219d5dfb00582", "exchange_order_id": "35615165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:33:31,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b219d5dfb00582. +2023-09-19 08:33:31,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b21d1fc4590582 for 80.00000000 SEI-USDT. +2023-09-19 08:33:31,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b21d1fc4590582", "creation_timestamp": 1695112411.0, "exchange_order_id": "35615538", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:34:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b21d1fc4590582. [clock=2023-09-19 08:34:26+00:00] +2023-09-19 08:34:26,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b21d1fc6a40582. [clock=2023-09-19 08:34:26+00:00] +2023-09-19 08:34:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237128907772977311754556584 amount: 80. +2023-09-19 08:34:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 08:34:26,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112466.0, "order_id": "x-XEKWYICXBSIUT605b21d1fc4590582", "exchange_order_id": "35615538", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:34:26,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b21d1fc4590582. +2023-09-19 08:34:26,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112466.0, "order_id": "x-XEKWYICXSSIUT605b21d1fc6a40582", "exchange_order_id": "35615537", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:34:26,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b21d1fc6a40582. +2023-09-19 08:34:26,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b220644dfc0582 for 80.00000000 SEI-USDT. +2023-09-19 08:34:26,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b220644dfc0582", "creation_timestamp": 1695112466.0, "exchange_order_id": "35615703", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:34:26,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b220644be60582 for 80.00000000 SEI-USDT. +2023-09-19 08:34:26,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b220644be60582", "creation_timestamp": 1695112466.0, "exchange_order_id": "35615704", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:35:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b220644be60582. [clock=2023-09-19 08:35:21+00:00] +2023-09-19 08:35:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b220644dfc0582. [clock=2023-09-19 08:35:21+00:00] +2023-09-19 08:35:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239718981861423141567147315 amount: 80. +2023-09-19 08:35:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246193057529686112947270699 amount: 80. +2023-09-19 08:35:21,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112521.0, "order_id": "x-XEKWYICXBSIUT605b220644be60582", "exchange_order_id": "35615704", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:35:21,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b220644be60582. +2023-09-19 08:35:21,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b223ab84630582 for 80.00000000 SEI-USDT. +2023-09-19 08:35:21,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b223ab84630582", "creation_timestamp": 1695112521.0, "exchange_order_id": "35615810", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:35:21,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112521.0, "order_id": "x-XEKWYICXSSIUT605b220644dfc0582", "exchange_order_id": "35615703", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:35:21,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b220644dfc0582. +2023-09-19 08:35:21,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b223ab820b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:35:21,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b223ab820b0582", "creation_timestamp": 1695112521.0, "exchange_order_id": "35615811", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:36:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b223ab820b0582. [clock=2023-09-19 08:36:16+00:00] +2023-09-19 08:36:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b223ab84630582. [clock=2023-09-19 08:36:16+00:00] +2023-09-19 08:36:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240891478324598252607776780 amount: 80. +2023-09-19 08:36:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 08:36:16,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112576.0, "order_id": "x-XEKWYICXBSIUT605b223ab820b0582", "exchange_order_id": "35615811", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:36:16,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b223ab820b0582. +2023-09-19 08:36:16,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112576.0, "order_id": "x-XEKWYICXSSIUT605b223ab84630582", "exchange_order_id": "35615810", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:36:16,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b223ab84630582. +2023-09-19 08:36:16,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b226f2c3110582 for 80.00000000 SEI-USDT. +2023-09-19 08:36:16,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112576.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b226f2c3110582", "creation_timestamp": 1695112576.0, "exchange_order_id": "35615900", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:36:16,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b226f2c57c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:36:16,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112576.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b226f2c57c0582", "creation_timestamp": 1695112576.0, "exchange_order_id": "35615901", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:37:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b226f2c3110582. [clock=2023-09-19 08:37:11+00:00] +2023-09-19 08:37:11,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b226f2c57c0582. [clock=2023-09-19 08:37:11+00:00] +2023-09-19 08:37:11,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241803839928228516692591739 amount: 80. +2023-09-19 08:37:11,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 08:37:11,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112631.0, "order_id": "x-XEKWYICXBSIUT605b226f2c3110582", "exchange_order_id": "35615900", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:37:11,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b226f2c3110582. +2023-09-19 08:37:11,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b22a3a0bc60582 for 80.00000000 SEI-USDT. +2023-09-19 08:37:11,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b22a3a0bc60582", "creation_timestamp": 1695112631.0, "exchange_order_id": "35615976", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:37:11,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b22a3a11010582 for 80.00000000 SEI-USDT. +2023-09-19 08:37:11,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b22a3a11010582", "creation_timestamp": 1695112631.0, "exchange_order_id": "35615977", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:37:11,322 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112631.0, "order_id": "x-XEKWYICXSSIUT605b226f2c57c0582", "exchange_order_id": "35615901", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:37:11,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b226f2c57c0582. +2023-09-19 08:38:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b22a3a0bc60582. [clock=2023-09-19 08:38:06+00:00] +2023-09-19 08:38:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b22a3a11010582. [clock=2023-09-19 08:38:06+00:00] +2023-09-19 08:38:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240537281748926114133008494 amount: 80. +2023-09-19 08:38:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12460000 amount: 80. +2023-09-19 08:38:06,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112686.0, "order_id": "x-XEKWYICXBSIUT605b22a3a0bc60582", "exchange_order_id": "35615976", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:38:06,326 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b22a3a0bc60582. +2023-09-19 08:38:06,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112686.0, "order_id": "x-XEKWYICXSSIUT605b22a3a11010582", "exchange_order_id": "35615977", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:38:06,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b22a3a11010582. +2023-09-19 08:38:06,405 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b22d8137ca0582 for 80.00000000 SEI-USDT. +2023-09-19 08:38:06,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b22d8137ca0582", "creation_timestamp": 1695112686.0, "exchange_order_id": "35616166", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:38:06,420 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b22d813a0f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:38:06,431 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b22d813a0f0582", "creation_timestamp": 1695112686.0, "exchange_order_id": "35616167", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:38:33,548 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b22d813a0f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:38:33,549 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:38:33+00:00] +2023-09-19 08:38:33,574 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112713.0, "order_id": "x-XEKWYICXSSIUT605b22d813a0f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "4997513", "exchange_order_id": "35616167", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:38:33,586 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112713.0, "order_id": "x-XEKWYICXSSIUT605b22d813a0f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35616167", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:38:33,586 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b22d813a0f0582 completely filled. +2023-09-19 08:39:01,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b22d8137ca0582. [clock=2023-09-19 08:39:01+00:00] +2023-09-19 08:39:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243620891159791003386650614 amount: 80. +2023-09-19 08:39:01,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251981763233300667612696701 amount: 80. +2023-09-19 08:39:01,140 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112741.0, "order_id": "x-XEKWYICXBSIUT605b22d8137ca0582", "exchange_order_id": "35616166", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:39:01,140 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b22d8137ca0582. +2023-09-19 08:39:01,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b230c87be10582 for 80.00000000 SEI-USDT. +2023-09-19 08:39:01,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b230c87be10582", "creation_timestamp": 1695112741.0, "exchange_order_id": "35616554", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:39:01,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b230c87f680582 for 80.00000000 SEI-USDT. +2023-09-19 08:39:01,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b230c87f680582", "creation_timestamp": 1695112741.0, "exchange_order_id": "35616555", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:39:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b230c87be10582. [clock=2023-09-19 08:39:56+00:00] +2023-09-19 08:39:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b230c87f680582. [clock=2023-09-19 08:39:56+00:00] +2023-09-19 08:39:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243425120831220890513228140 amount: 80. +2023-09-19 08:39:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252149792809700957006445908 amount: 80. +2023-09-19 08:39:56,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112796.0, "order_id": "x-XEKWYICXBSIUT605b230c87be10582", "exchange_order_id": "35616554", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:39:56,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b230c87be10582. +2023-09-19 08:39:56,429 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112796.0, "order_id": "x-XEKWYICXSSIUT605b230c87f680582", "exchange_order_id": "35616555", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:39:56,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b230c87f680582. +2023-09-19 08:39:56,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2340fad580582 for 80.00000000 SEI-USDT. +2023-09-19 08:39:56,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b2340fad580582", "creation_timestamp": 1695112796.0, "exchange_order_id": "35617077", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:39:56,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2340fb0c60582 for 80.00000000 SEI-USDT. +2023-09-19 08:39:56,457 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b2340fb0c60582", "creation_timestamp": 1695112796.0, "exchange_order_id": "35617078", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:40:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2340fad580582. [clock=2023-09-19 08:40:51+00:00] +2023-09-19 08:40:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2340fb0c60582. [clock=2023-09-19 08:40:51+00:00] +2023-09-19 08:40:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241707917756764401075391774 amount: 80. +2023-09-19 08:40:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250711441991272865650980565 amount: 80. +2023-09-19 08:40:51,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112851.0, "order_id": "x-XEKWYICXBSIUT605b2340fad580582", "exchange_order_id": "35617077", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:40:51,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2340fad580582. +2023-09-19 08:40:51,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112851.0, "order_id": "x-XEKWYICXSSIUT605b2340fb0c60582", "exchange_order_id": "35617078", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:40:51,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2340fb0c60582. +2023-09-19 08:40:51,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b23756eea60582 for 80.00000000 SEI-USDT. +2023-09-19 08:40:51,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b23756eea60582", "creation_timestamp": 1695112851.0, "exchange_order_id": "35617511", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:40:51,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b23756f2b80582 for 80.00000000 SEI-USDT. +2023-09-19 08:40:51,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b23756f2b80582", "creation_timestamp": 1695112851.0, "exchange_order_id": "35617512", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:41:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b23756eea60582. [clock=2023-09-19 08:41:46+00:00] +2023-09-19 08:41:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b23756f2b80582. [clock=2023-09-19 08:41:46+00:00] +2023-09-19 08:41:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244695038975968581817213921 amount: 80. +2023-09-19 08:41:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253926740561194327102232211 amount: 80. +2023-09-19 08:41:46,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112906.0, "order_id": "x-XEKWYICXBSIUT605b23756eea60582", "exchange_order_id": "35617511", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:41:46,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b23756eea60582. +2023-09-19 08:41:46,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112906.0, "order_id": "x-XEKWYICXSSIUT605b23756f2b80582", "exchange_order_id": "35617512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:41:46,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b23756f2b80582. +2023-09-19 08:41:46,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b23a9e2a5e0582 for 80.00000000 SEI-USDT. +2023-09-19 08:41:46,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112906.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b23a9e2a5e0582", "creation_timestamp": 1695112906.0, "exchange_order_id": "35617770", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:41:46,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b23a9e2d750582 for 80.00000000 SEI-USDT. +2023-09-19 08:41:46,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112906.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b23a9e2d750582", "creation_timestamp": 1695112906.0, "exchange_order_id": "35617771", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:42:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b23a9e2a5e0582. [clock=2023-09-19 08:42:41+00:00] +2023-09-19 08:42:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b23a9e2d750582. [clock=2023-09-19 08:42:41+00:00] +2023-09-19 08:42:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243603236753381391479346642 amount: 80. +2023-09-19 08:42:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251891094027439071256706320 amount: 80. +2023-09-19 08:42:41,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112961.0, "order_id": "x-XEKWYICXBSIUT605b23a9e2a5e0582", "exchange_order_id": "35617770", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:42:41,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b23a9e2a5e0582. +2023-09-19 08:42:41,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112961.0, "order_id": "x-XEKWYICXSSIUT605b23a9e2d750582", "exchange_order_id": "35617771", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:42:41,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b23a9e2d750582. +2023-09-19 08:42:41,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b23de56a770582 for 80.00000000 SEI-USDT. +2023-09-19 08:42:41,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b23de56a770582", "creation_timestamp": 1695112961.0, "exchange_order_id": "35618096", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:42:41,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b23de5663c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:42:41,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695112961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b23de5663c0582", "creation_timestamp": 1695112961.0, "exchange_order_id": "35618097", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:42:51,286 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 08:43:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b23de5663c0582. [clock=2023-09-19 08:43:36+00:00] +2023-09-19 08:43:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b23de56a770582. [clock=2023-09-19 08:43:36+00:00] +2023-09-19 08:43:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243126397765466783868989764 amount: 80. +2023-09-19 08:43:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250430857089872469103108238 amount: 80. +2023-09-19 08:43:36,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113016.0, "order_id": "x-XEKWYICXBSIUT605b23de5663c0582", "exchange_order_id": "35618097", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:43:36,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b23de5663c0582. +2023-09-19 08:43:36,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113016.0, "order_id": "x-XEKWYICXSSIUT605b23de56a770582", "exchange_order_id": "35618096", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:43:36,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b23de56a770582. +2023-09-19 08:43:36,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2412ca30b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:43:36,244 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b2412ca30b0582", "creation_timestamp": 1695113016.0, "exchange_order_id": "35618389", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:43:36,286 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2412ca6330582 for 80.00000000 SEI-USDT. +2023-09-19 08:43:36,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b2412ca6330582", "creation_timestamp": 1695113016.0, "exchange_order_id": "35618390", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:44:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2412ca30b0582. [clock=2023-09-19 08:44:31+00:00] +2023-09-19 08:44:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2412ca6330582. [clock=2023-09-19 08:44:31+00:00] +2023-09-19 08:44:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242985508779159000190154007 amount: 80. +2023-09-19 08:44:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250640340968877878953919036 amount: 80. +2023-09-19 08:44:31,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113071.0, "order_id": "x-XEKWYICXBSIUT605b2412ca30b0582", "exchange_order_id": "35618389", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:44:31,159 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2412ca30b0582. +2023-09-19 08:44:31,225 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113071.0, "order_id": "x-XEKWYICXSSIUT605b2412ca6330582", "exchange_order_id": "35618390", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:44:31,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2412ca6330582. +2023-09-19 08:44:31,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b24473dd1c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:44:31,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b24473dd1c0582", "creation_timestamp": 1695113071.0, "exchange_order_id": "35618509", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:44:31,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b24473df130582 for 80.00000000 SEI-USDT. +2023-09-19 08:44:31,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113071.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b24473df130582", "creation_timestamp": 1695113071.0, "exchange_order_id": "35618508", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:45:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b24473dd1c0582. [clock=2023-09-19 08:45:26+00:00] +2023-09-19 08:45:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b24473df130582. [clock=2023-09-19 08:45:26+00:00] +2023-09-19 08:45:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241487941993476047847899990 amount: 80. +2023-09-19 08:45:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249412817177466700362761066 amount: 80. +2023-09-19 08:45:26,340 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113126.0, "order_id": "x-XEKWYICXBSIUT605b24473dd1c0582", "exchange_order_id": "35618509", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:45:26,340 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b24473dd1c0582. +2023-09-19 08:45:26,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113126.0, "order_id": "x-XEKWYICXSSIUT605b24473df130582", "exchange_order_id": "35618508", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:45:26,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b24473df130582. +2023-09-19 08:45:26,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b247bb18e60582 for 80.00000000 SEI-USDT. +2023-09-19 08:45:26,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b247bb18e60582", "creation_timestamp": 1695113126.0, "exchange_order_id": "35618971", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:45:26,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b247bb16100582 for 80.00000000 SEI-USDT. +2023-09-19 08:45:26,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b247bb16100582", "creation_timestamp": 1695113126.0, "exchange_order_id": "35618972", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:46:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b247bb16100582. [clock=2023-09-19 08:46:21+00:00] +2023-09-19 08:46:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b247bb18e60582. [clock=2023-09-19 08:46:21+00:00] +2023-09-19 08:46:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238306235643943769068982077 amount: 80. +2023-09-19 08:46:21,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246194982384858350349988529 amount: 80. +2023-09-19 08:46:21,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113181.0, "order_id": "x-XEKWYICXBSIUT605b247bb16100582", "exchange_order_id": "35618972", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:46:21,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b247bb16100582. +2023-09-19 08:46:21,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b24b0257da0582 for 80.00000000 SEI-USDT. +2023-09-19 08:46:21,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b24b0257da0582", "creation_timestamp": 1695113181.0, "exchange_order_id": "35619214", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:46:21,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b24b025b010582 for 80.00000000 SEI-USDT. +2023-09-19 08:46:21,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113181.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b24b025b010582", "creation_timestamp": 1695113181.0, "exchange_order_id": "35619215", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:46:21,312 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113181.0, "order_id": "x-XEKWYICXSSIUT605b247bb18e60582", "exchange_order_id": "35618971", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:46:21,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b247bb18e60582. +2023-09-19 08:47:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b24b0257da0582. [clock=2023-09-19 08:47:16+00:00] +2023-09-19 08:47:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b24b025b010582. [clock=2023-09-19 08:47:16+00:00] +2023-09-19 08:47:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234599485178582960233018764 amount: 80. +2023-09-19 08:47:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12440000 amount: 80. +2023-09-19 08:47:16,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113236.0, "order_id": "x-XEKWYICXBSIUT605b24b0257da0582", "exchange_order_id": "35619214", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:47:16,171 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b24b0257da0582. +2023-09-19 08:47:16,242 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113236.0, "order_id": "x-XEKWYICXSSIUT605b24b025b010582", "exchange_order_id": "35619215", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:47:16,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b24b025b010582. +2023-09-19 08:47:16,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b24e4989bc0582 for 80.00000000 SEI-USDT. +2023-09-19 08:47:16,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b24e4989bc0582", "creation_timestamp": 1695113236.0, "exchange_order_id": "35619590", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:47:16,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b24e498cd80582 for 80.00000000 SEI-USDT. +2023-09-19 08:47:16,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b24e498cd80582", "creation_timestamp": 1695113236.0, "exchange_order_id": "35619591", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:47:19,806 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b24e498cd80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:47:19,807 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:47:19+00:00] +2023-09-19 08:47:19,834 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113239.0, "order_id": "x-XEKWYICXSSIUT605b24e498cd80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00995200"}]}, "exchange_trade_id": "4997868", "exchange_order_id": "35619591", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:47:19,847 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113239.0, "order_id": "x-XEKWYICXSSIUT605b24e498cd80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35619591", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:47:19,847 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b24e498cd80582 completely filled. +2023-09-19 08:48:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b24e4989bc0582. [clock=2023-09-19 08:48:11+00:00] +2023-09-19 08:48:11,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239781221003427153146649803 amount: 80. +2023-09-19 08:48:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247204581263123446200152443 amount: 80. +2023-09-19 08:48:11,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113291.0, "order_id": "x-XEKWYICXBSIUT605b24e4989bc0582", "exchange_order_id": "35619590", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:48:11,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b24e4989bc0582. +2023-09-19 08:48:11,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b25190c4dc0582 for 80.00000000 SEI-USDT. +2023-09-19 08:48:11,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b25190c4dc0582", "creation_timestamp": 1695113291.0, "exchange_order_id": "35619757", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:48:11,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b25190c78d0582 for 80.00000000 SEI-USDT. +2023-09-19 08:48:11,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b25190c78d0582", "creation_timestamp": 1695113291.0, "exchange_order_id": "35619758", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:49:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b25190c4dc0582. [clock=2023-09-19 08:49:06+00:00] +2023-09-19 08:49:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b25190c78d0582. [clock=2023-09-19 08:49:06+00:00] +2023-09-19 08:49:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242161291308056806498648187 amount: 80. +2023-09-19 08:49:06,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250780131442185403243518640 amount: 80. +2023-09-19 08:49:06,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113346.0, "order_id": "x-XEKWYICXBSIUT605b25190c4dc0582", "exchange_order_id": "35619757", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:49:06,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b25190c4dc0582. +2023-09-19 08:49:06,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113346.0, "order_id": "x-XEKWYICXSSIUT605b25190c78d0582", "exchange_order_id": "35619758", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:49:06,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b25190c78d0582. +2023-09-19 08:49:06,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b254d80a3c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:49:06,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b254d80a3c0582", "creation_timestamp": 1695113346.0, "exchange_order_id": "35620071", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:49:06,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b254d80e9f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:49:06,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113346.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b254d80e9f0582", "creation_timestamp": 1695113346.0, "exchange_order_id": "35620072", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:50:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b254d80a3c0582. [clock=2023-09-19 08:50:01+00:00] +2023-09-19 08:50:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b254d80e9f0582. [clock=2023-09-19 08:50:01+00:00] +2023-09-19 08:50:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240665647551728040580060922 amount: 80. +2023-09-19 08:50:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250388652793133644540581110 amount: 80. +2023-09-19 08:50:01,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113401.0, "order_id": "x-XEKWYICXBSIUT605b254d80a3c0582", "exchange_order_id": "35620071", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:50:01,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b254d80a3c0582. +2023-09-19 08:50:01,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113401.0, "order_id": "x-XEKWYICXSSIUT605b254d80e9f0582", "exchange_order_id": "35620072", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:50:01,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b254d80e9f0582. +2023-09-19 08:50:01,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2581f431f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:50:01,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b2581f431f0582", "creation_timestamp": 1695113401.0, "exchange_order_id": "35620183", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:50:01,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2581f46280582 for 80.00000000 SEI-USDT. +2023-09-19 08:50:01,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113401.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b2581f46280582", "creation_timestamp": 1695113401.0, "exchange_order_id": "35620184", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:50:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2581f431f0582. [clock=2023-09-19 08:50:56+00:00] +2023-09-19 08:50:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2581f46280582. [clock=2023-09-19 08:50:56+00:00] +2023-09-19 08:50:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238677104904284996099426255 amount: 80. +2023-09-19 08:50:56,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248461145491753632710077123 amount: 80. +2023-09-19 08:50:56,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113456.0, "order_id": "x-XEKWYICXBSIUT605b2581f431f0582", "exchange_order_id": "35620183", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:50:56,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2581f431f0582. +2023-09-19 08:50:56,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113456.0, "order_id": "x-XEKWYICXSSIUT605b2581f46280582", "exchange_order_id": "35620184", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:50:56,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2581f46280582. +2023-09-19 08:50:56,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b25b6685170582 for 80.00000000 SEI-USDT. +2023-09-19 08:50:56,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b25b6685170582", "creation_timestamp": 1695113456.0, "exchange_order_id": "35620414", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:50:56,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b25b6681f80582 for 80.00000000 SEI-USDT. +2023-09-19 08:50:56,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113456.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b25b6681f80582", "creation_timestamp": 1695113456.0, "exchange_order_id": "35620413", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:51:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b25b6681f80582. [clock=2023-09-19 08:51:51+00:00] +2023-09-19 08:51:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b25b6685170582. [clock=2023-09-19 08:51:51+00:00] +2023-09-19 08:51:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239810134827503675995984428 amount: 80. +2023-09-19 08:51:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248527869252023009040590313 amount: 80. +2023-09-19 08:51:51,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113511.0, "order_id": "x-XEKWYICXBSIUT605b25b6681f80582", "exchange_order_id": "35620413", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:51:51,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b25b6681f80582. +2023-09-19 08:51:51,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b25eadb95b0582 for 80.00000000 SEI-USDT. +2023-09-19 08:51:51,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b25eadb95b0582", "creation_timestamp": 1695113511.0, "exchange_order_id": "35620525", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:51:51,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b25eadb7130582 for 80.00000000 SEI-USDT. +2023-09-19 08:51:51,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b25eadb7130582", "creation_timestamp": 1695113511.0, "exchange_order_id": "35620526", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:51:51,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113511.0, "order_id": "x-XEKWYICXSSIUT605b25b6685170582", "exchange_order_id": "35620414", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:51:51,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b25b6685170582. +2023-09-19 08:52:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b25eadb7130582. [clock=2023-09-19 08:52:46+00:00] +2023-09-19 08:52:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b25eadb95b0582. [clock=2023-09-19 08:52:46+00:00] +2023-09-19 08:52:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241738732547289330970083579 amount: 80. +2023-09-19 08:52:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248524438533436291008906019 amount: 80. +2023-09-19 08:52:46,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113566.0, "order_id": "x-XEKWYICXBSIUT605b25eadb7130582", "exchange_order_id": "35620526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:52:46,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b25eadb7130582. +2023-09-19 08:52:46,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113566.0, "order_id": "x-XEKWYICXSSIUT605b25eadb95b0582", "exchange_order_id": "35620525", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:52:46,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b25eadb95b0582. +2023-09-19 08:52:46,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b261f4f6680582 for 80.00000000 SEI-USDT. +2023-09-19 08:52:46,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b261f4f6680582", "creation_timestamp": 1695113566.0, "exchange_order_id": "35620707", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:52:46,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b261f4f4580582 for 80.00000000 SEI-USDT. +2023-09-19 08:52:46,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b261f4f4580582", "creation_timestamp": 1695113566.0, "exchange_order_id": "35620708", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:53:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b261f4f4580582. [clock=2023-09-19 08:53:41+00:00] +2023-09-19 08:53:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b261f4f6680582. [clock=2023-09-19 08:53:41+00:00] +2023-09-19 08:53:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242462502415220164093095678 amount: 80. +2023-09-19 08:53:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247740145467161646445820710 amount: 80. +2023-09-19 08:53:41,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113621.0, "order_id": "x-XEKWYICXBSIUT605b261f4f4580582", "exchange_order_id": "35620708", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:53:41,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b261f4f4580582. +2023-09-19 08:53:41,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113621.0, "order_id": "x-XEKWYICXSSIUT605b261f4f6680582", "exchange_order_id": "35620707", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:53:41,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b261f4f6680582. +2023-09-19 08:53:41,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2653c324d0582 for 80.00000000 SEI-USDT. +2023-09-19 08:53:41,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b2653c324d0582", "creation_timestamp": 1695113621.0, "exchange_order_id": "35620928", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:53:41,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2653c2fae0582 for 80.00000000 SEI-USDT. +2023-09-19 08:53:41,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b2653c2fae0582", "creation_timestamp": 1695113621.0, "exchange_order_id": "35620929", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:54:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2653c2fae0582. [clock=2023-09-19 08:54:36+00:00] +2023-09-19 08:54:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2653c324d0582. [clock=2023-09-19 08:54:36+00:00] +2023-09-19 08:54:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242522907892581939798631013 amount: 80. +2023-09-19 08:54:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248600577516711832617852089 amount: 80. +2023-09-19 08:54:36,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113676.0, "order_id": "x-XEKWYICXBSIUT605b2653c2fae0582", "exchange_order_id": "35620929", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:54:36,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2653c2fae0582. +2023-09-19 08:54:36,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113676.0, "order_id": "x-XEKWYICXSSIUT605b2653c324d0582", "exchange_order_id": "35620928", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:54:36,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2653c324d0582. +2023-09-19 08:54:36,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b268836bf70582 for 80.00000000 SEI-USDT. +2023-09-19 08:54:36,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b268836bf70582", "creation_timestamp": 1695113676.0, "exchange_order_id": "35621045", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:54:36,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2688369ae0582 for 80.00000000 SEI-USDT. +2023-09-19 08:54:36,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113676.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b2688369ae0582", "creation_timestamp": 1695113676.0, "exchange_order_id": "35621046", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:55:21,862 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b268836bf70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:55:21,864 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:55:21+00:00] +2023-09-19 08:55:21,886 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113721.0, "order_id": "x-XEKWYICXSSIUT605b268836bf70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "4998081", "exchange_order_id": "35621045", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:55:21,898 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113721.0, "order_id": "x-XEKWYICXSSIUT605b268836bf70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35621045", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:55:21,899 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b268836bf70582 completely filled. +2023-09-19 08:55:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2688369ae0582. [clock=2023-09-19 08:55:31+00:00] +2023-09-19 08:55:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246611676346664294537838612 amount: 80. +2023-09-19 08:55:31,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255552828133693996327919894 amount: 80. +2023-09-19 08:55:31,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113731.0, "order_id": "x-XEKWYICXBSIUT605b2688369ae0582", "exchange_order_id": "35621046", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:55:31,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2688369ae0582. +2023-09-19 08:55:31,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b26bcab3ac0582 for 80.00000000 SEI-USDT. +2023-09-19 08:55:31,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b26bcab3ac0582", "creation_timestamp": 1695113731.0, "exchange_order_id": "35621494", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:55:31,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b26bcab01f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:55:31,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113731.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b26bcab01f0582", "creation_timestamp": 1695113731.0, "exchange_order_id": "35621495", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:56:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b26bcab01f0582. [clock=2023-09-19 08:56:26+00:00] +2023-09-19 08:56:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b26bcab3ac0582. [clock=2023-09-19 08:56:26+00:00] +2023-09-19 08:56:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 08:56:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262059956381814940360205423 amount: 80. +2023-09-19 08:56:26,169 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113786.0, "order_id": "x-XEKWYICXBSIUT605b26bcab01f0582", "exchange_order_id": "35621495", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:56:26,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b26bcab01f0582. +2023-09-19 08:56:26,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113786.0, "order_id": "x-XEKWYICXSSIUT605b26bcab3ac0582", "exchange_order_id": "35621494", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:56:26,317 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b26bcab3ac0582. +2023-09-19 08:56:26,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b26f11e81f0582 for 80.00000000 SEI-USDT. +2023-09-19 08:56:26,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b26f11e81f0582", "creation_timestamp": 1695113786.0, "exchange_order_id": "35621734", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:56:26,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b26f11e5150582 for 80.00000000 SEI-USDT. +2023-09-19 08:56:26,343 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113786.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b26f11e5150582", "creation_timestamp": 1695113786.0, "exchange_order_id": "35621735", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:56:45,300 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b26f11e5150582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:56:45,301 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:56:45+00:00] +2023-09-19 08:56:45,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113805.0, "order_id": "x-XEKWYICXBSIUT605b26f11e5150582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4998148", "exchange_order_id": "35621735", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:56:45,338 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113805.0, "order_id": "x-XEKWYICXBSIUT605b26f11e5150582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35621735", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:56:45,339 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b26f11e5150582 completely filled. +2023-09-19 08:57:21,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b26f11e81f0582. [clock=2023-09-19 08:57:21+00:00] +2023-09-19 08:57:21,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 08:57:21,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260905355895161816592578430 amount: 80. +2023-09-19 08:57:21,209 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113841.0, "order_id": "x-XEKWYICXSSIUT605b26f11e81f0582", "exchange_order_id": "35621734", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:57:21,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b26f11e81f0582. +2023-09-19 08:57:21,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2725995cd0582 for 80.00000000 SEI-USDT. +2023-09-19 08:57:21,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b2725995cd0582", "creation_timestamp": 1695113841.0, "exchange_order_id": "35622393", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:57:21,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2725993120582 for 80.00000000 SEI-USDT. +2023-09-19 08:57:21,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b2725993120582", "creation_timestamp": 1695113841.0, "exchange_order_id": "35622394", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:58:04,851 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b2725993120582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 08:58:04,852 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 08:58:04+00:00] +2023-09-19 08:58:04,873 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113884.0, "order_id": "x-XEKWYICXBSIUT605b2725993120582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4998248", "exchange_order_id": "35622394", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 08:58:04,885 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113884.0, "order_id": "x-XEKWYICXBSIUT605b2725993120582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35622394", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 08:58:04,886 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b2725993120582 completely filled. +2023-09-19 08:58:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2725995cd0582. [clock=2023-09-19 08:58:16+00:00] +2023-09-19 08:58:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246479368594915539844814221 amount: 80. +2023-09-19 08:58:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256650917344719254168658799 amount: 80. +2023-09-19 08:58:16,153 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113896.0, "order_id": "x-XEKWYICXSSIUT605b2725995cd0582", "exchange_order_id": "35622393", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:58:16,154 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2725995cd0582. +2023-09-19 08:58:16,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b275a0587d0582 for 80.00000000 SEI-USDT. +2023-09-19 08:58:16,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b275a0587d0582", "creation_timestamp": 1695113896.0, "exchange_order_id": "35622852", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:58:16,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b275a056640582 for 80.00000000 SEI-USDT. +2023-09-19 08:58:16,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b275a056640582", "creation_timestamp": 1695113896.0, "exchange_order_id": "35622854", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:59:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b275a056640582. [clock=2023-09-19 08:59:11+00:00] +2023-09-19 08:59:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b275a0587d0582. [clock=2023-09-19 08:59:11+00:00] +2023-09-19 08:59:11,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252594272473831352870890908 amount: 80. +2023-09-19 08:59:11,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262761947071726928978035008 amount: 80. +2023-09-19 08:59:11,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113951.0, "order_id": "x-XEKWYICXBSIUT605b275a056640582", "exchange_order_id": "35622854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:59:11,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b275a056640582. +2023-09-19 08:59:11,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113951.0, "order_id": "x-XEKWYICXSSIUT605b275a0587d0582", "exchange_order_id": "35622852", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 08:59:11,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b275a0587d0582. +2023-09-19 08:59:11,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b278e79e210582 for 80.00000000 SEI-USDT. +2023-09-19 08:59:11,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b278e79e210582", "creation_timestamp": 1695113951.0, "exchange_order_id": "35623309", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 08:59:11,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b278e7a03c0582 for 80.00000000 SEI-USDT. +2023-09-19 08:59:11,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695113951.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b278e7a03c0582", "creation_timestamp": 1695113951.0, "exchange_order_id": "35623310", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:00:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b278e79e210582. [clock=2023-09-19 09:00:06+00:00] +2023-09-19 09:00:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b278e7a03c0582. [clock=2023-09-19 09:00:06+00:00] +2023-09-19 09:00:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253563446061415102306703896 amount: 80. +2023-09-19 09:00:06,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264315372344651292135932198 amount: 80. +2023-09-19 09:00:06,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114006.0, "order_id": "x-XEKWYICXSSIUT605b278e7a03c0582", "exchange_order_id": "35623310", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:00:06,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b278e7a03c0582. +2023-09-19 09:00:06,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114006.0, "order_id": "x-XEKWYICXBSIUT605b278e79e210582", "exchange_order_id": "35623309", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:00:06,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b278e79e210582. +2023-09-19 09:00:06,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b27c2ed7a70582 for 80.00000000 SEI-USDT. +2023-09-19 09:00:06,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b27c2ed7a70582", "creation_timestamp": 1695114006.0, "exchange_order_id": "35623578", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:00:06,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b27c2edaef0582 for 80.00000000 SEI-USDT. +2023-09-19 09:00:06,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114006.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b27c2edaef0582", "creation_timestamp": 1695114006.0, "exchange_order_id": "35623579", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:00:35,095 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b27c2ed7a70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:00:35,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:00:35+00:00] +2023-09-19 09:00:35,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114035.0, "order_id": "x-XEKWYICXBSIUT605b27c2ed7a70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "4998413", "exchange_order_id": "35623578", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:00:35,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114035.0, "order_id": "x-XEKWYICXBSIUT605b27c2ed7a70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35623578", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:00:35,156 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b27c2ed7a70582 completely filled. +2023-09-19 09:01:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b27c2edaef0582. [clock=2023-09-19 09:01:01+00:00] +2023-09-19 09:01:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253282072252414829670371099 amount: 80. +2023-09-19 09:01:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265151210531110059891294717 amount: 80. +2023-09-19 09:01:01,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114061.0, "order_id": "x-XEKWYICXSSIUT605b27c2edaef0582", "exchange_order_id": "35623579", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:01:01,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b27c2edaef0582. +2023-09-19 09:01:01,239 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b27f7610310582 for 80.00000000 SEI-USDT. +2023-09-19 09:01:01,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b27f7610310582", "creation_timestamp": 1695114061.0, "exchange_order_id": "35624029", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:01:01,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b27f7614170582 for 80.00000000 SEI-USDT. +2023-09-19 09:01:01,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114061.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b27f7614170582", "creation_timestamp": 1695114061.0, "exchange_order_id": "35624030", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:01:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b27f7610310582. [clock=2023-09-19 09:01:56+00:00] +2023-09-19 09:01:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b27f7614170582. [clock=2023-09-19 09:01:56+00:00] +2023-09-19 09:01:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254339963985437449344171665 amount: 80. +2023-09-19 09:01:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265799839487633434639618617 amount: 80. +2023-09-19 09:01:56,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114116.0, "order_id": "x-XEKWYICXBSIUT605b27f7610310582", "exchange_order_id": "35624029", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:01:56,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b27f7610310582. +2023-09-19 09:01:56,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b282bd4c180582 for 80.00000000 SEI-USDT. +2023-09-19 09:01:56,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b282bd4c180582", "creation_timestamp": 1695114116.0, "exchange_order_id": "35624462", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:01:56,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b282bd4f170582 for 80.00000000 SEI-USDT. +2023-09-19 09:01:56,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b282bd4f170582", "creation_timestamp": 1695114116.0, "exchange_order_id": "35624463", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:01:56,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114116.0, "order_id": "x-XEKWYICXSSIUT605b27f7614170582", "exchange_order_id": "35624030", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:01:56,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b27f7614170582. +2023-09-19 09:02:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b282bd4c180582. [clock=2023-09-19 09:02:51+00:00] +2023-09-19 09:02:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b282bd4f170582. [clock=2023-09-19 09:02:51+00:00] +2023-09-19 09:02:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254385886271435607729367851 amount: 80. +2023-09-19 09:02:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265518641896552597454122432 amount: 80. +2023-09-19 09:02:51,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114171.0, "order_id": "x-XEKWYICXBSIUT605b282bd4c180582", "exchange_order_id": "35624462", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:02:51,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b282bd4c180582. +2023-09-19 09:02:51,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114171.0, "order_id": "x-XEKWYICXSSIUT605b282bd4f170582", "exchange_order_id": "35624463", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:02:51,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b282bd4f170582. +2023-09-19 09:02:51,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b286048bca0582 for 80.00000000 SEI-USDT. +2023-09-19 09:02:51,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b286048bca0582", "creation_timestamp": 1695114171.0, "exchange_order_id": "35624766", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:02:51,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2860488420582 for 80.00000000 SEI-USDT. +2023-09-19 09:02:51,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b2860488420582", "creation_timestamp": 1695114171.0, "exchange_order_id": "35624765", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:03:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2860488420582. [clock=2023-09-19 09:03:46+00:00] +2023-09-19 09:03:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b286048bca0582. [clock=2023-09-19 09:03:46+00:00] +2023-09-19 09:03:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250106026430288209059508156 amount: 80. +2023-09-19 09:03:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260971431795245161897179353 amount: 80. +2023-09-19 09:03:46,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114226.0, "order_id": "x-XEKWYICXBSIUT605b2860488420582", "exchange_order_id": "35624765", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:03:46,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2860488420582. +2023-09-19 09:03:46,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114226.0, "order_id": "x-XEKWYICXSSIUT605b286048bca0582", "exchange_order_id": "35624766", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:03:46,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b286048bca0582. +2023-09-19 09:03:46,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2894bbf540582 for 80.00000000 SEI-USDT. +2023-09-19 09:03:46,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b2894bbf540582", "creation_timestamp": 1695114226.0, "exchange_order_id": "35624981", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:03:46,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2894bc2fd0582 for 80.00000000 SEI-USDT. +2023-09-19 09:03:46,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114226.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b2894bc2fd0582", "creation_timestamp": 1695114226.0, "exchange_order_id": "35624982", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:04:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2894bbf540582. [clock=2023-09-19 09:04:41+00:00] +2023-09-19 09:04:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2894bc2fd0582. [clock=2023-09-19 09:04:41+00:00] +2023-09-19 09:04:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248439405479368180880735564 amount: 80. +2023-09-19 09:04:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259106942509819668421704998 amount: 80. +2023-09-19 09:04:41,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114281.0, "order_id": "x-XEKWYICXBSIUT605b2894bbf540582", "exchange_order_id": "35624981", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:04:41,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2894bbf540582. +2023-09-19 09:04:41,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114281.0, "order_id": "x-XEKWYICXSSIUT605b2894bc2fd0582", "exchange_order_id": "35624982", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:04:41,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2894bc2fd0582. +2023-09-19 09:04:41,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b28c92fdcc0582 for 80.00000000 SEI-USDT. +2023-09-19 09:04:41,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b28c92fdcc0582", "creation_timestamp": 1695114281.0, "exchange_order_id": "35625160", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:04:41,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b28c92fba30582 for 80.00000000 SEI-USDT. +2023-09-19 09:04:41,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114281.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b28c92fba30582", "creation_timestamp": 1695114281.0, "exchange_order_id": "35625161", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:05:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b28c92fba30582. [clock=2023-09-19 09:05:36+00:00] +2023-09-19 09:05:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b28c92fdcc0582. [clock=2023-09-19 09:05:36+00:00] +2023-09-19 09:05:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242871311328062406460448006 amount: 80. +2023-09-19 09:05:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255586023299947448883005010 amount: 80. +2023-09-19 09:05:36,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114336.0, "order_id": "x-XEKWYICXBSIUT605b28c92fba30582", "exchange_order_id": "35625161", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:05:36,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b28c92fba30582. +2023-09-19 09:05:36,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b28fda39bc0582 for 80.00000000 SEI-USDT. +2023-09-19 09:05:36,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b28fda39bc0582", "creation_timestamp": 1695114336.0, "exchange_order_id": "35625730", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:05:36,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114336.0, "order_id": "x-XEKWYICXSSIUT605b28c92fdcc0582", "exchange_order_id": "35625160", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:05:36,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b28c92fdcc0582. +2023-09-19 09:05:36,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b28fda3cd60582 for 80.00000000 SEI-USDT. +2023-09-19 09:05:36,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114336.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b28fda3cd60582", "creation_timestamp": 1695114336.0, "exchange_order_id": "35625731", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:06:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b28fda39bc0582. [clock=2023-09-19 09:06:31+00:00] +2023-09-19 09:06:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b28fda3cd60582. [clock=2023-09-19 09:06:31+00:00] +2023-09-19 09:06:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244894345416527099021483008 amount: 80. +2023-09-19 09:06:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255902515758721665067967952 amount: 80. +2023-09-19 09:06:31,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114391.0, "order_id": "x-XEKWYICXBSIUT605b28fda39bc0582", "exchange_order_id": "35625730", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:06:31,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b28fda39bc0582. +2023-09-19 09:06:31,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114391.0, "order_id": "x-XEKWYICXSSIUT605b28fda3cd60582", "exchange_order_id": "35625731", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:06:31,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b28fda3cd60582. +2023-09-19 09:06:31,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2932177610582 for 80.00000000 SEI-USDT. +2023-09-19 09:06:31,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b2932177610582", "creation_timestamp": 1695114391.0, "exchange_order_id": "35625937", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:06:31,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b293217b6f0582 for 80.00000000 SEI-USDT. +2023-09-19 09:06:31,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114391.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b293217b6f0582", "creation_timestamp": 1695114391.0, "exchange_order_id": "35625938", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:07:26,008 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2932177610582. [clock=2023-09-19 09:07:26+00:00] +2023-09-19 09:07:26,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b293217b6f0582. [clock=2023-09-19 09:07:26+00:00] +2023-09-19 09:07:26,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246333708277788130483960971 amount: 80. +2023-09-19 09:07:26,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256003542114994806902104753 amount: 80. +2023-09-19 09:07:26,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114446.0, "order_id": "x-XEKWYICXBSIUT605b2932177610582", "exchange_order_id": "35625937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:07:26,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2932177610582. +2023-09-19 09:07:26,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114446.0, "order_id": "x-XEKWYICXSSIUT605b293217b6f0582", "exchange_order_id": "35625938", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:07:26,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b293217b6f0582. +2023-09-19 09:07:26,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b29668cdb20582 for 80.00000000 SEI-USDT. +2023-09-19 09:07:26,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b29668cdb20582", "creation_timestamp": 1695114446.0, "exchange_order_id": "35626364", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:07:26,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b29668d0da0582 for 80.00000000 SEI-USDT. +2023-09-19 09:07:26,401 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b29668d0da0582", "creation_timestamp": 1695114446.0, "exchange_order_id": "35626365", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:08:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b29668cdb20582. [clock=2023-09-19 09:08:21+00:00] +2023-09-19 09:08:21,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b29668d0da0582. [clock=2023-09-19 09:08:21+00:00] +2023-09-19 09:08:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248083000022411142155689999 amount: 80. +2023-09-19 09:08:21,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256474354635699193337406277 amount: 80. +2023-09-19 09:08:21,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114501.0, "order_id": "x-XEKWYICXBSIUT605b29668cdb20582", "exchange_order_id": "35626364", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:08:21,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b29668cdb20582. +2023-09-19 09:08:21,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b299aff2b30582 for 80.00000000 SEI-USDT. +2023-09-19 09:08:21,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b299aff2b30582", "creation_timestamp": 1695114501.0, "exchange_order_id": "35626667", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:08:21,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b299aff5a80582 for 80.00000000 SEI-USDT. +2023-09-19 09:08:21,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b299aff5a80582", "creation_timestamp": 1695114501.0, "exchange_order_id": "35626668", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:08:21,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114501.0, "order_id": "x-XEKWYICXSSIUT605b29668d0da0582", "exchange_order_id": "35626365", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:08:21,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b29668d0da0582. +2023-09-19 09:09:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b299aff2b30582. [clock=2023-09-19 09:09:16+00:00] +2023-09-19 09:09:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b299aff5a80582. [clock=2023-09-19 09:09:16+00:00] +2023-09-19 09:09:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246660167447454873348052905 amount: 80. +2023-09-19 09:09:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255770133261092590358556253 amount: 80. +2023-09-19 09:09:16,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114556.0, "order_id": "x-XEKWYICXBSIUT605b299aff2b30582", "exchange_order_id": "35626667", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:09:16,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b299aff2b30582. +2023-09-19 09:09:16,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114556.0, "order_id": "x-XEKWYICXSSIUT605b299aff5a80582", "exchange_order_id": "35626668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:09:16,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b299aff5a80582. +2023-09-19 09:09:16,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b29cf722610582 for 80.00000000 SEI-USDT. +2023-09-19 09:09:16,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b29cf722610582", "creation_timestamp": 1695114556.0, "exchange_order_id": "35627067", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:09:16,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b29cf725340582 for 80.00000000 SEI-USDT. +2023-09-19 09:09:16,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114556.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b29cf725340582", "creation_timestamp": 1695114556.0, "exchange_order_id": "35627066", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:09:28,220 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b29cf725340582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:09:28,222 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:09:28+00:00] +2023-09-19 09:09:28,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114568.0, "order_id": "x-XEKWYICXSSIUT605b29cf725340582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01004000"}]}, "exchange_trade_id": "4998981", "exchange_order_id": "35627066", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:09:28,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114568.0, "order_id": "x-XEKWYICXSSIUT605b29cf725340582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35627066", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:09:28,284 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b29cf725340582 completely filled. +2023-09-19 09:10:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b29cf722610582. [clock=2023-09-19 09:10:11+00:00] +2023-09-19 09:10:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249810791330925387731878744 amount: 80. +2023-09-19 09:10:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261480328601014575614567226 amount: 80. +2023-09-19 09:10:11,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114611.0, "order_id": "x-XEKWYICXBSIUT605b29cf722610582", "exchange_order_id": "35627067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:10:11,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b29cf722610582. +2023-09-19 09:10:11,158 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2a03e603e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:10:11,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b2a03e603e0582", "creation_timestamp": 1695114611.0, "exchange_order_id": "35627656", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:10:11,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2a03e63120582 for 80.00000000 SEI-USDT. +2023-09-19 09:10:11,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114611.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b2a03e63120582", "creation_timestamp": 1695114611.0, "exchange_order_id": "35627657", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:11:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2a03e603e0582. [clock=2023-09-19 09:11:06+00:00] +2023-09-19 09:11:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2a03e63120582. [clock=2023-09-19 09:11:06+00:00] +2023-09-19 09:11:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249875353657323672406479872 amount: 80. +2023-09-19 09:11:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259817899679384730378356302 amount: 80. +2023-09-19 09:11:06,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114666.0, "order_id": "x-XEKWYICXBSIUT605b2a03e603e0582", "exchange_order_id": "35627656", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:11:06,189 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2a03e603e0582. +2023-09-19 09:11:06,480 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2a385a3b60582 for 80.00000000 SEI-USDT. +2023-09-19 09:11:06,494 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114666.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b2a385a3b60582", "creation_timestamp": 1695114666.0, "exchange_order_id": "35627933", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:11:06,507 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114666.0, "order_id": "x-XEKWYICXSSIUT605b2a03e63120582", "exchange_order_id": "35627657", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:11:06,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2a03e63120582. +2023-09-19 09:11:06,562 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2a385a0660582 for 80.00000000 SEI-USDT. +2023-09-19 09:11:06,574 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114666.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b2a385a0660582", "creation_timestamp": 1695114666.0, "exchange_order_id": "35627934", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:12:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2a385a0660582. [clock=2023-09-19 09:12:01+00:00] +2023-09-19 09:12:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2a385a3b60582. [clock=2023-09-19 09:12:01+00:00] +2023-09-19 09:12:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249708122860347192272755343 amount: 80. +2023-09-19 09:12:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261873845831161998529346157 amount: 80. +2023-09-19 09:12:01,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114721.0, "order_id": "x-XEKWYICXBSIUT605b2a385a0660582", "exchange_order_id": "35627934", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:12:01,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2a385a0660582. +2023-09-19 09:12:01,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114721.0, "order_id": "x-XEKWYICXSSIUT605b2a385a3b60582", "exchange_order_id": "35627933", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:12:01,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2a385a3b60582. +2023-09-19 09:12:01,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2a6cce04a0582 for 80.00000000 SEI-USDT. +2023-09-19 09:12:01,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b2a6cce04a0582", "creation_timestamp": 1695114721.0, "exchange_order_id": "35628205", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:12:01,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2a6ccdd7d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:12:01,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b2a6ccdd7d0582", "creation_timestamp": 1695114721.0, "exchange_order_id": "35628206", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:12:51,308 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 09:12:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2a6ccdd7d0582. [clock=2023-09-19 09:12:56+00:00] +2023-09-19 09:12:56,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2a6cce04a0582. [clock=2023-09-19 09:12:56+00:00] +2023-09-19 09:12:56,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245690467906530941472463068 amount: 80. +2023-09-19 09:12:56,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257355191282875424458235516 amount: 80. +2023-09-19 09:12:56,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114776.0, "order_id": "x-XEKWYICXBSIUT605b2a6ccdd7d0582", "exchange_order_id": "35628206", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:12:56,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2a6ccdd7d0582. +2023-09-19 09:12:56,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114776.0, "order_id": "x-XEKWYICXSSIUT605b2a6cce04a0582", "exchange_order_id": "35628205", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:12:56,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2a6cce04a0582. +2023-09-19 09:12:56,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2aa141f030582 for 80.00000000 SEI-USDT. +2023-09-19 09:12:56,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b2aa141f030582", "creation_timestamp": 1695114776.0, "exchange_order_id": "35628566", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:12:56,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2aa14225d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:12:56,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b2aa14225d0582", "creation_timestamp": 1695114776.0, "exchange_order_id": "35628567", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:13:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2aa141f030582. [clock=2023-09-19 09:13:51+00:00] +2023-09-19 09:13:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2aa14225d0582. [clock=2023-09-19 09:13:51+00:00] +2023-09-19 09:13:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248132325327810029436404450 amount: 80. +2023-09-19 09:13:51,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260552319155763130821945406 amount: 80. +2023-09-19 09:13:51,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114831.0, "order_id": "x-XEKWYICXBSIUT605b2aa141f030582", "exchange_order_id": "35628566", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:13:51,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2aa141f030582. +2023-09-19 09:13:51,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2ad5b63580582 for 80.00000000 SEI-USDT. +2023-09-19 09:13:51,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b2ad5b63580582", "creation_timestamp": 1695114831.0, "exchange_order_id": "35628996", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:13:51,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114831.0, "order_id": "x-XEKWYICXSSIUT605b2aa14225d0582", "exchange_order_id": "35628567", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:13:51,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2aa14225d0582. +2023-09-19 09:13:51,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2ad5b65e90582 for 80.00000000 SEI-USDT. +2023-09-19 09:13:51,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114831.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b2ad5b65e90582", "creation_timestamp": 1695114831.0, "exchange_order_id": "35628997", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:14:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2ad5b63580582. [clock=2023-09-19 09:14:46+00:00] +2023-09-19 09:14:46,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2ad5b65e90582. [clock=2023-09-19 09:14:46+00:00] +2023-09-19 09:14:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245489210031001213997024673 amount: 80. +2023-09-19 09:14:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257353825697750855461183781 amount: 80. +2023-09-19 09:14:46,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114886.0, "order_id": "x-XEKWYICXBSIUT605b2ad5b63580582", "exchange_order_id": "35628996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:14:46,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2ad5b63580582. +2023-09-19 09:14:46,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2b0a290a30582 for 80.00000000 SEI-USDT. +2023-09-19 09:14:46,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b2b0a290a30582", "creation_timestamp": 1695114886.0, "exchange_order_id": "35629318", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:14:46,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2b0a294fb0582 for 80.00000000 SEI-USDT. +2023-09-19 09:14:46,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114886.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b2b0a294fb0582", "creation_timestamp": 1695114886.0, "exchange_order_id": "35629319", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:14:46,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114886.0, "order_id": "x-XEKWYICXSSIUT605b2ad5b65e90582", "exchange_order_id": "35628997", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:14:46,346 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2ad5b65e90582. +2023-09-19 09:15:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2b0a290a30582. [clock=2023-09-19 09:15:41+00:00] +2023-09-19 09:15:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2b0a294fb0582. [clock=2023-09-19 09:15:41+00:00] +2023-09-19 09:15:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246717541996158431644475575 amount: 80. +2023-09-19 09:15:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258174370267702793921286019 amount: 80. +2023-09-19 09:15:41,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114941.0, "order_id": "x-XEKWYICXBSIUT605b2b0a290a30582", "exchange_order_id": "35629318", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:15:41,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2b0a290a30582. +2023-09-19 09:15:41,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114941.0, "order_id": "x-XEKWYICXSSIUT605b2b0a294fb0582", "exchange_order_id": "35629319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:15:41,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2b0a294fb0582. +2023-09-19 09:15:41,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2b3e9d0320582 for 80.00000000 SEI-USDT. +2023-09-19 09:15:41,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b2b3e9d0320582", "creation_timestamp": 1695114941.0, "exchange_order_id": "35629657", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:15:41,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2b3e9cc9d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:15:41,340 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114941.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b2b3e9cc9d0582", "creation_timestamp": 1695114941.0, "exchange_order_id": "35629658", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:16:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2b3e9cc9d0582. [clock=2023-09-19 09:16:36+00:00] +2023-09-19 09:16:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2b3e9d0320582. [clock=2023-09-19 09:16:36+00:00] +2023-09-19 09:16:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250945009293408441597857895 amount: 80. +2023-09-19 09:16:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264321634647103997597345140 amount: 80. +2023-09-19 09:16:36,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114996.0, "order_id": "x-XEKWYICXBSIUT605b2b3e9cc9d0582", "exchange_order_id": "35629658", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:16:36,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2b3e9cc9d0582. +2023-09-19 09:16:36,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2b73104e80582 for 80.00000000 SEI-USDT. +2023-09-19 09:16:36,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b2b73104e80582", "creation_timestamp": 1695114996.0, "exchange_order_id": "35630152", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:16:36,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114996.0, "order_id": "x-XEKWYICXSSIUT605b2b3e9d0320582", "exchange_order_id": "35629657", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:16:36,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2b3e9d0320582. +2023-09-19 09:16:36,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2b73101820582 for 80.00000000 SEI-USDT. +2023-09-19 09:16:36,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695114996.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b2b73101820582", "creation_timestamp": 1695114996.0, "exchange_order_id": "35630153", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:17:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2b73101820582. [clock=2023-09-19 09:17:31+00:00] +2023-09-19 09:17:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2b73104e80582. [clock=2023-09-19 09:17:31+00:00] +2023-09-19 09:17:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257397822403228406807199894 amount: 80. +2023-09-19 09:17:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1271814278318867498490529260 amount: 80. +2023-09-19 09:17:31,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115051.0, "order_id": "x-XEKWYICXBSIUT605b2b73101820582", "exchange_order_id": "35630153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:17:31,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2b73101820582. +2023-09-19 09:17:31,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115051.0, "order_id": "x-XEKWYICXSSIUT605b2b73104e80582", "exchange_order_id": "35630152", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:17:31,434 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2b73104e80582. +2023-09-19 09:17:31,435 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2ba78436d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:17:31,448 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b2ba78436d0582", "creation_timestamp": 1695115051.0, "exchange_order_id": "35630736", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:17:31,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2ba7846720582 for 80.00000000 SEI-USDT. +2023-09-19 09:17:31,463 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12710000", "order_id": "x-XEKWYICXSSIUT605b2ba7846720582", "creation_timestamp": 1695115051.0, "exchange_order_id": "35630737", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:18:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2ba78436d0582. [clock=2023-09-19 09:18:26+00:00] +2023-09-19 09:18:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2ba7846720582. [clock=2023-09-19 09:18:26+00:00] +2023-09-19 09:18:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258446693162378783199206112 amount: 80. +2023-09-19 09:18:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1271639246784482605526591016 amount: 80. +2023-09-19 09:18:26,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115106.0, "order_id": "x-XEKWYICXBSIUT605b2ba78436d0582", "exchange_order_id": "35630736", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:18:26,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2ba78436d0582. +2023-09-19 09:18:26,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115106.0, "order_id": "x-XEKWYICXSSIUT605b2ba7846720582", "exchange_order_id": "35630737", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:18:26,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2ba7846720582. +2023-09-19 09:18:26,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2bdbf79b50582 for 80.00000000 SEI-USDT. +2023-09-19 09:18:26,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b2bdbf79b50582", "creation_timestamp": 1695115106.0, "exchange_order_id": "35631287", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:18:26,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2bdbf7b990582 for 80.00000000 SEI-USDT. +2023-09-19 09:18:26,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12710000", "order_id": "x-XEKWYICXSSIUT605b2bdbf7b990582", "creation_timestamp": 1695115106.0, "exchange_order_id": "35631288", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:19:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2bdbf79b50582. [clock=2023-09-19 09:19:21+00:00] +2023-09-19 09:19:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2bdbf7b990582. [clock=2023-09-19 09:19:21+00:00] +2023-09-19 09:19:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257573885205425669902575175 amount: 80. +2023-09-19 09:19:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267815714028094841773700291 amount: 80. +2023-09-19 09:19:21,171 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115161.0, "order_id": "x-XEKWYICXBSIUT605b2bdbf79b50582", "exchange_order_id": "35631287", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:19:21,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2bdbf79b50582. +2023-09-19 09:19:21,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2c106b7c30582 for 80.00000000 SEI-USDT. +2023-09-19 09:19:21,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b2c106b7c30582", "creation_timestamp": 1695115161.0, "exchange_order_id": "35631524", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:19:21,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115161.0, "order_id": "x-XEKWYICXSSIUT605b2bdbf7b990582", "exchange_order_id": "35631288", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:19:21,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2bdbf7b990582. +2023-09-19 09:19:21,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2c106b5210582 for 80.00000000 SEI-USDT. +2023-09-19 09:19:21,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115161.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b2c106b5210582", "creation_timestamp": 1695115161.0, "exchange_order_id": "35631525", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:20:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2c106b5210582. [clock=2023-09-19 09:20:16+00:00] +2023-09-19 09:20:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2c106b7c30582. [clock=2023-09-19 09:20:16+00:00] +2023-09-19 09:20:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258502131253965680911961518 amount: 80. +2023-09-19 09:20:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1270437496997886870714011862 amount: 80. +2023-09-19 09:20:16,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115216.0, "order_id": "x-XEKWYICXBSIUT605b2c106b5210582", "exchange_order_id": "35631525", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:20:16,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2c106b5210582. +2023-09-19 09:20:16,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2c44df4c30582 for 80.00000000 SEI-USDT. +2023-09-19 09:20:16,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b2c44df4c30582", "creation_timestamp": 1695115216.0, "exchange_order_id": "35632102", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:20:16,389 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2c44df9650582 for 80.00000000 SEI-USDT. +2023-09-19 09:20:16,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115216.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12700000", "order_id": "x-XEKWYICXSSIUT605b2c44df9650582", "creation_timestamp": 1695115216.0, "exchange_order_id": "35632103", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:20:16,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115216.0, "order_id": "x-XEKWYICXSSIUT605b2c106b7c30582", "exchange_order_id": "35631524", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:20:16,432 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2c106b7c30582. +2023-09-19 09:21:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2c44df4c30582. [clock=2023-09-19 09:21:11+00:00] +2023-09-19 09:21:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2c44df9650582. [clock=2023-09-19 09:21:11+00:00] +2023-09-19 09:21:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257615774671069689640246566 amount: 80. +2023-09-19 09:21:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266889921569667909015930346 amount: 80. +2023-09-19 09:21:11,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115271.0, "order_id": "x-XEKWYICXBSIUT605b2c44df4c30582", "exchange_order_id": "35632102", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:21:11,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2c44df4c30582. +2023-09-19 09:21:11,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115271.0, "order_id": "x-XEKWYICXSSIUT605b2c44df9650582", "exchange_order_id": "35632103", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:21:11,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2c44df9650582. +2023-09-19 09:21:11,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2c7952f920582 for 80.00000000 SEI-USDT. +2023-09-19 09:21:11,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b2c7952f920582", "creation_timestamp": 1695115271.0, "exchange_order_id": "35632391", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:21:11,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2c795333d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:21:11,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115271.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b2c795333d0582", "creation_timestamp": 1695115271.0, "exchange_order_id": "35632392", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:22:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2c7952f920582. [clock=2023-09-19 09:22:06+00:00] +2023-09-19 09:22:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2c795333d0582. [clock=2023-09-19 09:22:06+00:00] +2023-09-19 09:22:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257594358473749565471540006 amount: 80. +2023-09-19 09:22:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267408808473135455255707138 amount: 80. +2023-09-19 09:22:06,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115326.0, "order_id": "x-XEKWYICXBSIUT605b2c7952f920582", "exchange_order_id": "35632391", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:22:06,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2c7952f920582. +2023-09-19 09:22:06,200 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115326.0, "order_id": "x-XEKWYICXSSIUT605b2c795333d0582", "exchange_order_id": "35632392", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:22:06,200 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2c795333d0582. +2023-09-19 09:22:06,317 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2cadc709b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:22:06,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b2cadc709b0582", "creation_timestamp": 1695115326.0, "exchange_order_id": "35632836", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:22:06,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2cadc73c60582 for 80.00000000 SEI-USDT. +2023-09-19 09:22:06,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b2cadc73c60582", "creation_timestamp": 1695115326.0, "exchange_order_id": "35632835", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:23:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2cadc709b0582. [clock=2023-09-19 09:23:01+00:00] +2023-09-19 09:23:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2cadc73c60582. [clock=2023-09-19 09:23:01+00:00] +2023-09-19 09:23:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262295916037129904320248148 amount: 80. +2023-09-19 09:23:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1278848459709298968005643285 amount: 80. +2023-09-19 09:23:01,166 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115381.0, "order_id": "x-XEKWYICXBSIUT605b2cadc709b0582", "exchange_order_id": "35632836", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:23:01,166 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2cadc709b0582. +2023-09-19 09:23:01,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2ce23a8510582 for 80.00000000 SEI-USDT. +2023-09-19 09:23:01,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605b2ce23a8510582", "creation_timestamp": 1695115381.0, "exchange_order_id": "35634647", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:23:01,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115381.0, "order_id": "x-XEKWYICXSSIUT605b2cadc73c60582", "exchange_order_id": "35632835", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:23:01,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2cadc73c60582. +2023-09-19 09:23:01,318 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2ce23ac580582 for 80.00000000 SEI-USDT. +2023-09-19 09:23:01,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12780000", "order_id": "x-XEKWYICXSSIUT605b2ce23ac580582", "creation_timestamp": 1695115381.0, "exchange_order_id": "35634648", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:23:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2ce23a8510582. [clock=2023-09-19 09:23:56+00:00] +2023-09-19 09:23:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2ce23ac580582. [clock=2023-09-19 09:23:56+00:00] +2023-09-19 09:23:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1262398130097610756290621650 amount: 80. +2023-09-19 09:23:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1276377058247201180695628876 amount: 80. +2023-09-19 09:23:56,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115436.0, "order_id": "x-XEKWYICXBSIUT605b2ce23a8510582", "exchange_order_id": "35634647", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:23:56,173 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2ce23a8510582. +2023-09-19 09:23:56,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115436.0, "order_id": "x-XEKWYICXSSIUT605b2ce23ac580582", "exchange_order_id": "35634648", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:23:56,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2ce23ac580582. +2023-09-19 09:23:56,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2d16ae3580582 for 80.00000000 SEI-USDT. +2023-09-19 09:23:56,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXBSIUT605b2d16ae3580582", "creation_timestamp": 1695115436.0, "exchange_order_id": "35634994", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:23:56,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2d16ae70a0582 for 80.00000000 SEI-USDT. +2023-09-19 09:23:56,346 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115436.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12760000", "order_id": "x-XEKWYICXSSIUT605b2d16ae70a0582", "creation_timestamp": 1695115436.0, "exchange_order_id": "35634995", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:23:58,995 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b2d16ae3580582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:23:58,997 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:23:58+00:00] +2023-09-19 09:23:59,021 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115438.0, "order_id": "x-XEKWYICXBSIUT605b2d16ae3580582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12620000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5000345", "exchange_order_id": "35634994", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:23:59,033 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115438.0, "order_id": "x-XEKWYICXBSIUT605b2d16ae3580582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35634994", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:23:59,033 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b2d16ae3580582 completely filled. +2023-09-19 09:24:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2d16ae70a0582. [clock=2023-09-19 09:24:51+00:00] +2023-09-19 09:24:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253595775857182886402873157 amount: 80. +2023-09-19 09:24:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1269233520004983641526041648 amount: 80. +2023-09-19 09:24:51,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115491.0, "order_id": "x-XEKWYICXSSIUT605b2d16ae70a0582", "exchange_order_id": "35634995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:24:51,152 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2d16ae70a0582. +2023-09-19 09:24:51,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2d4b21b600582 for 80.00000000 SEI-USDT. +2023-09-19 09:24:51,239 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b2d4b21b600582", "creation_timestamp": 1695115491.0, "exchange_order_id": "35636143", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:24:51,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2d4b21eea0582 for 80.00000000 SEI-USDT. +2023-09-19 09:24:51,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115491.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXSSIUT605b2d4b21eea0582", "creation_timestamp": 1695115491.0, "exchange_order_id": "35636144", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:25:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2d4b21b600582. [clock=2023-09-19 09:25:46+00:00] +2023-09-19 09:25:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2d4b21eea0582. [clock=2023-09-19 09:25:46+00:00] +2023-09-19 09:25:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260880337157790230442317530 amount: 80. +2023-09-19 09:25:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1275319319438860881645348980 amount: 80. +2023-09-19 09:25:46,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115546.0, "order_id": "x-XEKWYICXBSIUT605b2d4b21b600582", "exchange_order_id": "35636143", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:25:46,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2d4b21b600582. +2023-09-19 09:25:46,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115546.0, "order_id": "x-XEKWYICXSSIUT605b2d4b21eea0582", "exchange_order_id": "35636144", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:25:46,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2d4b21eea0582. +2023-09-19 09:25:46,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2d7f9582e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:25:46,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b2d7f9582e0582", "creation_timestamp": 1695115546.0, "exchange_order_id": "35636561", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:25:46,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2d7f95b790582 for 80.00000000 SEI-USDT. +2023-09-19 09:25:46,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115546.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12750000", "order_id": "x-XEKWYICXSSIUT605b2d7f95b790582", "creation_timestamp": 1695115546.0, "exchange_order_id": "35636562", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:26:23,137 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b2d7f9582e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:26:23,138 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:26:23+00:00] +2023-09-19 09:26:23,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115583.0, "order_id": "x-XEKWYICXBSIUT605b2d7f9582e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12600000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5000519", "exchange_order_id": "35636561", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:26:23,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115583.0, "order_id": "x-XEKWYICXBSIUT605b2d7f9582e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35636561", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:26:23,174 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b2d7f9582e0582 completely filled. +2023-09-19 09:26:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2d7f95b790582. [clock=2023-09-19 09:26:41+00:00] +2023-09-19 09:26:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251764918903590377372610900 amount: 80. +2023-09-19 09:26:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266012599704551215890235456 amount: 80. +2023-09-19 09:26:41,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115601.0, "order_id": "x-XEKWYICXSSIUT605b2d7f95b790582", "exchange_order_id": "35636562", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:26:41,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2d7f95b790582. +2023-09-19 09:26:41,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2db409ab80582 for 80.00000000 SEI-USDT. +2023-09-19 09:26:41,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b2db409ab80582", "creation_timestamp": 1695115601.0, "exchange_order_id": "35637316", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:26:41,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2db409e240582 for 80.00000000 SEI-USDT. +2023-09-19 09:26:41,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115601.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b2db409e240582", "creation_timestamp": 1695115601.0, "exchange_order_id": "35637317", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:27:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2db409ab80582. [clock=2023-09-19 09:27:36+00:00] +2023-09-19 09:27:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2db409e240582. [clock=2023-09-19 09:27:36+00:00] +2023-09-19 09:27:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252318622668898530899160006 amount: 80. +2023-09-19 09:27:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265619502221740538530595431 amount: 80. +2023-09-19 09:27:36,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115656.0, "order_id": "x-XEKWYICXBSIUT605b2db409ab80582", "exchange_order_id": "35637316", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:27:36,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2db409ab80582. +2023-09-19 09:27:36,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2de87d8310582 for 80.00000000 SEI-USDT. +2023-09-19 09:27:36,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b2de87d8310582", "creation_timestamp": 1695115656.0, "exchange_order_id": "35637850", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:27:36,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115656.0, "order_id": "x-XEKWYICXSSIUT605b2db409e240582", "exchange_order_id": "35637317", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:27:36,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2db409e240582. +2023-09-19 09:27:36,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2de87d6440582 for 80.00000000 SEI-USDT. +2023-09-19 09:27:36,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b2de87d6440582", "creation_timestamp": 1695115656.0, "exchange_order_id": "35637851", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:28:31,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2de87d6440582. [clock=2023-09-19 09:28:31+00:00] +2023-09-19 09:28:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2de87d8310582. [clock=2023-09-19 09:28:31+00:00] +2023-09-19 09:28:31,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250516284906443093328341815 amount: 80. +2023-09-19 09:28:31,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263068287669415446191609842 amount: 80. +2023-09-19 09:28:31,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115711.0, "order_id": "x-XEKWYICXBSIUT605b2de87d6440582", "exchange_order_id": "35637851", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:28:31,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2de87d6440582. +2023-09-19 09:28:31,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2e1cf66aa0582 for 80.00000000 SEI-USDT. +2023-09-19 09:28:31,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b2e1cf66aa0582", "creation_timestamp": 1695115711.0, "exchange_order_id": "35638237", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:28:31,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2e1cf637b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:28:31,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b2e1cf637b0582", "creation_timestamp": 1695115711.0, "exchange_order_id": "35638238", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:28:31,350 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115711.0, "order_id": "x-XEKWYICXSSIUT605b2de87d8310582", "exchange_order_id": "35637850", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:28:31,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2de87d8310582. +2023-09-19 09:29:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2e1cf637b0582. [clock=2023-09-19 09:29:26+00:00] +2023-09-19 09:29:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2e1cf66aa0582. [clock=2023-09-19 09:29:26+00:00] +2023-09-19 09:29:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251492060956535897051563094 amount: 80. +2023-09-19 09:29:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263477897116977700829638734 amount: 80. +2023-09-19 09:29:26,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115766.0, "order_id": "x-XEKWYICXBSIUT605b2e1cf637b0582", "exchange_order_id": "35638238", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:29:26,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2e1cf637b0582. +2023-09-19 09:29:26,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115766.0, "order_id": "x-XEKWYICXSSIUT605b2e1cf66aa0582", "exchange_order_id": "35638237", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:29:26,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2e1cf66aa0582. +2023-09-19 09:29:26,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2e51646840582 for 80.00000000 SEI-USDT. +2023-09-19 09:29:26,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b2e51646840582", "creation_timestamp": 1695115766.0, "exchange_order_id": "35638647", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:29:26,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2e5164cae0582 for 80.00000000 SEI-USDT. +2023-09-19 09:29:26,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115766.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b2e5164cae0582", "creation_timestamp": 1695115766.0, "exchange_order_id": "35638648", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:30:21,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2e51646840582. [clock=2023-09-19 09:30:21+00:00] +2023-09-19 09:30:21,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2e5164cae0582. [clock=2023-09-19 09:30:21+00:00] +2023-09-19 09:30:21,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253467023889869151236941516 amount: 80. +2023-09-19 09:30:21,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265871453799763217625210433 amount: 80. +2023-09-19 09:30:21,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115821.0, "order_id": "x-XEKWYICXBSIUT605b2e51646840582", "exchange_order_id": "35638647", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:30:21,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2e51646840582. +2023-09-19 09:30:21,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2e85dff8c0582 for 80.00000000 SEI-USDT. +2023-09-19 09:30:21,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b2e85dff8c0582", "creation_timestamp": 1695115821.0, "exchange_order_id": "35639230", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:30:21,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2e85e018b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:30:21,321 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115821.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b2e85e018b0582", "creation_timestamp": 1695115821.0, "exchange_order_id": "35639231", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:30:21,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115821.0, "order_id": "x-XEKWYICXSSIUT605b2e5164cae0582", "exchange_order_id": "35638648", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:30:21,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2e5164cae0582. +2023-09-19 09:31:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2e85dff8c0582. [clock=2023-09-19 09:31:16+00:00] +2023-09-19 09:31:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2e85e018b0582. [clock=2023-09-19 09:31:16+00:00] +2023-09-19 09:31:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253941802768938371814895795 amount: 80. +2023-09-19 09:31:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265562116224028316157702646 amount: 80. +2023-09-19 09:31:16,192 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115876.0, "order_id": "x-XEKWYICXBSIUT605b2e85dff8c0582", "exchange_order_id": "35639230", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:31:16,193 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2e85dff8c0582. +2023-09-19 09:31:16,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2eba4c22f0582 for 80.00000000 SEI-USDT. +2023-09-19 09:31:16,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b2eba4c22f0582", "creation_timestamp": 1695115876.0, "exchange_order_id": "35639776", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:31:16,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2eba4c4e70582 for 80.00000000 SEI-USDT. +2023-09-19 09:31:16,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115876.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b2eba4c4e70582", "creation_timestamp": 1695115876.0, "exchange_order_id": "35639777", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:31:16,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115876.0, "order_id": "x-XEKWYICXSSIUT605b2e85e018b0582", "exchange_order_id": "35639231", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:31:16,308 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2e85e018b0582. +2023-09-19 09:32:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2eba4c22f0582. [clock=2023-09-19 09:32:11+00:00] +2023-09-19 09:32:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2eba4c4e70582. [clock=2023-09-19 09:32:11+00:00] +2023-09-19 09:32:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255981292149957986976142019 amount: 80. +2023-09-19 09:32:11,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265891279727859273553173523 amount: 80. +2023-09-19 09:32:11,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115931.0, "order_id": "x-XEKWYICXBSIUT605b2eba4c22f0582", "exchange_order_id": "35639776", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:32:11,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2eba4c22f0582. +2023-09-19 09:32:11,193 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115931.0, "order_id": "x-XEKWYICXSSIUT605b2eba4c4e70582", "exchange_order_id": "35639777", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:32:11,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2eba4c4e70582. +2023-09-19 09:32:11,464 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2eeec060f0582 for 80.00000000 SEI-USDT. +2023-09-19 09:32:11,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b2eeec060f0582", "creation_timestamp": 1695115931.0, "exchange_order_id": "35640434", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:32:11,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2eeec03b70582 for 80.00000000 SEI-USDT. +2023-09-19 09:32:11,487 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605b2eeec03b70582", "creation_timestamp": 1695115931.0, "exchange_order_id": "35640435", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:33:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2eeec03b70582. [clock=2023-09-19 09:33:06+00:00] +2023-09-19 09:33:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2eeec060f0582. [clock=2023-09-19 09:33:06+00:00] +2023-09-19 09:33:06,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254236907654821569087333566 amount: 80. +2023-09-19 09:33:06,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263911689440167557976206048 amount: 80. +2023-09-19 09:33:06,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115986.0, "order_id": "x-XEKWYICXBSIUT605b2eeec03b70582", "exchange_order_id": "35640435", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:33:06,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2eeec03b70582. +2023-09-19 09:33:06,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115986.0, "order_id": "x-XEKWYICXSSIUT605b2eeec060f0582", "exchange_order_id": "35640434", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:33:06,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2eeec060f0582. +2023-09-19 09:33:06,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2f2334e0b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:33:06,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b2f2334e0b0582", "creation_timestamp": 1695115986.0, "exchange_order_id": "35640752", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:33:06,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2f2334a930582 for 80.00000000 SEI-USDT. +2023-09-19 09:33:06,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695115986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b2f2334a930582", "creation_timestamp": 1695115986.0, "exchange_order_id": "35640753", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:34:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2f2334a930582. [clock=2023-09-19 09:34:01+00:00] +2023-09-19 09:34:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2f2334e0b0582. [clock=2023-09-19 09:34:01+00:00] +2023-09-19 09:34:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254238346812236240959102129 amount: 80. +2023-09-19 09:34:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263913139698783964079272423 amount: 80. +2023-09-19 09:34:01,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116041.0, "order_id": "x-XEKWYICXBSIUT605b2f2334a930582", "exchange_order_id": "35640753", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:34:01,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2f2334a930582. +2023-09-19 09:34:01,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116041.0, "order_id": "x-XEKWYICXSSIUT605b2f2334e0b0582", "exchange_order_id": "35640752", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:34:01,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2f2334e0b0582. +2023-09-19 09:34:01,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2f57a7abc0582 for 80.00000000 SEI-USDT. +2023-09-19 09:34:01,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b2f57a7abc0582", "creation_timestamp": 1695116041.0, "exchange_order_id": "35640953", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:34:01,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2f57a74be0582 for 80.00000000 SEI-USDT. +2023-09-19 09:34:01,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116041.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b2f57a74be0582", "creation_timestamp": 1695116041.0, "exchange_order_id": "35640954", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:34:44,646 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b2f57a7abc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:34:44,648 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:34:44+00:00] +2023-09-19 09:34:44,671 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116084.0, "order_id": "x-XEKWYICXSSIUT605b2f57a7abc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12630000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01010400"}]}, "exchange_trade_id": "5000964", "exchange_order_id": "35640953", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:34:44,685 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116084.0, "order_id": "x-XEKWYICXSSIUT605b2f57a7abc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.1040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35640953", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:34:44,686 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b2f57a7abc0582 completely filled. +2023-09-19 09:34:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2f57a74be0582. [clock=2023-09-19 09:34:56+00:00] +2023-09-19 09:34:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256745567800453259795099370 amount: 80. +2023-09-19 09:34:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267608156206758315017744857 amount: 80. +2023-09-19 09:34:56,161 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116096.0, "order_id": "x-XEKWYICXBSIUT605b2f57a74be0582", "exchange_order_id": "35640954", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:34:56,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2f57a74be0582. +2023-09-19 09:34:56,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2f8c1b2660582 for 80.00000000 SEI-USDT. +2023-09-19 09:34:56,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b2f8c1b2660582", "creation_timestamp": 1695116096.0, "exchange_order_id": "35641526", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:34:56,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2f8c1ad9d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:34:56,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116096.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605b2f8c1ad9d0582", "creation_timestamp": 1695116096.0, "exchange_order_id": "35641527", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:35:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2f8c1ad9d0582. [clock=2023-09-19 09:35:51+00:00] +2023-09-19 09:35:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2f8c1b2660582. [clock=2023-09-19 09:35:51+00:00] +2023-09-19 09:35:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257374682423506996630932835 amount: 80. +2023-09-19 09:35:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266933577096990446288293607 amount: 80. +2023-09-19 09:35:51,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116151.0, "order_id": "x-XEKWYICXBSIUT605b2f8c1ad9d0582", "exchange_order_id": "35641527", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:35:51,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2f8c1ad9d0582. +2023-09-19 09:35:51,501 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116151.0, "order_id": "x-XEKWYICXSSIUT605b2f8c1b2660582", "exchange_order_id": "35641526", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:35:51,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2f8c1b2660582. +2023-09-19 09:35:51,505 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2fc08eca70582 for 80.00000000 SEI-USDT. +2023-09-19 09:35:51,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b2fc08eca70582", "creation_timestamp": 1695116151.0, "exchange_order_id": "35641910", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:35:51,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2fc08ee790582 for 80.00000000 SEI-USDT. +2023-09-19 09:35:51,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116151.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b2fc08ee790582", "creation_timestamp": 1695116151.0, "exchange_order_id": "35641911", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:36:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2fc08eca70582. [clock=2023-09-19 09:36:46+00:00] +2023-09-19 09:36:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2fc08ee790582. [clock=2023-09-19 09:36:46+00:00] +2023-09-19 09:36:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252484765034389700437089027 amount: 80. +2023-09-19 09:36:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263239498012631919576429895 amount: 80. +2023-09-19 09:36:46,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116206.0, "order_id": "x-XEKWYICXBSIUT605b2fc08eca70582", "exchange_order_id": "35641910", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:36:46,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2fc08eca70582. +2023-09-19 09:36:46,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b2ff5026640582 for 80.00000000 SEI-USDT. +2023-09-19 09:36:46,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b2ff5026640582", "creation_timestamp": 1695116206.0, "exchange_order_id": "35642333", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:36:46,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b2ff502a080582 for 80.00000000 SEI-USDT. +2023-09-19 09:36:46,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116206.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b2ff502a080582", "creation_timestamp": 1695116206.0, "exchange_order_id": "35642334", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:36:46,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116206.0, "order_id": "x-XEKWYICXSSIUT605b2fc08ee790582", "exchange_order_id": "35641911", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:36:46,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2fc08ee790582. +2023-09-19 09:37:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b2ff5026640582. [clock=2023-09-19 09:37:41+00:00] +2023-09-19 09:37:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b2ff502a080582. [clock=2023-09-19 09:37:41+00:00] +2023-09-19 09:37:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251614145417969852309885747 amount: 80. +2023-09-19 09:37:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262189308934748934427730885 amount: 80. +2023-09-19 09:37:41,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116261.0, "order_id": "x-XEKWYICXBSIUT605b2ff5026640582", "exchange_order_id": "35642333", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:37:41,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b2ff5026640582. +2023-09-19 09:37:41,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116261.0, "order_id": "x-XEKWYICXSSIUT605b2ff502a080582", "exchange_order_id": "35642334", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:37:41,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b2ff502a080582. +2023-09-19 09:37:41,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3029763b60582 for 80.00000000 SEI-USDT. +2023-09-19 09:37:41,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b3029763b60582", "creation_timestamp": 1695116261.0, "exchange_order_id": "35642683", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:37:41,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3029766b30582 for 80.00000000 SEI-USDT. +2023-09-19 09:37:41,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b3029766b30582", "creation_timestamp": 1695116261.0, "exchange_order_id": "35642682", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:38:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3029763b60582. [clock=2023-09-19 09:38:36+00:00] +2023-09-19 09:38:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3029766b30582. [clock=2023-09-19 09:38:36+00:00] +2023-09-19 09:38:36,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254755982846195300706850848 amount: 80. +2023-09-19 09:38:36,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262987468712356907283082012 amount: 80. +2023-09-19 09:38:36,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116316.0, "order_id": "x-XEKWYICXBSIUT605b3029763b60582", "exchange_order_id": "35642683", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:38:36,246 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3029763b60582. +2023-09-19 09:38:36,392 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b305deb0bb0582 for 80.00000000 SEI-USDT. +2023-09-19 09:38:36,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b305deb0bb0582", "creation_timestamp": 1695116316.0, "exchange_order_id": "35643016", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:38:36,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b305deb4890582 for 80.00000000 SEI-USDT. +2023-09-19 09:38:36,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b305deb4890582", "creation_timestamp": 1695116316.0, "exchange_order_id": "35643017", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:38:36,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116316.0, "order_id": "x-XEKWYICXSSIUT605b3029766b30582", "exchange_order_id": "35642682", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:38:36,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3029766b30582. +2023-09-19 09:39:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b305deb0bb0582. [clock=2023-09-19 09:39:31+00:00] +2023-09-19 09:39:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b305deb4890582. [clock=2023-09-19 09:39:31+00:00] +2023-09-19 09:39:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254710068647708656009619089 amount: 80. +2023-09-19 09:39:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263084480167765147681312819 amount: 80. +2023-09-19 09:39:31,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116371.0, "order_id": "x-XEKWYICXBSIUT605b305deb0bb0582", "exchange_order_id": "35643016", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:39:31,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b305deb0bb0582. +2023-09-19 09:39:31,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116371.0, "order_id": "x-XEKWYICXSSIUT605b305deb4890582", "exchange_order_id": "35643017", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:39:31,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b305deb4890582. +2023-09-19 09:39:31,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b30925dff10582 for 80.00000000 SEI-USDT. +2023-09-19 09:39:31,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b30925dff10582", "creation_timestamp": 1695116371.0, "exchange_order_id": "35643351", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:39:31,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b30925dca50582 for 80.00000000 SEI-USDT. +2023-09-19 09:39:31,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116371.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b30925dca50582", "creation_timestamp": 1695116371.0, "exchange_order_id": "35643352", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:40:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b30925dca50582. [clock=2023-09-19 09:40:26+00:00] +2023-09-19 09:40:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b30925dff10582. [clock=2023-09-19 09:40:26+00:00] +2023-09-19 09:40:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254707089563277955834995698 amount: 80. +2023-09-19 09:40:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262333544517045722061263702 amount: 80. +2023-09-19 09:40:26,172 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116426.0, "order_id": "x-XEKWYICXBSIUT605b30925dca50582", "exchange_order_id": "35643352", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:40:26,172 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b30925dca50582. +2023-09-19 09:40:26,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b30c6d170c0582 for 80.00000000 SEI-USDT. +2023-09-19 09:40:26,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b30c6d170c0582", "creation_timestamp": 1695116426.0, "exchange_order_id": "35643544", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:40:26,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b30c6d18f30582 for 80.00000000 SEI-USDT. +2023-09-19 09:40:26,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116426.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b30c6d18f30582", "creation_timestamp": 1695116426.0, "exchange_order_id": "35643545", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:40:26,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116426.0, "order_id": "x-XEKWYICXSSIUT605b30925dff10582", "exchange_order_id": "35643351", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:40:26,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b30925dff10582. +2023-09-19 09:40:52,040 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b30c6d18f30582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:40:52,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:40:52+00:00] +2023-09-19 09:40:52,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116452.0, "order_id": "x-XEKWYICXSSIUT605b30c6d18f30582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12620000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01009600"}]}, "exchange_trade_id": "5001169", "exchange_order_id": "35643545", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:40:52,077 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116452.0, "order_id": "x-XEKWYICXSSIUT605b30c6d18f30582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35643545", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:40:52,077 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b30c6d18f30582 completely filled. +2023-09-19 09:41:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b30c6d170c0582. [clock=2023-09-19 09:41:21+00:00] +2023-09-19 09:41:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1257435556310886199059126422 amount: 80. +2023-09-19 09:41:21,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265098158557836570021834326 amount: 80. +2023-09-19 09:41:21,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116481.0, "order_id": "x-XEKWYICXBSIUT605b30c6d170c0582", "exchange_order_id": "35643544", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:41:21,168 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b30c6d170c0582. +2023-09-19 09:41:21,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b30fb4592a0582 for 80.00000000 SEI-USDT. +2023-09-19 09:41:21,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b30fb4592a0582", "creation_timestamp": 1695116481.0, "exchange_order_id": "35643947", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:41:21,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b30fb456b00582 for 80.00000000 SEI-USDT. +2023-09-19 09:41:21,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116481.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXBSIUT605b30fb456b00582", "creation_timestamp": 1695116481.0, "exchange_order_id": "35643948", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:42:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b30fb456b00582. [clock=2023-09-19 09:42:16+00:00] +2023-09-19 09:42:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b30fb4592a0582. [clock=2023-09-19 09:42:16+00:00] +2023-09-19 09:42:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259298375151501422601746465 amount: 80. +2023-09-19 09:42:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1269214268865999362814697907 amount: 80. +2023-09-19 09:42:16,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116536.0, "order_id": "x-XEKWYICXBSIUT605b30fb456b00582", "exchange_order_id": "35643948", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:42:16,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b30fb456b00582. +2023-09-19 09:42:16,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116536.0, "order_id": "x-XEKWYICXSSIUT605b30fb4592a0582", "exchange_order_id": "35643947", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:42:16,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b30fb4592a0582. +2023-09-19 09:42:16,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b312fb92e00582 for 80.00000000 SEI-USDT. +2023-09-19 09:42:16,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605b312fb92e00582", "creation_timestamp": 1695116536.0, "exchange_order_id": "35644776", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:42:16,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b312fb957e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:42:16,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXSSIUT605b312fb957e0582", "creation_timestamp": 1695116536.0, "exchange_order_id": "35644777", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:42:51,337 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 09:43:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b312fb92e00582. [clock=2023-09-19 09:43:11+00:00] +2023-09-19 09:43:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b312fb957e0582. [clock=2023-09-19 09:43:11+00:00] +2023-09-19 09:43:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258784381339715308686739048 amount: 80. +2023-09-19 09:43:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266492973546068877693162996 amount: 80. +2023-09-19 09:43:11,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116591.0, "order_id": "x-XEKWYICXBSIUT605b312fb92e00582", "exchange_order_id": "35644776", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:43:11,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b312fb92e00582. +2023-09-19 09:43:11,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b31642c7270582 for 80.00000000 SEI-USDT. +2023-09-19 09:43:11,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b31642c7270582", "creation_timestamp": 1695116591.0, "exchange_order_id": "35645513", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:43:11,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b31642cad90582 for 80.00000000 SEI-USDT. +2023-09-19 09:43:11,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b31642cad90582", "creation_timestamp": 1695116591.0, "exchange_order_id": "35645514", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:43:11,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116591.0, "order_id": "x-XEKWYICXSSIUT605b312fb957e0582", "exchange_order_id": "35644777", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:43:11,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b312fb957e0582. +2023-09-19 09:44:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b31642c7270582. [clock=2023-09-19 09:44:06+00:00] +2023-09-19 09:44:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b31642cad90582. [clock=2023-09-19 09:44:06+00:00] +2023-09-19 09:44:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255676126749404942131084299 amount: 80. +2023-09-19 09:44:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263392830538367353090929205 amount: 80. +2023-09-19 09:44:06,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116646.0, "order_id": "x-XEKWYICXBSIUT605b31642c7270582", "exchange_order_id": "35645513", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:44:06,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b31642c7270582. +2023-09-19 09:44:06,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116646.0, "order_id": "x-XEKWYICXSSIUT605b31642cad90582", "exchange_order_id": "35645514", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:44:06,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b31642cad90582. +2023-09-19 09:44:06,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3198a09f10582 for 80.00000000 SEI-USDT. +2023-09-19 09:44:06,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605b3198a09f10582", "creation_timestamp": 1695116646.0, "exchange_order_id": "35645857", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:44:06,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3198a0cee0582 for 80.00000000 SEI-USDT. +2023-09-19 09:44:06,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116646.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b3198a0cee0582", "creation_timestamp": 1695116646.0, "exchange_order_id": "35645858", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:45:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3198a09f10582. [clock=2023-09-19 09:45:01+00:00] +2023-09-19 09:45:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3198a0cee0582. [clock=2023-09-19 09:45:01+00:00] +2023-09-19 09:45:01,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252884877556282003582036708 amount: 80. +2023-09-19 09:45:01,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262815810393388779787764945 amount: 80. +2023-09-19 09:45:01,211 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116701.0, "order_id": "x-XEKWYICXBSIUT605b3198a09f10582", "exchange_order_id": "35645857", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:45:01,212 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3198a09f10582. +2023-09-19 09:45:01,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b31cd147950582 for 80.00000000 SEI-USDT. +2023-09-19 09:45:01,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b31cd147950582", "creation_timestamp": 1695116701.0, "exchange_order_id": "35646164", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:45:01,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116701.0, "order_id": "x-XEKWYICXSSIUT605b3198a0cee0582", "exchange_order_id": "35645858", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:45:01,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3198a0cee0582. +2023-09-19 09:45:01,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b31cd14ab70582 for 80.00000000 SEI-USDT. +2023-09-19 09:45:01,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116701.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b31cd14ab70582", "creation_timestamp": 1695116701.0, "exchange_order_id": "35646165", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:45:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b31cd147950582. [clock=2023-09-19 09:45:56+00:00] +2023-09-19 09:45:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b31cd14ab70582. [clock=2023-09-19 09:45:56+00:00] +2023-09-19 09:45:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243369909908223885002845410 amount: 80. +2023-09-19 09:45:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12570000 amount: 80. +2023-09-19 09:45:56,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116756.0, "order_id": "x-XEKWYICXBSIUT605b31cd147950582", "exchange_order_id": "35646164", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:45:56,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b31cd147950582. +2023-09-19 09:45:56,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b320187b6e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:45:56,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b320187b6e0582", "creation_timestamp": 1695116756.0, "exchange_order_id": "35646811", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:45:56,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b320187edf0582 for 80.00000000 SEI-USDT. +2023-09-19 09:45:56,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116756.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b320187edf0582", "creation_timestamp": 1695116756.0, "exchange_order_id": "35646812", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:45:56,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116756.0, "order_id": "x-XEKWYICXSSIUT605b31cd14ab70582", "exchange_order_id": "35646165", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:45:56,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b31cd14ab70582. +2023-09-19 09:46:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b320187b6e0582. [clock=2023-09-19 09:46:51+00:00] +2023-09-19 09:46:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b320187edf0582. [clock=2023-09-19 09:46:51+00:00] +2023-09-19 09:46:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245026916114466487118543853 amount: 80. +2023-09-19 09:46:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12570000 amount: 80. +2023-09-19 09:46:51,176 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116811.0, "order_id": "x-XEKWYICXBSIUT605b320187b6e0582", "exchange_order_id": "35646811", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:46:51,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b320187b6e0582. +2023-09-19 09:46:51,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116811.0, "order_id": "x-XEKWYICXSSIUT605b320187edf0582", "exchange_order_id": "35646812", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:46:51,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b320187edf0582. +2023-09-19 09:46:51,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3235fbcde0582 for 80.00000000 SEI-USDT. +2023-09-19 09:46:51,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b3235fbcde0582", "creation_timestamp": 1695116811.0, "exchange_order_id": "35647355", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:46:51,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3235fb9350582 for 80.00000000 SEI-USDT. +2023-09-19 09:46:51,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b3235fb9350582", "creation_timestamp": 1695116811.0, "exchange_order_id": "35647354", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:47:06,529 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b3235fbcde0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:47:06,531 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:47:06+00:00] +2023-09-19 09:47:06,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116826.0, "order_id": "x-XEKWYICXSSIUT605b3235fbcde0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12570000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01005600"}]}, "exchange_trade_id": "5001519", "exchange_order_id": "35647355", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:47:06,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116826.0, "order_id": "x-XEKWYICXSSIUT605b3235fbcde0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0560000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35647355", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:47:06,566 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b3235fbcde0582 completely filled. +2023-09-19 09:47:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3235fb9350582. [clock=2023-09-19 09:47:46+00:00] +2023-09-19 09:47:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251150192225351599964993862 amount: 80. +2023-09-19 09:47:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262707051041109957485283800 amount: 80. +2023-09-19 09:47:46,155 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116866.0, "order_id": "x-XEKWYICXBSIUT605b3235fb9350582", "exchange_order_id": "35647354", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:47:46,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3235fb9350582. +2023-09-19 09:47:46,223 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b326a6f56e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:47:46,236 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b326a6f56e0582", "creation_timestamp": 1695116866.0, "exchange_order_id": "35647864", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:47:46,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b326a6f1fd0582 for 80.00000000 SEI-USDT. +2023-09-19 09:47:46,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b326a6f1fd0582", "creation_timestamp": 1695116866.0, "exchange_order_id": "35647865", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:48:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b326a6f1fd0582. [clock=2023-09-19 09:48:41+00:00] +2023-09-19 09:48:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b326a6f56e0582. [clock=2023-09-19 09:48:41+00:00] +2023-09-19 09:48:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251886262201929943295779299 amount: 80. +2023-09-19 09:48:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261985151928851501775090291 amount: 80. +2023-09-19 09:48:41,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116921.0, "order_id": "x-XEKWYICXBSIUT605b326a6f1fd0582", "exchange_order_id": "35647865", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:48:41,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b326a6f1fd0582. +2023-09-19 09:48:41,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b329ee33280582 for 80.00000000 SEI-USDT. +2023-09-19 09:48:41,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b329ee33280582", "creation_timestamp": 1695116921.0, "exchange_order_id": "35648241", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:48:41,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b329ee31210582 for 80.00000000 SEI-USDT. +2023-09-19 09:48:41,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b329ee31210582", "creation_timestamp": 1695116921.0, "exchange_order_id": "35648242", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:48:41,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116921.0, "order_id": "x-XEKWYICXSSIUT605b326a6f56e0582", "exchange_order_id": "35647864", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:48:41,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b326a6f56e0582. +2023-09-19 09:49:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b329ee31210582. [clock=2023-09-19 09:49:36+00:00] +2023-09-19 09:49:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b329ee33280582. [clock=2023-09-19 09:49:36+00:00] +2023-09-19 09:49:36,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250159566281976281726499997 amount: 80. +2023-09-19 09:49:36,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260236693699962526604415971 amount: 80. +2023-09-19 09:49:36,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116976.0, "order_id": "x-XEKWYICXBSIUT605b329ee31210582", "exchange_order_id": "35648242", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:49:36,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b329ee31210582. +2023-09-19 09:49:36,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116976.0, "order_id": "x-XEKWYICXSSIUT605b329ee33280582", "exchange_order_id": "35648241", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:49:36,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b329ee33280582. +2023-09-19 09:49:36,398 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b32d357fba0582 for 80.00000000 SEI-USDT. +2023-09-19 09:49:36,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b32d357fba0582", "creation_timestamp": 1695116976.0, "exchange_order_id": "35648469", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:49:36,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b32d357b260582 for 80.00000000 SEI-USDT. +2023-09-19 09:49:36,449 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695116976.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b32d357b260582", "creation_timestamp": 1695116976.0, "exchange_order_id": "35648468", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:50:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b32d357b260582. [clock=2023-09-19 09:50:31+00:00] +2023-09-19 09:50:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b32d357fba0582. [clock=2023-09-19 09:50:31+00:00] +2023-09-19 09:50:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250583979736855696701333990 amount: 80. +2023-09-19 09:50:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260398082641269154642450958 amount: 80. +2023-09-19 09:50:31,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117031.0, "order_id": "x-XEKWYICXBSIUT605b32d357b260582", "exchange_order_id": "35648468", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:50:31,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b32d357b260582. +2023-09-19 09:50:31,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117031.0, "order_id": "x-XEKWYICXSSIUT605b32d357fba0582", "exchange_order_id": "35648469", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:50:31,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b32d357fba0582. +2023-09-19 09:50:31,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3307ca9c40582 for 80.00000000 SEI-USDT. +2023-09-19 09:50:31,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b3307ca9c40582", "creation_timestamp": 1695117031.0, "exchange_order_id": "35648853", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:50:31,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3307cad300582 for 80.00000000 SEI-USDT. +2023-09-19 09:50:31,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117031.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b3307cad300582", "creation_timestamp": 1695117031.0, "exchange_order_id": "35648854", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:51:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3307ca9c40582. [clock=2023-09-19 09:51:26+00:00] +2023-09-19 09:51:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3307cad300582. [clock=2023-09-19 09:51:26+00:00] +2023-09-19 09:51:26,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253705364618754456048851791 amount: 80. +2023-09-19 09:51:26,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262212331976441602239311604 amount: 80. +2023-09-19 09:51:26,294 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117086.0, "order_id": "x-XEKWYICXBSIUT605b3307ca9c40582", "exchange_order_id": "35648853", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:51:26,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3307ca9c40582. +2023-09-19 09:51:26,447 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b333c4205b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:51:26,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b333c4205b0582", "creation_timestamp": 1695117086.0, "exchange_order_id": "35649127", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:51:26,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117086.0, "order_id": "x-XEKWYICXSSIUT605b3307cad300582", "exchange_order_id": "35648854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:51:26,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3307cad300582. +2023-09-19 09:51:26,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b333c424130582 for 80.00000000 SEI-USDT. +2023-09-19 09:51:26,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117086.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b333c424130582", "creation_timestamp": 1695117086.0, "exchange_order_id": "35649128", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:52:16,245 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b333c424130582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 09:52:16,246 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 09:52:16+00:00] +2023-09-19 09:52:16,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117136.0, "order_id": "x-XEKWYICXSSIUT605b333c424130582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12620000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01009600"}]}, "exchange_trade_id": "5001767", "exchange_order_id": "35649128", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 09:52:16,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117136.0, "order_id": "x-XEKWYICXSSIUT605b333c424130582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35649128", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 09:52:16,283 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b333c424130582 completely filled. +2023-09-19 09:52:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b333c4205b0582. [clock=2023-09-19 09:52:21+00:00] +2023-09-19 09:52:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258134624260172119386295589 amount: 80. +2023-09-19 09:52:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1269586847947630108957031199 amount: 80. +2023-09-19 09:52:21,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117141.0, "order_id": "x-XEKWYICXBSIUT605b333c4205b0582", "exchange_order_id": "35649127", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:52:21,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b333c4205b0582. +2023-09-19 09:52:21,222 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3370b190b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:52:21,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b3370b190b0582", "creation_timestamp": 1695117141.0, "exchange_order_id": "35649846", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:52:21,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3370b1b380582 for 80.00000000 SEI-USDT. +2023-09-19 09:52:21,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXSSIUT605b3370b1b380582", "creation_timestamp": 1695117141.0, "exchange_order_id": "35649847", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:53:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3370b190b0582. [clock=2023-09-19 09:53:16+00:00] +2023-09-19 09:53:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3370b1b380582. [clock=2023-09-19 09:53:16+00:00] +2023-09-19 09:53:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259023301196798994865712239 amount: 80. +2023-09-19 09:53:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267920791024702727276198879 amount: 80. +2023-09-19 09:53:16,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117196.0, "order_id": "x-XEKWYICXBSIUT605b3370b190b0582", "exchange_order_id": "35649846", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:53:16,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3370b190b0582. +2023-09-19 09:53:16,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117196.0, "order_id": "x-XEKWYICXSSIUT605b3370b1b380582", "exchange_order_id": "35649847", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:53:16,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3370b1b380582. +2023-09-19 09:53:16,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b33a52626f0582 for 80.00000000 SEI-USDT. +2023-09-19 09:53:16,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b33a52626f0582", "creation_timestamp": 1695117196.0, "exchange_order_id": "35650186", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:53:16,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b33a525f4b0582 for 80.00000000 SEI-USDT. +2023-09-19 09:53:16,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605b33a525f4b0582", "creation_timestamp": 1695117196.0, "exchange_order_id": "35650187", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:54:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b33a525f4b0582. [clock=2023-09-19 09:54:11+00:00] +2023-09-19 09:54:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b33a52626f0582. [clock=2023-09-19 09:54:11+00:00] +2023-09-19 09:54:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260462654631805016283380655 amount: 80. +2023-09-19 09:54:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267385400082760957969839234 amount: 80. +2023-09-19 09:54:11,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117251.0, "order_id": "x-XEKWYICXBSIUT605b33a525f4b0582", "exchange_order_id": "35650187", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:54:11,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b33a525f4b0582. +2023-09-19 09:54:11,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117251.0, "order_id": "x-XEKWYICXSSIUT605b33a52626f0582", "exchange_order_id": "35650186", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:54:11,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b33a52626f0582. +2023-09-19 09:54:11,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b33d99970e0582 for 80.00000000 SEI-USDT. +2023-09-19 09:54:11,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b33d99970e0582", "creation_timestamp": 1695117251.0, "exchange_order_id": "35650428", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:54:11,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b33d999a180582 for 80.00000000 SEI-USDT. +2023-09-19 09:54:11,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117251.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b33d999a180582", "creation_timestamp": 1695117251.0, "exchange_order_id": "35650429", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:55:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b33d99970e0582. [clock=2023-09-19 09:55:06+00:00] +2023-09-19 09:55:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b33d999a180582. [clock=2023-09-19 09:55:06+00:00] +2023-09-19 09:55:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260804134256710128008592353 amount: 80. +2023-09-19 09:55:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266186665863414736755853251 amount: 80. +2023-09-19 09:55:06,196 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117306.0, "order_id": "x-XEKWYICXBSIUT605b33d99970e0582", "exchange_order_id": "35650428", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:55:06,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b33d99970e0582. +2023-09-19 09:55:06,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b340e0d6490582 for 80.00000000 SEI-USDT. +2023-09-19 09:55:06,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b340e0d6490582", "creation_timestamp": 1695117306.0, "exchange_order_id": "35650630", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:55:06,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b340e0d1d80582 for 80.00000000 SEI-USDT. +2023-09-19 09:55:06,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117306.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b340e0d1d80582", "creation_timestamp": 1695117306.0, "exchange_order_id": "35650631", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:55:06,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117306.0, "order_id": "x-XEKWYICXSSIUT605b33d999a180582", "exchange_order_id": "35650429", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:55:06,365 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b33d999a180582. +2023-09-19 09:56:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b340e0d1d80582. [clock=2023-09-19 09:56:01+00:00] +2023-09-19 09:56:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b340e0d6490582. [clock=2023-09-19 09:56:01+00:00] +2023-09-19 09:56:01,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260556291084333108518878133 amount: 80. +2023-09-19 09:56:01,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267052839662040445778145125 amount: 80. +2023-09-19 09:56:01,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117361.0, "order_id": "x-XEKWYICXBSIUT605b340e0d1d80582", "exchange_order_id": "35650631", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:56:01,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b340e0d1d80582. +2023-09-19 09:56:01,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117361.0, "order_id": "x-XEKWYICXSSIUT605b340e0d6490582", "exchange_order_id": "35650630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:56:01,233 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b340e0d6490582. +2023-09-19 09:56:01,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3442822470582 for 80.00000000 SEI-USDT. +2023-09-19 09:56:01,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b3442822470582", "creation_timestamp": 1695117361.0, "exchange_order_id": "35651160", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:56:01,331 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3442826d60582 for 80.00000000 SEI-USDT. +2023-09-19 09:56:01,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117361.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b3442826d60582", "creation_timestamp": 1695117361.0, "exchange_order_id": "35651161", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:56:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3442822470582. [clock=2023-09-19 09:56:56+00:00] +2023-09-19 09:56:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3442826d60582. [clock=2023-09-19 09:56:56+00:00] +2023-09-19 09:56:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260630037816030031599444325 amount: 80. +2023-09-19 09:56:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266795160183450731277021639 amount: 80. +2023-09-19 09:56:56,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117416.0, "order_id": "x-XEKWYICXBSIUT605b3442822470582", "exchange_order_id": "35651160", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:56:56,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3442822470582. +2023-09-19 09:56:56,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117416.0, "order_id": "x-XEKWYICXSSIUT605b3442826d60582", "exchange_order_id": "35651161", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:56:56,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3442826d60582. +2023-09-19 09:56:56,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3476f496d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:56:56,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b3476f496d0582", "creation_timestamp": 1695117416.0, "exchange_order_id": "35651471", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:56:56,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3476f4b720582 for 80.00000000 SEI-USDT. +2023-09-19 09:56:56,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b3476f4b720582", "creation_timestamp": 1695117416.0, "exchange_order_id": "35651472", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:57:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3476f496d0582. [clock=2023-09-19 09:57:51+00:00] +2023-09-19 09:57:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3476f4b720582. [clock=2023-09-19 09:57:51+00:00] +2023-09-19 09:57:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1260687381938208583416561577 amount: 80. +2023-09-19 09:57:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266594761677419443152649608 amount: 80. +2023-09-19 09:57:51,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117471.0, "order_id": "x-XEKWYICXBSIUT605b3476f496d0582", "exchange_order_id": "35651471", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:57:51,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3476f496d0582. +2023-09-19 09:57:51,373 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117471.0, "order_id": "x-XEKWYICXSSIUT605b3476f4b720582", "exchange_order_id": "35651472", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:57:51,374 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3476f4b720582. +2023-09-19 09:57:51,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b34ab68a6d0582 for 80.00000000 SEI-USDT. +2023-09-19 09:57:51,398 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b34ab68a6d0582", "creation_timestamp": 1695117471.0, "exchange_order_id": "35651639", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:57:51,399 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b34ab687030582 for 80.00000000 SEI-USDT. +2023-09-19 09:57:51,416 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXBSIUT605b34ab687030582", "creation_timestamp": 1695117471.0, "exchange_order_id": "35651640", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:58:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b34ab687030582. [clock=2023-09-19 09:58:46+00:00] +2023-09-19 09:58:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b34ab68a6d0582. [clock=2023-09-19 09:58:46+00:00] +2023-09-19 09:58:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258853685125038485247120753 amount: 80. +2023-09-19 09:58:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265671388241418712751373494 amount: 80. +2023-09-19 09:58:46,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117526.0, "order_id": "x-XEKWYICXBSIUT605b34ab687030582", "exchange_order_id": "35651640", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:58:46,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b34ab687030582. +2023-09-19 09:58:46,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b34dfdbfbc0582 for 80.00000000 SEI-USDT. +2023-09-19 09:58:46,263 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b34dfdbfbc0582", "creation_timestamp": 1695117526.0, "exchange_order_id": "35651946", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:58:46,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117526.0, "order_id": "x-XEKWYICXSSIUT605b34ab68a6d0582", "exchange_order_id": "35651639", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:58:46,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b34ab68a6d0582. +2023-09-19 09:58:46,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b34dfdbdb60582 for 80.00000000 SEI-USDT. +2023-09-19 09:58:46,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b34dfdbdb60582", "creation_timestamp": 1695117526.0, "exchange_order_id": "35651947", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:59:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b34dfdbdb60582. [clock=2023-09-19 09:59:41+00:00] +2023-09-19 09:59:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b34dfdbfbc0582. [clock=2023-09-19 09:59:41+00:00] +2023-09-19 09:59:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256307104016146009047387072 amount: 80. +2023-09-19 09:59:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263579439394740619151601663 amount: 80. +2023-09-19 09:59:41,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117581.0, "order_id": "x-XEKWYICXBSIUT605b34dfdbdb60582", "exchange_order_id": "35651947", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:59:41,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b34dfdbdb60582. +2023-09-19 09:59:41,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b35144fed30582 for 80.00000000 SEI-USDT. +2023-09-19 09:59:41,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605b35144fed30582", "creation_timestamp": 1695117581.0, "exchange_order_id": "35652390", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:59:41,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3514502470582 for 80.00000000 SEI-USDT. +2023-09-19 09:59:41,276 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117581.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b3514502470582", "creation_timestamp": 1695117581.0, "exchange_order_id": "35652391", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 09:59:41,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117581.0, "order_id": "x-XEKWYICXSSIUT605b34dfdbfbc0582", "exchange_order_id": "35651946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 09:59:41,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b34dfdbfbc0582. +2023-09-19 10:00:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b35144fed30582. [clock=2023-09-19 10:00:36+00:00] +2023-09-19 10:00:36,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3514502470582. [clock=2023-09-19 10:00:36+00:00] +2023-09-19 10:00:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258627800788972117516162220 amount: 80. +2023-09-19 10:00:36,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267625675956070378374113506 amount: 80. +2023-09-19 10:00:36,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117636.0, "order_id": "x-XEKWYICXBSIUT605b35144fed30582", "exchange_order_id": "35652390", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:00:36,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b35144fed30582. +2023-09-19 10:00:36,270 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117636.0, "order_id": "x-XEKWYICXSSIUT605b3514502470582", "exchange_order_id": "35652391", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:00:36,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3514502470582. +2023-09-19 10:00:36,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3548c3e2e0582 for 80.00000000 SEI-USDT. +2023-09-19 10:00:36,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b3548c3e2e0582", "creation_timestamp": 1695117636.0, "exchange_order_id": "35652922", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:00:36,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3548c416e0582 for 80.00000000 SEI-USDT. +2023-09-19 10:00:36,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117636.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b3548c416e0582", "creation_timestamp": 1695117636.0, "exchange_order_id": "35652921", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:01:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3548c3e2e0582. [clock=2023-09-19 10:01:31+00:00] +2023-09-19 10:01:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3548c416e0582. [clock=2023-09-19 10:01:31+00:00] +2023-09-19 10:01:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254789329605594710330259267 amount: 80. +2023-09-19 10:01:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265122607789885238767862183 amount: 80. +2023-09-19 10:01:31,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117691.0, "order_id": "x-XEKWYICXBSIUT605b3548c3e2e0582", "exchange_order_id": "35652922", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:01:31,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3548c3e2e0582. +2023-09-19 10:01:31,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b357d376e40582 for 80.00000000 SEI-USDT. +2023-09-19 10:01:31,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b357d376e40582", "creation_timestamp": 1695117691.0, "exchange_order_id": "35653264", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:01:31,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117691.0, "order_id": "x-XEKWYICXSSIUT605b3548c416e0582", "exchange_order_id": "35652921", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:01:31,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3548c416e0582. +2023-09-19 10:01:31,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b357d37c7b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:01:31,307 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117691.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b357d37c7b0582", "creation_timestamp": 1695117691.0, "exchange_order_id": "35653265", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:02:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b357d376e40582. [clock=2023-09-19 10:02:26+00:00] +2023-09-19 10:02:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b357d37c7b0582. [clock=2023-09-19 10:02:26+00:00] +2023-09-19 10:02:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254731310049357073456447273 amount: 80. +2023-09-19 10:02:26,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262760585724423532645879596 amount: 80. +2023-09-19 10:02:26,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117746.0, "order_id": "x-XEKWYICXBSIUT605b357d376e40582", "exchange_order_id": "35653264", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:02:26,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b357d376e40582. +2023-09-19 10:02:26,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117746.0, "order_id": "x-XEKWYICXSSIUT605b357d37c7b0582", "exchange_order_id": "35653265", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:02:26,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b357d37c7b0582. +2023-09-19 10:02:26,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b35b1aba1c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:02:26,332 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b35b1aba1c0582", "creation_timestamp": 1695117746.0, "exchange_order_id": "35653518", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:02:26,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b35b1abdcc0582 for 80.00000000 SEI-USDT. +2023-09-19 10:02:26,356 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b35b1abdcc0582", "creation_timestamp": 1695117746.0, "exchange_order_id": "35653519", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:03:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b35b1aba1c0582. [clock=2023-09-19 10:03:21+00:00] +2023-09-19 10:03:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b35b1abdcc0582. [clock=2023-09-19 10:03:21+00:00] +2023-09-19 10:03:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251092430364306654321817564 amount: 80. +2023-09-19 10:03:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260671114584950506227235899 amount: 80. +2023-09-19 10:03:21,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117801.0, "order_id": "x-XEKWYICXBSIUT605b35b1aba1c0582", "exchange_order_id": "35653518", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:03:21,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b35b1aba1c0582. +2023-09-19 10:03:21,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117801.0, "order_id": "x-XEKWYICXSSIUT605b35b1abdcc0582", "exchange_order_id": "35653519", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:03:21,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b35b1abdcc0582. +2023-09-19 10:03:21,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b35e61e7670582 for 80.00000000 SEI-USDT. +2023-09-19 10:03:21,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b35e61e7670582", "creation_timestamp": 1695117801.0, "exchange_order_id": "35653922", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:03:21,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b35e61e99b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:03:21,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b35e61e99b0582", "creation_timestamp": 1695117801.0, "exchange_order_id": "35653923", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:04:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b35e61e7670582. [clock=2023-09-19 10:04:16+00:00] +2023-09-19 10:04:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b35e61e99b0582. [clock=2023-09-19 10:04:16+00:00] +2023-09-19 10:04:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253097199625999613049926935 amount: 80. +2023-09-19 10:04:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262769699198964547509761125 amount: 80. +2023-09-19 10:04:16,185 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117856.0, "order_id": "x-XEKWYICXBSIUT605b35e61e7670582", "exchange_order_id": "35653922", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:04:16,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b35e61e7670582. +2023-09-19 10:04:16,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117856.0, "order_id": "x-XEKWYICXSSIUT605b35e61e99b0582", "exchange_order_id": "35653923", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:04:16,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b35e61e99b0582. +2023-09-19 10:04:16,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b361a927bd0582 for 80.00000000 SEI-USDT. +2023-09-19 10:04:16,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b361a927bd0582", "creation_timestamp": 1695117856.0, "exchange_order_id": "35654264", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:04:16,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b361a92ad40582 for 80.00000000 SEI-USDT. +2023-09-19 10:04:16,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117856.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b361a92ad40582", "creation_timestamp": 1695117856.0, "exchange_order_id": "35654263", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:05:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b361a927bd0582. [clock=2023-09-19 10:05:11+00:00] +2023-09-19 10:05:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b361a92ad40582. [clock=2023-09-19 10:05:11+00:00] +2023-09-19 10:05:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254217477518539898707659931 amount: 80. +2023-09-19 10:05:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1263723567498743547450043994 amount: 80. +2023-09-19 10:05:11,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117911.0, "order_id": "x-XEKWYICXBSIUT605b361a927bd0582", "exchange_order_id": "35654264", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:05:11,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b361a927bd0582. +2023-09-19 10:05:11,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b364f068830582 for 80.00000000 SEI-USDT. +2023-09-19 10:05:11,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b364f068830582", "creation_timestamp": 1695117911.0, "exchange_order_id": "35654589", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:05:11,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b364f06bd50582 for 80.00000000 SEI-USDT. +2023-09-19 10:05:11,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117911.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12630000", "order_id": "x-XEKWYICXSSIUT605b364f06bd50582", "creation_timestamp": 1695117911.0, "exchange_order_id": "35654588", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:05:11,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117911.0, "order_id": "x-XEKWYICXSSIUT605b361a92ad40582", "exchange_order_id": "35654263", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:05:11,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b361a92ad40582. +2023-09-19 10:06:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b364f068830582. [clock=2023-09-19 10:06:06+00:00] +2023-09-19 10:06:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b364f06bd50582. [clock=2023-09-19 10:06:06+00:00] +2023-09-19 10:06:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256013212211898825735755844 amount: 80. +2023-09-19 10:06:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264272827573010836531316842 amount: 80. +2023-09-19 10:06:06,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117966.0, "order_id": "x-XEKWYICXBSIUT605b364f068830582", "exchange_order_id": "35654589", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:06:06,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b364f068830582. +2023-09-19 10:06:06,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117966.0, "order_id": "x-XEKWYICXSSIUT605b364f06bd50582", "exchange_order_id": "35654588", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:06:06,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b364f06bd50582. +2023-09-19 10:06:06,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b36837a71a0582 for 80.00000000 SEI-USDT. +2023-09-19 10:06:06,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b36837a71a0582", "creation_timestamp": 1695117966.0, "exchange_order_id": "35654918", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:06:06,444 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b36837a3bf0582 for 80.00000000 SEI-USDT. +2023-09-19 10:06:06,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695117966.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605b36837a3bf0582", "creation_timestamp": 1695117966.0, "exchange_order_id": "35654919", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:07:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b36837a3bf0582. [clock=2023-09-19 10:07:01+00:00] +2023-09-19 10:07:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b36837a71a0582. [clock=2023-09-19 10:07:01+00:00] +2023-09-19 10:07:01,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258100650068387187164648526 amount: 80. +2023-09-19 10:07:01,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1268594495553358070185327724 amount: 80. +2023-09-19 10:07:01,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118021.0, "order_id": "x-XEKWYICXBSIUT605b36837a3bf0582", "exchange_order_id": "35654919", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:07:01,216 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b36837a3bf0582. +2023-09-19 10:07:01,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b36b7eea1e0582 for 80.00000000 SEI-USDT. +2023-09-19 10:07:01,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12680000", "order_id": "x-XEKWYICXSSIUT605b36b7eea1e0582", "creation_timestamp": 1695118021.0, "exchange_order_id": "35655388", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:07:01,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b36b7ee6d80582 for 80.00000000 SEI-USDT. +2023-09-19 10:07:01,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b36b7ee6d80582", "creation_timestamp": 1695118021.0, "exchange_order_id": "35655389", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:07:01,352 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118021.0, "order_id": "x-XEKWYICXSSIUT605b36837a71a0582", "exchange_order_id": "35654918", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:07:01,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b36837a71a0582. +2023-09-19 10:07:56,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b36b7ee6d80582. [clock=2023-09-19 10:07:56+00:00] +2023-09-19 10:07:56,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b36b7eea1e0582. [clock=2023-09-19 10:07:56+00:00] +2023-09-19 10:07:56,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1258435063138251005085731810 amount: 80. +2023-09-19 10:07:56,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267708096354043376393943399 amount: 80. +2023-09-19 10:07:56,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118076.0, "order_id": "x-XEKWYICXBSIUT605b36b7ee6d80582", "exchange_order_id": "35655389", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:07:56,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b36b7ee6d80582. +2023-09-19 10:07:56,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118076.0, "order_id": "x-XEKWYICXSSIUT605b36b7eea1e0582", "exchange_order_id": "35655388", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:07:56,354 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b36b7eea1e0582. +2023-09-19 10:07:56,357 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b36ec78b8b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:07:56,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b36ec78b8b0582", "creation_timestamp": 1695118076.0, "exchange_order_id": "35655480", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:07:56,425 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b36ec788080582 for 80.00000000 SEI-USDT. +2023-09-19 10:07:56,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXBSIUT605b36ec788080582", "creation_timestamp": 1695118076.0, "exchange_order_id": "35655481", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:08:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b36ec788080582. [clock=2023-09-19 10:08:51+00:00] +2023-09-19 10:08:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b36ec78b8b0582. [clock=2023-09-19 10:08:51+00:00] +2023-09-19 10:08:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255065088738830648931444242 amount: 80. +2023-09-19 10:08:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1265610143791969354152973014 amount: 80. +2023-09-19 10:08:51,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118131.0, "order_id": "x-XEKWYICXBSIUT605b36ec788080582", "exchange_order_id": "35655481", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:08:51,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b36ec788080582. +2023-09-19 10:08:51,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118131.0, "order_id": "x-XEKWYICXSSIUT605b36ec78b8b0582", "exchange_order_id": "35655480", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:08:51,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b36ec78b8b0582. +2023-09-19 10:08:51,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3720d58fd0582 for 80.00000000 SEI-USDT. +2023-09-19 10:08:51,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605b3720d58fd0582", "creation_timestamp": 1695118131.0, "exchange_order_id": "35655922", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:08:51,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3720d5c2f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:08:51,342 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118131.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12650000", "order_id": "x-XEKWYICXSSIUT605b3720d5c2f0582", "creation_timestamp": 1695118131.0, "exchange_order_id": "35655923", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:09:04,387 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b3720d58fd0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:09:04,388 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:09:04+00:00] +2023-09-19 10:09:04,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118144.0, "order_id": "x-XEKWYICXBSIUT605b3720d58fd0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12550000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5002492", "exchange_order_id": "35655922", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:09:04,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118144.0, "order_id": "x-XEKWYICXBSIUT605b3720d58fd0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0400000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35655922", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:09:04,427 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b3720d58fd0582 completely filled. +2023-09-19 10:09:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3720d5c2f0582. [clock=2023-09-19 10:09:46+00:00] +2023-09-19 10:09:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248647179522549769857177537 amount: 80. +2023-09-19 10:09:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261274171515474530652883759 amount: 80. +2023-09-19 10:09:46,164 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118186.0, "order_id": "x-XEKWYICXSSIUT605b3720d5c2f0582", "exchange_order_id": "35655923", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:09:46,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3720d5c2f0582. +2023-09-19 10:09:46,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b375548d350582 for 80.00000000 SEI-USDT. +2023-09-19 10:09:46,245 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b375548d350582", "creation_timestamp": 1695118186.0, "exchange_order_id": "35656839", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:09:46,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3755489080582 for 80.00000000 SEI-USDT. +2023-09-19 10:09:46,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118186.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b3755489080582", "creation_timestamp": 1695118186.0, "exchange_order_id": "35656840", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:10:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3755489080582. [clock=2023-09-19 10:10:41+00:00] +2023-09-19 10:10:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b375548d350582. [clock=2023-09-19 10:10:41+00:00] +2023-09-19 10:10:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252422296920689417747649567 amount: 80. +2023-09-19 10:10:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264468336881154232750541149 amount: 80. +2023-09-19 10:10:41,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118241.0, "order_id": "x-XEKWYICXBSIUT605b3755489080582", "exchange_order_id": "35656840", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:10:41,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3755489080582. +2023-09-19 10:10:41,442 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118241.0, "order_id": "x-XEKWYICXSSIUT605b375548d350582", "exchange_order_id": "35656839", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:10:41,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b375548d350582. +2023-09-19 10:10:41,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3789bc74f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:10:41,456 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3789bc74f0582", "creation_timestamp": 1695118241.0, "exchange_order_id": "35657365", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:10:41,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3789bca790582 for 80.00000000 SEI-USDT. +2023-09-19 10:10:41,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118241.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b3789bca790582", "creation_timestamp": 1695118241.0, "exchange_order_id": "35657366", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:11:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3789bc74f0582. [clock=2023-09-19 10:11:36+00:00] +2023-09-19 10:11:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3789bca790582. [clock=2023-09-19 10:11:36+00:00] +2023-09-19 10:11:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250302614174665877879907730 amount: 80. +2023-09-19 10:11:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260527173835094248197538690 amount: 80. +2023-09-19 10:11:36,184 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118296.0, "order_id": "x-XEKWYICXBSIUT605b3789bc74f0582", "exchange_order_id": "35657365", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:11:36,185 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3789bc74f0582. +2023-09-19 10:11:36,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118296.0, "order_id": "x-XEKWYICXSSIUT605b3789bca790582", "exchange_order_id": "35657366", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:11:36,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3789bca790582. +2023-09-19 10:11:36,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b37be307e40582 for 80.00000000 SEI-USDT. +2023-09-19 10:11:36,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b37be307e40582", "creation_timestamp": 1695118296.0, "exchange_order_id": "35657743", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:11:36,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b37be30a430582 for 80.00000000 SEI-USDT. +2023-09-19 10:11:36,308 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118296.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b37be30a430582", "creation_timestamp": 1695118296.0, "exchange_order_id": "35657742", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:12:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b37be307e40582. [clock=2023-09-19 10:12:31+00:00] +2023-09-19 10:12:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b37be30a430582. [clock=2023-09-19 10:12:31+00:00] +2023-09-19 10:12:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250566646352141531840060431 amount: 80. +2023-09-19 10:12:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262475199387423946000460305 amount: 80. +2023-09-19 10:12:31,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118351.0, "order_id": "x-XEKWYICXBSIUT605b37be307e40582", "exchange_order_id": "35657743", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:12:31,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b37be307e40582. +2023-09-19 10:12:31,436 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b37f2a466a0582 for 80.00000000 SEI-USDT. +2023-09-19 10:12:31,450 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b37f2a466a0582", "creation_timestamp": 1695118351.0, "exchange_order_id": "35658141", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:12:31,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b37f2a44410582 for 80.00000000 SEI-USDT. +2023-09-19 10:12:31,470 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b37f2a44410582", "creation_timestamp": 1695118351.0, "exchange_order_id": "35658142", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:12:31,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118351.0, "order_id": "x-XEKWYICXSSIUT605b37be30a430582", "exchange_order_id": "35657742", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:12:31,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b37be30a430582. +2023-09-19 10:12:51,355 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 10:13:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b37f2a44410582. [clock=2023-09-19 10:13:26+00:00] +2023-09-19 10:13:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b37f2a466a0582. [clock=2023-09-19 10:13:26+00:00] +2023-09-19 10:13:26,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253397048513649301529566009 amount: 80. +2023-09-19 10:13:26,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262663009032546432835296394 amount: 80. +2023-09-19 10:13:26,213 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118406.0, "order_id": "x-XEKWYICXBSIUT605b37f2a44410582", "exchange_order_id": "35658142", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:13:26,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b37f2a44410582. +2023-09-19 10:13:26,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118406.0, "order_id": "x-XEKWYICXSSIUT605b37f2a466a0582", "exchange_order_id": "35658141", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:13:26,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b37f2a466a0582. +2023-09-19 10:13:26,323 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3827189680582 for 80.00000000 SEI-USDT. +2023-09-19 10:13:26,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b3827189680582", "creation_timestamp": 1695118406.0, "exchange_order_id": "35658444", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:13:26,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b382718ed30582 for 80.00000000 SEI-USDT. +2023-09-19 10:13:26,375 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b382718ed30582", "creation_timestamp": 1695118406.0, "exchange_order_id": "35658445", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:14:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3827189680582. [clock=2023-09-19 10:14:21+00:00] +2023-09-19 10:14:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b382718ed30582. [clock=2023-09-19 10:14:21+00:00] +2023-09-19 10:14:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255810380472981954459379508 amount: 80. +2023-09-19 10:14:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266731776652231868819555460 amount: 80. +2023-09-19 10:14:21,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118461.0, "order_id": "x-XEKWYICXBSIUT605b3827189680582", "exchange_order_id": "35658444", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:14:21,190 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3827189680582. +2023-09-19 10:14:21,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118461.0, "order_id": "x-XEKWYICXSSIUT605b382718ed30582", "exchange_order_id": "35658445", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:14:21,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b382718ed30582. +2023-09-19 10:14:21,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b385b8b6b80582 for 80.00000000 SEI-USDT. +2023-09-19 10:14:21,336 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605b385b8b6b80582", "creation_timestamp": 1695118461.0, "exchange_order_id": "35659067", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:14:21,336 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b385b8b89c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:14:21,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118461.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b385b8b89c0582", "creation_timestamp": 1695118461.0, "exchange_order_id": "35659068", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:15:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b385b8b6b80582. [clock=2023-09-19 10:15:16+00:00] +2023-09-19 10:15:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b385b8b89c0582. [clock=2023-09-19 10:15:16+00:00] +2023-09-19 10:15:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1256757728981727187108580522 amount: 80. +2023-09-19 10:15:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266358948165596109900566366 amount: 80. +2023-09-19 10:15:16,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118516.0, "order_id": "x-XEKWYICXBSIUT605b385b8b6b80582", "exchange_order_id": "35659067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:15:16,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b385b8b6b80582. +2023-09-19 10:15:16,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118516.0, "order_id": "x-XEKWYICXSSIUT605b385b8b89c0582", "exchange_order_id": "35659068", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:15:16,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b385b8b89c0582. +2023-09-19 10:15:16,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b388fff30d0582 for 80.00000000 SEI-USDT. +2023-09-19 10:15:16,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b388fff30d0582", "creation_timestamp": 1695118516.0, "exchange_order_id": "35659284", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:15:16,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b388ffefce0582 for 80.00000000 SEI-USDT. +2023-09-19 10:15:16,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118516.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXBSIUT605b388ffefce0582", "creation_timestamp": 1695118516.0, "exchange_order_id": "35659285", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:16:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b388ffefce0582. [clock=2023-09-19 10:16:11+00:00] +2023-09-19 10:16:11,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b388fff30d0582. [clock=2023-09-19 10:16:11+00:00] +2023-09-19 10:16:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259537622137046903146987460 amount: 80. +2023-09-19 10:16:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267877398213146260183950612 amount: 80. +2023-09-19 10:16:11,178 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118571.0, "order_id": "x-XEKWYICXBSIUT605b388ffefce0582", "exchange_order_id": "35659285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:16:11,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b388ffefce0582. +2023-09-19 10:16:11,190 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118571.0, "order_id": "x-XEKWYICXSSIUT605b388fff30d0582", "exchange_order_id": "35659284", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:16:11,191 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b388fff30d0582. +2023-09-19 10:16:11,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b38c472f720582 for 80.00000000 SEI-USDT. +2023-09-19 10:16:11,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605b38c472f720582", "creation_timestamp": 1695118571.0, "exchange_order_id": "35659668", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:16:11,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b38c4733430582 for 80.00000000 SEI-USDT. +2023-09-19 10:16:11,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118571.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b38c4733430582", "creation_timestamp": 1695118571.0, "exchange_order_id": "35659669", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:17:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b38c472f720582. [clock=2023-09-19 10:17:06+00:00] +2023-09-19 10:17:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b38c4733430582. [clock=2023-09-19 10:17:06+00:00] +2023-09-19 10:17:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259570688069930680087795927 amount: 80. +2023-09-19 10:17:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1267787730159994931078622371 amount: 80. +2023-09-19 10:17:06,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118626.0, "order_id": "x-XEKWYICXBSIUT605b38c472f720582", "exchange_order_id": "35659668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:17:06,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b38c472f720582. +2023-09-19 10:17:06,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b38f8e6c2f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:17:06,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605b38f8e6c2f0582", "creation_timestamp": 1695118626.0, "exchange_order_id": "35659995", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:17:06,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118626.0, "order_id": "x-XEKWYICXSSIUT605b38c4733430582", "exchange_order_id": "35659669", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:17:06,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b38c4733430582. +2023-09-19 10:17:06,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b38f8e70100582 for 80.00000000 SEI-USDT. +2023-09-19 10:17:06,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12670000", "order_id": "x-XEKWYICXSSIUT605b38f8e70100582", "creation_timestamp": 1695118626.0, "exchange_order_id": "35659996", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:18:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b38f8e6c2f0582. [clock=2023-09-19 10:18:01+00:00] +2023-09-19 10:18:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b38f8e70100582. [clock=2023-09-19 10:18:01+00:00] +2023-09-19 10:18:01,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1259366510816040390936435636 amount: 80. +2023-09-19 10:18:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1269807961614184867536406154 amount: 80. +2023-09-19 10:18:01,188 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118681.0, "order_id": "x-XEKWYICXBSIUT605b38f8e6c2f0582", "exchange_order_id": "35659995", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:18:01,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b38f8e6c2f0582. +2023-09-19 10:18:01,198 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118681.0, "order_id": "x-XEKWYICXSSIUT605b38f8e70100582", "exchange_order_id": "35659996", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:18:01,198 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b38f8e70100582. +2023-09-19 10:18:01,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b392d5a4a70582 for 80.00000000 SEI-USDT. +2023-09-19 10:18:01,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12690000", "order_id": "x-XEKWYICXSSIUT605b392d5a4a70582", "creation_timestamp": 1695118681.0, "exchange_order_id": "35660285", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:18:01,384 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b392d5a2910582 for 80.00000000 SEI-USDT. +2023-09-19 10:18:01,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXBSIUT605b392d5a2910582", "creation_timestamp": 1695118681.0, "exchange_order_id": "35660286", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:18:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b392d5a2910582. [clock=2023-09-19 10:18:56+00:00] +2023-09-19 10:18:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b392d5a4a70582. [clock=2023-09-19 10:18:56+00:00] +2023-09-19 10:18:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1255863564089239738064493721 amount: 80. +2023-09-19 10:18:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1266196969561779638640808537 amount: 80. +2023-09-19 10:18:56,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118736.0, "order_id": "x-XEKWYICXBSIUT605b392d5a2910582", "exchange_order_id": "35660286", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:18:56,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b392d5a2910582. +2023-09-19 10:18:56,279 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118736.0, "order_id": "x-XEKWYICXSSIUT605b392d5a4a70582", "exchange_order_id": "35660285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:18:56,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b392d5a4a70582. +2023-09-19 10:18:56,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3961ce2990582 for 80.00000000 SEI-USDT. +2023-09-19 10:18:56,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXBSIUT605b3961ce2990582", "creation_timestamp": 1695118736.0, "exchange_order_id": "35660629", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:18:56,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3961ce5990582 for 80.00000000 SEI-USDT. +2023-09-19 10:18:56,365 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118736.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12660000", "order_id": "x-XEKWYICXSSIUT605b3961ce5990582", "creation_timestamp": 1695118736.0, "exchange_order_id": "35660630", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:19:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3961ce2990582. [clock=2023-09-19 10:19:51+00:00] +2023-09-19 10:19:51,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3961ce5990582. [clock=2023-09-19 10:19:51+00:00] +2023-09-19 10:19:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253806037364135199212516791 amount: 80. +2023-09-19 10:19:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264064632898102775839723519 amount: 80. +2023-09-19 10:19:51,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118791.0, "order_id": "x-XEKWYICXBSIUT605b3961ce2990582", "exchange_order_id": "35660629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:19:51,184 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3961ce2990582. +2023-09-19 10:19:51,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118791.0, "order_id": "x-XEKWYICXSSIUT605b3961ce5990582", "exchange_order_id": "35660630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:19:51,267 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3961ce5990582. +2023-09-19 10:19:51,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3996425600582 for 80.00000000 SEI-USDT. +2023-09-19 10:19:51,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b3996425600582", "creation_timestamp": 1695118791.0, "exchange_order_id": "35661119", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:19:51,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3996428690582 for 80.00000000 SEI-USDT. +2023-09-19 10:19:51,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118791.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b3996428690582", "creation_timestamp": 1695118791.0, "exchange_order_id": "35661120", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:20:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3996425600582. [clock=2023-09-19 10:20:46+00:00] +2023-09-19 10:20:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3996428690582. [clock=2023-09-19 10:20:46+00:00] +2023-09-19 10:20:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250782436609937194822586195 amount: 80. +2023-09-19 10:20:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262700436085814863391436549 amount: 80. +2023-09-19 10:20:46,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118846.0, "order_id": "x-XEKWYICXBSIUT605b3996425600582", "exchange_order_id": "35661119", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:20:46,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3996425600582. +2023-09-19 10:20:46,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b39cab5adf0582 for 80.00000000 SEI-USDT. +2023-09-19 10:20:46,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b39cab5adf0582", "creation_timestamp": 1695118846.0, "exchange_order_id": "35661602", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:20:46,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118846.0, "order_id": "x-XEKWYICXSSIUT605b3996428690582", "exchange_order_id": "35661120", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:20:46,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3996428690582. +2023-09-19 10:20:46,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b39cab5e620582 for 80.00000000 SEI-USDT. +2023-09-19 10:20:46,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b39cab5e620582", "creation_timestamp": 1695118846.0, "exchange_order_id": "35661610", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:21:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b39cab5adf0582. [clock=2023-09-19 10:21:41+00:00] +2023-09-19 10:21:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b39cab5e620582. [clock=2023-09-19 10:21:41+00:00] +2023-09-19 10:21:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252282855435418036137359922 amount: 80. +2023-09-19 10:21:41,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264642837683987337547097247 amount: 80. +2023-09-19 10:21:41,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118901.0, "order_id": "x-XEKWYICXBSIUT605b39cab5adf0582", "exchange_order_id": "35661602", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:21:41,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b39cab5adf0582. +2023-09-19 10:21:41,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118901.0, "order_id": "x-XEKWYICXSSIUT605b39cab5e620582", "exchange_order_id": "35661610", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:21:41,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b39cab5e620582. +2023-09-19 10:21:41,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b39ff299af0582 for 80.00000000 SEI-USDT. +2023-09-19 10:21:41,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b39ff299af0582", "creation_timestamp": 1695118901.0, "exchange_order_id": "35661982", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:21:41,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b39ff29c350582 for 80.00000000 SEI-USDT. +2023-09-19 10:21:41,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118901.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b39ff29c350582", "creation_timestamp": 1695118901.0, "exchange_order_id": "35661983", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:22:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b39ff299af0582. [clock=2023-09-19 10:22:36+00:00] +2023-09-19 10:22:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b39ff29c350582. [clock=2023-09-19 10:22:36+00:00] +2023-09-19 10:22:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254548356442095524774501083 amount: 80. +2023-09-19 10:22:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1264168517327635360242335896 amount: 80. +2023-09-19 10:22:36,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118956.0, "order_id": "x-XEKWYICXBSIUT605b39ff299af0582", "exchange_order_id": "35661982", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:22:36,248 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b39ff299af0582. +2023-09-19 10:22:36,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118956.0, "order_id": "x-XEKWYICXSSIUT605b39ff29c350582", "exchange_order_id": "35661983", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:22:36,425 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b39ff29c350582. +2023-09-19 10:22:36,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3a339d3850582 for 80.00000000 SEI-USDT. +2023-09-19 10:22:36,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b3a339d3850582", "creation_timestamp": 1695118956.0, "exchange_order_id": "35662250", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:22:36,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3a339d6f80582 for 80.00000000 SEI-USDT. +2023-09-19 10:22:36,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695118956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12640000", "order_id": "x-XEKWYICXSSIUT605b3a339d6f80582", "creation_timestamp": 1695118956.0, "exchange_order_id": "35662251", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:23:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3a339d3850582. [clock=2023-09-19 10:23:31+00:00] +2023-09-19 10:23:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3a339d6f80582. [clock=2023-09-19 10:23:31+00:00] +2023-09-19 10:23:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1254024938573650781524180549 amount: 80. +2023-09-19 10:23:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262611615393987452456313405 amount: 80. +2023-09-19 10:23:31,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119011.0, "order_id": "x-XEKWYICXBSIUT605b3a339d3850582", "exchange_order_id": "35662250", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:23:31,183 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3a339d3850582. +2023-09-19 10:23:31,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3a6810fdf0582 for 80.00000000 SEI-USDT. +2023-09-19 10:23:31,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXBSIUT605b3a6810fdf0582", "creation_timestamp": 1695119011.0, "exchange_order_id": "35662396", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:23:31,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119011.0, "order_id": "x-XEKWYICXSSIUT605b3a339d6f80582", "exchange_order_id": "35662251", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:23:31,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3a339d6f80582. +2023-09-19 10:23:31,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3a68113e30582 for 80.00000000 SEI-USDT. +2023-09-19 10:23:31,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b3a68113e30582", "creation_timestamp": 1695119011.0, "exchange_order_id": "35662397", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:24:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3a6810fdf0582. [clock=2023-09-19 10:24:26+00:00] +2023-09-19 10:24:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3a68113e30582. [clock=2023-09-19 10:24:26+00:00] +2023-09-19 10:24:26,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252199358863437365419687591 amount: 80. +2023-09-19 10:24:26,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261960779874905529632131355 amount: 80. +2023-09-19 10:24:26,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119066.0, "order_id": "x-XEKWYICXBSIUT605b3a6810fdf0582", "exchange_order_id": "35662396", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:24:26,210 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3a6810fdf0582. +2023-09-19 10:24:26,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3a9c85c700582 for 80.00000000 SEI-USDT. +2023-09-19 10:24:26,315 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b3a9c85c700582", "creation_timestamp": 1695119066.0, "exchange_order_id": "35662786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:24:26,329 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119066.0, "order_id": "x-XEKWYICXSSIUT605b3a68113e30582", "exchange_order_id": "35662397", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:24:26,329 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3a68113e30582. +2023-09-19 10:24:26,333 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3a9c859040582 for 80.00000000 SEI-USDT. +2023-09-19 10:24:26,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119066.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3a9c859040582", "creation_timestamp": 1695119066.0, "exchange_order_id": "35662787", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:25:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3a9c859040582. [clock=2023-09-19 10:25:21+00:00] +2023-09-19 10:25:21,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3a9c85c700582. [clock=2023-09-19 10:25:21+00:00] +2023-09-19 10:25:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252333410323360739478889457 amount: 80. +2023-09-19 10:25:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261040123582141747933074049 amount: 80. +2023-09-19 10:25:21,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119121.0, "order_id": "x-XEKWYICXBSIUT605b3a9c859040582", "exchange_order_id": "35662787", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:25:21,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3a9c859040582. +2023-09-19 10:25:21,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119121.0, "order_id": "x-XEKWYICXSSIUT605b3a9c85c700582", "exchange_order_id": "35662786", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:25:21,330 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3a9c85c700582. +2023-09-19 10:25:21,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3ad0f89780582 for 80.00000000 SEI-USDT. +2023-09-19 10:25:21,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b3ad0f89780582", "creation_timestamp": 1695119121.0, "exchange_order_id": "35662980", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:25:21,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3ad0f85de0582 for 80.00000000 SEI-USDT. +2023-09-19 10:25:21,414 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119121.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3ad0f85de0582", "creation_timestamp": 1695119121.0, "exchange_order_id": "35662981", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:26:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3ad0f85de0582. [clock=2023-09-19 10:26:16+00:00] +2023-09-19 10:26:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3ad0f89780582. [clock=2023-09-19 10:26:16+00:00] +2023-09-19 10:26:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249880475466200548156782983 amount: 80. +2023-09-19 10:26:16,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258376542634977570012975280 amount: 80. +2023-09-19 10:26:16,181 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119176.0, "order_id": "x-XEKWYICXBSIUT605b3ad0f85de0582", "exchange_order_id": "35662981", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:26:16,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3ad0f85de0582. +2023-09-19 10:26:16,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3b056c46d0582 for 80.00000000 SEI-USDT. +2023-09-19 10:26:16,267 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b3b056c46d0582", "creation_timestamp": 1695119176.0, "exchange_order_id": "35663266", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:26:16,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119176.0, "order_id": "x-XEKWYICXSSIUT605b3ad0f89780582", "exchange_order_id": "35662980", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:26:16,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3ad0f89780582. +2023-09-19 10:26:16,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3b056c73c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:26:16,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119176.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b3b056c73c0582", "creation_timestamp": 1695119176.0, "exchange_order_id": "35663267", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:27:11,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3b056c46d0582. [clock=2023-09-19 10:27:11+00:00] +2023-09-19 10:27:11,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3b056c73c0582. [clock=2023-09-19 10:27:11+00:00] +2023-09-19 10:27:11,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243993217697936072923861881 amount: 80. +2023-09-19 10:27:11,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12530000 amount: 80. +2023-09-19 10:27:11,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119231.0, "order_id": "x-XEKWYICXBSIUT605b3b056c46d0582", "exchange_order_id": "35663266", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:27:11,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3b056c46d0582. +2023-09-19 10:27:11,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119231.0, "order_id": "x-XEKWYICXSSIUT605b3b056c73c0582", "exchange_order_id": "35663267", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:27:11,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3b056c73c0582. +2023-09-19 10:27:11,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3b39e8e020582 for 80.00000000 SEI-USDT. +2023-09-19 10:27:11,334 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b3b39e8e020582", "creation_timestamp": 1695119231.0, "exchange_order_id": "35663840", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:27:11,337 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3b39e90400582 for 80.00000000 SEI-USDT. +2023-09-19 10:27:11,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b3b39e90400582", "creation_timestamp": 1695119231.0, "exchange_order_id": "35663841", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:27:45,400 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b3b39e90400582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:27:45,401 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:27:45+00:00] +2023-09-19 10:27:45,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119265.0, "order_id": "x-XEKWYICXSSIUT605b3b39e90400582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01002400"}]}, "exchange_trade_id": "5003372", "exchange_order_id": "35663841", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:27:45,438 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119265.0, "order_id": "x-XEKWYICXSSIUT605b3b39e90400582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35663841", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:27:45,439 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b3b39e90400582 completely filled. +2023-09-19 10:28:06,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3b39e8e020582. [clock=2023-09-19 10:28:06+00:00] +2023-09-19 10:28:06,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244749275696842300814504299 amount: 80. +2023-09-19 10:28:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252271967409900399762559016 amount: 80. +2023-09-19 10:28:06,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119286.0, "order_id": "x-XEKWYICXBSIUT605b3b39e8e020582", "exchange_order_id": "35663840", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:28:06,157 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3b39e8e020582. +2023-09-19 10:28:06,161 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3b6e531750582 for 80.00000000 SEI-USDT. +2023-09-19 10:28:06,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b3b6e531750582", "creation_timestamp": 1695119286.0, "exchange_order_id": "35664380", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:28:06,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3b6e5350c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:28:06,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b3b6e5350c0582", "creation_timestamp": 1695119286.0, "exchange_order_id": "35664404", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:28:44,550 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b3b6e5350c0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:28:44,551 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:28:44+00:00] +2023-09-19 10:28:44,575 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119324.0, "order_id": "x-XEKWYICXSSIUT605b3b6e5350c0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "5003495", "exchange_order_id": "35664404", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:28:44,587 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119324.0, "order_id": "x-XEKWYICXSSIUT605b3b6e5350c0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35664404", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:28:44,587 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b3b6e5350c0582 completely filled. +2023-09-19 10:29:01,013 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3b6e531750582. [clock=2023-09-19 10:29:01+00:00] +2023-09-19 10:29:01,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242491720982598942890845182 amount: 80. +2023-09-19 10:29:01,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255559874800038566125565688 amount: 80. +2023-09-19 10:29:01,194 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119341.0, "order_id": "x-XEKWYICXBSIUT605b3b6e531750582", "exchange_order_id": "35664380", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:29:01,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3b6e531750582. +2023-09-19 10:29:01,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3ba2ca63f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:29:01,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b3ba2ca63f0582", "creation_timestamp": 1695119341.0, "exchange_order_id": "35665346", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:29:01,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3ba2ca99d0582 for 80.00000000 SEI-USDT. +2023-09-19 10:29:01,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119341.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b3ba2ca99d0582", "creation_timestamp": 1695119341.0, "exchange_order_id": "35665347", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:29:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3ba2ca63f0582. [clock=2023-09-19 10:29:56+00:00] +2023-09-19 10:29:56,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3ba2ca99d0582. [clock=2023-09-19 10:29:56+00:00] +2023-09-19 10:29:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243796968051510498455828535 amount: 80. +2023-09-19 10:29:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255073253378373925669199291 amount: 80. +2023-09-19 10:29:56,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119396.0, "order_id": "x-XEKWYICXBSIUT605b3ba2ca63f0582", "exchange_order_id": "35665346", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:29:56,180 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3ba2ca63f0582. +2023-09-19 10:29:56,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119396.0, "order_id": "x-XEKWYICXSSIUT605b3ba2ca99d0582", "exchange_order_id": "35665347", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:29:56,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3ba2ca99d0582. +2023-09-19 10:29:56,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3bd73b2ae0582 for 80.00000000 SEI-USDT. +2023-09-19 10:29:56,280 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b3bd73b2ae0582", "creation_timestamp": 1695119396.0, "exchange_order_id": "35665761", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:29:56,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3bd73b5de0582 for 80.00000000 SEI-USDT. +2023-09-19 10:29:56,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119396.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b3bd73b5de0582", "creation_timestamp": 1695119396.0, "exchange_order_id": "35665762", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:30:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3bd73b2ae0582. [clock=2023-09-19 10:30:51+00:00] +2023-09-19 10:30:51,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3bd73b5de0582. [clock=2023-09-19 10:30:51+00:00] +2023-09-19 10:30:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243006657588585860096851316 amount: 80. +2023-09-19 10:30:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253995333287829569365339064 amount: 80. +2023-09-19 10:30:51,187 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119451.0, "order_id": "x-XEKWYICXBSIUT605b3bd73b2ae0582", "exchange_order_id": "35665761", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:30:51,188 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3bd73b2ae0582. +2023-09-19 10:30:51,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119451.0, "order_id": "x-XEKWYICXSSIUT605b3bd73b5de0582", "exchange_order_id": "35665762", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:30:51,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3bd73b5de0582. +2023-09-19 10:30:51,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3c0baf4cf0582 for 80.00000000 SEI-USDT. +2023-09-19 10:30:51,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b3c0baf4cf0582", "creation_timestamp": 1695119451.0, "exchange_order_id": "35666083", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:30:51,290 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3c0baf7cc0582 for 80.00000000 SEI-USDT. +2023-09-19 10:30:51,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119451.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b3c0baf7cc0582", "creation_timestamp": 1695119451.0, "exchange_order_id": "35666084", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:31:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3c0baf4cf0582. [clock=2023-09-19 10:31:46+00:00] +2023-09-19 10:31:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3c0baf7cc0582. [clock=2023-09-19 10:31:46+00:00] +2023-09-19 10:31:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241171238528774940165019479 amount: 80. +2023-09-19 10:31:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251936190160243492918218347 amount: 80. +2023-09-19 10:31:46,186 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119506.0, "order_id": "x-XEKWYICXBSIUT605b3c0baf4cf0582", "exchange_order_id": "35666083", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:31:46,187 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3c0baf4cf0582. +2023-09-19 10:31:46,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119506.0, "order_id": "x-XEKWYICXSSIUT605b3c0baf7cc0582", "exchange_order_id": "35666084", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:31:46,273 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3c0baf7cc0582. +2023-09-19 10:31:46,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3c4022add0582 for 80.00000000 SEI-USDT. +2023-09-19 10:31:46,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b3c4022add0582", "creation_timestamp": 1695119506.0, "exchange_order_id": "35666427", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:31:46,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3c4022e880582 for 80.00000000 SEI-USDT. +2023-09-19 10:31:46,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119506.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b3c4022e880582", "creation_timestamp": 1695119506.0, "exchange_order_id": "35666428", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:32:05,158 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b3c4022e880582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:32:05,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:32:05+00:00] +2023-09-19 10:32:05,183 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119525.0, "order_id": "x-XEKWYICXSSIUT605b3c4022e880582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "5003631", "exchange_order_id": "35666428", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:32:05,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119525.0, "order_id": "x-XEKWYICXSSIUT605b3c4022e880582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35666428", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:32:05,195 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b3c4022e880582 completely filled. +2023-09-19 10:32:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3c4022add0582. [clock=2023-09-19 10:32:41+00:00] +2023-09-19 10:32:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243700468480577632586130363 amount: 80. +2023-09-19 10:32:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255409432115139179467751909 amount: 80. +2023-09-19 10:32:41,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119561.0, "order_id": "x-XEKWYICXBSIUT605b3c4022add0582", "exchange_order_id": "35666427", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:32:41,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3c4022add0582. +2023-09-19 10:32:41,421 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3c74966090582 for 80.00000000 SEI-USDT. +2023-09-19 10:32:41,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b3c74966090582", "creation_timestamp": 1695119561.0, "exchange_order_id": "35666945", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:32:41,438 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3c74960cc0582 for 80.00000000 SEI-USDT. +2023-09-19 10:32:41,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b3c74960cc0582", "creation_timestamp": 1695119561.0, "exchange_order_id": "35666946", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:33:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3c74960cc0582. [clock=2023-09-19 10:33:36+00:00] +2023-09-19 10:33:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3c74966090582. [clock=2023-09-19 10:33:36+00:00] +2023-09-19 10:33:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249338296170097083480069816 amount: 80. +2023-09-19 10:33:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:33:36,208 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119616.0, "order_id": "x-XEKWYICXBSIUT605b3c74960cc0582", "exchange_order_id": "35666946", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:33:36,208 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3c74960cc0582. +2023-09-19 10:33:36,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119616.0, "order_id": "x-XEKWYICXSSIUT605b3c74966090582", "exchange_order_id": "35666945", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:33:36,326 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3c74966090582. +2023-09-19 10:33:36,330 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3ca909fcc0582 for 80.00000000 SEI-USDT. +2023-09-19 10:33:36,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b3ca909fcc0582", "creation_timestamp": 1695119616.0, "exchange_order_id": "35667175", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:34:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3ca909fcc0582. [clock=2023-09-19 10:34:31+00:00] +2023-09-19 10:34:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251074039460718792064672867 amount: 80. +2023-09-19 10:34:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261029123141877448815132785 amount: 80. +2023-09-19 10:34:31,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119671.0, "order_id": "x-XEKWYICXBSIUT605b3ca909fcc0582", "exchange_order_id": "35667175", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:34:31,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3ca909fcc0582. +2023-09-19 10:34:31,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3cdd7e17b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:34:31,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b3cdd7e17b0582", "creation_timestamp": 1695119671.0, "exchange_order_id": "35667398", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:34:31,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3cdd7db830582 for 80.00000000 SEI-USDT. +2023-09-19 10:34:31,326 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119671.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b3cdd7db830582", "creation_timestamp": 1695119671.0, "exchange_order_id": "35667399", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:35:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3cdd7db830582. [clock=2023-09-19 10:35:26+00:00] +2023-09-19 10:35:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3cdd7e17b0582. [clock=2023-09-19 10:35:26+00:00] +2023-09-19 10:35:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251891660104448404156465851 amount: 80. +2023-09-19 10:35:26,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:35:26,179 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119726.0, "order_id": "x-XEKWYICXBSIUT605b3cdd7db830582", "exchange_order_id": "35667399", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:35:26,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3cdd7db830582. +2023-09-19 10:35:26,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119726.0, "order_id": "x-XEKWYICXSSIUT605b3cdd7e17b0582", "exchange_order_id": "35667398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:35:26,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3cdd7e17b0582. +2023-09-19 10:35:26,280 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3d11f1afb0582 for 80.00000000 SEI-USDT. +2023-09-19 10:35:26,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119726.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b3d11f1afb0582", "creation_timestamp": 1695119726.0, "exchange_order_id": "35667620", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:36:21,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3d11f1afb0582. [clock=2023-09-19 10:36:21+00:00] +2023-09-19 10:36:21,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251520782085818152109938547 amount: 80. +2023-09-19 10:36:21,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259519727457548102270549610 amount: 80. +2023-09-19 10:36:21,162 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119781.0, "order_id": "x-XEKWYICXBSIUT605b3d11f1afb0582", "exchange_order_id": "35667620", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:36:21,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3d11f1afb0582. +2023-09-19 10:36:21,276 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3d46653280582 for 80.00000000 SEI-USDT. +2023-09-19 10:36:21,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b3d46653280582", "creation_timestamp": 1695119781.0, "exchange_order_id": "35667753", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:36:21,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3d46657760582 for 80.00000000 SEI-USDT. +2023-09-19 10:36:21,305 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119781.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b3d46657760582", "creation_timestamp": 1695119781.0, "exchange_order_id": "35667754", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:37:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3d46653280582. [clock=2023-09-19 10:37:16+00:00] +2023-09-19 10:37:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3d46657760582. [clock=2023-09-19 10:37:16+00:00] +2023-09-19 10:37:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250591289189825229426384881 amount: 80. +2023-09-19 10:37:16,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:37:16,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119836.0, "order_id": "x-XEKWYICXBSIUT605b3d46653280582", "exchange_order_id": "35667753", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:37:16,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3d46653280582. +2023-09-19 10:37:16,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3d7ad8fde0582 for 80.00000000 SEI-USDT. +2023-09-19 10:37:16,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b3d7ad8fde0582", "creation_timestamp": 1695119836.0, "exchange_order_id": "35668031", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:37:16,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119836.0, "order_id": "x-XEKWYICXSSIUT605b3d46657760582", "exchange_order_id": "35667754", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:37:16,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3d46657760582. +2023-09-19 10:38:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3d7ad8fde0582. [clock=2023-09-19 10:38:11+00:00] +2023-09-19 10:38:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250461988939578142173780931 amount: 80. +2023-09-19 10:38:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258138608191129263268433791 amount: 80. +2023-09-19 10:38:11,163 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119891.0, "order_id": "x-XEKWYICXBSIUT605b3d7ad8fde0582", "exchange_order_id": "35668031", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:38:11,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3d7ad8fde0582. +2023-09-19 10:38:11,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3daf4ca210582 for 80.00000000 SEI-USDT. +2023-09-19 10:38:11,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b3daf4ca210582", "creation_timestamp": 1695119891.0, "exchange_order_id": "35668497", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:38:11,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3daf4cd2f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:38:11,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b3daf4cd2f0582", "creation_timestamp": 1695119891.0, "exchange_order_id": "35668498", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:39:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3daf4ca210582. [clock=2023-09-19 10:39:06+00:00] +2023-09-19 10:39:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3daf4cd2f0582. [clock=2023-09-19 10:39:06+00:00] +2023-09-19 10:39:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250250734293079436732768366 amount: 80. +2023-09-19 10:39:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:39:06,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119946.0, "order_id": "x-XEKWYICXBSIUT605b3daf4ca210582", "exchange_order_id": "35668497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:39:06,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3daf4ca210582. +2023-09-19 10:39:06,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3de3c06f30582 for 80.00000000 SEI-USDT. +2023-09-19 10:39:06,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119946.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b3de3c06f30582", "creation_timestamp": 1695119946.0, "exchange_order_id": "35668706", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:39:06,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695119946.0, "order_id": "x-XEKWYICXSSIUT605b3daf4cd2f0582", "exchange_order_id": "35668498", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:39:06,269 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3daf4cd2f0582. +2023-09-19 10:40:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3de3c06f30582. [clock=2023-09-19 10:40:01+00:00] +2023-09-19 10:40:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1250429458210173444092455172 amount: 80. +2023-09-19 10:40:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259494564575999201330489817 amount: 80. +2023-09-19 10:40:01,175 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120001.0, "order_id": "x-XEKWYICXBSIUT605b3de3c06f30582", "exchange_order_id": "35668706", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:40:01,176 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3de3c06f30582. +2023-09-19 10:40:01,179 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3e18342990582 for 80.00000000 SEI-USDT. +2023-09-19 10:40:01,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b3e18342990582", "creation_timestamp": 1695120001.0, "exchange_order_id": "35669146", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:40:01,327 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3e183466b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:40:01,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120001.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b3e183466b0582", "creation_timestamp": 1695120001.0, "exchange_order_id": "35669147", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:40:56,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3e18342990582. [clock=2023-09-19 10:40:56+00:00] +2023-09-19 10:40:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3e183466b0582. [clock=2023-09-19 10:40:56+00:00] +2023-09-19 10:40:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248433917265535764996472314 amount: 80. +2023-09-19 10:40:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:40:56,160 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120056.0, "order_id": "x-XEKWYICXBSIUT605b3e18342990582", "exchange_order_id": "35669146", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:40:56,160 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3e18342990582. +2023-09-19 10:40:56,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120056.0, "order_id": "x-XEKWYICXSSIUT605b3e183466b0582", "exchange_order_id": "35669147", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:40:56,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3e183466b0582. +2023-09-19 10:40:56,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3e4ca7c0e0582 for 80.00000000 SEI-USDT. +2023-09-19 10:40:56,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120056.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b3e4ca7c0e0582", "creation_timestamp": 1695120056.0, "exchange_order_id": "35669435", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:41:51,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3e4ca7c0e0582. [clock=2023-09-19 10:41:51+00:00] +2023-09-19 10:41:51,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253127330484551210234437417 amount: 80. +2023-09-19 10:41:51,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1262582178934507900337681200 amount: 80. +2023-09-19 10:41:51,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120111.0, "order_id": "x-XEKWYICXBSIUT605b3e4ca7c0e0582", "exchange_order_id": "35669435", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:41:51,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3e4ca7c0e0582. +2023-09-19 10:41:51,470 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3e8126f9c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:41:51,485 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12620000", "order_id": "x-XEKWYICXSSIUT605b3e8126f9c0582", "creation_timestamp": 1695120111.0, "exchange_order_id": "35669770", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:41:51,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3e8126d040582 for 80.00000000 SEI-USDT. +2023-09-19 10:41:51,507 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b3e8126d040582", "creation_timestamp": 1695120111.0, "exchange_order_id": "35669771", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:42:46,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3e8126d040582. [clock=2023-09-19 10:42:46+00:00] +2023-09-19 10:42:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3e8126f9c0582. [clock=2023-09-19 10:42:46+00:00] +2023-09-19 10:42:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1253240706097273567477683418 amount: 80. +2023-09-19 10:42:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:42:46,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120166.0, "order_id": "x-XEKWYICXBSIUT605b3e8126d040582", "exchange_order_id": "35669771", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:42:46,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3e8126d040582. +2023-09-19 10:42:46,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120166.0, "order_id": "x-XEKWYICXSSIUT605b3e8126f9c0582", "exchange_order_id": "35669770", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:42:46,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3e8126f9c0582. +2023-09-19 10:42:46,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3eb58f6550582 for 80.00000000 SEI-USDT. +2023-09-19 10:42:46,434 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b3eb58f6550582", "creation_timestamp": 1695120166.0, "exchange_order_id": "35669990", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:42:51,457 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 10:43:41,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3eb58f6550582. [clock=2023-09-19 10:43:41+00:00] +2023-09-19 10:43:41,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252411692678730588800072847 amount: 80. +2023-09-19 10:43:41,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258794065630759669111508923 amount: 80. +2023-09-19 10:43:41,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120221.0, "order_id": "x-XEKWYICXBSIUT605b3eb58f6550582", "exchange_order_id": "35669990", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:43:41,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3eb58f6550582. +2023-09-19 10:43:41,441 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3eea0c4d30582 for 80.00000000 SEI-USDT. +2023-09-19 10:43:41,454 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3eea0c4d30582", "creation_timestamp": 1695120221.0, "exchange_order_id": "35670224", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:43:41,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3eea0c8310582 for 80.00000000 SEI-USDT. +2023-09-19 10:43:41,467 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120221.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b3eea0c8310582", "creation_timestamp": 1695120221.0, "exchange_order_id": "35670223", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:44:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3eea0c4d30582. [clock=2023-09-19 10:44:36+00:00] +2023-09-19 10:44:36,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3eea0c8310582. [clock=2023-09-19 10:44:36+00:00] +2023-09-19 10:44:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252543928741430336991039968 amount: 80. +2023-09-19 10:44:36,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:44:36,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120276.0, "order_id": "x-XEKWYICXBSIUT605b3eea0c4d30582", "exchange_order_id": "35670224", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:44:36,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3eea0c4d30582. +2023-09-19 10:44:36,415 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120276.0, "order_id": "x-XEKWYICXSSIUT605b3eea0c8310582", "exchange_order_id": "35670223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:44:36,416 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3eea0c8310582. +2023-09-19 10:44:36,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3f1e7657a0582 for 80.00000000 SEI-USDT. +2023-09-19 10:44:36,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120276.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3f1e7657a0582", "creation_timestamp": 1695120276.0, "exchange_order_id": "35670420", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:45:31,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3f1e7657a0582. [clock=2023-09-19 10:45:31+00:00] +2023-09-19 10:45:31,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252645930687162686641879450 amount: 80. +2023-09-19 10:45:31,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256503711100804840448357342 amount: 80. +2023-09-19 10:45:31,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120331.0, "order_id": "x-XEKWYICXBSIUT605b3f1e7657a0582", "exchange_order_id": "35670420", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:45:31,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3f1e7657a0582. +2023-09-19 10:45:31,488 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3f52f16350582 for 80.00000000 SEI-USDT. +2023-09-19 10:45:31,501 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3f52f16350582", "creation_timestamp": 1695120331.0, "exchange_order_id": "35670617", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:45:31,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3f52f19230582 for 80.00000000 SEI-USDT. +2023-09-19 10:45:31,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120331.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b3f52f19230582", "creation_timestamp": 1695120331.0, "exchange_order_id": "35670618", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:46:26,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3f52f16350582. [clock=2023-09-19 10:46:26+00:00] +2023-09-19 10:46:26,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3f52f19230582. [clock=2023-09-19 10:46:26+00:00] +2023-09-19 10:46:26,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252309940639585481849901346 amount: 80. +2023-09-19 10:46:26,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:46:26,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120386.0, "order_id": "x-XEKWYICXBSIUT605b3f52f16350582", "exchange_order_id": "35670617", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:46:26,377 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3f52f16350582. +2023-09-19 10:46:26,639 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3f876048b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:46:26,666 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120386.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3f876048b0582", "creation_timestamp": 1695120386.0, "exchange_order_id": "35670912", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:46:26,685 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120386.0, "order_id": "x-XEKWYICXSSIUT605b3f52f19230582", "exchange_order_id": "35670618", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:46:26,686 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3f52f19230582. +2023-09-19 10:47:21,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3f876048b0582. [clock=2023-09-19 10:47:21+00:00] +2023-09-19 10:47:21,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1252463662219725012474596919 amount: 80. +2023-09-19 10:47:21,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255470785094263665123444118 amount: 80. +2023-09-19 10:47:21,277 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120441.0, "order_id": "x-XEKWYICXBSIUT605b3f876048b0582", "exchange_order_id": "35670912", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:47:21,277 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3f876048b0582. +2023-09-19 10:47:21,529 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3fbbe41860582 for 80.00000000 SEI-USDT. +2023-09-19 10:47:21,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b3fbbe41860582", "creation_timestamp": 1695120441.0, "exchange_order_id": "35670989", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:47:21,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3fbbe3e720582 for 80.00000000 SEI-USDT. +2023-09-19 10:47:21,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b3fbbe3e720582", "creation_timestamp": 1695120441.0, "exchange_order_id": "35670990", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:47:32,346 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b3fbbe3e720582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:47:32,347 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:47:32+00:00] +2023-09-19 10:47:32,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120452.0, "order_id": "x-XEKWYICXBSIUT605b3fbbe3e720582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5003971", "exchange_order_id": "35670990", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:47:32,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120452.0, "order_id": "x-XEKWYICXBSIUT605b3fbbe3e720582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35670990", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:47:32,383 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b3fbbe3e720582 completely filled. +2023-09-19 10:48:16,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3fbbe41860582. [clock=2023-09-19 10:48:16+00:00] +2023-09-19 10:48:16,096 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245614570975950230991978815 amount: 80. +2023-09-19 10:48:16,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251648731273296776231708077 amount: 80. +2023-09-19 10:48:16,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120496.0, "order_id": "x-XEKWYICXSSIUT605b3fbbe41860582", "exchange_order_id": "35670989", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:48:16,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3fbbe41860582. +2023-09-19 10:48:16,489 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b3ff0578580582 for 80.00000000 SEI-USDT. +2023-09-19 10:48:16,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b3ff0578580582", "creation_timestamp": 1695120496.0, "exchange_order_id": "35671466", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:48:16,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b3ff057a850582 for 80.00000000 SEI-USDT. +2023-09-19 10:48:16,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b3ff057a850582", "creation_timestamp": 1695120496.0, "exchange_order_id": "35671467", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:49:11,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b3ff0578580582. [clock=2023-09-19 10:49:11+00:00] +2023-09-19 10:49:11,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b3ff057a850582. [clock=2023-09-19 10:49:11+00:00] +2023-09-19 10:49:11,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246591190137013799051982386 amount: 80. +2023-09-19 10:49:11,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251285578843525398938024452 amount: 80. +2023-09-19 10:49:11,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120551.0, "order_id": "x-XEKWYICXBSIUT605b3ff0578580582", "exchange_order_id": "35671466", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:49:11,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b3ff0578580582. +2023-09-19 10:49:11,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120551.0, "order_id": "x-XEKWYICXSSIUT605b3ff057a850582", "exchange_order_id": "35671467", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:49:11,517 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b3ff057a850582. +2023-09-19 10:49:11,519 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4024cd9e90582 for 80.00000000 SEI-USDT. +2023-09-19 10:49:11,531 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b4024cd9e90582", "creation_timestamp": 1695120551.0, "exchange_order_id": "35671594", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:49:11,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4024cdca10582 for 80.00000000 SEI-USDT. +2023-09-19 10:49:11,603 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120551.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b4024cdca10582", "creation_timestamp": 1695120551.0, "exchange_order_id": "35671595", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:49:14,025 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b4024cdca10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 10:49:14,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 10:49:14+00:00] +2023-09-19 10:49:14,048 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120554.0, "order_id": "x-XEKWYICXSSIUT605b4024cdca10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01000800"}]}, "exchange_trade_id": "5004029", "exchange_order_id": "35671595", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 10:49:14,060 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120554.0, "order_id": "x-XEKWYICXSSIUT605b4024cdca10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35671595", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 10:49:14,061 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b4024cdca10582 completely filled. +2023-09-19 10:50:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4024cd9e90582. [clock=2023-09-19 10:50:06+00:00] +2023-09-19 10:50:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247189596470327735527297185 amount: 80. +2023-09-19 10:50:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251706092854465015396913006 amount: 80. +2023-09-19 10:50:06,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120606.0, "order_id": "x-XEKWYICXBSIUT605b4024cd9e90582", "exchange_order_id": "35671594", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:50:06,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4024cd9e90582. +2023-09-19 10:50:06,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b40592d1f90582 for 80.00000000 SEI-USDT. +2023-09-19 10:50:06,266 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b40592d1f90582", "creation_timestamp": 1695120606.0, "exchange_order_id": "35671935", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:50:06,520 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b40592d5b80582 for 80.00000000 SEI-USDT. +2023-09-19 10:50:06,532 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120606.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b40592d5b80582", "creation_timestamp": 1695120606.0, "exchange_order_id": "35671936", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:51:01,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b40592d1f90582. [clock=2023-09-19 10:51:01+00:00] +2023-09-19 10:51:01,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b40592d5b80582. [clock=2023-09-19 10:51:01+00:00] +2023-09-19 10:51:01,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243805885872228485751864975 amount: 80. +2023-09-19 10:51:01,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:51:01,386 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120661.0, "order_id": "x-XEKWYICXBSIUT605b40592d1f90582", "exchange_order_id": "35671935", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:51:01,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b40592d1f90582. +2023-09-19 10:51:01,631 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120661.0, "order_id": "x-XEKWYICXSSIUT605b40592d5b80582", "exchange_order_id": "35671936", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:51:01,632 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b40592d5b80582. +2023-09-19 10:51:01,634 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b408db505c0582 for 80.00000000 SEI-USDT. +2023-09-19 10:51:01,648 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120661.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b408db505c0582", "creation_timestamp": 1695120661.0, "exchange_order_id": "35672247", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:51:56,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b408db505c0582. [clock=2023-09-19 10:51:56+00:00] +2023-09-19 10:51:56,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246967547126417708661259520 amount: 80. +2023-09-19 10:51:56,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253327273291907866397231300 amount: 80. +2023-09-19 10:51:56,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120716.0, "order_id": "x-XEKWYICXBSIUT605b408db505c0582", "exchange_order_id": "35672247", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:51:56,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b408db505c0582. +2023-09-19 10:51:56,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b40c2281950582 for 80.00000000 SEI-USDT. +2023-09-19 10:51:56,545 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b40c2281950582", "creation_timestamp": 1695120716.0, "exchange_order_id": "35672520", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:51:56,637 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b40c227dc40582 for 80.00000000 SEI-USDT. +2023-09-19 10:51:56,671 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b40c227dc40582", "creation_timestamp": 1695120716.0, "exchange_order_id": "35672521", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:52:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b40c227dc40582. [clock=2023-09-19 10:52:51+00:00] +2023-09-19 10:52:51,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b40c2281950582. [clock=2023-09-19 10:52:51+00:00] +2023-09-19 10:52:51,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247419704145844932374558086 amount: 80. +2023-09-19 10:52:51,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:52:51,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120771.0, "order_id": "x-XEKWYICXBSIUT605b40c227dc40582", "exchange_order_id": "35672521", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:52:51,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b40c227dc40582. +2023-09-19 10:52:51,406 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120771.0, "order_id": "x-XEKWYICXSSIUT605b40c2281950582", "exchange_order_id": "35672520", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:52:51,406 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b40c2281950582. +2023-09-19 10:52:51,497 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b40f688e8b0582 for 80.00000000 SEI-USDT. +2023-09-19 10:52:51,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b40f688e8b0582", "creation_timestamp": 1695120771.0, "exchange_order_id": "35672749", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:53:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b40f688e8b0582. [clock=2023-09-19 10:53:46+00:00] +2023-09-19 10:53:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247938576854246507185917174 amount: 80. +2023-09-19 10:53:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252895050835291292866537290 amount: 80. +2023-09-19 10:53:46,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120826.0, "order_id": "x-XEKWYICXBSIUT605b40f688e8b0582", "exchange_order_id": "35672749", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:53:46,196 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b40f688e8b0582. +2023-09-19 10:53:46,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b412afb9950582 for 80.00000000 SEI-USDT. +2023-09-19 10:53:46,425 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b412afb9950582", "creation_timestamp": 1695120826.0, "exchange_order_id": "35672934", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:53:46,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b412afbce50582 for 80.00000000 SEI-USDT. +2023-09-19 10:53:46,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b412afbce50582", "creation_timestamp": 1695120826.0, "exchange_order_id": "35672935", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:54:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b412afb9950582. [clock=2023-09-19 10:54:41+00:00] +2023-09-19 10:54:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b412afbce50582. [clock=2023-09-19 10:54:41+00:00] +2023-09-19 10:54:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248772391819510298918898005 amount: 80. +2023-09-19 10:54:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:54:41,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120881.0, "order_id": "x-XEKWYICXBSIUT605b412afb9950582", "exchange_order_id": "35672934", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:54:41,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b412afb9950582. +2023-09-19 10:54:41,418 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120881.0, "order_id": "x-XEKWYICXSSIUT605b412afbce50582", "exchange_order_id": "35672935", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:54:41,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b412afbce50582. +2023-09-19 10:54:41,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b415f6f85f0582 for 80.00000000 SEI-USDT. +2023-09-19 10:54:41,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120881.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b415f6f85f0582", "creation_timestamp": 1695120881.0, "exchange_order_id": "35673219", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:55:36,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b415f6f85f0582. [clock=2023-09-19 10:55:36+00:00] +2023-09-19 10:55:36,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248046470479211012377039817 amount: 80. +2023-09-19 10:55:36,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252773777387571501817032455 amount: 80. +2023-09-19 10:55:36,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120936.0, "order_id": "x-XEKWYICXBSIUT605b415f6f85f0582", "exchange_order_id": "35673219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:55:36,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b415f6f85f0582. +2023-09-19 10:55:36,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4193e94db0582 for 80.00000000 SEI-USDT. +2023-09-19 10:55:36,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b4193e94db0582", "creation_timestamp": 1695120936.0, "exchange_order_id": "35673465", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:55:36,461 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4193e98570582 for 80.00000000 SEI-USDT. +2023-09-19 10:55:36,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120936.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b4193e98570582", "creation_timestamp": 1695120936.0, "exchange_order_id": "35673466", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:56:31,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4193e94db0582. [clock=2023-09-19 10:56:31+00:00] +2023-09-19 10:56:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4193e98570582. [clock=2023-09-19 10:56:31+00:00] +2023-09-19 10:56:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247774971277619908521479700 amount: 80. +2023-09-19 10:56:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:56:31,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120991.0, "order_id": "x-XEKWYICXBSIUT605b4193e94db0582", "exchange_order_id": "35673465", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:56:31,313 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4193e94db0582. +2023-09-19 10:56:31,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b41c8574fd0582 for 80.00000000 SEI-USDT. +2023-09-19 10:56:31,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120991.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b41c8574fd0582", "creation_timestamp": 1695120991.0, "exchange_order_id": "35673773", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:56:31,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695120991.0, "order_id": "x-XEKWYICXSSIUT605b4193e98570582", "exchange_order_id": "35673466", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:56:31,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4193e98570582. +2023-09-19 10:57:26,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b41c8574fd0582. [clock=2023-09-19 10:57:26+00:00] +2023-09-19 10:57:26,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249393942452028415107351982 amount: 80. +2023-09-19 10:57:26,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255772455266179194983168180 amount: 80. +2023-09-19 10:57:26,271 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121046.0, "order_id": "x-XEKWYICXBSIUT605b41c8574fd0582", "exchange_order_id": "35673773", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:57:26,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b41c8574fd0582. +2023-09-19 10:57:26,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b41fcdbbf40582 for 80.00000000 SEI-USDT. +2023-09-19 10:57:26,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b41fcdbbf40582", "creation_timestamp": 1695121046.0, "exchange_order_id": "35673969", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:57:26,514 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b41fcdbf530582 for 80.00000000 SEI-USDT. +2023-09-19 10:57:26,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b41fcdbf530582", "creation_timestamp": 1695121046.0, "exchange_order_id": "35673968", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:58:21,009 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b41fcdbbf40582. [clock=2023-09-19 10:58:21+00:00] +2023-09-19 10:58:21,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b41fcdbf530582. [clock=2023-09-19 10:58:21+00:00] +2023-09-19 10:58:21,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248994842128422591521990933 amount: 80. +2023-09-19 10:58:21,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 10:58:21,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121101.0, "order_id": "x-XEKWYICXBSIUT605b41fcdbbf40582", "exchange_order_id": "35673969", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:58:21,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b41fcdbbf40582. +2023-09-19 10:58:21,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b423140f0d0582 for 80.00000000 SEI-USDT. +2023-09-19 10:58:21,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b423140f0d0582", "creation_timestamp": 1695121101.0, "exchange_order_id": "35674180", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:58:21,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121101.0, "order_id": "x-XEKWYICXSSIUT605b41fcdbf530582", "exchange_order_id": "35673968", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:58:21,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b41fcdbf530582. +2023-09-19 10:59:16,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b423140f0d0582. [clock=2023-09-19 10:59:16+00:00] +2023-09-19 10:59:16,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249559139264364890345424550 amount: 80. +2023-09-19 10:59:16,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254280535242837854969343010 amount: 80. +2023-09-19 10:59:16,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121156.0, "order_id": "x-XEKWYICXBSIUT605b423140f0d0582", "exchange_order_id": "35674180", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 10:59:16,205 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b423140f0d0582. +2023-09-19 10:59:16,668 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4265b56e40582 for 80.00000000 SEI-USDT. +2023-09-19 10:59:16,680 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b4265b56e40582", "creation_timestamp": 1695121156.0, "exchange_order_id": "35674437", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 10:59:16,680 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4265b54a20582 for 80.00000000 SEI-USDT. +2023-09-19 10:59:16,691 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121156.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b4265b54a20582", "creation_timestamp": 1695121156.0, "exchange_order_id": "35674438", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:00:11,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4265b54a20582. [clock=2023-09-19 11:00:11+00:00] +2023-09-19 11:00:11,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4265b56e40582. [clock=2023-09-19 11:00:11+00:00] +2023-09-19 11:00:11,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247740239181299867371146441 amount: 80. +2023-09-19 11:00:11,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:00:11,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121211.0, "order_id": "x-XEKWYICXBSIUT605b4265b54a20582", "exchange_order_id": "35674438", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:00:11,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4265b54a20582. +2023-09-19 11:00:11,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121211.0, "order_id": "x-XEKWYICXSSIUT605b4265b56e40582", "exchange_order_id": "35674437", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:00:11,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4265b56e40582. +2023-09-19 11:00:11,478 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b429a2fec50582 for 80.00000000 SEI-USDT. +2023-09-19 11:00:11,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121211.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b429a2fec50582", "creation_timestamp": 1695121211.0, "exchange_order_id": "35674596", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:01:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b429a2fec50582. [clock=2023-09-19 11:01:06+00:00] +2023-09-19 11:01:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247022123495394619263518354 amount: 80. +2023-09-19 11:01:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251413294527337570269384100 amount: 80. +2023-09-19 11:01:06,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121266.0, "order_id": "x-XEKWYICXBSIUT605b429a2fec50582", "exchange_order_id": "35674596", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:01:06,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b429a2fec50582. +2023-09-19 11:01:06,590 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b42ce9a1970582 for 80.00000000 SEI-USDT. +2023-09-19 11:01:06,603 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b42ce9a1970582", "creation_timestamp": 1695121266.0, "exchange_order_id": "35674853", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:01:06,604 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b42ce9a58c0582 for 80.00000000 SEI-USDT. +2023-09-19 11:01:06,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121266.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b42ce9a58c0582", "creation_timestamp": 1695121266.0, "exchange_order_id": "35674852", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:02:01,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b42ce9a1970582. [clock=2023-09-19 11:02:01+00:00] +2023-09-19 11:02:01,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b42ce9a58c0582. [clock=2023-09-19 11:02:01+00:00] +2023-09-19 11:02:01,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247022497450590346754373540 amount: 80. +2023-09-19 11:02:01,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:02:01,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121321.0, "order_id": "x-XEKWYICXBSIUT605b42ce9a1970582", "exchange_order_id": "35674853", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:02:01,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b42ce9a1970582. +2023-09-19 11:02:01,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121321.0, "order_id": "x-XEKWYICXSSIUT605b42ce9a58c0582", "exchange_order_id": "35674852", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:02:01,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b42ce9a58c0582. +2023-09-19 11:02:01,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4303231940582 for 80.00000000 SEI-USDT. +2023-09-19 11:02:01,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b4303231940582", "creation_timestamp": 1695121321.0, "exchange_order_id": "35674931", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:02:56,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4303231940582. [clock=2023-09-19 11:02:56+00:00] +2023-09-19 11:02:56,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246728123787394882024821217 amount: 80. +2023-09-19 11:02:56,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251257595535057923991524593 amount: 80. +2023-09-19 11:02:56,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121376.0, "order_id": "x-XEKWYICXBSIUT605b4303231940582", "exchange_order_id": "35674931", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:02:56,265 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4303231940582. +2023-09-19 11:02:56,413 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b433783ab50582 for 80.00000000 SEI-USDT. +2023-09-19 11:02:56,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b433783ab50582", "creation_timestamp": 1695121376.0, "exchange_order_id": "35675268", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:02:56,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4337838990582 for 80.00000000 SEI-USDT. +2023-09-19 11:02:56,441 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b4337838990582", "creation_timestamp": 1695121376.0, "exchange_order_id": "35675267", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:03:51,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4337838990582. [clock=2023-09-19 11:03:51+00:00] +2023-09-19 11:03:51,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b433783ab50582. [clock=2023-09-19 11:03:51+00:00] +2023-09-19 11:03:51,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247011244730842941564882901 amount: 80. +2023-09-19 11:03:51,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:03:51,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121431.0, "order_id": "x-XEKWYICXBSIUT605b4337838990582", "exchange_order_id": "35675267", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:03:51,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4337838990582. +2023-09-19 11:03:51,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121431.0, "order_id": "x-XEKWYICXSSIUT605b433783ab50582", "exchange_order_id": "35675268", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:03:51,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b433783ab50582. +2023-09-19 11:03:51,460 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b436bfdc650582 for 80.00000000 SEI-USDT. +2023-09-19 11:03:51,473 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121431.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b436bfdc650582", "creation_timestamp": 1695121431.0, "exchange_order_id": "35675462", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:04:46,040 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b436bfdc650582. [clock=2023-09-19 11:04:46+00:00] +2023-09-19 11:04:46,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249507143685614732926245619 amount: 80. +2023-09-19 11:04:46,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254475139575601019115813129 amount: 80. +2023-09-19 11:04:46,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121486.0, "order_id": "x-XEKWYICXBSIUT605b436bfdc650582", "exchange_order_id": "35675462", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:04:46,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b436bfdc650582. +2023-09-19 11:04:46,450 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b43a0723a70582 for 80.00000000 SEI-USDT. +2023-09-19 11:04:46,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b43a0723a70582", "creation_timestamp": 1695121486.0, "exchange_order_id": "35675717", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:04:46,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b43a0726970582 for 80.00000000 SEI-USDT. +2023-09-19 11:04:46,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121486.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b43a0726970582", "creation_timestamp": 1695121486.0, "exchange_order_id": "35675718", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:05:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b43a0723a70582. [clock=2023-09-19 11:05:41+00:00] +2023-09-19 11:05:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b43a0726970582. [clock=2023-09-19 11:05:41+00:00] +2023-09-19 11:05:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248616969461307551257810008 amount: 80. +2023-09-19 11:05:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:05:41,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121541.0, "order_id": "x-XEKWYICXBSIUT605b43a0723a70582", "exchange_order_id": "35675717", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:05:41,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b43a0723a70582. +2023-09-19 11:05:41,406 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b43d4dc9560582 for 80.00000000 SEI-USDT. +2023-09-19 11:05:41,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121541.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b43d4dc9560582", "creation_timestamp": 1695121541.0, "exchange_order_id": "35675896", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:05:41,430 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121541.0, "order_id": "x-XEKWYICXSSIUT605b43a0726970582", "exchange_order_id": "35675718", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:05:41,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b43a0726970582. +2023-09-19 11:06:36,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b43d4dc9560582. [clock=2023-09-19 11:06:36+00:00] +2023-09-19 11:06:36,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247151807523789559204234766 amount: 80. +2023-09-19 11:06:36,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252130221115745749896598078 amount: 80. +2023-09-19 11:06:36,265 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121596.0, "order_id": "x-XEKWYICXBSIUT605b43d4dc9560582", "exchange_order_id": "35675896", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:06:36,266 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b43d4dc9560582. +2023-09-19 11:06:36,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b44095ba8f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:06:36,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b44095ba8f0582", "creation_timestamp": 1695121596.0, "exchange_order_id": "35676227", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:06:36,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b44095be450582 for 80.00000000 SEI-USDT. +2023-09-19 11:06:36,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b44095be450582", "creation_timestamp": 1695121596.0, "exchange_order_id": "35676226", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:07:31,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b44095ba8f0582. [clock=2023-09-19 11:07:31+00:00] +2023-09-19 11:07:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b44095be450582. [clock=2023-09-19 11:07:31+00:00] +2023-09-19 11:07:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246811156247536907186587415 amount: 80. +2023-09-19 11:07:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:07:31,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121651.0, "order_id": "x-XEKWYICXBSIUT605b44095ba8f0582", "exchange_order_id": "35676227", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:07:31,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b44095ba8f0582. +2023-09-19 11:07:31,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121651.0, "order_id": "x-XEKWYICXSSIUT605b44095be450582", "exchange_order_id": "35676226", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:07:31,497 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b44095be450582. +2023-09-19 11:07:31,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b443dc45ad0582 for 80.00000000 SEI-USDT. +2023-09-19 11:07:31,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b443dc45ad0582", "creation_timestamp": 1695121651.0, "exchange_order_id": "35676364", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:08:06,021 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b443dc45ad0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:08:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:08:05+00:00] +2023-09-19 11:08:06,055 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121685.0, "order_id": "x-XEKWYICXBSIUT605b443dc45ad0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5004511", "exchange_order_id": "35676364", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:08:06,079 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121686.0, "order_id": "x-XEKWYICXBSIUT605b443dc45ad0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35676364", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:08:06,079 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b443dc45ad0582 completely filled. +2023-09-19 11:08:26,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240135467241082888653814562 amount: 80. +2023-09-19 11:08:26,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246835710674494820786278755 amount: 80. +2023-09-19 11:08:26,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b447240edd0582 for 80.00000000 SEI-USDT. +2023-09-19 11:08:26,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b447240edd0582", "creation_timestamp": 1695121706.0, "exchange_order_id": "35676893", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:08:26,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b44724143e0582 for 80.00000000 SEI-USDT. +2023-09-19 11:08:26,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b44724143e0582", "creation_timestamp": 1695121706.0, "exchange_order_id": "35676894", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:09:21,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b447240edd0582. [clock=2023-09-19 11:09:21+00:00] +2023-09-19 11:09:21,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b44724143e0582. [clock=2023-09-19 11:09:21+00:00] +2023-09-19 11:09:21,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238605009055519202582944531 amount: 80. +2023-09-19 11:09:21,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246039028332120659430172870 amount: 80. +2023-09-19 11:09:21,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121761.0, "order_id": "x-XEKWYICXBSIUT605b447240edd0582", "exchange_order_id": "35676893", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:09:21,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b447240edd0582. +2023-09-19 11:09:21,493 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121761.0, "order_id": "x-XEKWYICXSSIUT605b44724143e0582", "exchange_order_id": "35676894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:09:21,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b44724143e0582. +2023-09-19 11:09:21,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b44a6b516a0582 for 80.00000000 SEI-USDT. +2023-09-19 11:09:21,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b44a6b516a0582", "creation_timestamp": 1695121761.0, "exchange_order_id": "35677161", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:09:21,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b44a6b53960582 for 80.00000000 SEI-USDT. +2023-09-19 11:09:21,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121761.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b44a6b53960582", "creation_timestamp": 1695121761.0, "exchange_order_id": "35677162", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:10:16,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b44a6b516a0582. [clock=2023-09-19 11:10:16+00:00] +2023-09-19 11:10:16,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b44a6b53960582. [clock=2023-09-19 11:10:16+00:00] +2023-09-19 11:10:16,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242182930667277853306532158 amount: 80. +2023-09-19 11:10:16,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249087387066671577441188474 amount: 80. +2023-09-19 11:10:16,247 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121816.0, "order_id": "x-XEKWYICXBSIUT605b44a6b516a0582", "exchange_order_id": "35677161", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:10:16,247 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b44a6b516a0582. +2023-09-19 11:10:16,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b44db268d50582 for 80.00000000 SEI-USDT. +2023-09-19 11:10:16,467 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b44db268d50582", "creation_timestamp": 1695121816.0, "exchange_order_id": "35677577", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:10:16,470 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b44db26c850582 for 80.00000000 SEI-USDT. +2023-09-19 11:10:16,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121816.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b44db26c850582", "creation_timestamp": 1695121816.0, "exchange_order_id": "35677578", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:10:16,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121816.0, "order_id": "x-XEKWYICXSSIUT605b44a6b53960582", "exchange_order_id": "35677162", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:10:16,493 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b44a6b53960582. +2023-09-19 11:11:11,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b44db268d50582. [clock=2023-09-19 11:11:11+00:00] +2023-09-19 11:11:11,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b44db26c850582. [clock=2023-09-19 11:11:11+00:00] +2023-09-19 11:11:11,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242222085486513573188512675 amount: 80. +2023-09-19 11:11:11,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249570073467813121238853969 amount: 80. +2023-09-19 11:11:11,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121871.0, "order_id": "x-XEKWYICXBSIUT605b44db268d50582", "exchange_order_id": "35677577", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:11:11,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b44db268d50582. +2023-09-19 11:11:11,540 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121871.0, "order_id": "x-XEKWYICXSSIUT605b44db26c850582", "exchange_order_id": "35677578", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:11:11,540 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b44db26c850582. +2023-09-19 11:11:11,647 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b450f9c1940582 for 80.00000000 SEI-USDT. +2023-09-19 11:11:11,673 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b450f9c1940582", "creation_timestamp": 1695121871.0, "exchange_order_id": "35677669", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:11:11,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b450f9bb4b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:11:11,695 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121871.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b450f9bb4b0582", "creation_timestamp": 1695121871.0, "exchange_order_id": "35677670", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:12:06,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b450f9bb4b0582. [clock=2023-09-19 11:12:06+00:00] +2023-09-19 11:12:06,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b450f9c1940582. [clock=2023-09-19 11:12:06+00:00] +2023-09-19 11:12:06,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241292463947565714898243456 amount: 80. +2023-09-19 11:12:06,136 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247870726833187883650247435 amount: 80. +2023-09-19 11:12:06,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121926.0, "order_id": "x-XEKWYICXBSIUT605b450f9bb4b0582", "exchange_order_id": "35677670", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:12:06,581 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b450f9bb4b0582. +2023-09-19 11:12:06,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121926.0, "order_id": "x-XEKWYICXSSIUT605b450f9c1940582", "exchange_order_id": "35677669", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:12:06,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b450f9c1940582. +2023-09-19 11:12:06,695 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4544226aa0582 for 80.00000000 SEI-USDT. +2023-09-19 11:12:06,709 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b4544226aa0582", "creation_timestamp": 1695121926.0, "exchange_order_id": "35677823", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:12:06,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4544222fe0582 for 80.00000000 SEI-USDT. +2023-09-19 11:12:06,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b4544222fe0582", "creation_timestamp": 1695121926.0, "exchange_order_id": "35677824", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:12:51,542 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 11:13:01,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4544222fe0582. [clock=2023-09-19 11:13:01+00:00] +2023-09-19 11:13:01,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4544226aa0582. [clock=2023-09-19 11:13:01+00:00] +2023-09-19 11:13:01,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240835345070365209216699918 amount: 80. +2023-09-19 11:13:01,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248525850239286366072983142 amount: 80. +2023-09-19 11:13:01,274 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121981.0, "order_id": "x-XEKWYICXBSIUT605b4544222fe0582", "exchange_order_id": "35677824", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:13:01,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4544222fe0582. +2023-09-19 11:13:01,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121981.0, "order_id": "x-XEKWYICXSSIUT605b4544226aa0582", "exchange_order_id": "35677823", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:13:01,636 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4544226aa0582. +2023-09-19 11:13:01,638 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b45787aa210582 for 80.00000000 SEI-USDT. +2023-09-19 11:13:01,652 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b45787aa210582", "creation_timestamp": 1695121981.0, "exchange_order_id": "35677970", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:13:01,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b45787ac750582 for 80.00000000 SEI-USDT. +2023-09-19 11:13:01,663 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695121981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b45787ac750582", "creation_timestamp": 1695121981.0, "exchange_order_id": "35677969", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:13:56,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b45787aa210582. [clock=2023-09-19 11:13:56+00:00] +2023-09-19 11:13:56,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b45787ac750582. [clock=2023-09-19 11:13:56+00:00] +2023-09-19 11:13:56,392 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241081452728853856572443853 amount: 80. +2023-09-19 11:13:56,393 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248174375519341005272679704 amount: 80. +2023-09-19 11:13:57,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122036.0, "order_id": "x-XEKWYICXBSIUT605b45787aa210582", "exchange_order_id": "35677970", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:13:57,202 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b45787aa210582. +2023-09-19 11:13:59,056 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122038.0, "order_id": "x-XEKWYICXSSIUT605b45787ac750582", "exchange_order_id": "35677969", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:13:59,071 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b45787ac750582. +2023-09-19 11:13:59,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b45ad4877e0582 for 80.00000000 SEI-USDT. +2023-09-19 11:13:59,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122039.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b45ad4877e0582", "creation_timestamp": 1695122036.0, "exchange_order_id": "35678440", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:13:59,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b45ad48b4f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:13:59,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122039.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b45ad48b4f0582", "creation_timestamp": 1695122036.0, "exchange_order_id": "35678441", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:14:51,468 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b45ad4877e0582. [clock=2023-09-19 11:14:51+00:00] +2023-09-19 11:14:51,482 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b45ad48b4f0582. [clock=2023-09-19 11:14:51+00:00] +2023-09-19 11:14:51,551 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241730686448710502569883847 amount: 80. +2023-09-19 11:14:51,552 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247246787282127613783925468 amount: 80. +2023-09-19 11:14:52,478 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122092.0, "order_id": "x-XEKWYICXBSIUT605b45ad4877e0582", "exchange_order_id": "35678440", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:14:52,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b45ad4877e0582. +2023-09-19 11:14:53,744 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122093.0, "order_id": "x-XEKWYICXSSIUT605b45ad48b4f0582", "exchange_order_id": "35678441", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:14:53,745 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b45ad48b4f0582. +2023-09-19 11:14:54,020 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b45e1e2ee50582 for 80.00000000 SEI-USDT. +2023-09-19 11:14:54,057 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122093.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b45e1e2ee50582", "creation_timestamp": 1695122091.0, "exchange_order_id": "35678610", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:14:54,067 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b45e1e33160582 for 80.00000000 SEI-USDT. +2023-09-19 11:14:54,086 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122093.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b45e1e33160582", "creation_timestamp": 1695122091.0, "exchange_order_id": "35678609", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:15:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b45e1e2ee50582. [clock=2023-09-19 11:15:46+00:00] +2023-09-19 11:15:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b45e1e33160582. [clock=2023-09-19 11:15:46+00:00] +2023-09-19 11:15:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239457664392882354215880290 amount: 80. +2023-09-19 11:15:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245719515548680169372621272 amount: 80. +2023-09-19 11:15:46,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122146.0, "order_id": "x-XEKWYICXBSIUT605b45e1e2ee50582", "exchange_order_id": "35678610", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:15:46,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b45e1e2ee50582. +2023-09-19 11:15:46,382 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4615d5f500582 for 80.00000000 SEI-USDT. +2023-09-19 11:15:46,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b4615d5f500582", "creation_timestamp": 1695122146.0, "exchange_order_id": "35678877", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:15:46,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122146.0, "order_id": "x-XEKWYICXSSIUT605b45e1e33160582", "exchange_order_id": "35678609", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:15:46,410 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b45e1e33160582. +2023-09-19 11:15:46,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4615d62920582 for 80.00000000 SEI-USDT. +2023-09-19 11:15:46,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122146.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b4615d62920582", "creation_timestamp": 1695122146.0, "exchange_order_id": "35678876", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:15:51,575 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b4615d62920582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:15:51,576 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:15:51+00:00] +2023-09-19 11:15:51,597 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122151.0, "order_id": "x-XEKWYICXSSIUT605b4615d62920582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996000"}]}, "exchange_trade_id": "5004724", "exchange_order_id": "35678876", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:15:51,610 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122151.0, "order_id": "x-XEKWYICXSSIUT605b4615d62920582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35678876", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:15:51,610 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b4615d62920582 completely filled. +2023-09-19 11:16:41,070 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4615d5f500582. [clock=2023-09-19 11:16:41+00:00] +2023-09-19 11:16:41,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244005991617910778748179661 amount: 80. +2023-09-19 11:16:41,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251728996841890671975466203 amount: 80. +2023-09-19 11:16:41,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122201.0, "order_id": "x-XEKWYICXBSIUT605b4615d5f500582", "exchange_order_id": "35678877", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:16:41,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4615d5f500582. +2023-09-19 11:16:41,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b464a5a3180582 for 80.00000000 SEI-USDT. +2023-09-19 11:16:41,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b464a5a3180582", "creation_timestamp": 1695122201.0, "exchange_order_id": "35679255", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:16:41,506 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b464a5a6400582 for 80.00000000 SEI-USDT. +2023-09-19 11:16:41,526 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122201.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b464a5a6400582", "creation_timestamp": 1695122201.0, "exchange_order_id": "35679256", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:17:36,343 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b464a5a3180582. [clock=2023-09-19 11:17:36+00:00] +2023-09-19 11:17:36,344 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b464a5a6400582. [clock=2023-09-19 11:17:36+00:00] +2023-09-19 11:17:36,440 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243721531088179598997591559 amount: 80. +2023-09-19 11:17:36,450 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:17:37,123 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122256.0, "order_id": "x-XEKWYICXBSIUT605b464a5a3180582", "exchange_order_id": "35679255", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:17:37,123 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b464a5a3180582. +2023-09-19 11:17:38,859 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122258.0, "order_id": "x-XEKWYICXSSIUT605b464a5a6400582", "exchange_order_id": "35679256", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:17:38,859 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b464a5a6400582. +2023-09-19 11:17:39,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b467f252800582 for 80.00000000 SEI-USDT. +2023-09-19 11:17:39,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122258.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b467f252800582", "creation_timestamp": 1695122256.0, "exchange_order_id": "35679489", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:18:31,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b467f252800582. [clock=2023-09-19 11:18:31+00:00] +2023-09-19 11:18:31,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245332049407744825895661459 amount: 80. +2023-09-19 11:18:31,176 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250870521074104298994794877 amount: 80. +2023-09-19 11:18:31,680 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122311.0, "order_id": "x-XEKWYICXBSIUT605b467f252800582", "exchange_order_id": "35679489", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:18:31,680 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b467f252800582. +2023-09-19 11:18:32,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b46b355ff60582 for 80.00000000 SEI-USDT. +2023-09-19 11:18:32,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122312.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b46b355ff60582", "creation_timestamp": 1695122311.0, "exchange_order_id": "35679735", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:18:32,784 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b46b3564790582 for 80.00000000 SEI-USDT. +2023-09-19 11:18:32,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122312.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b46b3564790582", "creation_timestamp": 1695122311.0, "exchange_order_id": "35679736", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:19:26,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b46b355ff60582. [clock=2023-09-19 11:19:26+00:00] +2023-09-19 11:19:26,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b46b3564790582. [clock=2023-09-19 11:19:26+00:00] +2023-09-19 11:19:26,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246594133144784055661876162 amount: 80. +2023-09-19 11:19:26,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:19:26,695 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122366.0, "order_id": "x-XEKWYICXBSIUT605b46b355ff60582", "exchange_order_id": "35679735", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:19:26,696 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b46b355ff60582. +2023-09-19 11:19:27,607 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122367.0, "order_id": "x-XEKWYICXSSIUT605b46b3564790582", "exchange_order_id": "35679736", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:19:27,608 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b46b3564790582. +2023-09-19 11:19:28,156 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b46e7c15840582 for 80.00000000 SEI-USDT. +2023-09-19 11:19:28,203 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b46e7c15840582", "creation_timestamp": 1695122366.0, "exchange_order_id": "35679894", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:19:45,143 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b46e7c15840582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:19:45,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:19:44+00:00] +2023-09-19 11:19:45,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122384.0, "order_id": "x-XEKWYICXBSIUT605b46e7c15840582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5004830", "exchange_order_id": "35679894", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:19:45,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122385.0, "order_id": "x-XEKWYICXBSIUT605b46e7c15840582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35679894", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:19:45,482 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b46e7c15840582 completely filled. +2023-09-19 11:20:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241668004164239531245498590 amount: 80. +2023-09-19 11:20:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248957380542889898074365576 amount: 80. +2023-09-19 11:20:21,219 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b471c17f960582 for 80.00000000 SEI-USDT. +2023-09-19 11:20:21,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b471c17f960582", "creation_timestamp": 1695122421.0, "exchange_order_id": "35680311", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:20:21,387 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b471c181b90582 for 80.00000000 SEI-USDT. +2023-09-19 11:20:21,405 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122421.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b471c181b90582", "creation_timestamp": 1695122421.0, "exchange_order_id": "35680313", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:21:16,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b471c17f960582. [clock=2023-09-19 11:21:16+00:00] +2023-09-19 11:21:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b471c181b90582. [clock=2023-09-19 11:21:16+00:00] +2023-09-19 11:21:16,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241732723208851277435530794 amount: 80. +2023-09-19 11:21:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249381130931180659960679737 amount: 80. +2023-09-19 11:21:16,289 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122476.0, "order_id": "x-XEKWYICXBSIUT605b471c17f960582", "exchange_order_id": "35680311", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:21:16,289 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b471c17f960582. +2023-09-19 11:21:16,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b47508bf6d0582 for 80.00000000 SEI-USDT. +2023-09-19 11:21:16,490 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b47508bf6d0582", "creation_timestamp": 1695122476.0, "exchange_order_id": "35680569", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:21:16,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b47508bc9c0582 for 80.00000000 SEI-USDT. +2023-09-19 11:21:16,613 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122476.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b47508bc9c0582", "creation_timestamp": 1695122476.0, "exchange_order_id": "35680570", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:21:16,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122476.0, "order_id": "x-XEKWYICXSSIUT605b471c181b90582", "exchange_order_id": "35680313", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:21:16,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b471c181b90582. +2023-09-19 11:22:11,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b47508bc9c0582. [clock=2023-09-19 11:22:11+00:00] +2023-09-19 11:22:11,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b47508bf6d0582. [clock=2023-09-19 11:22:11+00:00] +2023-09-19 11:22:11,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244199573906863393256222815 amount: 80. +2023-09-19 11:22:11,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253247050798144701890315079 amount: 80. +2023-09-19 11:22:11,778 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122531.0, "order_id": "x-XEKWYICXBSIUT605b47508bc9c0582", "exchange_order_id": "35680570", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:22:11,778 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b47508bc9c0582. +2023-09-19 11:22:12,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122532.0, "order_id": "x-XEKWYICXSSIUT605b47508bf6d0582", "exchange_order_id": "35680569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:22:12,669 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b47508bf6d0582. +2023-09-19 11:22:13,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4785139810582 for 80.00000000 SEI-USDT. +2023-09-19 11:22:13,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122532.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4785139810582", "creation_timestamp": 1695122531.0, "exchange_order_id": "35681028", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:22:13,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b478516a050582 for 80.00000000 SEI-USDT. +2023-09-19 11:22:13,350 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122532.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b478516a050582", "creation_timestamp": 1695122531.0, "exchange_order_id": "35681027", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:23:06,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4785139810582. [clock=2023-09-19 11:23:06+00:00] +2023-09-19 11:23:06,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b478516a050582. [clock=2023-09-19 11:23:06+00:00] +2023-09-19 11:23:06,226 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243600458229743290079794182 amount: 80. +2023-09-19 11:23:06,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250628317934970939556515853 amount: 80. +2023-09-19 11:23:07,102 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122586.0, "order_id": "x-XEKWYICXBSIUT605b4785139810582", "exchange_order_id": "35681028", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:23:07,103 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4785139810582. +2023-09-19 11:23:08,714 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b47b9a810f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:23:08,780 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122588.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b47b9a810f0582", "creation_timestamp": 1695122586.0, "exchange_order_id": "35681227", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:23:08,780 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b47b9a7e6e0582 for 80.00000000 SEI-USDT. +2023-09-19 11:23:08,834 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122588.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b47b9a7e6e0582", "creation_timestamp": 1695122586.0, "exchange_order_id": "35681228", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:23:08,886 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122588.0, "order_id": "x-XEKWYICXSSIUT605b478516a050582", "exchange_order_id": "35681027", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:23:08,886 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b478516a050582. +2023-09-19 11:24:01,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b47b9a7e6e0582. [clock=2023-09-19 11:24:01+00:00] +2023-09-19 11:24:01,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b47b9a810f0582. [clock=2023-09-19 11:24:01+00:00] +2023-09-19 11:24:01,272 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242974881826241773823777090 amount: 80. +2023-09-19 11:24:01,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251120950299935464760345606 amount: 80. +2023-09-19 11:24:02,173 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122641.0, "order_id": "x-XEKWYICXBSIUT605b47b9a7e6e0582", "exchange_order_id": "35681228", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:24:02,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b47b9a7e6e0582. +2023-09-19 11:24:04,012 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122643.0, "order_id": "x-XEKWYICXSSIUT605b47b9a810f0582", "exchange_order_id": "35681227", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:24:04,012 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b47b9a810f0582. +2023-09-19 11:24:04,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b47ee2444b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:24:04,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b47ee2444b0582", "creation_timestamp": 1695122641.0, "exchange_order_id": "35681311", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:24:04,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b47ee248b10582 for 80.00000000 SEI-USDT. +2023-09-19 11:24:04,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122643.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b47ee248b10582", "creation_timestamp": 1695122641.0, "exchange_order_id": "35681312", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:24:56,118 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b47ee2444b0582. [clock=2023-09-19 11:24:56+00:00] +2023-09-19 11:24:56,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b47ee248b10582. [clock=2023-09-19 11:24:56+00:00] +2023-09-19 11:24:56,164 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242427387855794866933449660 amount: 80. +2023-09-19 11:24:56,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248755819858383262305509936 amount: 80. +2023-09-19 11:24:56,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122696.0, "order_id": "x-XEKWYICXBSIUT605b47ee2444b0582", "exchange_order_id": "35681311", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:24:56,652 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b47ee2444b0582. +2023-09-19 11:24:57,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122697.0, "order_id": "x-XEKWYICXSSIUT605b47ee248b10582", "exchange_order_id": "35681312", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:24:57,388 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b47ee248b10582. +2023-09-19 11:24:57,678 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b48227d67f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:24:57,706 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b48227d67f0582", "creation_timestamp": 1695122696.0, "exchange_order_id": "35681589", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:24:57,707 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b48227d9210582 for 80.00000000 SEI-USDT. +2023-09-19 11:24:57,727 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122697.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b48227d9210582", "creation_timestamp": 1695122696.0, "exchange_order_id": "35681590", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:25:51,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b48227d67f0582. [clock=2023-09-19 11:25:51+00:00] +2023-09-19 11:25:51,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b48227d9210582. [clock=2023-09-19 11:25:51+00:00] +2023-09-19 11:25:51,111 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242501645039047684848538283 amount: 80. +2023-09-19 11:25:51,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248535043035105737077952707 amount: 80. +2023-09-19 11:25:51,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122751.0, "order_id": "x-XEKWYICXBSIUT605b48227d67f0582", "exchange_order_id": "35681589", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:25:51,326 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b48227d67f0582. +2023-09-19 11:25:51,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4856e48ff0582 for 80.00000000 SEI-USDT. +2023-09-19 11:25:51,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b4856e48ff0582", "creation_timestamp": 1695122751.0, "exchange_order_id": "35681690", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:25:51,571 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122751.0, "order_id": "x-XEKWYICXSSIUT605b48227d9210582", "exchange_order_id": "35681590", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:25:51,571 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b48227d9210582. +2023-09-19 11:25:51,572 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4856e45c90582 for 80.00000000 SEI-USDT. +2023-09-19 11:25:51,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122751.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b4856e45c90582", "creation_timestamp": 1695122751.0, "exchange_order_id": "35681691", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:26:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4856e45c90582. [clock=2023-09-19 11:26:46+00:00] +2023-09-19 11:26:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4856e48ff0582. [clock=2023-09-19 11:26:46+00:00] +2023-09-19 11:26:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241590587035855998422746857 amount: 80. +2023-09-19 11:26:46,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246281034962720062432373901 amount: 80. +2023-09-19 11:26:46,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122806.0, "order_id": "x-XEKWYICXBSIUT605b4856e45c90582", "exchange_order_id": "35681691", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:26:46,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4856e45c90582. +2023-09-19 11:26:46,742 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122806.0, "order_id": "x-XEKWYICXSSIUT605b4856e48ff0582", "exchange_order_id": "35681690", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:26:46,743 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4856e48ff0582. +2023-09-19 11:26:46,744 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b488b42e130582 for 80.00000000 SEI-USDT. +2023-09-19 11:26:46,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b488b42e130582", "creation_timestamp": 1695122806.0, "exchange_order_id": "35681873", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:26:46,756 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b488b42b4f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:26:46,767 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122806.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b488b42b4f0582", "creation_timestamp": 1695122806.0, "exchange_order_id": "35681874", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:27:41,437 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b488b42b4f0582. [clock=2023-09-19 11:27:41+00:00] +2023-09-19 11:27:41,438 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b488b42e130582. [clock=2023-09-19 11:27:41+00:00] +2023-09-19 11:27:41,480 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242903786675061089340669900 amount: 80. +2023-09-19 11:27:41,481 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246554239086268321757337494 amount: 80. +2023-09-19 11:27:42,032 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122861.0, "order_id": "x-XEKWYICXBSIUT605b488b42b4f0582", "exchange_order_id": "35681874", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:27:42,032 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b488b42b4f0582. +2023-09-19 11:27:44,076 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b48c025e520582 for 80.00000000 SEI-USDT. +2023-09-19 11:27:44,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122863.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b48c025e520582", "creation_timestamp": 1695122861.0, "exchange_order_id": "35682014", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:27:44,155 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b48c0260f20582 for 80.00000000 SEI-USDT. +2023-09-19 11:27:44,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122863.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b48c0260f20582", "creation_timestamp": 1695122861.0, "exchange_order_id": "35682013", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:27:44,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122863.0, "order_id": "x-XEKWYICXSSIUT605b488b42e130582", "exchange_order_id": "35681873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:27:44,254 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b488b42e130582. +2023-09-19 11:28:36,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b48c025e520582. [clock=2023-09-19 11:28:36+00:00] +2023-09-19 11:28:36,211 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b48c0260f20582. [clock=2023-09-19 11:28:36+00:00] +2023-09-19 11:28:36,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244154115670212991031730893 amount: 80. +2023-09-19 11:28:36,284 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247858883718048576455188333 amount: 80. +2023-09-19 11:28:37,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122916.0, "order_id": "x-XEKWYICXBSIUT605b48c025e520582", "exchange_order_id": "35682014", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:28:37,505 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b48c025e520582. +2023-09-19 11:28:39,525 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122919.0, "order_id": "x-XEKWYICXSSIUT605b48c0260f20582", "exchange_order_id": "35682013", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:28:39,525 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b48c0260f20582. +2023-09-19 11:28:40,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b48f4696c30582 for 80.00000000 SEI-USDT. +2023-09-19 11:28:40,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122919.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b48f4696c30582", "creation_timestamp": 1695122916.0, "exchange_order_id": "35682239", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:28:40,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b48f469af30582 for 80.00000000 SEI-USDT. +2023-09-19 11:28:40,182 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122919.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b48f469af30582", "creation_timestamp": 1695122916.0, "exchange_order_id": "35682240", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:29:31,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b48f4696c30582. [clock=2023-09-19 11:29:31+00:00] +2023-09-19 11:29:31,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b48f469af30582. [clock=2023-09-19 11:29:31+00:00] +2023-09-19 11:29:31,279 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244341948235608998352284016 amount: 80. +2023-09-19 11:29:31,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247222916321836189289769446 amount: 80. +2023-09-19 11:29:32,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122971.0, "order_id": "x-XEKWYICXBSIUT605b48f4696c30582", "exchange_order_id": "35682239", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:29:32,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b48f4696c30582. +2023-09-19 11:29:33,888 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122973.0, "order_id": "x-XEKWYICXSSIUT605b48f469af30582", "exchange_order_id": "35682240", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:29:33,888 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b48f469af30582. +2023-09-19 11:29:34,194 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4928de3d50582 for 80.00000000 SEI-USDT. +2023-09-19 11:29:34,229 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122973.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4928de3d50582", "creation_timestamp": 1695122971.0, "exchange_order_id": "35682383", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:29:34,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4928de6670582 for 80.00000000 SEI-USDT. +2023-09-19 11:29:34,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695122973.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b4928de6670582", "creation_timestamp": 1695122971.0, "exchange_order_id": "35682382", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:30:26,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4928de3d50582. [clock=2023-09-19 11:30:26+00:00] +2023-09-19 11:30:26,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4928de6670582. [clock=2023-09-19 11:30:26+00:00] +2023-09-19 11:30:26,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244488339500826630078760574 amount: 80. +2023-09-19 11:30:26,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246728779717023691893678680 amount: 80. +2023-09-19 11:30:26,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123026.0, "order_id": "x-XEKWYICXBSIUT605b4928de3d50582", "exchange_order_id": "35682383", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:30:26,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4928de3d50582. +2023-09-19 11:30:26,492 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123026.0, "order_id": "x-XEKWYICXSSIUT605b4928de6670582", "exchange_order_id": "35682382", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:30:26,492 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4928de6670582. +2023-09-19 11:30:26,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b495d1e5a90582 for 80.00000000 SEI-USDT. +2023-09-19 11:30:26,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b495d1e5a90582", "creation_timestamp": 1695123026.0, "exchange_order_id": "35682497", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:30:26,568 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b495d1e8020582 for 80.00000000 SEI-USDT. +2023-09-19 11:30:26,580 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123026.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b495d1e8020582", "creation_timestamp": 1695123026.0, "exchange_order_id": "35682498", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:31:02,501 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b495d1e8020582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:31:02,503 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:31:02+00:00] +2023-09-19 11:31:02,535 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123062.0, "order_id": "x-XEKWYICXSSIUT605b495d1e8020582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "5005054", "exchange_order_id": "35682498", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:31:02,617 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123062.0, "order_id": "x-XEKWYICXSSIUT605b495d1e8020582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35682498", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:31:02,617 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b495d1e8020582 completely filled. +2023-09-19 11:31:21,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b495d1e5a90582. [clock=2023-09-19 11:31:21+00:00] +2023-09-19 11:31:21,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245896212209310114433199284 amount: 80. +2023-09-19 11:31:21,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248752068311758883893210455 amount: 80. +2023-09-19 11:31:21,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123081.0, "order_id": "x-XEKWYICXBSIUT605b495d1e5a90582", "exchange_order_id": "35682497", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:31:21,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b495d1e5a90582. +2023-09-19 11:31:21,515 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b49919957d0582 for 80.00000000 SEI-USDT. +2023-09-19 11:31:21,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b49919957d0582", "creation_timestamp": 1695123081.0, "exchange_order_id": "35682668", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:31:21,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b49919938a0582 for 80.00000000 SEI-USDT. +2023-09-19 11:31:21,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123081.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b49919938a0582", "creation_timestamp": 1695123081.0, "exchange_order_id": "35682669", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:32:16,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b49919938a0582. [clock=2023-09-19 11:32:16+00:00] +2023-09-19 11:32:16,341 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b49919957d0582. [clock=2023-09-19 11:32:16+00:00] +2023-09-19 11:32:16,426 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245906324222901647787402756 amount: 80. +2023-09-19 11:32:16,428 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:32:17,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123136.0, "order_id": "x-XEKWYICXBSIUT605b49919938a0582", "exchange_order_id": "35682669", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:32:17,170 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b49919938a0582. +2023-09-19 11:32:19,626 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123138.0, "order_id": "x-XEKWYICXSSIUT605b49919957d0582", "exchange_order_id": "35682668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:32:19,626 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b49919957d0582. +2023-09-19 11:32:19,918 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b49c65b8140582 for 80.00000000 SEI-USDT. +2023-09-19 11:32:19,944 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123138.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b49c65b8140582", "creation_timestamp": 1695123136.0, "exchange_order_id": "35683078", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:33:11,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b49c65b8140582. [clock=2023-09-19 11:33:11+00:00] +2023-09-19 11:33:11,310 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245927857343140310604068683 amount: 80. +2023-09-19 11:33:11,312 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249864972978938149067003613 amount: 80. +2023-09-19 11:33:11,887 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123191.0, "order_id": "x-XEKWYICXBSIUT605b49c65b8140582", "exchange_order_id": "35683078", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:33:11,888 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b49c65b8140582. +2023-09-19 11:33:12,439 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b49fab32e50582 for 80.00000000 SEI-USDT. +2023-09-19 11:33:12,469 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123192.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b49fab32e50582", "creation_timestamp": 1695123191.0, "exchange_order_id": "35683157", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:33:12,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b49fab2f1e0582 for 80.00000000 SEI-USDT. +2023-09-19 11:33:12,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123192.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b49fab2f1e0582", "creation_timestamp": 1695123191.0, "exchange_order_id": "35683158", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:34:06,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b49fab2f1e0582. [clock=2023-09-19 11:34:06+00:00] +2023-09-19 11:34:06,137 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b49fab32e50582. [clock=2023-09-19 11:34:06+00:00] +2023-09-19 11:34:06,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244623061028055804421983791 amount: 80. +2023-09-19 11:34:06,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:34:06,704 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123246.0, "order_id": "x-XEKWYICXBSIUT605b49fab2f1e0582", "exchange_order_id": "35683158", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:34:06,704 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b49fab2f1e0582. +2023-09-19 11:34:06,724 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123246.0, "order_id": "x-XEKWYICXSSIUT605b49fab32e50582", "exchange_order_id": "35683157", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:34:06,724 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b49fab32e50582. +2023-09-19 11:34:07,400 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4a2f086ce0582 for 80.00000000 SEI-USDT. +2023-09-19 11:34:07,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123247.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4a2f086ce0582", "creation_timestamp": 1695123246.0, "exchange_order_id": "35683319", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:35:01,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4a2f086ce0582. [clock=2023-09-19 11:35:01+00:00] +2023-09-19 11:35:01,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244515897851502177596492925 amount: 80. +2023-09-19 11:35:01,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249555393068451826004601267 amount: 80. +2023-09-19 11:35:01,832 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123301.0, "order_id": "x-XEKWYICXBSIUT605b4a2f086ce0582", "exchange_order_id": "35683319", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:35:01,832 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4a2f086ce0582. +2023-09-19 11:35:03,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4a63759fc0582 for 80.00000000 SEI-USDT. +2023-09-19 11:35:03,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123303.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4a63759fc0582", "creation_timestamp": 1695123301.0, "exchange_order_id": "35683554", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:35:03,938 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4a6375cb10582 for 80.00000000 SEI-USDT. +2023-09-19 11:35:03,992 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123303.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b4a6375cb10582", "creation_timestamp": 1695123301.0, "exchange_order_id": "35683556", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:35:56,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4a63759fc0582. [clock=2023-09-19 11:35:56+00:00] +2023-09-19 11:35:56,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4a6375cb10582. [clock=2023-09-19 11:35:56+00:00] +2023-09-19 11:35:56,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244516658335930895296435051 amount: 80. +2023-09-19 11:35:56,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:35:56,227 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123356.0, "order_id": "x-XEKWYICXBSIUT605b4a63759fc0582", "exchange_order_id": "35683554", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:35:56,228 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4a63759fc0582. +2023-09-19 11:35:56,466 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123356.0, "order_id": "x-XEKWYICXSSIUT605b4a6375cb10582", "exchange_order_id": "35683556", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:35:56,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4a6375cb10582. +2023-09-19 11:35:56,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4a97cc6c50582 for 80.00000000 SEI-USDT. +2023-09-19 11:35:56,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123356.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4a97cc6c50582", "creation_timestamp": 1695123356.0, "exchange_order_id": "35683745", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:36:51,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4a97cc6c50582. [clock=2023-09-19 11:36:51+00:00] +2023-09-19 11:36:51,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244624742494499790731312167 amount: 80. +2023-09-19 11:36:51,072 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248536814369322256443260716 amount: 80. +2023-09-19 11:36:51,249 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123411.0, "order_id": "x-XEKWYICXBSIUT605b4a97cc6c50582", "exchange_order_id": "35683745", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:36:51,249 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4a97cc6c50582. +2023-09-19 11:36:51,470 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4acc477680582 for 80.00000000 SEI-USDT. +2023-09-19 11:36:51,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4acc477680582", "creation_timestamp": 1695123411.0, "exchange_order_id": "35684072", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:36:51,484 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4acc47a8a0582 for 80.00000000 SEI-USDT. +2023-09-19 11:36:51,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b4acc47a8a0582", "creation_timestamp": 1695123411.0, "exchange_order_id": "35684073", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:37:46,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4acc477680582. [clock=2023-09-19 11:37:46+00:00] +2023-09-19 11:37:46,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4acc47a8a0582. [clock=2023-09-19 11:37:46+00:00] +2023-09-19 11:37:46,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244708541307721560060249612 amount: 80. +2023-09-19 11:37:46,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:37:46,867 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123466.0, "order_id": "x-XEKWYICXBSIUT605b4acc477680582", "exchange_order_id": "35684072", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:37:46,868 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4acc477680582. +2023-09-19 11:37:47,778 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123467.0, "order_id": "x-XEKWYICXSSIUT605b4acc47a8a0582", "exchange_order_id": "35684073", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:37:47,778 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4acc47a8a0582. +2023-09-19 11:37:47,779 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4b00c588b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:37:47,814 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123467.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4b00c588b0582", "creation_timestamp": 1695123466.0, "exchange_order_id": "35684147", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:38:41,222 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4b00c588b0582. [clock=2023-09-19 11:38:41+00:00] +2023-09-19 11:38:41,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243969437306303759152868614 amount: 80. +2023-09-19 11:38:41,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249428756862381351591420816 amount: 80. +2023-09-19 11:38:42,195 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123521.0, "order_id": "x-XEKWYICXBSIUT605b4b00c588b0582", "exchange_order_id": "35684147", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:38:42,195 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4b00c588b0582. +2023-09-19 11:38:44,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4b3563f760582 for 80.00000000 SEI-USDT. +2023-09-19 11:38:44,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123523.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b4b3563f760582", "creation_timestamp": 1695123521.0, "exchange_order_id": "35684371", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:38:44,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4b35643720582 for 80.00000000 SEI-USDT. +2023-09-19 11:38:44,257 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123523.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b4b35643720582", "creation_timestamp": 1695123521.0, "exchange_order_id": "35684374", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:39:36,899 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4b3563f760582. [clock=2023-09-19 11:39:36+00:00] +2023-09-19 11:39:36,908 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4b35643720582. [clock=2023-09-19 11:39:36+00:00] +2023-09-19 11:39:36,991 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243720359688316789192849547 amount: 80. +2023-09-19 11:39:36,992 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:39:38,142 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123576.0, "order_id": "x-XEKWYICXBSIUT605b4b3563f760582", "exchange_order_id": "35684371", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:39:38,143 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4b3563f760582. +2023-09-19 11:39:39,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123579.0, "order_id": "x-XEKWYICXSSIUT605b4b35643720582", "exchange_order_id": "35684374", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:39:39,211 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4b35643720582. +2023-09-19 11:39:39,505 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4b6a8331c0582 for 80.00000000 SEI-USDT. +2023-09-19 11:39:39,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123579.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b4b6a8331c0582", "creation_timestamp": 1695123576.0, "exchange_order_id": "35684586", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:40:31,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4b6a8331c0582. [clock=2023-09-19 11:40:31+00:00] +2023-09-19 11:40:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1245292776200770585825556721 amount: 80. +2023-09-19 11:40:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251441994024008508748675221 amount: 80. +2023-09-19 11:40:31,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123631.0, "order_id": "x-XEKWYICXBSIUT605b4b6a8331c0582", "exchange_order_id": "35684586", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:40:31,261 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4b6a8331c0582. +2023-09-19 11:40:31,725 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4b9e0ae4b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:40:31,747 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b4b9e0ae4b0582", "creation_timestamp": 1695123631.0, "exchange_order_id": "35684819", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:40:31,748 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4b9e0aae00582 for 80.00000000 SEI-USDT. +2023-09-19 11:40:31,768 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123631.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXBSIUT605b4b9e0aae00582", "creation_timestamp": 1695123631.0, "exchange_order_id": "35684818", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:41:26,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4b9e0aae00582. [clock=2023-09-19 11:41:26+00:00] +2023-09-19 11:41:26,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4b9e0ae4b0582. [clock=2023-09-19 11:41:26+00:00] +2023-09-19 11:41:26,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243716011461832154029665272 amount: 80. +2023-09-19 11:41:26,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:41:26,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123686.0, "order_id": "x-XEKWYICXBSIUT605b4b9e0aae00582", "exchange_order_id": "35684818", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:41:26,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4b9e0aae00582. +2023-09-19 11:41:26,497 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4bd28d2bc0582 for 80.00000000 SEI-USDT. +2023-09-19 11:41:26,527 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123686.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b4bd28d2bc0582", "creation_timestamp": 1695123686.0, "exchange_order_id": "35684935", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:41:26,646 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123686.0, "order_id": "x-XEKWYICXSSIUT605b4b9e0ae4b0582", "exchange_order_id": "35684819", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:41:26,647 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4b9e0ae4b0582. +2023-09-19 11:42:21,840 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4bd28d2bc0582. [clock=2023-09-19 11:42:21+00:00] +2023-09-19 11:42:21,918 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244474106271375475383009952 amount: 80. +2023-09-19 11:42:21,919 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249053953223962353939660126 amount: 80. +2023-09-19 11:42:22,890 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123742.0, "order_id": "x-XEKWYICXBSIUT605b4bd28d2bc0582", "exchange_order_id": "35684935", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:42:22,891 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4bd28d2bc0582. +2023-09-19 11:42:24,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4c07cc9500582 for 80.00000000 SEI-USDT. +2023-09-19 11:42:24,408 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123744.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b4c07cc9500582", "creation_timestamp": 1695123741.0, "exchange_order_id": "35685190", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:42:24,817 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4c07ccdc50582 for 80.00000000 SEI-USDT. +2023-09-19 11:42:24,913 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123744.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b4c07ccdc50582", "creation_timestamp": 1695123741.0, "exchange_order_id": "35685192", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:42:51,884 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 11:43:16,256 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4c07cc9500582. [clock=2023-09-19 11:43:16+00:00] +2023-09-19 11:43:16,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4c07ccdc50582. [clock=2023-09-19 11:43:16+00:00] +2023-09-19 11:43:16,384 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243985639201751322732399074 amount: 80. +2023-09-19 11:43:16,385 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:43:17,113 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123796.0, "order_id": "x-XEKWYICXBSIUT605b4c07cc9500582", "exchange_order_id": "35685190", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:43:17,114 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4c07cc9500582. +2023-09-19 11:43:17,716 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123797.0, "order_id": "x-XEKWYICXSSIUT605b4c07ccdc50582", "exchange_order_id": "35685192", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:43:17,716 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4c07ccdc50582. +2023-09-19 11:43:18,371 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4c3bbdec60582 for 80.00000000 SEI-USDT. +2023-09-19 11:43:18,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123797.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b4c3bbdec60582", "creation_timestamp": 1695123796.0, "exchange_order_id": "35685312", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:44:11,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4c3bbdec60582. [clock=2023-09-19 11:44:11+00:00] +2023-09-19 11:44:11,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243212165269171301223374480 amount: 80. +2023-09-19 11:44:11,113 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246652959411249360436668535 amount: 80. +2023-09-19 11:44:11,611 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123851.0, "order_id": "x-XEKWYICXBSIUT605b4c3bbdec60582", "exchange_order_id": "35685312", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:44:11,611 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4c3bbdec60582. +2023-09-19 11:44:11,721 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4c6fef7d10582 for 80.00000000 SEI-USDT. +2023-09-19 11:44:11,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b4c6fef7d10582", "creation_timestamp": 1695123851.0, "exchange_order_id": "35685666", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:44:11,747 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4c6fef4c10582 for 80.00000000 SEI-USDT. +2023-09-19 11:44:11,775 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123851.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b4c6fef4c10582", "creation_timestamp": 1695123851.0, "exchange_order_id": "35685667", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:45:01,458 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b4c6fef7d10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:45:01,459 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:45:01+00:00] +2023-09-19 11:45:01,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123901.0, "order_id": "x-XEKWYICXSSIUT605b4c6fef7d10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "5005333", "exchange_order_id": "35685666", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:45:01,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123901.0, "order_id": "x-XEKWYICXSSIUT605b4c6fef7d10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35685666", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:45:01,645 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b4c6fef7d10582 completely filled. +2023-09-19 11:45:06,125 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4c6fef4c10582. [clock=2023-09-19 11:45:06+00:00] +2023-09-19 11:45:06,213 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12460000 amount: 80. +2023-09-19 11:45:06,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:45:07,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123906.0, "order_id": "x-XEKWYICXBSIUT605b4c6fef4c10582", "exchange_order_id": "35685667", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:45:07,453 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4c6fef4c10582. +2023-09-19 11:45:08,607 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4ca47bca40582 for 80.00000000 SEI-USDT. +2023-09-19 11:45:08,647 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123908.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b4ca47bca40582", "creation_timestamp": 1695123906.0, "exchange_order_id": "35686000", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:46:01,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4ca47bca40582. [clock=2023-09-19 11:46:01+00:00] +2023-09-19 11:46:01,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-19 11:46:01,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:46:01,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123961.0, "order_id": "x-XEKWYICXBSIUT605b4ca47bca40582", "exchange_order_id": "35686000", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:46:01,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4ca47bca40582. +2023-09-19 11:46:01,292 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4cd8d08430582 for 80.00000000 SEI-USDT. +2023-09-19 11:46:01,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695123961.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b4cd8d08430582", "creation_timestamp": 1695123961.0, "exchange_order_id": "35686219", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:46:56,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4cd8d08430582. [clock=2023-09-19 11:46:56+00:00] +2023-09-19 11:46:56,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-19 11:46:56,078 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:46:56,252 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124016.0, "order_id": "x-XEKWYICXBSIUT605b4cd8d08430582", "exchange_order_id": "35686219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:46:56,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4cd8d08430582. +2023-09-19 11:46:56,498 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4d0d41ede0582 for 80.00000000 SEI-USDT. +2023-09-19 11:46:56,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124016.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b4d0d41ede0582", "creation_timestamp": 1695124016.0, "exchange_order_id": "35686474", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:47:51,150 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4d0d41ede0582. [clock=2023-09-19 11:47:51+00:00] +2023-09-19 11:47:51,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-19 11:47:51,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:47:52,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124071.0, "order_id": "x-XEKWYICXBSIUT605b4d0d41ede0582", "exchange_order_id": "35686474", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:47:52,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4d0d41ede0582. +2023-09-19 11:47:53,804 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4d41d95990582 for 80.00000000 SEI-USDT. +2023-09-19 11:47:53,842 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124073.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b4d41d95990582", "creation_timestamp": 1695124071.0, "exchange_order_id": "35686586", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:47:56,314 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b4d41d95990582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:47:56,328 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:47:56+00:00] +2023-09-19 11:47:56,433 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124076.0, "order_id": "x-XEKWYICXBSIUT605b4d41d95990582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5005400", "exchange_order_id": "35686586", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:47:56,723 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124076.0, "order_id": "x-XEKWYICXBSIUT605b4d41d95990582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35686586", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:47:56,723 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b4d41d95990582 completely filled. +2023-09-19 11:48:46,213 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12470000 amount: 80. +2023-09-19 11:48:46,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251584915545656692599713307 amount: 80. +2023-09-19 11:48:47,306 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4d764cdf00582 for 80.00000000 SEI-USDT. +2023-09-19 11:48:47,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124126.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b4d764cdf00582", "creation_timestamp": 1695124126.0, "exchange_order_id": "35686884", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:48:48,591 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4d764d3290582 for 80.00000000 SEI-USDT. +2023-09-19 11:48:48,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124128.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b4d764d3290582", "creation_timestamp": 1695124126.0, "exchange_order_id": "35686889", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:49:41,179 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4d764cdf00582. [clock=2023-09-19 11:49:41+00:00] +2023-09-19 11:49:41,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4d764d3290582. [clock=2023-09-19 11:49:41+00:00] +2023-09-19 11:49:41,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12480000 amount: 80. +2023-09-19 11:49:41,275 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 11:49:42,443 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124181.0, "order_id": "x-XEKWYICXBSIUT605b4d764cdf00582", "exchange_order_id": "35686884", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:49:42,443 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4d764cdf00582. +2023-09-19 11:49:43,841 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124183.0, "order_id": "x-XEKWYICXSSIUT605b4d764d3290582", "exchange_order_id": "35686889", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:49:43,841 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4d764d3290582. +2023-09-19 11:49:44,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4daacd46d0582 for 80.00000000 SEI-USDT. +2023-09-19 11:49:44,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124183.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b4daacd46d0582", "creation_timestamp": 1695124181.0, "exchange_order_id": "35687173", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:49:52,406 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b4daacd46d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:49:52,415 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:49:52+00:00] +2023-09-19 11:49:52,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124192.0, "order_id": "x-XEKWYICXBSIUT605b4daacd46d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5005439", "exchange_order_id": "35687173", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:49:52,826 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124192.0, "order_id": "x-XEKWYICXBSIUT605b4daacd46d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35687173", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:49:52,826 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b4daacd46d0582 completely filled. +2023-09-19 11:50:36,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247681117515180596127054597 amount: 80. +2023-09-19 11:50:36,071 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1251279850049676961364269048 amount: 80. +2023-09-19 11:50:36,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4ddf0f3cf0582 for 80.00000000 SEI-USDT. +2023-09-19 11:50:36,301 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b4ddf0f3cf0582", "creation_timestamp": 1695124236.0, "exchange_order_id": "35687382", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:50:36,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4ddf0f6400582 for 80.00000000 SEI-USDT. +2023-09-19 11:50:36,478 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124236.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXSSIUT605b4ddf0f6400582", "creation_timestamp": 1695124236.0, "exchange_order_id": "35687383", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:51:31,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4ddf0f3cf0582. [clock=2023-09-19 11:51:31+00:00] +2023-09-19 11:51:31,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4ddf0f6400582. [clock=2023-09-19 11:51:31+00:00] +2023-09-19 11:51:31,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247310135440643797731112943 amount: 80. +2023-09-19 11:51:31,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252087636489747633706731132 amount: 80. +2023-09-19 11:51:31,331 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124291.0, "order_id": "x-XEKWYICXBSIUT605b4ddf0f3cf0582", "exchange_order_id": "35687382", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:51:31,332 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4ddf0f3cf0582. +2023-09-19 11:51:31,575 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124291.0, "order_id": "x-XEKWYICXSSIUT605b4ddf0f6400582", "exchange_order_id": "35687383", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:51:31,575 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4ddf0f6400582. +2023-09-19 11:51:31,577 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4e1383ffe0582 for 80.00000000 SEI-USDT. +2023-09-19 11:51:31,590 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b4e1383ffe0582", "creation_timestamp": 1695124291.0, "exchange_order_id": "35687696", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:51:31,695 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4e1383cc20582 for 80.00000000 SEI-USDT. +2023-09-19 11:51:31,709 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124291.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b4e1383cc20582", "creation_timestamp": 1695124291.0, "exchange_order_id": "35687697", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:52:26,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4e1383cc20582. [clock=2023-09-19 11:52:26+00:00] +2023-09-19 11:52:26,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4e1383ffe0582. [clock=2023-09-19 11:52:26+00:00] +2023-09-19 11:52:26,325 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-19 11:52:26,329 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253737300214336292546404759 amount: 80. +2023-09-19 11:52:27,634 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124347.0, "order_id": "x-XEKWYICXBSIUT605b4e1383cc20582", "exchange_order_id": "35687697", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:52:27,634 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4e1383cc20582. +2023-09-19 11:52:28,679 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124348.0, "order_id": "x-XEKWYICXSSIUT605b4e1383ffe0582", "exchange_order_id": "35687696", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:52:28,679 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4e1383ffe0582. +2023-09-19 11:52:28,969 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4e4835a350582 for 80.00000000 SEI-USDT. +2023-09-19 11:52:28,996 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124348.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b4e4835a350582", "creation_timestamp": 1695124346.0, "exchange_order_id": "35687874", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:52:28,997 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4e4835c8a0582 for 80.00000000 SEI-USDT. +2023-09-19 11:52:29,020 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124348.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b4e4835c8a0582", "creation_timestamp": 1695124346.0, "exchange_order_id": "35687873", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:53:21,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4e4835a350582. [clock=2023-09-19 11:53:21+00:00] +2023-09-19 11:53:21,121 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4e4835c8a0582. [clock=2023-09-19 11:53:21+00:00] +2023-09-19 11:53:21,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12490000 amount: 80. +2023-09-19 11:53:21,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254098879709373137704366470 amount: 80. +2023-09-19 11:53:21,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124401.0, "order_id": "x-XEKWYICXBSIUT605b4e4835a350582", "exchange_order_id": "35687874", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:53:21,631 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4e4835a350582. +2023-09-19 11:53:23,010 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124402.0, "order_id": "x-XEKWYICXSSIUT605b4e4835c8a0582", "exchange_order_id": "35687873", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:53:23,010 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4e4835c8a0582. +2023-09-19 11:53:23,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4e7c81f290582 for 80.00000000 SEI-USDT. +2023-09-19 11:53:23,333 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124403.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b4e7c81f290582", "creation_timestamp": 1695124401.0, "exchange_order_id": "35688016", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:53:23,334 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4e7c81d5b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:53:23,357 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124403.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b4e7c81d5b0582", "creation_timestamp": 1695124401.0, "exchange_order_id": "35688019", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:54:16,325 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4e7c81d5b0582. [clock=2023-09-19 11:54:16+00:00] +2023-09-19 11:54:16,326 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4e7c81f290582. [clock=2023-09-19 11:54:16+00:00] +2023-09-19 11:54:16,405 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 11:54:16,407 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256803418845065167663530886 amount: 80. +2023-09-19 11:54:17,223 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124456.0, "order_id": "x-XEKWYICXBSIUT605b4e7c81d5b0582", "exchange_order_id": "35688019", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:54:17,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4e7c81d5b0582. +2023-09-19 11:54:17,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124456.0, "order_id": "x-XEKWYICXSSIUT605b4e7c81f290582", "exchange_order_id": "35688016", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:54:17,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4e7c81f290582. +2023-09-19 11:54:19,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4eb1332620582 for 80.00000000 SEI-USDT. +2023-09-19 11:54:19,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124459.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b4eb1332620582", "creation_timestamp": 1695124456.0, "exchange_order_id": "35688257", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:54:19,525 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4eb12ff800582 for 80.00000000 SEI-USDT. +2023-09-19 11:54:19,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124459.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b4eb12ff800582", "creation_timestamp": 1695124456.0, "exchange_order_id": "35688256", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:55:11,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4eb12ff800582. [clock=2023-09-19 11:55:11+00:00] +2023-09-19 11:55:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4eb1332620582. [clock=2023-09-19 11:55:11+00:00] +2023-09-19 11:55:11,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 11:55:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256708591329883675921619262 amount: 80. +2023-09-19 11:55:11,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124511.0, "order_id": "x-XEKWYICXBSIUT605b4eb12ff800582", "exchange_order_id": "35688256", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:55:11,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4eb12ff800582. +2023-09-19 11:55:11,455 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124511.0, "order_id": "x-XEKWYICXSSIUT605b4eb1332620582", "exchange_order_id": "35688257", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:55:11,455 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4eb1332620582. +2023-09-19 11:55:11,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4ee5467bb0582 for 80.00000000 SEI-USDT. +2023-09-19 11:55:11,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b4ee5467bb0582", "creation_timestamp": 1695124511.0, "exchange_order_id": "35688369", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:55:11,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4ee5464490582 for 80.00000000 SEI-USDT. +2023-09-19 11:55:11,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124511.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b4ee5464490582", "creation_timestamp": 1695124511.0, "exchange_order_id": "35688370", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:55:27,177 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b4ee5464490582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:55:27,178 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 11:55:27+00:00] +2023-09-19 11:55:27,201 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124527.0, "order_id": "x-XEKWYICXBSIUT605b4ee5464490582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12500000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5005533", "exchange_order_id": "35688370", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:55:27,215 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124527.0, "order_id": "x-XEKWYICXBSIUT605b4ee5464490582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0000000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35688370", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:55:27,215 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b4ee5464490582 completely filled. +2023-09-19 11:56:06,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4ee5467bb0582. [clock=2023-09-19 11:56:06+00:00] +2023-09-19 11:56:06,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 11:56:06,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254793531149515760872823561 amount: 80. +2023-09-19 11:56:06,199 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124566.0, "order_id": "x-XEKWYICXSSIUT605b4ee5467bb0582", "exchange_order_id": "35688369", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:56:06,199 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4ee5467bb0582. +2023-09-19 11:56:06,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4f19ba8a60582 for 80.00000000 SEI-USDT. +2023-09-19 11:56:06,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b4f19ba8a60582", "creation_timestamp": 1695124566.0, "exchange_order_id": "35688561", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:56:06,279 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4f19ba5ec0582 for 80.00000000 SEI-USDT. +2023-09-19 11:56:06,290 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124566.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b4f19ba5ec0582", "creation_timestamp": 1695124566.0, "exchange_order_id": "35688562", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:57:01,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4f19ba5ec0582. [clock=2023-09-19 11:57:01+00:00] +2023-09-19 11:57:01,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4f19ba8a60582. [clock=2023-09-19 11:57:01+00:00] +2023-09-19 11:57:01,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-19 11:57:01,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257097944713187402981290031 amount: 80. +2023-09-19 11:57:01,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124621.0, "order_id": "x-XEKWYICXBSIUT605b4f19ba5ec0582", "exchange_order_id": "35688562", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:57:01,283 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4f19ba5ec0582. +2023-09-19 11:57:01,508 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124621.0, "order_id": "x-XEKWYICXSSIUT605b4f19ba8a60582", "exchange_order_id": "35688561", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:57:01,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4f19ba8a60582. +2023-09-19 11:57:01,512 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4f4e3cccb0582 for 80.00000000 SEI-USDT. +2023-09-19 11:57:01,525 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b4f4e3cccb0582", "creation_timestamp": 1695124621.0, "exchange_order_id": "35688743", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:57:01,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4f4e3cf0b0582 for 80.00000000 SEI-USDT. +2023-09-19 11:57:01,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124621.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b4f4e3cf0b0582", "creation_timestamp": 1695124621.0, "exchange_order_id": "35688744", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:57:56,448 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4f4e3cccb0582. [clock=2023-09-19 11:57:56+00:00] +2023-09-19 11:57:56,463 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4f4e3cf0b0582. [clock=2023-09-19 11:57:56+00:00] +2023-09-19 11:57:56,553 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-19 11:57:56,563 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257039907494323559961952710 amount: 80. +2023-09-19 11:57:57,814 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124677.0, "order_id": "x-XEKWYICXBSIUT605b4f4e3cccb0582", "exchange_order_id": "35688743", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:57:57,815 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4f4e3cccb0582. +2023-09-19 11:57:58,847 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124678.0, "order_id": "x-XEKWYICXSSIUT605b4f4e3cf0b0582", "exchange_order_id": "35688744", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:57:58,847 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4f4e3cf0b0582. +2023-09-19 11:57:59,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4f83257bb0582 for 80.00000000 SEI-USDT. +2023-09-19 11:57:59,204 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124678.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b4f83257bb0582", "creation_timestamp": 1695124676.0, "exchange_order_id": "35688836", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:57:59,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4f83252d70582 for 80.00000000 SEI-USDT. +2023-09-19 11:57:59,246 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124678.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b4f83252d70582", "creation_timestamp": 1695124676.0, "exchange_order_id": "35688837", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:58:51,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4f83252d70582. [clock=2023-09-19 11:58:51+00:00] +2023-09-19 11:58:51,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4f83257bb0582. [clock=2023-09-19 11:58:51+00:00] +2023-09-19 11:58:51,123 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12530000 amount: 80. +2023-09-19 11:58:51,124 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260309053190784843078929906 amount: 80. +2023-09-19 11:58:51,660 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124731.0, "order_id": "x-XEKWYICXBSIUT605b4f83252d70582", "exchange_order_id": "35688837", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:58:51,661 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4f83252d70582. +2023-09-19 11:58:52,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124732.0, "order_id": "x-XEKWYICXSSIUT605b4f83257bb0582", "exchange_order_id": "35688836", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:58:52,451 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4f83257bb0582. +2023-09-19 11:58:53,069 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4fb72dcda0582 for 80.00000000 SEI-USDT. +2023-09-19 11:58:53,103 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124732.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b4fb72dcda0582", "creation_timestamp": 1695124731.0, "exchange_order_id": "35689097", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:58:53,104 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4fb72d9870582 for 80.00000000 SEI-USDT. +2023-09-19 11:58:53,122 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124732.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXBSIUT605b4fb72d9870582", "creation_timestamp": 1695124731.0, "exchange_order_id": "35689098", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:59:08,453 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b4fb72d9870582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 11:59:08,455 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 11:59:08+00:00] +2023-09-19 11:59:08,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124748.0, "order_id": "x-XEKWYICXBSIUT605b4fb72d9870582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12530000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5005591", "exchange_order_id": "35689098", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 11:59:08,627 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124748.0, "order_id": "x-XEKWYICXBSIUT605b4fb72d9870582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0240000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35689098", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 11:59:08,628 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b4fb72d9870582 completely filled. +2023-09-19 11:59:46,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4fb72dcda0582. [clock=2023-09-19 11:59:46+00:00] +2023-09-19 11:59:46,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 11:59:46,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257170002904188429734663011 amount: 80. +2023-09-19 11:59:47,763 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124787.0, "order_id": "x-XEKWYICXSSIUT605b4fb72dcda0582", "exchange_order_id": "35689097", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 11:59:47,764 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4fb72dcda0582. +2023-09-19 11:59:49,013 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b4feb9c69f0582 for 80.00000000 SEI-USDT. +2023-09-19 11:59:49,078 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124788.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b4feb9c69f0582", "creation_timestamp": 1695124786.0, "exchange_order_id": "35689283", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 11:59:49,794 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b4feb9ca510582 for 80.00000000 SEI-USDT. +2023-09-19 11:59:49,862 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124789.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b4feb9ca510582", "creation_timestamp": 1695124786.0, "exchange_order_id": "35689285", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:00:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b4feb9c69f0582. [clock=2023-09-19 12:00:41+00:00] +2023-09-19 12:00:41,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b4feb9ca510582. [clock=2023-09-19 12:00:41+00:00] +2023-09-19 12:00:41,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 12:00:41,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257211346628935506029123282 amount: 80. +2023-09-19 12:00:41,304 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124841.0, "order_id": "x-XEKWYICXBSIUT605b4feb9c69f0582", "exchange_order_id": "35689283", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:00:41,305 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b4feb9c69f0582. +2023-09-19 12:00:41,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b501ffd9ec0582 for 80.00000000 SEI-USDT. +2023-09-19 12:00:41,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b501ffd9ec0582", "creation_timestamp": 1695124841.0, "exchange_order_id": "35689644", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:00:41,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124841.0, "order_id": "x-XEKWYICXSSIUT605b4feb9ca510582", "exchange_order_id": "35689285", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:00:41,495 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b4feb9ca510582. +2023-09-19 12:00:41,550 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b501ffde330582 for 80.00000000 SEI-USDT. +2023-09-19 12:00:41,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124841.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b501ffde330582", "creation_timestamp": 1695124841.0, "exchange_order_id": "35689645", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:01:08,300 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b501ffd9ec0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:01:08,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 12:01:08+00:00] +2023-09-19 12:01:08,337 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124868.0, "order_id": "x-XEKWYICXBSIUT605b501ffd9ec0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5005631", "exchange_order_id": "35689644", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:01:08,353 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124868.0, "order_id": "x-XEKWYICXBSIUT605b501ffd9ec0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35689644", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:01:08,354 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b501ffd9ec0582 completely filled. +2023-09-19 12:01:36,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b501ffde330582. [clock=2023-09-19 12:01:36+00:00] +2023-09-19 12:01:36,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249273492275425248518848966 amount: 80. +2023-09-19 12:01:36,053 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254180083103288126942558658 amount: 80. +2023-09-19 12:01:36,234 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124896.0, "order_id": "x-XEKWYICXSSIUT605b501ffde330582", "exchange_order_id": "35689645", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:01:36,235 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b501ffde330582. +2023-09-19 12:01:36,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b50547774d0582 for 80.00000000 SEI-USDT. +2023-09-19 12:01:36,486 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b50547774d0582", "creation_timestamp": 1695124896.0, "exchange_order_id": "35689893", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:01:36,488 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b505477b240582 for 80.00000000 SEI-USDT. +2023-09-19 12:01:36,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124896.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b505477b240582", "creation_timestamp": 1695124896.0, "exchange_order_id": "35689894", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:02:31,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b50547774d0582. [clock=2023-09-19 12:02:31+00:00] +2023-09-19 12:02:31,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b505477b240582. [clock=2023-09-19 12:02:31+00:00] +2023-09-19 12:02:31,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246451900748597650181990090 amount: 80. +2023-09-19 12:02:31,094 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252488633876594209562055898 amount: 80. +2023-09-19 12:02:31,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124951.0, "order_id": "x-XEKWYICXBSIUT605b50547774d0582", "exchange_order_id": "35689893", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:02:31,652 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b50547774d0582. +2023-09-19 12:02:32,358 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124952.0, "order_id": "x-XEKWYICXSSIUT605b505477b240582", "exchange_order_id": "35689894", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:02:32,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b505477b240582. +2023-09-19 12:02:32,640 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5088f565e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:02:32,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124952.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b5088f565e0582", "creation_timestamp": 1695124951.0, "exchange_order_id": "35690154", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:02:32,683 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5088f542c0582 for 80.00000000 SEI-USDT. +2023-09-19 12:02:32,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695124952.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b5088f542c0582", "creation_timestamp": 1695124951.0, "exchange_order_id": "35690155", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:03:26,151 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5088f542c0582. [clock=2023-09-19 12:03:26+00:00] +2023-09-19 12:03:26,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5088f565e0582. [clock=2023-09-19 12:03:26+00:00] +2023-09-19 12:03:26,258 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247115036390413342315929215 amount: 80. +2023-09-19 12:03:26,259 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252918810617385353359192511 amount: 80. +2023-09-19 12:03:27,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125007.0, "order_id": "x-XEKWYICXBSIUT605b5088f542c0582", "exchange_order_id": "35690155", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:03:27,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5088f542c0582. +2023-09-19 12:03:29,205 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125008.0, "order_id": "x-XEKWYICXSSIUT605b5088f565e0582", "exchange_order_id": "35690154", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:03:29,206 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5088f565e0582. +2023-09-19 12:03:29,779 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b50bd93abc0582 for 80.00000000 SEI-USDT. +2023-09-19 12:03:29,828 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125009.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b50bd93abc0582", "creation_timestamp": 1695125006.0, "exchange_order_id": "35690398", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:03:29,831 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b50bd915990582 for 80.00000000 SEI-USDT. +2023-09-19 12:03:29,898 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125009.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b50bd915990582", "creation_timestamp": 1695125006.0, "exchange_order_id": "35690399", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:04:04,918 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b50bd93abc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:04:04,927 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 12:04:04+00:00] +2023-09-19 12:04:05,011 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125044.0, "order_id": "x-XEKWYICXSSIUT605b50bd93abc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.01001600"}]}, "exchange_trade_id": "5005706", "exchange_order_id": "35690398", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:04:05,262 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125044.0, "order_id": "x-XEKWYICXSSIUT605b50bd93abc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35690398", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:04:05,262 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b50bd93abc0582 completely filled. +2023-09-19 12:04:21,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b50bd915990582. [clock=2023-09-19 12:04:21+00:00] +2023-09-19 12:04:21,215 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248503819397467089925131437 amount: 80. +2023-09-19 12:04:21,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256109698946449555020944197 amount: 80. +2023-09-19 12:04:22,138 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125061.0, "order_id": "x-XEKWYICXBSIUT605b50bd915990582", "exchange_order_id": "35690399", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:04:22,138 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b50bd915990582. +2023-09-19 12:04:23,733 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b50f1fabcf0582 for 80.00000000 SEI-USDT. +2023-09-19 12:04:23,759 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125063.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b50f1fabcf0582", "creation_timestamp": 1695125061.0, "exchange_order_id": "35690902", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:04:23,990 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b50f1fb02c0582 for 80.00000000 SEI-USDT. +2023-09-19 12:04:24,031 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125063.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b50f1fb02c0582", "creation_timestamp": 1695125061.0, "exchange_order_id": "35690904", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:05:16,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b50f1fabcf0582. [clock=2023-09-19 12:05:16+00:00] +2023-09-19 12:05:16,063 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b50f1fb02c0582. [clock=2023-09-19 12:05:16+00:00] +2023-09-19 12:05:16,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249057997947232504497495127 amount: 80. +2023-09-19 12:05:16,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254972304534628708382728880 amount: 80. +2023-09-19 12:05:16,318 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125116.0, "order_id": "x-XEKWYICXBSIUT605b50f1fabcf0582", "exchange_order_id": "35690902", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:05:16,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b50f1fabcf0582. +2023-09-19 12:05:16,555 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b51264f3240582 for 80.00000000 SEI-USDT. +2023-09-19 12:05:16,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b51264f3240582", "creation_timestamp": 1695125116.0, "exchange_order_id": "35691024", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:05:16,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125116.0, "order_id": "x-XEKWYICXSSIUT605b50f1fb02c0582", "exchange_order_id": "35690904", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:05:16,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b50f1fb02c0582. +2023-09-19 12:05:16,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b51264efd40582 for 80.00000000 SEI-USDT. +2023-09-19 12:05:16,618 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125116.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b51264efd40582", "creation_timestamp": 1695125116.0, "exchange_order_id": "35691025", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:06:11,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b51264efd40582. [clock=2023-09-19 12:06:11+00:00] +2023-09-19 12:06:11,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b51264f3240582. [clock=2023-09-19 12:06:11+00:00] +2023-09-19 12:06:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1249489934650727948025323143 amount: 80. +2023-09-19 12:06:11,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254089121926359041846094469 amount: 80. +2023-09-19 12:06:11,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125171.0, "order_id": "x-XEKWYICXBSIUT605b51264efd40582", "exchange_order_id": "35691025", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:06:11,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b51264efd40582. +2023-09-19 12:06:11,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125171.0, "order_id": "x-XEKWYICXSSIUT605b51264f3240582", "exchange_order_id": "35691024", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:06:11,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b51264f3240582. +2023-09-19 12:06:11,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b515ab4c440582 for 80.00000000 SEI-USDT. +2023-09-19 12:06:11,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b515ab4c440582", "creation_timestamp": 1695125171.0, "exchange_order_id": "35691131", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:06:11,529 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b515ab497a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:06:11,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125171.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXBSIUT605b515ab497a0582", "creation_timestamp": 1695125171.0, "exchange_order_id": "35691130", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:07:06,098 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b515ab497a0582. [clock=2023-09-19 12:07:06+00:00] +2023-09-19 12:07:06,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b515ab4c440582. [clock=2023-09-19 12:07:06+00:00] +2023-09-19 12:07:06,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247276959679878269615342119 amount: 80. +2023-09-19 12:07:06,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253691567388103901494281741 amount: 80. +2023-09-19 12:07:06,685 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125226.0, "order_id": "x-XEKWYICXBSIUT605b515ab497a0582", "exchange_order_id": "35691130", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:07:06,698 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b515ab497a0582. +2023-09-19 12:07:07,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125227.0, "order_id": "x-XEKWYICXSSIUT605b515ab4c440582", "exchange_order_id": "35691131", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:07:07,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b515ab4c440582. +2023-09-19 12:07:07,403 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b518f44fbc0582 for 80.00000000 SEI-USDT. +2023-09-19 12:07:07,428 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125227.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b518f44fbc0582", "creation_timestamp": 1695125226.0, "exchange_order_id": "35691322", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:07:07,429 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b518f44d0e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:07:07,439 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125227.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b518f44d0e0582", "creation_timestamp": 1695125226.0, "exchange_order_id": "35691323", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:08:01,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b518f44d0e0582. [clock=2023-09-19 12:08:01+00:00] +2023-09-19 12:08:01,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b518f44fbc0582. [clock=2023-09-19 12:08:01+00:00] +2023-09-19 12:08:01,159 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246179385745087730067226553 amount: 80. +2023-09-19 12:08:01,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253707409237743874398713979 amount: 80. +2023-09-19 12:08:01,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125281.0, "order_id": "x-XEKWYICXBSIUT605b518f44d0e0582", "exchange_order_id": "35691323", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:08:01,671 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b518f44d0e0582. +2023-09-19 12:08:02,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b51c3bbb840582 for 80.00000000 SEI-USDT. +2023-09-19 12:08:02,560 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125282.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b51c3bbb840582", "creation_timestamp": 1695125281.0, "exchange_order_id": "35691574", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:08:02,588 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125282.0, "order_id": "x-XEKWYICXSSIUT605b518f44fbc0582", "exchange_order_id": "35691322", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:08:02,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b518f44fbc0582. +2023-09-19 12:08:02,589 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b51c3bc0370582 for 80.00000000 SEI-USDT. +2023-09-19 12:08:02,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125282.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b51c3bc0370582", "creation_timestamp": 1695125281.0, "exchange_order_id": "35691575", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:08:56,486 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b51c3bbb840582. [clock=2023-09-19 12:08:56+00:00] +2023-09-19 12:08:56,490 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b51c3bc0370582. [clock=2023-09-19 12:08:56+00:00] +2023-09-19 12:08:56,583 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247114295122831438189091344 amount: 80. +2023-09-19 12:08:56,584 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254078553348794977313143844 amount: 80. +2023-09-19 12:08:57,449 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125337.0, "order_id": "x-XEKWYICXBSIUT605b51c3bbb840582", "exchange_order_id": "35691574", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:08:57,449 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b51c3bbb840582. +2023-09-19 12:08:59,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125338.0, "order_id": "x-XEKWYICXSSIUT605b51c3bc0370582", "exchange_order_id": "35691575", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:08:59,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b51c3bc0370582. +2023-09-19 12:08:59,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b51f896fb30582 for 80.00000000 SEI-USDT. +2023-09-19 12:08:59,759 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125339.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b51f896fb30582", "creation_timestamp": 1695125336.0, "exchange_order_id": "35691700", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:08:59,760 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b51f8972bb0582 for 80.00000000 SEI-USDT. +2023-09-19 12:08:59,791 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125339.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b51f8972bb0582", "creation_timestamp": 1695125336.0, "exchange_order_id": "35691699", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:09:51,151 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b51f896fb30582. [clock=2023-09-19 12:09:51+00:00] +2023-09-19 12:09:51,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b51f8972bb0582. [clock=2023-09-19 12:09:51+00:00] +2023-09-19 12:09:51,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247295406993127466732837280 amount: 80. +2023-09-19 12:09:51,240 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253822609654188360965909368 amount: 80. +2023-09-19 12:09:52,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125391.0, "order_id": "x-XEKWYICXBSIUT605b51f896fb30582", "exchange_order_id": "35691700", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:09:52,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b51f896fb30582. +2023-09-19 12:09:53,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125393.0, "order_id": "x-XEKWYICXSSIUT605b51f8972bb0582", "exchange_order_id": "35691699", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:09:53,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b51f8972bb0582. +2023-09-19 12:09:54,111 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b522cb6f960582 for 80.00000000 SEI-USDT. +2023-09-19 12:09:54,170 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b522cb6f960582", "creation_timestamp": 1695125391.0, "exchange_order_id": "35691784", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:09:54,182 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b522cb6b7e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:09:54,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125393.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b522cb6b7e0582", "creation_timestamp": 1695125391.0, "exchange_order_id": "35691783", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:10:46,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b522cb6b7e0582. [clock=2023-09-19 12:10:46+00:00] +2023-09-19 12:10:46,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b522cb6f960582. [clock=2023-09-19 12:10:46+00:00] +2023-09-19 12:10:46,032 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246873882780852541508661394 amount: 80. +2023-09-19 12:10:46,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253063341880732145297016708 amount: 80. +2023-09-19 12:10:46,231 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125446.0, "order_id": "x-XEKWYICXBSIUT605b522cb6b7e0582", "exchange_order_id": "35691783", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:10:46,232 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b522cb6b7e0582. +2023-09-19 12:10:46,447 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5260f83470582 for 80.00000000 SEI-USDT. +2023-09-19 12:10:46,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b5260f83470582", "creation_timestamp": 1695125446.0, "exchange_order_id": "35691854", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:10:46,567 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5260f7f7e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:10:46,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125446.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b5260f7f7e0582", "creation_timestamp": 1695125446.0, "exchange_order_id": "35691855", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:10:46,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125446.0, "order_id": "x-XEKWYICXSSIUT605b522cb6f960582", "exchange_order_id": "35691784", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:10:46,596 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b522cb6f960582. +2023-09-19 12:11:41,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5260f7f7e0582. [clock=2023-09-19 12:11:41+00:00] +2023-09-19 12:11:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5260f83470582. [clock=2023-09-19 12:11:41+00:00] +2023-09-19 12:11:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248543849034490287166457679 amount: 80. +2023-09-19 12:11:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254471509353362128814751997 amount: 80. +2023-09-19 12:11:41,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125501.0, "order_id": "x-XEKWYICXBSIUT605b5260f7f7e0582", "exchange_order_id": "35691855", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:11:41,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5260f7f7e0582. +2023-09-19 12:11:41,453 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125501.0, "order_id": "x-XEKWYICXSSIUT605b5260f83470582", "exchange_order_id": "35691854", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:11:41,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5260f83470582. +2023-09-19 12:11:41,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5295694800582 for 80.00000000 SEI-USDT. +2023-09-19 12:11:41,473 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b5295694800582", "creation_timestamp": 1695125501.0, "exchange_order_id": "35692177", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:11:41,527 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b52956979a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:11:41,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125501.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b52956979a0582", "creation_timestamp": 1695125501.0, "exchange_order_id": "35692176", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:12:36,131 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5295694800582. [clock=2023-09-19 12:12:36+00:00] +2023-09-19 12:12:36,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b52956979a0582. [clock=2023-09-19 12:12:36+00:00] +2023-09-19 12:12:36,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1248090486326372784640209719 amount: 80. +2023-09-19 12:12:36,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252696784795008396631633855 amount: 80. +2023-09-19 12:12:36,820 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125556.0, "order_id": "x-XEKWYICXBSIUT605b5295694800582", "exchange_order_id": "35692177", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:12:36,820 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5295694800582. +2023-09-19 12:12:38,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125558.0, "order_id": "x-XEKWYICXSSIUT605b52956979a0582", "exchange_order_id": "35692176", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:12:38,511 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b52956979a0582. +2023-09-19 12:12:39,031 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b52ca045a60582 for 80.00000000 SEI-USDT. +2023-09-19 12:12:39,100 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125558.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b52ca045a60582", "creation_timestamp": 1695125556.0, "exchange_order_id": "35692336", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:12:39,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b52ca041fd0582 for 80.00000000 SEI-USDT. +2023-09-19 12:12:39,128 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125558.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXBSIUT605b52ca041fd0582", "creation_timestamp": 1695125556.0, "exchange_order_id": "35692335", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:12:52,388 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 12:13:31,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b52ca041fd0582. [clock=2023-09-19 12:13:31+00:00] +2023-09-19 12:13:31,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b52ca045a60582. [clock=2023-09-19 12:13:31+00:00] +2023-09-19 12:13:31,311 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244430819594848205460405774 amount: 80. +2023-09-19 12:13:31,312 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250229693097529560608758654 amount: 80. +2023-09-19 12:13:32,690 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125612.0, "order_id": "x-XEKWYICXBSIUT605b52ca041fd0582", "exchange_order_id": "35692335", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:13:32,691 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b52ca041fd0582. +2023-09-19 12:13:34,082 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125613.0, "order_id": "x-XEKWYICXSSIUT605b52ca045a60582", "exchange_order_id": "35692336", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:13:34,083 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b52ca045a60582. +2023-09-19 12:13:34,628 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b52fe9a6c20582 for 80.00000000 SEI-USDT. +2023-09-19 12:13:34,697 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125614.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b52fe9a6c20582", "creation_timestamp": 1695125611.0, "exchange_order_id": "35692731", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:13:34,700 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b52fe973ca0582 for 80.00000000 SEI-USDT. +2023-09-19 12:13:34,782 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125614.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b52fe973ca0582", "creation_timestamp": 1695125611.0, "exchange_order_id": "35692730", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:14:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b52fe973ca0582. [clock=2023-09-19 12:14:26+00:00] +2023-09-19 12:14:26,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b52fe9a6c20582. [clock=2023-09-19 12:14:26+00:00] +2023-09-19 12:14:26,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239381466826476550917876572 amount: 80. +2023-09-19 12:14:26,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12470000 amount: 80. +2023-09-19 12:14:26,567 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125666.0, "order_id": "x-XEKWYICXBSIUT605b52fe973ca0582", "exchange_order_id": "35692730", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:14:26,567 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b52fe973ca0582. +2023-09-19 12:14:27,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125667.0, "order_id": "x-XEKWYICXSSIUT605b52fe9a6c20582", "exchange_order_id": "35692731", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:14:27,268 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b52fe9a6c20582. +2023-09-19 12:14:27,472 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5332d43fb0582 for 80.00000000 SEI-USDT. +2023-09-19 12:14:27,495 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b5332d43fb0582", "creation_timestamp": 1695125666.0, "exchange_order_id": "35693254", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:14:27,497 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5332d41a00582 for 80.00000000 SEI-USDT. +2023-09-19 12:14:27,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125667.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5332d41a00582", "creation_timestamp": 1695125666.0, "exchange_order_id": "35693253", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:14:41,219 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b5332d43fb0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:14:41,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:14:41+00:00] +2023-09-19 12:14:41,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125681.0, "order_id": "x-XEKWYICXSSIUT605b5332d43fb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "5005983", "exchange_order_id": "35693254", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:14:41,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125681.0, "order_id": "x-XEKWYICXSSIUT605b5332d43fb0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35693254", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:14:41,412 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b5332d43fb0582 completely filled. +2023-09-19 12:15:21,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5332d41a00582. [clock=2023-09-19 12:15:21+00:00] +2023-09-19 12:15:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242420854415256923936562613 amount: 80. +2023-09-19 12:15:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-19 12:15:21,224 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125721.0, "order_id": "x-XEKWYICXBSIUT605b5332d41a00582", "exchange_order_id": "35693253", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:15:21,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5332d41a00582. +2023-09-19 12:15:21,431 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5367381080582 for 80.00000000 SEI-USDT. +2023-09-19 12:15:21,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b5367381080582", "creation_timestamp": 1695125721.0, "exchange_order_id": "35693676", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:15:21,466 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b53673858f0582 for 80.00000000 SEI-USDT. +2023-09-19 12:15:21,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125721.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b53673858f0582", "creation_timestamp": 1695125721.0, "exchange_order_id": "35693675", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:16:16,015 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5367381080582. [clock=2023-09-19 12:16:16+00:00] +2023-09-19 12:16:16,016 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b53673858f0582. [clock=2023-09-19 12:16:16+00:00] +2023-09-19 12:16:16,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242517240094183451840336851 amount: 80. +2023-09-19 12:16:16,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-19 12:16:16,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125776.0, "order_id": "x-XEKWYICXBSIUT605b5367381080582", "exchange_order_id": "35693676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:16:16,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5367381080582. +2023-09-19 12:16:16,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125776.0, "order_id": "x-XEKWYICXSSIUT605b53673858f0582", "exchange_order_id": "35693675", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:16:16,474 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b53673858f0582. +2023-09-19 12:16:16,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b539baf5330582 for 80.00000000 SEI-USDT. +2023-09-19 12:16:16,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b539baf5330582", "creation_timestamp": 1695125776.0, "exchange_order_id": "35693785", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:16:16,632 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b539baf8620582 for 80.00000000 SEI-USDT. +2023-09-19 12:16:16,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125776.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b539baf8620582", "creation_timestamp": 1695125776.0, "exchange_order_id": "35693786", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:16:48,717 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b539baf8620582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:16:48,718 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:16:48+00:00] +2023-09-19 12:16:48,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125808.0, "order_id": "x-XEKWYICXSSIUT605b539baf8620582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "5006040", "exchange_order_id": "35693786", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:16:48,750 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125808.0, "order_id": "x-XEKWYICXSSIUT605b539baf8620582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35693786", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:16:48,750 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b539baf8620582 completely filled. +2023-09-19 12:17:11,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b539baf5330582. [clock=2023-09-19 12:17:11+00:00] +2023-09-19 12:17:11,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243322612624359616249945411 amount: 80. +2023-09-19 12:17:11,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248391131801753284772267369 amount: 80. +2023-09-19 12:17:11,699 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125831.0, "order_id": "x-XEKWYICXBSIUT605b539baf5330582", "exchange_order_id": "35693785", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:17:11,699 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b539baf5330582. +2023-09-19 12:17:12,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b53d03315d0582 for 80.00000000 SEI-USDT. +2023-09-19 12:17:12,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125832.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b53d03315d0582", "creation_timestamp": 1695125831.0, "exchange_order_id": "35694091", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:17:12,629 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b53d032e110582 for 80.00000000 SEI-USDT. +2023-09-19 12:17:12,650 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125832.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b53d032e110582", "creation_timestamp": 1695125831.0, "exchange_order_id": "35694090", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:17:55,850 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b53d03315d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:17:55,859 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:17:55+00:00] +2023-09-19 12:17:55,952 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125875.0, "order_id": "x-XEKWYICXSSIUT605b53d03315d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "5006104", "exchange_order_id": "35694091", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:17:56,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125875.0, "order_id": "x-XEKWYICXSSIUT605b53d03315d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35694091", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:17:56,129 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b53d03315d0582 completely filled. +2023-09-19 12:18:06,291 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b53d032e110582. [clock=2023-09-19 12:18:06+00:00] +2023-09-19 12:18:06,378 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244011474444901583194683494 amount: 80. +2023-09-19 12:18:06,379 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248819215517850782530759726 amount: 80. +2023-09-19 12:18:07,087 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125886.0, "order_id": "x-XEKWYICXBSIUT605b53d032e110582", "exchange_order_id": "35694090", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:18:07,087 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b53d032e110582. +2023-09-19 12:18:07,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5404ea7030582 for 80.00000000 SEI-USDT. +2023-09-19 12:18:07,755 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125887.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b5404ea7030582", "creation_timestamp": 1695125886.0, "exchange_order_id": "35694262", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:18:08,790 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5404edb150582 for 80.00000000 SEI-USDT. +2023-09-19 12:18:08,830 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125888.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b5404edb150582", "creation_timestamp": 1695125886.0, "exchange_order_id": "35694266", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:18:09,377 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b5404edb150582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:18:09,391 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:18:09+00:00] +2023-09-19 12:18:09,480 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125889.0, "order_id": "x-XEKWYICXSSIUT605b5404edb150582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "5006121", "exchange_order_id": "35694266", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:18:09,770 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125889.0, "order_id": "x-XEKWYICXSSIUT605b5404edb150582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35694266", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:18:09,770 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b5404edb150582 completely filled. +2023-09-19 12:19:01,224 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5404ea7030582. [clock=2023-09-19 12:19:01+00:00] +2023-09-19 12:19:01,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 12:19:01,307 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:19:03,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125941.0, "order_id": "x-XEKWYICXBSIUT605b5404ea7030582", "exchange_order_id": "35694262", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:19:03,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5404ea7030582. +2023-09-19 12:19:04,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b54394c6340582 for 80.00000000 SEI-USDT. +2023-09-19 12:19:04,378 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125944.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b54394c6340582", "creation_timestamp": 1695125941.0, "exchange_order_id": "35694780", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:19:56,297 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b54394c6340582. [clock=2023-09-19 12:19:56+00:00] +2023-09-19 12:19:56,361 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12500000 amount: 80. +2023-09-19 12:19:56,362 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:19:56,956 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125996.0, "order_id": "x-XEKWYICXBSIUT605b54394c6340582", "exchange_order_id": "35694780", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:19:56,956 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b54394c6340582. +2023-09-19 12:19:58,966 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b546dcdaec0582 for 80.00000000 SEI-USDT. +2023-09-19 12:19:59,017 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695125998.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXBSIUT605b546dcdaec0582", "creation_timestamp": 1695125996.0, "exchange_order_id": "35694863", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:20:51,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b546dcdaec0582. [clock=2023-09-19 12:20:51+00:00] +2023-09-19 12:20:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12510000 amount: 80. +2023-09-19 12:20:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:20:51,235 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126051.0, "order_id": "x-XEKWYICXBSIUT605b546dcdaec0582", "exchange_order_id": "35694863", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:20:51,236 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b546dcdaec0582. +2023-09-19 12:20:51,642 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b54a1ee9cf0582 for 80.00000000 SEI-USDT. +2023-09-19 12:20:51,656 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126051.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b54a1ee9cf0582", "creation_timestamp": 1695126051.0, "exchange_order_id": "35695075", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:21:01,705 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b54a1ee9cf0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:21:01,706 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 12:21:01+00:00] +2023-09-19 12:21:01,729 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126061.0, "order_id": "x-XEKWYICXBSIUT605b54a1ee9cf0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5006227", "exchange_order_id": "35695075", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:21:01,746 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126061.0, "order_id": "x-XEKWYICXBSIUT605b54a1ee9cf0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35695075", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:21:01,746 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b54a1ee9cf0582 completely filled. +2023-09-19 12:21:46,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 12:21:46,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1261004273122206378212628947 amount: 80. +2023-09-19 12:21:46,404 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b54d680d7a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:21:46,419 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b54d680d7a0582", "creation_timestamp": 1695126106.0, "exchange_order_id": "35695585", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:21:46,564 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b54d680f930582 for 80.00000000 SEI-USDT. +2023-09-19 12:21:46,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126106.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12610000", "order_id": "x-XEKWYICXSSIUT605b54d680f930582", "creation_timestamp": 1695126106.0, "exchange_order_id": "35695587", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:21:55,591 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b54d680d7a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:21:55,592 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 12:21:55+00:00] +2023-09-19 12:21:55,616 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126115.0, "order_id": "x-XEKWYICXBSIUT605b54d680d7a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12520000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5006296", "exchange_order_id": "35695585", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:21:55,631 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126115.0, "order_id": "x-XEKWYICXBSIUT605b54d680d7a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0160000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35695585", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:21:55,631 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b54d680d7a0582 completely filled. +2023-09-19 12:22:41,205 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b54d680f930582. [clock=2023-09-19 12:22:41+00:00] +2023-09-19 12:22:41,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251765540818306500582543162 amount: 80. +2023-09-19 12:22:41,289 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1259400483704270211027172072 amount: 80. +2023-09-19 12:22:42,355 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126161.0, "order_id": "x-XEKWYICXSSIUT605b54d680f930582", "exchange_order_id": "35695587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:22:42,356 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b54d680f930582. +2023-09-19 12:22:43,669 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b550b16f350582 for 80.00000000 SEI-USDT. +2023-09-19 12:22:43,716 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126163.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b550b16f350582", "creation_timestamp": 1695126161.0, "exchange_order_id": "35695789", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:22:44,229 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b550b173530582 for 80.00000000 SEI-USDT. +2023-09-19 12:22:44,268 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126163.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12590000", "order_id": "x-XEKWYICXSSIUT605b550b173530582", "creation_timestamp": 1695126161.0, "exchange_order_id": "35695790", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:23:36,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b550b16f350582. [clock=2023-09-19 12:23:36+00:00] +2023-09-19 12:23:36,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b550b173530582. [clock=2023-09-19 12:23:36+00:00] +2023-09-19 12:23:36,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 12:23:36,260 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1260976529585364796376822401 amount: 80. +2023-09-19 12:23:37,253 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126217.0, "order_id": "x-XEKWYICXBSIUT605b550b16f350582", "exchange_order_id": "35695789", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:23:37,253 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b550b16f350582. +2023-09-19 12:23:37,983 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126217.0, "order_id": "x-XEKWYICXSSIUT605b550b173530582", "exchange_order_id": "35695790", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:23:37,983 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b550b173530582. +2023-09-19 12:23:38,271 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b553f83c6a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:23:38,306 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126217.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b553f83c6a0582", "creation_timestamp": 1695126216.0, "exchange_order_id": "35695891", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:23:38,307 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b553f83f940582 for 80.00000000 SEI-USDT. +2023-09-19 12:23:38,339 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126217.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12600000", "order_id": "x-XEKWYICXSSIUT605b553f83f940582", "creation_timestamp": 1695126216.0, "exchange_order_id": "35695890", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:24:31,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b553f83c6a0582. [clock=2023-09-19 12:24:31+00:00] +2023-09-19 12:24:31,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b553f83f940582. [clock=2023-09-19 12:24:31+00:00] +2023-09-19 12:24:31,192 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12520000 amount: 80. +2023-09-19 12:24:31,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258978165215826694741901479 amount: 80. +2023-09-19 12:24:31,782 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126271.0, "order_id": "x-XEKWYICXBSIUT605b553f83c6a0582", "exchange_order_id": "35695891", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:24:31,782 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b553f83c6a0582. +2023-09-19 12:24:32,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5573e73cf0582 for 80.00000000 SEI-USDT. +2023-09-19 12:24:32,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126272.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b5573e73cf0582", "creation_timestamp": 1695126271.0, "exchange_order_id": "35696112", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:24:32,578 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5573e70c20582 for 80.00000000 SEI-USDT. +2023-09-19 12:24:32,605 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126272.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXBSIUT605b5573e70c20582", "creation_timestamp": 1695126271.0, "exchange_order_id": "35696113", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:24:32,624 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126272.0, "order_id": "x-XEKWYICXSSIUT605b553f83f940582", "exchange_order_id": "35695890", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:24:32,624 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b553f83f940582. +2023-09-19 12:25:26,105 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5573e70c20582. [clock=2023-09-19 12:25:26+00:00] +2023-09-19 12:25:26,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5573e73cf0582. [clock=2023-09-19 12:25:26+00:00] +2023-09-19 12:25:26,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251708471655774466614701961 amount: 80. +2023-09-19 12:25:26,128 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257947068028786627590418573 amount: 80. +2023-09-19 12:25:26,411 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126326.0, "order_id": "x-XEKWYICXBSIUT605b5573e70c20582", "exchange_order_id": "35696113", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:25:26,412 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5573e70c20582. +2023-09-19 12:25:26,643 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126326.0, "order_id": "x-XEKWYICXSSIUT605b5573e73cf0582", "exchange_order_id": "35696112", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:25:26,643 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5573e73cf0582. +2023-09-19 12:25:26,647 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b55a84ae320582 for 80.00000000 SEI-USDT. +2023-09-19 12:25:26,672 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b55a84ae320582", "creation_timestamp": 1695126326.0, "exchange_order_id": "35696188", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:25:26,673 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b55a84b1c10582 for 80.00000000 SEI-USDT. +2023-09-19 12:25:26,690 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126326.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b55a84b1c10582", "creation_timestamp": 1695126326.0, "exchange_order_id": "35696189", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:26:21,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b55a84ae320582. [clock=2023-09-19 12:26:21+00:00] +2023-09-19 12:26:21,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b55a84b1c10582. [clock=2023-09-19 12:26:21+00:00] +2023-09-19 12:26:21,109 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251233192472774238091880776 amount: 80. +2023-09-19 12:26:21,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1258316251577776354483104620 amount: 80. +2023-09-19 12:26:21,313 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126381.0, "order_id": "x-XEKWYICXBSIUT605b55a84ae320582", "exchange_order_id": "35696188", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:26:21,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b55a84ae320582. +2023-09-19 12:26:21,549 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126381.0, "order_id": "x-XEKWYICXSSIUT605b55a84b1c10582", "exchange_order_id": "35696189", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:26:21,549 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b55a84b1c10582. +2023-09-19 12:26:21,875 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b55dcba3530582 for 80.00000000 SEI-USDT. +2023-09-19 12:26:21,889 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b55dcba3530582", "creation_timestamp": 1695126381.0, "exchange_order_id": "35696398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:26:21,890 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b55dcba67b0582 for 80.00000000 SEI-USDT. +2023-09-19 12:26:21,903 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126381.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12580000", "order_id": "x-XEKWYICXSSIUT605b55dcba67b0582", "creation_timestamp": 1695126381.0, "exchange_order_id": "35696399", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:27:16,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b55dcba3530582. [clock=2023-09-19 12:27:16+00:00] +2023-09-19 12:27:16,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b55dcba67b0582. [clock=2023-09-19 12:27:16+00:00] +2023-09-19 12:27:16,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251782930560455167468935396 amount: 80. +2023-09-19 12:27:16,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1257286527004996386875224576 amount: 80. +2023-09-19 12:27:16,748 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126436.0, "order_id": "x-XEKWYICXBSIUT605b55dcba3530582", "exchange_order_id": "35696398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:27:16,749 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b55dcba3530582. +2023-09-19 12:27:18,191 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126437.0, "order_id": "x-XEKWYICXSSIUT605b55dcba67b0582", "exchange_order_id": "35696399", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:27:18,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b55dcba67b0582. +2023-09-19 12:27:18,691 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5611372440582 for 80.00000000 SEI-USDT. +2023-09-19 12:27:18,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126438.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b5611372440582", "creation_timestamp": 1695126436.0, "exchange_order_id": "35696615", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:27:18,763 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5611374e90582 for 80.00000000 SEI-USDT. +2023-09-19 12:27:18,827 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126438.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12570000", "order_id": "x-XEKWYICXSSIUT605b5611374e90582", "creation_timestamp": 1695126436.0, "exchange_order_id": "35696616", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:28:11,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5611372440582. [clock=2023-09-19 12:28:11+00:00] +2023-09-19 12:28:11,112 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5611374e90582. [clock=2023-09-19 12:28:11+00:00] +2023-09-19 12:28:11,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251797304325461034163278297 amount: 80. +2023-09-19 12:28:11,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1256942459208465466905068993 amount: 80. +2023-09-19 12:28:12,361 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126491.0, "order_id": "x-XEKWYICXBSIUT605b5611372440582", "exchange_order_id": "35696615", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:28:12,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5611372440582. +2023-09-19 12:28:12,418 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126491.0, "order_id": "x-XEKWYICXSSIUT605b5611374e90582", "exchange_order_id": "35696616", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:28:12,419 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5611374e90582. +2023-09-19 12:28:13,174 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5645b470d0582 for 80.00000000 SEI-USDT. +2023-09-19 12:28:13,221 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126492.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b5645b470d0582", "creation_timestamp": 1695126491.0, "exchange_order_id": "35696765", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:28:13,628 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5645b8be90582 for 80.00000000 SEI-USDT. +2023-09-19 12:28:13,708 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126493.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12560000", "order_id": "x-XEKWYICXSSIUT605b5645b8be90582", "creation_timestamp": 1695126491.0, "exchange_order_id": "35696766", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:29:06,149 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5645b470d0582. [clock=2023-09-19 12:29:06+00:00] +2023-09-19 12:29:06,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5645b8be90582. [clock=2023-09-19 12:29:06+00:00] +2023-09-19 12:29:06,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1251843001523763168544203999 amount: 80. +2023-09-19 12:29:06,257 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1255843107930909680621212377 amount: 80. +2023-09-19 12:29:07,359 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126547.0, "order_id": "x-XEKWYICXBSIUT605b5645b470d0582", "exchange_order_id": "35696765", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:29:07,360 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5645b470d0582. +2023-09-19 12:29:09,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126549.0, "order_id": "x-XEKWYICXSSIUT605b5645b8be90582", "exchange_order_id": "35696766", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:29:09,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5645b8be90582. +2023-09-19 12:29:10,101 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b567a39c7e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:29:10,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126549.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12550000", "order_id": "x-XEKWYICXSSIUT605b567a39c7e0582", "creation_timestamp": 1695126546.0, "exchange_order_id": "35696874", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:29:10,178 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b567a397b80582 for 80.00000000 SEI-USDT. +2023-09-19 12:29:10,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126549.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12510000", "order_id": "x-XEKWYICXBSIUT605b567a397b80582", "creation_timestamp": 1695126546.0, "exchange_order_id": "35696875", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:29:46,226 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b567a397b80582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:29:46,227 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.13 [clock=2023-09-19 12:29:46+00:00] +2023-09-19 12:29:46,330 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126586.0, "order_id": "x-XEKWYICXBSIUT605b567a397b80582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12510000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5006420", "exchange_order_id": "35696875", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:29:46,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126586.0, "order_id": "x-XEKWYICXBSIUT605b567a397b80582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "10.0080000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35696875", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:29:46,511 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b567a397b80582 completely filled. +2023-09-19 12:30:01,210 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b567a39c7e0582. [clock=2023-09-19 12:30:01+00:00] +2023-09-19 12:30:01,283 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1247616409777222439664872479 amount: 80. +2023-09-19 12:30:01,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1253834684321628587903194775 amount: 80. +2023-09-19 12:30:01,963 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126601.0, "order_id": "x-XEKWYICXSSIUT605b567a39c7e0582", "exchange_order_id": "35696874", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:30:01,963 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b567a39c7e0582. +2023-09-19 12:30:02,877 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b56aeb3dd40582 for 80.00000000 SEI-USDT. +2023-09-19 12:30:02,923 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126602.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXBSIUT605b56aeb3dd40582", "creation_timestamp": 1695126601.0, "exchange_order_id": "35697248", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:30:03,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b56aeb41730582 for 80.00000000 SEI-USDT. +2023-09-19 12:30:03,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126602.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12530000", "order_id": "x-XEKWYICXSSIUT605b56aeb41730582", "creation_timestamp": 1695126601.0, "exchange_order_id": "35697251", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:30:56,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b56aeb3dd40582. [clock=2023-09-19 12:30:56+00:00] +2023-09-19 12:30:56,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b56aeb41730582. [clock=2023-09-19 12:30:56+00:00] +2023-09-19 12:30:56,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244828427291014728196571416 amount: 80. +2023-09-19 12:30:56,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252998692180978763172168774 amount: 80. +2023-09-19 12:30:56,367 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126656.0, "order_id": "x-XEKWYICXBSIUT605b56aeb3dd40582", "exchange_order_id": "35697248", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:30:56,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b56aeb3dd40582. +2023-09-19 12:30:56,586 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b56e2f30b00582 for 80.00000000 SEI-USDT. +2023-09-19 12:30:56,611 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b56e2f30b00582", "creation_timestamp": 1695126656.0, "exchange_order_id": "35697495", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:30:56,611 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b56e2f33720582 for 80.00000000 SEI-USDT. +2023-09-19 12:30:56,634 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126656.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b56e2f33720582", "creation_timestamp": 1695126656.0, "exchange_order_id": "35697496", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:30:56,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126656.0, "order_id": "x-XEKWYICXSSIUT605b56aeb41730582", "exchange_order_id": "35697251", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:30:56,740 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b56aeb41730582. +2023-09-19 12:31:51,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b56e2f30b00582. [clock=2023-09-19 12:31:51+00:00] +2023-09-19 12:31:51,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b56e2f33720582. [clock=2023-09-19 12:31:51+00:00] +2023-09-19 12:31:51,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1246901454121627500761718531 amount: 80. +2023-09-19 12:31:51,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1254371457047577683518856399 amount: 80. +2023-09-19 12:31:51,320 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126711.0, "order_id": "x-XEKWYICXBSIUT605b56e2f30b00582", "exchange_order_id": "35697495", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:31:51,320 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b56e2f30b00582. +2023-09-19 12:31:51,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126711.0, "order_id": "x-XEKWYICXSSIUT605b56e2f33720582", "exchange_order_id": "35697496", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:31:51,555 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b56e2f33720582. +2023-09-19 12:31:51,556 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b57176fad10582 for 80.00000000 SEI-USDT. +2023-09-19 12:31:51,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12540000", "order_id": "x-XEKWYICXSSIUT605b57176fad10582", "creation_timestamp": 1695126711.0, "exchange_order_id": "35697668", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:31:51,674 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b57176f4da0582 for 80.00000000 SEI-USDT. +2023-09-19 12:31:51,698 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126711.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXBSIUT605b57176f4da0582", "creation_timestamp": 1695126711.0, "exchange_order_id": "35697669", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:32:20,805 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b57176f4da0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:32:20,806 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:32:20+00:00] +2023-09-19 12:32:20,859 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126740.0, "order_id": "x-XEKWYICXBSIUT605b57176f4da0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5006609", "exchange_order_id": "35697669", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:32:20,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126740.0, "order_id": "x-XEKWYICXBSIUT605b57176f4da0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35697669", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:32:20,969 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b57176f4da0582 completely filled. +2023-09-19 12:32:46,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b57176fad10582. [clock=2023-09-19 12:32:46+00:00] +2023-09-19 12:32:46,308 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236641950492444928643953938 amount: 80. +2023-09-19 12:32:46,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12480000 amount: 80. +2023-09-19 12:32:46,978 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126766.0, "order_id": "x-XEKWYICXSSIUT605b57176fad10582", "exchange_order_id": "35697668", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:32:46,978 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b57176fad10582. +2023-09-19 12:32:48,359 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b574c184dc0582 for 80.00000000 SEI-USDT. +2023-09-19 12:32:48,387 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b574c184dc0582", "creation_timestamp": 1695126766.0, "exchange_order_id": "35698490", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:32:48,388 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b574c184dc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:32:48,389 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:32:48+00:00] +2023-09-19 12:32:48,427 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126768.0, "order_id": "x-XEKWYICXSSIUT605b574c184dc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12480000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00998400"}]}, "exchange_trade_id": "5006666", "exchange_order_id": "35698490", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:32:48,428 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b574c1509e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:32:48,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126768.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b574c1509e0582", "creation_timestamp": 1695126766.0, "exchange_order_id": "35698489", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:32:48,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126768.0, "order_id": "x-XEKWYICXSSIUT605b574c184dc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9840000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35698490", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:32:48,553 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b574c184dc0582 completely filled. +2023-09-19 12:33:41,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b574c1509e0582. [clock=2023-09-19 12:33:41+00:00] +2023-09-19 12:33:41,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237000410104618685616325299 amount: 80. +2023-09-19 12:33:41,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247836930281482664459452861 amount: 80. +2023-09-19 12:33:41,602 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126821.0, "order_id": "x-XEKWYICXBSIUT605b574c1509e0582", "exchange_order_id": "35698489", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:33:41,603 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b574c1509e0582. +2023-09-19 12:33:42,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5780533200582 for 80.00000000 SEI-USDT. +2023-09-19 12:33:42,217 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126822.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b5780533200582", "creation_timestamp": 1695126821.0, "exchange_order_id": "35699046", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:33:42,515 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b57805372d0582 for 80.00000000 SEI-USDT. +2023-09-19 12:33:42,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126822.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b57805372d0582", "creation_timestamp": 1695126821.0, "exchange_order_id": "35699048", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:33:51,554 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b57805372d0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:33:51,555 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:33:51+00:00] +2023-09-19 12:33:51,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126831.0, "order_id": "x-XEKWYICXSSIUT605b57805372d0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12470000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00997600"}]}, "exchange_trade_id": "5006710", "exchange_order_id": "35699048", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:33:51,704 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126831.0, "order_id": "x-XEKWYICXSSIUT605b57805372d0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9760000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35699048", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:33:51,705 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b57805372d0582 completely filled. +2023-09-19 12:34:36,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5780533200582. [clock=2023-09-19 12:34:36+00:00] +2023-09-19 12:34:36,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240068539763385210875630538 amount: 80. +2023-09-19 12:34:36,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249615801349293425949966409 amount: 80. +2023-09-19 12:34:36,666 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126876.0, "order_id": "x-XEKWYICXBSIUT605b5780533200582", "exchange_order_id": "35699046", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:34:36,666 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5780533200582. +2023-09-19 12:34:38,163 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b57b4eb6860582 for 80.00000000 SEI-USDT. +2023-09-19 12:34:38,210 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126877.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b57b4eb6860582", "creation_timestamp": 1695126876.0, "exchange_order_id": "35699652", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:34:38,718 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b57b4eb9800582 for 80.00000000 SEI-USDT. +2023-09-19 12:34:38,774 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126878.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b57b4eb9800582", "creation_timestamp": 1695126876.0, "exchange_order_id": "35699655", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:35:31,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b57b4eb6860582. [clock=2023-09-19 12:35:31+00:00] +2023-09-19 12:35:31,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b57b4eb9800582. [clock=2023-09-19 12:35:31+00:00] +2023-09-19 12:35:31,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240612465429296613191846648 amount: 80. +2023-09-19 12:35:31,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248035020841437369028906828 amount: 80. +2023-09-19 12:35:31,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126931.0, "order_id": "x-XEKWYICXBSIUT605b57b4eb6860582", "exchange_order_id": "35699652", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:35:31,259 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b57b4eb6860582. +2023-09-19 12:35:31,477 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b57e9320700582 for 80.00000000 SEI-USDT. +2023-09-19 12:35:31,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b57e9320700582", "creation_timestamp": 1695126931.0, "exchange_order_id": "35699986", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:35:31,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126931.0, "order_id": "x-XEKWYICXSSIUT605b57b4eb9800582", "exchange_order_id": "35699655", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:35:31,500 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b57b4eb9800582. +2023-09-19 12:35:31,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b57e9323a30582 for 80.00000000 SEI-USDT. +2023-09-19 12:35:31,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126931.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b57e9323a30582", "creation_timestamp": 1695126931.0, "exchange_order_id": "35699987", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:36:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b57e9320700582. [clock=2023-09-19 12:36:26+00:00] +2023-09-19 12:36:26,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b57e9323a30582. [clock=2023-09-19 12:36:26+00:00] +2023-09-19 12:36:26,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240184807500050582955696292 amount: 80. +2023-09-19 12:36:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246821194199350351375264020 amount: 80. +2023-09-19 12:36:26,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126986.0, "order_id": "x-XEKWYICXBSIUT605b57e9320700582", "exchange_order_id": "35699986", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:36:26,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b57e9320700582. +2023-09-19 12:36:26,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b581d9e2690582 for 80.00000000 SEI-USDT. +2023-09-19 12:36:26,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b581d9e2690582", "creation_timestamp": 1695126986.0, "exchange_order_id": "35700391", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:36:26,528 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126986.0, "order_id": "x-XEKWYICXSSIUT605b57e9323a30582", "exchange_order_id": "35699987", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:36:26,528 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b57e9323a30582. +2023-09-19 12:36:26,529 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b581d9e46b0582 for 80.00000000 SEI-USDT. +2023-09-19 12:36:26,545 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695126986.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b581d9e46b0582", "creation_timestamp": 1695126986.0, "exchange_order_id": "35700392", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:36:49,119 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b581d9e46b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:36:49,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:36:49+00:00] +2023-09-19 12:36:49,144 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127009.0, "order_id": "x-XEKWYICXSSIUT605b581d9e46b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12460000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996800"}]}, "exchange_trade_id": "5006785", "exchange_order_id": "35700392", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:36:49,157 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127009.0, "order_id": "x-XEKWYICXSSIUT605b581d9e46b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9680000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35700392", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:36:49,157 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b581d9e46b0582 completely filled. +2023-09-19 12:37:21,088 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b581d9e2690582. [clock=2023-09-19 12:37:21+00:00] +2023-09-19 12:37:21,135 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1244319971696340422326940781 amount: 80. +2023-09-19 12:37:21,136 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1250605412968078483616554245 amount: 80. +2023-09-19 12:37:21,791 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127041.0, "order_id": "x-XEKWYICXBSIUT605b581d9e2690582", "exchange_order_id": "35700391", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:37:21,792 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b581d9e2690582. +2023-09-19 12:37:22,575 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b58522d7bb0582 for 80.00000000 SEI-USDT. +2023-09-19 12:37:22,601 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127042.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b58522d7bb0582", "creation_timestamp": 1695127041.0, "exchange_order_id": "35700801", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:37:22,602 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b58522fa9f0582 for 80.00000000 SEI-USDT. +2023-09-19 12:37:22,631 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127042.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12500000", "order_id": "x-XEKWYICXSSIUT605b58522fa9f0582", "creation_timestamp": 1695127041.0, "exchange_order_id": "35700802", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:38:15,467 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b58522d7bb0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:38:15,469 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:38:15+00:00] +2023-09-19 12:38:15,585 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127095.0, "order_id": "x-XEKWYICXBSIUT605b58522d7bb0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5006886", "exchange_order_id": "35700801", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:38:15,845 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127095.0, "order_id": "x-XEKWYICXBSIUT605b58522d7bb0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35700801", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:38:15,845 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b58522d7bb0582 completely filled. +2023-09-19 12:38:16,475 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b58522fa9f0582. [clock=2023-09-19 12:38:16+00:00] +2023-09-19 12:38:16,560 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239836186835773317307719121 amount: 80. +2023-09-19 12:38:16,570 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245576652873870675897721121 amount: 80. +2023-09-19 12:38:17,659 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127097.0, "order_id": "x-XEKWYICXSSIUT605b58522fa9f0582", "exchange_order_id": "35700802", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:38:17,659 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b58522fa9f0582. +2023-09-19 12:38:19,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b58870b57a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:38:19,503 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127099.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b58870b57a0582", "creation_timestamp": 1695127096.0, "exchange_order_id": "35701354", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:38:19,504 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b58870b57a0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:38:19,504 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:38:19+00:00] +2023-09-19 12:38:19,600 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127099.0, "order_id": "x-XEKWYICXSSIUT605b58870b57a0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12450000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00996000"}]}, "exchange_trade_id": "5006912", "exchange_order_id": "35701354", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:38:19,601 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b58870b1cd0582 for 80.00000000 SEI-USDT. +2023-09-19 12:38:19,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127099.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b58870b1cd0582", "creation_timestamp": 1695127096.0, "exchange_order_id": "35701338", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:38:19,892 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127099.0, "order_id": "x-XEKWYICXSSIUT605b58870b57a0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9600000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35701354", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:38:19,892 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b58870b57a0582 completely filled. +2023-09-19 12:39:11,574 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b58870b1cd0582. [clock=2023-09-19 12:39:11+00:00] +2023-09-19 12:39:11,650 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241149441092999664800169664 amount: 80. +2023-09-19 12:39:11,652 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247347139143123860738909510 amount: 80. +2023-09-19 12:39:12,717 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127152.0, "order_id": "x-XEKWYICXBSIUT605b58870b1cd0582", "exchange_order_id": "35701338", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:39:12,717 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b58870b1cd0582. +2023-09-19 12:39:14,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b58bb92d090582 for 80.00000000 SEI-USDT. +2023-09-19 12:39:14,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127154.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b58bb92d090582", "creation_timestamp": 1695127151.0, "exchange_order_id": "35701799", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:39:14,775 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b58bb931510582 for 80.00000000 SEI-USDT. +2023-09-19 12:39:14,822 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127154.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b58bb931510582", "creation_timestamp": 1695127151.0, "exchange_order_id": "35701802", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:40:06,324 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b58bb92d090582. [clock=2023-09-19 12:40:06+00:00] +2023-09-19 12:40:06,325 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b58bb931510582. [clock=2023-09-19 12:40:06+00:00] +2023-09-19 12:40:06,430 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1243367883085647366655075795 amount: 80. +2023-09-19 12:40:06,431 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:40:07,197 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127206.0, "order_id": "x-XEKWYICXBSIUT605b58bb92d090582", "exchange_order_id": "35701799", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:40:07,197 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b58bb92d090582. +2023-09-19 12:40:07,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127206.0, "order_id": "x-XEKWYICXSSIUT605b58bb931510582", "exchange_order_id": "35701802", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:40:07,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b58bb931510582. +2023-09-19 12:40:08,769 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b58efd0c200582 for 80.00000000 SEI-USDT. +2023-09-19 12:40:08,789 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127208.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b58efd0c200582", "creation_timestamp": 1695127206.0, "exchange_order_id": "35701937", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:41:01,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b58efd0c200582. [clock=2023-09-19 12:41:01+00:00] +2023-09-19 12:41:01,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239086651419948771280790907 amount: 80. +2023-09-19 12:41:01,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246990301558343478423873733 amount: 80. +2023-09-19 12:41:01,351 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127261.0, "order_id": "x-XEKWYICXBSIUT605b58efd0c200582", "exchange_order_id": "35701937", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:41:01,352 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b58efd0c200582. +2023-09-19 12:41:01,557 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5923f3f800582 for 80.00000000 SEI-USDT. +2023-09-19 12:41:01,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5923f3f800582", "creation_timestamp": 1695127261.0, "exchange_order_id": "35702425", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:41:01,577 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5923f42750582 for 80.00000000 SEI-USDT. +2023-09-19 12:41:01,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127261.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b5923f42750582", "creation_timestamp": 1695127261.0, "exchange_order_id": "35702426", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:41:56,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5923f3f800582. [clock=2023-09-19 12:41:56+00:00] +2023-09-19 12:41:56,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5923f42750582. [clock=2023-09-19 12:41:56+00:00] +2023-09-19 12:41:56,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238493037108301373359395409 amount: 80. +2023-09-19 12:41:56,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:41:56,364 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127316.0, "order_id": "x-XEKWYICXBSIUT605b5923f3f800582", "exchange_order_id": "35702425", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:41:56,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5923f3f800582. +2023-09-19 12:41:56,615 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b59585b6cb0582 for 80.00000000 SEI-USDT. +2023-09-19 12:41:56,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127316.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b59585b6cb0582", "creation_timestamp": 1695127316.0, "exchange_order_id": "35702685", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:41:56,747 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127316.0, "order_id": "x-XEKWYICXSSIUT605b5923f42750582", "exchange_order_id": "35702426", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:41:56,747 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5923f42750582. +2023-09-19 12:42:51,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b59585b6cb0582. [clock=2023-09-19 12:42:51+00:00] +2023-09-19 12:42:51,214 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235425327043238739234535079 amount: 80. +2023-09-19 12:42:51,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244392654068434679353487429 amount: 80. +2023-09-19 12:42:52,095 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127371.0, "order_id": "x-XEKWYICXBSIUT605b59585b6cb0582", "exchange_order_id": "35702685", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:42:52,096 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b59585b6cb0582. +2023-09-19 12:42:52,993 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 12:42:53,800 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b598cfa46c0582 for 80.00000000 SEI-USDT. +2023-09-19 12:42:53,876 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127373.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b598cfa46c0582", "creation_timestamp": 1695127371.0, "exchange_order_id": "35703250", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:42:54,252 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b598cfa8250582 for 80.00000000 SEI-USDT. +2023-09-19 12:42:54,317 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127373.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b598cfa8250582", "creation_timestamp": 1695127371.0, "exchange_order_id": "35703251", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:43:46,354 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b598cfa46c0582. [clock=2023-09-19 12:43:46+00:00] +2023-09-19 12:43:46,355 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b598cfa8250582. [clock=2023-09-19 12:43:46+00:00] +2023-09-19 12:43:46,421 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237972633756069118228641018 amount: 80. +2023-09-19 12:43:46,422 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:43:47,572 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127426.0, "order_id": "x-XEKWYICXBSIUT605b598cfa46c0582", "exchange_order_id": "35703250", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:43:47,573 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b598cfa46c0582. +2023-09-19 12:43:49,124 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127428.0, "order_id": "x-XEKWYICXSSIUT605b598cfa8250582", "exchange_order_id": "35703251", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:43:49,125 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b598cfa8250582. +2023-09-19 12:43:49,928 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b59c19d7e80582 for 80.00000000 SEI-USDT. +2023-09-19 12:43:49,987 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127429.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b59c19d7e80582", "creation_timestamp": 1695127426.0, "exchange_order_id": "35703676", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:44:41,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b59c19d7e80582. [clock=2023-09-19 12:44:41+00:00] +2023-09-19 12:44:41,334 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235712585342024263913215499 amount: 80. +2023-09-19 12:44:41,335 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244218877412273587372277143 amount: 80. +2023-09-19 12:44:41,891 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127481.0, "order_id": "x-XEKWYICXBSIUT605b59c19d7e80582", "exchange_order_id": "35703676", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:44:41,892 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b59c19d7e80582. +2023-09-19 12:44:43,224 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b59f5fbdf20582 for 80.00000000 SEI-USDT. +2023-09-19 12:44:43,248 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127483.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b59f5fbdf20582", "creation_timestamp": 1695127481.0, "exchange_order_id": "35703891", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:44:43,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b59f5fc1900582 for 80.00000000 SEI-USDT. +2023-09-19 12:44:43,489 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127483.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b59f5fc1900582", "creation_timestamp": 1695127481.0, "exchange_order_id": "35703892", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:45:36,005 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b59f5fbdf20582. [clock=2023-09-19 12:45:36+00:00] +2023-09-19 12:45:36,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b59f5fc1900582. [clock=2023-09-19 12:45:36+00:00] +2023-09-19 12:45:36,027 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233663122258630720951926056 amount: 80. +2023-09-19 12:45:36,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:45:36,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127536.0, "order_id": "x-XEKWYICXBSIUT605b59f5fbdf20582", "exchange_order_id": "35703891", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:45:36,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b59f5fbdf20582. +2023-09-19 12:45:36,621 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127536.0, "order_id": "x-XEKWYICXSSIUT605b59f5fc1900582", "exchange_order_id": "35703892", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:45:36,621 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b59f5fc1900582. +2023-09-19 12:45:36,624 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5a2a24b420582 for 80.00000000 SEI-USDT. +2023-09-19 12:45:36,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127536.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605b5a2a24b420582", "creation_timestamp": 1695127536.0, "exchange_order_id": "35704375", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:46:31,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5a2a24b420582. [clock=2023-09-19 12:46:31+00:00] +2023-09-19 12:46:31,206 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234311249727335713618701717 amount: 80. +2023-09-19 12:46:31,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241860448122784222803524721 amount: 80. +2023-09-19 12:46:31,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127591.0, "order_id": "x-XEKWYICXBSIUT605b5a2a24b420582", "exchange_order_id": "35704375", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:46:31,480 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5a2a24b420582. +2023-09-19 12:46:31,685 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5a5ec432a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:46:31,698 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b5a5ec432a0582", "creation_timestamp": 1695127591.0, "exchange_order_id": "35704575", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:46:31,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5a5ec46700582 for 80.00000000 SEI-USDT. +2023-09-19 12:46:31,713 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127591.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b5a5ec46700582", "creation_timestamp": 1695127591.0, "exchange_order_id": "35704576", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:47:26,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5a5ec432a0582. [clock=2023-09-19 12:47:26+00:00] +2023-09-19 12:47:26,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5a5ec46700582. [clock=2023-09-19 12:47:26+00:00] +2023-09-19 12:47:26,081 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234127007797809863078326023 amount: 80. +2023-09-19 12:47:26,082 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:47:26,697 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127646.0, "order_id": "x-XEKWYICXBSIUT605b5a5ec432a0582", "exchange_order_id": "35704575", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:47:26,698 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5a5ec432a0582. +2023-09-19 12:47:27,458 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127647.0, "order_id": "x-XEKWYICXSSIUT605b5a5ec46700582", "exchange_order_id": "35704576", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:47:27,458 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5a5ec46700582. +2023-09-19 12:47:27,640 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5a93194760582 for 80.00000000 SEI-USDT. +2023-09-19 12:47:27,675 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127647.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b5a93194760582", "creation_timestamp": 1695127646.0, "exchange_order_id": "35705276", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:48:21,214 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5a93194760582. [clock=2023-09-19 12:48:21+00:00] +2023-09-19 12:48:21,285 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236400541441899880879590751 amount: 80. +2023-09-19 12:48:21,294 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245352894861709467046974330 amount: 80. +2023-09-19 12:48:22,112 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127701.0, "order_id": "x-XEKWYICXBSIUT605b5a93194760582", "exchange_order_id": "35705276", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:48:22,112 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5a93194760582. +2023-09-19 12:48:24,192 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5ac7c0c700582 for 80.00000000 SEI-USDT. +2023-09-19 12:48:24,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127703.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5ac7c0c700582", "creation_timestamp": 1695127701.0, "exchange_order_id": "35705747", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:48:24,822 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5ac7c0e200582 for 80.00000000 SEI-USDT. +2023-09-19 12:48:24,888 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127704.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b5ac7c0e200582", "creation_timestamp": 1695127701.0, "exchange_order_id": "35705749", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:49:16,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5ac7c0c700582. [clock=2023-09-19 12:49:16+00:00] +2023-09-19 12:49:16,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5ac7c0e200582. [clock=2023-09-19 12:49:16+00:00] +2023-09-19 12:49:16,250 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239258988177635345981631286 amount: 80. +2023-09-19 12:49:16,260 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:49:17,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127756.0, "order_id": "x-XEKWYICXBSIUT605b5ac7c0c700582", "exchange_order_id": "35705747", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:49:17,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5ac7c0c700582. +2023-09-19 12:49:18,783 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127758.0, "order_id": "x-XEKWYICXSSIUT605b5ac7c0e200582", "exchange_order_id": "35705749", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:49:18,783 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5ac7c0e200582. +2023-09-19 12:49:19,351 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5afc2c29b0582 for 80.00000000 SEI-USDT. +2023-09-19 12:49:19,397 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127758.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5afc2c29b0582", "creation_timestamp": 1695127756.0, "exchange_order_id": "35706193", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:50:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5afc2c29b0582. [clock=2023-09-19 12:50:11+00:00] +2023-09-19 12:50:11,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239887342719770197491052386 amount: 80. +2023-09-19 12:50:11,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247035935563930825512672766 amount: 80. +2023-09-19 12:50:11,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127811.0, "order_id": "x-XEKWYICXBSIUT605b5afc2c29b0582", "exchange_order_id": "35706193", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:50:11,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5afc2c29b0582. +2023-09-19 12:50:11,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5b306c1a10582 for 80.00000000 SEI-USDT. +2023-09-19 12:50:11,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5b306c1a10582", "creation_timestamp": 1695127811.0, "exchange_order_id": "35706333", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:50:11,364 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5b306c4470582 for 80.00000000 SEI-USDT. +2023-09-19 12:50:11,379 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127811.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b5b306c4470582", "creation_timestamp": 1695127811.0, "exchange_order_id": "35706334", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:51:06,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5b306c1a10582. [clock=2023-09-19 12:51:06+00:00] +2023-09-19 12:51:06,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5b306c4470582. [clock=2023-09-19 12:51:06+00:00] +2023-09-19 12:51:06,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241129017665323513300595416 amount: 80. +2023-09-19 12:51:06,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 12:51:06,272 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127866.0, "order_id": "x-XEKWYICXBSIUT605b5b306c1a10582", "exchange_order_id": "35706333", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:51:06,272 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5b306c1a10582. +2023-09-19 12:51:06,499 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127866.0, "order_id": "x-XEKWYICXSSIUT605b5b306c4470582", "exchange_order_id": "35706334", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:51:06,499 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5b306c4470582. +2023-09-19 12:51:06,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5b64d9ef10582 for 80.00000000 SEI-USDT. +2023-09-19 12:51:06,515 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127866.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b5b64d9ef10582", "creation_timestamp": 1695127866.0, "exchange_order_id": "35706874", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:51:17,220 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b5b64d9ef10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 12:51:17,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 12:51:17+00:00] +2023-09-19 12:51:17,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127877.0, "order_id": "x-XEKWYICXBSIUT605b5b64d9ef10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5007456", "exchange_order_id": "35706874", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 12:51:17,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127877.0, "order_id": "x-XEKWYICXBSIUT605b5b64d9ef10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35706874", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 12:51:17,281 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b5b64d9ef10582 completely filled. +2023-09-19 12:52:01,099 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239306908419670310123577216 amount: 80. +2023-09-19 12:52:01,100 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249696086978823883094100386 amount: 80. +2023-09-19 12:52:01,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5b99606b00582 for 80.00000000 SEI-USDT. +2023-09-19 12:52:01,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5b99606b00582", "creation_timestamp": 1695127921.0, "exchange_order_id": "35707315", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:52:01,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5b996092e0582 for 80.00000000 SEI-USDT. +2023-09-19 12:52:01,658 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127921.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b5b996092e0582", "creation_timestamp": 1695127921.0, "exchange_order_id": "35707316", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:52:56,160 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5b99606b00582. [clock=2023-09-19 12:52:56+00:00] +2023-09-19 12:52:56,162 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5b996092e0582. [clock=2023-09-19 12:52:56+00:00] +2023-09-19 12:52:56,212 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239617917668555686131102099 amount: 80. +2023-09-19 12:52:56,213 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248809613230918670405596627 amount: 80. +2023-09-19 12:52:56,674 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127976.0, "order_id": "x-XEKWYICXBSIUT605b5b99606b00582", "exchange_order_id": "35707315", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:52:56,675 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5b99606b00582. +2023-09-19 12:52:57,666 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127977.0, "order_id": "x-XEKWYICXSSIUT605b5b996092e0582", "exchange_order_id": "35707316", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:52:57,666 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5b996092e0582. +2023-09-19 12:52:57,968 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5bcdeff8b0582 for 80.00000000 SEI-USDT. +2023-09-19 12:52:58,004 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127977.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b5bcdeff8b0582", "creation_timestamp": 1695127976.0, "exchange_order_id": "35707681", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:52:58,006 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5bcdefc970582 for 80.00000000 SEI-USDT. +2023-09-19 12:52:58,033 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695127977.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5bcdefc970582", "creation_timestamp": 1695127976.0, "exchange_order_id": "35707682", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:53:51,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5bcdefc970582. [clock=2023-09-19 12:53:51+00:00] +2023-09-19 12:53:51,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5bcdeff8b0582. [clock=2023-09-19 12:53:51+00:00] +2023-09-19 12:53:51,385 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240148795161839788194849716 amount: 80. +2023-09-19 12:53:51,387 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247295065687860605355458638 amount: 80. +2023-09-19 12:53:53,644 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128033.0, "order_id": "x-XEKWYICXBSIUT605b5bcdefc970582", "exchange_order_id": "35707682", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:53:53,657 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5bcdefc970582. +2023-09-19 12:53:53,977 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5c028e1720582 for 80.00000000 SEI-USDT. +2023-09-19 12:53:54,035 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128033.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b5c028e1720582", "creation_timestamp": 1695128031.0, "exchange_order_id": "35707816", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:53:54,108 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128033.0, "order_id": "x-XEKWYICXSSIUT605b5bcdeff8b0582", "exchange_order_id": "35707681", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:53:54,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5bcdeff8b0582. +2023-09-19 12:53:54,109 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5c028dd630582 for 80.00000000 SEI-USDT. +2023-09-19 12:53:54,151 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128033.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b5c028dd630582", "creation_timestamp": 1695128031.0, "exchange_order_id": "35707817", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:54:46,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5c028dd630582. [clock=2023-09-19 12:54:46+00:00] +2023-09-19 12:54:46,246 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5c028e1720582. [clock=2023-09-19 12:54:46+00:00] +2023-09-19 12:54:46,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240857175075364113578324209 amount: 80. +2023-09-19 12:54:46,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247525185172106131418961554 amount: 80. +2023-09-19 12:54:47,165 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128086.0, "order_id": "x-XEKWYICXBSIUT605b5c028dd630582", "exchange_order_id": "35707817", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:54:47,165 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5c028dd630582. +2023-09-19 12:54:49,576 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128089.0, "order_id": "x-XEKWYICXSSIUT605b5c028e1720582", "exchange_order_id": "35707816", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:54:49,577 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5c028e1720582. +2023-09-19 12:54:50,164 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5c36f47410582 for 80.00000000 SEI-USDT. +2023-09-19 12:54:50,232 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128089.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b5c36f47410582", "creation_timestamp": 1695128086.0, "exchange_order_id": "35707912", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:54:50,234 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5c36f43230582 for 80.00000000 SEI-USDT. +2023-09-19 12:54:50,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128089.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b5c36f43230582", "creation_timestamp": 1695128086.0, "exchange_order_id": "35707911", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:55:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5c36f43230582. [clock=2023-09-19 12:55:41+00:00] +2023-09-19 12:55:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5c36f47410582. [clock=2023-09-19 12:55:41+00:00] +2023-09-19 12:55:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239372466908500919129030454 amount: 80. +2023-09-19 12:55:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245667477181148366366247784 amount: 80. +2023-09-19 12:55:41,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128141.0, "order_id": "x-XEKWYICXBSIUT605b5c36f43230582", "exchange_order_id": "35707911", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:55:41,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5c36f43230582. +2023-09-19 12:55:41,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128141.0, "order_id": "x-XEKWYICXSSIUT605b5c36f47410582", "exchange_order_id": "35707912", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:55:41,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5c36f47410582. +2023-09-19 12:55:41,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5c6b1ce6a0582 for 80.00000000 SEI-USDT. +2023-09-19 12:55:41,551 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b5c6b1ce6a0582", "creation_timestamp": 1695128141.0, "exchange_order_id": "35708139", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:55:41,551 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5c6b1cc9f0582 for 80.00000000 SEI-USDT. +2023-09-19 12:55:41,566 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128141.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b5c6b1cc9f0582", "creation_timestamp": 1695128141.0, "exchange_order_id": "35708140", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:56:36,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5c6b1cc9f0582. [clock=2023-09-19 12:56:36+00:00] +2023-09-19 12:56:36,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5c6b1ce6a0582. [clock=2023-09-19 12:56:36+00:00] +2023-09-19 12:56:36,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240734522359238311376160374 amount: 80. +2023-09-19 12:56:36,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245633257612351520304740121 amount: 80. +2023-09-19 12:56:36,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128196.0, "order_id": "x-XEKWYICXBSIUT605b5c6b1cc9f0582", "exchange_order_id": "35708140", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:56:36,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5c6b1cc9f0582. +2023-09-19 12:56:36,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128196.0, "order_id": "x-XEKWYICXSSIUT605b5c6b1ce6a0582", "exchange_order_id": "35708139", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:56:36,524 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5c6b1ce6a0582. +2023-09-19 12:56:36,524 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5c9f9870b0582 for 80.00000000 SEI-USDT. +2023-09-19 12:56:36,536 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b5c9f9870b0582", "creation_timestamp": 1695128196.0, "exchange_order_id": "35708260", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:56:36,539 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5c9f984cb0582 for 80.00000000 SEI-USDT. +2023-09-19 12:56:36,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128196.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b5c9f984cb0582", "creation_timestamp": 1695128196.0, "exchange_order_id": "35708261", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:57:31,162 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5c9f984cb0582. [clock=2023-09-19 12:57:31+00:00] +2023-09-19 12:57:31,180 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5c9f9870b0582. [clock=2023-09-19 12:57:31+00:00] +2023-09-19 12:57:31,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242173034971022864498949207 amount: 80. +2023-09-19 12:57:31,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246849410087816927916418353 amount: 80. +2023-09-19 12:57:32,534 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128251.0, "order_id": "x-XEKWYICXBSIUT605b5c9f984cb0582", "exchange_order_id": "35708261", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:57:32,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5c9f984cb0582. +2023-09-19 12:57:33,862 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128253.0, "order_id": "x-XEKWYICXSSIUT605b5c9f9870b0582", "exchange_order_id": "35708260", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:57:33,862 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5c9f9870b0582. +2023-09-19 12:57:34,341 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5cd4428630582 for 80.00000000 SEI-USDT. +2023-09-19 12:57:34,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128254.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b5cd4428630582", "creation_timestamp": 1695128251.0, "exchange_order_id": "35708533", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:57:34,371 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5cd4424410582 for 80.00000000 SEI-USDT. +2023-09-19 12:57:34,394 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128254.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b5cd4424410582", "creation_timestamp": 1695128251.0, "exchange_order_id": "35708532", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:58:26,167 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5cd4424410582. [clock=2023-09-19 12:58:26+00:00] +2023-09-19 12:58:26,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5cd4428630582. [clock=2023-09-19 12:58:26+00:00] +2023-09-19 12:58:26,251 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240837494786478292569525705 amount: 80. +2023-09-19 12:58:26,264 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245338395467166309691772253 amount: 80. +2023-09-19 12:58:27,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128307.0, "order_id": "x-XEKWYICXBSIUT605b5cd4424410582", "exchange_order_id": "35708532", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:58:27,557 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5cd4424410582. +2023-09-19 12:58:28,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128308.0, "order_id": "x-XEKWYICXSSIUT605b5cd4428630582", "exchange_order_id": "35708533", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:58:28,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5cd4428630582. +2023-09-19 12:58:28,696 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5d08b2ac50582 for 80.00000000 SEI-USDT. +2023-09-19 12:58:28,742 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128308.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b5d08b2ac50582", "creation_timestamp": 1695128306.0, "exchange_order_id": "35708729", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:58:28,742 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5d08b2cfa0582 for 80.00000000 SEI-USDT. +2023-09-19 12:58:28,769 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128308.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b5d08b2cfa0582", "creation_timestamp": 1695128306.0, "exchange_order_id": "35708728", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:59:21,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5d08b2ac50582. [clock=2023-09-19 12:59:21+00:00] +2023-09-19 12:59:21,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5d08b2cfa0582. [clock=2023-09-19 12:59:21+00:00] +2023-09-19 12:59:21,219 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242017878624164214711710666 amount: 80. +2023-09-19 12:59:21,220 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247746398533146772319448611 amount: 80. +2023-09-19 12:59:21,774 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128361.0, "order_id": "x-XEKWYICXBSIUT605b5d08b2ac50582", "exchange_order_id": "35708729", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:59:21,774 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5d08b2ac50582. +2023-09-19 12:59:22,697 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128362.0, "order_id": "x-XEKWYICXSSIUT605b5d08b2cfa0582", "exchange_order_id": "35708728", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 12:59:22,697 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5d08b2cfa0582. +2023-09-19 12:59:22,699 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5d3d1b7a10582 for 80.00000000 SEI-USDT. +2023-09-19 12:59:22,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128362.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b5d3d1b7a10582", "creation_timestamp": 1695128361.0, "exchange_order_id": "35708974", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 12:59:23,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5d3d1d7540582 for 80.00000000 SEI-USDT. +2023-09-19 12:59:23,259 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128362.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b5d3d1d7540582", "creation_timestamp": 1695128361.0, "exchange_order_id": "35708975", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:00:16,003 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5d3d1b7a10582. [clock=2023-09-19 13:00:16+00:00] +2023-09-19 13:00:16,004 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5d3d1d7540582. [clock=2023-09-19 13:00:16+00:00] +2023-09-19 13:00:16,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240563374057151145386613403 amount: 80. +2023-09-19 13:00:16,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246129291296626438204015849 amount: 80. +2023-09-19 13:00:16,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128416.0, "order_id": "x-XEKWYICXBSIUT605b5d3d1b7a10582", "exchange_order_id": "35708974", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:00:16,299 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5d3d1b7a10582. +2023-09-19 13:00:16,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128416.0, "order_id": "x-XEKWYICXSSIUT605b5d3d1d7540582", "exchange_order_id": "35708975", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:00:16,315 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5d3d1d7540582. +2023-09-19 13:00:16,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5d71623050582 for 80.00000000 SEI-USDT. +2023-09-19 13:00:16,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b5d71623050582", "creation_timestamp": 1695128416.0, "exchange_order_id": "35709219", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:00:16,599 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5d71627f70582 for 80.00000000 SEI-USDT. +2023-09-19 13:00:16,615 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128416.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b5d71627f70582", "creation_timestamp": 1695128416.0, "exchange_order_id": "35709220", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:01:08,874 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b5d71623050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:01:08,875 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:01:08+00:00] +2023-09-19 13:01:08,897 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128468.0, "order_id": "x-XEKWYICXBSIUT605b5d71623050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5007630", "exchange_order_id": "35709219", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:01:08,910 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128468.0, "order_id": "x-XEKWYICXBSIUT605b5d71623050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35709219", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:01:08,910 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b5d71623050582 completely filled. +2023-09-19 13:01:11,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5d71627f70582. [clock=2023-09-19 13:01:11+00:00] +2023-09-19 13:01:11,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236984228496146098325736559 amount: 80. +2023-09-19 13:01:11,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243284579080310459044381515 amount: 80. +2023-09-19 13:01:11,445 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128471.0, "order_id": "x-XEKWYICXSSIUT605b5d71627f70582", "exchange_order_id": "35709220", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:01:11,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5d71627f70582. +2023-09-19 13:01:11,662 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5da5ddc1e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:01:11,677 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b5da5ddc1e0582", "creation_timestamp": 1695128471.0, "exchange_order_id": "35709629", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:01:11,680 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5da5dda280582 for 80.00000000 SEI-USDT. +2023-09-19 13:01:11,692 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128471.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5da5dda280582", "creation_timestamp": 1695128471.0, "exchange_order_id": "35709630", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:02:06,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5da5dda280582. [clock=2023-09-19 13:02:06+00:00] +2023-09-19 13:02:06,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5da5ddc1e0582. [clock=2023-09-19 13:02:06+00:00] +2023-09-19 13:02:06,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232520700444372013999384193 amount: 80. +2023-09-19 13:02:06,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239137213536947872027692481 amount: 80. +2023-09-19 13:02:06,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128526.0, "order_id": "x-XEKWYICXBSIUT605b5da5dda280582", "exchange_order_id": "35709630", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:02:06,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5da5dda280582. +2023-09-19 13:02:06,335 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128526.0, "order_id": "x-XEKWYICXSSIUT605b5da5ddc1e0582", "exchange_order_id": "35709629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:02:06,335 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5da5ddc1e0582. +2023-09-19 13:02:06,526 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5dda53af60582 for 80.00000000 SEI-USDT. +2023-09-19 13:02:06,543 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605b5dda53af60582", "creation_timestamp": 1695128526.0, "exchange_order_id": "35710116", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:02:06,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5dda53e380582 for 80.00000000 SEI-USDT. +2023-09-19 13:02:06,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128526.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b5dda53e380582", "creation_timestamp": 1695128526.0, "exchange_order_id": "35710117", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:03:01,079 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5dda53af60582. [clock=2023-09-19 13:03:01+00:00] +2023-09-19 13:03:01,080 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5dda53e380582. [clock=2023-09-19 13:03:01+00:00] +2023-09-19 13:03:01,134 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232523042097751774825062930 amount: 80. +2023-09-19 13:03:01,136 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239139567760972685610674176 amount: 80. +2023-09-19 13:03:01,796 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128581.0, "order_id": "x-XEKWYICXBSIUT605b5dda53af60582", "exchange_order_id": "35710116", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:03:01,796 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5dda53af60582. +2023-09-19 13:03:02,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128582.0, "order_id": "x-XEKWYICXSSIUT605b5dda53e380582", "exchange_order_id": "35710117", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:03:02,557 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5dda53e380582. +2023-09-19 13:03:02,759 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5e0ed616f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:03:02,785 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128582.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b5e0ed616f0582", "creation_timestamp": 1695128581.0, "exchange_order_id": "35710315", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:03:02,786 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5e0ed5e880582 for 80.00000000 SEI-USDT. +2023-09-19 13:03:02,818 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128582.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605b5e0ed5e880582", "creation_timestamp": 1695128581.0, "exchange_order_id": "35710316", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:03:56,161 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5e0ed5e880582. [clock=2023-09-19 13:03:56+00:00] +2023-09-19 13:03:56,175 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5e0ed616f0582. [clock=2023-09-19 13:03:56+00:00] +2023-09-19 13:03:56,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1233739670695983749665310477 amount: 80. +2023-09-19 13:03:56,253 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12390000 amount: 80. +2023-09-19 13:03:57,168 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128636.0, "order_id": "x-XEKWYICXBSIUT605b5e0ed5e880582", "exchange_order_id": "35710316", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:03:57,169 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5e0ed5e880582. +2023-09-19 13:03:57,841 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b5e0ed616f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:03:57,855 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:03:57+00:00] +2023-09-19 13:03:57,914 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128637.0, "order_id": "x-XEKWYICXSSIUT605b5e0ed616f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "5007700", "exchange_order_id": "35710315", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:03:58,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128637.0, "order_id": "x-XEKWYICXSSIUT605b5e0ed616f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35710315", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:03:58,256 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b5e0ed616f0582 completely filled. +2023-09-19 13:03:59,227 - 1 - hummingbot.connector.exchange.binance.binance_exchange.BinanceExchange - WARNING - Failed to cancel order x-XEKWYICXSSIUT605b5e0ed616f0582 (order not found) +2023-09-19 13:03:59,560 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5e43668d70582 for 80.00000000 SEI-USDT. +2023-09-19 13:03:59,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128639.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b5e43668d70582", "creation_timestamp": 1695128636.0, "exchange_order_id": "35710476", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:03:59,655 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5e43663500582 for 80.00000000 SEI-USDT. +2023-09-19 13:03:59,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128639.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXBSIUT605b5e43663500582", "creation_timestamp": 1695128636.0, "exchange_order_id": "35710477", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:03:59,740 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b5e43668d70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:03:59,741 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:03:59+00:00] +2023-09-19 13:03:59,832 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128639.0, "order_id": "x-XEKWYICXSSIUT605b5e43668d70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "5007710", "exchange_order_id": "35710476", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:03:59,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128639.0, "order_id": "x-XEKWYICXSSIUT605b5e43668d70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35710476", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:03:59,998 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b5e43668d70582 completely filled. +2023-09-19 13:04:51,787 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5e43663500582. [clock=2023-09-19 13:04:51+00:00] +2023-09-19 13:04:51,878 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236195050312530089243314876 amount: 80. +2023-09-19 13:04:51,888 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242185727239259514478426977 amount: 80. +2023-09-19 13:04:52,762 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128692.0, "order_id": "x-XEKWYICXBSIUT605b5e43663500582", "exchange_order_id": "35710477", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:04:52,762 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5e43663500582. +2023-09-19 13:04:54,162 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5e78752040582 for 80.00000000 SEI-USDT. +2023-09-19 13:04:54,233 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128693.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5e78752040582", "creation_timestamp": 1695128691.0, "exchange_order_id": "35710628", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:04:54,788 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5e78756ac0582 for 80.00000000 SEI-USDT. +2023-09-19 13:04:54,808 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128694.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b5e78756ac0582", "creation_timestamp": 1695128691.0, "exchange_order_id": "35710629", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:05:46,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5e78752040582. [clock=2023-09-19 13:05:46+00:00] +2023-09-19 13:05:46,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5e78756ac0582. [clock=2023-09-19 13:05:46+00:00] +2023-09-19 13:05:46,097 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237740863302063732625228299 amount: 80. +2023-09-19 13:05:46,098 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:05:46,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128746.0, "order_id": "x-XEKWYICXBSIUT605b5e78752040582", "exchange_order_id": "35710628", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:05:46,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5e78752040582. +2023-09-19 13:05:46,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128746.0, "order_id": "x-XEKWYICXSSIUT605b5e78756ac0582", "exchange_order_id": "35710629", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:05:46,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5e78756ac0582. +2023-09-19 13:05:46,524 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5eac27eb20582 for 80.00000000 SEI-USDT. +2023-09-19 13:05:46,536 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128746.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b5eac27eb20582", "creation_timestamp": 1695128746.0, "exchange_order_id": "35710844", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:06:41,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5eac27eb20582. [clock=2023-09-19 13:06:41+00:00] +2023-09-19 13:06:41,091 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236778686195063580200789798 amount: 80. +2023-09-19 13:06:41,093 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242377113337873126678097688 amount: 80. +2023-09-19 13:06:41,348 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128801.0, "order_id": "x-XEKWYICXBSIUT605b5eac27eb20582", "exchange_order_id": "35710844", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:06:41,349 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5eac27eb20582. +2023-09-19 13:06:41,593 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5ee09a5510582 for 80.00000000 SEI-USDT. +2023-09-19 13:06:41,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5ee09a5510582", "creation_timestamp": 1695128801.0, "exchange_order_id": "35711083", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:06:41,615 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5ee09a9cf0582 for 80.00000000 SEI-USDT. +2023-09-19 13:06:41,635 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128801.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b5ee09a9cf0582", "creation_timestamp": 1695128801.0, "exchange_order_id": "35711082", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:06:55,708 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b5ee09a5510582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:06:55,710 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:06:55+00:00] +2023-09-19 13:06:55,741 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128815.0, "order_id": "x-XEKWYICXBSIUT605b5ee09a5510582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12360000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5007797", "exchange_order_id": "35711083", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:06:55,758 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128815.0, "order_id": "x-XEKWYICXBSIUT605b5ee09a5510582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35711083", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:06:55,758 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b5ee09a5510582 completely filled. +2023-09-19 13:07:36,369 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5ee09a9cf0582. [clock=2023-09-19 13:07:36+00:00] +2023-09-19 13:07:36,445 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234511539872784729313294693 amount: 80. +2023-09-19 13:07:36,446 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240594956729087903969032557 amount: 80. +2023-09-19 13:07:37,206 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128856.0, "order_id": "x-XEKWYICXSSIUT605b5ee09a9cf0582", "exchange_order_id": "35711082", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:07:37,207 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5ee09a9cf0582. +2023-09-19 13:07:39,739 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5f15646f10582 for 80.00000000 SEI-USDT. +2023-09-19 13:07:39,783 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128859.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b5f15646f10582", "creation_timestamp": 1695128856.0, "exchange_order_id": "35711540", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:07:40,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5f1564b450582 for 80.00000000 SEI-USDT. +2023-09-19 13:07:40,368 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128859.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b5f1564b450582", "creation_timestamp": 1695128856.0, "exchange_order_id": "35711544", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:08:31,201 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5f15646f10582. [clock=2023-09-19 13:08:31+00:00] +2023-09-19 13:08:31,202 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5f1564b450582. [clock=2023-09-19 13:08:31+00:00] +2023-09-19 13:08:31,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236136852700456872101836361 amount: 80. +2023-09-19 13:08:31,282 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241734709556531620039678871 amount: 80. +2023-09-19 13:08:32,370 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128911.0, "order_id": "x-XEKWYICXBSIUT605b5f15646f10582", "exchange_order_id": "35711540", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:08:32,371 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5f15646f10582. +2023-09-19 13:08:33,059 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128912.0, "order_id": "x-XEKWYICXSSIUT605b5f1564b450582", "exchange_order_id": "35711544", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:08:33,060 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5f1564b450582. +2023-09-19 13:08:33,362 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5f49b032d0582 for 80.00000000 SEI-USDT. +2023-09-19 13:08:33,391 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128913.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b5f49b032d0582", "creation_timestamp": 1695128911.0, "exchange_order_id": "35711679", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:08:33,391 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5f49afeef0582 for 80.00000000 SEI-USDT. +2023-09-19 13:08:33,410 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128913.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5f49afeef0582", "creation_timestamp": 1695128911.0, "exchange_order_id": "35711680", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:09:26,129 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5f49afeef0582. [clock=2023-09-19 13:09:26+00:00] +2023-09-19 13:09:26,130 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5f49b032d0582. [clock=2023-09-19 13:09:26+00:00] +2023-09-19 13:09:26,189 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236620922578621883030877744 amount: 80. +2023-09-19 13:09:26,190 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241836683240896092060476856 amount: 80. +2023-09-19 13:09:26,642 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128966.0, "order_id": "x-XEKWYICXBSIUT605b5f49afeef0582", "exchange_order_id": "35711680", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:09:26,642 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5f49afeef0582. +2023-09-19 13:09:27,684 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128967.0, "order_id": "x-XEKWYICXSSIUT605b5f49b032d0582", "exchange_order_id": "35711679", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:09:27,684 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5f49b032d0582. +2023-09-19 13:09:27,903 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5f7e0d9490582 for 80.00000000 SEI-USDT. +2023-09-19 13:09:27,928 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128967.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b5f7e0d9490582", "creation_timestamp": 1695128966.0, "exchange_order_id": "35711815", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:09:27,930 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5f7e0d44b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:09:27,957 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695128967.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b5f7e0d44b0582", "creation_timestamp": 1695128966.0, "exchange_order_id": "35711814", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:10:21,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5f7e0d44b0582. [clock=2023-09-19 13:10:21+00:00] +2023-09-19 13:10:21,038 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5f7e0d9490582. [clock=2023-09-19 13:10:21+00:00] +2023-09-19 13:10:21,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237927008189710344016869409 amount: 80. +2023-09-19 13:10:21,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241986093260750069615771836 amount: 80. +2023-09-19 13:10:21,302 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129021.0, "order_id": "x-XEKWYICXBSIUT605b5f7e0d44b0582", "exchange_order_id": "35711814", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:10:21,302 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5f7e0d44b0582. +2023-09-19 13:10:21,516 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5fb2611f60582 for 80.00000000 SEI-USDT. +2023-09-19 13:10:21,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b5fb2611f60582", "creation_timestamp": 1695129021.0, "exchange_order_id": "35712013", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:10:21,532 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5fb2615430582 for 80.00000000 SEI-USDT. +2023-09-19 13:10:21,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129021.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b5fb2615430582", "creation_timestamp": 1695129021.0, "exchange_order_id": "35712014", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:10:21,608 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129021.0, "order_id": "x-XEKWYICXSSIUT605b5f7e0d9490582", "exchange_order_id": "35711815", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:10:21,608 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5f7e0d9490582. +2023-09-19 13:11:16,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5fb2611f60582. [clock=2023-09-19 13:11:16+00:00] +2023-09-19 13:11:16,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5fb2615430582. [clock=2023-09-19 13:11:16+00:00] +2023-09-19 13:11:16,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237165980229057916581305348 amount: 80. +2023-09-19 13:11:16,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240319956480831115817245150 amount: 80. +2023-09-19 13:11:16,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129076.0, "order_id": "x-XEKWYICXBSIUT605b5fb2611f60582", "exchange_order_id": "35712013", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:11:16,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5fb2611f60582. +2023-09-19 13:11:16,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129076.0, "order_id": "x-XEKWYICXSSIUT605b5fb2615430582", "exchange_order_id": "35712014", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:11:16,462 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5fb2615430582. +2023-09-19 13:11:16,464 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b5fe6cc0f70582 for 80.00000000 SEI-USDT. +2023-09-19 13:11:16,477 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b5fe6cc0f70582", "creation_timestamp": 1695129076.0, "exchange_order_id": "35712222", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:11:16,532 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b5fe6cc4200582 for 80.00000000 SEI-USDT. +2023-09-19 13:11:16,544 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129076.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b5fe6cc4200582", "creation_timestamp": 1695129076.0, "exchange_order_id": "35712223", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:12:11,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b5fe6cc0f70582. [clock=2023-09-19 13:12:11+00:00] +2023-09-19 13:12:11,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b5fe6cc4200582. [clock=2023-09-19 13:12:11+00:00] +2023-09-19 13:12:11,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238380995870212891840162721 amount: 80. +2023-09-19 13:12:11,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241698901932234471812044819 amount: 80. +2023-09-19 13:12:11,634 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129131.0, "order_id": "x-XEKWYICXBSIUT605b5fe6cc0f70582", "exchange_order_id": "35712222", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:12:11,635 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b5fe6cc0f70582. +2023-09-19 13:12:11,664 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129131.0, "order_id": "x-XEKWYICXSSIUT605b5fe6cc4200582", "exchange_order_id": "35712223", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:12:11,664 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b5fe6cc4200582. +2023-09-19 13:12:13,471 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b601b4cc790582 for 80.00000000 SEI-USDT. +2023-09-19 13:12:13,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129132.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b601b4cc790582", "creation_timestamp": 1695129131.0, "exchange_order_id": "35712403", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:12:13,951 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b601b4cfa20582 for 80.00000000 SEI-USDT. +2023-09-19 13:12:14,009 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129133.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b601b4cfa20582", "creation_timestamp": 1695129131.0, "exchange_order_id": "35712408", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:12:53,774 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 13:13:06,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b601b4cc790582. [clock=2023-09-19 13:13:06+00:00] +2023-09-19 13:13:06,263 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b601b4cfa20582. [clock=2023-09-19 13:13:06+00:00] +2023-09-19 13:13:06,352 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238518416680452794555315443 amount: 80. +2023-09-19 13:13:06,353 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241098528568963813338724437 amount: 80. +2023-09-19 13:13:07,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129186.0, "order_id": "x-XEKWYICXBSIUT605b601b4cc790582", "exchange_order_id": "35712403", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:13:07,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b601b4cc790582. +2023-09-19 13:13:10,322 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129189.0, "order_id": "x-XEKWYICXSSIUT605b601b4cfa20582", "exchange_order_id": "35712408", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:13:10,322 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b601b4cfa20582. +2023-09-19 13:13:10,838 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6050040600582 for 80.00000000 SEI-USDT. +2023-09-19 13:13:10,872 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b6050040600582", "creation_timestamp": 1695129186.0, "exchange_order_id": "35712492", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:13:10,873 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6050044e50582 for 80.00000000 SEI-USDT. +2023-09-19 13:13:10,898 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129189.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b6050044e50582", "creation_timestamp": 1695129186.0, "exchange_order_id": "35712493", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:14:01,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6050040600582. [clock=2023-09-19 13:14:01+00:00] +2023-09-19 13:14:01,241 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6050044e50582. [clock=2023-09-19 13:14:01+00:00] +2023-09-19 13:14:01,314 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238518416680452794555315443 amount: 80. +2023-09-19 13:14:01,332 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241098528568963813338724437 amount: 80. +2023-09-19 13:14:02,121 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129241.0, "order_id": "x-XEKWYICXBSIUT605b6050040600582", "exchange_order_id": "35712492", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:14:02,122 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6050040600582. +2023-09-19 13:14:02,926 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b608472acf0582 for 80.00000000 SEI-USDT. +2023-09-19 13:14:02,962 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129242.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b608472acf0582", "creation_timestamp": 1695129241.0, "exchange_order_id": "35712570", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:14:02,962 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b608472e080582 for 80.00000000 SEI-USDT. +2023-09-19 13:14:02,986 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129242.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b608472e080582", "creation_timestamp": 1695129241.0, "exchange_order_id": "35712569", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:14:03,002 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129242.0, "order_id": "x-XEKWYICXSSIUT605b6050044e50582", "exchange_order_id": "35712493", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:14:03,002 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6050044e50582. +2023-09-19 13:14:56,101 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b608472acf0582. [clock=2023-09-19 13:14:56+00:00] +2023-09-19 13:14:56,102 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b608472e080582. [clock=2023-09-19 13:14:56+00:00] +2023-09-19 13:14:56,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238121833279136504205238776 amount: 80. +2023-09-19 13:14:56,148 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241241723910591986936891501 amount: 80. +2023-09-19 13:14:56,700 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129296.0, "order_id": "x-XEKWYICXBSIUT605b608472acf0582", "exchange_order_id": "35712570", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:14:56,701 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b608472acf0582. +2023-09-19 13:14:57,423 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129297.0, "order_id": "x-XEKWYICXSSIUT605b608472e080582", "exchange_order_id": "35712569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:14:57,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b608472e080582. +2023-09-19 13:14:57,614 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b60b8b981e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:14:57,651 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129297.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b60b8b981e0582", "creation_timestamp": 1695129296.0, "exchange_order_id": "35712658", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:14:57,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b60b8b9d210582 for 80.00000000 SEI-USDT. +2023-09-19 13:14:57,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129297.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b60b8b9d210582", "creation_timestamp": 1695129296.0, "exchange_order_id": "35712657", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:15:51,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b60b8b981e0582. [clock=2023-09-19 13:15:51+00:00] +2023-09-19 13:15:51,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b60b8b9d210582. [clock=2023-09-19 13:15:51+00:00] +2023-09-19 13:15:51,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237317702640857870713784216 amount: 80. +2023-09-19 13:15:51,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239742029463020265718107034 amount: 80. +2023-09-19 13:15:51,526 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129351.0, "order_id": "x-XEKWYICXBSIUT605b60b8b981e0582", "exchange_order_id": "35712658", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:15:51,527 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b60b8b981e0582. +2023-09-19 13:15:51,528 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b60ed1e42e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:15:51,542 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b60ed1e42e0582", "creation_timestamp": 1695129351.0, "exchange_order_id": "35712797", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:15:51,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b60ed1e0840582 for 80.00000000 SEI-USDT. +2023-09-19 13:15:51,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129351.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b60ed1e0840582", "creation_timestamp": 1695129351.0, "exchange_order_id": "35712798", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:15:51,661 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129351.0, "order_id": "x-XEKWYICXSSIUT605b60b8b9d210582", "exchange_order_id": "35712657", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:15:51,662 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b60b8b9d210582. +2023-09-19 13:16:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b60ed1e0840582. [clock=2023-09-19 13:16:46+00:00] +2023-09-19 13:16:46,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b60ed1e42e0582. [clock=2023-09-19 13:16:46+00:00] +2023-09-19 13:16:46,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237020073212961261336156079 amount: 80. +2023-09-19 13:16:46,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240881531799410379325736981 amount: 80. +2023-09-19 13:16:46,291 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129406.0, "order_id": "x-XEKWYICXBSIUT605b60ed1e0840582", "exchange_order_id": "35712798", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:16:46,291 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b60ed1e0840582. +2023-09-19 13:16:46,538 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129406.0, "order_id": "x-XEKWYICXSSIUT605b60ed1e42e0582", "exchange_order_id": "35712797", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:16:46,538 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b60ed1e42e0582. +2023-09-19 13:16:46,542 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b612182d890582 for 80.00000000 SEI-USDT. +2023-09-19 13:16:46,555 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b612182d890582", "creation_timestamp": 1695129406.0, "exchange_order_id": "35712888", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:16:46,555 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b612182b720582 for 80.00000000 SEI-USDT. +2023-09-19 13:16:46,566 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129406.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b612182b720582", "creation_timestamp": 1695129406.0, "exchange_order_id": "35712889", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:17:41,183 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b612182b720582. [clock=2023-09-19 13:17:41+00:00] +2023-09-19 13:17:41,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b612182d890582. [clock=2023-09-19 13:17:41+00:00] +2023-09-19 13:17:41,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238237492921015066873011493 amount: 80. +2023-09-19 13:17:41,244 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241242763013434638421907554 amount: 80. +2023-09-19 13:17:41,689 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129461.0, "order_id": "x-XEKWYICXBSIUT605b612182b720582", "exchange_order_id": "35712889", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:17:41,690 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b612182b720582. +2023-09-19 13:17:42,690 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129462.0, "order_id": "x-XEKWYICXSSIUT605b612182d890582", "exchange_order_id": "35712888", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:17:42,690 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b612182d890582. +2023-09-19 13:17:42,934 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b61562c00f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:17:42,960 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129462.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b61562c00f0582", "creation_timestamp": 1695129461.0, "exchange_order_id": "35713083", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:17:42,960 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b61562c3240582 for 80.00000000 SEI-USDT. +2023-09-19 13:17:42,998 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129462.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b61562c3240582", "creation_timestamp": 1695129461.0, "exchange_order_id": "35713082", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:18:34,589 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b61562c3240582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:18:34,590 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:18:34+00:00] +2023-09-19 13:18:34,636 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129514.0, "order_id": "x-XEKWYICXSSIUT605b61562c3240582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "5007946", "exchange_order_id": "35713082", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:18:34,776 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129514.0, "order_id": "x-XEKWYICXSSIUT605b61562c3240582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35713082", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:18:34,777 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b61562c3240582 completely filled. +2023-09-19 13:18:36,014 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b61562c00f0582. [clock=2023-09-19 13:18:36+00:00] +2023-09-19 13:18:36,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240996932313691663329148094 amount: 80. +2023-09-19 13:18:36,066 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245315769152988418136321970 amount: 80. +2023-09-19 13:18:36,894 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129516.0, "order_id": "x-XEKWYICXBSIUT605b61562c00f0582", "exchange_order_id": "35713083", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:18:36,895 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b61562c00f0582. +2023-09-19 13:18:37,612 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b618a7474c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:18:37,637 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129517.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b618a7474c0582", "creation_timestamp": 1695129516.0, "exchange_order_id": "35713570", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:18:37,835 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b618a749ba0582 for 80.00000000 SEI-USDT. +2023-09-19 13:18:37,858 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129517.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b618a749ba0582", "creation_timestamp": 1695129516.0, "exchange_order_id": "35713571", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:19:31,233 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b618a7474c0582. [clock=2023-09-19 13:19:31+00:00] +2023-09-19 13:19:31,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b618a749ba0582. [clock=2023-09-19 13:19:31+00:00] +2023-09-19 13:19:31,315 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239994415048113955318094941 amount: 80. +2023-09-19 13:19:31,316 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:19:32,047 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129571.0, "order_id": "x-XEKWYICXBSIUT605b618a7474c0582", "exchange_order_id": "35713570", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:19:32,047 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b618a7474c0582. +2023-09-19 13:19:34,917 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129574.0, "order_id": "x-XEKWYICXSSIUT605b618a749ba0582", "exchange_order_id": "35713571", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:19:34,917 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b618a749ba0582. +2023-09-19 13:19:35,445 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b61bf251a30582 for 80.00000000 SEI-USDT. +2023-09-19 13:19:35,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129575.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b61bf251a30582", "creation_timestamp": 1695129571.0, "exchange_order_id": "35714061", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:20:26,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b61bf251a30582. [clock=2023-09-19 13:20:26+00:00] +2023-09-19 13:20:26,049 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240996813437661870248817169 amount: 80. +2023-09-19 13:20:26,050 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245148183814021819388447305 amount: 80. +2023-09-19 13:20:26,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129626.0, "order_id": "x-XEKWYICXBSIUT605b61bf251a30582", "exchange_order_id": "35714061", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:20:26,258 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b61bf251a30582. +2023-09-19 13:20:26,601 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b61f3582330582 for 80.00000000 SEI-USDT. +2023-09-19 13:20:26,614 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b61f3582330582", "creation_timestamp": 1695129626.0, "exchange_order_id": "35714282", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:20:26,617 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b61f35804c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:20:26,628 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129626.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b61f35804c0582", "creation_timestamp": 1695129626.0, "exchange_order_id": "35714283", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:21:21,067 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b61f35804c0582. [clock=2023-09-19 13:21:21+00:00] +2023-09-19 13:21:21,068 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b61f3582330582. [clock=2023-09-19 13:21:21+00:00] +2023-09-19 13:21:21,089 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-19 13:21:21,090 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:21:21,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129681.0, "order_id": "x-XEKWYICXBSIUT605b61f35804c0582", "exchange_order_id": "35714283", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:21:21,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b61f35804c0582. +2023-09-19 13:21:21,482 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6227d57360582 for 80.00000000 SEI-USDT. +2023-09-19 13:21:21,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129681.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b6227d57360582", "creation_timestamp": 1695129681.0, "exchange_order_id": "35714462", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:21:21,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129681.0, "order_id": "x-XEKWYICXSSIUT605b61f3582330582", "exchange_order_id": "35714282", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:21:21,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b61f3582330582. +2023-09-19 13:22:16,193 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6227d57360582. [clock=2023-09-19 13:22:16+00:00] +2023-09-19 13:22:16,273 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-19 13:22:16,274 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247584444860188928829746520 amount: 80. +2023-09-19 13:22:17,031 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129736.0, "order_id": "x-XEKWYICXBSIUT605b6227d57360582", "exchange_order_id": "35714462", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:22:17,032 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6227d57360582. +2023-09-19 13:22:18,654 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b625c761e30582 for 80.00000000 SEI-USDT. +2023-09-19 13:22:18,702 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129738.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b625c761e30582", "creation_timestamp": 1695129736.0, "exchange_order_id": "35714572", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:22:18,704 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b625c7659d0582 for 80.00000000 SEI-USDT. +2023-09-19 13:22:18,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129738.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b625c7659d0582", "creation_timestamp": 1695129736.0, "exchange_order_id": "35714573", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:23:11,155 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b625c761e30582. [clock=2023-09-19 13:23:11+00:00] +2023-09-19 13:23:11,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b625c7659d0582. [clock=2023-09-19 13:23:11+00:00] +2023-09-19 13:23:11,250 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12420000 amount: 80. +2023-09-19 13:23:11,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:23:12,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129792.0, "order_id": "x-XEKWYICXBSIUT605b625c761e30582", "exchange_order_id": "35714572", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:23:12,679 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b625c761e30582. +2023-09-19 13:23:13,942 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129793.0, "order_id": "x-XEKWYICXSSIUT605b625c7659d0582", "exchange_order_id": "35714573", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:23:13,943 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b625c7659d0582. +2023-09-19 13:23:14,124 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6290e462f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:23:14,154 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129794.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b6290e462f0582", "creation_timestamp": 1695129791.0, "exchange_order_id": "35714677", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:23:20,238 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6290e462f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:23:20,240 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:23:20+00:00] +2023-09-19 13:23:20,354 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129800.0, "order_id": "x-XEKWYICXBSIUT605b6290e462f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5008043", "exchange_order_id": "35714677", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:23:20,638 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129800.0, "order_id": "x-XEKWYICXBSIUT605b6290e462f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35714677", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:23:20,638 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6290e462f0582 completely filled. +2023-09-19 13:24:06,115 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240612911489412799548054606 amount: 80. +2023-09-19 13:24:06,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243923884415641609578718405 amount: 80. +2023-09-19 13:24:06,646 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b62c5373510582 for 80.00000000 SEI-USDT. +2023-09-19 13:24:06,676 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129846.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b62c5373510582", "creation_timestamp": 1695129846.0, "exchange_order_id": "35714868", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:24:07,257 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b62c53787c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:24:07,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129847.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b62c53787c0582", "creation_timestamp": 1695129846.0, "exchange_order_id": "35714870", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:25:01,375 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b62c5373510582. [clock=2023-09-19 13:25:01+00:00] +2023-09-19 13:25:01,376 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b62c53787c0582. [clock=2023-09-19 13:25:01+00:00] +2023-09-19 13:25:01,471 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240043340535104621238881620 amount: 80. +2023-09-19 13:25:01,472 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244469184967111719707397920 amount: 80. +2023-09-19 13:25:03,879 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129903.0, "order_id": "x-XEKWYICXBSIUT605b62c5373510582", "exchange_order_id": "35714868", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:25:03,880 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b62c5373510582. +2023-09-19 13:25:04,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b62fa01c4c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:25:04,263 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129903.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b62fa01c4c0582", "creation_timestamp": 1695129901.0, "exchange_order_id": "35714954", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:25:04,296 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129903.0, "order_id": "x-XEKWYICXSSIUT605b62c53787c0582", "exchange_order_id": "35714870", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:25:04,296 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b62c53787c0582. +2023-09-19 13:25:04,312 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b62fa01f8e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:25:04,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129903.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b62fa01f8e0582", "creation_timestamp": 1695129901.0, "exchange_order_id": "35714955", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:25:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b62fa01c4c0582. [clock=2023-09-19 13:25:56+00:00] +2023-09-19 13:25:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b62fa01f8e0582. [clock=2023-09-19 13:25:56+00:00] +2023-09-19 13:25:56,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240256312622196441975446747 amount: 80. +2023-09-19 13:25:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243697862467775505090930697 amount: 80. +2023-09-19 13:25:56,285 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129956.0, "order_id": "x-XEKWYICXBSIUT605b62fa01c4c0582", "exchange_order_id": "35714954", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:25:56,285 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b62fa01c4c0582. +2023-09-19 13:25:56,517 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b632e080ab0582 for 80.00000000 SEI-USDT. +2023-09-19 13:25:56,529 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b632e080ab0582", "creation_timestamp": 1695129956.0, "exchange_order_id": "35715079", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:25:56,529 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b632e082fa0582 for 80.00000000 SEI-USDT. +2023-09-19 13:25:56,541 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129956.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b632e082fa0582", "creation_timestamp": 1695129956.0, "exchange_order_id": "35715080", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:25:56,557 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695129956.0, "order_id": "x-XEKWYICXSSIUT605b62fa01f8e0582", "exchange_order_id": "35714955", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:25:56,557 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b62fa01f8e0582. +2023-09-19 13:26:51,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b632e080ab0582. [clock=2023-09-19 13:26:51+00:00] +2023-09-19 13:26:51,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b632e082fa0582. [clock=2023-09-19 13:26:51+00:00] +2023-09-19 13:26:51,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241662363878104621311419135 amount: 80. +2023-09-19 13:26:51,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246563625945431957241925673 amount: 80. +2023-09-19 13:26:51,287 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130011.0, "order_id": "x-XEKWYICXBSIUT605b632e080ab0582", "exchange_order_id": "35715079", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:26:51,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b632e080ab0582. +2023-09-19 13:26:51,438 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b63627b9ac0582 for 80.00000000 SEI-USDT. +2023-09-19 13:26:51,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b63627b9ac0582", "creation_timestamp": 1695130011.0, "exchange_order_id": "35715247", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:26:51,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130011.0, "order_id": "x-XEKWYICXSSIUT605b632e082fa0582", "exchange_order_id": "35715080", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:26:51,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b632e082fa0582. +2023-09-19 13:26:51,464 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b63627bc970582 for 80.00000000 SEI-USDT. +2023-09-19 13:26:51,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130011.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b63627bc970582", "creation_timestamp": 1695130011.0, "exchange_order_id": "35715246", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:27:46,250 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b63627b9ac0582. [clock=2023-09-19 13:27:46+00:00] +2023-09-19 13:27:46,252 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b63627bc970582. [clock=2023-09-19 13:27:46+00:00] +2023-09-19 13:27:46,331 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241189032711053136390343463 amount: 80. +2023-09-19 13:27:46,341 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246115370887319172692208639 amount: 80. +2023-09-19 13:27:47,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130066.0, "order_id": "x-XEKWYICXBSIUT605b63627b9ac0582", "exchange_order_id": "35715247", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:27:47,146 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b63627b9ac0582. +2023-09-19 13:27:49,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b63973ce8b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:27:49,393 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130069.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b63973ce8b0582", "creation_timestamp": 1695130066.0, "exchange_order_id": "35715426", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:27:49,394 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b63973d27a0582 for 80.00000000 SEI-USDT. +2023-09-19 13:27:49,437 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130069.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b63973d27a0582", "creation_timestamp": 1695130066.0, "exchange_order_id": "35715425", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:27:49,475 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130069.0, "order_id": "x-XEKWYICXSSIUT605b63627bc970582", "exchange_order_id": "35715246", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:27:49,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b63627bc970582. +2023-09-19 13:28:41,271 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b63973ce8b0582. [clock=2023-09-19 13:28:41+00:00] +2023-09-19 13:28:41,281 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b63973d27a0582. [clock=2023-09-19 13:28:41+00:00] +2023-09-19 13:28:41,366 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240370265519587763469678619 amount: 80. +2023-09-19 13:28:41,368 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244197646419206237266803193 amount: 80. +2023-09-19 13:28:42,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130122.0, "order_id": "x-XEKWYICXBSIUT605b63973ce8b0582", "exchange_order_id": "35715426", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:28:42,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b63973ce8b0582. +2023-09-19 13:28:44,818 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130124.0, "order_id": "x-XEKWYICXSSIUT605b63973d27a0582", "exchange_order_id": "35715425", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:28:44,819 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b63973d27a0582. +2023-09-19 13:28:45,396 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b63cbb75970582 for 80.00000000 SEI-USDT. +2023-09-19 13:28:45,444 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130124.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b63cbb75970582", "creation_timestamp": 1695130121.0, "exchange_order_id": "35715599", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:28:45,446 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b63cbb715b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:28:45,488 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130124.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b63cbb715b0582", "creation_timestamp": 1695130121.0, "exchange_order_id": "35715598", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:29:36,300 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b63cbb715b0582. [clock=2023-09-19 13:29:36+00:00] +2023-09-19 13:29:36,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b63cbb75970582. [clock=2023-09-19 13:29:36+00:00] +2023-09-19 13:29:36,426 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240510930299027616761100467 amount: 80. +2023-09-19 13:29:36,428 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243487099312957031677789927 amount: 80. +2023-09-19 13:29:37,174 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130177.0, "order_id": "x-XEKWYICXBSIUT605b63cbb715b0582", "exchange_order_id": "35715598", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:29:37,175 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b63cbb715b0582. +2023-09-19 13:29:38,136 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130177.0, "order_id": "x-XEKWYICXSSIUT605b63cbb75970582", "exchange_order_id": "35715599", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:29:38,136 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b63cbb75970582. +2023-09-19 13:29:38,459 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6400397ed0582 for 80.00000000 SEI-USDT. +2023-09-19 13:29:38,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130178.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6400397ed0582", "creation_timestamp": 1695130176.0, "exchange_order_id": "35715818", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:29:38,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b640039be10582 for 80.00000000 SEI-USDT. +2023-09-19 13:29:38,511 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130178.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b640039be10582", "creation_timestamp": 1695130176.0, "exchange_order_id": "35715819", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:30:31,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6400397ed0582. [clock=2023-09-19 13:30:31+00:00] +2023-09-19 13:30:31,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b640039be10582. [clock=2023-09-19 13:30:31+00:00] +2023-09-19 13:30:31,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241619492771928380401485909 amount: 80. +2023-09-19 13:30:31,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243935742028539938698252447 amount: 80. +2023-09-19 13:30:31,446 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130231.0, "order_id": "x-XEKWYICXBSIUT605b6400397ed0582", "exchange_order_id": "35715818", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:30:31,447 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6400397ed0582. +2023-09-19 13:30:31,688 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130231.0, "order_id": "x-XEKWYICXSSIUT605b640039be10582", "exchange_order_id": "35715819", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:30:31,689 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b640039be10582. +2023-09-19 13:30:31,692 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6434500790582 for 80.00000000 SEI-USDT. +2023-09-19 13:30:31,705 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b6434500790582", "creation_timestamp": 1695130231.0, "exchange_order_id": "35715993", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:30:31,705 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6434503a10582 for 80.00000000 SEI-USDT. +2023-09-19 13:30:31,717 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130231.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b6434503a10582", "creation_timestamp": 1695130231.0, "exchange_order_id": "35715994", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:31:26,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6434500790582. [clock=2023-09-19 13:31:26+00:00] +2023-09-19 13:31:26,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6434503a10582. [clock=2023-09-19 13:31:26+00:00] +2023-09-19 13:31:26,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240704149815708264255082882 amount: 80. +2023-09-19 13:31:26,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242503975833639056106234698 amount: 80. +2023-09-19 13:31:26,300 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130286.0, "order_id": "x-XEKWYICXBSIUT605b6434500790582", "exchange_order_id": "35715993", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:31:26,301 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6434500790582. +2023-09-19 13:31:26,481 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130286.0, "order_id": "x-XEKWYICXSSIUT605b6434503a10582", "exchange_order_id": "35715994", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:31:26,482 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6434503a10582. +2023-09-19 13:31:26,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6468bea840582 for 80.00000000 SEI-USDT. +2023-09-19 13:31:26,500 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b6468bea840582", "creation_timestamp": 1695130286.0, "exchange_order_id": "35716156", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:31:26,643 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6468be86c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:31:26,655 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130286.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6468be86c0582", "creation_timestamp": 1695130286.0, "exchange_order_id": "35716158", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:31:52,548 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b6468bea840582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:31:52,550 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:31:52+00:00] +2023-09-19 13:31:52,581 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130312.0, "order_id": "x-XEKWYICXSSIUT605b6468bea840582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "5008131", "exchange_order_id": "35716156", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:31:52,600 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130312.0, "order_id": "x-XEKWYICXSSIUT605b6468bea840582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35716156", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:31:52,601 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b6468bea840582 completely filled. +2023-09-19 13:32:21,181 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6468be86c0582. [clock=2023-09-19 13:32:21+00:00] +2023-09-19 13:32:21,240 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-19 13:32:21,241 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246504921847188477742300740 amount: 80. +2023-09-19 13:32:21,817 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130341.0, "order_id": "x-XEKWYICXBSIUT605b6468be86c0582", "exchange_order_id": "35716158", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:32:21,817 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6468be86c0582. +2023-09-19 13:32:23,023 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b649d671ba0582 for 80.00000000 SEI-USDT. +2023-09-19 13:32:23,065 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130342.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b649d671ba0582", "creation_timestamp": 1695130341.0, "exchange_order_id": "35716516", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:32:23,067 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b649d6759b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:32:23,088 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130342.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b649d6759b0582", "creation_timestamp": 1695130341.0, "exchange_order_id": "35716517", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:32:43,870 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b649d671ba0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:32:43,871 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:32:43+00:00] +2023-09-19 13:32:43,976 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130363.0, "order_id": "x-XEKWYICXBSIUT605b649d671ba0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5008144", "exchange_order_id": "35716516", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:32:44,024 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130363.0, "order_id": "x-XEKWYICXBSIUT605b649d671ba0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35716516", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:32:44,024 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b649d671ba0582 completely filled. +2023-09-19 13:33:16,075 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b649d6759b0582. [clock=2023-09-19 13:33:16+00:00] +2023-09-19 13:33:16,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241725093029463184381494168 amount: 80. +2023-09-19 13:33:16,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246577286906960998987726098 amount: 80. +2023-09-19 13:33:16,672 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130396.0, "order_id": "x-XEKWYICXSSIUT605b649d6759b0582", "exchange_order_id": "35716517", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:33:16,673 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b649d6759b0582. +2023-09-19 13:33:17,141 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b64d1bd3180582 for 80.00000000 SEI-USDT. +2023-09-19 13:33:17,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130397.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b64d1bd3180582", "creation_timestamp": 1695130396.0, "exchange_order_id": "35716856", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:33:17,484 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b64d1bd4ec0582 for 80.00000000 SEI-USDT. +2023-09-19 13:33:17,516 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130397.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b64d1bd4ec0582", "creation_timestamp": 1695130396.0, "exchange_order_id": "35716858", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:34:11,163 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b64d1bd3180582. [clock=2023-09-19 13:34:11+00:00] +2023-09-19 13:34:11,164 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b64d1bd4ec0582. [clock=2023-09-19 13:34:11+00:00] +2023-09-19 13:34:11,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240379664416056565258226138 amount: 80. +2023-09-19 13:34:11,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244150906389157464144244714 amount: 80. +2023-09-19 13:34:12,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130452.0, "order_id": "x-XEKWYICXBSIUT605b64d1bd3180582", "exchange_order_id": "35716856", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:34:12,401 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b64d1bd3180582. +2023-09-19 13:34:12,430 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130452.0, "order_id": "x-XEKWYICXSSIUT605b64d1bd4ec0582", "exchange_order_id": "35716858", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:34:12,430 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b64d1bd4ec0582. +2023-09-19 13:34:13,424 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b65065a3560582 for 80.00000000 SEI-USDT. +2023-09-19 13:34:13,484 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130453.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b65065a3560582", "creation_timestamp": 1695130451.0, "exchange_order_id": "35717071", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:34:13,794 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b65065a0720582 for 80.00000000 SEI-USDT. +2023-09-19 13:34:13,841 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130453.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b65065a0720582", "creation_timestamp": 1695130451.0, "exchange_order_id": "35717070", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:35:06,947 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b65065a0720582. [clock=2023-09-19 13:35:06+00:00] +2023-09-19 13:35:06,948 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b65065a3560582. [clock=2023-09-19 13:35:06+00:00] +2023-09-19 13:35:07,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239525996518284689676262486 amount: 80. +2023-09-19 13:35:07,031 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244190176206839781317339871 amount: 80. +2023-09-19 13:35:09,725 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130509.0, "order_id": "x-XEKWYICXBSIUT605b65065a0720582", "exchange_order_id": "35717070", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:35:09,725 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b65065a0720582. +2023-09-19 13:35:09,933 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130509.0, "order_id": "x-XEKWYICXSSIUT605b65065a3560582", "exchange_order_id": "35717071", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:35:09,934 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b65065a3560582. +2023-09-19 13:35:09,934 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b653b8324b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:35:09,952 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130509.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b653b8324b0582", "creation_timestamp": 1695130506.0, "exchange_order_id": "35717459", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:35:09,954 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b653b858710582 for 80.00000000 SEI-USDT. +2023-09-19 13:35:09,969 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130509.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b653b858710582", "creation_timestamp": 1695130506.0, "exchange_order_id": "35717458", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:36:01,054 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b653b8324b0582. [clock=2023-09-19 13:36:01+00:00] +2023-09-19 13:36:01,055 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b653b858710582. [clock=2023-09-19 13:36:01+00:00] +2023-09-19 13:36:01,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239525996518284689676262486 amount: 80. +2023-09-19 13:36:01,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244190176206839781317339871 amount: 80. +2023-09-19 13:36:01,298 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130561.0, "order_id": "x-XEKWYICXBSIUT605b653b8324b0582", "exchange_order_id": "35717459", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:36:01,298 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b653b8324b0582. +2023-09-19 13:36:01,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130561.0, "order_id": "x-XEKWYICXSSIUT605b653b858710582", "exchange_order_id": "35717458", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:36:01,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b653b858710582. +2023-09-19 13:36:01,513 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b656f0e6330582 for 80.00000000 SEI-USDT. +2023-09-19 13:36:01,526 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b656f0e6330582", "creation_timestamp": 1695130561.0, "exchange_order_id": "35717523", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:36:01,527 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b656f0e27b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:36:01,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130561.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b656f0e27b0582", "creation_timestamp": 1695130561.0, "exchange_order_id": "35717522", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:36:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b656f0e27b0582. [clock=2023-09-19 13:36:56+00:00] +2023-09-19 13:36:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b656f0e6330582. [clock=2023-09-19 13:36:56+00:00] +2023-09-19 13:36:56,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240236186507602960128622517 amount: 80. +2023-09-19 13:36:56,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244974795311157630062988343 amount: 80. +2023-09-19 13:36:56,238 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130616.0, "order_id": "x-XEKWYICXBSIUT605b656f0e27b0582", "exchange_order_id": "35717522", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:36:56,238 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b656f0e27b0582. +2023-09-19 13:36:56,662 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130616.0, "order_id": "x-XEKWYICXSSIUT605b656f0e6330582", "exchange_order_id": "35717523", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:36:56,663 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b656f0e6330582. +2023-09-19 13:36:56,664 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b65a37529e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:36:56,687 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b65a37529e0582", "creation_timestamp": 1695130616.0, "exchange_order_id": "35717615", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:36:56,743 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b65a374f740582 for 80.00000000 SEI-USDT. +2023-09-19 13:36:56,767 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130616.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b65a374f740582", "creation_timestamp": 1695130616.0, "exchange_order_id": "35717616", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:37:51,291 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b65a374f740582. [clock=2023-09-19 13:37:51+00:00] +2023-09-19 13:37:51,292 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b65a37529e0582. [clock=2023-09-19 13:37:51+00:00] +2023-09-19 13:37:51,364 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242708421127643676484817114 amount: 80. +2023-09-19 13:37:51,365 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247511460111902822578909404 amount: 80. +2023-09-19 13:37:52,376 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130672.0, "order_id": "x-XEKWYICXBSIUT605b65a374f740582", "exchange_order_id": "35717616", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:37:52,376 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b65a374f740582. +2023-09-19 13:37:54,278 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130674.0, "order_id": "x-XEKWYICXSSIUT605b65a37529e0582", "exchange_order_id": "35717615", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:37:54,278 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b65a37529e0582. +2023-09-19 13:37:54,828 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b65d83bc4f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:37:54,870 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130674.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b65d83bc4f0582", "creation_timestamp": 1695130671.0, "exchange_order_id": "35717908", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:37:54,870 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b65d83c0ab0582 for 80.00000000 SEI-USDT. +2023-09-19 13:37:54,892 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130674.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b65d83c0ab0582", "creation_timestamp": 1695130671.0, "exchange_order_id": "35717907", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:38:46,910 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b65d83bc4f0582. [clock=2023-09-19 13:38:46+00:00] +2023-09-19 13:38:46,911 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b65d83c0ab0582. [clock=2023-09-19 13:38:46+00:00] +2023-09-19 13:38:46,989 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-19 13:38:46,991 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1249182883483190667048991051 amount: 80. +2023-09-19 13:38:48,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130727.0, "order_id": "x-XEKWYICXBSIUT605b65d83bc4f0582", "exchange_order_id": "35717908", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:38:48,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b65d83bc4f0582. +2023-09-19 13:38:49,130 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130728.0, "order_id": "x-XEKWYICXSSIUT605b65d83c0ab0582", "exchange_order_id": "35717907", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:38:49,130 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b65d83c0ab0582. +2023-09-19 13:38:49,321 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b660d484930582 for 80.00000000 SEI-USDT. +2023-09-19 13:38:49,349 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130729.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b660d484930582", "creation_timestamp": 1695130726.0, "exchange_order_id": "35718009", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:38:49,351 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b660d4875c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:38:49,371 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130729.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12490000", "order_id": "x-XEKWYICXSSIUT605b660d4875c0582", "creation_timestamp": 1695130726.0, "exchange_order_id": "35718008", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:39:41,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b660d484930582. [clock=2023-09-19 13:39:41+00:00] +2023-09-19 13:39:41,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b660d4875c0582. [clock=2023-09-19 13:39:41+00:00] +2023-09-19 13:39:41,076 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1242356792088084025977978025 amount: 80. +2023-09-19 13:39:41,077 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247239773292317695322940743 amount: 80. +2023-09-19 13:39:41,703 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130781.0, "order_id": "x-XEKWYICXBSIUT605b660d484930582", "exchange_order_id": "35718009", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:39:41,703 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b660d484930582. +2023-09-19 13:39:42,670 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130782.0, "order_id": "x-XEKWYICXSSIUT605b660d4875c0582", "exchange_order_id": "35718008", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:39:42,670 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b660d4875c0582. +2023-09-19 13:39:42,955 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6640e04820582 for 80.00000000 SEI-USDT. +2023-09-19 13:39:42,986 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130782.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b6640e04820582", "creation_timestamp": 1695130781.0, "exchange_order_id": "35718118", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:39:42,987 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6640dcf100582 for 80.00000000 SEI-USDT. +2023-09-19 13:39:43,015 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130782.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXBSIUT605b6640dcf100582", "creation_timestamp": 1695130781.0, "exchange_order_id": "35718117", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:40:36,119 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6640dcf100582. [clock=2023-09-19 13:40:36+00:00] +2023-09-19 13:40:36,120 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6640e04820582. [clock=2023-09-19 13:40:36+00:00] +2023-09-19 13:40:36,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 13:40:36,141 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1248846341557497725477118001 amount: 80. +2023-09-19 13:40:36,413 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130836.0, "order_id": "x-XEKWYICXBSIUT605b6640dcf100582", "exchange_order_id": "35718117", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:40:36,414 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6640dcf100582. +2023-09-19 13:40:36,633 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b667560aee0582 for 80.00000000 SEI-USDT. +2023-09-19 13:40:36,646 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12480000", "order_id": "x-XEKWYICXSSIUT605b667560aee0582", "creation_timestamp": 1695130836.0, "exchange_order_id": "35718332", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:40:36,648 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6675606d00582 for 80.00000000 SEI-USDT. +2023-09-19 13:40:36,664 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130836.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b6675606d00582", "creation_timestamp": 1695130836.0, "exchange_order_id": "35718331", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:40:36,841 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130836.0, "order_id": "x-XEKWYICXSSIUT605b6640e04820582", "exchange_order_id": "35718118", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:40:36,841 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6640e04820582. +2023-09-19 13:40:42,539 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6675606d00582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:40:42,540 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:40:42+00:00] +2023-09-19 13:40:42,567 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130842.0, "order_id": "x-XEKWYICXBSIUT605b6675606d00582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5008267", "exchange_order_id": "35718331", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:40:42,582 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130842.0, "order_id": "x-XEKWYICXBSIUT605b6675606d00582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35718331", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:40:42,582 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6675606d00582 completely filled. +2023-09-19 13:41:31,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b667560aee0582. [clock=2023-09-19 13:41:31+00:00] +2023-09-19 13:41:31,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12430000 amount: 80. +2023-09-19 13:41:31,044 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246457846544974003208302341 amount: 80. +2023-09-19 13:41:31,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130891.0, "order_id": "x-XEKWYICXSSIUT605b667560aee0582", "exchange_order_id": "35718332", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:41:31,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b667560aee0582. +2023-09-19 13:41:31,496 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b66a9bc9ac0582 for 80.00000000 SEI-USDT. +2023-09-19 13:41:31,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b66a9bc9ac0582", "creation_timestamp": 1695130891.0, "exchange_order_id": "35718521", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:41:31,525 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b66a9bc72c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:41:31,552 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130891.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXBSIUT605b66a9bc72c0582", "creation_timestamp": 1695130891.0, "exchange_order_id": "35718520", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:42:26,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b66a9bc72c0582. [clock=2023-09-19 13:42:26+00:00] +2023-09-19 13:42:26,182 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b66a9bc9ac0582. [clock=2023-09-19 13:42:26+00:00] +2023-09-19 13:42:26,236 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 13:42:26,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246691496433352074031947602 amount: 80. +2023-09-19 13:42:26,720 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130946.0, "order_id": "x-XEKWYICXBSIUT605b66a9bc72c0582", "exchange_order_id": "35718520", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:42:26,720 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b66a9bc72c0582. +2023-09-19 13:42:28,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130947.0, "order_id": "x-XEKWYICXSSIUT605b66a9bc9ac0582", "exchange_order_id": "35718521", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:42:28,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b66a9bc9ac0582. +2023-09-19 13:42:28,263 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b66de5f6160582 for 80.00000000 SEI-USDT. +2023-09-19 13:42:28,314 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130947.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b66de5f6160582", "creation_timestamp": 1695130946.0, "exchange_order_id": "35718725", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:42:28,314 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b66de5f2f30582 for 80.00000000 SEI-USDT. +2023-09-19 13:42:28,362 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695130947.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b66de5f2f30582", "creation_timestamp": 1695130946.0, "exchange_order_id": "35718726", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:42:54,326 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 13:43:21,207 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b66de5f2f30582. [clock=2023-09-19 13:43:21+00:00] +2023-09-19 13:43:21,208 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b66de5f6160582. [clock=2023-09-19 13:43:21+00:00] +2023-09-19 13:43:21,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12440000 amount: 80. +2023-09-19 13:43:21,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246092760541443438401954154 amount: 80. +2023-09-19 13:43:22,639 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131002.0, "order_id": "x-XEKWYICXBSIUT605b66de5f2f30582", "exchange_order_id": "35718726", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:43:22,639 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b66de5f2f30582. +2023-09-19 13:43:24,900 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131004.0, "order_id": "x-XEKWYICXSSIUT605b66de5f6160582", "exchange_order_id": "35718725", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:43:24,900 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b66de5f6160582. +2023-09-19 13:43:25,588 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6712e024e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:43:25,650 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131004.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b6712e024e0582", "creation_timestamp": 1695131001.0, "exchange_order_id": "35718881", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:43:25,652 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6712dfd8b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:43:25,723 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131005.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXBSIUT605b6712dfd8b0582", "creation_timestamp": 1695131001.0, "exchange_order_id": "35718880", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:43:35,852 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6712dfd8b0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:43:35,861 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:43:35+00:00] +2023-09-19 13:43:35,959 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131015.0, "order_id": "x-XEKWYICXBSIUT605b6712dfd8b0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12440000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5008290", "exchange_order_id": "35718880", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:43:36,202 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131015.0, "order_id": "x-XEKWYICXBSIUT605b6712dfd8b0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9520000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35718880", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:43:36,202 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6712dfd8b0582 completely filled. +2023-09-19 13:44:16,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6712e024e0582. [clock=2023-09-19 13:44:16+00:00] +2023-09-19 13:44:16,396 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241923342735786380020020951 amount: 80. +2023-09-19 13:44:16,398 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244422627087093053391842351 amount: 80. +2023-09-19 13:44:17,031 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131056.0, "order_id": "x-XEKWYICXSSIUT605b6712e024e0582", "exchange_order_id": "35718881", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:44:17,032 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6712e024e0582. +2023-09-19 13:44:17,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b67476df990582 for 80.00000000 SEI-USDT. +2023-09-19 13:44:17,556 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131057.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b67476df990582", "creation_timestamp": 1695131056.0, "exchange_order_id": "35719067", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:44:18,243 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6747729040582 for 80.00000000 SEI-USDT. +2023-09-19 13:44:18,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131058.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b6747729040582", "creation_timestamp": 1695131056.0, "exchange_order_id": "35719074", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:45:11,019 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b67476df990582. [clock=2023-09-19 13:45:11+00:00] +2023-09-19 13:45:11,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6747729040582. [clock=2023-09-19 13:45:11+00:00] +2023-09-19 13:45:11,041 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1241163686411365069523280435 amount: 80. +2023-09-19 13:45:11,042 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243105950911178116972507673 amount: 80. +2023-09-19 13:45:11,295 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131111.0, "order_id": "x-XEKWYICXBSIUT605b67476df990582", "exchange_order_id": "35719067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:45:11,295 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b67476df990582. +2023-09-19 13:45:11,509 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131111.0, "order_id": "x-XEKWYICXSSIUT605b6747729040582", "exchange_order_id": "35719074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:45:11,510 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6747729040582. +2023-09-19 13:45:11,512 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b677b8ae3f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:45:11,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXBSIUT605b677b8ae3f0582", "creation_timestamp": 1695131111.0, "exchange_order_id": "35719398", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:45:11,523 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b677b8b1ed0582 for 80.00000000 SEI-USDT. +2023-09-19 13:45:11,533 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131111.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b677b8b1ed0582", "creation_timestamp": 1695131111.0, "exchange_order_id": "35719399", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:45:31,398 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b677b8b1ed0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:45:31,399 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:45:31+00:00] +2023-09-19 13:45:31,422 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131131.0, "order_id": "x-XEKWYICXSSIUT605b677b8b1ed0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12430000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00994400"}]}, "exchange_trade_id": "5008326", "exchange_order_id": "35719399", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:45:31,435 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131131.0, "order_id": "x-XEKWYICXSSIUT605b677b8b1ed0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9440000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35719399", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:45:31,435 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b677b8b1ed0582 completely filled. +2023-09-19 13:46:06,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b677b8ae3f0582. [clock=2023-09-19 13:46:06+00:00] +2023-09-19 13:46:06,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1239908547425836422802420265 amount: 80. +2023-09-19 13:46:06,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242282717311231025844831911 amount: 80. +2023-09-19 13:46:06,220 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131166.0, "order_id": "x-XEKWYICXBSIUT605b677b8ae3f0582", "exchange_order_id": "35719398", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:46:06,220 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b677b8ae3f0582. +2023-09-19 13:46:06,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b67affcef90582 for 80.00000000 SEI-USDT. +2023-09-19 13:46:06,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b67affcef90582", "creation_timestamp": 1695131166.0, "exchange_order_id": "35719811", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:46:06,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b67affd12f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:46:06,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131166.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b67affd12f0582", "creation_timestamp": 1695131166.0, "exchange_order_id": "35719813", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:47:01,262 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b67affcef90582. [clock=2023-09-19 13:47:01+00:00] +2023-09-19 13:47:01,263 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b67affd12f0582. [clock=2023-09-19 13:47:01+00:00] +2023-09-19 13:47:01,292 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238116226946935752027418322 amount: 80. +2023-09-19 13:47:01,293 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12420000 amount: 80. +2023-09-19 13:47:01,683 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131221.0, "order_id": "x-XEKWYICXBSIUT605b67affcef90582", "exchange_order_id": "35719811", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:47:01,684 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b67affcef90582. +2023-09-19 13:47:02,180 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131222.0, "order_id": "x-XEKWYICXSSIUT605b67affd12f0582", "exchange_order_id": "35719813", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:47:02,181 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b67affd12f0582. +2023-09-19 13:47:02,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b67e4af9950582 for 80.00000000 SEI-USDT. +2023-09-19 13:47:02,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131222.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b67e4af9950582", "creation_timestamp": 1695131221.0, "exchange_order_id": "35720038", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:47:02,251 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b67e4afcc40582 for 80.00000000 SEI-USDT. +2023-09-19 13:47:02,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131222.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b67e4afcc40582", "creation_timestamp": 1695131221.0, "exchange_order_id": "35720039", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:47:32,018 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b67e4afcc40582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:47:32,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:47:31+00:00] +2023-09-19 13:47:32,081 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131251.0, "order_id": "x-XEKWYICXSSIUT605b67e4afcc40582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12420000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00993600"}]}, "exchange_trade_id": "5008381", "exchange_order_id": "35720039", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:47:32,189 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131252.0, "order_id": "x-XEKWYICXSSIUT605b67e4afcc40582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9360000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35720039", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:47:32,189 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b67e4afcc40582 completely filled. +2023-09-19 13:47:56,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b67e4af9950582. [clock=2023-09-19 13:47:56+00:00] +2023-09-19 13:47:56,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240888614258769632655071845 amount: 80. +2023-09-19 13:47:56,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244714312183462723223002153 amount: 80. +2023-09-19 13:47:56,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131276.0, "order_id": "x-XEKWYICXBSIUT605b67e4af9950582", "exchange_order_id": "35720038", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:47:56,764 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b67e4af9950582. +2023-09-19 13:47:57,898 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b681909dd00582 for 80.00000000 SEI-USDT. +2023-09-19 13:47:57,934 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131277.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b681909dd00582", "creation_timestamp": 1695131276.0, "exchange_order_id": "35720342", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:47:57,935 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b681909a7b0582 for 80.00000000 SEI-USDT. +2023-09-19 13:47:57,959 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131277.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b681909a7b0582", "creation_timestamp": 1695131276.0, "exchange_order_id": "35720341", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:48:51,341 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b681909a7b0582. [clock=2023-09-19 13:48:51+00:00] +2023-09-19 13:48:51,350 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b681909dd00582. [clock=2023-09-19 13:48:51+00:00] +2023-09-19 13:48:51,434 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240611795120680272276679194 amount: 80. +2023-09-19 13:48:51,472 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1245565064721864990873814308 amount: 80. +2023-09-19 13:48:52,794 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131332.0, "order_id": "x-XEKWYICXBSIUT605b681909a7b0582", "exchange_order_id": "35720341", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:48:52,794 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b681909a7b0582. +2023-09-19 13:48:54,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131334.0, "order_id": "x-XEKWYICXSSIUT605b681909dd00582", "exchange_order_id": "35720342", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:48:54,506 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b681909dd00582. +2023-09-19 13:48:54,735 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b684dc30410582 for 80.00000000 SEI-USDT. +2023-09-19 13:48:54,792 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131334.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12450000", "order_id": "x-XEKWYICXSSIUT605b684dc30410582", "creation_timestamp": 1695131331.0, "exchange_order_id": "35720699", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:48:55,225 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b684dc2cfc0582 for 80.00000000 SEI-USDT. +2023-09-19 13:48:55,328 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131334.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b684dc2cfc0582", "creation_timestamp": 1695131331.0, "exchange_order_id": "35720700", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:49:47,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b684dc2cfc0582. [clock=2023-09-19 13:49:46+00:00] +2023-09-19 13:49:47,172 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b684dc30410582. [clock=2023-09-19 13:49:46+00:00] +2023-09-19 13:49:47,253 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1240920713073225722070966902 amount: 80. +2023-09-19 13:49:47,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244772506654777786698835845 amount: 80. +2023-09-19 13:49:48,281 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131386.0, "order_id": "x-XEKWYICXBSIUT605b684dc2cfc0582", "exchange_order_id": "35720700", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:49:48,281 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b684dc2cfc0582. +2023-09-19 13:49:50,146 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131389.0, "order_id": "x-XEKWYICXSSIUT605b684dc30410582", "exchange_order_id": "35720699", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:49:50,147 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b684dc30410582. +2023-09-19 13:49:50,350 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6882f91cd0582 for 80.00000000 SEI-USDT. +2023-09-19 13:49:50,424 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131390.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b6882f91cd0582", "creation_timestamp": 1695131386.0, "exchange_order_id": "35720792", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:49:50,799 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6882f8b4d0582 for 80.00000000 SEI-USDT. +2023-09-19 13:49:50,850 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131390.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6882f8b4d0582", "creation_timestamp": 1695131386.0, "exchange_order_id": "35720793", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:50:41,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6882f8b4d0582. [clock=2023-09-19 13:50:41+00:00] +2023-09-19 13:50:41,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6882f91cd0582. [clock=2023-09-19 13:50:41+00:00] +2023-09-19 13:50:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237971258130926881615142759 amount: 80. +2023-09-19 13:50:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244051532442717051846127103 amount: 80. +2023-09-19 13:50:41,237 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131441.0, "order_id": "x-XEKWYICXBSIUT605b6882f8b4d0582", "exchange_order_id": "35720793", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:50:41,237 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6882f8b4d0582. +2023-09-19 13:50:41,462 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131441.0, "order_id": "x-XEKWYICXSSIUT605b6882f91cd0582", "exchange_order_id": "35720792", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:50:41,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6882f91cd0582. +2023-09-19 13:50:41,463 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b68b63cf870582 for 80.00000000 SEI-USDT. +2023-09-19 13:50:41,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b68b63cf870582", "creation_timestamp": 1695131441.0, "exchange_order_id": "35721131", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:50:41,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b68b63cccb0582 for 80.00000000 SEI-USDT. +2023-09-19 13:50:41,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131441.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b68b63cccb0582", "creation_timestamp": 1695131441.0, "exchange_order_id": "35721132", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:51:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b68b63cccb0582. [clock=2023-09-19 13:51:36+00:00] +2023-09-19 13:51:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b68b63cf870582. [clock=2023-09-19 13:51:36+00:00] +2023-09-19 13:51:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237750423742153878490303551 amount: 80. +2023-09-19 13:51:36,025 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243593759097821878315998538 amount: 80. +2023-09-19 13:51:36,243 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131496.0, "order_id": "x-XEKWYICXBSIUT605b68b63cccb0582", "exchange_order_id": "35721132", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:51:36,244 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b68b63cccb0582. +2023-09-19 13:51:36,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131496.0, "order_id": "x-XEKWYICXSSIUT605b68b63cf870582", "exchange_order_id": "35721131", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:51:36,475 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b68b63cf870582. +2023-09-19 13:51:36,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b68eab0ebe0582 for 80.00000000 SEI-USDT. +2023-09-19 13:51:36,494 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b68eab0ebe0582", "creation_timestamp": 1695131496.0, "exchange_order_id": "35721346", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:51:36,494 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b68eab0c580582 for 80.00000000 SEI-USDT. +2023-09-19 13:51:36,506 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131496.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b68eab0c580582", "creation_timestamp": 1695131496.0, "exchange_order_id": "35721347", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:52:31,143 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b68eab0c580582. [clock=2023-09-19 13:52:31+00:00] +2023-09-19 13:52:31,152 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b68eab0ebe0582. [clock=2023-09-19 13:52:31+00:00] +2023-09-19 13:52:31,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237822427402583988752200670 amount: 80. +2023-09-19 13:52:31,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243478859346972315320550278 amount: 80. +2023-09-19 13:52:32,372 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131551.0, "order_id": "x-XEKWYICXBSIUT605b68eab0c580582", "exchange_order_id": "35721347", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:52:32,372 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b68eab0c580582. +2023-09-19 13:52:33,706 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131553.0, "order_id": "x-XEKWYICXSSIUT605b68eab0ebe0582", "exchange_order_id": "35721346", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:52:33,706 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b68eab0ebe0582. +2023-09-19 13:52:34,423 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b691f589660582 for 80.00000000 SEI-USDT. +2023-09-19 13:52:34,464 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131554.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b691f589660582", "creation_timestamp": 1695131551.0, "exchange_order_id": "35721515", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:52:34,465 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b691f5856c0582 for 80.00000000 SEI-USDT. +2023-09-19 13:52:34,530 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131554.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b691f5856c0582", "creation_timestamp": 1695131551.0, "exchange_order_id": "35721516", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:53:26,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b691f5856c0582. [clock=2023-09-19 13:53:26+00:00] +2023-09-19 13:53:26,186 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b691f589660582. [clock=2023-09-19 13:53:26+00:00] +2023-09-19 13:53:26,278 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231769468473679334362247139 amount: 80. +2023-09-19 13:53:26,279 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238990800231576880241619443 amount: 80. +2023-09-19 13:53:27,299 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131607.0, "order_id": "x-XEKWYICXBSIUT605b691f5856c0582", "exchange_order_id": "35721516", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:53:27,300 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b691f5856c0582. +2023-09-19 13:53:28,043 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131607.0, "order_id": "x-XEKWYICXSSIUT605b691f589660582", "exchange_order_id": "35721515", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:53:28,043 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b691f589660582. +2023-09-19 13:53:28,342 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6953d64950582 for 80.00000000 SEI-USDT. +2023-09-19 13:53:28,366 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131608.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605b6953d64950582", "creation_timestamp": 1695131606.0, "exchange_order_id": "35722191", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:53:28,366 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6953d686f0582 for 80.00000000 SEI-USDT. +2023-09-19 13:53:28,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131608.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605b6953d686f0582", "creation_timestamp": 1695131606.0, "exchange_order_id": "35722190", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:54:04,596 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b6953d686f0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 13:54:04,597 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 13:54:04+00:00] +2023-09-19 13:54:04,663 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131644.0, "order_id": "x-XEKWYICXSSIUT605b6953d686f0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00990400"}]}, "exchange_trade_id": "5008571", "exchange_order_id": "35722190", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 13:54:04,897 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131644.0, "order_id": "x-XEKWYICXSSIUT605b6953d686f0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35722190", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 13:54:04,897 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b6953d686f0582 completely filled. +2023-09-19 13:54:21,114 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6953d64950582. [clock=2023-09-19 13:54:21+00:00] +2023-09-19 13:54:21,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235720514542936654186514900 amount: 80. +2023-09-19 13:54:21,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243333705162059597413521071 amount: 80. +2023-09-19 13:54:21,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131661.0, "order_id": "x-XEKWYICXBSIUT605b6953d64950582", "exchange_order_id": "35722191", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:54:21,579 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6953d64950582. +2023-09-19 13:54:22,501 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b69882c3d00582 for 80.00000000 SEI-USDT. +2023-09-19 13:54:22,523 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131662.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b69882c3d00582", "creation_timestamp": 1695131661.0, "exchange_order_id": "35722748", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:54:22,710 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b69882c6c20582 for 80.00000000 SEI-USDT. +2023-09-19 13:54:22,745 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131662.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b69882c6c20582", "creation_timestamp": 1695131661.0, "exchange_order_id": "35722750", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:55:16,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b69882c3d00582. [clock=2023-09-19 13:55:16+00:00] +2023-09-19 13:55:16,035 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b69882c6c20582. [clock=2023-09-19 13:55:16+00:00] +2023-09-19 13:55:16,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235229359275422043643305502 amount: 80. +2023-09-19 13:55:16,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:55:16,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131716.0, "order_id": "x-XEKWYICXBSIUT605b69882c3d00582", "exchange_order_id": "35722748", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:55:16,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b69882c3d00582. +2023-09-19 13:55:16,813 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131716.0, "order_id": "x-XEKWYICXSSIUT605b69882c6c20582", "exchange_order_id": "35722750", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:55:16,814 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b69882c6c20582. +2023-09-19 13:55:16,815 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b69bc87c030582 for 80.00000000 SEI-USDT. +2023-09-19 13:55:16,827 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131716.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b69bc87c030582", "creation_timestamp": 1695131716.0, "exchange_order_id": "35722986", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:56:11,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b69bc87c030582. [clock=2023-09-19 13:56:11+00:00] +2023-09-19 13:56:11,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231924227213193323528986900 amount: 80. +2023-09-19 13:56:11,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238495168025776234641136882 amount: 80. +2023-09-19 13:56:11,261 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131771.0, "order_id": "x-XEKWYICXBSIUT605b69bc87c030582", "exchange_order_id": "35722986", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:56:11,262 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b69bc87c030582. +2023-09-19 13:56:11,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b69f0fb9b50582 for 80.00000000 SEI-USDT. +2023-09-19 13:56:11,341 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605b69f0fb9b50582", "creation_timestamp": 1695131771.0, "exchange_order_id": "35723514", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:56:11,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b69f0fbbfb0582 for 80.00000000 SEI-USDT. +2023-09-19 13:56:11,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131771.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605b69f0fbbfb0582", "creation_timestamp": 1695131771.0, "exchange_order_id": "35723515", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:57:06,106 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b69f0fb9b50582. [clock=2023-09-19 13:57:06+00:00] +2023-09-19 13:57:06,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b69f0fbbfb0582. [clock=2023-09-19 13:57:06+00:00] +2023-09-19 13:57:06,138 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227532767772673823525670283 amount: 80. +2023-09-19 13:57:06,139 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:57:06,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131826.0, "order_id": "x-XEKWYICXBSIUT605b69f0fb9b50582", "exchange_order_id": "35723514", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:57:06,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b69f0fb9b50582. +2023-09-19 13:57:06,654 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131826.0, "order_id": "x-XEKWYICXSSIUT605b69f0fbbfb0582", "exchange_order_id": "35723515", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:57:06,655 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b69f0fbbfb0582. +2023-09-19 13:57:06,658 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6a2582f950582 for 80.00000000 SEI-USDT. +2023-09-19 13:57:06,678 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131826.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605b6a2582f950582", "creation_timestamp": 1695131826.0, "exchange_order_id": "35724739", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:58:01,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6a2582f950582. [clock=2023-09-19 13:58:01+00:00] +2023-09-19 13:58:01,103 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226845197216652355130342278 amount: 80. +2023-09-19 13:58:01,104 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1236764389199630730172946524 amount: 80. +2023-09-19 13:58:02,344 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131882.0, "order_id": "x-XEKWYICXBSIUT605b6a2582f950582", "exchange_order_id": "35724739", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:58:02,344 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6a2582f950582. +2023-09-19 13:58:03,186 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6a59ee7160582 for 80.00000000 SEI-USDT. +2023-09-19 13:58:03,226 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131882.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXSSIUT605b6a59ee7160582", "creation_timestamp": 1695131881.0, "exchange_order_id": "35725412", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:58:03,226 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6a59ee4220582 for 80.00000000 SEI-USDT. +2023-09-19 13:58:03,258 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131882.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605b6a59ee4220582", "creation_timestamp": 1695131881.0, "exchange_order_id": "35725413", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:58:56,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6a59ee4220582. [clock=2023-09-19 13:58:56+00:00] +2023-09-19 13:58:56,238 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6a59ee7160582. [clock=2023-09-19 13:58:56+00:00] +2023-09-19 13:58:56,304 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226525172423403452814054460 amount: 80. +2023-09-19 13:58:56,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 13:58:57,363 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131936.0, "order_id": "x-XEKWYICXBSIUT605b6a59ee4220582", "exchange_order_id": "35725413", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:58:57,363 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6a59ee4220582. +2023-09-19 13:58:58,793 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131938.0, "order_id": "x-XEKWYICXSSIUT605b6a59ee7160582", "exchange_order_id": "35725412", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:58:58,794 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6a59ee7160582. +2023-09-19 13:58:59,326 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6a8e9321e0582 for 80.00000000 SEI-USDT. +2023-09-19 13:58:59,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131938.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605b6a8e9321e0582", "creation_timestamp": 1695131936.0, "exchange_order_id": "35726035", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:59:51,242 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6a8e9321e0582. [clock=2023-09-19 13:59:51+00:00] +2023-09-19 13:59:51,330 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1227054548327044761121566447 amount: 80. +2023-09-19 13:59:51,342 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1235029973236658007851427527 amount: 80. +2023-09-19 13:59:52,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131991.0, "order_id": "x-XEKWYICXBSIUT605b6a8e9321e0582", "exchange_order_id": "35726035", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 13:59:52,522 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6a8e9321e0582. +2023-09-19 13:59:53,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6ac30fece0582 for 80.00000000 SEI-USDT. +2023-09-19 13:59:53,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131993.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12270000", "order_id": "x-XEKWYICXBSIUT605b6ac30fece0582", "creation_timestamp": 1695131991.0, "exchange_order_id": "35726603", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 13:59:53,837 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6ac31269d0582 for 80.00000000 SEI-USDT. +2023-09-19 13:59:53,866 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695131993.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXSSIUT605b6ac31269d0582", "creation_timestamp": 1695131991.0, "exchange_order_id": "35726604", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:00:46,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6ac30fece0582. [clock=2023-09-19 14:00:46+00:00] +2023-09-19 14:00:46,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6ac31269d0582. [clock=2023-09-19 14:00:46+00:00] +2023-09-19 14:00:46,064 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225086613451725313461998761 amount: 80. +2023-09-19 14:00:46,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:00:46,264 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132046.0, "order_id": "x-XEKWYICXBSIUT605b6ac30fece0582", "exchange_order_id": "35726603", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:00:46,264 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6ac30fece0582. +2023-09-19 14:00:46,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6af73ffd10582 for 80.00000000 SEI-USDT. +2023-09-19 14:00:46,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132046.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605b6af73ffd10582", "creation_timestamp": 1695132046.0, "exchange_order_id": "35727524", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:00:46,520 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132046.0, "order_id": "x-XEKWYICXSSIUT605b6ac31269d0582", "exchange_order_id": "35726604", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:00:46,520 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6ac31269d0582. +2023-09-19 14:01:41,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6af73ffd10582. [clock=2023-09-19 14:01:41+00:00] +2023-09-19 14:01:41,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1224722282502849594231557143 amount: 80. +2023-09-19 14:01:41,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233233449995376076460118888 amount: 80. +2023-09-19 14:01:41,240 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132101.0, "order_id": "x-XEKWYICXBSIUT605b6af73ffd10582", "exchange_order_id": "35727524", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:01:41,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6af73ffd10582. +2023-09-19 14:01:41,469 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6b2baf6c50582 for 80.00000000 SEI-USDT. +2023-09-19 14:01:41,483 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12240000", "order_id": "x-XEKWYICXBSIUT605b6b2baf6c50582", "creation_timestamp": 1695132101.0, "exchange_order_id": "35728084", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:01:41,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6b2baf9f50582 for 80.00000000 SEI-USDT. +2023-09-19 14:01:41,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132101.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605b6b2baf9f50582", "creation_timestamp": 1695132101.0, "exchange_order_id": "35728086", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:02:36,270 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6b2baf6c50582. [clock=2023-09-19 14:02:36+00:00] +2023-09-19 14:02:36,280 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6b2baf9f50582. [clock=2023-09-19 14:02:36+00:00] +2023-09-19 14:02:36,358 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1225669561056097282703258220 amount: 80. +2023-09-19 14:02:36,360 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:02:36,870 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132156.0, "order_id": "x-XEKWYICXBSIUT605b6b2baf6c50582", "exchange_order_id": "35728084", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:02:36,871 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6b2baf6c50582. +2023-09-19 14:02:38,167 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132158.0, "order_id": "x-XEKWYICXSSIUT605b6b2baf9f50582", "exchange_order_id": "35728086", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:02:38,167 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6b2baf9f50582. +2023-09-19 14:02:38,441 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6b606f4c80582 for 80.00000000 SEI-USDT. +2023-09-19 14:02:38,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132158.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12250000", "order_id": "x-XEKWYICXBSIUT605b6b606f4c80582", "creation_timestamp": 1695132156.0, "exchange_order_id": "35728753", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:03:31,095 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6b606f4c80582. [clock=2023-09-19 14:03:31+00:00] +2023-09-19 14:03:31,152 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1226162397077496722730817464 amount: 80. +2023-09-19 14:03:31,153 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1233965312919390966985346292 amount: 80. +2023-09-19 14:03:31,652 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132211.0, "order_id": "x-XEKWYICXBSIUT605b6b606f4c80582", "exchange_order_id": "35728753", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:03:31,653 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6b606f4c80582. +2023-09-19 14:03:32,763 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6b94b08d40582 for 80.00000000 SEI-USDT. +2023-09-19 14:03:32,799 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132212.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12260000", "order_id": "x-XEKWYICXBSIUT605b6b94b08d40582", "creation_timestamp": 1695132211.0, "exchange_order_id": "35729587", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:03:33,108 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6b94b0bd20582 for 80.00000000 SEI-USDT. +2023-09-19 14:03:33,147 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132212.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12330000", "order_id": "x-XEKWYICXSSIUT605b6b94b0bd20582", "creation_timestamp": 1695132211.0, "exchange_order_id": "35729598", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:03:56,664 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b6b94b0bd20582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:03:56,665 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:03:56+00:00] +2023-09-19 14:03:56,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132236.0, "order_id": "x-XEKWYICXSSIUT605b6b94b0bd20582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12330000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00986400"}]}, "exchange_trade_id": "5009110", "exchange_order_id": "35729598", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:03:56,831 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132236.0, "order_id": "x-XEKWYICXSSIUT605b6b94b0bd20582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8640000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35729598", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:03:56,832 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b6b94b0bd20582 completely filled. +2023-09-19 14:04:26,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6b94b08d40582. [clock=2023-09-19 14:04:26+00:00] +2023-09-19 14:04:26,394 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230820558232659262584565058 amount: 80. +2023-09-19 14:04:26,404 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:04:27,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132266.0, "order_id": "x-XEKWYICXBSIUT605b6b94b08d40582", "exchange_order_id": "35729587", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:04:27,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6b94b08d40582. +2023-09-19 14:04:29,066 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6bc961bc90582 for 80.00000000 SEI-USDT. +2023-09-19 14:04:29,129 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132268.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605b6bc961bc90582", "creation_timestamp": 1695132266.0, "exchange_order_id": "35730067", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:05:21,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6bc961bc90582. [clock=2023-09-19 14:05:21+00:00] +2023-09-19 14:05:21,083 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1229357324262062264653083760 amount: 80. +2023-09-19 14:05:21,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:05:21,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132321.0, "order_id": "x-XEKWYICXBSIUT605b6bc961bc90582", "exchange_order_id": "35730067", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:05:21,309 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6bc961bc90582. +2023-09-19 14:05:21,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6bfd873460582 for 80.00000000 SEI-USDT. +2023-09-19 14:05:21,522 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132321.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12290000", "order_id": "x-XEKWYICXBSIUT605b6bfd873460582", "creation_timestamp": 1695132321.0, "exchange_order_id": "35730569", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:06:16,039 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6bfd873460582. [clock=2023-09-19 14:06:16+00:00] +2023-09-19 14:06:16,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1230747967110685184764348720 amount: 80. +2023-09-19 14:06:16,060 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:06:16,250 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132376.0, "order_id": "x-XEKWYICXBSIUT605b6bfd873460582", "exchange_order_id": "35730569", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:06:16,250 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6bfd873460582. +2023-09-19 14:06:16,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6c31f52570582 for 80.00000000 SEI-USDT. +2023-09-19 14:06:16,510 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132376.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12300000", "order_id": "x-XEKWYICXBSIUT605b6c31f52570582", "creation_timestamp": 1695132376.0, "exchange_order_id": "35730948", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:07:11,471 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6c31f52570582. [clock=2023-09-19 14:07:11+00:00] +2023-09-19 14:07:11,514 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232413941702013061436156780 amount: 80. +2023-09-19 14:07:11,515 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:07:12,254 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132431.0, "order_id": "x-XEKWYICXBSIUT605b6c31f52570582", "exchange_order_id": "35730948", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:07:12,255 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6c31f52570582. +2023-09-19 14:07:13,894 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6c66d7d080582 for 80.00000000 SEI-USDT. +2023-09-19 14:07:13,932 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132433.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605b6c66d7d080582", "creation_timestamp": 1695132431.0, "exchange_order_id": "35731374", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:08:06,359 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6c66d7d080582. [clock=2023-09-19 14:08:06+00:00] +2023-09-19 14:08:06,446 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12340000 amount: 80. +2023-09-19 14:08:06,447 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:08:07,319 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132487.0, "order_id": "x-XEKWYICXBSIUT605b6c66d7d080582", "exchange_order_id": "35731374", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:08:07,319 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6c66d7d080582. +2023-09-19 14:08:07,468 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6c9b3b1200582 for 80.00000000 SEI-USDT. +2023-09-19 14:08:07,497 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132487.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b6c9b3b1200582", "creation_timestamp": 1695132486.0, "exchange_order_id": "35731897", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:09:01,346 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6c9b3b1200582. [clock=2023-09-19 14:09:01+00:00] +2023-09-19 14:09:01,431 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12350000 amount: 80. +2023-09-19 14:09:01,432 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:09:02,286 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132541.0, "order_id": "x-XEKWYICXBSIUT605b6c9b3b1200582", "exchange_order_id": "35731897", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:09:02,287 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6c9b3b1200582. +2023-09-19 14:09:03,025 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6ccfab3e70582 for 80.00000000 SEI-USDT. +2023-09-19 14:09:03,056 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132542.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b6ccfab3e70582", "creation_timestamp": 1695132541.0, "exchange_order_id": "35732303", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:09:07,829 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6ccfab3e70582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:09:07,844 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:09:07+00:00] +2023-09-19 14:09:07,959 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132547.0, "order_id": "x-XEKWYICXBSIUT605b6ccfab3e70582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12350000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5009283", "exchange_order_id": "35732303", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:09:08,158 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132547.0, "order_id": "x-XEKWYICXBSIUT605b6ccfab3e70582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8800000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35732303", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:09:08,159 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6ccfab3e70582 completely filled. +2023-09-19 14:09:56,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234810565432548407776338860 amount: 80. +2023-09-19 14:09:56,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243411675993453023850559718 amount: 80. +2023-09-19 14:09:56,644 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6d03d5d830582 for 80.00000000 SEI-USDT. +2023-09-19 14:09:56,685 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132596.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b6d03d5d830582", "creation_timestamp": 1695132596.0, "exchange_order_id": "35732711", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:09:57,370 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6d03d60860582 for 80.00000000 SEI-USDT. +2023-09-19 14:09:57,400 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132597.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b6d03d60860582", "creation_timestamp": 1695132596.0, "exchange_order_id": "35732713", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:10:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6d03d5d830582. [clock=2023-09-19 14:10:51+00:00] +2023-09-19 14:10:51,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6d03d60860582. [clock=2023-09-19 14:10:51+00:00] +2023-09-19 14:10:51,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234829142228304930085855506 amount: 80. +2023-09-19 14:10:51,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:10:51,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132651.0, "order_id": "x-XEKWYICXBSIUT605b6d03d5d830582", "exchange_order_id": "35732711", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:10:51,230 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6d03d5d830582. +2023-09-19 14:10:51,452 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132651.0, "order_id": "x-XEKWYICXSSIUT605b6d03d60860582", "exchange_order_id": "35732713", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:10:51,452 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6d03d60860582. +2023-09-19 14:10:51,454 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6d38345950582 for 80.00000000 SEI-USDT. +2023-09-19 14:10:51,468 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132651.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b6d38345950582", "creation_timestamp": 1695132651.0, "exchange_order_id": "35733074", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:11:46,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6d38345950582. [clock=2023-09-19 14:11:46+00:00] +2023-09-19 14:11:46,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234738336992518886800912175 amount: 80. +2023-09-19 14:11:46,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241917217388132217708765153 amount: 80. +2023-09-19 14:11:46,241 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132706.0, "order_id": "x-XEKWYICXBSIUT605b6d38345950582", "exchange_order_id": "35733074", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:11:46,242 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6d38345950582. +2023-09-19 14:11:46,442 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6d6ca242a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:11:46,474 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b6d6ca242a0582", "creation_timestamp": 1695132706.0, "exchange_order_id": "35733431", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:11:46,566 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6d6ca26690582 for 80.00000000 SEI-USDT. +2023-09-19 14:11:46,595 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132706.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b6d6ca26690582", "creation_timestamp": 1695132706.0, "exchange_order_id": "35733432", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:12:41,140 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6d6ca242a0582. [clock=2023-09-19 14:12:41+00:00] +2023-09-19 14:12:41,158 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6d6ca26690582. [clock=2023-09-19 14:12:41+00:00] +2023-09-19 14:12:41,195 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12360000 amount: 80. +2023-09-19 14:12:41,196 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:12:41,712 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132761.0, "order_id": "x-XEKWYICXBSIUT605b6d6ca242a0582", "exchange_order_id": "35733431", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:12:41,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6d6ca242a0582. +2023-09-19 14:12:42,324 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132762.0, "order_id": "x-XEKWYICXSSIUT605b6d6ca26690582", "exchange_order_id": "35733432", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:12:42,324 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6d6ca26690582. +2023-09-19 14:12:42,645 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6da1406c10582 for 80.00000000 SEI-USDT. +2023-09-19 14:12:42,675 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132762.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b6da1406c10582", "creation_timestamp": 1695132761.0, "exchange_order_id": "35733849", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:12:54,629 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 14:13:10,377 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6da1406c10582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:13:10,380 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:13:10+00:00] +2023-09-19 14:13:10,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132790.0, "order_id": "x-XEKWYICXBSIUT605b6da1406c10582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12360000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5009362", "exchange_order_id": "35733849", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:13:10,668 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132790.0, "order_id": "x-XEKWYICXBSIUT605b6da1406c10582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8880000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35733849", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:13:10,669 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6da1406c10582 completely filled. +2023-09-19 14:13:36,268 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234949957871664502689461417 amount: 80. +2023-09-19 14:13:36,269 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241270655929088026741904718 amount: 80. +2023-09-19 14:13:37,353 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6dd5c60550582 for 80.00000000 SEI-USDT. +2023-09-19 14:13:37,412 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132817.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b6dd5c60550582", "creation_timestamp": 1695132816.0, "exchange_order_id": "35734153", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:13:38,692 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6dd5c64bc0582 for 80.00000000 SEI-USDT. +2023-09-19 14:13:38,745 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132818.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b6dd5c64bc0582", "creation_timestamp": 1695132816.0, "exchange_order_id": "35734155", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:14:26,840 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b6dd5c64bc0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:14:26,841 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:14:26+00:00] +2023-09-19 14:14:26,896 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132866.0, "order_id": "x-XEKWYICXSSIUT605b6dd5c64bc0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12410000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00992800"}]}, "exchange_trade_id": "5009445", "exchange_order_id": "35734155", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:14:27,001 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132866.0, "order_id": "x-XEKWYICXSSIUT605b6dd5c64bc0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9280000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35734155", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:14:27,001 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b6dd5c64bc0582 completely filled. +2023-09-19 14:14:31,302 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6dd5c60550582. [clock=2023-09-19 14:14:31+00:00] +2023-09-19 14:14:31,378 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-19 14:14:31,381 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1252950264940840948162248630 amount: 80. +2023-09-19 14:14:32,177 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132871.0, "order_id": "x-XEKWYICXBSIUT605b6dd5c60550582", "exchange_order_id": "35734153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:14:32,177 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6dd5c60550582. +2023-09-19 14:14:33,791 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6e0a5500e0582 for 80.00000000 SEI-USDT. +2023-09-19 14:14:33,859 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132873.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6e0a5500e0582", "creation_timestamp": 1695132871.0, "exchange_order_id": "35734772", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:14:34,368 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6e0a5936c0582 for 80.00000000 SEI-USDT. +2023-09-19 14:14:34,496 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132873.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12520000", "order_id": "x-XEKWYICXSSIUT605b6e0a5936c0582", "creation_timestamp": 1695132871.0, "exchange_order_id": "35734775", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:15:26,198 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6e0a5500e0582. [clock=2023-09-19 14:15:26+00:00] +2023-09-19 14:15:26,199 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6e0a5936c0582. [clock=2023-09-19 14:15:26+00:00] +2023-09-19 14:15:26,221 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-19 14:15:26,222 - 1 - hummingbot.strategy.script_strategy_base - INFO - Not enough funds to place the OrderType.LIMIT order +2023-09-19 14:15:26,553 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132926.0, "order_id": "x-XEKWYICXBSIUT605b6e0a5500e0582", "exchange_order_id": "35734772", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:15:26,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6e0a5500e0582. +2023-09-19 14:15:26,722 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132926.0, "order_id": "x-XEKWYICXSSIUT605b6e0a5936c0582", "exchange_order_id": "35734775", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:15:26,723 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6e0a5936c0582. +2023-09-19 14:15:26,726 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6e3ea1f450582 for 80.00000000 SEI-USDT. +2023-09-19 14:15:26,740 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132926.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6e3ea1f450582", "creation_timestamp": 1695132926.0, "exchange_order_id": "35735099", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:15:45,286 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6e3ea1f450582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:15:45,287 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:15:45+00:00] +2023-09-19 14:15:45,311 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132945.0, "order_id": "x-XEKWYICXBSIUT605b6e3ea1f450582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5009479", "exchange_order_id": "35735099", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:15:45,323 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132945.0, "order_id": "x-XEKWYICXBSIUT605b6e3ea1f450582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35735099", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:15:45,323 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6e3ea1f450582 completely filled. +2023-09-19 14:16:21,021 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238534955497404094629984119 amount: 80. +2023-09-19 14:16:21,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247051082294277582391724339 amount: 80. +2023-09-19 14:16:21,240 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6e72e50840582 for 80.00000000 SEI-USDT. +2023-09-19 14:16:21,251 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b6e72e50840582", "creation_timestamp": 1695132981.0, "exchange_order_id": "35735308", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:16:21,409 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6e72e55d90582 for 80.00000000 SEI-USDT. +2023-09-19 14:16:21,420 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695132981.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b6e72e55d90582", "creation_timestamp": 1695132981.0, "exchange_order_id": "35735309", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:17:03,716 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6e72e50840582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:17:03,717 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:17:03+00:00] +2023-09-19 14:17:03,737 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133023.0, "order_id": "x-XEKWYICXBSIUT605b6e72e50840582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12380000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5009516", "exchange_order_id": "35735308", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:17:03,749 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133023.0, "order_id": "x-XEKWYICXBSIUT605b6e72e50840582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9040000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35735308", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:17:03,750 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6e72e50840582 completely filled. +2023-09-19 14:17:16,842 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6e72e55d90582. [clock=2023-09-19 14:17:16+00:00] +2023-09-19 14:17:16,923 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235997974965303934856936532 amount: 80. +2023-09-19 14:17:16,924 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243729476191638604216017180 amount: 80. +2023-09-19 14:17:18,273 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133037.0, "order_id": "x-XEKWYICXSSIUT605b6e72e55d90582", "exchange_order_id": "35735309", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:17:18,274 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6e72e55d90582. +2023-09-19 14:17:19,417 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6ea834fa40582 for 80.00000000 SEI-USDT. +2023-09-19 14:17:19,471 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133039.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b6ea834fa40582", "creation_timestamp": 1695133036.0, "exchange_order_id": "35735640", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:17:19,828 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6ea8353160582 for 80.00000000 SEI-USDT. +2023-09-19 14:17:19,877 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133039.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b6ea8353160582", "creation_timestamp": 1695133036.0, "exchange_order_id": "35735641", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:18:11,194 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6ea834fa40582. [clock=2023-09-19 14:18:11+00:00] +2023-09-19 14:18:11,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6ea8353160582. [clock=2023-09-19 14:18:11+00:00] +2023-09-19 14:18:11,288 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238943012747391135749845349 amount: 80. +2023-09-19 14:18:11,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1246072865802708771452069951 amount: 80. +2023-09-19 14:18:12,604 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133092.0, "order_id": "x-XEKWYICXBSIUT605b6ea834fa40582", "exchange_order_id": "35735640", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:18:12,605 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6ea834fa40582. +2023-09-19 14:18:12,661 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133092.0, "order_id": "x-XEKWYICXSSIUT605b6ea8353160582", "exchange_order_id": "35735641", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:18:12,661 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6ea8353160582. +2023-09-19 14:18:13,482 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6edc0db760582 for 80.00000000 SEI-USDT. +2023-09-19 14:18:13,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133093.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b6edc0db760582", "creation_timestamp": 1695133091.0, "exchange_order_id": "35735981", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:18:13,708 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6edc0df650582 for 80.00000000 SEI-USDT. +2023-09-19 14:18:13,751 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133093.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12460000", "order_id": "x-XEKWYICXSSIUT605b6edc0df650582", "creation_timestamp": 1695133091.0, "exchange_order_id": "35735984", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:19:06,061 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6edc0db760582. [clock=2023-09-19 14:19:06+00:00] +2023-09-19 14:19:06,062 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6edc0df650582. [clock=2023-09-19 14:19:06+00:00] +2023-09-19 14:19:06,116 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12400000 amount: 80. +2023-09-19 14:19:06,117 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247514800439847607957706721 amount: 80. +2023-09-19 14:19:06,713 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133146.0, "order_id": "x-XEKWYICXBSIUT605b6edc0db760582", "exchange_order_id": "35735981", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:19:06,713 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6edc0db760582. +2023-09-19 14:19:07,407 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133147.0, "order_id": "x-XEKWYICXSSIUT605b6edc0df650582", "exchange_order_id": "35735984", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:19:07,408 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6edc0df650582. +2023-09-19 14:19:07,708 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6f10578dc0582 for 80.00000000 SEI-USDT. +2023-09-19 14:19:07,732 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133147.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b6f10578dc0582", "creation_timestamp": 1695133146.0, "exchange_order_id": "35737134", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:19:07,733 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6f10575c60582 for 80.00000000 SEI-USDT. +2023-09-19 14:19:07,758 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133147.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXBSIUT605b6f10575c60582", "creation_timestamp": 1695133146.0, "exchange_order_id": "35737131", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:19:27,468 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b6f10575c60582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:19:27,469 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:19:27+00:00] +2023-09-19 14:19:27,548 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133167.0, "order_id": "x-XEKWYICXBSIUT605b6f10575c60582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12400000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5009712", "exchange_order_id": "35737131", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:19:27,802 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133167.0, "order_id": "x-XEKWYICXBSIUT605b6f10575c60582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9200000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35737131", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:19:27,802 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b6f10575c60582 completely filled. +2023-09-19 14:20:01,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6f10578dc0582. [clock=2023-09-19 14:20:01+00:00] +2023-09-19 14:20:01,132 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236782118725172645856311082 amount: 80. +2023-09-19 14:20:01,133 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244294936815246024804549346 amount: 80. +2023-09-19 14:20:02,779 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133202.0, "order_id": "x-XEKWYICXSSIUT605b6f10578dc0582", "exchange_order_id": "35737134", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:20:02,780 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6f10578dc0582. +2023-09-19 14:20:03,485 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6f44d17130582 for 80.00000000 SEI-USDT. +2023-09-19 14:20:03,546 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133202.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b6f44d17130582", "creation_timestamp": 1695133201.0, "exchange_order_id": "35737821", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:20:03,623 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6f44cefda0582 for 80.00000000 SEI-USDT. +2023-09-19 14:20:03,669 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133202.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b6f44cefda0582", "creation_timestamp": 1695133201.0, "exchange_order_id": "35737822", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:20:56,001 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6f44cefda0582. [clock=2023-09-19 14:20:56+00:00] +2023-09-19 14:20:56,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6f44d17130582. [clock=2023-09-19 14:20:56+00:00] +2023-09-19 14:20:56,028 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236948486445567941171513579 amount: 80. +2023-09-19 14:20:56,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243902750056797274964967085 amount: 80. +2023-09-19 14:20:56,309 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133256.0, "order_id": "x-XEKWYICXBSIUT605b6f44cefda0582", "exchange_order_id": "35737822", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:20:56,310 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6f44cefda0582. +2023-09-19 14:20:56,518 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133256.0, "order_id": "x-XEKWYICXSSIUT605b6f44d17130582", "exchange_order_id": "35737821", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:20:56,518 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6f44d17130582. +2023-09-19 14:20:56,520 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6f79295cb0582 for 80.00000000 SEI-USDT. +2023-09-19 14:20:56,539 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b6f79295cb0582", "creation_timestamp": 1695133256.0, "exchange_order_id": "35738098", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:20:56,717 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6f792994a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:20:56,740 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133256.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b6f792994a0582", "creation_timestamp": 1695133256.0, "exchange_order_id": "35738099", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:21:51,018 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6f79295cb0582. [clock=2023-09-19 14:21:51+00:00] +2023-09-19 14:21:51,020 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6f792994a0582. [clock=2023-09-19 14:21:51+00:00] +2023-09-19 14:21:51,043 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237025227561377530664105120 amount: 80. +2023-09-19 14:21:51,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242435430519661727545154486 amount: 80. +2023-09-19 14:21:51,284 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133311.0, "order_id": "x-XEKWYICXBSIUT605b6f79295cb0582", "exchange_order_id": "35738098", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:21:51,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6f79295cb0582. +2023-09-19 14:21:51,502 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6fada0db60582 for 80.00000000 SEI-USDT. +2023-09-19 14:21:51,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b6fada0db60582", "creation_timestamp": 1695133311.0, "exchange_order_id": "35738411", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:21:51,528 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6fada10c80582 for 80.00000000 SEI-USDT. +2023-09-19 14:21:51,550 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133311.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b6fada10c80582", "creation_timestamp": 1695133311.0, "exchange_order_id": "35738412", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:21:51,565 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133311.0, "order_id": "x-XEKWYICXSSIUT605b6f792994a0582", "exchange_order_id": "35738099", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:21:51,565 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6f792994a0582. +2023-09-19 14:22:46,029 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6fada0db60582. [clock=2023-09-19 14:22:46+00:00] +2023-09-19 14:22:46,030 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6fada10c80582. [clock=2023-09-19 14:22:46+00:00] +2023-09-19 14:22:46,084 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238277157129799227962817629 amount: 80. +2023-09-19 14:22:46,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244707385766124926401021513 amount: 80. +2023-09-19 14:22:46,702 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133366.0, "order_id": "x-XEKWYICXBSIUT605b6fada0db60582", "exchange_order_id": "35738411", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:22:46,702 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6fada0db60582. +2023-09-19 14:22:47,230 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133367.0, "order_id": "x-XEKWYICXSSIUT605b6fada10c80582", "exchange_order_id": "35738412", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:22:47,231 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6fada10c80582. +2023-09-19 14:22:47,535 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b6fe21e9880582 for 80.00000000 SEI-USDT. +2023-09-19 14:22:47,554 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b6fe21e9880582", "creation_timestamp": 1695133366.0, "exchange_order_id": "35738817", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:22:47,554 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b6fe21e6760582 for 80.00000000 SEI-USDT. +2023-09-19 14:22:47,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133367.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b6fe21e6760582", "creation_timestamp": 1695133366.0, "exchange_order_id": "35738818", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:23:41,170 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b6fe21e6760582. [clock=2023-09-19 14:23:41+00:00] +2023-09-19 14:23:41,171 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b6fe21e9880582. [clock=2023-09-19 14:23:41+00:00] +2023-09-19 14:23:41,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238659627370585767988927984 amount: 80. +2023-09-19 14:23:41,218 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243659576166944634593846260 amount: 80. +2023-09-19 14:23:41,875 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133421.0, "order_id": "x-XEKWYICXBSIUT605b6fe21e6760582", "exchange_order_id": "35738818", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:23:41,876 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b6fe21e6760582. +2023-09-19 14:23:43,665 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133423.0, "order_id": "x-XEKWYICXSSIUT605b6fe21e9880582", "exchange_order_id": "35738817", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:23:43,666 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b6fe21e9880582. +2023-09-19 14:23:44,241 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7016b2afd0582 for 80.00000000 SEI-USDT. +2023-09-19 14:23:44,283 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133423.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b7016b2afd0582", "creation_timestamp": 1695133421.0, "exchange_order_id": "35738969", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:23:44,284 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7016b30ca0582 for 80.00000000 SEI-USDT. +2023-09-19 14:23:44,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133423.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b7016b30ca0582", "creation_timestamp": 1695133421.0, "exchange_order_id": "35738970", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:24:36,219 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b7016b2afd0582. [clock=2023-09-19 14:24:36+00:00] +2023-09-19 14:24:36,228 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b7016b30ca0582. [clock=2023-09-19 14:24:36+00:00] +2023-09-19 14:24:36,295 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1238236850935435374773339051 amount: 80. +2023-09-19 14:24:36,309 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243239286802767495073303510 amount: 80. +2023-09-19 14:24:37,426 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133476.0, "order_id": "x-XEKWYICXBSIUT605b7016b2afd0582", "exchange_order_id": "35738969", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:24:37,426 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b7016b2afd0582. +2023-09-19 14:24:39,385 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133478.0, "order_id": "x-XEKWYICXSSIUT605b7016b30ca0582", "exchange_order_id": "35738970", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:24:39,386 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b7016b30ca0582. +2023-09-19 14:24:40,531 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b704b3d05b0582 for 80.00000000 SEI-USDT. +2023-09-19 14:24:40,583 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133479.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b704b3d05b0582", "creation_timestamp": 1695133476.0, "exchange_order_id": "35739077", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:24:40,583 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b704b396b20582 for 80.00000000 SEI-USDT. +2023-09-19 14:24:40,626 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133479.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXBSIUT605b704b396b20582", "creation_timestamp": 1695133476.0, "exchange_order_id": "35739078", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:25:31,144 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b704b396b20582. [clock=2023-09-19 14:25:31+00:00] +2023-09-19 14:25:31,145 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b704b3d05b0582. [clock=2023-09-19 14:25:31+00:00] +2023-09-19 14:25:31,165 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237630005848202569052205050 amount: 80. +2023-09-19 14:25:31,166 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241517137598140769884170979 amount: 80. +2023-09-19 14:25:31,444 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133531.0, "order_id": "x-XEKWYICXBSIUT605b704b396b20582", "exchange_order_id": "35739078", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:25:31,444 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b704b396b20582. +2023-09-19 14:25:31,661 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133531.0, "order_id": "x-XEKWYICXSSIUT605b704b3d05b0582", "exchange_order_id": "35739077", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:25:31,662 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b704b3d05b0582. +2023-09-19 14:25:31,664 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b707f8d4f80582 for 80.00000000 SEI-USDT. +2023-09-19 14:25:31,677 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b707f8d4f80582", "creation_timestamp": 1695133531.0, "exchange_order_id": "35739351", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:25:31,678 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b707f8d8480582 for 80.00000000 SEI-USDT. +2023-09-19 14:25:31,688 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133531.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b707f8d8480582", "creation_timestamp": 1695133531.0, "exchange_order_id": "35739352", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:26:26,085 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b707f8d4f80582. [clock=2023-09-19 14:26:26+00:00] +2023-09-19 14:26:26,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b707f8d8480582. [clock=2023-09-19 14:26:26+00:00] +2023-09-19 14:26:26,107 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237185979609435330527808559 amount: 80. +2023-09-19 14:26:26,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242184383660037070156248522 amount: 80. +2023-09-19 14:26:26,325 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133586.0, "order_id": "x-XEKWYICXBSIUT605b707f8d4f80582", "exchange_order_id": "35739351", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:26:26,325 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b707f8d4f80582. +2023-09-19 14:26:26,547 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133586.0, "order_id": "x-XEKWYICXSSIUT605b707f8d8480582", "exchange_order_id": "35739352", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:26:26,547 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b707f8d8480582. +2023-09-19 14:26:26,549 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b70b3f31240582 for 80.00000000 SEI-USDT. +2023-09-19 14:26:26,562 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b70b3f31240582", "creation_timestamp": 1695133586.0, "exchange_order_id": "35739511", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:26:26,563 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b70b3f2e0b0582 for 80.00000000 SEI-USDT. +2023-09-19 14:26:26,573 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133586.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b70b3f2e0b0582", "creation_timestamp": 1695133586.0, "exchange_order_id": "35739512", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:27:21,108 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b70b3f2e0b0582. [clock=2023-09-19 14:27:21+00:00] +2023-09-19 14:27:21,110 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b70b3f31240582. [clock=2023-09-19 14:27:21+00:00] +2023-09-19 14:27:21,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237344059963085369951202005 amount: 80. +2023-09-19 14:27:21,157 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241233601329853290203055255 amount: 80. +2023-09-19 14:27:21,978 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133641.0, "order_id": "x-XEKWYICXBSIUT605b70b3f2e0b0582", "exchange_order_id": "35739512", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:27:21,979 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b70b3f2e0b0582. +2023-09-19 14:27:24,345 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133643.0, "order_id": "x-XEKWYICXSSIUT605b70b3f31240582", "exchange_order_id": "35739511", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:27:24,345 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b70b3f31240582. +2023-09-19 14:27:24,723 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b70e872b080582 for 80.00000000 SEI-USDT. +2023-09-19 14:27:24,780 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133644.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b70e872b080582", "creation_timestamp": 1695133641.0, "exchange_order_id": "35739729", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:27:24,782 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b70e872dd80582 for 80.00000000 SEI-USDT. +2023-09-19 14:27:24,823 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133644.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b70e872dd80582", "creation_timestamp": 1695133641.0, "exchange_order_id": "35739730", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:28:16,146 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b70e872b080582. [clock=2023-09-19 14:28:16+00:00] +2023-09-19 14:28:16,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b70e872dd80582. [clock=2023-09-19 14:28:16+00:00] +2023-09-19 14:28:16,229 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237619179822636866004745372 amount: 80. +2023-09-19 14:28:16,243 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241506500184780660824685258 amount: 80. +2023-09-19 14:28:17,509 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133697.0, "order_id": "x-XEKWYICXBSIUT605b70e872b080582", "exchange_order_id": "35739729", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:28:17,509 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b70e872b080582. +2023-09-19 14:28:17,564 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133697.0, "order_id": "x-XEKWYICXSSIUT605b70e872dd80582", "exchange_order_id": "35739730", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:28:17,564 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b70e872dd80582. +2023-09-19 14:28:18,659 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b711cf86370582 for 80.00000000 SEI-USDT. +2023-09-19 14:28:18,744 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133698.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b711cf86370582", "creation_timestamp": 1695133696.0, "exchange_order_id": "35739893", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:28:19,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b711cfbb810582 for 80.00000000 SEI-USDT. +2023-09-19 14:28:19,329 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133698.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b711cfbb810582", "creation_timestamp": 1695133696.0, "exchange_order_id": "35739897", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:29:11,261 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b711cf86370582. [clock=2023-09-19 14:29:11+00:00] +2023-09-19 14:29:11,263 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b711cfbb810582. [clock=2023-09-19 14:29:11+00:00] +2023-09-19 14:29:11,352 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237619164223146040614846006 amount: 80. +2023-09-19 14:29:11,353 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241506528495279475351931270 amount: 80. +2023-09-19 14:29:12,316 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133751.0, "order_id": "x-XEKWYICXBSIUT605b711cf86370582", "exchange_order_id": "35739893", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:29:12,316 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b711cf86370582. +2023-09-19 14:29:13,134 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133752.0, "order_id": "x-XEKWYICXSSIUT605b711cfbb810582", "exchange_order_id": "35739897", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:29:13,135 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b711cfbb810582. +2023-09-19 14:29:13,456 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b71518a03e0582 for 80.00000000 SEI-USDT. +2023-09-19 14:29:13,482 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133753.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b71518a03e0582", "creation_timestamp": 1695133751.0, "exchange_order_id": "35740024", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:29:13,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b71518a4b50582 for 80.00000000 SEI-USDT. +2023-09-19 14:29:13,507 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133753.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b71518a4b50582", "creation_timestamp": 1695133751.0, "exchange_order_id": "35740023", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:30:06,168 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b71518a03e0582. [clock=2023-09-19 14:30:06+00:00] +2023-09-19 14:30:06,169 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b71518a4b50582. [clock=2023-09-19 14:30:06+00:00] +2023-09-19 14:30:06,216 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237267621066218095730284820 amount: 80. +2023-09-19 14:30:06,217 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241157056889137745207520119 amount: 80. +2023-09-19 14:30:06,739 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133806.0, "order_id": "x-XEKWYICXBSIUT605b71518a03e0582", "exchange_order_id": "35740024", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:30:06,739 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b71518a03e0582. +2023-09-19 14:30:06,758 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133806.0, "order_id": "x-XEKWYICXSSIUT605b71518a4b50582", "exchange_order_id": "35740023", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:30:06,758 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b71518a4b50582. +2023-09-19 14:30:07,860 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7185dc8920582 for 80.00000000 SEI-USDT. +2023-09-19 14:30:07,885 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133807.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b7185dc8920582", "creation_timestamp": 1695133806.0, "exchange_order_id": "35740153", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:30:07,887 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7185dfbbd0582 for 80.00000000 SEI-USDT. +2023-09-19 14:30:07,916 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133807.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b7185dfbbd0582", "creation_timestamp": 1695133806.0, "exchange_order_id": "35740152", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:31:01,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b7185dc8920582. [clock=2023-09-19 14:31:01+00:00] +2023-09-19 14:31:01,065 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b7185dfbbd0582. [clock=2023-09-19 14:31:01+00:00] +2023-09-19 14:31:01,086 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235774311156450302569552823 amount: 80. +2023-09-19 14:31:01,087 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240771257017066502757412845 amount: 80. +2023-09-19 14:31:01,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133861.0, "order_id": "x-XEKWYICXBSIUT605b7185dc8920582", "exchange_order_id": "35740153", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:31:01,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b7185dc8920582. +2023-09-19 14:31:01,548 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133861.0, "order_id": "x-XEKWYICXSSIUT605b7185dfbbd0582", "exchange_order_id": "35740152", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:31:01,548 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b7185dfbbd0582. +2023-09-19 14:31:01,637 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b71ba3096a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:31:01,649 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b71ba3096a0582", "creation_timestamp": 1695133861.0, "exchange_order_id": "35740549", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:31:01,649 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b71ba30c9d0582 for 80.00000000 SEI-USDT. +2023-09-19 14:31:01,660 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133861.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b71ba30c9d0582", "creation_timestamp": 1695133861.0, "exchange_order_id": "35740550", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:31:56,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b71ba3096a0582. [clock=2023-09-19 14:31:56+00:00] +2023-09-19 14:31:56,012 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b71ba30c9d0582. [clock=2023-09-19 14:31:56+00:00] +2023-09-19 14:31:56,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236269775296464329891083447 amount: 80. +2023-09-19 14:31:56,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240156100907598436970489959 amount: 80. +2023-09-19 14:31:56,260 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133916.0, "order_id": "x-XEKWYICXBSIUT605b71ba3096a0582", "exchange_order_id": "35740549", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:31:56,260 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b71ba3096a0582. +2023-09-19 14:31:56,476 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133916.0, "order_id": "x-XEKWYICXSSIUT605b71ba30c9d0582", "exchange_order_id": "35740550", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:31:56,476 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b71ba30c9d0582. +2023-09-19 14:31:56,479 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b71ee9731d0582 for 80.00000000 SEI-USDT. +2023-09-19 14:31:56,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b71ee9731d0582", "creation_timestamp": 1695133916.0, "exchange_order_id": "35740742", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:31:56,545 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b71ee975480582 for 80.00000000 SEI-USDT. +2023-09-19 14:31:56,558 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133916.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b71ee975480582", "creation_timestamp": 1695133916.0, "exchange_order_id": "35740743", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:32:51,184 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b71ee9731d0582. [clock=2023-09-19 14:32:51+00:00] +2023-09-19 14:32:51,185 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b71ee975480582. [clock=2023-09-19 14:32:51+00:00] +2023-09-19 14:32:51,239 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235778396190674666190126531 amount: 80. +2023-09-19 14:32:51,240 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239913611909968488118513275 amount: 80. +2023-09-19 14:32:51,925 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133971.0, "order_id": "x-XEKWYICXBSIUT605b71ee9731d0582", "exchange_order_id": "35740742", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:32:51,925 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b71ee9731d0582. +2023-09-19 14:32:52,578 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133972.0, "order_id": "x-XEKWYICXSSIUT605b71ee975480582", "exchange_order_id": "35740743", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:32:52,579 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b71ee975480582. +2023-09-19 14:32:52,764 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b72233d61b0582 for 80.00000000 SEI-USDT. +2023-09-19 14:32:52,793 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b72233d61b0582", "creation_timestamp": 1695133971.0, "exchange_order_id": "35740951", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:32:52,793 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b72233d91e0582 for 80.00000000 SEI-USDT. +2023-09-19 14:32:52,822 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133972.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b72233d91e0582", "creation_timestamp": 1695133971.0, "exchange_order_id": "35740950", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:33:04,628 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b72233d91e0582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:33:04,630 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:33:04+00:00] +2023-09-19 14:33:04,681 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133984.0, "order_id": "x-XEKWYICXSSIUT605b72233d91e0582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00991200"}]}, "exchange_trade_id": "5009947", "exchange_order_id": "35740950", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:33:04,819 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695133984.0, "order_id": "x-XEKWYICXSSIUT605b72233d91e0582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35740950", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:33:04,819 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b72233d91e0582 completely filled. +2023-09-19 14:33:46,328 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b72233d61b0582. [clock=2023-09-19 14:33:46+00:00] +2023-09-19 14:33:46,445 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236592121261874612014429329 amount: 80. +2023-09-19 14:33:46,446 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240919057672693144900870979 amount: 80. +2023-09-19 14:33:47,931 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134026.0, "order_id": "x-XEKWYICXBSIUT605b72233d61b0582", "exchange_order_id": "35740951", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:33:47,931 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b72233d61b0582. +2023-09-19 14:33:49,761 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7257e358f0582 for 80.00000000 SEI-USDT. +2023-09-19 14:33:49,835 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b7257e358f0582", "creation_timestamp": 1695134026.0, "exchange_order_id": "35741310", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:33:50,444 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7257e39160582 for 80.00000000 SEI-USDT. +2023-09-19 14:33:50,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134029.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b7257e39160582", "creation_timestamp": 1695134026.0, "exchange_order_id": "35741318", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:34:41,362 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b7257e358f0582. [clock=2023-09-19 14:34:41+00:00] +2023-09-19 14:34:41,380 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b7257e39160582. [clock=2023-09-19 14:34:41+00:00] +2023-09-19 14:34:41,458 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1234709574140618223096572786 amount: 80. +2023-09-19 14:34:41,460 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239186556269737468419115782 amount: 80. +2023-09-19 14:34:42,604 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134081.0, "order_id": "x-XEKWYICXBSIUT605b7257e358f0582", "exchange_order_id": "35741310", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:34:42,605 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b7257e358f0582. +2023-09-19 14:34:43,821 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134083.0, "order_id": "x-XEKWYICXSSIUT605b7257e39160582", "exchange_order_id": "35741318", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:34:43,822 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b7257e39160582. +2023-09-19 14:34:44,371 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b728c5a83a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:34:44,479 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134083.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12340000", "order_id": "x-XEKWYICXBSIUT605b728c5a83a0582", "creation_timestamp": 1695134081.0, "exchange_order_id": "35741594", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:34:44,480 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b728c5ac3a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:34:44,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134083.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b728c5ac3a0582", "creation_timestamp": 1695134081.0, "exchange_order_id": "35741595", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:35:36,000 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b728c5a83a0582. [clock=2023-09-19 14:35:36+00:00] +2023-09-19 14:35:36,002 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b728c5ac3a0582. [clock=2023-09-19 14:35:36+00:00] +2023-09-19 14:35:36,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235219248685407424928157049 amount: 80. +2023-09-19 14:35:36,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1238701380379678960710323039 amount: 80. +2023-09-19 14:35:36,256 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134136.0, "order_id": "x-XEKWYICXBSIUT605b728c5a83a0582", "exchange_order_id": "35741594", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:35:36,256 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b728c5a83a0582. +2023-09-19 14:35:36,480 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134136.0, "order_id": "x-XEKWYICXSSIUT605b728c5ac3a0582", "exchange_order_id": "35741595", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:35:36,481 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b728c5ac3a0582. +2023-09-19 14:35:36,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b72c063d340582 for 80.00000000 SEI-USDT. +2023-09-19 14:35:36,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b72c063d340582", "creation_timestamp": 1695134136.0, "exchange_order_id": "35741787", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:35:36,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b72c0640350582 for 80.00000000 SEI-USDT. +2023-09-19 14:35:36,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134136.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12380000", "order_id": "x-XEKWYICXSSIUT605b72c0640350582", "creation_timestamp": 1695134136.0, "exchange_order_id": "35741788", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:36:31,024 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b72c063d340582. [clock=2023-09-19 14:36:31+00:00] +2023-09-19 14:36:31,026 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b72c0640350582. [clock=2023-09-19 14:36:31+00:00] +2023-09-19 14:36:31,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1231418091055710975099845535 amount: 80. +2023-09-19 14:36:31,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12370000 amount: 80. +2023-09-19 14:36:31,292 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134191.0, "order_id": "x-XEKWYICXBSIUT605b72c063d340582", "exchange_order_id": "35741787", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:36:31,293 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b72c063d340582. +2023-09-19 14:36:31,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b72f4dd8290582 for 80.00000000 SEI-USDT. +2023-09-19 14:36:31,524 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12310000", "order_id": "x-XEKWYICXBSIUT605b72f4dd8290582", "creation_timestamp": 1695134191.0, "exchange_order_id": "35742194", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:36:31,536 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134191.0, "order_id": "x-XEKWYICXSSIUT605b72c0640350582", "exchange_order_id": "35741788", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:36:31,537 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b72c0640350582. +2023-09-19 14:36:31,541 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b72f4ddbd30582 for 80.00000000 SEI-USDT. +2023-09-19 14:36:31,558 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134191.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605b72f4ddbd30582", "creation_timestamp": 1695134191.0, "exchange_order_id": "35742195", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:37:26,174 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b72f4dd8290582. [clock=2023-09-19 14:37:26+00:00] +2023-09-19 14:37:26,175 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b72f4ddbd30582. [clock=2023-09-19 14:37:26+00:00] +2023-09-19 14:37:26,265 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1232436727745485328581413680 amount: 80. +2023-09-19 14:37:26,266 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.12370000 amount: 80. +2023-09-19 14:37:27,594 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134247.0, "order_id": "x-XEKWYICXBSIUT605b72f4dd8290582", "exchange_order_id": "35742194", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:37:27,595 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b72f4dd8290582. +2023-09-19 14:37:29,288 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134248.0, "order_id": "x-XEKWYICXSSIUT605b72f4ddbd30582", "exchange_order_id": "35742195", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:37:29,288 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b72f4ddbd30582. +2023-09-19 14:37:29,845 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b73298658f0582 for 80.00000000 SEI-USDT. +2023-09-19 14:37:29,914 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134249.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12320000", "order_id": "x-XEKWYICXBSIUT605b73298658f0582", "creation_timestamp": 1695134246.0, "exchange_order_id": "35742330", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:37:29,916 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7329868e90582 for 80.00000000 SEI-USDT. +2023-09-19 14:37:29,966 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134249.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXSSIUT605b7329868e90582", "creation_timestamp": 1695134246.0, "exchange_order_id": "35742328", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:37:32,930 - 1 - hummingbot.connector.client_order_tracker - INFO - The SELL order x-XEKWYICXSSIUT605b7329868e90582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:37:32,931 - 1 - hummingbot.strategy.script_strategy_base - INFO - SELL 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:37:32+00:00] +2023-09-19 14:37:32,979 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134252.0, "order_id": "x-XEKWYICXSSIUT605b7329868e90582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.SELL", "order_type": "OrderType.LIMIT", "price": "0.12370000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "USDT", "flat_fees": [{"token": "USDT", "amount": "0.00989600"}]}, "exchange_trade_id": "5010056", "exchange_order_id": "35742328", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:37:33,067 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134252.0, "order_id": "x-XEKWYICXSSIUT605b7329868e90582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.8960000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35742328", "event_name": "SellOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:37:33,068 - 1 - hummingbot.connector.client_order_tracker - INFO - SELL order x-XEKWYICXSSIUT605b7329868e90582 completely filled. +2023-09-19 14:38:21,223 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b73298658f0582. [clock=2023-09-19 14:38:21+00:00] +2023-09-19 14:38:21,290 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237114318714285609429967302 amount: 80. +2023-09-19 14:38:21,305 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243563849206380998400287406 amount: 80. +2023-09-19 14:38:22,212 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134301.0, "order_id": "x-XEKWYICXBSIUT605b73298658f0582", "exchange_order_id": "35742330", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:38:22,213 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b73298658f0582. +2023-09-19 14:38:23,747 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b735e005b70582 for 80.00000000 SEI-USDT. +2023-09-19 14:38:23,779 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134303.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b735e005b70582", "creation_timestamp": 1695134301.0, "exchange_order_id": "35742664", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:38:24,073 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b735e068b90582 for 80.00000000 SEI-USDT. +2023-09-19 14:38:24,105 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134303.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b735e068b90582", "creation_timestamp": 1695134301.0, "exchange_order_id": "35742665", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:39:16,006 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b735e005b70582. [clock=2023-09-19 14:39:16+00:00] +2023-09-19 14:39:16,007 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b735e068b90582. [clock=2023-09-19 14:39:16+00:00] +2023-09-19 14:39:16,058 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235933529980603895465516375 amount: 80. +2023-09-19 14:39:16,059 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240946196193776889903993141 amount: 80. +2023-09-19 14:39:16,655 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134356.0, "order_id": "x-XEKWYICXBSIUT605b735e005b70582", "exchange_order_id": "35742664", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:39:16,656 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b735e005b70582. +2023-09-19 14:39:17,390 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134357.0, "order_id": "x-XEKWYICXSSIUT605b735e068b90582", "exchange_order_id": "35742665", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:39:17,390 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b735e068b90582. +2023-09-19 14:39:17,467 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b73923b8430582 for 80.00000000 SEI-USDT. +2023-09-19 14:39:17,491 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b73923b8430582", "creation_timestamp": 1695134356.0, "exchange_order_id": "35742846", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:39:17,491 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b73923b4ba0582 for 80.00000000 SEI-USDT. +2023-09-19 14:39:17,514 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134357.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b73923b4ba0582", "creation_timestamp": 1695134356.0, "exchange_order_id": "35742847", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:40:11,051 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b73923b4ba0582. [clock=2023-09-19 14:40:11+00:00] +2023-09-19 14:40:11,052 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b73923b8430582. [clock=2023-09-19 14:40:11+00:00] +2023-09-19 14:40:11,073 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235987394717443187510857812 amount: 80. +2023-09-19 14:40:11,074 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240750366676857073942587785 amount: 80. +2023-09-19 14:40:11,297 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134411.0, "order_id": "x-XEKWYICXBSIUT605b73923b4ba0582", "exchange_order_id": "35742847", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:40:11,297 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b73923b4ba0582. +2023-09-19 14:40:11,310 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134411.0, "order_id": "x-XEKWYICXSSIUT605b73923b8430582", "exchange_order_id": "35742846", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:40:11,311 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b73923b8430582. +2023-09-19 14:40:11,369 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b73c6b2b610582 for 80.00000000 SEI-USDT. +2023-09-19 14:40:11,382 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b73c6b2b610582", "creation_timestamp": 1695134411.0, "exchange_order_id": "35743060", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:40:11,610 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b73c6b2d9c0582 for 80.00000000 SEI-USDT. +2023-09-19 14:40:11,632 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134411.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b73c6b2d9c0582", "creation_timestamp": 1695134411.0, "exchange_order_id": "35743064", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:41:06,046 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b73c6b2b610582. [clock=2023-09-19 14:41:06+00:00] +2023-09-19 14:41:06,048 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b73c6b2d9c0582. [clock=2023-09-19 14:41:06+00:00] +2023-09-19 14:41:06,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236212864141462809869063494 amount: 80. +2023-09-19 14:41:06,069 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1239916487870829537052467118 amount: 80. +2023-09-19 14:41:06,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134466.0, "order_id": "x-XEKWYICXBSIUT605b73c6b2b610582", "exchange_order_id": "35743060", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:41:06,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b73c6b2b610582. +2023-09-19 14:41:06,504 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b73fb2551f0582 for 80.00000000 SEI-USDT. +2023-09-19 14:41:06,517 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b73fb2551f0582", "creation_timestamp": 1695134466.0, "exchange_order_id": "35743177", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:41:06,521 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b73fb257000582 for 80.00000000 SEI-USDT. +2023-09-19 14:41:06,549 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134466.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXSSIUT605b73fb257000582", "creation_timestamp": 1695134466.0, "exchange_order_id": "35743178", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:41:06,559 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134466.0, "order_id": "x-XEKWYICXSSIUT605b73c6b2d9c0582", "exchange_order_id": "35743064", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:41:06,559 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b73c6b2d9c0582. +2023-09-19 14:42:01,010 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b73fb2551f0582. [clock=2023-09-19 14:42:01+00:00] +2023-09-19 14:42:01,011 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b73fb257000582. [clock=2023-09-19 14:42:01+00:00] +2023-09-19 14:42:01,036 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235976097064307659922135279 amount: 80. +2023-09-19 14:42:01,037 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1240792132238225965329211514 amount: 80. +2023-09-19 14:42:01,269 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134521.0, "order_id": "x-XEKWYICXBSIUT605b73fb2551f0582", "exchange_order_id": "35743177", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:42:01,270 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b73fb2551f0582. +2023-09-19 14:42:01,293 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134521.0, "order_id": "x-XEKWYICXSSIUT605b73fb257000582", "exchange_order_id": "35743178", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:42:01,294 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b73fb257000582. +2023-09-19 14:42:01,483 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b742f913f80582 for 80.00000000 SEI-USDT. +2023-09-19 14:42:01,513 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b742f913f80582", "creation_timestamp": 1695134521.0, "exchange_order_id": "35743357", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:42:01,605 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b742f917770582 for 80.00000000 SEI-USDT. +2023-09-19 14:42:01,630 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134521.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12400000", "order_id": "x-XEKWYICXSSIUT605b742f917770582", "creation_timestamp": 1695134521.0, "exchange_order_id": "35743358", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:42:55,141 - 1 - hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource - INFO - Refreshed listen key LXEuwPG8V70SBq0ESfSo8NUZJFfwpEY5Yp95dsXlw8qXIH68O6wFwA4aKkdO. +2023-09-19 14:42:56,147 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b742f913f80582. [clock=2023-09-19 14:42:56+00:00] +2023-09-19 14:42:56,156 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b742f917770582. [clock=2023-09-19 14:42:56+00:00] +2023-09-19 14:42:56,235 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236729224529546248512715152 amount: 80. +2023-09-19 14:42:56,237 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242704364576742314648044970 amount: 80. +2023-09-19 14:42:57,508 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134577.0, "order_id": "x-XEKWYICXBSIUT605b742f913f80582", "exchange_order_id": "35743357", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:42:57,508 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b742f913f80582. +2023-09-19 14:42:58,844 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134578.0, "order_id": "x-XEKWYICXSSIUT605b742f917770582", "exchange_order_id": "35743358", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:42:58,845 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b742f917770582. +2023-09-19 14:43:00,150 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b746435b8e0582 for 80.00000000 SEI-USDT. +2023-09-19 14:43:00,216 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b746435b8e0582", "creation_timestamp": 1695134576.0, "exchange_order_id": "35743712", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:43:00,217 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b746435fed0582 for 80.00000000 SEI-USDT. +2023-09-19 14:43:00,255 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134578.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b746435fed0582", "creation_timestamp": 1695134576.0, "exchange_order_id": "35743711", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:43:51,187 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b746435b8e0582. [clock=2023-09-19 14:43:51+00:00] +2023-09-19 14:43:51,188 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b746435fed0582. [clock=2023-09-19 14:43:51+00:00] +2023-09-19 14:43:51,276 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.12390000 amount: 80. +2023-09-19 14:43:51,277 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1247779457850790721004763296 amount: 80. +2023-09-19 14:43:52,303 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134631.0, "order_id": "x-XEKWYICXBSIUT605b746435b8e0582", "exchange_order_id": "35743712", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:43:52,304 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b746435b8e0582. +2023-09-19 14:43:53,711 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134633.0, "order_id": "x-XEKWYICXSSIUT605b746435fed0582", "exchange_order_id": "35743711", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:43:53,712 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b746435fed0582. +2023-09-19 14:43:53,921 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7498b37a40582 for 80.00000000 SEI-USDT. +2023-09-19 14:43:53,950 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12470000", "order_id": "x-XEKWYICXSSIUT605b7498b37a40582", "creation_timestamp": 1695134631.0, "exchange_order_id": "35744219", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:43:53,954 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7498b34050582 for 80.00000000 SEI-USDT. +2023-09-19 14:43:53,984 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134633.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12390000", "order_id": "x-XEKWYICXBSIUT605b7498b34050582", "creation_timestamp": 1695134631.0, "exchange_order_id": "35744220", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:44:16,756 - 1 - hummingbot.connector.client_order_tracker - INFO - The BUY order x-XEKWYICXBSIUT605b7498b34050582 amounting to 80.00000000/80.00000000 SEI has been filled. +2023-09-19 14:44:16,758 - 1 - hummingbot.strategy.script_strategy_base - INFO - BUY 80.00 SEI-USDT binance at 0.12 [clock=2023-09-19 14:44:16+00:00] +2023-09-19 14:44:16,846 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134656.0, "order_id": "x-XEKWYICXBSIUT605b7498b34050582", "trading_pair": "SEI-USDT", "trade_type": "TradeType.BUY", "order_type": "OrderType.LIMIT", "price": "0.12390000", "amount": "80.00000000", "trade_fee": {"percent": "0", "percent_token": "SEI", "flat_fees": [{"token": "SEI", "amount": "0.08000000"}]}, "exchange_trade_id": "5010222", "exchange_order_id": "35744220", "leverage": 1, "position": "NIL", "event_name": "OrderFilledEvent", "event_source": "binance"} +2023-09-19 14:44:17,003 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134656.0, "order_id": "x-XEKWYICXBSIUT605b7498b34050582", "base_asset": "SEI", "quote_asset": "USDT", "base_asset_amount": "80.00000000", "quote_asset_amount": "9.9120000000000000", "order_type": "OrderType.LIMIT", "exchange_order_id": "35744220", "event_name": "BuyOrderCompletedEvent", "event_source": "binance"} +2023-09-19 14:44:17,003 - 1 - hummingbot.connector.client_order_tracker - INFO - BUY order x-XEKWYICXBSIUT605b7498b34050582 completely filled. +2023-09-19 14:44:46,092 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b7498b37a40582. [clock=2023-09-19 14:44:46+00:00] +2023-09-19 14:44:46,126 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1235405795004707700134768382 amount: 80. +2023-09-19 14:44:46,127 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1242973261791490502575632406 amount: 80. +2023-09-19 14:44:46,563 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134686.0, "order_id": "x-XEKWYICXSSIUT605b7498b37a40582", "exchange_order_id": "35744219", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:44:46,563 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b7498b37a40582. +2023-09-19 14:44:47,411 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b74cd027060582 for 80.00000000 SEI-USDT. +2023-09-19 14:44:47,451 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134687.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12350000", "order_id": "x-XEKWYICXBSIUT605b74cd027060582", "creation_timestamp": 1695134686.0, "exchange_order_id": "35744674", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:44:47,771 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b74cd06a810582 for 80.00000000 SEI-USDT. +2023-09-19 14:44:47,816 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134687.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12420000", "order_id": "x-XEKWYICXSSIUT605b74cd06a810582", "creation_timestamp": 1695134686.0, "exchange_order_id": "35744675", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:45:41,022 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b74cd027060582. [clock=2023-09-19 14:45:41+00:00] +2023-09-19 14:45:41,023 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b74cd06a810582. [clock=2023-09-19 14:45:41+00:00] +2023-09-19 14:45:41,045 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237161732876006862645318969 amount: 80. +2023-09-19 14:45:41,047 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1244162379993803754074371860 amount: 80. +2023-09-19 14:45:41,275 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134741.0, "order_id": "x-XEKWYICXBSIUT605b74cd027060582", "exchange_order_id": "35744674", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:45:41,275 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b74cd027060582. +2023-09-19 14:45:41,519 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134741.0, "order_id": "x-XEKWYICXSSIUT605b74cd06a810582", "exchange_order_id": "35744675", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:45:41,520 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b74cd06a810582. +2023-09-19 14:45:41,522 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7501627200582 for 80.00000000 SEI-USDT. +2023-09-19 14:45:41,535 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b7501627200582", "creation_timestamp": 1695134741.0, "exchange_order_id": "35744843", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:45:41,589 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b750162e270582 for 80.00000000 SEI-USDT. +2023-09-19 14:45:41,602 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134741.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12440000", "order_id": "x-XEKWYICXSSIUT605b750162e270582", "creation_timestamp": 1695134741.0, "exchange_order_id": "35744842", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:46:36,033 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b7501627200582. [clock=2023-09-19 14:46:36+00:00] +2023-09-19 14:46:36,034 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b750162e270582. [clock=2023-09-19 14:46:36+00:00] +2023-09-19 14:46:36,056 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1237570007849853260289520423 amount: 80. +2023-09-19 14:46:36,057 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1243013330072457910821126232 amount: 80. +2023-09-19 14:46:36,282 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134796.0, "order_id": "x-XEKWYICXBSIUT605b7501627200582", "exchange_order_id": "35744843", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:46:36,282 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b7501627200582. +2023-09-19 14:46:36,504 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134796.0, "order_id": "x-XEKWYICXSSIUT605b750162e270582", "exchange_order_id": "35744842", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:46:36,505 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b750162e270582. +2023-09-19 14:46:36,507 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b7535d89ac0582 for 80.00000000 SEI-USDT. +2023-09-19 14:46:36,521 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12370000", "order_id": "x-XEKWYICXBSIUT605b7535d89ac0582", "creation_timestamp": 1695134796.0, "exchange_order_id": "35744961", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:46:36,576 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b7535d8d7a0582 for 80.00000000 SEI-USDT. +2023-09-19 14:46:36,609 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134796.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12430000", "order_id": "x-XEKWYICXSSIUT605b7535d8d7a0582", "creation_timestamp": 1695134796.0, "exchange_order_id": "35744960", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:47:31,245 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXBSIUT605b7535d89ac0582. [clock=2023-09-19 14:47:31+00:00] +2023-09-19 14:47:31,247 - 1 - hummingbot.strategy.script_strategy_base - INFO - (SEI-USDT) Canceling the limit order x-XEKWYICXSSIUT605b7535d8d7a0582. [clock=2023-09-19 14:47:31+00:00] +2023-09-19 14:47:31,322 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT buy order: price: 0.1236889125332962219551080256 amount: 80. +2023-09-19 14:47:31,323 - 1 - hummingbot.strategy.script_strategy_base - INFO - Creating SEI-USDT sell order: price: 0.1241118424210280922338224594 amount: 80. +2023-09-19 14:47:32,432 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134851.0, "order_id": "x-XEKWYICXBSIUT605b7535d89ac0582", "exchange_order_id": "35744961", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:47:32,433 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXBSIUT605b7535d89ac0582. +2023-09-19 14:47:33,417 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134853.0, "order_id": "x-XEKWYICXSSIUT605b7535d8d7a0582", "exchange_order_id": "35744960", "event_name": "OrderCancelledEvent", "event_source": "binance"} +2023-09-19 14:47:33,418 - 1 - hummingbot.connector.client_order_tracker - INFO - Successfully canceled order x-XEKWYICXSSIUT605b7535d8d7a0582. +2023-09-19 14:47:33,727 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT BUY order x-XEKWYICXBSIUT605b756a8d54f0582 for 80.00000000 SEI-USDT. +2023-09-19 14:47:33,759 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134853.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12360000", "order_id": "x-XEKWYICXBSIUT605b756a8d54f0582", "creation_timestamp": 1695134851.0, "exchange_order_id": "35745217", "leverage": 1, "position": "NIL", "event_name": "BuyOrderCreatedEvent", "event_source": "binance"} +2023-09-19 14:47:33,760 - 1 - hummingbot.connector.client_order_tracker - INFO - Created LIMIT SELL order x-XEKWYICXSSIUT605b756a8d7b80582 for 80.00000000 SEI-USDT. +2023-09-19 14:47:33,801 - 1 - hummingbot.core.event.event_reporter - EVENT_LOG - {"timestamp": 1695134853.0, "type": "OrderType.LIMIT", "trading_pair": "SEI-USDT", "amount": "80.00000000", "price": "0.12410000", "order_id": "x-XEKWYICXSSIUT605b756a8d7b80582", "creation_timestamp": 1695134851.0, "exchange_order_id": "35745218", "leverage": 1, "position": "NIL", "event_name": "SellOrderCreatedEvent", "event_source": "binance"} diff --git a/scripts/bot_battle/start_bal.png b/scripts/bot_battle/start_bal.png new file mode 100644 index 0000000..1c34b02 Binary files /dev/null and b/scripts/bot_battle/start_bal.png differ diff --git a/scripts/bot_battle/trades_bot_ba.csv b/scripts/bot_battle/trades_bot_ba.csv new file mode 100644 index 0000000..bed501c --- /dev/null +++ b/scripts/bot_battle/trades_bot_ba.csv @@ -0,0 +1,393 @@ +exchange_trade_id,config_file_path,strategy,market,symbol,base_asset,quote_asset,timestamp,order_id,trade_type,order_type,price,amount,leverage,trade_fee,trade_fee_in_quote,position,age +4952744,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695032343000,x-XEKWYICXSSIUT6059f782dbe640582,SELL,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00989600'}]}",0.00989600,NIL,00:00:09 +4952802,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695032400000,x-XEKWYICXSSIUT6059f7b773a700582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:11 +4952853,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695032466000,x-XEKWYICXBSIUT6059f7eb589760582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:22 +4952948,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695032659000,x-XEKWYICXSSIUT6059f888bd9700582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:50 +4953616,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695033436000,x-XEKWYICXBSIUT6059fb9bc66fc0582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:02 +4953980,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695033650000,x-XEKWYICXBSIUT6059fc38d98eb0582,BUY,LIMIT,0.12530000,19.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.01900000'}]}",0.0023807000000000,NIL,00:00:51 +4954033,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695033685000,x-XEKWYICXBSIUT6059fc6d47e2f0582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:31 +4954408,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695033992000,x-XEKWYICXBSIUT6059fda7fe3c80582,BUY,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100400000000000,NIL,00:00:08 +4954760,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695034281000,x-XEKWYICXBSIUT6059feae43e3c0582,BUY,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100320000000000,NIL,00:00:22 +4955027,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695034546000,x-XEKWYICXBSIUT6059ffb4837a60582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:12 +4955180,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695034800000,x-XEKWYICXSSIUT605a0086848690582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:46 +4955210,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695034866000,x-XEKWYICXSSIUT605a00ef3ca0b0582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:02 +4955307,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695034999000,x-XEKWYICXSSIUT605a015823dc90582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:25 +4955369,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035060000,x-XEKWYICXSSIUT605a018c99c7c0582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:31 +4955471,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035181000,x-XEKWYICXBSIUT605a01f57ce660582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:42 +4955535,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035292000,x-XEKWYICXSSIUT605a025e648920582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:43 +4955585,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035410000,x-XEKWYICXBSIUT605a02c7531020582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:51 +4955618,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035466000,x-XEKWYICXSSIUT605a02fbc10220582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:52 +4955690,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035603000,x-XEKWYICXSSIUT605a03991a7c90582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:24 +4955877,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035826000,x-XEKWYICXBSIUT605a046ae94af0582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:27 +4955921,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035880000,x-XEKWYICXSSIUT605a049f5d5ba0582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:26 +4955965,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695035898000,x-XEKWYICXBSIUT605a049f5d0c40582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:44 +4956044,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695036029000,x-XEKWYICXSSIUT605a053cb8b4b0582,SELL,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00999200'}]}",0.00999200,NIL,00:00:10 +4956227,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695036283000,x-XEKWYICXBSIUT605a060e93aa80582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:44 +4956363,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695036550000,x-XEKWYICXSSIUT605a0714ee5ef0582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:36 +4956777,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037101000,x-XEKWYICXBSIUT605a09214f7300582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:37 +4956846,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037164000,x-XEKWYICXBSIUT605a0955d7cf80582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:45 +4956883,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037212000,x-XEKWYICXSSIUT605a098acc0a30582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:38 +4956925,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037244000,x-XEKWYICXSSIUT605a09beab5080582,SELL,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992000'}]}",0.00992000,NIL,00:00:15 +4957253,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037807000,x-XEKWYICXBSIUT605a0bcb31ca60582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:28 +4957348,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695037874000,x-XEKWYICXSSIUT605a0bffa4fcd0582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:40 +4957605,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695038614000,x-XEKWYICXBSIUT605a0ede0272b0582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:10 +4957656,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695038699000,x-XEKWYICXSSIUT605a0f127c8850582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:40 +4957680,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695038738000,x-XEKWYICXBSIUT605a0f46e77910582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:24 +4958204,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695039703000,x-XEKWYICXSSIUT605a12c28f6e80582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:54 +4958430,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695039895000,x-XEKWYICXBSIUT605a1394603850582,BUY,LIMIT,0.12460000,30.20000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.03020000'}]}",0.0037629200000000,NIL,00:00:26 +4958431,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695039895000,x-XEKWYICXBSIUT605a1394603850582,BUY,LIMIT,0.12460000,49.80000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.04980000'}]}",0.0062050800000000,NIL,00:00:26 +4958503,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695039942000,x-XEKWYICXSSIUT605a13c8d1adf0582,SELL,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00999200'}]}",0.00999200,NIL,00:00:18 +4959468,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695040747000,x-XEKWYICXBSIUT605a16a72e2810582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:53 +4959494,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695040760000,x-XEKWYICXBSIUT605a16dba7f950582,BUY,LIMIT,0.12560000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100480000000000,NIL,00:00:11 +4959628,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041022000,x-XEKWYICXBSIUT605a17ad693c00582,BUY,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100320000000000,NIL,00:00:53 +4959738,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041302000,x-XEKWYICXSSIUT605a18e82e8060582,SELL,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01003200'}]}",0.01003200,NIL,00:00:03 +4959793,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041371000,x-XEKWYICXBSIUT605a191cbce650582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:17 +4959922,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041523000,x-XEKWYICXSSIUT605a19b9ee6550582,SELL,LIMIT,0.12510000,40.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00500400'}]}",0.00500400,NIL,00:00:04 +4959923,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041523000,x-XEKWYICXSSIUT605a19b9ee6550582,SELL,LIMIT,0.12510000,40.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00500400'}]}",0.00500400,NIL,00:00:04 +4960005,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041575000,x-XEKWYICXBSIUT605a19ee61be20582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:01 +4960145,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041635000,x-XEKWYICXSSIUT605a1a22ddadd0582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:06 +4960245,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695041801000,x-XEKWYICXSSIUT605a1ac03145c0582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:07 +4960308,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042000000,x-XEKWYICXBSIUT605a1b5dc207c0582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:41 +4960338,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042016000,x-XEKWYICXSSIUT605a1b920012d0582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:02 +4960373,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042067000,x-XEKWYICXBSIUT605a1b91ffdc00582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:53 +4960466,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042234000,x-XEKWYICXSSIUT605a1c63d9b940582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,n/a +4960521,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042255000,x-XEKWYICXBSIUT605a1c63d975a0582,BUY,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099600000000000,NIL,00:00:21 +4960826,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042344000,x-XEKWYICXSSIUT605a1cccc0cae0582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:00 +4960961,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695042415000,x-XEKWYICXSSIUT605a1d012c3520582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:16 +4964173,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695045366000,x-XEKWYICXBSIUT605a2802505000582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:13 +4964588,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695045724000,x-XEKWYICXBSIUT605a293d18ec20582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:41 +4965413,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695046176000,x-XEKWYICXSSIUT605a2ae0ffa3c0582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:53 +4966154,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695046929000,x-XEKWYICXBSIUT605a2dca6aa680582,BUY,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100320000000000,NIL,00:00:24 +4966345,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695047052000,x-XEKWYICXSSIUT605a2e393990b0582,SELL,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01005600'}]}",0.01005600,NIL,00:00:31 +4967774,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695047313000,x-XEKWYICXBSIUT605a2f3f7dcab0582,BUY,LIMIT,0.12740000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0101920000000000,NIL,00:00:17 +4968060,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695047402000,x-XEKWYICXBSIUT605a2fa0004e30582,BUY,LIMIT,0.12760000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0102080000000000,NIL,00:00:05 +4968223,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695047457000,x-XEKWYICXBSIUT605a2fd4143af0582,BUY,LIMIT,0.12720000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0101760000000000,NIL,00:00:05 +4968921,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695047867000,x-XEKWYICXSSIUT605a31433ef280582,SELL,LIMIT,0.12680000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01014400'}]}",0.01014400,NIL,00:00:30 +4969178,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695048081000,x-XEKWYICXSSIUT605a31fa58d780582,SELL,LIMIT,0.12710000,75.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00953250'}]}",0.00953250,NIL,00:00:52 +4969341,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695048207000,x-XEKWYICXBSIUT605a32982425e0582,BUY,LIMIT,0.12760000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0102080000000000,NIL,00:00:13 +4969469,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695048281000,x-XEKWYICXSSIUT605a32dd7bf830582,SELL,LIMIT,0.12720000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01017600'}]}",0.01017600,NIL,00:00:14 +4970053,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695048810000,x-XEKWYICXSSIUT605a34c782f7d0582,SELL,LIMIT,0.12670000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01013600'}]}",0.01013600,NIL,00:00:29 +4970176,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695048926000,x-XEKWYICXSSIUT605a3551cab510582,SELL,LIMIT,0.12660000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01012800'}]}",0.01012800,NIL,00:00:00 +4970237,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049084000,x-XEKWYICXBSIUT605a35baf76160582,BUY,LIMIT,0.12650000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0101200000000000,NIL,00:00:48 +4970340,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049272000,x-XEKWYICXBSIUT605a368c818840582,BUY,LIMIT,0.12630000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0101040000000000,NIL,00:00:16 +4970422,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049316000,x-XEKWYICXSSIUT605a36c0fb52a0582,SELL,LIMIT,0.12610000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01008800'}]}",0.01008800,NIL,00:00:05 +4970532,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049413000,x-XEKWYICXSSIUT605a36f5797b10582,SELL,LIMIT,0.12630000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01010400'}]}",0.01010400,NIL,00:00:47 +4970559,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049475000,x-XEKWYICXBSIUT605a3729ff80a0582,BUY,LIMIT,0.12620000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100960000000000,NIL,00:00:54 +4970621,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049506000,x-XEKWYICXBSIUT605a375eda3270582,BUY,LIMIT,0.12590000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100720000000000,NIL,00:00:30 +4970667,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049535000,x-XEKWYICXSSIUT605a3792c456f0582,SELL,LIMIT,0.12590000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01007200'}]}",0.01007200,NIL,00:00:04 +4970829,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049783000,x-XEKWYICXBSIUT605a3864bb5370582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:32 +4970938,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049836000,x-XEKWYICXSSIUT605a38993f00e0582,SELL,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004000'}]}",0.01004000,NIL,00:00:30 +4970965,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695049904000,x-XEKWYICXSSIUT605a38cd7b2980582,SELL,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01005600'}]}",0.01005600,NIL,00:00:43 +4971131,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050002000,x-XEKWYICXBSIUT605a39367efc40582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:31 +4971336,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050393000,x-XEKWYICXBSIUT605a3aa5abd800582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:37 +4971348,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050417000,x-XEKWYICXSSIUT605a3ada164f20582,SELL,LIMIT,0.12580000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01006400'}]}",0.01006400,NIL,00:00:06 +4971361,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050488000,x-XEKWYICXBSIUT605a3b0e7e3d80582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:22 +4971494,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050721000,x-XEKWYICXBSIUT605a3be09532c0582,BUY,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100320000000000,NIL,00:00:35 +4971567,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695050922000,x-XEKWYICXSSIUT605a3cb24f7f10582,SELL,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004000'}]}",0.01004000,NIL,00:00:16 +4971612,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051021000,x-XEKWYICXBSIUT605a3d1b1e6ca0582,BUY,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100400000000000,NIL,00:00:05 +4971676,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051122000,x-XEKWYICXSSIUT605a3d4f753b00582,SELL,LIMIT,0.12560000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004800'}]}",0.01004800,NIL,00:00:51 +4971760,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051290000,x-XEKWYICXBSIUT605a3ded022d30582,BUY,LIMIT,0.12560000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100480000000000,NIL,00:00:54 +4971838,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051494000,x-XEKWYICXSSIUT605a3ebee327b0582,SELL,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01003200'}]}",0.01003200,NIL,00:00:38 +4971912,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051778000,x-XEKWYICXSSIUT605a3fc4e8f850582,SELL,LIMIT,0.12560000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004800'}]}",0.01004800,NIL,00:00:47 +4971958,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695051885000,x-XEKWYICXBSIUT605a402de09910582,BUY,LIMIT,0.12580000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100640000000000,NIL,00:00:44 +4972154,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695052165000,x-XEKWYICXBSIUT605a4134840100582,BUY,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100400000000000,NIL,00:00:49 +4972264,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695052423000,x-XEKWYICXSSIUT605a423a786060582,SELL,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01005600'}]}",0.01005600,NIL,00:00:32 +4972402,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695052811000,x-XEKWYICXSSIUT605a43a9afb610582,SELL,LIMIT,0.12620000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01009600'}]}",0.01009600,NIL,00:00:35 +4972415,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695052835000,x-XEKWYICXBSIUT605a43de138570582,BUY,LIMIT,0.12630000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0101040000000000,NIL,00:00:04 +4972492,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695052985000,x-XEKWYICXBSIUT605a4446d5a230582,BUY,LIMIT,0.12590000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100720000000000,NIL,00:00:44 +4972731,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695053247000,x-XEKWYICXSSIUT605a454d0f2d00582,SELL,LIMIT,0.12580000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01006400'}]}",0.01006400,NIL,00:00:31 +4972822,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695053466000,x-XEKWYICXBSIUT605a461eef95c0582,BUY,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100560000000000,NIL,00:00:30 +4973057,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695053625000,x-XEKWYICXSSIUT605a46bc6395b0582,SELL,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01002400'}]}",0.01002400,NIL,00:00:24 +4973242,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695053807000,x-XEKWYICXBSIUT605a4759960a70582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:41 +4973549,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695053921000,x-XEKWYICXSSIUT605a47c29dfa10582,SELL,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996000'}]}",0.00996000,NIL,00:00:45 +4973831,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695054422000,x-XEKWYICXSSIUT605a499a8df8e0582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:51 +4973904,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695054567000,x-XEKWYICXBSIUT605a4a37e83050582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:31 +4973993,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695054764000,x-XEKWYICXSSIUT605a4b09b8cf60582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:08 +4974007,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695054811000,x-XEKWYICXBSIUT605a4b3e2b3b30582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:00 +4974151,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695055094000,x-XEKWYICXBSIUT605a4c44721480582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:08 +4974238,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695055315000,x-XEKWYICXSSIUT605a4d163cef50582,SELL,LIMIT,0.12540000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01003200'}]}",0.01003200,NIL,00:00:09 +4974398,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695055714000,x-XEKWYICXBSIUT605a4e856706b0582,BUY,LIMIT,0.12600000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100800000000000,NIL,00:00:23 +4974484,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695055943000,x-XEKWYICXBSIUT605a4f57361060582,BUY,LIMIT,0.12590000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100720000000000,NIL,00:00:32 +4974510,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695055998000,x-XEKWYICXSSIUT605a4f8ba98ab0582,SELL,LIMIT,0.12610000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01008800'}]}",0.01008800,NIL,00:00:32 +4974537,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695056161000,x-XEKWYICXBSIUT605a5029050140582,BUY,LIMIT,0.12610000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100880000000000,NIL,00:00:30 +4974575,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695056331000,x-XEKWYICXBSIUT605a50c66031d0582,BUY,LIMIT,0.12610000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100880000000000,NIL,00:00:35 +4974604,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695056410000,x-XEKWYICXSSIUT605a512f495a80582,SELL,LIMIT,0.12600000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01008000'}]}",0.01008000,NIL,00:00:04 +4974746,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695056651000,x-XEKWYICXBSIUT605a5201164ff0582,BUY,LIMIT,0.12600000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100800000000000,NIL,00:00:25 +4974785,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695056724000,x-XEKWYICXSSIUT605a52358a6f00582,SELL,LIMIT,0.12600000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01008000'}]}",0.01008000,NIL,00:00:43 +4974947,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695057066000,x-XEKWYICXSSIUT605a53a4b4cf80582,SELL,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004000'}]}",0.01004000,NIL,00:00:00 +4974986,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695057135000,x-XEKWYICXSSIUT605a53d92877a0582,SELL,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004000'}]}",0.01004000,NIL,00:00:14 +4975321,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695057698000,x-XEKWYICXSSIUT605a55e5ae0020582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:27 +4975489,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695057989000,x-XEKWYICXSSIUT605a56ebf03ae0582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:43 +4975553,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695058016000,x-XEKWYICXBSIUT605a572063ee10582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:15 +4976609,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695058525000,x-XEKWYICXSSIUT605a58f8757bb0582,SELL,LIMIT,0.12360000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988800'}]}",0.00988800,NIL,00:00:29 +4976931,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695058781000,x-XEKWYICXBSIUT605a59feb7f970582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:10 +4977039,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695058905000,x-XEKWYICXBSIUT605a5a679fd240582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:24 +4977237,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695059197000,x-XEKWYICXBSIUT605a5b6de25d60582,BUY,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099440000000000,NIL,00:00:41 +4977316,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695059363000,x-XEKWYICXSSIUT605a5c0b3fa170582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:42 +4977399,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695059527000,x-XEKWYICXBSIUT605a5ca898f2e0582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:41 +4977496,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695059757000,x-XEKWYICXBSIUT605a5d7a67dd20582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:51 +4977639,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695059901000,x-XEKWYICXSSIUT605a5e17c3a810582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:30 +4977761,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695060189000,x-XEKWYICXSSIUT605a5f1e060290582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:43 +4977831,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695060339000,x-XEKWYICXBSIUT605a5fbb6162a0582,BUY,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099360000000000,NIL,00:00:28 +4978082,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695060865000,x-XEKWYICXSSIUT605a61c7e65f60582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:04 +4978282,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695061392000,x-XEKWYICXBSIUT605a639ff7e1f0582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:36 +4978350,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695061609000,x-XEKWYICXSSIUT605a6471c71030582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:33 +4978433,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695061801000,x-XEKWYICXSSIUT605a6543965680582,SELL,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00989600'}]}",0.00989600,NIL,00:00:05 +4978582,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695062355000,x-XEKWYICXBSIUT605a67501b8260582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:09 +4978635,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695062420000,x-XEKWYICXBSIUT605a67848e9ca0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:19 +4978790,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695062742000,x-XEKWYICXSSIUT605a68bf45d890582,SELL,LIMIT,0.12370000,79.90000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988363'}]}",0.00988363,NIL,00:00:11 +4978791,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695062742000,x-XEKWYICXSSIUT605a68bf45d890582,SELL,LIMIT,0.12370000,0.10000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00001237'}]}",0.00001237,NIL,00:00:11 +4978847,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695062876000,x-XEKWYICXBSIUT605a69282d5970582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:35 +4978918,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695063023000,x-XEKWYICXSSIUT605a69c5886890582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:17 +4979035,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695063513000,x-XEKWYICXSSIUT605a6b9d99f110582,SELL,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988000'}]}",0.00988000,NIL,00:00:12 +4979046,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695063574000,x-XEKWYICXSSIUT605a6bd20dc4f0582,SELL,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988000'}]}",0.00988000,NIL,00:00:18 +4979255,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695063830000,x-XEKWYICXBSIUT605a6ca3dc45b0582,BUY,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098560000000000,NIL,00:00:54 +4979488,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695064481000,x-XEKWYICXBSIUT605a6f194980f0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:45 +4979576,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695064883000,x-XEKWYICXSSIUT605a70bce7bf70582,SELL,LIMIT,0.12360000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988800'}]}",0.00988800,NIL,00:00:07 +4979634,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695065278000,x-XEKWYICXSSIUT605a722c1139e0582,SELL,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00989600'}]}",0.00989600,NIL,00:00:17 +4979706,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695065863000,x-XEKWYICXBSIUT605a743896bcf0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:52 +4979727,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695065913000,x-XEKWYICXSSIUT605a746d0a6090582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:47 +4980046,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695066482000,x-XEKWYICXBSIUT605a76ae0388d0582,BUY,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099440000000000,NIL,00:00:11 +4980162,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067230000,x-XEKWYICXSSIUT605a7957e48220582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:44 +4980208,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067320000,x-XEKWYICXBSIUT605a79c0cba240582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:24 +4980232,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067357000,x-XEKWYICXBSIUT605a79f53f7e00582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:06 +4980285,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067422000,x-XEKWYICXSSIUT605a7a29b366e0582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:16 +4980310,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067490000,x-XEKWYICXBSIUT605a7a5e26b1a0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:29 +4980344,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067648000,x-XEKWYICXSSIUT605a7afb82c9f0582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:22 +4980360,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067791000,x-XEKWYICXSSIUT605a7b98dd72c0582,SELL,LIMIT,0.12390000,30.40000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00376656'}]}",0.00376656,NIL,00:00:00 +4980361,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695067791000,x-XEKWYICXSSIUT605a7b98dd72c0582,SELL,LIMIT,0.12390000,49.60000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00614544'}]}",0.00614544,NIL,00:00:00 +4980448,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695068452000,x-XEKWYICXBSIUT605a7e0e4a0cd0582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:01 +4980462,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695068527000,x-XEKWYICXBSIUT605a7e42bd7290582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:21 +4980471,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695068599000,x-XEKWYICXSSIUT605a7e7738c050582,SELL,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992000'}]}",0.00992000,NIL,00:00:38 +4980481,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695068700000,x-XEKWYICXBSIUT605a7ee01a0830582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:29 +4980519,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695068893000,x-XEKWYICXSSIUT605a7fb1e86a10582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:02 +4980533,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695069058000,x-XEKWYICXSSIUT605a804f437cf0582,SELL,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992000'}]}",0.00992000,NIL,00:00:02 +4980663,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695069582000,x-XEKWYICXBSIUT605a822754b080582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:31 +4980699,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695069610000,x-XEKWYICXSSIUT605a825bc88400582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:04 +4980775,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695069829000,x-XEKWYICXBSIUT605a832d97a2f0582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:03 +4980901,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695070528000,x-XEKWYICXBSIUT605a85a30485e0582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:42 +4980911,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695070550000,x-XEKWYICXSSIUT605a85d77877e0582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:09 +4980971,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695070940000,x-XEKWYICXSSIUT605a8746a2b560582,SELL,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992000'}]}",0.00992000,NIL,00:00:14 +4980998,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695071000000,x-XEKWYICXBSIUT605a877b1627b0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:19 +4981204,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695071603000,x-XEKWYICXSSIUT605a89bc0f4500582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:17 +4981255,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695071669000,x-XEKWYICXBSIUT605a89f082a580582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:28 +4981265,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695071743000,x-XEKWYICXSSIUT605a8a24f67ee0582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:47 +4981353,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695072098000,x-XEKWYICXBSIUT605a8b9420b1f0582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:17 +4981373,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695072300000,x-XEKWYICXSSIUT605a8c317bef20582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:54 +4981644,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073064000,x-XEKWYICXBSIUT605a8f0fd05820582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:48 +4981781,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073335000,x-XEKWYICXBSIUT605a901612d000582,BUY,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100080000000000,NIL,00:00:44 +4981881,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073395000,x-XEKWYICXBSIUT605a904a868970582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:49 +4981918,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073437000,x-XEKWYICXBSIUT605a907efa7310582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:36 +4981950,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073472000,x-XEKWYICXBSIUT605a90b36ea750582,BUY,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100080000000000,NIL,00:00:16 +4982017,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073834000,x-XEKWYICXSSIUT605a91ee24cdb0582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:48 +4982045,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073885000,x-XEKWYICXBSIUT605a922298bdf0582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:44 +4982128,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695073985000,x-XEKWYICXBSIUT605a928b802610582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:34 +4982213,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074068000,x-XEKWYICXSSIUT605a92f4677390582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:07 +4982233,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074142000,x-XEKWYICXSSIUT605a9328db35d0582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:26 +4982263,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074191000,x-XEKWYICXSSIUT605a935d4f0e30582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:20 +4982359,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074328000,x-XEKWYICXSSIUT605a93c636be20582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:47 +4982366,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074350000,x-XEKWYICXSSIUT605a93faaa4e30582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:14 +4982426,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074457000,x-XEKWYICXBSIUT605a946391c3a0582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:11 +4982546,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695074943000,x-XEKWYICXSSIUT605a963ba37300582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:02 +4982634,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695075176000,x-XEKWYICXBSIUT605a970d7203b0582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:15 +4982643,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695075239000,x-XEKWYICXSSIUT605a9741e58050582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:23 +4982679,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695075419000,x-XEKWYICXBSIUT605a97df411ef0582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:38 +4982687,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695075468000,x-XEKWYICXSSIUT605a9813b518d0582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:32 +4982776,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695075723000,x-XEKWYICXBSIUT605a9919f74c80582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:12 +4982826,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695076051000,x-XEKWYICXBSIUT605a9a54af61b0582,BUY,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100080000000000,NIL,00:00:10 +4982938,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695076192000,x-XEKWYICXSSIUT605a9abd960000582,SELL,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000000'}]}",0.01000000,NIL,00:00:41 +4983080,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695076843000,x-XEKWYICXSSIUT605a9d3302df30582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:32 +4983104,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695076898000,x-XEKWYICXSSIUT605a9d6775cc90582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:32 +4983152,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695077002000,x-XEKWYICXBSIUT605a9dd05d1d60582,BUY,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099760000000000,NIL,00:00:26 +4983182,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695077061000,x-XEKWYICXBSIUT605a9e04d14c50582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:30 +4983198,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695077086000,x-XEKWYICXSSIUT605a9e39459f40582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:00 +4983243,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695077380000,x-XEKWYICXSSIUT605a9f3f885060582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:19 +4983294,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695077685000,x-XEKWYICXBSIUT605aa045ca4900582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:49 +4983466,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695078385000,x-XEKWYICXSSIUT605aa2efaae4e0582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:34 +4983629,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695078825000,x-XEKWYICXBSIUT605aa4934872c0582,BUY,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099440000000000,NIL,00:00:34 +4983678,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695078893000,x-XEKWYICXSSIUT605aa4c7bc5190582,SELL,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00995200'}]}",0.00995200,NIL,00:00:47 +4983760,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695079250000,x-XEKWYICXBSIUT605aa636e6af80582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:19 +4984001,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080206000,x-XEKWYICXBSIUT605aa9b2965960582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:40 +4984021,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080241000,x-XEKWYICXSSIUT605aa9e70a0240582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:20 +4984031,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080281000,x-XEKWYICXSSIUT605aaa1b7dce10582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:05 +4984103,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080495000,x-XEKWYICXBSIUT605aaab8d8bbd0582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:54 +4984124,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080497000,x-XEKWYICXBSIUT605aaaed585720582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:01 +4984195,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695080722000,x-XEKWYICXSSIUT605aabbf1c9170582,SELL,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00989600'}]}",0.00989600,NIL,00:00:06 +4984404,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695081469000,x-XEKWYICXBSIUT605aae68fbeff0582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:38 +4984487,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695081715000,x-XEKWYICXSSIUT605aaf6f3f02b0582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:09 +4984499,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695081763000,x-XEKWYICXSSIUT605aafa3b2a770582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:02 +4984539,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695081868000,x-XEKWYICXBSIUT605aafd8261cc0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:52 +4984614,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082146000,x-XEKWYICXBSIUT605ab112dd1b60582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:00 +4984713,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082436000,x-XEKWYICXSSIUT605ab1f2fd7330582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:55 +4984721,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082436000,x-XEKWYICXSSIUT605ab2276c5fc0582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:00 +4984772,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082567000,x-XEKWYICXBSIUT605ab290572bc0582,BUY,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098800000000000,NIL,00:00:21 +4985069,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082866000,x-XEKWYICXBSIUT605ab396957590582,BUY,LIMIT,0.12280000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098240000000000,NIL,00:00:45 +4985198,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695082989000,x-XEKWYICXSSIUT605ab433f89830582,SELL,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00984800'}]}",0.00984800,NIL,00:00:03 +4985802,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695084248000,x-XEKWYICXBSIUT605ab8b5e30550582,BUY,LIMIT,0.12280000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098240000000000,NIL,00:00:52 +4985863,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695084294000,x-XEKWYICXSSIUT605ab8ea566680582,SELL,LIMIT,0.12270000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00981600'}]}",0.00981600,NIL,00:00:43 +4986710,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695085468000,x-XEKWYICXBSIUT605abd6c524c30582,BUY,LIMIT,0.12150000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097200000000000,NIL,00:00:07 +4987141,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695085659000,x-XEKWYICXSSIUT605abe09ae9960582,SELL,LIMIT,0.12150000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00972000'}]}",0.00972000,NIL,00:00:33 +4987417,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086243000,x-XEKWYICXBSIUT605ac04a9cfdb0582,BUY,LIMIT,0.12220000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097760000000000,NIL,00:00:12 +4987447,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086312000,x-XEKWYICXBSIUT605ac07f110c40582,BUY,LIMIT,0.12220000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097760000000000,NIL,00:00:26 +4987498,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086552000,x-XEKWYICXBSIUT605ac150e34e50582,BUY,LIMIT,0.12230000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097840000000000,NIL,00:00:46 +4987507,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086585000,x-XEKWYICXBSIUT605ac185606e80582,BUY,LIMIT,0.12240000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097920000000000,NIL,00:00:24 +4987572,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086872000,x-XEKWYICXSSIUT605ac28ba90c70582,SELL,LIMIT,0.12230000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00978400'}]}",0.00978400,NIL,00:00:36 +4987623,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695086932000,x-XEKWYICXSSIUT605ac2c020ffa0582,SELL,LIMIT,0.12250000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00980000'}]}",0.00980000,NIL,00:00:41 +4987848,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695087387000,x-XEKWYICXBSIUT605ac4981b9c40582,BUY,LIMIT,0.12210000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0097680000000000,NIL,00:00:01 +4987937,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695087553000,x-XEKWYICXSSIUT605ac5012537b0582,SELL,LIMIT,0.12190000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00975200'}]}",0.00975200,NIL,00:00:57 +4987945,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695087554000,x-XEKWYICXSSIUT605ac536474c80582,SELL,LIMIT,0.12190000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00975200'}]}",0.00975200,NIL,00:00:03 +4988463,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695089319000,x-XEKWYICXBSIUT605acbc446c360582,BUY,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098480000000000,NIL,00:00:08 +4988488,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695089376000,x-XEKWYICXBSIUT605acbf8942120582,BUY,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098560000000000,NIL,00:00:10 +4988501,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695089438000,x-XEKWYICXBSIUT605acc2cd5fd50582,BUY,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098560000000000,NIL,00:00:17 +4988647,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695089813000,x-XEKWYICXSSIUT605acd9c009200582,SELL,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00984800'}]}",0.00984800,NIL,00:00:07 +4988670,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695089870000,x-XEKWYICXBSIUT605acdd0ad7270582,BUY,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098560000000000,NIL,00:00:09 +4988687,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695090111000,x-XEKWYICXSSIUT605acea2529890582,SELL,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00985600'}]}",0.00985600,NIL,00:00:30 +4988696,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695090216000,x-XEKWYICXSSIUT605acf0b737880582,SELL,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00986400'}]}",0.00986400,NIL,00:00:25 +4988724,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695090316000,x-XEKWYICXBSIUT605acf743117a0582,BUY,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098640000000000,NIL,00:00:15 +4988734,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695090381000,x-XEKWYICXBSIUT605acfa8921c10582,BUY,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098640000000000,NIL,00:00:25 +4988902,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695090990000,x-XEKWYICXSSIUT605ad1e99153c0582,SELL,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00985600'}]}",0.00985600,NIL,00:00:29 +4988950,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091047000,x-XEKWYICXSSIUT605ad21df26a00582,SELL,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00986400'}]}",0.00986400,NIL,00:00:31 +4989049,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091289000,x-XEKWYICXBSIUT605ad2efc19180582,BUY,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098560000000000,NIL,00:00:53 +4989079,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091466000,x-XEKWYICXSSIUT605ad3c1d6bc30582,SELL,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00985600'}]}",0.00985600,NIL,00:00:10 +4989136,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091612000,x-XEKWYICXBSIUT605ad42a8aa400582,BUY,LIMIT,0.12300000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098400000000000,NIL,00:00:46 +4989172,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091662000,x-XEKWYICXSSIUT605ad45ef58080582,SELL,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00984800'}]}",0.00984800,NIL,00:00:41 +4989356,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695091964000,x-XEKWYICXBSIUT605ad599c42670582,BUY,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098640000000000,NIL,00:00:13 +4989451,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695092299000,x-XEKWYICXSSIUT605ad6d4717ca0582,SELL,LIMIT,0.12340000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00987200'}]}",0.00987200,NIL,00:00:18 +4989553,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695092494000,x-XEKWYICXSSIUT605ad771d9fd20582,SELL,LIMIT,0.12340000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00987200'}]}",0.00987200,NIL,00:00:48 +4989670,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695093065000,x-XEKWYICXBSIUT605ad9b2bc8650582,BUY,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098480000000000,NIL,00:00:14 +4989731,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695093154000,x-XEKWYICXSSIUT605ad9e72a7390582,SELL,LIMIT,0.12290000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00983200'}]}",0.00983200,NIL,00:00:48 +4989750,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695093185000,x-XEKWYICXSSIUT605ada1bb2e0e0582,SELL,LIMIT,0.12300000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00984000'}]}",0.00984000,NIL,00:00:24 +4989928,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695094008000,x-XEKWYICXSSIUT605add2e5c6790582,SELL,LIMIT,0.12290000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00983200'}]}",0.00983200,NIL,00:00:22 +4989966,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695094451000,x-XEKWYICXBSIUT605aded2445d60582,BUY,LIMIT,0.12300000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098400000000000,NIL,00:00:25 +4990019,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695094788000,x-XEKWYICXBSIUT605ae00d01de10582,BUY,LIMIT,0.12280000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098240000000000,NIL,00:00:32 +4990106,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695095018000,x-XEKWYICXBSIUT605ae0dec9b4f0582,BUY,LIMIT,0.12260000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098080000000000,NIL,00:00:42 +4990164,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695095089000,x-XEKWYICXSSIUT605ae147974910582,SELL,LIMIT,0.12250000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00980000'}]}",0.00980000,NIL,00:00:03 +4990249,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695095303000,x-XEKWYICXSSIUT605ae1e503f690582,SELL,LIMIT,0.12250000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00980000'}]}",0.00980000,NIL,00:00:52 +4990420,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695096001000,x-XEKWYICXBSIUT605ae48ed13060582,BUY,LIMIT,0.12290000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098320000000000,NIL,00:00:35 +4990472,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695096085000,x-XEKWYICXBSIUT605ae4f795aca0582,BUY,LIMIT,0.12300000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098400000000000,NIL,00:00:09 +4990625,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695096580000,x-XEKWYICXSSIUT605ae6cffba480582,SELL,LIMIT,0.12300000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00984000'}]}",0.00984000,NIL,00:00:09 +4990669,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695096665000,x-XEKWYICXBSIUT605ae7040f7610582,BUY,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098480000000000,NIL,00:00:39 +4990727,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695096840000,x-XEKWYICXBSIUT605ae7a1bf9550582,BUY,LIMIT,0.12310000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098480000000000,NIL,00:00:49 +4990981,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695097698000,x-XEKWYICXSSIUT605aeae8e23940582,SELL,LIMIT,0.12320000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00985600'}]}",0.00985600,NIL,00:00:27 +4991012,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695097784000,x-XEKWYICXBSIUT605aeb51b50850582,BUY,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098640000000000,NIL,00:00:03 +4991028,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695097841000,x-XEKWYICXBSIUT605aeb86037ad0582,BUY,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098640000000000,NIL,00:00:05 +4991139,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695098171000,x-XEKWYICXSSIUT605aecc0b8c6f0582,SELL,LIMIT,0.12340000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00987200'}]}",0.00987200,NIL,00:00:05 +4991251,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695098425000,x-XEKWYICXBSIUT605aed92af7860582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:39 +4991423,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695098823000,x-XEKWYICXSSIUT605aef01c04830582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:52 +4991478,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695098894000,x-XEKWYICXBSIUT605aef6abb85f0582,BUY,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099120000000000,NIL,00:00:13 +4991747,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695099220000,x-XEKWYICXSSIUT605af0a56cb3f0582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:09 +4991899,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695099332000,x-XEKWYICXBSIUT605af10e4b0050582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:11 +4992109,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695099705000,x-XEKWYICXBSIUT605af248f08de0582,BUY,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099600000000000,NIL,00:00:54 +4992232,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695099966000,x-XEKWYICXSSIUT605af34f3f1cc0582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:40 +4992306,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695100261000,x-XEKWYICXSSIUT605af489e76d10582,SELL,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996000'}]}",0.00996000,NIL,00:00:05 +4992588,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695100797000,x-XEKWYICXSSIUT605af66219c280582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:46 +4992663,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695100932000,x-XEKWYICXBSIUT605af6ff535e40582,BUY,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099920000000000,NIL,00:00:16 +4992793,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695101153000,x-XEKWYICXBSIUT605af7d1281860582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:17 +4992869,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695101193000,x-XEKWYICXBSIUT605af8059f1b10582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:02 +4993307,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695101916000,x-XEKWYICXSSIUT605afaafc9ed00582,SELL,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996000'}]}",0.00996000,NIL,00:00:10 +4993386,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695102016000,x-XEKWYICXSSIUT605afb18704580582,SELL,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00999200'}]}",0.00999200,NIL,n/a +4993623,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695102688000,x-XEKWYICXBSIUT605afd8dd18260582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:12 +4993740,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695103176000,x-XEKWYICXSSIUT605aff65f7aa80582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:05 +4994044,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695103962000,x-XEKWYICXSSIUT605b02446fe500582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:21 +4994387,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695105126000,x-XEKWYICXBSIUT605b0691c09e60582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:30 +4994486,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695105319000,x-XEKWYICXBSIUT605b0763824da0582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:03 +4994854,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695106166000,x-XEKWYICXSSIUT605b0a7652a940582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:25 +4995015,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695106699000,x-XEKWYICXSSIUT605b0c82cc87c0582,SELL,LIMIT,0.12490000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00999200'}]}",0.00999200,NIL,00:00:08 +4995096,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695106911000,x-XEKWYICXBSIUT605b0d549afce0582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:00 +4995127,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695107167000,x-XEKWYICXBSIUT605b0e266a93c0582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:36 +4995540,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108168000,x-XEKWYICXSSIUT605b11d68dab20582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:47 +4995596,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108303000,x-XEKWYICXSSIUT605b1273e8a060582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:17 +4995675,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108546000,x-XEKWYICXSSIUT605b1345b7acf0582,SELL,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00995200'}]}",0.00995200,NIL,00:00:40 +4995685,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108590000,x-XEKWYICXSSIUT605b137a2b6a90582,SELL,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00995200'}]}",0.00995200,NIL,00:00:29 +4995702,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108619000,x-XEKWYICXSSIUT605b13ae9efdc0582,SELL,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00995200'}]}",0.00995200,NIL,00:00:03 +4995835,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695108861000,x-XEKWYICXBSIUT605b14806dcc70582,BUY,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099600000000000,NIL,00:00:25 +4995974,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695109464000,x-XEKWYICXSSIUT605b16c166e6f0582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:23 +4996116,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695109843000,x-XEKWYICXBSIUT605b183090f360582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:17 +4996503,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695110488000,x-XEKWYICXBSIUT605b1aa5fe74a0582,BUY,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099360000000000,NIL,00:00:02 +4996638,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695111038000,x-XEKWYICXBSIUT605b1cb283e740582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:02 +4996678,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695111160000,x-XEKWYICXBSIUT605b1d1b6c83c0582,BUY,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099600000000000,NIL,00:00:14 +4996776,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695111287000,x-XEKWYICXBSIUT605b1d84523a30582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:31 +4996964,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695111502000,x-XEKWYICXBSIUT605b1e56212e80582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:26 +4997513,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695112713000,x-XEKWYICXSSIUT605b22d813a0f0582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:27 +4997868,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695113239000,x-XEKWYICXSSIUT605b24e498cd80582,SELL,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00995200'}]}",0.00995200,NIL,00:00:03 +4998081,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695113721000,x-XEKWYICXSSIUT605b268836bf70582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:45 +4998148,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695113805000,x-XEKWYICXBSIUT605b26f11e5150582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:19 +4998248,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695113884000,x-XEKWYICXBSIUT605b2725993120582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:43 +4998413,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695114035000,x-XEKWYICXBSIUT605b27c2ed7a70582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:29 +4998981,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695114568000,x-XEKWYICXSSIUT605b29cf725340582,SELL,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01004000'}]}",0.01004000,NIL,00:00:12 +5000345,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695115438000,x-XEKWYICXBSIUT605b2d16ae3580582,BUY,LIMIT,0.12620000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100960000000000,NIL,00:00:02 +5000519,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695115583000,x-XEKWYICXBSIUT605b2d7f9582e0582,BUY,LIMIT,0.12600000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100800000000000,NIL,00:00:37 +5000964,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695116084000,x-XEKWYICXSSIUT605b2f57a7abc0582,SELL,LIMIT,0.12630000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01010400'}]}",0.01010400,NIL,00:00:43 +5001169,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695116452000,x-XEKWYICXSSIUT605b30c6d18f30582,SELL,LIMIT,0.12620000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01009600'}]}",0.01009600,NIL,00:00:26 +5001519,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695116826000,x-XEKWYICXSSIUT605b3235fbcde0582,SELL,LIMIT,0.12570000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01005600'}]}",0.01005600,NIL,00:00:15 +5001767,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695117136000,x-XEKWYICXSSIUT605b333c424130582,SELL,LIMIT,0.12620000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01009600'}]}",0.01009600,NIL,00:00:50 +5002492,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695118144000,x-XEKWYICXBSIUT605b3720d58fd0582,BUY,LIMIT,0.12550000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100400000000000,NIL,00:00:13 +5003372,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695119265000,x-XEKWYICXSSIUT605b3b39e90400582,SELL,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01002400'}]}",0.01002400,NIL,00:00:34 +5003495,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695119324000,x-XEKWYICXSSIUT605b3b6e5350c0582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:38 +5003631,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695119525000,x-XEKWYICXSSIUT605b3c4022e880582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:19 +5003971,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695120452000,x-XEKWYICXBSIUT605b3fbbe3e720582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:11 +5004029,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695120554000,x-XEKWYICXSSIUT605b4024cdca10582,SELL,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01000800'}]}",0.01000800,NIL,00:00:03 +5004511,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695121685000,x-XEKWYICXBSIUT605b443dc45ad0582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:34 +5004724,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695122151000,x-XEKWYICXSSIUT605b4615d62920582,SELL,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996000'}]}",0.00996000,NIL,00:00:05 +5004830,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695122384000,x-XEKWYICXBSIUT605b46e7c15840582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:18 +5005054,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695123062000,x-XEKWYICXSSIUT605b495d1e8020582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:36 +5005333,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695123901000,x-XEKWYICXSSIUT605b4c6fef7d10582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:50 +5005400,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695124076000,x-XEKWYICXBSIUT605b4d41d95990582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:05 +5005439,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695124192000,x-XEKWYICXBSIUT605b4daacd46d0582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:11 +5005533,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695124527000,x-XEKWYICXBSIUT605b4ee5464490582,BUY,LIMIT,0.12500000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100000000000000,NIL,00:00:16 +5005591,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695124748000,x-XEKWYICXBSIUT605b4fb72d9870582,BUY,LIMIT,0.12530000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100240000000000,NIL,00:00:17 +5005631,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695124868000,x-XEKWYICXBSIUT605b501ffd9ec0582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:27 +5005706,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695125044000,x-XEKWYICXSSIUT605b50bd93abc0582,SELL,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.01001600'}]}",0.01001600,NIL,00:00:38 +5005983,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695125681000,x-XEKWYICXSSIUT605b5332d43fb0582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:15 +5006040,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695125808000,x-XEKWYICXSSIUT605b539baf8620582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:32 +5006104,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695125875000,x-XEKWYICXSSIUT605b53d03315d0582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:44 +5006121,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695125889000,x-XEKWYICXSSIUT605b5404edb150582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:03 +5006227,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126061000,x-XEKWYICXBSIUT605b54a1ee9cf0582,BUY,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100080000000000,NIL,00:00:10 +5006296,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126115000,x-XEKWYICXBSIUT605b54d680d7a0582,BUY,LIMIT,0.12520000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100160000000000,NIL,00:00:09 +5006420,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126586000,x-XEKWYICXBSIUT605b567a397b80582,BUY,LIMIT,0.12510000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0100080000000000,NIL,00:00:40 +5006609,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126740000,x-XEKWYICXBSIUT605b57176f4da0582,BUY,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099680000000000,NIL,00:00:29 +5006666,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126768000,x-XEKWYICXSSIUT605b574c184dc0582,SELL,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00998400'}]}",0.00998400,NIL,00:00:02 +5006710,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695126831000,x-XEKWYICXSSIUT605b57805372d0582,SELL,LIMIT,0.12470000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00997600'}]}",0.00997600,NIL,00:00:10 +5006785,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695127009000,x-XEKWYICXSSIUT605b581d9e46b0582,SELL,LIMIT,0.12460000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996800'}]}",0.00996800,NIL,00:00:23 +5006886,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695127095000,x-XEKWYICXBSIUT605b58522d7bb0582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:54 +5006912,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695127099000,x-XEKWYICXSSIUT605b58870b57a0582,SELL,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00996000'}]}",0.00996000,NIL,00:00:03 +5007456,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695127877000,x-XEKWYICXBSIUT605b5b64d9ef10582,BUY,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099280000000000,NIL,00:00:11 +5007630,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695128468000,x-XEKWYICXBSIUT605b5d71623050582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:52 +5007700,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695128637000,x-XEKWYICXSSIUT605b5e0ed616f0582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:56 +5007710,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695128639000,x-XEKWYICXSSIUT605b5e43668d70582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:03 +5007797,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695128815000,x-XEKWYICXBSIUT605b5ee09a5510582,BUY,LIMIT,0.12360000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098880000000000,NIL,00:00:14 +5007946,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695129514000,x-XEKWYICXSSIUT605b61562c3240582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:53 +5008043,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695129800000,x-XEKWYICXBSIUT605b6290e462f0582,BUY,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099360000000000,NIL,00:00:09 +5008131,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695130312000,x-XEKWYICXSSIUT605b6468bea840582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:26 +5008144,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695130363000,x-XEKWYICXBSIUT605b649d671ba0582,BUY,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099440000000000,NIL,00:00:22 +5008267,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695130842000,x-XEKWYICXBSIUT605b6675606d00582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:06 +5008290,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695131015000,x-XEKWYICXBSIUT605b6712dfd8b0582,BUY,LIMIT,0.12440000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099520000000000,NIL,00:00:14 +5008326,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695131131000,x-XEKWYICXSSIUT605b677b8b1ed0582,SELL,LIMIT,0.12430000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00994400'}]}",0.00994400,NIL,00:00:20 +5008381,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695131251000,x-XEKWYICXSSIUT605b67e4afcc40582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:30 +5008571,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695131644000,x-XEKWYICXSSIUT605b6953d686f0582,SELL,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00990400'}]}",0.00990400,NIL,00:00:38 +5009110,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695132236000,x-XEKWYICXSSIUT605b6b94b0bd20582,SELL,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00986400'}]}",0.00986400,NIL,00:00:25 +5009283,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695132547000,x-XEKWYICXBSIUT605b6ccfab3e70582,BUY,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098800000000000,NIL,00:00:06 +5009362,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695132790000,x-XEKWYICXBSIUT605b6da1406c10582,BUY,LIMIT,0.12360000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098880000000000,NIL,00:00:29 +5009445,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695132866000,x-XEKWYICXSSIUT605b6dd5c64bc0582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:50 +5009479,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695132945000,x-XEKWYICXBSIUT605b6e3ea1f450582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:19 +5009516,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695133023000,x-XEKWYICXBSIUT605b6e72e50840582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:42 +5009712,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695133167000,x-XEKWYICXBSIUT605b6f10575c60582,BUY,LIMIT,0.12400000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099200000000000,NIL,00:00:21 +5009947,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695133984000,x-XEKWYICXSSIUT605b72233d91e0582,SELL,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00991200'}]}",0.00991200,NIL,00:00:13 +5010056,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695134252000,x-XEKWYICXSSIUT605b7329868e90582,SELL,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00989600'}]}",0.00989600,NIL,00:00:06 +5010222,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695134656000,x-XEKWYICXBSIUT605b7498b34050582,BUY,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099120000000000,NIL,00:00:25 +5010532,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695135284000,x-XEKWYICXBSIUT605b76d9d29570582,BUY,LIMIT,0.12340000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098720000000000,NIL,00:00:48 +5010563,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695135307000,x-XEKWYICXSSIUT605b770e18fd70582,SELL,LIMIT,0.12340000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00987200'}]}",0.00987200,NIL,00:00:16 +5010589,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695135399000,x-XEKWYICXSSIUT605b774255fda0582,SELL,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988000'}]}",0.00988000,NIL,00:00:53 +5010722,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695135744000,x-XEKWYICXBSIUT605b78b1c337b0582,BUY,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098800000000000,NIL,00:00:13 +5010987,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695135827000,x-XEKWYICXSSIUT605b78e62ffb40582,SELL,LIMIT,0.12350000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00988000'}]}",0.00988000,NIL,00:00:41 +5011236,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695136093000,x-XEKWYICXSSIUT605b79ec7ad640582,SELL,LIMIT,0.12330000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00986400'}]}",0.00986400,NIL,00:00:32 +5011303,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695136329000,x-XEKWYICXBSIUT605b7abe060870582,BUY,LIMIT,0.12370000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0098960000000000,NIL,00:00:48 +5011434,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695136773000,x-XEKWYICXBSIUT605b7c61bcf4d0582,BUY,LIMIT,0.12380000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099040000000000,NIL,00:00:52 +5011662,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137086000,x-XEKWYICXSSIUT605b7d9c8caa90582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:35 +5011959,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137359000,x-XEKWYICXBSIUT605b7ea2ad5080582,BUY,LIMIT,0.12450000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099600000000000,NIL,00:00:33 +5012106,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137707000,x-XEKWYICXBSIUT605b7fdd99bb80582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:51 +5012118,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137741000,x-XEKWYICXBSIUT605b8011c8dc70582,BUY,LIMIT,0.12480000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099840000000000,NIL,00:00:30 +5012327,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137911000,x-XEKWYICXBSIUT605b80af7277b0582,BUY,LIMIT,0.12390000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'SEI', 'flat_fees': [{'token': 'SEI', 'amount': '0.08000000'}]}",0.0099120000000000,NIL,00:00:35 +5012460,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695137990000,x-XEKWYICXSSIUT605b80e3ed2890582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:59 +5012494,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695138066000,x-XEKWYICXSSIUT605b814c810d10582,SELL,LIMIT,0.12410000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00992800'}]}",0.00992800,NIL,00:00:25 +5012508,bot_battle,bot_battle,binance,SEI-USDT,SEI,USDT,1695138106000,x-XEKWYICXSSIUT605b8180f2e010582,SELL,LIMIT,0.12420000,80.00000000,1,"{'fee_type': 'DeductedFromReturns', 'percent': '0', 'percent_token': 'USDT', 'flat_fees': [{'token': 'USDT', 'amount': '0.00993600'}]}",0.00993600,NIL,00:00:10 diff --git a/scripts/directional_strategy_rsi.py b/scripts/directional_strategy_rsi.py new file mode 100644 index 0000000..217e7eb --- /dev/null +++ b/scripts/directional_strategy_rsi.py @@ -0,0 +1,96 @@ +from decimal import Decimal + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class RSI(DirectionalStrategyBase): + """ + RSI (Relative Strength Index) strategy implementation based on the DirectionalStrategyBase. + + This strategy uses the RSI indicator to generate trading signals and execute trades based on the RSI values. + It defines the specific parameters and configurations for the RSI strategy. + + Parameters: + directional_strategy_name (str): The name of the strategy. + trading_pair (str): The trading pair to be traded. + exchange (str): The exchange to be used for trading. + order_amount_usd (Decimal): The amount of the order in USD. + leverage (int): The leverage to be used for trading. + + Position Parameters: + stop_loss (float): The stop-loss percentage for the position. + take_profit (float): The take-profit percentage for the position. + time_limit (int): The time limit for the position in seconds. + trailing_stop_activation_delta (float): The activation delta for the trailing stop. + trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. + + Candlestick Configuration: + candles (List[CandlesBase]): The list of candlesticks used for generating signals. + + Markets: + A dictionary specifying the markets and trading pairs for the strategy. + + Methods: + get_signal(): Generates the trading signal based on the RSI indicator. + get_processed_df(): Retrieves the processed dataframe with RSI values. + market_data_extra_info(): Provides additional information about the market data. + + Inherits from: + DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. + """ + directional_strategy_name: str = "RSI" + # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries + trading_pair: str = "ETH-USDT" + exchange: str = "binance_perpetual" + order_amount_usd = Decimal("40") + leverage = 10 + + # Configure the parameters for the position + stop_loss: float = 0.0075 + take_profit: float = 0.015 + time_limit: int = 60 * 1 + trailing_stop_activation_delta = 0.004 + trailing_stop_trailing_delta = 0.001 + cooldown_after_execution = 10 + + candles = [CandlesFactory.get_candle(CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="3m", max_records=1000))] + markets = {exchange: {trading_pair}} + + def get_signal(self): + """ + Generates the trading signal based on the RSI indicator. + Returns: + int: The trading signal (-1 for sell, 0 for hold, 1 for buy). + """ + candles_df = self.get_processed_df() + rsi_value = candles_df.iat[-1, -1] + if rsi_value > 70: + return -1 + elif rsi_value < 30: + return 1 + else: + return 0 + + def get_processed_df(self): + """ + Retrieves the processed dataframe with RSI values. + Returns: + pd.DataFrame: The processed dataframe with RSI values. + """ + candles_df = self.candles[0].candles_df + candles_df.ta.rsi(length=7, append=True) + return candles_df + + def market_data_extra_info(self): + """ + Provides additional information about the market data to the format status. + Returns: + List[str]: A list of formatted strings containing market data information. + """ + lines = [] + columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_7"] + candles_df = self.get_processed_df() + lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) + lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) + return lines diff --git a/scripts/download_candles.py b/scripts/download_candles.py new file mode 100644 index 0000000..46d737c --- /dev/null +++ b/scripts/download_candles.py @@ -0,0 +1,61 @@ +import os +from typing import Dict + +from hummingbot import data_path +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DownloadCandles(ScriptStrategyBase): + """ + This script provides an example of how to use the Candles Feed to download and store historical data. + It downloads 3-minute candles for 3 Binance trading pairs ["APE-USDT", "BTC-USDT", "BNB-USDT"] and stores them in + CSV files in the /data directory. The script stops after it has downloaded 50,000 max_records records for each pair. + Is important to notice that the component will fail if all the candles are not available since the idea of it is to + use it in production based on candles needed to compute technical indicators. + """ + exchange = os.getenv("EXCHANGE", "binance_perpetual") + trading_pairs = os.getenv("TRADING_PAIRS", "DODO-BUSD,LTC-USDT").split(",") + intervals = os.getenv("INTERVALS", "1m,3m,5m,1h").split(",") + days_to_download = int(os.getenv("DAYS_TO_DOWNLOAD", "3")) + # we can initialize any trading pair since we only need the candles + markets = {"binance_paper_trade": {"BTC-USDT"}} + + @staticmethod + def get_max_records(days_to_download: int, interval: str) -> int: + conversion = {"s": 1 / 60, "m": 1, "h": 60, "d": 1440} + unit = interval[-1] + quantity = int(interval[:-1]) + return int(days_to_download * 24 * 60 / (quantity * conversion[unit])) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + combinations = [(trading_pair, interval) for trading_pair in self.trading_pairs for interval in self.intervals] + + self.candles = {f"{combinations[0]}_{combinations[1]}": {} for combinations in combinations} + # we need to initialize the candles for each trading pair + for combination in combinations: + + candle = CandlesFactory.get_candle(CandlesConfig(connector=self.exchange, trading_pair=combination[0], interval=combination[1], max_records=self.get_max_records(self.days_to_download, combination[1]))) + candle.start() + # we are storing the candles object and the csv path to save the candles + self.candles[f"{combination[0]}_{combination[1]}"]["candles"] = candle + self.candles[f"{combination[0]}_{combination[1]}"][ + "csv_path"] = data_path() + f"/candles_{self.exchange}_{combination[0]}_{combination[1]}.csv" + + def on_tick(self): + for trading_pair, candles_info in self.candles.items(): + if not candles_info["candles"].is_ready: + self.logger().info(f"Candles not ready yet for {trading_pair}! Missing {candles_info['candles']._candles.maxlen - len(candles_info['candles']._candles)}") + pass + else: + df = candles_info["candles"].candles_df + df.to_csv(candles_info["csv_path"], index=False) + if all(candles_info["candles"].is_ready for candles_info in self.candles.values()): + HummingbotApplication.main_application().stop() + + def on_stop(self): + for candles_info in self.candles.values(): + candles_info["candles"].stop() diff --git a/scripts/download_order_book_and_trades.py b/scripts/download_order_book_and_trades.py new file mode 100644 index 0000000..d9c754f --- /dev/null +++ b/scripts/download_order_book_and_trades.py @@ -0,0 +1,99 @@ +import json +import os +from datetime import datetime +from typing import Dict + +from hummingbot import data_path +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DownloadTradesAndOrderBookSnapshots(ScriptStrategyBase): + exchange = os.getenv("EXCHANGE", "binance_paper_trade") + trading_pairs = os.getenv("TRADING_PAIRS", "ETH-USDT,BTC-USDT") + depth = int(os.getenv("DEPTH", 50)) + trading_pairs = [pair for pair in trading_pairs.split(",")] + last_dump_timestamp = 0 + time_between_csv_dumps = 10 + + ob_temp_storage = {trading_pair: [] for trading_pair in trading_pairs} + trades_temp_storage = {trading_pair: [] for trading_pair in trading_pairs} + current_date = None + ob_file_paths = {} + trades_file_paths = {} + markets = {exchange: set(trading_pairs)} + subscribed_to_order_book_trade_event: bool = False + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.create_order_book_and_trade_files() + self.order_book_trade_event = SourceInfoEventForwarder(self._process_public_trade) + + def on_tick(self): + if not self.subscribed_to_order_book_trade_event: + self.subscribe_to_order_book_trade_event() + self.check_and_replace_files() + for trading_pair in self.trading_pairs: + order_book_data = self.get_order_book_dict(self.exchange, trading_pair, self.depth) + self.ob_temp_storage[trading_pair].append(order_book_data) + if self.last_dump_timestamp < self.current_timestamp: + self.dump_and_clean_temp_storage() + + def get_order_book_dict(self, exchange: str, trading_pair: str, depth: int = 50): + order_book = self.connectors[exchange].get_order_book(trading_pair) + snapshot = order_book.snapshot + return { + "ts": self.current_timestamp, + "bids": snapshot[0].loc[:(depth - 1), ["price", "amount"]].values.tolist(), + "asks": snapshot[1].loc[:(depth - 1), ["price", "amount"]].values.tolist(), + } + + def dump_and_clean_temp_storage(self): + for trading_pair, order_book_info in self.ob_temp_storage.items(): + file = self.ob_file_paths[trading_pair] + json_strings = [json.dumps(obj) for obj in order_book_info] + json_data = '\n'.join(json_strings) + file.write(json_data) + self.ob_temp_storage[trading_pair] = [] + for trading_pair, trades_info in self.trades_temp_storage.items(): + file = self.trades_file_paths[trading_pair] + json_strings = [json.dumps(obj) for obj in trades_info] + json_data = '\n'.join(json_strings) + file.write(json_data) + self.trades_temp_storage[trading_pair] = [] + self.last_dump_timestamp = self.current_timestamp + self.time_between_csv_dumps + + def check_and_replace_files(self): + current_date = datetime.now().strftime("%Y-%m-%d") + if current_date != self.current_date: + for file in self.ob_file_paths.values(): + file.close() + self.create_order_book_and_trade_files() + + def create_order_book_and_trade_files(self): + self.current_date = datetime.now().strftime("%Y-%m-%d") + self.ob_file_paths = {trading_pair: self.get_file(self.exchange, trading_pair, "order_book_snapshots", self.current_date) for + trading_pair in self.trading_pairs} + self.trades_file_paths = {trading_pair: self.get_file(self.exchange, trading_pair, "trades", self.current_date) for + trading_pair in self.trading_pairs} + + @staticmethod + def get_file(exchange: str, trading_pair: str, source_type: str, current_date: str): + file_path = data_path() + f"/{exchange}_{trading_pair}_{source_type}_{current_date}.txt" + return open(file_path, "a") + + def _process_public_trade(self, event_tag: int, market: ConnectorBase, event: OrderBookTradeEvent): + self.trades_temp_storage[event.trading_pair].append({ + "ts": event.timestamp, + "price": event.price, + "q_base": event.amount, + "side": event.type.name.lower(), + }) + + def subscribe_to_order_book_trade_event(self): + for market in self.connectors.values(): + for order_book in market.order_books.values(): + order_book.add_listener(OrderBookEvent.TradeEvent, self.order_book_trade_event) + self.subscribed_to_order_book_trade_event = True diff --git a/scripts/fixed_grid.py b/scripts/fixed_grid.py new file mode 100644 index 0000000..09cfc4a --- /dev/null +++ b/scripts/fixed_grid.py @@ -0,0 +1,341 @@ +import logging +from decimal import Decimal +from typing import Dict, List + +import numpy as np +import pandas as pd + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import BuyOrderCompletedEvent, OrderFilledEvent, SellOrderCompletedEvent +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class FixedGrid(ScriptStrategyBase): + # Parameters to modify ----------------------------------------- + trading_pair = "ENJ-USDT" + exchange = "ascend_ex" + n_levels = 8 + grid_price_ceiling = Decimal(0.33) + grid_price_floor = Decimal(0.3) + order_amount = Decimal(18.0) + # Optional ---------------------- + spread_scale_factor = Decimal(1.0) + amount_scale_factor = Decimal(1.0) + rebalance_order_type = "limit" + rebalance_order_spread = Decimal(0.02) + rebalance_order_refresh_time = 60.0 + grid_orders_refresh_time = 3600000.0 + price_source = PriceType.MidPrice + # ---------------------------------------------------------------- + + markets = {exchange: {trading_pair}} + create_timestamp = 0 + price_levels = [] + base_inv_levels = [] + quote_inv_levels = [] + order_amount_levels = [] + quote_inv_levels_current_price = [] + current_level = -100 + grid_spread = (grid_price_ceiling - grid_price_floor) / (n_levels - 1) + inv_correct = True + rebalance_order_amount = Decimal(0.0) + rebalance_order_buy = True + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + + self.minimum_spread = (self.grid_price_ceiling - self.grid_price_floor) / (1 + 2 * sum([pow(self.spread_scale_factor, n) for n in range(1, int(self.n_levels / 2))])) + self.price_levels.append(self.grid_price_floor) + for i in range(2, int(self.n_levels / 2) + 1): + price = self.grid_price_floor + self.minimum_spread * sum([pow(self.spread_scale_factor, int(self.n_levels / 2) - n) for n in range(1, i)]) + self.price_levels.append(price) + for i in range(1, int(self.n_levels / 2) + 1): + self.order_amount_levels.append(self.order_amount * pow(self.amount_scale_factor, int(self.n_levels / 2) - i)) + + for i in range(int(self.n_levels / 2) + 1, self.n_levels + 1): + price = self.price_levels[int(self.n_levels / 2) - 1] + self.minimum_spread * sum([pow(self.spread_scale_factor, n) for n in range(0, i - int(self.n_levels / 2))]) + self.price_levels.append(price) + self.order_amount_levels.append(self.order_amount * pow(self.amount_scale_factor, i - int(self.n_levels / 2) - 1)) + + for i in range(1, self.n_levels + 1): + self.base_inv_levels.append(sum(self.order_amount_levels[i:self.n_levels])) + self.quote_inv_levels.append(sum([self.price_levels[n] * self.order_amount_levels[n] for n in range(0, i - 1)])) + for i in range(self.n_levels): + self.quote_inv_levels_current_price.append(self.quote_inv_levels[i] / self.price_levels[i]) + + def on_tick(self): + proposal = None + if self.create_timestamp <= self.current_timestamp: + # If grid level not yet set, find it. + if self.current_level == -100: + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + # Find level closest to market + min_diff = 1e8 + for i in range(self.n_levels): + if min(min_diff, abs(self.price_levels[i] - price)) < min_diff: + min_diff = abs(self.price_levels[i] - price) + self.current_level = i + + msg = (f"Current price {price}, Initial level {self.current_level+1}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + if price > self.grid_price_ceiling: + msg = ("WARNING: Current price is above grid ceiling") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + elif price < self.grid_price_floor: + msg = ("WARNING: Current price is below grid floor") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset) / self.price_levels[self.current_level]) + + if base_balance < self.base_inv_levels[self.current_level]: + self.inv_correct = False + msg = (f"WARNING: Insuffient {base_asset} balance for grid bot. Will attempt to rebalance") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + if base_balance + quote_balance < self.base_inv_levels[self.current_level] + self.quote_inv_levels_current_price[self.current_level]: + msg = (f"WARNING: Insuffient {base_asset} and {quote_asset} balance for grid bot. Unable to rebalance." + f"Please add funds or change grid parameters") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + return + else: + # Calculate additional base required with 5% tolerance + base_required = (Decimal(self.base_inv_levels[self.current_level]) - Decimal(base_balance)) * Decimal(1.05) + self.rebalance_order_buy = True + self.rebalance_order_amount = Decimal(base_required) + elif quote_balance < self.quote_inv_levels_current_price[self.current_level]: + self.inv_correct = False + msg = (f"WARNING: Insuffient {quote_asset} balance for grid bot. Will attempt to rebalance") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + if base_balance + quote_balance < self.base_inv_levels[self.current_level] + self.quote_inv_levels_current_price[self.current_level]: + msg = (f"WARNING: Insuffient {base_asset} and {quote_asset} balance for grid bot. Unable to rebalance." + f"Please add funds or change grid parameters") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + return + else: + # Calculate additional quote required with 5% tolerance + quote_required = (Decimal(self.quote_inv_levels_current_price[self.current_level]) - Decimal(quote_balance)) * Decimal(1.05) + self.rebalance_order_buy = False + self.rebalance_order_amount = Decimal(quote_required) + else: + self.inv_correct = True + + if self.inv_correct is True: + # Create proposals for Grid + proposal = self.create_grid_proposal() + else: + # Create rebalance proposal + proposal = self.create_rebalance_proposal() + + self.cancel_active_orders() + if proposal is not None: + self.execute_orders_proposal(proposal) + + def create_grid_proposal(self) -> List[OrderCandidate]: + buys = [] + sells = [] + + # Proposal will be created according to grid price levels + for i in range(self.current_level): + price = self.price_levels[i] + size = self.order_amount_levels[i] + if size > 0: + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price) + buys.append(buy_order) + + for i in range(self.current_level + 1, self.n_levels): + price = self.price_levels[i] + size = self.order_amount_levels[i] + if size > 0: + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price) + sells.append(sell_order) + + return buys + sells + + def create_rebalance_proposal(self): + buys = [] + sells = [] + + # Proposal will be created according to start order spread. + if self.rebalance_order_buy is True: + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + price = ref_price * (Decimal("100") - self.rebalance_order_spread) / Decimal("100") + size = self.rebalance_order_amount + + msg = (f"Placing buy order to rebalance; amount: {size}, price: {price}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + if size > 0: + if self.rebalance_order_type == "limit": + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price) + elif self.rebalance_order_type == "market": + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.MARKET, + order_side=TradeType.BUY, amount=size, price=price) + buys.append(buy_order) + + if self.rebalance_order_buy is False: + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + price = ref_price * (Decimal("100") + self.rebalance_order_spread) / Decimal("100") + size = self.rebalance_order_amount + msg = (f"Placing sell order to rebalance; amount: {size}, price: {price}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + if size > 0: + if self.rebalance_order_type == "limit": + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price) + elif self.rebalance_order_type == "market": + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.MARKET, + order_side=TradeType.SELL, amount=size, price=price) + sells.append(sell_order) + + return buys + sells + + def did_fill_order(self, event: OrderFilledEvent): + msg = (f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + if self.inv_correct is False: + self.create_timestamp = self.current_timestamp + float(1.0) + + if self.inv_correct is True: + # Set the new level + self.current_level -= 1 + # Add sell order above current level + price = self.price_levels[self.current_level + 1] + size = self.order_amount_levels[self.current_level + 1] + proposal = [OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price)] + self.execute_orders_proposal(proposal) + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + if self.inv_correct is False: + self.create_timestamp = self.current_timestamp + float(1.0) + + if self.inv_correct is True: + # Set the new level + self.current_level += 1 + # Add buy order above current level + price = self.price_levels[self.current_level - 1] + size = self.order_amount_levels[self.current_level - 1] + proposal = [OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price)] + self.execute_orders_proposal(proposal) + + def execute_orders_proposal(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + self.place_order(connector_name=self.exchange, order=order) + if self.inv_correct is False: + next_cycle = self.current_timestamp + self.rebalance_order_refresh_time + if self.create_timestamp <= self.current_timestamp: + self.create_timestamp = next_cycle + else: + next_cycle = self.current_timestamp + self.grid_orders_refresh_time + if self.create_timestamp <= self.current_timestamp: + self.create_timestamp = next_cycle + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def grid_assets_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + base_value = base_balance * float(price) + total_in_quote = base_value + quote_balance + base_ratio = base_value / total_in_quote if total_in_quote > 0 else 0 + quote_ratio = quote_balance / total_in_quote if total_in_quote > 0 else 0 + data = [ + ["", base_asset, quote_asset], + ["Total Balance", round(base_balance, 4), round(quote_balance, 4)], + ["Available Balance", round(available_base_balance, 4), round(available_quote_balance, 4)], + [f"Current Value ({quote_asset})", round(base_value, 4), round(quote_balance, 4)] + ] + data.append(["Current %", f"{base_ratio:.1%}", f"{quote_ratio:.1%}"]) + df = pd.DataFrame(data=data) + return df + + def grid_status_data_frame(self) -> pd.DataFrame: + grid_data = [] + grid_columns = ["Parameter", "Value"] + + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset) / self.price_levels[self.current_level]) + + grid_data.append(["Grid spread", round(self.grid_spread, 4)]) + grid_data.append(["Current grid level", self.current_level + 1]) + grid_data.append([f"{base_asset} required", round(self.base_inv_levels[self.current_level], 4)]) + grid_data.append([f"{quote_asset} required in {base_asset}", round(self.quote_inv_levels_current_price[self.current_level], 4)]) + grid_data.append([f"{base_asset} balance", round(base_balance, 4)]) + grid_data.append([f"{quote_asset} balance in {base_asset}", round(quote_balance, 4)]) + grid_data.append(["Correct inventory balance", self.inv_correct]) + + return pd.DataFrame(data=grid_data, columns=grid_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + """ + Displays the status of the fixed grid strategy + Returns status of the current strategy on user balances and current active orders. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + grid_df = map_df_to_str(self.grid_status_data_frame()) + lines.extend(["", " Grid:"] + [" " + line for line in grid_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.grid_assets_df()) + + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def cancel_active_orders(self): + """ + Cancels active orders + """ + for order in self.get_active_orders(connector_name=self.exchange): + self.cancel(self.exchange, order.trading_pair, order.client_order_id) diff --git a/scripts/klc_vol.py b/scripts/klc_vol.py new file mode 100644 index 0000000..88e7b8b --- /dev/null +++ b/scripts/klc_vol.py @@ -0,0 +1,96 @@ +import logging +import random +from decimal import Decimal +from typing import List, Optional + +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase +from hummingbot.logger import HummingbotLogger +from hummingbot.core.event.events import OrderFilledEvent + +# Import necessary modules from Whitebit exchange connector +from hummingbot.connector.exchange.whitebit.whitebit_exchange import WhitebitExchange + +class VolumeTradingStrategy(ScriptStrategyBase): + _logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._logger is None: + cls._logger = logging.getLogger(__name__) + return cls._logger + + """ + This strategy places a limit buy order and then a market sell order to increase trading volume. + It operates on a 15-second cycle with variable order amounts in KLC tokens, using the current market mid price. + """ + min_order_amount_klc = 400 # Minimum order amount in KLC tokens + max_order_amount_klc = 900 # Maximum order amount in KLC tokens + order_refresh_time = 15 # Time interval in seconds for each cycle + trading_pair = "KLC-USDT" # Trading pair + exchange = "whitebit" # Exchange name + price_source = "current_market" # Current market as price source + price_type = PriceType.MidPrice # Mid price as price type + markets = {exchange: {trading_pair}} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.last_order_timestamp = 0 + + async def on_tick(self): + if self.current_timestamp - self.last_order_timestamp >= self.order_refresh_time: + self.logger().info("Executing trading cycle.") + if not self._exchange_ready(): + self.logger().warning("Whitebit exchange is not ready. Waiting...") + # Optionally, manually trigger initialization here + return + + self.last_order_timestamp = self.current_timestamp + await self.cancel_all_orders() + proposal = self.create_proposal() + proposal_adjusted = self.adjust_proposal_to_budget(proposal) + await self.place_orders(proposal_adjusted) + + def _exchange_ready(self) -> bool: + exchange = self.connectors[self.exchange] + is_ready = exchange.is_ready() if isinstance(exchange, WhitebitExchange) else False + self.logger().debug(f"Exchange ready status: {is_ready}") + return is_ready + + def create_proposal(self) -> List[OrderCandidate]: + self.logger().info("Creating order proposal.") + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_type) + order_amount_klc = random.uniform(self.min_order_amount_klc, self.max_order_amount_klc) + buy_price = ref_price + + buy_order = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal(order_amount_klc), + price=buy_price + ) + return [buy_order] + + def adjust_proposal_to_budget(self, proposal: List[OrderCandidate]) -> List[OrderCandidate]: + self.logger().info("Adjusting proposal to budget.") + proposal_adjusted = self.connectors[self.exchange].budget_checker.adjust_candidates(proposal, all_or_none=True) + return proposal_adjusted + + async def place_orders(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + self.logger().info(f"Placing order: {order}") + await self.place_order(connector_name=self.exchange, order=order) + + async def cancel_all_orders(self): + self.logger().info("Cancelling all orders.") + for order in self.get_active_orders(connector_name=self.exchange): + await self.cancel(self.exchange, order.trading_pair, order.client_order_id) + + def did_fill_order(self, event: OrderFilledEvent): + self.logger().info(f"Order filled: {event}") + if event.trade_type == TradeType.BUY: + self.logger().info(f"Placing market sell order for {event.amount} of {event.trading_pair}") + self.sell(connector_name=self.exchange, trading_pair=event.trading_pair, amount=event.amount, order_type=OrderType.MARKET) diff --git a/scripts/simple_arbitrage_example.py b/scripts/simple_arbitrage_example.py new file mode 100644 index 0000000..07dc714 --- /dev/null +++ b/scripts/simple_arbitrage_example.py @@ -0,0 +1,193 @@ +import logging +from decimal import Decimal +from typing import Any, Dict + +import pandas as pd + +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class SimpleArbitrage(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Simple-Arbitrage-51b2af6e54b6493dab12e5d537798c07 + Video: TBD + Description: + A simplified version of Hummingbot arbitrage strategy, this bot checks the Volume Weighted Average Price for + bid and ask in two exchanges and if it finds a profitable opportunity, it will trade the tokens. + """ + order_amount = Decimal("0.01") # in base asset + min_profitability = Decimal("0.002") # in percentage + base = "ETH" + quote = "USDT" + trading_pair = f"{base}-{quote}" + exchange_A = "binance_paper_trade" + exchange_B = "kucoin_paper_trade" + + markets = {exchange_A: {trading_pair}, + exchange_B: {trading_pair}} + + def on_tick(self): + vwap_prices = self.get_vwap_prices_for_amount(self.order_amount) + proposal = self.check_profitability_and_create_proposal(vwap_prices) + if len(proposal) > 0: + proposal_adjusted: Dict[str, OrderCandidate] = self.adjust_proposal_to_budget(proposal) + self.place_orders(proposal_adjusted) + + def get_vwap_prices_for_amount(self, amount: Decimal): + bid_ex_a = self.connectors[self.exchange_A].get_vwap_for_volume(self.trading_pair, False, amount) + ask_ex_a = self.connectors[self.exchange_A].get_vwap_for_volume(self.trading_pair, True, amount) + bid_ex_b = self.connectors[self.exchange_B].get_vwap_for_volume(self.trading_pair, False, amount) + ask_ex_b = self.connectors[self.exchange_B].get_vwap_for_volume(self.trading_pair, True, amount) + vwap_prices = { + self.exchange_A: { + "bid": bid_ex_a.result_price, + "ask": ask_ex_a.result_price + }, + self.exchange_B: { + "bid": bid_ex_b.result_price, + "ask": ask_ex_b.result_price + } + } + return vwap_prices + + def get_fees_percentages(self, vwap_prices: Dict[str, Any]) -> Dict: + # We assume that the fee percentage for buying or selling is the same + a_fee = self.connectors[self.exchange_A].get_fee( + base_currency=self.base, + quote_currency=self.quote, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, + amount=self.order_amount, + price=vwap_prices[self.exchange_A]["ask"], + is_maker=False + ).percent + + b_fee = self.connectors[self.exchange_B].get_fee( + base_currency=self.base, + quote_currency=self.quote, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, + amount=self.order_amount, + price=vwap_prices[self.exchange_B]["ask"], + is_maker=False + ).percent + + return { + self.exchange_A: a_fee, + self.exchange_B: b_fee + } + + def get_profitability_analysis(self, vwap_prices: Dict[str, Any]) -> Dict: + fees = self.get_fees_percentages(vwap_prices) + buy_a_sell_b_quote = vwap_prices[self.exchange_A]["ask"] * (1 - fees[self.exchange_A]) * self.order_amount - \ + vwap_prices[self.exchange_B]["bid"] * (1 + fees[self.exchange_B]) * self.order_amount + buy_a_sell_b_base = buy_a_sell_b_quote / ( + (vwap_prices[self.exchange_A]["ask"] + vwap_prices[self.exchange_B]["bid"]) / 2) + + buy_b_sell_a_quote = vwap_prices[self.exchange_B]["ask"] * (1 - fees[self.exchange_B]) * self.order_amount - \ + vwap_prices[self.exchange_A]["bid"] * (1 + fees[self.exchange_A]) * self.order_amount + + buy_b_sell_a_base = buy_b_sell_a_quote / ( + (vwap_prices[self.exchange_B]["ask"] + vwap_prices[self.exchange_A]["bid"]) / 2) + + return { + "buy_a_sell_b": + { + "quote_diff": buy_a_sell_b_quote, + "base_diff": buy_a_sell_b_base, + "profitability_pct": buy_a_sell_b_base / self.order_amount + }, + "buy_b_sell_a": + { + "quote_diff": buy_b_sell_a_quote, + "base_diff": buy_b_sell_a_base, + "profitability_pct": buy_b_sell_a_base / self.order_amount + }, + } + + def check_profitability_and_create_proposal(self, vwap_prices: Dict[str, Any]) -> Dict: + proposal = {} + profitability_analysis = self.get_profitability_analysis(vwap_prices) + if profitability_analysis["buy_a_sell_b"]["profitability_pct"] > self.min_profitability: + # This means that the ask of the first exchange is lower than the bid of the second one + proposal[self.exchange_A] = OrderCandidate(trading_pair=self.trading_pair, is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, amount=self.order_amount, + price=vwap_prices[self.exchange_A]["ask"]) + proposal[self.exchange_B] = OrderCandidate(trading_pair=self.trading_pair, is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.SELL, amount=Decimal(self.order_amount), + price=vwap_prices[self.exchange_B]["bid"]) + elif profitability_analysis["buy_b_sell_a"]["profitability_pct"] > self.min_profitability: + # This means that the ask of the second exchange is lower than the bid of the first one + proposal[self.exchange_B] = OrderCandidate(trading_pair=self.trading_pair, is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.BUY, amount=self.order_amount, + price=vwap_prices[self.exchange_B]["ask"]) + proposal[self.exchange_A] = OrderCandidate(trading_pair=self.trading_pair, is_maker=False, + order_type=OrderType.MARKET, + order_side=TradeType.SELL, amount=Decimal(self.order_amount), + price=vwap_prices[self.exchange_A]["bid"]) + + return proposal + + def adjust_proposal_to_budget(self, proposal: Dict[str, OrderCandidate]) -> Dict[str, OrderCandidate]: + for connector, order in proposal.items(): + proposal[connector] = self.connectors[connector].budget_checker.adjust_candidate(order, all_or_none=True) + return proposal + + def place_orders(self, proposal: Dict[str, OrderCandidate]) -> None: + for connector, order in proposal.items(): + self.place_order(connector_name=connector, order=order) + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + vwap_prices = self.get_vwap_prices_for_amount(self.order_amount) + lines.extend(["", " VWAP Prices for amount"] + [" " + line for line in + pd.DataFrame(vwap_prices).to_string().split("\n")]) + profitability_analysis = self.get_profitability_analysis(vwap_prices) + lines.extend(["", " Profitability (%)"] + [ + f" Buy A: {self.exchange_A} --> Sell B: {self.exchange_B}"] + [ + f" Quote Diff: {profitability_analysis['buy_a_sell_b']['quote_diff']:.7f}"] + [ + f" Base Diff: {profitability_analysis['buy_a_sell_b']['base_diff']:.7f}"] + [ + f" Percentage: {profitability_analysis['buy_a_sell_b']['profitability_pct'] * 100:.4f} %"] + [ + f" Buy B: {self.exchange_B} --> Sell A: {self.exchange_A}"] + [ + f" Quote Diff: {profitability_analysis['buy_b_sell_a']['quote_diff']:.7f}"] + [ + f" Base Diff: {profitability_analysis['buy_b_sell_a']['base_diff']:.7f}"] + [ + f" Percentage: {profitability_analysis['buy_b_sell_a']['profitability_pct'] * 100:.4f} %" + ]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def did_fill_order(self, event: OrderFilledEvent): + msg = ( + f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) diff --git a/scripts/simple_pmm_example.py b/scripts/simple_pmm_example.py new file mode 100644 index 0000000..29dc7d0 --- /dev/null +++ b/scripts/simple_pmm_example.py @@ -0,0 +1,77 @@ +import logging +from decimal import Decimal +from typing import List + +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class SimplePMM(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-63cc765486dd42228d3da0b32537fc92 + Video: - + Description: + The bot will place two orders around the price_source (mid price or last traded price) in a trading_pair on + exchange, with a distance defined by the ask_spread and bid_spread. Every order_refresh_time in seconds, + the bot will cancel and replace the orders. + """ + bid_spread = 0.001 + ask_spread = 0.001 + order_refresh_time = 15 + order_amount = 0.01 + create_timestamp = 0 + trading_pair = "ETH-USDT" + exchange = "kucoin_paper_trade" + # Here you can use for example the LastTrade price to use in your strategy + price_source = PriceType.MidPrice + + markets = {exchange: {trading_pair}} + + def on_tick(self): + if self.create_timestamp <= self.current_timestamp: + self.cancel_all_orders() + proposal: List[OrderCandidate] = self.create_proposal() + proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal) + self.place_orders(proposal_adjusted) + self.create_timestamp = self.order_refresh_time + self.current_timestamp + + def create_proposal(self) -> List[OrderCandidate]: + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + buy_price = ref_price * Decimal(1 - self.bid_spread) + sell_price = ref_price * Decimal(1 + self.ask_spread) + + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=Decimal(self.order_amount), price=buy_price) + + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=Decimal(self.order_amount), price=sell_price) + + return [buy_order, sell_order] + + def adjust_proposal_to_budget(self, proposal: List[OrderCandidate]) -> List[OrderCandidate]: + proposal_adjusted = self.connectors[self.exchange].budget_checker.adjust_candidates(proposal, all_or_none=True) + return proposal_adjusted + + def place_orders(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + self.place_order(connector_name=self.exchange, order=order) + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def cancel_all_orders(self): + for order in self.get_active_orders(connector_name=self.exchange): + self.cancel(self.exchange, order.trading_pair, order.client_order_id) + + def did_fill_order(self, event: OrderFilledEvent): + msg = (f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) diff --git a/scripts/simple_vwap_example.py b/scripts/simple_vwap_example.py new file mode 100644 index 0000000..42323f4 --- /dev/null +++ b/scripts/simple_vwap_example.py @@ -0,0 +1,184 @@ +import logging +import math +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import OrderFilledEvent, OrderType, TradeType +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class VWAPExample(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Simple-VWAP-Example-d43a929cc5bd45c6b1a72f63e6635618 + Video: - + Description: + This example lets you create one VWAP in a market using a percentage of the sum volume of the order book + until a spread from the mid price. + This example demonstrates: + - How to get the account balance + - How to get the bids and asks of a market + - How to code a "utility" strategy + """ + last_ordered_ts = 0 + vwap: Dict = {"connector_name": "binance_paper_trade", "trading_pair": "ETH-USDT", "is_buy": True, + "total_volume_usd": 100000, "price_spread": 0.001, "volume_perc": 0.001, "order_delay_time": 10} + markets = {vwap["connector_name"]: {vwap["trading_pair"]}} + + def on_tick(self): + """ + Every order delay time the strategy will buy or sell the base asset. It will compute the cumulative order book + volume until the spread and buy a percentage of that. + The input of the strategy is in USD, but we will use the rate oracle to get a target base that will be static. + - Use the Rate Oracle to get a conversion rate + - Create proposal (a list of order candidates) + - Check the account balance and adjust the proposal accordingly (lower order amount if needed) + - Lastly, execute the proposal on the exchange + """ + if self.last_ordered_ts < (self.current_timestamp - self.vwap["order_delay_time"]): + if self.vwap.get("status") is None: + self.init_vwap_stats() + elif self.vwap.get("status") == "ACTIVE": + vwap_order: OrderCandidate = self.create_order() + vwap_order_adjusted = self.vwap["connector"].budget_checker.adjust_candidate(vwap_order, + all_or_none=False) + if math.isclose(vwap_order_adjusted.amount, Decimal("0"), rel_tol=1E-5): + self.logger().info(f"Order adjusted: {vwap_order_adjusted.amount}, too low to place an order") + else: + self.place_order( + connector_name=self.vwap["connector_name"], + trading_pair=self.vwap["trading_pair"], + is_buy=self.vwap["is_buy"], + amount=vwap_order_adjusted.amount, + order_type=vwap_order_adjusted.order_type) + self.last_ordered_ts = self.current_timestamp + + def init_vwap_stats(self): + # General parameters + vwap = self.vwap.copy() + vwap["connector"] = self.connectors[vwap["connector_name"]] + vwap["delta"] = 0 + vwap["trades"] = [] + vwap["status"] = "ACTIVE" + vwap["trade_type"] = TradeType.BUY if self.vwap["is_buy"] else TradeType.SELL + base_asset, quote_asset = split_hb_trading_pair(vwap["trading_pair"]) + + # USD conversion to quote and base asset + conversion_base_asset = f"{base_asset}-USD" + conversion_quote_asset = f"{quote_asset}-USD" + base_conversion_rate = RateOracle.get_instance().get_pair_rate(conversion_base_asset) + quote_conversion_rate = RateOracle.get_instance().get_pair_rate(conversion_quote_asset) + vwap["start_price"] = vwap["connector"].get_price(vwap["trading_pair"], vwap["is_buy"]) + vwap["target_base_volume"] = vwap["total_volume_usd"] / base_conversion_rate + vwap["ideal_quote_volume"] = vwap["total_volume_usd"] / quote_conversion_rate + + # Compute market order scenario + orderbook_query = vwap["connector"].get_quote_volume_for_base_amount(vwap["trading_pair"], vwap["is_buy"], + vwap["target_base_volume"]) + vwap["market_order_base_volume"] = orderbook_query.query_volume + vwap["market_order_quote_volume"] = orderbook_query.result_volume + vwap["volume_remaining"] = vwap["target_base_volume"] + vwap["real_quote_volume"] = Decimal(0) + self.vwap = vwap + + def create_order(self) -> OrderCandidate: + """ + Retrieves the cumulative volume of the order book until the price spread is reached, then takes a percentage + of that to use as order amount. + """ + # Compute the new price using the max spread allowed + mid_price = float(self.vwap["connector"].get_mid_price(self.vwap["trading_pair"])) + price_multiplier = 1 + self.vwap["price_spread"] if self.vwap["is_buy"] else 1 - self.vwap["price_spread"] + price_affected_by_spread = mid_price * price_multiplier + + # Query the cumulative volume until the price affected by spread + orderbook_query = self.vwap["connector"].get_volume_for_price( + trading_pair=self.vwap["trading_pair"], + is_buy=self.vwap["is_buy"], + price=price_affected_by_spread) + volume_for_price = orderbook_query.result_volume + + # Check if the volume available is higher than the remaining + amount = min(volume_for_price * Decimal(self.vwap["volume_perc"]), Decimal(self.vwap["volume_remaining"])) + + # Quantize the order amount and price + amount = self.vwap["connector"].quantize_order_amount(self.vwap["trading_pair"], amount) + price = self.vwap["connector"].quantize_order_price(self.vwap["trading_pair"], + Decimal(price_affected_by_spread)) + # Create the Order Candidate + vwap_order = OrderCandidate( + trading_pair=self.vwap["trading_pair"], + is_maker=False, + order_type=OrderType.MARKET, + order_side=self.vwap["trade_type"], + amount=amount, + price=price) + return vwap_order + + def place_order(self, + connector_name: str, + trading_pair: str, + is_buy: bool, + amount: Decimal, + order_type: OrderType, + price=Decimal("NaN"), + ): + if is_buy: + self.buy(connector_name, trading_pair, amount, order_type, price) + else: + self.sell(connector_name, trading_pair, amount, order_type, price) + + def did_fill_order(self, event: OrderFilledEvent): + """ + Listens to fill order event to log it and notify the Hummingbot application. + If you set up Telegram bot, you will get notification there as well. + """ + if event.trading_pair == self.vwap["trading_pair"] and event.trade_type == self.vwap["trade_type"]: + self.vwap["volume_remaining"] -= event.amount + self.vwap["delta"] = (self.vwap["target_base_volume"] - self.vwap["volume_remaining"]) / self.vwap[ + "target_base_volume"] + self.vwap["real_quote_volume"] += event.price * event.amount + self.vwap["trades"].append(event) + if math.isclose(self.vwap["delta"], 1, rel_tol=1e-5): + self.vwap["status"] = "COMPLETE" + msg = (f"({event.trading_pair}) {event.trade_type.name} order (price: {round(event.price, 2)}) of " + f"{round(event.amount, 2)} " + f"{split_hb_trading_pair(event.trading_pair)[0]} is filled.") + + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + lines.extend(["", "VWAP Info:"] + [" " + key + ": " + value + for key, value in self.vwap.items() + if isinstance(value, str)]) + + lines.extend(["", "VWAP Stats:"] + [" " + key + ": " + str(round(value, 4)) + for key, value in self.vwap.items() + if type(value) in [int, float, Decimal]]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) diff --git a/scripts/simple_xemm_example.py b/scripts/simple_xemm_example.py new file mode 100644 index 0000000..3c8244b --- /dev/null +++ b/scripts/simple_xemm_example.py @@ -0,0 +1,204 @@ +from decimal import Decimal + +import pandas as pd + +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class SimpleXEMM(ScriptStrategyBase): + """ + BotCamp Cohort: Sept 2022 + Design Template: https://hummingbot-foundation.notion.site/Simple-XEMM-Example-f08cf7546ea94a44b389672fd21bb9ad + Video: https://www.loom.com/share/ca08fe7bc3d14ba68ae704305ac78a3a + Description: + A simplified version of Hummingbot cross-exchange market making strategy, this bot makes a market on + the maker pair and hedges any filled trades in the taker pair. If the spread (difference between maker order price + and taker hedge price) dips below min_spread, the bot refreshes the order + """ + + maker_exchange = "kucoin_paper_trade" + maker_pair = "ETH-USDT" + taker_exchange = "gate_io_paper_trade" + taker_pair = "ETH-USDT" + + order_amount = 0.1 # amount for each order + spread_bps = 10 # bot places maker orders at this spread to taker price + min_spread_bps = 0 # bot refreshes order if spread is lower than min-spread + slippage_buffer_spread_bps = 100 # buffer applied to limit taker hedging trades on taker exchange + max_order_age = 120 # bot refreshes orders after this age + + markets = {maker_exchange: {maker_pair}, taker_exchange: {taker_pair}} + + buy_order_placed = False + sell_order_placed = False + + def on_tick(self): + taker_buy_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + taker_sell_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, False, self.order_amount) + + if not self.buy_order_placed: + maker_buy_price = taker_sell_result.result_price * Decimal(1 - self.spread_bps / 10000) + buy_order_amount = min(self.order_amount, self.buy_hedging_budget()) + buy_order = OrderCandidate(trading_pair=self.maker_pair, is_maker=True, order_type=OrderType.LIMIT, order_side=TradeType.BUY, amount=Decimal(buy_order_amount), price=maker_buy_price) + buy_order_adjusted = self.connectors[self.maker_exchange].budget_checker.adjust_candidate(buy_order, all_or_none=False) + self.buy(self.maker_exchange, self.maker_pair, buy_order_adjusted.amount, buy_order_adjusted.order_type, buy_order_adjusted.price) + self.buy_order_placed = True + + if not self.sell_order_placed: + maker_sell_price = taker_buy_result.result_price * Decimal(1 + self.spread_bps / 10000) + sell_order_amount = min(self.order_amount, self.sell_hedging_budget()) + sell_order = OrderCandidate(trading_pair=self.maker_pair, is_maker=True, order_type=OrderType.LIMIT, order_side=TradeType.SELL, amount=Decimal(sell_order_amount), price=maker_sell_price) + sell_order_adjusted = self.connectors[self.maker_exchange].budget_checker.adjust_candidate(sell_order, all_or_none=False) + self.sell(self.maker_exchange, self.maker_pair, sell_order_adjusted.amount, sell_order_adjusted.order_type, sell_order_adjusted.price) + self.sell_order_placed = True + + for order in self.get_active_orders(connector_name=self.maker_exchange): + cancel_timestamp = order.creation_timestamp / 1000000 + self.max_order_age + if order.is_buy: + buy_cancel_threshold = taker_sell_result.result_price * Decimal(1 - self.min_spread_bps / 10000) + if order.price > buy_cancel_threshold or cancel_timestamp < self.current_timestamp: + self.logger().info(f"Cancelling buy order: {order.client_order_id}") + self.cancel(self.maker_exchange, order.trading_pair, order.client_order_id) + self.buy_order_placed = False + else: + sell_cancel_threshold = taker_buy_result.result_price * Decimal(1 + self.min_spread_bps / 10000) + if order.price < sell_cancel_threshold or cancel_timestamp < self.current_timestamp: + self.logger().info(f"Cancelling sell order: {order.client_order_id}") + self.cancel(self.maker_exchange, order.trading_pair, order.client_order_id) + self.sell_order_placed = False + return + + def buy_hedging_budget(self) -> Decimal: + balance = self.connectors[self.taker_exchange].get_available_balance("ETH") + return balance + + def sell_hedging_budget(self) -> Decimal: + balance = self.connectors[self.taker_exchange].get_available_balance("USDT") + taker_buy_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + return balance / taker_buy_result.result_price + + def is_active_maker_order(self, event: OrderFilledEvent): + """ + Helper function that checks if order is an active order on the maker exchange + """ + for order in self.get_active_orders(connector_name=self.maker_exchange): + if order.client_order_id == event.order_id: + return True + return False + + def did_fill_order(self, event: OrderFilledEvent): + + mid_price = self.connectors[self.maker_exchange].get_mid_price(self.maker_pair) + if event.trade_type == TradeType.BUY and self.is_active_maker_order(event): + taker_sell_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, False, self.order_amount) + sell_price_with_slippage = taker_sell_result.result_price * Decimal(1 - self.slippage_buffer_spread_bps / 10000) + self.logger().info(f"Filled maker buy order with price: {event.price}") + sell_spread_bps = (taker_sell_result.result_price - event.price) / mid_price * 10000 + self.logger().info(f"Sending taker sell order at price: {taker_sell_result.result_price} spread: {int(sell_spread_bps)} bps") + sell_order = OrderCandidate(trading_pair=self.taker_pair, is_maker=False, order_type=OrderType.LIMIT, order_side=TradeType.SELL, amount=Decimal(event.amount), price=sell_price_with_slippage) + sell_order_adjusted = self.connectors[self.taker_exchange].budget_checker.adjust_candidate(sell_order, all_or_none=False) + self.sell(self.taker_exchange, self.taker_pair, sell_order_adjusted.amount, sell_order_adjusted.order_type, sell_order_adjusted.price) + self.buy_order_placed = False + else: + if event.trade_type == TradeType.SELL and self.is_active_maker_order(event): + taker_buy_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + buy_price_with_slippage = taker_buy_result.result_price * Decimal(1 + self.slippage_buffer_spread_bps / 10000) + buy_spread_bps = (event.price - taker_buy_result.result_price) / mid_price * 10000 + self.logger().info(f"Filled maker sell order at price: {event.price}") + self.logger().info(f"Sending taker buy order: {taker_buy_result.result_price} spread: {int(buy_spread_bps)}") + buy_order = OrderCandidate(trading_pair=self.taker_pair, is_maker=False, order_type=OrderType.LIMIT, order_side=TradeType.BUY, amount=Decimal(event.amount), price=buy_price_with_slippage) + buy_order_adjusted = self.connectors[self.taker_exchange].budget_checker.adjust_candidate(buy_order, all_or_none=False) + self.buy(self.taker_exchange, self.taker_pair, buy_order_adjusted.amount, buy_order_adjusted.order_type, buy_order_adjusted.price) + self.sell_order_placed = False + + def exchanges_df(self) -> pd.DataFrame: + """ + Return a custom data frame of prices on maker vs taker exchanges for display purposes + """ + mid_price = self.connectors[self.maker_exchange].get_mid_price(self.maker_pair) + maker_buy_result = self.connectors[self.maker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + maker_sell_result = self.connectors[self.maker_exchange].get_price_for_volume(self.taker_pair, False, self.order_amount) + taker_buy_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + taker_sell_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, False, self.order_amount) + maker_buy_spread_bps = (maker_buy_result.result_price - taker_buy_result.result_price) / mid_price * 10000 + maker_sell_spread_bps = (taker_sell_result.result_price - maker_sell_result.result_price) / mid_price * 10000 + columns = ["Exchange", "Market", "Mid Price", "Buy Price", "Sell Price", "Buy Spread", "Sell Spread"] + data = [] + data.append([ + self.maker_exchange, + self.maker_pair, + float(self.connectors[self.maker_exchange].get_mid_price(self.maker_pair)), + float(maker_buy_result.result_price), + float(maker_sell_result.result_price), + int(maker_buy_spread_bps), + int(maker_sell_spread_bps) + ]) + data.append([ + self.taker_exchange, + self.taker_pair, + float(self.connectors[self.taker_exchange].get_mid_price(self.maker_pair)), + float(taker_buy_result.result_price), + float(taker_sell_result.result_price), + int(-maker_buy_spread_bps), + int(-maker_sell_spread_bps) + ]) + df = pd.DataFrame(data=data, columns=columns) + return df + + def active_orders_df(self) -> pd.DataFrame: + """ + Returns a custom data frame of all active maker orders for display purposes + """ + columns = ["Exchange", "Market", "Side", "Price", "Amount", "Spread Mid", "Spread Cancel", "Age"] + data = [] + mid_price = self.connectors[self.maker_exchange].get_mid_price(self.maker_pair) + taker_buy_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, True, self.order_amount) + taker_sell_result = self.connectors[self.taker_exchange].get_price_for_volume(self.taker_pair, False, self.order_amount) + buy_cancel_threshold = taker_sell_result.result_price * Decimal(1 - self.min_spread_bps / 10000) + sell_cancel_threshold = taker_buy_result.result_price * Decimal(1 + self.min_spread_bps / 10000) + for connector_name, connector in self.connectors.items(): + for order in self.get_active_orders(connector_name): + age_txt = "n/a" if order.age() <= 0. else pd.Timestamp(order.age(), unit='s').strftime('%H:%M:%S') + spread_mid_bps = (mid_price - order.price) / mid_price * 10000 if order.is_buy else (order.price - mid_price) / mid_price * 10000 + spread_cancel_bps = (buy_cancel_threshold - order.price) / buy_cancel_threshold * 10000 if order.is_buy else (order.price - sell_cancel_threshold) / sell_cancel_threshold * 10000 + data.append([ + self.maker_exchange, + order.trading_pair, + "buy" if order.is_buy else "sell", + float(order.price), + float(order.quantity), + int(spread_mid_bps), + int(spread_cancel_bps), + age_txt + ]) + if not data: + raise ValueError + df = pd.DataFrame(data=data, columns=columns) + df.sort_values(by=["Market", "Side"], inplace=True) + return df + + def format_status(self) -> str: + """ + Returns status of the current strategy on user balances and current active orders. This function is called + when status command is issued. Override this function to create custom status display output. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + exchanges_df = self.exchanges_df() + lines.extend(["", " Exchanges:"] + [" " + line for line in exchanges_df.to_string(index=False).split("\n")]) + + try: + orders_df = self.active_orders_df() + lines.extend(["", " Active Orders:"] + [" " + line for line in orders_df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + return "\n".join(lines) diff --git a/scripts/v2_directional-trading_macd_bb_v1.py b/scripts/v2_directional-trading_macd_bb_v1.py new file mode 100644 index 0000000..2e72ccd --- /dev/null +++ b/scripts/v2_directional-trading_macd_bb_v1.py @@ -0,0 +1,91 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.macd_bb_v1 import MACDBBV1, MACDBBV1Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_executor_handler import ( + DirectionalTradingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanComposed(ScriptStrategyBase): + trading_pairs = ["HBAR-USDT", "CYBER-USDT", "ETH-USDT", "LPT-USDT", "UNFI-USDT"] + leverage_by_trading_pair = { + "HBAR-USDT": 25, + "CYBER-USDT": 20, + "ETH-USDT": 100, + "LPT-USDT": 10, + "UNFI-USDT": 20, + } + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.01"), take_profit=Decimal("0.03"), + time_limit=60 * 60 * 6, + trailing_stop_activation_price_delta=Decimal("0.008"), + trailing_stop_trailing_delta=Decimal("0.004"), + open_order_type=OrderType.MARKET + ) + + order_levels = [ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal("15"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal("15"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ] + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = MACDBBV1Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=100), + ], + leverage=leverage_by_trading_pair[trading_pair], + macd_fast=21, macd_slow=42, macd_signal=9, + bb_length=100, bb_std=2.0, bb_long_threshold=0.3, bb_short_threshold=0.7, + ) + controller = MACDBBV1(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = DirectionalTradingExecutorHandler(strategy=self, controller=controller) + + def on_stop(self): + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + if executor_handler.controller.all_candles_ready: + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/scripts/v2_market-making_dman_composed.py b/scripts/v2_market-making_dman_composed.py new file mode 100644 index 0000000..c539897 --- /dev/null +++ b/scripts/v2_market-making_dman_composed.py @@ -0,0 +1,145 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v1 import DManV1, DManV1Config +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanComposed(ScriptStrategyBase): + trading_pair = "HBAR-USDT" + triple_barrier_conf_top = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 1, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + triple_barrier_conf_bottom = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 3, + trailing_stop_activation_price_delta=Decimal("0.005"), + trailing_stop_trailing_delta=Decimal("0.001") + ) + + config_v1 = DManV1Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal("15"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("50"), + spread_factor=Decimal(5.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal("50"), + spread_factor=Decimal(8.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal("15"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("50"), + spread_factor=Decimal(5.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal("50"), + spread_factor=Decimal(8.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=25, + natr_length=21 + ) + config_v2 = DManV2Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(3.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(3.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=25, + natr_length=21, macd_fast=12, macd_slow=26, macd_signal=9 + ) + dman_v1 = DManV1(config=config_v1) + dman_v2 = DManV2(config=config_v2) + + empty_markets = {} + markets = dman_v1.update_strategy_markets_dict(empty_markets) + markets = dman_v2.update_strategy_markets_dict(markets) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.dman_v1_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v1) + self.dman_v2_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v2) + + def on_stop(self): + self.close_open_positions() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + if self.dman_v1_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v1_executor.start() + if self.dman_v2_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v2_executor.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + lines.extend(["DMAN V1", self.dman_v1_executor.to_format_status()]) + lines.extend(["\n-----------------------------------------\n"]) + lines.extend(["DMAN V2", self.dman_v2_executor.to_format_status()]) + return "\n".join(lines) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) diff --git a/scripts/v2_market-making_dman_v1_multiple_pairs.py b/scripts/v2_market-making_dman_v1_multiple_pairs.py new file mode 100644 index 0000000..24189cf --- /dev/null +++ b/scripts/v2_market-making_dman_v1_multiple_pairs.py @@ -0,0 +1,133 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v1 import DManV1, DManV1Config +from hummingbot.smart_components.strategy_frameworks.data_types import ExecutorHandlerStatus, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.smart_components.utils.distributions import Distributions +from hummingbot.smart_components.utils.order_level_builder import OrderLevelBuilder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DManV1MultiplePairs(ScriptStrategyBase): + # Account configuration + exchange = "binance_perpetual" + trading_pairs = ["ETH-USDT"] + leverage = 20 + + # Candles configuration + candles_exchange = "binance_perpetual" + candles_interval = "3m" + candles_max_records = 300 + + # Orders configuration + order_amount = Decimal("25") + n_levels = 5 + start_spread = 0.0006 + step_between_orders = 0.009 + order_refresh_time = 60 * 15 # 15 minutes + cooldown_time = 5 + + # Triple barrier configuration + stop_loss = Decimal("0.2") + take_profit = Decimal("0.06") + time_limit = 60 * 60 * 12 + trailing_stop_activation_price_delta = Decimal(str(step_between_orders / 2)) + trailing_stop_trailing_delta = Decimal(str(step_between_orders / 3)) + + # Advanced configurations + natr_length = 100 + + # Applying the configuration + order_level_builder = OrderLevelBuilder(n_levels=n_levels) + order_levels = order_level_builder.build_order_levels( + amounts=order_amount, + spreads=Distributions.arithmetic(n_levels=n_levels, start=start_spread, step=step_between_orders), + triple_barrier_confs=TripleBarrierConf( + stop_loss=stop_loss, take_profit=take_profit, time_limit=time_limit, + trailing_stop_activation_price_delta=trailing_stop_activation_price_delta, + trailing_stop_trailing_delta=trailing_stop_trailing_delta), + order_refresh_time=order_refresh_time, + cooldown_time=cooldown_time, + ) + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = DManV1Config( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=candles_exchange, trading_pair=trading_pair, + interval=candles_interval, max_records=candles_max_records), + ], + leverage=leverage, + natr_length=natr_length, + ) + controller = DManV1(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/scripts/v2_market-making_dman_v2_multiple_pairs.py b/scripts/v2_market-making_dman_v2_multiple_pairs.py new file mode 100644 index 0000000..a3d7af2 --- /dev/null +++ b/scripts/v2_market-making_dman_v2_multiple_pairs.py @@ -0,0 +1,139 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ExecutorHandlerStatus, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.smart_components.utils.distributions import Distributions +from hummingbot.smart_components.utils.order_level_builder import OrderLevelBuilder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DManV2MultiplePairs(ScriptStrategyBase): + # Account configuration + exchange = "binance_perpetual" + trading_pairs = ["ETH-USDT"] + leverage = 20 + + # Candles configuration + candles_exchange = "binance_perpetual" + candles_interval = "3m" + candles_max_records = 300 + + # Orders configuration + order_amount = Decimal("25") + n_levels = 5 + start_spread = 0.0006 + step_between_orders = 0.009 + order_refresh_time = 60 * 15 # 15 minutes + cooldown_time = 5 + + # Triple barrier configuration + stop_loss = Decimal("0.2") + take_profit = Decimal("0.06") + time_limit = 60 * 60 * 12 + trailing_stop_activation_price_delta = Decimal(str(step_between_orders / 2)) + trailing_stop_trailing_delta = Decimal(str(step_between_orders / 3)) + + # Advanced configurations + macd_fast = 12 + macd_slow = 26 + macd_signal = 9 + natr_length = 100 + + # Applying the configuration + order_level_builder = OrderLevelBuilder(n_levels=n_levels) + order_levels = order_level_builder.build_order_levels( + amounts=order_amount, + spreads=Distributions.arithmetic(n_levels=n_levels, start=start_spread, step=step_between_orders), + triple_barrier_confs=TripleBarrierConf( + stop_loss=stop_loss, take_profit=take_profit, time_limit=time_limit, + trailing_stop_activation_price_delta=trailing_stop_activation_price_delta, + trailing_stop_trailing_delta=trailing_stop_trailing_delta), + order_refresh_time=order_refresh_time, + cooldown_time=cooldown_time, + ) + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = DManV2Config( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=candles_exchange, trading_pair=trading_pair, + interval=candles_interval, max_records=candles_max_records), + ], + leverage=leverage, + macd_fast=macd_fast, + macd_slow=macd_slow, + macd_signal=macd_signal, + natr_length=natr_length, + ) + controller = DManV2(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/scripts/v2_market-making_dman_v3_multiple_pairs.py b/scripts/v2_market-making_dman_v3_multiple_pairs.py new file mode 100644 index 0000000..dd58f6c --- /dev/null +++ b/scripts/v2_market-making_dman_v3_multiple_pairs.py @@ -0,0 +1,141 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v3 import DManV3, DManV3Config +from hummingbot.smart_components.strategy_frameworks.data_types import ExecutorHandlerStatus, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.smart_components.utils.distributions import Distributions +from hummingbot.smart_components.utils.order_level_builder import OrderLevelBuilder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DManV3MultiplePairs(ScriptStrategyBase): + # Account configuration + exchange = "binance_perpetual" + trading_pairs = ["ETH-USDT"] + leverage = 20 + + # Candles configuration + candles_exchange = "binance_perpetual" + candles_interval = "1h" + candles_max_records = 300 + bollinger_band_length = 200 + bollinger_band_std = 3.0 + + # Orders configuration + order_amount = Decimal("25") + n_levels = 5 + start_spread = 0.5 # percentage of the bollinger band (0.5 means that the order will be between the bollinger mid-price and the upper band) + step_between_orders = 0.3 # percentage of the bollinger band (0.1 means that the next order will be 10% of the bollinger band away from the previous order) + + # Triple barrier configuration + stop_loss = Decimal("0.01") + take_profit = Decimal("0.03") + time_limit = 60 * 60 * 6 + trailing_stop_activation_price_delta = Decimal("0.008") + trailing_stop_trailing_delta = Decimal("0.004") + + # Advanced configurations + side_filter = True + dynamic_spread_factor = True + dynamic_target_spread = False + smart_activation = False + activation_threshold = Decimal("0.001") + + # Applying the configuration + order_level_builder = OrderLevelBuilder(n_levels=n_levels) + order_levels = order_level_builder.build_order_levels( + amounts=order_amount, + spreads=Distributions.arithmetic(n_levels=n_levels, start=start_spread, step=step_between_orders), + triple_barrier_confs=TripleBarrierConf( + stop_loss=stop_loss, take_profit=take_profit, time_limit=time_limit, + trailing_stop_activation_price_delta=trailing_stop_activation_price_delta, + trailing_stop_trailing_delta=trailing_stop_trailing_delta), + ) + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = DManV3Config( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=candles_exchange, trading_pair=trading_pair, + interval=candles_interval, max_records=candles_max_records), + ], + bb_length=bollinger_band_length, + bb_std=bollinger_band_std, + side_filter=side_filter, + dynamic_spread_factor=dynamic_spread_factor, + dynamic_target_spread=dynamic_target_spread, + smart_activation=smart_activation, + activation_threshold=activation_threshold, + leverage=leverage, + ) + controller = DManV3(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..193d216 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[nosetests] +verbosity=2 +detailed-errors=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0a82530 --- /dev/null +++ b/setup.py @@ -0,0 +1,172 @@ +import os +import subprocess +import sys +import fnmatch + +import numpy as np +from setuptools import find_packages, setup +from setuptools.command.build_ext import build_ext +from Cython.Build import cythonize + +is_posix = (os.name == "posix") + +if is_posix: + os_name = subprocess.check_output("uname").decode("utf8") + if "Darwin" in os_name: + os.environ["CFLAGS"] = "-stdlib=libc++ -std=c++11" + else: + os.environ["CFLAGS"] = "-std=c++11" + +if os.environ.get("WITHOUT_CYTHON_OPTIMIZATIONS"): + os.environ["CFLAGS"] += " -O0" + + +# Avoid a gcc warning below: +# cc1plus: warning: command line option ???-Wstrict-prototypes??? is valid +# for C/ObjC but not for C++ +class BuildExt(build_ext): + def build_extensions(self): + if os.name != "nt" and "-Wstrict-prototypes" in self.compiler.compiler_so: + self.compiler.compiler_so.remove("-Wstrict-prototypes") + super().build_extensions() + + +def main(): + cpu_count = os.cpu_count() or 8 + version = "20231127" + all_packages = find_packages(include=["hummingbot", "hummingbot.*"], ) + excluded_paths = ["hummingbot.connector.exchange.injective_v2", + "hummingbot.connector.derivative.injective_v2_perpetual", + "hummingbot.connector.gateway.clob_spot.data_sources.injective", + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual" + ] + packages = [pkg for pkg in all_packages if not any(fnmatch.fnmatch(pkg, pattern) for pattern in excluded_paths)] + package_data = { + "hummingbot": [ + "core/cpp/*", + "VERSION", + "templates/*TEMPLATE.yml" + ], + } + install_requires = [ + "bidict", + "aioconsole", + "aiohttp", + "aioprocessing", + "asyncssh", + "appdirs", + "appnope", + "async-timeout", + "base58", + "gql", + "cachetools", + "certifi", + "coincurve", + "cryptography", + "cython==3.0.0", + "cytoolz", + "commlib-py", + "docker", + "diff-cover", + "dydx-v3-python", + "eip712-structs", + "eth-abi", + "eth-account", + "eth-bloom", + "eth-keyfile", + "eth-typing", + "eth-utils", + "flake8", + "gql", + "hexbytes", + "importlib-metadata", + "injective-py", + "mypy-extensions", + "nose", + "nose-exclude", + "numpy", + "pandas", + "pip", + "pre-commit", + "prompt-toolkit", + "protobuf", + "gql", + "grpcio", + "grpcio-tools", + "psutil", + "pydantic", + "pyjwt", + "pyperclip", + "python-dateutil", + "python-telegram-bot", + "pyOpenSSL", + "requests", + "rsa", + "ruamel-yaml", + "scipy", + "signalr-client-aio", + "simplejson", + "six", + "sqlalchemy", + "tabulate", + "tzlocal", + "ujson", + "web3", + "websockets", + "yarl", + "python-telegram-bot==12.8", + "pandas_ta==0.3.14b", + ] + + cython_kwargs = { + "language": "c++", + "language_level": 3, + } + + cython_sources = ["hummingbot/**/*.pyx"] + + compiler_directives = { + "annotation_typing": False, + } + if os.environ.get("WITHOUT_CYTHON_OPTIMIZATIONS"): + compiler_directives.update({ + "optimize.use_switch": False, + "optimize.unpack_method_calls": False, + }) + + if is_posix: + cython_kwargs["nthreads"] = cpu_count + + if "DEV_MODE" in os.environ: + version += ".dev1" + package_data[""] = [ + "*.pxd", "*.pyx", "*.h" + ] + package_data["hummingbot"].append("core/cpp/*.cpp") + + if len(sys.argv) > 1 and sys.argv[1] == "build_ext" and is_posix: + sys.argv.append(f"--parallel={cpu_count}") + + setup(name="hummingbot", + version=version, + description="Hummingbot", + url="https://github.com/hummingbot/hummingbot", + author="Hummingbot Foundation", + author_email="dev@hummingbot.org", + license="Apache 2.0", + packages=packages, + package_data=package_data, + install_requires=install_requires, + ext_modules=cythonize(cython_sources, compiler_directives=compiler_directives, **cython_kwargs), + include_dirs=[ + np.get_include() + ], + scripts=[ + "bin/hummingbot_quickstart.py" + ], + cmdclass={"build_ext": BuildExt}, + ) + + +if __name__ == "__main__": + main() diff --git a/setup/environment.yml b/setup/environment.yml new file mode 100644 index 0000000..75af880 --- /dev/null +++ b/setup/environment.yml @@ -0,0 +1,67 @@ +name: hummingbot +channels: + - conda-forge + - defaults +dependencies: + - bidict + - coverage + - cython=3.0 + - grpcio-tools + - nomkl + - nose=1.3.7 + - nose-exclude + - numpy=1.23.5 + - numpy-base=1.23.5 + - pandas=1.5.3 + - pip + - prompt_toolkit=3.0.20 + - pydantic=1.10 + - pytest + - python=3.10 + - scipy=1.10.1 + - sqlalchemy=1.4 + - tabulate==0.8.9 + - ujson + - zlib + - pip: + - aiohttp==3.* + - aioprocessing==2.0 + - aioresponses + - aiounittest + - appdirs==1.4.3 + - async-timeout + - asyncssh==2.13.1 + - pyOpenSSL==21.0.0 + - appnope==0.1.3 + - base58==2.1.1 + - cachetools==4.0.0 + - commlib-py==0.10.6 + - cryptography==3.4.7 + - diff-cover + - docker==5.0.3 + - eip712-structs==1.1.0 + - dotmap==1.3.30 + - flake8==3.7.9 + - gql + - importlib-metadata==0.23 + - injective-py==0.9.* + - jsonpickle==3.0.1 + - mypy-extensions==0.4.3 + - pandas_ta==0.3.14b + - pre-commit==2.18.1 + - psutil==5.7.2 + - ptpython==3.0.20 + - pyjwt==1.7.1 + - pyperclip==1.7.0 + - python-telegram-bot==12.8 + - requests==2.* + - rsa==4.7 + - ruamel-yaml==0.16.10 + - signalr-client-aio==0.0.1.6.2 + - substrate-interface==1.6.2 + - solders==0.1.4 + - web3 + - websockets + - yarl==1.* + - git+https://github.com/CoinAlpha/python-signalr-client.git + - git+https://github.com/konichuvak/dydx-v3-python.git@web3 diff --git a/start b/start new file mode 100755 index 0000000..1fba97b --- /dev/null +++ b/start @@ -0,0 +1,60 @@ +#!/bin/bash + +PASSWORD="" +FILENAME="" + +# Argument parsing +while getopts ":p:f:" opt; do + case $opt in + p) + PASSWORD="$OPTARG" + ;; + f) + FILENAME="$OPTARG" + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done + +# Check if bin/hummingbot_quickstart.py exists +if [[ ! -f bin/hummingbot_quickstart.py ]]; then + echo "Error: bin/hummingbot_quickstart.py command not found. Make sure you are in the Hummingbot root directory" + exit 1 +fi + +# Check if the hummingbot conda environment is activated +if [[ $CONDA_DEFAULT_ENV != "hummingbot" ]]; then + echo "Error: 'hummingbot' conda environment is not activated. Please activate it and try again." + exit 1 +fi + +# Build the command to run +CMD="./bin/hummingbot_quickstart.py" +if [[ ! -z "$PASSWORD" ]]; then + CMD="$CMD -p \"$PASSWORD\"" +fi +if [[ ! -z "$FILENAME" ]]; then + CMD="$CMD -f \"$FILENAME\"" +fi + +# Clear the errors.log file first before executing +> ./logs/errors.log + +# Execute the command +eval $CMD 2>> ./logs/errors.log + +# Check errors.log for specific errors +if grep -q "Invalid password" ./logs/errors.log; then + echo "Error: Incorrect password provided." + exit 1 +elif grep -q "FileNotFoundError" ./logs/errors.log; then + echo "Error: Invalid file or filename provided." + exit 2 +fi diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/.gitignore b/test/connector/.gitignore new file mode 100644 index 0000000..dd8db5a --- /dev/null +++ b/test/connector/.gitignore @@ -0,0 +1 @@ +/*.sqlite diff --git a/test/connector/README.md b/test/connector/README.md new file mode 100644 index 0000000..5db335c --- /dev/null +++ b/test/connector/README.md @@ -0,0 +1,26 @@ +# Unit Testing + +Create a new environment called `MOCK_API_ENABLED` that switches between normal unit tests and mock tests. + +```bash +export MOCK_API_ENABLED=true +``` + +Before running the tests, make sure `conda` environment is enabled. + +```bash +conda activate hummingbot +``` + +Run nosetests from `hummingbot/test/integration` directory and add `-v` for logging to see what the tests are doing and what errors come up. + +```bash +nosetests -v test_binance_market.py +``` + +Markets that currently can run unit mock testing: + +- Binance +- Coinbase Pro +- Huobi +- KuCoin \ No newline at end of file diff --git a/test/connector/__init__.py b/test/connector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/derivative/__init__.py b/test/connector/derivative/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/derivative/binance_perpetual/__init__.py b/test/connector/derivative/binance_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/derivative/binance_perpetual/test_binance_perpetual_market.py b/test/connector/derivative/binance_perpetual/test_binance_perpetual_market.py new file mode 100644 index 0000000..409f003 --- /dev/null +++ b/test/connector/derivative/binance_perpetual/test_binance_perpetual_market.py @@ -0,0 +1,287 @@ +import asyncio +import contextlib +import logging +import time +import unittest +from decimal import Decimal +from typing import List + +import conf +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import BinancePerpetualDerivative +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class BinancePerpetualMarketUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + + market: BinancePerpetualDerivative + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls) -> None: + cls._ev_loop = asyncio.get_event_loop() + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: BinancePerpetualDerivative = BinancePerpetualDerivative( + api_key=conf.binance_perpetual_api_key, + api_secret=conf.binance_perpetual_api_secret, + trading_pairs=["ETH-USDT"] + ) + print("Initializing Binance Perpetual market... this will take about a minute.") + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.clock.add_iterator(cls.market) + cls.stack: contextlib.ExitStack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_till_ready()) + print("Market Ready.") + + @classmethod + async def wait_till_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self) -> None: + self.market_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_logger = None + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + @unittest.skip("Too Simple, Unnecessary") + def test_network_status(self): + network_status: NetworkStatus = self.ev_loop.run_until_complete(self.market.check_network()) + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @unittest.skip("") + def test_buy_and_sell_order_then_cancel_individually(self): + trading_pair = "ETH-USDT" + # Create Buy Order + buy_order_id = self.market.buy( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(300) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(buy_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(1, len(self.market.in_flight_orders)) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Create Sell Order + sell_order_id = self.market.sell( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(500) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: SellOrderCreatedEvent = order_created_event + self.assertEqual(sell_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(2, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id in self.market.in_flight_orders) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Cancel Buy Order + self.market.cancel(trading_pair, buy_order_id) + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(buy_order_id, order_cancelled_event.order_id) + self.assertEqual(1, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id in self.market.in_flight_orders) + self.assertTrue(buy_order_id not in self.market.in_flight_orders) + + # Cancel Sell Order + self.market.cancel(trading_pair, sell_order_id) + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(sell_order_id, order_cancelled_event.order_id) + self.assertEqual(0, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id not in self.market.in_flight_orders) + self.assertTrue(buy_order_id not in self.market.in_flight_orders) + + @unittest.skip("") + def test_buy_and_sell_order_then_cancel_all(self): + trading_pair = "ETH-USDT" + # Create Buy Order + buy_order_id = self.market.buy( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(300) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(buy_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(1, len(self.market.in_flight_orders)) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Create Sell Order + sell_order_id = self.market.sell( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(500) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: SellOrderCreatedEvent = order_created_event + self.assertEqual(sell_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(2, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id in self.market.in_flight_orders) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Cancel All Orders + [cancellation_results] = self.run_parallel(self.market.cancel_all(5)) + for cancel_result in cancellation_results: + self.assertEqual(cancel_result.success, True) + + self.assertEqual(0, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id not in self.market.in_flight_orders) + self.assertTrue(buy_order_id not in self.market.in_flight_orders) + + @unittest.skip("") + def test_buy_and_sell_order_then_cancel_account_orders(self): + trading_pair = "ETH-USDT" + # Create Buy Order + buy_order_id = self.market.buy( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(300) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(buy_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(1, len(self.market.in_flight_orders)) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Create Sell Order + sell_order_id = self.market.sell( + trading_pair=trading_pair, + amount=Decimal(0.01), + order_type=OrderType.LIMIT, + price=Decimal(500) + ) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: SellOrderCreatedEvent = order_created_event + self.assertEqual(sell_order_id, order_created_event.order_id) + self.assertEqual(trading_pair, order_created_event.trading_pair) + self.assertEqual(2, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id in self.market.in_flight_orders) + self.assertTrue(buy_order_id in self.market.in_flight_orders) + + # Cancel All Open Orders on Account (specified by trading pair) + self.ev_loop.run_until_complete(safe_ensure_future(self.market.cancel_all_account_orders(trading_pair))) + self.assertEqual(0, len(self.market.in_flight_orders)) + self.assertTrue(sell_order_id not in self.market.in_flight_orders) + self.assertTrue(buy_order_id not in self.market.in_flight_orders) + + @unittest.skip("") + def test_order_fill_event(self): + trading_pair = "ETH-USDT" + + amount: Decimal = Decimal(0.01) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + # Initialize Pricing (Buy) + price: Decimal = self.market.get_price(trading_pair, True) * Decimal("1.01") + quantized_price: Decimal = self.market.quantize_order_price(trading_pair, price) + + # Create Buy Order + buy_order_id = self.market.buy( + trading_pair=trading_pair, + amount=quantized_amount, + order_type=OrderType.LIMIT, + price=quantized_price + ) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + self.assertEqual(buy_order_id, order_completed_event.order_id) + self.assertEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual("ETH", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == buy_order_id + for event in self.market_logger.event_log])) + + # Initialize Pricing (Sell) + price = self.market.get_price(trading_pair, False) * Decimal("0.99") + quantized_price = self.market.quantize_order_price(trading_pair, price) + + # Create Sell Order + sell_order_id = self.market.sell( + trading_pair=trading_pair, + amount=quantized_amount, + order_type=OrderType.LIMIT, + price=quantized_price + ) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + self.assertEqual(sell_order_id, order_completed_event.order_id) + self.assertEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual("ETH", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == sell_order_id + for event in self.market_logger.event_log])) + + +def main(): + logging.getLogger("hummingbot.core.event.event_reporter").setLevel(logging.WARNING) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/derivative/binance_perpetual/test_binance_perpetual_order_book_tracker.py b/test/connector/derivative/binance_perpetual/test_binance_perpetual_order_book_tracker.py new file mode 100644 index 0000000..9c73aee --- /dev/null +++ b/test/connector/derivative/binance_perpetual/test_binance_perpetual_order_book_tracker.py @@ -0,0 +1,97 @@ +import asyncio +import logging +import unittest +from typing import Optional, List, Dict + +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_order_book_tracker import \ + BinancePerpetualOrderBookTracker +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather + + +class BinancePerpetualOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[BinancePerpetualOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USDT", + "ETH-USDT" + ] + + @classmethod + def setUpClass(cls) -> None: + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_tracker: BinancePerpetualOrderBookTracker = BinancePerpetualOrderBookTracker( + trading_pairs=cls.trading_pairs, base_url="https://testnet.binancefuture.com", stream_url="wss://stream.binancefuture.com" + ) + cls.order_book_tracker_task: asyncio.Task = safe_ensure_future(cls.order_book_tracker.start()) + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + while True: + if len(cls.order_book_tracker.order_books) > 0: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + def setUp(self) -> None: + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + @staticmethod + async def run_parallel_async(*tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + await asyncio.sleep(1) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_order_book_trade_occurs(self): + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertEqual(type(ob_trade_event), OrderBookTradeEvent) + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertEqual(type(ob_trade_event.timestamp), float) + self.assertEqual(type(ob_trade_event.amount), float) + self.assertEqual(type(ob_trade_event.price), float) + self.assertEqual(type(ob_trade_event.type), TradeType) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_adv(self): + self.ev_loop.run_until_complete(asyncio.sleep(10)) + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + btcusdt_book: OrderBook = order_books[self.trading_pairs[0]] + ethusdt_book: OrderBook = order_books[self.trading_pairs[1]] + + print("BTC-USDT SNAPSHOT: ") + print(btcusdt_book.snapshot) + print("ETH-USDT SNAPSHOT: ") + print(ethusdt_book.snapshot) + + self.assertGreaterEqual(btcusdt_book.get_price_for_volume(True, 10).result_price, + btcusdt_book.get_price(True)) + self.assertLessEqual(btcusdt_book.get_price_for_volume(False, 10).result_price, + btcusdt_book.get_price(False)) + self.assertGreaterEqual(ethusdt_book.get_price_for_volume(True, 10).result_price, + ethusdt_book.get_price(True)) + self.assertLessEqual(ethusdt_book.get_price_for_volume(False, 10).result_price, + ethusdt_book.get_price(False)) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py b/test/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py new file mode 100644 index 0000000..0db1eec --- /dev/null +++ b/test/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py @@ -0,0 +1,32 @@ +import unittest +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_utils as utils + + +class TradingPairUtilsTest(unittest.TestCase): + def test_parse_three_letters_base_and_three_letters_quote(self): + parsed_pair = utils.convert_from_exchange_trading_pair("BTCUSD") + self.assertEqual(parsed_pair, "BTC-USD") + + def test_parse_three_letters_base_and_four_letters_quote(self): + parsed_pair = utils.convert_from_exchange_trading_pair("BTCUSDT") + self.assertEqual(parsed_pair, "BTC-USDT") + + def test_parse_three_letters_base_and_three_letters_quote_matching_with_a_four_letters_quote_candidate(self): + parsed_pair = utils.convert_from_exchange_trading_pair("VETUSD") + self.assertEqual(parsed_pair, "VET-USD") + + def test_convert_to_exchange_format_three_letters_base_and_three_letters_quote(self): + converted_pair = utils.convert_to_exchange_trading_pair("BTC-USD") + self.assertEqual(converted_pair, "BTCUSD") + + def test_convert_to_exchange_format_three_letters_base_and_four_letters_quote(self): + converted_pair = utils.convert_to_exchange_trading_pair("BTC-USDT") + self.assertEqual(converted_pair, "BTCUSDT") + + def test_convert_to_exchange_format_three_letters_base_and_three_letters_quote_matching_with_a_four_letters_quote_candidate(self): + converted_pair = utils.convert_to_exchange_trading_pair("VET-USD") + self.assertEqual(converted_pair, "VETUSD") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/connector/exchange/__init__.py b/test/connector/exchange/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/ascend_ex/.gitignore b/test/connector/exchange/ascend_ex/.gitignore new file mode 100644 index 0000000..23d9952 --- /dev/null +++ b/test/connector/exchange/ascend_ex/.gitignore @@ -0,0 +1 @@ +backups \ No newline at end of file diff --git a/test/connector/exchange/ascend_ex/__init__.py b/test/connector/exchange/ascend_ex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/ascend_ex/test_ascend_ex_auth.py b/test/connector/exchange/ascend_ex/test_ascend_ex_auth.py new file mode 100644 index 0000000..effa247 --- /dev/null +++ b/test/connector/exchange/ascend_ex/test_ascend_ex_auth.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +import sys +import asyncio +import unittest +import aiohttp +import ujson +import websockets +import conf +import logging +from os.path import join, realpath +from typing import Dict, Any +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import REST_URL +from hummingbot.connector.exchange.ascend_ex.ascend_ex_utils import get_ws_url_private + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class TestAscendExAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + api_key = conf.ascend_ex_api_key + secret_key = conf.ascend_ex_secret_key + cls.auth = AscendExAuth(api_key, secret_key) + + async def rest_auth(self) -> Dict[Any, Any]: + headers = { + **self.auth.get_headers(), + **self.auth.get_auth_headers("info"), + } + response = await aiohttp.ClientSession().get(f"{REST_URL}/info", headers=headers) + return await response.json() + + async def ws_auth(self) -> Dict[Any, Any]: + info = await self.rest_auth() + accountGroup = info.get("data").get("accountGroup") + headers = self.auth.get_auth_headers("stream") + ws = await websockets.connect(f"{get_ws_url_private(accountGroup)}/stream", extra_headers=headers) + + raw_msg = await asyncio.wait_for(ws.recv(), 5000) + msg = ujson.loads(raw_msg) + + return msg + + def test_rest_auth(self): + result = self.ev_loop.run_until_complete(self.rest_auth()) + assert result["code"] == 0 + + def test_ws_auth(self): + result = self.ev_loop.run_until_complete(self.ws_auth()) + assert result["m"] == "connected" + assert result["type"] == "auth" diff --git a/test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py b/test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py new file mode 100644 index 0000000..4ee9aac --- /dev/null +++ b/test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py @@ -0,0 +1,432 @@ +import asyncio +import contextlib +import logging +import math +import os +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import List + +import conf +from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import safe_gather, safe_ensure_future +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill + +logging.basicConfig(level=METRICS_LOG_LEVEL) + +API_KEY = conf.ascend_ex_api_key +API_SECRET = conf.ascend_ex_secret_key + + +class AscendExExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + connector: AscendExExchange + event_logger: EventLogger + trading_pair = "ZIL-USDT" + base_token, quote_token = trading_pair.split("-") + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + + cls.ev_loop = asyncio.get_event_loop() + + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.connector: AscendExExchange = AscendExExchange( + ascend_ex_api_key=API_KEY, + ascend_ex_secret_key=API_SECRET, + trading_pairs=[cls.trading_pair], + trading_required=True + ) + print("Initializing AscendEx exchange... this will take about a minute.") + cls.clock.add_iterator(cls.connector) + cls.stack: contextlib.ExitStack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.connector.start(cls._clock, time.time()) + cls.ev_loop.create_task(cls.connector.start_network()) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls, connector = None): + if connector is None: + connector = cls.connector + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if connector.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../connector_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.event_logger = EventLogger() + for event_tag in self.events: + self.connector.add_listener(event_tag, self.event_logger) + + def tearDown(self): + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + self.event_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def _place_order(self, is_buy, amount, order_type, price, ex_order_id) -> str: + if is_buy: + cl_order_id = self.connector.buy(self.trading_pair, amount, order_type, price) + else: + cl_order_id = self.connector.sell(self.trading_pair, amount, order_type, price) + return cl_order_id + + def _cancel_order(self, cl_order_id): + self.connector.cancel(self.trading_pair, cl_order_id) + + def test_estimate_fee(self): + maker_fee = self.connector.estimate_fee_pct(True) + self.assertAlmostEqual(maker_fee, Decimal("0.001")) + taker_fee = self.connector.estimate_fee_pct(False) + self.assertAlmostEqual(taker_fee, Decimal("0.001")) + + def test_create_order_failure(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.10") + base_bal = self.connector.get_available_balance(self.base_token) + amount = base_bal + order_id_1 = self._place_order(False, amount, OrderType.LIMIT, price, 1) + order_id_2 = self._place_order(False, amount, OrderType.LIMIT, price, 1) + order_failed_event: MarketOrderFailureEvent = self.ev_loop.run_until_complete(asyncio.wait_for( + self.event_logger.wait_for(MarketOrderFailureEvent), 3.0)) + self.assertIn(order_failed_event.order_id, (order_id_1, order_id_2)) + + def test_buy_and_sell(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.05") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + quote_bal = self.connector.get_available_balance(self.quote_token) + base_bal = self.connector.get_available_balance(self.base_token) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(2)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual(self.base_token, order_completed_event.base_asset) + self.assertEqual(self.quote_token, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.event_logger.event_log])) + + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + expected_quote_bal = quote_bal - quote_amount_traded + # self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 1) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.95") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual(self.base_token, order_completed_event.base_asset) + self.assertEqual(self.quote_token, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertGreater(order_completed_event.fee_amount, Decimal(0)) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.event_logger.event_log])) + + # check available base balance gets updated, we need to wait a bit for the balance message to arrive + expected_base_bal = base_bal + # self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.assertAlmostEqual(expected_base_bal, self.connector.get_available_balance(self.base_token), 5) + + def test_limit_makers_unfilled(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + quote_bal = self.connector.get_available_balance(self.quote_token) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + expected_quote_bal = quote_bal - (price * amount) + self.ev_loop.run_until_complete(self.connector._update_balances()) + # self.ev_loop.run_until_complete(asyncio.sleep(2)) + + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 1) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + + cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + # # @TODO: find a way to create "rejected" + # def test_limit_maker_rejections(self): + # price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + # price = self.connector.get_price(self.trading_pair, False) * Decimal("0.8") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + def test_cancel_all(self): + bid_price = self.connector.get_price(self.trading_pair, True) + ask_price = self.connector.get_price(self.trading_pair, False) + bid_price = self.connector.quantize_order_price(self.trading_pair, bid_price * Decimal("0.9")) + ask_price = self.connector.quantize_order_price(self.trading_pair, ask_price * Decimal("1.1")) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + + buy_id = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + sell_id = self._place_order(False, amount, OrderType.LIMIT, ask_price, 2) + + self.ev_loop.run_until_complete(asyncio.sleep(1)) + asyncio.ensure_future(self.connector.cancel_all(3)) + self.ev_loop.run_until_complete(asyncio.sleep(3)) + cancel_events = [t for t in self.event_logger.event_log if isinstance(t, OrderCancelledEvent)] + self.assertEqual({buy_id, sell_id}, {o.order_id for o in cancel_events}) + + def test_order_quantized_values(self): + bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + ask_price: Decimal = self.connector.get_price(self.trading_pair, False) + mid_price: Decimal = (bid_price + ask_price) / 2 + zil_balance = self.connector.get_balance("ZIL") + usdt_balance = self.connector.get_balance("USDT") + + # Make sure there's enough balance to make the limit orders. + self.assertGreater(zil_balance, Decimal("90")) + self.assertGreater(usdt_balance, Decimal("10")) + + # Intentionally set some prices with too many decimal places s.t. they + # need to be quantized. Also, place them far away from the mid-price s.t. they won't + # get filled during the test. + bid_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("0.9333192292111341")) + ask_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("1.1492431474884933")) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + + # Test bid order + cl_order_id_1 = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + # Wait for the order created event and examine the order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + + # Test ask order + cl_order_id_2 = self._place_order(False, amount, OrderType.LIMIT, ask_price, 1) + # Wait for the order created event and examine and order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + + self._cancel_order(cl_order_id_1) + self._cancel_order(cl_order_id_2) + + def test_orders_saving_and_restoration(self): + config_path = "test_config" + strategy_name = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + self.connector._in_flight_orders.clear() + self.assertEqual(0, len(self.connector.tracking_states)) + + # Try to put limit buy order for 0.02 ETH worth of ZRX, and watch for order creation event. + current_bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + price: Decimal = current_bid_price * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.connector.tracking_states)) + self.assertEqual(cl_order_id, list(self.connector.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.connector) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(cl_order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.connector) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.connector.stop(self._clock) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + self.clock.remove_iterator(self.connector) + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + new_connector = AscendExExchange(API_KEY, API_SECRET, [self.trading_pair], True) + for event_tag in self.events: + new_connector.add_listener(event_tag, self.event_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [new_connector], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.clock.add_iterator(new_connector) + self.ev_loop.run_until_complete(self.wait_til_ready(new_connector)) + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + new_connector.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(new_connector.limit_orders)) + self.assertEqual(1, len(new_connector.tracking_states)) + + # Cancel the order and verify that the change is saved. + self._cancel_order(cl_order_id) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + order_id = None + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, cl_order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.connector.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + def test_filled_orders_recorded(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + # Try to buy some token from the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.05") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent, timeout_seconds=10)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.95") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("90")) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertGreaterEqual(len(trade_fills), 2) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertGreaterEqual(len(buy_fills), 1) + self.assertGreaterEqual(len(sell_fills), 1) + + order_id = None + + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) diff --git a/test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py b/test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py new file mode 100644 index 0000000..2ecfb8b --- /dev/null +++ b/test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py @@ -0,0 +1,104 @@ +import asyncio +import logging +import math +import time +import unittest +from typing import Dict, List, Optional + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS +from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source import AscendExAPIOrderBookDataSource +from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book_tracker import AscendExOrderBookTracker +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class AscendExOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[AscendExOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USDT", + "ETH-USDT", + ] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + cls.order_book_tracker: AscendExOrderBookTracker = AscendExOrderBookTracker(cls.throttler, cls.trading_pairs) + cls.order_book_tracker.start() + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + while True: + if len(cls.order_book_tracker.order_books) > 0: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + async def run_parallel_async(self, *tasks, timeout=None): + future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks)) + timer = 0 + while not future.done(): + if timeout and timer > timeout: + raise Exception("Timeout running parallel async tasks in tests") + timer += 1 + now = time.time() + _next_iteration = now // 1.0 + 1 # noqa: F841 + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def setUp(self): + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + def test_order_book_trade_event_emission(self): + """ + Tests if the order book tracker is able to retrieve order book trade message from exchange and emit order book + trade events after correctly parsing the trade messages + """ + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertTrue(type(ob_trade_event) == OrderBookTradeEvent) + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertTrue(type(ob_trade_event.timestamp) in [float, int]) + self.assertTrue(type(ob_trade_event.amount) == float) + self.assertTrue(type(ob_trade_event.price) == float) + self.assertTrue(type(ob_trade_event.type) == TradeType) + # datetime is in milliseconds + self.assertTrue(math.ceil(math.log10(ob_trade_event.timestamp)) == 13) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_integrity(self): + # Wait 5 seconds to process some diffs. + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + eth_usdt: OrderBook = order_books["ETH-USDT"] + self.assertIsNot(eth_usdt.last_diff_uid, 0) + self.assertGreaterEqual(eth_usdt.get_price_for_volume(True, 10).result_price, + eth_usdt.get_price(True)) + self.assertLessEqual(eth_usdt.get_price_for_volume(False, 10).result_price, + eth_usdt.get_price(False)) + + def test_api_get_last_traded_prices(self): + prices = self.ev_loop.run_until_complete( + AscendExAPIOrderBookDataSource.get_last_traded_prices(["BTC-USDT", "LTC-BTC"])) + for key, value in prices.items(): + print(f"{key} last_trade_price: {value}") + self.assertGreater(prices["BTC-USDT"], 1000) + self.assertLess(prices["LTC-BTC"], 1) diff --git a/test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py b/test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py new file mode 100644 index 0000000..04130bc --- /dev/null +++ b/test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +import sys +import asyncio +import logging +import unittest +import conf + +from os.path import join, realpath +from hummingbot.connector.exchange.ascend_ex.ascend_ex_user_stream_tracker import AscendExUserStreamTracker +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS + + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class AscendExUserStreamTrackerUnitTest(unittest.TestCase): + api_key = conf.ascend_ex_api_key + api_secret = conf.ascend_ex_secret_key + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + cls.ascend_ex_auth = AscendExAuth(cls.api_key, cls.api_secret) + cls.trading_pairs = ["BTC-USDT"] + cls.user_stream_tracker: AscendExUserStreamTracker = AscendExUserStreamTracker( + cls.throttler, ascend_ex_auth=cls.ascend_ex_auth, trading_pairs=cls.trading_pairs + ) + cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start()) + + def test_user_stream(self): + # Wait process some msgs. + self.ev_loop.run_until_complete(asyncio.sleep(120.0)) + print(self.user_stream_tracker.user_stream) diff --git a/test/connector/exchange/bitfinex/__init__.py b/test/connector/exchange/bitfinex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py b/test/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py new file mode 100644 index 0000000..afa29fe --- /dev/null +++ b/test/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +import aiohttp +from os.path import ( + join, + realpath +) +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +import asyncio +import logging +import unittest +from typing import List + +from hummingbot.connector.exchange.bitfinex.bitfinex_api_order_book_data_source import ( + BitfinexAPIOrderBookDataSource, +) + + +class BitfinexAPIOrderBookDataSourceUnitTest(unittest.TestCase): + trading_pairs: List[str] = [ + "BTC-USD", + ] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.data_source: BitfinexAPIOrderBookDataSource = BitfinexAPIOrderBookDataSource( + trading_pairs=cls.trading_pairs + ) + + def test_get_trading_pairs(self): + result: List[str] = self.ev_loop.run_until_complete( + self.data_source.get_trading_pairs()) + + self.assertIsInstance(result, list) + self.assertGreater(len(result), 0) + self.assertIsInstance(result[0], str) + self.assertEqual(result[0], "BTC-USD") + + def test_size_snapshot(self): + async def run_session_for_fetch_snaphot(): + async with aiohttp.ClientSession() as client: + result = await self.data_source.get_snapshot(client, "BTC-USD") + assert len(result["bids"]) == self.data_source.SNAPSHOT_LIMIT_SIZE + assert len(result["asks"]) == self.data_source.SNAPSHOT_LIMIT_SIZE + + # 25 is default fetch value, that is very small for use in production + assert len(result["bids"]) > 25 + assert len(result["asks"]) > 25 + + self.ev_loop.run_until_complete(run_session_for_fetch_snaphot()) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/bitfinex/test_bitfinex_auth.py b/test/connector/exchange/bitfinex/test_bitfinex_auth.py new file mode 100644 index 0000000..5618c1b --- /dev/null +++ b/test/connector/exchange/bitfinex/test_bitfinex_auth.py @@ -0,0 +1,32 @@ +import asyncio +import json +import unittest +from typing import List + +import websockets + +import conf +from hummingbot.connector.exchange.bitfinex import BITFINEX_WS_AUTH_URI +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth + + +class TestAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + + api_key = conf.bitfinex_api_key + secret_key = conf.bitfinex_secret_key + cls.auth = BitfinexAuth(api_key, secret_key) + + async def con_auth(self): + async with websockets.connect(BITFINEX_WS_AUTH_URI) as ws: + ws: websockets.WebSocketClientProtocol = ws + payload = self.auth.generate_auth_payload('AUTH{nonce}'.format(nonce=self.auth.get_nonce())) + await ws.send(json.dumps(payload)) + msg = await asyncio.wait_for(ws.recv(), timeout=30) # response + return msg + + def test_auth(self): + result: List[str] = self.ev_loop.run_until_complete(self.con_auth()) + assert "serverId" in result diff --git a/test/connector/exchange/bitfinex/test_bitfinex_market.py b/test/connector/exchange/bitfinex/test_bitfinex_market.py new file mode 100644 index 0000000..0a285a6 --- /dev/null +++ b/test/connector/exchange/bitfinex/test_bitfinex_market.py @@ -0,0 +1,373 @@ +import asyncio +import contextlib +import logging +import os +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import ( + List, + Optional, +) + +import conf +from hummingbot.connector.exchange.bitfinex.bitfinex_exchange import BitfinexExchange +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import SQLConnectionManager, SQLConnectionType + +logging.basicConfig(level=METRICS_LOG_LEVEL) +API_KEY = conf.bitfinex_api_key +API_SECRET = conf.bitfinex_secret_key +trading_pair = "ETH-USD" +base_asset = trading_pair.split("-")[0] +quote_asset = trading_pair.split("-")[1] + + +class BitfinexExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.WithdrawAsset, + MarketEvent.OrderFilled, + MarketEvent.OrderCancelled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + ] + + market: BitfinexExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: BitfinexExchange = BitfinexExchange( + API_KEY, + API_SECRET, + trading_pairs=[trading_pair] + ) + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.clock.add_iterator(cls.market) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../bitfinex_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self.clock.run_til(next_iteration) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_get_fee(self): + limit_fee: AddedToCostTradeFee = self.market.get_fee(base_asset, quote_asset, OrderType.LIMIT, + TradeType.BUY, 1, 1) + self.assertGreater(limit_fee.percent, 0) + self.assertEqual(len(limit_fee.flat_fees), 0) + market_fee: AddedToCostTradeFee = self.market.get_fee(base_asset, quote_asset, OrderType.MARKET, + TradeType.BUY, 1) + self.assertGreater(market_fee.percent, 0) + self.assertEqual(len(market_fee.flat_fees), 0) + + def test_minimum_order_size(self): + amount = Decimal("0.001") + quantized_amount = self.market.quantize_order_amount(trading_pair, amount) + self.assertEqual(quantized_amount, 0) + + def test_get_balance(self): + balance = self.market.get_balance(quote_asset) + self.assertGreater(balance, 10) + + def test_limit_buy(self): + amount: Decimal = Decimal("0.04") + current_ask_price: Decimal = self.market.get_price(trading_pair, False) + # no fill + bid_price: Decimal = Decimal("0.9") * current_ask_price + quantize_ask_price: Decimal = self.market.quantize_order_price( + trading_pair, + bid_price + ) + + order_id = self.market.buy( + trading_pair, + amount, + OrderType.LIMIT, + quantize_ask_price + ) + + # Wait for order creation event + self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + + # Cancel order. Automatically asserts that order is tracked + self.market.cancel(trading_pair, order_id) + + [order_cancelled_event] = self.run_parallel( + self.market_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(order_cancelled_event.order_id, order_id) + # # Reset the logs + self.market_logger.clear() + + def test_limit_sell(self): + amount: Decimal = Decimal("0.02") + current_ask_price: Decimal = self.market.get_price(trading_pair, False) + # for no fill + ask_price: Decimal = Decimal("1.1") * current_ask_price + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, + ask_price) + + order_id = self.market.sell(trading_pair, amount, OrderType.LIMIT, + quantize_ask_price) + # Wait for order creation event + self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + + # Cancel order. Automatically asserts that order is tracked + self.market.cancel(trading_pair, order_id) + + [order_cancelled_event] = self.run_parallel( + self.market_logger.wait_for(OrderCancelledEvent)) + + self.assertEqual(order_cancelled_event.order_id, order_id) + + # Reset the logs + self.market_logger.clear() + + def test_execute_limit_buy(self): + amount: Decimal = Decimal("0.04") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, + amount) + + bid_entries = self.market.order_books[trading_pair].bid_entries() + + most_top_bid = next(bid_entries) + bid_price: Decimal = Decimal(most_top_bid.price) + quantize_bid_price: Decimal = \ + self.market.quantize_order_price(trading_pair, bid_price) + quantize_bid_price = quantize_bid_price * Decimal("1.1") + + order_id = self.market.buy(trading_pair, + quantized_amount, + OrderType.LIMIT, + quantize_bid_price, + ) + + [order_completed_event] = self.run_parallel( + self.market_logger.wait_for(BuyOrderCompletedEvent)) + order_completed_event: BuyOrderCompletedEvent = order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, + order_completed_event.base_asset_amount) + self.assertEqual(base_asset, order_completed_event.base_asset) + self.assertEqual(quote_asset, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, + order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, + order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_execute_limit_sell(self): + amount: Decimal = Decimal(0.02) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, + amount) + ask_entries = self.market.order_books[trading_pair].ask_entries() + most_top_ask = next(ask_entries) + ask_price: Decimal = Decimal(most_top_ask.price) + quantize_ask_price: Decimal = \ + self.market.quantize_order_price(trading_pair, ask_price) + quantize_ask_price = quantize_ask_price * Decimal("0.9") + + order_id = self.market.sell(trading_pair, + quantized_amount, + OrderType.LIMIT, + quantize_ask_price, + ) + [order_completed_event] = self.run_parallel( + self.market_logger.wait_for(SellOrderCompletedEvent)) + + order_completed_event: SellOrderCompletedEvent = order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, + order_completed_event.base_asset_amount) + self.assertEqual(base_asset, order_completed_event.base_asset) + self.assertEqual(quote_asset, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, + order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, + order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_orders_saving_and_restoration(self): + self.tearDownClass() + self.setUpClass() + self.setUp() + + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + self.assertEqual(0, len(self.market.tracking_states)) + + amount: Decimal = Decimal("0.04") + current_ask_price: Decimal = self.market.get_price(trading_pair, False) + bid_price: Decimal = Decimal("0.9") * current_ask_price + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + order_id = self.market.buy(trading_pair, amount, OrderType.LIMIT, quantize_ask_price) + + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.market.tracking_states)) + self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.clock.remove_iterator(self.market) + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market: BitfinexExchange = BitfinexExchange( + API_KEY, + API_SECRET, + trading_pairs=[trading_pair] + ) + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, self.market) + self.clock.add_iterator(self.market) + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + self.market.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(self.market.limit_orders)) + self.assertEqual(1, len(self.market.tracking_states)) + + # Cancel the order and verify that the change is saved. + self.run_parallel(asyncio.sleep(5.0)) + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_id = None + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, self.market) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + self.setUpClass() + + def test_cancel_all(self): + bid_price: Decimal = self.market.get_price(trading_pair, True) + ask_price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal("0.04") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + # Intentionally setting invalid price to prevent getting filled + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price * Decimal("0.9")) + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, ask_price * Decimal("1.1")) + + self.market.buy(trading_pair, quantized_amount, OrderType.LIMIT, quantize_bid_price) + self.market.sell(trading_pair, quantized_amount, OrderType.LIMIT, quantize_ask_price) + self.run_parallel(asyncio.sleep(5)) + [cancellation_results] = self.run_parallel(self.market.cancel_all(45)) + for cr in cancellation_results: + self.assertEqual(cr.success, True) diff --git a/test/connector/exchange/bitfinex/test_bitfinex_order_book_tracker.py b/test/connector/exchange/bitfinex/test_bitfinex_order_book_tracker.py new file mode 100644 index 0000000..5ea356a --- /dev/null +++ b/test/connector/exchange/bitfinex/test_bitfinex_order_book_tracker.py @@ -0,0 +1,158 @@ +import asyncio +import logging +import math +import sys +import time +import unittest +from typing import Dict, List, Optional + +from hummingbot.connector.exchange.bitfinex.bitfinex_order_book_tracker import BitfinexOrderBookTracker +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.core.utils.async_utils import safe_ensure_future + + +class BitfinexOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[BitfinexOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USD", + ] + integrity_test_max_volume = 5 # Max volume in asks and bids for the book to be ready for tests + daily_volume = 2500 # Approximate total daily volume in BTC for this exchange for sanity test + book_enties = 5 # Number of asks and bids (each) for the book to be ready for tests + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_tracker: BitfinexOrderBookTracker = BitfinexOrderBookTracker(trading_pairs=cls.trading_pairs) + cls.order_book_tracker_task: asyncio.Task = safe_ensure_future(cls.order_book_tracker.start()) + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + ''' + Wait until the order book under test fills as needed + ''' + print("Waiting for order book to fill...") + while True: + book_present = cls.trading_pairs[0] in cls.order_book_tracker.order_books + enough_asks = False + enough_bids = False + enough_ask_rows = False + enough_bid_rows = False + if book_present: + ask_volume = sum(i.amount for i in cls.order_book_tracker.order_books[cls.trading_pairs[0]].ask_entries()) + ask_count = sum(1 for i in cls.order_book_tracker.order_books[cls.trading_pairs[0]].ask_entries()) + + bid_volume = sum(i.amount for i in cls.order_book_tracker.order_books[cls.trading_pairs[0]].bid_entries()) + bid_count = sum(1 for i in cls.order_book_tracker.order_books[cls.trading_pairs[0]].bid_entries()) + + enough_asks = ask_volume >= cls.integrity_test_max_volume + enough_bids = bid_volume >= cls.integrity_test_max_volume + + enough_ask_rows = ask_count >= cls.book_enties + enough_bid_rows = bid_count >= cls.book_enties + + print("Bid volume in book: %f (in %d bids), ask volume in book: %f (in %d asks)" % (bid_volume, bid_count, ask_volume, ask_count)) + + if book_present and enough_asks and enough_bids and enough_ask_rows and enough_bid_rows: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + async def run_parallel_async(self, *tasks, timeout=None): + future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks)) + timer = 0 + while not future.done(): + if timeout and timer > timeout: + raise Exception("Timeout running parallel async tasks in tests") + timer += 1 + now = time.time() + _next_iteration = now // 1.0 + 1 # noqa: F841 + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def setUp(self): + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + def test_order_book_trade_event_emission(self): + """2 + Tests if the order book tracker is able to retrieve order book trade message from exchange + and emit order book trade events after correctly parsing the trade messages + """ + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertTrue(isinstance(ob_trade_event, OrderBookTradeEvent)) + self.assertTrue(isinstance(ob_trade_event.timestamp, (int, float))) + self.assertTrue(isinstance(ob_trade_event.amount, float)) + self.assertTrue(isinstance(ob_trade_event.price, float)) + self.assertTrue(isinstance(ob_trade_event.type, TradeType)) + + self.assertTrue(math.ceil(math.log10(ob_trade_event.timestamp)) == 10) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_integrity(self): + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + sut_book: OrderBook = order_books[self.trading_pairs[0]] + + # # 1 - test that best bid is less than best ask + # self.assertGreater(sut_book.get_price(False), sut_book.get_price(True)) + + # 2 - test that price to buy integrity_test_max_volume BTC is is greater than or equal to best ask + self.assertGreaterEqual(sut_book.get_price_for_volume(True, self.integrity_test_max_volume).result_price, + sut_book.get_price(True)) + + # 3 - test that price to sell integrity_test_max_volume BTC is is less than or equal to best bid + self.assertLessEqual(sut_book.get_price_for_volume(False, self.integrity_test_max_volume).result_price, + sut_book.get_price(False)) + + # 4 - test that all bids in order book are sorted by price in descending order + previous_price = sys.float_info.max + for bid_row in sut_book.bid_entries(): + self.assertTrue(previous_price >= bid_row.price) + previous_price = bid_row.price + + # 5 - test that all asks in order book are sorted by price in ascending order + previous_price = 0 + for ask_row in sut_book.ask_entries(): + self.assertTrue(previous_price <= ask_row.price) + previous_price = ask_row.price + + # 6 - test that total volume in first orders in book is less than 10 times + # daily traded volumes for this exchange + total_volume = 0 + count = 0 + for bid_row in sut_book.bid_entries(): + total_volume += bid_row.amount + count += 1 + if count > self.book_enties: + break + count = 0 + for ask_row in sut_book.ask_entries(): + total_volume += ask_row.amount + count += 1 + if count > self.book_enties: + break + self.assertLessEqual(total_volume, 10 * self.daily_volume) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/bitfinex/test_bitfinex_user_steam_tracker.py b/test/connector/exchange/bitfinex/test_bitfinex_user_steam_tracker.py new file mode 100644 index 0000000..47917ca --- /dev/null +++ b/test/connector/exchange/bitfinex/test_bitfinex_user_steam_tracker.py @@ -0,0 +1,25 @@ +import asyncio +import unittest +import conf + +from hummingbot.connector.exchange.bitfinex.bitfinex_auth import BitfinexAuth +from hummingbot.connector.exchange.bitfinex.bitfinex_user_stream_tracker import \ + BitfinexUserStreamTracker + + +class BitfinexUserStreamTrackerUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.bitfinex_auth = BitfinexAuth(conf.bitfinex_api_key, + conf.bitfinex_secret_key) + cls.trading_pair = ["ETHUSD"] # Using V3 convention since OrderBook is built using V3 + cls.user_stream_tracker: BitfinexUserStreamTracker = BitfinexUserStreamTracker( + bitfinex_auth=cls.bitfinex_auth, trading_pairs=cls.trading_pair) + cls.user_stream_tracker_task: asyncio.Task = asyncio.ensure_future( + cls.user_stream_tracker.start()) + + def test_user_stream(self): + # Wait process some msgs. + self.ev_loop.run_until_complete(asyncio.sleep(120.0)) + assert self.user_stream_tracker.user_stream.qsize() > 0 diff --git a/test/connector/exchange/coinbase_pro/__init__.py b/test/connector/exchange/coinbase_pro/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/coinbase_pro/fixture_coinbase_pro.py b/test/connector/exchange/coinbase_pro/fixture_coinbase_pro.py new file mode 100644 index 0000000..40b84ed --- /dev/null +++ b/test/connector/exchange/coinbase_pro/fixture_coinbase_pro.py @@ -0,0 +1,81 @@ +class FixtureCoinbasePro: + BALANCES = [ + { + "id": "2d36cb78-5145-41fe-90b0-3f204f2e357d", "currency": "USDC", "balance": "90.1480261500000000", + "available": "90.14802615", "hold": "0.0000000000000000", + "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", "trading_enabled": True + }, + { + "id": "d3356a99-ad27-4b1b-92f8-26233be0d62a", "currency": "ETH", "balance": "0.4424257124965000", + "available": "0.4424257124965", "hold": "0.0000000000000000", + "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", "trading_enabled": True + } + ] + + TRADE_FEES = {"maker_fee_rate": "0.0050", "taker_fee_rate": "0.0050", "usd_volume": "462.93"} + + ORDERS_STATUS = [] + + OPEN_BUY_LIMIT_ORDER = { + "id": "4aa4773e-ca4e-4146-8ac1-a0ec8c39f835", "price": "278.05000000", "size": "0.02000000", + "product_id": "ETH-USDC", "side": "buy", "stp": "dc", "type": "limit", "time_in_force": "GTC", + "post_only": False, "created_at": "2020-02-14T06:52:32.167853Z", "fill_fees": "0", + "filled_size": "0", "executed_value": "0", "status": "pending", "settled": False} + + OPEN_SELL_LIMIT_ORDER = { + "id": "9087815a-3d3d-4c2c-b627-78fd83d8644e", "price": "787.44000000", "size": "0.07000000", + "product_id": "ETH-USDC", "side": "sell", "stp": "dc", "type": "limit", "time_in_force": "GTC", + "post_only": False, "created_at": "2020-02-14T07:57:31.842502Z", "fill_fees": "0", + "filled_size": "0", "executed_value": "0", "status": "pending", "settled": False} + + WS_AFTER_BUY_2 = { + "type": "done", "side": "buy", "product_id": "ETH-USDC", "time": "2020-02-14T06:52:32.172333Z", + "sequence": 544313348, "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", + "user_id": "5dc62091b2d9e604842cad56", "order_id": "4aa4773e-ca4e-4146-8ac1-a0ec8c39f835", + "reason": "filled", "price": "278.05", "remaining_size": "0"} + + BUY_MARKET_ORDER = { + "id": "dedfcd66-2324-4805-bd31-b8920c3a25b4", "size": "0.02000000", "product_id": "ETH-USDC", + "side": "buy", "stp": "dc", "funds": "84.33950263", "type": "market", "post_only": False, + "created_at": "2020-02-14T07:21:17.166831Z", "fill_fees": "0", "filled_size": "0", + "executed_value": "0", "status": "pending", "settled": False} + + SELL_MARKET_ORDER = { + "id": "CBS_MARKET_SELL", "size": "0.02000000", "product_id": "ETH-USDC", + "side": "sell", "stp": "dc", "funds": "84.33950263", "type": "market", "post_only": False, + "created_at": "2020-02-14T07:21:17.166831Z", "fill_fees": "0", "filled_size": "0", + "executed_value": "0", "status": "pending", "settled": False} + + WS_AFTER_MARKET_BUY_2 = { + "type": "done", "side": "buy", "product_id": "ETH-USDC", "time": "2020-02-14T07:21:17.171949Z", + "sequence": 544350029, "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", + "user_id": "5dc62091b2d9e604842cad56", "order_id": "dedfcd66-2324-4805-bd31-b8920c3a25b4", + "reason": "filled", "remaining_size": "0"} + + WS_ORDER_OPEN = { + "type": "open", "side": "buy", "product_id": "ETH-USDC", "time": "2020-02-14T07:41:45.174224Z", + "sequence": 544392466, "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", + "user_id": "5dc62091b2d9e604842cad56", "price": "235.67", + "order_id": "9d8c39b0-094f-4832-9a73-9b2e43b03780", "remaining_size": "0.02"} + + WS_ORDER_CANCELED = { + "type": "done", "side": "buy", "product_id": "ETH-USDC", + "time": "2020-02-14T07:41:45.450940Z", "sequence": 544392470, + "profile_id": "bc2f3a64-0c0b-49ce-bb3e-5efc978b5b5c", "user_id": "5dc62091b2d9e604842cad56", + "order_id": "9d8c39b0-094f-4832-9a73-9b2e43b03780", "reason": "canceled", "price": "235.67", + "remaining_size": "0.02"} + + COINBASE_ACCOUNTS_GET = [ + { + "id": "8543f030-4a21-58d6-ba63-3446e74a01fe", "name": "ETH Wallet", "balance": "0.00000000", + "currency": "ETH", + "type": "wallet", "primary": False, "active": True, "available_on_consumer": True, "hold_balance": "0.00", + "hold_currency": "PHP" + }, + { + "id": "414f0c91-8490-5790-bb6e-6007c7686267", "name": "USDC Wallet", "balance": "0.000000", + "currency": "USDC", + "type": "wallet", "primary": False, "active": True, "available_on_consumer": True, "hold_balance": "0.00", + "hold_currency": "PHP" + }, + ] diff --git a/test/connector/exchange/coinbase_pro/test_coinbase_pro_active_order_tracker.py b/test/connector/exchange/coinbase_pro/test_coinbase_pro_active_order_tracker.py new file mode 100644 index 0000000..854a3b4 --- /dev/null +++ b/test/connector/exchange/coinbase_pro/test_coinbase_pro_active_order_tracker.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python + +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +import logging +import unittest +from typing import ( + Any, + Dict, + Optional +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_tracker import CoinbaseProOrderBookTracker +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker import OrderBookTrackerDataSourceType +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_message import CoinbaseProOrderBookMessage +from hummingbot.core.data_type.order_book_row import OrderBookRow + +test_trading_pair = "BTC-USD" + + +class CoinbaseProOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[CoinbaseProOrderBookTracker] = None + + @classmethod + def setUpClass(cls): + cls.order_book_tracker: CoinbaseProOrderBookTracker = CoinbaseProOrderBookTracker( + OrderBookTrackerDataSourceType.EXCHANGE_API, + trading_pairs=[test_trading_pair]) + + def test_diff_message_not_found(self): + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + test_order_book: OrderBook = order_books[test_trading_pair] + test_active_order_tracker = self.order_book_tracker._active_order_trackers[test_trading_pair] + + # receive match message that is not in active orders (should be ignored) + match_msg_to_ignore: Dict[str, Any] = { + "type": "match", + "trade_id": 10, + "sequence": 50, + "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "size": "5.23512", + "price": "400.23", + "side": "sell" + } + ignore_msg: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(match_msg_to_ignore) + open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(ignore_msg) + self.assertEqual(open_ob_row, ([], [])) + + def test_buy_diff_message(self): + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + test_order_book: OrderBook = order_books[test_trading_pair] + test_active_order_tracker = self.order_book_tracker._active_order_trackers[test_trading_pair] + + # receive open buy message to be added to active orders + order_id = "abc" + side = "buy" + price = 1337.0 + open_size = 100.0 + open_sequence = 1 + open_message_dict: Dict[str, Any] = { + "type": "open", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "sequence": open_sequence, + "order_id": order_id, + "price": str(price), + "remaining_size": str(open_size), + "side": side + } + open_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(open_message_dict) + open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(open_message) + self.assertEqual(open_ob_row[0], [OrderBookRow(price, open_size, open_sequence)]) + + # receive change message + change_size = 50.0 + change_sequence = 2 + change_message_dict: Dict[str, Any] = { + "type": "change", + "time": "2014-11-07T08:19:27.028459Z", + "sequence": change_sequence, + "order_id": order_id, + "product_id": test_trading_pair, + "new_size": str(change_size), + "old_size": "100.0", + "price": str(price), + "side": side + } + change_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(change_message_dict) + + change_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(change_message) + self.assertEqual(change_ob_row[0], [OrderBookRow(price, change_size, change_sequence)]) + + # receive match message + match_size = 30.0 + match_sequence = 3 + match_message_dict: Dict[str, Any] = { + "type": "match", + "trade_id": 10, + "sequence": match_sequence, + "maker_order_id": order_id, + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "size": str(match_size), + "price": str(price), + "side": side + } + match_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(match_message_dict) + + match_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(match_message) + self.assertEqual(match_ob_row[0], [OrderBookRow(price, change_size - match_size, match_sequence)]) + + # receive done message + done_size = 0.0 + done_sequence = 4 + done_message_dict: Dict[str, Any] = { + "type": "done", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "sequence": done_sequence, + "price": str(price), + "order_id": order_id, + "reason": "filled", + "side": side, + "remaining_size": "0" + } + done_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(done_message_dict) + + done_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(done_message) + self.assertEqual(done_ob_row[0], [OrderBookRow(price, done_size, done_sequence)]) + + def test_sell_diff_message(self): + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + test_order_book: OrderBook = order_books[test_trading_pair] + test_active_order_tracker = self.order_book_tracker._active_order_trackers[test_trading_pair] + + # receive open sell message to be added to active orders + order_id = "abc" + side = "sell" + price = 1337.0 + open_size = 100.0 + open_sequence = 1 + open_message_dict: Dict[str, Any] = { + "type": "open", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "sequence": open_sequence, + "order_id": order_id, + "price": str(price), + "remaining_size": str(open_size), + "side": side + } + open_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(open_message_dict) + open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(open_message) + self.assertEqual(open_ob_row[1], [OrderBookRow(price, open_size, open_sequence)]) + + # receive open sell message to be added to active orders + order_id_2 = "def" + side = "sell" + price = 1337.0 + open_size_2 = 100.0 + open_sequence_2 = 2 + open_message_dict_2: Dict[str, Any] = { + "type": "open", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "sequence": open_sequence_2, + "order_id": order_id_2, + "price": str(price), + "remaining_size": str(open_size), + "side": side + } + open_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(open_message_dict_2) + open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(open_message) + self.assertEqual(open_ob_row[1], [OrderBookRow(price, open_size + open_size_2, open_sequence_2)]) + + # receive change message + change_size = 50.0 + change_sequence = 3 + change_message_dict: Dict[str, Any] = { + "type": "change", + "time": "2014-11-07T08:19:27.028459Z", + "sequence": change_sequence, + "order_id": order_id, + "product_id": test_trading_pair, + "new_size": str(change_size), + "old_size": "100.0", + "price": str(price), + "side": side + } + change_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(change_message_dict) + + change_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(change_message) + self.assertEqual(change_ob_row[1], [OrderBookRow(price, change_size + open_size_2, change_sequence)]) + + # receive match message + match_size = 30.0 + match_sequence = 4 + match_message_dict: Dict[str, Any] = { + "type": "match", + "trade_id": 10, + "sequence": match_sequence, + "maker_order_id": order_id, + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "size": str(match_size), + "price": str(price), + "side": side + } + match_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(match_message_dict) + + match_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(match_message) + self.assertEqual(match_ob_row[1], [OrderBookRow(price, change_size - match_size + open_size_2, match_sequence)]) + + # receive done message + done_size = 0.0 + done_sequence = 5 + done_message_dict: Dict[str, Any] = { + "type": "done", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": test_trading_pair, + "sequence": done_sequence, + "price": str(price), + "order_id": order_id, + "reason": "filled", + "side": side, + "remaining_size": "0" + } + done_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange(done_message_dict) + + done_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(done_message) + self.assertEqual(done_ob_row[1], [OrderBookRow(price, done_size + open_size_2, done_sequence)]) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/coinbase_pro/test_coinbase_pro_market.py b/test/connector/exchange/coinbase_pro/test_coinbase_pro_market.py new file mode 100644 index 0000000..f49eff1 --- /dev/null +++ b/test/connector/exchange/coinbase_pro/test_coinbase_pro_market.py @@ -0,0 +1,563 @@ +import asyncio +import contextlib +import logging +import math +import os +import sys +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import ( + List, + Optional +) +from unittest import mock + +import conf +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_exchange import CoinbaseProExchange +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.mock_api.mock_web_server import MockWebServer +from hummingbot.core.mock_api.mock_web_socket_server import MockWebSocketServerFactory +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill +from test.connector.exchange.coinbase_pro.fixture_coinbase_pro import FixtureCoinbasePro + +# API_SECRET length must be multiple of 4 otherwise base64.b64decode will fail +API_MOCK_ENABLED = conf.mock_api_enabled is not None and conf.mock_api_enabled.lower() in ['true', 'yes', '1'] +API_KEY = "XXXX" if API_MOCK_ENABLED else conf.coinbase_pro_api_key +API_SECRET = "YYYY" if API_MOCK_ENABLED else conf.coinbase_pro_secret_key +API_PASSPHRASE = "ZZZZ" if API_MOCK_ENABLED else conf.coinbase_pro_passphrase +API_BASE_URL = "api.pro.coinbase.com" +WS_BASE_URL = "wss://ws-feed.pro.coinbase.com" + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class CoinbaseProExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.OrderCancelled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + + market: CoinbaseProExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + cls.ev_loop = asyncio.get_event_loop() + trading_pair = "ETH-USDC" + if API_MOCK_ENABLED: + cls.web_app = MockWebServer.get_instance() + cls.web_app.add_host_to_mock(API_BASE_URL, ["/time", "/products", f"/products/{trading_pair}/book"]) + cls.web_app.start() + cls.ev_loop.run_until_complete(cls.web_app.wait_til_started()) + cls._patcher = mock.patch("aiohttp.client.URL") + cls._url_mock = cls._patcher.start() + cls._url_mock.side_effect = cls.web_app.reroute_local + cls.web_app.update_response("get", API_BASE_URL, "/accounts", FixtureCoinbasePro.BALANCES) + cls.web_app.update_response("get", API_BASE_URL, "/fees", FixtureCoinbasePro.TRADE_FEES) + cls.web_app.update_response("get", API_BASE_URL, "/orders", FixtureCoinbasePro.ORDERS_STATUS) + + MockWebSocketServerFactory.start_new_server(WS_BASE_URL) + cls._ws_patcher = unittest.mock.patch("websockets.connect", autospec=True) + cls._ws_mock = cls._ws_patcher.start() + cls._ws_mock.side_effect = MockWebSocketServerFactory.reroute_ws_connect + + cls._t_nonce_patcher = unittest.mock.patch( + "hummingbot.connector.exchange.coinbase_pro.coinbase_pro_exchange.get_tracking_nonce") + cls._t_nonce_mock = cls._t_nonce_patcher.start() + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: CoinbaseProExchange = CoinbaseProExchange( + API_KEY, + API_SECRET, + API_PASSPHRASE, + trading_pairs=[trading_pair] + ) + print("Initializing Coinbase Pro market... this will take about a minute.") + cls.clock.add_iterator(cls.market) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + if API_MOCK_ENABLED: + cls.web_app.stop() + cls._patcher.stop() + cls._t_nonce_patcher.stop() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../coinbase_pro_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self.clock.run_til(next_iteration) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_get_fee(self): + limit_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDC", OrderType.LIMIT_MAKER, TradeType.BUY, 1, 1) + self.assertGreater(limit_fee.percent, 0) + self.assertEqual(len(limit_fee.flat_fees), 0) + market_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDC", OrderType.LIMIT, TradeType.BUY, 1) + self.assertGreater(market_fee.percent, 0) + self.assertEqual(len(market_fee.flat_fees), 0) + + def test_fee_overrides_config(self): + fee_overrides_config_map["coinbase_pro_taker_fee"].value = None + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.005"), taker_fee.percent) + fee_overrides_config_map["coinbase_pro_taker_fee"].value = Decimal('0.2') + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.002"), taker_fee.percent) + fee_overrides_config_map["coinbase_pro_maker_fee"].value = None + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.005"), maker_fee.percent) + fee_overrides_config_map["coinbase_pro_maker_fee"].value = Decimal('0.75') + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.0075"), maker_fee.percent) + + def place_order(self, is_buy, trading_pair, amount, order_type, price, nonce, fixture_resp, fixture_ws): + order_id, exch_order_id = None, None + if API_MOCK_ENABLED: + self._t_nonce_mock.return_value = nonce + side = 'buy' if is_buy else 'sell' + resp = fixture_resp.copy() + exch_order_id = resp["id"] + resp["side"] = side + self.web_app.update_response("post", API_BASE_URL, "/orders", resp) + if is_buy: + order_id = self.market.buy(trading_pair, amount, order_type, price) + else: + order_id = self.market.sell(trading_pair, amount, order_type, price) + if API_MOCK_ENABLED: + resp = fixture_ws.copy() + resp["order_id"] = exch_order_id + resp["side"] = side + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1) + return order_id, exch_order_id + + def cancel_order(self, trading_pair, order_id, exchange_order_id, fixture_ws): + if API_MOCK_ENABLED: + self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exchange_order_id}", exchange_order_id) + self.market.cancel(trading_pair, order_id) + if API_MOCK_ENABLED: + resp = fixture_ws.copy() + resp["order_id"] = exchange_order_id + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1) + + def test_limit_maker_rejections(self): + if API_MOCK_ENABLED: + return + trading_pair = "ETH-USDC" + + # Try to put a buy limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('1.02') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal("0.02")) + order_id = self.market.buy(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + self.market_logger.clear() + + # Try to put a sell limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('0.98') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal("0.02")) + + order_id = self.market.sell(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + def test_limit_makers_unfilled(self): + if API_MOCK_ENABLED: + return + trading_pair = "ETH-USDC" + bid_price: Decimal = self.market.get_price(trading_pair, True) * Decimal("0.5") + ask_price: Decimal = self.market.get_price(trading_pair, False) * 2 + amount: Decimal = 10 / bid_price + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + # Intentionally setting invalid price to prevent getting filled + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price * Decimal("0.7")) + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, ask_price * Decimal("1.5")) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, + 10001, FixtureCoinbasePro.OPEN_BUY_LIMIT_ORDER, + FixtureCoinbasePro.WS_ORDER_OPEN) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + order_id_2, exch_order_id_2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_ask_price, 10002, + FixtureCoinbasePro.OPEN_SELL_LIMIT_ORDER, + FixtureCoinbasePro.WS_ORDER_OPEN) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id_2, order_created_event.order_id) + + self.run_parallel(asyncio.sleep(1)) + + if API_MOCK_ENABLED: + self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id}", exch_order_id) + self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id_2}", exch_order_id_2) + [cancellation_results] = self.run_parallel(self.market.cancel_all(5)) + if API_MOCK_ENABLED: + resp = FixtureCoinbasePro.WS_ORDER_CANCELED.copy() + resp["order_id"] = exch_order_id + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1) + resp = FixtureCoinbasePro.WS_ORDER_CANCELED.copy() + resp["order_id"] = exch_order_id_2 + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.11) + for cr in cancellation_results: + self.assertEqual(cr.success, True) + + # NOTE that orders of non-USD pairs (including USDC pairs) are LIMIT only + def test_limit_taker_buy(self): + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.1")) + trading_pair = "ETH-USDC" + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, _ = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT, price, 10001, + FixtureCoinbasePro.BUY_MARKET_ORDER, FixtureCoinbasePro.WS_AFTER_MARKET_BUY_2) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + order_completed_event: BuyOrderCompletedEvent = order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT_MAKER for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual("ETH", order_completed_event.base_asset) + self.assertEqual("USDC", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + # NOTE that orders of non-USD pairs (including USDC pairs) are LIMIT only + def test_limit_taker_sell(self): + trading_pair = "ETH-USDC" + price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, _ = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT, price, 10001, + FixtureCoinbasePro.BUY_MARKET_ORDER, FixtureCoinbasePro.WS_AFTER_MARKET_BUY_2) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + order_completed_event: SellOrderCompletedEvent = order_completed_event + trade_events = [t for t in self.market_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT_MAKER for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual("ETH", order_completed_event.base_asset) + self.assertEqual("USDC", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_cancel_order(self): + trading_pair = "ETH-USDC" + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.2") + self.assertGreater(self.market.get_balance("ETH"), amount) + + bid_price: Decimal = current_bid_price - Decimal("0.1") * current_bid_price + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, 10001, FixtureCoinbasePro.OPEN_BUY_LIMIT_ORDER, + FixtureCoinbasePro.WS_ORDER_OPEN) + + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureCoinbasePro.WS_ORDER_CANCELED) + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(order_cancelled_event.order_id, order_id) + + def test_cancel_all(self): + trading_pair = "ETH-USDC" + bid_price: Decimal = self.market.get_price(trading_pair, True) * Decimal("0.5") + ask_price: Decimal = self.market.get_price(trading_pair, False) * 2 + amount: Decimal = 10 / bid_price + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + # Intentionally setting invalid price to prevent getting filled + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price * Decimal("0.7")) + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, ask_price * Decimal("1.5")) + + _, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, quantize_bid_price, + 10001, FixtureCoinbasePro.OPEN_BUY_LIMIT_ORDER, FixtureCoinbasePro.WS_ORDER_OPEN) + _, exch_order_id_2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_ask_price, 10002, FixtureCoinbasePro.OPEN_SELL_LIMIT_ORDER, + FixtureCoinbasePro.WS_ORDER_OPEN) + self.run_parallel(asyncio.sleep(1)) + + if API_MOCK_ENABLED: + self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id}", exch_order_id) + self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id_2}", exch_order_id_2) + [cancellation_results] = self.run_parallel(self.market.cancel_all(5)) + if API_MOCK_ENABLED: + resp = FixtureCoinbasePro.WS_ORDER_CANCELED.copy() + resp["order_id"] = exch_order_id + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1) + resp = FixtureCoinbasePro.WS_ORDER_CANCELED.copy() + resp["order_id"] = exch_order_id_2 + MockWebSocketServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.11) + for cr in cancellation_results: + self.assertEqual(cr.success, True) + + @unittest.skipUnless(any("test_list_orders" in arg for arg in sys.argv), "List order test requires manual action.") + def test_list_orders(self): + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.1")) + trading_pair = "ETH-USDC" + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price + Decimal("0.05") * current_bid_price + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + self.market.buy(trading_pair, quantized_amount, OrderType.LIMIT_MAKER, quantize_bid_price) + self.run_parallel(asyncio.sleep(1)) + [order_details] = self.run_parallel(self.market.list_orders()) + self.assertGreaterEqual(len(order_details), 1) + + self.market_logger.clear() + + def test_orders_saving_and_restoration(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDC" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + self.assertEqual(0, len(self.market.tracking_states)) + + # Try to put limit buy order for 0.04 ETH, and watch for order creation event. + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price * Decimal("0.8") + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, 10001, FixtureCoinbasePro.OPEN_BUY_LIMIT_ORDER, + FixtureCoinbasePro.WS_ORDER_OPEN) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.market.tracking_states)) + self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.clock.remove_iterator(self.market) + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market: CoinbaseProExchange = CoinbaseProExchange( + coinbase_pro_api_key=API_KEY, + coinbase_pro_secret_key=API_SECRET, + coinbase_pro_passphrase=API_PASSPHRASE, + trading_pairs=["ETH-USDC"] + ) + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, self.market) + self.clock.add_iterator(self.market) + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + self.market.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(self.market.limit_orders)) + self.assertEqual(1, len(self.market.tracking_states)) + + # Cancel the order and verify that the change is saved. + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureCoinbasePro.WS_ORDER_CANCELED) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_id = None + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, self.market) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureCoinbasePro.WS_ORDER_CANCELED) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.market.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + print(order_book.last_trade_price) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + def test_order_fill_record(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDC" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + # Try to buy 0.04 ETH from the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.02") + order_id, exch_order_id = self.place_order(True, trading_pair, amount, OrderType.LIMIT, price, + 10001, FixtureCoinbasePro.BUY_MARKET_ORDER, + FixtureCoinbasePro.WS_AFTER_MARKET_BUY_2) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + # Reset the logs + self.market_logger.clear() + + # Try to sell back the same amount of ETH to the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, False) + amount = buy_order_completed_event.base_asset_amount + order_id, exch_order_id = self.place_order(False, trading_pair, amount, OrderType.LIMIT, price, + 10002, FixtureCoinbasePro.SELL_MARKET_ORDER, + FixtureCoinbasePro.WS_AFTER_MARKET_BUY_2) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertEqual(2, len(trade_fills)) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertEqual(1, len(buy_fills)) + self.assertEqual(1, len(sell_fills)) + + order_id = None + + finally: + if order_id is not None: + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureCoinbasePro.WS_ORDER_CANCELED) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/connector/exchange/coinbase_pro/test_coinbase_pro_order_book_tracker.py b/test/connector/exchange/coinbase_pro/test_coinbase_pro_order_book_tracker.py new file mode 100644 index 0000000..ea98aa0 --- /dev/null +++ b/test/connector/exchange/coinbase_pro/test_coinbase_pro_order_book_tracker.py @@ -0,0 +1,217 @@ +import asyncio +import logging +import math +import unittest +from datetime import datetime +from decimal import Decimal +from typing import ( + Dict, + Optional, + List, +) + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_order_book_data_source import \ + CoinbaseProAPIOrderBookDataSource +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book import CoinbaseProOrderBook +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_tracker import CoinbaseProOrderBookTracker +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_tracker import OrderBookTrackerDataSource +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + OrderBookEvent, + OrderBookTradeEvent, +) +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) + + +class CoinbaseProOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[CoinbaseProOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USD" + ] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_tracker: CoinbaseProOrderBookTracker = CoinbaseProOrderBookTracker( + trading_pairs=cls.trading_pairs) + cls.order_book_tracker_task: asyncio.Task = safe_ensure_future(cls.order_book_tracker.start()) + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + await cls.order_book_tracker._order_books_initialized.wait() + # while True: + # if len(cls.order_book_tracker.order_books) > 0: + # print("Initialized real-time order books.") + # return + # await asyncio.sleep(1) + + async def run_parallel_async(self, *tasks, timeout=None): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + timer = 0 + while not future.done(): + if timeout and timer > timeout: + raise Exception("Time out running parallel async task in tests.") + timer += 1 + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def setUp(self): + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + def test_order_book_trade_event_emission(self): + """ + Test if order book tracker is able to retrieve order book trade message from exchange and + emit order book trade events after correctly parsing the trade messages + """ + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertTrue(type(ob_trade_event) == OrderBookTradeEvent) + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertTrue(type(ob_trade_event.timestamp) == float) + self.assertTrue(type(ob_trade_event.amount) == float) + self.assertTrue(type(ob_trade_event.price) == float) + self.assertTrue(type(ob_trade_event.type) == TradeType) + self.assertTrue(math.ceil(math.log10(ob_trade_event.timestamp)) == 10) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_integrity(self): + # Wait 5 seconds to process some diffs. + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + test_order_book: OrderBook = order_books["BTC-USD"] + # print("test_order_book") + # print(test_order_book.snapshot) + self.assertGreaterEqual(test_order_book.get_price_for_volume(True, 10).result_price, + test_order_book.get_price(True)) + self.assertLessEqual(test_order_book.get_price_for_volume(False, 10).result_price, + test_order_book.get_price(False)) + + test_active_order_tracker = self.order_book_tracker._active_order_trackers["BTC-USD"] + self.assertTrue(len(test_active_order_tracker.active_asks) > 0) + self.assertTrue(len(test_active_order_tracker.active_bids) > 0) + for order_book in self.order_book_tracker.order_books.values(): + # print(order_book.last_trade_price) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + def test_order_book_data_source(self): + self.assertTrue(isinstance(self.order_book_tracker.data_source, OrderBookTrackerDataSource)) + + def test_diff_msg_get_added_to_order_book(self): + test_active_order_tracker = self.order_book_tracker._active_order_trackers["BTC-USD"] + + price = "200" + order_id = "test_order_id" + product_id = "BTC-USD" + remaining_size = "1.00" + + # Test open message diff + raw_open_message = { + "type": "open", + "time": datetime.now().isoformat(), + "product_id": product_id, + "sequence": 20000000000, + "order_id": order_id, + "price": price, + "remaining_size": remaining_size, + "side": "buy" + } + open_message = CoinbaseProOrderBook.diff_message_from_exchange(raw_open_message) + self.order_book_tracker._order_book_diff_stream.put_nowait(open_message) + self.run_parallel(asyncio.sleep(5)) + + test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] + self.assertEqual(test_order_book_row[order_id]["remaining_size"], remaining_size) + + # Test change message diff + new_size = "2.00" + raw_change_message = { + "type": "change", + "time": datetime.now().isoformat(), + "product_id": product_id, + "sequence": 20000000001, + "order_id": order_id, + "price": price, + "new_size": new_size, + "old_size": remaining_size, + "side": "buy", + } + change_message = CoinbaseProOrderBook.diff_message_from_exchange(raw_change_message) + self.order_book_tracker._order_book_diff_stream.put_nowait(change_message) + self.run_parallel(asyncio.sleep(5)) + + test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] + self.assertEqual(test_order_book_row[order_id]["remaining_size"], new_size) + + # Test match message diff + match_size = "0.50" + raw_match_message = { + "type": "match", + "trade_id": 10, + "sequence": 20000000002, + "maker_order_id": order_id, + "taker_order_id": "test_order_id_2", + "time": datetime.now().isoformat(), + "product_id": "BTC-USD", + "size": match_size, + "price": price, + "side": "buy" + } + match_message = CoinbaseProOrderBook.diff_message_from_exchange(raw_match_message) + self.order_book_tracker._order_book_diff_stream.put_nowait(match_message) + self.run_parallel(asyncio.sleep(5)) + + test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] + self.assertEqual(Decimal(test_order_book_row[order_id]["remaining_size"]), + Decimal(new_size) - Decimal(match_size)) + + # Test done message diff + raw_done_message = { + "type": "done", + "time": datetime.now().isoformat(), + "product_id": "BTC-USD", + "sequence": 20000000003, + "price": price, + "order_id": order_id, + "reason": "filled", + "remaining_size": 0, + "side": "buy", + } + done_message = CoinbaseProOrderBook.diff_message_from_exchange(raw_done_message) + self.order_book_tracker._order_book_diff_stream.put_nowait(done_message) + self.run_parallel(asyncio.sleep(5)) + + test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] + self.assertTrue(order_id not in test_order_book_row) + + def test_api_get_last_traded_prices(self): + prices = self.ev_loop.run_until_complete( + CoinbaseProAPIOrderBookDataSource.get_last_traded_prices(["BTC-USD", "LTC-USD"])) + for key, value in prices.items(): + print(f"{key} last_trade_price: {value}") + self.assertGreater(prices["BTC-USD"], 1000) + self.assertLess(prices["LTC-USD"], 1000) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/coinbase_pro/test_coinbase_pro_user_stream_tracker.py b/test/connector/exchange/coinbase_pro/test_coinbase_pro_user_stream_tracker.py new file mode 100644 index 0000000..38a0ad4 --- /dev/null +++ b/test/connector/exchange/coinbase_pro/test_coinbase_pro_user_stream_tracker.py @@ -0,0 +1,181 @@ +import asyncio +import contextlib +import logging +import time +import unittest +from decimal import Decimal +from typing import Optional + +import conf +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_exchange import CoinbaseProAuth, CoinbaseProExchange +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_message import CoinbaseProOrderBookMessage +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_user_stream_tracker import CoinbaseProUserStreamTracker +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) + + +class CoinbaseProUserStreamTrackerUnitTest(unittest.TestCase): + user_stream_tracker: Optional[CoinbaseProUserStreamTracker] = None + + market: CoinbaseProExchange + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.coinbase_pro_auth = CoinbaseProAuth(conf.coinbase_pro_api_key, + conf.coinbase_pro_secret_key, + conf.coinbase_pro_passphrase) + cls.trading_pairs = ["ETH-USDC"] + cls.user_stream_tracker: CoinbaseProUserStreamTracker = CoinbaseProUserStreamTracker( + coinbase_pro_auth=cls.coinbase_pro_auth, trading_pairs=cls.trading_pairs) + cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start()) + + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: CoinbaseProExchange = CoinbaseProExchange( + conf.coinbase_pro_api_key, + conf.coinbase_pro_secret_key, + conf.coinbase_pro_passphrase, + trading_pairs=cls.trading_pairs + ) + print("Initializing Coinbase Pro market... this will take about a minute.") + cls.clock.add_iterator(cls.market) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self.clock.run_til(next_iteration) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_limit_order_cancelled(self): + """ + This test should be run after the developer has implemented the limit buy and cancel + in the corresponding market class + """ + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.1")) + trading_pair = self.trading_pairs[0] + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price * Decimal("0.8") + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + client_order_id = self.market.buy(trading_pair, quantized_amount, OrderType.LIMIT, quantize_bid_price) + + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + [open_message] = self.run_parallel(self.user_stream_tracker.user_stream.get()) + + # print(open_message) + self.assertTrue(isinstance(open_message, CoinbaseProOrderBookMessage)) + self.assertEqual(open_message.trading_pair, trading_pair) + self.assertEqual(open_message.content["type"], "open") + self.assertEqual(open_message.content["side"], "buy") + self.assertEqual(open_message.content["product_id"], trading_pair) + self.assertEqual(Decimal(open_message.content["price"]), quantize_bid_price) + self.assertEqual(Decimal(open_message.content["remaining_size"]), quantized_amount) + + self.run_parallel(asyncio.sleep(5.0)) + self.market.cancel(trading_pair, client_order_id) + + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + [done_message] = self.run_parallel(self.user_stream_tracker.user_stream.get()) + + # print(done_message) + self.assertEqual(done_message.trading_pair, trading_pair) + self.assertEqual(done_message.content["type"], "done") + self.assertEqual(done_message.content["side"], "buy") + self.assertEqual(done_message.content["product_id"], trading_pair) + self.assertEqual(Decimal(done_message.content["price"]), quantize_bid_price) + self.assertEqual(Decimal(done_message.content["remaining_size"]), quantized_amount) + self.assertEqual(done_message.content["reason"], "canceled") + + @unittest.skip + def test_limit_order_filled(self): + """ + This test should be run after the developer has implemented the limit buy in the corresponding market class + """ + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.1")) + trading_pair = self.trading_pairs[0] + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price * Decimal("1.05") + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + self.market.buy(trading_pair, quantized_amount, OrderType.LIMIT, quantize_bid_price) + + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + [message_1, message_2] = self.run_parallel(self.user_stream_tracker.user_stream.get(), + self.user_stream_tracker.user_stream.get()) + self.assertTrue(isinstance(message_1, CoinbaseProOrderBookMessage)) + self.assertTrue(isinstance(message_2, CoinbaseProOrderBookMessage)) + if message_1.content["type"] == "done": + done_message = message_1 + match_message = message_2 + else: + done_message = message_2 + match_message = message_1 + + # print(done_message) + self.assertEqual(done_message.trading_pair, trading_pair) + self.assertEqual(done_message.content["type"], "done") + self.assertEqual(done_message.content["side"], "buy") + self.assertEqual(done_message.content["product_id"], trading_pair) + self.assertEqual(Decimal(done_message.content["price"]), quantize_bid_price) + self.assertEqual(Decimal(done_message.content["remaining_size"]), Decimal(0.0)) + self.assertEqual(done_message.content["reason"], "filled") + + # print(match_message) + self.assertEqual(match_message.trading_pair, trading_pair) + self.assertEqual(match_message.content["type"], "match") + self.assertEqual(match_message.content["side"], "sell") + self.assertEqual(match_message.content["product_id"], trading_pair) + self.assertLessEqual(Decimal(match_message.content["price"]), quantize_bid_price) + self.assertEqual(Decimal(match_message.content["size"]), quantized_amount) + + @unittest.skip + def test_user_stream_manually(self): + """ + This test should be run before market functions like buy and sell are implemented. + Developer needs to manually trigger those actions in order for the messages to show up in the user stream. + """ + self.ev_loop.run_until_complete(asyncio.sleep(30.0)) + print(self.user_stream_tracker.user_stream) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/gate_io/.gitignore b/test/connector/exchange/gate_io/.gitignore new file mode 100644 index 0000000..23d9952 --- /dev/null +++ b/test/connector/exchange/gate_io/.gitignore @@ -0,0 +1 @@ +backups \ No newline at end of file diff --git a/test/connector/exchange/gate_io/__init__.py b/test/connector/exchange/gate_io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/gate_io/test_gate_io_auth.py b/test/connector/exchange/gate_io/test_gate_io_auth.py new file mode 100644 index 0000000..b9acfce --- /dev/null +++ b/test/connector/exchange/gate_io/test_gate_io_auth.py @@ -0,0 +1,84 @@ +import asyncio +import logging +import sys +import unittest +from os.path import join, realpath +from typing import Any, Dict + +import aiohttp +import ujson + +import conf +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.connector.exchange.gate_io.gate_io_utils import build_gate_io_api_factory, rest_response_with_errors +from hummingbot.connector.exchange.gate_io.gate_io_websocket import GateIoWebsocket +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class TestAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + api_key = conf.gate_io_api_key + secret_key = conf.gate_io_secret_key + cls.auth = GateIoAuth(api_key, secret_key) + + async def rest_auth(self) -> Dict[Any, Any]: + endpoint = CONSTANTS.USER_BALANCES_PATH_URL + headers = self.auth.get_headers("GET", f"{CONSTANTS.REST_URL_AUTH}/{endpoint}", None) + http_client = aiohttp.ClientSession() + response = await http_client.get(f"{CONSTANTS.REST_URL}/{endpoint}", headers=headers) + await http_client.close() + return await response.json() + + async def rest_auth_post(self) -> Dict[Any, Any]: + endpoint = CONSTANTS.ORDER_CREATE_PATH_URL + http_client = aiohttp.ClientSession() + order_params = ujson.dumps({ + 'currency_pair': 'ETH_BTC', + 'type': 'limit', + 'side': 'buy', + 'amount': '0.00000001', + 'price': '0.0000001', + }) + headers = self.auth.get_headers("POST", f"{CONSTANTS.REST_URL_AUTH}/{endpoint}", order_params) + http_status, response, request_errors = await rest_response_with_errors( + http_client.request( + method='POST', url=f"{CONSTANTS.REST_URL}/{endpoint}", headers=headers, data=order_params + ) + ) + await http_client.close() + return response + + async def ws_auth(self) -> Dict[Any, Any]: + ws = GateIoWebsocket(api_factory=build_gate_io_api_factory( + throttler=AsyncThrottler(CONSTANTS.RATE_LIMITS)), + auth=self.auth) + await ws.connect() + await ws.subscribe(CONSTANTS.USER_BALANCE_ENDPOINT_NAME) + async for response in ws.on_message(): + if ws.is_subscribed: + return True + return False + + def test_rest_auth(self): + result = self.ev_loop.run_until_complete(self.rest_auth()) + if len(result) == 0 or "currency" not in result[0].keys(): + print(f"Unexpected response for API call: {result}") + assert "currency" in result[0].keys() + + def test_rest_auth_post(self): + result = self.ev_loop.run_until_complete(self.rest_auth_post()) + if "message" not in result.keys(): + print(f"Unexpected response for API call: {result}") + assert "message" in result.keys() + assert "Your order size 0.00000001 is too small" in result['message'] + + def test_ws_auth(self): + response = self.ev_loop.run_until_complete(self.ws_auth()) + assert response is True diff --git a/test/connector/exchange/gate_io/test_gate_io_exchange.py b/test/connector/exchange/gate_io/test_gate_io_exchange.py new file mode 100644 index 0000000..c167886 --- /dev/null +++ b/test/connector/exchange/gate_io/test_gate_io_exchange.py @@ -0,0 +1,436 @@ +import asyncio +import contextlib +import logging +import math +import os +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import List + +import conf +from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import safe_gather, safe_ensure_future +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill + +logging.basicConfig(level=METRICS_LOG_LEVEL) + +API_KEY = conf.gate_io_api_key +API_SECRET = conf.gate_io_secret_key + + +class GateIoExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + connector: GateIoExchange + event_logger: EventLogger + trading_pair = "BTC-USDT" + base_token, quote_token = trading_pair.split("-") + order_amount = Decimal("0.0001") + order_amount_quant = Decimal("0.000123456") + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + global MAINNET_RPC_URL + + cls.ev_loop = asyncio.get_event_loop() + + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.connector: GateIoExchange = GateIoExchange( + gate_io_api_key=API_KEY, + gate_io_secret_key=API_SECRET, + trading_pairs=[cls.trading_pair], + trading_required=True + ) + print("Initializing Gate.Io market... this will take about a minute.") + cls.clock.add_iterator(cls.connector) + cls.stack: contextlib.ExitStack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls, connector = None): + if connector is None: + connector = cls.connector + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if connector.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../connector_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.event_logger = EventLogger() + for event_tag in self.events: + self.connector.add_listener(event_tag, self.event_logger) + + def tearDown(self): + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + self.event_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def _place_order(self, is_buy, amount, order_type, price, ex_order_id) -> str: + if is_buy: + cl_order_id = self.connector.buy(self.trading_pair, amount, order_type, price) + else: + cl_order_id = self.connector.sell(self.trading_pair, amount, order_type, price) + return cl_order_id + + def _cancel_order(self, cl_order_id, connector=None): + if connector is None: + connector = self.connector + return connector.cancel(self.trading_pair, cl_order_id) + + def test_estimate_fee(self): + maker_fee = self.connector.estimate_fee_pct(True) + self.assertAlmostEqual(maker_fee, Decimal("0.001")) + taker_fee = self.connector.estimate_fee_pct(False) + self.assertAlmostEqual(taker_fee, Decimal("0.0025")) + + def test_buy_and_sell(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.02") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + quote_bal = self.connector.get_available_balance(self.quote_token) + base_bal = self.connector.get_available_balance(self.base_token) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual("BTC", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and str(event.order_id) == str(order_id) + for event in self.event_logger.event_log])) + + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + expected_quote_bal = quote_bal - quote_amount_traded + # self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 1) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.98") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual("BTC", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertGreater(order_completed_event.fee_amount, Decimal(0)) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.event_logger.event_log])) + + # check available base balance gets updated, we need to wait a bit for the balance message to arrive + expected_base_bal = base_bal + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + self.assertAlmostEqual(expected_base_bal, self.connector.get_available_balance(self.base_token), 5) + + def test_limit_makers_unfilled(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(2)) + quote_bal = self.connector.get_available_balance(self.quote_token) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + quote_amount = (price * amount) + expected_quote_bal = quote_bal - quote_amount + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(2)) + + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 5) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + + cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + # # @TODO: find a way to create "rejected" + # def test_limit_maker_rejections(self): + # price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + # price = self.connector.get_price(self.trading_pair, False) * Decimal("0.8") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + def test_cancel_all(self): + bid_price = self.connector.get_price(self.trading_pair, True) + ask_price = self.connector.get_price(self.trading_pair, False) + bid_price = self.connector.quantize_order_price(self.trading_pair, bid_price * Decimal("0.9")) + ask_price = self.connector.quantize_order_price(self.trading_pair, ask_price * Decimal("1.1")) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + + buy_id = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + sell_id = self._place_order(False, amount, OrderType.LIMIT, ask_price, 2) + + self.ev_loop.run_until_complete(asyncio.sleep(1)) + asyncio.ensure_future(self.connector.cancel_all(5)) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + cancel_events = [t for t in self.event_logger.event_log if isinstance(t, OrderCancelledEvent)] + self.assertEqual({buy_id, sell_id}, {o.order_id for o in cancel_events}) + + def test_order_quantized_values(self): + bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + ask_price: Decimal = self.connector.get_price(self.trading_pair, False) + mid_price: Decimal = (bid_price + ask_price) / 2 + + # Make sure there's enough balance to make the limit orders. + self.assertGreater(self.connector.get_balance("BTC"), Decimal("0.0005")) + self.assertGreater(self.connector.get_balance("USDT"), Decimal("10")) + + # Intentionally set some prices with too many decimal places s.t. they + # need to be quantized. Also, place them far away from the mid-price s.t. they won't + # get filled during the test. + bid_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("0.9333192292111341")) + ask_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("1.1492431474884933")) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount_quant) + + # Test bid order + cl_order_id_1 = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + # Wait for the order created event and examine the order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + + # Test ask order + cl_order_id_2 = self._place_order(False, amount, OrderType.LIMIT, ask_price, 1) + # Wait for the order created event and examine and order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + + self._cancel_order(cl_order_id_1) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self._cancel_order(cl_order_id_2) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + + def test_orders_saving_and_restoration(self): + config_path = "test_config" + strategy_name = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + self.connector._in_flight_orders.clear() + self.assertEqual(0, len(self.connector.tracking_states)) + + # Try to put limit buy order for 0.02 ETH worth of ZRX, and watch for order creation event. + current_bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + price: Decimal = current_bid_price * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + + amount: Decimal = self.order_amount + amount = self.connector.quantize_order_amount(self.trading_pair, amount) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.connector.tracking_states)) + self.assertEqual(cl_order_id, list(self.connector.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.connector) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(cl_order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.connector) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.connector.stop(self._clock) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + self.clock.remove_iterator(self.connector) + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + # Clear the event loop + self.event_logger.clear() + new_connector = GateIoExchange(API_KEY, API_SECRET, [self.trading_pair], True) + for event_tag in self.events: + new_connector.add_listener(event_tag, self.event_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [new_connector], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.clock.add_iterator(new_connector) + self.ev_loop.run_until_complete(self.wait_til_ready(new_connector)) + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + new_connector.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(new_connector.limit_orders)) + self.assertEqual(1, len(new_connector.tracking_states)) + + # Cancel the order and verify that the change is saved. + self._cancel_order(cl_order_id, new_connector) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + recorder.save_market_states(config_path, new_connector) + order_id = None + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, cl_order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.connector.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + def test_filled_orders_recorded(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + # Try to buy some token from the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.05") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.95") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertGreaterEqual(len(trade_fills), 2) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertGreaterEqual(len(buy_fills), 1) + self.assertGreaterEqual(len(sell_fills), 1) + + order_id = None + + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) diff --git a/test/connector/exchange/gate_io/test_gate_io_order_book_tracker.py b/test/connector/exchange/gate_io/test_gate_io_order_book_tracker.py new file mode 100755 index 0000000..52d7b77 --- /dev/null +++ b/test/connector/exchange/gate_io/test_gate_io_order_book_tracker.py @@ -0,0 +1,103 @@ +import asyncio +import logging +import math +import time +import unittest +from typing import Dict, Optional, List + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source import GateIoAPIOrderBookDataSource +from hummingbot.connector.exchange.gate_io.gate_io_order_book_tracker import GateIoOrderBookTracker +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class GateIoOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[GateIoOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USDT", + "ETH-USDT", + ] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + cls.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + cls.order_book_tracker: GateIoOrderBookTracker = GateIoOrderBookTracker(cls.throttler, cls.trading_pairs) + cls.order_book_tracker.start() + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + while True: + if len(cls.order_book_tracker.order_books) > 0: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + async def run_parallel_async(self, *tasks, timeout=None): + future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks)) + timer = 0 + while not future.done(): + if timeout and timer > timeout: + raise Exception("Timeout running parallel async tasks in tests") + timer += 1 + now = time.time() + _next_iteration = now // 1.0 + 1 # noqa: F841 + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def setUp(self): + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + def test_order_book_trade_event_emission(self): + """ + Tests if the order book tracker is able to retrieve order book trade message from exchange and emit order book + trade events after correctly parsing the trade messages + """ + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertTrue(type(ob_trade_event) == OrderBookTradeEvent) + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertTrue(type(ob_trade_event.timestamp) in [float, int]) + self.assertTrue(type(ob_trade_event.amount) == float) + self.assertTrue(type(ob_trade_event.price) == float) + self.assertTrue(type(ob_trade_event.type) == TradeType) + # datetime is in seconds + self.assertTrue(math.ceil(math.log10(ob_trade_event.timestamp)) == 10) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_integrity(self): + # Wait 5 seconds to process some diffs. + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + eth_usd: OrderBook = order_books["ETH-USDT"] + self.assertIsNot(eth_usd.last_diff_uid, 0) + self.assertGreaterEqual(eth_usd.get_price_for_volume(True, 10).result_price, + eth_usd.get_price(True)) + self.assertLessEqual(eth_usd.get_price_for_volume(False, 10).result_price, + eth_usd.get_price(False)) + + def test_api_get_last_traded_prices(self): + prices = self.ev_loop.run_until_complete( + GateIoAPIOrderBookDataSource.get_last_traded_prices(["BTC-USDT", "LTC-BTC"])) + for key, value in prices.items(): + print(f"{key} last_trade_price: {value}") + self.assertGreater(prices["BTC-USDT"], 1000) + self.assertLess(prices["LTC-BTC"], 1) diff --git a/test/connector/exchange/gate_io/test_gate_io_order_status.py b/test/connector/exchange/gate_io/test_gate_io_order_status.py new file mode 100644 index 0000000..f82cb2b --- /dev/null +++ b/test/connector/exchange/gate_io/test_gate_io_order_status.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +import sys +import asyncio +import unittest +import aiohttp +import conf +import logging +import os +from os.path import join, realpath +from typing import Dict, Any +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.connector.exchange.gate_io.gate_io_utils import rest_response_with_errors +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class TestAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + api_key = conf.gate_io_api_key + secret_key = conf.gate_io_secret_key + cls.auth = GateIoAuth(api_key, secret_key) + cls.exchange_order_id = os.getenv("TEST_ORDER_ID") + cls.trading_pair = os.getenv("TEST_TRADING_PAIR") + + async def fetch_order_status(self) -> Dict[Any, Any]: + endpoint = CONSTANTS.ORDER_STATUS_PATH_URL.format(id=self.exchange_order_id) + params = {'currency_pair': self.trading_pair} + http_client = aiohttp.ClientSession() + headers = self.auth.get_headers("GET", f"{CONSTANTS.REST_URL_AUTH}/{endpoint}", params) + http_status, response, request_errors = await rest_response_with_errors( + http_client.request(method='GET', url=f"{CONSTANTS.REST_URL}/{endpoint}", headers=headers, params=params) + ) + await http_client.close() + return response + + def test_order_status(self): + status_test_ready = all({ + 'id': self.exchange_order_id is not None and len(self.exchange_order_id), + 'pair': self.trading_pair is not None and len(self.trading_pair), + }.values()) + if status_test_ready: + result = self.ev_loop.run_until_complete(self.fetch_order_status()) + print(f"Response:\n{result}") diff --git a/test/connector/exchange/gate_io/test_gate_io_user_stream_tracker.py b/test/connector/exchange/gate_io/test_gate_io_user_stream_tracker.py new file mode 100644 index 0000000..ef5dc56 --- /dev/null +++ b/test/connector/exchange/gate_io/test_gate_io_user_stream_tracker.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +import sys +import asyncio +import logging +import unittest +import conf + +from os.path import join, realpath +from hummingbot.connector.exchange.gate_io.gate_io_user_stream_tracker import GateIoUserStreamTracker +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class GateIoUserStreamTrackerUnitTest(unittest.TestCase): + api_key = conf.gate_io_api_key + api_secret = conf.gate_io_secret_key + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.trading_pairs = ["BTC-USDT"] + cls.user_stream_tracker: GateIoUserStreamTracker = GateIoUserStreamTracker( + gate_io_auth=GateIoAuth(cls.api_key, cls.api_secret), + trading_pairs=cls.trading_pairs) + cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start()) + + def test_user_stream(self): + # Wait process some msgs. + print("Sleeping for 30s to gather some user stream messages.") + self.ev_loop.run_until_complete(asyncio.sleep(30.0)) + print(self.user_stream_tracker.user_stream) diff --git a/test/connector/exchange/hitbtc/.gitignore b/test/connector/exchange/hitbtc/.gitignore new file mode 100644 index 0000000..23d9952 --- /dev/null +++ b/test/connector/exchange/hitbtc/.gitignore @@ -0,0 +1 @@ +backups \ No newline at end of file diff --git a/test/connector/exchange/hitbtc/__init__.py b/test/connector/exchange/hitbtc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/hitbtc/test_hitbtc_auth.py b/test/connector/exchange/hitbtc/test_hitbtc_auth.py new file mode 100644 index 0000000..1943412 --- /dev/null +++ b/test/connector/exchange/hitbtc/test_hitbtc_auth.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +import sys +import asyncio +import unittest +import aiohttp +import conf +import logging +from os.path import join, realpath +from typing import Dict, Any +from hummingbot.connector.exchange.hitbtc.hitbtc_auth import HitbtcAuth +from hummingbot.connector.exchange.hitbtc.hitbtc_websocket import HitbtcWebsocket +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class TestAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + api_key = conf.hitbtc_api_key + secret_key = conf.hitbtc_secret_key + cls.auth = HitbtcAuth(api_key, secret_key) + + async def rest_auth(self) -> Dict[Any, Any]: + endpoint = Constants.ENDPOINT['USER_BALANCES'] + headers = self.auth.get_headers("GET", f"{Constants.REST_URL_AUTH}/{endpoint}", None) + http_client = aiohttp.ClientSession() + response = await http_client.get(f"{Constants.REST_URL}/{endpoint}", headers=headers) + await http_client.close() + return await response.json() + + async def ws_auth(self) -> Dict[Any, Any]: + ws = HitbtcWebsocket(self.auth) + await ws.connect() + await ws.subscribe(Constants.WS_SUB["USER_ORDERS_TRADES"], None, {}) + async for response in ws.on_message(): + return response + + def test_rest_auth(self): + result = self.ev_loop.run_until_complete(self.rest_auth()) + if len(result) == 0 or "currency" not in result[0].keys(): + print(f"Unexpected response for API call: {result}") + assert "currency" in result[0].keys() + + def test_ws_auth(self): + response = self.ev_loop.run_until_complete(self.ws_auth()) + if 'result' not in response: + print(f"Unexpected response for API call: {response}") + assert response['result'] is True diff --git a/test/connector/exchange/hitbtc/test_hitbtc_currencies.py b/test/connector/exchange/hitbtc/test_hitbtc_currencies.py new file mode 100644 index 0000000..3e73e75 --- /dev/null +++ b/test/connector/exchange/hitbtc/test_hitbtc_currencies.py @@ -0,0 +1,47 @@ +import sys +import asyncio +import unittest +import aiohttp +import logging +from os.path import join, realpath +from typing import Dict, Any + +from hummingbot.connector.exchange.hitbtc.hitbtc_api_order_book_data_source import HitbtcAPIOrderBookDataSource +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants +from hummingbot.connector.exchange.hitbtc.hitbtc_utils import aiohttp_response_with_errors +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class TestAuth(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + + async def fetch_symbols(self) -> Dict[Any, Any]: + endpoint = Constants.ENDPOINT['SYMBOL'] + http_client = aiohttp.ClientSession() + http_status, response, request_errors = await aiohttp_response_with_errors(http_client.request(method='GET', + url=f"{Constants.REST_URL}/{endpoint}")) + await http_client.close() + return response + + def test_all_trading_pairs_matched(self): + result = self.ev_loop.run_until_complete(self.fetch_symbols()) + print('') + pairs = [i['id'] for i in result] + unmatched_pairs = [] + for pair in pairs: + matched_pair = asyncio.get_event_loop().run_until_complete( + HitbtcAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol(pair)) + if matched_pair is None: + matched_pair_split = None + print(f"\nUnmatched pair: {pair}\n") + unmatched_pairs.append(pair) + else: + matched_pair_split = matched_pair.split('-') + if 'USDUSD' in pair or ('USD' in matched_pair_split[0] and 'USD' in matched_pair_split[1]): + print(f'Found double USD pair: `{pair}` matched to => `{matched_pair}`') + assert len(unmatched_pairs) == 0 diff --git a/test/connector/exchange/hitbtc/test_hitbtc_exchange.py b/test/connector/exchange/hitbtc/test_hitbtc_exchange.py new file mode 100644 index 0000000..ff962a7 --- /dev/null +++ b/test/connector/exchange/hitbtc/test_hitbtc_exchange.py @@ -0,0 +1,437 @@ +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +import asyncio +import logging +from decimal import Decimal +import unittest +import contextlib +import time +import os +from typing import List +import conf +import math + +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.core.utils.async_utils import safe_gather, safe_ensure_future +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.data_type.common import OrderType +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.trade_fill import TradeFill +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.connector.exchange.hitbtc.hitbtc_exchange import HitbtcExchange + +logging.basicConfig(level=METRICS_LOG_LEVEL) + +API_KEY = conf.hitbtc_api_key +API_SECRET = conf.hitbtc_secret_key + + +class HitbtcExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + connector: HitbtcExchange + event_logger: EventLogger + trading_pair = "BTC-USDT" + base_token, quote_token = trading_pair.split("-") + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + global MAINNET_RPC_URL + + cls.ev_loop = asyncio.get_event_loop() + + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.connector: HitbtcExchange = HitbtcExchange( + hitbtc_api_key=API_KEY, + hitbtc_secret_key=API_SECRET, + trading_pairs=[cls.trading_pair], + trading_required=True + ) + print("Initializing Hitbtc market... this will take about a minute.") + cls.clock.add_iterator(cls.connector) + cls.stack: contextlib.ExitStack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls, connector = None): + if connector is None: + connector = cls.connector + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if connector.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../connector_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.event_logger = EventLogger() + for event_tag in self.events: + self.connector.add_listener(event_tag, self.event_logger) + + def tearDown(self): + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + self.event_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def _place_order(self, is_buy, amount, order_type, price, ex_order_id) -> str: + if is_buy: + cl_order_id = self.connector.buy(self.trading_pair, amount, order_type, price) + else: + cl_order_id = self.connector.sell(self.trading_pair, amount, order_type, price) + return cl_order_id + + def _cancel_order(self, cl_order_id, connector=None): + if connector is None: + connector = self.connector + return connector.cancel(self.trading_pair, cl_order_id) + + def test_estimate_fee(self): + maker_fee = self.connector.estimate_fee_pct(True) + self.assertAlmostEqual(maker_fee, Decimal("0.001")) + taker_fee = self.connector.estimate_fee_pct(False) + self.assertAlmostEqual(taker_fee, Decimal("0.0025")) + + def test_buy_and_sell(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.02") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + quote_bal = self.connector.get_available_balance(self.quote_token) + base_bal = self.connector.get_available_balance(self.base_token) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual("BTC", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and str(event.order_id) == str(order_id) + for event in self.event_logger.event_log])) + + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + expected_quote_bal = quote_bal - quote_amount_traded + # self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 1) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.98") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + order_completed_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + trade_events = [t for t in self.event_logger.event_log if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertEqual(amount, order_completed_event.base_asset_amount) + self.assertEqual("BTC", order_completed_event.base_asset) + self.assertEqual("USDT", order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertGreater(order_completed_event.fee_amount, Decimal(0)) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.event_logger.event_log])) + + # check available base balance gets updated, we need to wait a bit for the balance message to arrive + expected_base_bal = base_bal + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + self.assertAlmostEqual(expected_base_bal, self.connector.get_available_balance(self.base_token), 5) + + def test_limit_makers_unfilled(self): + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(2)) + quote_bal = self.connector.get_available_balance(self.quote_token) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + # check available quote balance gets updated, we need to wait a bit for the balance message to arrive + taker_fee = self.connector.estimate_fee_pct(False) + quote_amount = ((price * amount)) + quote_amount = ((price * amount) * (Decimal("1") + taker_fee)) + expected_quote_bal = quote_bal - quote_amount + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.ev_loop.run_until_complete(self.connector._update_balances()) + self.ev_loop.run_until_complete(asyncio.sleep(2)) + + self.assertAlmostEqual(expected_quote_bal, self.connector.get_available_balance(self.quote_token), 5) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + + cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + self._cancel_order(cl_order_id) + event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.assertEqual(cl_order_id, event.order_id) + + # # @TODO: find a way to create "rejected" + # def test_limit_maker_rejections(self): + # price = self.connector.get_price(self.trading_pair, True) * Decimal("1.2") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + # price = self.connector.get_price(self.trading_pair, False) * Decimal("0.8") + # price = self.connector.quantize_order_price(self.trading_pair, price) + # amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000001")) + # cl_order_id = self._place_order(False, amount, OrderType.LIMIT_MAKER, price, 2) + # event = self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + # self.assertEqual(cl_order_id, event.order_id) + + def test_cancel_all(self): + bid_price = self.connector.get_price(self.trading_pair, True) + ask_price = self.connector.get_price(self.trading_pair, False) + bid_price = self.connector.quantize_order_price(self.trading_pair, bid_price * Decimal("0.9")) + ask_price = self.connector.quantize_order_price(self.trading_pair, ask_price * Decimal("1.1")) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + + buy_id = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + sell_id = self._place_order(False, amount, OrderType.LIMIT, ask_price, 2) + + self.ev_loop.run_until_complete(asyncio.sleep(1)) + asyncio.ensure_future(self.connector.cancel_all(5)) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + cancel_events = [t for t in self.event_logger.event_log if isinstance(t, OrderCancelledEvent)] + self.assertEqual({buy_id, sell_id}, {o.order_id for o in cancel_events}) + + def test_order_quantized_values(self): + bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + ask_price: Decimal = self.connector.get_price(self.trading_pair, False) + mid_price: Decimal = (bid_price + ask_price) / 2 + + # Make sure there's enough balance to make the limit orders. + self.assertGreater(self.connector.get_balance("BTC"), Decimal("0.0005")) + self.assertGreater(self.connector.get_balance("USDT"), Decimal("10")) + + # Intentionally set some prices with too many decimal places s.t. they + # need to be quantized. Also, place them far away from the mid-price s.t. they won't + # get filled during the test. + bid_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("0.9333192292111341")) + ask_price = self.connector.quantize_order_price(self.trading_pair, mid_price * Decimal("1.1492431474884933")) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.000223456")) + + # Test bid order + cl_order_id_1 = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1) + # Wait for the order created event and examine the order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + + # Test ask order + cl_order_id_2 = self._place_order(False, amount, OrderType.LIMIT, ask_price, 1) + # Wait for the order created event and examine and order made + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCreatedEvent)) + + self._cancel_order(cl_order_id_1) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + self._cancel_order(cl_order_id_2) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + + def test_orders_saving_and_restoration(self): + config_path = "test_config" + strategy_name = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + self.connector._in_flight_orders.clear() + self.assertEqual(0, len(self.connector.tracking_states)) + + # Try to put limit buy order for 0.02 ETH worth of ZRX, and watch for order creation event. + current_bid_price: Decimal = self.connector.get_price(self.trading_pair, True) + price: Decimal = current_bid_price * Decimal("0.8") + price = self.connector.quantize_order_price(self.trading_pair, price) + + amount: Decimal = Decimal("0.0002") + amount = self.connector.quantize_order_amount(self.trading_pair, amount) + + cl_order_id = self._place_order(True, amount, OrderType.LIMIT_MAKER, price, 1) + order_created_event = self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(cl_order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.connector.tracking_states)) + self.assertEqual(cl_order_id, list(self.connector.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.connector) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(cl_order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.connector) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.connector.stop(self._clock) + self.ev_loop.run_until_complete(asyncio.sleep(5)) + self.clock.remove_iterator(self.connector) + for event_tag in self.events: + self.connector.remove_listener(event_tag, self.event_logger) + # Clear the event loop + self.event_logger.clear() + new_connector = HitbtcExchange(API_KEY, API_SECRET, [self.trading_pair], True) + for event_tag in self.events: + new_connector.add_listener(event_tag, self.event_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [new_connector], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.clock.add_iterator(new_connector) + self.ev_loop.run_until_complete(self.wait_til_ready(new_connector)) + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + new_connector.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(new_connector.limit_orders)) + self.assertEqual(1, len(new_connector.tracking_states)) + + # Cancel the order and verify that the change is saved. + self._cancel_order(cl_order_id, new_connector) + self.ev_loop.run_until_complete(self.event_logger.wait_for(OrderCancelledEvent)) + recorder.save_market_states(config_path, new_connector) + order_id = None + self.assertEqual(0, len(new_connector.limit_orders)) + self.assertEqual(0, len(new_connector.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, new_connector) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, cl_order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.connector.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + def test_filled_orders_recorded(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id = None + recorder = MarketsRecorder(sql, [self.connector], config_path, strategy_name) + recorder.start() + + try: + # Try to buy some token from the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("1.05") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + + order_id = self._place_order(True, amount, OrderType.LIMIT, price, 1) + self.ev_loop.run_until_complete(self.event_logger.wait_for(BuyOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + + # Reset the logs + self.event_logger.clear() + + # Try to sell back the same amount to the exchange, and watch for completion event. + price = self.connector.get_price(self.trading_pair, True) * Decimal("0.95") + price = self.connector.quantize_order_price(self.trading_pair, price) + amount = self.connector.quantize_order_amount(self.trading_pair, Decimal("0.0002")) + order_id = self._place_order(False, amount, OrderType.LIMIT, price, 2) + self.ev_loop.run_until_complete(self.event_logger.wait_for(SellOrderCompletedEvent)) + self.ev_loop.run_until_complete(asyncio.sleep(1)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertGreaterEqual(len(trade_fills), 2) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertGreaterEqual(len(buy_fills), 1) + self.assertGreaterEqual(len(sell_fills), 1) + + order_id = None + + finally: + if order_id is not None: + self.connector.cancel(self.trading_pair, order_id) + self.run_parallel(self.event_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) diff --git a/test/connector/exchange/hitbtc/test_hitbtc_order_book_tracker.py b/test/connector/exchange/hitbtc/test_hitbtc_order_book_tracker.py new file mode 100755 index 0000000..5ae1f74 --- /dev/null +++ b/test/connector/exchange/hitbtc/test_hitbtc_order_book_tracker.py @@ -0,0 +1,101 @@ +import math +import time +import asyncio +import logging +import unittest + +from typing import Dict, Optional, List +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.core.data_type.common import TradeType +from hummingbot.connector.exchange.hitbtc.hitbtc_order_book_tracker import HitbtcOrderBookTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_api_order_book_data_source import HitbtcAPIOrderBookDataSource +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class HitbtcOrderBookTrackerUnitTest(unittest.TestCase): + order_book_tracker: Optional[HitbtcOrderBookTracker] = None + events: List[OrderBookEvent] = [ + OrderBookEvent.TradeEvent + ] + trading_pairs: List[str] = [ + "BTC-USDT", + "ETH-USDT", + ] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_tracker: HitbtcOrderBookTracker = HitbtcOrderBookTracker(cls.trading_pairs) + cls.order_book_tracker.start() + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + while True: + if len(cls.order_book_tracker.order_books) > 0: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + async def run_parallel_async(self, *tasks, timeout=None): + future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks)) + timer = 0 + while not future.done(): + if timeout and timer > timeout: + raise Exception("Timeout running parallel async tasks in tests") + timer += 1 + now = time.time() + _next_iteration = now // 1.0 + 1 # noqa: F841 + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def setUp(self): + self.event_logger = EventLogger() + for event_tag in self.events: + for trading_pair, order_book in self.order_book_tracker.order_books.items(): + order_book.add_listener(event_tag, self.event_logger) + + def test_order_book_trade_event_emission(self): + """ + Tests if the order book tracker is able to retrieve order book trade message from exchange and emit order book + trade events after correctly parsing the trade messages + """ + self.run_parallel(self.event_logger.wait_for(OrderBookTradeEvent)) + for ob_trade_event in self.event_logger.event_log: + self.assertTrue(type(ob_trade_event) == OrderBookTradeEvent) + self.assertTrue(ob_trade_event.trading_pair in self.trading_pairs) + self.assertTrue(type(ob_trade_event.timestamp) in [float, int]) + self.assertTrue(type(ob_trade_event.amount) == float) + self.assertTrue(type(ob_trade_event.price) == float) + self.assertTrue(type(ob_trade_event.type) == TradeType) + # datetime is in seconds + self.assertTrue(math.ceil(math.log10(ob_trade_event.timestamp)) == 10) + self.assertTrue(ob_trade_event.amount > 0) + self.assertTrue(ob_trade_event.price > 0) + + def test_tracker_integrity(self): + # Wait 5 seconds to process some diffs. + self.ev_loop.run_until_complete(asyncio.sleep(5.0)) + order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books + eth_usd: OrderBook = order_books["ETH-USDT"] + self.assertIsNot(eth_usd.last_diff_uid, 0) + self.assertGreaterEqual(eth_usd.get_price_for_volume(True, 10).result_price, + eth_usd.get_price(True)) + self.assertLessEqual(eth_usd.get_price_for_volume(False, 10).result_price, + eth_usd.get_price(False)) + + def test_api_get_last_traded_prices(self): + prices = self.ev_loop.run_until_complete( + HitbtcAPIOrderBookDataSource.get_last_traded_prices(["BTC-USDT", "LTC-BTC"])) + for key, value in prices.items(): + print(f"{key} last_trade_price: {value}") + self.assertGreater(prices["BTC-USDT"], 1000) + self.assertLess(prices["LTC-BTC"], 1) diff --git a/test/connector/exchange/hitbtc/test_hitbtc_user_stream_tracker.py b/test/connector/exchange/hitbtc/test_hitbtc_user_stream_tracker.py new file mode 100644 index 0000000..c53dcff --- /dev/null +++ b/test/connector/exchange/hitbtc/test_hitbtc_user_stream_tracker.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +import sys +import asyncio +import logging +import unittest +import conf + +from os.path import join, realpath +from hummingbot.connector.exchange.hitbtc.hitbtc_user_stream_tracker import HitbtcUserStreamTracker +from hummingbot.connector.exchange.hitbtc.hitbtc_auth import HitbtcAuth +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + + +sys.path.insert(0, realpath(join(__file__, "../../../../../"))) +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class HitbtcUserStreamTrackerUnitTest(unittest.TestCase): + api_key = conf.hitbtc_api_key + api_secret = conf.hitbtc_secret_key + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.trading_pairs = ["BTC-USDT"] + cls.user_stream_tracker: HitbtcUserStreamTracker = HitbtcUserStreamTracker( + hitbtc_auth=HitbtcAuth(cls.api_key, cls.api_secret), + trading_pairs=cls.trading_pairs) + cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start()) + + def test_user_stream(self): + # Wait process some msgs. + print("Sleeping for 30s to gather some user stream messages.") + self.ev_loop.run_until_complete(asyncio.sleep(30.0)) + print(self.user_stream_tracker.user_stream) diff --git a/test/connector/exchange/kraken/__init__.py b/test/connector/exchange/kraken/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/kraken/test_kraken_api_order_book_data_source.py b/test/connector/exchange/kraken/test_kraken_api_order_book_data_source.py new file mode 100644 index 0000000..ff6b56c --- /dev/null +++ b/test/connector/exchange/kraken/test_kraken_api_order_book_data_source.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +from hummingbot.connector.exchange.kraken.kraken_api_order_book_data_source import KrakenAPIOrderBookDataSource +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry +import asyncio +import aiohttp +import logging +from typing import ( + Dict, + Optional, + Any, + List, +) +import unittest + + +class KrakenAPIOrderBookDataSourceUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_data_source: KrakenAPIOrderBookDataSource = KrakenAPIOrderBookDataSource(["ETHUSDC", "XBTUSDC", "ETHDAI"]) + + def run_async(self, task): + return self.ev_loop.run_until_complete(task) + + def test_get_trading_pairs(self): + trading_pairs: List[str] = self.run_async(self.order_book_data_source.get_trading_pairs()) + self.assertIn("ETHDAI", trading_pairs) + + async def get_snapshot(self): + async with aiohttp.ClientSession() as client: + trading_pairs: List[str] = await self.order_book_data_source.get_trading_pairs() + trading_pair: str = trading_pairs[0] + try: + snapshot: Dict[str, Any] = await self.order_book_data_source.get_snapshot(client, trading_pair, 1000) + return snapshot + except Exception: + return None + + def test_get_snapshot(self): + snapshot: Optional[Dict[str, Any]] = self.run_async(self.get_snapshot()) + self.assertIsNotNone(snapshot) + self.assertIn(snapshot["trading_pair"], self.run_async(self.order_book_data_source.get_trading_pairs())) + + def test_get_tracking_pairs(self): + tracking_pairs: Dict[str, OrderBookTrackerEntry] = self.run_async(self.order_book_data_source.get_tracking_pairs()) + self.assertIsInstance(tracking_pairs["ETHDAI"], OrderBookTrackerEntry) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py b/test/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py new file mode 100644 index 0000000..1ada757 --- /dev/null +++ b/test/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +from hummingbot.connector.exchange.kraken.kraken_api_user_stream_data_source import KrakenAPIUserStreamDataSource +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +import asyncio +import logging +import unittest +import conf + + +class KrakenAPIOrderBookDataSourceUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.kraken_auth = KrakenAuth(conf.kraken_api_key.strip(), conf.kraken_secret_key.strip()) + cls.user_stream_data_source: KrakenAPIUserStreamDataSource = KrakenAPIUserStreamDataSource(kraken_auth=cls.kraken_auth) + + def run_async(self, task): + return self.ev_loop.run_until_complete(task) + + def test_get_auth_token(self): + self.token: str = self.run_async(self.user_stream_data_source.get_auth_token()) + self.assertIsInstance(self.token, str) + self.run_async(self.user_stream_data_source.stop()) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/kraken/test_kraken_market.py b/test/connector/exchange/kraken/test_kraken_market.py new file mode 100644 index 0000000..74bcc53 --- /dev/null +++ b/test/connector/exchange/kraken/test_kraken_market.py @@ -0,0 +1,429 @@ +import asyncio +import contextlib +import logging +import time +import unittest +from decimal import Decimal +from os import unlink +from os.path import join, realpath +from typing import List, Optional + +import conf +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map +from hummingbot.connector.exchange.kraken.kraken_exchange import KrakenExchange +from hummingbot.connector.exchange.kraken.kraken_utils import convert_to_exchange_trading_pair +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import ( + Clock, + ClockMode, +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill + +PAIR = "ETH-USDC" +BASE = "ETH" +QUOTE = "USDC" + + +class KrakenExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.OrderCancelled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled + ] + + market: KrakenExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: KrakenExchange = KrakenExchange( + conf.kraken_api_key, + conf.kraken_secret_key, + trading_pairs=[PAIR] + ) + + cls.count = 0 + + print("Initializing Kraken market... this will take about a minute. ") + cls.clock.add_iterator(cls.market) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + cls.count += 1 + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../kraken_test.sqlite")) + try: + unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = asyncio.ensure_future(asyncio.gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self.clock.run_til(next_iteration) + return future.result() + + def run_parallel(self, *tasks): + return self.run_async(self.run_parallel_async(*tasks)) + + def run_async(self, task): + return self.ev_loop.run_until_complete(task) + + def sleep(self, t=1.0): + self.run_parallel(asyncio.sleep(t)) + + def test_get_fee(self): + limit_fee: AddedToCostTradeFee = self.market.get_fee(BASE, QUOTE, OrderType.LIMIT_MAKER, TradeType.BUY, 1, 1) + self.assertGreater(limit_fee.percent, 0) + self.assertEqual(len(limit_fee.flat_fees), 0) + market_fee: AddedToCostTradeFee = self.market.get_fee(BASE, QUOTE, OrderType.LIMIT, TradeType.BUY, 1) + self.assertGreater(market_fee.percent, 0) + self.assertEqual(len(market_fee.flat_fees), 0) + + def test_fee_overrides_config(self): + fee_overrides_config_map["kraken_taker_fee"].value = None + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.0026"), taker_fee.percent) + fee_overrides_config_map["kraken_taker_fee"].value = Decimal('0.2') + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.002"), taker_fee.percent) + fee_overrides_config_map["kraken_maker_fee"].value = None + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.0016"), maker_fee.percent) + fee_overrides_config_map["kraken_maker_fee"].value = Decimal('0.5') + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.005"), maker_fee.percent) + + def place_order(self, is_buy, trading_pair, amount, order_type, price): + order_id = None + if is_buy: + order_id = self.market.buy(trading_pair, amount, order_type, price) + else: + order_id = self.market.sell(trading_pair, amount, order_type, price) + return order_id + + def cancel_order(self, trading_pair, order_id): + self.market.cancel(trading_pair, order_id) + + def test_limit_taker_buy(self): + self.assertGreater(self.market.get_balance(QUOTE), 6) + trading_pair = PAIR + + self.sleep(3) + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id = self.place_order( + True, + trading_pair, + quantized_amount, + OrderType.LIMIT, + price + ) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + order_completed_event: BuyOrderCompletedEvent = order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent) and t.amount is not None] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual(BASE, order_completed_event.base_asset) + self.assertEqual(QUOTE, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_limit_sell(self): + self.assertGreater(self.market.get_balance(BASE), 0.02) + trading_pair = PAIR + + self.sleep(3) + price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id = self.place_order( + False, + trading_pair, + quantized_amount, + OrderType.LIMIT, + price + ) + [order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + order_completed_event: SellOrderCompletedEvent = order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent) and t.amount is not None] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, order_completed_event.base_asset_amount) + self.assertEqual(BASE, order_completed_event.base_asset) + self.assertEqual(QUOTE, order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, order_completed_event.quote_asset_amount) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def underpriced_limit_buy(self): + self.assertGreater(self.market.get_balance(QUOTE), 4) + trading_pair = PAIR + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price * Decimal('0.005') + quantized_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id = self.place_order( + True, + trading_pair, + quantized_amount, + OrderType.LIMIT_MAKER, + quantized_bid_price + ) + + return order_id + + def underpriced_limit_buy_multiple(self, num): + order_ids = [] + for _ in range(num): + order_ids.append(self.underpriced_limit_buy()) + self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + return order_ids + + def test_cancel_order(self): + order_id = self.underpriced_limit_buy() + self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + + self.cancel_order(PAIR, order_id) + + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(order_cancelled_event.order_id, order_id) + + def test_cancel_all(self): + order_ids = self.underpriced_limit_buy_multiple(2) + + cancelled_orders = self.run_async(self.market.cancel_all(10.)) + self.assertEqual([order.order_id for order in cancelled_orders], order_ids) + self.assertTrue([order.success for order in cancelled_orders]) + + def test_order_saving_and_restoration(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + self.assertEqual(0, len(self.market.tracking_states)) + + # Try to put limit buy order for 0.02 ETH at fraction of USDC market price, and watch for order creation event. + order_id = self.underpriced_limit_buy() + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.market.tracking_states)) + self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.clock.remove_iterator(self.market) + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market: KrakenExchange = KrakenExchange( + conf.kraken_api_key, + conf.kraken_secret_key, + trading_pairs=[PAIR] + ) + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, self.market) + self.clock.add_iterator(self.market) + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + self.market.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(self.market.limit_orders)) + self.assertEqual(1, len(self.market.tracking_states)) + + # Cancel the order and verify that the change is saved. + self.market.cancel(PAIR, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_id = None + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, self.market) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.market.cancel(PAIR, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + unlink(self.db_path) + + def test_order_fill_record(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + # Try to buy 0.02 ETH from the exchange, and watch for completion event. + price: Decimal = self.market.get_price(PAIR, True) + amount: Decimal = Decimal("0.02") + quantized_amount: Decimal = self.market.quantize_order_amount(PAIR, amount) + order_id = self.place_order( + True, + PAIR, + quantized_amount, + OrderType.LIMIT, + price + ) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + # Reset the logs + self.market_logger.clear() + + # Try to sell back the same amount of ETH to the exchange, and watch for completion event. + price: Decimal = self.market.get_price(PAIR, False) + amount = buy_order_completed_event.base_asset_amount + quantized_amount: Decimal = self.market.quantize_order_amount(PAIR, amount) + order_id = self.place_order( + False, + PAIR, + quantized_amount, + OrderType.LIMIT, + price + ) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertGreaterEqual(len(trade_fills), 2) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertGreaterEqual(len(buy_fills), 1) + self.assertGreaterEqual(len(sell_fills), 1) + + order_id = None + + finally: + if order_id is not None: + self.market.cancel(PAIR, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + unlink(self.db_path) + + def test_pair_convesion(self): + for pair in self.market.trading_rules: + exchange_pair = convert_to_exchange_trading_pair(pair) + self.assertTrue(exchange_pair in self.market.order_books) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/kraken/test_kraken_order_book_tracker.py b/test/connector/exchange/kraken/test_kraken_order_book_tracker.py new file mode 100644 index 0000000..9bd53c3 --- /dev/null +++ b/test/connector/exchange/kraken/test_kraken_order_book_tracker.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +from hummingbot.connector.exchange.kraken.kraken_order_book_tracker import KrakenOrderBookTracker +from hummingbot.connector.exchange.kraken.kraken_api_order_book_data_source import KrakenAPIOrderBookDataSource +import asyncio +import logging +import unittest + + +class KrakenOrderBookTrackerUnitTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.order_book_tracker: KrakenOrderBookTracker = KrakenOrderBookTracker(trading_pairs=["ETHUSDC", "XBTUSDC"]) + cls.order_book_tracker.start() + cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready()) + + @classmethod + async def wait_til_tracker_ready(cls): + while True: + if len(cls.order_book_tracker.order_books) > 0: + print("Initialized real-time order books.") + return + await asyncio.sleep(1) + + def run_async(self, task): + return self.ev_loop.run_until_complete(task) + + def test_data_source(self): + self.assertIsInstance(self.order_book_tracker.data_source, KrakenAPIOrderBookDataSource) + + def test_name(self): + self.assertEqual(self.order_book_tracker.exchange_name, "kraken") + + def test_start_stop(self): + self.assertTrue(asyncio.isfuture(self.order_book_tracker._order_book_snapshot_router_task)) + self.order_book_tracker.stop() + self.assertIsNone(self.order_book_tracker._order_book_snapshot_router_task) + self.order_book_tracker.start() + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/kraken/test_kraken_user_stream_tracker.py b/test/connector/exchange/kraken/test_kraken_user_stream_tracker.py new file mode 100644 index 0000000..646235c --- /dev/null +++ b/test/connector/exchange/kraken/test_kraken_user_stream_tracker.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../"))) + +from hummingbot.connector.exchange.kraken.kraken_user_stream_tracker import KrakenUserStreamTracker +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +from hummingbot.core.utils.async_utils import safe_ensure_future +import asyncio +import logging +import unittest +import conf + + +class KrakenUserStreamTrackerUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.kraken_auth = KrakenAuth(conf.kraken_api_key, conf.kraken_secret_key) + cls.user_stream_tracker: KrakenUserStreamTracker = KrakenUserStreamTracker(kraken_auth=cls.kraken_auth) + cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start()) + + def run_async(self, task): + return self.ev_loop.run_until_complete(task) + + def test_user_stream(self): + self.ev_loop.run_until_complete(asyncio.sleep(20.0)) + print(self.user_stream_tracker.user_stream) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/connector/exchange/kucoin/__init__.py b/test/connector/exchange/kucoin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/kucoin/fixture_kucoin.py b/test/connector/exchange/kucoin/fixture_kucoin.py new file mode 100644 index 0000000..10890da --- /dev/null +++ b/test/connector/exchange/kucoin/fixture_kucoin.py @@ -0,0 +1,106 @@ +class FixtureKucoin: + BALANCES = {"code": "200000", "data": [ + {"balance": "0.1910973", "available": "0.1910973", "holds": "0", "currency": "ETH", + "id": "5e3291017e612d0009cb8fa6", "type": "trade"}, + {"balance": "1", "available": "1", "holds": "0", "currency": "GRIN", "id": "5e32910f6743620009c134b0", + "type": "trade"}, + {"balance": "0", "available": "0", "holds": "0", "currency": "ETH", "id": "5e3275507cb36900083d9f8e", + "type": "main"}]} + + ORDER_PLACE = {"code": "200000", "data": {"orderId": "5e3cd0540fb53d000961491a"}} + + FILLED_SELL_LIMIT_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0.0021957", + "channel": "API", "feeCurrency": "USDT", "type": "limit", "isActive": False, + "createdAt": 1581043796000, "visibleSize": "0", "price": "208.61", + "iceberg": False, "stopTriggered": False, "funds": "0", + "id": "5e3cd0540fb53d000961491a", "timeInForce": "GTC", "tradeType": "TRADE", + "side": "sell", "dealSize": "0.01", "cancelAfter": 0, "dealFunds": "2.1957", + "stp": "", "postOnly": False, "stopPrice": "0", "size": "0.01", "stop": "", + "cancelExist": False, "clientOid": "sell-ETH-USDT-1581043796007943"}} + + FILLED_BUY_LIMIT_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0.001969718114", + "channel": "API", "feeCurrency": "USDT", "type": "limit", "isActive": False, + "createdAt": 1581045461000, "visibleSize": "0", "price": "229.8", "iceberg": False, + "stopTriggered": False, "funds": "0", "id": "5e3cd6d56e350a00094d32b8", + "timeInForce": "GTC", "tradeType": "TRADE", "side": "buy", "dealSize": "0.01", + "cancelAfter": 0, "dealFunds": "1.969718114", "stp": "", "postOnly": False, + "stopPrice": "0", "size": "0.01", "stop": "", "cancelExist": False, + "clientOid": "buy-ETH-USDT-1581045461006371"}} + + SELL_MARKET_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0.002401058172", + "channel": "API", "feeCurrency": "USDT", "type": "market", "isActive": False, + "createdAt": 1581055817000, "visibleSize": "0", "price": "0", "iceberg": False, + "stopTriggered": False, "funds": "0", "id": "5e3cff496e350a0009aa51d6", + "timeInForce": "GTC", "tradeType": "TRADE", "side": "sell", + "dealSize": "0.0109999", "cancelAfter": 0, "dealFunds": "2.401058172", "stp": "", + "postOnly": False, "stopPrice": "0", "size": "0.0109999", "stop": "", + "cancelExist": False, "clientOid": "sell-ETH-USDT-1581055817012353"}} + + BUY_MARKET_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0.0021843", + "channel": "API", "feeCurrency": "USDT", "type": "market", "isActive": False, + "createdAt": 1581056207000, "visibleSize": "0", "price": "0", "iceberg": False, + "stopTriggered": False, "funds": "0", "id": "5e3d00cf1fbc8d0008d81a18", + "timeInForce": "GTC", "tradeType": "TRADE", "side": "buy", "dealSize": "0.01", + "cancelAfter": 0, "dealFunds": "2.1843", "stp": "", "postOnly": False, + "stopPrice": "0", "size": "0.01", "stop": "", "cancelExist": False, + "clientOid": "buy-ETH-USDT-1581056207008008"}} + + CANCEL_ORDER = {"code": "200000", "data": {"cancelledOrderIds": ["5e3d03c86e350a0009b380a7"]}} + + OPEN_SELL_LIMIT_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0", + "channel": "API", + "feeCurrency": "USDT", "type": "limit", "isActive": True, + "createdAt": 1581056968000, + "visibleSize": "0", "price": "240.11", "iceberg": False, + "stopTriggered": False, + "funds": "0", "id": "5e3d03c86e350a0009b380a7", "timeInForce": "GTC", + "tradeType": "TRADE", "side": "sell", "dealSize": "0", "cancelAfter": 0, + "dealFunds": "0", "stp": "", "postOnly": False, "stopPrice": "0", + "size": "0.01", + "stop": "", "cancelExist": False, + "clientOid": "sell-ETH-USDT-1581056966892386"}} + + GET_CANCELED_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0", + "channel": "API", "feeCurrency": "USDT", "type": "limit", "isActive": False, + "createdAt": 1581056968000, "visibleSize": "0", "price": "240.11", "iceberg": False, + "stopTriggered": False, "funds": "0", "id": "5e3d03c86e350a0009b380a7", + "timeInForce": "GTC", "tradeType": "TRADE", "side": "sell", "dealSize": "0", + "cancelAfter": 0, "dealFunds": "0", "stp": "", "postOnly": False, "stopPrice": "0", + "size": "0.01", "stop": "", "cancelExist": True, + "clientOid": "sell-ETH-USDT-1581056966892386"}} + + ORDER_PLACE_2 = {"code": "200000", "data": {"orderId": "5e3d08516e350a0009bcd272"}} + + OPEN_BUY_LIMIT_ORDER = { + "code": "200000", + "data": { + "symbol": "ETH-USDT", "hidden": False, "opType": "DEAL", "fee": "0", + "channel": "API", "feeCurrency": "USDT", "type": "limit", "isActive": True, + "createdAt": 1581058129000, "visibleSize": "0", "price": "174.61", + "iceberg": False, "stopTriggered": False, "funds": "0", + "id": "5e3d08516e350a0009bcd272", "timeInForce": "GTC", "tradeType": "TRADE", + "side": "buy", "dealSize": "0", "cancelAfter": 0, "dealFunds": "0", "stp": "", + "postOnly": False, "stopPrice": "0", "size": "0.01", "stop": "", + "cancelExist": False, "clientOid": "buy-ETH-USDT-1581058129011078"}} + + ORDERS_BATCH_CANCELED = { + "code": "200000", + "data": {"cancelledOrderIds": ["5e3d0851051a350008723a81", "5e3d08516e350a0009bcd272"]}} diff --git a/test/connector/exchange/kucoin/test_kucoin_market.py b/test/connector/exchange/kucoin/test_kucoin_market.py new file mode 100644 index 0000000..43983cc --- /dev/null +++ b/test/connector/exchange/kucoin/test_kucoin_market.py @@ -0,0 +1,547 @@ +import asyncio +import contextlib +import logging +import math +import os +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import ( + List, + Optional +) +from unittest import mock + +import conf +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map +from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.mock_api.mock_web_server import MockWebServer +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill +from test.connector.exchange.kucoin.fixture_kucoin import FixtureKucoin + +logging.basicConfig(level=METRICS_LOG_LEVEL) +API_MOCK_ENABLED = conf.mock_api_enabled is not None and conf.mock_api_enabled.lower() in ['true', 'yes', '1'] +API_KEY = "XXX" if API_MOCK_ENABLED else conf.kucoin_api_key +API_SECRET = "YYY" if API_MOCK_ENABLED else conf.kucoin_secret_key +API_PASSPHRASE = "ZZZ" if API_MOCK_ENABLED else conf.kucoin_passphrase +API_BASE_URL = "api.kucoin.com" +EXCHANGE_ORDER_ID = 20001 + + +class KucoinExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.OrderCancelled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + + market: KucoinExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + if API_MOCK_ENABLED: + cls.web_app = MockWebServer.get_instance() + cls.web_app.add_host_to_mock(API_BASE_URL, ["/api/v1/timestamp", "/api/v1/symbols", + "/api/v1/bullet-public", + "/api/v2/market/orderbook/level2"]) + cls.web_app.start() + cls.ev_loop.run_until_complete(cls.web_app.wait_til_started()) + cls._patcher = mock.patch("aiohttp.client.URL") + cls._url_mock = cls._patcher.start() + cls._url_mock.side_effect = cls.web_app.reroute_local + cls.web_app.update_response("get", API_BASE_URL, "/api/v1/accounts", FixtureKucoin.BALANCES) + + cls._t_nonce_patcher = unittest.mock.patch( + "hummingbot.connector.exchange.kucoin.kucoin_exchange.get_tracking_nonce") + cls._t_nonce_mock = cls._t_nonce_patcher.start() + cls._exch_order_id = 20001 + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: KucoinExchange = KucoinExchange( + kucoin_api_key=API_KEY, + kucoin_passphrase=API_PASSPHRASE, + kucoin_secret_key=API_SECRET, + trading_pairs=["ETH-USDT"] + ) + # Need 2nd instance of market to prevent events mixing up across tests + cls.market_2: KucoinExchange = KucoinExchange( + kucoin_api_key=API_KEY, + kucoin_passphrase=API_PASSPHRASE, + kucoin_secret_key=API_SECRET, + trading_pairs=["ETH-USDT"] + ) + cls.clock.add_iterator(cls.market) + cls.clock.add_iterator(cls.market_2) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + if API_MOCK_ENABLED: + cls.web_app.stop() + cls._patcher.stop() + cls._t_nonce_patcher.stop() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready and cls.market_2.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../kucoin_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + self.market_2_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + self.market_2.add_listener(event_tag, self.market_2_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_2.remove_listener(event_tag, self.market_2_logger) + self.market_logger = None + self.market_2_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(0.5) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_get_fee(self): + limit_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDT", OrderType.LIMIT_MAKER, TradeType.BUY, 1, 10) + self.assertGreater(limit_fee.percent, 0) + self.assertEqual(len(limit_fee.flat_fees), 0) + market_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDT", OrderType.LIMIT, TradeType.BUY, 1) + self.assertGreater(market_fee.percent, 0) + self.assertEqual(len(market_fee.flat_fees), 0) + sell_trade_fee: AddedToCostTradeFee = self.market.get_fee( + "ETH", "USDT", OrderType.LIMIT_MAKER, TradeType.SELL, 1, 10 + ) + self.assertGreater(sell_trade_fee.percent, 0) + self.assertEqual(len(sell_trade_fee.flat_fees), 0) + + def order_response(self, fixture_data, nonce): + self._t_nonce_mock.return_value = nonce + order_resp = fixture_data.copy() + return order_resp + + def place_order(self, is_buy, trading_pair, amount, order_type, price, nonce, post_resp, get_resp): + global EXCHANGE_ORDER_ID + order_id, exch_order_id = None, None + if API_MOCK_ENABLED: + exch_order_id = f"KUCOIN_{EXCHANGE_ORDER_ID}" + EXCHANGE_ORDER_ID += 1 + resp = self.order_response(post_resp, nonce) + resp["data"]["orderId"] = exch_order_id + self.web_app.update_response("post", API_BASE_URL, "/api/v1/orders", resp) + if is_buy: + order_id = self.market.buy(trading_pair, amount, order_type, price) + else: + order_id = self.market.sell(trading_pair, amount, order_type, price) + if API_MOCK_ENABLED: + resp = get_resp.copy() + resp["data"]["id"] = exch_order_id + resp["data"]["clientOid"] = order_id + self.web_app.update_response("get", API_BASE_URL, f"/api/v1/orders/{exch_order_id}", resp) + return order_id, exch_order_id + + def test_fee_overrides_config(self): + fee_overrides_config_map["kucoin_taker_fee"].value = None + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.001"), taker_fee.percent) + fee_overrides_config_map["kucoin_taker_fee"].value = Decimal('0.2') + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.002"), taker_fee.percent) + fee_overrides_config_map["kucoin_maker_fee"].value = None + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.001"), maker_fee.percent) + fee_overrides_config_map["kucoin_maker_fee"].value = Decimal('0.5') + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.005"), maker_fee.percent) + + def test_limit_maker_rejections(self): + if API_MOCK_ENABLED: + return + trading_pair = "ETH-USDT" + + # Try to put a buy limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('1.02') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal(0.01)) + + order_id = self.market.buy(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + self.market_logger.clear() + + # Try to put a sell limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('0.98') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal(0.01)) + + order_id = self.market.sell(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + def test_limit_makers_unfilled(self): + if API_MOCK_ENABLED: + return + trading_pair = "ETH-USDT" + bid_price = self.market.get_price(trading_pair, True) * Decimal("0.8") + quantized_bid_price = self.market.quantize_order_price(trading_pair, bid_price) + quantized_bid_amount = self.market.quantize_order_amount(trading_pair, Decimal(0.01)) + + order_id, _ = self.place_order(True, trading_pair, quantized_bid_amount, OrderType.LIMIT_MAKER, + quantized_bid_price, 10001, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.ORDER_GET_BUY_UNMATCHED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + ask_price = self.market.get_price(trading_pair, True) * Decimal("1.2") + quatized_ask_price = self.market.quantize_order_price(trading_pair, ask_price) + quatized_ask_amount = self.market.quantize_order_amount(trading_pair, Decimal(0.01)) + + order_id, _ = self.place_order(False, trading_pair, quatized_ask_amount, OrderType.LIMIT_MAKER, + quatized_ask_price, 10002, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.ORDER_GET_SELL_UNMATCHED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + [cancellation_results] = self.run_parallel(self.market.cancel_all(5)) + for cr in cancellation_results: + self.assertEqual(cr.success, True) + + def test_limit_taker_buy(self): + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.05")) + trading_pair = "ETH-USDT" + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal(0.01) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, _ = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT, price, 10001, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.BUY_MARKET_ORDER) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + buy_order_completed_event: BuyOrderCompletedEvent = buy_order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded: float = sum(t.amount for t in trade_events) + quote_amount_traded: float = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, buy_order_completed_event.order_id) + self.assertAlmostEqual(float(quantized_amount), buy_order_completed_event.base_asset_amount, places=4) + self.assertEqual("ETH", buy_order_completed_event.base_asset) + self.assertEqual("USDT", buy_order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, float(buy_order_completed_event.base_asset_amount), places=4) + self.assertAlmostEqual(quote_amount_traded, float(buy_order_completed_event.quote_asset_amount), places=4) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_limit_taker_sell(self): + self.assertGreater(self.market.get_balance("ETH"), Decimal("0.05")) + trading_pair = "ETH-USDT" + price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal(0.011) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + order_id, _ = self.place_order(False, trading_pair, amount, OrderType.LIMIT, price, 10001, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.SELL_MARKET_ORDER) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + sell_order_completed_event: SellOrderCompletedEvent = sell_order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, sell_order_completed_event.order_id) + self.assertAlmostEqual(float(quantized_amount), sell_order_completed_event.base_asset_amount) + self.assertEqual("ETH", sell_order_completed_event.base_asset) + self.assertEqual("USDT", sell_order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, float(sell_order_completed_event.base_asset_amount)) + self.assertAlmostEqual(quote_amount_traded, float(sell_order_completed_event.quote_asset_amount)) + self.assertGreater(sell_order_completed_event.fee_amount, Decimal(0)) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + # Reset the logs + self.market_logger.clear() + + def test_cancel(self): + trading_pair = "ETH-USDT" + + current_price: float = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal(0.01) + + price: Decimal = Decimal(current_price) * Decimal(1.1) + quantized_price: Decimal = self.market.quantize_order_price(trading_pair, price) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantized_price, 10001, + FixtureKucoin.ORDER_PLACE_2, FixtureKucoin.OPEN_SELL_LIMIT_ORDER) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + if API_MOCK_ENABLED: + resp = FixtureKucoin.CANCEL_ORDER.copy() + resp["data"]["cancelledOrderIds"] = [exch_order_id] + self.web_app.update_response("delete", API_BASE_URL, f"/api/v1/orders/{exch_order_id}", resp) + self.market.cancel(trading_pair, order_id) + if API_MOCK_ENABLED: + resp = FixtureKucoin.GET_CANCELED_ORDER.copy() + resp["data"]["id"] = exch_order_id + resp["data"]["clientOid"] = order_id + self.web_app.update_response("get", API_BASE_URL, f"/api/v1/orders/{exch_order_id}", resp) + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(order_cancelled_event.order_id, order_id) + self.market_logger.clear() + + def test_cancel_all(self): + trading_pair = "ETH-USDT" + + bid_price: Decimal = Decimal(self.market_2.get_price(trading_pair, True)) + ask_price: Decimal = Decimal(self.market_2.get_price(trading_pair, False)) + amount: Decimal = Decimal(0.01) + quantized_amount: Decimal = self.market_2.quantize_order_amount(trading_pair, amount) + + # Intentionally setting high price to prevent getting filled + quantize_bid_price: Decimal = self.market_2.quantize_order_price(trading_pair, bid_price * Decimal(0.8)) + quantize_ask_price: Decimal = self.market_2.quantize_order_price(trading_pair, ask_price * Decimal(1.2)) + + _, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, quantize_bid_price, + 10001, FixtureKucoin.ORDER_PLACE, FixtureKucoin.OPEN_BUY_LIMIT_ORDER) + + _, exch_order_id2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, quantize_ask_price, + 10002, FixtureKucoin.ORDER_PLACE, FixtureKucoin.OPEN_SELL_LIMIT_ORDER) + + self.run_parallel(asyncio.sleep(1)) + if API_MOCK_ENABLED: + resp = FixtureKucoin.ORDERS_BATCH_CANCELED.copy() + resp["data"]["cancelledOrderIds"] = [exch_order_id, exch_order_id2] + self.web_app.update_response("delete", API_BASE_URL, "/api/v1/orders", resp) + [cancellation_results] = self.run_parallel(self.market_2.cancel_all(5)) + for cr in cancellation_results: + self.assertEqual(cr.success, True) + self.market_2_logger.clear() + + def test_orders_saving_and_restoration(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDT" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + self.assertEqual(0, len(self.market.tracking_states)) + + # Try to put limit buy order for 0.04 ETH, and watch for order creation event. + current_bid_price: float = self.market.get_price(trading_pair, True) + bid_price: Decimal = Decimal(current_bid_price * Decimal(0.8)) + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + amount: Decimal = Decimal(0.04) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, + 10001, FixtureKucoin.ORDER_PLACE, + FixtureKucoin.OPEN_BUY_LIMIT_ORDER) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.market.tracking_states)) + self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.clock.remove_iterator(self.market) + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market: KucoinExchange = KucoinExchange( + kucoin_api_key=API_KEY, + kucoin_passphrase=API_PASSPHRASE, + kucoin_secret_key=API_SECRET, + trading_pairs=["ETH-USDT"] + ) + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, self.market) + self.clock.add_iterator(self.market) + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + self.market.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(self.market.limit_orders)) + self.assertEqual(1, len(self.market.tracking_states)) + + if API_MOCK_ENABLED: + resp = FixtureKucoin.CANCEL_ORDER.copy() + resp["data"]["cancelledOrderIds"] = exch_order_id + self.web_app.update_response("delete", API_BASE_URL, f"/api/v1/orders/{exch_order_id}", resp) + # Cancel the order and verify that the change is saved. + self.market.cancel(trading_pair, order_id) + if API_MOCK_ENABLED: + resp = FixtureKucoin.GET_CANCELED_ORDER.copy() + resp["data"]["id"] = exch_order_id + resp["data"]["clientOid"] = order_id + self.web_app.update_response("get", API_BASE_URL, f"/api/v1/orders/{exch_order_id}", resp) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_id = None + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, self.market) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + self.market_logger.clear() + + def test_order_fill_record(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDT" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + # Try to buy 0.01 ETH from the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal(0.01) + order_id, _ = self.place_order(True, trading_pair, amount, OrderType.LIMIT, price, 10001, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.BUY_MARKET_ORDER) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + # Reset the logs + self.market_logger.clear() + + # Try to sell back the same amount of ETH to the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal(buy_order_completed_event.base_asset_amount) + order_id, _ = self.place_order(False, trading_pair, amount, OrderType.LIMIT, price, 10002, + FixtureKucoin.ORDER_PLACE, FixtureKucoin.SELL_MARKET_ORDER) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertEqual(2, len(trade_fills)) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertEqual(1, len(buy_fills)) + self.assertEqual(1, len(sell_fills)) + + order_id = None + + finally: + if order_id is not None: + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + self.market_logger.clear() + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.market.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/connector/exchange/mexc/__init__.py b/test/connector/exchange/mexc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/connector/exchange/mexc/fixture_mexc.py b/test/connector/exchange/mexc/fixture_mexc.py new file mode 100644 index 0000000..c87c799 --- /dev/null +++ b/test/connector/exchange/mexc/fixture_mexc.py @@ -0,0 +1,247 @@ +class FixtureMEXC: + PING_DATA = {"code": 200} + + MEXC_TICKERS = { + "code": "0", + "msg": "", + "data": [ + { + "symbol": "ETH_USDT", + "volume": "14155.61237", + "high": "4394.18", + "low": "4166.27", + "bid": "4205.44", + "ask": "4206.28", + "open": "4311.7", + "last": "4205.89", + "time": 1635685800000, + "change_rate": "-0.0245402" + } + ] + } + + TICKER_DATA = { + "code": 200, + "data": [ + { + "symbol": "ETH_USDT", + "volume": "0", + "high": "182.4117576", + "low": "182.4117576", + "bid": "182.0017985", + "ask": "183.1983186", + "open": "182.4117576", + "last": "182.4117576", + "time": 1574668200000, + "change_rate": "0.00027307" + } + ] + } + + MEXC_MARKET_SYMBOL = { + "code": 200, + "data": [ + { + "symbol": "ETH_USDT", + "state": "ENABLED", + "vcoinName": "ETH", + "vcoinStatus": 1, + "price_scale": 2, + "quantity_scale": 5, + "min_amount": "5", + "max_amount": "5000000", + "maker_fee_rate": "0.002", + "taker_fee_rate": "0.002", + "limited": False, + "etf_mark": 0, + "symbol_partition": "MAIN" + } + ] + } + + MEXC_ORDER_BOOK = { + "code": 200, + "data": { + "asks": [ + { + "price": "183.1683154", + "quantity": "128.5" + }, + { + "price": "183.1983186", + "quantity": "101.6" + } + ], + "bids": [ + { + "price": "182.4417544", + "quantity": "115.5" + }, + { + "price": "182.4217568", + "quantity": "135.7" + } + ] + } + } + + MEXC_BALANCE_URL = { + "code": 200, + "data": { + "BTC": { + "frozen": "0", + "available": "140" + }, + "ETH": { + "frozen": "8471.296525048", + "available": "483280.9653659222035" + }, + "USDT": { + "frozen": "0", + "available": "27.3629" + }, + "MX": { + "frozen": "30.9863", + "available": "450.0137" + } + } + } + + ORDER_PLACE = { + "code": 200, + "data": "c8663a12a2fc457fbfdd55307b463495" + } + + ORDER_GET_LIMIT_BUY_UNFILLED = { + "code": 200, + "data": [ + { + "id": "2a0ad973f6a8452bae1533164ec3ef72", + "symbol": "ETH_USDT", + "price": "3500", + "quantity": "0.06", + "state": "NEW", + "type": "BID", + "deal_quantity": "0", + "deal_amount": "0", + "create_time": 1635824885000, + "order_type": "LIMIT_ORDER" + } + ] + } + + ORDER_GET_LIMIT_BUY_FILLED = { + "code": 200, + "data": [ + { + "id": "c8663a12a2fc457fbfdd55307b463495", + "symbol": "ETH_USDT", + "price": "4001", + "quantity": "0.06", + "state": "FILLED", + "type": "BID", + "deal_quantity": "0.06", + "deal_amount": "0.06", + "create_time": 1573117266000, + "client_order_id": "aaa" + } + ] + } + + ORDERS_BATCH_CANCELED = { + "code": "0", + "msg": "", + "data": [ + { + "clOrdId": "", + "ordId": "2482659399697407", + "sCode": "0", + "sMsg": "" + }, + { + "clOrdId": "", + "ordId": "2482659399697408", + "sCode": "0", + "sMsg": "" + }, + ] + } + + ORDER_CANCEL = { + "code": 200, + "data": { + "2510832677225473": "success" + } + } + + ORDER_GET_CANCELED = { + "code": 200, + "data": [ + { + "id": "c38a9449ee2e422ca83593833a2595d7", + "symbol": "ETH_USDT", + "price": "3500", + "quantity": "0.06", + "state": "CANCELED", + "type": "BID", + "deal_quantity": "0", + "deal_amount": "0", + "create_time": 1635822195000, + "order_type": "LIMIT_ORDER" + } + ] + } + + ORDER_GET_MARKET_BUY = { + "code": 200, + "data": [ + { + "id": "c8663a12a2fc457fbfdd55307b463495", + "symbol": "ETH_USDT", + "price": "4001", + "quantity": "0.06", + "state": "FILLED", + "type": "BID", + "deal_quantity": "0.06", + "deal_amount": "0.06", + "create_time": 1573117266000, + "client_order_id": "aaa" + } + ] + } + + ORDER_GET_MARKET_SELL = { + "code": 200, + "data": [ + { + "id": "c8663a12a2fc457fbfdd55307b463495", + "symbol": "ETH_USDT", + "price": "4001", + "quantity": "0.06", + "state": "FILLED", + "type": "BID", + "deal_quantity": "0.06", + "deal_amount": "0.06", + "create_time": 1573117266000, + "client_order_id": "aaa" + } + ] + } + + ORDER_DEAL_DETAIL = { + "code": 200, + "data": [ + { + "symbol": "ETH_USDT", + "order_id": "a39ea6b7afcf4f5cbba1e515210ff827", + "quantity": "54.1", + "price": "182.6317377", + "amount": "9880.37700957", + "fee": "9.88037700957", + "trade_type": "BID", + "fee_currency": "USDT", + "is_taker": True, + "create_time": 1572693911000 + } + ] + } diff --git a/test/connector/exchange/mexc/test_mexc_market.py b/test/connector/exchange/mexc/test_mexc_market.py new file mode 100644 index 0000000..1d3dbd0 --- /dev/null +++ b/test/connector/exchange/mexc/test_mexc_market.py @@ -0,0 +1,581 @@ +import asyncio +import contextlib +import logging +import math +import os +import time +import unittest +from decimal import Decimal +from os.path import join, realpath +from typing import ( + List, + Optional +) +from unittest import mock + +import conf + +from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map +from hummingbot.connector.exchange.mexc.mexc_constants import ( + MEXC_BALANCE_URL, + MEXC_BASE_URL, + MEXC_BATCH_ORDER_CANCEL, + MEXC_DEAL_DETAIL, + MEXC_DEPTH_URL, + MEXC_ORDER_CANCEL, + MEXC_ORDER_DETAILS_URL, + MEXC_PING_URL, + MEXC_PLACE_ORDER, + MEXC_PRICE_URL, + MEXC_SYMBOL_URL, +) +from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange +from hummingbot.connector.exchange_base import OrderType +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.mock_api.mock_web_server import MockWebServer +from hummingbot.core.utils.async_utils import ( + safe_ensure_future, + safe_gather, +) +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL +from hummingbot.model.market_state import MarketState +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import ( + SQLConnectionManager, + SQLConnectionType +) +from hummingbot.model.trade_fill import TradeFill +from test.connector.exchange.mexc.fixture_mexc import FixtureMEXC + +# MOCK_API_ENABLED = conf.mock_api_enabled is not None and conf.mock_api_enabled.lower() in ['true', 'yes', '1'] +MOCK_API_ENABLED = True + +API_KEY = "API_PASSPHRASE_MOCK" if MOCK_API_ENABLED else conf.mexc_api_key +API_SECRET = "API_SECRET_MOCK" if MOCK_API_ENABLED else conf.mexc_secret_key + +API_BASE_URL = MEXC_BASE_URL.replace("https://", "").replace("/", "") + +EXCHANGE_ORDER_ID = 20001 + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class MexcExchangeUnitTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.ReceivedAsset, + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.OrderCancelled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled, + MarketEvent.OrderFailure + ] + + market: MexcExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def strip_host_from_mexc_url(cls, url): + HOST = "https://www.mexc.com" + return url.split(HOST)[-1] + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + if MOCK_API_ENABLED: + cls.web_app = MockWebServer.get_instance() + cls.web_app.add_host_to_mock(API_BASE_URL, []) + cls.web_app.start() + cls.ev_loop.run_until_complete(cls.web_app.wait_til_started()) + cls._patcher = mock.patch("aiohttp.client.URL") + cls._url_mock = cls._patcher.start() + cls._url_mock.side_effect = cls.web_app.reroute_local + + cls.web_app.update_response("get", API_BASE_URL, cls.strip_host_from_mexc_url(MEXC_SYMBOL_URL), + FixtureMEXC.MEXC_MARKET_SYMBOL) + cls.web_app.update_response("get", API_BASE_URL, MEXC_PRICE_URL.split("?")[:1][0], FixtureMEXC.MEXC_TICKERS, + params={"symbol": "ETH-USDT"}) + cls.web_app.update_response("get", API_BASE_URL, MEXC_DEPTH_URL.split("?")[:1][0], + FixtureMEXC.MEXC_ORDER_BOOK, params={"symbol": "ETH-USDT", "depth": 200}) + # cls.web_app.update_response("get", API_BASE_URL, MEXC_TICKERS_URL, FixtureMEXC.MEXC_TICKERS) + cls.web_app.update_response("get", API_BASE_URL, MEXC_BALANCE_URL, FixtureMEXC.MEXC_BALANCE_URL) + cls.web_app.update_response("get", API_BASE_URL, MEXC_DEAL_DETAIL, FixtureMEXC.ORDER_DEAL_DETAIL) + cls.web_app.update_response("get", API_BASE_URL, MEXC_PING_URL, FixtureMEXC.PING_DATA) + cls._t_nonce_patcher = unittest.mock.patch( + "hummingbot.connector.exchange.mexc.mexc_exchange.get_tracking_nonce") + cls._t_nonce_mock = cls._t_nonce_patcher.start() + cls.clock: Clock = Clock(ClockMode.REALTIME) + cls.market: MexcExchange = MexcExchange( + API_KEY, + API_SECRET, + trading_pairs=["ETH-USDT"] + ) + # Need 2nd instance of market to prevent events mixing up across tests + cls.market_2: MexcExchange = MexcExchange( + API_KEY, + API_SECRET, + trading_pairs=["ETH-USDT"] + ) + # a = cls.market + cls.clock.add_iterator(cls.market) + cls.clock.add_iterator(cls.market_2) + cls.stack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + if MOCK_API_ENABLED: + cls.web_app.stop() + cls._patcher.stop() + cls._t_nonce_patcher.stop() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready and cls.market_2.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../mexc_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + self.market_2_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + self.market_2.add_listener(event_tag, self.market_2_logger) + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_2.remove_listener(event_tag, self.market_2_logger) + self.market_logger = None + self.market_2_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(0.5) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_get_fee(self): + limit_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDT", OrderType.LIMIT_MAKER, TradeType.BUY, 1, 10) + self.assertGreater(limit_fee.percent, 0) + self.assertEqual(len(limit_fee.flat_fees), 0) + market_fee: AddedToCostTradeFee = self.market.get_fee("ETH", "USDT", OrderType.LIMIT, TradeType.BUY, 1) + self.assertGreater(market_fee.percent, 0) + self.assertEqual(len(market_fee.flat_fees), 0) + sell_trade_fee: AddedToCostTradeFee = self.market.get_fee( + "ETH", "USDT", OrderType.LIMIT_MAKER, TradeType.SELL, 1, 10 + ) + self.assertGreater(sell_trade_fee.percent, 0) + self.assertEqual(len(sell_trade_fee.flat_fees), 0) + + def test_fee_overrides_config(self): + fee_overrides_config_map["mexc_taker_fee"].value = None + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.002"), taker_fee.percent) + fee_overrides_config_map["mexc_taker_fee"].value = Decimal('0.1') + taker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", "ETH", OrderType.LIMIT, TradeType.BUY, Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.001"), taker_fee.percent) + fee_overrides_config_map["mexc_maker_fee"].value = None + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.002"), maker_fee.percent) + fee_overrides_config_map["mexc_maker_fee"].value = Decimal('0.5') + maker_fee: AddedToCostTradeFee = self.market.get_fee("LINK", + "ETH", + OrderType.LIMIT_MAKER, + TradeType.BUY, + Decimal(1), + Decimal('0.1')) + self.assertAlmostEqual(Decimal("0.005"), maker_fee.percent) + + def place_order(self, is_buy, trading_pair, amount, order_type, price, nonce, get_resp, market_connector=None): + global EXCHANGE_ORDER_ID + order_id, exch_order_id = None, None + if MOCK_API_ENABLED: + exch_order_id = f"MEXC_{EXCHANGE_ORDER_ID}" + EXCHANGE_ORDER_ID += 1 + self._t_nonce_mock.return_value = nonce + resp = FixtureMEXC.ORDER_PLACE.copy() + resp["data"] = exch_order_id + side = 'buy' if is_buy else 'sell' + order_id = f"{side}-{trading_pair}-{nonce}" + self.web_app.update_response("post", API_BASE_URL, MEXC_PLACE_ORDER, resp) + market = self.market if market_connector is None else market_connector + if is_buy: + order_id = market.buy(trading_pair, amount, order_type, price) + else: + order_id = market.sell(trading_pair, amount, order_type, price) + if MOCK_API_ENABLED: + resp = get_resp.copy() + # resp is the response passed by parameter + resp["data"][0]["id"] = exch_order_id + resp["data"][0]["client_order_id"] = order_id + self.web_app.update_response("get", API_BASE_URL, + MEXC_ORDER_DETAILS_URL.format(ordId=exch_order_id, trading_pair="ETH-USDT"), + resp) + return order_id, exch_order_id + + def cancel_order(self, trading_pair, order_id, exchange_order_id, get_resp): + if MOCK_API_ENABLED: + resp = FixtureMEXC.ORDER_CANCEL.copy() + resp.get('data').clear() + resp.get('data')[order_id] = 'success' + self.web_app.update_response("delete", API_BASE_URL, MEXC_ORDER_CANCEL, + resp, params={"order_ids": order_id}) + self.market.cancel(trading_pair, order_id) + if MOCK_API_ENABLED: + resp = get_resp.copy() + resp["data"][0]["id"] = exchange_order_id + resp["data"][0]["client_order_id"] = order_id + self.web_app.update_response("get", API_BASE_URL, MEXC_ORDER_DETAILS_URL.format(ordId=exchange_order_id, + trading_pair="ETH-USDT"), + resp) + + def test_limit_maker_rejections(self): + if MOCK_API_ENABLED: + return + trading_pair = "ETH-USDT" + + # Try to put a buy limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('1.02') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal("0.006")) + + order_id = self.market.buy(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + self.market_logger.clear() + + # Try to put a sell limit maker order that is going to match, this should triggers order failure event. + price: Decimal = self.market.get_price(trading_pair, True) * Decimal('0.98') + price: Decimal = self.market.quantize_order_price(trading_pair, price) + amount = self.market.quantize_order_amount(trading_pair, Decimal("0.006")) + + order_id = self.market.sell(trading_pair, amount, OrderType.LIMIT_MAKER, price) + [order_failure_event] = self.run_parallel(self.market_logger.wait_for(MarketOrderFailureEvent)) + self.assertEqual(order_id, order_failure_event.order_id) + + def test_limit_makers_unfilled(self): + if MOCK_API_ENABLED: + return + + trading_pair = "ETH-USDT" + + bid_price: Decimal = self.market.get_price(trading_pair, True) * Decimal("0.5") + ask_price: Decimal = self.market.get_price(trading_pair, False) * 2 + amount: Decimal = Decimal("0.006") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + # Intentionally setting invalid price to prevent getting filled + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price * Decimal("0.9")) + quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, ask_price * Decimal("1.1")) + + order_id1, exch_order_id1 = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, + 10001, FixtureMEXC.ORDER_GET_LIMIT_BUY_UNFILLED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id1, order_created_event.order_id) + + order_id2, exch_order_id2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_ask_price, + 10002, FixtureMEXC.ORDER_GET_LIMIT_SELL_UNFILLED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + self.assertEqual(order_id2, order_created_event.order_id) + + self.run_parallel(asyncio.sleep(1)) + if MOCK_API_ENABLED: + resp = FixtureMEXC.ORDERS_BATCH_CANCELED.copy() + resp["data"]["success"] = [exch_order_id1, exch_order_id2] + self.web_app.update_response("delete", API_BASE_URL, "/open/api/v2/order/cancel_by_symbol", resp) + [cancellation_results] = self.run_parallel(self.market_2.cancel_all(5)) + for cr in cancellation_results: + self.assertEqual(cr.success, True) + + # Reset the logs + self.market_logger.clear() + + def test_limit_taker_buy(self): + trading_pair = "ETH-USDT" + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.06") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, _ = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT, price, 10001, + FixtureMEXC.ORDER_GET_MARKET_BUY) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + buy_order_completed_event: BuyOrderCompletedEvent = buy_order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded: Decimal = sum(t.amount for t in trade_events) + quote_amount_traded: Decimal = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, buy_order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, buy_order_completed_event.base_asset_amount, places=4) + self.assertEqual("ETH", buy_order_completed_event.base_asset) + self.assertEqual("USDT", buy_order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, buy_order_completed_event.base_asset_amount, places=4) + self.assertAlmostEqual(quote_amount_traded, buy_order_completed_event.quote_asset_amount, places=4) + self.assertTrue(any([isinstance(event, BuyOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + self.market_logger.clear() + + def test_limit_taker_sell(self): + trading_pair = "ETH-USDT" + price: Decimal = self.market.get_price(trading_pair, False) + amount: Decimal = Decimal("0.06") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, _ = self.place_order(False, trading_pair, amount, OrderType.LIMIT, price, 10001, + FixtureMEXC.ORDER_GET_MARKET_SELL) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + sell_order_completed_event: SellOrderCompletedEvent = sell_order_completed_event + trade_events: List[OrderFilledEvent] = [t for t in self.market_logger.event_log + if isinstance(t, OrderFilledEvent)] + base_amount_traded = sum(t.amount for t in trade_events) + quote_amount_traded = sum(t.amount * t.price for t in trade_events) + + self.assertTrue([evt.order_type == OrderType.LIMIT for evt in trade_events]) + self.assertEqual(order_id, sell_order_completed_event.order_id) + self.assertAlmostEqual(quantized_amount, sell_order_completed_event.base_asset_amount) + self.assertEqual("ETH", sell_order_completed_event.base_asset) + self.assertEqual("USDT", sell_order_completed_event.quote_asset) + self.assertAlmostEqual(base_amount_traded, sell_order_completed_event.base_asset_amount) + self.assertAlmostEqual(quote_amount_traded, sell_order_completed_event.quote_asset_amount) + self.assertGreater(sell_order_completed_event.fee_amount, Decimal(0)) + self.assertTrue(any([isinstance(event, SellOrderCreatedEvent) and event.order_id == order_id + for event in self.market_logger.event_log])) + self.market_logger.clear() + + def test_cancel_order(self): + trading_pair = "ETH-USDT" + + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.05") + + bid_price: Decimal = current_bid_price - Decimal("0.1") * current_bid_price + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, 10001, FixtureMEXC.ORDER_GET_LIMIT_BUY_UNFILLED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureMEXC.ORDER_GET_CANCELED) + [order_cancelled_event] = self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + order_cancelled_event: OrderCancelledEvent = order_cancelled_event + self.assertEqual(order_cancelled_event.order_id, order_id) + + def test_cancel_all(self): + trading_pair = "ETH-USDT" + + bid_price: Decimal = self.market_2.get_price(trading_pair, True) * Decimal("0.5") + ask_price: Decimal = self.market_2.get_price(trading_pair, False) * 2 + amount: Decimal = Decimal("0.06") + quantized_amount: Decimal = self.market_2.quantize_order_amount(trading_pair, amount) + + # Intentionally setting invalid price to prevent getting filled + quantize_bid_price: Decimal = self.market_2.quantize_order_price(trading_pair, bid_price * Decimal("0.9")) + quantize_ask_price: Decimal = self.market_2.quantize_order_price(trading_pair, ask_price * Decimal("1.1")) + + _, exch_order_id1 = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, + 1001, FixtureMEXC.ORDER_GET_LIMIT_BUY_UNFILLED, self.market_2) + _, exch_order_id2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_ask_price, + 1002, FixtureMEXC.ORDER_GET_LIMIT_BUY_FILLED, self.market_2) + self.run_parallel(asyncio.sleep(1)) + if MOCK_API_ENABLED: + resp = FixtureMEXC.ORDERS_BATCH_CANCELED.copy() + resp["data"][0]["ordId"] = exch_order_id1 + self.web_app.update_response("delete", API_BASE_URL, '/' + MEXC_BATCH_ORDER_CANCEL, resp) + + [cancellation_results] = self.run_parallel(self.market_2.cancel_all(5)) + for cr in cancellation_results: + self.assertEqual(cr.success, '0') + + def test_orders_saving_and_restoration(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDT" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + self.assertEqual(0, len(self.market.tracking_states)) + + # Try to put limit buy order for 0.04 ETH, and watch for order creation event. + current_bid_price: Decimal = self.market.get_price(trading_pair, True) + bid_price: Decimal = current_bid_price * Decimal("0.8") + quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price) + + amount: Decimal = Decimal("0.06") + quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount) + + order_id, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, + quantize_bid_price, 10001, + FixtureMEXC.ORDER_GET_LIMIT_BUY_UNFILLED) + [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) + order_created_event: BuyOrderCreatedEvent = order_created_event + # self.assertEqual(order_id, order_created_event.order_id) + + # Verify tracking states + self.assertEqual(1, len(self.market.tracking_states)) + self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) + + # Verify orders from recorder + recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) + self.assertEqual(1, len(recorded_orders)) + self.assertEqual(order_id, recorded_orders[0].id) + + # Verify saved market states + saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) + self.assertIsNotNone(saved_market_states) + self.assertIsInstance(saved_market_states.saved_state, dict) + self.assertGreater(len(saved_market_states.saved_state), 0) + + # Close out the current market and start another market. + self.clock.remove_iterator(self.market) + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market: MexcExchange = MexcExchange( + API_KEY, + API_SECRET, + trading_pairs=["ETH-USDT"] + ) + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + recorder.stop() + recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + saved_market_states = recorder.get_market_states(config_path, self.market) + self.clock.add_iterator(self.market) + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + self.market.restore_tracking_states(saved_market_states.saved_state) + self.assertEqual(1, len(self.market.limit_orders)) + self.assertEqual(1, len(self.market.tracking_states)) + + # Cancel the order and verify that the change is saved. + self.cancel_order(trading_pair, order_id, exch_order_id, FixtureMEXC.ORDER_GET_CANCELED) + # saved_market_states2 = recorder.get_market_states(config_path, self.market) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + # saved_market_states3 = recorder.get_market_states(config_path, self.market) + order_id = None + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(self.market.tracking_states)) + saved_market_states = recorder.get_market_states(config_path, self.market) + self.assertEqual(0, len(saved_market_states.saved_state)) + finally: + if order_id is not None: + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_order_fill_record(self): + config_path: str = "test_config" + strategy_name: str = "test_strategy" + trading_pair: str = "ETH-USDT" + sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) + order_id: Optional[str] = None + recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) + recorder.start() + + try: + # Try to buy 0.04 ETH from the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, True) + amount: Decimal = Decimal("0.06") + order_id, _ = self.place_order(True, trading_pair, amount, OrderType.LIMIT, price, 10001, + FixtureMEXC.ORDER_GET_MARKET_BUY) + [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + # Reset the logs + self.market_logger.clear() + + # Try to sell back the same amount of ETH to the exchange, and watch for completion event. + price: Decimal = self.market.get_price(trading_pair, False) + amount = buy_order_completed_event.base_asset_amount + order_id, _ = self.place_order(False, trading_pair, amount, OrderType.LIMIT, price, 10002, + FixtureMEXC.ORDER_GET_MARKET_SELL) + [sell_order_completed_event] = self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + # Query the persisted trade logs + trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path) + self.assertEqual(2, len(trade_fills)) + buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"] + sell_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "SELL"] + self.assertEqual(1, len(buy_fills)) + self.assertEqual(1, len(sell_fills)) + + order_id = None + + finally: + if order_id is not None: + self.market.cancel(trading_pair, order_id) + self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) + + recorder.stop() + os.unlink(self.db_path) + + def test_update_last_prices(self): + # This is basic test to see if order_book last_trade_price is initiated and updated. + for order_book in self.market.order_books.values(): + for _ in range(5): + self.ev_loop.run_until_complete(asyncio.sleep(1)) + self.assertFalse(math.isnan(order_book.last_trade_price)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/connector/test_in_flight_order_base.py b/test/connector/test_in_flight_order_base.py new file mode 100644 index 0000000..79a07f1 --- /dev/null +++ b/test/connector/test_in_flight_order_base.py @@ -0,0 +1,149 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.in_flight_order import OrderState +from hummingbot.core.event.events import LimitOrderStatus, OrderType, TradeType + + +class InFlightOrderBaseTests(TestCase): + + def test_string_repr(self): + order = InFlightOrderBase( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + initial_state=OrderState.PENDING_CREATE.name, + creation_timestamp=1640001112.0 + ) + + expected_repr = ("InFlightOrder(client_order_id='OID1', exchange_order_id='EOID1', " + "creation_timestamp=1640001112, trading_pair='COINALPHA-HBOT', order_type=OrderType.LIMIT, " + "trade_type=TradeType.BUY, price=1000, amount=1, executed_amount_base=0, " + "executed_amount_quote=0, fee_asset='None', fee_paid=0, last_state='PENDING_CREATE')") + + self.assertEqual(expected_repr, repr(order)) + + def test_get_creation_timestamp(self): + order = InFlightOrderBase( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + initial_state=OrderState.PENDING_CREATE.name, + creation_timestamp=1640001112.0 + ) + + self.assertEqual(1640001112, order.creation_timestamp) + + def test_creation_timestamp_taken_from_order_id_when_not_specified(self): + order = InFlightOrderBase( + client_order_id="OID1-1640001112223334", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + creation_timestamp=-1, + initial_state=OrderState.PENDING_CREATE.name + ) + + self.assertEqual(1640001112.223334, order.creation_timestamp) + + def test_serialize_order_to_json(self): + order = InFlightOrderBase( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + initial_state=OrderState.PENDING_CREATE.name, + creation_timestamp=1640001112.0 + ) + + expected_json = { + "client_order_id": order.client_order_id, + "exchange_order_id": order.exchange_order_id, + "trading_pair": order.trading_pair, + "order_type": order.order_type.name, + "trade_type": order.trade_type.name, + "price": str(order.price), + "amount": str(order.amount), + "executed_amount_base": str(order.executed_amount_base), + "executed_amount_quote": str(order.executed_amount_quote), + "fee_asset": order.fee_asset, + "fee_paid": str(order.fee_paid), + "last_state": order.last_state, + "creation_timestamp": order.creation_timestamp + } + + self.assertEqual(expected_json, order.to_json()) + + def test_deserialize_order_from_json(self): + json = { + "client_order_id": "OID1", + "exchange_order_id": "EOID", + "trading_pair": "COINALPHA-HBOT", + "order_type": OrderType.LIMIT.name, + "trade_type": TradeType.BUY.name, + "price": "1000.0", + "amount": "1.0", + "executed_amount_base": "0.5", + "executed_amount_quote": "510.0", + "fee_asset": "BNB", + "fee_paid": "10.0", + "last_state": OrderState.PARTIALLY_FILLED.name, + "creation_timestamp": 1640001112.0 + } + + order = InFlightOrderBase.from_json(json) + + self.assertEqual(json["client_order_id"], order.client_order_id) + self.assertEqual(json["exchange_order_id"], order.exchange_order_id) + self.assertEqual(json["trading_pair"], order.trading_pair) + self.assertEqual(OrderType.LIMIT, order.order_type) + self.assertEqual(TradeType.BUY, order.trade_type) + self.assertEqual(Decimal(json["price"]), order.price) + self.assertEqual(Decimal(json["amount"]), order.amount) + self.assertEqual(Decimal(json["executed_amount_base"]), order.executed_amount_base) + self.assertEqual(Decimal(json["executed_amount_quote"]), order.executed_amount_quote) + self.assertEqual(json["fee_asset"], order.fee_asset) + self.assertEqual(Decimal(json["fee_paid"]), order.fee_paid) + self.assertEqual(OrderState.PARTIALLY_FILLED.name, order.last_state) + self.assertEqual(json["creation_timestamp"], order.creation_timestamp) + + def test_to_limit_order(self): + order = InFlightOrderBase( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + initial_state=OrderState.PENDING_CREATE.name, + creation_timestamp=1640001112.223330 + ) + + limit_order = order.to_limit_order() + + self.assertEqual("OID1", limit_order.client_order_id) + self.assertEqual("COINALPHA-HBOT", limit_order.trading_pair) + self.assertTrue(limit_order.is_buy) + self.assertEqual("COINALPHA", limit_order.base_currency) + self.assertEqual("HBOT", limit_order.quote_currency) + self.assertEqual(Decimal(1000), limit_order.price) + self.assertEqual(Decimal(1), limit_order.quantity) + self.assertTrue(limit_order.filled_quantity.is_nan()) + self.assertEqual(1640001112223330, limit_order.creation_timestamp) + self.assertEqual(LimitOrderStatus.UNKNOWN, limit_order.status) diff --git a/test/connector/test_parrot.py b/test/connector/test_parrot.py new file mode 100644 index 0000000..0c8491b --- /dev/null +++ b/test/connector/test_parrot.py @@ -0,0 +1,29 @@ +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../../"))) +import unittest +import asyncio +from hummingbot.connector.parrot import get_active_campaigns, get_campaign_summary + + +class ParrotConnectorUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + + def test_get_active_campaigns(self): + self.ev_loop.run_until_complete(self._test_get_active_campaigns()) + + async def _test_get_active_campaigns(self): + results = await get_active_campaigns("binance") + self.assertGreater(len(results), 0) + for result in results.values(): + print(result) + + def test_get_campaign_summary(self): + self.ev_loop.run_until_complete(self._test_get_campaign_summary()) + + async def _test_get_campaign_summary(self): + results = await get_campaign_summary("binance", ["RLC-BTC", "RLC-ETH"]) + self.assertLessEqual(len(results), 2) + for result in results.values(): + print(result) diff --git a/test/debug/__init__.py b/test/debug/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/debug/debug_aiohttp_gather.py b/test/debug/debug_aiohttp_gather.py new file mode 100644 index 0000000..b3392be --- /dev/null +++ b/test/debug/debug_aiohttp_gather.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +import aiohttp +import asyncio +import pandas as pd +import random +import time +from typing import ( + NamedTuple, + List, + Dict, + Optional +) +from hummingbot.core.utils.async_utils import safe_gather + +shared_client_session: Optional[aiohttp.ClientSession] = None + + +class FetchTask(NamedTuple): + nonce: int + future: asyncio.Future + + @classmethod + def create_task(cls): + global shared_client_session + nonce: int = random.randint(0, 0xffffffff) + future: asyncio.Future = shared_client_session.get("https://postman-echo.com/get", params={"nonce": nonce}) + return FetchTask(nonce, future) + + +async def init_client(): + global shared_client_session + if shared_client_session is None: + shared_client_session = aiohttp.ClientSession() + + +async def generate_tasks(length: int) -> List[FetchTask]: + return [FetchTask.create_task() for _ in range(0, length)] + + +async def main(): + await init_client() + + while True: + try: + tasks: List[FetchTask] = await generate_tasks(10) + results: List[aiohttp.ClientResponse] = await safe_gather(*[t.future for t in tasks]) + data: List[Dict[str, any]] = await safe_gather(*[r.json() for r in results]) + mismatches: int = 0 + + for task, response in zip(tasks, data): + returned_nonce: int = int(response["args"]["nonce"]) + if task.nonce != returned_nonce: + print(f" - Error: requested for {task.nonce} but got {returned_nonce} back.") + mismatches += 1 + + if mismatches < 1: + print(f"[{str(pd.Timestamp.utcnow())}] All fetches passed.") + else: + print(f"[{str(pd.Timestamp.utcnow())}] {mismatches} out of 10 requests failed.") + + now: float = time.time() + next_tick: float = now // 1 + 1 + await asyncio.sleep(next_tick - now) + except asyncio.CancelledError: + raise + + +if __name__ == "__main__": + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + try: + ev_loop.run_until_complete(main()) + except KeyboardInterrupt: + print("Done!") diff --git a/test/debug/debug_arbitrage.py b/test/debug/debug_arbitrage.py new file mode 100644 index 0000000..4ebb2bb --- /dev/null +++ b/test/debug/debug_arbitrage.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +import faulthandler; faulthandler.enable() +import sys +import os; sys.path.insert(0, os.path.realpath(os.path.join(__file__, "../../"))) +import logging; logging.basicConfig(level=logging.INFO) +import pandas as pd +import hummingsim +from hummingsim.backtest.backtest_market import BacktestMarket +from hummingsim.backtest.binance_order_book_loader_v2 import BinanceOrderBookLoaderV2 +from hummingsim.backtest.ddex_order_book_loader import DDEXOrderBookLoader +from hummingsim.backtest.market import QuantizationParams +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingsim.backtest.market_config import ( + MarketConfig, + AssetType +) +from hummingbot.strategy.arbitrage import ( + ArbitrageStrategy, + ArbitrageMarketPair +) + +# Define the data cache path. +hummingsim.set_data_path(os.path.join(os.environ["PWD"], "data")) + +# Define the parameters for the backtest. +start = pd.Timestamp("2018-12-21-00:29:06", tz="UTC") +end = pd.Timestamp("2019-12-24-00:43:00", tz="UTC") +binance_trading_pair = ("ETHUSDT", "ETH", "USDT") +ddex_trading_pair = ("WETH-DAI", "WETH", "DAI") + + +binance_market = BacktestMarket() +ddex_market = BacktestMarket() +binance_loader = BinanceOrderBookLoaderV2(*binance_trading_pair) +ddex_loader = DDEXOrderBookLoader(*ddex_trading_pair) + + +binance_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) +ddex_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) + +binance_market.add_data(binance_loader) +ddex_market.add_data(ddex_loader) + +binance_market.set_quantization_param(QuantizationParams("ETHUSDT", 5, 3, 5, 3)) +ddex_market.set_quantization_param(QuantizationParams("WETH-DAI", 5, 3, 5, 3)) + +market_pair1 = ArbitrageMarketPair(*([ddex_market] + list(ddex_trading_pair) + [binance_market] + list(binance_trading_pair))) + +strategy = ArbitrageStrategy([market_pair1], 0.025, + logging_options=ArbitrageStrategy.OPTION_LOG_CREATE_ORDER) + +clock = Clock(ClockMode.BACKTEST, start_time=start.timestamp(), end_time=end.timestamp()) +clock.add_iterator(binance_market) +clock.add_iterator(ddex_market) +clock.add_iterator(strategy) + + +binance_market.set_balance("ETH", 100.0) +binance_market.set_balance("USDT", 10000.0) +ddex_market.set_balance("WETH", 100.0) +ddex_market.set_balance("DAI", 10000.0) + +clock.backtest_til(start.timestamp() + 1) + +ddex_weth_price = ddex_market.get_price("WETH-DAI", False) +binance_eth_price = binance_market.get_price("ETHUSDT", False) +start_ddex_portfolio_value = ddex_market.get_balance("DAI") + ddex_market.get_balance("WETH") * ddex_weth_price +start_binance_portfolio_value = binance_market.get_balance("USDT") + binance_market.get_balance("ETH") * binance_eth_price +print(f"start DDEX portfolio value: {start_ddex_portfolio_value}\n" + f"start Binance portfolio value: {start_binance_portfolio_value}") + +clock.backtest_til(end.timestamp()) + +ddex_weth_price = ddex_market.get_price("WETH-DAI", False) +binance_eth_price = binance_market.get_price("ETHUSDT", False) +ddex_portfolio_value = ddex_market.get_balance("DAI") + ddex_market.get_balance("WETH") * ddex_weth_price +binance_portfolio_value = binance_market.get_balance("USDT") + binance_market.get_balance("ETH") * binance_eth_price +print(f"DDEX portfolio value: {ddex_portfolio_value}\nBinance portfolio value: {binance_portfolio_value}\n") +print(f"DDEX balances: {ddex_market.get_all_balances()}\nBinance balances: {binance_market.get_all_balances()}") + +print(f"start DDEX portfolio value: {start_ddex_portfolio_value}\n" + f"start Binance portfolio value: {start_binance_portfolio_value}") + +print(f"Profit DDEX {ddex_portfolio_value/start_ddex_portfolio_value}\n" + f"Profit Binance {binance_portfolio_value/start_binance_portfolio_value}\n" + f"Profit Total " + f"{(ddex_portfolio_value + binance_portfolio_value)/(start_ddex_portfolio_value + start_binance_portfolio_value)}") diff --git a/test/debug/debug_cross_exchange_market_making.py b/test/debug/debug_cross_exchange_market_making.py new file mode 100644 index 0000000..abd46a9 --- /dev/null +++ b/test/debug/debug_cross_exchange_market_making.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +import sys +import os; sys.path.insert(0, os.path.realpath(os.path.join(__file__, "../../"))) +import logging; logging.basicConfig(level=logging.DEBUG) +import pandas as pd +import hummingsim +from hummingsim.backtest.backtest_market import BacktestMarket +from hummingsim.backtest.binance_order_book_loader_v2 import BinanceOrderBookLoaderV2 +from hummingsim.backtest.market import QuantizationParams +from hummingsim.backtest.market_config import ( + MarketConfig, + AssetType +) +from hummingsim.backtest.ddex_order_book_loader import DDEXOrderBookLoader +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.strategy.cross_exchange_market_making import ( + CrossExchangeMarketMakingStrategy, + CrossExchangeMarketPair, +) + + +def main(): + # Define the data cache path. + hummingsim.set_data_path(os.path.join(os.environ["PWD"], "data")) + + # Define the parameters for the backtest. + start = pd.Timestamp("2018-12-12", tz="UTC") + end = pd.Timestamp("2019-01-12", tz="UTC") + binance_trading_pair = ("ETHUSDT", "ETH", "USDT") + ddex_trading_pair = ("WETH-DAI", "WETH", "DAI") + + binance_market = BacktestMarket() + ddex_market = BacktestMarket() + binance_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) + ddex_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) + binance_loader = BinanceOrderBookLoaderV2(*binance_trading_pair) + ddex_loader = DDEXOrderBookLoader(*ddex_trading_pair) + + binance_market.add_data(binance_loader) + ddex_market.add_data(ddex_loader) + + binance_market.set_quantization_param(QuantizationParams("ETHUSDT", 5, 3, 5, 3)) + ddex_market.set_quantization_param(QuantizationParams("WETH-DAI", 5, 3, 5, 3)) + + market_pair = CrossExchangeMarketPair(*( + [ddex_market] + list(ddex_trading_pair) + [binance_market] + list(binance_trading_pair))) + + strategy = CrossExchangeMarketMakingStrategy( + [market_pair], 0.003, + logging_options= + CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_FILLED) + + clock = Clock(ClockMode.BACKTEST, start_time=start.timestamp(), end_time=end.timestamp()) + clock.add_iterator(binance_market) + clock.add_iterator(ddex_market) + clock.add_iterator(strategy) + + binance_market.set_balance("ETH", 10.0) + binance_market.set_balance("USDT", 1000.0) + ddex_market.set_balance("WETH", 10.0) + ddex_market.set_balance("DAI", 1000.0) + + clock.backtest() + binance_loader.close() + ddex_loader.close() + + +if __name__ == "__main__": + main() diff --git a/test/debug/fixture_configs.py b/test/debug/fixture_configs.py new file mode 100644 index 0000000..ebc3def --- /dev/null +++ b/test/debug/fixture_configs.py @@ -0,0 +1,31 @@ +class FixtureConfigs: + in_mem_new_pass_configs = [ + {"prompt": "Enter your new password >>> ", "input": "a"}, + {"prompt": "Please reenter your password >>> ", "input": "a"}, + {"prompt": "Import previous configs or create a new config file? (import/create) >>> ", "input": "create"} + ] + + pure_mm_basic_responses = { + "exchange": "binance", + "market": "LINK-ETH", + "bid_spread": "1", + "ask_spread": "1", + "order_refresh_time": "", + "order_amount": "4", + "advanced_mode": "Hell No!" + } + + global_binance_config = { + "binance_api_key": "", + "binance_api_secret": "", + "kill_switch_enabled": "no", + "send_error_logs": "no" + } + + in_mem_existing_pass_import_configs = [ + {"prompt": "Import previous configs or create a new config file? (import/create) >>> ", "input": "import"} + ] + + in_mem_existing_pass_create_configs = [ + {"prompt": "Import previous configs or create a new config file? (import/create) >>> ", "input": "create"} + ] diff --git a/test/debug/huobi_mock_api.py b/test/debug/huobi_mock_api.py new file mode 100644 index 0000000..460d856 --- /dev/null +++ b/test/debug/huobi_mock_api.py @@ -0,0 +1,289 @@ +from aiohttp import web + + +class HuobiMockAPI: + MOCK_HUOBI_USER_ID = 10000000 + MOCK_HUOBI_LIMIT_BUY_ORDER_ID = 11111 + MOCK_HUOBI_LIMIT_SELL_ORDER_ID = 22222 + MOCK_HUOBI_MARKET_BUY_ORDER_ID = 33333 + MOCK_HUOBI_MARKET_SELL_ORDER_ID = 44444 + MOCK_HUOBI_LIMIT_CANCEL_ORDER_ID = 55555 + MOCK_HUOBI_LIMIT_OPEN_ORDER_ID = 66666 + MOCK_HUOBI_LIMIT_BUY_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_LIMIT_BUY_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "0.020000000000000000", + "price": "189.770000000000000000", + "created-at": 1570494069606, + "type": "buy-limit", + "field-amount": "0.020000000000000000", + "field-cash-amount": "3.614600000000000000", + "field-fees": "0.000040000000000000", + "finished-at": 1570494069689, + "user-id": MOCK_HUOBI_USER_ID, + "source": "spot-api", + "state": "filled", + "canceled-at": 0 + } + } + MOCK_HUOBI_LIMIT_SELL_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_LIMIT_SELL_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "0.020000000000000000", + "price": "189.770000000000000000", + "created-at": 1570494069606, + "type": "sell-limit", + "field-amount": "0.020000000000000000", + "field-cash-amount": "3.614600000000000000", + "field-fees": "0.000040000000000000", + "finished-at": 1570494069689, + "user-id": MOCK_HUOBI_USER_ID, + "source": "spot-api", + "state": "filled", + "canceled-at": 0 + } + } + MOCK_HUOBI_MARKET_BUY_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_LIMIT_BUY_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "3.580000000000000000", + "price": "0.0", + "created-at": 1570571586091, + "type": "buy-market", + "field-amount": "0.020024611254055263", + "field-cash-amount": "3.579999999999999919", + "field-fees": "0.000040049222508111", + "finished-at": 1570571586178, + "source": "spot-api", + "state": "filled", + "canceled-at": 0 + } + } + MOCK_HUOBI_MARKET_SELL_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_MARKET_SELL_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "0.020000000000000000", + "price": "0.0", + "created-at": 1570494069606, + "type": "sell-market", + "field-amount": "0.020000000000000000", + "field-cash-amount": "3.614600000000000000", + "field-fees": "0.000040000000000000", + "finished-at": 1570494069689, + "user-id": MOCK_HUOBI_USER_ID, + "source": "spot-api", + "state": "filled", + "canceled-at": 0 + } + } + MOCK_HUOBI_LIMIT_CANCEL_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_LIMIT_CANCEL_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "0.020000000000000000", + "price": "162.670000000000000000", + "created-at": 1570575422098, + "type": "buy-limit", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 1570575423650, + "source": "spot-api", + "state": "submitted", + "canceled-at": 1570575423600 + } + } + MOCK_HUOBI_LIMIT_OPEN_RESPONSE = { + "status": "ok", + "data": { + "id": MOCK_HUOBI_LIMIT_OPEN_ORDER_ID, + "symbol": "ethusdt", + "account-id": 10055506, + "amount": "0.040000000000000000", + "price": "162.670000000000000000", + "created-at": 1570575422098, + "type": "buy-limit", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 1570575423650, + "source": "spot-api", + "state": "submitted", + "canceled-at": 1570575423600 + } + } + + def __init__(self): + self.order_id = None + self.cancel_all_order_ids = [] + self.order_response_dict = { + self.MOCK_HUOBI_LIMIT_BUY_ORDER_ID: self.MOCK_HUOBI_LIMIT_BUY_RESPONSE, + self.MOCK_HUOBI_LIMIT_SELL_ORDER_ID: self.MOCK_HUOBI_LIMIT_SELL_RESPONSE, + self.MOCK_HUOBI_MARKET_BUY_ORDER_ID: self.MOCK_HUOBI_MARKET_BUY_RESPONSE, + self.MOCK_HUOBI_MARKET_SELL_ORDER_ID: self.MOCK_HUOBI_MARKET_SELL_RESPONSE, + self.MOCK_HUOBI_LIMIT_CANCEL_ORDER_ID: self.MOCK_HUOBI_LIMIT_CANCEL_RESPONSE, + self.MOCK_HUOBI_LIMIT_OPEN_ORDER_ID: self.MOCK_HUOBI_LIMIT_OPEN_RESPONSE + } + + async def get_mock_snapshot(self, _): + return web.json_response({ + "ch": "market.ethusdt.depth.step0", + "ts": 1570486543309, + "tick": { + "bids": [ + [ + 100.21, + 23.5445 + ], + [ + 100.2, + 86.4019 + ], + [ + 100.17, + 6.1261 + ], + [ + 100.16, + 10.0 + ], + [ + 100.14, + 8.0 + ] + ], + "asks": [ + [ + 100.24, + 2.3602 + ], + [ + 100.25, + 15.1513 + ], + [ + 100.27, + 19.1565 + ], + [ + 100.29, + 12.0 + ], + [ + 100.3, + 23.3643 + ] + ], + "version": 102339771356, + "ts": 1570486543009 + } + }) + + async def get_market_tickers(self, _): + response = { + "status": "ok", + "ts": 1570060262253, + "data": [{ + "symbol": "ethusdt", + "open": 175.57, + "high": 181, + "low": 175, + "close": 180.11, + "amount": 330265.5220692477, + "vol": 58300213.797686026, + "count": 93755 + }] + } + return web.json_response(response, status=200) + + async def get_account_accounts(self, _): + response = { + "status": "ok", + "data": [{ + "id": self.MOCK_HUOBI_USER_ID, + "type": "spot", + "subtype": "", + "state": "working" + }] + } + return web.json_response(response, status=200) + + async def get_common_timestamp(self, _): + response = {"status": "ok", "data": 1569445000000} + return web.json_response(response, status=200) + + async def get_common_symbols(self, _): + response = { + "status": "ok", + "data": [ + { + "base-currency": "eth", + "quote-currency": "usdt", + "price-precision": 2, + "amount-precision": 4, + "symbol-partition": "main", + "symbol": "ethusdt", + "state": "online", + "value-precision": 8, + "min-order-amt": 0.001, + "max-order-amt": 10000, + "min-order-value": 1 + } + ] + } + return web.json_response(response, status=200) + + async def get_user_balance(self, _): + response = { + "status": "ok", + "data": { + "id": self.MOCK_HUOBI_USER_ID, + "type": "spot", + "state": "working", + "list": [{ + "currency": "eth", + "type": "trade", + "balance": "0.259942948171422263" + }] + } + } + return web.json_response(response, status=200) + + async def post_order_place(self, req: web.Request): + response = { + "status": "ok", + "data": self.order_id + } + return web.json_response(response, status=200) + + async def post_submit_cancel(self, _): + response = { + "status": "ok", + "data": self.order_id + } + return web.json_response(response, status=200) + + async def get_order_update(self, _): + response = self.order_response_dict[self.order_id] + return web.json_response(response, status=200) + + async def post_batch_cancel(self, _): + response = { + "status": "ok", + "data": {"success": self.cancel_all_order_ids, "failed": []} + } + return web.json_response(response, status=200) diff --git a/test/debug/test_composite_order_book.py b/test/debug/test_composite_order_book.py new file mode 100644 index 0000000..1955e82 --- /dev/null +++ b/test/debug/test_composite_order_book.py @@ -0,0 +1,240 @@ +import unittest + +import pandas as pd + +from hummingbot.core.clock import ( + ClockMode, + Clock +) +from hummingbot.core.data_type.common import TradeType + + +# class CompositeOrderBookTestStrategy(UnitTestStrategy): +# """ +# Makes market orders and record fill events +# """ +# +# class OrderFilledEventLogger(EventListener): +# def __init__(self, owner: "CompositeOrderBookTestStrategy"): +# self._owner: "CompositeOrderBookTestStrategy" = owner +# +# def __call__(self, order_filled_event: OrderFilledEvent): +# self._owner.log_order_filled_event(order_filled_event) +# +# def __init__(self, market: Market, trades: Dict[str, Tuple[str, float]]): +# super().__init__(market) +# self.trades = trades +# self.tick_size = 5 +# self._order_filled_event_timestamps: List[float] = [] +# self._order_filled_events: List[OrderFilledEvent] = [] +# self._trade_logger: CompositeOrderBookTestStrategy.OrderFilledEventLogger = self.OrderFilledEventLogger(self) +# market.add_listener(MarketEvent.OrderFilled, self._trade_logger) +# +# self.start_printing = False +# +# def log_order_filled_event(self, evt: OrderFilledEvent): +# self._order_filled_event_timestamps.append(self.current_timestamp) +# self._order_filled_events.append(evt) +# +# def process_tick(self): +# if self.current_timestamp in self.trades: +# for trade in self.trades[self.current_timestamp]: +# if trade[1] == "buy": +# self.market.buy(trade[0], trade[2]) +# elif trade[1] == "sell": +# self.market.sell(trade[0], trade[2]) +# self.start_printing = True +# +# composite_ob = self.market.get_order_book("WETH-DAI") +# composite_bids = list(composite_ob.bid_entries()) +# composite_asks = list(composite_ob.ask_entries()) +# +# if not self.start_printing: +# return +# +# original_bids = list(composite_ob.original_bid_entries()) +# original_asks = list(composite_ob.original_ask_entries()) +# +# filled_bids = list(composite_ob.traded_order_book.bid_entries()) +# filled_asks = list(composite_ob.traded_order_book.ask_entries()) +# +# order_books_top = [composite_bids[i] + composite_asks[i] + original_bids[i] + original_asks[i] for i in range(5)] +# +# filled_order_books = [] +# for i in range(max(len(filled_bids), len(filled_asks))): +# if i + 1 > len(filled_bids): +# fb = (None, None, None) +# else: +# fb = filled_bids[i] +# if i + 1 > len(filled_asks): +# fa = (None, None, None) +# else: +# fa = filled_asks[i] +# filled_order_books.append(fb + fa) +# +# print(str(pd.Timestamp(self.current_timestamp, unit="s", tz="UTC")) + "\n" + +# pd.DataFrame(data=filled_order_books, +# columns=['filled_bid_price', 'filled_bid_amount', 'uid', +# 'filled_ask_price', 'filled_ask_amount', 'uid' +# ] +# ).to_string() +# ) +# +# print(str(pd.Timestamp(self.current_timestamp, unit="s", tz="UTC")) + "\n" + +# pd.DataFrame(data=order_books_top, +# columns=['composite_bid_price', 'composite_bid_amount', 'uid', +# 'composite_ask_price', 'composite_ask_amount', 'uid', +# 'original_bid_price', 'original_bid_amount', 'uid', +# 'original_ask_price', 'original_ask_amount', 'uid', +# ] +# ).to_string() +# ) +# +# @property +# def order_filled_events(self) -> pd.DataFrame: +# retval: pd.DataFrame = pd.DataFrame(data=self._order_filled_events, +# columns=OrderFilledEvent._fields, +# index=pd.Index(self._order_filled_event_timestamps, dtype="float64")) +# retval.index = (retval.index * 1e9).astype("int64").astype("datetime64[ns]") +# return retval + +@unittest.skip("The test seems to be out of date. It requires the hummingsim component that is not present") +class CompositeOrderBookTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-25", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-26", tz="UTC") + + def setUp(self): + # self.weth_dai_data = DDEXOrderBookLoader("WETH-DAI", "WETH", "DAI") + self.clock = Clock(ClockMode.BACKTEST, 1.0, self.start.timestamp(), self.end.timestamp()) + # self.market = BacktestMarket() + self.market.add_data(self.weth_dai_data) + self.market.set_balance("WETH", 200.0) + self.market.set_balance("DAI", 20000.0) + self.clock.add_iterator(self.market) + + def tearDown(self): + self.weth_dai_data.close() + + def verify_filled_order_recorded(self, recorded_filled_events, composite_order_book): + bid_dict = {entry.price: (entry.amount, entry.update_id) + for entry in composite_order_book.traded_order_book.bid_entries()} + ask_dict = {entry.price: (entry.amount, entry.update_id) + for entry in composite_order_book.traded_order_book.ask_entries()} + for index, fill_event in recorded_filled_events.iterrows(): + if fill_event.trade_type is TradeType.SELL: + self.assertTrue(fill_event.price in bid_dict) + self.assertTrue(bid_dict[fill_event.price][0] == fill_event.amount) + self.assertTrue(bid_dict[fill_event.price][1] == fill_event.timestamp) + elif fill_event.trade_type is TradeType.BUY: + self.assertTrue(fill_event.price in ask_dict) + self.assertTrue(ask_dict[fill_event.price][0] == fill_event.amount) + self.assertTrue(ask_dict[fill_event.price][1] == fill_event.timestamp) + + def verify_composite_order_book_correctness(self, composite_order_book): + filled_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.bid_entries()} + filled_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.ask_entries()} + + composite_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.bid_entries()} + composite_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.ask_entries()} + + original_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_bid_entries()} + original_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_ask_entries()} + + for filled_bid_price, filled_bid_amount in filled_bid_dict.items(): + if filled_bid_price in original_bid_dict: + if (original_bid_dict[filled_bid_price] - filled_bid_amount) <= 0: + self.assertTrue(filled_bid_price not in composite_bid_dict) + else: + self.assertTrue(composite_bid_dict[filled_bid_price] == + original_bid_dict[filled_bid_price] - filled_bid_amount) + + for filled_ask_price, filled_ask_amount in filled_ask_dict.items(): + if filled_ask_price in original_ask_dict: + if (original_bid_dict[filled_ask_price] - filled_ask_amount) <= 0: + self.assertTrue(filled_ask_price not in composite_ask_dict) + else: + self.assertTrue(composite_bid_dict[filled_ask_price] == + original_bid_dict[filled_ask_price] - filled_ask_amount) + + def verify_composite_order_book_cleanup(self, recorded_filled_events, composite_order_book): + """ + Recorded fill order should be cleaned up when the original order book no longer contain that price entry + """ + filled_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.bid_entries()} + filled_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.ask_entries()} + + original_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_bid_entries()} + original_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_ask_entries()} + + for index, fill_event in recorded_filled_events.iterrows(): + if fill_event.trade_type is TradeType.SELL: + if fill_event.price not in original_bid_dict: + self.assertTrue(fill_event.price not in filled_bid_dict) + + elif fill_event.trade_type is TradeType.BUY: + if fill_event.price not in original_ask_dict: + self.assertTrue(fill_event.price not in filled_ask_dict) + + def verify_composite_order_book_adjustment(self, composite_order_book): + """ + Recorded fill order sohuld adjust it's amount to no larger than the original price entries' amount + """ + filled_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.bid_entries()} + filled_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.traded_order_book.ask_entries()} + + original_bid_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_bid_entries()} + original_ask_dict = {o.price: (o.amount, o.update_id) + for o in composite_order_book.original_ask_entries()} + + for filled_bid_price, filled_bid_entry in filled_bid_dict.items(): + if filled_bid_price in original_bid_dict: + self.assertTrue(original_bid_dict[filled_bid_price][0] >= filled_bid_entry[0]) + + for filled_ask_price, filled_ask_entry in filled_ask_dict.items(): + if filled_ask_price in original_ask_dict: + self.assertTrue(original_ask_dict[filled_ask_price][0] >= filled_ask_entry[0]) + + # def test_market_order(self): + # trades = { + # pd.Timestamp("2019-01-25 00:00:10+00:00").timestamp(): [ + # ("WETH-DAI", "buy", 5.0), + # ("WETH-DAI", "sell", 5.0) + # ] + # } + # strategy: CompositeOrderBookTestStrategy = CompositeOrderBookTestStrategy(self.market, trades) + # self.clock.add_iterator(strategy) + # self.clock.backtest_til(self.start.timestamp() + 10) + # + # self.verify_filled_order_recorded(strategy.order_filled_events, self.market.get_order_book("WETH-DAI")) + # self.verify_composite_order_book_correctness(self.market.get_order_book("WETH-DAI")) + # + # self.clock.backtest_til(self.start.timestamp() + 70) + # + # self.verify_composite_order_book_cleanup(strategy.order_filled_events, self.market.get_order_book("WETH-DAI")) + # + # def test_composite_order_book_adjustment(self): + # trades = { + # pd.Timestamp("2019-01-25 00:02:15+00:00").timestamp(): [ + # ("WETH-DAI", "sell", 93.53 + 23.65) + # ] + # } + # strategy: CompositeOrderBookTestStrategy = CompositeOrderBookTestStrategy(self.market, trades) + # self.clock.add_iterator(strategy) + # self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 15) + # self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 25) + # self.verify_composite_order_book_adjustment(self.market.get_order_book("WETH-DAI")) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/debug/test_config_process.py b/test/debug/test_config_process.py new file mode 100644 index 0000000..21d9cb8 --- /dev/null +++ b/test/debug/test_config_process.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +import asyncio +import inspect +import os +import time +import unittest +from os.path import join, realpath +from test.debug.fixture_configs import FixtureConfigs + +from bin.hummingbot import main as hb_main +from hummingbot.client import settings +from hummingbot.client.config.global_config_map import global_config_map +from hummingbot.client.config.security import Security +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.strategy.pure_market_making.pure_market_making_config_map import pure_market_making_config_map + +import sys; sys.path.insert(0, realpath(join(__file__, "../../"))) +import sys; sys.path.append(realpath(join(__file__, "../../bin"))) + + +async def wait_til(condition_func, timeout=10): + start_time = time.perf_counter() + while True: + if condition_func(): + return + elif time.perf_counter() - start_time > timeout: + raise Exception(f"{inspect.getsource(condition_func).strip()} condition is never met. Time out reached.") + else: + await asyncio.sleep(0.1) + + +async def wait_til_notified(text): + await wait_til(lambda: text in HummingbotApplication.main_application().app.output_field.document.lines[:-1]) + + +def user_response(text): + hb = HummingbotApplication.main_application() + hb.app.set_text(text) + hb.app.accept(None) + hb.app.set_text("") + + +def add_files_extension(folder, file_extensions, additional_extension): + for f in os.listdir(folder): + f_path = os.path.join(folder, f) + if os.path.isfile(f_path): + extension = os.path.splitext(f_path)[1] + if extension in file_extensions: + os.rename(f_path, f_path + f"{additional_extension}") + + +def remove_files_extension(folder, file_extension): + for f in os.listdir(folder): + f_path = os.path.join(folder, f) + if os.path.isfile(f_path): + extension = os.path.splitext(f_path)[1] + if extension == file_extension: + os.rename(f_path, os.path.splitext(f_path)[0]) + + +def remove_files(folder, file_extensions): + for f in os.listdir(folder): + f_path = os.path.join(folder, f) + if os.path.isfile(f_path): + extension = os.path.splitext(f_path)[1] + if extension in file_extensions: + os.remove(f_path) + + +class ConfigProcessTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ev_loop = asyncio.new_event_loop() + cls.ev_loop.run_until_complete(cls.set_up_class()) + cls.file_no = 0 + + @classmethod + def tearDownClass(cls) -> None: + remove_files(settings.STRATEGIES_CONF_DIR_PATH, [".yml", ".json"]) + remove_files_extension(settings.STRATEGIES_CONF_DIR_PATH, ".temp") + user_response("stop") + cls.ev_loop.run_until_complete(wait_til(lambda: cls.hb.markets_recorder is None)) + + @classmethod + async def set_up_class(cls): + add_files_extension(settings.STRATEGIES_CONF_DIR_PATH, [".yml", ".json"], ".temp") + asyncio.ensure_future(hb_main()) + cls.hb = HummingbotApplication.main_application() + await wait_til(lambda: 'Enter "config" to create a bot' in cls.hb.app.output_field.document.text) + + async def check_prompt_and_input(self, expected_prompt_text, input_text): + self.assertEqual(self.hb.app.prompt_text, expected_prompt_text) + last_output = str(self.hb.app.output_field.document.lines[-1]) + user_response(input_text) + await wait_til(lambda: str(self.hb.app.output_field.document.lines[-1]) != last_output) + await asyncio.sleep(0.1) + + async def _test_pure_mm_basic_til_start(self): + config_file_name = f"{settings.CONF_PREFIX}pure_market_making{settings.CONF_POSTFIX}_{self.file_no}.yml" + await self.check_prompt_and_input(">>> ", "config") + # For the second time this test is called, it's to reconfigure the bot + if self.file_no > 0: + await self.check_prompt_and_input("Would you like to reconfigure the bot? (Yes/No) >>> ", "yes") + ConfigProcessTest.file_no += 1 + fixture_in_mem = FixtureConfigs.in_mem_new_pass_configs if Security.password is None \ + else FixtureConfigs.in_mem_existing_pass_create_configs + for fixture_config in fixture_in_mem: + await self.check_prompt_and_input(fixture_config["prompt"], fixture_config["input"]) + await wait_til(lambda: f'A new config file {config_file_name}' in self.hb.app.output_field.document.text) + # configs that are required will be prompted + for config_name, response in FixtureConfigs.pure_mm_basic_responses.items(): + config = pure_market_making_config_map[config_name] + await self.check_prompt_and_input(config.prompt, response) + # advance_mode will be asked again as the previous response is not valid. + await asyncio.sleep(0.2) + self.assertEqual(self.hb.app.output_field.document.lines[-1], + f"{FixtureConfigs.pure_mm_basic_responses['advanced_mode']} " + f"is not a valid advanced_mode value") + await self.check_prompt_and_input(pure_market_making_config_map["advanced_mode"].prompt, "no") + + # input for cancel_order_wait_time is empty, check the assigned value is its default value + self.assertEqual(pure_market_making_config_map["cancel_order_wait_time"].value, + pure_market_making_config_map["cancel_order_wait_time"].default) + + # Check that configs that are not prompted get assigned correct default value + for name, config in pure_market_making_config_map.items(): + if config.default is not None and name not in FixtureConfigs.pure_mm_basic_responses: + self.assertEqual(config.value, config.default) + + # if not conf_global_file_exists: + for name, config in global_config_map.items(): + if config.required and config.value is None: + await self.check_prompt_and_input(config.prompt, FixtureConfigs.global_binance_config[name]) + + self.assertEqual(pure_market_making_config_map["mode"].value, + pure_market_making_config_map["mode"].default) + await wait_til(lambda: "Config process complete." in self.hb.app.output_field.document.text) + + def test_pure_mm_basic_til_start(self): + self.ev_loop.run_until_complete(self._test_pure_mm_basic_til_start()) + + async def _test_pure_mm_basic_import_config_file(self): + config_file_name = f"{settings.CONF_PREFIX}pure_market_making{settings.CONF_POSTFIX}_0.yml" + # update the config file to put in some blank and invalid values. + with open(os.path.join(settings.STRATEGIES_CONF_DIR_PATH, config_file_name), "r+") as f: + content = f.read() # read everything in the file + f.seek(0) # rewind + content = content.replace("bid_place_threshold: 0.01", "bid_place_threshold: ") + content = content.replace("advanced_mode: false", "advanced_mode: better not") + f.write(content) # write the new line before + await self.check_prompt_and_input(">>> ", "stop") + await self.check_prompt_and_input(">>> ", "config") + await self.check_prompt_and_input("Would you like to reconfigure the bot? (Yes/No) >>> ", "yes") + for fixture_config in FixtureConfigs.in_mem_existing_pass_import_configs: + await self.check_prompt_and_input(fixture_config["prompt"], fixture_config["input"]) + # await self.check_prompt_and_input(default_strategy_conf_path_prompt(), config_file_name) + # advanced_mode should be prompted here as its file value not valid. + await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "0.01") + await self.check_prompt_and_input(pure_market_making_config_map["advanced_mode"].prompt, "no") + await wait_til(lambda: "Config process complete." in self.hb.app.output_field.document.text) + + def test_pure_mm_basic_import_config_file(self): + self.ev_loop.run_until_complete(self._test_pure_mm_basic_import_config_file()) + + async def _test_single_configs(self): + await self.check_prompt_and_input(">>> ", "config bid_place_threshold") + # try inputting invalid value + await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "-0.01") + self.assertEqual(self.hb.app.output_field.document.lines[-1], "-0.01 is not a valid bid_place_threshold value") + await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "0.01") + + def test_single_configs(self): + self.ev_loop.run_until_complete(self._test_single_configs()) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ConfigProcessTest('test_pure_mm_basic_til_start')) + suite.addTest(ConfigProcessTest('test_single_configs')) + suite.addTest(ConfigProcessTest('test_pure_mm_basic_import_config_file')) + suite.addTest(ConfigProcessTest('test_pure_mm_basic_til_start')) + return suite + + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/test/debug/test_order_expiration.py b/test/debug/test_order_expiration.py new file mode 100644 index 0000000..9cb19b6 --- /dev/null +++ b/test/debug/test_order_expiration.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +from os.path import join, realpath +import sys; sys.path.insert(0, realpath(join(__file__, "../../../"))) +import pandas as pd +from typing import ( + List, + Dict, + Tuple) +import unittest +from hummingsim.backtest.binance_order_book_loader_v2 import BinanceOrderBookLoaderV2 +from hummingsim.backtest.backtest_market import BacktestMarket +from hummingsim.backtest.market import Market, OrderType +from hummingsim.strategy.unit_test_strategy import UnitTestStrategy +from hummingbot.core.clock import ( + ClockMode, + Clock +) +from hummingbot.core.event.events import ( + MarketEvent, + OrderExpiredEvent, +) +from hummingbot.core.event.event_listener import EventListener + + +class OrderExpirationTestStrategy(UnitTestStrategy): + """ + Makes expiring limit orders and record order expired events + """ + + class OrderFilledEventLogger(EventListener): + def __init__(self, owner: "OrderExpirationTestStrategy"): + self._owner: "OrderExpirationTestStrategy" = owner + + def __call__(self, order_expired_event: OrderExpiredEvent): + self._owner.log_order_expired_event(order_expired_event) + + def __init__(self, market: Market, trades: Dict[str, Tuple[str, float]]): + super().__init__(market) + self.trades = trades + self.tick_size = 5 + self._order_expired_event_timestamps: List[float] = [] + self._order_expired_events: List[OrderExpiredEvent] = [] + self._order_expired_logger: OrderExpirationTestStrategy.OrderFilledEventLogger = self.OrderFilledEventLogger(self) + market.add_listener(MarketEvent.OrderExpired, self._order_expired_logger) + + self.start_printing = False + + def log_order_expired_event(self, evt: OrderExpiredEvent): + self._order_expired_event_timestamps.append(self.current_timestamp) + self._order_expired_events.append(evt) + + def process_tick(self): + if self.current_timestamp in self.trades: + for trade in self.trades[self.current_timestamp]: + if trade[1] == "buy": + self.market.buy(trade[0], trade[2], order_type=OrderType.LIMIT, price=trade[3], kwargs=trade[4]) + elif trade[1] == "sell": + self.market.sell(trade[0], trade[2], order_type=OrderType.LIMIT, price=trade[3], kwargs=trade[4]) + self.start_printing = True + if not self.start_printing: + return + # print(self.order_expired_events) + # print(self.market.limit_orders) + print(self.market.order_expirations) + + @property + def order_expired_events(self) -> pd.DataFrame: + retval: pd.DataFrame = pd.DataFrame(data=self._order_expired_events, + columns=OrderExpiredEvent._fields, + index=pd.Index(self._order_expired_event_timestamps, dtype="float64")) + retval.index = (retval.index * 1e9).astype("int64").astype("datetime64[ns]") + return retval + + +class OrderExpirationTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-24", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-26", tz="UTC") + market_name = "ETHUSDT" + quote = "ETH" + base = "USDT" + + def setUp(self): + # self.weth_dai_data = DDEXOrderBookLoader("WETH-DAI", "WETH", "DAI") + self.pair_data = BinanceOrderBookLoaderV2(self.market_name, "ETH", "USDT") + # self.pair_data = HuobiOrderBookLoader(self.market_name, "", "") + self.clock = Clock(ClockMode.BACKTEST, 1.0, self.start.timestamp(), self.end.timestamp()) + self.market = BacktestMarket() + # self.market.add_data(self.weth_dai_data) + self.market.add_data(self.pair_data) + self.market.set_balance(self.quote, 200.0) + self.market.set_balance(self.base, 20000.0) + self.clock.add_iterator(self.market) + + def tearDown(self): + # self.weth_dai_data.close() + # self.eth_usd_data.close() + self.pair_data.close() + + def verify_expired_order_cleanup(self, order_expired_events, limit_orders): + """ + Recorded order expired event should indicate that these orders are no longer in the limit orders + """ + limit_order_dict = {o.client_order_id: o for o in limit_orders} + + for index, order_expired_event in order_expired_events.iterrows(): + self.assertTrue(order_expired_event.order_id not in limit_order_dict) + + def test_ask_order_expiration_clean_up(self): + ts_1 = pd.Timestamp("2019-01-24 00:02:15+00:00").timestamp() + ts_2 = pd.Timestamp("2019-01-24 00:02:20+00:00").timestamp() + trades = { + ts_1: [ + (self.market_name, "sell", 1302, 255, {"expiration_ts": ts_1 + 9}) + ], + ts_2: [ + (self.market_name, "sell", 1302, 250, {"expiration_ts": ts_2 + 9}) + ] + } + strategy: OrderExpirationTestStrategy = OrderExpirationTestStrategy(self.market, trades) + self.clock.add_iterator(strategy) + + # first limit order made + self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 15) + first_order_id = self.market.limit_orders[0].client_order_id + self.assertTrue(len(self.market.limit_orders) == 1) + self.assertTrue(first_order_id in {o.order_id: o for o in self.market.order_expirations}) + + # second limit order made + self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 20) + self.assertTrue(len(self.market.limit_orders) == 2) + + # first limit order expired + self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 25) + # check if order expired event is fired + self.assertTrue(first_order_id in [evt.order_id for i, evt in strategy.order_expired_events.iterrows()]) + # check if the expired limit order is cleaned up + self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) + + self.assertTrue(len(self.market.limit_orders) == 1) + second_order_id = self.market.limit_orders[0].client_order_id + self.assertTrue(second_order_id in {o.order_id: o for o in self.market.order_expirations}) + + # second limit order expired + self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 30) + # check if order expired event is fired + self.assertTrue(second_order_id in [evt.order_id for i, evt in strategy.order_expired_events.iterrows()]) + # check if the expired limit order is cleaned up + self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) + + def test_bid_order_expiration_clean_up(self): + ts_1 = pd.Timestamp("2019-01-24 00:12:15+00:00").timestamp() + ts_2 = pd.Timestamp("2019-01-24 00:12:20+00:00").timestamp() + + trades = { + ts_1: [ + (self.market_name, "buy", 100, 55, {"expiration_ts": ts_1 + 9}) + + ], + ts_2: [ + (self.market_name, "buy", 100, 50, {"expiration_ts": ts_2 + 9}), + (self.market_name, "buy", 100, 55, {"expiration_ts": ts_2 + 9}) + ] + } + strategy: OrderExpirationTestStrategy = OrderExpirationTestStrategy(self.market, trades) + self.clock.add_iterator(strategy) + + # first limit order made + self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 15) + first_order_id = self.market.limit_orders[0].client_order_id + self.assertTrue(len(self.market.limit_orders) == 1) + self.assertTrue(first_order_id in {o.order_id: o for o in self.market.order_expirations}) + + # second limit order made + self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 20) + self.assertTrue(len(self.market.limit_orders) == 3) + + # first limit order expired + self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 25) + # check if order expired event is fired + self.assertTrue(first_order_id in [evt.order_id for i, evt in strategy.order_expired_events.iterrows()]) + # check if the expired limit order is cleaned up + self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) + + self.assertTrue(len(self.market.limit_orders) == 2) + second_order_id_1 = self.market.limit_orders[0].client_order_id + second_order_id_2 = self.market.limit_orders[1].client_order_id + + self.assertTrue(second_order_id_1 in {o.order_id: o for o in self.market.order_expirations}) + self.assertTrue(second_order_id_2 in {o.order_id: o for o in self.market.order_expirations}) + + # second limit order expired + self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 30) + # check if order expired event is fired + self.assertTrue(second_order_id_1 in [evt.order_id for i, evt in strategy.order_expired_events.iterrows()]) + self.assertTrue(second_order_id_2 in [evt.order_id for i, evt in strategy.order_expired_events.iterrows()]) + # check if the expired limit order is cleaned up + self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/debug/test_paper_trade_market.py b/test/debug/test_paper_trade_market.py new file mode 100644 index 0000000..83c7027 --- /dev/null +++ b/test/debug/test_paper_trade_market.py @@ -0,0 +1,506 @@ +import asyncio +import contextlib +import logging +import os +import sys +import time +import unittest +from os.path import join, realpath +from typing import Dict, Iterator, List, NamedTuple + +import pandas as pd + +from hummingbot.connector.exchange.binance.binance_api_order_book_data_source import BinanceAPIOrderBookDataSource +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import PaperTradeExchange, QueuedOrder +from hummingbot.connector.exchange.paper_trade.trading_pair import TradingPair +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderBookTradeEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.utils.async_utils import safe_ensure_future, safe_gather + +logging.basicConfig(level=logging.INFO) +sys.path.insert(0, realpath(join(__file__, "../../../"))) + + +class TestUtils: + @staticmethod + def filter_events_by_type(event_logs, event_type): + return [e for e in event_logs if type(e) == event_type] + + @classmethod + def get_match_events(cls, event_logs: List[NamedTuple], event_type: NamedTuple, match_dict: Dict[str, any]): + match_events = [] + for e in cls.filter_events_by_type(event_logs, event_type): + match = True + for k, v in match_dict.items(): + try: + event_value = getattr(e, k) + if type(v) in [float]: + if abs(v - float(event_value)) <= 1 * 10 ** (-8): + continue + elif event_value != v: + match = False + break + except Exception as err: + print(f"Key {k} does not exist in event {e}. Error: {err}") + if match: + match_events.append(e) + return match_events + + @classmethod + def get_match_limit_orders(cls, limit_orders: List[LimitOrder], match_dict: Dict[str, any]): + match_orders = [] + for o in limit_orders: + match = True + for k, v in match_dict.items(): + try: + order_value = getattr(o, k) + if type(v) in [float]: + if abs(v - float(order_value)) <= 1 * 10 ** (-8): + continue + elif order_value != v: + match = False + break + except Exception as err: + print(f"Key {k} does not exist in LimitOrder {o}. Error: {err}") + if match: + match_orders.append(o) + return match_orders + + +class OrderBookUtils: + @classmethod + def ob_rows_data_frame(cls, ob_rows): + data = [] + try: + for ob_row in ob_rows: + data.append([ + ob_row.price, ob_row.amount + ]) + df = pd.DataFrame(data=data, columns=[ + "price", "amount"]) + df.index = df.price + return df + except Exception as e: + print(f"Error formatting market stats. {e}") + + @classmethod + def get_compare_df(cls, row_it_1: Iterator[OrderBookRow], row_it_2: Iterator[OrderBookRow], + n_rows: int = 20000, diffs_only: bool = False) -> pd.DataFrame: + rows_1 = list(row_it_1) + rows_2 = list(row_it_2) + book_1: pd.DataFrame = cls.ob_rows_data_frame(rows_1) + book_2: pd.DataFrame = cls.ob_rows_data_frame(rows_2) + book_1.index = book_1.price + book_2.index = book_2.price + compare_df: pd.DataFrame = pd.concat([book_1.iloc[0:n_rows], book_2.iloc[0:n_rows]], + axis="columns", keys=["pre", "post"]) + compare_df = compare_df.fillna(0.0) + compare_df['diff'] = compare_df['pre'].amount - compare_df['post'].amount + if not diffs_only: + return compare_df + else: + return compare_df[(compare_df["pre"]["amount"] - compare_df["post"]["amount"]).abs() > 1e-8] + + +class PaperTradeExchangeTest(unittest.TestCase): + events: List[MarketEvent] = [ + MarketEvent.BuyOrderCompleted, + MarketEvent.SellOrderCompleted, + MarketEvent.OrderFilled, + MarketEvent.TransactionFailure, + MarketEvent.BuyOrderCreated, + MarketEvent.SellOrderCreated, + MarketEvent.OrderCancelled + ] + + market: PaperTradeExchange + market_logger: EventLogger + stack: contextlib.ExitStack + + @classmethod + def setUpClass(cls): + global MAINNET_RPC_URL + + cls.clock: Clock = Clock(ClockMode.REALTIME) + connector = BinanceExchange( + binance_api_key="", + binance_api_secret="", + trading_pairs=["ETH-USDT", "BTC-USDT"], + trading_required=False) + cls.market: PaperTradeExchange = PaperTradeExchange( + order_book_tracker=OrderBookTracker( + data_source=BinanceAPIOrderBookDataSource( + trading_pairs=["ETH-USDT", "BTC-USDT"], + connector=connector, + api_factory=connector._api_factory), + trading_pairs=["ETH-USDT", "BTC-USDT"]), + target_market=BinanceExchange, + exchange_name="binance", + ) + print("Initializing PaperTrade execute orders market... this will take about a minute.") + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.clock.add_iterator(cls.market) + cls.stack: contextlib.ExitStack = contextlib.ExitStack() + cls._clock = cls.stack.enter_context(cls.clock) + cls.ev_loop.run_until_complete(cls.wait_til_ready()) + print("Ready.") + + @classmethod + def tearDownClass(cls) -> None: + cls.stack.close() + + @classmethod + async def wait_til_ready(cls): + while True: + now = time.time() + next_iteration = now // 1.0 + 1 + if cls.market.ready: + break + else: + await cls._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + + def setUp(self): + self.db_path: str = realpath(join(__file__, "../binance_test.sqlite")) + try: + os.unlink(self.db_path) + except FileNotFoundError: + pass + + self.market_logger = EventLogger() + for event_tag in self.events: + self.market.add_listener(event_tag, self.market_logger) + + for trading_pair, orderbook in self.market.order_books.items(): + orderbook.clear_traded_order_book() + + def tearDown(self): + for event_tag in self.events: + self.market.remove_listener(event_tag, self.market_logger) + self.market_logger = None + + async def run_parallel_async(self, *tasks): + future: asyncio.Future = safe_ensure_future(safe_gather(*tasks)) + while not future.done(): + now = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration) + await asyncio.sleep(1.0) + return future.result() + + def run_parallel(self, *tasks): + return self.ev_loop.run_until_complete(self.run_parallel_async(*tasks)) + + def test_place_market_orders(self): + self.market.sell("ETH-USDT", 30, OrderType.MARKET) + list_queued_orders: List[QueuedOrder] = self.market.queued_orders + first_queued_order: QueuedOrder = list_queued_orders[0] + self.assertFalse(first_queued_order.is_buy, msg="Market order is not sell") + self.assertEqual(first_queued_order.trading_pair, "ETH-USDT", msg="Trading pair is incorrect") + self.assertEqual(first_queued_order.amount, 30, msg="Quantity is incorrect") + self.assertEqual(len(list_queued_orders), 1, msg="First market order did not get added") + + # Figure out why this test is failing + self.market.buy("BTC-USDT", 30, OrderType.MARKET) + list_queued_orders: List[QueuedOrder] = self.market.queued_orders + second_queued_order: QueuedOrder = list_queued_orders[1] + self.assertTrue(second_queued_order.is_buy, msg="Market order is not buy") + self.assertEqual(second_queued_order.trading_pair, "BTC-USDT", msg="Trading pair is incorrect") + self.assertEqual(second_queued_order.amount, 30, msg="Quantity is incorrect") + self.assertEqual(second_queued_order.amount, 30, msg="Quantity is incorrect") + self.assertEqual(len(list_queued_orders), 2, msg="Second market order did not get added") + + def test_market_order_simulation(self): + self.market.set_balance("ETH", 20) + self.market.set_balance("USDT", 100) + self.market.sell("ETH-USDT", 10, OrderType.MARKET) + self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + # Get diff between composite bid entries and original bid entries + compare_df = OrderBookUtils.get_compare_df( + self.market.order_books['ETH-USDT'].original_bid_entries(), + self.market.order_books['ETH-USDT'].bid_entries(), diffs_only=True).sort_index().round(10) + filled_bids = OrderBookUtils.ob_rows_data_frame( + list(self.market.order_books['ETH-USDT'].traded_order_book.bid_entries())).sort_index().round(10) + + # assert filled orders matches diff + diff_bid = compare_df["diff"] - filled_bids["amount"] + + self.assertFalse(diff_bid.to_numpy().any()) + + self.assertEquals(10, self.market.get_balance("ETH"), msg="Balance was not updated.") + self.market.buy("ETH-USDT", 5, OrderType.MARKET) + self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + # Get diff between composite bid entries and original bid entries + compare_df = OrderBookUtils.get_compare_df( + self.market.order_books['ETH-USDT'].original_ask_entries(), + self.market.order_books['ETH-USDT'].ask_entries(), diffs_only=True).sort_index().round(10) + filled_asks = OrderBookUtils.ob_rows_data_frame( + list(self.market.order_books['ETH-USDT'].traded_order_book.ask_entries())).sort_index().round(10) + + # assert filled orders matches diff + diff_ask = compare_df["diff"] - filled_asks["amount"] + + self.assertFalse(diff_ask.to_numpy().any()) + self.assertEquals(15, self.market.get_balance("ETH"), msg="Balance was not updated.") + + def test_limit_order_crossed(self): + starting_base_balance = 20 + starting_quote_balance = 1000 + self.market.set_balance("ETH", starting_base_balance) + self.market.set_balance("USDT", starting_quote_balance) + self.market.sell("ETH-USDT", 10, OrderType.LIMIT, 100) + self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + self.assertEquals(starting_base_balance - 10, self.market.get_balance("ETH"), + msg="ETH Balance was not updated.") + self.assertEquals(starting_quote_balance + 1000, self.market.get_balance("USDT"), + msg="USDT Balance was not updated.") + self.market.buy("ETH-USDT", 1, OrderType.LIMIT, 500) + self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + self.assertEquals(11, self.market.get_balance("ETH"), + msg="ETH Balance was not updated.") + self.assertEquals(1500, self.market.get_balance("USDT"), + msg="USDT Balance was not updated.") + + def test_bid_limit_order_trade_match(self): + """ + Test bid limit order fill and balance simulation, and market events emission + """ + trading_pair = TradingPair("ETH-USDT", "ETH", "USDT") + base_quantity = 2.0 + starting_base_balance = 200 + starting_quote_balance = 2000 + self.market.set_balance(trading_pair.base_asset, starting_base_balance) + self.market.set_balance(trading_pair.quote_asset, starting_quote_balance) + + best_bid_price = self.market.order_books[trading_pair.trading_pair].get_price(True) + client_order_id = self.market.buy(trading_pair.trading_pair, base_quantity, OrderType.LIMIT, best_bid_price) + + matched_limit_orders = TestUtils.get_match_limit_orders(self.market.limit_orders, { + "client_order_id": client_order_id, + "trading_pair": trading_pair.trading_pair, + "is_buy": True, + "base_currency": trading_pair.base_asset, + "quote_currency": trading_pair.quote_asset, + "price": best_bid_price, + "quantity": base_quantity + }) + # Market should track limit orders + self.assertEqual(1, len(matched_limit_orders)) + + # Market should on hold balance for the created order + self.assertAlmostEqual(float(self.market.on_hold_balances[trading_pair.quote_asset]), + base_quantity * best_bid_price) + # Market should reflect on hold balance in available balance + self.assertAlmostEqual(float(self.market.get_available_balance(trading_pair.quote_asset)), + starting_quote_balance - base_quantity * best_bid_price) + + matched_order_create_events = TestUtils.get_match_events(self.market_logger.event_log, BuyOrderCreatedEvent, { + "type": OrderType.LIMIT, + "amount": base_quantity, + "price": best_bid_price, + "order_id": client_order_id + }) + # Market should emit BuyOrderCreatedEvent + self.assertEqual(1, len(matched_order_create_events)) + + async def delay_trigger_event1(): + await asyncio.sleep(1) + trade_event1 = OrderBookTradeEvent( + trading_pair="ETH-USDT", timestamp=time.time(), type=TradeType.SELL, price=best_bid_price + 1, + amount=1.0) + self.market.order_books['ETH-USDT'].apply_trade(trade_event1) + + safe_ensure_future(delay_trigger_event1()) + self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent)) + + placed_bid_orders: List[LimitOrder] = [o for o in self.market.limit_orders if o.is_buy] + # Market should delete limit order when it is filled + self.assertEqual(0, len(placed_bid_orders)) + + matched_order_complete_events = TestUtils.get_match_events( + self.market_logger.event_log, BuyOrderCompletedEvent, { + "order_type": OrderType.LIMIT, + "quote_asset_amount": base_quantity * best_bid_price, + "order_id": client_order_id + }) + # Market should emit BuyOrderCompletedEvent + self.assertEqual(1, len(matched_order_complete_events)) + + matched_order_fill_events = TestUtils.get_match_events( + self.market_logger.event_log, OrderFilledEvent, { + "order_type": OrderType.LIMIT, + "trade_type": TradeType.BUY, + "trading_pair": trading_pair.trading_pair, + "order_id": client_order_id + }) + # Market should emit OrderFilledEvent + self.assertEqual(1, len(matched_order_fill_events)) + + # Market should have no more on hold balance + self.assertAlmostEqual(float(self.market.on_hold_balances[trading_pair.quote_asset]), 0) + # Market should update balance for the filled order + self.assertAlmostEqual(float(self.market.get_available_balance(trading_pair.quote_asset)), + starting_quote_balance - base_quantity * best_bid_price) + + def test_ask_limit_order_trade_match(self): + """ + Test ask limit order fill and balance simulation, and market events emission + """ + trading_pair = TradingPair("ETH-USDT", "ETH", "USDT") + base_quantity = 2.0 + starting_base_balance = 200 + starting_quote_balance = 2000 + self.market.set_balance(trading_pair.base_asset, starting_base_balance) + self.market.set_balance(trading_pair.quote_asset, starting_quote_balance) + + best_ask_price = self.market.order_books[trading_pair.trading_pair].get_price(False) + client_order_id = self.market.sell(trading_pair.trading_pair, base_quantity, OrderType.LIMIT, best_ask_price) + + matched_limit_orders = TestUtils.get_match_limit_orders(self.market.limit_orders, { + "client_order_id": client_order_id, + "trading_pair": trading_pair.trading_pair, + "is_buy": False, + "base_currency": trading_pair.base_asset, + "quote_currency": trading_pair.quote_asset, + "price": best_ask_price, + "quantity": base_quantity + }) + # Market should track limit orders + self.assertEqual(1, len(matched_limit_orders)) + + # Market should on hold balance for the created order + self.assertAlmostEqual(float(self.market.on_hold_balances[trading_pair.base_asset]), base_quantity) + # Market should reflect on hold balance in available balance + self.assertAlmostEqual(self.market.get_available_balance(trading_pair.base_asset), + starting_base_balance - base_quantity) + + self.run_parallel(self.market_logger.wait_for(SellOrderCreatedEvent, timeout_seconds=10)) + matched_order_create_events = TestUtils.get_match_events(self.market_logger.event_log, SellOrderCreatedEvent, { + "type": OrderType.LIMIT, + "amount": base_quantity, + "price": best_ask_price, + "order_id": client_order_id + }) + # Market should emit BuyOrderCreatedEvent + self.assertEqual(1, len(matched_order_create_events)) + + async def delay_trigger_event2(): + await asyncio.sleep(1) + trade_event = OrderBookTradeEvent( + trading_pair=trading_pair.trading_pair, timestamp=time.time(), type=TradeType.BUY, + price=best_ask_price - 1, amount=base_quantity) + self.market.order_books[trading_pair.trading_pair].apply_trade(trade_event) + + safe_ensure_future(delay_trigger_event2()) + + self.run_parallel(self.market_logger.wait_for(SellOrderCompletedEvent)) + + placed_ask_orders: List[LimitOrder] = [o for o in self.market.limit_orders if not o.is_buy] + + # Market should delete limit order when it is filled + self.assertEqual(0, len(placed_ask_orders)) + + matched_order_complete_events = TestUtils.get_match_events( + self.market_logger.event_log, SellOrderCompletedEvent, { + "order_type": OrderType.LIMIT, + "quote_asset_amount": base_quantity * base_quantity, + "order_id": client_order_id + }) + # Market should emit BuyOrderCompletedEvent + self.assertEqual(1, len(matched_order_complete_events)) + + matched_order_fill_events = TestUtils.get_match_events( + self.market_logger.event_log, OrderFilledEvent, { + "order_type": OrderType.LIMIT, + "trade_type": TradeType.SELL, + "trading_pair": trading_pair.trading_pair, + "order_id": client_order_id + }) + # Market should emit OrderFilledEvent + self.assertEqual(1, len(matched_order_fill_events)) + + # Market should have no more on hold balance + self.assertAlmostEqual(float(self.market.on_hold_balances[trading_pair.base_asset]), 0) + # Market should update balance for the filled order + self.assertAlmostEqual(self.market.get_available_balance(trading_pair.base_asset), + starting_base_balance - base_quantity) + + def test_order_cancellation(self): + trading_pair = TradingPair("ETH-USDT", "ETH", "USDT") + base_quantity = 2.0 + starting_base_balance = 200 + starting_quote_balance = 2000 + self.market.set_balance(trading_pair.base_asset, starting_base_balance) + self.market.set_balance(trading_pair.quote_asset, starting_quote_balance) + best_ask_price = self.market.order_books[trading_pair.trading_pair].get_price(False) + ask_client_order_id = self.market.sell(trading_pair.trading_pair, base_quantity, + OrderType.LIMIT, best_ask_price) + best_bid_price = self.market.order_books[trading_pair.trading_pair].get_price(True) + self.market.buy(trading_pair.trading_pair, base_quantity, OrderType.LIMIT, best_bid_price) + + # Market should track limit orders + self.assertEqual(2, len(self.market.limit_orders)) + self.market.cancel(trading_pair.trading_pair, ask_client_order_id) + + matched_limit_orders = TestUtils.get_match_limit_orders(self.market.limit_orders, { + "client_order_id": ask_client_order_id, + "trading_pair": trading_pair.trading_pair, + "is_buy": False, + "base_currency": trading_pair.base_asset, + "quote_currency": trading_pair.quote_asset, + "price": best_ask_price, + "quantity": base_quantity + }) + + # Market should remove canceled orders + self.assertEqual(0, len(matched_limit_orders)) + + matched_order_cancel_events = TestUtils.get_match_events( + self.market_logger.event_log, OrderCancelledEvent, { + "order_id": ask_client_order_id + }) + # Market should emit cancel event + self.assertEqual(1, len(matched_order_cancel_events)) + + def test_order_cancel_all(self): + trading_pair = TradingPair("ETH-USDT", "ETH", "USDT") + base_quantity = 2.0 + starting_base_balance = 200 + starting_quote_balance = 2000 + self.market.set_balance(trading_pair.base_asset, starting_base_balance) + self.market.set_balance(trading_pair.quote_asset, starting_quote_balance) + best_ask_price = self.market.order_books[trading_pair.trading_pair].get_price(False) + self.market.sell(trading_pair.trading_pair, base_quantity, + OrderType.LIMIT, best_ask_price) + best_bid_price = self.market.order_books[trading_pair.trading_pair].get_price(True) + self.market.buy(trading_pair.trading_pair, base_quantity, OrderType.LIMIT, best_bid_price) + + # Market should track limit orders + self.assertEqual(2, len(self.market.limit_orders)) + + asyncio.get_event_loop().run_until_complete(self.market.cancel_all(0)) + + # Market should remove all canceled orders + self.assertEqual(0, len(self.market.limit_orders)) + + matched_order_cancel_events = TestUtils.get_match_events( + self.market_logger.event_log, OrderCancelledEvent, {}) + # Market should emit cancel event + self.assertEqual(2, len(matched_order_cancel_events)) diff --git a/test/hummingbot/__init__.py b/test/hummingbot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/__init__.py b/test/hummingbot/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/command/__init__.py b/test/hummingbot/client/command/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/command/test_balance_command.py b/test/hummingbot/client/command/test_balance_command.py new file mode 100644 index 0000000..b422951 --- /dev/null +++ b/test/hummingbot/client/command/test_balance_command.py @@ -0,0 +1,136 @@ +import asyncio +import unittest +from decimal import Decimal +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.config_helpers import read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class BalanceCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + + self.app = HummingbotApplication() + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + return async_sleep + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + @patch("hummingbot.user.user_balances.UserBalances.all_balances_all_exchanges") + def test_show_balances_handles_network_timeouts( + self, all_balances_all_exchanges_mock + ): + all_balances_all_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + self.app.client_config_map.commands_timeout.other_commands_timeout = Decimal("0.01") + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.show_balances()) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the balances to update. See logs for more details." + ) + ) + + @patch("hummingbot.user.user_balances.UserBalances.all_available_balances_all_exchanges") + @patch("hummingbot.user.user_balances.UserBalances.all_balances_all_exchanges") + def test_show_balances_empty_balances( + self, + all_balances_all_exchanges_mock: AsyncMock, + all_available_balances_all_exchanges_mock: AsyncMock, + ): + all_balances_all_exchanges_mock.return_value = {"binance": {}} + all_available_balances_all_exchanges_mock.return_value = {"binance": {}} + + self.async_run_with_timeout(self.app.show_balances()) + + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg="\nbinance:") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg="You have no balance on this exchange.") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg=f"\n\nExchanges Total: {self.app.client_config_map.global_token.global_token_symbol} 0 " + ) + ) + + @patch("hummingbot.core.rate_oracle.rate_oracle.RateOracle.get_rate") + @patch("hummingbot.user.user_balances.UserBalances.all_available_balances_all_exchanges") + @patch("hummingbot.user.user_balances.UserBalances.all_balances_all_exchanges") + def test_show_balances( + self, + all_balances_all_exchanges_mock: AsyncMock, + all_available_balances_all_exchanges_mock: AsyncMock, + get_rate_mock: AsyncMock, + ): + all_balances_all_exchanges_mock.return_value = { + "binance": {"BTC": Decimal("10")}, + } + all_available_balances_all_exchanges_mock.return_value = { + "binance": {"BTC": Decimal("5")}, + } + get_rate_mock.return_value = Decimal("2") + + self.async_run_with_timeout(self.app.show_balances()) + + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg="\nbinance:") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg=( + f" Asset Total Total ({self.app.client_config_map.global_token.global_token_symbol}) Allocated" + f"\n BTC 10.0000 20.00 50%" + ) + ) + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg=f"\n Total: {self.app.client_config_map.global_token.global_token_symbol} 20.00" + ) + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg="Allocated: 50.00%") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg=f"\n\nExchanges Total: {self.app.client_config_map.global_token.global_token_symbol} 20 " + ) + ) diff --git a/test/hummingbot/client/command/test_config_command.py b/test/hummingbot/client/command/test_config_command.py new file mode 100644 index 0000000..56c190c --- /dev/null +++ b/test/hummingbot/client/command/test_config_command.py @@ -0,0 +1,261 @@ +import asyncio +import unittest +from decimal import Decimal +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable, Union +from unittest.mock import MagicMock, patch + +from pydantic import Field + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_data_types import BaseClientModel, ClientFieldData +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.strategy_config_data_types import BaseStrategyConfigMap +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class ConfigCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + + self.client_config = ClientConfigMap() + self.config_adapter = ClientConfigAdapter(self.client_config) + + self.app = HummingbotApplication(client_config_map=self.config_adapter) + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.client.hummingbot_application.get_strategy_config_map") + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_list_configs(self, notify_mock, get_strategy_config_map_mock): + captures = [] + self.app.client_config_map.instance_id = "TEST_ID" + notify_mock.side_effect = lambda s: captures.append(s) + strategy_name = "some-strategy" + self.app.strategy_name = strategy_name + + strategy_config_map_mock = { + "five": ConfigVar(key="five", prompt=""), + "six": ConfigVar(key="six", prompt="", default="sixth"), + } + strategy_config_map_mock["five"].value = "fifth" + strategy_config_map_mock["six"].value = "sixth" + get_strategy_config_map_mock.return_value = strategy_config_map_mock + + self.app.list_configs() + + self.assertEqual(6, len(captures)) + self.assertEqual("\nGlobal Configurations:", captures[0]) + + df_str_expected = (" +-----------------------------------+----------------------+\n" + " | Key | Value |\n" + " |-----------------------------------+----------------------|\n" + " | instance_id | TEST_ID |\n" + " | fetch_pairs_from_all_exchanges | False |\n" + " | kill_switch_mode | kill_switch_disabled |\n" + " | autofill_import | disabled |\n" + " | telegram_mode | telegram_disabled |\n" + " | mqtt_bridge | |\n" + " | ∟ mqtt_host | localhost |\n" + " | ∟ mqtt_port | 1883 |\n" + " | ∟ mqtt_username | |\n" + " | ∟ mqtt_password | |\n" + " | ∟ mqtt_namespace | hbot |\n" + " | ∟ mqtt_ssl | False |\n" + " | ∟ mqtt_logger | True |\n" + " | ∟ mqtt_notifier | True |\n" + " | ∟ mqtt_commands | True |\n" + " | ∟ mqtt_events | True |\n" + " | ∟ mqtt_external_events | True |\n" + " | ∟ mqtt_autostart | False |\n" + " | send_error_logs | True |\n" + " | pmm_script_mode | pmm_script_disabled |\n" + " | gateway | |\n" + " | ∟ gateway_api_host | localhost |\n" + " | ∟ gateway_api_port | 15888 |\n" + " | rate_oracle_source | binance |\n" + " | global_token | |\n" + " | ∟ global_token_name | USDT |\n" + " | ∟ global_token_symbol | $ |\n" + " | rate_limits_share_pct | 100 |\n" + " | commands_timeout | |\n" + " | ∟ create_command_timeout | 10 |\n" + " | ∟ other_commands_timeout | 30 |\n" + " | tables_format | psql |\n" + " | tick_size | 1.0 |\n" + " | market_data_collection | |\n" + " | ∟ market_data_collection_enabled | True |\n" + " | ∟ market_data_collection_interval | 60 |\n" + " | ∟ market_data_collection_depth | 20 |\n" + " +-----------------------------------+----------------------+") + + self.assertEqual(df_str_expected, captures[1]) + self.assertEqual("\nColor Settings:", captures[2]) + + df_str_expected = (" +--------------------+---------+\n" + " | Key | Value |\n" + " |--------------------+---------|\n" + " | ∟ top_pane | #000000 |\n" + " | ∟ bottom_pane | #000000 |\n" + " | ∟ output_pane | #262626 |\n" + " | ∟ input_pane | #1C1C1C |\n" + " | ∟ logs_pane | #121212 |\n" + " | ∟ terminal_primary | #5FFFD7 |\n" + " +--------------------+---------+") + + self.assertEqual(df_str_expected, captures[3]) + self.assertEqual("\nStrategy Configurations:", captures[4]) + + df_str_expected = ( + " +-------+---------+" + "\n | Key | Value |" + "\n |-------+---------|" + "\n | five | fifth |" + "\n | six | sixth |" + "\n +-------+---------+" + ) + + self.assertEqual(df_str_expected, captures[5]) + + @patch("hummingbot.client.hummingbot_application.get_strategy_config_map") + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_list_configs_pydantic_model(self, notify_mock, get_strategy_config_map_mock): + captures = [] + notify_mock.side_effect = lambda s: captures.append(s) + strategy_name = "some-strategy" + self.app.strategy_name = strategy_name + + class DoubleNestedModel(BaseClientModel): + double_nested_attr: float = Field(default=3.0) + + class Config: + title = "double_nested_model" + + class NestedModelOne(BaseClientModel): + nested_attr: str = Field(default="some value") + double_nested_model: DoubleNestedModel = Field(default=DoubleNestedModel()) + + class Config: + title = "nested_mode_one" + + class NestedModelTwo(BaseClientModel): + class Config: + title = "nested_mode_two" + + class DummyModel(BaseClientModel): + some_attr: int = Field(default=1) + nested_model: Union[NestedModelTwo, NestedModelOne] = Field(default=NestedModelOne()) + another_attr: Decimal = Field(default=Decimal("1.0")) + missing_no_default: int = Field(default=...) + + class Config: + title = "dummy_model" + + get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct()) + + self.app.list_configs() + + self.assertEqual(6, len(captures)) + + self.assertEqual("\nStrategy Configurations:", captures[4]) + + df_str_expected = ( + " +------------------------+------------------------+" + "\n | Key | Value |" + "\n |------------------------+------------------------|" + "\n | some_attr | 1 |" + "\n | nested_model | nested_mode_one |" + "\n | ∟ nested_attr | some value |" + "\n | ∟ double_nested_model | |" + "\n | ∟ double_nested_attr | 3.0 |" + "\n | another_attr | 1.0 |" + "\n | missing_no_default | &cMISSING_AND_REQUIRED |" + "\n +------------------------+------------------------+" + ) + + self.assertEqual(df_str_expected, captures[5]) + + @patch("hummingbot.client.hummingbot_application.get_strategy_config_map") + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_config_non_configurable_key_fails(self, notify_mock, get_strategy_config_map_mock): + class DummyModel(BaseStrategyConfigMap): + strategy: str = Field(default="pure_market_making", client_data=None) + some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt")) + another_attr: Decimal = Field(default=Decimal("1.0")) + + class Config: + title = "dummy_model" + + strategy_name = "some-strategy" + self.app.strategy_name = strategy_name + get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct()) + self.app.config(key="some_attr") + + notify_mock.assert_not_called() + + self.app.config(key="another_attr") + + notify_mock.assert_called_once_with("Invalid key, please choose from the list.") + + notify_mock.reset_mock() + self.app.config(key="some_key") + + notify_mock.assert_called_once_with("Invalid key, please choose from the list.") + + @patch("hummingbot.client.command.config_command.save_to_yml") + @patch("hummingbot.client.hummingbot_application.get_strategy_config_map") + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_config_single_keys(self, _, get_strategy_config_map_mock, save_to_yml_mock): + class NestedModel(BaseClientModel): + nested_attr: str = Field( + default="some value", client_data=ClientFieldData(prompt=lambda mi: "some prompt") + ) + + class Config: + title = "nested_model" + + class DummyModel(BaseStrategyConfigMap): + strategy: str = Field(default="pure_market_making", client_data=None) + some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt")) + nested_model: NestedModel = Field(default=NestedModel()) + + class Config: + title = "dummy_model" + + strategy_name = "some-strategy" + self.app.strategy_name = strategy_name + self.app.strategy_file_name = f"{strategy_name}.yml" + config_map = ClientConfigAdapter(DummyModel.construct()) + get_strategy_config_map_mock.return_value = config_map + + self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=2)) + + self.assertEqual(2, config_map.some_attr) + save_to_yml_mock.assert_called_once() + + save_to_yml_mock.reset_mock() + self.cli_mock_assistant.queue_prompt_reply("3") + self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=None)) + + self.assertEqual(3, config_map.some_attr) + save_to_yml_mock.assert_called_once() + + save_to_yml_mock.reset_mock() + self.cli_mock_assistant.queue_prompt_reply("another value") + self.async_run_with_timeout(self.app._config_single_key(key="nested_model.nested_attr", input_value=None)) + + self.assertEqual("another value", config_map.nested_model.nested_attr) + save_to_yml_mock.assert_called_once() diff --git a/test/hummingbot/client/command/test_connect_command.py b/test/hummingbot/client/command/test_connect_command.py new file mode 100644 index 0000000..3699a75 --- /dev/null +++ b/test/hummingbot/client/command/test_connect_command.py @@ -0,0 +1,181 @@ +import asyncio +import unittest +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.config.security import Security +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class ConnectCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + Security._decryption_done.clear() + super().tearDown() + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + return async_sleep + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + @patch("hummingbot.client.config.security.Security.wait_til_decryption_done") + @patch("hummingbot.client.config.security.Security.update_secure_config") + @patch("hummingbot.client.config.security.Security.connector_config_file_exists") + @patch("hummingbot.client.config.security.Security.api_keys") + @patch("hummingbot.user.user_balances.UserBalances.add_exchange") + def test_connect_exchange_success( + self, + add_exchange_mock: AsyncMock, + api_keys_mock: AsyncMock, + connector_config_file_exists_mock: MagicMock, + update_secure_config_mock: MagicMock, + _: MagicMock, + ): + add_exchange_mock.return_value = None + exchange = "binance" + api_key = "someKey" + api_secret = "someSecret" + api_keys_mock.return_value = {"binance_api_key": api_key, "binance_api_secret": api_secret} + connector_config_file_exists_mock.return_value = False + self.cli_mock_assistant.queue_prompt_reply(api_key) # binance API key + self.cli_mock_assistant.queue_prompt_reply(api_secret) # binance API secret + + self.async_run_with_timeout(self.app.connect_exchange(exchange)) + self.assertTrue(self.cli_mock_assistant.check_log_called_with(msg=f"\nYou are now connected to {exchange}.")) + self.assertFalse(self.app.placeholder_mode) + self.assertFalse(self.app.app.hide_input) + self.assertEqual(update_secure_config_mock.call_count, 1) + + @patch("hummingbot.client.config.security.Security.wait_til_decryption_done") + @patch("hummingbot.client.config.security.Security.update_secure_config") + @patch("hummingbot.client.config.security.Security.connector_config_file_exists") + @patch("hummingbot.client.config.security.Security.api_keys") + @patch("hummingbot.user.user_balances.UserBalances.add_exchange") + def test_connect_exchange_handles_network_timeouts( + self, + add_exchange_mock: AsyncMock, + api_keys_mock: AsyncMock, + connector_config_file_exists_mock: MagicMock, + _: MagicMock, + __: MagicMock, + ): + add_exchange_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + api_key = "someKey" + api_secret = "someSecret" + api_keys_mock.return_value = {"binance_api_key": api_key, "binance_api_secret": api_secret} + connector_config_file_exists_mock.return_value = False + self.cli_mock_assistant.queue_prompt_reply(api_key) # binance API key + self.cli_mock_assistant.queue_prompt_reply(api_secret) # binance API secret + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.connect_exchange("binance")) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the connection to complete. See logs for more details." + ) + ) + self.assertFalse(self.app.placeholder_mode) + self.assertFalse(self.app.app.hide_input) + + @patch("hummingbot.user.user_balances.UserBalances.update_exchanges") + @patch("hummingbot.client.config.security.Security.wait_til_decryption_done") + def test_connection_df_handles_network_timeouts(self, _: AsyncMock, update_exchanges_mock: AsyncMock): + update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.connection_df()) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the connection table to populate. See logs for more details." + ) + ) + + @patch("hummingbot.user.user_balances.UserBalances.update_exchanges") + @patch("hummingbot.client.config.security.Security.wait_til_decryption_done") + def test_connection_df_handles_network_timeouts_logs_hidden(self, _: AsyncMock, update_exchanges_mock: AsyncMock): + self.cli_mock_assistant.toggle_logs() + + update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.connection_df()) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the connection table to populate. See logs for more details." + ) + ) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.connection_df") + def test_show_connections(self, connection_df_mock, notify_mock): + self.client_config_map.db_mode = DBSqliteMode() + + Security._decryption_done.set() + + captures = [] + notify_mock.side_effect = lambda s: captures.append(s) + + connections_df = pd.DataFrame( + columns=pd.Index(['Exchange', ' Keys Added', ' Keys Confirmed', ' Status'], dtype='object'), + data=[ + ["ascend_ex", "Yes", "Yes", "&cYELLOW"], + ] + ) + connection_df_mock.return_value = (connections_df, []) + + self.async_run_with_timeout(self.app.show_connections()) + + self.assertEqual(2, len(captures)) + self.assertEqual("\nTesting connections, please wait...", captures[0]) + + df_str_expected = ( + " +------------+----------------+--------------------+------------+" + "\n | Exchange | Keys Added | Keys Confirmed | Status |" + "\n |------------+----------------+--------------------+------------|" + "\n | ascend_ex | Yes | Yes | &cYELLOW |" + "\n +------------+----------------+--------------------+------------+" + ) + + self.assertEqual(df_str_expected, captures[1]) diff --git a/test/hummingbot/client/command/test_create_command.py b/test/hummingbot/client/command/test_create_command.py new file mode 100644 index 0000000..0f3cecb --- /dev/null +++ b/test/hummingbot/client/command/test_create_command.py @@ -0,0 +1,212 @@ +import asyncio +import unittest +from decimal import Decimal +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + get_strategy_config_map, + read_system_configs_from_yml, +) +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class CreateCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + + return async_sleep + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 5): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + @patch("shutil.copy") + @patch("hummingbot.client.command.create_command.save_to_yml_legacy") + @patch("hummingbot.client.config.security.Security.is_decryption_done") + @patch("hummingbot.client.command.status_command.StatusCommand.validate_required_connections") + @patch("hummingbot.core.utils.market_price.get_last_price") + def test_prompt_for_configuration_re_prompts_on_lower_than_minimum_amount( + self, + get_last_price_mock: AsyncMock, + validate_required_connections_mock: AsyncMock, + is_decryption_done_mock: MagicMock, + save_to_yml_mock: MagicMock, + _: MagicMock, + ): + get_last_price_mock.return_value = Decimal("11") + validate_required_connections_mock.return_value = {} + is_decryption_done_mock.return_value = True + config_maps = [] + save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm) + + self.client_config_map.commands_timeout.create_command_timeout = 10 + self.client_config_map.commands_timeout.other_commands_timeout = 30 + strategy_name = "some-strategy" + strategy_file_name = f"{strategy_name}.yml" + base_strategy = "pure_market_making" + self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy + self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector + self.cli_mock_assistant.queue_prompt_reply("BTC-USDT") # trading pair + self.cli_mock_assistant.queue_prompt_reply("1") # bid spread + self.cli_mock_assistant.queue_prompt_reply("1") # ask spread + self.cli_mock_assistant.queue_prompt_reply("30") # order refresh time + self.cli_mock_assistant.queue_prompt_reply("0") # unacceptable order amount + self.cli_mock_assistant.queue_prompt_reply("1") # acceptable order amount + self.cli_mock_assistant.queue_prompt_reply("No") # ping pong feature + + self.async_run_with_timeout(self.app.prompt_for_configuration(strategy_file_name)) + self.assertEqual(strategy_file_name, self.app.strategy_file_name) + self.assertEqual(base_strategy, self.app.strategy_name) + self.assertTrue(self.cli_mock_assistant.check_log_called_with(msg="Value must be more than 0.")) + + @patch("shutil.copy") + @patch("hummingbot.client.command.create_command.save_to_yml_legacy") + @patch("hummingbot.client.config.security.Security.is_decryption_done") + @patch("hummingbot.client.command.status_command.StatusCommand.validate_required_connections") + @patch("hummingbot.core.utils.market_price.get_last_price") + def test_prompt_for_configuration_accepts_zero_amount_on_get_last_price_network_timeout( + self, + get_last_price_mock: AsyncMock, + validate_required_connections_mock: AsyncMock, + is_decryption_done_mock: MagicMock, + save_to_yml_mock: MagicMock, + _: MagicMock, + ): + get_last_price_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + validate_required_connections_mock.return_value = {} + is_decryption_done_mock.return_value = True + config_maps = [] + save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm) + + self.client_config_map.commands_timeout.create_command_timeout = 0.005 + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + strategy_name = "some-strategy" + strategy_file_name = f"{strategy_name}.yml" + base_strategy = "pure_market_making" + self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy + self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector + self.cli_mock_assistant.queue_prompt_reply("BTC-USDT") # trading pair + self.cli_mock_assistant.queue_prompt_reply("1") # bid spread + self.cli_mock_assistant.queue_prompt_reply("1") # ask spread + self.cli_mock_assistant.queue_prompt_reply("30") # order refresh time + self.cli_mock_assistant.queue_prompt_reply("1") # order amount + self.cli_mock_assistant.queue_prompt_reply("No") # ping pong feature + + self.async_run_with_timeout(self.app.prompt_for_configuration(strategy_file_name)) + self.assertEqual(strategy_file_name, self.app.strategy_file_name) + self.assertEqual(base_strategy, self.app.strategy_name) + + def test_create_command_restores_config_map_after_config_stop(self): + base_strategy = "pure_market_making" + strategy_config = get_strategy_config_map(base_strategy) + original_exchange = "bybit" + strategy_config["exchange"].value = original_exchange + + self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy + self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector + self.cli_mock_assistant.queue_prompt_to_stop_config() # cancel on trading pair prompt + + self.async_run_with_timeout(self.app.prompt_for_configuration(None)) + strategy_config = get_strategy_config_map(base_strategy) + + self.assertEqual(original_exchange, strategy_config["exchange"].value) + + def test_create_command_restores_config_map_after_config_stop_on_new_file_prompt(self): + base_strategy = "pure_market_making" + strategy_config = get_strategy_config_map(base_strategy) + original_exchange = "bybit" + strategy_config["exchange"].value = original_exchange + + self.cli_mock_assistant.queue_prompt_reply(base_strategy) # strategy + self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector + self.cli_mock_assistant.queue_prompt_reply("BTC-USDT") # trading pair + self.cli_mock_assistant.queue_prompt_reply("1") # bid spread + self.cli_mock_assistant.queue_prompt_reply("1") # ask spread + self.cli_mock_assistant.queue_prompt_reply("30") # order refresh time + self.cli_mock_assistant.queue_prompt_reply("1") # order amount + self.cli_mock_assistant.queue_prompt_reply("No") # ping pong feature + self.cli_mock_assistant.queue_prompt_to_stop_config() # cancel on new file prompt + + self.async_run_with_timeout(self.app.prompt_for_configuration(None)) + strategy_config = get_strategy_config_map(base_strategy) + + self.assertEqual(original_exchange, strategy_config["exchange"].value) + + @patch("shutil.copy") + @patch("hummingbot.client.command.create_command.save_to_yml_legacy") + @patch("hummingbot.client.config.security.Security.is_decryption_done") + @patch("hummingbot.client.command.status_command.StatusCommand.validate_required_connections") + @patch("hummingbot.core.utils.market_price.get_last_price") + def test_prompt_for_configuration_handles_status_network_timeout( + self, + get_last_price_mock: AsyncMock, + validate_required_connections_mock: AsyncMock, + is_decryption_done_mock: MagicMock, + _: MagicMock, + __: MagicMock, + ): + get_last_price_mock.return_value = None + validate_required_connections_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + is_decryption_done_mock.return_value = True + self.client_config_map.commands_timeout.create_command_timeout = 0.005 + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + strategy_file_name = "some-strategy.yml" + self.cli_mock_assistant.queue_prompt_reply("pure_market_making") # strategy + self.cli_mock_assistant.queue_prompt_reply("binance") # spot connector + self.cli_mock_assistant.queue_prompt_reply("BTC-USDT") # trading pair + self.cli_mock_assistant.queue_prompt_reply("1") # bid spread + self.cli_mock_assistant.queue_prompt_reply("1") # ask spread + self.cli_mock_assistant.queue_prompt_reply("30") # order refresh time + self.cli_mock_assistant.queue_prompt_reply("1") # order amount + self.cli_mock_assistant.queue_prompt_reply("No") # ping pong feature + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout( + self.app.prompt_for_configuration(strategy_file_name) + ) + self.assertEqual(None, self.app.strategy_file_name) + self.assertEqual(None, self.app.strategy_name) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the connection check to complete. See logs for more details." + ) + ) diff --git a/test/hummingbot/client/command/test_history_command.py b/test/hummingbot/client/command/test_history_command.py new file mode 100644 index 0000000..989cbe8 --- /dev/null +++ b/test/hummingbot/client/command/test_history_command.py @@ -0,0 +1,174 @@ +import asyncio +import datetime +import time +import unittest +from decimal import Decimal +from pathlib import Path +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable, List +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange.paper_trade import PaperTradeExchange +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import SQLConnectionManager +from hummingbot.model.trade_fill import TradeFill + + +class HistoryCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + self.mock_strategy_name = "test-strategy" + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + db_path = Path(SQLConnectionManager.create_db_path(db_name=self.mock_strategy_name)) + db_path.unlink(missing_ok=True) + super().tearDown() + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + + return async_sleep + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + def get_trades(self) -> List[TradeFill]: + trade_fee = AddedToCostTradeFee(percent=Decimal("5")) + trades = [ + TradeFill( + config_file_path=f"{self.mock_strategy_name}.yml", + strategy=self.mock_strategy_name, + market="binance", + symbol="BTC-USDT", + base_asset="BTC", + quote_asset="USDT", + timestamp=int(time.time()), + order_id="someId", + trade_type="BUY", + order_type="LIMIT", + price=1, + amount=2, + leverage=1, + trade_fee=trade_fee.to_json(), + exchange_trade_id="someExchangeId", + ) + ] + return trades + + @patch("hummingbot.client.command.history_command.HistoryCommand.get_current_balances") + def test_history_report_raises_on_get_current_balances_network_timeout(self, get_current_balances_mock: AsyncMock): + get_current_balances_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + trades = self.get_trades() + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout( + self.app.history_report(start_time=time.time(), trades=trades) + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the balances retrieval to complete. See logs for more details." + ) + ) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_list_trades(self, notify_mock): + self.client_config_map.db_mode = DBSqliteMode() + + captures = [] + notify_mock.side_effect = lambda s: captures.append(s) + self.app.strategy_file_name = f"{self.mock_strategy_name}.yml" + + trade_fee = AddedToCostTradeFee(percent=Decimal("5")) + order_id = PaperTradeExchange.random_order_id(order_side="BUY", trading_pair="BTC-USDT") + with self.app.trade_fill_db.get_new_session() as session: + o = Order( + id=order_id, + config_file_path=f"{self.mock_strategy_name}.yml", + strategy=self.mock_strategy_name, + market="binance", + symbol="BTC-USDT", + base_asset="BTC", + quote_asset="USDT", + creation_timestamp=0, + order_type="LMT", + amount=4, + leverage=0, + price=3, + last_status="PENDING", + last_update_timestamp=0, + ) + session.add(o) + for i in [1, 2]: + t = TradeFill( + config_file_path=f"{self.mock_strategy_name}.yml", + strategy=self.mock_strategy_name, + market="binance", + symbol="BTC-USDT", + base_asset="BTC", + quote_asset="USDT", + timestamp=i, + order_id=order_id, + trade_type="BUY", + order_type="LIMIT", + price=i, + amount=2, + leverage=1, + trade_fee=trade_fee.to_json(), + exchange_trade_id=f"someExchangeId{i}", + ) + session.add(t) + session.commit() + + self.app.list_trades(start_time=0) + + self.assertEqual(1, len(captures)) + + creation_time_str = str(datetime.datetime.fromtimestamp(0)) + + df_str_expected = ( + f"\n Recent trades:" + f"\n +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+----------+" # noqa: E501 + f"\n | Timestamp | Exchange | Market | Order_type | Side | Price | Amount | Leverage | Position | Age |" # noqa: E501 + f"\n |---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+----------|" # noqa: E501 + f"\n | {creation_time_str} | binance | BTC-USDT | limit | buy | 1 | 2 | 1 | NIL | 00:00:00 |" # noqa: E501 + f"\n | {creation_time_str} | binance | BTC-USDT | limit | buy | 2 | 2 | 1 | NIL | 00:00:00 |" # noqa: E501 + f"\n +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+----------+" # noqa: E501 + ) + + self.assertEqual(df_str_expected, captures[0]) diff --git a/test/hummingbot/client/command/test_import_command.py b/test/hummingbot/client/command/test_import_command.py new file mode 100644 index 0000000..c184cfc --- /dev/null +++ b/test/hummingbot/client/command/test_import_command.py @@ -0,0 +1,230 @@ +import asyncio +import unittest +from datetime import date, datetime, time +from decimal import Decimal +from pathlib import Path +from tempfile import TemporaryDirectory +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable, Type +from unittest.mock import AsyncMock, MagicMock, patch + +from pydantic import Field + +from hummingbot.client.command import import_command +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml, save_to_yml +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.strategy_config_data_types import BaseTradingStrategyConfigMap +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class ImportCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + + self.app = HummingbotApplication() + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + @staticmethod + async def raise_timeout(*args, **kwargs): + raise asyncio.TimeoutError + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + @staticmethod + def build_dummy_strategy_config_cls(strategy_name: str) -> Type[BaseClientModel]: + class SomeEnum(ClientConfigEnum): + ONE = "one" + + class DoubleNestedModel(BaseClientModel): + double_nested_attr: datetime = Field( + default=datetime(2022, 1, 1, 10, 30), + description="Double nested attr description" + ) + + class NestedModel(BaseClientModel): + nested_attr: str = Field( + default="some value", + description="Nested attr\nmultiline description", + ) + double_nested_model: DoubleNestedModel = Field( + default=DoubleNestedModel(), + ) + + class DummyModel(BaseTradingStrategyConfigMap): + strategy: str = strategy_name + exchange: str = "binance" + market: str = "BTC-USDT" + some_attr: SomeEnum = Field( + default=SomeEnum.ONE, + description="Some description", + ) + nested_model: NestedModel = Field( + default=NestedModel(), + description="Nested model description", + ) + another_attr: Decimal = Field( + default=Decimal("1.0"), + description="Some other\nmultiline description", + ) + non_nested_no_description: time = Field(default=time(10, 30),) + date_attr: date = Field(default=date(2022, 1, 2)) + no_default: str = Field(default=...) + + class Config: + title = "dummy_model" + + return DummyModel + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_import_config_file_success_legacy( + self, status_check_all_mock: AsyncMock, load_strategy_config_map_from_file: AsyncMock + ): + strategy_name = "some_strategy" + strategy_file_name = f"{strategy_name}.yml" + status_check_all_mock.return_value = True + strategy_conf_var = ConfigVar("strategy", None) + strategy_conf_var.value = strategy_name + load_strategy_config_map_from_file.return_value = {"strategy": strategy_conf_var} + + self.async_run_with_timeout(self.app.import_config_file(strategy_file_name)) + self.assertEqual(strategy_file_name, self.app.strategy_file_name) + self.assertEqual(strategy_name, self.app.strategy_name) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with("\nEnter \"start\" to start market making.") + ) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_import_config_file_handles_network_timeouts_legacy( + self, status_check_all_mock: AsyncMock, load_strategy_config_map_from_file: AsyncMock + ): + strategy_name = "some_strategy" + strategy_file_name = f"{strategy_name}.yml" + status_check_all_mock.side_effect = self.raise_timeout + strategy_conf_var = ConfigVar("strategy", None) + strategy_conf_var.value = strategy_name + load_strategy_config_map_from_file.return_value = {"strategy": strategy_conf_var} + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout( + self.app.import_config_file(strategy_file_name) + ) + self.assertEqual(None, self.app.strategy_file_name) + self.assertEqual(None, self.app.strategy_name) + + @patch("hummingbot.client.config.config_helpers.get_strategy_pydantic_config_cls") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_import_config_file_success( + self, status_check_all_mock: AsyncMock, get_strategy_pydantic_config_cls: MagicMock + ): + strategy_name = "perpetual_market_making" + strategy_file_name = f"{strategy_name}.yml" + status_check_all_mock.return_value = True + dummy_strategy_config_cls = self.build_dummy_strategy_config_cls(strategy_name) + get_strategy_pydantic_config_cls.return_value = dummy_strategy_config_cls + cm = ClientConfigAdapter(dummy_strategy_config_cls(no_default="some value")) + + with TemporaryDirectory() as d: + d = Path(d) + import_command.STRATEGIES_CONF_DIR_PATH = d + temp_file_name = d / strategy_file_name + save_to_yml(temp_file_name, cm) + self.async_run_with_timeout(self.app.import_config_file(strategy_file_name)) + + self.assertEqual(strategy_file_name, self.app.strategy_file_name) + self.assertEqual(strategy_name, self.app.strategy_name) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with("\nEnter \"start\" to start market making.") + ) + self.assertEqual(cm, self.app.strategy_config_map) + + @patch("hummingbot.client.config.config_helpers.get_strategy_pydantic_config_cls") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_import_incomplete_config_file_success( + self, status_check_all_mock: AsyncMock, get_strategy_pydantic_config_cls: MagicMock + ): + strategy_name = "perpetual_market_making" + strategy_file_name = f"{strategy_name}.yml" + status_check_all_mock.return_value = True + dummy_strategy_config_cls = self.build_dummy_strategy_config_cls(strategy_name) + get_strategy_pydantic_config_cls.return_value = dummy_strategy_config_cls + cm = ClientConfigAdapter(dummy_strategy_config_cls(no_default="some value")) + + with TemporaryDirectory() as d: + d = Path(d) + import_command.STRATEGIES_CONF_DIR_PATH = d + temp_file_name = d / strategy_file_name + cm_yml_str = cm.generate_yml_output_str_with_comments() + cm_yml_str = cm_yml_str.replace("\nno_default: some value\n", "") + with open(temp_file_name, "w+") as outfile: + outfile.write(cm_yml_str) + self.async_run_with_timeout(self.app.import_config_file(strategy_file_name)) + + self.assertEqual(strategy_file_name, self.app.strategy_file_name) + self.assertEqual(strategy_name, self.app.strategy_name) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with("\nEnter \"start\" to start market making.") + ) + self.assertNotEqual(cm, self.app.strategy_config_map) + + validation_errors = self.app.strategy_config_map.validate_model() + + self.assertEqual(1, len(validation_errors)) + self.assertEqual("no_default - field required", validation_errors[0]) + + @patch("hummingbot.client.config.config_helpers.get_strategy_pydantic_config_cls") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_import_config_file_wrong_name( + self, status_check_all_mock: AsyncMock, get_strategy_pydantic_config_cls: MagicMock + ): + strategy_name = "perpetual_market_making" + strategy_file_name = f"{strategy_name}.yml" + status_check_all_mock.return_value = True + dummy_strategy_config_cls = self.build_dummy_strategy_config_cls(strategy_name) + get_strategy_pydantic_config_cls.return_value = dummy_strategy_config_cls + cm = ClientConfigAdapter(dummy_strategy_config_cls(no_default="some value")) + + wrong_strategy_file_name = f"wrong-{strategy_file_name}" + with TemporaryDirectory() as d: + d = Path(d) + import_command.STRATEGIES_CONF_DIR_PATH = d + temp_file_name = d / strategy_file_name + save_to_yml(temp_file_name, cm) + try: + self.async_run_with_timeout( + self.app.import_config_file(wrong_strategy_file_name)) + except FileNotFoundError: + self.assertNotEqual(strategy_file_name, self.app.strategy_file_name) + self.assertNotEqual(strategy_name, self.app.strategy_name) + return + self.assertTrue(False) diff --git a/test/hummingbot/client/command/test_mqtt_command.py b/test/hummingbot/client/command/test_mqtt_command.py new file mode 100644 index 0000000..00a5eff --- /dev/null +++ b/test/hummingbot/client/command/test_mqtt_command.py @@ -0,0 +1,136 @@ +import asyncio +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, PropertyMock, patch + +from async_timeout import timeout + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.core.mock_api.mock_mqtt_server import FakeMQTTBroker + + +@patch("hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_HEALTH_CHECK", 0.0) +@patch("hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_RESTART_LONG", 0.0) +@patch("hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_RESTART_SHORT", 0.0) +class RemoteIfaceMQTTTests(TestCase): + # logging.Level required to receive logs from the exchange + level = 0 + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.instance_id = 'TEST_ID' + cls.fake_err_msg = "Some error" + cls.client_config_map = ClientConfigAdapter(ClientConfigMap()) + cls.hbapp = HummingbotApplication(client_config_map=cls.client_config_map) + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.hbapp.ev_loop = cls.ev_loop + cls.client_config_map.mqtt_bridge.mqtt_port = 1888 + cls.prev_instance_id = cls.client_config_map.instance_id + cls.client_config_map.instance_id = cls.instance_id + cls.fake_mqtt_broker = FakeMQTTBroker() + + @classmethod + def tearDownClass(cls) -> None: + cls.client_config_map.instance_id = cls.prev_instance_id + del cls.fake_mqtt_broker + super().tearDownClass() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.resume_test_event = asyncio.Event() + self.hbapp.logger().setLevel(1) + self.hbapp.logger().addHandler(self) + # MQTT Transport Patcher + self.mqtt_transport_patcher = patch( + 'commlib.transports.mqtt.MQTTTransport' + ) + self.addCleanup(self.mqtt_transport_patcher.stop) + self.mqtt_transport_mock = self.mqtt_transport_patcher.start() + self.mqtt_transport_mock.side_effect = self.fake_mqtt_broker.create_transport + # MQTT Patch Loggers Patcher + self.patch_loggers_patcher = patch( + 'hummingbot.remote_iface.mqtt.MQTTGateway.patch_loggers' + ) + self.addCleanup(self.patch_loggers_patcher.stop) + self.patch_loggers_mock = self.patch_loggers_patcher.start() + self.patch_loggers_mock.return_value = None + + def tearDown(self): + self.ev_loop.run_until_complete(self.hbapp.stop_mqtt_async()) + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.fake_mqtt_broker.clear() + self.mqtt_transport_patcher.stop() + self.patch_loggers_patcher.stop() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and str(record.getMessage()) == str(message) for record in self.log_records) + + async def wait_for_logged(self, log_level: str, message: str): + try: + async with timeout(3): + while not self._is_logged(log_level=log_level, message=message): + await asyncio.sleep(0.1) + except asyncio.TimeoutError as e: + print(f"Message: {message} was not logged.") + print(f"Received Logs: {[record.getMessage() for record in self.log_records]}") + raise e + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + async def _create_exception_and_unlock_test_with_event_async(self, *args, **kwargs): + self.resume_test_event.set() + raise RuntimeError(self.fake_err_msg) + + def _create_exception_and_unlock_test_with_event(self, *args, **kwargs): + self.resume_test_event.set() + raise RuntimeError(self.fake_err_msg) + + def _create_exception_and_unlock_test_with_event_not_impl(self, *args, **kwargs): + self.resume_test_event.set() + raise NotImplementedError(self.fake_err_msg) + + def test_start_mqtt_command(self): + self.ev_loop.run_until_complete( + self.hbapp.start_mqtt_async() + ) + self.ev_loop.run_until_complete(self.wait_for_logged("INFO", "MQTT Bridge connected with success.")) + + @patch('hummingbot.remote_iface.mqtt.MQTTGateway.start') + def test_start_mqtt_command_fails( + self, + mqtt_start_mock: MagicMock, + ): + mqtt_start_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.ev_loop.run_until_complete( + self.hbapp.start_mqtt_async() + ) + self.ev_loop.run_until_complete(self.wait_for_logged("ERROR", f"Failed to connect MQTT Bridge: {self.fake_err_msg}")) + + @patch('hummingbot.client.command.mqtt_command.MQTTCommand._mqtt_sleep_rate_autostart_retry', new_callable=PropertyMock) + @patch('hummingbot.remote_iface.mqtt.MQTTGateway.health', new_callable=PropertyMock) + def test_start_mqtt_command_retries_with_autostart( + self, + mqtt_health_mock: PropertyMock, + autostart_retry_mock: PropertyMock, + ): + mqtt_health_mock.side_effect = self._create_exception_and_unlock_test_with_event + autostart_retry_mock.return_value = 0.0 + self.client_config_map.mqtt_bridge.mqtt_autostart = True + self.hbapp.mqtt_start() + self.async_run_with_timeout(self.resume_test_event.wait()) + self.ev_loop.run_until_complete(self.wait_for_logged( + "ERROR", + f"Failed to connect MQTT Bridge: {self.fake_err_msg}. Retrying in 0.0 seconds." + )) + mqtt_health_mock.side_effect = lambda: True + self.ev_loop.run_until_complete(self.wait_for_logged("INFO", "MQTT Bridge connected with success.")) diff --git a/test/hummingbot/client/command/test_order_book_command.py b/test/hummingbot/client/command/test_order_book_command.py new file mode 100644 index 0000000..12f9e7d --- /dev/null +++ b/test/hummingbot/client/command/test_order_book_command.py @@ -0,0 +1,61 @@ +import asyncio +import unittest +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange + + +class OrderBookCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_show_order_book(self, notify_mock): + self.client_config_map.db_mode = DBSqliteMode() + + captures = [] + notify_mock.side_effect = lambda s: captures.append(s) + + exchange_name = "paper" + exchange = MockPaperExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.app.markets[exchange_name] = exchange + trading_pair = "BTC-USDT" + exchange.set_balanced_order_book( + trading_pair, + mid_price=10, + min_price=8.5, + max_price=11.5, + price_step_size=1, + volume_step_size=1, + ) + + self.async_run_with_timeout(self.app.show_order_book(exchange=exchange_name, live=False)) + + self.assertEqual(1, len(captures)) + + df_str_expected = ( + " market: mock_paper_exchange BTC-USDT" + "\n +-------------+--------------+-------------+--------------+" + "\n | bid_price | bid_volume | ask_price | ask_volume |" + "\n |-------------+--------------+-------------+--------------|" + "\n | 9.5 | 1 | 10.5 | 1 |" + "\n | 8.5 | 2 | 11.5 | 2 |" + "\n +-------------+--------------+-------------+--------------+" + ) + + self.assertEqual(df_str_expected, captures[0]) diff --git a/test/hummingbot/client/command/test_previous_command.py b/test/hummingbot/client/command/test_previous_command.py new file mode 100644 index 0000000..8fe78db --- /dev/null +++ b/test/hummingbot/client/command/test_previous_command.py @@ -0,0 +1,62 @@ +import asyncio +import unittest +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication + +from test.mock.mock_cli import CLIMockingAssistant # isort: skip + + +class PreviousCommandUnitTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + + self.client_config = ClientConfigMap() + self.config_adapter = ClientConfigAdapter(self.client_config) + + self.app = HummingbotApplication(self.config_adapter) + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def mock_user_response(self, config): + config.value = "yes" + + def test_no_previous_strategy_found(self): + self.config_adapter.previous_strategy = None + self.app.previous_strategy(option="") + self.assertTrue( + self.cli_mock_assistant.check_log_called_with("No previous strategy found.")) + + @patch("hummingbot.client.command.import_command.ImportCommand.import_command") + def test_strategy_found_and_user_declines(self, import_command: MagicMock): + strategy_name = "conf_1.yml" + self.cli_mock_assistant.queue_prompt_reply("No") + self.async_run_with_timeout( + self.app.prompt_for_previous_strategy(strategy_name) + ) + import_command.assert_not_called() + + @patch("hummingbot.client.command.import_command.ImportCommand.import_command") + def test_strategy_found_and_user_accepts(self, import_command: MagicMock): + strategy_name = "conf_1.yml" + self.config_adapter.previous_strategy = strategy_name + self.cli_mock_assistant.queue_prompt_reply("Yes") + self.async_run_with_timeout( + self.app.prompt_for_previous_strategy(strategy_name) + ) + import_command.assert_called() + self.assertTrue(import_command.call_args[0][1] == strategy_name) diff --git a/test/hummingbot/client/command/test_rate_command.py b/test/hummingbot/client/command/test_rate_command.py new file mode 100644 index 0000000..0c47010 --- /dev/null +++ b/test/hummingbot/client/command/test_rate_command.py @@ -0,0 +1,92 @@ +import asyncio +import unittest +from copy import deepcopy +from decimal import Decimal +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable, Dict, Optional +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.config_helpers import read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase + + +class DummyRateSource(RateSourceBase): + def __init__(self, price_dict: Dict[str, Decimal]): + self._price_dict = price_dict + + @property + def name(self): + return "dummy_rate_source" + + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + return deepcopy(self._price_dict) + + +class RateCommandTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.original_source = RateOracle.get_instance().source + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + + self.app = HummingbotApplication() + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + RateOracle.get_instance().source = self.original_source + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_show_token_value(self): + self.app.client_config_map.global_token.global_token_name = self.global_token + global_token_symbol = "$" + self.app.client_config_map.global_token.global_token_symbol = global_token_symbol + expected_rate = Decimal("2.0") + dummy_source = DummyRateSource(price_dict={self.trading_pair: expected_rate}) + RateOracle.get_instance().source = dummy_source + RateOracle.get_instance().quote_token = self.global_token + + self.async_run_with_timeout(self.app.show_token_value(self.target_token)) + + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg=f"Source: {dummy_source.name}") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg=f"1 {self.target_token} = {global_token_symbol} {expected_rate} {self.global_token}" + ) + ) + + def test_show_token_value_rate_not_available(self): + self.app.client_config_map.global_token.global_token_name = self.global_token + global_token_symbol = "$" + self.app.client_config_map.global_token.global_token_symbol = global_token_symbol + expected_rate = Decimal("2.0") + dummy_source = DummyRateSource(price_dict={self.trading_pair: expected_rate}) + RateOracle.get_instance().source = dummy_source + + self.async_run_with_timeout(self.app.show_token_value("SOMETOKEN")) + + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg=f"Source: {dummy_source.name}") + ) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with(msg="Rate is not available.") + ) diff --git a/test/hummingbot/client/command/test_status_command.py b/test/hummingbot/client/command/test_status_command.py new file mode 100644 index 0000000..8cc83d9 --- /dev/null +++ b/test/hummingbot/client/command/test_status_command.py @@ -0,0 +1,76 @@ +import asyncio +import unittest +from test.mock.mock_cli import CLIMockingAssistant +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class StatusCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + self.cli_mock_assistant = CLIMockingAssistant(self.app.app) + self.cli_mock_assistant.start() + + def tearDown(self) -> None: + self.cli_mock_assistant.stop() + super().tearDown() + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + return async_sleep + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + @patch("hummingbot.client.command.status_command.StatusCommand.validate_configs") + @patch("hummingbot.client.command.status_command.StatusCommand.validate_required_connections") + @patch("hummingbot.client.config.security.Security.is_decryption_done") + def test_status_check_all_handles_network_timeouts( + self, is_decryption_done_mock, validate_required_connections_mock, validate_configs_mock + ): + validate_required_connections_mock.side_effect = self.get_async_sleep_fn(delay=0.02) + validate_configs_mock.return_value = [] + self.client_config_map.commands_timeout.other_commands_timeout = 0.01 + is_decryption_done_mock.return_value = True + strategy_name = "avellaneda_market_making" + self.app.strategy_name = strategy_name + self.app.strategy_file_name = f"{strategy_name}.yml" + + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.status_check_all()) + self.assertTrue( + self.cli_mock_assistant.check_log_called_with( + msg="\nA network error prevented the connection check to complete. See logs for more details." + ) + ) diff --git a/test/hummingbot/client/command/test_ticker_command.py b/test/hummingbot/client/command/test_ticker_command.py new file mode 100644 index 0000000..6b92422 --- /dev/null +++ b/test/hummingbot/client/command/test_ticker_command.py @@ -0,0 +1,60 @@ +import asyncio +import unittest +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange + + +class TickerCommandTest(unittest.TestCase): + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher") + def setUp(self, _: MagicMock) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + self.async_run_with_timeout(read_system_configs_from_yml()) + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.app = HummingbotApplication(client_config_map=self.client_config_map) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify") + def test_show_ticker(self, notify_mock): + self.client_config_map.db_mode = DBSqliteMode() + + captures = [] + notify_mock.side_effect = lambda s: captures.append(s) + + exchange_name = "paper" + exchange = MockPaperExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.app.markets[exchange_name] = exchange + trading_pair = "BTC-USDT" + exchange.set_balanced_order_book( + trading_pair, + mid_price=10, + min_price=8.5, + max_price=11.5, + price_step_size=1, + volume_step_size=1, + ) + + self.async_run_with_timeout(self.app.show_ticker(exchange=exchange_name, live=False)) + + self.assertEqual(1, len(captures)) + + df_str_expected = ( + " Market: mock_paper_exchange" + "\n+------------+------------+-------------+--------------+" + "\n| Best Bid | Best Ask | Mid Price | Last Trade |" + "\n|------------+------------+-------------+--------------|" + "\n| 9.5 | 10.5 | 10 | nan |" + "\n+------------+------------+-------------+--------------+" + ) + + self.assertEqual(df_str_expected, captures[0]) diff --git a/test/hummingbot/client/config/__init__.py b/test/hummingbot/client/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/config/test_config_data_types.py b/test/hummingbot/client/config/test_config_data_types.py new file mode 100644 index 0000000..369b5d5 --- /dev/null +++ b/test/hummingbot/client/config/test_config_data_types.py @@ -0,0 +1,248 @@ +import json +import unittest +from datetime import date, datetime, time +from decimal import Decimal +from typing import Union + +from pydantic import Field, SecretStr +from pydantic.fields import FieldInfo + +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger +from hummingbot.client.config.config_data_types import BaseClientModel, ClientConfigEnum, ClientFieldData +from hummingbot.client.config.config_helpers import ClientConfigAdapter, ConfigTraversalItem +from hummingbot.client.config.security import Security + + +class SomeEnum(ClientConfigEnum): + ONE = "one" + + +class DoubleNestedModel(BaseClientModel): + double_nested_attr: datetime = Field( + default=datetime(2022, 1, 1, 10, 30), + description="Double nested attr description" + ) + + +class NestedModel(BaseClientModel): + nested_attr: str = Field( + default="some value", + description="Nested attr\nmultiline description", + ) + double_nested_model: DoubleNestedModel = Field( + default=DoubleNestedModel(), + ) + + +class DummyModel(BaseClientModel): + some_attr: SomeEnum = Field( + default=SomeEnum.ONE, + description="Some description", + client_data=ClientFieldData(), + ) + nested_model: NestedModel = Field( + default=NestedModel(), + description="Nested model description", + ) + another_attr: Decimal = Field( + default=Decimal("1.0"), + description="Some other\nmultiline description", + ) + non_nested_no_description: time = Field(default=time(10, 30), ) + date_attr: date = Field(default=date(2022, 1, 2)) + + class Config: + title = "dummy_model" + + +class BaseClientModelTest(unittest.TestCase): + def test_schema_encoding_removes_client_data_functions(self): + class DummyModel(BaseClientModel): + some_attr: str = Field( + default=..., + client_data=ClientFieldData( + prompt=lambda mi: "Some prompt?", + prompt_on_new=True, + ), + ) + + schema = DummyModel.schema_json() + j = json.loads(schema) + expected = { + "prompt": None, + "prompt_on_new": True, + "is_secure": False, + "is_connect_key": False, + } + self.assertEqual(expected, j["properties"]["some_attr"]["client_data"]) + + def test_traverse(self): + class DoubleNestedModel(BaseClientModel): + double_nested_attr: float = Field(default=3.0) + + class Config: + title = "double_nested_model" + + class NestedModelOne(BaseClientModel): + nested_attr: str = Field(default="some value") + double_nested_model: DoubleNestedModel = Field(default=DoubleNestedModel()) + + class Config: + title = "nested_mode_one" + + class NestedModelTwo(BaseClientModel): + class Config: + title = "nested_mode_two" + + class DummyModel(BaseClientModel): + some_attr: int = Field(default=1, client_data=ClientFieldData()) + nested_model: Union[NestedModelTwo, NestedModelOne] = Field(default=NestedModelOne()) + another_attr: Decimal = Field(default=Decimal("1.0")) + + class Config: + title = "dummy_model" + + expected_values = [ + ConfigTraversalItem(0, "some_attr", "some_attr", 1, "1", ClientFieldData(), None, int), + ConfigTraversalItem( + 0, + "nested_model", + "nested_model", + ClientConfigAdapter(NestedModelOne()), + "nested_mode_one", + None, + None, + NestedModel, + ), + ConfigTraversalItem( + 1, "nested_model.nested_attr", "nested_attr", "some value", "some value", None, None, str + ), + ConfigTraversalItem( + 1, + "nested_model.double_nested_model", + "double_nested_model", + ClientConfigAdapter(DoubleNestedModel()), + "", + None, + None, + DoubleNestedModel, + ), + ConfigTraversalItem( + 2, + "nested_model.double_nested_model.double_nested_attr", + "double_nested_attr", + 3.0, + "3.0", + None, + None, + float, + ), + ] + cm = ClientConfigAdapter(DummyModel()) + + for expected, actual in zip(expected_values, cm.traverse()): + self.assertEqual(expected.depth, actual.depth) + self.assertEqual(expected.config_path, actual.config_path) + self.assertEqual(expected.attr, actual.attr) + self.assertEqual(expected.value, actual.value) + self.assertEqual(expected.printable_value, actual.printable_value) + self.assertEqual(expected.client_field_data, actual.client_field_data) + self.assertIsInstance(actual.field_info, FieldInfo) + + def test_generate_yml_output_dict_with_comments(self): + instance = self._nested_config_adapter() + res_str = instance.generate_yml_output_str_with_comments() + expected_str = """\ +############################## +### dummy_model config ### +############################## + +# Some description +some_attr: one + +# Nested model description +nested_model: + nested_attr: some value + double_nested_model: + double_nested_attr: 2022-01-01 10:30:00 + +# Some other +# multiline description +another_attr: 1.0 + +non_nested_no_description: '10:30:00' + +date_attr: 2022-01-02 +""" + + self.assertEqual(expected_str, res_str) + + def test_generate_yml_output_dict_with_secret(self): + class DummyModel(BaseClientModel): + secret_attr: SecretStr + + class Config: + title = "dummy_model" + + Security.secrets_manager = ETHKeyFileSecretManger(password="some-password") + secret_value = "some_secret" + instance = ClientConfigAdapter(DummyModel(secret_attr=secret_value)) + res_str = instance.generate_yml_output_str_with_comments() + expected_str = """\ +############################## +### dummy_model config ### +############################## + +secret_attr: """ + + self.assertTrue(res_str.startswith(expected_str)) + self.assertNotIn(secret_value, res_str) + + def test_generate_yml_output_dict_with_sub_model_with_secret(self): + class DummySubModel(BaseClientModel): + secret_attr: SecretStr + + class Config: + title = "dummy_sub_model" + + class DummyModel(BaseClientModel): + sub_model: DummySubModel + + class Config: + title = "dummy_model" + + Security.secrets_manager = ETHKeyFileSecretManger(password="some-password") + secret_value = "some_secret" + sub_model = DummySubModel(secret_attr=secret_value) + instance = ClientConfigAdapter(DummyModel(sub_model=sub_model)) + res_str = instance.generate_yml_output_str_with_comments() + expected_str = ( + "##############################\n" + "### dummy_model config ###\n" + "##############################\n\n" + "sub_model:\n" + " secret_attr: " + ) + + self.assertTrue(res_str.startswith(expected_str)) + self.assertNotIn(secret_value, res_str) + + def test_config_paths_includes_all_intermediate_keys(self): + adapter = self._nested_config_adapter() + + all_config_paths = list(adapter.config_paths()) + expected_config_paths = [ + 'some_attr', + 'nested_model', + 'nested_model.nested_attr', + 'nested_model.double_nested_model', + 'nested_model.double_nested_model.double_nested_attr', + 'another_attr', + 'non_nested_no_description', + 'date_attr', + ] + + self.assertEqual(expected_config_paths, all_config_paths) + + def _nested_config_adapter(self): + return ClientConfigAdapter(DummyModel()) diff --git a/test/hummingbot/client/config/test_config_helpers.py b/test/hummingbot/client/config/test_config_helpers.py new file mode 100644 index 0000000..6f61331 --- /dev/null +++ b/test/hummingbot/client/config/test_config_helpers.py @@ -0,0 +1,182 @@ +import asyncio +import unittest +from decimal import Decimal +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Awaitable, List, Optional +from unittest.mock import MagicMock, patch + +from pydantic import Field, SecretStr + +from hummingbot.client.config import config_helpers +from hummingbot.client.config.client_config_map import ClientConfigMap, CommandShortcutModel +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger +from hummingbot.client.config.config_data_types import BaseClientModel, BaseConnectorConfigMap, ClientFieldData +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + ReadOnlyClientConfigAdapter, + get_connector_config_yml_path, + get_strategy_config_map, + load_connector_config_map_from_file, + save_to_yml, +) +from hummingbot.client.config.security import Security +from hummingbot.client.config.strategy_config_data_types import BaseStrategyConfigMap +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, +) + + +class ConfigHelpersTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_async_sleep_fn(delay: float): + async def async_sleep(*_, **__): + await asyncio.sleep(delay) + + return async_sleep + + def test_get_strategy_config_map(self): + cm = get_strategy_config_map(strategy="avellaneda_market_making") + self.assertIsInstance(cm.hb_config, AvellanedaMarketMakingConfigMap) + self.assertFalse(hasattr(cm, "market")) # uninitialized instance + + def test_save_to_yml(self): + class DummyStrategy(BaseStrategyConfigMap): + class Config: + title = "pure_market_making" + + strategy: str = "pure_market_making" + + cm = ClientConfigAdapter(DummyStrategy()) + expected_str = """\ +##################################### +### pure_market_making config ### +##################################### + +strategy: pure_market_making +""" + with TemporaryDirectory() as d: + d = Path(d) + temp_file_name = d / "cm.yml" + save_to_yml(temp_file_name, cm) + with open(temp_file_name) as f: + actual_str = f.read() + self.assertEqual(expected_str, actual_str) + + def test_save_command_shortcuts_to_yml(self): + class DummyStrategy(BaseClientModel): + command_shortcuts: List[CommandShortcutModel] = Field( + default=[ + CommandShortcutModel( + command="spreads", + help="Set bid and ask spread", + arguments=["Bid Spread", "Ask Spread"], + output=["config bid_spread $1", "config ask_spread $2"] + ) + ] + ) + another_attr: Decimal = Field( + default=Decimal("1.0"), + description="Some other\nmultiline description", + ) + + class Config: + title = "dummy_global_config" + + cm = ClientConfigAdapter(DummyStrategy()) + expected_str = ( + "######################################\n" + "### dummy_global_config config ###\n" + "######################################\n\n" + "command_shortcuts:\n" + "- command: spreads\n" + " help: Set bid and ask spread\n" + " arguments:\n" + " - Bid Spread\n" + " - Ask Spread\n" + " output:\n" + " - config bid_spread $1\n" + " - config ask_spread $2\n\n" + "# Some other\n" + "# multiline description\n" + "another_attr: 1.0\n" + ) + + with TemporaryDirectory() as d: + d = Path(d) + temp_file_name = d / "cm.yml" + save_to_yml(temp_file_name, cm) + with open(temp_file_name) as f: + actual_str = f.read() + self.assertEqual(expected_str, actual_str) + + @patch("hummingbot.client.config.config_helpers.AllConnectorSettings.get_connector_config_keys") + def test_load_connector_config_map_from_file_with_secrets(self, get_connector_config_keys_mock: MagicMock): + class DummyConnectorModel(BaseConnectorConfigMap): + connector = "some-connector" + secret_attr: Optional[SecretStr] = Field(default=None, client_data=ClientFieldData(is_secure=True)) + + password = "some-pass" + Security.secrets_manager = ETHKeyFileSecretManger(password) + cm = ClientConfigAdapter(DummyConnectorModel(secret_attr="some_secret")) + get_connector_config_keys_mock.return_value = DummyConnectorModel() + with TemporaryDirectory() as d: + d = Path(d) + config_helpers.CONNECTORS_CONF_DIR_PATH = d + temp_file_name = get_connector_config_yml_path(cm.connector) + save_to_yml(temp_file_name, cm) + cm_loaded = load_connector_config_map_from_file(temp_file_name) + + self.assertEqual(cm, cm_loaded) + + def test_decrypt_config_map_secret_values(self): + class DummySubModel(BaseClientModel): + secret_attr: SecretStr + + class Config: + title = "dummy_sub_model" + + class DummyModel(BaseClientModel): + sub_model: DummySubModel + + class Config: + title = "dummy_model" + + Security.secrets_manager = ETHKeyFileSecretManger(password="some-password") + secret_value = "some_secret" + encrypted_secret_value = Security.secrets_manager.encrypt_secret_value("secret_attr", secret_value) + sub_model = DummySubModel(secret_attr=encrypted_secret_value) + instance = ClientConfigAdapter(DummyModel(sub_model=sub_model)) + + self.assertEqual(encrypted_secret_value, instance.sub_model.secret_attr.get_secret_value()) + + instance._decrypt_all_internal_secrets() + + self.assertEqual(secret_value, instance.sub_model.secret_attr.get_secret_value()) + + +class ReadOnlyClientAdapterTest(unittest.TestCase): + + def test_read_only_adapter_can_be_created(self): + adapter = ClientConfigAdapter(ClientConfigMap()) + read_only_adapter = ReadOnlyClientConfigAdapter(adapter.hb_config) + + self.assertEqual(adapter.hb_config, read_only_adapter.hb_config) + + def test_read_only_adapter_raises_exception_when_setting_value(self): + read_only_adapter = ReadOnlyClientConfigAdapter(ClientConfigMap()) + initial_instance_id = read_only_adapter.instance_id + + with self.assertRaises(AttributeError) as context: + read_only_adapter.instance_id = "newInstanceID" + + self.assertEqual("Cannot set an attribute on a read-only client adapter", str(context.exception)) + self.assertEqual(initial_instance_id, read_only_adapter.instance_id) diff --git a/test/hummingbot/client/config/test_config_templates.py b/test/hummingbot/client/config/test_config_templates.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/config/test_config_validators.py b/test/hummingbot/client/config/test_config_validators.py new file mode 100644 index 0000000..989825d --- /dev/null +++ b/test/hummingbot/client/config/test_config_validators.py @@ -0,0 +1,288 @@ + +import unittest + +import hummingbot.client.config.config_validators as config_validators +from hummingbot.client.settings import AllConnectorSettings + + +class ConfigValidatorsTests(unittest.TestCase): + def test_validation_does_not_fail_with_valid_timestamp_string(self): + timestamp_string = "2021-06-23 10:15:20" + + self.assertIsNone(config_validators.validate_datetime_iso_string(timestamp_string)) + + def test_validation_fails_with_valid_timestamp_string(self): + timestamp_string = "21-6-23 10:15:20" + validation_error = config_validators.validate_datetime_iso_string(timestamp_string) + self.assertEqual(validation_error, "Incorrect date time format (expected is YYYY-MM-DD HH:MM:SS)") + + def test_validate_exchange_connector_exist(self): + exchange = "binance" + + self.assertIsNone(config_validators.validate_exchange(exchange)) + + def test_validate_exchange_connector_does_not_exist(self): + non_existant_exchange = "TEST_NON_EXISTANT_EXCHANGE" + + validation_error = config_validators.validate_exchange(non_existant_exchange) + self.assertEqual(validation_error, f"Invalid exchange, please choose value from {AllConnectorSettings.get_exchange_names()}") + + def test_validate_derivative_connector_exist(self): + derivative = "binance_perpetual" + + self.assertIsNone(config_validators.validate_derivative(derivative)) + + def test_validate_derivative_connector_does_not_exist(self): + non_existant_derivative = "TEST_NON_EXISTANT_DERIVATIVE" + + validation_error = config_validators.validate_derivative(non_existant_derivative) + self.assertEqual(validation_error, f"Invalid derivative, please choose value from {AllConnectorSettings.get_derivative_names()}") + + def test_validate_connector_connector_exist(self): + connector = "binance" + + self.assertIsNone(config_validators.validate_connector(connector)) + + def test_validate_connector_connector_does_not_exist(self): + non_existant_connector = "TEST_NON_EXISTANT_CONNECTOR" + + validation_error = config_validators.validate_connector(non_existant_connector) + self.assertEqual(validation_error, f"Invalid connector, please choose value from {AllConnectorSettings.get_connector_settings().keys()}") + + def test_validate_bool_succeed(self): + valid_values = ['true', 'yes', 'y', 'false', 'no', 'n'] + + validations = [config_validators.validate_bool(value) for value in valid_values] + for validation in validations: + self.assertIsNone(validation) + + def test_validate_bool_fails(self): + wrong_value = "ye" + valid_values = ('true', 'yes', 'y', 'false', 'no', 'n') + + validation_error = config_validators.validate_bool(wrong_value) + self.assertEqual(validation_error, f"Invalid value, please choose value from {valid_values}") + + def test_validate_int_without_min_and_max_succeed(self): + value = 1 + + validation = config_validators.validate_int(value) + self.assertIsNone(validation) + + def test_validate_int_wrong_value_fails(self): + value = "wrong_value" + + validation = config_validators.validate_int(value) + self.assertEqual(validation, f"{value} is not in integer format.") + + def test_validate_int_with_min_and_max_exclusive_succeed(self): + value = 1 + min_value = 0 + max_value = 2 + inclusive = False + + validation = config_validators.validate_int(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_min_and_max_inclusive_succeed(self): + value = 1 + min_value = 0 + max_value = 1 + inclusive = True + + validation = config_validators.validate_int(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_min_and_max_exclusive_fails(self): + value = 1 + min_value = 0 + max_value = 1 + inclusive = False + + validation = config_validators.validate_int(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be between {min_value} and {max_value} (exclusive).") + + def test_validate_int_with_min_and_max_inclusive_fails(self): + value = 5 + min_value = 0 + max_value = 1 + inclusive = True + + validation = config_validators.validate_int(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be between {min_value} and {max_value}.") + + def test_validate_int_with_min_exclusive_succeed(self): + value = 1 + min_value = 0 + inclusive = False + + validation = config_validators.validate_int(value, min_value=min_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_min_inclusive_succeed(self): + value = 1 + min_value = 0 + inclusive = True + + validation = config_validators.validate_int(value, min_value=min_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_min_exclusive_fails(self): + value = 1 + min_value = 1 + inclusive = False + + validation = config_validators.validate_int(value, min_value=min_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be more than {min_value}.") + + def test_validate_int_with_min_inclusive_fails(self): + value = 1 + min_value = 2 + inclusive = True + + validation = config_validators.validate_int(value, min_value=min_value, inclusive=inclusive) + self.assertEqual(validation, f"Value cannot be less than {min_value}.") + + def test_validate_int_with_max_exclusive_succeed(self): + value = 1 + max_value = 2 + inclusive = False + + validation = config_validators.validate_int(value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_max_inclusive_succeed(self): + value = 1 + max_value = 1 + inclusive = True + + validation = config_validators.validate_int(value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_int_with_max_exclusive_fails(self): + value = 1 + max_value = 1 + inclusive = False + + validation = config_validators.validate_int(value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be less than {max_value}.") + + def test_validate_int_with_max_inclusive_fails(self): + value = 5 + max_value = 1 + inclusive = True + + validation = config_validators.validate_int(value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value cannot be more than {max_value}.") + + def test_validate_float_without_min_and_max_succeed(self): + value = 1.0 + + validation = config_validators.validate_float(value) + self.assertIsNone(validation) + + def test_validate_float_wrong_value_fails(self): + value = "wrong_value" + + validation = config_validators.validate_float(value) + self.assertEqual(validation, f"{value} is not in integer format.") + + def test_validate_float_with_min_and_max_exclusive_succeed(self): + value = 1.0 + min_value = 0.0 + max_value = 2.0 + inclusive = False + + validation = config_validators.validate_float(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_min_and_max_inclusive_succeed(self): + value = 1.0 + min_value = 0.0 + max_value = 1.0 + inclusive = True + + validation = config_validators.validate_float(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_min_and_max_exclusive_fails(self): + value = 1.0 + min_value = 0.0 + max_value = 1.0 + inclusive = False + + validation = config_validators.validate_float(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be between {min_value} and {max_value} (exclusive).") + + def test_validate_float_with_min_and_max_inclusive_fails(self): + value = 5.0 + min_value = 0.0 + max_value = 1.0 + inclusive = True + + validation = config_validators.validate_float(value, min_value=min_value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be between {min_value} and {max_value}.") + + def test_validate_float_with_min_exclusive_succeed(self): + value = 1.0 + min_value = 0.0 + inclusive = False + + validation = config_validators.validate_float(value, min_value=min_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_min_inclusive_succeed(self): + value = 1.0 + min_value = 0.0 + inclusive = True + + validation = config_validators.validate_float(value, min_value=min_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_min_exclusive_fails(self): + value = 1.0 + min_value = 1.0 + inclusive = False + + validation = config_validators.validate_float(value, min_value=min_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be more than {min_value}.") + + def test_validate_float_with_min_inclusive_fails(self): + value = 1.0 + min_value = 2.0 + inclusive = True + + validation = config_validators.validate_float(value, min_value=min_value, inclusive=inclusive) + self.assertEqual(validation, f"Value cannot be less than {min_value}.") + + def test_validate_float_with_max_exclusive_succeed(self): + value = 1.0 + max_value = 2.0 + inclusive = False + + validation = config_validators.validate_float(value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_max_inclusive_succeed(self): + value = 1.0 + max_value = 1.0 + inclusive = True + + validation = config_validators.validate_float(value, max_value=max_value, inclusive=inclusive) + self.assertIsNone(validation) + + def test_validate_float_with_max_exclusive_fails(self): + value = 1.0 + max_value = 1.0 + inclusive = False + + validation = config_validators.validate_float(value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value must be less than {max_value}.") + + def test_validate_float_with_max_inclusive_fails(self): + value = 5.0 + max_value = 1.0 + inclusive = True + + validation = config_validators.validate_float(value, max_value=max_value, inclusive=inclusive) + self.assertEqual(validation, f"Value cannot be more than {max_value}.") diff --git a/test/hummingbot/client/config/test_config_var.py b/test/hummingbot/client/config/test_config_var.py new file mode 100644 index 0000000..0661df9 --- /dev/null +++ b/test/hummingbot/client/config/test_config_var.py @@ -0,0 +1,131 @@ +import asyncio +import unittest +from hummingbot.client.config.config_var import ConfigVar + + +class ConfigVarTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + def test_init_defaults_assigned(self): + var = ConfigVar("key", "test prompt") + self.assertEqual("key", var.key) + self.assertEqual("test prompt", var.prompt) + self.assertEqual(False, var.is_secure) + self.assertEqual(None, var.default) + self.assertEqual("str", var.type) + self.assertTrue(callable(var._required_if)) + self.assertTrue(callable(var._validator)) + self.assertTrue(callable(var._on_validated)) + self.assertFalse(var.prompt_on_new) + self.assertFalse(var.is_connect_key) + self.assertIsNone(var.printable_key) + + def test_init_values_assigned(self): + def fn_a(): + return 1 + + def fn_b(): + return 2 + + def fn_c(): + return 3 + var = ConfigVar(key="key", + prompt="test prompt", + is_secure=True, + default=1, + type_str="int", + required_if=fn_a, + validator=fn_b, + on_validated=fn_c, + prompt_on_new=True, + is_connect_key=True, + printable_key="print_key") + self.assertEqual("key", var.key) + self.assertEqual("test prompt", var.prompt) + self.assertEqual(True, var.is_secure) + self.assertEqual(1, var.default) + self.assertEqual("int", var.type) + self.assertEqual(fn_a, var._required_if) + self.assertEqual(fn_b, var._validator) + self.assertEqual(fn_c, var._on_validated) + self.assertTrue(var.prompt_on_new) + self.assertTrue(var.is_connect_key) + self.assertEqual("print_key", var.printable_key) + + def test_get_prompt(self): + def prompt(): + return "fn prompt" + + async def async_prompt(): + return "async fn prompt" + var = ConfigVar("key", 'text prompt') + self.assertEqual("text prompt", asyncio.get_event_loop().run_until_complete(var.get_prompt())) + var = ConfigVar("key", prompt) + self.assertEqual("fn prompt", asyncio.get_event_loop().run_until_complete(var.get_prompt())) + var = ConfigVar("key", async_prompt) + self.assertEqual("async fn prompt", asyncio.get_event_loop().run_until_complete(var.get_prompt())) + + def test_required(self): + var = ConfigVar("key", 'prompt', required_if=lambda: True) + self.assertTrue(var.required) + + def test_required_assertion_error(self): + var = ConfigVar("key", 'prompt', required_if=True) + with self.assertRaises(AssertionError): + var.required + + def test_validate_assertion_errors(self): + loop = asyncio.get_event_loop() + var = ConfigVar("key", 'prompt', validator="a") + with self.assertRaises(AssertionError): + loop.run_until_complete(var.validate("1")) + var = ConfigVar("key", 'prompt', validator=lambda v: None, on_validated="a") + with self.assertRaises(AssertionError): + loop.run_until_complete(var.validate("1")) + + def test_validate_value_required(self): + loop = asyncio.get_event_loop() + var = ConfigVar("key", 'prompt', required_if=lambda: True, validator=lambda v: None) + self.assertEqual("Value is required.", loop.run_until_complete(var.validate(None))) + self.assertEqual("Value is required.", loop.run_until_complete(var.validate(""))) + var = ConfigVar("key", 'prompt', required_if=lambda: False, validator=lambda v: None) + self.assertEqual(None, loop.run_until_complete(var.validate(None))) + self.assertEqual(None, loop.run_until_complete(var.validate(""))) + self.assertEqual(None, loop.run_until_complete(var.validate(1))) + + def test_validator_functions_called(self): + def validator(_): + return "validator error" + + async def async_validator(_): + return "async validator error" + loop = asyncio.get_event_loop() + var = ConfigVar("key", 'prompt', validator=validator) + self.assertEqual("validator error", loop.run_until_complete(var.validate("a"))) + var = ConfigVar("key", 'prompt', validator=async_validator) + self.assertEqual("async validator error", loop.run_until_complete(var.validate("a"))) + + def test_on_validated_called(self): + on_validated_txt = "" + + def on_validated(value): + nonlocal on_validated_txt + on_validated_txt = value + " on validated" + + async def async_on_validated(value): + nonlocal on_validated_txt + on_validated_txt = value + " async on validated" + loop = asyncio.get_event_loop() + var = ConfigVar("key", 'prompt', validator=lambda v: None, on_validated=on_validated) + loop.run_until_complete(var.validate("a")) + self.assertEqual("a on validated", on_validated_txt) + on_validated_txt = "" + var = ConfigVar("key", 'prompt', validator=lambda v: None, on_validated=async_on_validated) + loop.run_until_complete(var.validate("b")) + self.assertEqual("b async on validated", on_validated_txt) + on_validated_txt = "" + var = ConfigVar("key", 'prompt', validator=lambda v: "validate error", on_validated=async_on_validated) + loop.run_until_complete(var.validate("b")) + self.assertEqual("", on_validated_txt) diff --git a/test/hummingbot/client/config/test_security.py b/test/hummingbot/client/config/test_security.py new file mode 100644 index 0000000..0bd2a87 --- /dev/null +++ b/test/hummingbot/client/config/test_security.py @@ -0,0 +1,152 @@ +import asyncio +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Awaitable + +from hummingbot.client.config import config_crypt, config_helpers, security +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger, store_password_verification, validate_password +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + api_keys_from_connector_config_map, + get_connector_config_yml_path, + save_to_yml, +) +from hummingbot.client.config.security import Security +from hummingbot.connector.exchange.binance.binance_utils import BinanceConfigMap +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler + + +class SecurityTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + + def setUp(self) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + self.new_conf_dir_path = TemporaryDirectory() + self.default_pswrd_verification_path = security.PASSWORD_VERIFICATION_PATH + self.default_connectors_conf_dir_path = config_helpers.CONNECTORS_CONF_DIR_PATH + mock_conf_dir = Path(self.new_conf_dir_path.name) / "conf" + mock_conf_dir.mkdir(parents=True, exist_ok=True) + config_crypt.PASSWORD_VERIFICATION_PATH = mock_conf_dir / ".password_verification" + + security.PASSWORD_VERIFICATION_PATH = config_crypt.PASSWORD_VERIFICATION_PATH + config_helpers.CONNECTORS_CONF_DIR_PATH = ( + Path(self.new_conf_dir_path.name) / "connectors" + ) + config_helpers.CONNECTORS_CONF_DIR_PATH.mkdir(parents=True, exist_ok=True) + self.connector = "binance" + self.api_key = "someApiKey" + self.api_secret = "someSecret" + + def tearDown(self) -> None: + config_crypt.PASSWORD_VERIFICATION_PATH = self.default_pswrd_verification_path + security.PASSWORD_VERIFICATION_PATH = config_crypt.PASSWORD_VERIFICATION_PATH + config_helpers.CONNECTORS_CONF_DIR_PATH = self.default_connectors_conf_dir_path + self.new_conf_dir_path.cleanup() + self.reset_security() + super().tearDown() + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 3): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def store_binance_config(self) -> ClientConfigAdapter: + config_map = ClientConfigAdapter( + BinanceConfigMap(binance_api_key=self.api_key, binance_api_secret=self.api_secret) + ) + file_path = get_connector_config_yml_path(self.connector) + save_to_yml(file_path, config_map) + return config_map + + @staticmethod + def reset_decryption_done(): + Security._decryption_done = asyncio.Event() + + @staticmethod + def reset_security(): + Security.__instance = None + Security.secrets_manager = None + Security._secure_configs = {} + Security._decryption_done = asyncio.Event() + + def test_password_process(self): + self.assertTrue(Security.new_password_required()) + + password = "som-password" + secrets_manager = ETHKeyFileSecretManger(password) + store_password_verification(secrets_manager) + + self.assertFalse(Security.new_password_required()) + self.assertTrue(validate_password(secrets_manager)) + + another_secrets_manager = ETHKeyFileSecretManger("another-password") + + self.assertFalse(validate_password(another_secrets_manager)) + + def test_login(self): + password = "som-password" + secrets_manager = ETHKeyFileSecretManger(password) + store_password_verification(secrets_manager) + + Security.login(secrets_manager) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + config_map = self.store_binance_config() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.reset_decryption_done() + Security.decrypt_all() + self.async_run_with_timeout(Security.wait_til_decryption_done()) + + self.assertTrue(Security.is_decryption_done()) + self.assertTrue(Security.any_secure_configs()) + self.assertTrue(Security.connector_config_file_exists(self.connector)) + + api_keys = Security.api_keys(self.connector) + expected_keys = api_keys_from_connector_config_map(config_map) + + self.assertEqual(expected_keys, api_keys) + + def test_update_secure_config(self): + password = "som-password" + secrets_manager = ETHKeyFileSecretManger(password) + store_password_verification(secrets_manager) + + Security.login(secrets_manager) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + + binance_config = ClientConfigAdapter( + BinanceConfigMap(binance_api_key=self.api_key, binance_api_secret=self.api_secret) + ) + Security.update_secure_config(binance_config) + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + + self.reset_decryption_done() + Security.decrypt_all() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + + binance_loaded_config = Security.decrypted_value(binance_config.connector) + + self.assertEqual(binance_config, binance_loaded_config) + + binance_config.binance_api_key = "someOtherApiKey" + Security.update_secure_config(binance_config) + + self.reset_decryption_done() + Security.decrypt_all() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + + binance_loaded_config = Security.decrypted_value(binance_config.connector) + + self.assertEqual(binance_config, binance_loaded_config) diff --git a/test/hummingbot/client/config/test_strategy_config_data_types.py b/test/hummingbot/client/config/test_strategy_config_data_types.py new file mode 100644 index 0000000..801053d --- /dev/null +++ b/test/hummingbot/client/config/test_strategy_config_data_types.py @@ -0,0 +1,133 @@ +import asyncio +import json +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.client import settings +from hummingbot.client.config.config_data_types import BaseClientModel +from hummingbot.client.config.config_helpers import ClientConfigAdapter, ConfigValidationError +from hummingbot.client.config.strategy_config_data_types import ( + BaseTradingStrategyConfigMap, + BaseTradingStrategyMakerTakerConfigMap, +) +from hummingbot.client.settings import AllConnectorSettings, ConnectorType + + +class BaseStrategyConfigMapTest(TestCase): + def test_generate_yml_output_dict_title(self): + class DummyStrategy(BaseClientModel): + class Config: + title = "pure_market_making" + + strategy: str = "pure_market_making" + + instance = ClientConfigAdapter(DummyStrategy()) + res_str = instance.generate_yml_output_str_with_comments() + + expected_str = """\ +##################################### +### pure_market_making config ### +##################################### + +strategy: pure_market_making +""" + + self.assertEqual(expected_str, res_str) + + +class BaseTradingStrategyConfigMapTest(TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.exchange = "binance" + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + # Reset the list of connectors (there could be changes introduced by other tests when running the suite + AllConnectorSettings.create_connector_settings() + + def setUp(self) -> None: + super().setUp() + config_settings = self.get_default_map() + self.config_map = ClientConfigAdapter(BaseTradingStrategyConfigMap(**config_settings)) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_default_map(self) -> Dict[str, str]: + config_settings = { + "strategy": "pure_market_making", + "exchange": self.exchange, + "market": self.trading_pair, + } + return config_settings + + @patch("hummingbot.client.config.strategy_config_data_types.validate_market_trading_pair") + def test_validators(self, validate_market_trading_pair_mock): + with self.assertRaises(ConfigValidationError) as e: + self.config_map.exchange = "test-exchange" + + error_msg = "Invalid exchange, please choose value from " + self.assertTrue(str(e.exception).startswith(error_msg)) + + alt_pair = "ETH-USDT" + error_msg = "Failed" + validate_market_trading_pair_mock.side_effect = ( + lambda m, v: None if v in [self.trading_pair, alt_pair] else error_msg + ) + + self.config_map.market = alt_pair + self.assertEqual(alt_pair, self.config_map.market) + + with self.assertRaises(ConfigValidationError) as e: + self.config_map.market = "XXX-USDT" + + self.assertTrue(str(e.exception).startswith(error_msg)) + + def test_jason_schema_includes_all_connectors_for_exchange_field(self): + schema = BaseTradingStrategyConfigMap.schema_json() + schema_dict = json.loads(schema) + + self.assertIn("Exchanges", schema_dict["definitions"]) + expected_connectors = [ + connector_setting.name for connector_setting in + AllConnectorSettings.get_connector_settings().values() + if connector_setting.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + ] + expected_connectors.extend(settings.PAPER_TRADE_EXCHANGES) + expected_connectors.sort() + self.assertEqual(expected_connectors, schema_dict["definitions"]["Exchanges"]["enum"]) + + +class BaseTradingStrategyMakerTakerConfigMapTests(TestCase): + + def test_maker_field_jason_schema_includes_all_connectors_for_exchange_field(self): + schema = BaseTradingStrategyMakerTakerConfigMap.schema_json() + schema_dict = json.loads(schema) + + self.assertIn("MakerMarkets", schema_dict["definitions"]) + expected_connectors = [ + connector_setting.name for connector_setting in + AllConnectorSettings.get_connector_settings().values() + if connector_setting.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + ] + expected_connectors.extend(settings.PAPER_TRADE_EXCHANGES) + expected_connectors.sort() + self.assertEqual(expected_connectors, schema_dict["definitions"]["MakerMarkets"]["enum"]) + + def test_taker_field_jason_schema_includes_all_connectors_for_exchange_field(self): + schema = BaseTradingStrategyMakerTakerConfigMap.schema_json() + schema_dict = json.loads(schema) + + self.assertIn("TakerMarkets", schema_dict["definitions"]) + expected_connectors = [ + connector_setting.name for connector_setting in + AllConnectorSettings.get_connector_settings().values() + if connector_setting.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + ] + expected_connectors.extend(settings.PAPER_TRADE_EXCHANGES) + expected_connectors.sort() + self.assertEqual(expected_connectors, schema_dict["definitions"]["TakerMarkets"]["enum"]) diff --git a/test/hummingbot/client/test_connector_setting.py b/test/hummingbot/client/test_connector_setting.py new file mode 100644 index 0000000..b74c723 --- /dev/null +++ b/test/hummingbot/client/test_connector_setting.py @@ -0,0 +1,40 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + + +class ConnectorSettingTests(TestCase): + + def test_connector_setting_creates_non_trading_connector_instance(self): + setting = ConnectorSetting( + name='binance', + type=ConnectorType.Exchange, + example_pair='ZRX-ETH', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'binance_api_key': ConfigVar(key='binance_api_key', prompt=""), + 'binance_api_secret': ConfigVar(key='binance_api_secret', prompt="")}, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + + connector: BinanceExchange = setting.non_trading_connector_instance_with_default_configuration() + + self.assertEqual("binance", connector.name) + self.assertEqual("com", connector._domain) + self.assertEqual("", connector._auth.api_key) + self.assertEqual("", connector._auth.secret_key) + self.assertFalse(connector._trading_required) diff --git a/test/hummingbot/client/test_formatter.py b/test/hummingbot/client/test_formatter.py new file mode 100644 index 0000000..55ef205 --- /dev/null +++ b/test/hummingbot/client/test_formatter.py @@ -0,0 +1,39 @@ +import unittest +from decimal import Decimal + +from hummingbot.client import format_decimal, FLOAT_PRINTOUT_PRECISION + + +class FormatterTest(unittest.TestCase): + def test_precision_assumption(self): + self.assertEqual(8, FLOAT_PRINTOUT_PRECISION) # test cases must be adapted on precision change + + def test_format_float_decimal_places_rounded(self): + n = 0.987654321 + s = format_decimal(n) + self.assertEqual("0.98765432", s) + + def test_format_float_places_no_rounding(self): + n = 0.21 + s = format_decimal(n) + self.assertEqual("0.21", s) + + def test_format_large_float(self): + n = 987654321.0 + s = format_decimal(n) + self.assertEqual("987654321", s) + + def test_format_decimal_obj_decimal_places_rounded(self): + n = Decimal("0.987654321") + s = format_decimal(n) + self.assertEqual("0.98765432", s) + + def test_format_decimal_obj_places_no_rounding(self): + n = Decimal("0.21") + s = format_decimal(n) + self.assertEqual("0.21", s) + + def test_format_large_decimal_obj(self): + n = Decimal("987654321.0") + s = format_decimal(n) + self.assertEqual("987654321", s) diff --git a/test/hummingbot/client/test_performance.py b/test/hummingbot/client/test_performance.py new file mode 100644 index 0000000..fb8439f --- /dev/null +++ b/test/hummingbot/client/test_performance.py @@ -0,0 +1,343 @@ +import asyncio +import time +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.client.performance import PerformanceMetrics +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.trade import Trade +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, DeductedFromReturnsTradeFee, TokenAmount +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.model.order import Order # noqa — Order needs to be defined for TradeFill +from hummingbot.model.order_status import OrderStatus # noqa — Order needs to be defined for TradeFill +from hummingbot.model.trade_fill import TradeFill + +trading_pair = "HBOT-USDT" +base, quote = trading_pair.split("-") + + +class PerformanceMetricsUnitTest(unittest.TestCase): + + def tearDown(self) -> None: + RateOracle._shared_instance = None + super().tearDown() + + def mock_trade(self, id, amount, price, position="OPEN", type="BUY", fee=None): + trade = MagicMock() + trade.order_id = id + trade.position = position + trade.trade_type = type + trade.amount = amount + trade.price = price + if fee: + trade.trade_fee = fee.to_json() + + return trade + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_position_order_returns_nothing_when_no_open_and_no_close_orders(self): + trade_for_open = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") + for i in range(3)] + trades_for_close = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") + for i in range(2)] + + self.assertIsNone(PerformanceMetrics.position_order(trade_for_open, trades_for_close)) + + trade_for_open[1].position = "OPEN" + + self.assertIsNone(PerformanceMetrics.position_order(trade_for_open, trades_for_close)) + + trade_for_open[1].position = "INVALID" + trades_for_close[-1].position = "CLOSE" + + self.assertIsNone(PerformanceMetrics.position_order(trade_for_open, trades_for_close)) + + def test_position_order_returns_open_and_close_pair(self): + trades_for_open = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") + for i in range(3)] + trades_for_close = [self.mock_trade(id=f"order{i}", amount=100, price=10, position="INVALID") + for i in range(2)] + + trades_for_open[1].position = "OPEN" + trades_for_close[-1].position = "CLOSE" + + selected_open, selected_close = PerformanceMetrics.position_order(trades_for_open.copy(), + trades_for_close.copy()) + self.assertEqual(selected_open, trades_for_open[1]) + self.assertEqual(selected_close, trades_for_close[-1]) + + def test_aggregated_position_with_no_trades(self): + aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order([], []) + + self.assertEqual(len(aggregated_buys), 0) + self.assertEqual(len(aggregated_sells), 0) + + def test_aggregated_position_for_unrelated_trades(self): + trades = [] + + trades.append(self.mock_trade(id="order1", amount=100, price=10)) + trades.append(self.mock_trade(id="order2", amount=200, price=15)) + trades.append(self.mock_trade(id="order3", amount=300, price=20)) + + aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order(trades, []) + + self.assertEqual(aggregated_buys, trades) + self.assertEqual(len(aggregated_sells), 0) + + aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order([], trades) + + self.assertEqual(len(aggregated_buys), 0) + self.assertEqual(aggregated_sells, trades) + + def test_aggregated_position_with_two_related_trades_from_three(self): + trades = [] + + trades.append(self.mock_trade(id="order1", amount=100, price=10)) + trades.append(self.mock_trade(id="order2", amount=200, price=15)) + trades.append(self.mock_trade(id="order1", amount=300, price=20)) + + aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order(trades, []) + + self.assertEqual(len(aggregated_buys), 2) + trade = aggregated_buys[0] + self.assertTrue(trade.order_id == "order1" and trade.amount == 400 and trade.price == 15) + self.assertEqual(aggregated_buys[1], trades[1]) + self.assertEqual(len(aggregated_sells), 0) + + trades = [] + + trades.append(self.mock_trade(id="order1", amount=100, price=10)) + trades.append(self.mock_trade(id="order2", amount=200, price=15)) + trades.append(self.mock_trade(id="order1", amount=300, price=20)) + + aggregated_buys, aggregated_sells = PerformanceMetrics.aggregate_position_order([], trades) + + self.assertEqual(len(aggregated_buys), 0) + self.assertEqual(len(aggregated_sells), 2) + trade = aggregated_sells[0] + self.assertTrue(trade.order_id == "order1" and trade.amount == 400 and trade.price == 15) + self.assertEqual(aggregated_sells[1], trades[1]) + + def test_performance_metrics(self): + rate_oracle = RateOracle() + rate_oracle._prices["USDT-HBOT"] = Decimal("5") + RateOracle._shared_instance = rate_oracle + + trade_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(quote, Decimal("0"))]) + trades = [ + TradeFill( + config_file_path="some-strategy.yml", + strategy="pure_market_making", + market="binance", + symbol=trading_pair, + base_asset=base, + quote_asset=quote, + timestamp=int(time.time()), + order_id="someId0", + trade_type="BUY", + order_type="LIMIT", + price=100, + amount=10, + trade_fee=trade_fee.to_json(), + exchange_trade_id="someExchangeId0", + position=PositionAction.NIL.value, + ), + TradeFill( + config_file_path="some-strategy.yml", + strategy="pure_market_making", + market="binance", + symbol=trading_pair, + base_asset=base, + quote_asset=quote, + timestamp=int(time.time()), + order_id="someId1", + trade_type="SELL", + order_type="LIMIT", + price=120, + amount=15, + trade_fee=trade_fee.to_json(), + exchange_trade_id="someExchangeId1", + position=PositionAction.NIL.value, + ) + ] + cur_bals = {base: 100, quote: 10000} + metrics = asyncio.get_event_loop().run_until_complete( + PerformanceMetrics.create(trading_pair, trades, cur_bals)) + self.assertEqual(Decimal("799"), metrics.trade_pnl) + print(metrics) + + @patch('hummingbot.client.performance.PerformanceMetrics._is_trade_fill') + def test_performance_metrics_for_derivatives(self, is_trade_fill_mock): + rate_oracle = RateOracle() + rate_oracle._prices["USDT-HBOT"] = Decimal("5") + RateOracle._shared_instance = rate_oracle + + is_trade_fill_mock.return_value = True + trades = [] + trades.append(self.mock_trade(id="order1", + amount=Decimal("100"), + price=Decimal("10"), + position="OPEN", + type="BUY", + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(quote, Decimal("0"))]))) + trades.append(self.mock_trade(id="order2", + amount=Decimal("100"), + price=Decimal("15"), + position="CLOSE", + type="SELL", + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(quote, Decimal("0"))]))) + trades.append(self.mock_trade(id="order3", + amount=Decimal("100"), + price=Decimal("20"), + position="OPEN", + type="SELL", + fee=AddedToCostTradeFee(Decimal("0.1"), + flat_fees=[TokenAmount("USD", Decimal("0"))]))) + trades.append(self.mock_trade(id="order4", + amount=Decimal("100"), + price=Decimal("15"), + position="CLOSE", + type="BUY", + fee=AddedToCostTradeFee(Decimal("0.1"), + flat_fees=[TokenAmount("USD", Decimal("0"))]))) + + cur_bals = {base: 100, quote: 10000} + metrics = asyncio.get_event_loop().run_until_complete( + PerformanceMetrics.create(trading_pair, trades, cur_bals)) + self.assertEqual(metrics.num_buys, 2) + self.assertEqual(metrics.num_sells, 2) + self.assertEqual(metrics.num_trades, 4) + self.assertEqual(metrics.b_vol_base, Decimal("200")) + self.assertEqual(metrics.s_vol_base, Decimal("-200")) + self.assertEqual(metrics.tot_vol_base, Decimal("0")) + self.assertEqual(metrics.b_vol_quote, Decimal("-2500")) + self.assertEqual(metrics.s_vol_quote, Decimal("3500")) + self.assertEqual(metrics.tot_vol_quote, Decimal("1000")) + self.assertEqual(metrics.avg_b_price, Decimal("12.5")) + self.assertEqual(metrics.avg_s_price, Decimal("17.5")) + self.assertEqual(metrics.avg_tot_price, Decimal("15")) + self.assertEqual(metrics.start_base_bal, Decimal("100")) + self.assertEqual(metrics.start_quote_bal, Decimal("9000")) + self.assertEqual(metrics.cur_base_bal, 100) + self.assertEqual(metrics.cur_quote_bal, 10000), + self.assertEqual(metrics.start_price, Decimal("10")), + self.assertEqual(metrics.cur_price, Decimal("0.2")) + self.assertEqual(metrics.trade_pnl, Decimal("1000")) + self.assertEqual(metrics.total_pnl, Decimal("650")) + + def test_smart_round(self): + value = PerformanceMetrics.smart_round(None) + self.assertIsNone(value) + value = PerformanceMetrics.smart_round(Decimal("NaN")) + self.assertTrue(value.is_nan()) + + value = PerformanceMetrics.smart_round(Decimal("10000.123456789")) + self.assertEqual(value, Decimal("10000")) + value = PerformanceMetrics.smart_round(Decimal("100.123456789")) + self.assertEqual(value, Decimal("100.1")) + value = PerformanceMetrics.smart_round(Decimal("1.123456789")) + self.assertEqual(value, Decimal("1.12")) + value = PerformanceMetrics.smart_round(Decimal("0.123456789")) + self.assertEqual(value, Decimal("0.1234")) + value = PerformanceMetrics.smart_round(Decimal("0.000456789")) + self.assertEqual(value, Decimal("0.00045")) + value = PerformanceMetrics.smart_round(Decimal("0.000056789")) + self.assertEqual(value, Decimal("0.00005678")) + value = PerformanceMetrics.smart_round(Decimal("0")) + self.assertEqual(value, Decimal("0")) + + value = PerformanceMetrics.smart_round(Decimal("0.123456"), 2) + self.assertEqual(value, Decimal("0.12")) + + def test_calculate_fees_in_quote_for_one_trade_with_fees_different_tokens(self): + rate_oracle = RateOracle() + rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") + rate_oracle._prices["USDT-DAI"] = Decimal("0.9") + RateOracle._shared_instance = rate_oracle + + performance_metric = PerformanceMetrics() + flat_fees = [ + TokenAmount(token="USDT", amount=Decimal("10")), + TokenAmount(token="DAI", amount=Decimal("5")), + ] + trade = Trade( + trading_pair="HBOT-COINALPHA", + side=TradeType.BUY, + price=Decimal("1000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + market="binance", + timestamp=1640001112.223, + trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), + percent_token="COINALPHA", + flat_fees=flat_fees) + ) + + self.async_run_with_timeout(performance_metric._calculate_fees( + quote="COINALPHA", + trades=[trade])) + + expected_fee_amount = trade.amount * trade.price * trade.trade_fee.percent + expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal("2") + expected_fee_amount += flat_fees[1].amount * Decimal("2") + self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote) + + def test_calculate_fees_in_quote_for_one_trade_fill_with_fees_different_tokens(self): + rate_oracle = RateOracle() + rate_oracle._prices["DAI-COINALPHA"] = Decimal("2") + rate_oracle._prices["USDT-DAI"] = Decimal("0.9") + RateOracle._shared_instance = rate_oracle + + performance_metric = PerformanceMetrics() + flat_fees = [ + TokenAmount(token="USDT", amount=Decimal("10")), + TokenAmount(token="DAI", amount=Decimal("5")), + ] + trade = TradeFill( + config_file_path="some-strategy.yml", + strategy="pure_market_making", + market="binance", + symbol="HBOT-COINALPHA", + base_asset="HBOT", + quote_asset="COINALPHA", + timestamp=int(time.time()), + order_id="someId0", + trade_type="BUY", + order_type="LIMIT", + price=1000, + amount=1, + trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"), + percent_token="COINALPHA", + flat_fees=flat_fees).to_json(), + exchange_trade_id="someExchangeId0", + position=PositionAction.NIL.value, + ) + + self.async_run_with_timeout(performance_metric._calculate_fees( + quote="COINALPHA", + trades=[trade])) + + expected_fee_amount = Decimal(str(trade.amount)) * Decimal(str(trade.price)) * Decimal("0.1") + expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal("2") + expected_fee_amount += flat_fees[1].amount * Decimal("2") + self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote) + + def test__process_deducted_fees_impact_in_quote_vol(self): + dummy_trade = Trade(trading_pair="HBOT-COINALPHA", + side=TradeType.BUY, + price=1000, + amount=1, + order_type=OrderType.LIMIT, + market="binance", + timestamp=1640001112.223, + trade_fee=DeductedFromReturnsTradeFee(percent=Decimal("0.1"), + percent_token="COINALPHA")) + + performance_metric = PerformanceMetrics() + returned_impact = performance_metric._process_deducted_fees_impact_in_quote_vol(dummy_trade) + self.assertEqual(returned_impact, Decimal("-100.0")) diff --git a/test/hummingbot/client/test_settings.py b/test/hummingbot/client/test_settings.py new file mode 100644 index 0000000..85c0343 --- /dev/null +++ b/test/hummingbot/client/test_settings.py @@ -0,0 +1,197 @@ +import unittest +from unittest.mock import MagicMock, patch + +from pydantic import SecretStr + +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.connector.exchange.binance.binance_utils import BinanceConfigMap +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source import ( + InjectiveAPIDataSource, +) +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_api_data_source import KujiraAPIDataSource +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + + +class SettingsTest(unittest.TestCase): + def test_non_trading_connector_instance_with_default_configuration_secrets_revealed(self): + api_key = "someKey" + api_secret = "someSecret" + config_map = BinanceConfigMap(binance_api_key=api_key, binance_api_secret=api_secret) + conn_settings = ConnectorSetting( + name="binance", + type=ConnectorType.Exchange, + example_pair="BTC-USDT", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema(), + config_keys=config_map, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + connector = conn_settings.non_trading_connector_instance_with_default_configuration() + + self.assertNotIsInstance(connector.api_key, SecretStr) + self.assertEqual(api_key, connector.api_key) + self.assertNotIsInstance(connector.secret_key, SecretStr) + self.assertEqual(api_secret, connector.secret_key) + + def test_conn_init_parameters_for_cex_connector(self): + api_key = "someKey" + api_secret = "someSecret" + config_map = BinanceConfigMap(binance_api_key=api_key, binance_api_secret=api_secret) + conn_settings = ConnectorSetting( + name="binance", + type=ConnectorType.Exchange, + example_pair="BTC-USDT", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema(), + config_keys=config_map, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + + api_keys = {"binance_api_key": api_key, "binance_api_secret": api_secret} + params = conn_settings.conn_init_parameters(api_keys=api_keys) + expected_params = { + "binance_api_key": api_key, + "binance_api_secret": api_secret, + "trading_pairs": [], + "trading_required": False, + "client_config_map": None, + } + + self.assertEqual(expected_params, params) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + def test_conn_init_parameters_for_gateway_generic_connector( + self, get_connector_spec_from_market_name_mock: MagicMock + ): + get_connector_spec_from_market_name_mock.return_value = { + "connector": "vvs", + "chain": "cronos", + "network": "mainnet", + "trading_type": "AMM", + "chain_type": "EVM", + "wallet_address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "additional_spenders": [], + } + conn_settings = ConnectorSetting( + name="vvs_cronos_mainnet", + type=ConnectorType.AMM, + example_pair="WETH-USDC", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema(), + config_keys=None, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + + expected_params = { + "connector_name": "vvs", + "chain": "cronos", + "network": "mainnet", + "address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "additional_spenders": [], + "trading_pairs": [], + "trading_required": False, + "client_config_map": None, + } + params = conn_settings.conn_init_parameters() + + self.assertEqual(expected_params, params) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + def test_conn_init_parameters_for_gateway_injective_connector( + self, get_connector_spec_from_market_name_mock: MagicMock + ): + get_connector_spec_from_market_name_mock.return_value = { + "connector": "injective", + "chain": "injective", + "network": "mainnet", + "trading_type": "CLOB_SPOT", + "wallet_address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "additional_spenders": [], + } + conn_settings = ConnectorSetting( + name="injective_injective_mainnet", + type=ConnectorType.CLOB_SPOT, + example_pair="WETH-USDC", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema(), + config_keys=None, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + + expected_params_without_api_data_source = { + "connector_name": "injective", + "chain": "injective", + "network": "mainnet", + "address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "trading_pairs": [], + "trading_required": False, + "client_config_map": None, + } + params = conn_settings.conn_init_parameters() + + self.assertIn("api_data_source", params) + + api_data_source = params.pop("api_data_source") + + self.assertIsInstance(api_data_source, InjectiveAPIDataSource) + self.assertEqual(expected_params_without_api_data_source, params) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + def test_conn_init_parameters_for_gateway_kujira_connector( + self, get_connector_spec_from_market_name_mock: MagicMock + ): + get_connector_spec_from_market_name_mock.return_value = { + "connector": "kujira", + "chain": "kujira", + "network": "mainnet", + "trading_type": "CLOB_SPOT", + "wallet_address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "additional_spenders": [], + } + conn_settings = ConnectorSetting( + name="kujira_kujira_mainnet", + type=ConnectorType.CLOB_SPOT, + example_pair="KUJI-DEMO", + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema(), + config_keys=None, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False, + ) + + expected_params_without_api_data_source = { + "connector_name": "kujira", + "chain": "kujira", + "network": "mainnet", + "address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "trading_pairs": [], + "trading_required": False, + "client_config_map": None, + } + params = conn_settings.conn_init_parameters() + + self.assertIn("api_data_source", params) + + api_data_source = params.pop("api_data_source") + + self.assertIsInstance(api_data_source, KujiraAPIDataSource) + self.assertEqual(expected_params_without_api_data_source, params) diff --git a/test/hummingbot/client/ui/__init__.py b/test/hummingbot/client/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/client/ui/test_custom_widgets.py b/test/hummingbot/client/ui/test_custom_widgets.py new file mode 100644 index 0000000..2c92cb0 --- /dev/null +++ b/test/hummingbot/client/ui/test_custom_widgets.py @@ -0,0 +1,93 @@ +import asyncio +import unittest +from typing import Awaitable + +from prompt_toolkit.document import Document + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.ui.custom_widgets import FormattedTextLexer + + +class CustomWidgetUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.ev_loop.run_until_complete(read_system_configs_from_yml()) + + cls.text_style_tag = {"&cSPECIAL_WORD": "SPECIAL_LABEL"} + cls.tag_css_style = {"SPECIAL_LABEL": "bg: #FF0000"} + + def setUp(self) -> None: + super().setUp() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.lexer = FormattedTextLexer(client_config_map=self.client_config_map) + + self.lexer.text_style_tag_map.update(self.text_style_tag) + self.lexer.html_tag_css_style_map.update(self.tag_css_style) + + @classmethod + def async_run_with_timeout(cls, coroutine: Awaitable, timeout: float = 1): + ret = cls.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_get_css_style_text_not_listed(self): + expected_styling = "" + self.assertEqual(expected_styling, self.lexer.get_css_style("STYLING NOT FOUND")) + + def test_get_css_style_text_listed(self): + expected_styling = self.tag_css_style["SPECIAL_LABEL"] + self.assertEqual(expected_styling, self.lexer.get_css_style("SPECIAL_LABEL")) + + def test_get_line_command_promt(self): + TEST_PROMPT_TEXT = ">>> SOME_RANDOM_COMMAND AND_ARGUMENTS" + document = Document(text=TEST_PROMPT_TEXT) + get_line = self.lexer.lex_document(document) + + expected_fragments = [(self.lexer.get_css_style("primary_label"), TEST_PROMPT_TEXT)] + + line_fragments = get_line(0) + self.assertEqual(1, len(line_fragments)) + self.assertEqual(expected_fragments, line_fragments) + + def test_get_line_match_found(self): + TEXT = "SOME RANDOM TEXT WITH &cSPECIAL_WORD AND &cSPECIAL_WORD" + document = Document(text=TEXT) + get_line = self.lexer.lex_document(document) + + expected_fragments = [ + ("", "SOME RANDOM TEXT WITH "), + (self.lexer.get_css_style("output_pane"), "&c"), + (self.lexer.get_css_style("SPECIAL_LABEL"), "SPECIAL_WORD"), + ("", " AND "), + (self.lexer.get_css_style("output_pane"), "&c"), + (self.lexer.get_css_style("SPECIAL_LABEL"), "SPECIAL_WORD"), + ("", ""), + ] + + line_fragments = get_line(0) + self.assertEqual(7, len(line_fragments)) + self.assertEqual(expected_fragments, line_fragments) + + def test_get_line_no_match_found(self): + TEXT = "SOME RANDOM TEXT WITHOUT SPECIAL_WORD" + document = Document(text=TEXT) + get_line = self.lexer.lex_document(document) + + expected_fragments = [("", TEXT)] + + line_fragments = get_line(0) + self.assertEqual(1, len(line_fragments)) + self.assertEqual(expected_fragments, line_fragments) + + def test_get_line_index_error(self): + TEXT = "SOME RANDOM TEXT WITHOUT SPECIAL_WORD" + document = Document(text=TEXT) + get_line = self.lexer.lex_document(document) + + expected_fragments = [] + + line_fragments = get_line(1) + self.assertEqual(0, len(line_fragments)) + self.assertEqual(expected_fragments, line_fragments) diff --git a/test/hummingbot/client/ui/test_hummingbot_cli.py b/test/hummingbot/client/ui/test_hummingbot_cli.py new file mode 100644 index 0000000..8ec9858 --- /dev/null +++ b/test/hummingbot/client/ui/test_hummingbot_cli.py @@ -0,0 +1,137 @@ +import asyncio +import unittest +from unittest.mock import MagicMock, patch + +from prompt_toolkit.widgets import Button + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter, read_system_configs_from_yml +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.client.tab.data_types import CommandTab +from hummingbot.client.ui.custom_widgets import CustomTextArea +from hummingbot.client.ui.hummingbot_cli import HummingbotCLI +from hummingbot.core.event.event_listener import EventListener +from hummingbot.core.event.events import HummingbotUIEvent + + +class HummingbotCLITest(unittest.TestCase): + command_name = "command_1" + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.ev_loop.run_until_complete(read_system_configs_from_yml()) + + def setUp(self) -> None: + super().setUp() + + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + tabs = {self.command_name: CommandTab(self.command_name, None, None, None, MagicMock())} + self.mock_hb = MagicMock() + self.app = HummingbotCLI( + client_config_map=self.client_config_map, + input_handler=None, + bindings=None, + completer=None, + command_tabs=tabs) + self.app.app = MagicMock() + self.hb = HummingbotApplication() + + def test_handle_tab_command_on_close_argument(self): + tab = self.app.command_tabs[self.command_name] + tab.close_button = MagicMock() + tab.button = MagicMock() + tab.output_field = MagicMock() + self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": True}) + self.assertIsNone(tab.button) + self.assertIsNone(tab.close_button) + self.assertIsNone(tab.output_field) + self.assertFalse(tab.is_selected) + self.assertEqual(tab.tab_index, 0) + + def test_handle_tab_command_create_new_tab_and_display(self): + tab = self.app.command_tabs[self.command_name] + self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False}) + self.assertIsInstance(tab.button, Button) + self.assertIsInstance(tab.close_button, Button) + self.assertIsInstance(tab.output_field, CustomTextArea) + self.assertEqual(tab.tab_index, 1) + self.assertTrue(tab.is_selected) + self.assertTrue(tab.tab_class.display.called) + + @patch("hummingbot.client.ui.layout.Layout") + @patch("hummingbot.client.ui.layout.FloatContainer") + @patch("hummingbot.client.ui.layout.ConditionalContainer") + @patch("hummingbot.client.ui.layout.Box") + @patch("hummingbot.client.ui.layout.HSplit") + @patch("hummingbot.client.ui.layout.VSplit") + def test_handle_tab_command_on_existing_tab(self, mock_vsplit, mock_hsplit, mock_box, moc_cc, moc_fc, mock_layout): + tab = self.app.command_tabs[self.command_name] + tab.button = MagicMock() + tab.output_field = MagicMock() + tab.close_button = MagicMock() + tab.is_selected = False + self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False}) + self.assertTrue(tab.is_selected) + self.assertTrue(tab.tab_class.display.call_count == 1) + + # Test display not called if there is a running task + tab.is_selected = False + tab.task = MagicMock() + tab.task.done.return_value = False + self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False}) + self.assertTrue(tab.is_selected) + self.assertTrue(tab.tab_class.display.call_count == 1) + + @patch("hummingbot.client.ui.layout.Layout") + @patch("hummingbot.client.ui.layout.FloatContainer") + @patch("hummingbot.client.ui.layout.ConditionalContainer") + @patch("hummingbot.client.ui.layout.Box") + @patch("hummingbot.client.ui.layout.HSplit") + @patch("hummingbot.client.ui.layout.VSplit") + def test_tab_navigation(self, mock_vsplit, mock_hsplit, mock_box, moc_cc, moc_fc, mock_layout): + tab2 = CommandTab("command_2", None, None, None, MagicMock(), False) + + self.app.command_tabs["command_2"] = tab2 + tab1 = self.app.command_tabs[self.command_name] + + self.app.handle_tab_command(self.mock_hb, self.command_name, {"close": False}) + self.app.handle_tab_command(self.mock_hb, "command_2", {"close": False}) + self.assertTrue(tab2.is_selected) + + self.app.tab_navigate_left() + self.assertTrue(tab1.is_selected) + self.assertFalse(tab2.is_selected) + self.app.tab_navigate_left() + self.assertTrue(all(not t.is_selected for t in self.app.command_tabs.values())) + self.app.tab_navigate_left() + self.assertTrue(all(not t.is_selected for t in self.app.command_tabs.values())) + + self.app.tab_navigate_right() + self.assertTrue(tab1.is_selected) + + self.app.tab_navigate_right() + self.assertFalse(tab1.is_selected) + self.assertTrue(tab2.is_selected) + + self.app.tab_navigate_right() + self.assertFalse(tab1.is_selected) + self.assertTrue(tab2.is_selected) + + @patch("hummingbot.client.ui.hummingbot_cli.init_logging") + def test_did_start_ui(self, mock_init_logging: MagicMock): + class UIStartHandler(EventListener): + def __init__(self): + super().__init__() + self.mock = MagicMock() + + def __call__(self, _): + self.mock() + + handler: UIStartHandler = UIStartHandler() + self.app.add_listener(HummingbotUIEvent.Start, handler) + self.app.did_start_ui() + + mock_init_logging.assert_called() + handler.mock.assert_called() diff --git a/test/hummingbot/client/ui/test_interface_utils.py b/test/hummingbot/client/ui/test_interface_utils.py new file mode 100644 index 0000000..3ce4b09 --- /dev/null +++ b/test/hummingbot/client/ui/test_interface_utils.py @@ -0,0 +1,243 @@ +import asyncio +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch + +import pandas as pd + +from hummingbot.client.ui.interface_utils import ( + format_bytes, + format_df_for_printout, + start_process_monitor, + start_timer, + start_trade_monitor, +) + + +class ExpectedException(Exception): + pass + + +class InterfaceUtilsTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + for task in asyncio.all_tasks(ev_loop): + task.cancel() + + def setUp(self) -> None: + super().setUp() + self.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_format_bytes(self): + size = 1024. + self.assertEqual("1.00 KB", format_bytes(size)) + self.assertEqual("157.36 GB", format_bytes(168963795964)) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + def test_start_timer(self, mock_sleep): + mock_timer = MagicMock() + mock_sleep.side_effect = [None, ExpectedException()] + with self.assertRaises(ExpectedException): + self.async_run_with_timeout(start_timer(mock_timer)) + self.assertEqual('Uptime: 0 day(s), 00:00:02', mock_timer.log.call_args_list[0].args[0]) + self.assertEqual('Uptime: 0 day(s), 00:00:03', mock_timer.log.call_args_list[1].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("psutil.Process") + def test_start_process_monitor(self, mock_process, mock_sleep): + mock_process.return_value.num_threads.return_value = 2 + mock_process.return_value.cpu_percent.return_value = 30 + + memory_info = MagicMock() + type(memory_info).vms = PropertyMock(return_value=1024.0) + type(memory_info).rss = PropertyMock(return_value=1024.0) + + mock_process.return_value.memory_info.return_value = memory_info + mock_monitor = MagicMock() + mock_sleep.side_effect = asyncio.CancelledError + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_process_monitor(mock_monitor)) + self.assertEqual( + "CPU: 30%, Mem: 512.00 B (1.00 KB), Threads: 2, ", + mock_monitor.log.call_args_list[0].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.ui.interface_utils.PerformanceMetrics.create", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_multi_loops(self, mock_hb_app, mock_perf, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.return_value = False + mock_app.markets.return_values = {"a": MagicMock(ready=True)} + mock_app._get_trades_from_session.return_value = [MagicMock(market="ExchangeA", symbol="HBOT-USDT")] + mock_app.get_current_balances = AsyncMock() + mock_perf.side_effect = [MagicMock(return_pct=Decimal("0.01"), total_pnl=Decimal("2")), + MagicMock(return_pct=Decimal("0.02"), total_pnl=Decimal("2"))] + mock_sleep.side_effect = [None, asyncio.CancelledError()] + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(3, mock_result.log.call_count) + self.assertEqual('Trades: 0, Total P&L: 0.00, Return %: 0.00%', mock_result.log.call_args_list[0].args[0]) + self.assertEqual('Trades: 1, Total P&L: 2.00 USDT, Return %: 1.00%', mock_result.log.call_args_list[1].args[0]) + self.assertEqual('Trades: 1, Total P&L: 2.00 USDT, Return %: 2.00%', mock_result.log.call_args_list[2].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.ui.interface_utils.PerformanceMetrics.create", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_multi_pairs_diff_quotes(self, mock_hb_app, mock_perf, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.return_value = False + mock_app.markets.return_values = {"a": MagicMock(ready=True)} + mock_app._get_trades_from_session.return_value = [ + MagicMock(market="ExchangeA", symbol="HBOT-USDT"), + MagicMock(market="ExchangeA", symbol="HBOT-BTC") + ] + mock_app.get_current_balances = AsyncMock() + mock_perf.side_effect = [MagicMock(return_pct=Decimal("0.01"), total_pnl=Decimal("2")), + MagicMock(return_pct=Decimal("0.02"), total_pnl=Decimal("3"))] + mock_sleep.side_effect = asyncio.CancelledError() + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(2, mock_result.log.call_count) + self.assertEqual('Trades: 0, Total P&L: 0.00, Return %: 0.00%', mock_result.log.call_args_list[0].args[0]) + self.assertEqual('Trades: 2, Total P&L: N/A, Return %: 1.50%', mock_result.log.call_args_list[1].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.ui.interface_utils.PerformanceMetrics.create", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_multi_pairs_same_quote(self, mock_hb_app, mock_perf, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.return_value = False + mock_app.markets.return_values = {"a": MagicMock(ready=True)} + mock_app._get_trades_from_session.return_value = [ + MagicMock(market="ExchangeA", symbol="HBOT-USDT"), + MagicMock(market="ExchangeA", symbol="BTC-USDT") + ] + mock_app.get_current_balances = AsyncMock() + mock_perf.side_effect = [MagicMock(return_pct=Decimal("0.01"), total_pnl=Decimal("2")), + MagicMock(return_pct=Decimal("0.02"), total_pnl=Decimal("3"))] + mock_sleep.side_effect = asyncio.CancelledError() + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(2, mock_result.log.call_count) + self.assertEqual('Trades: 0, Total P&L: 0.00, Return %: 0.00%', mock_result.log.call_args_list[0].args[0]) + self.assertEqual('Trades: 2, Total P&L: 5.00 USDT, Return %: 1.50%', mock_result.log.call_args_list[1].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_market_not_ready(self, mock_hb_app, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.return_value = False + mock_app.markets.return_values = {"a": MagicMock(ready=False)} + mock_sleep.side_effect = asyncio.CancelledError() + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(1, mock_result.log.call_count) + self.assertEqual('Trades: 0, Total P&L: 0.00, Return %: 0.00%', mock_result.log.call_args_list[0].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_market_no_trade(self, mock_hb_app, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.return_value = False + mock_app.markets.return_values = {"a": MagicMock(ready=True)} + mock_app._get_trades_from_session.return_value = [] + mock_sleep.side_effect = asyncio.CancelledError() + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(1, mock_result.log.call_count) + self.assertEqual('Trades: 0, Total P&L: 0.00, Return %: 0.00%', mock_result.log.call_args_list[0].args[0]) + + @patch("hummingbot.client.ui.interface_utils._sleep", new_callable=AsyncMock) + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_start_trade_monitor_loop_continues_on_failure(self, mock_hb_app, mock_sleep): + mock_result = MagicMock() + mock_app = mock_hb_app.main_application() + mock_app.strategy_task.done.side_effect = [RuntimeError(), asyncio.CancelledError()] + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(start_trade_monitor(mock_result)) + self.assertEqual(2, mock_app.strategy_task.done.call_count) # was called again after exception + + def test_format_df_for_printout(self): + df = pd.DataFrame( + data={ + "first": [1, 2], + "second": ["12345", "67890"], + } + ) + + df_str = format_df_for_printout(df, table_format="psql") + target_str = ( + "+---------+----------+" + "\n| first | second |" + "\n|---------+----------|" + "\n| 1 | 12345 |" + "\n| 2 | 67890 |" + "\n+---------+----------+" + ) + + self.assertEqual(target_str, df_str) + + df_str = format_df_for_printout(df, table_format="psql", max_col_width=4) + target_str = ( + "+--------+--------+" + "\n| f... | s... |" + "\n|--------+--------|" + "\n| 1 | 1... |" + "\n| 2 | 6... |" + "\n+--------+--------+" + ) + + self.assertEqual(target_str, df_str) + + df_str = format_df_for_printout(df, table_format="psql", index=True) + target_str = ( + "+----+---------+----------+" + "\n| | first | second |" + "\n|----+---------+----------|" + "\n| 0 | 1 | 12345 |" + "\n| 1 | 2 | 67890 |" + "\n+----+---------+----------+" + ) + + self.assertEqual(target_str, df_str) + + def test_format_df_for_printout_table_format_from_global_config(self): + df = pd.DataFrame( + data={ + "first": [1, 2], + "second": ["12345", "67890"], + } + ) + + df_str = format_df_for_printout(df, table_format="psql") + target_str = ( + "+---------+----------+" + "\n| first | second |" + "\n|---------+----------|" + "\n| 1 | 12345 |" + "\n| 2 | 67890 |" + "\n+---------+----------+" + ) + + self.assertEqual(target_str, df_str) + + df_str = format_df_for_printout(df, table_format="simple") + target_str = ( + " first second" + "\n------- --------" + "\n 1 12345" + "\n 2 67890" + ) + + self.assertEqual(target_str, df_str) diff --git a/test/hummingbot/client/ui/test_layout.py b/test/hummingbot/client/ui/test_layout.py new file mode 100644 index 0000000..ee56d63 --- /dev/null +++ b/test/hummingbot/client/ui/test_layout.py @@ -0,0 +1,25 @@ +import unittest + +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.client.ui.layout import get_active_strategy, get_strategy_file + + +class LayoutTest(unittest.TestCase): + + def test_get_active_strategy(self): + hb = HummingbotApplication.main_application() + hb.strategy_name = "SomeStrategy" + res = get_active_strategy() + style, text = res[0] + + self.assertEqual("class:log_field", style) + self.assertEqual(f"Strategy: {hb.strategy_name}", text) + + def test_get_strategy_file(self): + hb = HummingbotApplication.main_application() + hb._strategy_file_name = "some_strategy.yml" + res = get_strategy_file() + style, text = res[0] + + self.assertEqual("class:log_field", style) + self.assertEqual(f"Strategy File: {hb._strategy_file_name}", text) diff --git a/test/hummingbot/client/ui/test_login_prompt.py b/test/hummingbot/client/ui/test_login_prompt.py new file mode 100644 index 0000000..1ccbcd6 --- /dev/null +++ b/test/hummingbot/client/ui/test_login_prompt.py @@ -0,0 +1,152 @@ +import unittest +from unittest.mock import MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.ui import login_prompt +from hummingbot.client.ui.style import load_style + + +class LoginPromptTest(unittest.TestCase): + # @classmethod + # def setUpClass(cls) -> None: + # super().setUpClass() + # cls.ev_loop = asyncio.get_event_loop() + # cls.ev_loop.run_until_complete(read_system_configs_from_yml()) + + def setUp(self) -> None: + super().setUp() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.password = "som-password" + + @patch("hummingbot.client.ui.message_dialog") + @patch("hummingbot.client.ui.input_dialog") + @patch("hummingbot.client.config.security.Security.login") + @patch("hummingbot.client.config.security.Security.new_password_required") + def test_login_success( + self, + new_password_required_mock: MagicMock, + login_mock: MagicMock, + input_dialog_mock: MagicMock, + message_dialog_mock: MagicMock, + ): + new_password_required_mock.return_value = False + run_mock = MagicMock() + run_mock.run.return_value = self.password + input_dialog_mock.return_value = run_mock + login_mock.return_value = True + + self.assertTrue(login_prompt(ETHKeyFileSecretManger, style=load_style(self.client_config_map))) + self.assertEqual(1, len(login_mock.mock_calls)) + message_dialog_mock.assert_not_called() + + @patch("hummingbot.client.ui.message_dialog") + @patch("hummingbot.client.ui.input_dialog") + @patch("hummingbot.client.config.security.Security.login") + @patch("hummingbot.client.config.security.Security.new_password_required") + def test_login_error_retries( + self, + new_password_required_mock: MagicMock, + login_mock: MagicMock, + input_dialog_mock: MagicMock, + message_dialog_mock: MagicMock, + ): + new_password_required_mock.return_value = False + run_mock = MagicMock() + run_mock.run.return_value = "somePassword" + input_dialog_mock.return_value = run_mock + message_dialog_mock.return_value = run_mock + login_mock.side_effect = [False, True] + + self.assertTrue(login_prompt(ETHKeyFileSecretManger, style=load_style(self.client_config_map))) + self.assertEqual(2, len(login_mock.mock_calls)) + message_dialog_mock.assert_called() + + @patch("hummingbot.client.ui.message_dialog") + @patch("hummingbot.client.ui.input_dialog") + @patch("hummingbot.client.config.security.Security.login") + @patch("hummingbot.client.config.security.Security.new_password_required") + def test_login_blank_password_error_retries( + self, + new_password_required_mock: MagicMock, + login_mock: MagicMock, + input_dialog_mock: MagicMock, + message_dialog_mock: MagicMock, + ): + new_password_required_mock.return_value = True + input_dialog_mock_run_mock = MagicMock() + input_dialog_mock_run_mock.run.side_effect = ["", "a", "a"] + input_dialog_mock.return_value = input_dialog_mock_run_mock + + run_mock = MagicMock() + run_mock.run.return_value = "somePassword" + + message_dialog_text = [] + + def side_effect(title, text, style): + message_dialog_text.append(text) + return run_mock + + message_dialog_mock.side_effect = side_effect + login_mock.return_value = True + + self.assertTrue(login_prompt(ETHKeyFileSecretManger, style=load_style(self.client_config_map))) + self.assertEqual(1, len(login_mock.mock_calls)) + self.assertIn("The password must not be empty.", message_dialog_text) + + @patch("hummingbot.client.ui.message_dialog") + @patch("hummingbot.client.ui.input_dialog") + @patch("hummingbot.client.config.security.Security.login") + @patch("hummingbot.client.config.security.Security.new_password_required") + def test_login_password_do_not_match_error_retries( + self, + new_password_required_mock: MagicMock, + login_mock: MagicMock, + input_dialog_mock: MagicMock, + message_dialog_mock: MagicMock, + ): + new_password_required_mock.return_value = True + input_dialog_mock_run_mock = MagicMock() + input_dialog_mock_run_mock.run.side_effect = ["a", "b", "a", "a"] + input_dialog_mock.return_value = input_dialog_mock_run_mock + + run_mock = MagicMock() + run_mock.run.return_value = "somePassword" + + message_dialog_text = [] + + def side_effect(title, text, style): + message_dialog_text.append(text) + return run_mock + + message_dialog_mock.side_effect = side_effect + login_mock.return_value = True + + self.assertTrue(login_prompt(ETHKeyFileSecretManger, style=load_style(self.client_config_map))) + self.assertEqual(1, len(login_mock.mock_calls)) + self.assertIn("Passwords entered do not match, please try again.", message_dialog_text) + + @patch("hummingbot.client.ui.message_dialog") + @patch("hummingbot.client.ui.input_dialog") + @patch("hummingbot.client.config.security.Security.login") + @patch("hummingbot.client.config.security.Security.new_password_required") + def test_login_password_none_exit( + self, + new_password_required_mock: MagicMock, + login_mock: MagicMock, + input_dialog_mock: MagicMock, + message_dialog_mock: MagicMock, + ): + new_password_required_mock.return_value = True + input_dialog_mock_run_mock = MagicMock() + input_dialog_mock_run_mock.run.side_effect = ["a", None] + input_dialog_mock.return_value = input_dialog_mock_run_mock + + run_mock = MagicMock() + run_mock.run.return_value = "somePassword" + + message_dialog_mock.return_value = run_mock + login_mock.return_value = True + + self.assertEqual(login_prompt(ETHKeyFileSecretManger, style=load_style(self.client_config_map)), None) diff --git a/test/hummingbot/client/ui/test_stdout_redirection.py b/test/hummingbot/client/ui/test_stdout_redirection.py new file mode 100644 index 0000000..20ac585 --- /dev/null +++ b/test/hummingbot/client/ui/test_stdout_redirection.py @@ -0,0 +1,15 @@ +import unittest + +from hummingbot.client.ui.stdout_redirection import StdoutProxy + + +class StdoutProxyTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.stdout_proxy = StdoutProxy() + + def test_isatty(self): + self.assertFalse(self.stdout_proxy.isatty()) + + def test_fileno(self): + self.assertEqual(1, self.stdout_proxy.fileno()) diff --git a/test/hummingbot/client/ui/test_style.py b/test/hummingbot/client/ui/test_style.py new file mode 100644 index 0000000..29b6a65 --- /dev/null +++ b/test/hummingbot/client/ui/test_style.py @@ -0,0 +1,211 @@ +import unittest +from unittest.mock import patch + +from prompt_toolkit.styles import Style + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.ui.style import hex_to_ansi, load_style, reset_style + + +class StyleTest(unittest.TestCase): + class ConfigVar: + value = None + default = None + + def __init__(self, value, default=None): + self.value = value + self.default = default + + @patch("hummingbot.client.ui.style.is_windows") + def test_load_style_unix(self, is_windows_mock): + is_windows_mock.return_value = False + + global_config_map = ClientConfigMap() + global_config_map.color.top_pane = "#FAFAFA" + global_config_map.color.bottom_pane = "#FAFAFA" + global_config_map.color.output_pane = "#FAFAFA" + global_config_map.color.input_pane = "#FAFAFA" + global_config_map.color.logs_pane = "#FAFAFA" + global_config_map.color.terminal_primary = "#FCFCFC" + + global_config_map.color.primary_label = "#5FFFD7" + global_config_map.color.secondary_label = "#FFFFFF" + global_config_map.color.success_label = "#5FFFD7" + global_config_map.color.warning_label = "#FFFF00" + global_config_map.color.info_label = "#5FD7FF" + global_config_map.color.error_label = "#FF0000" + global_config_map.color.gold_label = "#FAFAFA" + global_config_map.color.silver_label = "#FAFAFA" + global_config_map.color.bronze_label = "#FAFAFA" + + adapter = ClientConfigAdapter(global_config_map) + + style = Style.from_dict( + { + "output_field": "bg:#FAFAFA #FCFCFC", + "input_field": "bg:#FAFAFA #FFFFFF", + "log_field": "bg:#FAFAFA #FFFFFF", + "header": "bg:#FAFAFA #AAAAAA", + "footer": "bg:#FAFAFA #AAAAAA", + "search": "bg:#000000 #93C36D", + "search.current": "bg:#000000 #1CD085", + "primary": "#FCFCFC", + "warning": "#93C36D", + "error": "#F5634A", + "tab_button.focused": "bg:#FCFCFC #FAFAFA", + "tab_button": "bg:#FFFFFF #FAFAFA", + "dialog": "bg:#171E2B", + "dialog frame.label": "bg:#FCFCFC #000000", + "dialog.body": "bg:#000000 #FCFCFC", + "dialog shadow": "bg:#171E2B", + "button": "bg:#000000", + "text-area": "bg:#000000 #FCFCFC", + # Label bg and font color + "primary_label": "bg:#5FFFD7 #FAFAFA", + "secondary_label": "bg:#FFFFFF #FAFAFA", + "success_label": "bg:#5FFFD7 #FAFAFA", + "warning_label": "bg:#FFFF00 #FAFAFA", + "info_label": "bg:#5FD7FF #FAFAFA", + "error_label": "bg:#FF0000 #FAFAFA", + "gold_label": "bg:#FAFAFA #FAFAFA", + "silver_label": "bg:#FAFAFA #FAFAFA", + "bronze_label": "bg:#FAFAFA #FAFAFA", + } + ) + + self.assertEqual(style.class_names_and_attrs, load_style(adapter).class_names_and_attrs) + + @patch("hummingbot.client.ui.style.is_windows") + def test_load_style_windows(self, is_windows_mock): + is_windows_mock.return_value = True + + global_config_map = ClientConfigMap() + global_config_map.color.top_pane = "#FAFAFA" + global_config_map.color.bottom_pane = "#FAFAFA" + global_config_map.color.output_pane = "#FAFAFA" + global_config_map.color.input_pane = "#FAFAFA" + global_config_map.color.logs_pane = "#FAFAFA" + global_config_map.color.terminal_primary = "#FCFCFC" + + global_config_map.color.primary_label = "#5FFFD7" + global_config_map.color.secondary_label = "#FFFFFF" + global_config_map.color.success_label = "#5FFFD7" + global_config_map.color.warning_label = "#FFFF00" + global_config_map.color.info_label = "#5FD7FF" + global_config_map.color.error_label = "#FF0000" + + adapter = ClientConfigAdapter(global_config_map) + + style = Style.from_dict( + { + "output_field": "bg:#ansiwhite #ansiwhite", + "input_field": "bg:#ansiwhite #ansiwhite", + "log_field": "bg:#ansiwhite #ansiwhite", + "header": "bg:#ansiwhite #ansiwhite", + "footer": "bg:#ansiwhite #ansiwhite", + "search": "#ansiwhite", + "search.current": "#ansiwhite", + "primary": "#ansiwhite", + "warning": "#ansibrightyellow", + "error": "#ansired", + "tab_button.focused": "bg:#ansiwhite #ansiwhite", + "tab_button": "bg:#ansiwhite #ansiwhite", + "dialog": "bg:#ansigreen", + "dialog frame.label": "bg:#ansiwhite #ansiblack", + "dialog.body": "bg:#ansiblack #ansiwhite", + "dialog shadow": "bg:#ansigreen", + "button": "bg:#ansigreen", + "text-area": "bg:#ansiblack #ansiwhite", + # Label bg and font color + "primary_label": "bg:#ansicyan #ansiwhite", + "secondary_label": "bg:#ansiwhite #ansiwhite", + "success_label": "bg:#ansicyan #ansiwhite", + "warning_label": "bg:#ansiyellow #ansiwhite", + "info_label": "bg:#ansicyan #ansiwhite", + "error_label": "bg:#ansired #ansiwhite", + "gold_label": "bg:#ansiwhite #ansiyellow", + "silver_label": "bg:#ansiwhite #ansilightgray", + "bronze_label": "bg:#ansiwhite #ansibrown", + } + ) + + self.assertEqual(style.class_names_and_attrs, load_style(adapter).class_names_and_attrs) + + def test_reset_style(self): + global_config_map = ClientConfigMap() + global_config_map.color.top_pane = "#FAFAFA" + global_config_map.color.bottom_pane = "#FAFAFA" + global_config_map.color.output_pane = "#FAFAFA" + global_config_map.color.input_pane = "#FAFAFA" + global_config_map.color.logs_pane = "#FAFAFA" + global_config_map.color.terminal_primary = "#FCFCFC" + + global_config_map.color.primary_label = "#FAFAFA" + global_config_map.color.secondary_label = "#FAFAFA" + global_config_map.color.success_label = "#FAFAFA" + global_config_map.color.warning_label = "#FAFAFA" + global_config_map.color.info_label = "#FAFAFA" + global_config_map.color.error_label = "#FAFAFA" + global_config_map.color.gold_label = "#FAFAFA" + global_config_map.color.silver_label = "#FAFAFA" + global_config_map.color.bronze_label = "#FAFAFA" + + adapter = ClientConfigAdapter(global_config_map) + + style = Style.from_dict( + { + "output_field": "bg:#262626 #5FFFD7", + "input_field": "bg:#1C1C1C #FFFFFF", + "log_field": "bg:#121212 #FFFFFF", + "header": "bg:#000000 #AAAAAA", + "footer": "bg:#000000 #AAAAAA", + "search": "bg:#000000 #93C36D", + "search.current": "bg:#000000 #1CD085", + "primary": "#5FFFD7", + "warning": "#93C36D", + "error": "#F5634A", + "tab_button.focused": "bg:#5FFFD7 #121212", + "tab_button": "bg:#FFFFFF #121212", + "dialog": "bg:#171E2B", + "dialog frame.label": "bg:#5FFFD7 #000000", + "dialog.body": "bg:#000000 #5FFFD7", + "dialog shadow": "bg:#171E2B", + "button": "bg:#000000", + "text-area": "bg:#000000 #5FFFD7", + # Label bg and font color + "primary_label": "bg:#5FFFD7 #262626", + "secondary_label": "bg:#FFFFFF #262626", + "success_label": "bg:#5FFFD7 #262626", + "warning_label": "bg:#FFFF00 #262626", + "info_label": "bg:#5FD7FF #262626", + "error_label": "bg:#FF0000 #262626", + "gold_label": "bg:#262626 #FFD700", + "silver_label": "bg:#262626 #C0C0C0", + "bronze_label": "bg:#262626 #CD7F32", + } + ) + + self.assertEqual(style.class_names_and_attrs, reset_style(config_map=adapter, save=False).class_names_and_attrs) + + def test_hex_to_ansi(self): + self.assertEqual("#ansiblack", hex_to_ansi("#000000")) + self.assertEqual("#ansired", hex_to_ansi("#FF0000")) + self.assertEqual("#ansigreen", hex_to_ansi("#00FF00")) + self.assertEqual("#ansiyellow", hex_to_ansi("#FFFF00")) + self.assertEqual("#ansiblue", hex_to_ansi("#0000FF")) + self.assertEqual("#ansimagenta", hex_to_ansi("#FF00FF")) + self.assertEqual("#ansicyan", hex_to_ansi("#00FFFF")) + self.assertEqual("#ansigray", hex_to_ansi("#F0F0F0")) + + self.assertEqual("#ansiyellow", hex_to_ansi("#FFFF00")) + self.assertEqual("#ansiyellow", hex_to_ansi("#FFAA00")) + self.assertEqual("#ansiyellow", hex_to_ansi("#FFFF00")) + self.assertEqual("#ansired", hex_to_ansi("#FF1100")) + + self.assertEqual("#ansiyellow", hex_to_ansi("#ffff00")) + self.assertEqual("#ansiyellow", hex_to_ansi("#ffaa00")) + self.assertEqual("#ansiyellow", hex_to_ansi("#ffff00")) + self.assertEqual("#ansired", hex_to_ansi("#ff1100")) + + self.assertEqual("#ansiyellow", hex_to_ansi("ffff00")) diff --git a/test/hummingbot/connector/__init__.py b/test/hummingbot/connector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/__init__.py b/test/hummingbot/connector/derivative/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/binance_perpetual/__init__.py b/test/hummingbot/connector/derivative/binance_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..cae0fbc --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_api_order_book_data_source.py @@ -0,0 +1,444 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.binance_perpetual import binance_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_api_order_book_data_source import ( + BinancePerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import BinancePerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BinancePerpetualAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + cls.domain = "binance_perpetual_testnet" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.async_tasks: List[asyncio.Task] = [] + + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BinancePerpetualDerivative( + client_config_map, + binance_perpetual_api_key="", + binance_perpetual_api_secret="", + trading_pairs=[self.trading_pair], + trading_required=False, + domain=self.domain, + ) + self.data_source = BinancePerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict({self.ex_trading_pair: self.trading_pair}) + } + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}{self.quote_asset}": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + for task in self.async_tasks: + task.cancel() + BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def resume_test_callback(self, *_, **__): + self.resume_test_event.set() + return None + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _raise_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _orderbook_update_event(self): + resp = { + "stream": f"{self.ex_trading_pair.lower()}@depth", + "data": { + "e": "depthUpdate", + "E": 1631591424198, + "T": 1631591424189, + "s": self.ex_trading_pair, + "U": 752409354963, + "u": 752409360466, + "pu": 752409354901, + "b": [ + ["43614.31", "0.000"], + ], + "a": [ + ["45277.14", "0.257"], + ], + }, + } + return resp + + def _orderbook_trade_event(self): + resp = { + "stream": f"{self.ex_trading_pair.lower()}@aggTrade", + "data": { + "e": "aggTrade", + "E": 1631594403486, + "a": 817295132, + "s": self.ex_trading_pair, + "p": "45266.16", + "q": "2.206", + "f": 1437689393, + "l": 1437689407, + "T": 1631594403330, + "m": False, + }, + } + return resp + + def _funding_info_event(self): + resp = { + "stream": f"{self.ex_trading_pair.lower()}@markPrice", + "data": { + "e": "markPriceUpdate", + "E": 1641288864000, + "s": self.ex_trading_pair, + "p": "46353.99600757", + "P": "46507.47845460", + "i": "46358.63622407", + "r": "0.00010000", + "T": 1641312000000, + }, + } + return resp + + @aioresponses() + def test_get_snapshot_exception_raised(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + with self.assertRaises(IOError) as context: + self.async_run_with_timeout( + self.data_source._order_book_snapshot( + trading_pair=self.trading_pair) + ) + + self.assertIn("HTTP status is 400. Error: [\"ERROR\"]", + str(context.exception)) + + @aioresponses() + def test_get_snapshot_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = { + "lastUpdateId": 1027024, + "E": 1589436922972, + "T": 1589436922959, + "bids": [["10", "1"]], + "asks": [["11", "1"]], + } + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source._request_order_book_snapshot( + trading_pair=self.trading_pair) + ) + self.assertEqual(mock_response, result) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = { + "lastUpdateId": 1027024, + "E": 1589436922972, + "T": 1589436922959, + "bids": [["10", "1"]], + "asks": [["11", "1"]], + } + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + result = self.async_run_with_timeout(self.data_source.get_new_order_book(trading_pair=self.trading_pair)) + self.assertIsInstance(result, OrderBook) + self.assertEqual(1027024, result.snapshot_uid) + + @aioresponses() + def test_get_funding_info_from_exchange_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.MARK_PRICE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "symbol": self.ex_trading_pair, + "markPrice": "46382.32704603", + "indexPrice": "46385.80064948", + "estimatedSettlePrice": "46510.13598963", + "lastFundingRate": "0.00010000", + "interestRate": "0.00010000", + "nextFundingTime": 1641312000000, + "time": 1641288825000, + } + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_funding_info(self.trading_pair)) + + self.assertIsInstance(result, FundingInfo) + self.assertEqual(result.trading_pair, self.trading_pair) + self.assertEqual(result.index_price, Decimal(mock_response["indexPrice"])) + self.assertEqual(result.mark_price, Decimal(mock_response["markPrice"])) + self.assertEqual(result.next_funding_utc_timestamp, mock_response["nextFundingTime"]) + self.assertEqual(result.rate, Decimal(mock_response["lastFundingRate"])) + + @aioresponses() + def test_get_funding_info(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.MARK_PRICE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "symbol": self.ex_trading_pair, + "markPrice": "46382.32704603", + "indexPrice": "46385.80064948", + "estimatedSettlePrice": "46510.13598963", + "lastFundingRate": "0.00010000", + "interestRate": "0.00010000", + "nextFundingTime": 1641312000000, + "time": 1641288825000, + } + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_funding_info(trading_pair=self.trading_pair)) + + self.assertIsInstance(result, FundingInfo) + self.assertEqual(result.trading_pair, self.trading_pair) + self.assertEqual(result.index_price, Decimal(mock_response["indexPrice"])) + self.assertEqual(result.mark_price, Decimal(mock_response["markPrice"])) + self.assertEqual(result.next_funding_utc_timestamp, mock_response["nextFundingTime"]) + self.assertEqual(result.rate, Decimal(mock_response["lastFundingRate"])) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_cancelled_when_connecting(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + self.assertEqual(msg_queue.qsize(), 0) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + sleep_mock.side_effect = asyncio.CancelledError + mock_ws.side_effect = Exception("TEST ERROR.") + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...") + ) + + def test_subscribe_to_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_to_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_channel_originating_message_returns_correct(self): + event_type = self._orderbook_update_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._diff_messages_queue_key, event_message) + + event_type = self._funding_info_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._funding_info_messages_queue_key, event_message) + + event_type = self._orderbook_trade_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._trade_messages_queue_key, event_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successful(self, mock_ws): + msg_queue_diffs: asyncio.Queue = asyncio.Queue() + msg_queue_trades: asyncio.Queue = asyncio.Queue() + msg_queue_funding: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.close.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_update_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_trade_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._funding_info_event()) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.listening_task_diffs = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue_diffs) + ) + self.listening_task_trades = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue_trades) + ) + self.listening_task_funding_info = self.ev_loop.create_task( + self.data_source.listen_for_funding_info(msg_queue_funding)) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_diffs.get()) + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.DIFF, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(result.update_id, 752409360466) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + self.assertEqual(1, len(result.content["bids"])) + self.assertEqual(1, len(result.content["asks"])) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_trades.get()) + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.TRADE, result.type) + self.assertTrue(result.has_trade_id) + self.assertEqual(result.trade_id, 817295132) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_error_raised(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + def test_listen_for_order_book_snapshots_logs_exception_error_with_response(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "m": 1, + "i": 2, + } + mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "lastUpdateId": 1027024, + "E": 1589436922972, + "T": 1589436922959, + "bids": [["10", "1"]], + "asks": [["11", "1"]], + } + mock_api.get(regex_url, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + result = self.async_run_with_timeout(msg_queue.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(result.update_id, 1027024) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + def test_listen_for_funding_info_cancelled_error_raised(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.data_source._message_queue[CONSTANTS.FUNDING_INFO_STREAM_ID] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_funding_info(mock_queue)) diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_auth.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_auth.py new file mode 100644 index 0000000..7db4ca5 --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_auth.py @@ -0,0 +1,87 @@ +import asyncio +import copy +import hashlib +import hmac +import json +import unittest +from typing import Awaitable +from urllib.parse import urlencode + +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_auth import BinancePerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class BinancePerpetualAuthUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.emulated_time = 1640001112.223 + self.test_params = { + "test_param": "test_input", + "timestamp": int(self.emulated_time * 1e3), + } + self.auth = BinancePerpetualAuth( + api_key=self.api_key, + api_secret=self.secret_key, + time_provider=self) + + def _get_test_payload(self): + return urlencode(dict(copy.deepcopy(self.test_params))) + + def _get_signature_from_test_payload(self): + return hmac.new( + bytes(self.auth._api_secret.encode("utf-8")), self._get_test_payload().encode("utf-8"), hashlib.sha256 + ).hexdigest() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_generate_signature_from_payload(self): + payload = self._get_test_payload() + signature = self.auth.generate_signature_from_payload(payload) + + self.assertEqual(signature, self._get_signature_from_test_payload()) + + def test_rest_authenticate_parameters_provided(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url="/TEST_PATH_URL", params=copy.deepcopy(self.test_params), is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("X-MBX-APIKEY", signed_request.headers) + self.assertEqual(signed_request.headers["X-MBX-APIKEY"], self.api_key) + self.assertIn("signature", signed_request.params) + self.assertEqual(signed_request.params["signature"], self._get_signature_from_test_payload()) + + def test_rest_authenticate_data_provided(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, url="/TEST_PATH_URL", data=json.dumps(self.test_params), is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("X-MBX-APIKEY", signed_request.headers) + self.assertEqual(signed_request.headers["X-MBX-APIKEY"], self.api_key) + self.assertIn("signature", signed_request.data) + self.assertEqual(signed_request.data["signature"], self._get_signature_from_test_payload()) + + def test_ws_authenticate(self): + request: WSJSONRequest = WSJSONRequest( + payload={"TEST": "SOME_TEST_PAYLOAD"}, throttler_limit_id="TEST_LIMIT_ID", is_auth_required=True + ) + + signed_request: WSJSONRequest = self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(request, signed_request) diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py new file mode 100644 index 0000000..5f587a8 --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_derivative.py @@ -0,0 +1,2354 @@ +import asyncio +import functools +import json +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Callable, Dict, List, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +import pandas as pd +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_api_order_book_data_source import ( + BinancePerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import BinancePerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent + + +class BinancePerpetualDerivativeUnitTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.symbol = f"{cls.base_asset}{cls.quote_asset}" + cls.domain = CONSTANTS.TESTNET_DOMAIN + cls.listen_key = "TEST_LISTEN_KEY" + + cls.ev_loop = asyncio.get_event_loop() + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + + self.ws_sent_messages = [] + self.ws_incoming_messages = asyncio.Queue() + self.resume_test_event = asyncio.Event() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = BinancePerpetualDerivative( + client_config_map=self.client_config_map, + binance_perpetual_api_key="testAPIKey", + binance_perpetual_api_secret="testSecret", + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + + if hasattr(self.exchange, "_time_synchronizer"): + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + + BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict({self.symbol: self.trading_pair}) + } + + self.exchange._set_current_timestamp(1640780000) + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + self.mocking_assistant = NetworkMockingAssistant() + self.test_task: Optional[asyncio.Task] = None + self.resume_test_event = asyncio.Event() + self._initialize_event_loggers() + + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL) + return url + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.PING_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.ACCOUNT_INFO_URL) + return url + + @property + def funding_info_url(self): + url = web_utils.public_rest_url( + path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + url = web_utils.private_rest_url( + path_url=CONSTANTS.GET_INCOME_HISTORY_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + BinancePerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.funding_payment_completed_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.FundingPaymentCompleted, self.funding_payment_completed_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _return_calculation_and_set_done_event(self, calculation: Callable, *args, **kwargs): + if self.resume_test_event.is_set(): + raise asyncio.CancelledError + self.resume_test_event.set() + return calculation(*args, **kwargs) + + def _get_position_risk_api_endpoint_single_position_list(self) -> List[Dict[str, Any]]: + positions = [ + { + "symbol": self.symbol, + "positionAmt": "1", + "entryPrice": "10", + "markPrice": "11", + "unRealizedProfit": "1", + "liquidationPrice": "100", + "leverage": "1", + "maxNotionalValue": "9", + "marginType": "cross", + "isolatedMargin": "0", + "isAutoAddMargin": "false", + "positionSide": "BOTH", + "notional": "11", + "isolatedWallet": "0", + "updateTime": int(self.start_timestamp), + } + ] + return positions + + def _get_wrong_symbol_position_risk_api_endpoint_single_position_list(self) -> List[Dict[str, Any]]: + positions = [ + { + "symbol": f"{self.symbol}_230331", + "positionAmt": "1", + "entryPrice": "10", + "markPrice": "11", + "unRealizedProfit": "1", + "liquidationPrice": "100", + "leverage": "1", + "maxNotionalValue": "9", + "marginType": "cross", + "isolatedMargin": "0", + "isAutoAddMargin": "false", + "positionSide": "BOTH", + "notional": "11", + "isolatedWallet": "0", + "updateTime": int(self.start_timestamp), + } + ] + return positions + + def _get_account_update_ws_event_single_position_dict(self) -> Dict[str, Any]: + account_update = { + "e": "ACCOUNT_UPDATE", + "E": 1564745798939, + "T": 1564745798938, + "a": { + "m": "POSITION", + "B": [ + {"a": "USDT", "wb": "122624.12345678", "cw": "100.12345678", "bc": "50.12345678"}, + ], + "P": [ + { + "s": self.symbol, + "pa": "1", + "ep": "10", + "cr": "200", + "up": "1", + "mt": "cross", + "iw": "0.00000000", + "ps": "BOTH", + }, + ], + }, + } + return account_update + + def _get_wrong_symbol_account_update_ws_event_single_position_dict(self) -> Dict[str, Any]: + account_update = { + "e": "ACCOUNT_UPDATE", + "E": 1564745798939, + "T": 1564745798938, + "a": { + "m": "POSITION", + "B": [ + {"a": "USDT", "wb": "122624.12345678", "cw": "100.12345678", "bc": "50.12345678"}, + ], + "P": [ + { + "s": f"{self.symbol}_230331", + "pa": "1", + "ep": "10", + "cr": "200", + "up": "1", + "mt": "cross", + "iw": "0.00000000", + "ps": "BOTH", + }, + ], + }, + } + return account_update + + def _get_income_history_dict(self) -> List: + income_history = [{ + "income": 1, + "symbol": self.symbol, + "time": self.start_timestamp, + }] + return income_history + + def _get_funding_info_dict(self) -> Dict[str, Any]: + funding_info = { + "indexPrice": 1000, + "markPrice": 1001, + "nextFundingTime": self.start_timestamp + 8 * 60 * 60, + "lastFundingRate": 1010 + } + return funding_info + + def _get_trading_pair_symbol_map(self) -> Dict[str, str]: + trading_pair_symbol_map = {self.symbol: f"{self.base_asset}-{self.quote_asset}"} + return trading_pair_symbol_map + + def _get_exchange_info_mock_response( + self, + margin_asset: str = "HBOT", + min_order_size: float = 1, + min_price_increment: float = 2, + min_base_amount_increment: float = 3, + min_notional_size: float = 4, + ) -> Dict[str, Any]: + mocked_exchange_info = { # irrelevant fields removed + "symbols": [ + { + "symbol": self.symbol, + "pair": self.symbol, + "contractType": "PERPETUAL", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "marginAsset": margin_asset, + "status": "TRADING", + "filters": [ + { + "filterType": "PRICE_FILTER", + "maxPrice": "300", + "minPrice": "0.0001", + "tickSize": str(min_price_increment), + }, + { + "filterType": "LOT_SIZE", + "maxQty": "10000000", + "minQty": str(min_order_size), + "stepSize": str(min_base_amount_increment), + }, + { + "filterType": "MIN_NOTIONAL", + "notional": str(min_notional_size), + }, + ], + } + ], + } + + return mocked_exchange_info + + def _get_exchange_info_error_mock_response( + self, + margin_asset: str = "HBOT", + min_order_size: float = 1, + min_price_increment: float = 2, + min_base_amount_increment: float = 3, + min_notional_size: float = 4, + ) -> Dict[str, Any]: + mocked_exchange_info = { # irrelevant fields removed + "symbols": [ + { + "symbol": self.symbol, + "pair": self.symbol, + "contractType": "PERPETUAL", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "marginAsset": margin_asset, + "status": "TRADING", + } + ], + } + + return mocked_exchange_info + + @aioresponses() + def test_existing_account_position_detected_on_positions_update(self, req_mock): + self._simulate_trading_rules_initialized() + + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.trading_pair.replace("-", ""), self.symbol) + + @aioresponses() + def test_wrong_symbol_position_detected_on_positions_update(self, req_mock): + self._simulate_trading_rules_initialized() + + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_wrong_symbol_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 0) + + @aioresponses() + def test_account_position_updated_on_positions_update(self, req_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 1) + + positions[0]["positionAmt"] = "2" + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 2) + + @aioresponses() + def test_new_account_position_detected_on_positions_update(self, req_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps([])) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 0) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + + @aioresponses() + def test_closed_account_position_removed_on_positions_update(self, req_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + + positions[0]["positionAmt"] = "0" + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_new_account_position_detected_on_stream_event(self, mock_api, ws_connect_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + listen_key_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=json.dumps(listen_key_response)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 0) + + account_update = self._get_account_update_ws_event_single_position_dict() + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 1) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_account_position_updated_on_stream_event(self, mock_api, ws_connect_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + url = web_utils.private_rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + listen_key_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=json.dumps(listen_key_response)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 1) + + account_update = self._get_account_update_ws_event_single_position_dict() + account_update["a"]["P"][0]["pa"] = 2 + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 2) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_closed_account_position_removed_on_stream_event(self, mock_api, ws_connect_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + url = web_utils.private_rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + listen_key_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=json.dumps(listen_key_response)) + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 1) + + account_update = self._get_account_update_ws_event_single_position_dict() + account_update["a"]["P"][0]["pa"] = 0 + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_wrong_symbol_new_account_position_detected_on_stream_event(self, mock_api, ws_connect_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url(CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + listen_key_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=json.dumps(listen_key_response)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 0) + + account_update = self._get_wrong_symbol_account_update_ws_event_single_position_dict() + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + url = web_utils.private_rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 0) + + def test_supported_position_modes(self): + linear_connector = self.exchange + expected_result = [PositionMode.ONEWAY, PositionMode.HEDGE] + self.assertEqual(expected_result, linear_connector.supported_position_modes()) + + @aioresponses() + def test_set_position_mode_initial_mode_is_none(self, mock_api): + self._simulate_trading_rules_initialized() + self.assertIsNone(self.exchange._position_mode) + + url = web_utils.private_rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + get_position_mode_response = {"dualSidePosition": False} # True: Hedge Mode; False: One-way Mode + post_position_mode_response = {"code": 200, "msg": "success"} + mock_api.get(regex_url, body=json.dumps(get_position_mode_response)) + mock_api.post(regex_url, body=json.dumps(post_position_mode_response)) + + task = self.ev_loop.create_task( + self.exchange._trading_pair_position_mode_set(PositionMode.HEDGE, self.trading_pair)) + self.async_run_with_timeout(task) + + self.assertEqual(PositionMode.HEDGE, self.exchange._position_mode) + + @aioresponses() + def test_set_position_initial_mode_unchanged(self, mock_api): + self.exchange._position_mode = PositionMode.ONEWAY + url = web_utils.private_rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + get_position_mode_response = {"dualSidePosition": False} # True: Hedge Mode; False: One-way Mode + + mock_api.get(regex_url, body=json.dumps(get_position_mode_response)) + task = self.ev_loop.create_task( + self.exchange._trading_pair_position_mode_set(PositionMode.ONEWAY, self.trading_pair)) + self.async_run_with_timeout(task) + + self.assertEqual(PositionMode.ONEWAY, self.exchange.position_mode) + + @aioresponses() + def test_set_position_mode_diff_initial_mode_change_successful(self, mock_api): + self.exchange._position_mode = PositionMode.ONEWAY + url = web_utils.private_rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + get_position_mode_response = {"dualSidePosition": False} # True: Hedge Mode; False: One-way Mode + post_position_mode_response = {"code": 200, "msg": "success"} + + mock_api.get(regex_url, body=json.dumps(get_position_mode_response)) + mock_api.post(regex_url, body=json.dumps(post_position_mode_response)) + + task = self.ev_loop.create_task( + self.exchange._trading_pair_position_mode_set(PositionMode.HEDGE, self.trading_pair)) + self.async_run_with_timeout(task) + + self.assertEqual(PositionMode.HEDGE, self.exchange._position_mode) + + @aioresponses() + def test_set_position_mode_diff_initial_mode_change_fail(self, mock_api): + self.exchange._position_mode = PositionMode.ONEWAY + url = web_utils.private_rest_url(CONSTANTS.CHANGE_POSITION_MODE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + get_position_mode_response = {"dualSidePosition": False} # True: Hedge Mode; False: One-way Mode + post_position_mode_response = {"code": -4059, "msg": "No need to change position side."} + + mock_api.get(regex_url, body=json.dumps(get_position_mode_response)) + mock_api.post(regex_url, body=json.dumps(post_position_mode_response)) + + task = self.ev_loop.create_task( + self.exchange._trading_pair_position_mode_set(PositionMode.HEDGE, self.trading_pair)) + self.async_run_with_timeout(task) + + self.assertEqual(PositionMode.ONEWAY, self.exchange.position_mode) + + def test_format_trading_rules(self): + margin_asset = self.quote_asset + min_order_size = 1 + min_price_increment = 2 + min_base_amount_increment = 3 + min_notional_size = 4 + mocked_response = self._get_exchange_info_mock_response( + margin_asset, min_order_size, min_price_increment, min_base_amount_increment, min_notional_size + ) + self._simulate_trading_rules_initialized() + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + + self.assertEqual(1, len(trading_rules)) + + trading_rule = trading_rules[0] + + self.assertEqual(min_order_size, trading_rule.min_order_size) + self.assertEqual(min_price_increment, trading_rule.min_price_increment) + self.assertEqual(min_base_amount_increment, trading_rule.min_base_amount_increment) + self.assertEqual(min_notional_size, trading_rule.min_notional_size) + self.assertEqual(margin_asset, trading_rule.buy_order_collateral_token) + self.assertEqual(margin_asset, trading_rule.sell_order_collateral_token) + + def test_format_trading_rules_exception(self): + margin_asset = self.quote_asset + min_order_size = 1 + min_price_increment = 2 + min_base_amount_increment = 3 + min_notional_size = 4 + mocked_response = self._get_exchange_info_error_mock_response( + margin_asset, min_order_size, min_price_increment, min_base_amount_increment, min_notional_size + ) + self._simulate_trading_rules_initialized() + + self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.assertTrue(self._is_logged( + "ERROR", + f"Error parsing the trading pair rule {mocked_response['symbols'][0]}. Error: 'filters'. Skipping..." + )) + + def test_get_collateral_token(self): + margin_asset = self.quote_asset + self._simulate_trading_rules_initialized() + + self.assertEqual(margin_asset, self.exchange.get_buy_collateral_token(self.trading_pair)) + self.assertEqual(margin_asset, self.exchange.get_sell_collateral_token(self.trading_pair)) + + def test_buy_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "PARTIALLY_FILLED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + "N": "HBOT", + "n": "20", + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual( + [TokenAmount(partial_fill["o"]["N"], Decimal(partial_fill["o"]["n"]))], fill_event.trade_fee.flat_fees + ) + + complete_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "FILLED", + "i": 8886774, + "l": "0.9", + "z": "1", + "L": "10000", + "N": "HBOT", + "n": "30", + "T": 1568879465651, + "t": 2, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual([TokenAmount(complete_fill["o"]["N"], Decimal(complete_fill["o"]["n"]))], + fill_event.trade_fee.flat_fees) + + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) + + def test_sell_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "SELL", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "PARTIALLY_FILLED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + "N": self.quote_asset, + "n": "20", + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual( + [TokenAmount(partial_fill["o"]["N"], Decimal(partial_fill["o"]["n"]))], fill_event.trade_fee.flat_fees + ) + + complete_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "SELL", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "FILLED", + "i": 8886774, + "l": "0.9", + "z": "1", + "L": "10000", + "N": self.quote_asset, + "n": "30", + "T": 1568879465651, + "t": 2, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual([TokenAmount(complete_fill["o"]["N"], Decimal(complete_fill["o"]["n"]))], + fill_event.trade_fee.flat_fees) + + self.assertEqual(1, len(self.sell_order_completed_logger.event_log)) + + def test_order_fill_event_ignored_for_repeated_trade_id(self): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "PARTIALLY_FILLED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + "N": self.quote_asset, + "n": "20", + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual( + [TokenAmount(partial_fill["o"]["N"], Decimal(partial_fill["o"]["n"]))], fill_event.trade_fee.flat_fees + ) + + repeated_partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "PARTIALLY_FILLED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + "N": self.quote_asset, + "n": "20", + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: repeated_partial_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + def test_fee_is_zero_when_not_included_in_fill_event(self): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "PARTIALLY_FILLED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + # "N": "USDT", //Do not include fee asset + # "n": "20", //Do not include fee amount + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + + } + + task = self.ev_loop.create_task(self.exchange._process_user_stream_event(event_message=partial_fill)) + self.async_run_with_timeout(task) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual(0, len(fill_event.trade_fee.flat_fees)) + + def test_order_event_with_cancelled_status_marks_order_as_cancelled(self): + self._simulate_trading_rules_initialized() + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "e": "ORDER_TRADE_UPDATE", + "E": 1568879465651, + "T": 1568879465650, + "o": { + "s": self.trading_pair, + "c": order.client_order_id, + "S": "BUY", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "1", + "p": "10000", + "ap": "0", + "sp": "7103.04", + "x": "TRADE", + "X": "CANCELED", + "i": 8886774, + "l": "0.1", + "z": "0.1", + "L": "10000", + "N": self.quote_asset, + "n": "20", + "T": 1568879465651, + "t": 1, + "b": "0", + "a": "9.91", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "7476.89", + "cr": "5.0", + "rp": "0" + } + + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + + self.assertTrue(self._is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + )) + + def test_user_stream_event_listener_raises_cancelled_error(self): + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = asyncio.CancelledError + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.test_task) + + def test_margin_call_event(self): + self._simulate_trading_rules_initialized() + margin_call = { + "e": "MARGIN_CALL", + "E": 1587727187525, + "cw": "3.16812045", + "p": [ + { + "s": self.symbol, + "ps": "LONG", + "pa": "1.327", + "mt": "CROSSED", + "iw": "0", + "mp": "187.17127", + "up": "-1.166074", + "mm": "1.614445" + } + ] + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: margin_call) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged( + "WARNING", + "Margin Call: Your position risk is too high, and you are at risk of liquidation. " + "Close your positions or add additional margin to your wallet." + )) + self.assertTrue(self._is_logged( + "INFO", + f"Margin Required: 1.614445. Negative PnL assets: {self.trading_pair}: -1.166074, ." + )) + + def test_wrong_symbol_margin_call_event(self): + self._simulate_trading_rules_initialized() + margin_call = { + "e": "MARGIN_CALL", + "E": 1587727187525, + "cw": "3.16812045", + "p": [ + { + "s": f"{self.symbol}_230331", + "ps": "LONG", + "pa": "1.327", + "mt": "CROSSED", + "iw": "0", + "mp": "187.17127", + "up": "-1.166074", + "mm": "1.614445" + } + ] + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: margin_call) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged( + "WARNING", + "Margin Call: Your position risk is too high, and you are at risk of liquidation. " + "Close your positions or add additional margin to your wallet." + )) + self.assertTrue(self._is_logged( + "INFO", + "Margin Required: 0. Negative PnL assets: ." + )) + + @aioresponses() + @patch("hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative." + "BinancePerpetualDerivative.current_timestamp") + def test_update_order_fills_from_trades_successful(self, req_mock, mock_timestamp): + self._simulate_trading_rules_initialized() + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + trades = [{"buyer": False, + "commission": "0", + "commissionAsset": self.quote_asset, + "id": 698759, + "maker": False, + "orderId": "8886774", + "price": "10000", + "qty": "0.5", + "quoteQty": "5000", + "realizedPnl": "0", + "side": "SELL", + "positionSide": "SHORT", + "symbol": "COINALPHAHBOT", + "time": 1000}] + + url = web_utils.private_rest_url( + CONSTANTS.ACCOUNT_TRADE_LIST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(trades)) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + in_flight_orders = self.exchange._order_tracker.active_orders + + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) + self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) + self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) + self.assertEqual(10000, in_flight_orders["OID1"].price) + self.assertEqual(1, in_flight_orders["OID1"].amount) + self.assertEqual("8886774", in_flight_orders["OID1"].exchange_order_id) + self.assertEqual(OrderState.PENDING_CREATE, in_flight_orders["OID1"].current_state) + self.assertEqual(1, in_flight_orders["OID1"].leverage) + self.assertEqual(PositionAction.OPEN, in_flight_orders["OID1"].position) + + self.assertEqual(0.5, in_flight_orders["OID1"].executed_amount_base) + self.assertEqual(5000, in_flight_orders["OID1"].executed_amount_quote) + self.assertEqual(1, in_flight_orders["OID1"].last_update_timestamp) + + self.assertTrue("698759" in in_flight_orders["OID1"].order_fills.keys()) + + @aioresponses() + def test_update_order_fills_from_trades_failed(self, req_mock): + self.exchange._set_current_timestamp(1640001112.0) + self.exchange._last_poll_timestamp = 0 + self._simulate_trading_rules_initialized() + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + url = web_utils.private_rest_url( + CONSTANTS.ACCOUNT_TRADE_LIST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, exception=Exception()) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + in_flight_orders = self.exchange._order_tracker.active_orders + + # Nothing has changed + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) + self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) + self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) + self.assertEqual(10000, in_flight_orders["OID1"].price) + self.assertEqual(1, in_flight_orders["OID1"].amount) + self.assertEqual("8886774", in_flight_orders["OID1"].exchange_order_id) + self.assertEqual(OrderState.PENDING_CREATE, in_flight_orders["OID1"].current_state) + self.assertEqual(1, in_flight_orders["OID1"].leverage) + self.assertEqual(PositionAction.OPEN, in_flight_orders["OID1"].position) + + self.assertEqual(0, in_flight_orders["OID1"].executed_amount_base) + self.assertEqual(0, in_flight_orders["OID1"].executed_amount_quote) + self.assertEqual(1640001112.0, in_flight_orders["OID1"].last_update_timestamp) + + # Error was logged + self.assertTrue(self._is_logged("NETWORK", + f"Error fetching trades update for the order {self.trading_pair}: .")) + + @aioresponses() + @patch("hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative." + "BinancePerpetualDerivative.current_timestamp") + def test_update_order_status_successful(self, req_mock, mock_timestamp): + self._simulate_trading_rules_initialized() + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + order = {"avgPrice": "0.00000", + "clientOrderId": "OID1", + "cumQuote": "5000", + "executedQty": "0.5", + "orderId": 8886774, + "origQty": "1", + "origType": "LIMIT", + "price": "10000", + "reduceOnly": False, + "side": "SELL", + "positionSide": "LONG", + "status": "PARTIALLY_FILLED", + "closePosition": False, + "symbol": f"{self.base_asset}{self.quote_asset}", + "time": 1000, + "timeInForce": "GTC", + "type": "LIMIT", + "priceRate": "0.3", + "updateTime": 2000, + "workingType": "CONTRACT_PRICE", + "priceProtect": False} + + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + in_flight_orders = self.exchange._order_tracker.active_orders + + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) + self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) + self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) + self.assertEqual(10000, in_flight_orders["OID1"].price) + self.assertEqual(1, in_flight_orders["OID1"].amount) + self.assertEqual("8886774", in_flight_orders["OID1"].exchange_order_id) + self.assertEqual(OrderState.PARTIALLY_FILLED, in_flight_orders["OID1"].current_state) + self.assertEqual(1, in_flight_orders["OID1"].leverage) + self.assertEqual(PositionAction.OPEN, in_flight_orders["OID1"].position) + + # Processing an order update should not impact trade fill information + self.assertEqual(Decimal("0"), in_flight_orders["OID1"].executed_amount_base) + self.assertEqual(Decimal("0"), in_flight_orders["OID1"].executed_amount_quote) + + self.assertEqual(2, in_flight_orders["OID1"].last_update_timestamp) + + self.assertEqual(0, len(in_flight_orders["OID1"].order_fills)) + + @aioresponses() + @patch("hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative." + "BinancePerpetualDerivative.current_timestamp") + def test_request_order_status_successful(self, req_mock, mock_timestamp): + self._simulate_trading_rules_initialized() + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + tracked_order = self.exchange._order_tracker.fetch_order("OID1") + + order = {"avgPrice": "0.00000", + "clientOrderId": "OID1", + "cumQuote": "5000", + "executedQty": "0.5", + "orderId": 8886774, + "origQty": "1", + "origType": "LIMIT", + "price": "10000", + "reduceOnly": False, + "side": "SELL", + "positionSide": "LONG", + "status": "PARTIALLY_FILLED", + "closePosition": False, + "symbol": f"{self.base_asset}{self.quote_asset}", + "time": 1000, + "timeInForce": "GTC", + "type": "LIMIT", + "priceRate": "0.3", + "updateTime": 2000, + "workingType": "CONTRACT_PRICE", + "priceProtect": False} + + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + order_update = self.async_run_with_timeout(self.exchange._request_order_status(tracked_order)) + + in_flight_orders = self.exchange._order_tracker.active_orders + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual(order_update.client_order_id, in_flight_orders["OID1"].client_order_id) + self.assertEqual(OrderState.PARTIALLY_FILLED, order_update.new_state) + self.assertEqual(0, len(in_flight_orders["OID1"].order_fills)) + + @aioresponses() + def test_set_leverage_successful(self, req_mock): + self._simulate_trading_rules_initialized() + trading_pair = f"{self.base_asset}-{self.quote_asset}" + symbol = f"{self.base_asset}{self.quote_asset}" + leverage = 21 + + response = { + "leverage": leverage, + "maxNotionalValue": "1000000", + "symbol": symbol + } + + url = web_utils.private_rest_url( + CONSTANTS.SET_LEVERAGE_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.post(regex_url, body=json.dumps(response)) + + success, msg = self.async_run_with_timeout(self.exchange._set_trading_pair_leverage(trading_pair, leverage)) + self.assertEqual(success, True) + self.assertEqual(msg, '') + + @aioresponses() + def test_set_leverage_failed(self, req_mock): + self._simulate_trading_rules_initialized() + trading_pair = f"{self.base_asset}-{self.quote_asset}" + symbol = f"{self.base_asset}{self.quote_asset}" + leverage = 21 + + response = {"leverage": 0, + "maxNotionalValue": "1000000", + "symbol": symbol} + + url = web_utils.private_rest_url( + CONSTANTS.SET_LEVERAGE_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.post(regex_url, body=json.dumps(response)) + + success, message = self.async_run_with_timeout(self.exchange._set_trading_pair_leverage(trading_pair, leverage)) + self.assertEqual(success, False) + self.assertEqual(message, 'Unable to set leverage') + + @aioresponses() + def test_fetch_funding_payment_successful(self, req_mock): + self._simulate_trading_rules_initialized() + income_history = self._get_income_history_dict() + + url = web_utils.private_rest_url( + CONSTANTS.GET_INCOME_HISTORY_URL, domain=self.domain + ) + regex_url_income_history = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url_income_history, body=json.dumps(income_history)) + + funding_info = self._get_funding_info_dict() + + url = web_utils.public_rest_url( + CONSTANTS.MARK_PRICE_URL, domain=self.domain + ) + regex_url_funding_info = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url_funding_info, body=json.dumps(funding_info)) + + # Fetch from exchange with REST API - safe_ensure_future, not immediately + self.async_run_with_timeout(self.exchange._update_funding_payment(self.trading_pair, True)) + + req_mock.get(regex_url_income_history, body=json.dumps(income_history)) + + # Fetch once received + self.async_run_with_timeout(self.exchange._update_funding_payment(self.trading_pair, True)) + + self.assertTrue(len(self.funding_payment_completed_logger.event_log) == 1) + + funding_info_logged = self.funding_payment_completed_logger.event_log[0] + + self.assertTrue(funding_info_logged.trading_pair == f"{self.base_asset}-{self.quote_asset}") + + self.assertEqual(funding_info_logged.funding_rate, funding_info["lastFundingRate"]) + self.assertEqual(funding_info_logged.amount, income_history[0]["income"]) + + @aioresponses() + def test_fetch_funding_payment_failed(self, req_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.GET_INCOME_HISTORY_URL, domain=self.domain + ) + regex_url_income_history = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url_income_history, exception=Exception) + + self.async_run_with_timeout(self.exchange._update_funding_payment(self.trading_pair, False)) + + self.assertTrue(self._is_logged( + "NETWORK", + f"Unexpected error while fetching last fee payment for {self.trading_pair}.", + )) + + @aioresponses() + def test_cancel_all_successful(self, mocked_api): + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"code": 200, "msg": "success", "status": "CANCELED"} + mocked_api.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="8886775", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10101"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + self.assertTrue("OID2" in self.exchange._order_tracker._in_flight_orders) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(timeout_seconds=1)) + + order_cancelled_events = self.order_cancelled_logger.event_log + + self.assertEqual(0, len(order_cancelled_events)) + self.assertEqual(2, len(cancellation_results)) + + @aioresponses() + def test_cancel_all_unknown_order(self, req_mock): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"code": -2011, "msg": "Unknown order sent."} + req_mock.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + tracked_order = self.exchange._order_tracker.fetch_order("OID1") + tracked_order.current_state = OrderState.OPEN + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(timeout_seconds=1)) + + self.assertEqual(1, len(cancellation_results)) + self.assertEqual("OID1", cancellation_results[0].order_id) + + self.assertTrue(self._is_logged( + "DEBUG", + "The order OID1 does not exist on Binance Perpetuals. " + "No cancelation needed." + )) + + self.assertTrue("OID1" in self.exchange._order_tracker._order_not_found_records) + + @aioresponses() + def test_cancel_all_exception(self, req_mock): + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.delete(regex_url, exception=Exception()) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + tracked_order = self.exchange._order_tracker.fetch_order("OID1") + tracked_order.current_state = OrderState.OPEN + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(timeout_seconds=1)) + + self.assertEqual(1, len(cancellation_results)) + self.assertEqual("OID1", cancellation_results[0].order_id) + + self.assertTrue(self._is_logged( + "ERROR", + "Failed to cancel order OID1", + )) + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + @aioresponses() + def test_cancel_order_successful(self, mock_api): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = { + "clientOrderId": "ODI1", + "cumQty": "0", + "cumQuote": "0", + "executedQty": "0", + "orderId": 283194212, + "origQty": "11", + "origType": "TRAILING_STOP_MARKET", + "price": "0", + "reduceOnly": False, + "side": "BUY", + "positionSide": "SHORT", + "status": "CANCELED", + "stopPrice": "9300", + "closePosition": False, + "symbol": "BTCUSDT", + "timeInForce": "GTC", + "type": "TRAILING_STOP_MARKET", + "activatePrice": "9020", + "priceRate": "0.3", + "updateTime": 1571110484038, + "workingType": "CONTRACT_PRICE", + "priceProtect": False + } + mock_api.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + tracked_order = self.exchange._order_tracker.fetch_order("OID1") + tracked_order.current_state = OrderState.OPEN + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + canceled_order_id = self.async_run_with_timeout(self.exchange._execute_cancel(trading_pair=self.trading_pair, + order_id="OID1")) + + order_cancelled_events = self.order_cancelled_logger.event_log + + self.assertEqual(1, len(order_cancelled_events)) + self.assertEqual("OID1", canceled_order_id) + + @aioresponses() + def test_cancel_order_failed(self, mock_api): + self._simulate_trading_rules_initialized() + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = { + "clientOrderId": "ODI1", + "cumQty": "0", + "cumQuote": "0", + "executedQty": "0", + "orderId": 283194212, + "origQty": "11", + "origType": "TRAILING_STOP_MARKET", + "price": "0", + "reduceOnly": False, + "side": "BUY", + "positionSide": "SHORT", + "status": "FILLED", + "stopPrice": "9300", + "closePosition": False, + "symbol": "BTCUSDT", + "timeInForce": "GTC", + "type": "TRAILING_STOP_MARKET", + "activatePrice": "9020", + "priceRate": "0.3", + "updateTime": 1571110484038, + "workingType": "CONTRACT_PRICE", + "priceProtect": False + } + mock_api.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + tracked_order = self.exchange._order_tracker.fetch_order("OID1") + tracked_order.current_state = OrderState.OPEN + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + self.async_run_with_timeout(self.exchange._execute_cancel(trading_pair=self.trading_pair, order_id="OID1")) + + order_cancelled_events = self.order_cancelled_logger.event_log + + self.assertEqual(0, len(order_cancelled_events)) + + @aioresponses() + def test_create_order_successful(self, req_mock): + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "status": "NEW", + "orderId": "8886774"} + req_mock.post(regex_url, body=json.dumps(create_response)) + self._simulate_trading_rules_initialized() + + self.async_run_with_timeout(self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("10000"))) + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + @aioresponses() + @patch("hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils.get_current_server_time") + def test_place_order_manage_server_overloaded_error_unkown_order(self, mock_api, mock_seconds_counter: MagicMock): + mock_seconds_counter.return_value = 1640780000 + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = {"code": -1003, "msg": "Unknown error, please check your request or try again later."} + + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + self._simulate_trading_rules_initialized() + + o_id, timestamp = self.async_run_with_timeout(self.exchange._place_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("10000"))) + self.assertEqual(o_id, "UNKNOWN") + + @aioresponses() + def test_create_limit_maker_successful(self, req_mock): + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "status": "NEW", + "orderId": "8886774"} + req_mock.post(regex_url, body=json.dumps(create_response)) + self._simulate_trading_rules_initialized() + + self.async_run_with_timeout(self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT_MAKER, + position_action=PositionAction.OPEN, + price=Decimal("10000"))) + + self.assertTrue("OID1" in self.exchange._order_tracker._in_flight_orders) + + @aioresponses() + def test_create_order_exception(self, req_mock): + url = web_utils.private_rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + req_mock.post(regex_url, exception=Exception()) + self._simulate_trading_rules_initialized() + self.async_run_with_timeout(self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("1010"))) + + self.assertTrue("OID1" not in self.exchange._order_tracker._in_flight_orders) + + # The order amount is quantizied + # "Error submitting buy LIMIT order to Binance_perpetual for 9999 COINALPHA-HBOT 1010." + self.assertTrue(self._is_logged( + "NETWORK", + f"Error submitting {TradeType.BUY.name.lower()} {OrderType.LIMIT.name.upper()} order to {self.exchange.name_cap} for " + f"{Decimal('9999')} {self.trading_pair} {Decimal('1010')}.", + )) + + def test_create_order_min_order_size_failure(self): + self._simulate_trading_rules_initialized() + margin_asset = self.quote_asset + min_order_size = 3 + mocked_response = self._get_exchange_info_mock_response(margin_asset, min_order_size=min_order_size) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + trade_type = TradeType.BUY + amount = Decimal("2") + + self.async_run_with_timeout(self.exchange._create_order(trade_type=trade_type, + order_id="OID1", + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("1010"))) + + self.assertTrue("OID1" not in self.exchange._order_tracker._in_flight_orders) + + self.assertTrue(self._is_logged( + "WARNING", + f"{trade_type.name.title()} order amount {amount} is lower than the minimum order " + f"size {trading_rules[0].min_order_size}. The order will not be created, increase the " + f"amount to be higher than the minimum order size." + )) + + def test_create_order_min_notional_size_failure(self): + margin_asset = self.quote_asset + min_notional_size = 10 + self._simulate_trading_rules_initialized() + mocked_response = self._get_exchange_info_mock_response(margin_asset, + min_notional_size=min_notional_size, + min_base_amount_increment=0.5) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + trade_type = TradeType.BUY + amount = Decimal("2") + price = Decimal("4") + + self.async_run_with_timeout(self.exchange._create_order(trade_type=trade_type, + order_id="OID1", + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=price)) + + self.assertTrue("OID1" not in self.exchange._order_tracker._in_flight_orders) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.exchange.in_flight_orders) + self.assertNotIn("OID2", self.exchange.in_flight_orders) + self.assertNotIn("OID3", self.exchange.in_flight_orders) + self.assertNotIn("OID4", self.exchange.in_flight_orders) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 4 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + @aioresponses() + def test_update_balances(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"serverTime": 1640000003000} + + mock_api.get(regex_url, + body=json.dumps(response)) + + url = web_utils.private_rest_url(CONSTANTS.ACCOUNT_INFO_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "feeTier": 0, + "canTrade": True, + "canDeposit": True, + "canWithdraw": True, + "updateTime": 0, + "totalInitialMargin": "0.00000000", + "totalMaintMargin": "0.00000000", + "totalWalletBalance": "23.72469206", + "totalUnrealizedProfit": "0.00000000", + "totalMarginBalance": "23.72469206", + "totalPositionInitialMargin": "0.00000000", + "totalOpenOrderInitialMargin": "0.00000000", + "totalCrossWalletBalance": "23.72469206", + "totalCrossUnPnl": "0.00000000", + "availableBalance": "23.72469206", + "maxWithdrawAmount": "23.72469206", + "assets": [ + { + "asset": "USDT", + "walletBalance": "23.72469206", + "unrealizedProfit": "0.00000000", + "marginBalance": "23.72469206", + "maintMargin": "0.00000000", + "initialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "openOrderInitialMargin": "0.00000000", + "crossWalletBalance": "23.72469206", + "crossUnPnl": "0.00000000", + "availableBalance": "23.72469206", + "maxWithdrawAmount": "23.72469206", + "marginAvailable": True, + "updateTime": 1625474304765, + }, + { + "asset": "BUSD", + "walletBalance": "103.12345678", + "unrealizedProfit": "0.00000000", + "marginBalance": "103.12345678", + "maintMargin": "0.00000000", + "initialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "openOrderInitialMargin": "0.00000000", + "crossWalletBalance": "103.12345678", + "crossUnPnl": "0.00000000", + "availableBalance": "100.12345678", + "maxWithdrawAmount": "103.12345678", + "marginAvailable": True, + "updateTime": 1625474304765, + } + ], + "positions": [{ + "symbol": "BTCUSDT", + "initialMargin": "0", + "maintMargin": "0", + "unrealizedProfit": "0.00000000", + "positionInitialMargin": "0", + "openOrderInitialMargin": "0", + "leverage": "100", + "isolated": True, + "entryPrice": "0.00000", + "maxNotional": "250000", + "bidNotional": "0", + "askNotional": "0", + "positionSide": "BOTH", + "positionAmt": "0", + "updateTime": 0, + } + ] + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("23.72469206"), available_balances["USDT"]) + self.assertEqual(Decimal("100.12345678"), available_balances["BUSD"]) + self.assertEqual(Decimal("23.72469206"), total_balances["USDT"]) + self.assertEqual(Decimal("103.12345678"), total_balances["BUSD"]) + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_account_info_request_includes_timestamp(self, mock_api, mock_seconds_counter): + mock_seconds_counter.return_value = 1000 + + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"serverTime": 1640000003000} + + mock_api.get(regex_url, + body=json.dumps(response)) + + url = web_utils.private_rest_url(CONSTANTS.ACCOUNT_INFO_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "feeTier": 0, + "canTrade": True, + "canDeposit": True, + "canWithdraw": True, + "updateTime": 0, + "totalInitialMargin": "0.00000000", + "totalMaintMargin": "0.00000000", + "totalWalletBalance": "23.72469206", + "totalUnrealizedProfit": "0.00000000", + "totalMarginBalance": "23.72469206", + "totalPositionInitialMargin": "0.00000000", + "totalOpenOrderInitialMargin": "0.00000000", + "totalCrossWalletBalance": "23.72469206", + "totalCrossUnPnl": "0.00000000", + "availableBalance": "23.72469206", + "maxWithdrawAmount": "23.72469206", + "assets": [ + { + "asset": "USDT", + "walletBalance": "23.72469206", + "unrealizedProfit": "0.00000000", + "marginBalance": "23.72469206", + "maintMargin": "0.00000000", + "initialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "openOrderInitialMargin": "0.00000000", + "crossWalletBalance": "23.72469206", + "crossUnPnl": "0.00000000", + "availableBalance": "23.72469206", + "maxWithdrawAmount": "23.72469206", + "marginAvailable": True, + "updateTime": 1625474304765, + }, + { + "asset": "BUSD", + "walletBalance": "103.12345678", + "unrealizedProfit": "0.00000000", + "marginBalance": "103.12345678", + "maintMargin": "0.00000000", + "initialMargin": "0.00000000", + "positionInitialMargin": "0.00000000", + "openOrderInitialMargin": "0.00000000", + "crossWalletBalance": "103.12345678", + "crossUnPnl": "0.00000000", + "availableBalance": "100.12345678", + "maxWithdrawAmount": "103.12345678", + "marginAvailable": True, + "updateTime": 1625474304765, + } + ], + "positions": [{ + "symbol": "BTCUSDT", + "initialMargin": "0", + "maintMargin": "0", + "unrealizedProfit": "0.00000000", + "positionInitialMargin": "0", + "openOrderInitialMargin": "0", + "leverage": "100", + "isolated": True, + "entryPrice": "0.00000", + "maxNotional": "250000", + "bidNotional": "0", + "askNotional": "0", + "positionSide": "BOTH", + "positionAmt": "0", + "updateTime": 0, + } + ] + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + account_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = account_request[1][0].kwargs["params"] + self.assertIsInstance(request_params["timestamp"], int) + + def test_limit_orders(self): + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="8886774", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + leverage=1, + position_action=PositionAction.OPEN, + ) + + limit_orders = self.exchange.limit_orders + + self.assertEqual(len(limit_orders), 2) + self.assertIsInstance(limit_orders, list) + self.assertIsInstance(limit_orders[0], LimitOrder) + + def _simulate_trading_rules_initialized(self): + + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + self.exchange._initialize_trading_pair_symbols_from_exchange_info(mocked_response) + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(1)), + min_price_increment=Decimal(str(2)), + min_base_amount_increment=Decimal(str(3)), + min_notional_size=Decimal(str(4)), + ) + } + return self.exchange._trading_rules diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..3b799fb --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_user_stream_data_source.py @@ -0,0 +1,365 @@ +import asyncio +import re +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, patch + +import ujson +from aioresponses.core import aioresponses + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.binance_perpetual import binance_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_auth import BinancePerpetualAuth +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_derivative import BinancePerpetualDerivative +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_user_stream_data_source import ( + BinancePerpetualUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class BinancePerpetualUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.TESTNET_DOMAIN + + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.emulated_time = 1640001112.223 + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BinancePerpetualDerivative( + client_config_map=client_config_map, + binance_perpetual_api_key="", + binance_perpetual_api_secret="", + domain=self.domain, + trading_pairs=[]) + + self.auth = BinancePerpetualAuth(api_key=self.api_key, + api_secret=self.secret_key, + time_provider=self) + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + api_factory = web_utils.build_api_factory(auth=self.auth) + self.data_source = BinancePerpetualUserStreamDataSource( + auth=self.auth, domain=self.domain, api_factory=api_factory, connector=self.connector, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mock_done_event = asyncio.Event() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _mock_responses_done_callback(self, *_, **__): + self.mock_done_event.set() + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def _successful_get_listen_key_response(self) -> str: + resp = {"listenKey": self.listen_key} + return ujson.dumps(resp) + + def _error_response(self) -> Dict[str, Any]: + resp = {"code": "ERROR CODE", "msg": "ERROR MESSAGE"} + + return resp + + def _simulate_user_update_event(self): + # Order Trade Update + resp = { + "e": "ORDER_TRADE_UPDATE", + "E": 1591274595442, + "T": 1591274595453, + "i": "SfsR", + "o": { + "s": "BTCUSD_200925", + "c": "TEST", + "S": "SELL", + "o": "TRAILING_STOP_MARKET", + "f": "GTC", + "q": "2", + "p": "0", + "ap": "0", + "sp": "9103.1", + "x": "NEW", + "X": "NEW", + "i": 8888888, + "l": "0", + "z": "0", + "L": "0", + "ma": "BTC", + "N": "BTC", + "n": "0", + "T": 1591274595442, + "t": 0, + "rp": "0", + "b": "0", + "a": "0", + "m": False, + "R": False, + "wt": "CONTRACT_PRICE", + "ot": "TRAILING_STOP_MARKET", + "ps": "LONG", + "cp": False, + "AP": "9476.8", + "cr": "5.0", + "pP": False, + }, + } + return ujson.dumps(resp) + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + @aioresponses() + def test_get_listen_key_exception_raised(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, status=400, body=ujson.dumps(self._error_response())) + + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source._get_listen_key()) + + @aioresponses() + def test_get_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_listen_key_response()) + + result: str = self.async_run_with_timeout(self.data_source._get_listen_key()) + + self.assertEqual(self.listen_key, result) + + @aioresponses() + def test_ping_listen_key_failed_log_warning(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.put(regex_url, status=400, body=ujson.dumps(self._error_response())) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + + self.assertTrue( + self._is_logged("WARNING", f"Failed to refresh the listen key {self.listen_key}: {self._error_response()}") + ) + self.assertFalse(result) + + @aioresponses() + def test_ping_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.put(regex_url, body=ujson.dumps({})) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + self.assertTrue(result) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_create_websocket_connection_log_exception(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_listen_key_response()) + + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch( + "hummingbot.connector.derivative.binance_perpetual.binance_perpetual_user_stream_data_source.BinancePerpetualUserStreamDataSource" + "._ping_listen_key", + new_callable=AsyncMock) + def test_manage_listen_key_task_loop_keep_alive_failed(self, mock_ping_listen_key): + mock_ping_listen_key.side_effect = (lambda *args, **kwargs: + self._create_return_value_and_unlock_test_with_event(False)) + + self.data_source._current_listen_key = self.listen_key + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged("ERROR", "Error occurred renewing listen key ...")) + self.assertIsNone(self.data_source._current_listen_key) + self.assertFalse(self.data_source._listen_key_initialized_event.is_set()) + + @aioresponses() + def test_manage_listen_key_task_loop_keep_alive_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.put(regex_url, body=ujson.dumps({}), callback=self._mock_responses_done_callback) + + self.data_source._current_listen_key = self.listen_key + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.mock_done_event.wait()) + + self.assertTrue(self._is_logged("INFO", f"Refreshed listen key {self.listen_key}.")) + self.assertGreater(self.data_source._last_listen_key_ping_ts, 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_create_websocket_connection_failed(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_listen_key_response()) + + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue(self._is_logged("INFO", f"Successfully obtained listen key {self.listen_key}")) + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=ujson.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + mock_ws.return_value.closed = False + mock_ws.return_value.close.side_effect = Exception + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except Exception: + pass + + self.assertTrue(self._is_logged("INFO", f"Successfully obtained listen key {self.listen_key}")) + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_successful(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_listen_key_response()) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._simulate_user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertTrue(msg, self._simulate_user_update_event) + mock_ws.return_value.ping.assert_called() + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_ENDPOINT, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_listen_key_response()) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py new file mode 100644 index 0000000..6621760 --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_utils.py @@ -0,0 +1,10 @@ +import unittest + + +class BinancePerpetualUtilsUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" diff --git a/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_web_utils.py b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_web_utils.py new file mode 100644 index 0000000..9e0f594 --- /dev/null +++ b/test/hummingbot/connector/derivative/binance_perpetual/test_binance_perpetual_web_utils.py @@ -0,0 +1,85 @@ +import asyncio +import unittest +from typing import Awaitable + +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.binance_perpetual.binance_perpetual_web_utils import ( + BinancePerpetualRESTPreProcessor, +) +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BinancePerpetualWebUtilsUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.pre_processor = BinancePerpetualRESTPreProcessor() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_binance_perpetual_rest_pre_processor_non_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/x-www-form-urlencoded") + + def test_binance_perpetual_rest_pre_processor_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/json") + + def test_rest_url_main_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.PERPETUAL_BASE_URL}{path_url}" + self.assertEqual(expected_url, web_utils.public_rest_url(path_url)) + + def test_rest_url_testnet_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.TESTNET_BASE_URL}{path_url}" + self.assertEqual( + expected_url, web_utils.public_rest_url(path_url=path_url, domain="testnet") + ) + + def test_wss_url_main_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.PERPETUAL_WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint)) + + def test_wss_url_testnet_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.TESTNET_WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint, domain="testnet")) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory( + time_synchronizer=TimeSynchronizer(), + time_provider=lambda: None, + ) + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(2, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/__init__.py b/test/hummingbot/connector/derivative/bit_com_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..ab0c3d8 --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_api_order_book_data_source.py @@ -0,0 +1,548 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bit_com_perpetual import bit_com_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_api_order_book_data_source import ( + BitComPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_derivative import BitComPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitComPerpetualAPIOrderBookDataSourceTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USD" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-{cls.quote_asset}-PERPETUAL" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BitComPerpetualDerivative( + client_config_map, + bit_com_perpetual_api_key="", + bit_com_perpetual_api_secret="", + trading_pairs=[self.trading_pair], + ) + self.data_source = BitComPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}-{self.quote_asset}-PERPETUAL": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_rest_snapshot_msg(self) -> Dict: + return { + "code": 0, + "message": "", + "data": { + "instrument_id": "BTC-USD-PERPETUAL", + "timestamp": 1642994567453, + "bids": [ + ["35324.15000000", "0.47000000"], + ["35324.10000000", "1.67000000"], + ["35321.95000000", "2.40000000"], + ["35321.90000000", "4.36000000"], + ["35321.85000000", "1.24000000"] + ], + "asks": [ + ["35325.15000000", "4.68000000"], + ["35327.80000000", "0.53000000"], + ["35351.00000000", "1.00000000"], + ["35352.00000000", "1.00000000"], + ["35353.00000000", "1.00000000"] + ] + } + } + + def get_ws_snapshot_msg(self) -> Dict: + return { + "channel": "depth", + "timestamp": 1643094930373, + "module": "linear", + "data": { + "type": "snapshot", + "instrument_id": "BTC-USD-PERPETUAL", + "sequence": 9, + "bids": [ + [ + "35731.05000000", + "6.82000000" + ] + ], + "asks": [ + [ + "35875.00000000", + "1.00000000" + ] + ] + } + } + + def get_ws_diff_msg(self) -> Dict: + return { + "channel": "depth", + "timestamp": 1643094930373, + "module": "linear", + "data": { + "type": "update", + "instrument_id": "BTC-USD-PERPETUAL", + "sequence": 10, + "prev_sequence": 9, + "changes": [ + [ + "sell", + "35733.00000000", + "1.10000000" + ], + [ + "buy", + "35732.00000000", + "1.10000000" + ] + ] + } + } + + def get_funding_info_msg(self) -> Dict: + return { + "code": 0, + "message": "", + "data": { + "instrument_id": "BTC-USD-PERPETUAL", + "time": 1635913370000, + "funding_rate": "0.00000000", + "funding_rate_8h": "-0.00102858", + "index_price": "62989.63000000", + "mark_price": "62969.83608581" + } + } + + def get_funding_info_rest_msg(self): + return { + "code": 0, + "message": "", + "data": { + "instrument_id": "BTC-USD-PERPETUAL", + "time": 1635913370000, + "funding_rate": "0.00000000", + "funding_rate_8h": "-0.00102858", + "index_price": "62989.63000000", + "mark_price": "62969.83608581" + } + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + endpoint = CONSTANTS.SNAPSHOT_REST_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + resp = self.get_rest_snapshot_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + self.assertEqual(1642994567453, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(5, len(bids)) + self.assertEqual(35324.15, bids[0].price) + self.assertEqual(0.47, bids[0].amount) + self.assertEqual(5, len(asks)) + self.assertEqual(35325.15, asks[0].price) + self.assertEqual(4.68, asks[0].amount) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + endpoint = CONSTANTS.SNAPSHOT_REST_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_diffs_and_orderbooks(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_snapshot_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(3, len(sent_subscription_messages)) + expected_trade_subscription_channel = [CONSTANTS.TRADES_ENDPOINT_NAME] + expected_trade_subscription_payload = [self.ex_trading_pair] + self.assertEqual(expected_trade_subscription_channel, sent_subscription_messages[0]["channels"]) + self.assertEqual(expected_trade_subscription_payload, sent_subscription_messages[0]["instruments"]) + expected_depth_subscription_channel = [CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME] + expected_depth_subscription_payload = [self.ex_trading_pair] + self.assertEqual(expected_depth_subscription_channel, sent_subscription_messages[1]["channels"]) + self.assertEqual(expected_depth_subscription_payload, sent_subscription_messages[1]["instruments"]) + expected_funding_rate_subscription_channel = [CONSTANTS.FUNDING_INFO_STREAM_NAME] + expected_funding_rate_subscription_payload = [self.ex_trading_pair] + self.assertEqual(expected_funding_rate_subscription_channel, sent_subscription_messages[2]["channels"]) + self.assertEqual(expected_funding_rate_subscription_payload, sent_subscription_messages[2]["instruments"]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book, trade and fundingrate channels...") + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." + ) + ) + + def test_subscribe_to_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_to_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book data streams.") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "code": 0, + "message": "", + "data": [ + { + "created_at": 1642994704633, + "trade_id": 1005483402, + "instrument_id": "BTC-USD-PERPETUAL", + "qty": "1.00000000", + "side": "sell", + "sigma": "0.00000000", + "index_price": "2447.79750000", + "underlying_price": "0.00000000", + "is_block_trade": False + }, + { + "created_at": 1642994704241, + "trade_id": 1005483400, + "instrument_id": "BTC-USD-PERPETUAL", + "qty": "1.00000000", + "side": "sell", + "sigma": "0.00000000", + "index_price": "2447.79750000", + "underlying_price": "0.00000000", + "is_block_trade": False + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "code": 0, + "timestamp": 1643099734031, + "channel": "trade", + "data": [ + { + "created_at": 1642994704633, + "trade_id": 1005483402, + "instrument_id": "BTC-USD-PERPETUAL", + "price": "2449.20000000", + "qty": "1.00000000", + "side": "sell", + "sigma": "0.00000000", + "index_price": "2447.79750000", + "underlying_price": "0.00000000", + "is_block_trade": False + } + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_event["data"][0]["trade_id"], msg.trade_id) + self.assertEqual(trade_event["timestamp"] * 1e-3, msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = self.get_ws_diff_msg() + del incomplete_resp["data"]["sequence"] + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self.get_ws_diff_msg() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = int(diff_event["data"]["sequence"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(35732, bids[0].price) + self.assertEqual(1.1, bids[0].amount) + self.assertEqual(1, len(asks)) + self.assertEqual(35733, asks[0].price) + self.assertEqual(1.1, asks[0].amount) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + endpoint = CONSTANTS.SNAPSHOT_REST_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + endpoint = CONSTANTS.SNAPSHOT_REST_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, exception=Exception) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + msg_queue: asyncio.Queue = asyncio.Queue() + endpoint = CONSTANTS.SNAPSHOT_REST_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + resp = self.get_rest_snapshot_msg() + + mock_api.get(regex_url, body=json.dumps(resp)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = resp["data"]["timestamp"] + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(5, len(bids)) + self.assertEqual(35324.15, bids[0].price) + self.assertEqual(0.47, bids[0].amount) + self.assertEqual(5, len(asks)) + self.assertEqual(35325.15, asks[0].price) + self.assertEqual(4.68, asks[0].amount) + + @aioresponses() + def test_get_funding_info(self, mock_api): + endpoint = CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + resp = self.get_funding_info_rest_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + msg_result = resp + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(Decimal(str(msg_result["data"]["mark_price"])), funding_info.mark_price) + self.assertEqual((int(msg_result["data"]["time"] / CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND) + 1) * + CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND, + funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(str(msg_result["data"]["funding_rate"])), funding_info.rate) diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_auth.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_auth.py new file mode 100644 index 0000000..1b0f354 --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_auth.py @@ -0,0 +1,78 @@ +import asyncio +import hashlib +import hmac +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_auth import BitComPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + + +class BitComPerpetualAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + + self.auth = BitComPerpetualAuth(api_key=self.api_key, api_secret=self.secret_key) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _get_timestamp(self): + return 1678974447.926 + + @patch( + "hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_auth.BitComPerpetualAuth._get_timestamp") + def test_add_auth_to_get_request(self, ts_mock: MagicMock): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/linear/v1/orders", + params=params, + is_auth_required=True, + ) + timestamp = self._get_timestamp() + ts_mock.return_value = timestamp + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + raw_signature = f'/linear/v1/orders&one=1×tamp={int(self._get_timestamp() * 1e3)}' + expected_signature = hmac.new(bytes(self.secret_key.encode("utf-8")), + raw_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + params = request.params + headers = request.headers + + self.assertEqual(3, len(params)) + self.assertEqual("1", params.get("one")) + self.assertEqual(self.api_key, headers.get("X-Bit-Access-Key")) + self.assertEqual(expected_signature, params.get("signature")) + + @patch( + "hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_auth.BitComPerpetualAuth._get_timestamp") + def test_add_auth_to_post_request(self, ts_mock: MagicMock): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/linear/v1/orders", + data=json.dumps(params), + is_auth_required=True, + ) + timestamp = self._get_timestamp() + ts_mock.return_value = timestamp + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + raw_signature = f'/linear/v1/orders&one=1×tamp={int(self._get_timestamp() * 1e3)}' + expected_signature = hmac.new(bytes(self.secret_key.encode("utf-8")), + raw_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + params = json.loads(request.data) + headers = request.headers + + self.assertEqual(3, len(params)) + self.assertEqual("1", params.get("one")) + self.assertEqual(self.api_key, headers.get("X-Bit-Access-Key")) + self.assertEqual(expected_signature, params.get("signature")) diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_derivative.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_derivative.py new file mode 100644 index 0000000..7e73bfa --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_derivative.py @@ -0,0 +1,1733 @@ +import asyncio +import json +import logging +import re +from copy import deepcopy +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +import pandas as pd +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_derivative import BitComPerpetualDerivative +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class BitComPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + _logger = logging.getLogger(__name__) + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.user_id = "someUserId" + cls.base_asset = "BTC" + cls.quote_asset = "USD" # linear + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(CONSTANTS.EXCHANGE_INFO_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url( + CONSTANTS.TICKER_PRICE_CHANGE_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.PING_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(CONSTANTS.EXCHANGE_INFO_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def order_creation_url(self): + url = web_utils.public_rest_url( + CONSTANTS.CREATE_ORDER_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def balance_url(self): + url = web_utils.public_rest_url(CONSTANTS.ACCOUNT_INFO_URL) + return url + + @property + def funding_info_url(self): + url = web_utils.public_rest_url( + CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + pass + + @property + def balance_request_mock_response_only_base(self): + pass + + @property + def all_symbols_request_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": [ + { + "instrument_id": self.exchange_trading_pair, + "created_at": 1640944328750, + "updated_at": 1640944328750, + "base_currency": "BTC", + "quote_currency": "USD", + "strike_price": "", + "expiration_at": 4102444800000, + "option_type": "", + "category": "future", + "min_price": "0.00050000", + "max_price": "1000000.00000000", + "price_step": "0.01000000", + "min_size": "0.00010000", + "size_step": "0.00010000", + "delivery_fee_rate": "", + "contract_size": "", + "contract_size_currency": "BTC", + "active": True, + "status": "online", + "groups": [ + 1, + 10, + 100, + 10000 + ], + "group_steps": [ + "0.01000000", + "0.10000000", + "1.00000000", + "100.00000000" + ], + "display_at": 1640944328422, + "is_display": True + } + ] + } + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": { + "time": self.target_funding_info_next_funding_utc_timestamp, + "instrument_id": self.exchange_trading_pair, + "best_bid": "35310.40000000", + "best_ask": "35311.15000000", + "best_bid_qty": "4.46000000", + "best_ask_qty": "3.10000000", + "ask_sigma": "", + "bid_sigma": "", + "last_price": str(self.expected_latest_price), + "last_qty": "5.85000000", + "open24h": "35064.40000000", + "high24h": "36480.60000000", + "low24h": "34688.50000000", + "price_change24h": "0.00700426", + "volume24h": "2329.37000000", + "volume_usd24h": "82236990.06700000", + "open_interest": "94.21840000", + "funding_rate": "0.00000000", + "funding_rate8h": "0.00017589", + "underlying_price": "0.00000000", + "mark_price": "35310.69417074", + "index_price": "1", + "min_sell": "34781.03000000", + "max_buy": "35840.36000000" + } + } + + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": [ + { + "instrument_id": f"{self.base_asset}_{self.quote_asset}", + "created_at": 1640944328750, + "updated_at": 1640944328750, + "base_currency": "BTC", + "quote_currency": "USD", + "strike_price": "", + "expiration_at": 4102444800000, + "option_type": "", + "category": "future", + "min_price": "0.00050000", + "max_price": "1000000.00000000", + "price_step": "0.01000000", + "min_size": "0.00010000", + "size_step": "0.00010000", + "delivery_fee_rate": "", + "contract_size": "", + "contract_size_currency": "BTC", + "active": True, + "status": "online", + "groups": [ + 1, + 10, + 100, + 10000 + ], + "group_steps": [ + "0.01000000", + "0.10000000", + "1.00000000", + "100.00000000" + ], + "display_at": 1640944328422, + "is_display": True + } + ] + } + return "INVALID-PAIR", mock_response + + def empty_funding_payment_mock_response(self): + pass + + @aioresponses() + def test_funding_payment_polling_loop_sends_update_event(self, *args, **kwargs): + pass + + @property + def network_status_request_successful_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": 1587884283175 + } + return mock_response + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": [ + { + "instrument_id": self.exchange_trading_pair, + "created_at": 1640944328750, + "updated_at": 1640944328750, + "base_currency": "BTC", + "quote_currency": "USD", + "strike_price": "", + "expiration_at": 4102444800000, + "option_type": "", + "category": "future", + "size_step": "0.00010000", + "delivery_fee_rate": "", + "contract_size": "", + "contract_size_currency": "BTC", + "active": True, + "status": "online", + "groups": [ + 1, + 10, + 100, + 10000 + ], + "group_steps": [ + "0.01000000", + "0.10000000", + "1.00000000", + "100.00000000" + ], + "display_at": 1640944328422, + "is_display": True + } + ] + } + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "code": 0, + "message": "", + "data": { + "order_id": self.expected_exchange_order_id, + "created_at": 1589523803017, + "updated_at": 1589523803017, + "user_id": "51140", + "instrument_id": self.exchange_trading_pair, + "order_type": "limit", + "side": "buy", + "price": "50000.00000000", + "qty": "3.00000000", + "time_in_force": "gtc", + "avg_price": "0.00000000", + "filled_qty": "0.00000000", + "status": "open", + "is_liquidation": False, + "auto_price": "0.00000000", + "auto_price_type": "", + "taker_fee_rate": "0.00050000", + "maker_fee_rate": "0.00020000", + "label": get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ), + "stop_price": "0.00000000", + "reduce_only": False, + "post_only": False, + "reject_post_only": False, + "mmp": False, + "source": "api", + "hidden": False + } + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + mock_response = { + "code": 0, + "message": "", + "data": { + "user_id": 481554, + "created_at": 1649923879505, + "total_collateral": "3170125.05978108", + "total_margin_balance": "3170125.05978108", + "total_available": "3169721.64891398", + "total_initial_margin": "403.41086710", + "total_maintenance_margin": "303.16627631", + "total_initial_margin_ratio": "0.00012725", + "total_maintenance_margin_ratio": "0.00009563", + "total_liability": "0.00000000", + "total_unsettled_amount": "-0.84400340", + "total_future_value": "1.26000000", + "total_option_value": "0.00000000", + "spot_orders_hc_loss": "0.00000000", + "total_position_pnl": "1225.53245820", + "details": [ + { + "currency": "BTC", + "equity": "78.13359310", + "liability": "0.00000000", + "index_price": "41311.20615385", + "cash_balance": "78.13360190", + "margin_balance": "78.13359310", + "available_balance": "78.12382795", + "initial_margin": "0.00976516", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00733859", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.02966586", + "total_delta": "0.48532539", + "session_rpl": "0.00001552", + "session_upl": "-0.00003595", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_value": "1.23000000", + "future_pnl": "0.02966586", + "future_session_rpl": "0.00001552", + "future_session_upl": "-0.00003595", + "future_session_funding": "0.00001552", + "future_delta": "0.48532539", + "future_available_balance": "76.72788921", + "option_available_balance": "76.72788921", + "unsettled_amount": "-0.00002043", + "usdt_index_price": "41311.20615385" + }, + { + "currency": self.quote_asset, + "equity": "2000", + "liability": "0.00000000", + "index_price": "3119.01923077", + "cash_balance": "1.99960000", + "margin_balance": "1.99960000", + "available_balance": "2000", + "initial_margin": "0.00000000", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00000000", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.00000000", + "total_delta": "0.00000000", + "session_rpl": "0.00000000", + "session_upl": "0.00000000", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_value": "0.03000000", + "future_pnl": "0.00000000", + "future_session_rpl": "0.00000000", + "future_session_upl": "0.00000000", + "future_session_funding": "0.00000000", + "future_delta": "0.00000000", + "future_available_balance": "1.99960000", + "option_available_balance": "1.99960000", + "unsettled_amount": "0.00000000", + "usdt_index_price": "3119.01923077" + } + ], + "usdt_total_collateral": "3170125.05978108", + "usdt_total_margin_balance": "3170125.05978108", + "usdt_total_available": "3169721.64891398", + "usdt_total_initial_margin": "403.41086710", + "usdt_total_maintenance_margin": "303.16627631", + "usdt_total_initial_margin_ratio": "0.00012725", + "usdt_total_maintenance_margin_ratio": "0.00009563", + "usdt_total_liability": "0.00000000", + "usdt_total_unsettled_amount": "-0.84400340" + } + } + return mock_response + + @aioresponses() + def test_update_balances(self, mock_api): + response = self.balance_request_mock_response_for_base_and_quote + self._configure_balance_response(response=response, mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + pass + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + pass + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + self.exchange.set_position_mode(PositionMode.HEDGE) + self.assertTrue( + self.is_logged( + log_level="ERROR", + message="Position mode PositionMode.HEDGE is not supported. Mode not set." + ) + ) + + def funding_info_event_for_websocket_update(self): + return { + "channel": "ticker", + "timestamp": 1643099422727, + "module": "linear", + "data": { + "ask_sigma": "", + "best_ask": "36295.00000000", + "best_ask_qty": "1.00000000", + "best_bid": "36242.30000000", + "best_bid_qty": "7.01000000", + "bid_sigma": "", + "funding_rate": "0.00203429", + "funding_rate8h": "0.00009707", + "high24h": "37377.00000000", + "instrument_id": "BTC-USD-PERPETUAL", + "last_price": "36242.30000000", + "last_qty": "0.42000000", + "low24h": "33117.95000000", + "mark_price": "36261.48392714", + "max_buy": "36805.41000000", + "min_sell": "35717.56000000", + "open24h": "34998.65000000", + "open_interest": "87.69310000", + "price_change24h": "0.03553423", + "time": 1643099422727, + "volume24h": "4422.94140000" + } + } + + def is_cancel_request_executed_synchronously_by_server(self): + return False + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + self.exchange.set_position_mode(PositionMode.ONEWAY) + self.async_run_with_timeout(asyncio.sleep(0.5)) + self.assertTrue( + self.is_logged( + log_level="DEBUG", + message=f"Position mode switched to {PositionMode.ONEWAY}.", + ) + ) + + @property + def balance_event_websocket_update(self): + mock_response = { + "channel": "um_account", + "timestamp": 1632439007081, + "module": "um", + "data": { + "user_id": 481554, + "created_at": 1649923879505, + "total_collateral": "3170125.05978108", + "total_margin_balance": "3170125.05978108", + "total_available": "3169721.64891398", + "total_initial_margin": "403.41086710", + "total_maintenance_margin": "303.16627631", + "total_initial_margin_ratio": "0.00012725", + "total_maintenance_margin_ratio": "0.00009563", + "total_liability": "0.00000000", + "total_unsettled_amount": "-0.84400340", + "spot_orders_hc_loss": "0.00000000", + "total_position_pnl": "1225.53245820", + "details": [ + { + "currency": "BTC", + "equity": "78.13359310", + "liability": "0.00000000", + "index_price": "41311.20615385", + "cash_balance": "78.13360190", + "margin_balance": "78.13359310", + "available_balance": "78.12382795", + "initial_margin": "0.00976516", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00733859", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.02966586", + "total_delta": "0.48532539", + "session_rpl": "0.00001552", + "session_upl": "-0.00003595", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_pnl": "0.02966586", + "future_session_rpl": "0.00001552", + "future_session_upl": "-0.00003595", + "future_session_funding": "0.00001552", + "future_delta": "0.48532539", + "future_available_balance": "76.72788921", + "option_available_balance": "76.72788921", + "unsettled_amount": "-0.00002043", + "usdt_index_price": "41311.20615385" + }, + { + "currency": "USD", + "equity": "15", + "liability": "0.00000000", + "index_price": "3119.01923077", + "cash_balance": "1.99960000", + "margin_balance": "1.99960000", + "available_balance": "10", + "initial_margin": "0.00000000", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00000000", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.00000000", + "total_delta": "0.00000000", + "session_rpl": "0.00000000", + "session_upl": "0.00000000", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_pnl": "0.00000000", + "future_session_rpl": "0.00000000", + "future_session_upl": "0.00000000", + "future_session_funding": "0.00000000", + "future_delta": "0.00000000", + "future_available_balance": "1.99960000", + "option_available_balance": "1.99960000", + "unsettled_amount": "0.00000000", + "usdt_index_price": "3119.01923077" + } + ], + "usdt_total_collateral": "3170125.05978108", + "usdt_total_margin_balance": "3170125.05978108", + "usdt_total_available": "3169721.64891398", + "usdt_total_initial_margin": "403.41086710", + "usdt_total_maintenance_margin": "303.16627631", + "usdt_total_initial_margin_ratio": "0.00012725", + "usdt_total_maintenance_margin_ratio": "0.00009563", + "usdt_total_liability": "0.00000000", + "usdt_total_unsettled_amount": "-0.84400340" + } + } + return mock_response + + @property + def position_event_websocket_update(self): + mock_response = { + "channel": "position", + "timestamp": 1643101230232, + "module": "linear", + "data": [ + { + "avg_price": "42474.49668874", + "category": "future", + "expiration_at": 4102444800000, + "index_price": "36076.66600000", + "initial_margin": "21.81149685", + "instrument_id": "BTC-USD-PERPETUAL", + "leverage": "50.00000000", + "maintenance_margin": "16.36076260", + "mark_price": "36097.57784846", + "position_pnl": "192.58294898", + "position_session_rpl": "-0.16699671", + "position_session_upl": "-1.28505101", + "qty": "1", + "qty_base": "1", + "roi": "8.82942378", + "session_avg_price": "36055.02649047", + "session_funding": "-0.16699671", + "liq_price": "3587263.29572346", + } + ] + } + + return mock_response + + @property + def position_event_websocket_update_zero(self): + mock_response = { + "channel": "position", + "timestamp": 1643101230232, + "module": "linear", + "data": [ + { + "avg_price": "42474.49668874", + "category": "future", + "expiration_at": 4102444800000, + "index_price": "36076.66600000", + "initial_margin": "21.81149685", + "instrument_id": "BTC-USD-PERPETUAL", + "leverage": "50.00000000", + "maintenance_margin": "16.36076260", + "mark_price": "36097.57784846", + "position_pnl": "192.58294898", + "position_session_rpl": "-0.16699671", + "position_session_upl": "-1.28505101", + "qty": "0", + "qty_base": "1", + "roi": "8.82942378", + "session_avg_price": "36055.02649047", + "session_funding": "-0.16699671", + "liq_price": "3587263.29572346", + } + ] + } + + return mock_response + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def funding_payment_mock_response(self): + raise NotImplementedError + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError # test is overwritten + + @property + def target_funding_info_next_funding_utc_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_info_next_funding_utc_str_ws_updated(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp_ws_updated) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_payment_timestamp_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_payment_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def funding_info_mock_response(self): + mock_response = self.latest_prices_request_mock_response + funding_info = mock_response["data"] + funding_info["mark_price"] = self.target_funding_info_mark_price + funding_info["index_price"] = self.target_funding_info_index_price + funding_info["funding_rate"] = self.target_funding_info_rate + return mock_response + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + rule = self.trading_rules_request_mock_response["data"][0] + collateral_token = rule["quote_currency"] + + return TradingRule(self.trading_pair, + min_order_size=Decimal(rule.get("min_size")), + min_price_increment=Decimal(rule.get("price_step")), + min_base_amount_increment=Decimal(rule.get("size_step")), + buy_order_collateral_token=collateral_token, + sell_order_collateral_token=collateral_token, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "335fd977-e5a5-4781-b6d0-c772d5bfb95b" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return False + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.1"))], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6" + + @property + def latest_trade_hist_timestamp(self) -> int: + return 1234 + + def async_run_with_timeout(self, coroutine, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}-{quote_token}-PERPETUAL" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = BitComPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + trading_pairs=[self.trading_pair], + ) + # exchange._last_trade_history_timestamp = self.latest_trade_hist_timestamp + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn("X-Bit-Access-Key", request_headers) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(self.exchange_trading_pair, request_data["instrument_id"]) + self.assertEqual(order.amount, abs(Decimal(str(request_data["qty"])))) + self.assertEqual(order.client_order_id, request_data["label"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertIsNone(request_params) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = request_call.kwargs["data"] + self.assertIsNone(request_data) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["instrument_id"]) + self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.public_rest_url( + CONSTANTS.CANCEL_ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.CANCEL_ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + mock_api.post(regex_url, status=400, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ORDER_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ACCOUNT_TRADE_LIST_URL, + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + CONSTANTS.ACCOUNT_TRADE_LIST_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_failed_set_leverage( + self, + leverage: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + endpoint = CONSTANTS.SET_LEVERAGE_URL + url = web_utils.public_rest_url( + endpoint + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + err_msg = "Unable to set leverage" + mock_response = { + "code": 0, + "message": "", + "data": { + "pair": "BTC-USD", + "leverage_ratio": "60.00000000" + } + } + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + return url, err_msg + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + endpoint = CONSTANTS.SET_LEVERAGE_URL + url = web_utils.public_rest_url( + endpoint + ) + regex_url = re.compile(f"^{url}") + + mock_response = { + "code": 0, + "message": "", + "data": { + "pair": "BTC-USD", + "leverage_ratio": str(leverage) + } + } + + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "channel": "order", + "timestamp": 1643101425658, + "module": "linear", + "data": [ + { + "auto_price": "0.00000000", + "auto_price_type": "", + "avg_price": "0.00000000", + "cash_flow": "0.00000000", + "created_at": 1643101425539, + "fee": "0.00000000", + "filled_qty": "0.00000000", + "hidden": False, + "initial_margin": "", + "instrument_id": self.exchange_trading_pair, + "is_liquidation": False, + "is_um": True, + "label": order.client_order_id or "", + "maker_fee_rate": "0.00010000", + "mmp": False, + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_type": "limit", + "pnl": "0.00000000", + "post_only": False, + "price": order.price, + "qty": float(order.amount), + "reduce_only": False, + "reject_post_only": False, + "reorder_index": 0, + "side": "buy", + "source": "web", + "status": "open", + "stop_order_id": "", + "stop_price": "0.00000000", + "taker_fee_rate": "0.00010000", + "time_in_force": "gtc", + "updated_at": 1643101425539, + "user_id": "606122" + } + ] + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "channel": "order", + "timestamp": 1643101425658, + "module": "linear", + "data": [ + { + "auto_price": "0.00000000", + "auto_price_type": "", + "avg_price": "0.00000000", + "cash_flow": "0.00000000", + "created_at": 1643101425539, + "fee": "0.00000000", + "filled_qty": "0.00000000", + "hidden": False, + "initial_margin": "", + "instrument_id": self.exchange_trading_pair, + "is_liquidation": False, + "is_um": True, + "label": order.client_order_id or "", + "maker_fee_rate": "0.00010000", + "mmp": False, + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_type": "limit", + "pnl": "0.00000000", + "post_only": False, + "price": order.price, + "qty": float(order.amount), + "reduce_only": False, + "reject_post_only": False, + "reorder_index": 0, + "side": "buy", + "source": "web", + "status": "cancelled", + "stop_order_id": "", + "stop_price": "0.00000000", + "taker_fee_rate": "0.00010000", + "time_in_force": "gtc", + "updated_at": 1643101425539, + "user_id": "606122" + } + ] + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + + return { + "channel": "order", + "timestamp": 1643101425658, + "module": "linear", + "data": [ + { + "auto_price": "0.00000000", + "auto_price_type": "", + "avg_price": "0.00000000", + "cash_flow": "0.00000000", + "created_at": 1643101425539, + "fee": "0.00000000", + "filled_qty": "0.00000000", + "hidden": False, + "initial_margin": "", + "instrument_id": self.exchange_trading_pair, + "is_liquidation": False, + "is_um": True, + "label": order.client_order_id or "", + "maker_fee_rate": "0.00010000", + "mmp": False, + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_type": "limit", + "pnl": "0.00000000", + "post_only": False, + "price": order.price, + "qty": float(order.amount), + "reduce_only": False, + "reject_post_only": False, + "reorder_index": 0, + "side": "buy", + "source": "web", + "status": "filled", + "stop_order_id": "", + "stop_price": "0.00000000", + "taker_fee_rate": "0.00010000", + "time_in_force": "gtc", + "updated_at": 1643101425539, + "user_id": "606122" + } + ] + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + return { + "channel": "user_trade", + "timestamp": 1643101722258, + "module": "linear", + "data": [ + { + "created_at": 1643101722020, + "fee": Decimal(self.expected_fill_fee.flat_fees[0].amount), + "fee_rate": "0.00010000", + "index_price": "36214.05400000", + "instrument_id": self.exchange_trading_pair, + "is_block_trade": False, + "is_taker": True, + "label": order.client_order_id or "", + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_type": "limit", + "price": str(order.price), + "qty": Decimal(order.amount), + "side": "buy", + "sigma": "0.00000000", + "trade_id": self.expected_fill_trade_id, + "underlying_price": "", + "usd_price": "" + } + ] + } + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + pass + + def test_create_order_with_invalid_position_action_raises_value_error(self): + self._simulate_trading_rules_initialized() + + with self.assertRaises(ValueError) as exception_context: + asyncio.get_event_loop().run_until_complete( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="C1", + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("46000"), + position_action=PositionAction.NIL, + ), + ) + + self.assertEqual( + f"Invalid position action {PositionAction.NIL}. Must be one of {[PositionAction.OPEN, PositionAction.CLOSE]}", + str(exception_context.exception) + ) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["11"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + event = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertTrue(order.is_open) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = BitComPerpetualDerivative( + client_config_map=client_config_map, + bit_com_perpetual_api_key=self.api_key, + bit_com_perpetual_api_secret=self.api_secret, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.quote_asset)) + + def test_user_stream_position_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = BitComPerpetualDerivative( + client_config_map=client_config_map, + bit_com_perpetual_api_key=self.api_key, + bit_com_perpetual_api_secret=self.api_secret, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + position_event = self.position_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + self._simulate_trading_rules_initialized() + self.exchange.account_positions[self.trading_pair] = Position( + + trading_pair=self.trading_pair, + position_side=PositionSide.SHORT, + unrealized_pnl=Decimal('1'), + entry_price=Decimal('1'), + amount=Decimal('1'), + leverage=Decimal('1'), + ) + amount_precision = Decimal(self.exchange.trading_rules[self.trading_pair].min_base_amount_increment) + try: + asyncio.get_event_loop().run_until_complete(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, Decimal(1e6) * amount_precision) + + def test_user_stream_remove_position_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = BitComPerpetualDerivative( + client_config_map=client_config_map, + bit_com_perpetual_api_key=self.api_key, + bit_com_perpetual_api_secret=self.api_secret, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + position_event = self.position_event_websocket_update_zero + self._simulate_trading_rules_initialized() + self.exchange.account_positions[self.trading_pair] = Position( + trading_pair=self.trading_pair, + position_side=PositionSide.SHORT, + unrealized_pnl=Decimal('1'), + entry_price=Decimal('1'), + amount=Decimal('1'), + leverage=Decimal('1'), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + asyncio.get_event_loop().run_until_complete(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + self.assertEqual(len(self.exchange.account_positions), 0) + + def test_supported_position_modes(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + linear_connector = BitComPerpetualDerivative( + client_config_map=client_config_map, + bit_com_perpetual_api_key=self.api_key, + bit_com_perpetual_api_secret=self.api_secret, + trading_pairs=[self.trading_pair], + ) + + expected_result = [PositionMode.ONEWAY] + self.assertEqual(expected_result, linear_connector.supported_position_modes()) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + self.assertEqual(self.quote_asset, buy_collateral_token) + self.assertEqual(self.quote_asset, sell_collateral_token) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp + CONSTANTS.FUNDING_RATE_INTERNAL_MIL_SECOND, + funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_first_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response["data"] + duplicate = deepcopy(results[0]) + duplicate["instrument_id"] = f"{self.exchange_trading_pair}_12345" + duplicate["min_size"] = str(float(duplicate["min_size"]) + 1) + results.append(duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_second_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response["data"] + duplicate = deepcopy(results[0]) + duplicate["instrument_id"] = f"{self.exchange_trading_pair}_12345" + duplicate["min_size"] = str(float(duplicate["min_size"]) + 1) + results.insert(0, duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_cannot_resolve(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response["data"] + first_duplicate = deepcopy(results[0]) + first_duplicate["instrument_id"] = f"{self.exchange_trading_pair}_12345" + first_duplicate["min_size"] = ( + str(float(first_duplicate["min_size"]) + 1) + ) + second_duplicate = deepcopy(results[0]) + second_duplicate["instrument_id"] = f"{self.exchange_trading_pair}_67890" + second_duplicate["min_size"] = ( + str(float(second_duplicate["min_size"]) + 2) + ) + results.pop(0) + results.append(first_duplicate) + results.append(second_duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange.trading_rules)) + self.assertNotIn(self.trading_pair, self.exchange.trading_rules) + self.assertTrue( + self.is_logged( + log_level="ERROR", + message=( + f"Could not resolve the exchange symbols" + f" {self.exchange_trading_pair}_67890" + f" and {self.exchange_trading_pair}_12345" + ), + ) + ) + + @aioresponses() + def test_cancel_lost_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + url = self.configure_erroneous_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order = self.exchange.in_flight_orders["OID1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + self.assertEqual(leverage, fill_event.leverage) + self.assertEqual(PositionAction.OPEN.value, fill_event.position) + + buy_event = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "message": "", + "data": { + "num_cancelled": 1 + } + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "message": "", + "data": [{ + "order_id": order.exchange_order_id, + "created_at": 1589202185000, + "updated_at": 1589460149000, + "user_id": "51140", + "instrument_id": self.exchange_trading_pair, + "order_type": "limit", + "side": "buy", + "price": str(order.price), + "qty": float(order.amount), + "time_in_force": "gtc", + "avg_price": str(order.price), + "filled_qty": float(order.amount), + "status": "filled", + "fee": "0.00000000", + "is_liquidation": False, + "auto_price": "0.00000000", + "auto_price_type": "", + "pnl": "0.00000000", + "cash_flow": "0.00000000", + "initial_margin": "", + "taker_fee_rate": "0.00050000", + "maker_fee_rate": "0.00020000", + "label": order.client_order_id or "2b1d811c-8ff0-4ef0-92ed-b4ed5fd6de34", + "stop_price": "0.00000000", + "reduce_only": False, + "post_only": False, + "reject_post_only": False, + "mmp": False, + "reorder_index": 1, + "source": "api", + "hidden": False, + "is_um": True + }] + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"][0]["status"] = "cancelled" + resp["data"][0]["filled_qty"] = "0" + resp["data"][0]["avg_price"] = "0" + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"][0]["status"] = "open" + resp["data"][0]["avg_price"] = "0" + return resp + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"][0]["status"] = "open" + resp["data"][0]["avg_price"] = str(order.price) + return resp + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + pass + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"][0]["status"] = "open" + resp["data"][0]["avg_price"] = str(order.price) + resp["data"][0]["filled_qty"] = float(order.amount) / 2 + return resp + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + return { + "code": 0, + "message": "", + "data": [{ + "trade_id": self.expected_fill_trade_id, + "order_id": order.exchange_order_id, + "instrument_id": self.exchange_trading_pair, + "qty": str(Decimal(order.amount)), + "price": str(order.price), + "sigma": "0.00000000", + "underlying_price": "", + "index_price": "50012.81000000", + "usd_price": "", + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "fee_rate": "0.00050000", + "side": "buy", + "created_at": 1589521371000, + "is_taker": True, + "order_type": "limit", + "label": order.client_order_id, + }] + } + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..15413b0 --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_user_stream_data_source.py @@ -0,0 +1,395 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bit_com_perpetual import bit_com_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_auth import BitComPerpetualAuth +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_derivative import BitComPerpetualDerivative +from hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_user_stream_data_source import ( + BitComPerpetualUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestBitComPerpetualAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_secret_key = "someSecretKey" + cls.user_id = "someUserId" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = BitComPerpetualAuth( + api_key=self.api_key, + api_secret=self.api_secret_key) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BitComPerpetualDerivative( + client_config_map=client_config_map, + bit_com_perpetual_api_key="", + bit_com_perpetual_api_secret="", + trading_pairs=[]) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = BitComPerpetualUserStreamDataSource( + self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 2): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + async def get_token(self): + return "be4ffcc9-2b2b-4c3e-9d47-68bf062cf651" + + @aioresponses() + def test_get_new_token_successful(self, mock_api): + endpoint = CONSTANTS.USERSTREAM_AUTH_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + resp = { + "code": 0, + "message": "", + "data": { + "token": "be4ffcc9-2b2b-4c3e-9d47-68bf062cf651" + } + } + mock_api.get(regex_url, body=json.dumps(resp)) + + date = self.async_run_with_timeout( + self.data_source.get_token() + ) + + self.assertEqual("be4ffcc9-2b2b-4c3e-9d47-68bf062cf651", date) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.bit_com_perpetual.bit_com_perpetual_user_stream_data_source.BitComPerpetualUserStreamDataSource" + ".get_token") + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, token_mock, ws_connect_mock): + token_mock.return_value = "be4ffcc9-2b2b-4c3e-9d47-68bf062cf651" + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "channel": "order", + "timestamp": 1643101425658, + "module": "linear", + "data": [ + { + "auto_price": "0.00000000", + "auto_price_type": "", + "avg_price": "0.00000000", + "cash_flow": "0.00000000", + "created_at": 1643101425539, + "fee": "0.00000000", + "filled_qty": "0.00000000", + "hidden": False, + "initial_margin": "", + "instrument_id": "BTC-USD-PERPETUAL", + "is_liquidation": False, + "is_um": True, + "label": "", + "maker_fee_rate": "0.00010000", + "mmp": False, + "order_id": "1034087", + "order_type": "limit", + "pnl": "0.00000000", + "post_only": False, + "price": "36088.95000000", + "qty": "0.02000000", + "reduce_only": False, + "reject_post_only": False, + "reorder_index": 0, + "side": "buy", + "source": "web", + "status": "pending", + "stop_order_id": "", + "stop_price": "0.00000000", + "taker_fee_rate": "0.00010000", + "time_in_force": "gtc", + "updated_at": 1643101425539, + "user_id": "606122" + } + ] + } + result_subscribe_trades = { + "channel": "user_trade", + "timestamp": 1643101722258, + "module": "linear", + "data": [ + { + "created_at": 1643101722020, + "fee": "0.00000000", + "fee_rate": "0.00010000", + "index_price": "36214.05400000", + "instrument_id": "BTC-USD-PERPETUAL", + "is_block_trade": False, + "is_taker": True, + "label": "", + "order_id": "1034149", + "order_type": "limit", + "price": "36219.85000000", + "qty": "0.00100000", + "side": "buy", + "sigma": "0.00000000", + "trade_id": "1005590992", + "underlying_price": "", + "usd_price": "" + } + ] + } + result_subscribe_positions = { + "channel": "position", + "timestamp": 1643101230232, + "module": "linear", + "data": [ + { + "avg_price": "42474.49668874", + "category": "future", + "expiration_at": 4102444800000, + "index_price": "36076.66600000", + "initial_margin": "21.81149685", + "instrument_id": "BTC-USD-PERPETUAL", + "leverage": "50.00000000", + "maintenance_margin": "16.36076260", + "mark_price": "36097.57784846", + "position_pnl": "192.58294898", + "position_session_rpl": "-0.16699671", + "position_session_upl": "-1.28505101", + "qty": "-0.03020000", + "qty_base": "-0.03020000", + "roi": "8.82942378", + "session_avg_price": "36055.02649047", + "session_funding": "-0.16699671", + "liq_price": "3587263.29572346", + } + ] + } + + result_subscribe_balances = { + "channel": "um_account", + "timestamp": 1632439007081, + "module": "um", + "data": { + "user_id": 481554, + "created_at": 1649923879505, + "total_collateral": "3170125.05978108", + "total_margin_balance": "3170125.05978108", + "total_available": "3169721.64891398", + "total_initial_margin": "403.41086710", + "total_maintenance_margin": "303.16627631", + "total_initial_margin_ratio": "0.00012725", + "total_maintenance_margin_ratio": "0.00009563", + "total_liability": "0.00000000", + "total_unsettled_amount": "-0.84400340", + "spot_orders_hc_loss": "0.00000000", + "total_position_pnl": "1225.53245820", + "details": [ + { + "currency": "BTC", + "equity": "78.13359310", + "liability": "0.00000000", + "index_price": "41311.20615385", + "cash_balance": "78.13360190", + "margin_balance": "78.13359310", + "available_balance": "78.12382795", + "initial_margin": "0.00976516", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00733859", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.02966586", + "total_delta": "0.48532539", + "session_rpl": "0.00001552", + "session_upl": "-0.00003595", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_pnl": "0.02966586", + "future_session_rpl": "0.00001552", + "future_session_upl": "-0.00003595", + "future_session_funding": "0.00001552", + "future_delta": "0.48532539", + "future_available_balance": "76.72788921", + "option_available_balance": "76.72788921", + "unsettled_amount": "-0.00002043", + "usdt_index_price": "41311.20615385" + }, + { + "currency": "ETH", + "equity": "1.9996000", + "liability": "0.00000000", + "index_price": "3119.01923077", + "cash_balance": "1.99960000", + "margin_balance": "1.99960000", + "available_balance": "1.99960000", + "initial_margin": "0.00000000", + "spot_margin": "0.00000000", + "maintenance_margin": "0.00000000", + "potential_liability": "0.00000000", + "interest": "0.00000000", + "interest_rate": "0.07000000", + "pnl": "0.00000000", + "total_delta": "0.00000000", + "session_rpl": "0.00000000", + "session_upl": "0.00000000", + "option_value": "0.00000000", + "option_pnl": "0.00000000", + "option_session_rpl": "0.00000000", + "option_session_upl": "0.00000000", + "option_delta": "0.00000000", + "option_gamma": "0.00000000", + "option_vega": "0.00000000", + "option_theta": "0.00000000", + "future_pnl": "0.00000000", + "future_session_rpl": "0.00000000", + "future_session_upl": "0.00000000", + "future_session_funding": "0.00000000", + "future_delta": "0.00000000", + "future_available_balance": "1.99960000", + "option_available_balance": "1.99960000", + "unsettled_amount": "0.00000000", + "usdt_index_price": "3119.01923077" + } + ], + "usdt_total_collateral": "3170125.05978108", + "usdt_total_margin_balance": "3170125.05978108", + "usdt_total_available": "3169721.64891398", + "usdt_total_initial_margin": "403.41086710", + "usdt_total_maintenance_margin": "303.16627631", + "usdt_total_initial_margin_ratio": "0.00012725", + "usdt_total_maintenance_margin_ratio": "0.00009563", + "usdt_total_liability": "0.00000000", + "usdt_total_unsettled_amount": "-0.84400340" + } + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_positions)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_balances)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_orders_subscription = { + "type": "subscribe", + "instruments": [self.ex_trading_pair], + "channels": [CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + CONSTANTS.USER_TRADES_ENDPOINT_NAME, + CONSTANTS.USER_BALANCES_ENDPOINT_NAME, + ], + "pairs": [self.trading_pair], + "categories": ["future"], + "interval": "raw", + "token": "be4ffcc9-2b2b-4c3e-9d47-68bf062cf651", + } + self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private order changes channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + # @unittest.skip("Test with error") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, sleep_mock, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_utils.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_utils.py new file mode 100644 index 0000000..d27f230 --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_utils.py @@ -0,0 +1,5 @@ +from unittest import TestCase + + +class BitComPerpetualUtilsTests(TestCase): + pass diff --git a/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_web_utils.py b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_web_utils.py new file mode 100644 index 0000000..e91af3b --- /dev/null +++ b/test/hummingbot/connector/derivative/bit_com_perpetual/test_bit_com_perpetual_web_utils.py @@ -0,0 +1,22 @@ +import unittest + +from hummingbot.connector.derivative.bit_com_perpetual import ( + bit_com_perpetual_constants as CONSTANTS, + bit_com_perpetual_web_utils as web_utils, +) +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitComPerpetualWebUtilsTest(unittest.TestCase): + + def test_public_rest_url(self): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL) + self.assertEqual("https://api.bit.com/linear/v1/orderbooks", url) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory() + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(2, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/__init__.py b/test/hummingbot/connector/derivative/bitget_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_auth.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_auth.py new file mode 100644 index 0000000..d82b264 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_auth.py @@ -0,0 +1,85 @@ +import asyncio +import base64 +import hashlib +import hmac +import time +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_auth import BitgetPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class BitgetPerpetualAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + self.passphrase = "testPassphrase" + self._time_synchronizer_mock = MagicMock() + self._time_synchronizer_mock.time.return_value = 1640001112.223 + + self.auth = BitgetPerpetualAuth( + api_key=self.api_key, + secret_key=self.secret_key, + passphrase=self.passphrase, + time_provider=self._time_synchronizer_mock) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _get_timestamp(self): + return str(int(time.time())) + + def test_add_auth_to_rest_request(self): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url", + throttler_limit_id="/api/endpoint", + params=params, + is_auth_required=True, + headers={}, + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + raw_signature = (request.headers.get("ACCESS-TIMESTAMP") + + request.method.value + + request.throttler_limit_id + "?one=1") + expected_signature = base64.b64encode( + hmac.new(self.secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256).digest() + ).decode().strip() + + params = request.params + + self.assertEqual(1, len(params)) + self.assertEqual("1", params.get("one")) + self.assertEqual( + self._time_synchronizer_mock.time(), + int(request.headers.get("ACCESS-TIMESTAMP")) * 1e-3) + self.assertEqual(self.api_key, request.headers.get("ACCESS-KEY")) + self.assertEqual(expected_signature, request.headers.get("ACCESS-SIGN")) + + def test_ws_auth_payload(self): + payload = self.auth.get_ws_auth_payload() + + raw_signature = str(int(self._time_synchronizer_mock.time())) + "GET/user/verify" + expected_signature = base64.b64encode( + hmac.new(self.secret_key.encode("utf-8"), raw_signature.encode("utf-8"), hashlib.sha256).digest() + ).decode().strip() + + self.assertEqual(1, len(payload)) + self.assertEqual(self.api_key, payload[0]["apiKey"]) + self.assertEqual(str(int(self._time_synchronizer_mock.time())), payload[0]["timestamp"]) + self.assertEqual(expected_signature, payload[0]["sign"]) + + def test_no_auth_added_to_ws_request(self): + payload = {"one": "1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_derivative.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_derivative.py new file mode 100644 index 0000000..7fbaa71 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_derivative.py @@ -0,0 +1,1726 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict + +import hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import BitgetPerpetualDerivative +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase, TradeFeeSchema + + +class BitgetPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.passphrase = "somePassphrase" + cls.quote_asset = "USDT" # linear + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def latest_prices_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SERVER_TIME_PATH_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return regex_url + + @property + def order_creation_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def balance_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.GET_WALLET_BALANCE_PATH_URL) + return url + + @property + def funding_info_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_FUNDING_FEES_PATH_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def all_symbols_request_mock_response(self): + mock_response = { + "code": "00000", + "data": [{ + "baseCoin": self.base_asset, + "buyLimitPriceRatio": "0.01", + "feeRateUpRatio": "0.005", + "makerFeeRate": "0.0002", + "minTradeNum": "0.001", + "openCostUpRatio": "0.01", + "priceEndStep": "5", + "pricePlace": "1", + "quoteCoin": self.quote_asset, + "sellLimitPriceRatio": "0.01", + "supportMarginCoins": [ + self.quote_asset + ], + "symbol": self.exchange_trading_pair, + "takerFeeRate": "0.0006", + "volumePlace": "3", + "sizeMultiplier": "5" + }], + "msg": "success", + "requestTime": 1627114525850 + } + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = { + "code": "00000", + "msg": "success", + "data": { + "symbol": self.exchange_trading_pair, + "last": "23990.5", + "bestAsk": "23991", + "bestBid": "23989.5", + "high24h": "24131.5", + "low24h": "23660.5", + "timestamp": "1660705778888", + "priceChangePercent": "0.00442", + "baseVolume": "156243.358", + "quoteVolume": "3735854069.908", + "usdtVolume": "3735854069.908", + "openUtc": "23841.5", + "chgUtc": "0.00625" + } + } + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + mock_response = self.all_symbols_request_mock_response + return None, mock_response + + @property + def network_status_request_successful_mock_response(self): + mock_response = {"flag": True, "requestTime": 1662584739780} + return mock_response + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = { + "code": "00000", + "data": [{ + "baseCoin": self.base_asset, + "quoteCoin": self.quote_asset, + "symbol": self.exchange_trading_pair, + }], + "msg": "success", + "requestTime": 1627114525850 + } + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "code": "00000", + "data": { + "orderId": "1627293504612", + "clientOid": "BITGET#1627293504612" + }, + "msg": "success", + "requestTime": 1627293504612 + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + mock_response = { + "code": "00000", + "data": [ + { + "marginCoin": self.quote_asset, + "locked": "0", + "available": "2000", + "crossMaxAvailable": "2000", + "fixedMaxAvailable": "2000", + "maxTransferOut": "10572.92904289", + "equity": "2000", + "usdtEquity": "10582.902657719473", + "btcEquity": "0.204885807029" + }, + { + "marginCoin": self.base_asset, + "locked": "5", + "available": "10", + "crossMaxAvailable": "10", + "fixedMaxAvailable": "10", + "maxTransferOut": "10572.92904289", + "equity": "15", + "usdtEquity": "10582.902657719473", + "btcEquity": "0.204885807029" + } + ], + "msg": "success", + "requestTime": 1630901215622 + } + return mock_response + + @property + def balance_request_mock_response_only_base(self): + return { + "code": "00000", + "data": [ + { + "marginCoin": self.base_asset, + "locked": "5", + "available": "10", + "crossMaxAvailable": "10", + "fixedMaxAvailable": "10", + "maxTransferOut": "10572.92904289", + "equity": "15", + "usdtEquity": "10582.902657719473", + "btcEquity": "0.204885807029" + } + ], + "msg": "success", + "requestTime": 1630901215622 + } + + @property + def balance_event_websocket_update(self): + mock_response = { + "arg": { + "channel": CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME, + "instType": "umcbl", + "instId": "default" + }, + "data": [ + { + "marginCoin": self.base_asset, + "available": "100", + "locked": "5", + "maxOpenPosAvailable": "10", + "equity": "15", + } + ] + } + return mock_response + + @property + def expected_latest_price(self): + return 23990.5 + + @property + def empty_funding_payment_mock_response(self): + return { + "code": "00000", + "msg": "success", + "data": { + "result": [], + "endId": "885353495773458432", + "nextFlag": False, + "preFlag": False + } + } + + @property + def funding_payment_mock_response(self): + return { + "code": "00000", + "msg": "success", + "data": { + "result": [ + { + "id": "892962903462432768", + "symbol": self.exchange_symbol_for_tokens(base_token=self.base_asset, + quote_token=self.quote_asset), + "marginCoin": self.quote_asset, + "amount": str(self.target_funding_payment_payment_amount), + "fee": "0", + "feeByCoupon": "", + "feeCoin": self.quote_asset, + "business": "contract_settle_fee", + "cTime": "1657110053000" + } + ], + "endId": "885353495773458432", + "nextFlag": False, + "preFlag": False + } + } + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + return list(CONSTANTS.POSITION_MODE_MAP.keys()) + + @property + def target_funding_info_next_funding_utc_str(self): + return self.target_funding_info_next_funding_utc_timestamp * 1e3 + + @property + def target_funding_info_next_funding_utc_str_ws_updated(self): + return self.target_funding_info_next_funding_utc_timestamp_ws_updated * 1e3 + + @property + def target_funding_payment_timestamp_str(self): + return self.target_funding_payment_timestamp * 1e3 + + @property + def funding_info_mock_response(self): + funding_info = {"data": {}} + funding_info["data"]["amount"] = self.target_funding_info_index_price + funding_info["data"]["markPrice"] = self.target_funding_info_mark_price + funding_info["data"]["fundingTime"] = self.target_funding_info_next_funding_utc_str + funding_info["data"]["fundingRate"] = self.target_funding_info_rate + return funding_info + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + @property + def expected_trading_rule(self): + trading_rules_resp = self.trading_rules_request_mock_response["data"][0] + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(trading_rules_resp["minTradeNum"])), + min_price_increment=(Decimal(str(trading_rules_resp["priceEndStep"])) + * Decimal(f"1e-{trading_rules_resp['pricePlace']}")), + min_base_amount_increment=Decimal(str(trading_rules_resp["sizeMultiplier"])), + buy_order_collateral_token=self.quote_asset, + sell_order_collateral_token=self.quote_asset, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "1627293504612" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.1"))], + ) + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + return self.expected_fill_fee + + @property + def expected_fill_trade_id(self) -> str: + return "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6" + + @property + def latest_trade_hist_timestamp(self) -> int: + return 1234 + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}_UMCBL" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = BitgetPerpetualDerivative( + client_config_map=client_config_map, + bitget_perpetual_api_key=self.api_key, + bitget_perpetual_secret_key=self.api_secret, + bitget_perpetual_passphrase=self.passphrase, + trading_pairs=[self.trading_pair], + ) + exchange._last_trade_history_timestamp = self.latest_trade_hist_timestamp + funding_info = FundingInfo( + trading_pair=self.trading_pair, + index_price=Decimal(-1), + mark_price=Decimal(-1), + next_funding_utc_timestamp=1640001119, + rate=self.target_funding_payment_funding_rate + ) + exchange._perpetual_trading._funding_info[self.trading_pair] = funding_info + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_data = request_call.kwargs["headers"] + + self.assertIn("ACCESS-TIMESTAMP", request_data) + self.assertIn("ACCESS-KEY", request_data) + self.assertEqual(self.api_key, request_data["ACCESS-KEY"]) + self.assertIn("ACCESS-SIGN", request_data) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + if order.position in [PositionAction.OPEN, PositionAction.NIL]: + contract = "long" if order.trade_type == TradeType.BUY else "short" + else: + contract = "short" if order.trade_type == TradeType.BUY else "long" + pos_action = order.position.name.lower() if order.position.name.lower() in ["open", "close"] else "open" + self.assertEqual(f"{pos_action}_{contract}", request_data["side"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.amount, Decimal(request_data["size"])) + self.assertEqual(CONSTANTS.DEFAULT_TIME_IN_FORCE, request_data["timeInForceValue"]) + self.assertEqual(order.client_order_id, request_data["clientOid"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.exchange_order_id, request_data["orderId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = { + "code": "43026", + "msg": "Could not find order", + } + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_cancelled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + return self.configure_canceled_order_status_response( + order=order, + mock_api=mock_api, + callback=callback + ) + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.USER_TRADE_RECORDS_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.USER_TRADE_RECORDS_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SET_POSITION_MODE_URL) + response = { + "code": "00000", + "data": { + "symbol": self.exchange_trading_pair, + "marginCoin": "USDT", + "longLeverage": 25, + "shortLeverage": 20, + "marginMode": "crossed" + }, + "msg": "success", + "requestTime": 1627293445916 + } + mock_api.post(url, body=json.dumps(response), callback=callback) + + return url + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SET_POSITION_MODE_URL) + regex_url = re.compile(f"^{url}") + + error_code = CONSTANTS.RET_CODE_PARAMS_ERROR + error_msg = "Some problem" + mock_response = { + "code": error_code, + "data": { + "symbol": self.exchange_trading_pair, + "marginCoin": "USDT", + "longLeverage": 25, + "shortLeverage": 20, + "marginMode": "crossed" + }, + "msg": error_msg, + "requestTime": 1627293445916 + } + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"ret_code <{error_code}> - {error_msg}" + + def configure_failed_set_leverage( + self, + leverage: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL + ) + regex_url = re.compile(f"^{url}") + + err_code = CONSTANTS.RET_CODE_PARAMS_ERROR + err_msg = "Some problem" + mock_response = { + "code": err_code, + "data": { + "symbol": self.exchange_trading_pair, + "marginCoin": "USDT", + "longLeverage": 25, + "shortLeverage": 20, + "marginMode": "crossed" + }, + "msg": err_msg, + "requestTime": 1627293049406 + } + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"ret_code <{err_code}> - {err_msg}" + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL + ) + regex_url = re.compile(f"^{url}") + + mock_response = { + "code": "00000", + "data": { + "symbol": self.exchange_trading_pair, + "marginCoin": "USDT", + "longLeverage": 25, + "shortLeverage": 20, + "marginMode": "crossed" + }, + "msg": "success", + "requestTime": 1627293049406 + } + + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME, + "instType": "umcbl", + "instId": "default" + }, + "data": [{ + "instId": "default", + "ordId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "clOrdId": order.client_order_id or "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "100", + "ordType": order.order_type.name.capitalize(), + "force": "post_only", + "side": order.trade_type.name.capitalize(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": self.base_asset, + "fillPx": "0", + "tradeId": "0", + "fillSz": "0", + "fillTime": "1627293049406", + "fillFee": "0", + "fillFeeCcy": "USDT", + "execType": "maker", + "accFillSz": "0", + "fillNotionalUsd": "0", + "avgPx": "0", + "status": "new", + "lever": "1", + "orderFee": [ + {"feeCcy": "USDT", + "fee": "0.001"}, + ], + "pnl": "0.1", + "uTime": "1627293049406", + "cTime": "1627293049406", + }], + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME, + "instType": "umcbl", + "instId": "default" + }, + "data": [{ + "instId": "default", + "ordId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "clOrdId": order.client_order_id or "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "100", + "ordType": order.order_type.name.capitalize(), + "force": "post_only", + "side": order.trade_type.name.capitalize(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": self.base_asset, + "fillPx": str(order.price), + "tradeId": "0", + "fillSz": "10", + "fillTime": "1627293049406", + "fillFee": "0", + "fillFeeCcy": self.quote_asset, + "execType": "maker", + "accFillSz": "10", + "fillNotionalUsd": "10", + "avgPx": str(order.price), + "status": "cancelled", + "lever": "1", + "orderFee": [ + {"feeCcy": "USDT", + "fee": "0.001"}, + ], + "pnl": "0.1", + "uTime": "1627293049416", + "cTime": "1627293049416", + }], + } + + def order_event_for_partially_canceled_websocket_update(self, order: InFlightOrder): + return self.order_event_for_canceled_order_websocket_update(order=order) + + def order_event_for_partially_filled_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME, + "instType": "umcbl", + "instId": "default" + }, + "data": [{ + "instId": "default", + "ordId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "clOrdId": order.client_order_id or "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "100", + "ordType": order.order_type.name.capitalize(), + "force": "post_only", + "side": order.trade_type.name.capitalize(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": self.base_asset, + "fillPx": str(self.expected_partial_fill_price), + "tradeId": "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6", + "fillSz": str(self.expected_partial_fill_amount), + "fillTime": "1627293049409", + "fillFee": "10", + "fillFeeCcy": self.quote_asset, + "execType": "maker", + "accFillSz": str(self.expected_partial_fill_amount), + "fillNotionalUsd": "10", + "avgPx": str(self.expected_partial_fill_price), + "status": "partial-fill", + "lever": "1", + "orderFee": [ + {"feeCcy": self.quote_asset, + "fee": str(self.expected_partial_fill_fee.flat_fees[0].amount)}, + ], + "pnl": "0.1", + "uTime": "1627293049409", + "cTime": "1627293049409", + }], + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": "orders", + "instType": "umcbl", + "instId": "default" + }, + "data": [{ + "instId": "default", + "ordId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "clOrdId": order.client_order_id or "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "100", + "ordType": order.order_type.name.capitalize(), + "force": "post_only", + "side": order.trade_type.name.capitalize(), + "posSide": "short", + "tdMode": "cross", + "tgtCcy": self.base_asset, + "fillPx": str(order.price), + "tradeId": "0", + "fillSz": str(order.amount), + "fillTime": "1627293049406", + "fillFee": str(self.expected_fill_fee.flat_fees[0].amount), + "fillFeeCcy": self.quote_asset, + "execType": "maker", + "accFillSz": str(order.amount), + "fillNotionalUsd": "0", + "avgPx": str(order.price), + "status": "full-fill", + "lever": "1", + "orderFee": [ + {"feeCcy": self.quote_asset, + "fee": str(self.expected_fill_fee.flat_fees[0].amount)}, + ], + "pnl": "0.1", + "uTime": "1627293049406", + "cTime": "1627293049406", + }], + } + + def trade_event_for_partial_fill_websocket_update(self, order: InFlightOrder): + return self.order_event_for_partially_filled_websocket_update(order) + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return self.order_event_for_full_fill_websocket_update(order) + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + return { + "action": "snapshot", + "arg": { + "channel": "positions", + "instType": "umcbl", + "instId": "default" + }, + "data": [{ + "instId": self.exchange_symbol_for_tokens(base_token=self.base_asset, quote_token=self.quote_asset), + "posId": order.exchange_order_id or "960836851453296640", + "instName": self.exchange_trading_pair, + "marginCoin": self.quote_asset, + "margin": str(order.amount), + "marginMode": "fixed", + "holdSide": "short", + "holdMode": "double_hold", + "total": str(order.amount), + "available": str(order.amount), + "locked": "0", + "averageOpenPrice": str(order.price), + "leverage": str(order.leverage), + "achievedProfits": "0", + "upl": str(unrealized_pnl), + "uplRate": "1627293049406", + "liqPx": "0", + "keepMarginRate": "", + "fixedMarginRate": "", + "marginRate": "0", + "uTime": "1627293049406", + "cTime": "1627293049406", + "markPrice": "1317.43", + }], + } + + def funding_info_event_for_websocket_update(self): + return { + "arg": { + "channel": "ticker", + "instType": "UMCBL", + "instId": f"{self.base_asset}{self.quote_asset}" + }, + "data": [{ + "instId": f"{self.base_asset}{self.quote_asset}", + "indexPrice": "0", + "markPrice": "0", + "nextSettleTime": "0", + "capitalRate": "0", + }], + } + + def configure_all_symbols_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + all_urls = [] + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USDT_PRODUCT_TYPE.lower()}") + response = self.all_symbols_request_mock_response + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USD_PRODUCT_TYPE.lower()}") + response = self._all_usd_symbols_request_mock_response() + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USDC_PRODUCT_TYPE.lower()}") + response = self._all_usdc_symbols_request_mock_response() + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + return all_urls + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + return self.configure_all_symbols_response(mock_api=mock_api, callback=callback) + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + all_urls = [] + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USDT_PRODUCT_TYPE.lower()}") + response = self.trading_rules_request_erroneous_mock_response + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USD_PRODUCT_TYPE.lower()}") + response = { + "code": "00000", + "data": [], + "msg": "success", + "requestTime": "0" + } + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + url = (f"{web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT)}" + f"?productType={CONSTANTS.USDC_PRODUCT_TYPE.lower()}") + mock_api.get(url, body=json.dumps(response)) + all_urls.append(url) + + return all_urls + + def test_create_order_with_invalid_position_action_raises_value_error(self): + self._simulate_trading_rules_initialized() + + with self.assertRaises(ValueError) as exception_context: + asyncio.get_event_loop().run_until_complete( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="C1", + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("46000"), + position_action=PositionAction.NIL, + ), + ) + + self.assertEqual( + f"Invalid position action {PositionAction.NIL}. Must be one of {[PositionAction.OPEN, PositionAction.CLOSE]}", + str(exception_context.exception) + ) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + def test_get_buy_and_sell_collateral_tokens_without_trading_rules(self): + self.exchange._set_trading_pair_symbol_map(None) + + collateral_token = self.exchange.get_buy_collateral_token(trading_pair="BTC-USDT") + self.assertEqual("USDT", collateral_token) + collateral_token = self.exchange.get_sell_collateral_token(trading_pair="BTC-USDT") + self.assertEqual("USDT", collateral_token) + + collateral_token = self.exchange.get_buy_collateral_token(trading_pair="BTC-USDC") + self.assertEqual("USDC", collateral_token) + collateral_token = self.exchange.get_sell_collateral_token(trading_pair="BTC-USDC") + self.assertEqual("USDC", collateral_token) + + collateral_token = self.exchange.get_buy_collateral_token(trading_pair="BTC-USD") + self.assertEqual("BTC", collateral_token) + collateral_token = self.exchange.get_sell_collateral_token(trading_pair="BTC-USD") + self.assertEqual("BTC", collateral_token) + + def test_time_synchronizer_related_reqeust_error_detection(self): + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_AUTH_TIMESTAMP_ERROR) + exception = IOError(f"{error_code_str} - Request timestamp expired.") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_ORDER_NOT_EXISTS) + exception = IOError(f"{error_code_str} - Failed to cancel order because it was not found.") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + def test_user_stream_empty_position_event_removes_current_position(self): + self.exchange._set_current_timestamp(1640780000) + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order = self.exchange.in_flight_orders["OID1"] + + fake_position = Position( + trading_pair=self.trading_pair, + position_side=PositionSide.LONG, + unrealized_pnl=Decimal("0"), + entry_price=order.price, + amount=order.amount, + leverage=Decimal("1") + ) + self.exchange._perpetual_trading.set_position(self.exchange_trading_pair, fake_position) + + self.assertIn(fake_position, self.exchange._perpetual_trading._account_positions.values()) + + position_event = { + "action": "snapshot", + "arg": { + "channel": CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME, + "instType": "umcbl", + "instId": "default" + }, + "data": [], + } + + mock_queue = AsyncMock() + event_messages = [] + event_messages.append(position_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(0, len(self.exchange._perpetual_trading._account_positions)) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_updates_funding_info(self, mock_api, mock_queue_get): + rate_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL)}".replace(".", + r"\.").replace( + "?", r"\?") + ) + interest_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.OPEN_INTEREST_PATH_URL)}".replace(".", r"\.").replace("?", + r"\?") + ) + mark_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.MARK_PRICE_PATH_URL)}".replace(".", r"\.").replace("?", + r"\?") + ) + settlement_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.FUNDING_SETTLEMENT_TIME_PATH_URL)}".replace(".", + r"\.").replace( + "?", r"\?") + ) + resp = self.funding_info_mock_response + mock_api.get(rate_regex_url, body=json.dumps(resp)) + mock_api.get(interest_regex_url, body=json.dumps(resp)) + mock_api.get(mark_regex_url, body=json.dumps(resp)) + mock_api.get(settlement_regex_url, body=json.dumps(resp)) + + funding_info_event = self.funding_info_event_for_websocket_update() + + event_messages = [funding_info_event, asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + rate_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL)}".replace(".", + r"\.").replace( + "?", r"\?") + ) + interest_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.OPEN_INTEREST_PATH_URL)}".replace(".", r"\.").replace("?", + r"\?") + ) + mark_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.MARK_PRICE_PATH_URL)}".replace(".", r"\.").replace("?", + r"\?") + ) + settlement_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.FUNDING_SETTLEMENT_TIME_PATH_URL)}".replace(".", + r"\.").replace( + "?", r"\?") + ) + resp = self.funding_info_mock_response + mock_api.get(rate_regex_url, body=json.dumps(resp)) + mock_api.get(interest_regex_url, body=json.dumps(resp)) + mock_api.get(mark_regex_url, body=json.dumps(resp)) + mock_api.get(settlement_regex_url, body=json.dumps(resp)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp, funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + def test_exchange_symbol_associated_to_pair_without_product_type(self): + self.exchange._set_trading_pair_symbol_map( + bidict({ + self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset): self.trading_pair, + "BTCUSD_DMCBL": "BTC-USD", + "ETHPERP_CMCBL": "ETH-USDC", + })) + + trading_pair = self.async_run_with_timeout( + self.exchange.trading_pair_associated_to_exchange_instrument_id( + instrument_id=f"{self.base_asset}{self.quote_asset}")) + self.assertEqual(self.trading_pair, trading_pair) + + trading_pair = self.async_run_with_timeout( + self.exchange.trading_pair_associated_to_exchange_instrument_id( + instrument_id="BTCUSD")) + self.assertEqual("BTC-USD", trading_pair) + + trading_pair = self.async_run_with_timeout( + self.exchange.trading_pair_associated_to_exchange_instrument_id( + instrument_id="ETHPERP")) + self.assertEqual("ETH-USDC", trading_pair) + + with self.assertRaises(ValueError) as context: + self.async_run_with_timeout( + self.exchange.trading_pair_associated_to_exchange_instrument_id( + instrument_id="XMRPERP")) + self.assertEqual("No trading pair associated to instrument ID XMRPERP", str(context.exception)) + + @aioresponses() + def test_update_trading_fees(self, mock_api): + self.exchange._set_trading_pair_symbol_map( + bidict( + { + self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset): self.trading_pair, + "BTCUSD_DMCBL": "BTC-USD", + "BTCPERP_CMCBL": "BTC-USDC", + } + ) + ) + + urls = self.configure_all_symbols_response(mock_api=mock_api) + url = urls[0] + resp = self.all_symbols_request_mock_response + + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + fees_request = self._all_executed_requests(mock_api, url)[0] + request_params = fees_request.kwargs["params"] + self.assertEqual(CONSTANTS.USDT_PRODUCT_TYPE.lower(), request_params["productType"]) + + expected_trading_fees = TradeFeeSchema( + maker_percent_fee_decimal=Decimal(resp["data"][0]["makerFeeRate"]), + taker_percent_fee_decimal=Decimal(resp["data"][0]["takerFeeRate"]), + ) + + self.assertEqual(expected_trading_fees, self.exchange._trading_fees[self.trading_pair]) + + def test_collateral_token_balance_updated_when_processing_order_creation_update(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange._account_balances[self.quote_asset] = Decimal(10_000) + self.exchange._account_available_balances[self.quote_asset] = Decimal(10_000) + + order_creation_event = { + "action": "snapshot", + "arg": { + "instType": "umcbl", + "channel": "orders", + "instId": "default" + }, + "data": [ + { + "accFillSz": "0", + "cTime": 1664807277548, + "clOrdId": "960836851453296644", + "force": "normal", + "instId": self.exchange_trading_pair, + "lever": "1", + "notionalUsd": "13.199", + "ordId": "960836851386187777", + "ordType": "limit", + "orderFee": [{"feeCcy": "USDT", "fee": "0"}], + "posSide": "long", + "px": "1000", + "side": "buy", + "status": "new", + "sz": "1", + "tdMode": "cross", + "tgtCcy": "USDT", + "uTime": 1664807277548} + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [order_creation_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal(9_000), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal(10_000), self.exchange.get_balance(self.quote_asset)) + + def test_collateral_token_balance_updated_when_processing_order_cancelation_update(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange._account_balances[self.quote_asset] = Decimal(10_000) + self.exchange._account_available_balances[self.quote_asset] = Decimal(9_000) + + order_creation_event = { + "action": "snapshot", + "arg": { + "instType": "umcbl", + "channel": "orders", + "instId": "default" + }, + "data": [ + { + "accFillSz": "0", + "cTime": 1664807277548, + "clOrdId": "960836851453296644", + "force": "normal", + "instId": self.exchange_trading_pair, + "lever": "1", + "notionalUsd": "13.199", + "ordId": "960836851386187777", + "ordType": "limit", + "orderFee": [{"feeCcy": "USDT", "fee": "0"}], + "posSide": "long", + "px": "1000", + "side": "buy", + "status": "canceled", + "sz": "1", + "tdMode": "cross", + "tgtCcy": "USDT", + "uTime": 1664807277548} + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [order_creation_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal(10_000), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal(10_000), self.exchange.get_balance(self.quote_asset)) + + def test_collateral_token_balance_updated_when_processing_order_creation_update_considering_leverage(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange._account_balances[self.quote_asset] = Decimal(10_000) + self.exchange._account_available_balances[self.quote_asset] = Decimal(10_000) + + order_creation_event = { + "action": "snapshot", + "arg": { + "instType": "umcbl", + "channel": "orders", + "instId": "default" + }, + "data": [ + { + "accFillSz": "0", + "cTime": 1664807277548, + "clOrdId": "960836851453296644", + "force": "normal", + "instId": self.exchange_trading_pair, + "lever": "10", + "notionalUsd": "13.199", + "ordId": "960836851386187777", + "ordType": "limit", + "orderFee": [{"feeCcy": "USDT", "fee": "0"}], + "posSide": "long", + "px": "1000", + "side": "buy", + "status": "new", + "sz": "1", + "tdMode": "cross", + "tgtCcy": "USDT", + "uTime": 1664807277548} + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [order_creation_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal(9_900), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal(10_000), self.exchange.get_balance(self.quote_asset)) + + def test_collateral_token_balance_not_updated_for_order_creation_event_to_not_open_position(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange._account_balances[self.quote_asset] = Decimal(10_000) + self.exchange._account_available_balances[self.quote_asset] = Decimal(10_000) + + order_creation_event = { + "action": "snapshot", + "arg": { + "instType": "umcbl", + "channel": "orders", + "instId": "default" + }, + "data": [ + { + "accFillSz": "0", + "cTime": 1664807277548, + "clOrdId": "960836851453296644", + "force": "normal", + "instId": self.exchange_trading_pair, + "lever": "1", + "notionalUsd": "13.199", + "ordId": "960836851386187777", + "ordType": "limit", + "orderFee": [{"feeCcy": "USDT", "fee": "0"}], + "posSide": "long", + "px": "1000", + "side": "sell", + "status": "new", + "sz": "1", + "tdMode": "cross", + "tgtCcy": "USDT", + "uTime": 1664807277548} + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [order_creation_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal(10_000), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal(10_000), self.exchange.get_balance(self.quote_asset)) + + @aioresponses() + def test_update_balances_for_tokens_in_several_product_type_markets(self, mock_api): + self.exchange._trading_pairs = [] + url = self.balance_url + f"?productType={CONSTANTS.USDT_PRODUCT_TYPE.lower()}" + response = self.balance_request_mock_response_for_base_and_quote + mock_api.get(url, body=json.dumps(response)) + + url = self.balance_url + f"?productType={CONSTANTS.USD_PRODUCT_TYPE.lower()}" + response = { + "code": "00000", + "data": [ + { + "marginCoin": self.base_asset, + "locked": "5", + "available": "50", + "crossMaxAvailable": "50", + "fixedMaxAvailable": "50", + "maxTransferOut": "10572.92904289", + "equity": "70", + "usdtEquity": "10582.902657719473", + "btcEquity": "0.204885807029" + } + ], + "msg": "success", + "requestTime": 1630901215622 + } + mock_api.get(url, body=json.dumps(response)) + + url = self.balance_url + f"?productType={CONSTANTS.USDC_PRODUCT_TYPE.lower()}" + response = { + "code": "00000", + "data": [], + "msg": "success", + "requestTime": 1630901215622 + } + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("60"), available_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("85"), total_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + response = self.balance_request_mock_response_only_base + + self._configure_balance_response(response=response, mock_api=mock_api) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), total_balances[self.base_asset]) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _expected_valid_trading_pairs(self): + return [self.trading_pair, "BTC-USD", "BTC-USDC"] + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "00000", + "data": { + "orderId": self.expected_exchange_order_id, + "clientOid": str(order.client_order_id) + }, + "msg": "success", + "requestTime": 1627293504612 + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "00000", + "data": { + "symbol": self.exchange_trading_pair, + "size": float(order.amount), + "orderId": str(order.exchange_order_id), + "clientOid": str(order.client_order_id), + "filledQty": float(order.amount), + "priceAvg": float(order.price), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "price": str(order.price), + "state": "filled", + "side": "open_long", + "timeInForce": "normal", + "totalProfits": "10", + "posSide": "long", + "marginCoin": self.quote_asset, + "presetTakeProfitPrice": 69582.5, + "presetStopLossPrice": 21432.5, + "filledAmount": float(order.amount), + "orderType": "limit", + "cTime": 1627028708807, + "uTime": 1627028717807 + }, + "msg": "success", + "requestTime": 1627300098776 + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["state"] = "canceled" + resp["data"]["filledQty"] = 0 + resp["data"]["priceAvg"] = 0 + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["state"] = "new" + resp["data"]["filledQty"] = 0 + resp["data"]["priceAvg"] = 0 + return resp + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["state"] = "partially_filled" + resp["data"]["filledQty"] = float(self.expected_partial_fill_amount) + resp["data"]["priceAvg"] = float(self.expected_partial_fill_price) + return resp + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "code": "00000", + "data": [ + { + "tradeId": self.expected_fill_trade_id, + "symbol": self.exchange_trading_pair, + "orderId": order.exchange_order_id, + "price": str(self.expected_partial_fill_price), + "sizeQty": float(self.expected_partial_fill_amount), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "side": "close_long", + "cTime": "1627027632241" + } + ], + "msg": "success", + "requestTime": 1627386245672 + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "code": "00000", + "data": [ + { + "tradeId": self.expected_fill_trade_id, + "symbol": self.exchange_trading_pair, + "orderId": order.exchange_order_id, + "price": str(order.price), + "sizeQty": float(order.amount), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "side": "close_short", + "cTime": "1627027632241" + } + ], + "msg": "success", + "requestTime": 1627386245672 + } + + def _all_usd_symbols_request_mock_response(self): + return { + "code": "00000", + "data": [ + { + "baseCoin": "BTC", + "buyLimitPriceRatio": "0.01", + "feeRateUpRatio": "0.005", + "makerFeeRate": "0.0002", + "minTradeNum": "0.001", + "openCostUpRatio": "0.01", + "priceEndStep": "5", + "pricePlace": "1", + "quoteCoin": "USD", + "sellLimitPriceRatio": "0.01", + "sizeMultiplier": "0.001", + "supportMarginCoins": ["BTC", "ETH", "USDC", "XRP", "BGB"], + "symbol": "BTCUSD_DMCBL", + "takerFeeRate": "0.0006", + "volumePlace": "3"}, + ], + "msg": "success", + "requestTime": "0" + } + + def _all_usdc_symbols_request_mock_response(self): + return { + "code": "00000", + "data": [ + { + "baseCoin": "BTC", + "buyLimitPriceRatio": "0.02", + "feeRateUpRatio": "0.005", + "makerFeeRate": "0.0002", + "minTradeNum": "0.0001", + "openCostUpRatio": "0.01", + "priceEndStep": "5", + "pricePlace": "1", + "quoteCoin": "USD", + "sellLimitPriceRatio": "0.02", + "sizeMultiplier": "0.0001", + "supportMarginCoins": ["USDC"], + "symbol": "BTCPERP_CMCBL", + "takerFeeRate": "0.0006", + "volumePlace": "4" + }, + ], + "msg": "success", + "requestTime": "0" + } + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + + return_url = super()._configure_balance_response(response=response, mock_api=mock_api, callback=callback) + + url = self.balance_url + f"?productType={CONSTANTS.USD_PRODUCT_TYPE.lower()}" + response = { + "code": "00000", + "data": [], + "msg": "success", + "requestTime": 1630901215622 + } + mock_api.get(url, body=json.dumps(response)) + + url = self.balance_url + f"?productType={CONSTANTS.USDC_PRODUCT_TYPE.lower()}" + mock_api.get(url, body=json.dumps(response)) + + return return_url + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ), + } diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py new file mode 100644 index 0000000..7b01ee9 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py @@ -0,0 +1,849 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bitget_perpetual import bitget_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_api_order_book_data_source import ( + BitgetPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import BitgetPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitgetPerpetualAPIOrderBookDataSourceTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BitgetPerpetualDerivative( + client_config_map, + bitget_perpetual_api_key="", + bitget_perpetual_secret_key="", + bitget_perpetual_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + domain=self.domain, + ) + self.data_source = BitgetPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}{self.quote_asset}_UMCBL": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_rest_snapshot_msg(self) -> Dict: + return { + "code": "00000", + "data": { + "asks": [ + [ + "9487.5", + "522147" + ], + ], + "bids": [ + [ + "9487", + "336241" + ], + ], + "timestamp": "1627115809358" + }, + "msg": "success", + "requestTime": 1627115809358 + } + + def get_ws_diff_msg(self) -> Dict: + return { + "action": "update", + "arg": { + "instType": "mc", + "channel": CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC, + "instId": self.ex_trading_pair + }, + "data": [ + { + "asks": [["3001", "0", "1", "4"]], + "bids": [ + ["2999.0", "8", "1", "4"], + ["2998.0", "10", "1", "4"] + ], + "ts": "1627115809358" + } + ] + } + + def ws_snapshot_msg(self) -> Dict: + return { + "action": "snapshot", + "arg": { + "instType": "mc", + "channel": CONSTANTS.WS_ORDER_BOOK_EVENTS_TOPIC, + "instId": self.ex_trading_pair + }, + "data": [ + { + "asks": [["3001", "0", "1", "4"]], + "bids": [ + ["2999.0", "8", "1", "4"], + ["2998.0", "10", "1", "4"] + ], + "ts": "1627115809358" + } + ] + } + + def get_funding_info_msg(self) -> Dict: + return { + "action": "snapshot", + "arg": { + "instType": "mc", + "channel": "ticker", + "instId": self.ex_trading_pair, + }, + "data": [ + { + "instId": self.ex_trading_pair, + "last": "44962.00", + "bestAsk": "44962", + "bestBid": "44961", + "high24h": "45136.50", + "low24h": "43620.00", + "priceChangePercent": "0.02", + "capitalRate": "-0.00010", + "nextSettleTime": 1632495600000, + "systemTime": 1632470889087, + "markPrice": "44936.21", + "indexPrice": "44959.23", + "holding": "1825.822", + "baseVolume": "39746.470", + "quoteVolume": "1760329683.834" + } + ] + } + + def get_funding_info_event(self): + return self.get_funding_info_msg() + + def get_funding_info_rest_msg(self): + return { + "data": { + "symbol": self.ex_trading_pair, + "index": "35000", + "fundingTime": "1627311600000", + "timestamp": "1627291836179", + "fundingRate": "0.0002", + "amount": "757.8338", + "markPrice": "35000", + }, + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_rest_snapshot_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = int(resp["data"]["timestamp"]) + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_diffs_and_funding_info(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_diff_msg() + result_subscribe_funding_info = self.get_funding_info_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_funding_info), + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_subscription = { + "op": "subscribe", + "args": [ + { + "instType": "mc", + "channel": "books", + "instId": self.ex_trading_pair + }, + { + "instType": "mc", + "channel": "trade", + "instId": self.ex_trading_pair + }, + { + "instType": "mc", + "channel": "ticker", + "instId": self.ex_trading_pair + } + ], + } + + self.assertEqual(expected_subscription, sent_subscription_messages[0]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book, trade and funding info channels...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_for_usdc_product_type_pair(self, ws_connect_mock): + local_base_asset = "BTC" + local_quote_asset = "USDC" + local_trading_pair = f"{local_base_asset}-{local_quote_asset}" + local_exchange_trading_pair_without_type = f"{local_base_asset}{local_quote_asset}" + local_exchange_trading_pair = f"{local_exchange_trading_pair_without_type}_{CONSTANTS.USDC_PRODUCT_TYPE}" + + local_data_source = BitgetPerpetualAPIOrderBookDataSource( + trading_pairs=[local_trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self.connector._set_trading_pair_symbol_map( + bidict({f"{local_exchange_trading_pair}": local_trading_pair})) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_diff_msg() + result_subscribe_funding_info = self.get_funding_info_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_funding_info), + ) + + self.listening_task = self.ev_loop.create_task(local_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_subscription = { + "op": "subscribe", + "args": [ + { + "instType": "mc", + "channel": "books", + "instId": local_exchange_trading_pair_without_type + }, + { + "instType": "mc", + "channel": "trade", + "instId": local_exchange_trading_pair_without_type + }, + { + "instType": "mc", + "channel": "ticker", + "instId": local_exchange_trading_pair_without_type + } + ], + } + + self.assertEqual(expected_subscription, sent_subscription_messages[0]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book, trade and funding info channels...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_for_usd_product_type_pair(self, ws_connect_mock): + local_base_asset = "BTC" + local_quote_asset = "USD" + local_trading_pair = f"{local_base_asset}-{local_quote_asset}" + local_exchange_trading_pair_without_type = f"{local_base_asset}{local_quote_asset}" + local_exchange_trading_pair = f"{local_exchange_trading_pair_without_type}_{CONSTANTS.USD_PRODUCT_TYPE}" + + local_data_source = BitgetPerpetualAPIOrderBookDataSource( + trading_pairs=[local_trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self.connector._set_trading_pair_symbol_map( + bidict({f"{local_exchange_trading_pair}": local_trading_pair})) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_diff_msg() + result_subscribe_funding_info = self.get_funding_info_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_funding_info), + ) + + self.listening_task = self.ev_loop.create_task(local_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_subscription = { + "op": "subscribe", + "args": [ + { + "instType": "mc", + "channel": "books", + "instId": local_exchange_trading_pair_without_type + }, + { + "instType": "mc", + "channel": "trade", + "instId": local_exchange_trading_pair_without_type + }, + { + "instType": "mc", + "channel": "ticker", + "instId": local_exchange_trading_pair_without_type + } + ], + } + + self.assertEqual(expected_subscription, sent_subscription_messages[0]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book, trade and funding info channels...") + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + ) + ) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = {} + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "action": "snapshot", + "arg": { + "instType": "mc", + "channel": CONSTANTS.WS_TRADES_TOPIC, + "instId": self.ex_trading_pair, + }, + "data": [ + ["1632470889087", "10", "411.8", "buy"], + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(int(trade_event["data"][0][0]), msg.trade_id) + self.assertEqual(int(trade_event["data"][0][0]) * 1e-3, msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = self.get_ws_diff_msg() + incomplete_resp["data"] = 1 + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self.get_ws_diff_msg() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = int(diff_event["data"][0]["ts"]) + expected_timestamp = expected_update_id * 1e-3 + self.assertEqual(expected_timestamp, msg.timestamp) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(2999.0, bids[0].price) + self.assertEqual(8, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(3001, asks[0].price) + self.assertEqual(0, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_log_exception(self, mock_api): + msg_queue: asyncio.Queue = asyncio.Queue() + + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + mock_api.get(regex_url, exception=asyncio.CancelledError) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + try: + self.async_run_with_timeout(self.listening_task, timeout=2) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_listen_for_order_book_rest_snapshots_successful(self, mock_api): + msg_queue: asyncio.Queue = asyncio.Queue() + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_rest_snapshot_msg() + + mock_api.get(regex_url, body=json.dumps(resp)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(float(resp["data"]["timestamp"]) * 1e-3, msg.timestamp) + expected_update_id = float(resp["data"]["timestamp"]) + expected_timestamp = expected_update_id * 1e-3 + self.assertEqual(expected_update_id, msg.update_id) + self.assertEqual(expected_timestamp, msg.timestamp) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_order_book_snapshots_successful(self): + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + + mock_queue = AsyncMock() + event = self.ws_snapshot_msg() + mock_queue.get.side_effect = [event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = int(event["data"][0]["ts"]) + expected_timestamp = expected_update_id * 1e-3 + self.assertEqual(expected_timestamp, msg.timestamp) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(2999.0, bids[0].price) + self.assertEqual(8, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(3001, asks[0].price) + self.assertEqual(0, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_funding_info_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_funding_info(msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_funding_info_logs_exception(self): + incomplete_resp = self.get_funding_info_event() + incomplete_resp["data"] = 1 + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public funding info updates from exchange")) + + def test_listen_for_funding_info_successful(self): + funding_info_event = self.get_funding_info_event() + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + msg: FundingInfoUpdate = self.async_run_with_timeout(msg_queue.get()) + funding_update = funding_info_event["data"][0] + + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_index_price = Decimal(str(funding_update["indexPrice"])) + self.assertEqual(expected_index_price, msg.index_price) + expected_mark_price = Decimal(str(funding_update["markPrice"])) + self.assertEqual(expected_mark_price, msg.mark_price) + expected_funding_time = int(funding_update["nextSettleTime"]) * 1e-3 + self.assertEqual(expected_funding_time, msg.next_funding_utc_timestamp) + expected_rate = Decimal(funding_update["capitalRate"]) + self.assertEqual(expected_rate, msg.rate) + + @aioresponses() + def test_get_funding_info(self, mock_api): + rate_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + interest_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.OPEN_INTEREST_PATH_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + mark_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.MARK_PRICE_PATH_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + settlement_regex_url = re.compile( + f"^{web_utils.get_rest_url_for_endpoint(CONSTANTS.FUNDING_SETTLEMENT_TIME_PATH_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + resp = self.get_funding_info_rest_msg() + mock_api.get(rate_regex_url, body=json.dumps(resp)) + mock_api.get(interest_regex_url, body=json.dumps(resp)) + mock_api.get(mark_regex_url, body=json.dumps(resp)) + mock_api.get(settlement_regex_url, body=json.dumps(resp)) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + msg_result = resp["data"] + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(Decimal(str(msg_result["amount"])), funding_info.index_price) + self.assertEqual(Decimal(str(msg_result["markPrice"])), funding_info.mark_price) + self.assertEqual(int(msg_result["fundingTime"]) * 1e-3, funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(str(msg_result["fundingRate"])), funding_info.rate) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_events_enqueued_correctly_after_channel_detection(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + diff_event = self.get_ws_diff_msg() + funding_event = self.get_funding_info_msg() + trade_event = { + "action": "snapshot", + "arg": { + "instType": "mc", + "channel": CONSTANTS.WS_TRADES_TOPIC, + "instId": self.ex_trading_pair, + }, + "data": [ + ["1632470889087", "10", "411.8", "buy"], + ] + } + snapshot_event = self.ws_snapshot_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(snapshot_event), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(diff_event), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(funding_event), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(trade_event), + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(1, self.data_source._message_queue[self.data_source._snapshot_messages_queue_key].qsize()) + self.assertEqual( + snapshot_event, + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key].get_nowait()) + self.assertEqual(1, self.data_source._message_queue[self.data_source._diff_messages_queue_key].qsize()) + self.assertEqual( + diff_event, + self.data_source._message_queue[self.data_source._diff_messages_queue_key].get_nowait()) + self.assertEqual(1, self.data_source._message_queue[self.data_source._funding_info_messages_queue_key].qsize()) + self.assertEqual( + funding_event, + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key].get_nowait()) + self.assertEqual(1, self.data_source._message_queue[self.data_source._trade_messages_queue_key].qsize()) + self.assertEqual( + trade_event, + self.data_source._message_queue[self.data_source._trade_messages_queue_key].get_nowait()) diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..91d5e4a --- /dev/null +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_user_stream_data_source.py @@ -0,0 +1,237 @@ +import asyncio +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +from bidict import bidict + +import hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_auth import BitgetPerpetualAuth +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_derivative import BitgetPerpetualDerivative +from hummingbot.connector.derivative.bitget_perpetual.bitget_perpetual_user_stream_data_source import ( + BitgetPerpetualUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer + + +class BitgetPerpetualUserStreamDataSourceTests(TestCase): + # the level is required to receive logs from the data source loger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + "_UMCBL" + cls.domain = None + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + auth = BitgetPerpetualAuth( + api_key="TEST_API_KEY", + secret_key="TEST_SECRET", + passphrase="PASSPHRASE", + time_provider=TimeSynchronizer()) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BitgetPerpetualDerivative( + client_config_map, + bitget_perpetual_api_key="", + bitget_perpetual_secret_key="", + bitget_perpetual_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + domain=self.domain, + ) + + self.data_source = BitgetPerpetualUserStreamDataSource( + auth=auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}{self.quote_asset}_UMCBL": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _authentication_response(self, authenticated: bool) -> str: + message = { + "event": "login" if authenticated else "err", + "code": "0" if authenticated else "4000", + "msg": "" + } + + return json.dumps(message) + + def _subscription_response(self, subscribed: bool, subscription: str) -> str: + message = { + "event": "subscribe", + "arg": [{"instType": "SP", "channel": subscription, "instId": "BTCUSDT"}] + } + + return json.dumps(message) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + initial_last_recv_time = self.data_source.last_recv_time + + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, self._authentication_response(True)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME)) + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages) + ) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to private account, position and orders channels...") + ) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + self.assertEqual(2, len(sent_messages)) + authentication_request = sent_messages[0] + subscription_request = sent_messages[1] + + self.assertEqual(CONSTANTS.WS_AUTHENTICATE_USER_ENDPOINT_NAME, + authentication_request["op"]) + + expected_payload = { + "op": "subscribe", + "args": [ + { + "instType": CONSTANTS.USDT_PRODUCT_TYPE, + "channel": CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME, + "instId": "default" + }, + { + "instType": CONSTANTS.USDT_PRODUCT_TYPE, + "channel": CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME, + "instId": "default" + }, + { + "instType": CONSTANTS.USDT_PRODUCT_TYPE, + "channel": CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME, + "instId": "default" + }, + ] + } + self.assertEqual(expected_payload, subscription_request) + + self.assertGreater(self.data_source.last_recv_time, initial_last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_authentication_failure(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(False)) + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(self._is_logged("ERROR", "Error authenticating the private websocket connection")) + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, self._authentication_response(True) + ) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_ws): + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_on_cancel_exception(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + self.async_run_with_timeout(self.listening_task) diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_web_utils.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_web_utils.py new file mode 100644 index 0000000..f42973a --- /dev/null +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_web_utils.py @@ -0,0 +1,36 @@ +import asyncio +import json +import unittest +from typing import Awaitable + +from aioresponses import aioresponses + +from hummingbot.connector.derivative.bitget_perpetual import ( + bitget_perpetual_constants as CONSTANTS, + bitget_perpetual_web_utils as web_utils, +) + + +class BitgetPerpetualWebUtilsTest(unittest.TestCase): + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_get_rest_url_for_endpoint(self): + endpoint = "/testEndpoint" + url = web_utils.get_rest_url_for_endpoint(endpoint) + self.assertEqual("https://api.bitget.com/testEndpoint", url) + + @aioresponses() + def test_get_current_server_time(self, api_mock): + url = web_utils.public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL) + data = { + "flag": True, + "requestTime": 1640001112223} + + api_mock.get(url=url, status=400, body=json.dumps(data)) + + time = self.async_run_with_timeout(web_utils.get_current_server_time()) + + self.assertEqual(data["requestTime"], time) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/__init__.py b/test/hummingbot/connector/derivative/bitmex_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..c5eeaf8 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_api_order_book_data_source.py @@ -0,0 +1,452 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_utils as utils +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_api_order_book_data_source import ( + BitmexPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitmexPerpetualAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "ETH" + cls.quote_asset = "USD" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + cls.domain = "bitmex_perpetual_testnet" + utils.TRADING_PAIR_SIZE_CURRENCY["ETHUSD"] = utils.TRADING_PAIR_SIZE("USD", False, None) + utils.TRADING_PAIR_INDICES["ETHUSD"] = utils.TRADING_PAIR_INDEX(297, 0.05) + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.async_tasks: List[asyncio.Task] = [] + + self.data_source = BitmexPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict({self.ex_trading_pair: self.trading_pair}) + } + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + for task in self.async_tasks: + task.cancel() + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 60): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def resume_test_callback(self, *_, **__): + self.resume_test_event.set() + return None + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _raise_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _orderbook_update_event(self): + resp = { + "table": "orderBookL2", + "action": "insert", + "data": [{ + "symbol": "ETHUSD", + "id": 3333377777, + "size": 10, + "side": "Sell" + }], + } + return resp + + def _orderbook_trade_event(self): + resp = { + "table": "trade", + "data": [{ + "symbol": "ETHUSD", + "side": "Sell", + "price": 1000.0, + "size": 10, + "timestamp": "2020-02-11T9:30:02.123Z" + }], + } + return resp + + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [{ + "symbol": "ETHUSD", + "lastPrice": 100.0 + }] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source.get_last_traded_prices(trading_pairs=[self.trading_pair], domain=self.domain) + ) + self.assertTrue(self.trading_pair in result) + self.assertEqual(100.0, result[self.trading_pair]) + + def test_get_throttler_instance(self): + self.assertTrue(isinstance(self.data_source._get_throttler_instance(), AsyncThrottler)) + + @aioresponses() + def test_init_trading_pair_symbols_failure(self, mock_api): + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + map = self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(0, len(map)) + + @aioresponses() + def test_init_trading_pair_symbols_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "ETHUSD", + "rootSymbol": "ETH", + "quoteCurrency": "USD" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + self.async_run_with_timeout(self.data_source.init_trading_pair_symbols(domain=self.domain)) + self.assertEqual(1, len(self.data_source._trading_pair_symbol_map)) + + @aioresponses() + def test_trading_pair_symbol_map_dictionary_not_initialized(self, mock_api): + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "ETHUSD", + "rootSymbol": "ETH", + "quoteCurrency": "USD" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(1, len(self.data_source._trading_pair_symbol_map)) + + def test_trading_pair_symbol_map_dictionary_initialized(self): + result = self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(1, len(result)) + + def test_convert_from_exchange_trading_pair_not_found(self): + unknown_pair = "UNKNOWN-PAIR" + with self.assertRaisesRegex(ValueError, f"There is no symbol mapping for exchange trading pair {unknown_pair}"): + self.async_run_with_timeout( + self.data_source.convert_from_exchange_trading_pair(unknown_pair, domain=self.domain)) + + def test_convert_from_exchange_trading_pair_successful(self): + result = self.async_run_with_timeout( + self.data_source.convert_from_exchange_trading_pair(self.ex_trading_pair, domain=self.domain)) + self.assertEqual(result, self.trading_pair) + + def test_convert_to_exchange_trading_pair_not_found(self): + unknown_pair = "UNKNOWN-PAIR" + with self.assertRaisesRegex(ValueError, f"There is no symbol mapping for trading pair {unknown_pair}"): + self.async_run_with_timeout( + self.data_source.convert_to_exchange_trading_pair(unknown_pair, domain=self.domain)) + + def test_convert_to_exchange_trading_pair_successful(self): + result = self.async_run_with_timeout( + self.data_source.convert_to_exchange_trading_pair(self.trading_pair, domain=self.domain)) + self.assertEqual(result, self.ex_trading_pair) + + @aioresponses() + def test_get_snapshot_exception_raised(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + with self.assertRaises(IOError) as context: + self.async_run_with_timeout( + self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain) + ) + + self.assertEqual(str(context.exception), "Error executing request GET /orderBook/L2. HTTP status is 400. Error: [\"ERROR\"]") + + @aioresponses() + def test_get_snapshot_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [{ + 'symbol': 'ETHUSD', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4 + }] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain) + ) + self.assertEqual(mock_response, result) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [ + { + 'symbol': 'ETHUSD', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4, + 'id': 2543 + }, + { + 'symbol': 'ETHUSD', + 'side': 'Buy', + 'size': 100, + 'price': 3000.1, + 'id': 2555 + } + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + result = self.async_run_with_timeout(self.data_source.get_new_order_book(trading_pair=self.trading_pair)) + self.assertIsInstance(result, OrderBook) + + @aioresponses() + def test_get_funding_info_from_exchange_error_response(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + try: + self.async_run_with_timeout(self.data_source._get_funding_info_from_exchange(self.trading_pair)) + except Exception: + pass + + self._is_logged("ERROR", f"Unable to fetch FundingInfo for {self.trading_pair}. Error: None") + + @aioresponses() + def test_get_funding_info_from_exchange_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = [{ + "lastPrice": 1000.0, + "fairPrice": 999.1, + "fundingRate": 0.1, + "fundingTimestamp": "2022-02-11T05:30:30.000Z" + }] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source._get_funding_info_from_exchange(self.trading_pair)) + + self.assertIsInstance(result, FundingInfo) + self.assertEqual(result.trading_pair, self.trading_pair) + self.assertEqual(result.rate, Decimal(mock_response[0]["fundingRate"])) + + @aioresponses() + def test_get_funding_info(self, mock_api): + self.assertNotIn(self.trading_pair, self.data_source._funding_info) + + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = [{ + "lastPrice": 1000.0, + "fairPrice": 999.1, + "fundingRate": 0.1, + "fundingTimestamp": "2022-02-11T05:30:30.000Z" + }] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_funding_info(trading_pair=self.trading_pair)) + + self.assertIsInstance(result, FundingInfo) + self.assertEqual(result.trading_pair, self.trading_pair) + + self.assertEqual(result.rate, Decimal(mock_response[0]["fundingRate"])) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_cancelled_when_connecting(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + self.assertEqual(msg_queue.qsize(), 0) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successful(self, mock_ws): + msg_queue_diffs: asyncio.Queue = asyncio.Queue() + msg_queue_trades: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.close.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_update_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_trade_event()) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.listening_task_diffs = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue_diffs) + ) + self.listening_task_trades = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue_trades) + ) + + try: + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_diffs.get()) + except Exception as e: + print(e) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.DIFF, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + self.assertEqual(0, len(result.content["bids"])) + self.assertEqual(1, len(result.content["asks"])) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_trades.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.TRADE, result.type) + self.assertTrue(result.has_trade_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + self.listening_task.cancel() + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_error_raised(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + def test_listen_for_order_book_snapshots_logs_exception_error_with_response(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "m": 1, + "i": 2, + } + mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [{ + 'symbol': 'ETHUSD', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4, + 'id': 33337777 + }] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + result = self.async_run_with_timeout(msg_queue.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py new file mode 100644 index 0000000..d072716 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py @@ -0,0 +1,107 @@ +import asyncio +import copy +import hashlib +import hmac +import json +import unittest +from typing import Awaitable +from unittest.mock import patch +from urllib.parse import urlencode + +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import EXPIRATION, BitmexPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + +MOCK_TS = 1648733370.792768 + + +class BitmexPerpetualAuthUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.test_params = { + "test_param": "test_input" + } + self.auth = BitmexPerpetualAuth( + api_key=self.api_key, + api_secret=self.secret_key + ) + + def _get_test_payload(self): + return urlencode(dict(copy.deepcopy(self.test_params))) + + def _get_signature_from_test_payload(self, payload): + return hmac.new( + bytes(self.auth._api_secret.encode("utf-8")), + payload.encode("utf-8"), + hashlib.sha256 + ).hexdigest() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_generate_signature_from_payload(self): + payload = self._get_test_payload() + signature = self.auth.generate_signature_from_payload(payload) + + self.assertEqual(signature, self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_no_parameters_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + payload = 'GET' + mock_path + str(int(MOCK_TS) + EXPIRATION) + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url=mock_path, is_auth_required=True + ) + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_parameters_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + mock_query = "?test_param=param" + payload = 'GET' + mock_path + mock_query + str(int(MOCK_TS) + EXPIRATION) + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url=mock_path, params={"test_param": "param"}, is_auth_required=True + ) + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_data_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + mock_data = json.dumps(self.test_params) + payload = 'POST' + mock_path + str(int(MOCK_TS) + EXPIRATION) + mock_data + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, url="/TEST_PATH_URL", data=self.test_params, is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + def test_generate_ws_signature(self): + payload = 'GET/realtime' + str(int(MOCK_TS)) + + signature = self.async_run_with_timeout(self.auth.generate_ws_signature(str(int(MOCK_TS)))) + self.assertEqual(signature, self._get_signature_from_test_payload(payload)) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py new file mode 100644 index 0000000..d71f5ff --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py @@ -0,0 +1,1097 @@ +import asyncio +import functools +import json +import re +import time +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Callable, Dict, List, Optional +from unittest.mock import AsyncMock, patch + +import pandas as pd +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_utils as utils +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_api_order_book_data_source import ( + BitmexPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_derivative import BitmexPerpetualDerivative +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_status import BitmexPerpetualOrderStatus +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent + + +class BitmexPerpetualDerivativeUnitTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.symbol = f"{cls.base_asset}{cls.quote_asset}" + cls.domain = CONSTANTS.TESTNET_DOMAIN + cls.listen_key = "TEST_LISTEN_KEY" + cls.ev_loop = asyncio.get_event_loop() + utils.TRADING_PAIR_SIZE_CURRENCY["COINALPHAHBOT"] = utils.TRADING_PAIR_SIZE("COINALPHA", True, 1) + utils.TRADING_PAIR_SIZE_CURRENCY["XBTUSD"] = utils.TRADING_PAIR_SIZE("USD", False, None) + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + + self.ws_sent_messages = [] + self.ws_incoming_messages = asyncio.Queue() + self.resume_test_event = asyncio.Event() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = BitmexPerpetualDerivative( + client_config_map=self.client_config_map, + bitmex_perpetual_api_key="testAPIKey", + bitmex_perpetual_api_secret="testSecret", + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.symbol: self.trading_pair, + "XBTUSD": "XBT-USD" + } + self.exchange._set_current_timestamp(1640780000) + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._client_order_tracker.logger().setLevel(1) + self.exchange._client_order_tracker.logger().addHandler(self) + self.exchange._trading_pair_to_size_type["COINALPHA-HBOT"] = utils.TRADING_PAIR_SIZE("COINALPHA", True, 1) + self.exchange._trading_pair_to_size_type["XBT-USD"] = utils.TRADING_PAIR_SIZE("USD", False, None) + self.mocking_assistant = NetworkMockingAssistant() + self.test_task: Optional[asyncio.Task] = None + self.resume_test_event = asyncio.Event() + self._initialize_event_loggers() + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict( + { + self.symbol: self.trading_pair, + "XBTUSD": "XBT-USD" + } + ) + } + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.funding_payment_completed_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.FundingPaymentCompleted, self.funding_payment_completed_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _return_calculation_and_set_done_event(self, calculation: Callable, *args, **kwargs): + if self.resume_test_event.is_set(): + raise asyncio.CancelledError + self.resume_test_event.set() + return calculation(*args, **kwargs) + + def _get_position_risk_api_endpoint_single_position_list(self) -> List[Dict[str, Any]]: + positions = [ + { + "symbol": self.symbol, + "currentQty": 1, + "avgEntryPrice": 10, + "unrealisedPnl": 1, + "leverage": 1, + "openOrderBuyQty": 1 + } + ] + return positions + + def _get_position_risk_api_endpoint_single_position_closed_list(self) -> List[Dict[str, Any]]: + positions = [ + { + "symbol": self.symbol, + "currentQty": 0, + "avgEntryPrice": 10, + "unrealisedPnl": 1, + "leverage": 1, + "openOrderBuyQty": 1 + } + ] + return positions + + def _get_position_update_ws_event_single_position_dict(self) -> Dict[str, Any]: + account_update = { + "table": "position", + "data": [{ + "symbol": self.symbol, + "currentQty": 1, + "avgEntryPrice": 10, + "unrealisedPnl": 1, + "leverage": 1, + "openOrderBuyQty": 1 + }], + } + return account_update + + def _get_income_history_dict(self) -> List: + income_history = [{ + "income": 1, + "symbol": self.symbol, + "time": self.start_timestamp, + }] + return income_history + + def _get_funding_info_dict(self) -> Dict[str, Any]: + funding_info = { + "lastPrice": 100.0, + "fairPrice": 101.1, + "fundingTimestamp": "2022-02-11T09:30:30.000Z", + "fundingRate": 0.05, + } + return funding_info + + def _get_trading_pair_symbol_map(self) -> Dict[str, str]: + trading_pair_symbol_map = {self.symbol: f"{self.base_asset}-{self.quote_asset}"} + return trading_pair_symbol_map + + def _get_exchange_info_mock_response( + self, + margin_asset: str = "HBOT", + min_order_size: float = 1, + min_price_increment: float = 2, + min_notional_size: float = 4, + max_order_size: float = 1000 + ) -> Dict[str, Any]: + mocked_exchange_info = [ + { + "symbol": self.symbol, + "typ": "FFWCSX", + "rootSymbol": self.base_asset, + "quoteCurrency": self.quote_asset, + "maxOrderQty": max_order_size, + "lotSize": min_order_size, # this gets divided by the multiplier, which is set to 1 + "settlCurrency": margin_asset, + "tickSize": min_price_increment + }, + { + "symbol": "XBTUSD", + "typ": "FFWCSX", + "rootSymbol": "XBT", + "quoteCurrency": "USD", + "lotSize": 100, + "tickSize": 0.5, + "settlCurrency": "XBt", + "maxOrderQty": 10000000 + }, + ] + return mocked_exchange_info + + @aioresponses() + def test_existing_account_position_detected_on_positions_update(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.trading_pair.replace("-", ""), self.symbol) + + @aioresponses() + def test_account_position_updated_on_positions_update(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 1) + + positions[0]["currentQty"] = "2" + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 2) + + @aioresponses() + def test_new_account_position_detected_on_positions_update(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_closed_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 0) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + + @aioresponses() + def test_closed_account_position_removed_on_positions_update(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + positions = self._get_position_risk_api_endpoint_single_position_list() + req_mock.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 1) + + positions[0]["currentQty"] = "0" + req_mock.get(regex_url, body=json.dumps(positions)) + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + self.assertEqual(len(self.exchange.account_positions), 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_new_account_position_detected_on_stream_event(self, mock_api, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 0) + + account_update = self._get_position_update_ws_event_single_position_dict() + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 1) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_account_position_updated_on_stream_event(self, mock_api, ws_connect_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 1) + + account_update = self._get_position_update_ws_event_single_position_dict() + account_update["data"][0]["currentQty"] = 2 + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 2) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_closed_account_position_removed_on_stream_event(self, mock_api, ws_connect_mock): + url = web_utils.rest_url( + CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + positions = self._get_position_risk_api_endpoint_single_position_list() + mock_api.get(regex_url, body=json.dumps(positions)) + + task = self.ev_loop.create_task(self.exchange._update_positions()) + self.async_run_with_timeout(task) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) + + self.assertEqual(len(self.exchange.account_positions), 1) + + account_update = self._get_position_update_ws_event_single_position_dict() + account_update["data"][0]["currentQty"] = 0 + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(account_update)) + + self.ev_loop.create_task(self.exchange._user_stream_event_listener()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(len(self.exchange.account_positions), 0) + + @aioresponses() + def test_set_position_mode_initial_mode_is_none(self, mock_api): + self.assertIsNone(self.exchange.position_mode) + + self.exchange.set_position_mode(PositionMode.ONEWAY) + + self.assertEqual(PositionMode.ONEWAY, self.exchange.position_mode) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._trading_pairs.append("XBT-USD") + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: Dict[str, Any] = [ + { + "symbol": "COINALPHAHBOT", + "rootSymbol": "COINALPHA", + "quoteCurrency": "HBOT", + "settlCurrency": "HBOT", + "lotSize": 1.0, + "tickSize": 0.0001, + "minProvideSize": 0.001, + "maxOrderQty": 1000000 + }, + { + "symbol": "XBTUSD", + "rootSymbol": "XBT", + "quoteCurrency": "USD", + "lotSize": 100, + "tickSize": 0.5, + "settlCurrency": "XBt", + "maxOrderQty": 10000000 + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + url_2 = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace("?", r"\?")) + mock_response_2: List[Dict[str, Any]] = [ + { + "symbol": "COINALPHAHBOT", + "lastPrice": 1000.0 + } + ] + mock_api.get(regex_url_2, body=json.dumps(mock_response_2)) + url_3 = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url_3 = re.compile(f"^{url_3}".replace(".", r"\.").replace("?", r"\?")) + mock_response_3: List[Dict[str, Any]] = [ + { + "symbol": "XBTUSD", + "lastPrice": 1000.0 + } + ] + mock_api.get(regex_url_3, body=json.dumps(mock_response_3)) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + self.assertTrue(len(self.exchange._trading_rules) > 0) + quant_amount = self.exchange.quantize_order_amount('XBT-USD', Decimal('0.00001'), Decimal('10000')) + self.assertEqual(quant_amount, Decimal('0')) + quant_price = self.exchange.quantize_order_price('COINALPHA-HBOT', Decimal('1')) + self.assertEqual(quant_price, Decimal('1.0')) + quant_amount = self.exchange.quantize_order_amount('COINALPHA-HBOT', Decimal('0.00001')) + self.assertEqual(quant_amount, Decimal('0')) + self.exchange._trading_pairs.remove("XBT-USD") + + def test_format_trading_rules(self): + margin_asset = self.quote_asset + min_order_size = 1 + min_price_increment = 2 + min_base_amount_increment = 1 + mocked_response = self._get_exchange_info_mock_response() + + task = self.ev_loop.create_task(self.exchange._format_trading_rules(mocked_response)) + trading_rules = self.async_run_with_timeout(task) + + self.assertEqual(1, len(trading_rules)) + + trading_rule = trading_rules[0] + + self.assertEqual(min_order_size, trading_rule.min_order_size) + self.assertEqual(min_price_increment, trading_rule.min_price_increment) + self.assertEqual(min_base_amount_increment, trading_rule.min_base_amount_increment) + self.assertEqual(margin_asset, trading_rule.buy_order_collateral_token) + self.assertEqual(margin_asset, trading_rule.sell_order_collateral_token) + + def test_get_collateral_token(self): + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + task = self.ev_loop.create_task(self.exchange._format_trading_rules(mocked_response)) + trading_rules = self.async_run_with_timeout(task) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + + self.assertEqual(margin_asset, self.exchange.get_buy_collateral_token(self.trading_pair)) + self.assertEqual(margin_asset, self.exchange.get_sell_collateral_token(self.trading_pair)) + + def test_buy_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + partial_fill = { + "table": "order", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 9999, + } + ] + } + logger_len = len(self.order_filled_logger.event_log) + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + complete_fill = { + "table": "order", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.0, + "ordStatus": "Filled", + "avgPx": 9999, + } + ] + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) + + def test_sell_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + partial_fill = { + "table": "order", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + } + logger_len = len(self.order_filled_logger.event_log) + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + self.assertEqual(logger_len + 1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + complete_fill = { + "table": "order", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.0, + "ordStatus": "Filled", + "avgPx": 10001, + } + ] + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + self.assertEqual(1, len(self.sell_order_completed_logger.event_log)) + + def test_order_event_with_cancelled_status_marks_order_as_cancelled(self): + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "table": "order", + "data": [ + { + "clOrdID": "OID1", + "ordStatus": "Canceled", + "leavesQty": 1 + } + ] + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + + event = self.order_cancelled_logger.event_log[0] + + self.assertEqual(event.order_id, order.client_order_id) + + def test_user_stream_event_listener_raises_cancelled_error(self): + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = asyncio.CancelledError + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.test_task) + + @aioresponses() + @patch("hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_derivative." + "BitmexPerpetualDerivative.current_timestamp") + def test_update_order_status_successful(self, req_mock, mock_timestamp): + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + order = [ + { + "clOrdID": "OID1", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + in_flight_orders = self.exchange._in_flight_orders + + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) + self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) + self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) + self.assertEqual(10000, in_flight_orders["OID1"].price) + self.assertEqual(1, in_flight_orders["OID1"].amount) + self.assertEqual(BitmexPerpetualOrderStatus.PartiallyFilled, in_flight_orders["OID1"].state) + self.assertEqual(1, in_flight_orders["OID1"].leverage) + self.assertEqual(PositionAction.OPEN, in_flight_orders["OID1"].position) + + # Processing an order update should not impact trade fill information + self.assertEqual(Decimal("0.5"), in_flight_orders["OID1"].executed_amount_base) + self.assertEqual(Decimal("5000.5"), in_flight_orders["OID1"].executed_amount_quote) + + @aioresponses() + @patch("hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_derivative." + "BitmexPerpetualDerivative.current_timestamp") + def test_update_order_status_failure_old_order(self, req_mock, mock_timestamp): + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=0, + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + order = [ + { + "clOrdID": "OID2", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + self.assertEqual(len(self.exchange.in_flight_orders), 0) + + @aioresponses() + def test_set_leverage_successful(self, req_mock): + trading_pair = f"{self.base_asset}-{self.quote_asset}" + leverage = 21 + + self.exchange.set_leverage(trading_pair, leverage) + + self.assertEqual(self.exchange._leverage[trading_pair], 21) + + @aioresponses() + @patch("hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_derivative." + "LatchingEventResponder.wait_for_completion") + def test_cancel_all_successful(self, mocked_api, mock_wait): + mock_wait.return_value = True + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"code": 200, "msg": "success"} + mocked_api.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.CLOSE, + ) + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID2", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + self.assertTrue("OID2" in self.exchange._in_flight_orders) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(timeout_seconds=1)) + + order_cancelled_events = self.order_cancelled_logger.event_log + + self.assertEqual(0, len(order_cancelled_events)) + self.assertEqual(2, len(cancellation_results)) + self.assertEqual("OID1", cancellation_results[0].order_id) + self.assertEqual("OID2", cancellation_results[1].order_id) + + @aioresponses() + def test_cancel_unknown_new_order(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"error": {"message": "order not found", "name": "Not Found"}} + req_mock.delete(regex_url, status=400, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + tracked_order = self.exchange.in_flight_orders.get("OID1") + tracked_order.state = BitmexPerpetualOrderStatus.New + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + + try: + self.async_run_with_timeout(self.exchange.cancel_order("OID1")) + except Exception as e: + self.assertEqual(str(e), f"order {tracked_order.client_order_id} does not yet exist on the exchange and could not be cancelled.") + + @aioresponses() + def test_cancel_unknown_old_order(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"error": {"message": "order not found", "name": "Not Found"}} + req_mock.delete(regex_url, status=400, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=0.0, + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + leverage=1, + position=PositionAction.OPEN, + ) + + tracked_order = self.exchange.in_flight_orders.get("OID1") + tracked_order.state = BitmexPerpetualOrderStatus.New + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + try: + cancellation_result = self.async_run_with_timeout(self.exchange.cancel_order("OID1")) + except Exception: + pass + + self.assertFalse(cancellation_result) + + self.assertTrue("OID1" not in self.exchange._in_flight_orders) + + @aioresponses() + def test_create_order_successful(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "ordStatus": "New", + "orderID": "8886774"} + req_mock.post(regex_url, body=json.dumps(create_response)) + + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + + self.async_run_with_timeout(self.exchange.execute_buy(order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("10000"))) + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + + @aioresponses() + def test_create_order_exception(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "ordStatus": "Canceled", + "orderID": "8886774"} + + req_mock.post(regex_url, body=json.dumps(create_response)) + + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + + self.async_run_with_timeout(self.exchange.execute_sell(order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT, + position_action=PositionAction.OPEN, + price=Decimal("1010"))) + + self.assertTrue("OID1" not in self.exchange._in_flight_orders) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexPerpetualOrderStatus.Canceled + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexPerpetualOrderStatus.Filled + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexPerpetualOrderStatus.FAILURE + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.exchange.in_flight_orders) + self.assertNotIn("OID2", self.exchange.in_flight_orders) + self.assertNotIn("OID3", self.exchange.in_flight_orders) + self.assertNotIn("OID4", self.exchange.in_flight_orders) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 4 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + @aioresponses() + def test_update_balances(self, mock_api): + url = web_utils.rest_url(CONSTANTS.ACCOUNT_INFO_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = [ + { + "currency": "USDT", + "amount": 100000000, + "pendingCredit": 2000000, + "pendingDebit": 0 + }, + { + "currency": "XBT", + "amount": 1000000000, + "pendingCredit": 20000000, + "pendingDebit": 0 + } + ] + + mock_api.get(regex_url, body=json.dumps(response)) + + url_2 = web_utils.rest_url(CONSTANTS.TOKEN_INFO_URL, domain=self.domain) + regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace("?", r"\?")) + + response_2 = [ + { + "asset": "USDT", + "scale": 6 + }, + { + "asset": "XBT", + "scale": 8, + } + ] + + mock_api.get(regex_url_2, body=json.dumps(response_2)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("98"), available_balances["USDT"]) + self.assertEqual(Decimal("9.8"), available_balances["XBT"]) + self.assertEqual(Decimal("100"), total_balances["USDT"]) + self.assertEqual(Decimal("10"), total_balances["XBT"]) + + def test_adjust_quote_based_amounts(self): + self.exchange._trading_pairs.append("XBT-USD") + mocked_response = self._get_exchange_info_mock_response() + + task = self.ev_loop.create_task(self.exchange._format_trading_rules(mocked_response)) + trading_rules = self.async_run_with_timeout(task) + self.exchange._trading_rules["XBT-USD"] = trading_rules[1] + base, quote = self.exchange.adjust_quote_based_amounts("XBT-USD", Decimal('1000'), Decimal('10')) + self.exchange._trading_pairs.remove("XBT-USD") diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_book_tracker.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_book_tracker.py new file mode 100644 index 0000000..f9d4e4f --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_book_tracker.py @@ -0,0 +1,198 @@ +import asyncio +import time +import unittest +from collections import deque +from typing import Deque, Optional, Union + +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_book import BitmexPerpetualOrderBook +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_book_tracker import ( + BitmexPerpetualOrderBookTracker, +) +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitmexPerpetualOrderBookTrackerUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "USD" + cls.domain = "bitmex_perpetual_testnet" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ev_loop = asyncio.get_event_loop() + + def setUp(self) -> None: + super().setUp() + self.tracker: BitmexPerpetualOrderBookTracker = BitmexPerpetualOrderBookTracker(trading_pairs=[self.trading_pair]) + self.tracking_task: Optional[asyncio.Task] = None + + # Simulate start() + self.tracker._order_books[self.trading_pair] = BitmexPerpetualOrderBook() + self.tracker._tracking_message_queues[self.trading_pair] = asyncio.Queue() + self.tracker._past_diffs_windows[self.trading_pair] = deque() + self.tracker._order_books_initialized.set() + + def tearDown(self) -> None: + self.tracking_task and self.tracking_task.cancel() + super().tearDown() + + def _simulate_message_enqueue(self, message_queue: Union[asyncio.Queue, Deque], msg: OrderBookMessage): + if isinstance(message_queue, asyncio.Queue): + self.ev_loop.run_until_complete(message_queue.put(msg)) + elif isinstance(message_queue, Deque): + message_queue.append(msg) + else: + raise NotImplementedError + + def test_exchange_name(self): + self.assertEqual("bitmex_perpetual", self.tracker.exchange_name) + + def test_order_book_diff_router_trading_pair_not_found_append_to_saved_message_queue(self): + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + self.tracker._tracking_message_queues.clear() + + task = self.ev_loop.create_task( + self.tracker._track_single_book("COINALPHA-USD") + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertEqual(0, len(self.tracker._tracking_message_queues)) + task.cancel() + + def test_order_book_diff_router_snapshot_uid_above_diff_message_update_id(self): + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + task = self.ev_loop.create_task( + self.tracker._track_single_book("COINALPHA-USD") + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + task.cancel() + + def test_order_book_diff_router_snapshot_uid_below_diff_message_update_id(self): + # Updates the snapshot_uid + self.tracker.order_books[self.trading_pair].apply_snapshot([], [], 2) + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + task = self.ev_loop.create_task( + self.tracker._order_book_diff_router() + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertEqual(0, self.tracker._tracking_message_queues[self.trading_pair].qsize()) + task.cancel() + + def test_track_single_book_snapshot_message_no_past_diffs(self): + snapshot_msg: OrderBookMessage = BitmexPerpetualOrderBook.snapshot_message_from_exchange( + msg={ + "trading_pair": "COINALPHA-USD", + "update_id": 2, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=time.time() + ) + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertTrue(1 < self.tracker.order_books[self.trading_pair].snapshot_uid) + + def test_track_single_book_snapshot_message_with_past_diffs(self): + past_diff_msg: OrderBookMessage = BitmexPerpetualOrderBook.diff_message_from_exchange( + msg={ + "lastUpdateId": 1, + "data_dict": { + "symbol": "COINALPHA-USD", + "bids": [ + ["4.00000100", "431.00000000"] + ], + "asks": [ + ["4.00000300", "12.00000000"] + ] + } + }, + timestamp=time.time() + ) + snapshot_msg: OrderBookMessage = BitmexPerpetualOrderBook.snapshot_message_from_exchange( + msg={ + "trading_pair": "COINALPHA-USD", + "update_id": 2, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=time.time() + ) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self._simulate_message_enqueue(self.tracker._past_diffs_windows[self.trading_pair], past_diff_msg) + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg) + + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertTrue(1 < self.tracker.order_books[self.trading_pair].snapshot_uid) + + def test_track_single_book_diff_message(self): + diff_msg: OrderBookMessage = BitmexPerpetualOrderBook.diff_message_from_exchange( + msg={ + "lastUpdateId": 1, + "data_dict": { + "symbol": "COINALPHA-USD", + "bids": [ + ["4.00000100", "431.00000000"] + ], + "asks": [ + ["4.00000300", "12.00000000"] + ] + } + }, + timestamp=time.time() + ) + + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], diff_msg) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_status.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_status.py new file mode 100644 index 0000000..0801f1e --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_order_status.py @@ -0,0 +1,33 @@ +import unittest + +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_order_status import BitmexPerpetualOrderStatus + + +class BitmexPerpetualOrderStatusUnitTests(unittest.TestCase): + def test_ge(self): + status_1 = BitmexPerpetualOrderStatus.New + status_2 = BitmexPerpetualOrderStatus.PartiallyFilled + + self.assertTrue(status_2 >= status_1) + self.assertEqual(status_2.__ge__(1), NotImplemented) + + def test_gt(self): + status_1 = BitmexPerpetualOrderStatus.Canceled + status_2 = BitmexPerpetualOrderStatus.FAILURE + + self.assertTrue(status_2 > status_1) + self.assertEqual(status_2.__gt__(1), NotImplemented) + + def test_le(self): + status_1 = BitmexPerpetualOrderStatus.New + status_2 = BitmexPerpetualOrderStatus.Canceled + + self.assertTrue(status_1 <= status_2) + self.assertEqual(status_2.__le__(1), NotImplemented) + + def test_lt(self): + status_1 = BitmexPerpetualOrderStatus.PartiallyFilled + status_2 = BitmexPerpetualOrderStatus.FAILURE + + self.assertTrue(status_1 < status_2) + self.assertEqual(status_2.__lt__(1), NotImplemented) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..4b71d47 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_user_stream_data_source.py @@ -0,0 +1,193 @@ +import asyncio +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, patch + +import ujson +from aioresponses.core import aioresponses + +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import BitmexPerpetualAuth +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_user_stream_data_source import ( + BitmexPerpetualUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class BitmexPerpetualUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.TESTNET_DOMAIN + + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.emulated_time = 1640001112.223 + self.auth = BitmexPerpetualAuth(api_key=self.api_key, + api_secret=self.secret_key) + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.data_source = BitmexPerpetualUserStreamDataSource( + auth=self.auth, domain=self.domain, throttler=self.throttler + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mock_done_event = asyncio.Event() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _mock_responses_done_callback(self, *_, **__): + self.mock_done_event.set() + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _error_response(self) -> Dict[str, Any]: + resp = {"code": "ERROR CODE", "msg": "ERROR MESSAGE"} + + return resp + + def _simulate_user_update_event(self): + # Order Trade Update + resp = { + "table": "order", + "data": [{ + "orderID": "1", + "clordID": "2", + "price": 20, + "orderQty": 100, + "symbol": "COINALPHAHBOT", + "side": "Sell", + "leavesQty": "1" + }], + } + return ujson.dumps(resp) + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_create_websocket_connection_log_exception(self, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR.", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_create_websocket_connection_failed(self, mock_api, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR.", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + mock_ws.return_value.closed = False + mock_ws.return_value.close.side_effect = Exception + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except Exception: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_successful(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._simulate_user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertTrue(msg, self._simulate_user_update_event) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_utils.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_utils.py new file mode 100644 index 0000000..101332c --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_utils.py @@ -0,0 +1,59 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Dict, List + +from aioresponses.core import aioresponses + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_utils as utils +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS + + +class BitmexPerpetualUtilsUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls._ev_loop = asyncio.get_event_loop() + + @aioresponses() + def test_get_trading_pair_index_and_tick_size(self, mock_api): + url = f"{CONSTANTS.PERPETUAL_BASE_URL}{CONSTANTS.EXCHANGE_INFO_URL}?count=500&start=0" + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "COINALPHAHBOT", + "tickSize": 5 + }, + ] + mock_api.get(url, status=200, body=json.dumps(mock_response)) + id_tick = self._ev_loop.run_until_complete(utils.get_trading_pair_index_and_tick_size("COINALPHAHBOT")) + self.assertEqual(id_tick.index, 0) + self.assertEqual(id_tick.tick_size, 5) + + @aioresponses() + def test_get_trading_pair_size_currency(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain="bitmex_perpetual" + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: Dict[str, Any] = [ + { + "symbol": "COINALPHAHBOT", + "rootSymbol": "COINALPHA", + "quoteCurrency": "HBOT", + "settlCurrency": "HBOT", + "lotSize": 1.0, + "tickSize": 0.0001, + "minProvideSize": 0.001, + "maxOrderQty": 1000000, + "underlyingToPositionMultiplier": 100, + "positionCurrency": "COINALPHA" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + size_currency = self._ev_loop.run_until_complete(utils.get_trading_pair_size_currency("COINALPHAHBOT")) + self.assertTrue(size_currency.is_base) diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_web_utils.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_web_utils.py new file mode 100644 index 0000000..92714d5 --- /dev/null +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_web_utils.py @@ -0,0 +1,80 @@ +import asyncio +import unittest +from typing import Awaitable + +import hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils as web_utils +import hummingbot.connector.derivative.bitmex_perpetual.constants as CONSTANTS +from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_web_utils import BitmexPerpetualRESTPreProcessor +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitmexPerpetualWebUtilsUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.pre_processor = BitmexPerpetualRESTPreProcessor() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_bitmex_perpetual_rest_pre_processor_non_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/x-www-form-urlencoded") + + def test_bitmex_perpetual_rest_pre_processor_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/json") + + def test_rest_url_main_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.PERPETUAL_BASE_URL}{path_url}" + self.assertEqual(expected_url, web_utils.rest_url(path_url)) + self.assertEqual(expected_url, web_utils.rest_url(path_url)) + + def test_rest_url_testnet_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.TESTNET_BASE_URL}{path_url}" + self.assertEqual( + expected_url, web_utils.rest_url(path_url=path_url, domain="testnet") + ) + + def test_wss_url_main_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.PERPETUAL_WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint)) + + def test_wss_url_testnet_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.TESTNET_WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint, domain="testnet")) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory() + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(1, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/__init__.py b/test/hummingbot/connector/derivative/bybit_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..b370c2c --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_api_order_book_data_source.py @@ -0,0 +1,748 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +import pandas as pd +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bybit_perpetual import bybit_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_api_order_book_data_source import ( + BybitPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_derivative import BybitPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BybitPerpetualAPIOrderBookDataSourceTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "bybit_perpetual_testnet" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BybitPerpetualDerivative( + client_config_map, + bybit_perpetual_api_key="", + bybit_perpetual_secret_key="", + trading_pairs=[self.trading_pair], + trading_required=False, + domain=self.domain, + ) + self.data_source = BybitPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}{self.quote_asset}": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_rest_snapshot_msg(self) -> Dict: + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "symbol": self.ex_trading_pair, + "price": "9487", + "size": 336241, + "side": "Buy" + }, + { + "symbol": self.ex_trading_pair, + "price": "9487.5", + "size": 522147, + "side": "Sell" + } + ], + "time_now": "1567108756.834357" + } + + def get_ws_snapshot_msg(self) -> Dict: + return { + "topic": f"orderBook_200.100ms.{self.ex_trading_pair}", + "type": "snapshot", + "data": [ + { + "price": "2999.00", + "symbol": self.ex_trading_pair, + "id": 29990000, + "side": "Buy", + "size": 9 + }, + { + "price": "3001.00", + "symbol": self.ex_trading_pair, + "id": 30010000, + "side": "Sell", + "size": 10 + } + ], + "cross_seq": 11518, + "timestamp_e6": 1555647164875373 + } + + def get_ws_diff_msg(self) -> Dict: + return { + "topic": f"orderBook_200.100ms.{self.ex_trading_pair}", + "type": "delta", + "data": { + "delete": [ + { + "price": "3001.00", + "symbol": self.ex_trading_pair, + "id": 30010000, + "side": "Sell" + } + ], + "update": [ + { + "price": "2999.00", + "symbol": self.ex_trading_pair, + "id": 29990000, + "side": "Buy", + "size": 8 + } + ], + "insert": [ + { + "price": "2998.00", + "symbol": self.ex_trading_pair, + "id": 29980000, + "side": "Buy", + "size": 8 + } + ], + "transactTimeE6": 0 + }, + "cross_seq": 11519, + "timestamp_e6": 1555647221331673 + } + + def get_funding_info_msg(self) -> Dict: + return { + "topic": f"instrument_info.100ms.{self.ex_trading_pair}", + "type": "snapshot", + "data": { + "id": 1, + "symbol": self.ex_trading_pair, + "last_price_e4": 81165000, + "last_price": "81165000", + "bid1_price_e4": 400025000, + "bid1_price": "400025000", + "ask1_price_e4": 475450000, + "ask1_price": "475450000", + "last_tick_direction": "ZeroPlusTick", + "prev_price_24h_e4": 81585000, + "prev_price_24h": "81585000", + "price_24h_pcnt_e6": -5148, + "high_price_24h_e4": 82900000, + "high_price_24h": "82900000", + "low_price_24h_e4": 79655000, + "low_price_24h": "79655000", + "prev_price_1h_e4": 81395000, + "prev_price_1h": "81395000", + "price_1h_pcnt_e6": -2825, + "mark_price_e4": 81178500, + "mark_price": "81178500", + "index_price_e4": 81172800, + "index_price": "81172800", + "open_interest": 154418471, + "open_value_e8": 1997561103030, + "total_turnover_e8": 2029370141961401, + "turnover_24h_e8": 9072939873591, + "total_volume": 175654418740, + "volume_24h": 735865248, + "funding_rate_e6": 100, + "predicted_funding_rate_e6": 100, + "cross_seq": 1053192577, + "created_at": "2018-11-14T16:33:26Z", + "updated_at": "2020-01-12T18:25:16Z", + "next_funding_time": "2020-01-13T00:00:00Z", + + "countdown_hour": 6, + "funding_rate_interval": 8 + }, + "cross_seq": 9267002, + "timestamp_e6": 1615794861826248 + } + + def get_funding_info_event(self): + return { + "topic": f"instrument_info.100ms.{self.ex_trading_pair}", + "type": "delta", + "data": { + "delete": [], + "update": [ + { + "id": 1, + "symbol": self.ex_trading_pair, + "prev_price_24h_e4": 81565000, + "prev_price_24h": "81565000", + "price_24h_pcnt_e6": -4904, + "open_value_e8": 2000479681106, + "total_turnover_e8": 2029370495672976, + "turnover_24h_e8": 9066215468687, + "volume_24h": 735316391, + "cross_seq": 1053192657, + "created_at": "2018-11-14T16:33:26Z", + "updated_at": "2020-01-12T18:25:25Z", + "index_price": 123, + "mark_price": 234, + "next_funding_time": "2020-01-12T18:25:25Z", + "predicted_funding_rate_e6": 456, + } + ], + "insert": [] + }, + "cross_seq": 1053192657, + "timestamp_e6": 1578853525691123 + } + + def get_general_info_rest_msg(self): + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "symbol": self.ex_trading_pair, + "bid_price": "7230", + "ask_price": "7230.5", + "last_price": "7230.00", + "last_tick_direction": "ZeroMinusTick", + "prev_price_24h": "7163.00", + "price_24h_pcnt": "0.009353", + "high_price_24h": "7267.50", + "low_price_24h": "7067.00", + "prev_price_1h": "7209.50", + "price_1h_pcnt": "0.002843", + "mark_price": "7230.31", + "index_price": "7230.14", + "open_interest": 117860186, + "open_value": "16157.26", + "total_turnover": "3412874.21", + "turnover_24h": "10864.63", + "total_volume": 28291403954, + "volume_24h": 78053288, + "funding_rate": "0.0001", + "predicted_funding_rate": "0.0001", + "next_funding_time": "2019-12-28T00:00:00Z", + "countdown_hour": 2, + "delivery_fee_rate": "0", + "predicted_delivery_price": "0.00", + "delivery_time": "" + }, + ], + "time_now": "1577484619.817968", + } + + def get_predicted_funding_info(self): + return { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": { + "predicted_funding_rate": 0.0001, + "predicted_funding_fee": 0 + }, + "ext_info": None, + "time_now": "1577447415.583259", + "rate_limit_status": 118, + "rate_limit_reset_ms": 1577447415590, + "rate_limit": 120 + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair, self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_rest_snapshot_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = int(float(resp["time_now"]) * 1e6) + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair, self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_diffs_and_funding_info(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_snapshot_msg() + result_subscribe_funding_info = self.get_funding_info_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_funding_info), + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(3, len(sent_subscription_messages)) + expected_trade_subscription = { + "op": "subscribe", + "args": [f"trade.{self.ex_trading_pair}"], + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "op": "subscribe", + "args": [f"orderBook_200.100ms.{self.ex_trading_pair}"], + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + expected_funding_info_subscription = { + "op": "subscribe", + "args": [f"instrument_info.100ms.{self.ex_trading_pair}"], + } + self.assertEqual(expected_funding_info_subscription, sent_subscription_messages[2]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book, trade and funding info channels...") + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams" + " wss://stream-testnet.bybit.com/realtime. Retrying in 5 seconds...", + ) + ) + + def test_subscribe_to_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_to_channels(mock_ws, [self.trading_pair]) + ) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_to_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_to_channels(mock_ws, [self.trading_pair]) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "topic": f"trade.{self.ex_trading_pair}", + "data": [ + { + "timestamp": "2020-01-12T16:59:59.000Z", + "symbol": self.ex_trading_pair, + "side": "Sell", + "size": 328, + "price": 8098, + "tick_direction": "MinusTick", + "trade_id": "00c706e1-ba52-5bb0-98d0-bf694bdc69f7", + "cross_seq": 1052816407 + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "topic": f"trade.{self.ex_trading_pair}", + "data": [ + { + "timestamp": "2020-01-12T16:59:59.000Z", + "trade_time_ms": 1582793344685, + "symbol": self.ex_trading_pair, + "side": "Sell", + "size": 328, + "price": 8098, + "tick_direction": "MinusTick", + "trade_id": "00c706e1-ba52-5bb0-98d0-bf694bdc69f7", + "cross_seq": 1052816407 + } + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_event["data"][0]["trade_id"], msg.trade_id) + self.assertEqual(trade_event["data"][0]["trade_time_ms"] * 1e-3, msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = self.get_ws_diff_msg() + del incomplete_resp["timestamp_e6"] + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self.get_ws_diff_msg() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(diff_event["timestamp_e6"] * 1e-6, msg.timestamp) + expected_update_id = int(diff_event["timestamp_e6"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(2999.0, bids[0].price) + self.assertEqual(8, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(3001, asks[0].price) + self.assertEqual(0, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint( + endpoint=endpoint, trading_pair=self.trading_pair, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint( + endpoint=endpoint, trading_pair=self.trading_pair, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + msg_queue: asyncio.Queue = asyncio.Queue() + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT + url = web_utils.get_rest_url_for_endpoint( + endpoint=endpoint, trading_pair=self.trading_pair, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_rest_snapshot_msg() + + mock_api.get(regex_url, body=json.dumps(resp)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(float(resp["time_now"]), msg.timestamp) + expected_update_id = int(float(resp["time_now"]) * 1e6) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_funding_info_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_funding_info(msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_funding_info_logs_exception(self): + incomplete_resp = self.get_funding_info_event() + del incomplete_resp["type"] + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public funding info updates from exchange")) + + def test_listen_for_funding_info_successful(self): + funding_info_event = self.get_funding_info_event() + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + msg: FundingInfoUpdate = self.async_run_with_timeout(msg_queue.get()) + funding_update = funding_info_event["data"]["update"][0] + + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_index_price = Decimal(str(funding_update["index_price"])) + self.assertEqual(expected_index_price, msg.index_price) + expected_mark_price = Decimal(str(funding_update["mark_price"])) + self.assertEqual(expected_mark_price, msg.mark_price) + expected_funding_time = int( + pd.Timestamp(str(funding_update["next_funding_time"]), tz="UTC").timestamp() + ) + self.assertEqual(expected_funding_time, msg.next_funding_utc_timestamp) + expected_rate = Decimal(str(funding_update["predicted_funding_rate_e6"])) * Decimal(1e-6) + self.assertEqual(expected_rate, msg.rate) + + @aioresponses() + def test_get_funding_info(self, mock_api): + endpoint = CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair, self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + general_resp = self.get_general_info_rest_msg() + mock_api.get(regex_url, body=json.dumps(general_resp)) + + endpoint = CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair, self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + funding_resp = self.get_predicted_funding_info() + mock_api.get(regex_url, body=json.dumps(funding_resp)) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + general_info_result = general_resp["result"][0] + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(Decimal(str(general_info_result["index_price"])), funding_info.index_price) + self.assertEqual(Decimal(str(general_info_result["mark_price"])), funding_info.mark_price) + expected_utc_timestamp = int(pd.Timestamp(general_info_result["next_funding_time"]).timestamp()) + self.assertEqual(expected_utc_timestamp, funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(str(funding_resp["result"]["predicted_funding_rate"])), funding_info.rate) diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_auth.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_auth.py new file mode 100644 index 0000000..324614a --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_auth.py @@ -0,0 +1,81 @@ +import asyncio +import hashlib +import hmac +import time +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth import BybitPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class BybitPerpetualAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + self.auth = BybitPerpetualAuth(api_key=self.api_key, secret_key=self.secret_key) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _get_timestamp(self): + return str(int(time.time() * 1e3)) + + def _get_expiration_timestamp(self): + return str(int(time.time() + 1 * 1e3)) + + @patch("hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth.BybitPerpetualAuth._get_timestamp") + def test_add_auth_to_rest_request(self, ts_mock: MagicMock): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params=params, + is_auth_required=True, + ) + timestamp = self._get_timestamp() + ts_mock.return_value = timestamp + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + raw_signature = "api_key=" + self.api_key + "&one=1" + "×tamp=" + timestamp + expected_signature = hmac.new(self.secret_key.encode("utf-8"), + raw_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + params = request.params + + self.assertEqual(4, len(params)) + self.assertEqual("1", params.get("one")) + self.assertEqual(timestamp, params.get("timestamp")) + self.assertEqual(self.api_key, params.get("api_key")) + self.assertEqual(expected_signature, params.get("sign")) + + @patch( + "hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth" + ".BybitPerpetualAuth._get_expiration_timestamp" + ) + def test_ws_auth_payload(self, ts_mock: MagicMock): + expires = self._get_expiration_timestamp() + ts_mock.return_value = expires + payload = self.auth.get_ws_auth_payload() + + raw_signature = "GET/realtime" + expires + expected_signature = hmac.new(self.secret_key.encode("utf-8"), + raw_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + + self.assertEqual(3, len(payload)) + self.assertEqual(self.api_key, payload[0]) + self.assertEqual(expires, payload[1]) + self.assertEqual(expected_signature, payload[2]) + + def test_no_auth_added_to_ws_request(self): + payload = {"one": "1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_derivative.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_derivative.py new file mode 100644 index 0000000..2974dfe --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_derivative.py @@ -0,0 +1,1573 @@ +import asyncio +import json +import re +from copy import deepcopy +from decimal import Decimal +from itertools import chain, product +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +import pandas as pd +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_derivative import BybitPerpetualDerivative +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class BybitPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.quote_asset = "USDT" # linear + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + cls.non_linear_quote_asset = "USD" + cls.non_linear_trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.non_linear_quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + return url + + @property + def latest_prices_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT, trading_pair=self.trading_pair + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SERVER_TIME_PATH_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + return url + + @property + def order_creation_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.PLACE_ACTIVE_ORDER_PATH_URL, trading_pair=self.trading_pair + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def balance_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.GET_WALLET_BALANCE_PATH_URL) + return url + + @property + def funding_info_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT, trading_pair=self.trading_pair + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_LAST_FUNDING_RATE_PATH_URL, trading_pair=self.trading_pair + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def all_symbols_request_mock_response(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "name": self.exchange_trading_pair, + "alias": self.exchange_trading_pair, + "status": "Trading", + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "price_scale": 2, + "taker_fee": "0.00075", + "maker_fee": "-0.00025", + "leverage_filter": { + "min_leverage": 1, + "max_leverage": 100, + "leverage_step": "0.01" + }, + "price_filter": { + "min_price": "0.5", + "max_price": "999999.5", + "tick_size": "0.5" + }, + "lot_size_filter": { + "max_trading_qty": 1000000, + "min_trading_qty": 1, + "qty_step": 1 + } + }, + ], + "time_now": "1615801223.589808", + } + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "symbol": self.exchange_trading_pair, + "bid_price": "7230", + "ask_price": "7230.5", + "last_price": str(self.expected_latest_price), + "last_tick_direction": "ZeroMinusTick", + "prev_price_24h": "7163.00", + "price_24h_pcnt": "0.009353", + "high_price_24h": "7267.50", + "low_price_24h": "7067.00", + "prev_price_1h": "7209.50", + "price_1h_pcnt": "0.002843", + "mark_price": "7230.31", + "index_price": "7230.14", + "open_interest": 117860186, + "open_value": "16157.26", + "total_turnover": "3412874.21", + "turnover_24h": "10864.63", + "total_volume": 28291403954, + "volume_24h": 78053288, + "funding_rate": "0.0001", + "predicted_funding_rate": "0.0001", + "next_funding_time": "2019-12-28T00:00:00Z", + "countdown_hour": 2, + "delivery_fee_rate": "0", + "predicted_delivery_price": "0.00", + "delivery_time": "" + } + ], + "time_now": "1577484619.817968" + } + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "name": self.exchange_trading_pair, + "alias": self.exchange_trading_pair, + "status": "Trading", + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "price_scale": 2, + "taker_fee": "0.00075", + "maker_fee": "-0.00025", + "leverage_filter": { + "min_leverage": 1, + "max_leverage": 100, + "leverage_step": "0.01" + }, + "price_filter": { + "min_price": "0.5", + "max_price": "999999.5", + "tick_size": "0.5" + }, + "lot_size_filter": { + "max_trading_qty": 1000000, + "min_trading_qty": 1, + "qty_step": 1 + } + }, + { + "name": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "alias": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "status": "Closed", + "base_currency": "INVALID", + "quote_currency": "PAIR", + "price_scale": 2, + "taker_fee": "0.00075", + "maker_fee": "-0.00025", + "leverage_filter": { + "min_leverage": 1, + "max_leverage": 100, + "leverage_step": "0.01" + }, + "price_filter": { + "min_price": "0.5", + "max_price": "999999.5", + "tick_size": "0.5" + }, + "lot_size_filter": { + "max_trading_qty": 1000000, + "min_trading_qty": 1, + "qty_step": 1 + } + }, + ], + "time_now": "1615801223.589808" + } + return "INVALID-PAIR", mock_response + + @property + def network_status_request_successful_mock_response(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": {}, + "time_now": "1577444332.192859" + } + return mock_response + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": [ + { + "name": self.exchange_trading_pair, + "alias": self.exchange_trading_pair, + "status": "Trading", + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "price_scale": 2, + "taker_fee": "0.00075", + "maker_fee": "-0.00025", + }, + ], + "time_now": "1615801223.589808", + } + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + "user_id": 1, + "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b", + "symbol": self.exchange_trading_pair, + "side": "Buy", + "order_type": "Limit", + "price": 46000, + "qty": 1, + "time_in_force": "GoodTillCancel", + "order_status": "Created", + "last_exec_time": 0, + "last_exec_price": 0, + "leaves_qty": 1, + "cum_exec_qty": 0, + "cum_exec_value": 0, + "cum_exec_fee": 0, + "reject_reason": "", + "order_link_id": get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_BROKER_ID, + max_id_len=CONSTANTS.MAX_ID_LEN, + ), + "created_at": "2019-11-30T11:03:43.452Z", + "updated_at": "2019-11-30T11:03:43.455Z" + }, + "time_now": "1575111823.458705", + "rate_limit_status": 98, + "rate_limit_reset_ms": 1580885703683, + "rate_limit": 100 + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + mock_response = { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + self.base_asset: { + "equity": 15, + "available_balance": 10, + "used_margin": 0.00012529, + "order_margin": 0.00012529, + "position_margin": 0, + "occ_closing_fee": 0, + "occ_funding_fee": 0, + "wallet_balance": 15, + "realised_pnl": 0, + "unrealised_pnl": 2, + "cum_realised_pnl": 0, + "given_cash": 0, + "service_cash": 0 + }, + self.quote_asset: { + "equity": 2000, + "available_balance": 2000, + "used_margin": 49500, + "order_margin": 49500, + "position_margin": 0, + "occ_closing_fee": 0, + "occ_funding_fee": 0, + "wallet_balance": 2000, + "realised_pnl": 0, + "unrealised_pnl": 0, + "cum_realised_pnl": 0, + "given_cash": 0, + "service_cash": 0 + } + }, + "time_now": "1578284274.816029", + "rate_limit_status": 98, + "rate_limit_reset_ms": 1580885703683, + "rate_limit": 100 + } + return mock_response + + @property + def balance_request_mock_response_only_base(self): + mock_response = self.balance_request_mock_response_for_base_and_quote + del mock_response["result"][self.quote_asset] + return mock_response + + @property + def balance_event_websocket_update(self): + mock_response = { + "topic": "wallet", + "data": [ + { + "available_balance": "10", + "wallet_balance": "15" + } + ] + } + return mock_response + + @property + def non_linear_balance_event_websocket_update(self): + mock_response = { + "topic": "wallet", + "data": [ + { + "user_id": 738713, + "coin": self.base_asset, + "available_balance": "10", + "wallet_balance": "15" + }, + { + "user_id": 738713, + "coin": self.non_linear_quote_asset, + "available_balance": "20", + "wallet_balance": "25" + }, + ] + } + return mock_response + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def empty_funding_payment_mock_response(self): + return { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": None, + "ext_info": None, + "time_now": "1577446900.717204", + "rate_limit_status": 119, + "rate_limit_reset_ms": 1577446900724, + "rate_limit": 120 + } + + @property + def funding_payment_mock_response(self): + return { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": { + "symbol": self.exchange_trading_pair, + "side": "Buy", + "size": float(self.target_funding_payment_payment_amount / self.target_funding_payment_funding_rate), + "funding_rate": float(self.target_funding_payment_funding_rate), + "exec_fee": "0.0001", + "exec_time": self.target_funding_payment_timestamp_str, + }, + "ext_info": None, + "time_now": "1577446900.717204", + "rate_limit_status": 119, + "rate_limit_reset_ms": 1577446900724, + "rate_limit": 120 + } + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError # test is overwritten + + @property + def target_funding_info_next_funding_utc_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_info_next_funding_utc_str_ws_updated(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp_ws_updated) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_payment_timestamp_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_payment_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def funding_info_mock_response(self): + mock_response = self.latest_prices_request_mock_response + funding_info = mock_response["result"][0] + funding_info["index_price"] = self.target_funding_info_index_price + funding_info["mark_price"] = self.target_funding_info_mark_price + funding_info["next_funding_time"] = self.target_funding_info_next_funding_utc_str + funding_info["predicted_funding_rate"] = self.target_funding_info_rate + return mock_response + + @property + def get_predicted_funding_info(self): + return { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": { + "predicted_funding_rate": 3, + "predicted_funding_fee": 0 + }, + "ext_info": None, + "time_now": "1577447415.583259", + "rate_limit_status": 118, + "rate_limit_reset_ms": 1577447415590, + "rate_limit": 120 + } + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + @property + def expected_trading_rule(self): + trading_rules_resp = self.trading_rules_request_mock_response["result"][0] + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(trading_rules_resp["lot_size_filter"]["min_trading_qty"])), + max_order_size=Decimal(str(trading_rules_resp["lot_size_filter"]["max_trading_qty"])), + min_price_increment=Decimal(str(trading_rules_resp["price_filter"]["tick_size"])), + min_base_amount_increment=Decimal(str(trading_rules_resp["lot_size_filter"]["qty_step"])), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["result"][0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "335fd977-e5a5-4781-b6d0-c772d5bfb95b" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return False + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.1"))], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6" + + @property + def latest_trade_hist_timestamp(self) -> int: + return 1234 + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = BybitPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + trading_pairs=[self.trading_pair], + ) + exchange._last_trade_history_timestamp = self.latest_trade_hist_timestamp + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertEqual("application/json", request_headers["Content-Type"]) + + request_data = request_call.kwargs["params"] + if request_data is None: + request_data = json.loads(request_call.kwargs["data"]) + + self.assertIn("timestamp", request_data) + self.assertIn("api_key", request_data) + self.assertEqual(self.api_key, request_data["api_key"]) + self.assertIn("sign", request_data) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.trade_type.name.capitalize(), request_data["side"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.amount, request_data["qty"]) + self.assertEqual(CONSTANTS.DEFAULT_TIME_IN_FORCE, request_data["time_in_force"]) + self.assertEqual(order.position == PositionAction.CLOSE, request_data["close_on_trigger"]) + self.assertEqual(order.client_order_id, request_data["order_link_id"]) + self.assertEqual(order.position == PositionAction.CLOSE, request_data["reduce_only"]) + self.assertIn("position_idx", request_data) + self.assertEqual(order.order_type.name.capitalize(), request_data["order_type"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.exchange_order_id, request_data["order_id"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["order_link_id"]) + self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(self.latest_trade_hist_timestamp * 1e3, request_params["start_time"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = { + "ret_code": 20000, + "ret_msg": "Could not find order", + } + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.USER_TRADE_RECORDS_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ACTIVE_ORDER_PATH_URL, trading_pair=order.trading_pair + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_POSITION_MODE_URL, trading_pair=self.trading_pair + ) + response = { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": None, + "ext_info": None, + "time_now": "1577477968.175013", + "rate_limit_status": 74, + "rate_limit_reset_ms": 1577477968183, + "rate_limit": 75 + } + mock_api.post(url, body=json.dumps(response), callback=callback) + + return url + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_POSITION_MODE_URL, trading_pair=self.trading_pair + ) + regex_url = re.compile(f"^{url}") + + error_code = 1_000 + error_msg = "Some problem" + mock_response = { + "ret_code": error_code, + "ret_msg": error_msg, + "ext_code": "", + "result": None, + "ext_info": None, + "time_now": "1577477968.175013", + "rate_limit_status": 74, + "rate_limit_reset_ms": 1577477968183, + "rate_limit": 75 + } + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"ret_code <{error_code}> - {error_msg}" + + def configure_failed_set_leverage( + self, + leverage: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL, trading_pair=self.trading_pair + ) + regex_url = re.compile(f"^{url}") + + err_code = 1 + err_msg = "Some problem" + mock_response = { + "ret_code": err_code, + "ret_msg": err_msg, + "ext_code": "", + "result": leverage, + "ext_info": None, + "time_now": "1577477968.175013", + "rate_limit_status": 74, + "rate_limit_reset_ms": 1577477968183, + "rate_limit": 75 + } + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"ret_code <{err_code}> - {err_msg}" + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL, trading_pair=self.trading_pair + ) + regex_url = re.compile(f"^{url}") + + mock_response = { + "ret_code": 0, + "ret_msg": "ok", + "ext_code": "", + "result": leverage, + "ext_info": None, + "time_now": "1577477968.175013", + "rate_limit_status": 74, + "rate_limit_reset_ms": 1577477968183, + "rate_limit": 75 + } + + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "topic": "order", + "data": [ + { + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_link_id": order.client_order_id or "", + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_type": order.order_type.name.capitalize(), + "price": str(order.price), + "qty": float(order.amount), + "time_in_force": "GoodTillCancel", + "create_type": "CreateByUser", + "cancel_type": "", + "order_status": "New", + "leaves_qty": 0, + "cum_exec_qty": 0, + "cum_exec_value": "0", + "cum_exec_fee": "0", + "timestamp": "2022-06-21T07:35:56.505Z", + "take_profit": "18500", + "tp_trigger_by": "LastPrice", + "stop_loss": "22000", + "sl_trigger_by": "LastPrice", + "trailing_stop": "0", + "last_exec_price": "21196.5", + "reduce_only": order.position == PositionAction.CLOSE, + "close_on_trigger": order.position == PositionAction.CLOSE, + } + ] + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "topic": "order", + "data": [ + { + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_link_id": order.client_order_id or "", + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_type": order.order_type.name.capitalize(), + "price": str(order.price), + "qty": float(order.amount), + "time_in_force": "GoodTillCancel", + "create_type": "CreateByUser", + "cancel_type": "", + "order_status": "Cancelled", + "leaves_qty": 0, + "cum_exec_qty": 0, + "cum_exec_value": "0", + "cum_exec_fee": "0.00000567", + "timestamp": "2022-06-21T07:35:56.505Z", + "take_profit": "18500", + "tp_trigger_by": "LastPrice", + "stop_loss": "22000", + "sl_trigger_by": "LastPrice", + "trailing_stop": "0", + "last_exec_price": "21196.5", + "reduce_only": order.position == PositionAction.CLOSE, + "close_on_trigger": order.position == PositionAction.CLOSE, + } + ] + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "topic": "order", + "data": [ + { + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "order_link_id": order.client_order_id or "", + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_type": order.order_type.name.capitalize(), + "price": str(order.price), + "qty": float(order.amount), + "time_in_force": "GoodTillCancel", + "create_type": "CreateByUser", + "cancel_type": "", + "order_status": "Filled", + "leaves_qty": 0, + "cum_exec_qty": float(order.amount), + "cum_exec_value": str(order.price), + "cum_exec_fee": str(self.expected_fill_fee.flat_fees[0].amount), + "timestamp": "2022-06-21T07:35:56.505Z", + "take_profit": "18500", + "tp_trigger_by": "LastPrice", + "stop_loss": "22000", + "sl_trigger_by": "LastPrice", + "trailing_stop": "0", + "last_exec_price": "21196.5", + "reduce_only": order.position == PositionAction.CLOSE, + "close_on_trigger": order.position == PositionAction.CLOSE, + } + ] + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "topic": "execution", + "data": [ + { + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "exec_id": self.expected_fill_trade_id, + "order_link_id": order.client_order_id or "", + "price": str(order.price), + "order_qty": float(order.amount), + "exec_type": "Trade", + "exec_qty": float(order.amount), + "exec_fee": str(self.expected_fill_fee.flat_fees[0].amount), + "leaves_qty": 0, + "is_maker": False, + "trade_time": "2020-01-14T14:07:23.629Z" + } + ] + } + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + position_value = unrealized_pnl + order.amount * order.price * order.leverage + return { + "topic": "position", + "data": [ + { + "user_id": 533285, + "symbol": self.exchange_trading_pair, + "size": float(order.amount), + "side": order.trade_type.name.capitalize(), + "position_value": str(position_value), + "entry_price": str(order.price), + "liq_price": "489", + "bust_price": "489", + "leverage": str(order.leverage), + "order_margin": "0", + "position_margin": "0.39929535", + "available_balance": "0.39753405", + "take_profit": "0", + "stop_loss": "0", + "realised_pnl": "0.00055631", + "trailing_stop": "0", + "trailing_active": "0", + "wallet_balance": "0.40053971", + "risk_id": 1, + "occ_closing_fee": "0.0002454", + "occ_funding_fee": "0", + "auto_add_margin": 1, + "cum_realised_pnl": "0.00055105", + "position_status": "Normal", + "position_seq": 0, + "Isolated": False, + "mode": 0, + "position_idx": 0, + "tp_sl_mode": "Partial", + "tp_order_num": 0, + "sl_order_num": 0, + "tp_free_size_x": 200, + "sl_free_size_x": 200 + } + ] + } + + def funding_info_event_for_websocket_update(self): + return { + "topic": f"instrument_info.100ms.{self.exchange_trading_pair}", + "type": "delta", + "data": { + "delete": [], + "update": [ + { + "id": 1, + "symbol": self.exchange_trading_pair, + "prev_price_24h_e4": 81565000, + "prev_price_24h": "81565000", + "price_24h_pcnt_e6": -4904, + "open_value_e8": 2000479681106, + "total_turnover_e8": 2029370495672976, + "turnover_24h_e8": 9066215468687, + "volume_24h": 735316391, + "cross_seq": 1053192657, + "created_at": "2018-11-14T16:33:26Z", + "updated_at": "2020-01-12T18:25:25Z", + "index_price": self.target_funding_info_index_price_ws_updated, + "mark_price": self.target_funding_info_mark_price_ws_updated, + "next_funding_time": self.target_funding_info_next_funding_utc_str_ws_updated, + "predicted_funding_rate_e6": self.target_funding_info_rate_ws_updated * 1e6, + } + ], + "insert": [] + }, + "cross_seq": 1053192657, + "timestamp_e6": 1578853525691123 + } + + def test_create_order_with_invalid_position_action_raises_value_error(self): + self._simulate_trading_rules_initialized() + + with self.assertRaises(ValueError) as exception_context: + asyncio.get_event_loop().run_until_complete( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="C1", + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("46000"), + position_action=PositionAction.NIL, + ), + ) + + self.assertEqual( + f"Invalid position action {PositionAction.NIL}. Must be one of {[PositionAction.OPEN, PositionAction.CLOSE]}", + str(exception_context.exception) + ) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + non_linear_connector = BybitPerpetualDerivative( + client_config_map=client_config_map, + bybit_perpetual_api_key=self.api_key, + bybit_perpetual_secret_key=self.api_secret, + trading_pairs=[self.non_linear_trading_pair], + ) + non_linear_connector._set_current_timestamp(1640780000) + + balance_event = self.non_linear_balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + self.assertEqual(Decimal("20"), self.exchange.available_balances[self.non_linear_quote_asset]) + self.assertEqual(Decimal("25"), self.exchange.get_balance(self.non_linear_quote_asset)) + + def test_supported_position_modes(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + linear_connector = BybitPerpetualDerivative( + client_config_map=client_config_map, + bybit_perpetual_api_key=self.api_key, + bybit_perpetual_secret_key=self.api_secret, + trading_pairs=[self.trading_pair], + ) + non_linear_connector = BybitPerpetualDerivative( + client_config_map=client_config_map, + bybit_perpetual_api_key=self.api_key, + bybit_perpetual_secret_key=self.api_secret, + trading_pairs=[self.non_linear_trading_pair], + ) + + expected_result = [PositionMode.ONEWAY, PositionMode.HEDGE] + self.assertEqual(expected_result, linear_connector.supported_position_modes()) + + expected_result = [PositionMode.ONEWAY] + self.assertEqual(expected_result, non_linear_connector.supported_position_modes()) + + def test_set_position_mode_nonlinear(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + non_linear_connector = BybitPerpetualDerivative( + client_config_map=client_config_map, + bybit_perpetual_api_key=self.api_key, + bybit_perpetual_secret_key=self.api_secret, + trading_pairs=[self.non_linear_trading_pair], + ) + non_linear_connector.set_position_mode(PositionMode.HEDGE) + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message=f"Position mode {PositionMode.HEDGE} is not supported. Mode not set.", + ) + ) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + non_linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.non_linear_trading_pair) + non_linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.non_linear_trading_pair) + + self.assertEqual(self.non_linear_quote_asset, non_linear_buy_collateral_token) + self.assertEqual(self.non_linear_quote_asset, non_linear_sell_collateral_token) + + def test_get_position_index(self): + perpetual_trading: PerpetualTrading = self.exchange._perpetual_trading + perpetual_trading.set_position_mode(value=PositionMode.ONEWAY) + + for trade_type, position_action in product( + [TradeType.BUY, TradeType.SELL], [PositionAction.OPEN, PositionAction.CLOSE] + ): + position_idx = self.exchange._get_position_idx(trade_type=trade_type, position_action=position_action) + self.assertEqual( + CONSTANTS.POSITION_IDX_ONEWAY, position_idx, msg=f"Failed on {trade_type} and {position_action}." + ) + + perpetual_trading.set_position_mode(value=PositionMode.HEDGE) + + for trade_type, position_action in zip( + [TradeType.BUY, TradeType.SELL], [PositionAction.OPEN, PositionAction.CLOSE] + ): + position_idx = self.exchange._get_position_idx(trade_type=trade_type, position_action=position_action) + self.assertEqual( + CONSTANTS.POSITION_IDX_HEDGE_BUY, position_idx, msg=f"Failed on {trade_type} and {position_action}." + ) + + for trade_type, position_action in zip( + [TradeType.BUY, TradeType.SELL], [PositionAction.CLOSE, PositionAction.OPEN] + ): + position_idx = self.exchange._get_position_idx(trade_type=trade_type, position_action=position_action) + self.assertEqual( + CONSTANTS.POSITION_IDX_HEDGE_SELL, position_idx, msg=f"Failed on {trade_type} and {position_action}." + ) + + for trade_type, position_action in chain( + product([TradeType.RANGE], [PositionAction.CLOSE, PositionAction.OPEN]), + product([TradeType.BUY, TradeType.SELL], [PositionAction.NIL]), + ): + with self.assertRaises(NotImplementedError, msg=f"Failed on {trade_type} and {position_action}."): + self.exchange._get_position_idx(trade_type=trade_type, position_action=position_action) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_first_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + response = self.trading_rules_request_mock_response + results = response["result"] + duplicate = deepcopy(results[0]) + duplicate["name"] = f"{self.exchange_trading_pair}_12345" + duplicate["alias"] = f"{self.exchange_trading_pair}_12345" + duplicate["lot_size_filter"]["min_trading_qty"] = duplicate["lot_size_filter"]["min_trading_qty"] + 1 + results.append(duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_second_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + response = self.trading_rules_request_mock_response + results = response["result"] + duplicate = deepcopy(results[0]) + duplicate["name"] = f"{self.exchange_trading_pair}_12345" + duplicate["alias"] = f"{self.exchange_trading_pair}_12345" + duplicate["lot_size_filter"]["min_trading_qty"] = duplicate["lot_size_filter"]["min_trading_qty"] + 1 + results.insert(0, duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_cannot_resolve(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + response = self.trading_rules_request_mock_response + results = response["result"] + first_duplicate = deepcopy(results[0]) + first_duplicate["name"] = f"{self.exchange_trading_pair}_12345" + first_duplicate["alias"] = f"{self.exchange_trading_pair}_12345" + first_duplicate["lot_size_filter"]["min_trading_qty"] = ( + first_duplicate["lot_size_filter"]["min_trading_qty"] + 1 + ) + second_duplicate = deepcopy(results[0]) + second_duplicate["name"] = f"{self.exchange_trading_pair}_67890" + second_duplicate["alias"] = f"{self.exchange_trading_pair}_67890" + second_duplicate["lot_size_filter"]["min_trading_qty"] = ( + second_duplicate["lot_size_filter"]["min_trading_qty"] + 2 + ) + results.pop(0) + results.append(first_duplicate) + results.append(second_duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange.trading_rules)) + self.assertNotIn(self.trading_pair, self.exchange.trading_rules) + self.assertTrue( + self.is_logged( + log_level="ERROR", + message=( + f"Could not resolve the exchange symbols" + f" {self.exchange_trading_pair}_67890" + f" and {self.exchange_trading_pair}_12345" + ), + ) + ) + + def test_time_synchronizer_related_reqeust_error_detection(self): + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_AUTH_TIMESTAMP_ERROR) + exception = IOError(f"{error_code_str} - Failed to cancel order for timestamp reason.") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_ORDER_NOT_EXISTS) + exception = IOError(f"{error_code_str} - Failed to cancel order because it was not found.") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + endpoint = CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + funding_resp = self.get_predicted_funding_info + mock_api.get(regex_url, body=json.dumps(funding_resp)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp, funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_updates_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + endpoint = CONSTANTS.GET_PREDICTED_FUNDING_RATE_PATH_URL + url = web_utils.get_rest_url_for_endpoint(endpoint, self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + funding_resp = self.get_predicted_funding_info + mock_api.get(regex_url, body=json.dumps(funding_resp)) + + funding_info_event = self.funding_info_event_for_websocket_update() + + event_messages = [funding_info_event, asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + "user_id": 533285, + "order_id": order.exchange_order_id, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_type": order.order_type.name.capitalize(), + "price": float(order.price), + "qty": float(order.amount), + "time_in_force": "GoodTillCancel", + "order_status": "PendingCancel", + "last_exec_time": 1655711524.37661, + "last_exec_price": 0, + "leaves_qty": float(order.amount), + "cum_exec_qty": 0, + "cum_exec_value": 0, + "cum_exec_fee": 0, + "reject_reason": "EC_NoError", + "order_link_id": "IPBTC00001", + "created_at": "2022-06-20T07:52:04.376Z", + "updated_at": "2022-06-20T07:54:35.339Z", + "take_profit": "", + "stop_loss": "", + "tp_trigger_by": "", + "sl_trigger_by": "" + }, + "time_now": "1655711675.340162", + "rate_limit_status": 99, + "rate_limit_reset_ms": 1655711675338, + "rate_limit": 100 + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + "user_id": 533285, + "position_idx": 0, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "order_type": order.order_type.name.capitalize(), + "price": str(order.price), + "qty": float(order.amount), + "time_in_force": "GoodTillCancel", + "order_status": "Filled", + "ext_fields": { + "o_req_num": 1240101436 + }, + "last_exec_time": "1655716503.5852108", + "leaves_qty": 0, + "leaves_value": "0.01", + "cum_exec_qty": float(order.amount), + "cum_exec_value": float(order.price + 2), + "cum_exec_fee": "0.01", + "reject_reason": "EC_NoError", + "cancel_type": "UNKNOWN", + "order_link_id": order.client_order_id or "", + "created_at": "2022-06-20T09:15:03.585128212Z", + "updated_at": "2022-06-20T09:15:03.590398174Z", + "order_id": order.exchange_order_id or "2b1d811c-8ff0-4ef0-92ed-b4ed5fd6de34", + "take_profit": "23000.00", + "stop_loss": "18000.00", + "tp_trigger_by": "MarkPrice", + "sl_trigger_by": "MarkPrice" + }, + "time_now": "1655718311.123686", + "rate_limit_status": 597, + "rate_limit_reset_ms": 1655718311122, + "rate_limit": 600 + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["result"]["order_status"] = "Cancelled" + resp["result"]["cum_exec_qty"] = 0 + resp["result"]["cum_exec_value"] = 0 + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["result"]["order_status"] = "New" + resp["result"]["cum_exec_qty"] = 0 + resp["result"]["cum_exec_value"] = 0 + return resp + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["result"]["order_status"] = "PartiallyFilled" + resp["result"]["cum_exec_qty"] = float(self.expected_partial_fill_amount) + resp["result"]["cum_exec_value"] = float(self.expected_partial_fill_price) + return resp + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + "order_id": "Abandoned!!", + "data": [ + { + "closed_size": 0, + "cross_seq": 277136382, + "exec_fee": str(self.expected_fill_fee.flat_fees[0].amount), + "exec_id": self.expected_fill_trade_id, + "exec_price": str(self.expected_partial_fill_price), + "exec_qty": float(self.expected_partial_fill_amount), + "exec_time": "1571676941.70682", + "exec_type": "Trade", + "exec_value": "0.00012227", + "fee_rate": "0.00075", + "last_liquidity_ind": "RemovedLiquidity", + "leaves_qty": 0, + "nth_fill": 2, + "order_id": order.exchange_order_id, + "order_link_id": order.client_order_id, + "order_price": str(order.price), + "order_qty": float(order.amount), + "order_type": order.order_type.name.capitalize(), + "side": order.trade_type.name.capitalize(), + "symbol": self.exchange_trading_pair, + "user_id": 1, + "trade_time_ms": 1577480599000 + } + ] + }, + "time_now": "1577483699.281488", + "rate_limit_status": 118, + "rate_limit_reset_ms": 1577483699244737, + "rate_limit": 120 + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "ret_code": 0, + "ret_msg": "OK", + "ext_code": "", + "ext_info": "", + "result": { + "order_id": "Abandoned!!", + "data": [ + { + "closed_size": 0, + "cross_seq": 277136382, + "exec_fee": str(self.expected_fill_fee.flat_fees[0].amount), + "exec_id": self.expected_fill_trade_id, + "exec_price": str(order.price), + "exec_qty": float(order.amount), + "exec_time": "1571676941.70682", + "exec_type": "Trade", + "exec_value": "0.00012227", + "fee_rate": "0.00075", + "last_liquidity_ind": "RemovedLiquidity", + "leaves_qty": 0, + "nth_fill": 2, + "order_id": order.exchange_order_id, + "order_link_id": order.client_order_id, + "order_price": str(order.price), + "order_qty": float(order.amount), + "order_type": order.order_type.name.capitalize(), + "side": order.trade_type.name.capitalize(), + "symbol": self.exchange_trading_pair, + "user_id": 1, + "trade_time_ms": 1577480599000 + } + ] + }, + "time_now": "1577483699.281488", + "rate_limit_status": 118, + "rate_limit_reset_ms": 1577483699244737, + "rate_limit": 120 + } + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ), + self.non_linear_trading_pair: TradingRule( # non-linear + trading_pair=self.non_linear_trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ), + } diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..5299bae --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_user_stream_data_source.py @@ -0,0 +1,213 @@ +import asyncio +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_auth import BybitPerpetualAuth +from hummingbot.connector.derivative.bybit_perpetual.bybit_perpetual_user_stream_data_source import ( + BybitPerpetualUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class BybitPerpetualUserStreamDataSourceTests(TestCase): + # the level is required to receive logs from the data source loger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + auth = BybitPerpetualAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET") + api_factory = web_utils.build_api_factory(auth=auth) + self.data_source = BybitPerpetualUserStreamDataSource( + auth=auth, api_factory=api_factory, domain=self.domain + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _authentication_response(self, authenticated: bool) -> str: + request = {"op": "auth", + "args": ["testAPIKey", "testExpires", "testSignature"]} + message = {"success": authenticated, + "ret_msg": "", + "conn_id": "testConnectionID", + "request": request} + + return json.dumps(message) + + def _subscription_response(self, subscribed: bool, subscription: str) -> str: + request = {"op": "subscribe", + "args": [subscription]} + message = {"success": subscribed, + "ret_msg": "", + "conn_id": "testConnectionID", + "request": request} + + return json.dumps(message) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + initial_last_recv_time = self.data_source.last_recv_time + + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, self._authentication_response(True)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_POSITIONS_ENDPOINT_NAME)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_ORDERS_ENDPOINT_NAME)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_EXECUTIONS_ENDPOINT_NAME)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response(True, CONSTANTS.WS_SUBSCRIPTION_WALLET_ENDPOINT_NAME)) + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source._listen_for_user_stream_on_url("test_url", messages) + ) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to private account and orders channels test_url...") + ) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + self.assertEqual(5, len(sent_messages)) + authentication_request = sent_messages[0] + subscription_positions_request = sent_messages[1] + subscription_orders_request = sent_messages[2] + subscription_executions_request = sent_messages[3] + subscription_wallet_request = sent_messages[4] + + self.assertEqual(CONSTANTS.WS_AUTHENTICATE_USER_ENDPOINT_NAME, + web_utils.endpoint_from_message(authentication_request)) + + expected_payload = {"op": "subscribe", + "args": ["position"]} + self.assertEqual(expected_payload, subscription_positions_request) + + expected_payload = {"op": "subscribe", + "args": ["order"]} + self.assertEqual(expected_payload, subscription_orders_request) + + expected_payload = {"op": "subscribe", + "args": ["execution"]} + self.assertEqual(expected_payload, subscription_executions_request) + + expected_payload = {"op": "subscribe", + "args": ["wallet"]} + self.assertEqual(expected_payload, subscription_wallet_request) + + self.assertGreater(self.data_source.last_recv_time, initial_last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_authentication_failure(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source._listen_for_user_stream_on_url("test_url", messages)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(False)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(self._is_logged("ERROR", "Error authenticating the private websocket connection")) + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream test_url. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, self._authentication_response(True) + ) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source._listen_for_user_stream_on_url("test_url", msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_ws): + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source._listen_for_user_stream_on_url("test_url", msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", "Unexpected error while listening to user stream test_url. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_on_cancel_exception(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + self.async_run_with_timeout(self.listening_task) diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_utils.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_utils.py new file mode 100644 index 0000000..d5f05dc --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_utils.py @@ -0,0 +1,69 @@ +from unittest import TestCase + +import pandas as pd + +from hummingbot.connector.derivative.bybit_perpetual import bybit_perpetual_utils as utils + + +class BybitPerpetualUtilsTests(TestCase): + def test_is_exchange_information_valid(self): + exchange_info = { + "name": "BTCUSD", + "alias": "BTCUSD", + "status": "Trading", + "base_currency": "BTC", + "quote_currency": "USD", + "price_scale": 2, + "taker_fee": "0.00075", + "maker_fee": "-0.00025", + "funding_interval": 480, + "leverage_filter": { + "min_leverage": 1, + "max_leverage": 100, + "leverage_step": "0.01" + }, + "price_filter": { + "min_price": "0.5", + "max_price": "999999.5", + "tick_size": "0.5" + }, + "lot_size_filter": { + "max_trading_qty": 1000000, + "min_trading_qty": 1, + "qty_step": 1, + "post_only_max_trading_qty": "5000000" + } + } + + self.assertTrue(utils.is_exchange_information_valid(exchange_info)) + + exchange_info["status"] = "Closed" + + self.assertFalse(utils.is_exchange_information_valid(exchange_info)) + + del exchange_info["status"] + + self.assertFalse(utils.is_exchange_information_valid(exchange_info)) + + def test_get_linear_non_linear_split(self): + trading_pairs = ["ETH-USDT", "ETH-BTC"] + linear_trading_pairs, non_linear_trading_pairs = utils.get_linear_non_linear_split(trading_pairs) + + self.assertEqual(["ETH-USDT"], linear_trading_pairs) + self.assertEqual(["ETH-BTC"], non_linear_trading_pairs) + + def test_get_next_funding_timestamp(self): + # Simulate 01:00 UTC + timestamp = pd.Timestamp("2021-08-21-01:00:00", tz="UTC").timestamp() + expected_ts = pd.Timestamp("2021-08-21-08:00:00", tz="UTC").timestamp() + self.assertEqual(expected_ts, utils.get_next_funding_timestamp(timestamp)) + + # Simulate 09:00 UTC + timestamp = pd.Timestamp("2021-08-21-09:00:00", tz="UTC").timestamp() + expected_ts = pd.Timestamp("2021-08-21-16:00:00", tz="UTC").timestamp() + self.assertEqual(expected_ts, utils.get_next_funding_timestamp(timestamp)) + + # Simulate 17:00 UTC + timestamp = pd.Timestamp("2021-08-21-17:00:00", tz="UTC").timestamp() + expected_ts = pd.Timestamp("2021-08-22-00:00:00", tz="UTC").timestamp() + self.assertEqual(expected_ts, utils.get_next_funding_timestamp(timestamp)) diff --git a/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_web_utils.py b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_web_utils.py new file mode 100644 index 0000000..ea25bad --- /dev/null +++ b/test/hummingbot/connector/derivative/bybit_perpetual/test_bybit_perpetual_web_utils.py @@ -0,0 +1,74 @@ +import unittest + +from hummingbot.connector.derivative.bybit_perpetual import ( + bybit_perpetual_constants as CONSTANTS, + bybit_perpetual_web_utils as web_utils, +) + + +class BybitPerpetualWebUtilsTest(unittest.TestCase): + def test_get_rest_url_for_endpoint(self): + endpoint = {"linear": "testEndpoint/linear", + "non_linear": "testEndpoint/non_linear"} + linear_pair = "ETH-USDT" + non_linear_pair = "ETH-BTC" + + url = web_utils.get_rest_url_for_endpoint(endpoint, domain="bybit_perpetual_main") + self.assertEqual("https://api.bybit.com/testEndpoint/linear", url) + + url = web_utils.get_rest_url_for_endpoint(endpoint, domain="bybit_perpetual_testnet") + self.assertEqual("https://api-testnet.bybit.com/testEndpoint/linear", url) + + url = web_utils.get_rest_url_for_endpoint(endpoint, trading_pair=linear_pair, domain="bybit_perpetual_main") + self.assertEqual("https://api.bybit.com/testEndpoint/linear", url) + + url = web_utils.get_rest_url_for_endpoint(endpoint, trading_pair=linear_pair, domain="bybit_perpetual_testnet") + self.assertEqual("https://api-testnet.bybit.com/testEndpoint/linear", url) + + url = web_utils.get_rest_url_for_endpoint(endpoint, trading_pair=non_linear_pair, domain="bybit_perpetual_main") + self.assertEqual("https://api.bybit.com/testEndpoint/non_linear", url) + + url = web_utils.get_rest_url_for_endpoint( + endpoint, trading_pair=non_linear_pair, domain="bybit_perpetual_testnet" + ) + self.assertEqual("https://api-testnet.bybit.com/testEndpoint/non_linear", url) + + def test_wss_linear_public_url(self): + url = web_utils.wss_linear_public_url(None) + self.assertEqual(CONSTANTS.WSS_LINEAR_PUBLIC_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_linear_public_url("bybit_perpetual_main") + self.assertEqual(CONSTANTS.WSS_LINEAR_PUBLIC_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_linear_public_url("bybit_perpetual_testnet") + self.assertEqual(CONSTANTS.WSS_LINEAR_PUBLIC_URLS.get("bybit_perpetual_testnet"), url) + + def test_wss_linear_private_url(self): + url = web_utils.wss_linear_private_url(None) + self.assertEqual(CONSTANTS.WSS_LINEAR_PRIVATE_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_linear_private_url("bybit_perpetual_main") + self.assertEqual(CONSTANTS.WSS_LINEAR_PRIVATE_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_linear_private_url("bybit_perpetual_testnet") + self.assertEqual(CONSTANTS.WSS_LINEAR_PRIVATE_URLS.get("bybit_perpetual_testnet"), url) + + def test_wss_non_linear_public_url(self): + url = web_utils.wss_non_linear_public_url(None) + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PUBLIC_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_non_linear_public_url("bybit_perpetual_main") + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PUBLIC_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_non_linear_public_url("bybit_perpetual_testnet") + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PUBLIC_URLS.get("bybit_perpetual_testnet"), url) + + def test_wss_non_linear_private_url(self): + url = web_utils.wss_non_linear_private_url(None) + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PRIVATE_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_non_linear_private_url("bybit_perpetual_main") + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PRIVATE_URLS.get("bybit_perpetual_main"), url) + + url = web_utils.wss_non_linear_private_url("bybit_perpetual_testnet") + self.assertEqual(CONSTANTS.WSS_NON_LINEAR_PRIVATE_URLS.get("bybit_perpetual_testnet"), url) diff --git a/test/hummingbot/connector/derivative/dydx_perpetual/__init__.py b/test/hummingbot/connector/derivative/dydx_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..078cca4 --- /dev/null +++ b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_api_order_book_data_source.py @@ -0,0 +1,560 @@ +import asyncio +import re +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +import dateutil.parser as dp +import ujson +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source import ( + DydxPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_derivative import DydxPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class DydxPerpetualAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_task: Optional[asyncio.Task] = None + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = DydxPerpetualDerivative( + client_config_map, + dydx_perpetual_api_key="", + dydx_perpetual_api_secret="", + dydx_perpetual_passphrase="", + dydx_perpetual_ethereum_address="", + dydx_perpetual_stark_private_key="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}-{self.quote_asset}": self.trading_pair}) + ) + self.data_source = DydxPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.async_task and self.async_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.PATH_MARKETS) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + } + } + } + + mock_api.get(regex_url, body=ujson.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_last_traded_prices([self.trading_pair])) + + self.assertEqual(1, len(result)) + self.assertEqual(float("12"), result[self.trading_pair]) + + @aioresponses() + def test_get_snapshot_raise_io_error(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.PATH_SNAPSHOT + "/" + self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400, body=ujson.dumps({})) + + with self.assertRaisesRegex( + IOError, + f"Error executing request GET {url}. " f"HTTP status is 400. Error: {{}}", + ): + self.async_run_with_timeout(self.data_source._order_book_snapshot(self.trading_pair)) + + @aioresponses() + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source." + "DydxPerpetualAPIOrderBookDataSource._time" + ) + def test_get_snapshot_successful(self, mock_api, mock_time): + mock_time.return_value = 1640780000 + + url = web_utils.public_rest_url(CONSTANTS.PATH_SNAPSHOT + "/" + self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "asks": [{"size": "2.0", "price": "20.0"}], + "bids": [{"size": "1.0", "price": "10.0"}], + } + mock_api.get(regex_url, body=ujson.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source._order_book_snapshot(self.trading_pair)) + + self.assertEqual(mock_response["asks"][0]["size"], str(result.asks[0].amount)) + self.assertEqual(mock_response["asks"][0]["price"], str(result.asks[0].price)) + self.assertEqual(mock_response["bids"][0]["size"], str(result.bids[0].amount)) + self.assertEqual(mock_response["bids"][0]["price"], str(result.bids[0].price)) + + self.assertEqual(result.content["update_id"], 1640780000000000) + + self.assertEqual(self.trading_pair, result.trading_pair) + + @aioresponses() + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source" + ".DydxPerpetualAPIOrderBookDataSource._time" + ) + def test_get_snapshot_raises_error(self, mock_api, mock_time): + mock_time.return_value = 1640780000 + + url = web_utils.public_rest_url(CONSTANTS.PATH_SNAPSHOT + "/" + self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + + with self.assertRaisesRegex(IOError, f"Error executing request GET {url}. HTTP status is 400. "): + self.async_run_with_timeout(self.data_source._order_book_snapshot(self.trading_pair)) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.PATH_SNAPSHOT + "/" + self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "asks": [{"size": "2.0", "price": "20.0"}], + "bids": [{"size": "1.0", "price": "10.0"}], + } + mock_api.get(regex_url, body=ujson.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + self.assertIsInstance(result, OrderBook) + self.assertEqual(1, len(list(result.bid_entries()))) + self.assertEqual(1, len(list(result.ask_entries()))) + self.assertEqual(float(mock_response["bids"][0]["price"]), list(result.bid_entries())[0].price) + self.assertEqual(float(mock_response["bids"][0]["size"]), list(result.bid_entries())[0].amount) + self.assertEqual(float(mock_response["asks"][0]["price"]), list(result.ask_entries())[0].price) + self.assertEqual(float(mock_response["asks"][0]["size"]), list(result.ask_entries())[0].amount) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source." + "DydxPerpetualAPIOrderBookDataSource._sleep" + ) + def test_listen_for_subscriptions_raises_cancelled_exception(self, _, ws_connect_mock): + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source." + "DydxPerpetualAPIOrderBookDataSource._sleep" + ) + def test_listen_for_subscriptions_raises_logs_exception(self, mock_sleep, ws_connect_mock): + mock_sleep.side_effect = lambda: (self.ev_loop.run_until_complete(asyncio.sleep(0.5))) + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = lambda *_: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR") + ) + self.async_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait(), 1.0) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source." + "DydxPerpetualAPIOrderBookDataSource._sleep" + ) + def test_listen_for_subscriptions_successful(self, mock_sleep, ws_connect_mock): + mock_sleep.side_effect = lambda: (self.ev_loop.run_until_complete(asyncio.sleep(0.5))) + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + mock_response = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "connection_id": "d600a0d2-8039-4cd9-a010-2d6f5c336473", + "message_id": 2, + "id": self.trading_pair, + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "contents": {"offset": "3218381978", "bids": [], "asks": [["36.152", "304.8"]]}, + } + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=ujson.dumps(mock_response) + ) + + self.async_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(1, self.data_source._message_queue[self.data_source._diff_messages_queue_key].qsize()) + + message = self.data_source._message_queue[self.data_source._diff_messages_queue_key]._queue[0] + self.assertEqual(message, mock_response) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_subscribe_channels_successful(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + self.async_run_with_timeout(self.data_source._subscribe_channels(ws)) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + + self.assertEqual(len(sent_messages), 3) + + self.assertEqual(sent_messages[0]["type"], CONSTANTS.WS_TYPE_SUBSCRIBE) + self.assertEqual(sent_messages[0]["channel"], CONSTANTS.WS_CHANNEL_ORDERBOOK) + self.assertEqual(sent_messages[0]["id"], self.trading_pair) + + self.assertEqual(sent_messages[1]["type"], CONSTANTS.WS_TYPE_SUBSCRIBE) + self.assertEqual(sent_messages[1]["channel"], CONSTANTS.WS_CHANNEL_TRADES) + self.assertEqual(sent_messages[1]["id"], self.trading_pair) + + self.assertEqual(sent_messages[2]["type"], CONSTANTS.WS_TYPE_SUBSCRIBE) + self.assertEqual(sent_messages[2]["channel"], CONSTANTS.WS_CHANNEL_MARKETS) + self.assertEqual(sent_messages[2]["id"], self.trading_pair) + + self.assertTrue(self._is_logged("INFO", "Subscribed to public orderbook and trade channels...")) + + def test_subscribe_channels_canceled(self): + ws = MagicMock() + ws.send.side_effect = asyncio.CancelledError() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source._subscribe_channels(ws)) + + def test_subscribe_channels_error(self): + ws = MagicMock() + ws.send.side_effect = Exception() + + with self.assertRaises(Exception): + self.async_run_with_timeout(self.data_source._subscribe_channels(ws)) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "id": self.trading_pair, + "connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d", + "channel": CONSTANTS.WS_CHANNEL_TRADES, + "message_id": 2, + "contents": { + "trades": [ + { + "side": "BUY", + "size": "100", + }, + {"side": "SELL", "size": "100", "price": "4000", "createdAt": "2020-11-29T14:00:03.382Z"}, + ] + }, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue(self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "id": self.trading_pair, + "connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d", + "channel": CONSTANTS.WS_CHANNEL_TRADES, + "message_id": 2, + "contents": { + "trades": [ + {"side": "BUY", "size": "100", "price": "4000", "createdAt": "2020-11-29T00:26:30.759Z"}, + {"side": "SELL", "size": "100", "price": "4000", "createdAt": "2020-11-29T14:00:03.382Z"}, + ] + }, + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + timestamp = dp.parse(trade_event["contents"]["trades"][0]["createdAt"]).timestamp() + trade_id = timestamp * 1e3 + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_id, msg.trade_id) + self.assertEqual(timestamp, msg.timestamp) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "id": self.trading_pair, + "connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d", + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "message_id": 2, + "contents": {"offset": "178", "bids": [["102"]], "asks": [["104", "0"]]}, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange") + ) + + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source." + "DydxPerpetualAPIOrderBookDataSource._time" + ) + def test_listen_for_order_book_diffs_successful(self, mock_time): + mock_time.return_value = 1640780000 + + mock_queue = AsyncMock() + diff_event = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "id": self.trading_pair, + "connection_id": "e2a6c717-6f77-4c1c-ac22-72ce2b7ed77d", + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "message_id": 2, + "contents": {"offset": "178", "bids": [["102", "11"]], "asks": [["104", "0"]]}, + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(1640780000, msg.timestamp) + # Decreased by 1 because previous nonce is already taked by the execution + expected_update_id = self.data_source._nonce_provider.get_tracking_nonce(timestamp=1640780000) - 1 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(102, bids[0].price) + self.assertEqual(11, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(104, asks[0].price) + self.assertEqual(0, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source" + ".DydxPerpetualAPIOrderBookDataSource._sleep" + ) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source" + ".DydxPerpetualAPIOrderBookDataSource._time" + ) + def test_listen_for_order_book_snapshots_log_exception(self, mock_time, mock_sleep): + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = 1 + + mock_time.return_value = 1640780000 + + mock_input_queue = AsyncMock() + mock_output_queue = AsyncMock() + + mock_sleep.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + incomplete_resp = { + "type": CONSTANTS.WS_TYPE_SUBSCRIBED, + "connection_id": "87b25218-0170-4111-bfbf-d9f0a506fcab", + "message_id": 1, + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "id": self.trading_pair, + "contents": { + "bids": [ + { + "price": "1779", + }, + {"price": "1778.5", "size": "18"}, + ], + "asks": [{"price": "1782.8", "size": "10"}, {"price": "1784", "size": "2.81"}], + }, + } + + mock_input_queue.get.side_effect = [incomplete_resp] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_input_queue + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(ev_loop=self.ev_loop, output=mock_output_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book snapshots from exchange") + ) + + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source" + ".DydxPerpetualAPIOrderBookDataSource._sleep" + ) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source" + ".DydxPerpetualAPIOrderBookDataSource._time" + ) + def test_listen_for_order_book_snapshots_successful(self, mock_time, mock_sleep): + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = 1 + + mock_time.return_value = 1640780000 + + mock_input_queue = AsyncMock() + output_queue = asyncio.Queue() + + mock_sleep.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + resp = { + "type": CONSTANTS.WS_TYPE_SUBSCRIBED, + "connection_id": "87b25218-0170-4111-bfbf-d9f0a506fcab", + "message_id": 1, + "channel": CONSTANTS.WS_CHANNEL_ORDERBOOK, + "id": self.trading_pair, + "contents": { + "bids": [{"price": "1779", "size": "1"}, {"price": "1778.5", "size": "18"}], + "asks": [{"price": "1782.8", "size": "10"}, {"price": "1784", "size": "2.81"}], + }, + } + + mock_input_queue.get.side_effect = [resp, Exception] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_input_queue + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(ev_loop=self.ev_loop, output=output_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + msg: OrderBookMessage = self.async_run_with_timeout(output_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(1640780000, msg.timestamp) + # Decreased by 1 because previous nonce is already taked by the execution + expected_update_id = self.data_source._nonce_provider.get_tracking_nonce(timestamp=1640780000) - 1 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(1779, bids[0].price) + self.assertEqual(1, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(2, len(asks)) + self.assertEqual(1782.8, asks[0].price) + self.assertEqual(10, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) diff --git a/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_auth.py b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_auth.py new file mode 100644 index 0000000..4333a42 --- /dev/null +++ b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_auth.py @@ -0,0 +1,83 @@ +import asyncio +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, patch + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth import DydxPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class DydxPerpetualAuthTests(TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someApiKey" + # Not a real secret key + cls.api_secret = "AA1p9oklqBkDT8xw2FRWwlZCfUf98wEG" + cls.passphrase = "somePassphrase" + cls.ethereum_address = "someEthAddress" + cls.stark_private_key = "0123456789" + + def setUp(self) -> None: + super().setUp() + self.auth = DydxPerpetualAuth( + self.api_key, self.api_secret, self.passphrase, self.ethereum_address, self.stark_private_key + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth.DydxPerpetualAuth._get_iso_timestamp") + def test_add_auth_to_rest_request(self, ts_mock: MagicMock): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params=params, + data="{}", + is_auth_required=True, + ) + ts_mock.return_value = "2022-07-06T12:20:53.000Z" + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual("f9KTZzueyS1MazebIiorgGnZ5aOsVB3o7N2mRaW520g=", request.headers["DYDX-SIGNATURE"]) + self.assertEqual("someApiKey", request.headers["DYDX-API-KEY"]) + self.assertEqual(ts_mock.return_value, request.headers["DYDX-TIMESTAMP"]) + self.assertEqual("somePassphrase", request.headers["DYDX-PASSPHRASE"]) + + @patch("hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth.DydxPerpetualAuth._get_iso_timestamp") + def test_add_auth_to_ws_request(self, ts_mock: MagicMock): + ts_mock.return_value = "2022-07-06T12:20:53.000Z" + + request = WSJSONRequest( + payload={"channel": CONSTANTS.WS_CHANNEL_ACCOUNTS}, + is_auth_required=True, + ) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual("someApiKey", request.payload["apiKey"]) + self.assertEqual("somePassphrase", request.payload["passphrase"]) + self.assertEqual(ts_mock.return_value, request.payload["timestamp"]) + self.assertEqual("MLJvgJDWv-o1lz1e6oRuU96SbCay1Qo9m-E6kKleOxY=", request.payload["signature"]) + + def test_get_order_signature(self): + result = self.auth.get_order_signature( + position_id="0123456789", + client_id="someClientOrderId", + market="BTC-USD", + side="BUY", + size="1", + price="10", + limit_fee="33", + expiration_epoch_seconds=1000, + ) + + self.assertEqual( + result, + "0776f3b3427920efdfad8f5cff438621bebcea4c1a15763a3436119fd2f896680551f3e9d8ae48e45a328814" # noqa: mock + "d49370165e19ecd24e543071c81d53211950982f", + ) # noqa: mock diff --git a/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_derivative.py b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_derivative.py new file mode 100644 index 0000000..8848ad0 --- /dev/null +++ b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_derivative.py @@ -0,0 +1,1279 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import AsyncMock + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_derivative import ( + DydxPerpetualAuth, + DydxPerpetualDerivative, +) +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest + + +class DydxPerpetualAuthMock(DydxPerpetualAuth): + def get_order_signature( + self, + position_id: str, + client_id: str, + market: str, + side: str, + size: str, + price: str, + limit_fee: str, + expiration_epoch_seconds: int, + ) -> str: + return "0123456789" + + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + headers = { + "DYDX-SIGNATURE": "0123456789", + "DYDX-API-KEY": "someKey", + "DYDX-TIMESTAMP": "1640780000", + "DYDX-PASSPHRASE": "somePassphrase", + } + + if request.headers is not None: + headers.update(request.headers) + + request.headers = headers + return request + + def get_account_id(self): + return "someAccountNumber" + + +class DydxPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.passphrase = "somePassphrase" + cls.account_number = "someAccountNumber" + cls.ethereum_address = "someEthAddress" + cls.stark_private_key = "0123456789" + cls.base_asset = "HBOT" + cls.quote_asset = "USD" # linear + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_MARKETS) + return url + + @property + def latest_prices_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_MARKETS + r"\?.*") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.PATH_TIME) + return url + + @property + def trading_rules_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_MARKETS) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_ACCOUNTS + "/" + str(self.account_number)) + return url + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY] + + @property + def all_symbols_request_mock_response(self): + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + } + } + } + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + } + } + } + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + }, + "INVALID-PAIR": { + "market": "INVALID-PAIR", + "status": "OFFLINE", + "baseAsset": "INVALID", + "quoteAsset": "PAIR", + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + }, + } + } + return "INVALID-PAIR", mock_response + + @property + def network_status_request_successful_mock_response(self): + mock_response = { + "iso": "2021-02-02T18:35:45Z", + "epoch": "1611965998.515", + } + return mock_response + + @property + def trading_rules_request_mock_response(self): + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "12", + "oraclePrice": "101", + "priceChange24H": "0", + "nextFundingRate": "0.0000125000", + "nextFundingAt": "2022-07-06T12:20:53.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + } + } + } + return mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + } + } + } + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "order": { + "id": self.exchange_order_id_prefix + "1", + "clientId": self.client_order_id_prefix + "1", + "accountId": "someAccountId", + "market": self.trading_pair, + "side": "SELL", + "price": "18000", + "triggerPrice": None, + "trailingPercent": None, + "size": "100", + "remainingSize": "100", + "type": "LIMIT", + "createdAt": "2021-01-04T23:44:59.690Z", + "unfillableAt": None, + "expiresAt": "2022-12-21T21:30:20.200Z", + "status": "PENDING", + "timeInForce": "GTT", + "postOnly": False, + "reduceOnly": False, + "cancelReason": None, + } + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + return {} + + @property + def balance_request_mock_response_only_base(self): + return {} + + @property + def balance_request_mock_response_only_quote(self): + mock_response = { + "account": { + "starkKey": "180913017c740260fea4b2c62828a4008ca8b0d6e4", + "positionId": "1812", + "equity": "10000", + "freeCollateral": "10000", + "quoteBalance": "10000", + "pendingDeposits": "0", + "pendingWithdrawals": "0", + "createdAt": "2021-04-09T21:08:34.984Z", + "openPositions": { + self.trading_pair: { + "market": self.trading_pair, + "status": "OPEN", + "side": "LONG", + "size": "1000", + "maxSize": "1050", + "entryPrice": "100", + "exitPrice": None, + "unrealizedPnl": "50", + "realizedPnl": "100", + "createdAt": "2021-01-04T23:44:59.690Z", + "closedAt": None, + "netFunding": "500", + "sumOpen": "1050", + "sumClose": "50", + } + }, + "accountNumber": "5", + "id": "id", + } + } + return mock_response + + @property + def balance_event_websocket_update(self): + mock_response = { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "accounts": [ + { + "id": self.account_number, + "positionId": "somePositionId", + "userId": "someUserId", + "accountNumber": "0", + "starkKey": "0x456...", + "quoteBalance": "700", + "pendingDeposits": "400", + "pendingWithdrawals": "0", + "lastTransactionId": "14", + } + ] + }, + } + return mock_response + + @property + def expected_latest_price(self): + return 12 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + trading_rules_resp = self.trading_rules_request_mock_response["markets"][self.trading_pair] + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(trading_rules_resp["minOrderSize"]), + min_price_increment=Decimal(trading_rules_resp["tickSize"]), + min_base_amount_increment=Decimal(trading_rules_resp["stepSize"]), + min_notional_size=Decimal(trading_rules_resp["minOrderSize"]) * Decimal(trading_rules_resp["tickSize"]), + supports_limit_orders=True, + supports_market_orders=True, + buy_order_collateral_token=trading_rules_resp["quoteAsset"], + sell_order_collateral_token=trading_rules_resp["quoteAsset"], + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + return "Error updating trading rules" + + @property + def expected_exchange_order_id(self): + return self.exchange_order_id_prefix + "1" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.1"))], + ) + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("10"))], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "someFillId" + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}-{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = DydxPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + self.passphrase, + self.ethereum_address, + self.stark_private_key, + trading_pairs=[self.trading_pair], + ) + exchange._position_id = 1234 + + authenticator = DydxPerpetualAuthMock( + self.api_key, self.api_secret, self.passphrase, self.ethereum_address, self.stark_private_key + ) + + exchange._auth = authenticator + exchange._web_assistants_factory._auth = authenticator + + exchange._rate_limits_config["placeOrderRateLimiting"] = {} + exchange._rate_limits_config["placeOrderRateLimiting"]["targetNotional"] = 40000 + exchange._rate_limits_config["placeOrderRateLimiting"]["minLimitConsumption"] = 4 + exchange._rate_limits_config["placeOrderRateLimiting"]["minMarketConsumption"] = 20 + exchange._rate_limits_config["placeOrderRateLimiting"]["maxOrderConsumption"] = 100 + exchange._rate_limits_config["placeOrderRateLimiting"]["minTriggerableConsumption"] = 100 + exchange._rate_limits_config["placeOrderRateLimiting"]["maxPoints"] = 1750 + exchange._rate_limits_config["placeOrderRateLimiting"]["windowSec"] = 10 + + return exchange + + def place_buy_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT, + position_action: PositionAction = PositionAction.OPEN, + ): + notional_amount = amount * price + self.exchange._order_notional_amounts[notional_amount] = len(self.exchange._order_notional_amounts.keys()) + self.exchange._current_place_order_requests = 1 + self.exchange._throttler.set_rate_limits(self.exchange.rate_limits_rules) + return super().place_buy_order(amount, price, order_type, position_action) + + def place_sell_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.LIMIT, + position_action: PositionAction = PositionAction.OPEN, + ): + notional_amount = amount * price + self.exchange._order_notional_amounts[notional_amount] = len(self.exchange._order_notional_amounts.keys()) + self.exchange._current_place_order_requests = 1 + self.exchange._throttler.set_rate_limits(self.exchange.rate_limits_rules) + return super().place_sell_order(amount, price, order_type, position_action) + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn(request_headers["Content-Type"], ["application/json", "application/x-www-form-urlencoded"]) + + self.assertIn("DYDX-SIGNATURE", request_headers) + self.assertIn("DYDX-API-KEY", request_headers) + self.assertIn("DYDX-TIMESTAMP", request_headers) + self.assertIn("DYDX-PASSPHRASE", request_headers) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["market"]) + self.assertEqual(order.trade_type.name.upper(), request_data["side"]) + self.assertEqual(order.price, Decimal(request_data["price"])) + self.assertEqual(order.amount, Decimal(request_data["size"])) + self.assertEqual(order.order_type.name.upper(), request_data["type"]) + self.assertEqual(CONSTANTS.TIF_GOOD_TIL_TIME, request_data["timeInForce"]) + self.assertFalse(request_data["reduceOnly"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(order.exchange_order_id, request_params["id"]) + self.assertEqual(self.trading_pair, request_params["market"]) + self.assertEqual("BUY" if order.trade_type == TradeType.BUY else "SELL", request_params["side"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + if request_params is not None: + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + self.assertEqual(CONSTANTS.LAST_FILLS_MAX, request_params["limit"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + if request_params is not None: + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + self.assertEqual(CONSTANTS.LAST_FILLS_MAX, request_params["limit"]) + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_ACTIVE_ORDERS) + + regex_url = re.compile(f"^{url}".replace(".", r"\.") + r"\?.*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_ACTIVE_ORDERS) + + regex_url = re.compile(f"^{url}".replace(".", r"\.") + r"\?.*") + response = {} + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + """ + :return: the URL configured + """ + url_order_status = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS + "/" + str(order.exchange_order_id)) + + response_order_status = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(url_order_status, body=json.dumps(response_order_status), callback=callback) + + return [url_order_status] + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + """ + :return: the URL configured + """ + url_fills = web_utils.private_rest_url(CONSTANTS.PATH_FILLS) + + response_fills = self._order_fills_request_canceled_mock_response(order=order) + mock_api.get(url_fills, body=json.dumps(response_fills), callback=callback) + + url_order_status = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS + "/" + str(order.exchange_order_id)) + + response_order_status = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(url_order_status, body=json.dumps(response_order_status), callback=callback) + + return [url_fills, url_order_status] + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS + "/" + str(order.exchange_order_id)) + + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return [url] + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS + "/" + str(order.exchange_order_id)) + + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Dydx has no partial fill status + raise NotImplementedError + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Dydx has no partial fill status + raise NotImplementedError + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_ORDERS + "/" + str(order.exchange_order_id)) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.PATH_FILLS) + + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "cancelOrders": [ + { + "id": order.exchange_order_id, + "clientId": order.client_order_id, + "accountId": "someAccountId", + "market": self.trading_pair, + "side": order.trade_type.name, + "price": str(order.price), + "triggerPrice": None, + "trailingPercent": None, + "size": str(order.amount), + "remainingSize": str(order.amount), + "type": "LIMIT", + "createdAt": "2021-01-04T23:44:59.690Z", + "unfillableAt": None, + "expiresAt": "2022-12-21T21:30:20.200Z", + "status": "PENDING", + "timeInForce": "GTT", + "postOnly": False, + "reduceOnly": False, + "cancelReason": None, + } + ] + } + + def _order_fills_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "fills": [ + { + "id": self.expected_fill_trade_id, + "accountId": self.account_number, + "side": order.trade_type.name, + "liquidity": "MAKER" if order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "TAKER", + "market": self.trading_pair, + "orderId": self.exchange_order_id_prefix + "1", + "size": str(order.amount), + "price": str(order.price), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "transactionId": "1", + "orderClientId": order.client_order_id, + "createdAt": "2020-09-22T20:25:26.399Z", + } + ] + } + + def _order_fills_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return {"fills": []} + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + mock_response = { + "order": { + "id": self.exchange_order_id_prefix + "1", + "clientId": order.client_order_id, + "accountId": "someAccountId", + "market": self.trading_pair, + "side": order.trade_type.name, + "price": str(order.price), + "triggerPrice": None, + "trailingPercent": None, + "size": str(order.amount), + "remainingSize": "0", + "type": "LIMIT", + "createdAt": "2021-01-04T23:44:59.690Z", + "unfillableAt": None, + "expiresAt": "2022-12-21T21:30:20.200Z", + "status": "FILLED", + "timeInForce": "GTT", + "postOnly": False, + "reduceOnly": False, + "cancelReason": None, + } + } + return mock_response + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["order"]["status"] = "CANCELED" + resp["order"]["remainingSize"] = resp["order"]["size"] + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["order"]["status"] = "OPEN" + resp["order"]["remainingSize"] = resp["order"]["size"] + return resp + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "fills": [ + { + "id": self.expected_fill_trade_id, + "accountId": self.account_number, + "side": order.trade_type.name, + "liquidity": "MAKER" if order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "TAKER", + "market": self.trading_pair, + "orderId": order.exchange_order_id, + "size": str(order.amount), + "price": str(order.price), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "transactionId": "1", + "orderClientId": order.client_order_id, + "createdAt": "2020-09-22T20:25:26.399Z", + } + ] + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "fills": [ + { + "id": self.expected_fill_trade_id, + "accountId": self.account_number, + "side": order.trade_type.name, + "liquidity": "MAKER" if order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] else "TAKER", + "market": self.trading_pair, + "orderId": order.exchange_order_id, + "size": str(order.amount), + "price": str(order.price), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "transactionId": "1", + "orderClientId": order.client_order_id, + "createdAt": "2020-09-22T20:25:26.399Z", + } + ] + } + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "orders": [ + { + "id": order.exchange_order_id, + "clientId": self.client_order_id_prefix + "1", + "market": self.trading_pair, + "accountId": self.account_number, + "side": order.trade_type.name, + "size": str(order.amount), + "remainingSize": "0", + "price": str(order.price), + "limitFee": str(self.expected_fill_fee.flat_fees[0].amount), + "type": "LIMIT", + "status": "OPEN", + "signature": "0x456...", + "timeInForce": "FOK", + "postOnly": "False", + "expiresAt": "2021-09-22T20:22:26.399Z", + "createdAt": "2020-09-22T20:22:26.399Z", + } + ] + }, + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "orders": [ + { + "id": order.exchange_order_id, + "clientId": order.client_order_id, + "market": self.trading_pair, + "accountId": self.account_number, + "side": order.trade_type.name, + "size": str(order.amount), + "remainingSize": "0", + "price": str(order.price), + "limitFee": str(self.expected_fill_fee.flat_fees[0].amount), + "type": "LIMIT", + "status": "CANCELED", + "signature": "0x456...", + "timeInForce": "FOK", + "postOnly": "False", + "expiresAt": "2021-09-22T20:22:26.399Z", + "createdAt": "2020-09-22T20:22:26.399Z", + } + ] + }, + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "orders": [ + { + "id": order.exchange_order_id, + "clientId": order.client_order_id, + "market": self.trading_pair, + "accountId": self.account_number, + "side": order.trade_type.name, + "size": str(order.amount), + "remainingSize": "0", + "price": str(order.price), + "limitFee": str(self.expected_fill_fee.flat_fees[0].amount), + "type": "LIMIT", + "status": "FILLED", + "signature": "0x456...", + "timeInForce": "FOK", + "postOnly": "False", + "expiresAt": "2021-09-22T20:22:26.399Z", + "createdAt": "2020-09-22T20:22:26.399Z", + } + ] + }, + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "fills": [ + { + "id": self.expected_fill_trade_id, + "accountId": self.account_number, + "side": order.trade_type.name, + "liquidity": "MAKER" + if order.order_type in [OrderType.LIMIT, OrderType.LIMIT_MAKER] + else "TAKER", + "market": self.trading_pair, + "orderId": order.exchange_order_id, + "size": str(order.amount), + "price": str(order.price), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "transactionId": "1", + "orderClientId": order.client_order_id, + "createdAt": "2020-09-22T20:25:26.399Z", + } + ] + }, + } + + @property + def funding_info_url(self): + url = web_utils.public_rest_url(CONSTANTS.PATH_MARKETS) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + url = web_utils.private_rest_url(CONSTANTS.PATH_FUNDING) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_info_mock_response(self): + mock_response = { + "markets": { + self.trading_pair: { + "market": self.trading_pair, + "status": "ONLINE", + "baseAsset": self.base_asset, + "quoteAsset": self.quote_asset, + "stepSize": "0.1", + "tickSize": "0.01", + "indexPrice": "1", + "oraclePrice": "2", + "priceChange24H": "0", + "nextFundingRate": "3", + "nextFundingAt": "2022-07-06T09:17:33.000Z", + "minOrderSize": "1", + "type": "PERPETUAL", + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + "baselinePositionSize": "1000", + "incrementalPositionSize": "1000", + "incrementalInitialMarginFraction": "0.2", + "volume24H": "0", + "trades24H": "0", + "openInterest": "0", + "maxPositionSize": "10000", + "assetResolution": "10000000", + "syntheticAssetId": "0x4c494e4b2d37000000000000000000", + } + } + } + return mock_response + + @property + def empty_funding_payment_mock_response(self): + mock_response = { + "fundingPayments": [ + { + "market": self.trading_pair, + "payment": "200", + "rate": "100", + "positionSize": "500", + "price": "90", + "effectiveAt": "2022-07-05T12:20:53.000Z", + } + ] + } + return mock_response + + @property + def funding_payment_mock_response(self): + mock_response = { + "fundingPayments": [ + { + "market": self.trading_pair, + "payment": "200", + "rate": "100", + "positionSize": "500", + "price": "90", + "effectiveAt": "2022-07-06T12:20:53.000Z", + } + ] + } + return mock_response + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "channel": CONSTANTS.WS_CHANNEL_ACCOUNTS, + "connection_id": "someConnectionId", + "id": self.account_number, + "message_id": 2, + "contents": { + "positions": [ + { + "id": self.expected_fill_trade_id, + "accountId": self.account_number, + "market": self.trading_pair, + "side": "LONG" if order.trade_type == TradeType.BUY else "SHORT", + "status": "CLOSED", + "size": str(order.amount) if order.order_type == TradeType.BUY else str(-order.amount), + "maxSize": "300", + "entryPrice": "10000", + "exitPrice": "38", + "realizedPnl": "50", + "unrealizedPnl": str(unrealized_pnl), + "createdAt": "2020-09-22T20:25:26.399Z", + "openTransactionId": "2", + "closeTransactionId": "23", + "lastTransactionId": "23", + "closedAt": "2020-14-22T20:25:26.399Z", + "sumOpen": "300", + "sumClose": "100", + } + ] + }, + } + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + # There's only one way position mode + pass + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + # There's only one way position mode, this should never be called + pass + + def configure_failed_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + url = web_utils.public_rest_url(CONSTANTS.PATH_MARKETS) + regex_url = re.compile(f"^{url}") + + # No "markets" in response + mock_response = {} + mock_api.get(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, "Failed to obtain markets information." + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.public_rest_url(CONSTANTS.PATH_MARKETS) + regex_url = re.compile(f"^{url}") + + # No "markets" in response + mock_response = { + "markets": { + self.trading_pair: { + "initialMarginFraction": "0.10", + "maintenanceMarginFraction": "0.05", + } + } + } + mock_api.get(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def funding_info_event_for_websocket_update(self): + return { + "type": CONSTANTS.WS_TYPE_CHANNEL_DATA, + "connection_id": "someConnectionId", + "channel": CONSTANTS.WS_CHANNEL_MARKETS, + "message_id": 2, + "contents": { + self.trading_pair: { + "indexPrice": "100.23", + "oraclePrice": "100.23", + "priceChange24H": "0.12", + "initialMarginFraction": "1.23", + } + }, + } + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + @aioresponses() + def test_update_balances(self, mock_api): + response = self.balance_request_mock_response_only_quote + + self._configure_balance_response(response=response, mock_api=mock_api) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.base_asset, available_balances) + self.assertNotIn(self.base_asset, total_balances) + self.assertEqual(Decimal("10000"), available_balances["USD"]) + self.assertEqual(Decimal("10000"), total_balances["USD"]) + + def test_user_stream_balance_update(self): + if self.exchange.real_time_balance_update: + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("700"), self.exchange.available_balances["USD"]) + self.assertEqual(Decimal("0"), self.exchange.get_balance("USD")) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + # Dydx has no partial fill status + pass + + @aioresponses() + def test_update_order_status_when_order_partially_filled_and_cancelled(self, mock_api): + # Dydx has no partial fill status + pass + + @aioresponses() + def test_user_stream_update_for_partially_cancelled_order(self, mock_api): + # Dydx has no partial fill status + pass + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def place_buy_market_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + order_type: OrderType = OrderType.MARKET, + position_action: PositionAction = PositionAction.OPEN, + ): + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[], + asks=[OrderBookRow(price=5.1, amount=2000, update_id=1)], + update_id=1, + ) + + notional_amount = amount * price + self.exchange._order_notional_amounts[notional_amount] = len(self.exchange._order_notional_amounts.keys()) + self.exchange._current_place_order_requests = 1 + self.exchange._throttler.set_rate_limits(self.exchange.rate_limits_rules) + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=order_type, + price=price, + position_action=position_action, + ) + return order_id + + @aioresponses() + def test_create_buy_market_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.place_buy_market_order() + self.async_run_with_timeout(request_sent_event.wait()) + order_request = self._all_executed_requests(mock_api, url)[0] + request_data = json.loads(order_request.kwargs["data"]) + self.assertEqual(Decimal("1.5") * Decimal("5.1"), Decimal(request_data["price"])) diff --git a/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..59c95ac --- /dev/null +++ b/test/hummingbot/connector/derivative/dydx_perpetual/test_dydx_perpetual_user_stream_data_source.py @@ -0,0 +1,115 @@ +import asyncio +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_derivative import DydxPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class DydxPerpetualUserStreamDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_task: Optional[asyncio.Task] = None + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = DydxPerpetualDerivative( + client_config_map, + dydx_perpetual_api_key="someApiKey", + dydx_perpetual_api_secret="AA1p9oklqBkDT8xw2FRWwlZCfUf98wEG", + dydx_perpetual_passphrase="somePassphrase", + dydx_perpetual_ethereum_address="someEthAddress", + dydx_perpetual_stark_private_key="0123456789", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.data_source = self.connector._create_user_stream_data_source() + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.async_task and self.async_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_user_stream_data_source." + "DydxPerpetualUserStreamDataSource._sleep" + ) + def test_listen_for_user_stream_raises_cancelled_exception(self, _, ws_connect_mock): + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_user_stream(asyncio.Queue())) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth.DydxPerpetualAuth._get_iso_timestamp") + @patch( + "hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_user_stream_data_source." + "DydxPerpetualUserStreamDataSource._sleep" + ) + def test_listen_for_user_stream_raises_logs_exception(self, mock_sleep, ts_mock, ws_connect_mock): + mock_sleep.side_effect = lambda: (self.ev_loop.run_until_complete(asyncio.sleep(0.5))) + ts_mock.return_value = "2022-07-06T12:20:53.000Z" + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = lambda *_: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR") + ) + self.async_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + self.async_run_with_timeout(self.resume_test_event.wait(), 1.0) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth.DydxPerpetualAuth._get_iso_timestamp") + def test_ws_authentication_successful(self, ts_mock: MagicMock, ws_connect_mock): + ts_mock.return_value = "2022-07-06T12:20:53.000Z" + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + + json_msgs = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + + self.assertEqual("someApiKey", json_msgs[0]["apiKey"]) + self.assertEqual("somePassphrase", json_msgs[0]["passphrase"]) + self.assertEqual(ts_mock.return_value, json_msgs[0]["timestamp"]) + self.assertEqual("MLJvgJDWv-o1lz1e6oRuU96SbCay1Qo9m-E6kKleOxY=", json_msgs[0]["signature"]) diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/__init__.py b/test/hummingbot/connector/derivative/gate_io_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..d73003d --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_api_order_book_data_source.py @@ -0,0 +1,623 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.gate_io_perpetual import gate_io_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_api_order_book_data_source import ( + GateIoPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_derivative import GateIoPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class GateIoPerpetualAPIOrderBookDataSourceTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = GateIoPerpetualDerivative( + client_config_map, + gate_io_perpetual_api_key="", + gate_io_perpetual_secret_key="", + gate_io_perpetual_user_id="", + trading_pairs=[self.trading_pair], + ) + self.data_source = GateIoPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}_{self.quote_asset}": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_rest_snapshot_msg(self) -> Dict: + return { + "id": 123456, + "current": 1623898993.123, + "update": 1623898993.121, + "asks": [ + { + "p": "1.52", + "s": 100 + }, + { + "p": "1.53", + "s": 40 + } + ], + "bids": [ + { + "p": "1.17", + "s": 150 + }, + { + "p": "1.16", + "s": 203 + } + ] + } + + def get_ws_snapshot_msg(self) -> Dict: + return { + "channel": "futures.order_book", + "event": "all", + "time": 1541500161, + "result": { + "t": 1541500161123, + "contract": self.ex_trading_pair, + "id": 93973511, + "asks": [ + { + "p": "97.1", + "s": 2245 + }, + { + "p": "97.1", + "s": 2245 + } + ], + "bids": [ + { + "p": "97.1", + "s": 2245 + }, + { + "p": "97.1", + "s": 2245 + } + ] + } + } + + def get_ws_diff_msg(self) -> Dict: + return { + "time": 1615366381, + "channel": "futures.order_book_update", + "event": "update", + "error": None, + "result": { + "t": 1615366381417, + "s": self.ex_trading_pair, + "U": 2517661101, + "u": 2517661113, + "b": [ + { + "p": "54672.1", + "s": 0 + }, + { + "p": "54664.5", + "s": 58794 + } + ], + "a": [ + { + "p": "54743.6", + "s": 0 + }, + { + "p": "54742", + "s": 95 + } + ] + } + } + + def get_funding_info_msg(self) -> Dict: + return { + "name": self.ex_trading_pair, + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": False, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + + def get_funding_info_rest_msg(self): + return { + "name": self.ex_trading_pair, + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": False, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + self._simulate_trading_rules_initialized() + endpoint = CONSTANTS.ORDER_BOOK_PATH_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + resp = self.get_rest_snapshot_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + self.assertEqual(123456, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(2, len(bids)) + self.assertEqual(1.17, bids[0].price) + self.assertEqual(0.00015, bids[0].amount) + self.assertEqual(2, len(asks)) + self.assertEqual(1.52, asks[0].price) + self.assertEqual(0.0001, asks[0].amount) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_PATH_URL + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_diffs_and_orderbooks(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_diffs = self.get_ws_snapshot_msg() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs), + ) + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription_channel = CONSTANTS.TRADES_ENDPOINT_NAME + expected_trade_subscription_payload = [self.ex_trading_pair] + self.assertEqual(expected_trade_subscription_channel, sent_subscription_messages[0]["channel"]) + self.assertEqual(expected_trade_subscription_payload, sent_subscription_messages[0]["payload"]) + expected_trade_subscription_channel = CONSTANTS.ORDERS_UPDATE_ENDPOINT_NAME + expected_trade_subscription_payload = [self.ex_trading_pair, "100ms"] + self.assertEqual(expected_trade_subscription_channel, sent_subscription_messages[1]["channel"]) + self.assertEqual(expected_trade_subscription_payload, sent_subscription_messages[1]["payload"]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to public order book and trade channels...") + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." + ) + ) + + def test_subscribe_to_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_to_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(mock_ws) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book data streams.") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "channel": "futures.trades", + "event": "update", + "time": 1541503698, + "result": [ + { + "size": -108, + "id": 27753479, + "create_time": 1545136464, + "create_time_ms": 1545136464123, + "price": "96.4", + "contract": "BTC_USD" + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + self._simulate_trading_rules_initialized() + mock_queue = AsyncMock() + trade_event = { + "channel": "futures.trades", + "event": "update", + "time": 1541503698, + "result": [ + { + "size": -108, + "id": "00c706e1-ba52-5bb0-98d0-bf694bdc69f7", + "create_time": 1545136464, + "create_time_ms": 1545136464123, + "price": "96.4", + "contract": self.ex_trading_pair + } + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_event["result"][0]["id"], msg.trade_id) + self.assertEqual(trade_event["result"][0]["create_time"], msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = self.get_ws_diff_msg() + del incomplete_resp["result"]["u"] + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + self._simulate_trading_rules_initialized() + mock_queue = AsyncMock() + diff_event = self.get_ws_diff_msg() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = int(diff_event["result"]["u"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(54672.1, bids[0].price) + self.assertEqual(0, bids[0].amount) + self.assertEqual(2, len(asks)) + self.assertEqual(54743.6, asks[0].price) + self.assertEqual(0, asks[0].amount) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_PATH_URL + url = web_utils.public_rest_url( + endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + endpoint = CONSTANTS.ORDER_SNAPSHOT_ENDPOINT_NAME + url = web_utils.public_rest_url( + endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, exception=Exception) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + self._simulate_trading_rules_initialized() + msg_queue: asyncio.Queue = asyncio.Queue() + endpoint = CONSTANTS.ORDER_BOOK_PATH_URL + url = web_utils.public_rest_url( + endpoint=endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + resp = self.get_rest_snapshot_msg() + + mock_api.get(regex_url, body=json.dumps(resp)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + expected_update_id = resp["id"] + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(1.17, bids[0].price) + self.assertEqual(0.00015, bids[0].amount) + self.assertEqual(2, len(asks)) + self.assertEqual(1.52, asks[0].price) + self.assertEqual(0.0001, asks[0].amount) + + @aioresponses() + def test_get_funding_info(self, mock_api): + endpoint = CONSTANTS.MARK_PRICE_URL.format(id=self.ex_trading_pair) + url = web_utils.public_rest_url(endpoint) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + resp = self.get_funding_info_rest_msg() + mock_api.get(regex_url, body=json.dumps(resp)) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + msg_result = resp + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(Decimal(str(msg_result["index_price"])), funding_info.index_price) + self.assertEqual(Decimal(str(msg_result["mark_price"])), funding_info.mark_price) + self.assertEqual(msg_result["funding_next_apply"], funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(str(msg_result["funding_rate_indicative"])), funding_info.rate) + + def _simulate_trading_rules_initialized(self): + self.connector._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_auth.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_auth.py new file mode 100644 index 0000000..3559920 --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_auth.py @@ -0,0 +1,89 @@ +import asyncio +import hashlib +import hmac +import time +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_auth import GateIoPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class GateIoPerpetualAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + + self.auth = GateIoPerpetualAuth(api_key=self.api_key, secret_key=self.secret_key) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _get_timestamp(self): + return str(int(time.time() * 1e3)) + + def _get_expiration_timestamp(self): + return str(int(time.time() + 1 * 1e3)) + + @patch( + "hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_auth.GateIoPerpetualAuth._get_timestamp") + def test_add_auth_to_rest_request(self, ts_mock: MagicMock): + params = {"one": "1"} + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/v4/futures/orders", + params=params, + is_auth_required=True, + ) + timestamp = self._get_timestamp() + ts_mock.return_value = timestamp + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + m = hashlib.sha512() + body_hash = m.hexdigest() + + # raw_signature = "api_key=" + self.api_key + "&one=1" + "×tamp=" + timestamp + raw_signature = f'GET\n/api/v4/futures/orders\none=1\n{body_hash}\n{timestamp}' + expected_signature = hmac.new(self.secret_key.encode("utf-8"), + raw_signature.encode("utf-8"), + hashlib.sha512).hexdigest() + params = request.params + headers = request.headers + + self.assertEqual(1, len(params)) + self.assertEqual("1", params.get("one")) + self.assertEqual(self.api_key, headers.get("KEY")) + self.assertEqual(expected_signature, headers.get("SIGN")) + + def test_no_auth_added_to_ws_request(self): + payload = { + "time": 1611541000, + "channel": 1, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + request = WSJSONRequest(payload=payload, is_auth_required=False) + self.assertNotIn("auth", request.payload) + + def test_ws_authenticate(self): + request: WSJSONRequest = WSJSONRequest( + payload={ + "time": 1611541000, + "channel": 1, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + }, is_auth_required=True + ) + + signed_request: WSJSONRequest = self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(request, signed_request) diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_derivative.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_derivative.py new file mode 100644 index 0000000..3d486e9 --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_derivative.py @@ -0,0 +1,1805 @@ +import asyncio +import json +import logging +import re +from copy import deepcopy +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import AsyncMock + +import pandas as pd +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +import hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_derivative import GateIoPerpetualDerivative +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair, get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class GateIoPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + _logger = logging.getLogger(__name__) + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.user_id = "someUserId" + cls.base_asset = "BTC" + cls.quote_asset = "USDT" # linear + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(endpoint=CONSTANTS.EXCHANGE_INFO_URL) + return url + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url( + endpoint=CONSTANTS.TICKER_PATH_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(endpoint=CONSTANTS.EXCHANGE_INFO_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(endpoint=CONSTANTS.EXCHANGE_INFO_URL) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def order_creation_url(self): + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_CREATE_PATH_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def balance_url(self): + url = web_utils.public_rest_url(endpoint=CONSTANTS.USER_BALANCES_PATH_URL) + return url + + @property + def funding_info_url(self): + url = web_utils.public_rest_url( + endpoint=CONSTANTS.MARK_PRICE_URL.format(id=self.exchange_trading_pair) + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + pass + + @property + def balance_request_mock_response_only_base(self): + pass + + @property + def all_symbols_request_mock_response(self): + mock_response = [ + { + "name": self.exchange_trading_pair, + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": False, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + ] + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = [ + { + "contract": self.exchange_trading_pair, + "last": str(self.expected_latest_price), + "low_24h": "6278", + "high_24h": "6790", + "change_percentage": "4.43", + "total_size": "32323904", + "volume_24h": "184040233284", + "volume_24h_btc": "28613220", + "volume_24h_usd": "184040233284", + "volume_24h_base": "28613220", + "volume_24h_quote": "184040233284", + "volume_24h_settle": "28613220", + "mark_price": "6534", + "funding_rate": "3", + "funding_next_apply": self.target_funding_info_next_funding_utc_timestamp, + "funding_rate_indicative": "3", + "index_price": "6531" + } + ] + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self): + mock_response = [ + { + "name": f"{self.base_asset}_{self.quote_asset}", + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": True, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + ] + return "INVALID-PAIR", mock_response + + def empty_funding_payment_mock_response(self): + pass + + @aioresponses() + def test_funding_payment_polling_loop_sends_update_event(self, *args, **kwargs): + pass + + @property + def network_status_request_successful_mock_response(self): + mock_response = [ + { + "name": self.exchange_trading_pair, + "type": "direct", + "quanto_multiplier": "0.0001", + "ref_discount_rate": "0", + "order_price_deviate": "0.5", + "maintenance_rate": "0.005", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "funding_rate_indicative": "0.000219", + "mark_price_round": "0.01", + "funding_offset": 0, + "in_delisting": False, + "risk_limit_base": "1000000", + "interest_rate": "0.0003", + "order_price_round": "0.1", + "order_size_min": 1, + "ref_rebate_rate": "0.2", + "funding_interval": 28800, + "risk_limit_step": "1000000", + "leverage_min": "1", + "leverage_max": "100", + "risk_limit_max": "8000000", + "maker_fee_rate": "-0.00025", + "taker_fee_rate": "0.00075", + "funding_rate": "0.002053", + "order_size_max": 1000000, + "funding_next_apply": 1610035200, + "short_users": 977, + "config_change_time": 1609899548, + "trade_size": 28530850594, + "position_size": 5223816, + "long_users": 455, + "funding_impact_value": "60000", + "orders_limit": 50, + "trade_id": 10851092, + "orderbook_id": 2129638396 + } + ] + return mock_response + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = [ + { + "name": self.exchange_trading_pair, + "type": "direct", + "mark_type": "index", + "last_price": "38026", + "mark_price": "37985.6", + "index_price": "37954.92", + "in_delisting": False, + } + ] + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "id": self.expected_exchange_order_id, + "user": 100000, + "contract": self.exchange_trading_pair, + "create_time": 1546569968, + "size": 6024, + "iceberg": 0, + "left": 6024, + "price": "3765", + "fill_price": "0", + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "gtc", + "refu": 0, + "is_reduce_only": False, + "is_close": False, + "is_liq": False, + "text": get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_BROKER_ID, + max_id_len=CONSTANTS.MAX_ID_LEN, + ), + "status": "open", + "finish_time": 1514764900, + "finish_as": "" + } + return mock_response + + @property + def limit_maker_order_creation_request_successful_mock_response(self): + mock_response = { + "id": self.expected_exchange_order_id, + "user": 100000, + "contract": self.exchange_trading_pair, + "create_time": 1546569968, + "size": 6024, + "iceberg": 0, + "left": 6024, + "price": "3765", + "fill_price": "0", + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "poc", + "refu": 0, + "is_reduce_only": False, + "is_close": False, + "is_liq": False, + "text": get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_BROKER_ID, + max_id_len=CONSTANTS.MAX_ID_LEN, + ), + "status": "open", + "finish_time": 1514764900, + "finish_as": "" + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + mock_response = { + "user": 1666, + "currency": self.quote_asset, + "total": "2000", + "unrealised_pnl": "3371.248828", + "position_margin": "38.712189181", + "order_margin": "0", + "available": "2000", + "point": "0", + "bonus": "0", + "in_dual_mode": False, + "history": { + "dnw": "10000", + "pnl": "68.3685", + "fee": "-1.645812875", + "refr": "0", + "fund": "-358.919120009855", + "point_dnw": "0", + "point_fee": "0", + "point_refr": "0", + "bonus_dnw": "0", + "bonus_offset": "0" + } + } + return mock_response + + @aioresponses() + def test_update_balances(self, mock_api): + response = self.balance_request_mock_response_for_base_and_quote + self._configure_balance_response(response=response, mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + @property + def balance_event_websocket_update(self): + mock_response = { + "channel": "futures.balances", + "event": "update", + "time": 1541505434, + "result": [ + { + "balance": 15, + "change": 5, + "text": "BTC_USD:3914424", + "time": 1547199246, + "time_ms": 1547199246123, + "type": "fee", + "user": "211xxx" + } + ] + } + return mock_response + + @property + def position_event_websocket_update(self): + mock_response = { + "time": 1588212926, + "time_ms": 1588212926123, + "channel": "futures.positions", + "event": "update", + "result": [ + { + "contract": "BTC_USDT", + "cross_leverage_limit": 0, + "entry_price": 40000.36666661111, + "history_pnl": -0.000108569505, + "history_point": 0, + "last_close_pnl": -0.000050123368, + "leverage": 0, + "leverage_max": 100, + "liq_price": 0.1, + "maintenance_rate": 0.005, + "margin": 49.999890611186, + "mode": "single", + "realised_pnl": -1.25e-8, + "realised_point": 0, + "risk_limit": 100, + "size": 3, + "time": 1628736848, + "time_ms": 1628736848321, + "user": "110xxxxx" + } + ] + } + return mock_response + + @property + def position_event_websocket_update_zero(self): + mock_response = { + "time": 1588212926, + "time_ms": 1588212926123, + "channel": "futures.positions", + "event": "update", + "result": [ + { + "contract": "BTC_USDT", + "cross_leverage_limit": 0, + "entry_price": 40000.36666661111, + "history_pnl": -0.000108569505, + "history_point": 0, + "last_close_pnl": -0.000050123368, + "leverage": 0, + "leverage_max": 100, + "liq_price": 0.1, + "maintenance_rate": 0.005, + "margin": 49.999890611186, + "mode": "single", + "realised_pnl": -1.25e-8, + "realised_point": 0, + "risk_limit": 100, + "size": 0, + "time": 1628736848, + "time_ms": 1628736848321, + "user": "110xxxxx" + } + ] + } + return mock_response + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def funding_payment_mock_response(self): + raise NotImplementedError + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError # test is overwritten + + @property + def target_funding_info_next_funding_utc_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_info_next_funding_utc_str_ws_updated(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp_ws_updated) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_payment_timestamp_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_payment_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def funding_info_mock_response(self): + mock_response = self.latest_prices_request_mock_response + funding_info = mock_response[0] + funding_info["index_price"] = self.target_funding_info_index_price + funding_info["mark_price"] = self.target_funding_info_mark_price + funding_info["predicted_funding_rate"] = self.target_funding_info_rate + return funding_info + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + rule = self.trading_rules_request_mock_response[0] + + min_amount_inc = Decimal(f"{rule['quanto_multiplier']}") + min_price_inc = Decimal(f"{rule['order_price_round']}") + min_amount = min_amount_inc + min_notional = Decimal(str(1)) + + return TradingRule(self.trading_pair, + min_order_size=min_amount, + min_price_increment=min_price_inc, + min_base_amount_increment=min_amount_inc, + min_notional_size=min_notional, + min_order_value=min_notional, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "335fd977-e5a5-4781-b6d0-c772d5bfb95b" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return False + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.1"))], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6" + + @property + def latest_trade_hist_timestamp(self) -> int: + return 1234 + + def async_run_with_timeout(self, coroutine, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}_{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = GateIoPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + self.user_id, + trading_pairs=[self.trading_pair], + ) + # exchange._last_trade_history_timestamp = self.latest_trade_hist_timestamp + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertEqual("application/json", request_headers["Content-Type"]) + + self.assertIn("Timestamp", request_headers) + self.assertIn("KEY", request_headers) + self.assertEqual(self.api_key, request_headers["KEY"]) + self.assertIn("SIGN", request_headers) + + def _format_amount_to_size(self, amount: Decimal) -> Decimal: + # trading_rule = self.trading_rules_request_mock_response[0] + trading_rule = self.exchange._trading_rules[self.trading_pair] + size = amount / Decimal(str(trading_rule.min_base_amount_increment)) + return size + + def _format_size_to_amount(self, size: Decimal) -> Decimal: + self._simulate_trading_rules_initialized() + # trading_rule = self.trading_rules_request_mock_response[0] + trading_rule = self.exchange._trading_rules[self.trading_pair] + amount = Decimal(str(size)) * Decimal(str(trading_rule.min_base_amount_increment)) + return amount + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.trade_type.name.lower(), "buy" if request_data["size"] > 0 else "sell") + self.assertEqual(self.exchange_trading_pair, request_data["contract"]) + self.assertEqual(order.amount, self._format_size_to_amount(abs(Decimal(str(request_data["size"]))))) + self.assertEqual(order.client_order_id, request_data["text"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + request_data = request_call.kwargs["data"] + self.assertIsNone(request_params) + self.assertIsNone(request_data) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + request_data = request_call.kwargs["data"] + self.assertIsNone(request_params) + self.assertIsNone(request_data) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["contract"]) + self.assertEqual(order.exchange_order_id, request_params["order"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_DELETE_PATH_URL.format(id=order.exchange_order_id) + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_DELETE_PATH_URL.format(id=order.exchange_order_id) + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + mock_api.delete(regex_url, status=400, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.MY_TRADES_PATH_URL, + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.public_rest_url( + endpoint=CONSTANTS.ORDER_STATUS_PATH_URL.format(id=order.exchange_order_id), + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.public_rest_url( + endpoint=CONSTANTS.SET_POSITION_MODE_URL + ) + regex_url = re.compile(f"^{url}") + get_position_url = web_utils.public_rest_url( + endpoint=CONSTANTS.POSITION_INFORMATION_URL + ) + regex_get_position_url = re.compile(f"^{get_position_url}") + get_position_mock_response = [ + {"mode": 'dual'} if position_mode is PositionMode.ONEWAY else {"mode": 'single'} + ] + response = { + "user": 1666, + "currency": "USDT", + "total": "9707.803567115145", + "size": "9707.803567115145", + "unrealised_pnl": "3371.248828", + "position_margin": "38.712189181", + "order_margin": "0", + "available": "9669.091377934145", + "point": "0", + "bonus": "0", + "in_dual_mode": True if position_mode is PositionMode.HEDGE else False, + "mode": "single" if position_mode is PositionMode.ONEWAY else "dual_long", + "history": { + "dnw": "10000", + "pnl": "68.3685", + "fee": "-1.645812875", + "refr": "0", + "fund": "-358.919120009855", + "point_dnw": "0", + "point_fee": "0", + "point_refr": "0", + "bonus_dnw": "0", + "bonus_offset": "0" + } + } + mock_api.get(regex_get_position_url, body=json.dumps(get_position_mock_response), callback=callback) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + url = web_utils.public_rest_url( + endpoint=CONSTANTS.SET_POSITION_MODE_URL + ) + get_position_url = web_utils.public_rest_url( + endpoint=CONSTANTS.POSITION_INFORMATION_URL + ) + regex_url = re.compile(f"^{url}") + regex_get_position_url = re.compile(f"^{get_position_url}") + + error_msg = "" + get_position_mock_response = [ + {"mode": 'single'} + ] + mock_response = { + "label": "1666", + "detail": "", + } + mock_api.get(regex_get_position_url, body=json.dumps(get_position_mock_response), callback=callback) + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"{error_msg}" + + def configure_failed_set_leverage( + self, + leverage: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + if self.exchange.position_mode is PositionMode.ONEWAY: + endpoint = CONSTANTS.ONEWAY_SET_LEVERAGE_PATH_URL.format(contract=self.exchange_trading_pair) + else: + endpoint = CONSTANTS.HEDGE_SET_LEVERAGE_PATH_URL.format(contract=self.exchange_trading_pair) + url = web_utils.public_rest_url( + endpoint=endpoint + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + + err_msg = "leverage is diff" + mock_response = [ + { + "user": 10000, + "contract": "BTC_USDT", + "size": -9440, + "leverage": 200, + "risk_limit": "100", + "leverage_max": "100", + "maintenance_rate": "0.005", + "value": "2.497143098997", + "margin": "4.431548146258", + "entry_price": "3779.55", + "liq_price": "99999999", + "mark_price": "3780.32", + "unrealised_pnl": "-0.000507486844", + "realised_pnl": "0.045543982432", + "history_pnl": "0", + "last_close_pnl": "0", + "realised_point": "0", + "history_point": "0", + "adl_ranking": 5, + "pending_orders": 16, + "close_order": { + "id": 232323, + "price": "3779", + "is_liq": False + }, + "mode": "single", + "cross_leverage_limit": "0" + } + ] + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + return url, err_msg + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + if self.exchange.position_mode is PositionMode.ONEWAY: + endpoint = CONSTANTS.ONEWAY_SET_LEVERAGE_PATH_URL.format(contract=self.exchange_trading_pair) + else: + endpoint = CONSTANTS.HEDGE_SET_LEVERAGE_PATH_URL.format(contract=self.exchange_trading_pair) + url = web_utils.public_rest_url( + endpoint=endpoint + ) + regex_url = re.compile(f"^{url}") + + mock_response = [ + { + "user": 10000, + "contract": "BTC_USDT", + "size": -9440, + "leverage": str(leverage), + "risk_limit": "100", + "leverage_max": "100", + "maintenance_rate": "0.005", + "value": "2.497143098997", + "margin": "4.431548146258", + "entry_price": "3779.55", + "liq_price": "99999999", + "mark_price": "3780.32", + "unrealised_pnl": "-0.000507486844", + "realised_pnl": "0.045543982432", + "history_pnl": "0", + "last_close_pnl": "0", + "realised_point": "0", + "history_point": "0", + "adl_ranking": 5, + "pending_orders": 16, + "close_order": { + "id": 232323, + "price": "3779", + "is_liq": False + }, + "mode": "single", + "cross_leverage_limit": "0" + } + ] + + mock_api.post(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "channel": "futures.orders", + "event": "update", + "time": 1541505434, + "result": [ + { + "contract": self.exchange_trading_pair, + "create_time": 1628736847, + "create_time_ms": 1628736847325, + "fill_price": 40000.4, + "finish_as": "", + "finish_time": 1628736848, + "finish_time_ms": 1628736848321, + "iceberg": 0, + "id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "is_close": False, + "is_liq": False, + "is_reduce_only": False, + "left": 0, + "mkfr": -0.00025, + "price": order.price, + "refr": 0, + "refu": 0, + "size": float(order.amount), + "status": "open", + "text": order.client_order_id or "", + "tif": "gtc", + "tkfr": 0.0005, + "user": "110xxxxx" + } + ] + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "channel": "futures.orders", + "event": "update", + "time": 1541505434, + "result": [ + { + "contract": self.exchange_trading_pair, + "create_time": 1628736847, + "create_time_ms": 1628736847325, + "fill_price": 40000.4, + "finish_as": "cancelled", + "finish_time": 1628736848, + "finish_time_ms": 1628736848321, + "iceberg": 0, + "id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "is_close": False, + "is_liq": False, + "is_reduce_only": False, + "left": 0, + "mkfr": -0.00025, + "price": order.price, + "refr": 0, + "refu": 0, + "size": float(order.amount), + "status": "finished", + "text": order.client_order_id or "", + "tif": "gtc", + "tkfr": 0.0005, + "user": "110xxxxx" + } + ] + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + + return { + "channel": "futures.orders", + "event": "update", + "time": 1541505434, + "result": [ + { + "contract": self.exchange_trading_pair, + "create_time": 2628736847, + "create_time_ms": 1628736847325, + "fill_price": 40000.4, + "finish_as": "filled", + "finish_time": 1628736848, + "finish_time_ms": 1628736848321, + "iceberg": 0, + "id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "is_close": False, + "is_liq": False, + "is_reduce_only": False, + "left": 0, + "mkfr": -0.00025, + "price": order.price, + "refr": 0, + "refu": 0, + "size": self._format_amount_to_size(Decimal(order.amount)), + "status": "finished", + "text": order.client_order_id or "", + "tif": "gtc", + "tkfr": 0.0005, + "user": "110xxxxx" + } + ] + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + return { + "time": 1543205083, + "channel": "futures.usertrades", + "event": "update", + "error": None, + "result": [ + { + "id": self.expected_fill_trade_id, + "create_time": 2628736848, + "create_time_ms": 1628736848321, + "contract": self.exchange_trading_pair, + "order_id": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "size": self._format_amount_to_size(Decimal(order.amount)), + "price": str(order.price), + "role": "maker", + "text": order.client_order_id or "", + "fee": Decimal(self.expected_fill_fee.flat_fees[0].amount), + "point_fee": 0 + } + ] + } + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + pass + + def funding_info_event_for_websocket_update(self): + return { + "time": 1541659086, + "channel": "futures.tickers", + "event": "update", + "error": None, + "result": [ + { + "contract": self.exchange_trading_pair, + "last": "118.4", + "change_percentage": "0.77", + "funding_rate": "-0.000114", + "funding_rate_indicative": self.target_funding_info_rate_ws_updated * 1e6, + "mark_price": self.target_funding_info_mark_price_ws_updated, + "index_price": self.target_funding_info_index_price_ws_updated, + "total_size": "73648", + "volume_24h": "745487577", + "volume_24h_btc": "117", + "volume_24h_usd": "419950", + "quanto_base_rate": "", + "volume_24h_quote": "1665006", + "volume_24h_settle": "178", + "volume_24h_base": "5526", + "low_24h": "99.2", + "high_24h": "132.5" + } + ] + } + + def test_create_order_with_invalid_position_action_raises_value_error(self): + self._simulate_trading_rules_initialized() + + with self.assertRaises(ValueError) as exception_context: + asyncio.get_event_loop().run_until_complete( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="C1", + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("46000"), + position_action=PositionAction.NIL, + ), + ) + + self.assertEqual( + f"Invalid position action {PositionAction.NIL}. Must be one of {[PositionAction.OPEN, PositionAction.CLOSE]}", + str(exception_context.exception) + ) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["11"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + self.assertTrue(order.is_open) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = GateIoPerpetualDerivative( + client_config_map=client_config_map, + gate_io_perpetual_api_key=self.api_key, + gate_io_perpetual_secret_key=self.api_secret, + gate_io_perpetual_user_id=self.user_id, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.quote_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.quote_asset)) + + def test_user_stream_position_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = GateIoPerpetualDerivative( + client_config_map=client_config_map, + gate_io_perpetual_api_key=self.api_key, + gate_io_perpetual_secret_key=self.api_secret, + gate_io_perpetual_user_id=self.user_id, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + position_event = self.position_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + self._simulate_trading_rules_initialized() + self.exchange.account_positions[self.trading_pair] = Position( + + trading_pair=self.trading_pair, + position_side=PositionSide.SHORT, + unrealized_pnl=Decimal('1'), + entry_price=Decimal('1'), + amount=Decimal('1'), + leverage=Decimal('1'), + ) + amount_precision = Decimal(self.exchange.trading_rules[self.trading_pair].min_base_amount_increment) + try: + asyncio.get_event_loop().run_until_complete(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(pos.amount, 3 * amount_precision) + + def test_user_stream_remove_position_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = GateIoPerpetualDerivative( + client_config_map=client_config_map, + gate_io_perpetual_api_key=self.api_key, + gate_io_perpetual_secret_key=self.api_secret, + gate_io_perpetual_user_id=self.user_id, + trading_pairs=[self.trading_pair], + ) + connector._set_current_timestamp(1640780000) + + position_event = self.position_event_websocket_update_zero + self._simulate_trading_rules_initialized() + self.exchange.account_positions[self.trading_pair] = Position( + trading_pair=self.trading_pair, + position_side=PositionSide.SHORT, + unrealized_pnl=Decimal('1'), + entry_price=Decimal('1'), + amount=Decimal('1'), + leverage=Decimal('1'), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + asyncio.get_event_loop().run_until_complete(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + self.assertEqual(len(self.exchange.account_positions), 0) + + def test_supported_position_modes(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + linear_connector = GateIoPerpetualDerivative( + client_config_map=client_config_map, + gate_io_perpetual_api_key=self.api_key, + gate_io_perpetual_secret_key=self.api_secret, + gate_io_perpetual_user_id=self.user_id, + trading_pairs=[self.trading_pair], + ) + + expected_result = [PositionMode.ONEWAY, PositionMode.HEDGE] + self.assertEqual(expected_result, linear_connector.supported_position_modes()) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + self.assertEqual(self.quote_asset, buy_collateral_token) + self.assertEqual(self.quote_asset, sell_collateral_token) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_first_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response + duplicate = deepcopy(results[0]) + duplicate["name"] = f"{self.exchange_trading_pair}_12345" + duplicate["quanto_multiplier"] = str(float(duplicate["quanto_multiplier"]) + 1) + results.append(duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_second_is_good(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response + duplicate = deepcopy(results[0]) + duplicate["name"] = f"{self.exchange_trading_pair}_12345" + duplicate["quanto_multiplier"] = str(float(duplicate["quanto_multiplier"]) + 1) + results.insert(0, duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_resolving_trading_pair_symbol_duplicates_on_trading_rules_update_cannot_resolve(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response + first_duplicate = deepcopy(results[0]) + first_duplicate["name"] = f"{self.exchange_trading_pair}_12345" + first_duplicate["quanto_multiplier"] = ( + str(float(first_duplicate["quanto_multiplier"]) + 1) + ) + second_duplicate = deepcopy(results[0]) + second_duplicate["name"] = f"{self.exchange_trading_pair}_67890" + second_duplicate["quanto_multiplier"] = ( + str(float(second_duplicate["quanto_multiplier"]) + 2) + ) + results.pop(0) + results.append(first_duplicate) + results.append(second_duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange.trading_rules)) + self.assertNotIn(self.trading_pair, self.exchange.trading_rules) + self.assertTrue( + self.is_logged( + log_level="ERROR", + message=( + f"Could not resolve the exchange symbols" + f" {self.exchange_trading_pair}_67890" + f" and {self.exchange_trading_pair}_12345" + ), + ) + ) + + @aioresponses() + def test_cancel_lost_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + url = self.configure_erroneous_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order = self.exchange.in_flight_orders["OID1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + self.assertEqual(leverage, fill_event.leverage) + self.assertEqual(PositionAction.OPEN.value, fill_event.position) + + buy_event = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "user": self.user_id, + "contract": self.exchange_trading_pair, + "create_time": 1546569968, + "size": float(order.amount), + "iceberg": 0, + "left": 6024, + "price": str(order.price), + "fill_price": "0", + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "gtc", + "refu": 0, + "is_reduce_only": False, + "is_close": False, + "is_liq": False, + "text": order.client_order_id or "", + "status": "finished", + "finish_time": 1514764900, + "finish_as": "cancelled" + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "user": 100000, + "contract": self.exchange_trading_pair, + "create_time": 1546569968, + "size": float(order.amount), + "iceberg": 0, + "left": 0, + "price": str(order.price), + "fill_price": str(order.price), + "mkfr": "-0.00025", + "tkfr": "0.00075", + "tif": "gtc", + "refu": 0, + "is_reduce_only": False, + "is_close": False, + "is_liq": False, + "text": order.client_order_id or "2b1d811c-8ff0-4ef0-92ed-b4ed5fd6de34", + "status": "finished", + "finish_time": 1514764900, + "finish_as": "filled" + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["finish_as"] = "cancelled" + resp["left"] = float(order.amount) + resp["fill_price"] = "0" + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["status"] = "open" + resp["finish_as"] = "" + resp["left"] = float(order.amount) + resp["fill_price"] = "0" + return resp + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["status"] = "open" + resp["finish_as"] = "" + resp["left"] = float(order.amount) / 2 + resp["fill_price"] = str(order.price) + return resp + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + resp = self._order_status_request_completely_filled_mock_response(order) + resp["status"] = "open" + resp["finish_as"] = "" + resp["left"] = float(order.amount) / 2 + resp["fill_price"] = str(order.price) + return resp + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + return [ + { + "id": self.expected_fill_trade_id, + "create_time": 1514764800.123, + "contract": self.exchange_trading_pair, + "order_id": order.exchange_order_id, + "size": str(self._format_amount_to_size(Decimal(order.amount))), + "price": str(order.price), + "text": order.client_order_id, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "point_fee": "0", + "role": "taker" + } + ] + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + @aioresponses() + def test_start_network_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response + duplicate = deepcopy(results[0]) + duplicate["name"] = f"{self.exchange_trading_pair}_12345" + duplicate["quanto_multiplier"] = str(float(duplicate["quanto_multiplier"]) + 1) + results.append(duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange.start_network()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + def place_limit_maker_buy_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.LIMIT_MAKER, + price=price, + position_action=position_action, + ) + return order_id + + @aioresponses() + def test_create_buy_limit_maker_order_successfully(self, mock_api): + """Open long position""" + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.limit_maker_order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_limit_maker_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + self.validate_order_creation_request( + order=self.exchange.in_flight_orders[order_id], + request_call=order_request) + + create_event = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, + create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT_MAKER, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), + create_event.exchange_order_id) + self.assertEqual(leverage, create_event.leverage) + self.assertEqual(PositionAction.OPEN.value, create_event.position) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT_MAKER.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} to {PositionAction.OPEN.name} a {self.trading_pair} position." + ) + ) + + @aioresponses() + def test_update_position_mode( + self, + mock_api: aioresponses, + ): + self._simulate_trading_rules_initialized() + get_position_url = web_utils.public_rest_url( + endpoint=CONSTANTS.POSITION_INFORMATION_URL + ) + regex_get_position_url = re.compile(f"^{get_position_url}") + response = [ + { + "user": 10000, + "contract": "BTC_USDT", + "size": 9440, + "leverage": "0", + "risk_limit": "100", + "leverage_max": "100", + "maintenance_rate": "0.005", + "value": "2.497143098997", + "margin": "4.431548146258", + "entry_price": "3779.55", + "liq_price": "99999999", + "mark_price": "3780.32", + "unrealised_pnl": "-0.000507486844", + "realised_pnl": "0.045543982432", + "history_pnl": "0", + "last_close_pnl": "0", + "realised_point": "0", + "history_point": "0", + "adl_ranking": 5, + "pending_orders": 16, + "close_order": { + "id": 232323, + "price": "3779", + "is_liq": False + }, + "mode": "single", + "update_time": 1684994406, + "cross_leverage_limit": "0" + } + ] + mock_api.get(regex_get_position_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_positions()) + + position: Position = self.exchange.account_positions[self.trading_pair] + self.assertEqual(self.trading_pair, position.trading_pair) + self.assertEqual(PositionSide.LONG, position.position_side) + + get_position_url = web_utils.public_rest_url( + endpoint=CONSTANTS.POSITION_INFORMATION_URL + ) + regex_get_position_url = re.compile(f"^{get_position_url}") + response = [ + { + "user": 10000, + "contract": "BTC_USDT", + "size": 9440, + "leverage": "0", + "risk_limit": "100", + "leverage_max": "100", + "maintenance_rate": "0.005", + "value": "2.497143098997", + "margin": "4.431548146258", + "entry_price": "3779.55", + "liq_price": "99999999", + "mark_price": "3780.32", + "unrealised_pnl": "-0.000507486844", + "realised_pnl": "0.045543982432", + "history_pnl": "0", + "last_close_pnl": "0", + "realised_point": "0", + "history_point": "0", + "adl_ranking": 5, + "pending_orders": 16, + "close_order": { + "id": 232323, + "price": "3779", + "is_liq": False + }, + "mode": "dual_long", + "update_time": 1684994406, + "cross_leverage_limit": "0" + } + ] + mock_api.get(regex_get_position_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_positions()) + position: Position = self.exchange.account_positions[f"{self.trading_pair}LONG"] + self.assertEqual(self.trading_pair, position.trading_pair) + self.assertEqual(PositionSide.LONG, position.position_side) diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_user_stream_data_source.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_user_stream_data_source.py new file mode 100644 index 0000000..5e7a88a --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_user_stream_data_source.py @@ -0,0 +1,269 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.gate_io_perpetual import gate_io_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_auth import GateIoPerpetualAuth +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_derivative import GateIoPerpetualDerivative +from hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_user_stream_data_source import ( + GateIoPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestGateIoPerpetualAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_secret_key = "someSecretKey" + cls.user_id = "someUserId" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = GateIoPerpetualAuth( + api_key=self.api_key, + secret_key=self.api_secret_key) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = GateIoPerpetualDerivative( + client_config_map=client_config_map, + gate_io_perpetual_api_key="", + gate_io_perpetual_secret_key="", + gate_io_perpetual_user_id="", + trading_pairs=[]) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = GateIoPerpetualAPIUserStreamDataSource( + self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + user_id=self.user_id, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_user_stream_data_source.GateIoPerpetualAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, time_mock, ws_connect_mock): + time_mock.return_value = 1000 + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "time": 1611541000, + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_trades = { + "time": 1611541000, + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_positions = { + "time": 1611541000, + "channel": CONSTANTS.USER_POSITIONS_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_positions)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(3, len(sent_subscription_messages)) + expected_orders_subscription = { + "time": int(self.mock_time_provider.time()), + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "payload": [self.user_id, "!all"], + "auth": { + "KEY": self.api_key, + "SIGN": '0fb3b313fe07c7d23164a4ae86adf306a48f5787c54b9a7595f0a50a164c01eb54d8de5d5ad65fbc3ea94e60e73446d999d23424e52f715713ee6cb32a7d0df1',# noqa: mock + "method": "api_key"}, + } + self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) + expected_trades_subscription = { + "time": int(self.mock_time_provider.time()), + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": [self.user_id, "!all"], + "auth": { + "KEY": self.api_key, + "SIGN": 'a7681c836307cbb57c7ba7a66862120770c019955953e5ec043fd00e93722d478096f0a8238e3f893dcb3e0f084dc67a2a7ff6e6e08bc1bf0ad80fee57fff113',# noqa: mock + "method": "api_key"} + } + self.assertEqual(expected_trades_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private order changes channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.gate_io_perpetual.gate_io_perpetual_user_stream_data_source.GateIoPerpetualAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_skips_subscribe_unsubscribe_messages(self, time_mock, ws_connect_mock): + time_mock.return_value = 1000 + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "time": 1611541000, + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_trades = { + "time": 1611541000, + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_pong_payload(self, mock_ws): + mock_pong = { + "time": 1545404023, + "channel": CONSTANTS.PONG_CHANNEL_NAME, + "event": "", + "error": None, + "result": None + } + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_pong)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, sleep_mock, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_utils.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_utils.py new file mode 100644 index 0000000..a27837c --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_utils.py @@ -0,0 +1,5 @@ +from unittest import TestCase + + +class GateIoPerpetualUtilsTests(TestCase): + pass diff --git a/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_web_utils.py b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_web_utils.py new file mode 100644 index 0000000..543292c --- /dev/null +++ b/test/hummingbot/connector/derivative/gate_io_perpetual/test_gate_io_perpetual_web_utils.py @@ -0,0 +1,22 @@ +import unittest + +from hummingbot.connector.derivative.gate_io_perpetual import ( + gate_io_perpetual_constants as CONSTANTS, + gate_io_perpetual_web_utils as web_utils, +) +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class GateIoPerpetualWebUtilsTest(unittest.TestCase): + + def test_public_rest_url(self): + url = web_utils.public_rest_url(CONSTANTS.ORDER_BOOK_PATH_URL) + self.assertEqual("https://api.gateio.ws/api/v4/futures/usdt/order_book", url) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory() + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(2, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/derivative/injective_v2_perpetual/__init__.py b/test/hummingbot/connector/derivative/injective_v2_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_delegated_account.py b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_delegated_account.py new file mode 100644 index 0000000..60a403c --- /dev/null +++ b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_delegated_account.py @@ -0,0 +1,2915 @@ +import asyncio +import base64 +import json +from collections import OrderedDict +from decimal import Decimal +from functools import partial +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from unittest.mock import AsyncMock, MagicMock + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict +from grpc import RpcError +from pyinjective import Address, PrivateKey +from pyinjective.composer import Composer +from pyinjective.orderhash import OrderHashResponse + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_derivative import ( + InjectiveV2PerpetualDerivative, +) +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveDelegatedAccountMode, + InjectiveTestnetNetworkMode, +) +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_utils import OrderHashManager +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayPerpetualInFlightOrder +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + FundingPaymentCompletedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_gather + + +class InjectiveV2PerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.base_asset_denom = "inj" + cls.quote_asset_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" # noqa: mock + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" # noqa: mock + + _, grantee_private_key = PrivateKey.generate() + cls.trading_account_private_key = grantee_private_key.to_hex() + cls.trading_account_subaccount_index = 0 + _, granter_private_key = PrivateKey.generate() + granter_address = Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())) + cls.portfolio_account_injective_address = granter_address.to_acc_bech32() + cls.portfolio_account_subaccount_index = 0 + portfolio_adderss = Address.from_acc_bech32(cls.portfolio_account_injective_address) + cls.portfolio_account_subaccount_id = portfolio_adderss.get_subaccount_id( + index=cls.portfolio_account_subaccount_index + ) + cls.base_decimals = 18 + cls.quote_decimals = 6 + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + self._logs_event: Optional[asyncio.Event] = None + self.exchange._data_source.logger().setLevel(1) + self.exchange._data_source.logger().addHandler(self) + + self.exchange._orders_processing_delta_time = 0.1 + self.async_tasks.append(self.async_loop.create_task(self.exchange._process_queued_orders())) + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + self._logs_event = None + + def handle(self, record): + super().handle(record=record) + if self._logs_event is not None: + self._logs_event.set() + + def reset_log_event(self): + if self._logs_event is not None: + self._logs_event.clear() + + async def wait_for_a_log(self): + if self._logs_event is not None: + await self._logs_event.wait() + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY] + + @property + def funding_info_url(self): + raise NotImplementedError + + @property + def funding_payment_url(self): + raise NotImplementedError + + @property + def funding_info_mock_response(self): + raise NotImplementedError + + @property + def empty_funding_payment_mock_response(self): + raise NotImplementedError + + @property + def funding_payment_mock_response(self): + raise NotImplementedError + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + raise NotImplementedError + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None): + raise NotImplementedError + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Tuple[str, str]: + # Do nothing + return "", "" + + def configure_failed_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Tuple[str, str]: + raise NotImplementedError + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + raise NotImplementedError + + def funding_info_event_for_websocket_update(self): + raise NotImplementedError + + @property + def all_symbols_url(self): + raise NotImplementedError + + @property + def latest_prices_url(self): + raise NotImplementedError + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + raise NotImplementedError + + @property + def order_creation_url(self): + raise NotImplementedError + + @property + def balance_url(self): + raise NotImplementedError + + @property + def all_symbols_request_mock_response(self): + raise NotImplementedError + + @property + def latest_prices_request_mock_response(self): + return { + "trades": [ + { + "orderHash": "0x9ffe4301b24785f09cb529c1b5748198098b17bd6df8fe2744d923a574179229", # noqa: mock + "subaccountId": "0xa73ad39eab064051fb468a5965ee48ca87ab66d4000000000000000000000000", # noqa: mock + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "tradeExecutionType": "limitMatchRestingOrder", + "positionDelta": { + "tradeDirection": "sell", + "executionPrice": str(Decimal(str(self.expected_latest_price)) * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "142000000000000000000", + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": "-112393", + "executedAt": "1688734042063", + "feeRecipient": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock + "tradeId": "13374245_801_0", + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = self.all_derivative_markets_mock_response + response.append({ + "marketId": "invalid_market_id", + "marketStatus": "active", + "ticker": "INVALID/MARKET", + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }) + + return ("INVALID_MARKET", response) + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + raise NotImplementedError + + @property + def trading_rules_request_erroneous_mock_response(self): + return [{ + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + }] + + @property + def order_creation_request_successful_mock_response(self): + return {"txhash": "017C130E3602A48E5C9D661CAC657BF1B79262D4B71D5C25B1DA62DE2338DA0E", "rawLog": "[]"} # noqa: mock + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "accountAddress": self.portfolio_account_injective_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + { + "denom": self.quote_asset_denom, + "amount": str(Decimal(1000) * Decimal(1e6)) + } + ], + "subaccounts": [ + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.quote_asset_denom, + "deposit": { + "totalBalance": str(Decimal(1000) * Decimal(1e6)), + "availableBalance": str(Decimal(1000) * Decimal(1e6)) + } + }, + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(10) * Decimal(1e18)), + "availableBalance": str(Decimal(5) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "accountAddress": self.portfolio_account_injective_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + ], + "subaccounts": [ + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(10) * Decimal(1e18)), + "availableBalance": str(Decimal(5) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_event_websocket_update(self): + return { + "balance": { + "subaccountId": self.portfolio_account_subaccount_id, + "accountAddress": self.portfolio_account_injective_address, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)), + } + }, + "timestamp": "1688659208000" + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + market_info = self.all_derivative_markets_mock_response[0] + min_price_tick_size = (Decimal(market_info["minPriceTickSize"]) + * Decimal(f"1e{-market_info['quoteTokenMeta']['decimals']}")) + min_quantity_tick_size = Decimal(market_info["minQuantityTickSize"]) + trading_rule = TradingRule( + trading_pair=self.trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + + return trading_rule + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "0x3870fbdd91f07d54425147b1bb96404f4f043ba6335b422a6d494d285b387f00" # noqa: mock + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "10414162_22_33" + + @property + def all_spot_markets_mock_response(self): + return [{ + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] + + @property + def all_derivative_markets_mock_response(self): + return [ + { + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset} PERP", + "oracleBase": "0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", # noqa: mock + "oracleQuote": "0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", # noqa: mock + "oracleType": "pyth", + "oracleScaleFactor": 6, + "initialMarginRatio": "0.195", + "maintenanceMarginRatio": "0.05", + "quoteDenom": self.quote_asset_denom, + "quoteTokenMeta": { + "name": "Testnet Tether USDT", + "address": "0x0000000000000000000000000000000000000000", + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0003", + "takerFeeRate": "0.003", + "serviceProviderFee": "0.4", + "isPerpetual": True, + "minPriceTickSize": "100", + "minQuantityTickSize": "0.0001", + "perpetualMarketInfo": { + "hourlyFundingRateCap": "0.000625", + "hourlyInterestRate": "0.00000416666", + "nextFundingTimestamp": str(self.target_funding_info_next_funding_utc_timestamp), + "fundingInterval": "3600" + }, + "perpetualMarketFunding": { + "cumulativeFunding": "81363.592243119007273334", + "cumulativePrice": "1.432536051546776736", + "lastTimestamp": "1689423842" + } + }, + ] + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return self.market_id + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + granter_address=self.portfolio_account_injective_address, + granter_subaccount_index=self.portfolio_account_subaccount_index, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange = InjectiveV2PerpetualDerivative( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange._data_source._query_executor = ProgrammableQueryExecutor() + exchange._data_source._spot_market_and_trading_pair_map = bidict() + exchange._data_source._derivative_market_and_trading_pair_map = bidict({self.market_id: self.trading_pair}) + + exchange._data_source._composer = Composer(network=exchange._data_source.network_name) + + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def configure_all_symbols_response( + self, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + all_markets_mock_response = self.all_spot_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + all_markets_mock_response = self.all_derivative_markets_mock_response + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(all_markets_mock_response) + return "" + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.configure_all_symbols_response(mock_api=mock_api, callback=callback) + return "" + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait([]) + response = self.trading_rules_request_erroneous_mock_response + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(response) + return "" + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_erroneous_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + raise NotImplementedError + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses + ) -> List[str]: + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_canceled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_open_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_open_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_http_error_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for historical orders responses") + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return None + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return None + + def configure_order_not_found_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_not_found_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def configure_full_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": str(order.amount), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": order.creation_transaction_hash + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower(), + "executionPrice": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(order.amount), + "executionMargin": "3693162304" + }, + "payout": "3693278402.762361271848955224", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1687878089569", + "feeRecipient": self.portfolio_account_injective_address, # noqa: mock + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + } + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + queue_mock = AsyncMock() + queue_mock.get.side_effect = Exception("Test error") + self.exchange._data_source._query_executor._spot_markets_responses = queue_mock + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs(), timeout=10) + + self.assertEqual(0, len(result)) + + def test_batch_order_create(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1", "hash2"] + ) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("11"), + quantity=Decimal("3"), + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="hash1", + creation_transaction_hash=response["txhash"] + ) + sell_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[1].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=orders[1].price, + amount=orders[1].quantity, + exchange_order_id="hash2", + creation_transaction_hash=response["txhash"] + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + def test_batch_order_create_with_one_market_order(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1", "hash2"] + ) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[OrderBookRow(price=5000, amount=20, update_id=1)], + asks=[], + update_id=1, + ) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + position=PositionAction.OPEN, + ) + sell_order_to_create = MarketOrder( + order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_asset=self.base_asset, + quote_asset=self.quote_asset, + amount=3, + timestamp=self.exchange.current_timestamp, + position=PositionAction.CLOSE, + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=True, + volume=Decimal(str(sell_order_to_create.amount)), + ).result_price + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="hash1", + creation_transaction_hash=response["txhash"], + position=PositionAction.OPEN + ) + sell_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[1].order_id, + trading_pair=self.trading_pair, + order_type=OrderType.MARKET, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=expected_price_for_volume, + amount=orders[1].quantity, + exchange_order_id="hash2", + creation_transaction_hash=response["txhash"], + position=PositionAction.CLOSE + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + """Open long position""" + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_buy_market_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[], + asks=[OrderBookRow(price=5000, amount=20, update_id=1)], + update_id=1, + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_amount = Decimal(1) + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=True, + volume=order_amount + ).result_price + + order_id = self.place_buy_order(amount=order_amount, price=None, order_type=OrderType.MARKET) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + self.assertEqual(expected_price_for_volume, order.price) + + @aioresponses() + def test_create_sell_market_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[OrderBookRow(price=5000, amount=20, update_id=1)], + asks=[], + update_id=1, + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_amount = Decimal(1) + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=False, + volume=order_amount + ).result_price + + order_id = self.place_sell_order(amount=order_amount, price=None, order_type=OrderType.MARKET) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + self.assertEqual(expected_price_for_volume, order.price) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order size 0.01. The order will not be created, " + "increase the amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_to_close_short_position(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 4 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_order_to_close_long_position(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=[], derivative=["hash1"] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 5 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_sell_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + def test_batch_order_cancel(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id=self.expected_exchange_order_id + "2", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("110"), + order_type=OrderType.LIMIT, + ) + + buy_order_to_cancel: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders["11"] + sell_order_to_cancel: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders["12"] + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait(transaction_simulation_response) + + response = self._order_cancelation_request_successful_mock_response(order=buy_order_to_cancel) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + self.exchange.batch_order_cancel(orders_to_cancel=orders_to_cancel) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], buy_order_to_cancel.cancel_tx_hash) + self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], sell_order_to_cancel.cancel_tx_hash) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + def test_order_not_found_in_its_creating_transaction_marked_as_failed_during_order_creation_check(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="0x9f94598b4842ab66037eaa7c64ec10ae16dcf196e61db8522921628522c0f62e", # noqa: mock + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + transaction_data = (b'\x12\xd1\x01\n8/injective.exchange.v1beta1.MsgBatchUpdateOrdersResponse' + b'\x12\x94\x01\n\x02\x00\x00\x12\x02\x00\x00\x1aB' + b'0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1' # noqa: mock + b'\x1aB' + b'0x115975551b4f86188eee6b93d789fcc78df6e89e40011b929299b6e142f53515' # noqa: mock + b'"\x00"\x00') + transaction_messages = [ + { + "type": "/cosmos.authz.v1beta1.MsgExec", + "value": { + "grantee": PrivateKey.from_hex(self.trading_account_private_key).to_public_key().to_acc_bech32(), + "msgs": [ + { + "@type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "sender": self.portfolio_account_injective_address, + "subaccount_id": "", + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "spot_orders_to_create": [ + { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str((order.amount + Decimal(1)) * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": order.trade_type.name, + "trigger_price": "0.000000000000000000" + } + ], + "derivative_orders_to_create": [], + "binary_options_orders_to_cancel": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_create": [] + } + ] + } + } + ] + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "13302254", + "blockTimestamp": "2023-07-05 13:55:09.94 +0000 UTC", + "hash": "0x66a360da2fd6884b53b5c019f1a2b5bed7c7c8fc07e83a9c36ad3362ede096ae", # noqa: mock + "data": base64.b64encode(transaction_data).decode(), + "gasWanted": "168306", + "gasUsed": "167769", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "84153000000000" + } + ], + "gasLimit": "168306", + "payer": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" # noqa: mock + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(transaction_messages).encode()).decode(), + "signatures": [ + { + "pubkey": "035ddc4d5642b9383e2f087b2ee88b7207f6286ebc9f310e9df1406eccc2c31813", # noqa: mock + "address": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "sequence": "16450", + "signature": "S9atCwiVg9+8vTpbciuwErh54pJOAry3wHvbHT2fG8IumoE+7vfuoP7mAGDy2w9am+HHa1yv60VSWo3cRhWC9g==" + } + ], + "txNumber": "13182", + "blockUnixTimestamp": "1688565309940", + "logs": "W3sibXNnX2luZGV4IjowLCJldmVudHMiOlt7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5IjoiYWN0aW9uIiwidmFsdWUiOiIvaW5qZWN0aXZlLmV4Y2hhbmdlLnYxYmV0YTEuTXNnQmF0Y2hVcGRhdGVPcmRlcnMifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJtb2R1bGUiLCJ2YWx1ZSI6ImV4Y2hhbmdlIn1dfSx7InR5cGUiOiJjb2luX3NwZW50IiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic3BlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjE2NTE2NTAwMHBlZ2d5MHg4N2FCM0I0Qzg2NjFlMDdENjM3MjM2MTIxMUI5NmVkNERjMzZCMUI1In1dfSx7InR5cGUiOiJjb2luX3JlY2VpdmVkIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjZWl2ZXIiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxNjUxNjUwMDBwZWdneTB4ODdhQjNCNEM4NjYxZTA3RDYzNzIzNjEyMTFCOTZlZDREYzM2QjFCNSJ9XX0seyJ0eXBlIjoidHJhbnNmZXIiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNpcGllbnQiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMTY1MTY1MDAwcGVnZ3kweDg3YUIzQjRDODY2MWUwN0Q2MzcyMzYxMjExQjk2ZWQ0RGMzNkIxQjUifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiI1NTAwMDAwMDAwMDAwMDAwMDAwMGluaiJ9XX0seyJ0eXBlIjoiY29pbl9yZWNlaXZlZCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2VpdmVyIiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiNTUwMDAwMDAwMDAwMDAwMDAwMDBpbmoifV19LHsidHlwZSI6InRyYW5zZmVyIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjaXBpZW50IiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjU1MDAwMDAwMDAwMDAwMDAwMDAwaW5qIn1dfSx7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifV19XX1d" # noqa: mock + } + } + self.exchange._data_source._query_executor._transaction_by_hash_responses.put_nowait(transaction_response) + + original_order_hash_manager = self.exchange._data_source.order_hash_manager + + self.async_run_with_timeout(self.exchange._check_orders_creation_transactions()) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order.client_order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order.client_order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + self.assertNotEqual(original_order_hash_manager, self.exchange._data_source._order_hash_manager) + + def test_order_creation_check_waits_for_originating_transaction_to_be_mined(self): + request_sent_event = asyncio.Event() + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="hash1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "2", + exchange_order_id="hash2", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("20000"), + amount=Decimal("200"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + + hash_not_matching_order: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + hash_not_matching_order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + no_mined_tx_order: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "2"] + no_mined_tx_order.update_creation_transaction_hash( + creation_transaction_hash="HHHHHHHHHHHHHHH") + + transaction_data = (b'\x12\xd1\x01\n8/injective.exchange.v1beta1.MsgBatchUpdateOrdersResponse' + b'\x12\x94\x01\n\x02\x00\x00\x12\x02\x00\x00\x1aB' + b'0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1' # noqa: mock + b'\x1aB' + b'0x115975551b4f86188eee6b93d789fcc78df6e89e40011b929299b6e142f53515' # noqa: mock + b'"\x00"\x00') + transaction_messages = [ + { + "type": "/cosmos.authz.v1beta1.MsgExec", + "value": { + "grantee": PrivateKey.from_hex(self.trading_account_private_key).to_public_key().to_acc_bech32(), + "msgs": [ + { + "@type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "sender": self.portfolio_account_injective_address, + "subaccount_id": "", + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "spot_orders_to_create": [], + "derivative_orders_to_create": [ + { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str( + hash_not_matching_order.price * Decimal(f"1e{self.quote_decimals}")), + "quantity": str(hash_not_matching_order.amount) + }, + "order_type": hash_not_matching_order.trade_type.name, + "trigger_price": "0.000000000000000000" + } + ], + "binary_options_orders_to_cancel": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_create": [] + } + ] + } + } + ] + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "13302254", + "blockTimestamp": "2023-07-05 13:55:09.94 +0000 UTC", + "hash": "0x66a360da2fd6884b53b5c019f1a2b5bed7c7c8fc07e83a9c36ad3362ede096ae", # noqa: mock + "data": base64.b64encode(transaction_data).decode(), + "gasWanted": "168306", + "gasUsed": "167769", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "84153000000000" + } + ], + "gasLimit": "168306", + "payer": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" # noqa: mock + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(transaction_messages).encode()).decode(), + "signatures": [ + { + "pubkey": "035ddc4d5642b9383e2f087b2ee88b7207f6286ebc9f310e9df1406eccc2c31813", # noqa: mock + "address": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "sequence": "16450", + "signature": "S9atCwiVg9+8vTpbciuwErh54pJOAry3wHvbHT2fG8IumoE+7vfuoP7mAGDy2w9am+HHa1yv60VSWo3cRhWC9g==" + } + ], + "txNumber": "13182", + "blockUnixTimestamp": "1688565309940", + "logs": "W3sibXNnX2luZGV4IjowLCJldmVudHMiOlt7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5IjoiYWN0aW9uIiwidmFsdWUiOiIvaW5qZWN0aXZlLmV4Y2hhbmdlLnYxYmV0YTEuTXNnQmF0Y2hVcGRhdGVPcmRlcnMifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJtb2R1bGUiLCJ2YWx1ZSI6ImV4Y2hhbmdlIn1dfSx7InR5cGUiOiJjb2luX3NwZW50IiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic3BlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjE2NTE2NTAwMHBlZ2d5MHg4N2FCM0I0Qzg2NjFlMDdENjM3MjM2MTIxMUI5NmVkNERjMzZCMUI1In1dfSx7InR5cGUiOiJjb2luX3JlY2VpdmVkIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjZWl2ZXIiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxNjUxNjUwMDBwZWdneTB4ODdhQjNCNEM4NjYxZTA3RDYzNzIzNjEyMTFCOTZlZDREYzM2QjFCNSJ9XX0seyJ0eXBlIjoidHJhbnNmZXIiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNpcGllbnQiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMTY1MTY1MDAwcGVnZ3kweDg3YUIzQjRDODY2MWUwN0Q2MzcyMzYxMjExQjk2ZWQ0RGMzNkIxQjUifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiI1NTAwMDAwMDAwMDAwMDAwMDAwMGluaiJ9XX0seyJ0eXBlIjoiY29pbl9yZWNlaXZlZCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2VpdmVyIiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiNTUwMDAwMDAwMDAwMDAwMDAwMDBpbmoifV19LHsidHlwZSI6InRyYW5zZmVyIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjaXBpZW50IiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjU1MDAwMDAwMDAwMDAwMDAwMDAwaW5qIn1dfSx7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifV19XX1d" # noqa: mock + } + } + mock_tx_by_hash_queue = AsyncMock() + mock_tx_by_hash_queue.get.side_effect = [transaction_response, ValueError("Transaction not found in a block")] + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_tx_by_hash_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=13302254 + ) + self.exchange._data_source._query_executor._transaction_block_height_responses = mock_queue + + original_order_hash_manager = self.exchange._data_source.order_hash_manager + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._check_orders_creation_transactions() + ) + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotEqual(original_order_hash_manager, self.exchange._data_source._order_hash_manager) + + mock_queue.get.assert_called() + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_partially_filled_order_status_response( + order=order, + mock_api=mock_api) + + if self.is_order_fill_http_update_included_in_status_update: + self.configure_partial_fill_trade_response( + order=order, + mock_api=mock_api) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + if self.is_order_fill_http_update_included_in_status_update: + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(self.expected_partial_fill_price, fill_event.price) + self.assertEqual(self.expected_partial_fill_amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + granter_address=self.portfolio_account_injective_address, + granter_subaccount_index=1, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange_with_non_default_subaccount = InjectiveV2PerpetualDerivative( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange_with_non_default_subaccount._data_source._query_executor = self.exchange._data_source._query_executor + self.exchange = exchange_with_non_default_subaccount + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_balance_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_account_balance_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_derivative_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_derivative_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_logs_errors(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_user_stream_raises_cancel_exception(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_derivative_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_derivative_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + if self.is_order_fill_http_update_included_in_status_update: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + else: + # If the fill events will not be requested with the order status, we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + request_sent_event.set() + + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + if self.is_order_fill_http_update_included_in_status_update: + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + request_sent_event.clear() + + # Configure again the response to the order fills request since it is required by lost orders update logic + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait( + self.all_spot_markets_mock_response + ) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(response) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_check_network_success(self, mock_api): + response = self.network_status_request_successful_mock_response + self.exchange._data_source._query_executor._ping_responses.put_nowait(response) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=10) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = RpcError("Test Error") + self.exchange._data_source._query_executor._ping_responses = mock_queue + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.exchange._data_source._query_executor._ping_responses = mock_queue + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + self.configure_all_symbols_response(mock_api=mock_api) + response = self.latest_prices_request_mock_response + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait(response) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_get_fee(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + maker_fee_rate = Decimal(self.all_derivative_markets_mock_response[0]["makerFeeRate"]) + taker_fee_rate = Decimal(self.all_derivative_markets_mock_response[0]["takerFeeRate"]) + + maker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + position_action=PositionAction.OPEN, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=True + ) + + self.assertEqual(maker_fee_rate, maker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + taker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + position_action=PositionAction.OPEN, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=False, + ) + + self.assertEqual(taker_fee_rate, taker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "2", + exchange_order_id=self.exchange_order_id_prefix + "2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "3", + exchange_order_id=self.exchange_order_id_prefix + "3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "4", + exchange_order_id=self.exchange_order_id_prefix + "4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "3", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "4", self.exchange.in_flight_orders) + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_leverage_failure(self, mock_api): + # Leverage is configured in a per order basis + pass + + @aioresponses() + def test_set_leverage_success(self, mock_api): + # Leverage is configured in a per order basis + pass + + @aioresponses() + def test_funding_payment_polling_loop_sends_update_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + + self.async_tasks.append(asyncio.get_event_loop().create_task(self.exchange._funding_payment_polling_loop())) + + funding_payments = { + "payments": [{ + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "amount": str(self.target_funding_payment_payment_amount), + "timestamp": 1000 * 1e3, + }], + "paging": { + "total": 1000 + } + } + self.exchange._data_source.query_executor._funding_payments_responses.put_nowait(funding_payments) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_payment_funding_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=funding_rate + ) + self.exchange._data_source.query_executor._funding_rates_responses = mock_queue + + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + request_sent_event.clear() + + funding_payments = { + "payments": [{ + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "amount": str(self.target_funding_payment_payment_amount), + "timestamp": self.target_funding_payment_timestamp * 1e3, + }], + "paging": { + "total": 1000 + } + } + self.exchange._data_source.query_executor._funding_payments_responses.put_nowait(funding_payments) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_payment_funding_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=funding_rate + ) + self.exchange._data_source.query_executor._funding_rates_responses = mock_queue + + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.funding_payment_logger.event_log)) + funding_event: FundingPaymentCompletedEvent = self.funding_payment_logger.event_log[0] + self.assertEqual(self.target_funding_payment_timestamp, funding_event.timestamp) + self.assertEqual(self.exchange.name, funding_event.market) + self.assertEqual(self.trading_pair, funding_event.trading_pair) + self.assertEqual(self.target_funding_payment_payment_amount, funding_event.amount) + self.assertEqual(self.target_funding_payment_funding_rate, funding_event.funding_rate) + + def test_listen_for_funding_info_update_initializes_funding_info(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.exchange._data_source._query_executor._derivative_market_responses.put_nowait( + self.all_derivative_markets_mock_response[0] + ) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_info_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.exchange._data_source.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": str(self.target_funding_info_mark_price) + } + self.exchange._data_source.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": str(self.target_funding_info_index_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.exchange._data_source.query_executor._derivative_trades_responses.put_nowait(trades) + + funding_info_update = FundingInfoUpdate( + trading_pair=self.trading_pair, + index_price=Decimal("29423.16356086"), + mark_price=Decimal("9084900"), + next_funding_utc_timestamp=1690426800, + rate=Decimal("0.000004"), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_update, asyncio.CancelledError] + self.exchange.order_book_tracker.data_source._message_queue[ + self.exchange.order_book_tracker.data_source._funding_info_messages_queue_key + ] = mock_queue + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp, funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + def test_listen_for_funding_info_update_updates_funding_info(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.exchange._data_source._query_executor._derivative_market_responses.put_nowait( + self.all_derivative_markets_mock_response[0] + ) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_info_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.exchange._data_source.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": str(self.target_funding_info_mark_price) + } + self.exchange._data_source.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": str( + self.target_funding_info_index_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.exchange._data_source.query_executor._derivative_trades_responses.put_nowait(trades) + + funding_info_update = FundingInfoUpdate( + trading_pair=self.trading_pair, + index_price=Decimal("29423.16356086"), + mark_price=Decimal("9084900"), + next_funding_utc_timestamp=1690426800, + rate=Decimal("0.000004"), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_update, asyncio.CancelledError] + self.exchange.order_book_tracker.data_source._message_queue[ + self.exchange.order_book_tracker.data_source._funding_info_messages_queue_key + ] = mock_queue + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + def test_existing_account_position_detected_on_positions_update(self): + self._simulate_trading_rules_initialized() + self.configure_all_symbols_response(mock_api=None) + + position_data = { + "ticker": "BTC/USDT PERP", + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "direction": "long", + "quantity": "0.01", + "entryPrice": "25000000000", + "margin": "248483436.058851", + "liquidationPrice": "47474612957.985809", + "markPrice": "28984256513.07", + "aggregateReduceOnlyQuantity": "0", + "updatedAt": "1691077382583", + "createdAt": "-62135596800000" + } + positions = { + "positions": [position_data], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + self.exchange._data_source._query_executor._derivative_positions_responses.put_nowait(positions) + + self.async_run_with_timeout(self.exchange._update_positions()) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(self.trading_pair, pos.trading_pair) + self.assertEqual(PositionSide.LONG, pos.position_side) + self.assertEqual(Decimal(position_data["quantity"]), pos.amount) + entry_price = Decimal(position_data["entryPrice"]) * Decimal(f"1e{-self.quote_decimals}") + self.assertEqual(entry_price, pos.entry_price) + expected_leverage = ((Decimal(position_data["entryPrice"]) * Decimal(position_data["quantity"])) + / Decimal(position_data["margin"])) + self.assertEqual(expected_leverage, pos.leverage) + mark_price = Decimal(position_data["markPrice"]) * Decimal(f"1e{-self.quote_decimals}") + expected_unrealized_pnl = (mark_price - entry_price) * Decimal(position_data["quantity"]) + self.assertEqual(expected_unrealized_pnl, pos.unrealized_pnl) + + def test_user_stream_position_update(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + position_data = { + "ticker": "BTC/USDT PERP", + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "direction": "long", + "quantity": "0.01", + "entryPrice": "25000000000", + "margin": "248483436.058851", + "liquidationPrice": "47474612957.985809", + "markPrice": "28984256513.07", + "aggregateReduceOnlyQuantity": "0", + "updatedAt": "1691077382583", + "createdAt": "-62135596800000" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_data, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_positions_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_positions_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(self.trading_pair, pos.trading_pair) + self.assertEqual(PositionSide.LONG, pos.position_side) + self.assertEqual(Decimal(position_data["quantity"]), pos.amount) + entry_price = Decimal(position_data["entryPrice"]) * Decimal(f"1e{-self.quote_decimals}") + self.assertEqual(entry_price, pos.entry_price) + expected_leverage = ((Decimal(position_data["entryPrice"]) * Decimal(position_data["quantity"])) + / Decimal(position_data["margin"])) + self.assertEqual(expected_leverage, pos.leverage) + mark_price = Decimal(position_data["markPrice"]) * Decimal(f"1e{-self.quote_decimals}") + expected_unrealized_pnl = (mark_price - entry_price) * Decimal(position_data["quantity"]) + self.assertEqual(expected_unrealized_pnl, pos.unrealized_pnl) + + def _expected_initial_status_dict(self) -> Dict[str, bool]: + status_dict = super()._expected_initial_status_dict() + status_dict["data_source_initialized"] = False + return status_dict + + @staticmethod + def _callback_wrapper_with_response(callback: Callable, response: Any, *args, **kwargs): + callback(args, kwargs) + if isinstance(response, Exception): + raise response + else: + return response + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + self.exchange._data_source._query_executor._account_portfolio_responses.put_nowait(response) + return "" + + def _msg_exec_simulation_mock_response(self) -> Any: + return { + "gasInfo": { + "gasWanted": "50000000", + "gasUsed": "90749" + }, + "result": { + "data": "Em8KJS9jb3Ntb3MuYXV0aHoudjFiZXRhMS5Nc2dFeGVjUmVzcG9uc2USRgpECkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA=", # noqa: mock + "log": "", + "events": [], + "msgResponses": [ + OrderedDict([ + ("@type", "/cosmos.authz.v1beta1.MsgExecResponse"), + ("results", [ + "CkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA="]) # noqa: mock + ]) + ] + } + } + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "[]"} # noqa: mock + + def _order_cancelation_request_erroneous_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "Error"} # noqa: mock + + def _order_status_request_open_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_partially_filled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": str(self.expected_partial_fill_amount), + "state": "partial_filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_completely_filled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": str(order.amount), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_canceled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_not_found_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [], + "paging": { + "total": "0" + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower, + "executionPrice": str(self.expected_partial_fill_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(self.expected_partial_fill_amount), + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.portfolio_account_injective_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _order_fills_request_full_fill_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower, + "executionPrice": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(order.amount), + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.portfolio_account_injective_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } diff --git a/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_offchain_vault.py b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_offchain_vault.py new file mode 100644 index 0000000..1d3d04e --- /dev/null +++ b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_derivative_for_offchain_vault.py @@ -0,0 +1,2705 @@ +import asyncio +import base64 +import json +from collections import OrderedDict +from copy import copy +from decimal import Decimal +from functools import partial +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from unittest.mock import AsyncMock + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict +from grpc import RpcError +from pyinjective import Address, PrivateKey +from pyinjective.composer import Composer + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_derivative import ( + InjectiveV2PerpetualDerivative, +) +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_utils import InjectiveConfigMap +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveTestnetNetworkMode, + InjectiveVaultAccountMode, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayPerpetualInFlightOrder +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + FundingPaymentCompletedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_gather + + +class InjectiveV2PerpetualDerivativeForOffChainVaultTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.base_asset_denom = "inj" + cls.quote_asset_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" # noqa: mock + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" # noqa: mock + + _, grantee_private_key = PrivateKey.generate() + cls.trading_account_private_key = grantee_private_key.to_hex() + cls.trading_account_public_key = grantee_private_key.to_public_key().to_address().to_acc_bech32() + cls.trading_account_subaccount_index = 0 + cls.vault_contract_address = "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp" # noqa: mock" + cls.vault_contract_subaccount_index = 1 + vault_address = Address.from_acc_bech32(cls.vault_contract_address) + cls.vault_contract_subaccount_id = vault_address.get_subaccount_id( + index=cls.vault_contract_subaccount_index + ) + cls.base_decimals = 18 + cls.quote_decimals = 6 + + cls._transaction_hash = "017C130E3602A48E5C9D661CAC657BF1B79262D4B71D5C25B1DA62DE2338DA0E" # noqa: mock" + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + self._logs_event: Optional[asyncio.Event] = None + self.exchange._data_source.logger().setLevel(1) + self.exchange._data_source.logger().addHandler(self) + + self.exchange._orders_processing_delta_time = 0.1 + self.async_tasks.append(self.async_loop.create_task(self.exchange._process_queued_orders())) + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + self._logs_event = None + + def handle(self, record): + super().handle(record=record) + if self._logs_event is not None: + self._logs_event.set() + + def reset_log_event(self): + if self._logs_event is not None: + self._logs_event.clear() + + async def wait_for_a_log(self): + if self._logs_event is not None: + await self._logs_event.wait() + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY] + + @property + def funding_info_url(self): + raise NotImplementedError + + @property + def funding_payment_url(self): + raise NotImplementedError + + @property + def funding_info_mock_response(self): + raise NotImplementedError + + @property + def empty_funding_payment_mock_response(self): + raise NotImplementedError + + @property + def funding_payment_mock_response(self): + raise NotImplementedError + + @property + def all_symbols_url(self): + raise NotImplementedError + + @property + def latest_prices_url(self): + raise NotImplementedError + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + raise NotImplementedError + + @property + def order_creation_url(self): + raise NotImplementedError + + @property + def balance_url(self): + raise NotImplementedError + + @property + def all_symbols_request_mock_response(self): + raise NotImplementedError + + @property + def latest_prices_request_mock_response(self): + return { + "trades": [ + { + "orderHash": "0x9ffe4301b24785f09cb529c1b5748198098b17bd6df8fe2744d923a574179229", # noqa: mock + "subaccountId": "0xa73ad39eab064051fb468a5965ee48ca87ab66d4000000000000000000000000", # noqa: mock + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "tradeExecutionType": "limitMatchRestingOrder", + "positionDelta": { + "tradeDirection": "sell", + "executionPrice": str( + Decimal(str(self.expected_latest_price)) * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "142000000000000000000", + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": "-112393", + "executedAt": "1688734042063", + "feeRecipient": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock + "tradeId": "13374245_801_0", + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = self.all_derivative_markets_mock_response + response.append({ + "marketId": "invalid_market_id", + "marketStatus": "active", + "ticker": "INVALID/MARKET", + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }) + + return ("INVALID_MARKET", response) + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + raise NotImplementedError + + @property + def trading_rules_request_erroneous_mock_response(self): + return [{ + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + }] + + @property + def order_creation_request_successful_mock_response(self): + return {"txhash": "017C130E3602A48E5C9D661CAC657BF1B79262D4B71D5C25B1DA62DE2338DA0E", # noqa: mock" + "rawLog": "[]"} + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "accountAddress": self.vault_contract_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + { + "denom": self.quote_asset_denom, + "amount": str(Decimal(1000) * Decimal(1e6)) + } + ], + "subaccounts": [ + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.quote_asset_denom, + "deposit": { + "totalBalance": str(Decimal(2000) * Decimal(1e6)), + "availableBalance": str(Decimal(2000) * Decimal(1e6)) + } + }, + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "accountAddress": self.vault_contract_address, + "bankBalances": [], + "subaccounts": [ + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_event_websocket_update(self): + return { + "balance": { + "subaccountId": self.vault_contract_subaccount_id, + "accountAddress": self.vault_contract_address, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)), + } + }, + "timestamp": "1688659208000" + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + market_info = self.all_derivative_markets_mock_response[0] + min_price_tick_size = (Decimal(market_info["minPriceTickSize"]) + * Decimal(f"1e{-market_info['quoteTokenMeta']['decimals']}")) + min_quantity_tick_size = Decimal(market_info["minQuantityTickSize"]) + trading_rule = TradingRule( + trading_pair=self.trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + + return trading_rule + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "0x3870fbdd91f07d54425147b1bb96404f4f043ba6335b422a6d494d285b387f00" # noqa: mock + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + raise NotImplementedError + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "10414162_22_33" + + @property + def all_spot_markets_mock_response(self): + return [{ + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] + + @property + def all_derivative_markets_mock_response(self): + return [ + { + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset} PERP", + "oracleBase": "0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", # noqa: mock + "oracleQuote": "0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", # noqa: mock + "oracleType": "pyth", + "oracleScaleFactor": 6, + "initialMarginRatio": "0.195", + "maintenanceMarginRatio": "0.05", + "quoteDenom": self.quote_asset_denom, + "quoteTokenMeta": { + "name": "Testnet Tether USDT", + "address": "0x0000000000000000000000000000000000000000", + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0003", + "takerFeeRate": "0.003", + "serviceProviderFee": "0.4", + "isPerpetual": True, + "minPriceTickSize": "100", + "minQuantityTickSize": "0.0001", + "perpetualMarketInfo": { + "hourlyFundingRateCap": "0.000625", + "hourlyInterestRate": "0.00000416666", + "nextFundingTimestamp": str(self.target_funding_info_next_funding_utc_timestamp), + "fundingInterval": "3600" + }, + "perpetualMarketFunding": { + "cumulativeFunding": "81363.592243119007273334", + "cumulativePrice": "1.432536051546776736", + "lastTimestamp": "1689423842" + } + }, + ] + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + raise NotImplementedError + + def configure_successful_set_position_mode(self, position_mode: PositionMode, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None): + raise NotImplementedError + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Tuple[str, str]: + raise NotImplementedError + + def configure_failed_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Tuple[str, str]: + raise NotImplementedError + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + raise NotImplementedError + + def funding_info_event_for_websocket_update(self): + raise NotImplementedError + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return self.market_id + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveVaultAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + vault_contract_address=self.vault_contract_address, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange = InjectiveV2PerpetualDerivative( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange._data_source._query_executor = ProgrammableQueryExecutor() + exchange._data_source._spot_market_and_trading_pair_map = bidict() + exchange._data_source._derivative_market_and_trading_pair_map = bidict({self.market_id: self.trading_pair}) + + exchange._data_source._composer = Composer(network=exchange._data_source.network_name) + + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def configure_all_symbols_response( + self, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + all_markets_mock_response = self.all_spot_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + all_markets_mock_response = self.all_derivative_markets_mock_response + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(all_markets_mock_response) + return "" + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.configure_all_symbols_response(mock_api=mock_api, callback=callback) + return "" + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait([]) + response = self.trading_rules_request_erroneous_mock_response + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(response) + return "" + + def configure_successful_cancelation_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_erroneous_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + raise NotImplementedError + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses + ) -> List[str]: + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_canceled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_open_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_open_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_http_error_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for historical orders responses") + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return None + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return None + + def configure_order_not_found_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_not_found_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_derivative_orders_responses = mock_queue + return [] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def configure_full_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._derivative_trades_responses = mock_queue + return None + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock" + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": str(order.amount), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "margin": "31342413000", + "txHash": order.creation_transaction_hash + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower(), + "executionPrice": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(order.amount), + "executionMargin": "3693162304" + }, + "payout": "3693278402.762361271848955224", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1687878089569", + "feeRecipient": self.vault_contract_address, # noqa: mock + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + } + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + queue_mock = AsyncMock() + queue_mock.get.side_effect = Exception("Test error") + self.exchange._data_source._query_executor._spot_markets_responses = queue_mock + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs(), timeout=10) + + self.assertEqual(0, len(result)) + + def test_batch_order_create(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("11"), + quantity=Decimal("3"), + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944", # noqa: mock" + creation_transaction_hash=response["txhash"] + ) + sell_order_to_create_in_flight = GatewayPerpetualInFlightOrder( + client_order_id=orders[1].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=orders[1].price, + amount=orders[1].quantity, + exchange_order_id="0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e945", # noqa: mock" + creation_transaction_hash=response["txhash"] + ) + + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + + expected_order_hashes = [ + buy_order_to_create_in_flight.exchange_order_id, + sell_order_to_create_in_flight.exchange_order_id, + ] + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response( + orders=[buy_order_to_create_in_flight, sell_order_to_create_in_flight], + order_hashes=[expected_order_hashes] + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + """Open long position""" + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response(orders=[order], + order_hashes=[expected_order_hash]) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self.configure_all_symbols_response(mock_api=None) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response( + orders=[order], + order_hashes=[expected_order_hash] + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order size 0.01. The order will not be created, " + "increase the amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_to_close_short_position(self, mock_api): + self.configure_all_symbols_response(mock_api=None) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 4 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_buy_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response(orders=[order], + order_hashes=[expected_order_hash]) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_order_to_close_long_position(self, mock_api): + self.configure_all_symbols_response(mock_api=None) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + leverage = 5 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + order_id = self.place_sell_order(position_action=PositionAction.CLOSE) + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response(orders=[order], + order_hashes=[expected_order_hash]) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + def test_batch_order_cancel(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id=self.expected_exchange_order_id + "2", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("110"), + order_type=OrderType.LIMIT, + ) + + buy_order_to_cancel: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders["11"] + sell_order_to_cancel: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders["12"] + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait(transaction_simulation_response) + + response = self._order_cancelation_request_successful_mock_response(order=buy_order_to_cancel) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + self.exchange.batch_order_cancel(orders_to_cancel=orders_to_cancel) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], buy_order_to_cancel.cancel_tx_hash) + self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], sell_order_to_cancel.cancel_tx_hash) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + def test_order_not_found_in_its_creating_transaction_marked_as_failed_during_order_creation_check(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="0x9f94598b4842ab66037eaa7c64ec10ae16dcf196e61db8522921628522c0f62e", # noqa: mock + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: GatewayPerpetualInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + modified_order = copy(order) + modified_order.amount = modified_order.amount + Decimal("1") + transaction_response = self._orders_creation_transaction_response( + orders=[modified_order], + order_hashes=["0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1"], # noqa: mock" + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses.put_nowait(transaction_response) + + self.async_run_with_timeout(self.exchange._check_orders_creation_transactions()) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order.client_order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order.client_order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + def test_user_stream_balance_update(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_balance_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_account_balance_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_derivative_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_derivative_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_logs_errors(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_user_stream_raises_cancel_exception(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_partially_filled_order_status_response( + order=order, + mock_api=mock_api) + + if self.is_order_fill_http_update_included_in_status_update: + self.configure_partial_fill_trade_response( + order=order, + mock_api=mock_api) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + if self.is_order_fill_http_update_included_in_status_update: + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(self.expected_partial_fill_price, fill_event.price) + self.assertEqual(self.expected_partial_fill_amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_derivative_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_derivative_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_derivative_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_derivative_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_completely_filled_order_status_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + if self.is_order_fill_http_update_included_in_status_update: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + else: + # If the fill events will not be requested with the order status, we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + request_sent_event.set() + + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + if self.is_order_fill_http_update_included_in_status_update: + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + request_sent_event.clear() + + # Configure again the response to the order fills request since it is required by lost orders update logic + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait( + self.all_spot_markets_mock_response + ) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait(response) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_check_network_success(self, mock_api): + response = self.network_status_request_successful_mock_response + self.exchange._data_source._query_executor._ping_responses.put_nowait(response) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=10) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = RpcError("Test Error") + self.exchange._data_source._query_executor._ping_responses = mock_queue + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.exchange._data_source._query_executor._ping_responses = mock_queue + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + self.configure_all_symbols_response(mock_api=mock_api) + response = self.latest_prices_request_mock_response + self.exchange._data_source._query_executor._derivative_trades_responses.put_nowait(response) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_get_fee(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + maker_fee_rate = Decimal(self.all_derivative_markets_mock_response[0]["makerFeeRate"]) + taker_fee_rate = Decimal(self.all_derivative_markets_mock_response[0]["takerFeeRate"]) + + maker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + position_action=PositionAction.OPEN, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=True + ) + + self.assertEqual(maker_fee_rate, maker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + taker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + position_action=PositionAction.OPEN, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=False, + ) + + self.assertEqual(taker_fee_rate, taker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "2", + exchange_order_id=self.exchange_order_id_prefix + "2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "3", + exchange_order_id=self.exchange_order_id_prefix + "3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(GatewayPerpetualInFlightOrder( + client_order_id=self.client_order_id_prefix + "4", + exchange_order_id=self.exchange_order_id_prefix + "4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "3", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "4", self.exchange.in_flight_orders) + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_leverage_failure(self, mock_api): + # Leverage is configured in a per order basis + pass + + @aioresponses() + def test_set_leverage_success(self, mock_api): + # Leverage is configured in a per order basis + pass + + @aioresponses() + def test_funding_payment_polling_loop_sends_update_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + + self.async_tasks.append(asyncio.get_event_loop().create_task(self.exchange._funding_payment_polling_loop())) + + funding_payments = { + "payments": [{ + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "amount": str(self.target_funding_payment_payment_amount), + "timestamp": 1000 * 1e3, + }], + "paging": { + "total": 1000 + } + } + self.exchange._data_source.query_executor._funding_payments_responses.put_nowait(funding_payments) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_payment_funding_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=funding_rate + ) + self.exchange._data_source.query_executor._funding_rates_responses = mock_queue + + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + request_sent_event.clear() + + funding_payments = { + "payments": [{ + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "amount": str(self.target_funding_payment_payment_amount), + "timestamp": self.target_funding_payment_timestamp * 1e3, + }], + "paging": { + "total": 1000 + } + } + self.exchange._data_source.query_executor._funding_payments_responses.put_nowait(funding_payments) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_payment_funding_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=funding_rate + ) + self.exchange._data_source.query_executor._funding_rates_responses = mock_queue + + self.exchange._funding_fee_poll_notifier.set() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.funding_payment_logger.event_log)) + funding_event: FundingPaymentCompletedEvent = self.funding_payment_logger.event_log[0] + self.assertEqual(self.target_funding_payment_timestamp, funding_event.timestamp) + self.assertEqual(self.exchange.name, funding_event.market) + self.assertEqual(self.trading_pair, funding_event.trading_pair) + self.assertEqual(self.target_funding_payment_payment_amount, funding_event.amount) + self.assertEqual(self.target_funding_payment_funding_rate, funding_event.funding_rate) + + def test_listen_for_funding_info_update_initializes_funding_info(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.exchange._data_source._query_executor._derivative_market_responses.put_nowait( + self.all_derivative_markets_mock_response[0] + ) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_info_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.exchange._data_source.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": str(self.target_funding_info_mark_price) + } + self.exchange._data_source.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": str( + self.target_funding_info_index_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.exchange._data_source.query_executor._derivative_trades_responses.put_nowait(trades) + + funding_info_update = FundingInfoUpdate( + trading_pair=self.trading_pair, + index_price=Decimal("29423.16356086"), + mark_price=Decimal("9084900"), + next_funding_utc_timestamp=1690426800, + rate=Decimal("0.000004"), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_update, asyncio.CancelledError] + self.exchange.order_book_tracker.data_source._message_queue[ + self.exchange.order_book_tracker.data_source._funding_info_messages_queue_key + ] = mock_queue + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual( + self.target_funding_info_next_funding_utc_timestamp, funding_info.next_funding_utc_timestamp + ) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + def test_listen_for_funding_info_update_updates_funding_info(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.exchange._data_source._query_executor._derivative_market_responses.put_nowait( + self.all_derivative_markets_mock_response[0] + ) + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": str(self.target_funding_info_rate), + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.exchange._data_source.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": str(self.target_funding_info_mark_price) + } + self.exchange._data_source.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": str( + self.target_funding_info_index_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.exchange._data_source.query_executor._derivative_trades_responses.put_nowait(trades) + + funding_info_update = FundingInfoUpdate( + trading_pair=self.trading_pair, + index_price=Decimal("29423.16356086"), + mark_price=Decimal("9084900"), + next_funding_utc_timestamp=1690426800, + rate=Decimal("0.000004"), + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_update, asyncio.CancelledError] + self.exchange.order_book_tracker.data_source._message_queue[ + self.exchange.order_book_tracker.data_source._funding_info_messages_queue_key + ] = mock_queue + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + def test_existing_account_position_detected_on_positions_update(self): + self._simulate_trading_rules_initialized() + self.configure_all_symbols_response(mock_api=None) + + position_data = { + "ticker": "BTC/USDT PERP", + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "direction": "long", + "quantity": "0.01", + "entryPrice": "25000000000", + "margin": "248483436.058851", + "liquidationPrice": "47474612957.985809", + "markPrice": "28984256513.07", + "aggregateReduceOnlyQuantity": "0", + "updatedAt": "1691077382583", + "createdAt": "-62135596800000" + } + positions = { + "positions": [position_data], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + self.exchange._data_source._query_executor._derivative_positions_responses.put_nowait(positions) + + self.async_run_with_timeout(self.exchange._update_positions()) + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(self.trading_pair, pos.trading_pair) + self.assertEqual(PositionSide.LONG, pos.position_side) + self.assertEqual(Decimal(position_data["quantity"]), pos.amount) + entry_price = Decimal(position_data["entryPrice"]) * Decimal(f"1e{-self.quote_decimals}") + self.assertEqual(entry_price, pos.entry_price) + expected_leverage = ((Decimal(position_data["entryPrice"]) * Decimal(position_data["quantity"])) + / Decimal(position_data["margin"])) + self.assertEqual(expected_leverage, pos.leverage) + mark_price = Decimal(position_data["markPrice"]) * Decimal(f"1e{-self.quote_decimals}") + expected_unrealized_pnl = (mark_price - entry_price) * Decimal(position_data["quantity"]) + self.assertEqual(expected_unrealized_pnl, pos.unrealized_pnl) + + def test_user_stream_position_update(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + position_data = { + "ticker": "BTC/USDT PERP", + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "direction": "long", + "quantity": "0.01", + "entryPrice": "25000000000", + "margin": "248483436.058851", + "liquidationPrice": "47474612957.985809", + "markPrice": "28984256513.07", + "aggregateReduceOnlyQuantity": "0", + "updatedAt": "1691077382583", + "createdAt": "-62135596800000" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [position_data, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_positions_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_positions_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(len(self.exchange.account_positions), 1) + pos = list(self.exchange.account_positions.values())[0] + self.assertEqual(self.trading_pair, pos.trading_pair) + self.assertEqual(PositionSide.LONG, pos.position_side) + self.assertEqual(Decimal(position_data["quantity"]), pos.amount) + entry_price = Decimal(position_data["entryPrice"]) * Decimal(f"1e{-self.quote_decimals}") + self.assertEqual(entry_price, pos.entry_price) + expected_leverage = ((Decimal(position_data["entryPrice"]) * Decimal(position_data["quantity"])) + / Decimal(position_data["margin"])) + self.assertEqual(expected_leverage, pos.leverage) + mark_price = Decimal(position_data["markPrice"]) * Decimal(f"1e{-self.quote_decimals}") + expected_unrealized_pnl = (mark_price - entry_price) * Decimal(position_data["quantity"]) + self.assertEqual(expected_unrealized_pnl, pos.unrealized_pnl) + + def _expected_initial_status_dict(self) -> Dict[str, bool]: + status_dict = super()._expected_initial_status_dict() + status_dict["data_source_initialized"] = False + return status_dict + + @staticmethod + def _callback_wrapper_with_response(callback: Callable, response: Any, *args, **kwargs): + callback(args, kwargs) + if isinstance(response, Exception): + raise response + else: + return response + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + self.exchange._data_source._query_executor._account_portfolio_responses.put_nowait(response) + return "" + + def _msg_exec_simulation_mock_response(self) -> Any: + return { + "gasInfo": { + "gasWanted": "50000000", + "gasUsed": "90749" + }, + "result": { + "data": "Em8KJS9jb3Ntb3MuYXV0aHoudjFiZXRhMS5Nc2dFeGVjUmVzcG9uc2USRgpECkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA=", # noqa: mock + "log": "", + "events": [], + "msgResponses": [ + OrderedDict([ + ("@type", "/cosmos.authz.v1beta1.MsgExecResponse"), + ("results", [ + "CkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA="]) # noqa: mock + ]) + ] + } + } + + def _orders_creation_transaction_event(self) -> Dict[str, Any]: + return { + 'blockNumber': '44237', + 'blockTimestamp': '2023-07-18 20:25:43.518 +0000 UTC', + 'hash': self._transaction_hash, + 'messages': '[{"type":"/cosmwasm.wasm.v1.MsgExecuteContract","value":{"sender":"inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa","contract":"inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp","msg":{"admin_execute_message":{"injective_message":{"custom":{"route":"exchange","msg_data":{"batch_update_orders":{"sender":"inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp","spot_orders_to_create":[],"spot_market_ids_to_cancel_all":[],"derivative_market_ids_to_cancel_all":[],"spot_orders_to_cancel":[],"derivative_orders_to_cancel":[],"derivative_orders_to_create":[{"market_id":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0","order_info":{"subaccount_id":"1","price":"0.000000000002559000","quantity":"10000000000000000000.000000000000000000"},"order_type":1,"trigger_price":"0"}]}}}}}},"funds":[]}}]', # noqa: mock" + 'txNumber': '122692' + } + + def _orders_creation_transaction_response(self, orders: List[GatewayPerpetualInFlightOrder], order_hashes: List[str]): + derivative_orders = [] + for order in orders: + order_creation_message = { + "market_id": self.market_id, + "order_info": { + "subaccount_id": str(self.vault_contract_subaccount_index), + "fee_recipient": self.vault_contract_address, + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "quantity": str(order.amount) + }, + "order_type": 1 if order.trade_type == TradeType.BUY else 2, + "margin": str(order.amount * order.price * Decimal(f"1e{self.quote_decimals}")), + "trigger_price": "0" + } + derivative_orders.append(order_creation_message) + messages = [ + { + "type": "/cosmwasm.wasm.v1.MsgExecuteContract", + "value": { + "sender": self.trading_account_public_key, + "contract": self.vault_contract_address, + "msg": { + "admin_execute_message": { + "injective_message": { + "custom": { + "route": "exchange", + "msg_data": { + "batch_update_orders": { + "sender": self.vault_contract_address, + "spot_orders_to_create": [], + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "derivative_orders_to_create": derivative_orders}}}}}}, + "funds": []}}] + + logs = [{ + "msg_index": 0, + "events": [ + { + "type": "message", + "attributes": [{"key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract"}, + {"key": "sender", "value": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa"}, # noqa: mock" + {"key": "module", "value": "wasm"}]}, + { + "type": "execute", + "attributes": [ + {"key": "_contract_address", "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}]}, # noqa: mock" + { + "type": "reply", + "attributes": [ + {"key": "_contract_address", "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}]}, # noqa: mock" + { + "type": "wasm", + "attributes": [ + { + "key": "_contract_address", + "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}, # noqa: mock" + { + "key": "method", + "value": "instantiate"}, + { + "key": "reply_id", + "value": "1"}, + { + "key": "batch_update_orders_response", + "value": f'MsgBatchUpdateOrdersResponse {{ spot_cancel_success: [], derivative_cancel_success: [], spot_order_hashes: [], derivative_order_hashes: {order_hashes}, binary_options_cancel_success: [], binary_options_order_hashes: [], unknown_fields: UnknownFields {{ fields: None }}, cached_size: CachedSize {{ size: 0 }} }}' + } + ] + } + ] + }] + + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "30159", + "blockTimestamp": "2023-07-19 15:39:21.798 +0000 UTC", + "hash": self._transaction_hash, + "data": "Ei4KLC9jb3Ntd2FzbS53YXNtLnYxLk1zZ0V4ZWN1dGVDb250cmFjdFJlc3BvbnNl", # noqa: mock" + "gasWanted": "163571", + "gasUsed": "162984", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "81785500000000"}], "gasLimit": "163571", + "payer": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa" # noqa: mock" + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(messages).encode()).decode(), + "signatures": [ + { + "pubkey": "0382e03bf4b0ad77bef5f756a717a1a54d3c444b250b4ce097acb578aa80f58aab", # noqa: mock" + "address": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock" + "sequence": "2", + "signature": "mF+KepSndvbu5UznsqfSl3rS9HkQQkDIcwBM3UIEzlF/SORCoI2fLue5okALWX5ZzfZXmwJGdjLqfjHDcJ3uEg==" # noqa: mock" + } + ], + "txNumber": "5", + "blockUnixTimestamp": "1689781161798", + "logs": base64.b64encode(json.dumps(logs).encode()).decode(), + } + } + + return transaction_response + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "[]"} # noqa: mock + + def _order_cancelation_request_erroneous_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "Error"} # noqa: mock + + def _order_status_request_partially_filled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(self.expected_partial_fill_amount), + "state": "partial_filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower, + "executionPrice": str(self.expected_partial_fill_price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(self.expected_partial_fill_amount), + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.vault_contract_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _order_status_request_canceled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_completely_filled_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": str(order.amount), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_fills_request_full_fill_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "positionDelta": { + "tradeDirection": order.trade_type.name.lower, + "executionPrice": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "executionQuantity": str(order.amount), + "executionMargin": "1245280000" + }, + "payout": "1187984833.579447998034818126", + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.vault_contract_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _order_status_request_open_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "isReduceOnly": True, + "direction": order.trade_type.name.lower(), + "margin": "7219676852.725", + "txHash": order.creation_transaction_hash, + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_not_found_mock_response(self, order: GatewayPerpetualInFlightOrder) -> Dict[str, Any]: + return { + "orders": [], + "paging": { + "total": "0" + }, + } diff --git a/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_order_book_data_source.py b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_order_book_data_source.py new file mode 100644 index 0000000..02f932c --- /dev/null +++ b/test/hummingbot/connector/derivative/injective_v2_perpetual/test_injective_v2_perpetual_order_book_data_source.py @@ -0,0 +1,715 @@ +import asyncio +import re +from decimal import Decimal +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Awaitable, Optional, Union +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict +from pyinjective import Address, PrivateKey + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_api_order_book_data_source import ( + InjectiveV2PerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.injective_v2_perpetual.injective_v2_perpetual_derivative import ( + InjectiveV2PerpetualDerivative, +) +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveDelegatedAccountMode, + InjectiveTestnetNetworkMode, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class InjectiveV2APIOrderBookDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}/{cls.quote_asset}" + cls.market_id = "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6" # noqa: mock + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher.fetch_all") + def setUp(self, _) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + self.async_tasks = [] + asyncio.set_event_loop(self.async_loop) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + _, grantee_private_key = PrivateKey.generate() + _, granter_private_key = PrivateKey.generate() + + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=grantee_private_key.to_hex(), + subaccount_index=0, + granter_address=Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())).to_acc_bech32(), + granter_subaccount_index=0, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + self.connector = InjectiveV2PerpetualDerivative( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + self.data_source = InjectiveV2PerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + data_source=self.connector._data_source, + ) + + self.initialize_trading_account_patch = patch( + "hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source" + ".InjectiveGranteeDataSource.initialize_trading_account" + ) + self.initialize_trading_account_patch.start() + + self.query_executor = ProgrammableQueryExecutor() + self.connector._data_source._query_executor = self.query_executor + + self.log_records = [] + self._logs_event: Optional[asyncio.Event] = None + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + self.data_source._data_source.logger().setLevel(1) + self.data_source._data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.market_id: self.trading_pair})) + + def tearDown(self) -> None: + self.async_run_with_timeout(self.data_source._data_source.stop()) + self.initialize_trading_account_patch.stop() + for task in self.async_tasks: + task.cancel() + self.async_loop.stop() + # self.async_loop.close() + # Since the event loop will change we need to remove the logs event created in the old event loop + self._logs_event = None + asyncio.set_event_loop(self._original_async_loop) + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.async_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def create_task(self, coroutine: Awaitable) -> asyncio.Task: + task = self.async_loop.create_task(coroutine) + self.async_tasks.append(task) + return task + + def handle(self, record): + self.log_records.append(record) + if self._logs_event is not None: + self._logs_event.set() + + def is_logged(self, log_level: str, message: Union[str, re.Pattern]) -> bool: + expression = ( + re.compile( + f"^{message}$" + .replace(".", r"\.") + .replace("?", r"\?") + .replace("/", r"\/") + .replace("(", r"\(") + .replace(")", r"\)") + .replace("[", r"\[") + .replace("]", r"\]") + ) + if isinstance(message, str) + else message + ) + return any( + record.levelname == log_level and expression.match(record.getMessage()) is not None + for record in self.log_records + ) + + def test_get_new_order_book_successful(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + quote_decimals = derivative_markets_response[0]["quoteTokenMeta"]["decimals"] + + order_book_snapshot = { + "buys": [(Decimal("9487") * Decimal(f"1e{quote_decimals}"), + Decimal("336241"), + 1640001112223)], + "sells": [(Decimal("9487.5") * Decimal(f"1e{quote_decimals}"), + Decimal("522147"), + 1640001112224)], + "sequence": 512, + "timestamp": 1650001112223, + } + + self.query_executor._derivative_order_book_responses.put_nowait(order_book_snapshot) + + order_book = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = order_book_snapshot["sequence"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + def test_listen_for_trades_logs_exception(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + self.query_executor._public_derivative_trade_updates.put_nowait({}) + trade_data = { + "orderHash": "0x86a2f3c8aba313569ae1c985e1ec155a77434c0c8d2b1feb629ebdf9d0b2515b", # noqa: mock + "subaccountId": "0x85123cdf535f83345417918d3a78e6a5ca07b9f0000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": "8205874.039333444390458155", + "executionQuantity": "4942.2013", + "executionMargin": "0" + }, + "payout": "20495725066.893133760410882059", + "fee": "36499573.210347000000000001", + "executedAt": "1689008963214", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13492005_801_0", + "executionSide": "taker" + } + self.query_executor._public_derivative_trade_updates.put_nowait(trade_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue( + self.is_logged( + "WARNING", re.compile(r"^Invalid public derivative trade event format \(.*") + ) + ) + + def test_listen_for_trades_successful(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + quote_decimals = derivative_markets_response[0]["quoteTokenMeta"]["decimals"] + + trade_data = { + "orderHash": "0x86a2f3c8aba313569ae1c985e1ec155a77434c0c8d2b1feb629ebdf9d0b2515b", # noqa: mock + "subaccountId": "0x85123cdf535f83345417918d3a78e6a5ca07b9f0000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "sell", + "executionPrice": "8205874.039333444390458155", + "executionQuantity": "4942.2013", + "executionMargin": "0" + }, + "payout": "20495725066.893133760410882059", + "fee": "36499573.210347000000000001", + "executedAt": "1689008963214", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13492005_801_0", + "executionSide": "taker" + } + self.query_executor._public_derivative_trade_updates.put_nowait(trade_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_data["tradeId"], msg.trade_id) + self.assertEqual(int(trade_data["executedAt"]) * 1e-3, msg.timestamp) + expected_price = Decimal(trade_data["positionDelta"]["executionPrice"]) * Decimal(f"1e{-quote_decimals}") + expected_amount = Decimal(trade_data["positionDelta"]["executionQuantity"]) + self.assertEqual(expected_amount, msg.content["amount"]) + self.assertEqual(expected_price, msg.content["price"]) + self.assertEqual(self.trading_pair, msg.content["trading_pair"]) + self.assertEqual(float(TradeType.SELL.value), msg.content["trade_type"]) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + def test_listen_for_order_book_diffs_logs_exception(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + self.query_executor._derivative_order_book_updates.put_nowait({}) + order_book_data = { + "marketId": self.market_id, + "sequence": "7734169", + "buys": [ + { + "price": "0.000000000007684", + "quantity": "4578787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + }, + { + "price": "0.000000000007685", + "quantity": "4412340000000000000000", + "isActive": True, + "timestamp": "1687889316000" + } + ], + "sells": [ + { + "price": "0.000000000007723", + "quantity": "3478787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + } + ], + "updatedAt": "1687889315683", + } + self.query_executor._derivative_order_book_updates.put_nowait(order_book_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions(), timeout=5) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue( + self.is_logged( + "WARNING", re.compile(r"^Invalid derivative order book event format \(.*") + ) + ) + + @patch( + "hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source.InjectiveGranteeDataSource._initialize_timeout_height") + def test_listen_for_order_book_diffs_successful(self, _): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + quote_decimals = derivative_markets_response[0]["quoteTokenMeta"]["decimals"] + + order_book_data = { + "marketId": self.market_id, + "sequence": "7734169", + "buys": [ + { + "price": "0.000000000007684", + "quantity": "4578787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + }, + { + "price": "0.000000000007685", + "quantity": "4412340000000000000000", + "isActive": True, + "timestamp": "1687889316000" + } + ], + "sells": [ + { + "price": "0.000000000007723", + "quantity": "3478787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + } + ], + "updatedAt": "1687889315683", + } + self.query_executor._derivative_order_book_updates.put_nowait(order_book_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(order_book_data["updatedAt"]) * 1e-3, msg.timestamp) + expected_update_id = int(order_book_data["sequence"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + first_bid_price = Decimal(order_book_data["buys"][0]["price"]) * Decimal(f"1e{-quote_decimals}") + first_bid_quantity = Decimal(order_book_data["buys"][0]["quantity"]) + self.assertEqual(float(first_bid_price), bids[0].price) + self.assertEqual(float(first_bid_quantity), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + first_ask_price = Decimal(order_book_data["sells"][0]["price"]) * Decimal(f"1e{-quote_decimals}") + first_ask_quantity = Decimal(order_book_data["sells"][0]["quantity"]) + self.assertEqual(float(first_ask_price), asks[0].price) + self.assertEqual(float(first_ask_quantity), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_funding_info_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_funding_info(msg_queue)) + + def test_listen_for_funding_info_logs_exception(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + funding_rate = { + "fundingRates": [], + "paging": { + "total": "2370" + } + } + self.query_executor._funding_rates_responses.put_nowait(funding_rate) + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": "0.000004", + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": "29423.16356086" + } + self.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": "9084900", + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.query_executor._derivative_trades_responses.put_nowait(trades) + + self.query_executor._derivative_market_responses.put_nowait(derivative_markets_response[0]) + + oracle_price_event = { + "price": "29430.23874999", + "timestamp": "1690467421160" + } + self.query_executor._oracle_prices_updates.put_nowait(oracle_price_event) + self.query_executor._oracle_prices_updates.put_nowait(oracle_price_event) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions(), timeout=5) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue( + self.is_logged( + "WARNING", re.compile(r"^Invalid funding info event format \(.*") + ) + ) + + def test_listen_for_funding_info_successful(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + quote_decimals = derivative_markets_response[0]["quoteTokenMeta"]["decimals"] + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": "0.000004", + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": "29423.16356086" + } + self.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": "9084900", + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.query_executor._derivative_trades_responses.put_nowait(trades) + + self.query_executor._derivative_market_responses.put_nowait(derivative_markets_response[0]) + + oracle_price_event = { + "price": "29430.23874999", + "timestamp": "1690467421160" + } + self.query_executor._oracle_prices_updates.put_nowait(oracle_price_event) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + funding_info: FundingInfoUpdate = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual( + Decimal(trades["trades"][0]["positionDelta"]["executionPrice"]) * Decimal(f"1e{-quote_decimals}"), + funding_info.index_price) + self.assertEqual(Decimal(oracle_price["price"]), funding_info.mark_price) + self.assertEqual( + int(derivative_markets_response[0]["perpetualMarketInfo"]["nextFundingTimestamp"]), + funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(funding_rate["fundingRates"][0]["rate"]), funding_info.rate) + + def test_get_funding_info(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + derivative_markets_response = self._derivative_markets_response() + self.query_executor._derivative_markets_responses.put_nowait(derivative_markets_response) + + quote_decimals = derivative_markets_response[0]["quoteTokenMeta"]["decimals"] + + funding_rate = { + "fundingRates": [ + { + "marketId": self.market_id, + "rate": "0.000004", + "timestamp": "1690426800493" + }, + ], + "paging": { + "total": "2370" + } + } + self.query_executor._funding_rates_responses.put_nowait(funding_rate) + + oracle_price = { + "price": "29423.16356086" + } + self.query_executor._oracle_prices_responses.put_nowait(oracle_price) + + trades = { + "trades": [ + { + "orderHash": "0xbe1db35669028d9c7f45c23d31336c20003e4f8879721bcff35fc6f984a6481a", # noqa: mock + "subaccountId": "0x16aef18dbaa341952f1af1795cb49960f68dfee3000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "market", + "positionDelta": { + "tradeDirection": "buy", + "executionPrice": "9084900", + "executionQuantity": "3", + "executionMargin": "5472660" + }, + "payout": "0", + "fee": "81764.1", + "executedAt": "1689423842613", + "feeRecipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", + "tradeId": "13659264_800_0", + "executionSide": "taker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + self.query_executor._derivative_trades_responses.put_nowait(trades) + + self.query_executor._derivative_market_responses.put_nowait(derivative_markets_response[0]) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual( + Decimal(trades["trades"][0]["positionDelta"]["executionPrice"]) * Decimal(f"1e{-quote_decimals}"), + funding_info.index_price) + self.assertEqual(Decimal(oracle_price["price"]), funding_info.mark_price) + self.assertEqual( + int(derivative_markets_response[0]["perpetualMarketInfo"]["nextFundingTimestamp"]), + funding_info.next_funding_utc_timestamp) + self.assertEqual(Decimal(funding_rate["fundingRates"][0]["rate"]), funding_info.rate) + + def _spot_markets_response(self): + return [{ + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": self.ex_trading_pair, + "baseDenom": "inj", + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1687190809715" + }, + "quoteDenom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] + + def _derivative_markets_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.ex_trading_pair} PERP", + "oracleBase": "0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", # noqa: mock + "oracleQuote": "0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", # noqa: mock + "oracleType": "pyth", + "oracleScaleFactor": 6, + "initialMarginRatio": "0.195", + "maintenanceMarginRatio": "0.05", + "quoteDenom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0003", + "takerFeeRate": "0.003", + "serviceProviderFee": "0.4", + "isPerpetual": True, + "minPriceTickSize": "100", + "minQuantityTickSize": "0.0001", + "perpetualMarketInfo": { + "hourlyFundingRateCap": "0.000625", + "hourlyInterestRate": "0.00000416666", + "nextFundingTimestamp": "1690318800", + "fundingInterval": "3600" + }, + "perpetualMarketFunding": { + "cumulativeFunding": "81363.592243119007273334", + "cumulativePrice": "1.432536051546776736", + "lastTimestamp": "1689423842" + } + }] diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/__init__.py b/test/hummingbot/connector/derivative/kucoin_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..3c76a18 --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_order_book_data_source.py @@ -0,0 +1,660 @@ +import asyncio +import json +import logging +import os +import re +from decimal import Decimal +from typing import Awaitable, Dict +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_api_order_book_data_source import ( + KucoinPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative import KucoinPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + +os.environ['PYTHONASYNCIODEBUG'] = '1' + + +class KucoinPerpetualAPIOrderBookDataSourceTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "HBOT" + cls.quote_asset = "PERP" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = KucoinPerpetualDerivative( + client_config_map, + kucoin_perpetual_api_key="", + kucoin_perpetual_secret_key="", + kucoin_perpetual_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.data_source = KucoinPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map( + bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_rest_snapshot_msg(self) -> Dict: + return { + "code": "200000", + "data": { + "symbol": "XBTUSDM", + "sequence": 100, + "asks": [ + ["5000.0", 1000], + ["6000.0", 1983] + ], + "bids": [ + ["3200.0", 800], + ["3100.0", 100] + ], + "ts": 1604643655040584408 + } + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + self._simulate_trading_rules_initialized() + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.ORDER_BOOK_ENDPOINT.format(symbol=self.trading_pair) + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = { + "code": "200000", + "data": { + "asks": [ + [ + 4114.25, + 6.263 + ] + ], + "bids": [ + [ + 4112.25, + 49.29 + ] + ] + } + } + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(4112.25, bids[0].price) + self.assertEqual(49.29 * 0.000001, bids[0].amount) + self.assertEqual(1, len(asks)) + self.assertEqual(4114.25, asks[0].price) + self.assertEqual(6.263 * 0.000001, asks[0].amount) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.ORDER_BOOK_ENDPOINT.format(symbol=self.trading_pair)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_web_utils.next_message_id") + def test_listen_for_subscriptions_subscribes_to_trades_order_diffs_and_instruments(self, mock_api, id_mock, mock_ws): + id_mock.side_effect = [1, 2, 3] + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "type": "ack", + "id": 1 + } + result_subscribe_diffs = { + "type": "ack", + "id": 2 + } + result_subscribe_instruments = { + "type": "ack", + "id": 3 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(result_subscribe_diffs)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(result_subscribe_instruments)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=mock_ws.return_value) + + self.assertEqual(3, len(sent_subscription_messages)) + expected_trade_subscription = { + "id": 1, + "type": "subscribe", + "topic": f"/contractMarket/ticker:{self.trading_pair}", + "privateChannel": False, + "response": False + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "id": 2, + "type": "subscribe", + "topic": f"/contractMarket/level2:{self.trading_pair}", + "privateChannel": False, + "response": False + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book, trade and funding info channels..." + )) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_logs_exception_details(self, mock_api, _, ws_connect_mock): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_api, _, ws_connect_mock): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "channel": CONSTANTS.WS_TRADES_TOPIC, + "market": self.ex_trading_pair, + "type": "update", + "data": [ + { + "price": 10000, + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + self._simulate_trading_rules_initialized() + mock_queue = AsyncMock() + trade_event = { + "type": "message", + "topic": f"/market/match:{self.trading_pair}", + "subject": "trade.l3match", + "data": { + "sequence": "1545896669145", + "type": "match", + "symbol": self.trading_pair, + "side": "buy", + "price": "0.08200000000000000000", + "size": "0.01022222000000000000", + "tradeId": "5c24c5da03aa673885cd67aa", + "takerOrderId": "5c24c5d903aa6772d55b371e", + "makerOrderId": "5c2187d003aa677bd09d5c93", + "time": "1545913818099033203" + } + } + + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertTrue(trade_event["data"]["tradeId"], msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "type": "message", + "topic": f"/contractMarket/level2:{self.trading_pair}", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + self._simulate_trading_rules_initialized() + mock_queue = AsyncMock() + diff_event = { + "subject": "level2", + "topic": f"/contractMarket/level2:{self.trading_pair}", + "type": "message", + "data": { + "sequence": 18, + "change": "5000.0,sell,83", + "timestamp": 1551770400000, + } + } + + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(diff_event["data"]["timestamp"] * 1e-3, msg.timestamp) + expected_update_id = 18 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(0, len(bids)) + self.assertEqual(1, len(asks)) + self.assertEqual(5000.0, asks[0].price) + self.assertEqual(83 * 0.000001, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + endpoint = CONSTANTS.ORDER_BOOK_ENDPOINT.format(symbol=self.trading_pair) + url = web_utils.get_rest_url_for_endpoint( + endpoint=endpoint + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_api_order_book_data_source" + ".KucoinPerpetualAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = asyncio.CancelledError + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.ORDER_BOOK_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + try: + self.async_run_with_timeout(self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + self._simulate_trading_rules_initialized() + logging.getLogger("asyncio").setLevel(logging.WARNING) + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.ORDER_BOOK_ENDPOINT.format(symbol=self.trading_pair)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + snapshot_data = { + "code": "200000", + "data": { + "sequence": "3262786978", + "time": 1550653727731, + "bids": [["6500.12", "0.45054140"], + ["6500.11", "0.45054140"]], + "asks": [["6500.16", "0.57753524"], + ["6500.15", "0.57753524"]] + } + } + + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(snapshot_data["data"]["sequence"]), msg.update_id) + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(float(snapshot_data["data"]["time"]) * 1e-3, msg.timestamp) + + bids = msg.bids + asks = msg.asks + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(snapshot_data["data"]["sequence"]), msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(6500.12, bids[0].price) + self.assertEqual(4.505414e-07, bids[0].amount) + self.assertEqual(6500.11, bids[1].price) + self.assertEqual(4.505414e-07, bids[1].amount) + self.assertEqual(2, len(asks)) + self.assertEqual(6500.16, asks[0].price) + self.assertEqual(5.7753524e-07, asks[0].amount) + self.assertEqual(6500.15, asks[1].price) + self.assertEqual(5.7753524e-07, asks[1].amount) + + def test_listen_for_funding_info_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_funding_info(msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_funding_info_logs_exception(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [Exception, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public funding info updates from exchange")) + + def test_listen_for_funding_info_successful(self): + # KuCoin doesn't have ws updates for funding info + pass + + @aioresponses() + def test_get_funding_info(self, mock_api): + future_info_url = web_utils.get_rest_url_for_endpoint( + endpoint = CONSTANTS.GET_CONTRACT_INFO_PATH_URL.format(symbol=self.ex_trading_pair) + ) + future_info_regex_url = re.compile(f"^{future_info_url}".replace(".", r"\.").replace("?", r"\?")) + future_info_response = { + "code": "200000", + "data": { + "symbol": self.ex_trading_pair, + "rootSymbol": "USDT", + "type": "FFWCSX", + "firstOpenDate": 1610697600000, + "baseCurrency": "HBOT", + "quoteCurrency": "USDT", + "settleCurrency": "USDT", + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 0.01, + "indexPriceTickSize": 0.01, + "multiplier": 0.01, + "initialMargin": 0.05, + "maintainMargin": 0.025, + "maxRiskLimit": 100000, + "minRiskLimit": 100000, + "riskStep": 50000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "isDeleverage": True, + "isQuanto": False, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "fundingBaseSymbol": ".HBOTINT8H", + "fundingQuoteSymbol": ".USDTINT8H", + "fundingRateSymbol": ".HBOTUSDTMFPI8H", + "indexSymbol": ".KHBOTUSDT", + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "2487402", + "turnoverOf24h": 3166644.36115288, + "volumeOf24h": 32299.4, + "markPrice": 101.6, + "indexPrice": 101.59, + "lastTradePrice": 101.54, + "nextFundingRateTime": 22646889, + "maxLeverage": 20, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "premiumsSymbol1M": ".HBOTUSDTMPI", + "premiumsSymbol8H": ".HBOTUSDTMPI8H", + "fundingBaseSymbol1M": ".HBOTINT", + "fundingQuoteSymbol1M": ".USDTINT", + "lowPrice": 88.88, + "highPrice": 102.21, + "priceChgPct": 0.1401, + "priceChg": 12.48 + } + } + mock_api.get(future_info_regex_url, body=json.dumps(future_info_response)) + + funding_info: FundingInfo = self.async_run_with_timeout( + self.data_source.get_funding_info(self.trading_pair) + ) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(Decimal(str(future_info_response["data"]["indexPrice"])), funding_info.index_price) + self.assertEqual(Decimal(str(future_info_response["data"]["markPrice"])), funding_info.mark_price) + + def _simulate_trading_rules_initialized(self): + self.connector._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_user_stream_data_source.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_user_stream_data_source.py new file mode 100644 index 0000000..41b21d6 --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_api_user_stream_data_source.py @@ -0,0 +1,367 @@ +import asyncio +import re +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, patch + +import ujson +from aioresponses.core import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.kucoin_perpetual import ( + kucoin_perpetual_constants as CONSTANTS, + kucoin_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_api_user_stream_data_source import ( + KucoinPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_auth import KucoinPerpetualAuth +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative import KucoinPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class KucoinPerpetualAPIUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.emulated_time = 1640001112.223 + self.auth = KucoinPerpetualAuth( + api_key="TEST_API_KEY", + passphrase="TEST_PASSPHRASE", + secret_key="TEST_SECRET", + time_provider=self) + self.connector = KucoinPerpetualDerivative( + client_config_map, + kucoin_perpetual_api_key="", + kucoin_perpetual_secret_key="", + kucoin_perpetual_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + self.data_source = KucoinPerpetualAPIUserStreamDataSource( + trading_pairs=[self.trading_pair], connector=self.connector, auth=self.auth, api_factory=self.connector._web_assistants_factory, domain=self.domain + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mock_done_event = asyncio.Event() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _mock_responses_done_callback(self, *_, **__): + self.mock_done_event.set() + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _successful_get_server_time(self) -> str: + resp = { + "code": "200000", + "msg": "success", + "data": 1546837113087, + } + return ujson.dumps(resp) + + def _all_symbols_request_mock_response(self): + mock_response = { + "code": "200000", + "data": [ + { + "symbol": "COINALPHAHBOT", + "rootSymbol": "COINALPHA", + "type": "FFWCSX", + "firstOpenDate": 1585555200000, + "expireDate": None, + "settleDate": None, + "baseCurrency": "COINALPHA", + "quoteCurrency": "HBOT", + "settleCurrency": "HBOT", + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 1.0, + "indexPriceTickSize": 0.01, + "multiplier": 0.001, + "initialMargin": 0.01, + "maintainMargin": 0.005, + "maxRiskLimit": 2000000, + "minRiskLimit": 2000000, + "riskStep": 1000000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": None, + "isDeleverage": True, + "isQuanto": True, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "5191275", + "turnoverOf24h": 2361994501.712677, + "volumeOf24h": 56067.116, + "markPrice": 44514.03, + "indexPrice": 44510.78, + "lastTradePrice": 44493.0, + "nextFundingRateTime": 21031525, + "maxLeverage": 100, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "lowPrice": 38040, + "highPrice": 44948, + "priceChgPct": 0.1702, + "priceChg": 6476 + } + ] + } + return ujson.dumps(mock_response) + + def _successful_get_connection_token_response(self) -> str: + resp = { + "code": "200000", + "data": { + "token": self.listen_key, + "instanceServers": [ + { + "endpoint": "wss://someEndpoint", + "encrypt": True, + "protocol": "websocket", + "pingInterval": 18000, + "pingTimeout": 10000, + } + ] + } + } + return ujson.dumps(resp) + + def _error_response(self) -> Dict[str, Any]: + resp = {"code": "400100", "msg": "Invalid Parameter."} + + return resp + + def _simulate_user_update_event(self): + # Order Trade Update + resp = { + "type": "message", + "topic": "/contractMarket/tradeOrders:HBOTALPHAM", + "subject": "symbolOrderChange", + "channelType": "private", + "data": { + "orderId": "5cdfc138b21023a909e5ad55", # Order ID + "symbol": "HBOTALPHAM", # Symbol + "type": "match", # Message Type: "open", "match", "filled", "canceled", "update" + "status": "open", # Order Status: "match", "open", "done" + "matchSize": "", # Match Size (when the type is "match") + "matchPrice": "", # Match Price (when the type is "match") + "orderType": "limit", # Order Type, "market" indicates market order, "limit" indicates limit order + "side": "buy", # Trading direction,include buy and sell + "price": "3600", # Order Price + "size": "20000", # Order Size + "remainSize": "20001", # Remaining Size for Trading + "filledSize": "20000", # Filled Size + "canceledSize": "0", # In the update message, the Size of order reduced + "tradeId": "5ce24c16b210233c36eexxxx", # Trade ID (when the type is "match") + "clientOid": "5ce24c16b210233c36ee321d", # clientOid + "orderTime": 1545914149935808589, # Order Time + "oldSize ": "15000", # Size Before Update (when the type is "update") + "liquidity": "maker", # Trading direction, buy or sell in taker + "ts": 1545914149935808589 # Timestamp + } + } + return ujson.dumps(resp) + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_create_websocket_connection_log_exception(self, mock_api, mock_ws): + url = web_utils.wss_private_url(self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_connection_token_response()) + + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + f"Unexpected error while listening to user stream {url}. Retrying after 5 seconds...", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_create_websocket_connection_failed(self, mock_api, mock_ws): + url = web_utils.wss_private_url(self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_connection_token_response()) + + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + f"Unexpected error while listening to user stream {url}. Retrying after 5 seconds...", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, _, mock_ws): + url = web_utils.wss_private_url(self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = {"listenKey": self.listen_key} + mock_api.post(regex_url, body=ujson.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + mock_ws.return_value.closed = False + mock_ws.return_value.close.side_effect = Exception + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except Exception: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + f"Unexpected error while listening to user stream {url}. Retrying after 5 seconds...", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_successful(self, mock_api, mock_ws): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_connection_token_response()) + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SERVER_TIME_PATH_URL) + mock_api.get(url, body=self._successful_get_server_time()) + mock_api.get(url, body=self._successful_get_server_time()) + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + mock_api.get(url, body=self._all_symbols_request_mock_response()) + mock_api.get(url, body=self._all_symbols_request_mock_response()) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._simulate_user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertTrue(msg, self._simulate_user_update_event) + mock_ws.return_value.ping.assert_called() + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, body=self._successful_get_connection_token_response()) + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SERVER_TIME_PATH_URL) + mock_api.get(url, body=self._successful_get_server_time()) + mock_api.get(url, body=self._successful_get_server_time()) + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + mock_api.get(url, body=self._all_symbols_request_mock_response()) + mock_api.get(url, body=self._all_symbols_request_mock_response()) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_auth.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_auth.py new file mode 100644 index 0000000..b9da2dc --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_auth.py @@ -0,0 +1,141 @@ +import asyncio +import base64 +import hashlib +import hmac +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_auth import KucoinPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class KucoinPerpetualAuthTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "testSecretKey" + self.passphrase = "testPassphrase" + self.subaccount_name = "test!?Subaccount" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000000 + + self.auth = KucoinPerpetualAuth( + api_key=self.api_key, + passphrase = self.passphrase, + secret_key=self.secret_key, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _sign(self, passphrase: str, key: str) -> str: + signed_message = base64.b64encode( + hmac.new( + key.encode("utf-8"), + passphrase.encode("utf-8"), + hashlib.sha256).digest()) + return signed_message.decode("utf-8") + + def test_add_auth_headers_to_get_request_without_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request, use_time_provider=1)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + expected_signature = self._sign("1000000" + "GET" + request.throttler_limit_id, key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_add_auth_headers_to_get_request_with_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params={"param1": "value1", "param2": "value2"}, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request, use_time_provider=1)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + full_endpoint = f"{request.throttler_limit_id}?param1=value1¶m2=value2" + expected_signature = self._sign("1000000" + "GET" + full_endpoint, key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_add_auth_headers_to_post_request(self): + body = {"param_z": "value_param_z", "param_a": "value_param_a"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=json.dumps(body), + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request, use_time_provider=1)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + expected_signature = self._sign("1000000" + "POST" + request.throttler_limit_id + json.dumps(body), + key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) + + def test_ws_auth_payload(self): + expires = self.auth._get_expiration_timestamp() + self.mock_time_provider.return_value = expires + payload = self.auth.get_ws_auth_payload() + + raw_signature = "GET/realtime" + expires + expected_signature = hmac.new(self.secret_key.encode("utf-8"), + raw_signature.encode("utf-8"), + hashlib.sha256).hexdigest() + + self.assertEqual(3, len(payload)) + self.assertEqual(self.api_key, payload[0]) + self.assertEqual(expires, payload[1]) + self.assertEqual(expected_signature, payload[2]) diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_derivative.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_derivative.py new file mode 100644 index 0000000..4eec884 --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_derivative.py @@ -0,0 +1,1795 @@ +import asyncio +import json +import re +from copy import deepcopy +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +import pandas as pd +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative import KucoinPerpetualDerivative +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class KucoinPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.passphrase = "somePassphrase" + cls.quote_asset = "USDT" + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + cls.non_linear_quote_asset = "USD" + cls.non_linear_trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.non_linear_quote_asset) + + @property + def all_symbols_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + return url + + @property + def latest_prices_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.LATEST_SYMBOL_INFORMATION_ENDPOINT.format(symbol=self.exchange_trading_pair), + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def network_status_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.SERVER_TIME_PATH_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_SYMBOL_ENDPOINT) + return url + + @property + def order_creation_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CREATE_ORDER_PATH_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def balance_url(self): + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.GET_WALLET_BALANCE_PATH_URL.format(currency="USDT")) + return url + + @property + def funding_info_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_CONTRACT_INFO_PATH_URL + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_payment_url(self): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_FUNDING_HISTORY_PATH_URL.format(symbol=self.exchange_trading_pair), + ) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def all_symbols_request_mock_response(self): + mock_response = { + "code": "200000", + "data": [ + { + "symbol": self.exchange_trading_pair, + "rootSymbol": self.quote_asset, + "type": "FFWCSX", + "firstOpenDate": 1585555200000, + "expireDate": None, + "settleDate": None, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "settleCurrency": self.quote_asset, + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 1.0, + "indexPriceTickSize": 0.01, + "multiplier": 0.001, + "initialMargin": 0.01, + "maintainMargin": 0.005, + "maxRiskLimit": 2000000, + "minRiskLimit": 2000000, + "riskStep": 1000000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": None, + "isDeleverage": True, + "isQuanto": True, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "5191275", + "turnoverOf24h": 2361994501.712677, + "volumeOf24h": 56067.116, + "markPrice": 44514.03, + "indexPrice": 44510.78, + "lastTradePrice": 44493.0, + "nextFundingRateTime": 21031525, + "maxLeverage": 100, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "lowPrice": 38040, + "highPrice": 44948, + "priceChgPct": 0.1702, + "priceChg": 6476 + } + ] + } + return mock_response + + @property + def latest_prices_request_mock_response(self): + mock_response = { + "code": "200000", + "data": [ + { + "symbol": self.exchange_trading_pair, + "rootSymbol": self.quote_asset, + "type": "FFWCSX", + "firstOpenDate": 1610697600000, + "expireDate": None, + "settleDate": None, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "settleCurrency": self.quote_asset, + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 0.01, + "indexPriceTickSize": 0.01, + "multiplier": 0.01, + "initialMargin": 0.05, + "maintainMargin": 0.025, + "maxRiskLimit": 100000, + "minRiskLimit": 100000, + "riskStep": 50000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": "", + "isDeleverage": True, + "isQuanto": False, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "fundingBaseSymbol": self.exchange_trading_pair, + "fundingQuoteSymbol": self.exchange_trading_pair, + "fundingRateSymbol": self.exchange_trading_pair, + "indexSymbol": self.exchange_trading_pair, + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "2487402", + "turnoverOf24h": 3166644.36115288, + "volumeOf24h": 32299.4, + "markPrice": 101.6, + "indexPrice": 101.59, + "lastTradePrice": str(self.expected_latest_price), + "nextFundingRateTime": 22646889, + "maxLeverage": 20, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "premiumsSymbol1M": self.exchange_trading_pair, + "premiumsSymbol8H": self.exchange_trading_pair, + "fundingBaseSymbol1M": self.base_asset, + "fundingQuoteSymbol1M": self.quote_asset, + "lowPrice": 88.88, + "highPrice": 102.21, + "priceChgPct": 0.1401, + "priceChg": 12.48 + } + ] + } + return mock_response + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + mock_response = { + "code": "200000", + "data": [ + { + "symbol": self.exchange_trading_pair, + "rootSymbol": self.quote_asset, + "type": "FFWCSX", + "firstOpenDate": 1585555200000, + "expireDate": None, + "settleDate": None, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "settleCurrency": self.quote_asset, + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 1.0, + "indexPriceTickSize": 0.01, + "multiplier": 0.001, + "initialMargin": 0.01, + "maintainMargin": 0.005, + "maxRiskLimit": 2000000, + "minRiskLimit": 2000000, + "riskStep": 1000000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": None, + "isDeleverage": True, + "isQuanto": True, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "5191275", + "turnoverOf24h": 2361994501.712677, + "volumeOf24h": 56067.116, + "markPrice": 44514.03, + "indexPrice": 44510.78, + "lastTradePrice": 44493.0, + "nextFundingRateTime": 21031525, + "maxLeverage": 100, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "lowPrice": 38040, + "highPrice": 44948, + "priceChgPct": 0.1702, + "priceChg": 6476 + }, + { + "symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "rootSymbol": self.quote_asset, + "type": "FFWCSX", + "firstOpenDate": 1585555200000, + "expireDate": None, + "settleDate": None, + "baseCurrency": "INVALID", + "quoteCurrency": "PAIR", + "settleCurrency": "PAIR", + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 1.0, + "indexPriceTickSize": 0.01, + "multiplier": 0.001, + "initialMargin": 0.01, + "maintainMargin": 0.005, + "maxRiskLimit": 2000000, + "minRiskLimit": 2000000, + "riskStep": 1000000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": None, + "isDeleverage": True, + "isQuanto": True, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "settlementSymbol": "", + "status": "Closed", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "5191275", + "turnoverOf24h": 2361994501.712677, + "volumeOf24h": 56067.116, + "markPrice": 44514.03, + "indexPrice": 44510.78, + "lastTradePrice": 44493.0, + "nextFundingRateTime": 21031525, + "maxLeverage": 100, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "lowPrice": 38040, + "highPrice": 44948, + "priceChgPct": 0.1702, + "priceChg": 6476 + }, + ] + } + return "INVALID-PAIR", mock_response + + @property + def network_status_request_successful_mock_response(self): + mock_response = { + "code": "200000", + "data": { + "status": "open", + "msg": "upgrade match engine" + } + } + return mock_response + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + mock_response = { + "code": "200000", + "data": [ + { + "symbol": self.exchange_trading_pair, + "rootSymbol": self.quote_asset, + "type": "FFWCSX", + "firstOpenDate": 1610697600000, + "expireDate": None, + "settleDate": None, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "settleCurrency": self.quote_asset, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + } + ] + } + return mock_response + + @property + def order_creation_request_successful_mock_response(self): + mock_response = { + "code": "200000", + "data": { + "orderId": "335fd977-e5a5-4781-b6d0-c772d5bfb95b" + } + } + return mock_response + + @property + def balance_request_mock_response_for_base_and_quote(self): + mock_response = { + "code": "200000", + "data": [{ + "accountEquity": 15, + "unrealisedPNL": 0, + "marginBalance": 15, + "positionMargin": 0, + "orderMargin": 0, + "frozenFunds": 0, + "availableBalance": 10, + "currency": self.base_asset, + }, + { + "accountEquity": 2000, + "unrealisedPNL": 0, + "marginBalance": 2000, + "positionMargin": 0, + "orderMargin": 0, + "frozenFunds": 0, + "availableBalance": 2000, + "currency": self.quote_asset, + } + ] + } + return mock_response + + @property + def balance_request_mock_response_only_base(self): + mock_response = self.balance_request_mock_response_for_base_and_quote + del mock_response["data"][1] + return mock_response + + @property + def balance_event_websocket_update(self): + mock_response = { + "userId": 738713, + "topic": "/contractAccount/wallet", + "subject": "availableBalance.change", + "data": { + "availableBalance": 10, + "holdBalance": 15, + "currency": self.base_asset, + "timestamp": 1553842862614 + } + } + return mock_response + + @property + def non_linear_balance_event_websocket_update(self): + return self.balance_event_websocket_update + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def empty_funding_payment_mock_response(self): + return { + "code": "200000", + "dataList": [{}], + } + + @property + def funding_payment_mock_response(self): + return { + "code": "200000", + "dataList": [ + { + "id": 36275152660006, + "symbol": self.exchange_trading_pair, + "timePoint": self.target_funding_payment_timestamp_str, + "fundingRate": float(self.target_funding_payment_funding_rate), + "markPrice": 8058.27, + "positionQty": float(self.target_funding_payment_payment_amount / self.target_funding_payment_funding_rate), + "positionCost": -0.001241, + "funding": -0.00000464, + "settleCurrency": self.base_asset, + }] + } + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + raise NotImplementedError # test is overwritten + + @property + def target_funding_info_next_funding_utc_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_info_next_funding_utc_str_ws_updated(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_info_next_funding_utc_timestamp_ws_updated) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def target_funding_payment_timestamp_str(self): + datetime_str = str( + pd.Timestamp.utcfromtimestamp( + self.target_funding_payment_timestamp) + ).replace(" ", "T") + "Z" + return datetime_str + + @property + def funding_info_mock_response(self): + mock_response = self.latest_prices_request_mock_response + funding_info = mock_response["data"][0] + funding_info["indexPrice"] = self.target_funding_info_index_price + funding_info["markPrice"] = self.target_funding_info_mark_price + funding_info["nextFundingRateTime"] = self.target_funding_info_next_funding_utc_str + funding_info["predictedFundingFeeRate"] = self.target_funding_info_rate + return mock_response + + @property + def get_predicted_funding_info(self): + return self.latest_prices_request_mock_response + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + trading_rules_resp = self.trading_rules_request_mock_response["data"][0] + multiplier = Decimal(str(trading_rules_resp["multiplier"])) + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(trading_rules_resp["lotSize"])) * multiplier, + max_order_size=Decimal(str(trading_rules_resp["maxOrderQty"])) * multiplier, + min_price_increment=Decimal(str(trading_rules_resp["tickSize"])), + min_base_amount_increment=multiplier, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "335fd977-e5a5-4781-b6d0-c772d5bfb95b" + + @property + def is_cancel_request_executed_synchronously_by_server(self) -> bool: + return False + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return False + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent=Decimal('0.0002'), + percent_token=self.quote_asset, + ) + + @property + def expected_trade_history_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent=Decimal('0'), + percent_token=self.quote_asset, + flat_fees=[TokenAmount(amount=Decimal('0.0002'), token=self.quote_asset)] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6" + + @property + def latest_trade_hist_timestamp(self) -> int: + return 1234 + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = KucoinPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + self.passphrase, + trading_pairs=[self.trading_pair], + ) + exchange._last_trade_history_timestamp = self.latest_trade_hist_timestamp + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertEqual("application/json", request_headers["Content-Type"]) + + self.assertIn("KC-API-TIMESTAMP", request_headers) + self.assertIn("KC-API-KEY", request_headers) + self.assertEqual(self.api_key, request_headers["KC-API-KEY"]) + self.assertIn("KC-API-SIGN", request_headers) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.amount, request_data["size"] * 1e-6) + self.assertEqual(CONSTANTS.DEFAULT_TIME_IN_FORCE, request_data["timeInForce"]) + self.assertEqual(order.client_order_id, request_data["clientOid"]) + self.assertIn("clientOid", request_data) + self.assertEqual(order.order_type.name.lower(), request_data["type"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.exchange_order_id, request_data["order_id"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + request_data = request_call.kwargs["data"] + self.assertIsNone(request_params) + self.assertIsNone(request_data) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(self.latest_trade_hist_timestamp * 1e3, request_params["start_time"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + """ + :return: the URL configured for the cancelation + """ + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ORDER_PATH_URL.format(orderid=order.exchange_order_id) + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.CANCEL_ORDER_PATH_URL.format(orderid=order.exchange_order_id) + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = { + "code": str(CONSTANTS.RET_CODE_PARAMS_ERROR), + "msg": "Order does not exist", + } + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses, + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format(orderid=order.exchange_order_id)) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format(orderid=order.exchange_order_id) + ) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format(orderid=order.exchange_order_id) + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format(orderid=order.exchange_order_id) + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ORDER_BY_EXCHANGE_ORDER_ID_PATH_URL.format(orderid=order.exchange_order_id) + ) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.QUERY_ALL_ORDER_PATH_URL, exchange_order_id=order.exchange_order_id + ) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_FILL_INFO_PATH_URL.format(orderid=order.exchange_order_id), + ) + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_fill_history_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_RECENT_FILLS_INFO_PATH_URL, + ) + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.ACTIVE_ORDER_PATH_URL, exchange_order_id=order.exchange_order_id + ) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL + ) + response = { + "code": "200000", + "data": True + } + mock_api.post(url, body=json.dumps(response), callback=callback) + + return url + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.SET_LEVERAGE_PATH_URL + ) + error_code = "300016" + error_msg = "Some problem" + response = { + "code": "300016", + "data": False + } + mock_api.post(url, body=json.dumps(response), callback=callback) + + return url, f"ret_code <{error_code}> - {error_msg}" + + def configure_failed_set_leverage( + self, + leverage: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> Tuple[str, str]: + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_RISK_LIMIT_LEVEL_PATH_URL.format(symbol=self.exchange_trading_pair) + ) + regex_url = re.compile(f"^{url}") + + error_code = "300016" + error_msg = "Some problem" + mock_response = { + "code": "300016", + "data": [ + { + "symbol": "ADAUSDTM", + "level": 1, + "maxRiskLimit": 500, + "minRiskLimit": 0, + "maxLeverage": 1, + "initialMargin": 0.05, + "maintainMargin": 0.025 + }, + { + "symbol": "ADAUSDTM", + "level": 2, + "maxRiskLimit": 1000, + "minRiskLimit": 500, + "maxLeverage": 1, + "initialMargin": 0.5, + "maintainMargin": 0.25 + } + ] + } + + mock_api.get(regex_url, body=json.dumps(mock_response), callback=callback) + + return url, f"ret_code <{error_code}> - {error_msg}" + + def configure_successful_set_leverage( + self, + leverage: int, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_RISK_LIMIT_LEVEL_PATH_URL.format(symbol=self.exchange_trading_pair) + ) + regex_url = re.compile(f"^{url}") + + mock_response = { + "code": "200000", + "data": [ + { + "symbol": "ADAUSDTM", + "level": 1, + "maxRiskLimit": 500, + "minRiskLimit": 0, + "maxLeverage": 20, + "initialMargin": 0.05, + "maintainMargin": 0.025 + }, + { + "symbol": "ADAUSDTM", + "level": 2, + "maxRiskLimit": 1000, + "minRiskLimit": 500, + "maxLeverage": 2, + "initialMargin": 0.5, + "maintainMargin": 0.25 + } + ] + } + + mock_api.get(regex_url, body=json.dumps(mock_response), callback=callback) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "type": "message", + "topic": "/contractMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "orderId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "symbol": self.exchange_trading_pair, + "type": "open", + "status": "open", + "orderType": order.order_type.name.lower(), + "side": order.trade_type.name.lower(), + "price": str(order.price), + "size": float(order.amount), + "remainSize": float(order.amount), + "filledSize": "0", + "canceledSize": "0", + "clientOid": order.client_order_id or "", + "orderTime": 1545914149935808589, + "liquidity": "maker", + "ts": 1545914149935808589 + } + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "type": "message", + "topic": "/contractMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "orderId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "symbol": self.exchange_trading_pair, + "type": "canceled", + "status": "done", + "orderType": order.order_type.name.lower(), + "side": order.trade_type.name.lower(), + "price": str(order.price), + "size": float(order.amount), + "remainSize": "0", + "filledSize": "0", + "canceledSize": float(order.amount), + "clientOid": order.client_order_id or "", + "orderTime": 1545914149935808589, + "liquidity": "maker", + "ts": 1545914149935808589 + } + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "type": "message", + "topic": "/contractMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "orderId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "symbol": self.exchange_trading_pair, + "type": "filled", + "status": "done", + "orderType": order.order_type.name.lower(), + "side": order.trade_type.name.lower(), + "matchPrice": str(order.price), + "size": float(order.amount) * 1000, + "remainSize": "0", + "matchSize": float(order.amount) * 1000, + "fee": str(self.expected_fill_fee.percent), + "canceledSize": "0", + "clientOid": order.client_order_id or "", + "orderTime": 1545914149935808589, + "liquidity": "maker", + "ts": 1545914149935808589 + } + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "type": "message", + "topic": "/contractMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "orderId": order.exchange_order_id or "1640b725-75e9-407d-bea9-aae4fc666d33", + "tradeId": self.expected_fill_trade_id, + "symbol": self.exchange_trading_pair, + "type": "match", + "status": "done", + "orderType": order.order_type.name.lower(), + "side": order.trade_type.name.lower(), + "matchPrice": str(order.price), + "size": float(order.amount) * 1000, + "fee": str(self.expected_fill_fee.percent), + "remainSize": "0", + "matchSize": float(order.amount) * 1000000, + "canceledSize": "0", + "clientOid": order.client_order_id or "", + "orderTime": 1545914149935808589, + "liquidity": "maker", + "ts": 1545914149935808589 + } + } + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + position_value = unrealized_pnl + order.amount * order.price * order.leverage + return { + "type": "message", + "userId": 533285, + "channelType": "private", + "topic": "/contract/position:" + self.exchange_trading_pair, + "subject": "position.change", + "data": { + "realisedGrossPnl": "0.00055631", + "symbol": self.exchange_trading_pair, + "crossMode": False, + "liquidationPrice": "489", + "posLoss": 0E-8, + "avgEntryPrice": str(order.price), + "unrealisedPnl": unrealized_pnl, + "markPrice": str(order.price), + "posMargin": 0.00266779, + "autoDeposit": False, + "riskLimit": 100000, + "unrealisedCost": 0.00266375, + "posComm": 0.00000392, + "posMaint": 0.00001724, + "posCost": str(position_value), + "maintMarginReq": 0.005, + "bankruptPrice": 1000000.0, + "realisedCost": 0.00000271, + "markValue": 0.00251640, + "posInit": 0.39929535, + "realisedPnl": -0.00000253, + "maintMargin": 0.39929535, + "realLeverage": str(order.leverage), + "changeReason": "positionChange", + "currentCost": str(position_value), + "openingTimestamp": 1558433191000, + "currentQty": -int(order.amount), + "delevPercentage": 0.52, + "currentComm": 0.00000271, + "realisedGrossCost": 0E-8, + "isOpen": True, + "posCross": 1.2E-7, + "currentTimestamp": 1558506060394, + "unrealisedRoePcnt": -0.0553, + "unrealisedPnlPcnt": -0.0553, + "settleCurrency": self.quote_asset, + } + } + + def funding_info_event_for_websocket_update(self): + return { + "userId": "xbc453tg732eba53a88ggyt8c", # Deprecated, will detele later + "topic": "/contract/position:" + self.exchange_trading_pair, + "subject": "position.settlement", + "data": { + "fundingTime": 1551770400000, # Funding time + "qty": 100, # Position size + "markPrice": self.target_funding_info_mark_price_ws_updated, # Settlement price + "fundingRate": self.target_funding_info_rate_ws_updated, # Funding rate + "fundingFee": -296, # Funding fees + "ts": 1547697294838004923, # Current time (nanosecond) + "settleCurrency": "XBT" # Currency used to clear and settle the trades + } + } + + def test_create_order_with_invalid_position_action_raises_value_error(self): + self._simulate_trading_rules_initialized() + + with self.assertRaises(ValueError) as exception_context: + asyncio.get_event_loop().run_until_complete( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="C1", + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("46000"), + position_action=PositionAction.NIL, + ), + ) + + self.assertEqual( + f"Invalid position action {PositionAction.NIL}. Must be one of {[PositionAction.OPEN, PositionAction.CLOSE]}", + str(exception_context.exception) + ) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + non_linear_connector = KucoinPerpetualDerivative( + client_config_map=client_config_map, + kucoin_perpetual_api_key=self.api_key, + kucoin_perpetual_secret_key=self.api_secret, + trading_pairs=[self.base_asset], + ) + non_linear_connector._set_current_timestamp(1640780000) + + balance_event = self.non_linear_balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("25"), self.exchange.get_balance(self.base_asset)) + + def test_supported_position_modes(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + linear_connector = KucoinPerpetualDerivative( + client_config_map=client_config_map, + kucoin_perpetual_api_key=self.api_key, + kucoin_perpetual_secret_key=self.api_secret, + trading_pairs=[self.trading_pair], + ) + non_linear_connector = KucoinPerpetualDerivative( + client_config_map=client_config_map, + kucoin_perpetual_api_key=self.api_key, + kucoin_perpetual_secret_key=self.api_secret, + trading_pairs=[self.non_linear_trading_pair], + ) + + expected_result = [PositionMode.ONEWAY] + self.assertEqual(expected_result, linear_connector.supported_position_modes()) + + expected_result = [PositionMode.ONEWAY] + self.assertEqual(expected_result, non_linear_connector.supported_position_modes()) + + def test_set_position_mode_nonlinear(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + non_linear_connector = KucoinPerpetualDerivative( + client_config_map=client_config_map, + kucoin_perpetual_api_key=self.api_key, + kucoin_perpetual_secret_key=self.api_secret, + trading_pairs=[self.non_linear_trading_pair], + ) + non_linear_connector.set_position_mode(PositionMode.HEDGE) + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message=f"Position mode {PositionMode.HEDGE} is not supported. Mode not set.", + ) + ) + + def test_get_buy_and_sell_collateral_tokens(self): + self._simulate_trading_rules_initialized() + + linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.trading_pair) + linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.trading_pair) + + self.assertEqual(self.quote_asset, linear_buy_collateral_token) + self.assertEqual(self.quote_asset, linear_sell_collateral_token) + + non_linear_buy_collateral_token = self.exchange.get_buy_collateral_token(self.non_linear_trading_pair) + non_linear_sell_collateral_token = self.exchange.get_sell_collateral_token(self.non_linear_trading_pair) + + self.assertEqual(self.non_linear_quote_asset, non_linear_buy_collateral_token) + self.assertEqual(self.non_linear_quote_asset, non_linear_sell_collateral_token) + + def test_time_synchronizer_related_reqeust_error_detection(self): + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_AUTH_TIMESTAMP_ERROR) + exception = IOError(f"{error_code_str} - Failed to cancel order for timestamp reason.") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + error_code_str = self.exchange._format_ret_code_for_print(ret_code=CONSTANTS.RET_CODE_ORDER_NOT_EXISTS) + exception = IOError(f"{error_code_str} - Failed to cancel order because it was not found.") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + def place_buy_limit_maker_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.LIMIT_MAKER, + price=price, + position_action=position_action, + ) + return order_id + + def place_buy_market_order( + self, + amount: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.MARKET, + price=price, + position_action=position_action, + ) + return order_id + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.GET_CONTRACT_INFO_PATH_URL.format(symbol=self.exchange_trading_pair)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, body=json.dumps(response)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_updates_funding_info(self, mock_api, mock_queue_get): + url = self.funding_info_url + + response = self.funding_info_mock_response + mock_api.get(url, body=json.dumps(response)) + + url = web_utils.get_rest_url_for_endpoint(endpoint=CONSTANTS.GET_CONTRACT_INFO_PATH_URL.format(symbol=self.exchange_trading_pair)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + funding_resp = self.get_predicted_funding_info + mock_api.get(regex_url, body=json.dumps(funding_resp)) + + funding_info_event = self.funding_info_event_for_websocket_update() + + event_messages = [funding_info_event, asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout( + self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "200000", + "data": { + "cancelledOrderIds": [ + order.exchange_order_id + ] + } + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "200000", + "data": { + "id": order.exchange_order_id or "2b1d811c-8ff0-4ef0-92ed-b4ed5fd6de34", + "symbol": self.exchange_trading_pair, + "type": "limit", + "side": order.trade_type.name.lower(), + "price": str(order.price), + "size": float(order.amount), + "value": float(order.price + 2), + "dealValue": float(order.price + 2), + "dealSize": float(order.amount), + "stp": "", + "stop": "", + "stopPriceType": "", + "stopTriggered": True, + "stopPrice": None, + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "leverage": "5", + "forceHold": False, + "closeOrder": False, + "visibleSize": "", + "clientOid": order.client_order_id or "", + "remark": None, + "tags": None, + "isActive": False, + "cancelExist": False, + "createdAt": 1558167872000, + "updatedAt": 1558167872000, + "endAt": 1558167872000, + "orderTime": 1558167872000000000, + "settleCurrency": order.quote_asset, + "status": "done", + "filledValue": float(order.price + 2), + "filledSize": float(order.amount), + "reduceOnly": False, + } + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["cancelExist"] = True + resp["data"]["dealSize"] = 0 + resp["data"]["dealValue"] = 0 + return resp + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["status"] = "open" + resp["data"]["dealSize"] = 0 + resp["data"]["dealValue"] = 0 + return resp + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + resp = self._order_status_request_completely_filled_mock_response(order) + resp["data"]["status"] = "open" + resp["data"]["dealSize"] = float(self.expected_partial_fill_amount) + resp["data"]["dealValue"] = float(self.expected_partial_fill_price) + return resp + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "code": "200000", + "data": { + "currentPage": 1, + "pageSize": 1, + "totalNum": 251915, + "totalPage": 251915, + "items": [ + { + "symbol": self.exchange_trading_pair, + "tradeId": self.expected_fill_trade_id, + "orderId": order.exchange_order_id, + "side": order.trade_type.name.lower(), + "liquidity": "taker", + "forceTaker": True, + "price": str(self.expected_partial_fill_price), # Filled price + "size": float(self.expected_partial_fill_amount), # Filled amount + "filledSize": float(self.expected_partial_fill_amount), # Filled amount + "value": "0.00012227", # Order value + "feeRate": "0.0005", # Floating fees + "fixFee": "0.00000006", # Fixed fees + "feeCurrency": "XBT", # Charging currency + "stop": "", # A mark to the stop order type + "fee": str(self.expected_fill_fee.percent), # Transaction fee + "orderType": order.order_type.name.lower(), # Order type + "tradeType": "trade", # Trade type (trade, liquidation, ADL or settlement) + "createdAt": 1558334496000, # Time the order created + "settleCurrency": order.base_asset, # settlement currency + "tradeTime": 1558334496000000000 # trade time in nanosecond + }] + } + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + self._simulate_trading_rules_initialized() + return { + "code": "200000", + "data": { + "currentPage": 1, + "pageSize": 100, + "totalNum": 1000, + "totalPage": 10, + "items": [ + { + "symbol": self.exchange_trading_pair, # Symbol of the contract + "tradeId": self.expected_fill_trade_id, # Trade ID + "orderId": order.exchange_order_id, # Order ID + "side": order.trade_type.name.lower(), # Transaction side + "liquidity": "taker", # Liquidity- taker or maker + "forceTaker": True, # Whether to force processing as a taker + "price": str(order.price), # Filled price + "matchPrice": str(order.price), # Filled price + "size": float(self.exchange.get_quantity_of_contracts(self.trading_pair, order.amount)), # Order amount + "filledSize": float(order.amount), # Filled amount + "matchSize": float(order.amount), # Filled amount + "value": "0.001204529", # Order value + "feeRate": "0.0005", # Floating fees + "fixFee": "0.00000006", # Fixed fees + "feeCurrency": "USDT", # Charging currency + "stop": "", # A mark to the stop order type + "fee": str(self.expected_fill_fee.percent), # Transaction fee + "orderType": order.order_type.name.lower(), # Order type + "tradeType": "trade", # Trade type (trade, liquidation, ADL or settlement) + "createdAt": 1558334496000, # Time the order created + "settleCurrency": order.base_asset, # settlement currency + "tradeTime": 1558334496000000000, # trade time in nanosecond + "ts": 1558334496000000000 # trade time in nanosecond + } + ] + } + } + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ), + self.non_linear_trading_pair: TradingRule( # non-linear + trading_pair=self.non_linear_trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ), + } + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + # KuCoin has no partial fill status + pass + + @aioresponses() + def test_update_order_status_when_order_partially_filled_and_cancelled(self, mock_api): + # KuCoin has no partial fill status + pass + + @aioresponses() + def test_user_stream_update_for_partially_cancelled_order(self, mock_api): + # KuCoin has no partial fill status + pass + + @aioresponses() + def test_set_position_mode_success(self, mock_api): + # There's only ONEWAY position mode + pass + + @aioresponses() + def test_set_position_mode_failure(self, mock_api): + # There's only ONEWAY position mode + pass + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + @aioresponses() + def test_create_buy_limit_maker_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_buy_limit_maker_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + request_data = json.loads(order_request.kwargs["data"]) + self.assertEqual(True, request_data["postOnly"]) + + @aioresponses() + @patch("hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_derivative.KucoinPerpetualDerivative.get_price") + def test_create_buy_market_order_successfully(self, mock_api, get_price_mock): + get_price_mock.return_value = Decimal(10000) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_buy_market_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + request_data = json.loads(order_request.kwargs["data"]) + self.assertEqual("IOC", request_data["timeInForce"]) + + @aioresponses() + def test_update_order_status_processes_trade_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + self.configure_fill_history_trade_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + self.async_run_with_timeout(self.exchange._update_trade_history()) + + self.async_run_with_timeout(request_sent_event.wait()) + fill_event = self.order_filled_logger.event_log[0] + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_trade_history_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + @aioresponses() + def test_start_network_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.trading_rules_url + + response = self.trading_rules_request_mock_response + results = response + duplicate = deepcopy(results['data'][0]) + duplicate["symbol"] = f"{self.exchange_trading_pair}_12345" + duplicate["multiplier"] = str(float(duplicate["multiplier"]) + 1) + results['data'].append(duplicate) + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange.start_network()) + + self.assertEqual(1, len(self.exchange.trading_rules)) + self.assertIn(self.trading_pair, self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(self.exchange.trading_rules[self.trading_pair])) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self._simulate_trading_rules_initialized() + leverage = 2 + self.exchange._perpetual_trading.set_leverage(self.trading_pair, leverage) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("10000"), + amount=Decimal("1"), + position_action=PositionAction.OPEN, + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + expected_unrealized_pnl = 12 + position_event = self.position_event_for_full_fill_websocket_update( + order=order, unrealized_pnl=expected_unrealized_pnl + ) + + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + if position_event: + event_messages.append(position_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + self.assertEqual(leverage, fill_event.leverage) + self.assertEqual(PositionAction.OPEN.value, fill_event.position) + + sell_event = self.sell_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, sell_event.timestamp) + self.assertEqual(order.client_order_id, sell_event.order_id) + self.assertEqual(order.base_asset, sell_event.base_asset) + self.assertEqual(order.quote_asset, sell_event.quote_asset) + self.assertEqual(order.amount, sell_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, sell_event.quote_asset_amount) + self.assertEqual(order.order_type, sell_event.order_type) + self.assertEqual(order.exchange_order_id, sell_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"SELL order {order.client_order_id} completely filled." + ) + ) + + self.assertEqual(1, len(self.exchange.account_positions)) + + position: Position = self.exchange.account_positions[self.trading_pair] + self.assertEqual(self.trading_pair, position.trading_pair) + self.assertEqual(PositionSide.SHORT, position.position_side) + self.assertEqual(expected_unrealized_pnl, position.unrealized_pnl) + self.assertEqual(fill_event.price, position.entry_price) + self.assertEqual(-fill_event.amount, (self.exchange.get_quantity_of_contracts(self.trading_pair, position.amount))) + self.assertEqual(leverage, position.leverage) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self._simulate_trading_rules_initialized() + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [] + if trade_event: + event_messages.append(trade_event) + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response( + order=order, + mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_fail_max_leverage(self, mock_api, callback: Optional[Callable] = lambda *args, **kwargs: None): + target_leverage = 10000 + request_sent_event = asyncio.Event() + url = web_utils.get_rest_url_for_endpoint( + endpoint=CONSTANTS.GET_RISK_LIMIT_LEVEL_PATH_URL.format(symbol=self.exchange_trading_pair) + ) + regex_url = re.compile(f"^{url}") + + mock_response = { + "code": "200000", + "data": [ + { + "symbol": "ADAUSDTM", + "level": 1, + "maxRiskLimit": 500, + "minRiskLimit": 0, + "maxLeverage": 20, + "initialMargin": 0.05, + "maintainMargin": 0.025 + }, + { + "symbol": "ADAUSDTM", + "level": 2, + "maxRiskLimit": 1000, + "minRiskLimit": 500, + "maxLeverage": 2, + "initialMargin": 0.5, + "maintainMargin": 0.25 + } + ] + } + + mock_api.get(regex_url, body=json.dumps(mock_response), callback=lambda *args, **kwargs: request_sent_event.set()) + self.exchange.set_leverage(trading_pair=self.trading_pair, leverage=target_leverage) + self.async_run_with_timeout(request_sent_event.wait()) + max_leverage = mock_response["data"][0]["maxLeverage"] + self.assertTrue( + self.is_logged( + log_level="NETWORK", + message=f"Error setting leverage {target_leverage} for {self.trading_pair}: Max leverage for {self.trading_pair} is {max_leverage}.", + ) + ) diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_utils.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_utils.py new file mode 100644 index 0000000..5262ebf --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_utils.py @@ -0,0 +1,85 @@ +import unittest + +import hummingbot.connector.derivative.kucoin_perpetual.kucoin_perpetual_utils as utils + + +class TradingPairUtilsTest(unittest.TestCase): + def test_is_exchange_information_valid(self): + exchange_info = { + "symbol": "XBTUSDTM", + "rootSymbol": "USDT", + "type": "FFWCSX", + "firstOpenDate": 1585555200000, + "expireDate": None, + "settleDate": None, + "baseCurrency": "XBT", + "quoteCurrency": "USDT", + "settleCurrency": "USDT", + "maxOrderQty": 1000000, + "maxPrice": 1000000.0, + "lotSize": 1, + "tickSize": 1.0, + "indexPriceTickSize": 0.01, + "multiplier": 0.001, + "initialMargin": 0.01, + "maintainMargin": 0.005, + "maxRiskLimit": 2000000, + "minRiskLimit": 2000000, + "riskStep": 1000000, + "makerFeeRate": 0.0002, + "takerFeeRate": 0.0006, + "takerFixFee": 0.0, + "makerFixFee": 0.0, + "settlementFee": None, + "isDeleverage": True, + "isQuanto": True, + "isInverse": False, + "markMethod": "FairPrice", + "fairMethod": "FundingRate", + "fundingBaseSymbol": ".XBTINT8H", + "fundingQuoteSymbol": ".USDTINT8H", + "fundingRateSymbol": ".XBTUSDTMFPI8H", + "indexSymbol": ".KXBTUSDT", + "settlementSymbol": "", + "status": "Open", + "fundingFeeRate": 0.0001, + "predictedFundingFeeRate": 0.0001, + "openInterest": "5191275", + "turnoverOf24h": 2361994501.712677, + "volumeOf24h": 56067.116, + "markPrice": 44514.03, + "indexPrice": 44510.78, + "lastTradePrice": 44493.0, + "nextFundingRateTime": 21031525, + "maxLeverage": 100, + "sourceExchanges": [ + "huobi", + "Okex", + "Binance", + "Kucoin", + "Poloniex", + "Hitbtc" + ], + "premiumsSymbol1M": ".XBTUSDTMPI", + "premiumsSymbol8H": ".XBTUSDTMPI8H", + "fundingBaseSymbol1M": ".XBTINT", + "fundingQuoteSymbol1M": ".USDTINT", + "lowPrice": 38040, + "highPrice": 44948, + "priceChgPct": 0.1702, + "priceChg": 6476 + } + + self.assertTrue(utils.is_exchange_information_valid(exchange_info)) + + exchange_info["status"] = "Closed" + + self.assertFalse(utils.is_exchange_information_valid(exchange_info)) + + del exchange_info["status"] + + self.assertFalse(utils.is_exchange_information_valid(exchange_info)) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_web_utils.py b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_web_utils.py new file mode 100644 index 0000000..622a48c --- /dev/null +++ b/test/hummingbot/connector/derivative/kucoin_perpetual/test_kucoin_perpetual_web_utils.py @@ -0,0 +1,11 @@ +from unittest import TestCase + +from hummingbot.connector.derivative.kucoin_perpetual import kucoin_perpetual_web_utils as web_utils + + +class WebUtilsTests(TestCase): + def test_get_rest_url_for_endpoint(self): + endpoint = "testEndpoint" + + url = web_utils.get_rest_url_for_endpoint(endpoint, domain="kucoin_perpetual_main") + self.assertEqual("https://api-futures.kucoin.com/testEndpoint", url) diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/__init__.py b/test/hummingbot/connector/derivative/phemex_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_order_book_data_source.py b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_order_book_data_source.py new file mode 100644 index 0000000..2d85c84 --- /dev/null +++ b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_order_book_data_source.py @@ -0,0 +1,523 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.phemex_perpetual import phemex_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_order_book_data_source import ( + PhemexPerpetualAPIOrderBookDataSource, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_derivative import PhemexPerpetualDerivative +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class PhemexPerpetualAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source loggelates + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + cls.domain = CONSTANTS.TESTNET_DOMAIN + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.async_tasks: List[asyncio.Task] = [] + + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = PhemexPerpetualDerivative( + client_config_map=client_config_map, + phemex_perpetual_api_key="", + phemex_perpetual_api_secret="", + trading_pairs=[self.trading_pair], + trading_required=False, + domain=self.domain, + ) + self.data_source = PhemexPerpetualAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + PhemexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict({self.ex_trading_pair: self.trading_pair}) + } + + self.connector._set_trading_pair_symbol_map(bidict({f"{self.base_asset}{self.quote_asset}": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + for task in self.async_tasks: + task.cancel() + PhemexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def resume_test_callback(self, *_, **__): + self.resume_test_event.set() + return None + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _raise_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _orderbook_update_event(self): + resp = { + "orderbook_p": {"asks": [[86775000, 4621]], "bids": []}, + "depth": 30, + "sequence": 1191905, + "symbol": self.ex_trading_pair, + "type": "incremental", + } + return resp + + def _orderbook_trade_event(self): + resp = { + "sequence": 1167852, + "symbol": self.ex_trading_pair, + "trades_p": [ + [1573716998128563500, "Buy", 86735000, 56], + [1573716995033683000, "Buy", 86735000, 52], + [1573716991485286000, "Buy", 86735000, 51], + [1573716988636291300, "Buy", 86735000, 12], + ], + "type": "snapshot", + } + return resp + + def _funding_info_event(self): + resp = { + "data": [ + [ + self.ex_trading_pair, + "1533.72", + "1594.17", + "1510.05", + "1547.52", + "545942.34", + "848127644.5712", + "0", + "1548.31694379", + "1548.44513153", + "0.0001", + "0.0001", + ], + [ + "BTCUSDT", + "20614.5", + "21628.4", + "19258.6", + "20626.3", + "8819.819", + "182892627.4297", + "0", + "20641.8167574", + "20643.52572781", + "0.0001", + "0.0001", + ], + ], + "fields": [ + "symbol", + "openRp", + "highRp", + "lowRp", + "lastRp", + "volumeRq", + "turnoverRv", + "openInterestRv", + "indexRp", + "markRp", + "fundingRateRr", + "predFundingRateRr", + ], + "method": "perp_market24h_pack_p.update", + "timestamp": 1666862556850547000, + "type": "snapshot", + } + return resp + + @aioresponses() + def test_get_snapshot_exception_raised(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + with self.assertRaises(IOError) as context: + self.async_run_with_timeout(self.data_source._order_book_snapshot(trading_pair=self.trading_pair)) + + self.assertEqual( + 'Error executing request GET https://testnet-api.phemex.com/md/v2/orderbook. HTTP status is 400. Error: ["ERROR"]', + str(context.exception), + ) + + @aioresponses() + def test_get_snapshot_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = { + "error": None, + "id": 0, + "result": { + "orderbook_p": { + "asks": [[87705000, 1000000], [87710000, 200000]], + "bids": [[87700000, 2000000], [87695000, 200000]], + }, + "depth": 30, + "sequence": 455476965, + "timestamp": 1583555482434235628, + "symbol": self.ex_trading_pair, + "type": "snapshot", + }, + } + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source._request_order_book_snapshot(trading_pair=self.trading_pair) + ) + self.assertEqual(mock_response, result) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = { + "error": None, + "id": 0, + "result": { + "orderbook_p": { + "asks": [[87705000, 1000000], [87710000, 200000]], + "bids": [[87700000, 2000000], [87695000, 200000]], + }, + "depth": 30, + "sequence": 455476965, + "timestamp": 1583555482434235628, + "symbol": self.ex_trading_pair, + "type": "snapshot", + }, + } + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + result = self.async_run_with_timeout(self.data_source.get_new_order_book(trading_pair=self.trading_pair)) + self.assertIsInstance(result, OrderBook) + self.assertEqual(455476965, result.snapshot_uid) + + @aioresponses() + def test_get_funding_info_from_exchange_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.MARK_PRICE_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "error": None, + "id": 0, + "result": { + "closeRp": "20731", + "fundingRateRr": "0.0001", + "highRp": "20818.8", + "indexPriceRp": "20737.09857143", + "lowRp": "20425.2", + "markPriceRp": "20737.788944", + "openInterestRv": "0", + "openRp": "20709", + "predFundingRateRr": "0.0001", + "symbol": self.ex_trading_pair, + "timestamp": 1667222412794076700, + "turnoverRv": "139029311.7517", + "volumeRq": "6747.727", + }, + } + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_funding_info(self.trading_pair)) + + self.assertIsInstance(result, FundingInfo) + self.assertEqual(result.trading_pair, self.trading_pair) + self.assertEqual(result.index_price, Decimal(mock_response["result"]["indexPriceRp"])) + self.assertEqual(result.mark_price, Decimal(mock_response["result"]["markPriceRp"])) + self.assertEqual(result.rate, Decimal(mock_response["result"]["fundingRateRr"])) + + def test_listen_for_funding_info_successful(self): + funding_info_event = { + "data": [ + [ + self.ex_trading_pair, + "0.6597", + "0.6887", + "0.6149", + "0.6429", + "8416322", + "5502514.8318", + "291407", + "0.6418", + "0.642054889", + "0.0001", + "0.0001", + ] + ], + "fields": [ + "symbol", + "openRp", + "highRp", + "lowRp", + "lastRp", + "volumeRq", + "turnoverRv", + "openInterestRv", + "indexRp", + "markRp", + "fundingRateRr", + "predFundingRateRr", + ], + "method": "perp_market24h_pack_p.update", + "timestamp": 1681507081332818939, + "type": "snapshot", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [funding_info_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_funding_info(msg_queue)) + + msg: FundingInfoUpdate = self.async_run_with_timeout(msg_queue.get()) + funding_update = funding_info_event["data"][0] + + self.assertEqual(self.trading_pair, msg.trading_pair) + expected_index_price = Decimal(str(funding_update[8])) + self.assertEqual(expected_index_price, msg.index_price) + expected_mark_price = Decimal(str(funding_update[9])) + self.assertEqual(expected_mark_price, msg.mark_price) + expected_rate = Decimal(funding_update[10]) + self.assertEqual(expected_rate, msg.rate) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_cancelled_when_connecting(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + self.assertEqual(msg_queue.qsize(), 0) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + sleep_mock.side_effect = asyncio.CancelledError + mock_ws.side_effect = Exception("TEST ERROR.") + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged( + "ERROR", "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." + ) + ) + + def test_subscribe_to_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_to_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_channel_originating_message_returns_correct(self): + event_type = self._orderbook_update_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._diff_messages_queue_key, event_message) + + event_type = self._funding_info_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._funding_info_messages_queue_key, event_message) + + event_type = self._orderbook_trade_event() + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._trade_messages_queue_key, event_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successful(self, mock_ws): + msg_queue_diffs: asyncio.Queue = asyncio.Queue() + msg_queue_trades: asyncio.Queue = asyncio.Queue() + msg_queue_funding: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.close.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_update_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_trade_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._funding_info_event()) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.listening_task_diffs = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue_diffs) + ) + self.listening_task_trades = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue_trades) + ) + self.listening_task_funding_info = self.ev_loop.create_task( + self.data_source.listen_for_funding_info(msg_queue_funding) + ) + self.async_tasks.extend([self.listening_task, self.listening_task_diffs, self.listening_task_trades, self.listening_task_funding_info]) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_diffs.get()) + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.DIFF, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(result.update_id, 1191905) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + self.assertEqual(0, len(result.content["bids"])) + self.assertEqual(1, len(result.content["asks"])) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_trades.get()) + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.TRADE, result.type) + self.assertTrue(result.has_trade_id) + self.assertEqual(result.trade_id, 1573716998128563500) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_error_raised(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + def test_listen_for_order_book_snapshots_logs_exception_error_with_response(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "m": 1, + "i": 2, + } + mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "error": None, + "id": 0, + "result": { + "orderbook_p": { + "asks": [[87705000, 1000000], [87710000, 200000]], + "bids": [[87700000, 2000000], [87695000, 200000]], + }, + "depth": 30, + "sequence": 455476965, + "timestamp": 1583555482434235628, + "symbol": self.ex_trading_pair, + "type": "snapshot", + }, + } + mock_api.get(regex_url, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + result = self.async_run_with_timeout(msg_queue.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(result.update_id, 455476965) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + def test_listen_for_funding_info_cancelled_error_raised(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.data_source._message_queue[self.data_source._funding_info_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_funding_info(mock_queue)) diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_user_stream_data_source.py b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_user_stream_data_source.py new file mode 100644 index 0000000..00e5989 --- /dev/null +++ b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_api_user_stream_data_source.py @@ -0,0 +1,308 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, patch + +from hummingbot.connector.derivative.phemex_perpetual import ( + phemex_perpetual_constants as CONSTANTS, + phemex_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_user_stream_data_source import ( + PhemexPerpetualAPIUserStreamDataSource, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_auth import PhemexPerpetualAuth +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class PhemexPerpetualAPIUserStreamDataSourceTest(unittest.TestCase): + ev_loop: asyncio.AbstractEventLoop + base_asset: str + quote_asset: str + trading_pair: str + exchange_trading_pair: str + domain: str + api_key: str + secret_key: str + listen_key: str + + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.exchange_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.emulated_time = 1640001112.223 + + self.auth = PhemexPerpetualAuth( + api_key=self.api_key, + api_secret=self.secret_key, + time_provider=self, + ) + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + api_factory = web_utils.build_api_factory(auth=self.auth) + self.data_source = PhemexPerpetualAPIUserStreamDataSource( + auth=self.auth, + api_factory=api_factory, + domain=self.domain, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mock_done_event = asyncio.Event() + self.resume_test_event = asyncio.Event() + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_user_stream_data_source." + "PhemexPerpetualAPIUserStreamDataSource._sleep" + ) + def test_create_websocket_connection_log_exception(self, sleep_mock: AsyncMock, ws_mock: AsyncMock): + ws_mock.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(coroutine=self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.exceptions.CancelledError: + pass + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message="Unexpected error while listening to user stream. Retrying after 5 seconds...", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_user_stream_data_source." + "PhemexPerpetualAPIUserStreamDataSource._sleep" + ) + def test_create_websocket_connection_log_authentication_failure(self, sleep_mock: AsyncMock, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + auth_error_message = { + "error": {"code": 6012, "message": "invalid login token"}, + "id": None, + "result": None, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(auth_error_message) + ) + + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(coroutine=self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.exceptions.CancelledError: + pass + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message="Error authenticating the private websocket connection", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_create_websocket_connection_authenticates(self, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + auth_success_message = { + "error": None, + "id": 0, + "result": {"status": "success"}, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(auth_success_message) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=ws_mock.return_value + ) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_mock.return_value, + ) + + self.assertLessEqual(1, len(sent_messages)) + + auth_message = sent_messages[0] + expected_auth_message = self.auth.get_ws_auth_payload() + + self.assertEqual(expected_auth_message, auth_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_subscription_errors(self, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + auth_success_message = { + "error": None, + "id": 0, + "result": {"status": "success"}, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(auth_success_message) + ) + subscription_error_message = { + "error": {'code': 6001, 'message': 'invalid argument'}, + "id": None, + "result": None, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(subscription_error_message) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=ws_mock.return_value + ) + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message="Unexpected error occurred subscribing to the private account channel..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_user_stream(self, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + success_message = { + "error": None, + "id": 0, + "result": {"status": "success"}, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(success_message) # auth + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(success_message) # subscription + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=ws_mock.return_value + ) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_mock.return_value, + ) + + self.assertEqual(3, len(sent_messages)) + + auth_message = sent_messages[1] + expected_auth_message = { + "id": 0, + "method": "aop_p.subscribe", + "params": [], + } + + self.assertEqual(expected_auth_message, auth_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_api_user_stream_data_source." + "PhemexPerpetualAPIUserStreamDataSource._sleep" + ) + def test_listen_for_user_stream_iter_message_throws_exception(self, _: AsyncMock, ws_mock: AsyncMock): + msg_queue: asyncio.Queue = asyncio.Queue() + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_mock.return_value.receive.side_effect = Exception("TEST ERROR") + ws_mock.return_value.closed = False + ws_mock.return_value.close.side_effect = Exception + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except Exception: + pass + + self.assertTrue( + self.is_logged( + log_level="ERROR", + message="Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_forwards_events(self, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + success_message = { + "error": None, + "id": 0, + "result": {"status": "success"}, + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(success_message) # auth + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(success_message) # subscription + ) + event_message = { + "some": "event", + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(event_message) + ) + + message_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(message_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=ws_mock.return_value + ) + + self.assertFalse(message_queue.empty()) + + received_message = message_queue.get_nowait() + + self.assertEqual(event_message, received_message) diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_auth.py b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_auth.py new file mode 100644 index 0000000..97a6d88 --- /dev/null +++ b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_auth.py @@ -0,0 +1,121 @@ +import asyncio +import copy +import hashlib +import hmac +import json +import unittest +from typing import Awaitable +from urllib.parse import urlencode + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_auth import PhemexPerpetualAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class PhemexPerpetualAuthUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.emulated_time = 1640001112.223 + self.path = "/TEST_PATH_URL" + self.test_params = { + "test_param": "test_input", + "timestamp": int(self.emulated_time), + } + self.auth = PhemexPerpetualAuth( + api_key=self.api_key, + api_secret=self.secret_key, + time_provider=self) + + def _get_test_payload(self, is_get: bool = True): + payload = "" + if is_get is True: + payload += ( + self.path + + urlencode(dict(copy.deepcopy(self.test_params))) + + str(int(self.emulated_time) + CONSTANTS.ONE_MINUTE)) + else: + payload += ( + self.path + + str(int(self.emulated_time) + CONSTANTS.ONE_MINUTE) + + json.dumps(copy.deepcopy(self.test_params)) + ) + return payload + + def _get_signature_from_test_payload(self, is_get: bool = True): + return hmac.new( + bytes(self.auth._api_secret.encode("utf-8")), self._get_test_payload(is_get=is_get).encode("utf-8"), hashlib.sha256 + ).hexdigest() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_generate_signature_from_payload(self): + payload = self._get_test_payload() + signature = self.auth.generate_signature_from_payload(payload) + + self.assertEqual(signature, self._get_signature_from_test_payload()) + + def test_rest_authenticate_parameters_provided(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url=self.path, params=copy.deepcopy(self.test_params), is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("x-phemex-access-token", signed_request.headers) + self.assertEqual(signed_request.headers["x-phemex-access-token"], self.api_key) + self.assertIn("x-phemex-request-signature", signed_request.headers) + self.assertEqual(signed_request.headers["x-phemex-request-signature"], self._get_signature_from_test_payload()) + + def test_rest_authenticate_data_provided(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, url=self.path, data=json.dumps(self.test_params), is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("x-phemex-access-token", signed_request.headers) + self.assertEqual(signed_request.headers["x-phemex-access-token"], self.api_key) + self.assertIn("x-phemex-request-signature", signed_request.headers) + self.assertEqual( + signed_request.headers["x-phemex-request-signature"], + self._get_signature_from_test_payload(is_get=False) + ) + + def test_ws_authenticate(self): + request: WSJSONRequest = WSJSONRequest( + payload={"TEST": "SOME_TEST_PAYLOAD"}, throttler_limit_id="TEST_LIMIT_ID", is_auth_required=True + ) + + signed_request: WSJSONRequest = self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(request, signed_request) + + def test_get_ws_auth_payload(self): + auth_payload = self.auth.get_ws_auth_payload() + payload = f"{self.api_key}{int(self.emulated_time) + 2}" + signature = hmac.new(self.secret_key.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).hexdigest() + target_auth_payload = { + "method": "user.auth", + "params": [ + "API", + self.api_key, + signature, + int(self.emulated_time) + 2, + ], + "id": 0, + } + + self.assertEqual(target_auth_payload, auth_payload) diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_derivative.py b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_derivative.py new file mode 100644 index 0000000..f908b35 --- /dev/null +++ b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_derivative.py @@ -0,0 +1,1601 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple, Union +from unittest.mock import patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.phemex_perpetual import ( + phemex_perpetual_constants as CONSTANTS, + phemex_perpetual_web_utils as web_utils, +) +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_derivative import PhemexPerpetualDerivative +from hummingbot.connector.test_support.perpetual_derivative_test import AbstractPerpetualDerivativeTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import OrderCancelledEvent + + +class PhemexPerpetualDerivativeTests(AbstractPerpetualDerivativeTests.PerpetualDerivativeTests): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_secret = "someSecret" + cls.quote_asset = "USDT" + cls.trading_pair = combine_to_hb_trading_pair(cls.base_asset, cls.quote_asset) + + def setUp(self) -> None: + super().setUp() + self.exchange._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}{self.quote_asset}": self.trading_pair, "ABCDEF": "ABC-DEF"}) + ) + + @property + def expected_supported_position_modes(self) -> List[PositionMode]: + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + @property + def funding_info_url(self): + return CONSTANTS.TICKER_PRICE_URL + + @property + def funding_payment_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.FUNDING_PAYMENT) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def funding_info_mock_response(self): + return { + "error": None, + "id": 0, + "result": { + "closeRp": "20731", + "fundingRateRr": "3", + "highRp": "20818.8", + "indexPriceRp": "1", + "lowRp": "20425.2", + "markPriceRp": "2", + "openInterestRv": "0", + "openRp": "20709", + "predFundingRateRr": "3", + "symbol": self.exchange_trading_pair, + "timestamp": self.exchange._orderbook_ds._next_funding_time(), + "turnoverRv": "139029311.7517", + "volumeRq": "6747.727", + }, + } + + @property + def target_funding_payment_timestamp(self): + return 1666226932259 + + @property + def empty_funding_payment_mock_response(self): + return {"code": 0, "msg": "OK", "data": {"total": 4, "rows": []}} + + @property + def funding_payment_mock_response(self): + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "createTime": 1666226932259, + "symbol": self.exchange_trading_pair, + "currency": "USDT", + "action": 1, + "tradeType": 1, + "execQtyRq": "0.01", + "execPriceRp": "1271.9", + "side": 1, + "orderQtyRq": "0.78", + "priceRp": "1271.9", + "execValueRv": "200", + "feeRateRr": "100", + "execFeeRv": "200", + "ordType": 2, + "execId": "8718cae", + "execStatus": 6, + }, + ], + } + } + + def position_event_for_full_fill_websocket_update(self, order: InFlightOrder, unrealized_pnl: float): + return { + "positions_p": [ + { + "accountID": 9328670003, + "assignedPosBalanceRv": "30.861734862748", + "avgEntryPriceRp": str(order.price), + "bankruptCommRv": "0.0000006", + "bankruptPriceRp": "0.1", + "buyLeavesQty": "0", + "buyLeavesValueRv": "0", + "buyValueToCostRr": "0.10114", + "createdAtNs": 0, + "crossSharedBalanceRv": "1165.319989135354", + "cumClosedPnlRv": "0", + "cumFundingFeeRv": "0.089061821453", + "cumTransactFeeRv": "0.57374652", + "curTermRealisedPnlRv": "-0.662808341453", + "currency": "USDT", + "dataVer": 11, + "deleveragePercentileRr": "0", + "displayLeverageRr": "0.79941382", + "estimatedOrdLossRv": "0", + "execSeq": 77751555, + "freeCostRv": "0", + "freeQty": "-0.046", + "initMarginReqRr": "0.1", + "lastFundingTime": 1666857600000000000, + "lastTermEndTime": 0, + "leverageRr": str(order.leverage), + "liquidationPriceRp": "0.1", + "maintMarginReqRr": "0.01", + "makerFeeRateRr": "-1", + "markPriceRp": "20735.47347096", + "minPosCostRv": "0", + "orderCostRv": "0", + "posCostRv": "30.284669572349", + "posMode": "Oneway", + "posSide": "Merged", + "positionMarginRv": "1196.181723398102", + "positionStatus": "Normal", + "riskLimitRv": "1000000", + "sellLeavesQty": "0", + "sellLeavesValueRv": "0", + "sellValueToCostRr": "0.10126", + "side": "Sell", + "size": str(order.amount), + "symbol": self.exchange_trading_pair, + "takerFeeRateRr": "-1", + "term": 1, + "transactTimeNs": 1666858780881545305, + "unrealisedPnlRv": str(unrealized_pnl), + "updatedAtNs": 0, + "usedBalanceRv": "30.861734862748", + "userID": 932867, + "valueRv": "956.2442", + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + def configure_successful_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.private_rest_url(path_url=CONSTANTS.POSITION_MODE) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = {"code": 0, "data": "", "msg": ""} + mock_api.put(regex_url, body=json.dumps(response), callback=callback) + + return url + + def configure_failed_set_position_mode( + self, + position_mode: PositionMode, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ): + url = web_utils.private_rest_url(path_url=CONSTANTS.POSITION_MODE) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = {"code": -1, "data": "", "msg": "Error."} + mock_api.put(regex_url, body=json.dumps(response), callback=callback) + + return url, response["msg"] + + def configure_failed_set_leverage( + self, leverage: int, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Tuple[str, str]: + url = web_utils.private_rest_url(path_url=CONSTANTS.POSITION_LEVERAGE) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = {"code": -1, "data": "", "msg": "Error."} + mock_api.put(regex_url, body=json.dumps(response), callback=callback) + + return url, response["msg"] + + def configure_successful_set_leverage( + self, leverage: int, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ): + url = web_utils.private_rest_url(path_url=CONSTANTS.POSITION_LEVERAGE) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = {"code": 0, "data": "", "msg": ""} + mock_api.put(regex_url, body=json.dumps(response), callback=callback) + + return url + + def funding_info_event_for_websocket_update(self): + return { + "data": [ + [ + self.exchange_trading_pair, + "1533.72", + "1594.17", + "1510.05", + "1547.52", + "545942.34", + "848127644.5712", + "0", + "1548.31694379", + "1548.44513153", + "0.0001", + "0.0001", + ] + ], + "fields": [ + "symbol", + "openRp", + "highRp", + "lowRp", + "lastRp", + "volumeRq", + "turnoverRv", + "openInterestRv", + "indexRp", + "markRp", + "fundingRateRr", + "predFundingRateRr", + ], + "method": "perp_market24h_pack_p.update", + "timestamp": 1666862556850547000, + "type": "snapshot", + } + + def test_get_buy_and_sell_collateral_tokens(self): + return "USDT" + + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return regex_url + + @property + def network_status_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL) + + @property + def trading_rules_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_URL) + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.PLACE_ORDERS) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNT_INFO) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "code": 0, + "msg": "", + "data": { + "currencies": [ + { + "currency": "BTC", + "name": "Bitcoin", + "code": 1, + "valueScale": 8, + "minValueEv": 1, + "maxValueEv": 5000000000000000000, + "needAddrTag": 0, + "status": "Listed", + "displayCurrency": "BTC", + "inAssetsDisplay": 1, + "perpetual": 0, + "stableCoin": 0, + "assetsPrecision": 8, + }, + ], + "products": [ + { + "symbol": "XRPUSD", + "code": 21, + "type": "Perpetual", + "displaySymbol": "XRP / USD", + "indexSymbol": ".XRP", + "markSymbol": ".MXRP", + "fundingRateSymbol": ".XRPFR", + "fundingRate8hSymbol": ".XRPFR8H", + "contractUnderlyingAssets": "XRP", + "settleCurrency": "USD", + "quoteCurrency": "USD", + "contractSize": 5.0, + "lotSize": 1, + "tickSize": 1.0e-4, + "priceScale": 4, + "ratioScale": 8, + "pricePrecision": 4, + "minPriceEp": 1, + "maxPriceEp": 2000000, + "maxOrderQty": 500000, + "description": ( + "XRP/USD perpetual contracts are priced on the .XRP Index. Each contract is " + "worth 5 XRP. Funding fees are paid and received every 8 hours at UTC " + "time: 00:00, 08:00 and 16:00." + ), + "status": "Listed", + "tipOrderQty": 100000, + "listTime": 1574650800000, + "majorSymbol": False, + "defaultLeverage": "-10", + "fundingInterval": 28800, + "maxLeverage": 100, + }, + ], + "perpProductsV2": [ + { + "symbol": self.exchange_trading_pair, + "code": 41641, + "type": "PerpetualV2", + "displaySymbol": f"{self.base_asset} / {self.quote_asset}", + "indexSymbol": f".{self.exchange_trading_pair}", + "markSymbol": ".METHUSDT", + "fundingRateSymbol": ".ETHUSDTFR", + "fundingRate8hSymbol": ".ETHUSDTFR8H", + "contractUnderlyingAssets": self.base_asset, + "settleCurrency": self.quote_asset, + "quoteCurrency": self.quote_asset, + "tickSize": "0.01", + "priceScale": 0, + "ratioScale": 0, + "pricePrecision": 2, + "baseCurrency": self.base_asset, + "description": ( + "ETH/USDT perpetual contracts are priced on the .ETHUSDT Index. Each contract " + "is worth 1 ETH. Funding fees are paid and received every 8 hours at UTC " + "time: 00:00, 08:00 and 16:00." + ), + "status": "Listed", + "tipOrderQty": 0, + "listTime": 1668225600000, + "majorSymbol": False, + "defaultLeverage": "-10", + "fundingInterval": 28800, + "maxLeverage": 100, + "maxOrderQtyRq": "500000", + "maxPriceRp": "200000000", + "minOrderValueRv": "1", + "minPriceRp": "100.0", + "qtyPrecision": 2, + "qtyStepSize": "0.01", + "tipOrderQtyRq": "100000", + }, + ], + "riskLimits": [ + { + "symbol": "BTCUSD", + "steps": "50", + "riskLimits": [ + { + "limit": 100, + "initialMargin": "1.0%", + "initialMarginEr": 1000000, + "maintenanceMargin": "0.5%", + "maintenanceMarginEr": 500000, + }, + { + "limit": 150, + "initialMargin": "1.5%", + "initialMarginEr": 1500000, + "maintenanceMargin": "1.0%", + "maintenanceMarginEr": 1000000, + }, + { + "limit": 200, + "initialMargin": "2.0%", + "initialMarginEr": 2000000, + "maintenanceMargin": "1.5%", + "maintenanceMarginEr": 1500000, + }, + { + "limit": 250, + "initialMargin": "2.5%", + "initialMarginEr": 2500000, + "maintenanceMargin": "2.0%", + "maintenanceMarginEr": 2000000, + }, + { + "limit": 300, + "initialMargin": "3.0%", + "initialMarginEr": 3000000, + "maintenanceMargin": "2.5%", + "maintenanceMarginEr": 2500000, + }, + { + "limit": 350, + "initialMargin": "3.5%", + "initialMarginEr": 3500000, + "maintenanceMargin": "3.0%", + "maintenanceMarginEr": 3000000, + }, + { + "limit": 400, + "initialMargin": "4.0%", + "initialMarginEr": 4000000, + "maintenanceMargin": "3.5%", + "maintenanceMarginEr": 3500000, + }, + { + "limit": 450, + "initialMargin": "4.5%", + "initialMarginEr": 4500000, + "maintenanceMargin": "4.0%", + "maintenanceMarginEr": 4000000, + }, + { + "limit": 500, + "initialMargin": "5.0%", + "initialMarginEr": 5000000, + "maintenanceMargin": "4.5%", + "maintenanceMarginEr": 4500000, + }, + { + "limit": 550, + "initialMargin": "5.5%", + "initialMarginEr": 5500000, + "maintenanceMargin": "5.0%", + "maintenanceMarginEr": 5000000, + }, + ], + }, + ], + "riskLimitsV2": [ + { + "symbol": "BTCUSDT", + "steps": "2000K", + "riskLimits": [ + {"limit": 2000000, "initialMarginRr": "0.01", "maintenanceMarginRr": "0.005"}, + {"limit": 4000000, "initialMarginRr": "0.015", "maintenanceMarginRr": "0.0075"}, + {"limit": 6000000, "initialMarginRr": "0.02", "maintenanceMarginRr": "0.01"}, + {"limit": 8000000, "initialMarginRr": "0.025", "maintenanceMarginRr": "0.0125"}, + {"limit": 10000000, "initialMarginRr": "0.03", "maintenanceMarginRr": "0.015"}, + {"limit": 12000000, "initialMarginRr": "0.035", "maintenanceMarginRr": "0.0175"}, + {"limit": 14000000, "initialMarginRr": "0.04", "maintenanceMarginRr": "0.02"}, + {"limit": 16000000, "initialMarginRr": "0.045", "maintenanceMarginRr": "0.0225"}, + {"limit": 18000000, "initialMarginRr": "0.05", "maintenanceMarginRr": "0.025"}, + {"limit": 20000000, "initialMarginRr": "0.055", "maintenanceMarginRr": "0.0275"}, + ], + }, + ], + "ratioScale": 8, + "md5Checksum": "1c894ae8fa2f98163af663e288752ad4", + }, + } + + @property + def latest_prices_request_mock_response(self): + return {"code": 0, "msg": "OK", "data": {"total": -1, "rows": [[0, 0, 1]]}} + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = self.all_symbols_request_mock_response + invalid_product = { + "symbol": "INVALIDPAIR", + "code": 41641, + "type": "PerpetualV2", + "displaySymbol": "INVALID / PAIR", + "indexSymbol": f".{self.exchange_trading_pair}", + "markSymbol": ".METHUSDT", + "fundingRateSymbol": ".ETHUSDTFR", + "fundingRate8hSymbol": ".ETHUSDTFR8H", + "contractUnderlyingAssets": "INVALID", + "settleCurrency": "PAIR", + "quoteCurrency": "PAIR", + "tickSize": "0.01", + "priceScale": 0, + "ratioScale": 0, + "pricePrecision": 2, + "baseCurrency": self.base_asset, + "description": ( + "ETH/USDT perpetual contracts are priced on the .ETHUSDT Index. Each contract " + "is worth 1 ETH. Funding fees are paid and received every 8 hours at UTC " + "time: 00:00, 08:00 and 16:00." + ), + "status": "Delisted", + "tipOrderQty": 0, + "listTime": 1668225600000, + "majorSymbol": False, + "defaultLeverage": "-10", + "fundingInterval": 28800, + "maxLeverage": 100, + "maxOrderQtyRq": "500000", + "maxPriceRp": "200000000", + "minOrderValueRv": "1", + "minPriceRp": "100.0", + "qtyPrecision": 2, + "qtyStepSize": "0.01", + "tipOrderQtyRq": "100000", + } + response["data"]["perpProductsV2"].append(invalid_product) + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return {"code": 0, "msg": "", "data": {"serverTime": 1680564306718}} + + @property + def trading_rules_request_mock_response(self): + return { + "code": 0, + "msg": "", + "data": { + "perpProductsV2": [ + { + "symbol": self.exchange_trading_pair, + "code": 41541, + "type": "PerpetualV2", + "displaySymbol": "COINALPHA / USDT", + "indexSymbol": ".COINALPHAUSDT", + "markSymbol": ".MCOINALPHAUSDT", + "fundingRateSymbol": ".BCOINALPHAUSDTFR", + "fundingRate8hSymbol": ".BTCUSDTFR8H", + "contractUnderlyingAssets": "COINALPHA", + "settleCurrency": "USDT", + "quoteCurrency": "USDT", + "tickSize": "0.1", + "priceScale": 0, + "ratioScale": 0, + "pricePrecision": 1, + "baseCurrency": "COINALPHA", + "description": "COINALPHA/USDT perpetual contracts are priced on the .BTCUSDT Index. Each contract is worth 1 BTC. Funding fees are paid and received every 8 hours at UTC time: 00:00, 08:00 and 16:00.", + "status": "Listed", + "tipOrderQty": 0, + "listTime": 1668225600000, + "majorSymbol": True, + "defaultLeverage": "-10", + "fundingInterval": 28800, + "maxLeverage": 100, + "maxOrderQtyRq": "100000", + "maxPriceRp": "2000000000", + "minOrderValueRv": "1", + "minPriceRp": "1000.0", + "qtyPrecision": 3, + "qtyStepSize": "0.001", + "tipOrderQtyRq": "20000", + } + ], + "ratioScale": 8, + "md5Checksum": "e90521d639d35356ccb76643d0b7e57c", + }, + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "code": -1, + "data": { + "perpProductsV2": [ + { + "symbol": self.exchange_trading_pair, + "code": 41541, + "type": "PerpetualV2", + "displaySymbol": "COINALPHA / USDT", + "indexSymbol": ".COINALPHAUSDT", + "markSymbol": ".MCOINALPHAUSDT", + "fundingRateSymbol": ".BCOINALPHAUSDTFR", + "fundingRate8hSymbol": ".BTCUSDTFR8H", + "contractUnderlyingAssets": "COINALPHA", + "settleCurrency": "USDT", + "quoteCurrency": "USDT", + "tickSize": "0.1", + "priceScale": 0, + "ratioScale": 0, + "pricePrecision": 1, + "baseCurrency": "COINALPHA", + "description": "COINALPHA/USDT perpetual contracts are priced on the .BTCUSDT Index. Each contract is worth 1 BTC. Funding fees are paid and received every 8 hours at UTC time: 00:00, 08:00 and 16:00.", + "status": "Listed", + "tipOrderQty": 0, + "listTime": 1668225600000, + "majorSymbol": True, + "defaultLeverage": "-10", + "fundingInterval": 28800, + "maxLeverage": 100, + "maxOrderQtyRq": "100000", + "maxPriceRp": "2000000000", + "minOrderValueRv": "1", + "minPriceRp": "1000.0", + "qtyPrecision": 3, + "qtyStepSize": "ERROR", + "tipOrderQtyRq": "20000", + } + ], + }, + "msg": "Unable to set position mode.", + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "code": 0, + "data": { + "actionTimeNs": 1580547265848034600, + "bizError": 0, + "clOrdID": "137e1928-5d25-fecd-dbd1-705ded659a4f", + "closedPnlRv": "1271.9", + "closedSizeRq": "0.01", + "cumQtyRq": "0.01", + "cumValueRv": "1271.9", + "displayQtyRq": "0.01", + "execInst": "ReduceOnly", + "execStatus": "Init", + "leavesQtyRq": "0.01", + "leavesValueRv": "1271.9", + "ordStatus": "Init", + "orderID": "ab90a08c-b728-4b6b-97c4-36fa497335bf", + "orderQtyRq": "0.01", + "orderType": "Limit", + "pegOffsetValueRp": "1271.9", + "pegPriceType": "LastPeg", + "priceRq": "98970000", + "reduceOnly": True, + "side": "Sell", + "stopDirection": "Rising", + "stopPxRp": "1271.9", + "symbol": self.exchange_trading_pair, + "timeInForce": "GoodTillCancel", + "transactTimeNs": 0, + "trigger": "ByMarkPrice", + }, + "msg": "", + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "code": 0, + "msg": "", + "data": { + "account": { + "userID": 4724193, + "accountId": 47241930003, + "currency": "USDT", + "accountBalanceRv": "15", + "totalUsedBalanceRv": "5", + "bonusBalanceRv": "0", + }, + "positions": [], + }, + } + + @property + def balance_request_mock_response_only_base(self): + return self.balance_request_mock_response_for_base_and_quote + + @property + def balance_event_websocket_update(self): + return { + "accounts_p": [ + { + "accountBalanceRv": "15", + "accountID": 9328670003, + "bonusBalanceRv": "0", + "currency": self.base_asset, + "totalUsedBalanceRv": "5", + "userID": 932867, + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + @property + def expected_latest_price(self): + return Decimal("1.0") + + @property + def expected_supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + trading_rules_resp = self.trading_rules_request_mock_response["data"]["perpProductsV2"][0] + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(trading_rules_resp["qtyStepSize"])), + min_price_increment=Decimal(str(trading_rules_resp["tickSize"])), + min_base_amount_increment=Decimal(str(trading_rules_resp["qtyStepSize"])), + min_notional_size=Decimal(str(trading_rules_resp["minOrderValueRv"])), + buy_order_collateral_token=self.quote_asset, + sell_order_collateral_token=self.quote_asset, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"]["perpProductsV2"][0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "ab90a08c-b728-4b6b-97c4-36fa497335bf" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return False + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return True + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("10500") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "5c3d96e1-8874-53b6-b6e5-9dcc4d28b4ab" + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + exchange = PhemexPerpetualDerivative( + client_config_map, + self.api_key, + self.api_secret, + trading_pairs=[self.trading_pair], + ) + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_data = request_call.kwargs["headers"] + + self.assertIn("x-phemex-request-expiry", request_data) + self.assertIn("x-phemex-access-token", request_data) + self.assertEqual(self.api_key, request_data["x-phemex-access-token"]) + self.assertIn("x-phemex-request-signature", request_data) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(order.client_order_id, request_data["clOrdID"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(order.amount, Decimal(request_data["orderQtyRq"])) + self.assertEqual(order.order_type.name.capitalize(), request_data["ordType"]) + self.assertEqual(order.price, Decimal(request_data["priceRp"])) + self.assertEqual(order.trade_type.name.capitalize(), request_data["side"]) + if self.exchange.position_mode == PositionMode.ONEWAY: + position_side = "Merged" + else: + if order.position in [PositionAction.OPEN, PositionAction.NIL]: + position_side = "Long" if order.trade_type == TradeType.BUY else "Short" + else: + position_side = "Short" if order.trade_type == TradeType.BUY else "Long" + self.assertEqual(position_side, request_data["posSide"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + + if self.exchange._position_mode is PositionMode.ONEWAY: + posSide = "Merged" + else: + if order.position is PositionAction.OPEN: + posSide = "Long" if order.trade_type is TradeType.BUY else "Short" + else: + posSide = "Short" if order.trade_type is TradeType.BUY else "Long" + self.assertEqual(order.client_order_id, request_params["clOrdID"]) + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(posSide, request_params["posSide"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_trading_pair, request_params["symbol"]) + self.assertEqual(200, request_params["limit"]) + self.assertIn("start", request_params) + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.CANCEL_ORDERS) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_cancel_all_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = { + "code": 0 if order.trading_pair == self.trading_pair else 10003, + "msg": "", + } + url = web_utils.private_rest_url(path_url=CONSTANTS.CANCEL_ALL_ORDERS) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.CANCEL_ORDERS) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = { + "code": 10003, + "msg": "OM_ORDER_PENDING_CANCEL", + } + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.CANCEL_ORDERS) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = { + "code": CONSTANTS.ORDER_NOT_FOUND_ERROR_CODE, + "msg": CONSTANTS.ORDER_NOT_FOUND_ERROR_MESSAGE, + } + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + all_urls = [] + url = self.configure_cancel_all_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_cancel_all_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return [url] + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_ORDERS) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADES) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADES) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADES) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orders_p": [ + { + "accountID": 9328670003, + "action": "New", + "actionBy": "ByUser", + "actionTimeNs": 1666858780876924611, + "addedSeq": 77751555, + "apRp": "0", + "bonusChangedAmountRv": "0", + "bpRp": "0", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSize": "0", + "code": 0, + "cumFeeRv": "0", + "cumQty": "0", + "cumValueRv": "0", + "curAccBalanceRv": "1508.489893982237", + "curAssignedPosBalanceRv": "24.62786650928", + "curBonusBalanceRv": "0", + "curLeverageRr": "-10", + "curPosSide": order.trade_type.name.capitalize(), + "curPosSize": "0.043", + "curPosTerm": 1, + "curPosValueRv": "894.0689", + "curRiskLimitRv": "1000000", + "currency": "USDT", + "cxlRejReason": 0, + "displayQty": "0.003", + "execFeeRv": "0", + "execID": "00000000-0000-0000-0000-000000000000", + "execPriceRp": "20723.7", + "execQty": "0", + "execSeq": 77751555, + "execStatus": "New", + "execValueRv": "0", + "feeRateRr": "0", + "leavesQty": "0.003", + "leavesValueRv": "63.4503", + "message": "No error", + "ordStatus": "New", + "ordType": "Market", + "orderID": order.exchange_order_id, + "orderQty": "0.003", + "pegOffsetValueRp": "0", + "posSide": "Long", + "priceRp": "21150.1", + "relatedPosTerm": 1, + "relatedReqNum": 11, + "side": "Buy", + "slTrigger": "ByMarkPrice", + "stopLossRp": "0", + "stopPxRp": "0", + "symbol": self.exchange_trading_pair, + "takeProfitRp": "0", + "timeInForce": "ImmediateOrCancel", + "tpTrigger": "ByLastPrice", + "tradeType": "Amend", + "transactTimeNs": 1666858780881545305, + "userID": 932867, + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orders_p": [ + { + "accountID": 9328670003, + "action": "Canceled", + "actionBy": "ByUser", + "actionTimeNs": 1666858780876924611, + "addedSeq": 77751555, + "apRp": "0", + "bonusChangedAmountRv": "0", + "bpRp": "0", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSize": "0", + "code": 0, + "cumFeeRv": "0", + "cumQty": "0", + "cumValueRv": "0", + "curAccBalanceRv": "1508.489893982237", + "curAssignedPosBalanceRv": "24.62786650928", + "curBonusBalanceRv": "0", + "curLeverageRr": "-10", + "curPosSide": order.trade_type.name.capitalize(), + "curPosSize": "0.043", + "curPosTerm": 1, + "curPosValueRv": "894.0689", + "curRiskLimitRv": "1000000", + "currency": "USDT", + "cxlRejReason": 0, + "displayQty": "0.003", + "execFeeRv": "0", + "execID": "00000000-0000-0000-0000-000000000000", + "execPriceRp": "20723.7", + "execQty": "0", + "execSeq": 77751555, + "execStatus": "New", + "execValueRv": "0", + "feeRateRr": "0", + "leavesQty": "0.003", + "leavesValueRv": "63.4503", + "message": "No error", + "ordStatus": "Canceled", + "ordType": "Market", + "orderID": order.exchange_order_id, + "orderQty": "0.003", + "pegOffsetValueRp": "0", + "posSide": "Long", + "priceRp": "21150.1", + "relatedPosTerm": 1, + "relatedReqNum": 11, + "side": "Buy", + "slTrigger": "ByMarkPrice", + "stopLossRp": "0", + "stopPxRp": "0", + "symbol": self.exchange_trading_pair, + "takeProfitRp": "0", + "timeInForce": "ImmediateOrCancel", + "tpTrigger": "ByLastPrice", + "tradeType": "Amend", + "transactTimeNs": 1666858780881545305, + "userID": 932867, + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orders_p": [ + { + "accountID": 9328670003, + "action": "Filled", + "actionBy": "ByUser", + "actionTimeNs": 1666858780876924611, + "addedSeq": 77751555, + "apRp": "0", + "bonusChangedAmountRv": "0", + "bpRp": "0", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSize": "0", + "code": 0, + "cumFeeRv": "0", + "cumQty": "0", + "cumValueRv": "0", + "curAccBalanceRv": "1508.489893982237", + "curAssignedPosBalanceRv": "24.62786650928", + "curBonusBalanceRv": "0", + "curLeverageRr": "-10", + "curPosSide": order.trade_type.name.capitalize(), + "curPosSize": "0.043", + "curPosTerm": 1, + "curPosValueRv": "894.0689", + "curRiskLimitRv": "1000000", + "currency": "USDT", + "cxlRejReason": 0, + "displayQty": "0.003", + "execFeeRv": "0", + "execID": "00000000-0000-0000-0000-000000000000", + "execPriceRp": "20723.7", + "execQty": "0", + "execSeq": 77751555, + "execStatus": "New", + "execValueRv": "0", + "feeRateRr": "0", + "leavesQty": "0.003", + "leavesValueRv": "63.4503", + "message": "No error", + "ordStatus": "Filled", + "ordType": "Market", + "orderID": order.exchange_order_id, + "orderQty": "0.003", + "pegOffsetValueRp": "0", + "posSide": "Long", + "priceRp": "21150.1", + "relatedPosTerm": 1, + "relatedReqNum": 11, + "side": "Buy", + "slTrigger": "ByMarkPrice", + "stopLossRp": "0", + "stopPxRp": "0", + "symbol": self.exchange_trading_pair, + "takeProfitRp": "0", + "timeInForce": "ImmediateOrCancel", + "tpTrigger": "ByLastPrice", + "tradeType": "Amend", + "transactTimeNs": 1666858780881545305, + "userID": 932867, + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orders_p": [ + { + "accountID": 9328670003, + "action": "Filled", + "actionBy": "ByUser", + "actionTimeNs": 1666858780876924611, + "addedSeq": 77751555, + "apRp": "0", + "bonusChangedAmountRv": "0", + "bpRp": "0", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSize": "0", + "code": 0, + "cumFeeRv": "0", + "cumQty": "0", + "cumValueRv": "0", + "curAccBalanceRv": "1508.489893982237", + "curAssignedPosBalanceRv": "24.62786650928", + "curBonusBalanceRv": "0", + "curLeverageRr": "-10", + "curPosSide": order.trade_type.name.capitalize(), + "curPosSize": "0.043", + "curPosTerm": 1, + "curPosValueRv": "894.0689", + "curRiskLimitRv": "1000000", + "currency": "USDT", + "cxlRejReason": 0, + "displayQty": "0.003", + "execFeeRv": str(self.expected_fill_fee.flat_fees[0].amount), + "execID": "00000000-0000-0000-0000-000000000000", + "execPriceRp": "10000", + "execQty": str(order.amount), + "execSeq": 77751555, + "execStatus": "New", + "execValueRv": "10000", + "feeRateRr": "0", + "leavesQty": "0.003", + "leavesValueRv": "63.4503", + "message": "No error", + "ordStatus": "Filled", + "ordType": "Market", + "orderID": order.exchange_order_id, + "orderQty": "0.003", + "pegOffsetValueRp": "0", + "posSide": "Long", + "priceRp": "21150.1", + "relatedPosTerm": 1, + "relatedReqNum": 11, + "side": "Buy", + "slTrigger": "ByMarkPrice", + "stopLossRp": "0", + "stopPxRp": "0", + "symbol": self.exchange_trading_pair, + "takeProfitRp": "0", + "timeInForce": "ImmediateOrCancel", + "tpTrigger": "ByLastPrice", + "tradeType": "Amend", + "transactTimeNs": 1666858780881545305, + "userID": 932867, + } + ], + "sequence": 68744, + "timestamp": 1666858780883525030, + "type": "incremental", + "version": 0, + } + + @aioresponses() + def test_update_balances(self, mock_api): + # Phemex only returns balance for the collateral token (USDT in the connector supported markets) + + response = self.balance_request_mock_response_for_base_and_quote + self._configure_balance_response(response=response, mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("10"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("15"), total_balances[self.quote_asset]) + self.assertNotIn(self.base_asset, available_balances) + self.assertNotIn(self.base_asset, total_balances) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "data": { + "actionTimeNs": 450000000, + "bizError": 0, + "clOrdID": order.client_order_id, + "closedPnlRv": "1271.9", + "closedSizeRq": "0.01", + "cumQtyRq": "0.01", + "cumValueRv": "1271.9", + "displayQtyRq": "0.01", + "execInst": "ReduceOnly", + "execStatus": "Init", + "leavesQtyRq": "0.01", + "leavesValueRv": "0.01", + "ordStatus": "Canceled", + "orderID": order.exchange_order_id, + "orderQtyRq": str(order.amount), + "orderType": "Market" if order.order_type == OrderType.MARKET else "Limit", + "pegOffsetValueRp": "1271.9", + "pegPriceType": "LastPeg", + "priceRq": str(order.price), + "reduceOnly": True, + "side": order.trade_type.name.capitalize(), + "stopDirection": "Rising", + "stopPxRp": "0.01", + "symbol": self.exchange_trading_pair, + "timeInForce": "GoodTillCancel", + "transactTimeNs": 450000000, + "trigger": "ByMarkPrice", + }, + "msg": "", + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "orderId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "ordType": "Market" if order.order_type == OrderType.MARKET else "Limit", + "actionTimeNs": 1667562110213260743, + "priceRp": str(order.price), + "orderQtyRq": str(order.amount), + "displayQtyRq": str(order.amount), + "timeInForce": "ImmediateOrCancel", + "reduceOnly": False, + "takeProfitRp": "0", + "stopLossRp": "0", + "closedPnlRv": "0", + "closedSizeRq": "0", + "cumQtyRq": "0.001", + "cumValueRv": "20.5795", + "leavesQtyRq": "0", + "leavesValueRv": "0", + "stopDirection": "UNSPECIFIED", + "ordStatus": "Filled", + "transactTimeNs": 1667562110221077395, + "bizError": 0, + } + ] + }, + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "orderId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "ordType": "Market" if order.order_type == OrderType.MARKET else "Limit", + "actionTimeNs": 1667562110213260743, + "priceRp": str(order.price), + "orderQtyRq": str(order.amount), + "displayQtyRq": str(order.amount), + "timeInForce": "ImmediateOrCancel", + "reduceOnly": False, + "takeProfitRp": "0", + "stopLossRp": "0", + "closedPnlRv": "0", + "closedSizeRq": "0", + "cumQtyRq": "0.001", + "cumValueRv": "20.5795", + "leavesQtyRq": "0", + "leavesValueRv": "0", + "stopDirection": "UNSPECIFIED", + "ordStatus": "Canceled", + "transactTimeNs": 1667562110221077395, + "bizError": 0, + } + ] + }, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "orderId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "ordType": "Market" if order.order_type == OrderType.MARKET else "Limit", + "actionTimeNs": 1667562110213260743, + "priceRp": str(order.price), + "orderQtyRq": str(order.amount), + "displayQtyRq": str(order.amount), + "timeInForce": "ImmediateOrCancel", + "reduceOnly": False, + "takeProfitRp": "0", + "stopLossRp": "0", + "closedPnlRv": "0", + "closedSizeRq": "0", + "cumQtyRq": "0.001", + "cumValueRv": "20.5795", + "leavesQtyRq": "0", + "leavesValueRv": "0", + "stopDirection": "UNSPECIFIED", + "ordStatus": "New", + "transactTimeNs": 1667562110221077395, + "bizError": 0, + } + ] + }, + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "orderId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "symbol": self.exchange_trading_pair, + "side": order.trade_type.name.capitalize(), + "ordType": "Market" if order.order_type == OrderType.MARKET else "Limit", + "actionTimeNs": 1667562110213260743, + "priceRp": str(order.price), + "orderQtyRq": str(order.amount), + "displayQtyRq": str(order.amount), + "timeInForce": "ImmediateOrCancel", + "reduceOnly": False, + "takeProfitRp": "0", + "stopLossRp": "0", + "closedPnlRv": "0", + "closedSizeRq": "0", + "cumQtyRq": str(self.expected_partial_fill_amount), + "cumValueRv": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + "leavesQtyRq": str(order.amount - self.expected_partial_fill_amount), + "leavesValueRv": str( + (order.amount * order.price) + - (self.expected_partial_fill_amount * self.expected_partial_fill_price) + ), + "stopDirection": "UNSPECIFIED", + "ordStatus": "PartiallyFilled", + "transactTimeNs": 1667562110221077395, + "bizError": 0, + } + ] + }, + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "action": "New", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSizeRq": "0", + "currency": self.quote_asset, + "execFeeRv": str(self.expected_fill_fee.flat_fees[0].amount), + "execID": self.expected_fill_trade_id, + "execPriceRp": str(order.price), + "execQtyRq": str(order.amount), + "execStatus": "MakerFill", + "execValueRv": str(order.amount * order.price), + "feeRateRr": "0.0001", + "orderID": order.exchange_order_id, + "orderQtyRq": str(order.amount), + "ordType": "LimitIfTouched", + "priceRp": str(order.price), + "side": order.trade_type.name.capitalize(), + "symbol": self.exchange_trading_pair, + "tradeType": "Trade", + "transactTimeNs": 1669407633926215067, + } + ] + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "code": 0, + "msg": "OK", + "data": { + "rows": [ + { + "action": "New", + "clOrdID": order.client_order_id, + "closedPnlRv": "0", + "closedSizeRq": "0", + "currency": self.quote_asset, + "execFeeRv": str(self.expected_fill_fee.flat_fees[0].amount), + "execID": self.expected_fill_trade_id, + "execPriceRp": str(self.expected_partial_fill_price), + "execQtyRq": str(self.expected_partial_fill_amount), + "execStatus": "MakerFill", + "execValueRv": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + "feeRateRr": "0.0001", + "orderID": order.exchange_order_id, + "orderQtyRq": str(order.amount), + "ordType": "LimitIfTouched", + "priceRp": str(order.price), + "side": order.trade_type.name.capitalize(), + "symbol": self.exchange_trading_pair, + "tradeType": "Trade", + "transactTimeNs": 1669407633926215067, + } + ] + }, + } + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_updates_funding_info(self, mock_api, mock_queue_get): + mark_regex_url = re.compile( + f"^{web_utils.public_rest_url(CONSTANTS.TICKER_PRICE_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + resp = self.funding_info_mock_response + mock_api.get(mark_regex_url, body=json.dumps(resp)) + + funding_info_event = self.funding_info_event_for_websocket_update() + + event_messages = [funding_info_event, asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + self.assertEqual(1, self.exchange._perpetual_trading.funding_info_stream.qsize()) # rest in OB DS tests + + @aioresponses() + @patch("asyncio.Queue.get") + def test_listen_for_funding_info_update_initializes_funding_info(self, mock_api, mock_queue_get): + mark_regex_url = re.compile( + f"^{web_utils.public_rest_url(CONSTANTS.TICKER_PRICE_URL)}".replace(".", r"\.").replace("?", r"\?") + ) + resp = self.funding_info_mock_response + mock_api.get(mark_regex_url, body=json.dumps(resp)) + + event_messages = [asyncio.CancelledError] + mock_queue_get.side_effect = event_messages + + try: + self.async_run_with_timeout(self.exchange._listen_for_funding_info()) + except asyncio.CancelledError: + pass + + funding_info: FundingInfo = self.exchange.get_funding_info(self.trading_pair) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(self.target_funding_info_index_price, funding_info.index_price) + self.assertEqual(self.target_funding_info_mark_price, funding_info.mark_price) + self.assertEqual(self.target_funding_info_rate, funding_info.rate) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id="5", + trading_pair="ABC-DEF", + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("12", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["12"] + + urls = self.configure_one_successful_one_erroneous_cancel_all_response( + successful_order=order1, erroneous_order=order2, mock_api=mock_api + ) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + for url in urls: + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue(self.is_logged("INFO", f"Successfully canceled order {order1.client_order_id}.")) diff --git a/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_web_utils.py b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_web_utils.py new file mode 100644 index 0000000..2d96279 --- /dev/null +++ b/test/hummingbot/connector/derivative/phemex_perpetual/test_phemex_perpetual_web_utils.py @@ -0,0 +1,84 @@ +import asyncio +import unittest +from typing import Awaitable + +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_constants as CONSTANTS +import hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_web_utils as web_utils +from hummingbot.connector.derivative.phemex_perpetual.phemex_perpetual_web_utils import PhemexPerpetualRESTPreProcessor +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class PhemexPerpetualWebUtilsUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.pre_processor = PhemexPerpetualRESTPreProcessor() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_phemex_perpetual_rest_pre_processor_non_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/x-www-form-urlencoded") + + def test_phemex_perpetual_rest_pre_processor_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/json") + + def test_rest_url_main_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.BASE_URLS[CONSTANTS.DEFAULT_DOMAIN]}{path_url}" + self.assertEqual(expected_url, web_utils.public_rest_url(path_url)) + self.assertEqual(expected_url, web_utils.public_rest_url(path_url)) + + def test_rest_url_testnet_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.BASE_URLS[CONSTANTS.TESTNET_DOMAIN]}{path_url}" + self.assertEqual( + expected_url, web_utils.public_rest_url(path_url=path_url, domain=CONSTANTS.TESTNET_DOMAIN) + ) + + def test_wss_url_main_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.WSS_URLS[CONSTANTS.DEFAULT_DOMAIN]}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint)) + + def test_wss_url_testnet_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.WSS_URLS[CONSTANTS.TESTNET_DOMAIN]}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint, domain=CONSTANTS.TESTNET_DOMAIN)) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory( + time_synchronizer=TimeSynchronizer(), + time_provider=lambda: None, + ) + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(2, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/derivative/test_perpetual_budget_checker.py b/test/hummingbot/connector/derivative/test_perpetual_budget_checker.py new file mode 100644 index 0000000..854aa16 --- /dev/null +++ b/test/hummingbot/connector/derivative/test_perpetual_budget_checker.py @@ -0,0 +1,277 @@ +import unittest +from decimal import Decimal +from test.mock.mock_perp_connector import MockPerpConnector + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.perpetual_budget_checker import PerpetualBudgetChecker +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import PerpetualOrderCandidate +from hummingbot.core.data_type.trade_fee import TradeFeeSchema + + +class PerpetualBudgetCheckerTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + + self.base_asset = "COINALPHA" + self.quote_asset = "HBOT" + self.trading_pair = f"{self.base_asset}-{self.quote_asset}" + + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.01"), taker_percent_fee_decimal=Decimal("0.02") + ) + self.exchange = MockPerpConnector( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + self.budget_checker = self.exchange.budget_checker + + def test_populate_collateral_fields_buy_order(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_fields_taker_buy_order(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_fields_buy_order_with_leverage(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + leverage=Decimal("2") + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_fields_sell_order(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_fields_sell_order_with_leverage(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + leverage=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_fields_percent_fees_in_third_token(self): + pfc_token = "PFC" + trade_fee_schema = TradeFeeSchema( + percent_fee_token=pfc_token, + maker_percent_fee_decimal=Decimal("0.01"), + taker_percent_fee_decimal=Decimal("0.01"), + ) + exchange = MockPerpConnector( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + pfc_quote_pair = combine_to_hb_trading_pair(self.quote_asset, pfc_token) + exchange.set_balanced_order_book( # the quote to pfc price will be 1:2 + trading_pair=pfc_quote_pair, + mid_price=1.5, + min_price=1, + max_price=2, + price_step_size=1, + volume_step_size=1, + ) + budget_checker: PerpetualBudgetChecker = exchange.budget_checker + + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + leverage=Decimal("2"), + ) + populated_candidate = budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), populated_candidate.order_collateral.amount) + self.assertEqual(pfc_token, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(pfc_token, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertIsNone(populated_candidate.potential_returns) # order results in position open + + def test_populate_collateral_for_position_close(self): + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + leverage=Decimal("2"), + position_close=True, + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertIsNone(populated_candidate.order_collateral) # the collateral is the contract itself + self.assertIsNone(populated_candidate.percent_fee_collateral) + self.assertIsNone(populated_candidate.percent_fee_value) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.quote_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("19.8"), populated_candidate.potential_returns.amount) + + def test_adjust_candidate_sufficient_funds(self): + self.exchange.set_balance(self.quote_asset, Decimal("100")) + + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate) + + self.assertEqual(Decimal("10"), adjusted_candidate.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), adjusted_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_value.amount) + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertIsNone(adjusted_candidate.potential_returns) # order results in position open + + def test_adjust_candidate_buy_insufficient_funds_partial_adjustment_allowed(self): + q_params = QuantizationParams( + trading_pair=self.trading_pair, + price_precision=8, + price_decimals=2, + order_size_precision=8, + order_size_decimals=2, + ) + self.exchange.set_quantization_param(q_params) + self.exchange.set_balance(self.quote_asset, Decimal("10")) + + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("4.95"), adjusted_candidate.amount) # 5 * .99 + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("9.9"), adjusted_candidate.order_collateral.amount) # 4.95 * 2 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_collateral.amount) # 9.9 * 0.01 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_value.amount) # 9.9 * 0.01 + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertIsNone(adjusted_candidate.potential_returns) # order results in position open + + def test_adjust_candidate_sell_insufficient_funds_partial_adjustment_allowed(self): + q_params = QuantizationParams( + trading_pair=self.trading_pair, + price_precision=8, + price_decimals=2, + order_size_precision=8, + order_size_decimals=2, + ) + self.exchange.set_quantization_param(q_params) + self.exchange.set_balance(self.quote_asset, Decimal("10")) + + order_candidate = PerpetualOrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("4.95"), adjusted_candidate.amount) # 5 * .99 + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("9.9"), adjusted_candidate.order_collateral.amount) # 4.95 * 2 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_collateral.amount) # 9.9 * 0.01 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_value.amount) # 9.9 * 0.01 + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertIsNone(adjusted_candidate.potential_returns) # order results in position open diff --git a/test/hummingbot/connector/exchange/__init__.py b/test/hummingbot/connector/exchange/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/ascend_ex/__init__.py b/test/hummingbot/connector/exchange/ascend_ex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_order_book_data_source.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_order_book_data_source.py new file mode 100644 index 0000000..d526149 --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_order_book_data_source.py @@ -0,0 +1,387 @@ +import asyncio +import json +import re +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS, ascend_ex_web_utils as web_utils +from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source import AscendExAPIOrderBookDataSource +from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class AscendExAPIOrderBookDataSourceUnitTests(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + "/" + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = AscendExExchange( + client_config_map=client_config_map, + ascend_ex_api_key="", + ascend_ex_secret_key="", + ascend_ex_group_id="", + trading_pairs=[], + trading_required=False, + ) + self.data_source = AscendExAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _successfully_subscribed_event(self): + resp = {} + return resp + + def _trade_update_event(self): + resp = { + "m": "trades", + "symbol": self.ex_trading_pair, + "data": [{"p": "0.068600", "q": "100.000", "ts": 12345, "bm": False, "seqnum": 12345}], + } + return resp + + def _order_diff_event(self): + resp = { + "m": "depth", + "symbol": self.ex_trading_pair, + "data": { + "ts": 1573069021376, + "seqnum": 2097965, + "asks": [["0.06844", "10760"]], + "bids": [["0.06777", "562.4"], ["0.05", "221760.6"]], + }, + } + return resp + + def _snapshot_response(self): + resp = { + "code": 0, + "data": { + "m": "depth-snapshot", + "symbol": self.ex_trading_pair, + "data": { + "seqnum": 1027024, + "ts": 1573165838976, + "bids": [["4", "431"]], + "asks": [["4.000002", "12"]], + }, + }, + } + return resp + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self._snapshot_response() + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = int(resp["data"]["data"]["ts"] / 1000) + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(4, bids[0].price) + self.assertEqual(431, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(4.000002, asks[0].price) + self.assertEqual(12, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = {} + result_subscribe_diffs = {} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_diffs) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_diff_subscription = {"op": "sub", "ch": f"depth:{self.ex_trading_pair}"} + self.assertEqual(expected_diff_subscription, sent_subscription_messages[0]) + expected_trade_subscription = {"op": "sub", "ch": f"trades:{self.ex_trading_pair}"} + self.assertEqual(expected_trade_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged("INFO", "Subscribed to public order book and trade channels...")) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...") + ) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.TRADE_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "m": "trades", + "symbol": self.ex_trading_pair, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue(self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(12.345, msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "m": "depth", + "symbol": self.ex_trading_pair, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange") + ) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self._order_diff_event() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_TOPIC_ID] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(diff_event["data"]["ts"] / 1000, msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + # url = f"{web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL)}?symbol={self.ex_trading_pair}" + url = web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue())) + + @aioresponses() + @patch( + "hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source" + ".AscendExAPIOrderBookDataSource._sleep" + ) + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + url = web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful( + self, + mock_api, + ): + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(path_url=CONSTANTS.DEPTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1573165838.976, msg.update_id) diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_user_stream_datasource.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_user_stream_datasource.py new file mode 100644 index 0000000..1567ffc --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_api_user_stream_datasource.py @@ -0,0 +1,244 @@ +import asyncio +import json +import re +from typing import Awaitable, Optional +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS, ascend_ex_web_utils as web_utils +from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_user_stream_data_source import ( + AscendExAPIUserStreamDataSource, +) +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth +from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class AscendExUserStreamTrackerTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = AscendExAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET") + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = AscendExExchange( + client_config_map=client_config_map, + ascend_ex_api_key="", + ascend_ex_secret_key="", + ascend_ex_group_id="", + trading_pairs=[], + trading_required=False, + ) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = AscendExAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_listen_key_mock(): + listen_key = {"data": {"accountGroup": 6}} + return listen_key + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, mock_api, ws_connect_mock): + url = web_utils.public_rest_url(path_url=CONSTANTS.INFO_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_listen_key_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = {} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_orders_subscription = {"op": "sub", "ch": "order:cash"} + self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) + + self.assertTrue(self._is_logged("INFO", "Subscribed to private order changes and balance updates channels...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): + url = web_utils.public_rest_url(path_url=CONSTANTS.INFO_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_listen_key_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + order_event = { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": "BTC/USDT", + "sn": 8159711, + "sd": "Buy", + "ap": "0", + "bab": "2006.5974027", + "btb": "2006.5974027", + "cf": "0", + "cfq": "0", + "err": "", + "fa": "USDT", + "orderId": "s16ef210b1a50866943712bfaf1584b", + "ot": "Market", + "p": "7967.62", + "q": "0.0083", + "qab": "793.23", + "qtb": "860.23", + "sp": "", + "st": "New", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertEqual(order_event, msg) + mock_ws.return_value.ping.assert_called() + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_ping_payload(self, mock_api, mock_ws): + url = web_utils.public_rest_url(path_url=CONSTANTS.INFO_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_listen_key_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + mock_ping = {"op": "ping"} + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_ping)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.ascend_ex.ascend_ex_api_user_stream_data_source.AscendExAPIUserStreamDataSource" + "._sleep" + ) + def test_listen_for_user_stream_connection_failed(self, mock_api, sleep_mock, mock_ws): + url = web_utils.public_rest_url(path_url=CONSTANTS.INFO_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_listen_key_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + mock_ws.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.ascend_ex.ascend_ex_api_user_stream_data_source.AscendExAPIUserStreamDataSource" + "._sleep" + ) + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, sleep_mock, mock_ws): + url = web_utils.public_rest_url(path_url=CONSTANTS.INFO_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self.get_listen_key_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_auth.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_auth.py new file mode 100644 index 0000000..3d28788 --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_auth.py @@ -0,0 +1,37 @@ +import hashlib +import hmac +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth + + +class AscendExAuthTests(TestCase): + @property + def api_key(self): + return "test_api_key" + + @property + def secret_key(self): + return "test_secret_key" + + @patch("hummingbot.connector.exchange.ascend_ex.ascend_ex_auth.AscendExAuth._time") + def test_authentication_headers(self, time_mock): + time_mock.return_value = 1640001112.223 + + timestamp = "1640001112223" + path_url = "test.com" + + auth = AscendExAuth(api_key=self.api_key, secret_key=self.secret_key) + + headers = auth.get_auth_headers(path_url=path_url) + + message = timestamp + path_url + expected_signature = hmac.new( + self.secret_key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + self.assertEqual(3, len(headers)) + self.assertEqual(timestamp, headers.get("x-auth-timestamp")) + self.assertEqual(self.api_key, headers.get("x-auth-key")) + self.assertEqual(expected_signature, headers.get("x-auth-signature")) diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_exchange.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_exchange.py new file mode 100644 index 0000000..f608ec1 --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_exchange.py @@ -0,0 +1,1117 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS, ascend_ex_web_utils as web_utils +from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, TradeUpdate +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import BuyOrderCompletedEvent, MarketOrderFailureEvent, OrderFilledEvent + + +class AscendExExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.PRODUCTS_PATH_URL) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PATH_URL) + url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.SERVER_LIMIT_INFO) + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(CONSTANTS.PRODUCTS_PATH_URL) + return url + + @property + def order_creation_url(self): + url = self.private_rest_url(CONSTANTS.ORDER_PATH_URL) + return url + + @property + def balance_url(self): + url = self.private_rest_url(CONSTANTS.BALANCE_PATH_URL) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "code": 0, + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "displayName": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "domain": "USDS", + "tradingStartTime": 1546300800000, + "collapseDecimals": "1,0.1,0.01", + "minQty": "0.000000001", + "maxQty": "1000000000", + "minNotional": "5", + "maxNotional": "400000", + "statusCode": "Normal", + "statusMessage": "", + "tickSize": "0.01", + "useTick": False, + "lotSize": "0.00001", + "useLot": False, + "commissionType": "Quote", + "commissionReserveRate": "0.001", + "qtyScale": 5, + "priceScale": 2, + "notionalScale": 4, + }, + ], + } + self.exchange._trading_rules[self.trading_pair].min_order_size = Decimal(str(0.01)) + + @property + def latest_prices_request_mock_response(self): + return { + "code": 0, + "data": { + "symbol": "ASD/USDT", + "open": "0.06777", + "close": "0.06809", + "high": "0.06899", + "low": "0.06708", + "volume": "19823722", + "ask": ["0.0681", "43641"], + "bid": ["0.0676", "443"], + }, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "code": 0, + "data": [ + { + "symbol": "INVALID/PAIR", + "displayName": "INVALID/PAIR", + "domain": "USDS", + "tradingStartTime": 1546300800000, + "collapseDecimals": "1,0.1,0.01", + "minQty": "0.000000001", + "maxQty": "1000000000", + "minNotional": "5", + "maxNotional": "400000", + "statusCode": "Normal", + "statusMessage": "", + "tickSize": "0.01", + "useTick": False, + "lotSize": "0.00001", + "useLot": False, + "commissionType": "Quote", + "commissionReserveRate": "0.001", + "qtyScale": 5, + "priceScale": 2, + "notionalScale": 4, + }, + ], + } + + return response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "code": 0, + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "displayName": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "domain": "USDS", + "tradingStartTime": 1546300800000, + "collapseDecimals": "1,0.1,0.01", + "minQty": "0.000000001", + "maxQty": "1000000000", + "minNotional": "5", + "maxNotional": "400000", + "statusCode": "Normal", + "statusMessage": "", + "tickSize": "0.01", + "useTick": False, + "lotSize": "0.00001", + "useLot": False, + "commissionType": "Quote", + "commissionReserveRate": "0.001", + "qtyScale": 5, + "priceScale": 2, + "notionalScale": 4, + }, + ], + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "code": 0, + "data": [ + { + "symbol": "A/B", + "displayName": None, + "domain": "USDS", + "tradingStartTime": 1546300800000, + "collapseDecimals": "1,0.1,0.01", + "minQty": "0.000000001", + "maxQty": "1000000000", + "minNotional": "5", + "maxNotional": "400000", + "statusCode": "Normal", + "statusMessage": "Normal", + "tickSize": "0.01", + "useTick": False, + "lotSize": "0.00001", + "useLot": False, + "commissionType": "Quote", + "commissionReserveRate": "0.001", + "qtyScale": 5, + "priceScale": 2, + "notionalScale": 4, + }, + ], + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "code": 0, + "data": { + "ac": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "action": "place-order", + "info": { + "id": "11", + "orderId": self.expected_exchange_order_id, + "orderType": "Market", + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "timestamp": 1573576916201, + }, + "status": "Ack", + }, + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "code": 0, + "data": [ + {"asset": self.quote_asset, "totalBalance": "2000", "availableBalance": "2000"}, + {"asset": self.base_asset, "totalBalance": "15", "availableBalance": "10"}, + {"asset": "ETH", "totalBalance": "0.6", "availableBalance": "0.6"}, + ], + } + + @property + def balance_request_mock_response_only_base(self): + return { + "code": 0, + "data": [ + {"asset": self.base_asset, "totalBalance": "15", "availableBalance": "10"}, + ], + } + + @property + def balance_event_websocket_update(self): + # AscendEx sends balance update information inside the order events + return { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "sn": "30000", + "sd": "Buy", + "ap": "0", + "bab": "10", + "btb": "15", + "cf": "0", + "cfq": "0", + "err": "", + "fa": self.quote_asset, + "orderId": "testId1", + "ot": "Limit", + "p": "7967.62", + "q": "0.0083", + "qab": "0.0", + "qtb": "0.0", + "sp": "", + "st": "New", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + + @property + def expected_latest_price(self): + return 0.06809 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["minQty"]), + max_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["maxQty"]), + min_price_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["tickSize"]), + min_base_amount_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["lotSize"]), + min_notional_size=Decimal(self.trading_rules_request_mock_response["data"][0]["minNotional"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 21 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10000) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.1") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + return self.expected_fill_fee + + @property + def expected_fill_trade_id(self) -> str: + return str(30000) + + @staticmethod + def private_rest_url(endpoint: str): + return web_utils.private_rest_url(path_url=endpoint).format(group_id="6") + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}/{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return AscendExExchange( + client_config_map=client_config_map, + ascend_ex_api_key="testAPIKey", + ascend_ex_secret_key="testSecret", + ascend_ex_group_id="6", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument( + request_call_tuple=request_call, params=request_call.kwargs["headers"] + ) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(OrderType.LIMIT.name.lower(), request_data["orderType"]) + self.assertEqual(Decimal("100"), Decimal(request_data["orderQty"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["orderPrice"])) + self.assertEqual(order.client_order_id, request_data["id"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertIn("sn", request_params) + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = self.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = self.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.delete(regex_url, status=400, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=21" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=21" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url(path_url="") + url = url.replace("/v1/", f"/{CONSTANTS.BALANCE_HISTORY_PATH_URL}") + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=21" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + # hist_url = f"{self.private_rest_url(CONSTANTS.HIST_PATH_URL)}?symbol=COINALPHA/HBOT" + # hist_regex_url = re.compile(f"^{hist_url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + # mock_api.get(hist_regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=21" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + # hist_url = f"{self.private_rest_url(CONSTANTS.HIST_PATH_URL)}?symbol=COINALPHA/HBOT" + # hist_regex_url = re.compile(f"^{hist_url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback) + # mock_api.get(hist_regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=21" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + # hist_url = f"{self.private_rest_url(CONSTANTS.HIST_PATH_URL)}?symbol=COINALPHA/HBOT" + # hist_regex_url = re.compile(f"^{hist_url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + # mock_api.get(hist_regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url(path_url="") + url = url.replace("/v1/", f"/{CONSTANTS.BALANCE_HISTORY_PATH_URL}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url(path_url="") + url = url.replace("/v1/", f"/{CONSTANTS.BALANCE_HISTORY_PATH_URL}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "sn": "30000", + "sd": order.trade_type.name.capitalize(), + "ap": "0", + "bab": "2006.5974027", + "btb": "2006.5974027", + "cf": "0", + "cfq": "0", + "err": "", + "fa": self.quote_asset, + "orderId": order.exchange_order_id, + "ot": order.order_type.name.capitalize(), + "p": str(order.price), + "q": str(order.amount), + "qab": "793.23", + "qtb": "860.23", + "sp": "", + "st": "New", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "sn": "30000", + "sd": order.trade_type.name.capitalize(), + "ap": "0", + "bab": "2006.5974027", + "btb": "2006.5974027", + "cf": "0", + "cfq": "0", + "err": "", + "fa": self.quote_asset, + "orderId": order.exchange_order_id, + "ot": order.order_type.name.capitalize(), + "p": str(order.price), + "q": str(order.amount), + "qab": "793.23", + "qtb": "860.23", + "sp": "", + "st": "Canceled", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "sn": "30000", + "sd": order.trade_type.name.capitalize(), + "ap": str(order.price), + "bab": "2006.5974027", + "btb": "2006.5974027", + "cf": str(self.expected_fill_fee.flat_fees[0].amount), + "cfq": str(order.amount), + "err": "", + "fa": self.expected_fill_fee.flat_fees[0].token, + "orderId": order.exchange_order_id, + "ot": order.order_type.name.capitalize(), + "p": str(order.price), + "q": str(order.amount), + "qab": "793.23", + "qtb": "860.23", + "sp": "", + "st": "Filled", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + + def order_event_for_partially_filled_websocket_update(self, order: InFlightOrder): + return { + "m": "order", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "ac": "CASH", + "data": { + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "sn": "30000", + "sd": order.trade_type.name.capitalize(), + "ap": str(self.expected_partial_fill_price), + "bab": "2006.5974027", + "btb": "2006.5974027", + "cf": str(self.expected_partial_fill_fee.flat_fees[0].amount), + "cfq": str(self.expected_partial_fill_amount), + "err": "", + "fa": self.expected_partial_fill_fee.flat_fees[0].token, + "orderId": order.exchange_order_id, + "ot": order.order_type.name.capitalize(), + "p": str(order.price), + "q": str(order.amount), + "qab": "793.23", + "qtb": "860.23", + "sp": "", + "st": "PartiallyFilled", + "t": 1576019215402, + "ei": "NULL_VAL", + }, + } + + def order_event_for_partially_canceled_websocket_update(self, order: InFlightOrder): + return self.order_event_for_canceled_order_websocket_update(order=order) + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return None + + def trade_event_for_partial_fill_websocket_update(self, order: InFlightOrder): + return None + + @aioresponses() + def test_update_order_status_when_canceled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = ( + self.exchange.current_timestamp - self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1 + ) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["11"] + + url = f"{self.private_rest_url(CONSTANTS.ORDER_STATUS_PATH_URL)}?orderId=100234" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + hist_url = f"{self.private_rest_url(CONSTANTS.HIST_PATH_URL)}?symbol=COINALPHA/HBOT" + hist_regex_url = re.compile(f"^{hist_url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "8130.24", + "orderQty": "0.00082", + "orderType": "Limit", + "avgPx": "7391.13", + "cumFee": "0.005151618", + "cumFilledQty": "0.00082", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 2622058, + "side": "Buy", + "status": "Canceled", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + } + + history_status = { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "8130.24", + "orderQty": "0.00082", + "orderType": "Limit", + "avgPx": "7391.13", + "cumFee": "0.005151618", + "cumFilledQty": "0.00082", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 2622058, + "side": "Buy", + "status": "Canceled", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + ], + } + + mock_api.get(regex_url, body=json.dumps(order_status)) + mock_api.get(hist_regex_url, body=json.dumps(history_status)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + + canceled_event: MarketOrderFailureEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, canceled_event.timestamp) + self.assertEqual(order.client_order_id, canceled_event.order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + + @aioresponses() + def test_user_stream_update_for_order_full_fill_when_it_had_a_partial_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + previous_fill_fee = AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("10"))] + ) + + trade_update = TradeUpdate( + trade_id="98765", + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fee=previous_fill_fee, + fill_base_amount=self.expected_partial_fill_amount, + fill_quote_amount=self.expected_partial_fill_amount * self.expected_partial_fill_price, + fill_price=self.expected_partial_fill_price, + fill_timestamp=1640001112.223, + ) + order.update_with_trade_update(trade_update=trade_update) + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [] + if order_event: + event_messages.append(order_event) + event_messages.append(asyncio.CancelledError) + mock_queue.get.side_effect = event_messages + self.exchange._user_stream_tracker._user_stream = mock_queue + + if self.is_order_fill_http_update_executed_during_websocket_order_event_processing: + self.configure_full_fill_trade_response(order=order, mock_api=mock_api) + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount + self.expected_partial_fill_amount) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue(self.is_logged("INFO", f"BUY order {order.client_order_id} completely filled.")) + + @aioresponses() + def test_create_order_fails_with_error_response_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = self.order_creation_url + creation_response = { + "code": 300011, + "ac": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "action": "place-order", + "info": {"id": "JkpnjJRuBtFpW7F7PWDB7uwBEJtUOISZ", "symbol": self.exchange_trading_pair}, + "message": "Not Enough Account Balance", + "reason": "INVALID_BALANCE", + "status": "Err", + } + mock_api.post( + url, body=json.dumps(creation_response), callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + order_to_validate_request = InFlightOrder( + client_order_id=order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("100"), + creation_timestamp=self.exchange.current_timestamp, + price=Decimal("10000"), + ) + self.validate_order_creation_request(order=order_to_validate_request, request_call=order_request) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "NETWORK", + f"Error submitting {order_to_validate_request.trade_type.name.lower()} " + f"{order_to_validate_request.order_type.name} order to Ascend_ex for 100.000000 {self.trading_pair} " + f"10000.0000.", + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)", + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _validate_auth_credentials_taking_parameters_from_argument( + self, request_call_tuple: RequestCall, params: Dict[str, Any] + ): + self.assertIn("x-auth-timestamp", params) + self.assertIn("x-auth-signature", params) + request_headers = request_call_tuple.kwargs["headers"] + self.assertIn("x-auth-key", request_headers) + self.assertEqual("testAPIKey", request_headers["x-auth-key"]) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return {"code": 0} + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "10000", + "orderQty": "1", + "orderType": "Limit", + "avgPx": "10000", + "cumFee": "0.1", + "cumFilledQty": "0", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 2622058, + "side": "Buy", + "status": "Filled", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + } + + def _order_trade_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + base_amount = str(order.amount) if order.trade_type == TradeType.BUY else str(-1 * order.amount) + quote_amount = ( + str(order.amount * order.price) + if order.trade_type == TradeType.SELL + else str(-1 * order.amount * order.price) + ) + return { + "meta": {"ac": "cash", "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo"}, + "order": [ + { + "data": [ + { + "asset": order.base_asset, + "curBalance": base_amount, + "dataType": "trade", + "deltaQty": base_amount, + }, + { + "asset": order.quote_asset, + "curBalance": quote_amount, + "dataType": "trade", + "deltaQty": quote_amount, + }, + { + "asset": self.expected_fill_fee.flat_fees[0].token, + "curBalance": str(self.expected_fill_fee.flat_fees[0].amount * -1), + "dataType": "fee", + "deltaQty": str(self.expected_fill_fee.flat_fees[0].amount * -1), + }, + ], + "liquidityInd": "RemovedLiquidity", + "orderId": order.exchange_order_id, + "orderType": "Limit", + "side": order.trade_type.name.capitalize(), + "sn": int(self.expected_fill_trade_id), + "transactTime": 1616852892564, + } + ], + "balance": [], + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "8130.24", + "orderQty": "1", + "orderType": "Limit", + "avgPx": "7391.13", + "cumFee": "0.005151618", + "cumFilledQty": "1", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 2622058, + "side": "Buy", + "status": "Canceled", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "8130.24", + "orderQty": "1", + "orderType": "Limit", + "avgPx": "7391.13", + "cumFee": "0.005151618", + "cumFilledQty": "1", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 2622058, + "side": "Buy", + "status": "New", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 0, + "accountCategory": "CASH", + "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "price": "10000", + "orderQty": "10", + "orderType": "Limit", + "avgPx": "7391.13", + "cumFee": "3", + "cumFilledQty": "10", + "errorCode": "", + "feeAsset": self.quote_asset, + "lastExecTime": 1575953134011, + "orderId": order.exchange_order_id, + "seqNum": 30000, + "side": "Buy", + "status": "PartiallyFilled", + "stopPrice": "", + "execInst": "NULL_VAL", + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + base_amount = ( + str(self.expected_partial_fill_amount) + if order.trade_type == TradeType.BUY + else str(-1 * self.expected_partial_fill_amount) + ) + quote_amount = ( + str(self.expected_partial_fill_amount * self.expected_partial_fill_price) + if order.trade_type == TradeType.SELL + else str(-1 * self.expected_partial_fill_amount * self.expected_partial_fill_price) + ) + return { + "meta": {"ac": "cash", "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo"}, + "order": [ + { + "data": [ + { + "asset": order.base_asset, + "curBalance": base_amount, + "dataType": "trade", + "deltaQty": base_amount, + }, + { + "asset": order.quote_asset, + "curBalance": quote_amount, + "dataType": "trade", + "deltaQty": quote_amount, + }, + { + "asset": self.expected_partial_fill_fee.flat_fees[0].token, + "curBalance": str(self.expected_partial_fill_fee.flat_fees[0].amount * -1), + "dataType": "fee", + "deltaQty": str(self.expected_partial_fill_fee.flat_fees[0].amount * -1), + }, + ], + "liquidityInd": "RemovedLiquidity", + "orderId": order.exchange_order_id, + "orderType": "Limit", + "side": order.trade_type.name.capitalize(), + "sn": int(self.expected_fill_trade_id), + "transactTime": 1616852892564, + } + ], + "balance": [], + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return self._order_trade_request_completely_filled_mock_response(order) + + def place_buy_market_order(self, amount: Decimal = Decimal("100"), price: Decimal = Decimal("10_000")): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=amount, + order_type=OrderType.MARKET, + price=price, + ) + return order_id + + @aioresponses() + @patch("hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange.AscendExExchange.get_price") + def test_create_buy_market_order_successfully(self, mock_api, get_price_mock): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + get_price_mock.return_value = Decimal("10_000") + url = self.order_creation_url + + creation_response = self.order_creation_request_successful_mock_response + + mock_api.post(url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + order_id = self.place_buy_market_order() + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertIn(order_id, self.exchange.in_flight_orders) + request_data = json.loads(order_request.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(self.exchange.in_flight_orders[order_id].trade_type.name.lower(), request_data["side"]) + self.assertEqual(OrderType.MARKET.name.lower(), request_data["orderType"]) + self.assertEqual(Decimal("100"), Decimal(request_data["orderQty"])) + self.assertEqual("IOC", request_data["timeInForce"]) + self.assertEqual(self.exchange.in_flight_orders[order_id].client_order_id, request_data["id"]) + create_event = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.MARKET.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} {self.trading_pair}." + ) + ) diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_utils.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_utils.py new file mode 100644 index 0000000..87de26b --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_utils.py @@ -0,0 +1,39 @@ +import unittest + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_utils as utils + + +class AscendExUtilTestCases(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hb_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def test_is_pair_information_valid(self): + invalid_info_1 = { + "statusCode": None, + } + + self.assertFalse(utils.is_pair_information_valid(invalid_info_1)) + + invalid_info_2 = { + "statusCode": "", + } + + self.assertFalse(utils.is_pair_information_valid(invalid_info_2)) + + invalid_info_3 = { + "statusCode": "Err", + } + + self.assertFalse(utils.is_pair_information_valid(invalid_info_3)) + + invalid_info_4 = { + "statusCode": "Normal", + } + + self.assertTrue(utils.is_pair_information_valid(invalid_info_4)) diff --git a/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_web_utils.py b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_web_utils.py new file mode 100644 index 0000000..ba3d4c1 --- /dev/null +++ b/test/hummingbot/connector/exchange/ascend_ex/test_ascend_ex_web_utils.py @@ -0,0 +1,15 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS, ascend_ex_web_utils as web_utils + + +class AscendExWebUtilsTests(TestCase): + def test_public_rest_url(self): + path_url = "/TEST_PATH" + expected_url = CONSTANTS.PUBLIC_REST_URL + path_url + self.assertEqual(expected_url, web_utils.public_rest_url(path_url)) + + def test_rest_api_url_private(self): + path_url = "/TEST_PATH" + expected_url = CONSTANTS.PRIVATE_REST_URL + path_url + self.assertEqual(expected_url, web_utils.private_rest_url(path_url)) diff --git a/test/hummingbot/connector/exchange/binance/__init__.py b/test/hummingbot/connector/exchange/binance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/binance/test_binance_api_order_book_data_source.py b/test/hummingbot/connector/exchange/binance/test_binance_api_order_book_data_source.py new file mode 100644 index 0000000..9b8866a --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_api_order_book_data_source.py @@ -0,0 +1,411 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.exchange.binance.binance_api_order_book_data_source import BinanceAPIOrderBookDataSource +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class BinanceAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BinanceExchange( + client_config_map=client_config_map, + binance_api_key="", + binance_api_secret="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.data_source = BinanceAPIOrderBookDataSource(trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + def _trade_update_event(self): + resp = { + "e": "trade", + "E": 123456789, + "s": self.ex_trading_pair, + "t": 12345, + "p": "0.001", + "q": "100", + "b": 88, + "a": 50, + "T": 123456785, + "m": True, + "M": True + } + return resp + + def _order_diff_event(self): + resp = { + "e": "depthUpdate", + "E": 123456789, + "s": self.ex_trading_pair, + "U": 157, + "u": 160, + "b": [["0.0024", "10"]], + "a": [["0.0026", "100"]] + } + return resp + + def _snapshot_response(self): + resp = { + "lastUpdateId": 1027024, + "bids": [ + [ + "4.00000000", + "431.00000000" + ] + ], + "asks": [ + [ + "4.00000200", + "12.00000000" + ] + ] + } + return resp + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self._snapshot_response() + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = resp["lastUpdateId"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(4, bids[0].price) + self.assertEqual(431, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(4.000002, asks[0].price) + self.assertEqual(12, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "result": None, + "id": 1 + } + result_subscribe_diffs = { + "result": None, + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "method": "SUBSCRIBE", + "params": [f"{self.ex_trading_pair.lower()}@trade"], + "id": 1} + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "method": "SUBSCRIBE", + "params": [f"{self.ex_trading_pair.lower()}@depth@100ms"], + "id": 2} + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(12345, msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self._order_diff_event() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(diff_event["u"], msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError, repeat=True) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.binance.binance_api_order_book_data_source" + ".BinanceAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception, repeat=True) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api, ): + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1027024, msg.update_id) diff --git a/test/hummingbot/connector/exchange/binance/test_binance_auth.py b/test/hummingbot/connector/exchange/binance/test_binance_auth.py new file mode 100644 index 0000000..45d9da3 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_auth.py @@ -0,0 +1,51 @@ +import asyncio +import hashlib +import hmac +from copy import copy +from unittest import TestCase +from unittest.mock import MagicMock + +from typing_extensions import Awaitable + +from hummingbot.connector.exchange.binance.binance_auth import BinanceAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + + +class BinanceAuthTests(TestCase): + + def setUp(self) -> None: + self._api_key = "testApiKey" + self._secret = "testSecret" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_authenticate(self): + now = 1234567890.000 + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + + params = { + "symbol": "LTCBTC", + "side": "BUY", + "type": "LIMIT", + "timeInForce": "GTC", + "quantity": 1, + "price": "0.1", + } + full_params = copy(params) + + auth = BinanceAuth(api_key=self._api_key, secret_key=self._secret, time_provider=mock_time_provider) + request = RESTRequest(method=RESTMethod.GET, params=params, is_auth_required=True) + configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) + + full_params.update({"timestamp": 1234567890000}) + encoded_params = "&".join([f"{key}={value}" for key, value in full_params.items()]) + expected_signature = hmac.new( + self._secret.encode("utf-8"), + encoded_params.encode("utf-8"), + hashlib.sha256).hexdigest() + self.assertEqual(now * 1e3, configured_request.params["timestamp"]) + self.assertEqual(expected_signature, configured_request.params["signature"]) + self.assertEqual({"X-MBX-APIKEY": self._api_key}, configured_request.headers) diff --git a/test/hummingbot/connector/exchange/binance/test_binance_exchange.py b/test/hummingbot/connector/exchange/binance/test_binance_exchange.py new file mode 100644 index 0000000..37fd9b9 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_exchange.py @@ -0,0 +1,1380 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import MarketOrderFailureEvent, OrderFilledEvent + + +class BinanceExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=self.exchange._domain) + url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" + return url + + @property + def network_status_url(self): + url = web_utils.private_rest_url(CONSTANTS.PING_PATH_URL, domain=self.exchange._domain) + return url + + @property + def trading_rules_url(self): + url = web_utils.private_rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL, domain=self.exchange._domain) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL, domain=self.exchange._domain) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "TRADING", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "SPOT", + "MARGIN" + ] + }, + ] + } + + @property + def latest_prices_request_mock_response(self): + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "priceChange": "-94.99999800", + "priceChangePercent": "-95.960", + "weightedAvgPrice": "0.29628482", + "prevClosePrice": "0.10002000", + "lastPrice": str(self.expected_latest_price), + "lastQty": "200.00000000", + "bidPrice": "4.00000000", + "bidQty": "100.00000000", + "askPrice": "4.00000200", + "askQty": "100.00000000", + "openPrice": "99.00000000", + "highPrice": "100.00000000", + "lowPrice": "0.10000000", + "volume": "8913.30000000", + "quoteVolume": "15.30000000", + "openTime": 1499783499040, + "closeTime": 1499869899040, + "firstId": 28385, + "lastId": 28460, + "count": 76, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "TRADING", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + { + "symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "status": "TRADING", + "baseAsset": "INVALID", + "baseAssetPrecision": 8, + "quoteAsset": "PAIR", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + ] + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1565246363776, + "rateLimits": [{}], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "TRADING", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "orderTypes": ["LIMIT", "LIMIT_MAKER"], + "icebergAllowed": True, + "ocoAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "200000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000" + } + ], + "permissions": [ + "SPOT", + "MARGIN" + ] + } + ] + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1565246363776, + "rateLimits": [{}], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "TRADING", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "orderTypes": ["LIMIT", "LIMIT_MAKER"], + "icebergAllowed": True, + "ocoAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "permissions": [ + "SPOT", + "MARGIN" + ] + } + ] + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": self.expected_exchange_order_id, + "orderListId": -1, + "clientOrderId": "OID1", + "transactTime": 1507725176595 + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "makerCommission": 15, + "takerCommission": 15, + "buyerCommission": 0, + "sellerCommission": 0, + "canTrade": True, + "canWithdraw": True, + "canDeposit": True, + "updateTime": 123456789, + "accountType": "SPOT", + "balances": [ + { + "asset": self.base_asset, + "free": "10.0", + "locked": "5.0" + }, + { + "asset": self.quote_asset, + "free": "2000", + "locked": "0.00000000" + } + ], + "permissions": [ + "SPOT" + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "makerCommission": 15, + "takerCommission": 15, + "buyerCommission": 0, + "sellerCommission": 0, + "canTrade": True, + "canWithdraw": True, + "canDeposit": True, + "updateTime": 123456789, + "accountType": "SPOT", + "balances": [{"asset": self.base_asset, "free": "10.0", "locked": "5.0"}], + "permissions": ["SPOT"], + } + + @property + def balance_event_websocket_update(self): + return { + "e": "outboundAccountPosition", + "E": 1564034571105, + "u": 1564034571073, + "B": [{"a": self.base_asset, "f": "10", "l": "5"}], + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["symbols"][0]["filters"][1]["minQty"]), + min_price_increment=Decimal( + self.trading_rules_request_mock_response["symbols"][0]["filters"][0]["tickSize"]), + min_base_amount_increment=Decimal( + self.trading_rules_request_mock_response["symbols"][0]["filters"][1]["stepSize"]), + min_notional_size=Decimal( + self.trading_rules_request_mock_response["symbols"][0]["filters"][2]["minNotional"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["symbols"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 28 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return str(30000) + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return BinanceExchange( + client_config_map=client_config_map, + binance_api_key="testAPIKey", + binance_api_secret="testSecret", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument( + request_call_tuple=request_call, + params=request_call.kwargs["params"] or request_call.kwargs["data"] + ) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(order.trade_type.name.upper(), request_data["side"]) + self.assertEqual(BinanceExchange.binance_order_type(OrderType.LIMIT), request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["quantity"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["newClientOrderId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["params"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["symbol"]) + self.assertEqual(order.client_order_id, request_data["origClientOrderId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.exchange_order_id, str(request_params["orderId"])) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.delete(regex_url, status=400, callback=callback) + return url + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2011, "msg": "Unknown order sent."} + mock_api.delete(regex_url, status=400, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2013, "msg": "Order does not exist."} + mock_api.get(regex_url, body=json.dumps(response), status=400, callback=callback) + return [url] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "e": "executionReport", + "E": 1499405658658, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "c": order.client_order_id, + "S": order.trade_type.name.upper(), + "o": order.order_type.name.upper(), + "f": "GTC", + "q": str(order.amount), + "p": str(order.price), + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "NEW", + "X": "NEW", + "r": "NONE", + "i": order.exchange_order_id, + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": None, + "T": 1499405658657, + "t": -1, + "I": 8641984, + "w": True, + "m": False, + "M": False, + "O": 1499405658657, + "Z": "0.00000000", + "Y": "0.00000000", + "Q": "0.00000000" + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "e": "executionReport", + "E": 1499405658658, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "c": "dummyText", + "S": order.trade_type.name.upper(), + "o": order.order_type.name.upper(), + "f": "GTC", + "q": str(order.amount), + "p": str(order.price), + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": order.client_order_id, + "x": "CANCELED", + "X": "CANCELED", + "r": "NONE", + "i": order.exchange_order_id, + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": None, + "T": 1499405658657, + "t": -1, + "I": 8641984, + "w": True, + "m": False, + "M": False, + "O": 1499405658657, + "Z": "0.00000000", + "Y": "0.00000000", + "Q": "0.00000000" + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "e": "executionReport", + "E": 1499405658658, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "c": order.client_order_id, + "S": order.trade_type.name.upper(), + "o": order.order_type.name.upper(), + "f": "GTC", + "q": str(order.amount), + "p": str(order.price), + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "TRADE", + "X": "FILLED", + "r": "NONE", + "i": order.exchange_order_id, + "l": str(order.amount), + "z": str(order.amount), + "L": str(order.price), + "n": str(self.expected_fill_fee.flat_fees[0].amount), + "N": self.expected_fill_fee.flat_fees[0].token, + "T": 1499405658657, + "t": 1, + "I": 8641984, + "w": True, + "m": False, + "M": False, + "O": 1499405658657, + "Z": "10050.00000000", + "Y": "10050.00000000", + "Q": "10000.00000000" + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return None + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + request_sent_event = asyncio.Event() + seconds_counter_mock.side_effect = [0, 0, 0] + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"serverTime": 1640000003000} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertEqual(response["serverTime"] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + request_sent_event = asyncio.Event() + + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"code": -1121, "msg": "Dummy error"} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertTrue(self.is_logged("NETWORK", "Error getting server time.")) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, + exception=asyncio.CancelledError) + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, self.exchange._update_time_synchronizer()) + + @aioresponses() + def test_update_order_fills_from_trades_triggers_filled_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 28457, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": "9999", + "qty": "1", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": self.quote_asset, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + trade_fill_non_tracked_order = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 30000, + "orderId": 99999, + "orderListId": -1, + "price": "4.00000100", + "qty": "12.00000000", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": "BNB", + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + mock_response = [trade_fill, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(trade_fill["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([TokenAmount(trade_fill["commissionAsset"], Decimal(trade_fill["commission"]))], + fill_event.trade_fee.flat_fees) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) + self.assertEqual("OID99", fill_event.order_id) + self.assertEqual(self.trading_pair, fill_event.trading_pair) + self.assertEqual(TradeType.BUY, fill_event.trade_type) + self.assertEqual(OrderType.LIMIT, fill_event.order_type) + self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount( + trade_fill_non_tracked_order["commissionAsset"], + Decimal(trade_fill_non_tracked_order["commission"]))], + fill_event.trade_fee.flat_fees) + self.assertTrue(self.is_logged( + "INFO", + f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" + )) + + @aioresponses() + def test_update_order_fills_request_parameters(self, mock_api): + self.exchange._set_current_timestamp(0) + self.exchange._last_poll_timestamp = -1 + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = [] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertNotIn("startTime", request_params) + + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + self.exchange._last_trades_poll_binance_timestamp = 10 + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[1] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertEqual(10 * 1e3, request_params["startTime"]) + + @aioresponses() + def test_update_order_fills_from_trades_with_repeated_fill_triggers_only_one_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill_non_tracked_order = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 30000, + "orderId": 99999, + "orderListId": -1, + "price": "4.00000100", + "qty": "12.00000000", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": "BNB", + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + mock_response = [trade_fill_non_tracked_order, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) + self.assertEqual("OID99", fill_event.order_id) + self.assertEqual(self.trading_pair, fill_event.trading_pair) + self.assertEqual(TradeType.BUY, fill_event.trade_type) + self.assertEqual(OrderType.LIMIT, fill_event.order_type) + self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount(trade_fill_non_tracked_order["commissionAsset"], + Decimal(trade_fill_non_tracked_order["commission"]))], + fill_event.trade_fee.flat_fees) + self.assertTrue(self.is_logged( + "INFO", + f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" + )) + + @aioresponses() + def test_update_order_status_when_failed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": "10000.0", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "REJECTED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": "10000.000000" + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) + + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(order.client_order_id, failure_event.order_id) + self.assertEqual(order.order_type, failure_event.order_type) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}'," + f" update_timestamp={order_status['updateTime'] * 1e-3}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order.client_order_id}', exchange_order_id='{order.exchange_order_id}', " + "misc_updates=None)") + ) + + def test_user_stream_update_for_order_failure(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "e": "executionReport", + "E": 1499405658658, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "c": order.client_order_id, + "S": "BUY", + "o": "LIMIT", + "f": "GTC", + "q": "1.00000000", + "p": "1000.00000000", + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "REJECTED", + "X": "REJECTED", + "r": "NONE", + "i": int(order.exchange_order_id), + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": None, + "T": 1499405658657, + "t": 1, + "I": 8641984, + "w": True, + "m": False, + "M": False, + "O": 1499405658657, + "Z": "0.00000000", + "Y": "0.00000000", + "Q": "0.00000000" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(order.client_order_id, failure_event.order_id) + self.assertEqual(order.order_type, failure_event.order_type) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_failure) + self.assertTrue(order.is_done) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 7 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + def test_time_synchronizer_related_request_error_detection(self): + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Timestamp for this request is outside of the recvWindow.'}") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Timestamp for this request was 1000ms ahead of the server's " + "time.'}") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1022,'msg':'Timestamp for this request was 1000ms ahead of the server's " + "time.'}") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Other error.'}") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + @aioresponses() + def test_place_order_manage_server_overloaded_error_unkown_order(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = {"code": -1003, "msg": "Unknown error, please check your request or try again later."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + o_id, transact_time = self.async_run_with_timeout(self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + self.assertEqual(o_id, "UNKNOWN") + + @aioresponses() + def test_place_order_manage_server_overloaded_error_failure(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = {"code": -1003, "msg": "Service Unavailable."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + self.assertRaises( + IOError, + self.async_run_with_timeout, + self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + + mock_response = {"code": -1003, "msg": "Internal error; unable to process your request. Please try again."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + self.assertRaises( + IOError, + self.async_run_with_timeout, + self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + + def test_format_trading_rules__min_notional_present(self): + trading_rules = [{ + "symbol": "COINALPHAHBOT", + "baseAssetPrecision": 8, + "status": "TRADING", + "quotePrecision": 8, + "orderTypes": ["LIMIT", "MARKET"], + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000" + } + ], + "permissions": [ + "SPOT" + ] + }] + exchange_info = {"symbols": trading_rules} + + result = self.async_run_with_timeout(self.exchange._format_trading_rules(exchange_info)) + + self.assertEqual(result[0].min_notional_size, Decimal("0.00100000")) + + def test_format_trading_rules__notional_but_no_min_notional_present(self): + trading_rules = [{ + "symbol": "COINALPHAHBOT", + "baseAssetPrecision": 8, + "status": "TRADING", + "quotePrecision": 8, + "orderTypes": ["LIMIT", "MARKET"], + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "NOTIONAL", + "minNotional": "10.00000000", + "applyMinToMarket": False, + "maxNotional": "10000.00000000", + "applyMaxToMarket": False, + "avgPriceMins": 5 + } + ], + "permissions": [ + "SPOT" + ] + }] + exchange_info = {"symbols": trading_rules} + + result = self.async_run_with_timeout(self.exchange._format_trading_rules(exchange_info)) + + self.assertEqual(result[0].min_notional_size, Decimal("10")) + + def _validate_auth_credentials_taking_parameters_from_argument(self, + request_call_tuple: RequestCall, + params: Dict[str, Any]): + self.assertIn("timestamp", params) + self.assertIn("signature", params) + request_headers = request_call_tuple.kwargs["headers"] + self.assertIn("X-MBX-APIKEY", request_headers) + self.assertEqual("testAPIKey", request_headers["X-MBX-APIKEY"]) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "origClientOrderId": order.exchange_order_id or "dummyOrdId", + "orderId": 4, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(Decimal("0")), + "cummulativeQuoteQty": str(Decimal("0")), + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY" + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(order.amount), + "cummulativeQuoteQty": str(order.price + Decimal(2)), + "status": "FILLED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": "0.0", + "cummulativeQuoteQty": "10000.0", + "status": "CANCELED", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": "0.0", + "cummulativeQuoteQty": "10000.0", + "status": "NEW", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(order.amount), + "cummulativeQuoteQty": str(self.expected_partial_fill_amount * order.price), + "status": "PARTIALLY_FILLED", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return [ + { + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "id": self.expected_fill_trade_id, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": str(self.expected_partial_fill_price), + "qty": str(self.expected_partial_fill_amount), + "quoteQty": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + "commission": str(self.expected_fill_fee.flat_fees[0].amount), + "commissionAsset": self.expected_fill_fee.flat_fees[0].token, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + ] + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return [ + { + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "id": self.expected_fill_trade_id, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": str(order.price), + "qty": str(order.amount), + "quoteQty": str(order.amount * order.price), + "commission": str(self.expected_fill_fee.flat_fees[0].amount), + "commissionAsset": self.expected_fill_fee.flat_fees[0].token, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + ] diff --git a/test/hummingbot/connector/exchange/binance/test_binance_order_book.py b/test/hummingbot/connector/exchange/binance/test_binance_order_book.py new file mode 100644 index 0000000..8f216c3 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_order_book.py @@ -0,0 +1,103 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.binance.binance_order_book import BinanceOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class BinanceOrderBookTests(TestCase): + + def test_snapshot_message_from_exchange(self): + snapshot_message = BinanceOrderBook.snapshot_message_from_exchange( + msg={ + "lastUpdateId": 1, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", snapshot_message.trading_pair) + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type) + self.assertEqual(1640000000.0, snapshot_message.timestamp) + self.assertEqual(1, snapshot_message.update_id) + self.assertEqual(-1, snapshot_message.trade_id) + self.assertEqual(1, len(snapshot_message.bids)) + self.assertEqual(4.0, snapshot_message.bids[0].price) + self.assertEqual(431.0, snapshot_message.bids[0].amount) + self.assertEqual(1, snapshot_message.bids[0].update_id) + self.assertEqual(1, len(snapshot_message.asks)) + self.assertEqual(4.000002, snapshot_message.asks[0].price) + self.assertEqual(12.0, snapshot_message.asks[0].amount) + self.assertEqual(1, snapshot_message.asks[0].update_id) + + def test_diff_message_from_exchange(self): + diff_msg = BinanceOrderBook.diff_message_from_exchange( + msg={ + "e": "depthUpdate", + "E": 123456789, + "s": "COINALPHAHBOT", + "U": 1, + "u": 2, + "b": [ + [ + "0.0024", + "10" + ] + ], + "a": [ + [ + "0.0026", + "100" + ] + ] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1640000000.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(1, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0.0024, diff_msg.bids[0].price) + self.assertEqual(10.0, diff_msg.bids[0].amount) + self.assertEqual(2, diff_msg.bids[0].update_id) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.0026, diff_msg.asks[0].price) + self.assertEqual(100.0, diff_msg.asks[0].amount) + self.assertEqual(2, diff_msg.asks[0].update_id) + + def test_trade_message_from_exchange(self): + trade_update = { + "e": "trade", + "E": 1234567890123, + "s": "COINALPHAHBOT", + "t": 12345, + "p": "0.001", + "q": "100", + "b": 88, + "a": 50, + "T": 123456785, + "m": True, + "M": True + } + + trade_message = BinanceOrderBook.trade_message_from_exchange( + msg=trade_update, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", trade_message.trading_pair) + self.assertEqual(OrderBookMessageType.TRADE, trade_message.type) + self.assertEqual(1234567890.123, trade_message.timestamp) + self.assertEqual(-1, trade_message.update_id) + self.assertEqual(-1, trade_message.first_update_id) + self.assertEqual(12345, trade_message.trade_id) diff --git a/test/hummingbot/connector/exchange/binance/test_binance_user_stream_data_source.py b/test/hummingbot/connector/exchange/binance/test_binance_user_stream_data_source.py new file mode 100644 index 0000000..bf22945 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_user_stream_data_source.py @@ -0,0 +1,313 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.exchange.binance.binance_api_user_stream_data_source import BinanceAPIUserStreamDataSource +from hummingbot.connector.exchange.binance.binance_auth import BinanceAuth +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class BinanceUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = BinanceAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET", time_provider=self.mock_time_provider) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BinanceExchange( + client_config_map=client_config_map, + binance_api_key="", + binance_api_secret="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = BinanceAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _error_response(self) -> Dict[str, Any]: + resp = { + "code": "ERROR CODE", + "msg": "ERROR MESSAGE" + } + + return resp + + def _user_update_event(self): + # Balance Update + resp = { + "e": "balanceUpdate", + "E": 1573200697110, + "a": "BTC", + "d": "100.00000000", + "T": 1573200697068 + } + return json.dumps(resp) + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + @aioresponses() + def test_get_listen_key_log_exception(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, status=400, body=json.dumps(self._error_response())) + + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source._get_listen_key()) + + @aioresponses() + def test_get_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + result: str = self.async_run_with_timeout(self.data_source._get_listen_key()) + + self.assertEqual(self.listen_key, result) + + @aioresponses() + def test_ping_listen_key_log_exception(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.put(regex_url, status=400, body=json.dumps(self._error_response())) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + + self.assertTrue(self._is_logged("WARNING", f"Failed to refresh the listen key {self.listen_key}: " + f"{self._error_response()}")) + self.assertFalse(result) + + @aioresponses() + def test_ping_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.put(regex_url, body=json.dumps({})) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + self.assertTrue(result) + + @patch("hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource" + "._ping_listen_key", + new_callable=AsyncMock) + def test_manage_listen_key_task_loop_keep_alive_failed(self, mock_ping_listen_key): + mock_ping_listen_key.side_effect = (lambda *args, **kwargs: + self._create_return_value_and_unlock_test_with_event(False)) + + self.data_source._current_listen_key = self.listen_key + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged("ERROR", "Error occurred renewing listen key ...")) + self.assertIsNone(self.data_source._current_listen_key) + self.assertFalse(self.data_source._listen_key_initialized_event.is_set()) + + @patch("hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource." + "_ping_listen_key", + new_callable=AsyncMock) + def test_manage_listen_key_task_loop_keep_alive_successful(self, mock_ping_listen_key): + mock_ping_listen_key.side_effect = (lambda *args, **kwargs: + self._create_return_value_and_unlock_test_with_event(True)) + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._current_listen_key = self.listen_key + self.data_source._listen_key_initialized_event.set() + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged("INFO", f"Refreshed listen key {self.listen_key}.")) + self.assertGreater(self.data_source._last_listen_key_ping_ts, 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertEqual(json.loads(self._user_update_event()), msg) + mock_ws.return_value.ping.assert_called() + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = (lambda *args, **kwargs: + self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR"))) + mock_ws.close.return_value = None + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/exchange/binance/test_binance_utils.py b/test/hummingbot/connector/exchange/binance/test_binance_utils.py new file mode 100644 index 0000000..40c9666 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_utils.py @@ -0,0 +1,44 @@ +import unittest + +from hummingbot.connector.exchange.binance import binance_utils as utils + + +class BinanceUtilTestCases(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hb_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def test_is_exchange_information_valid(self): + invalid_info_1 = { + "status": "BREAK", + "permissions": ["MARGIN"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_1)) + + invalid_info_2 = { + "status": "BREAK", + "permissions": ["SPOT"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_2)) + + invalid_info_3 = { + "status": "TRADING", + "permissions": ["MARGIN"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_3)) + + invalid_info_4 = { + "status": "TRADING", + "permissions": ["SPOT"], + } + + self.assertTrue(utils.is_exchange_information_valid(invalid_info_4)) diff --git a/test/hummingbot/connector/exchange/binance/test_binance_web_utils.py b/test/hummingbot/connector/exchange/binance/test_binance_web_utils.py new file mode 100644 index 0000000..bf7f1d1 --- /dev/null +++ b/test/hummingbot/connector/exchange/binance/test_binance_web_utils.py @@ -0,0 +1,19 @@ +import unittest + +import hummingbot.connector.exchange.binance.binance_constants as CONSTANTS +from hummingbot.connector.exchange.binance import binance_web_utils as web_utils + + +class BinanceUtilTestCases(unittest.TestCase): + + def test_public_rest_url(self): + path_url = "/TEST_PATH" + domain = "com" + expected_url = CONSTANTS.REST_URL.format(domain) + CONSTANTS.PUBLIC_API_VERSION + path_url + self.assertEqual(expected_url, web_utils.public_rest_url(path_url, domain)) + + def test_private_rest_url(self): + path_url = "/TEST_PATH" + domain = "com" + expected_url = CONSTANTS.REST_URL.format(domain) + CONSTANTS.PRIVATE_API_VERSION + path_url + self.assertEqual(expected_url, web_utils.private_rest_url(path_url, domain)) diff --git a/test/hummingbot/connector/exchange/bitfinex/__init__.py b/test/hummingbot/connector/exchange/bitfinex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py new file mode 100644 index 0000000..69ac02c --- /dev/null +++ b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_api_order_book_data_source.py @@ -0,0 +1,61 @@ +import asyncio +import json +from unittest import TestCase + +from aioresponses import aioresponses + +import hummingbot.connector.exchange.bitfinex.bitfinex_utils as utils +from hummingbot.connector.exchange.bitfinex import BITFINEX_REST_URL +from hummingbot.connector.exchange.bitfinex.bitfinex_api_order_book_data_source import BitfinexAPIOrderBookDataSource + + +class BitfinexAPIOrderBookDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + BitfinexAPIOrderBookDataSource.logger().setLevel(1) + BitfinexAPIOrderBookDataSource.logger().addHandler(self) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + @aioresponses() + def test_get_last_traded_price(self, api_mock): + response = [ + 10645, + 73.93854271, + 10647, + 75.22266119, + 731.60645389, + 0.0738, + 10644.00645389, + 14480.89849423, + 10766, + 9889.1449809] + api_mock.get(f"{BITFINEX_REST_URL}/ticker/{utils.convert_to_exchange_trading_pair('BTC-USDT')}", + body=json.dumps(response)) + last_price = asyncio.get_event_loop().run_until_complete( + BitfinexAPIOrderBookDataSource.get_last_traded_price("BTC-USDT")) + + self.assertEqual(response[6], last_price) + + @aioresponses() + def test_get_last_traded_price_returns_zero_when_an_error_happens(self, api_mock): + response = {"error": "ERR_RATE_LIMIT"} + api_mock.get(f"{BITFINEX_REST_URL}/ticker/{utils.convert_to_exchange_trading_pair('BTC-USDT')}", + body=json.dumps(response)) + last_price = asyncio.get_event_loop().run_until_complete( + BitfinexAPIOrderBookDataSource.get_last_traded_price("BTC-USDT")) + + self.assertEqual(0, last_price) + self.assertTrue(self._is_logged( + "ERROR", + f"Error encountered requesting ticker information. The response was: {response} " + f"(There was an error requesting ticker information BTC-USDT ({response}))" + )) diff --git a/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_exchange.py b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_exchange.py new file mode 100644 index 0000000..f57f4bc --- /dev/null +++ b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_exchange.py @@ -0,0 +1,161 @@ +import asyncio +from decimal import Decimal +from typing import Awaitable, Optional +from unittest import TestCase + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bitfinex.bitfinex_exchange import BitfinexExchange +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent + + +class BitfinexExchangeTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.symbol = f"{cls.base_asset}{cls.quote_asset}" + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.test_task: Optional[asyncio.Task] = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = BitfinexExchange( + client_config_map=self.client_config_map, + bitfinex_api_key="testAPIKey", + bitfinex_secret_key="testSecret", + trading_pairs=[self.trading_pair], + ) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + + self._initialize_event_loggers() + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.order_filled_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.OrderFilled, self.order_filled_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_id="OID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + order = self.exchange.in_flight_orders.get("OID1") + order.update_exchange_order_id("34938060782") + + partial_fill = [ + 0, # CHAN_ID + "tu", # TYPE + [ + 1, # ID + f"t{self.trading_pair}", # SYMBOL + 1574963975602, # MTS_CREATE + 34938060782, # ORDER_ID + 0.1, # EXEC_AMOUNT + 10053.57, # EXEC_PRICE + "LIMIT", # ORDER_TYPE + 0, # ORDER_PRICE + -1, # MAKER + 10.0, # FEE + "USDT", # FEE_CURRENCY + 0 # CID + ] + ] + + self.exchange._process_trade_event(event_message=partial_fill) + + self.assertEqual(partial_fill[2][10], order.fee_asset) + self.assertEqual(Decimal(str(partial_fill[2][9])), order.fee_paid) + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual( + [TokenAmount(partial_fill[2][10], Decimal(str(partial_fill[2][9])))], fill_event.trade_fee.flat_fees + ) + self.assertTrue(self._is_logged( + "INFO", + f"Order filled {Decimal(str(partial_fill[2][4]))} out of {order.amount} of the " + f"{order.order_type_description} order {order.client_order_id}" + )) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + complete_fill = [ + 0, # CHAN_ID + "tu", # TYPE + [ + 2, # ID + f"t{self.trading_pair}", # SYMBOL + 1574963975602, # MTS_CREATE + 34938060782, # ORDER_ID + 0.9, # EXEC_AMOUNT + 10060.0, # EXEC_PRICE + "LIMIT", # ORDER_TYPE + 0, # ORDER_PRICE + -1, # MAKER + 20.0, # FEE + "USDT", # FEE_CURRENCY + 0 # CID + ] + ] + + self.exchange._process_trade_event(event_message=complete_fill) + + self.assertEqual(complete_fill[2][10], order.fee_asset) + self.assertEqual(Decimal(30), order.fee_paid) + + self.assertEqual(2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0"), fill_event.trade_fee.percent) + self.assertEqual([TokenAmount(complete_fill[2][10], Decimal(complete_fill[2][9]))], + fill_event.trade_fee.flat_fees) + + self.assertTrue(self._is_logged( + "INFO", + f"The market {order.trade_type.name.lower()} " + f"order {order.client_order_id} has completed " + "according to Bitfinex user stream." + )) + + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) diff --git a/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_in_flight_order.py b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_in_flight_order.py new file mode 100644 index 0000000..6b60470 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitfinex/test_bitfinex_in_flight_order.py @@ -0,0 +1,180 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.exchange.bitfinex import OrderStatus +from hummingbot.connector.exchange.bitfinex.bitfinex_in_flight_order import BitfinexInFlightOrder +from hummingbot.core.data_type.common import OrderType, TradeType + + +class BitfinexInFlightOrderTests(TestCase): + + def setUp(self): + super().setUp() + self.base_token = "BTC" + self.quote_token = "USDT" + self.trading_pair = f"{self.base_token}-{self.quote_token}" + + def test_update_with_partial_trade_event(self): + order = BitfinexInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0, + ) + + trade_event_info = { + "order_id": "EOID1", + "trade_id": 1, + "amount": 0.1, + "price": 10050.0, + "fee": 10.0, + "fee_currency": "USDC", + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertTrue(order.is_open) + self.assertEqual(OrderStatus.PARTIALLY, order.last_state) + self.assertEqual(Decimal(str(trade_event_info["amount"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["amount"])) * Decimal(str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["fee"]), order.fee_paid) + self.assertEqual(trade_event_info["fee_currency"], order.fee_asset) + + def test_update_with_full_fill_trade_event(self): + order = BitfinexInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "order_id": "EOID1", + "trade_id": 1, + "amount": 0.1, + "price": 10050.0, + "fee": 10.0, + "fee_currency": "USDC", + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertTrue(order.is_open) + self.assertEqual(OrderStatus.PARTIALLY, order.last_state) + self.assertEqual(Decimal(str(trade_event_info["amount"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["amount"])) * Decimal(str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["fee"]), order.fee_paid) + self.assertEqual(trade_event_info["fee_currency"], order.fee_asset) + + complete_event_info = { + "order_id": "EOID1", + "trade_id": 2, + "amount": 0.9, + "price": 10060.0, + "fee": 50.0, + "fee_currency": "USDC", + } + + update_result = order.update_with_trade_update(complete_event_info) + + self.assertTrue(update_result) + self.assertFalse(order.is_open) + self.assertTrue(order.is_done) + self.assertEqual(OrderStatus.EXECUTED, order.last_state) + self.assertEqual(order.amount, order.executed_amount_base) + expected_executed_quote_amount += Decimal(str(complete_event_info["amount"])) * Decimal( + str(complete_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["fee"]) + Decimal(complete_event_info["fee"]), order.fee_paid) + self.assertEqual(complete_event_info["fee_currency"], order.fee_asset) + + def test_update_with_repeated_trade_id_is_ignored(self): + order = BitfinexInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "order_id": "EOID1", + "trade_id": 1, + "amount": 0.1, + "price": 10050.0, + "fee": 10.0, + "fee_currency": "USDC", + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertTrue(order.is_open) + self.assertEqual(OrderStatus.PARTIALLY, order.last_state) + self.assertEqual(Decimal(str(trade_event_info["amount"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["amount"])) * Decimal(str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["fee"]), order.fee_paid) + self.assertEqual(trade_event_info["fee_currency"], order.fee_asset) + + complete_event_info = { + "order_id": "EOID1", + "trade_id": 1, + "amount": 1, + "price": 10060.0, + "fee": 50.0, + "fee_currency": "USDC", + } + + update_result = order.update_with_trade_update(complete_event_info) + + self.assertFalse(update_result) + self.assertTrue(order.is_open) + self.assertEqual(OrderStatus.PARTIALLY, order.last_state) + self.assertEqual(Decimal(str(trade_event_info["amount"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["amount"])) * Decimal( + str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["fee"]), order.fee_paid) + self.assertEqual(trade_event_info["fee_currency"], order.fee_asset) + + def test_fee_currency_is_translated_when_processing_trade_event(self): + order = BitfinexInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "order_id": "EOID1", + "trade_id": 1, + "amount": 0.1, + "price": 10050.0, + "fee": 10.0, + "fee_currency": "UST", + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertEqual("USDT", order.fee_asset) diff --git a/test/hummingbot/connector/exchange/bitmart/__init__.py b/test/hummingbot/connector/exchange/bitmart/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_order_book_data_source.py b/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_order_book_data_source.py new file mode 100644 index 0000000..216840d --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_order_book_data_source.py @@ -0,0 +1,485 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aiohttp import WSMsgType +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.exchange.bitmart.bitmart_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bitmart import bitmart_utils +from hummingbot.connector.exchange.bitmart.bitmart_api_order_book_data_source import BitmartAPIOrderBookDataSource +from hummingbot.connector.exchange.bitmart.bitmart_exchange import BitmartExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitmartAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + + @classmethod + def tearDownClass(cls) -> None: + for task in asyncio.all_tasks(loop=cls.ev_loop): + task.cancel() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.connector = BitmartExchange( + client_config_map=self.client_config_map, + bitmart_api_key="", + bitmart_secret_key="", + bitmart_memo="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.data_source = BitmartAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map( + bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _order_book_snapshot_example(self): + return { + "data": { + "timestamp": 1527777538000, + "buys": [ + { + "amount": "4800.00", + "total": "4800.00", + "price": "0.000767", + "count": "1" + }, + { + "amount": "99996475.79", + "total": "100001275.79", + "price": "0.000201", + "count": "1" + }, + ], + "sells": [ + { + "amount": "100.00", + "total": "100.00", + "price": "0.007000", + "count": "1" + }, + { + "amount": "6997.00", + "total": "7097.00", + "price": "1.000000", + "count": "1" + }, + ] + } + } + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + @aioresponses() + def test_get_last_traded_prices(self, mock_get): + mock_response: Dict[Any] = { + "message": "OK", + "code": 1000, + "trace": "6e42c7c9-fdc5-461b-8fd1-b4e2e1b9ed57", + "data": { + "tickers": [ + { + "symbol": "COINALPHA_HBOT", + "last_price": "1.00", + "quote_volume_24h": "201477650.88000", + "base_volume_24h": "25186.48000", + "high_24h": "8800.00", + "low_24h": "1.00", + "open_24h": "8800.00", + "close_24h": "1.00", + "best_ask": "0.00", + "best_ask_size": "0.00000", + "best_bid": "0.00", + "best_bid_size": "0.00000", + "fluctuation": "-0.9999", + "url": "https://www.bitmart.com/trade?symbol=COINALPHA_HBOT" + } + ] + } + } + regex_url = re.compile(f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL}") + mock_get.get(regex_url, body=json.dumps(mock_response)) + + results = self.ev_loop.run_until_complete( + asyncio.gather(self.data_source.get_last_traded_prices([self.trading_pair]))) + results: Dict[str, Any] = results[0] + + self.assertEqual(results[self.trading_pair], float("1.00")) + + @aioresponses() + def test_get_new_order_book_successful(self, mock_get): + mock_response: Dict[str, Any] = self._order_book_snapshot_example() + regex_url = re.compile(f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_ORDER_BOOK_PATH_URL}") + mock_get.get(regex_url, body=json.dumps(mock_response)) + + results = self.ev_loop.run_until_complete( + asyncio.gather(self.data_source.get_new_order_book(self.trading_pair))) + order_book: OrderBook = results[0] + + self.assertTrue(type(order_book) == OrderBook) + self.assertEqual(order_book.snapshot_uid, mock_response["data"]["timestamp"]) + + self.assertEqual(mock_response["data"]["timestamp"], order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(2, len(bids)) + self.assertEqual(float(mock_response["data"]["buys"][0]["price"]), bids[0].price) + self.assertEqual(float(mock_response["data"]["buys"][0]["amount"]), bids[0].amount) + self.assertEqual(mock_response["data"]["timestamp"], bids[0].update_id) + self.assertEqual(2, len(asks)) + self.assertEqual(float(mock_response["data"]["sells"][0]["price"]), asks[0].price) + self.assertEqual(float(mock_response["data"]["sells"][0]["amount"]), asks[0].amount) + self.assertEqual(mock_response["data"]["timestamp"], asks[0].update_id) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "event": "subscribe", + "table": f"{CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME}:{self.ex_trading_pair}", + } + result_subscribe_diffs = { + "event": "subscribe", + "table": f"{CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME}:{self.ex_trading_pair}", + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "op": "subscribe", + "args": [f"{CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME}:{self.ex_trading_pair}"] + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "op": "subscribe", + "args": [f"{CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME}:{self.ex_trading_pair}"] + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_compressed_messages_are_correctly_read(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "event": "subscribe", + "table": f"{CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME}:{self.ex_trading_pair}", + } + result_subscribe_diffs = { + "event": "subscribe", + "table": f"{CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME}:{self.ex_trading_pair}", + } + + trade_event = { + "table": CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME, + "data": [ + { + "symbol": self.ex_trading_pair, + "price": "162.12", + "side": "buy", + "size": "11.085", + "s_t": 1542337219 + }, + { + "symbol": self.ex_trading_pair, + "price": "163.12", + "side": "buy", + "size": "15", + "s_t": 1542337238 + } + ] + } + + compressed_trade_event = bitmart_utils.compress_ws_message(json.dumps(trade_event)) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=compressed_trade_event, + message_type=WSMsgType.BINARY + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + trade_message = self.async_run_with_timeout( + self.data_source._message_queue[self.data_source._trade_messages_queue_key].get()) + + self.assertEqual(trade_event, trade_message) + + def test_listen_for_trades(self): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_queue = AsyncMock() + + trade_event = { + "table": CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME, + "data": [ + { + "symbol": self.ex_trading_pair, + "price": "162.12", + "side": "buy", + "size": "11.085", + "s_t": 1542337219 + }, + { + "symbol": self.ex_trading_pair, + "price": "163.12", + "side": "buy", + "size": "15", + "s_t": 1542337238 + } + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + trade1: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + trade2: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(msg_queue.empty()) + self.assertEqual(1542337219, int(trade1.trade_id)) + self.assertEqual(1542337238, int(trade2.trade_id)) + + def test_listen_for_trades_raises_cancelled_exception(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "table": CONSTANTS.PUBLIC_TRADE_CHANNEL_NAME, + "data": [ + { + "symbol": self.ex_trading_pair, + + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + snapshot_event = { + "table": CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME, + "data": [ + { + "asks": [["161.96", "7.37567"]], + "bids": [["161.94", "4.552355"]], + "symbol": self.ex_trading_pair, + "ms_t": 1542337219120 + } + ] + } + mock_queue.get.side_effect = [snapshot_event, asyncio.CancelledError] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(snapshot_event["data"][0]["ms_t"]) * 1e-3, msg.timestamp) + expected_update_id = int(snapshot_event["data"][0]["ms_t"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(float(snapshot_event["data"][0]["bids"][0][0]), bids[0].price) + self.assertEqual(float(snapshot_event["data"][0]["bids"][0][1]), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(float(snapshot_event["data"][0]["asks"][0][0]), asks[0].price) + self.assertEqual(float(snapshot_event["data"][0]["asks"][0][1]), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_order_book_snapshots_raises_cancelled_exception(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_snapshots_logs_exception(self): + incomplete_resp = { + "table": CONSTANTS.PUBLIC_DEPTH_CHANNEL_NAME, + "data": [ + { + "symbol": self.ex_trading_pair, + "ms_t": 1542337219120 + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) diff --git a/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_user_stream_data_source.py new file mode 100644 index 0000000..c1d8d13 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmart/test_bitmart_api_user_stream_data_source.py @@ -0,0 +1,469 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aiohttp import WSMsgType +from bidict import bidict + +import hummingbot.connector.exchange.bitmart.bitmart_constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bitmart import bitmart_utils +from hummingbot.connector.exchange.bitmart.bitmart_api_user_stream_data_source import BitmartAPIUserStreamDataSource +from hummingbot.connector.exchange.bitmart.bitmart_auth import BitmartAuth +from hummingbot.connector.exchange.bitmart.bitmart_exchange import BitmartExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class BitmartAPIUserStreamDataSourceTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.time_synchronizer = MagicMock() + self.time_synchronizer.time.return_value = 1640001112.223 + + self.auth = BitmartAuth( + api_key="test_api_key", + secret_key="test_secret_key", + memo="test_memo", + time_provider=self.time_synchronizer) + + self.connector = BitmartExchange( + client_config_map=self.client_config_map, + bitmart_api_key="test_api_key", + bitmart_secret_key="test_secret_key", + bitmart_memo="test_memo", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = BitmartAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map( + bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_orders_events(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + successful_login_response = {"event": "login"} + result_subscribe_orders = { + "event": "subscribe", + "topic": CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME, + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(successful_login_response)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_messages)) + expected_login = { + "op": "login", + "args": [ + "test_api_key", + str(int(self.time_synchronizer.time() * 1e3)), + "f0f176c799346a7730c9c237a09d14742971f3ab59848dde75ef1ac95b04c4e5"] # noqa: mock + } + self.assertEqual(expected_login, sent_messages[0]) + expected_orders_subscription = { + "op": "subscribe", + "args": [f"{CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME}:{self.ex_trading_pair}"] + } + self.assertEqual(expected_orders_subscription, sent_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private account and orders channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_error_when_login_fails(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + erroneous_login_response = {"event": "login", "errorCode": "4001"} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(erroneous_login_response)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(self._is_logged( + "ERROR", + "Error authenticating the private websocket connection" + )) + + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_invalid_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = {"event": "login"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(successful_login_response)) + + event_without_data = { + "table": CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(event_without_data)) + + event_without_table = { + "data": [ + { + "symbol": self.ex_trading_pair, + "side": "buy", + "type": "market", + "notional": "", + "size": "1.0000000000", + "ms_t": "1609926028000", + "price": "46100.0000000000", + "filled_notional": "46100.0000000000", + "filled_size": "1.0000000000", + "margin_trading": "0", + "state": "4", + "order_id": "2147857398", + "order_type": "0", + "last_fill_time": "1609926039226", + "last_fill_price": "46100.00000", + "last_fill_count": "1.00000", + "exec_type": "M", + "detail_id": "256348632", + "client_order_id": "order4872191" + } + ], + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(event_without_table)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_initialization(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(messages)) + self.ev_loop.run_until_complete(self.listening_task) + + @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_authentication(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(messages)) + self.ev_loop.run_until_complete(self.listening_task) + + def test_subscribe_channels_raises_cancel_exception(self): + ws_assistant = AsyncMock() + ws_assistant.send.side_effect = asyncio.CancelledError + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(ws_assistant)) + self.ev_loop.run_until_complete(self.listening_task) + + # @unittest.skip("Test with error") + @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listening_process_logs_exception_during_events_subscription(self, sleep_mock, mock_ws): + self.connector._set_trading_pair_symbol_map({}) + + messages = asyncio.Queue() + sleep_mock.side_effect = asyncio.CancelledError + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, + json.dumps({"event": "login"})) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(messages)) + + try: + self.async_run_with_timeout(self.listening_task, timeout=3) + except asyncio.CancelledError: + pass + + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error occurred subscribing to order book trading and delta streams...")) + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_order_event(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = {"event": "login"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(successful_login_response)) + + order_event = { + "data": [ + { + "symbol": self.ex_trading_pair, + "side": "buy", + "type": "market", + "notional": "", + "size": "1.0000000000", + "ms_t": "1609926028000", + "price": "46100.0000000000", + "filled_notional": "46100.0000000000", + "filled_size": "1.0000000000", + "margin_trading": "0", + "state": "4", + "order_id": "2147857398", + "order_type": "0", + "last_fill_time": "1609926039226", + "last_fill_price": "46100.00000", + "last_fill_count": "1.00000", + "exec_type": "M", + "detail_id": "256348632", + "client_order_id": "order4872191" + } + ], + "table": CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(1, msg_queue.qsize()) + order_event_message = msg_queue.get_nowait() + self.assertEqual(order_event, order_event_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_compressed_order_event(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = {"event": "login"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(successful_login_response)) + + order_event = { + "data": [ + { + "symbol": self.ex_trading_pair, + "side": "buy", + "type": "market", + "notional": "", + "size": "1.0000000000", + "ms_t": "1609926028000", + "price": "46100.0000000000", + "filled_notional": "46100.0000000000", + "filled_size": "1.0000000000", + "margin_trading": "0", + "state": "4", + "order_id": "2147857398", + "order_type": "0", + "last_fill_time": "1609926039226", + "last_fill_price": "46100.00000", + "last_fill_count": "1.00000", + "exec_type": "M", + "detail_id": "256348632", + "client_order_id": "order4872191" + } + ], + "table": CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=bitmart_utils.compress_ws_message(json.dumps(order_event)), + message_type=WSMsgType.BINARY) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(1, msg_queue.qsize()) + order_event_message = msg_queue.get_nowait() + self.assertEqual(order_event, order_event_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_details_for_order_event_with_errors(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = {"event": "login"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(successful_login_response)) + + order_event = { + "errorCode": "4001", + "errorMessage": "Error", + "data": [ + { + "symbol": self.ex_trading_pair, + "side": "buy", + "type": "market", + "notional": "", + "size": "1.0000000000", + "ms_t": "1609926028000", + "price": "46100.0000000000", + "filled_notional": "46100.0000000000", + "filled_size": "1.0000000000", + "margin_trading": "0", + "state": "4", + "order_id": "2147857398", + "order_type": "0", + "last_fill_time": "1609926039226", + "last_fill_price": "46100.00000", + "last_fill_count": "1.00000", + "exec_type": "M", + "detail_id": "256348632", + "client_order_id": "order4872191" + } + ], + "table": CONSTANTS.PRIVATE_ORDER_PROGRESS_CHANNEL_NAME + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_details_for_invalid_event_message(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = {"event": "login"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(successful_login_response)) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message="invalid message content") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + self.assertTrue(self._is_logged( + "WARNING", + "Invalid event message received through the order book data source connection (invalid message content)" + )) diff --git a/test/hummingbot/connector/exchange/bitmart/test_bitmart_exchange.py b/test/hummingbot/connector/exchange/bitmart/test_bitmart_exchange.py new file mode 100644 index 0000000..0c251a6 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmart/test_bitmart_exchange.py @@ -0,0 +1,796 @@ +import json +import math +import re +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bitmart import bitmart_constants as CONSTANTS, bitmart_web_utils as web_utils +from hummingbot.connector.exchange.bitmart.bitmart_exchange import BitmartExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class BitmartExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.GET_TRADING_RULES_PATH_URL) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.GET_LAST_TRADING_PRICES_PATH_URL) + url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" + return url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.CHECK_NETWORK_PATH_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(CONSTANTS.GET_TRADING_RULES_PATH_URL) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.CREATE_ORDER_PATH_URL) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.GET_ACCOUNT_SUMMARY_PATH_URL) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "symbol_id": 1024, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "quote_increment": "1.00000000", + "base_min_size": "1.00000000", + "price_min_precision": 6, + "price_max_precision": 8, + "expiration": "NA", + "min_buy_amount": "0.00010000", + "min_sell_amount": "0.00010000", + "trade_status": "trading" + }, + ] + } + } + + @property + def latest_prices_request_mock_response(self): + return { + "message": "OK", + "code": 1000, + "trace": "6e42c7c9-fdc5-461b-8fd1-b4e2e1b9ed57", + "data": { + "tickers": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "last_price": str(self.expected_latest_price), + "quote_volume_24h": "201477650.88000", + "base_volume_24h": "25186.48000", + "high_24h": "8800.00", + "low_24h": "1.00", + "open_24h": "8800.00", + "close_24h": "1.00", + "best_ask": "0.00", + "best_ask_size": "0.00000", + "best_bid": "0.00", + "best_bid_size": "0.00000", + "fluctuation": "-0.9999", + "url": "https://www.bitmart.com/trade?symbol=BTC_USDT" + } + ] + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "symbol_id": 1024, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "quote_increment": "1.00000000", + "base_min_size": "1.00000000", + "price_min_precision": 6, + "price_max_precision": 8, + "expiration": "NA", + "min_buy_amount": "0.00010000", + "min_sell_amount": "0.00010000", + "trade_status": "trading" + }, + { + "symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "symbol_id": 1025, + "base_currency": "INVALID", + "quote_currency": "PAIR", + "quote_increment": "1.00000000", + "base_min_size": "1.00000000", + "price_min_precision": 6, + "price_max_precision": 8, + "expiration": "NA", + "min_buy_amount": "0.00010000", + "min_sell_amount": "0.00010000", + "trade_status": "pre-trade" + }, + ] + } + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "serivce": [ + { + "title": "Spot API Stop", + "service_type": "spot", + "status": "2", + "start_time": 1527777538000, + "end_time": 1527777538000 + }, + { + "title": "Contract API Stop", + "service_type": "contract", + "status": "2", + "start_time": 1527777538000, + "end_time": 1527777538000 + } + ] + } + } + + @property + def trading_rules_request_mock_response(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "symbol_id": 1024, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "quote_increment": "1.00000000", + "base_min_size": "5.00000000", + "price_min_precision": 6, + "price_max_precision": 8, + "expiration": "NA", + "min_buy_amount": "0.00020000", + "min_sell_amount": "0.00030000", + "trade_status": "trading" + }, + ] + } + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "symbol_id": 1024, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "expiration": "NA", + "trade_status": "trading" + }, + ] + } + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "order_id": self.expected_exchange_order_id + } + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "wallet": [ + { + "id": self.base_asset, + "available": "10.000000", + "name": "CoinAlpha", + "frozen": "5.000000", + }, + { + "id": self.quote_asset, + "available": "2000.000000", + "name": "Hbot", + "frozen": "0.0", + }, + ] + } + } + + @property + def balance_request_mock_response_only_base(self): + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "wallet": [ + { + "id": self.base_asset, + "available": "10.000000", + "name": "CoinAlpha", + "frozen": "5.000000", + }, + ] + } + } + + @property + def balance_event_websocket_update(self): + # Bitmart does not provide balance updates through websocket + self.fail() + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + price_decimals = Decimal(str( + self.trading_rules_request_mock_response["data"]["symbols"][0]["price_max_precision"])) + price_step = Decimal("1") / Decimal(str(math.pow(10, price_decimals))) + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["data"]["symbols"][0]["base_min_size"]), + min_order_value=Decimal(self.trading_rules_request_mock_response["data"]["symbols"][0]["min_buy_amount"]), + min_base_amount_increment=Decimal(str( + self.trading_rules_request_mock_response["data"]["symbols"][0]["base_min_size"])), + min_price_increment=price_step, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"]["symbols"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 1736871726781 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return True + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return 30000 + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return base_token + "_" + quote_token + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return BitmartExchange( + client_config_map=client_config_map, + bitmart_api_key="testAPIKey", + bitmart_secret_key="testSecret", + bitmart_memo="testMemo", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn("X-BM-KEY", request_headers) + self.assertEqual("testAPIKey", request_headers["X-BM-KEY"]) + self.assertIn("X-BM-TIMESTAMP", request_headers) + self.assertIn("X-BM-SIGN", request_headers) + self.assertIn("X-BM-BROKER-ID", request_headers) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["symbol"]) + self.assertEqual("limit", request_data["type"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(Decimal("100"), Decimal(request_data["size"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["clientOrderId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(json.loads(request_call.kwargs["data"])) + self.assertEqual(order.client_order_id, request_data["clientOrderId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + def configure_successful_cancelation_response(self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.post(regex_url, status=400, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response(self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_DETAIL_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response(self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_DETAIL_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response(self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_DETAIL_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_DETAIL_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_DETAIL_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADE_DETAIL_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADE_DETAIL_PATH_URL) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.GET_TRADE_DETAIL_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "notional": "", + "size": str(order.amount), + "ms_t": "1609926028000", + "price": str(order.price), + "filled_notional": "00.0000000000", + "filled_size": "0.0000000000", + "margin_trading": "0", + "state": "4", + "order_id": order.exchange_order_id, + "order_type": "0", + "last_fill_time": "0", + "last_fill_price": "0.00000", + "last_fill_count": "0.00000", + "exec_type": "M", + "detail_id": "", + "client_order_id": order.client_order_id + } + ], + "table": "spot/user/order" + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "notional": "", + "size": str(order.amount), + "ms_t": "1609926028000", + "price": str(order.price), + "filled_notional": "00.0000000000", + "filled_size": "0.0000000000", + "margin_trading": "0", + "state": "8", + "order_id": order.exchange_order_id, + "order_type": "0", + "last_fill_time": "0", + "last_fill_price": "0.00000", + "last_fill_count": "0.00000", + "exec_type": "M", + "detail_id": "", + "client_order_id": order.client_order_id + } + ], + "table": "spot/user/order" + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": order.trade_type.name.lower(), + "type": "limit", + "notional": "", + "size": str(order.amount), + "ms_t": "1609926028000", + "price": str(order.price), + "filled_notional": str(order.amount * order.price), + "filled_size": str(order.amount), + "margin_trading": "0", + "state": "6", + "order_id": order.exchange_order_id, + "order_type": "0", + "last_fill_time": "1609926039226", + "last_fill_price": str(order.price), + "last_fill_count": str(order.amount), + "exec_type": "M", + "detail_id": self.expected_fill_trade_id, + "client_order_id": order.client_order_id + } + ], + "table": "spot/user/order" + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + pass + + def test_time_synchronizer_related_request_error_detection(self): + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + 'Error: {"code":30007,"msg":"Header X-BM-TIMESTAMP range. Within a minute"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + 'Error: {"code":30008,"msg":"Header X-BM-TIMESTAMP invalid format"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + 'Error: {"code":30000,"msg":"Header X-BM-TIMESTAMP range. Within a minute"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + 'Error: {"code":30007,"msg":"Other message"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.binance.com/api/v3/order. HTTP status is 400. " + 'Error: {"code":30008,"msg":"Other message"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": 1000, + "trace": "886fb6ae-456b-4654-b4e0-d681ac05cea1", + "message": "OK", + "data": { + "result": True + } + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a27c2cb5-ead4-471d-8455-1cfeda054ea6", + "data": { + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "create_time": 1591096004000, + "side": order.trade_type.name.lower(), + "type": "limit", + "price": str(order.price), + "price_avg": "0.00", + "size": str(order.amount), + "notional": str(order.amount * order.price), + "filled_notional": "0.00000000", + "filled_size": "0.00000", + "unfilled_volume": "0.02000", + "status": "8", + "clientOrderId": order.client_order_id + } + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a27c2cb5-ead4-471d-8455-1cfeda054ea6", + "data": { + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "create_time": 1591096004000, + "side": order.trade_type.name.lower(), + "type": "limit", + "price": str(order.price), + "price_avg": str(order.price + Decimal(2)), + "size": str(order.amount), + "notional": str(order.amount * order.price), + "filled_notional": str(order.amount * (order.price + Decimal(2))), + "filled_size": str(order.amount), + "unfilled_volume": "0.00000", + "status": "6", + "clientOrderId": order.client_order_id + } + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a06a5c53-8e6f-42d6-8082-2ff4718d221c", + "data": { + "current_page": 1, + "trades": [ + { + "detail_id": self.expected_fill_trade_id, + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "create_time": 1590462303000, + "side": order.trade_type.name.lower(), + "fees": str(self.expected_fill_fee.flat_fees[0].amount), + "fee_coin_name": self.expected_fill_fee.flat_fees[0].token, + "notional": str(order.amount * order.price), + "price_avg": str(order.price), + "size": str(order.amount), + "exec_type": "M", + "clientOrderId": order.client_order_id + }, + ] + } + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a27c2cb5-ead4-471d-8455-1cfeda054ea6", + "data": { + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "create_time": 1591096004000, + "side": order.trade_type.name.lower(), + "type": "limit", + "price": str(order.price), + "price_avg": "0.00", + "size": str(order.amount), + "notional": str(order.amount * order.price), + "filled_notional": "0.00000000", + "filled_size": "0.00000", + "unfilled_volume": "0.02000", + "status": "4", + "clientOrderId": order.client_order_id + } + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a27c2cb5-ead4-471d-8455-1cfeda054ea6", + "data": { + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "create_time": 1591096004000, + "side": order.trade_type.name.lower(), + "type": "limit", + "price": str(order.price), + "price_avg": "0.00", + "size": str(order.amount), + "notional": str(order.amount * order.price), + "filled_notional": str(self.expected_partial_fill_amount * order.price), + "filled_size": str(self.expected_partial_fill_amount), + "unfilled_volume": str((order.amount * order.price) - + (self.expected_partial_fill_amount * self.expected_partial_fill_price)), + "status": "5", + "clientOrderId": order.client_order_id + } + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + exchange_order_id = order.exchange_order_id or 1736871726781 + return { + "message": "OK", + "code": 1000, + "trace": "a06a5c53-8e6f-42d6-8082-2ff4718d221c", + "data": { + "current_page": 1, + "trades": [ + { + "detail_id": self.expected_fill_trade_id, + "order_id": exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "create_time": 1590462303000, + "side": order.trade_type.name.lower(), + "fees": str(self.expected_fill_fee.flat_fees[0].amount), + "fee_coin_name": self.expected_fill_fee.flat_fees[0].token, + "notional": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + "price_avg": str(self.expected_partial_fill_price), + "size": str(self.expected_partial_fill_amount), + "exec_type": "M", + "clientOrderId": order.client_order_id + }, + ] + } + } diff --git a/test/hummingbot/connector/exchange/bitmex/__init__.py b/test/hummingbot/connector/exchange/bitmex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_api_order_book_data_source.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_api_order_book_data_source.py new file mode 100644 index 0000000..20438ee --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_api_order_book_data_source.py @@ -0,0 +1,385 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.exchange.bitmex.bitmex_utils as utils +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.exchange.bitmex.bitmex_api_order_book_data_source import BitmexAPIOrderBookDataSource +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitmexAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "ETH" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.domain = "bitmex_testnet" + utils.TRADING_PAIR_MULTIPLIERS["ETH_USDT"] = utils.TRADING_PAIR_MULTIPLIERS_TUPLE(1000000000, 1000000) + utils.TRADING_PAIR_INDICES["ETH_USDT"] = utils.TRADING_PAIR_INDEX(954, 0.05) + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.async_tasks: List[asyncio.Task] = [] + + self.data_source = BitmexAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict({self.ex_trading_pair: self.trading_pair}) + } + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + for task in self.async_tasks: + task.cancel() + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def resume_test_callback(self, *_, **__): + self.resume_test_event.set() + return None + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _raise_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _orderbook_update_event(self): + resp = { + "table": "orderBookL2", + "action": "insert", + "data": [{ + "symbol": "ETH_USDT", + "id": 3333377777, + "size": 10, + "side": "Sell" + }], + } + return resp + + def _orderbook_trade_event(self): + resp = { + "table": "trade", + "data": [{ + "symbol": "ETH_USDT", + "side": "Sell", + "price": 1000.0, + "size": 10, + "timestamp": "2020-02-11T9:30:02.123Z" + }], + } + return resp + + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [{ + "symbol": "ETH_USDT", + "lastPrice": 100.0 + }] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source.get_last_traded_prices(trading_pairs=[self.trading_pair], domain=self.domain) + ) + self.assertTrue(self.trading_pair in result) + self.assertEqual(100.0, result[self.trading_pair]) + + def test_get_throttler_instance(self): + self.assertTrue(isinstance(self.data_source._get_throttler_instance(), AsyncThrottler)) + + @aioresponses() + def test_init_trading_pair_symbols_failure(self, mock_api): + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = {} + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + map = self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(0, len(map)) + + @aioresponses() + def test_init_trading_pair_symbols_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "ETH_USDT", + "rootSymbol": "ETH", + "quoteCurrency": "USDT" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + self.async_run_with_timeout(self.data_source.init_trading_pair_symbols(domain=self.domain)) + self.assertEqual(1, len(self.data_source._trading_pair_symbol_map)) + + @aioresponses() + def test_trading_pair_symbol_map_dictionary_not_initialized(self, mock_api): + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = {} + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "ETH_USDT", + "rootSymbol": "ETH", + "quoteCurrency": "USDT" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(1, len(self.data_source._trading_pair_symbol_map)) + + def test_trading_pair_symbol_map_dictionary_initialized(self): + result = self.async_run_with_timeout(self.data_source.trading_pair_symbol_map(domain=self.domain)) + self.assertEqual(1, len(result)) + + def test_convert_from_exchange_trading_pair_not_found(self): + unknown_pair = "UNKNOWN-PAIR" + with self.assertRaisesRegex(ValueError, f"There is no symbol mapping for exchange trading pair {unknown_pair}"): + self.async_run_with_timeout( + self.data_source.convert_from_exchange_trading_pair(unknown_pair, domain=self.domain)) + + def test_convert_from_exchange_trading_pair_successful(self): + result = self.async_run_with_timeout( + self.data_source.convert_from_exchange_trading_pair(self.ex_trading_pair, domain=self.domain)) + self.assertEqual(result, self.trading_pair) + + def test_convert_to_exchange_trading_pair_not_found(self): + unknown_pair = "UNKNOWN-PAIR" + with self.assertRaisesRegex(ValueError, f"There is no symbol mapping for trading pair {unknown_pair}"): + self.async_run_with_timeout( + self.data_source.convert_to_exchange_trading_pair(unknown_pair, domain=self.domain)) + + def test_convert_to_exchange_trading_pair_successful(self): + result = self.async_run_with_timeout( + self.data_source.convert_to_exchange_trading_pair(self.trading_pair, domain=self.domain)) + self.assertEqual(result, self.ex_trading_pair) + + @aioresponses() + def test_get_snapshot_exception_raised(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) + + with self.assertRaises(IOError) as context: + self.async_run_with_timeout( + self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain) + ) + + self.assertEqual(str(context.exception), "Error executing request GET /orderBook/L2. HTTP status is 400. Error: [\"ERROR\"]") + + @aioresponses() + def test_get_snapshot_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [{ + 'symbol': 'ETH_USDT', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4 + }] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + result: Dict[str, Any] = self.async_run_with_timeout( + self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain) + ) + self.assertEqual(mock_response, result) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [ + { + 'symbol': 'ETH_USDT', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4, + 'id': 2543 + }, + { + 'symbol': 'ETH_USDT', + 'side': 'Buy', + 'size': 100, + 'price': 3000.1, + 'id': 2555 + } + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + result = self.async_run_with_timeout(self.data_source.get_new_order_book(trading_pair=self.trading_pair)) + self.assertIsInstance(result, OrderBook) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_cancelled_when_connecting(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + self.assertEqual(msg_queue.qsize(), 0) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successful(self, mock_ws): + msg_queue_diffs: asyncio.Queue = asyncio.Queue() + msg_queue_trades: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.close.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_update_event()) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, json.dumps(self._orderbook_trade_event()) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.listening_task_diffs = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue_diffs) + ) + self.listening_task_trades = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue_trades) + ) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_diffs.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.DIFF, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + self.assertEqual(0, len(result.content["bids"])) + self.assertEqual(1, len(result.content["asks"])) + + result: OrderBookMessage = self.async_run_with_timeout(msg_queue_trades.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.TRADE, result.type) + self.assertTrue(result.has_trade_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) + + self.listening_task.cancel() + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_error_raised(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + def test_listen_for_order_book_snapshots_logs_exception_error_with_response(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "m": 1, + "i": 2, + } + mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds...") + ) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = [{ + 'symbol': 'ETH_USDT', + 'side': 'Sell', + 'size': 348, + 'price': 3127.4, + 'id': 33337777 + }] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + result = self.async_run_with_timeout(msg_queue.get()) + + self.assertIsInstance(result, OrderBookMessage) + self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) + self.assertTrue(result.has_update_id) + self.assertEqual(self.trading_pair, result.content["trading_pair"]) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py new file mode 100644 index 0000000..ef4d625 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py @@ -0,0 +1,107 @@ +import asyncio +import copy +import hashlib +import hmac +import json +import unittest +from typing import Awaitable +from unittest.mock import patch +from urllib.parse import urlencode + +from hummingbot.connector.exchange.bitmex.bitmex_auth import EXPIRATION, BitmexAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + +MOCK_TS = 1648733370.792768 + + +class BitmexAuthUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.test_params = { + "test_param": "test_input" + } + self.auth = BitmexAuth( + api_key=self.api_key, + api_secret=self.secret_key + ) + + def _get_test_payload(self): + return urlencode(dict(copy.deepcopy(self.test_params))) + + def _get_signature_from_test_payload(self, payload): + return hmac.new( + bytes(self.auth._api_secret.encode("utf-8")), + payload.encode("utf-8"), + hashlib.sha256 + ).hexdigest() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_generate_signature_from_payload(self): + payload = self._get_test_payload() + signature = self.auth.generate_signature_from_payload(payload) + + self.assertEqual(signature, self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_no_parameters_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + payload = 'GET' + mock_path + str(int(MOCK_TS) + EXPIRATION) + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url=mock_path, is_auth_required=True + ) + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_parameters_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + mock_query = "?test_param=param" + payload = 'GET' + mock_path + mock_query + str(int(MOCK_TS) + EXPIRATION) + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, url=mock_path, params={"test_param": "param"}, is_auth_required=True + ) + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + @patch("time.time") + def test_rest_authenticate_data_provided(self, mock_ts): + mock_ts.return_value = MOCK_TS + mock_path = "/TEST_PATH_URL" + mock_data = json.dumps(self.test_params) + payload = 'POST' + mock_path + str(int(MOCK_TS) + EXPIRATION) + mock_data + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, url="/TEST_PATH_URL", data=self.test_params, is_auth_required=True + ) + + signed_request: RESTRequest = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("api-key", signed_request.headers) + self.assertEqual(signed_request.headers["api-key"], self.api_key) + self.assertIn("api-signature", signed_request.headers) + self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload)) + + def test_generate_ws_signature(self): + payload = 'GET/realtime' + str(int(MOCK_TS)) + + signature = self.async_run_with_timeout(self.auth.generate_ws_signature(str(int(MOCK_TS)))) + self.assertEqual(signature, self._get_signature_from_test_payload(payload)) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_exchange.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_exchange.py new file mode 100644 index 0000000..c6caa80 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_exchange.py @@ -0,0 +1,823 @@ +import asyncio +import functools +import json +import re +import time +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Callable, Dict, List, Optional +from unittest.mock import AsyncMock, patch + +import pandas as pd +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.exchange.bitmex.bitmex_utils as utils +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bitmex.bitmex_api_order_book_data_source import BitmexAPIOrderBookDataSource +from hummingbot.connector.exchange.bitmex.bitmex_exchange import BitmexExchange +from hummingbot.connector.exchange.bitmex.bitmex_order_status import BitmexOrderStatus +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent + + +class BitmexExchangeUnitTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.symbol = f"{cls.base_asset}_{cls.quote_asset}" + cls.domain = CONSTANTS.TESTNET_DOMAIN + cls.listen_key = "TEST_LISTEN_KEY" + cls.ev_loop = asyncio.get_event_loop() + utils.TRADING_PAIR_MULTIPLIERS["COINALPHA_HBOT"] = utils.TRADING_PAIR_MULTIPLIERS_TUPLE(1000000000, 1000000) + utils.TRADING_PAIR_MULTIPLIERS["XBT_USDT"] = utils.TRADING_PAIR_MULTIPLIERS_TUPLE(100000000, 1000000) + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + + self.ws_sent_messages = [] + self.ws_incoming_messages = asyncio.Queue() + self.resume_test_event = asyncio.Event() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = BitmexExchange( + client_config_map=self.client_config_map, + bitmex_api_key="testAPIKey", + bitmex_api_secret="testSecret", + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = { + self.symbol: self.trading_pair, + "XBT_USDT": "XBT-USDT" + } + self.exchange._set_current_timestamp(1640780000) + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._client_order_tracker.logger().setLevel(1) + self.exchange._client_order_tracker.logger().addHandler(self) + self.exchange._trading_pair_to_multipliers["COINALPHA-HBOT"] = utils.TRADING_PAIR_MULTIPLIERS_TUPLE(1000000000, 1000000) + self.exchange._trading_pair_to_multipliers["XBT-USDT"] = utils.TRADING_PAIR_MULTIPLIERS_TUPLE(1000000000, 1000000) + self.mocking_assistant = NetworkMockingAssistant() + self.test_task: Optional[asyncio.Task] = None + self.resume_test_event = asyncio.Event() + self._initialize_event_loggers() + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = { + self.domain: bidict( + { + self.symbol: self.trading_pair, + "XBT_USDT": "XBT-USDT" + } + ) + } + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + BitmexAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_filled_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFilled, self.order_filled_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _return_calculation_and_set_done_event(self, calculation: Callable, *args, **kwargs): + if self.resume_test_event.is_set(): + raise asyncio.CancelledError + self.resume_test_event.set() + return calculation(*args, **kwargs) + + def _get_income_history_dict(self) -> List: + income_history = [{ + "income": 1, + "symbol": self.symbol, + "time": self.start_timestamp, + }] + return income_history + + def _get_trading_pair_symbol_map(self) -> Dict[str, str]: + trading_pair_symbol_map = {self.symbol: f"{self.base_asset}-{self.quote_asset}"} + return trading_pair_symbol_map + + def _get_exchange_info_mock_response( + self, + margin_asset: str = "HBOT", + min_order_size: float = 1, + min_price_increment: float = 2, + min_notional_size: float = 4, + max_order_size: float = 1000 + ) -> Dict[str, Any]: + mocked_exchange_info = [ + { + "symbol": self.symbol, + "typ": "FFWCSX", + "rootSymbol": self.base_asset, + "quoteCurrency": self.quote_asset, + "maxOrderQty": max_order_size, + "lotSize": min_order_size, # this gets divided by the multiplier, which is set to 1 + "settlCurrency": margin_asset, + "tickSize": min_price_increment + }, + { + "symbol": "XBT_USDT", + "typ": "FFWCSX", + "rootSymbol": "XBT", + "quoteCurrency": "USDT", + "lotSize": 100, + "tickSize": 0.5, + "settlCurrency": "XBt", + "maxOrderQty": 10000000 + }, + ] + return mocked_exchange_info + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._trading_pairs.append("XBT-USDT") + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: Dict[str, Any] = [ + { + "symbol": "COINALPHA_HBOT", + "rootSymbol": "COINALPHA", + "quoteCurrency": "HBOT", + "settlCurrency": "HBOT", + "lotSize": 1.0, + "tickSize": 0.0001, + "minProvideSize": 0.001, + "maxOrderQty": 1000000 + }, + { + "symbol": "XBT_USDT", + "rootSymbol": "XBT", + "quoteCurrency": "USDT", + "lotSize": 100, + "tickSize": 0.5, + "settlCurrency": "XBt", + "maxOrderQty": 10000000 + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + url_2 = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace("?", r"\?")) + mock_response_2: List[Dict[str, Any]] = [ + { + "symbol": "COINALPHA_HBOT", + "lastPrice": 1000.0 + } + ] + mock_api.get(regex_url_2, body=json.dumps(mock_response_2)) + url_3 = web_utils.rest_url( + CONSTANTS.TICKER_PRICE_URL, domain=self.domain + ) + regex_url_3 = re.compile(f"^{url_3}".replace(".", r"\.").replace("?", r"\?")) + mock_response_3: List[Dict[str, Any]] = [ + { + "symbol": "XBT_USDT", + "lastPrice": 1000.0 + } + ] + mock_api.get(regex_url_3, body=json.dumps(mock_response_3)) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + self.assertTrue(len(self.exchange._trading_rules) > 0) + quant_amount = self.exchange.quantize_order_amount('XBT-USDT', Decimal('0.00001'), Decimal('10000')) + self.assertEqual(quant_amount, Decimal('0.000010')) + quant_price = self.exchange.quantize_order_price('COINALPHA-HBOT', Decimal('1')) + self.assertEqual(quant_price, Decimal('1.0')) + quant_amount = self.exchange.quantize_order_amount('COINALPHA-HBOT', Decimal('0.00001'), Decimal('10000')) + self.assertEqual(quant_amount, Decimal('0.000010')) + self.exchange._trading_pairs.remove("XBT-USDT") + + def test_format_trading_rules(self): + min_order_size = Decimal('1E-9') + min_price_increment = 2 + min_base_amount_increment = Decimal('1E-9') + mocked_response = self._get_exchange_info_mock_response() + + task = self.ev_loop.create_task(self.exchange._format_trading_rules(mocked_response)) + trading_rules = self.async_run_with_timeout(task) + + self.assertEqual(1, len(trading_rules)) + + trading_rule = trading_rules[0] + + self.assertEqual(min_order_size, trading_rule.min_order_size) + self.assertEqual(min_price_increment, trading_rule.min_price_increment) + self.assertEqual(min_base_amount_increment, trading_rule.min_base_amount_increment) + + def test_buy_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + partial_fill = { + "table": "execution", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 9999, + } + ] + } + logger_len = len(self.order_filled_logger.event_log) + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + complete_fill = { + "table": "execution", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.0, + "ordStatus": "Filled", + "avgPx": 9999, + } + ] + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) + + def test_sell_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + partial_fill = { + "table": "execution", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + } + logger_len = len(self.order_filled_logger.event_log) + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + self.assertEqual(logger_len + 1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + complete_fill = { + "table": "execution", + "data": [ + { + "clOrdID": "OID1", + "leavesQty": 0.0, + "ordStatus": "Filled", + "avgPx": 10001, + } + ] + } + + self.resume_test_event = asyncio.Event() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(logger_len + 2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0.0002"), fill_event.trade_fee.percent) + + self.assertEqual(1, len(self.sell_order_completed_logger.event_log)) + + def test_order_event_with_cancelled_status_marks_order_as_cancelled(self): + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + order = self.exchange.in_flight_orders.get("OID1") + + partial_fill = { + "table": "execution", + "data": [ + { + "clOrdID": "OID1", + "ordStatus": "Canceled", + "leavesQty": 1 + } + ] + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + + event = self.order_cancelled_logger.event_log[0] + + self.assertEqual(event.order_id, order.client_order_id) + + def test_user_stream_event_listener_raises_cancelled_error(self): + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = asyncio.CancelledError + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.test_task) + + @aioresponses() + @patch("hummingbot.connector.exchange.bitmex.bitmex_exchange." + "BitmexExchange.current_timestamp") + def test_update_order_status_successful(self, req_mock, mock_timestamp): + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + order = [ + { + "clOrdID": "OID1", + "leavesQty": 500000000, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + in_flight_orders = self.exchange._in_flight_orders + + self.assertTrue("OID1" in in_flight_orders) + + self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) + self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) + self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) + self.assertEqual(10000, in_flight_orders["OID1"].price) + self.assertEqual(1, in_flight_orders["OID1"].amount) + self.assertEqual(BitmexOrderStatus.PartiallyFilled, in_flight_orders["OID1"].state) + # Processing an order update should not impact trade fill information + self.assertEqual(Decimal("0.5"), in_flight_orders["OID1"].executed_amount_base) + self.assertEqual(Decimal("5000.5"), in_flight_orders["OID1"].executed_amount_quote) + + @aioresponses() + @patch("hummingbot.connector.exchange.bitmex.bitmex_exchange." + "BitmexExchange.current_timestamp") + def test_update_order_status_failure_old_order(self, req_mock, mock_timestamp): + self.exchange._last_poll_timestamp = 0 + mock_timestamp.return_value = 1 + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=0, + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + order = [ + { + "clOrdID": "OID2", + "leavesQty": 0.5, + "ordStatus": "PartiallyFilled", + "avgPx": 10001, + } + ] + + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + req_mock.get(regex_url, body=json.dumps(order)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + self.assertEqual(len(self.exchange.in_flight_orders), 0) + + @aioresponses() + @patch("hummingbot.connector.exchange.bitmex.bitmex_exchange." + "LatchingEventResponder.wait_for_completion") + def test_cancel_all_successful(self, mocked_api, mock_wait): + mock_wait.return_value = True + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"code": 200, "msg": "success"} + mocked_api.delete(regex_url, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + self.exchange.start_tracking_order( + order_side=TradeType.SELL, + client_order_id="OID2", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + self.assertTrue("OID2" in self.exchange._in_flight_orders) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(timeout_seconds=1)) + + order_cancelled_events = self.order_cancelled_logger.event_log + + self.assertEqual(0, len(order_cancelled_events)) + self.assertEqual(2, len(cancellation_results)) + self.assertEqual("OID1", cancellation_results[0].order_id) + self.assertEqual("OID2", cancellation_results[1].order_id) + + @aioresponses() + def test_cancel_unknown_new_order(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"error": {"message": "order not found", "name": "Not Found"}} + req_mock.delete(regex_url, status=400, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=time.time(), + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + tracked_order = self.exchange.in_flight_orders.get("OID1") + tracked_order.state = BitmexOrderStatus.New + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + + try: + self.async_run_with_timeout(self.exchange.cancel_order("OID1")) + except Exception as e: + self.assertEqual(str(e), f"order {tracked_order.client_order_id} does not yet exist on the exchange and could not be cancelled.") + + @aioresponses() + def test_cancel_unknown_old_order(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + cancel_response = {"error": {"message": "order not found", "name": "Not Found"}} + req_mock.delete(regex_url, status=400, body=json.dumps(cancel_response)) + + self.exchange.start_tracking_order( + order_side=TradeType.BUY, + client_order_id="OID1", + order_type=OrderType.LIMIT, + created_at=0.0, + hash="8886774", + trading_pair=self.trading_pair, + price=Decimal("10000"), + amount=Decimal("1") + ) + + tracked_order = self.exchange.in_flight_orders.get("OID1") + tracked_order.state = BitmexOrderStatus.New + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + try: + cancellation_result = self.async_run_with_timeout(self.exchange.cancel_order("OID1")) + except Exception: + pass + + self.assertFalse(cancellation_result) + + self.assertTrue("OID1" not in self.exchange._in_flight_orders) + + @aioresponses() + def test_create_order_successful(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "ordStatus": "New", + "orderID": "8886774"} + req_mock.post(regex_url, body=json.dumps(create_response)) + + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + + self.async_run_with_timeout(self.exchange.execute_buy(order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + + self.assertTrue("OID1" in self.exchange._in_flight_orders) + + @aioresponses() + def test_create_order_exception(self, req_mock): + url = web_utils.rest_url( + CONSTANTS.ORDER_URL, domain=self.domain + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + create_response = {"updateTime": int(self.start_timestamp), + "ordStatus": "Canceled", + "orderID": "8886774"} + + req_mock.post(regex_url, body=json.dumps(create_response)) + + margin_asset = self.quote_asset + mocked_response = self._get_exchange_info_mock_response(margin_asset) + trading_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(mocked_response)) + self.exchange._trading_rules[self.trading_pair] = trading_rules[0] + + self.async_run_with_timeout(self.exchange.execute_sell(order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("10000"), + order_type=OrderType.LIMIT, + price=Decimal("1010"))) + + self.assertTrue("OID1" not in self.exchange._in_flight_orders) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexOrderStatus.Canceled + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexOrderStatus.Filled + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=BitmexOrderStatus.FAILURE + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.exchange.in_flight_orders) + self.assertNotIn("OID2", self.exchange.in_flight_orders) + self.assertNotIn("OID3", self.exchange.in_flight_orders) + self.assertNotIn("OID4", self.exchange.in_flight_orders) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 4 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + position_action="OPEN", + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + @aioresponses() + def test_update_balances(self, mock_api): + url = web_utils.rest_url(CONSTANTS.ACCOUNT_INFO_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = [ + { + "currency": "USDT", + "amount": 100000000, + "pendingCredit": 2000000, + "pendingDebit": 0 + }, + { + "currency": "XBT", + "amount": 1000000000, + "pendingCredit": 20000000, + "pendingDebit": 0 + } + ] + + mock_api.get(regex_url, body=json.dumps(response)) + + url_2 = web_utils.rest_url(CONSTANTS.TOKEN_INFO_URL, domain=self.domain) + regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace("?", r"\?")) + + response_2 = [ + { + "asset": "USDT", + "scale": 6 + }, + { + "asset": "XBT", + "scale": 8, + } + ] + + mock_api.get(regex_url_2, body=json.dumps(response_2)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("98"), available_balances["USDT"]) + self.assertEqual(Decimal("9.8"), available_balances["XBT"]) + self.assertEqual(Decimal("100"), total_balances["USDT"]) + self.assertEqual(Decimal("10"), total_balances["XBT"]) + + def test_adjust_quote_based_amounts(self): + self.exchange._trading_pairs.append("XBT-USDT") + mocked_response = self._get_exchange_info_mock_response() + + task = self.ev_loop.create_task(self.exchange._format_trading_rules(mocked_response)) + trading_rules = self.async_run_with_timeout(task) + self.exchange._trading_rules["XBT-USDT"] = trading_rules[1] + base, quote = self.exchange.adjust_quote_based_amounts("XBT-USDT", Decimal('1000'), Decimal('10')) + self.exchange._trading_pairs.remove("XBT-USDT") diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_book_tracker.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_book_tracker.py new file mode 100644 index 0000000..a309486 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_book_tracker.py @@ -0,0 +1,196 @@ +import asyncio +import time +import unittest +from collections import deque +from typing import Deque, Optional, Union + +from hummingbot.connector.exchange.bitmex.bitmex_order_book import BitmexOrderBook +from hummingbot.connector.exchange.bitmex.bitmex_order_book_tracker import BitmexOrderBookTracker +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BitmexOrderBookTrackerUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "USD" + cls.domain = "bitmex_testnet" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ev_loop = asyncio.get_event_loop() + + def setUp(self) -> None: + super().setUp() + self.tracker: BitmexOrderBookTracker = BitmexOrderBookTracker(trading_pairs=[self.trading_pair]) + self.tracking_task: Optional[asyncio.Task] = None + + # Simulate start() + self.tracker._order_books[self.trading_pair] = BitmexOrderBook() + self.tracker._tracking_message_queues[self.trading_pair] = asyncio.Queue() + self.tracker._past_diffs_windows[self.trading_pair] = deque() + self.tracker._order_books_initialized.set() + + def tearDown(self) -> None: + self.tracking_task and self.tracking_task.cancel() + super().tearDown() + + def _simulate_message_enqueue(self, message_queue: Union[asyncio.Queue, Deque], msg: OrderBookMessage): + if isinstance(message_queue, asyncio.Queue): + self.ev_loop.run_until_complete(message_queue.put(msg)) + elif isinstance(message_queue, Deque): + message_queue.append(msg) + else: + raise NotImplementedError + + def test_exchange_name(self): + self.assertEqual("bitmex", self.tracker.exchange_name) + + def test_order_book_diff_router_trading_pair_not_found_append_to_saved_message_queue(self): + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + self.tracker._tracking_message_queues.clear() + + task = self.ev_loop.create_task( + self.tracker._track_single_book("COINALPHA-USD") + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertEqual(0, len(self.tracker._tracking_message_queues)) + task.cancel() + + def test_order_book_diff_router_snapshot_uid_above_diff_message_update_id(self): + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + task = self.ev_loop.create_task( + self.tracker._track_single_book("COINALPHA-USD") + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + task.cancel() + + def test_order_book_diff_router_snapshot_uid_below_diff_message_update_id(self): + # Updates the snapshot_uid + self.tracker.order_books[self.trading_pair].apply_snapshot([], [], 2) + expected_msg: OrderBookMessage = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": 1, + "trading_pair": self.trading_pair, + } + ) + + self._simulate_message_enqueue(self.tracker._order_book_diff_stream, expected_msg) + + task = self.ev_loop.create_task( + self.tracker._order_book_diff_router() + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertEqual(0, self.tracker._tracking_message_queues[self.trading_pair].qsize()) + task.cancel() + + def test_track_single_book_snapshot_message_no_past_diffs(self): + snapshot_msg: OrderBookMessage = BitmexOrderBook.snapshot_message_from_exchange( + msg={ + "trading_pair": "COINALPHA-USD", + "update_id": 2, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=time.time() + ) + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertTrue(1 < self.tracker.order_books[self.trading_pair].snapshot_uid) + + def test_track_single_book_snapshot_message_with_past_diffs(self): + past_diff_msg: OrderBookMessage = BitmexOrderBook.diff_message_from_exchange( + msg={ + "lastUpdateId": 1, + "data_dict": { + "symbol": "COINALPHA-USD", + "bids": [ + ["4.00000100", "431.00000000"] + ], + "asks": [ + ["4.00000300", "12.00000000"] + ] + } + }, + timestamp=time.time() + ) + snapshot_msg: OrderBookMessage = BitmexOrderBook.snapshot_message_from_exchange( + msg={ + "trading_pair": "COINALPHA-USD", + "update_id": 2, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=time.time() + ) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self._simulate_message_enqueue(self.tracker._past_diffs_windows[self.trading_pair], past_diff_msg) + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], snapshot_msg) + + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.assertTrue(1 < self.tracker.order_books[self.trading_pair].snapshot_uid) + + def test_track_single_book_diff_message(self): + diff_msg: OrderBookMessage = BitmexOrderBook.diff_message_from_exchange( + msg={ + "lastUpdateId": 1, + "data_dict": { + "symbol": "COINALPHA-USD", + "bids": [ + ["4.00000100", "431.00000000"] + ], + "asks": [ + ["4.00000300", "12.00000000"] + ] + } + }, + timestamp=time.time() + ) + + self._simulate_message_enqueue(self.tracker._tracking_message_queues[self.trading_pair], diff_msg) + + self.tracking_task = self.ev_loop.create_task( + self.tracker._track_single_book(self.trading_pair) + ) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_status.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_status.py new file mode 100644 index 0000000..1b804ff --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_order_status.py @@ -0,0 +1,33 @@ +import unittest + +from hummingbot.connector.exchange.bitmex.bitmex_order_status import BitmexOrderStatus + + +class BitmexOrderStatusUnitTests(unittest.TestCase): + def test_ge(self): + status_1 = BitmexOrderStatus.New + status_2 = BitmexOrderStatus.PartiallyFilled + + self.assertTrue(status_2 >= status_1) + self.assertEqual(status_2.__ge__(1), NotImplemented) + + def test_gt(self): + status_1 = BitmexOrderStatus.Canceled + status_2 = BitmexOrderStatus.FAILURE + + self.assertTrue(status_2 > status_1) + self.assertEqual(status_2.__gt__(1), NotImplemented) + + def test_le(self): + status_1 = BitmexOrderStatus.New + status_2 = BitmexOrderStatus.Canceled + + self.assertTrue(status_1 <= status_2) + self.assertEqual(status_2.__le__(1), NotImplemented) + + def test_lt(self): + status_1 = BitmexOrderStatus.PartiallyFilled + status_2 = BitmexOrderStatus.FAILURE + + self.assertTrue(status_1 < status_2) + self.assertEqual(status_2.__lt__(1), NotImplemented) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_user_stream_data_source.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_user_stream_data_source.py new file mode 100644 index 0000000..4b21122 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_user_stream_data_source.py @@ -0,0 +1,191 @@ +import asyncio +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, patch + +import ujson +from aioresponses.core import aioresponses + +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.exchange.bitmex.bitmex_auth import BitmexAuth +from hummingbot.connector.exchange.bitmex.bitmex_user_stream_data_source import BitmexUserStreamDataSource +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class BitmexUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.domain = CONSTANTS.TESTNET_DOMAIN + + cls.api_key = "TEST_API_KEY" + cls.secret_key = "TEST_SECRET_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.emulated_time = 1640001112.223 + self.auth = BitmexAuth(api_key=self.api_key, + api_secret=self.secret_key) + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.data_source = BitmexUserStreamDataSource( + auth=self.auth, domain=self.domain, throttler=self.throttler + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mock_done_event = asyncio.Event() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _mock_responses_done_callback(self, *_, **__): + self.mock_done_event.set() + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _error_response(self) -> Dict[str, Any]: + resp = {"code": "ERROR CODE", "msg": "ERROR MESSAGE"} + + return resp + + def _simulate_user_update_event(self): + # Order Trade Update + resp = { + "table": "execution", + "data": [{ + "orderID": "1", + "clordID": "2", + "price": 20, + "orderQty": 100, + "symbol": "COINALPHA_HBOT", + "side": "Sell", + "leavesQty": "1" + }], + } + return ujson.dumps(resp) + + def time(self): + # Implemented to emulate a TimeSynchronizer + return self.emulated_time + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_create_websocket_connection_log_exception(self, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR.", + ) + ) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_create_websocket_connection_failed(self, mock_api, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + + msg_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except asyncio.exceptions.TimeoutError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR.", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, _, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + mock_ws.return_value.closed = False + mock_ws.return_value.close.side_effect = Exception + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(msg_queue.get()) + except Exception: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds... Error: TEST ERROR", + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_successful(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._simulate_user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertTrue(msg, self._simulate_user_update_event) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_utils.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_utils.py new file mode 100644 index 0000000..84149c3 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_utils.py @@ -0,0 +1,60 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Dict, List + +from aioresponses.core import aioresponses + +import hummingbot.connector.exchange.bitmex.bitmex_utils as utils +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS + + +class BitmexUtilsUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls._ev_loop = asyncio.get_event_loop() + + @aioresponses() + def test_get_trading_pair_index_and_tick_size(self, mock_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.EXCHANGE_INFO_URL}?count=500&start=0" + mock_response: List[Dict[str, Any]] = [ + { + "symbol": "COINALPHA_HBOT", + "tickSize": 5 + }, + ] + mock_api.get(url, status=200, body=json.dumps(mock_response)) + id_tick = self._ev_loop.run_until_complete(utils.get_trading_pair_index_and_tick_size("COINALPHA_HBOT")) + self.assertEqual(id_tick.index, 0) + self.assertEqual(id_tick.tick_size, 5) + + @aioresponses() + def test_get_trading_pair_multipliers(self, mock_api): + url = web_utils.rest_url( + CONSTANTS.EXCHANGE_INFO_URL, domain="bitmex_perpetual" + ) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: Dict[str, Any] = [ + { + "symbol": "COINALPHA_HBOT", + "rootSymbol": "COINALPHA", + "quoteCurrency": "HBOT", + "settlCurrency": "HBOT", + "lotSize": 1.0, + "tickSize": 0.0001, + "minProvideSize": 0.001, + "maxOrderQty": 1000000, + "underlyingToPositionMultiplier": 100, + "quoteToSettleMultiplier": 1000, + "positionCurrency": "COINALPHA" + }, + ] + mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) + tp_multipliers = self._ev_loop.run_until_complete(utils.get_trading_pair_multipliers("COINALPHA_HBOT")) + self.assertTrue(tp_multipliers.base_multiplier > 0) diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_web_utils.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_web_utils.py new file mode 100644 index 0000000..2a94cf9 --- /dev/null +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_web_utils.py @@ -0,0 +1,80 @@ +import asyncio +import unittest +from typing import Awaitable + +import hummingbot.connector.exchange.bitmex.bitmex_web_utils as web_utils +import hummingbot.connector.exchange.bitmex.constants as CONSTANTS +from hummingbot.connector.exchange.bitmex.bitmex_web_utils import BitmexRESTPreProcessor +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory + + +class BitmexWebUtilsUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.pre_processor = BitmexRESTPreProcessor() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_bitmex_perpetual_rest_pre_processor_non_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.GET, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/x-www-form-urlencoded") + + def test_bitmex_perpetual_rest_pre_processor_post_request(self): + request: RESTRequest = RESTRequest( + method=RESTMethod.POST, + url="/TEST_URL", + ) + + result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) + + self.assertIn("Content-Type", result_request.headers) + self.assertEqual(result_request.headers["Content-Type"], "application/json") + + def test_rest_url_main_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.BASE_URL}{path_url}" + self.assertEqual(expected_url, web_utils.rest_url(path_url)) + self.assertEqual(expected_url, web_utils.rest_url(path_url)) + + def test_rest_url_testnet_domain(self): + path_url = "/TEST_PATH_URL" + + expected_url = f"{CONSTANTS.TESTNET_BASE_URL}{path_url}" + self.assertEqual( + expected_url, web_utils.rest_url(path_url=path_url, domain="testnet") + ) + + def test_wss_url_main_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint)) + + def test_wss_url_testnet_domain(self): + endpoint = "TEST_SUBSCRIBE" + + expected_url = f"{CONSTANTS.TESTNET_WS_URL}{endpoint}" + self.assertEqual(expected_url, web_utils.wss_url(endpoint=endpoint, domain="testnet")) + + def test_build_api_factory(self): + api_factory = web_utils.build_api_factory() + + self.assertIsInstance(api_factory, WebAssistantsFactory) + self.assertIsNone(api_factory._auth) + + self.assertTrue(1, len(api_factory._rest_pre_processors)) diff --git a/test/hummingbot/connector/exchange/btc_markets/__init__.py b/test/hummingbot/connector/exchange/btc_markets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_order_book_data_source.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_order_book_data_source.py new file mode 100644 index 0000000..35955a3 --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_order_book_data_source.py @@ -0,0 +1,584 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.btc_markets import ( + btc_markets_constants as CONSTANTS, + btc_markets_web_utils as web_utils, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_api_order_book_data_source import ( + BtcMarketsAPIOrderBookDataSource, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_exchange import BtcMarketsExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class BtcMarketsAPIOrderBookDataSourceTest(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + cls.api_key = "someKey" + cls.api_secret_key = "XXXX" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.async_task = None + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.connector = BtcMarketsExchange( + client_config_map=self.client_config_map, + btc_markets_api_key=self.api_key, + btc_markets_api_secret=self.api_secret_key, + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.data_source = BtcMarketsAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map( + bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + BtcMarketsAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _order_book_snapshot_example(self): + return { + "marketId": "BAT-AUD", + "snapshotId": 1567334110144000, + "bids": [["50005.12", "403.0416"]], + "asks": [["50006.34", "0.2297"]] + } + + def _setup_time_mock(self, mock_api): + time_url = web_utils.public_rest_url(path_url=CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{time_url}".replace(".", r"\.").replace("?", r"\?")) + resp = { + "timestamp": "2019-09-01T10:35:04.940000Z" + } + mock_api.get(regex_url, body=json.dumps(resp)) + + def test_channel_originating_message_returns_correct(self): + event_type = { + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._diff_messages_queue_key, event_message) + + event_type = { + "messageType": CONSTANTS.SNAPSHOT_EVENT_TYPE + } + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._snapshot_messages_queue_key, event_message) + + event_type = { + "messageType": CONSTANTS.TRADE_EVENT_TYPE + } + event_message = self.data_source._channel_originating_message(event_type) + self.assertEqual(self.data_source._trade_messages_queue_key, event_message) + + # LAST TRADED PRICES + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + self._setup_time_mock(mock_api) + + url = web_utils.public_rest_url(path_url=CONSTANTS.MARKETS_URL) + url = f"{url}/{self.ex_trading_pair}/ticker" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = { + "marketId": self.ex_trading_pair, + "bestBid": "0.2612", + "bestAsk": "0.2677", + "lastPrice": "0.265", + "volume24h": "6392.34930418", + "volumeQte24h": "1.39", + "price24h": "130", + "pricePct24h": "0.002", + "low24h": "0.2621", + "high24h": "0.2708", + "timestamp": "2019-09-01T10:35:04.940000Z" + } + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout( + coroutine=self.data_source.get_last_traded_prices([self.trading_pair]) + ) + + self.assertIn(self.trading_pair, ret) + self.assertEqual(ret[self.trading_pair], 0.265) + + @aioresponses() + def test_get_new_order_book_successful(self, mock_get): + self._setup_time_mock(mock_get) + + mock_response: Dict[str, Any] = self._order_book_snapshot_example() + url = web_utils.public_rest_url(path_url=CONSTANTS.MARKETS_URL) + url = f"{url}/{self.ex_trading_pair}/orderbook" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_get.get(regex_url, body=json.dumps(mock_response)) + + order_book = self.async_run_with_timeout(coroutine=self.data_source.get_new_order_book(self.trading_pair)) + + bid_entries = list(order_book.bid_entries()) + ask_entries = list(order_book.ask_entries()) + self.assertEqual(1, len(bid_entries)) + self.assertEqual(50005.12, bid_entries[0].price) + self.assertEqual(403.0416, bid_entries[0].amount) + self.assertEqual(int(mock_response["snapshotId"]), bid_entries[0].update_id) + self.assertEqual(1, len(ask_entries)) + self.assertEqual(50006.34, ask_entries[0].price) + self.assertEqual(0.2297, ask_entries[0].amount) + self.assertEqual(int(mock_response["snapshotId"]), ask_entries[0].update_id) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subscription_result = { + "messageType": "subscribe", + "marketIds": [self.trading_pair], + "channels": [CONSTANTS.DIFF_EVENT_TYPE, CONSTANTS.SNAPSHOT_EVENT_TYPE, CONSTANTS.TRADE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(subscription_result)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_trade_subscription = { + "messageType": "subscribe", + "marketIds": [self.ex_trading_pair], + "channels": [CONSTANTS.DIFF_EVENT_TYPE, CONSTANTS.SNAPSHOT_EVENT_TYPE, CONSTANTS.TRADE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels for all trading pairs ..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + # "marketId": self.trading_pair, + "timestamp": '2019-04-08T20:54:27.632Z', + "tradeId": 3153171493, + "price": '7370.11', + "volume": '0.10901605', + "side": 'Ask', + "messageType": CONSTANTS.TRADE_EVENT_TYPE + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_queue = AsyncMock() + trade_event = { + "marketId": self.ex_trading_pair, + "timestamp": '2019-04-08T20:54:27.632Z', + "tradeId": 3153171493, + "price": '7370.11', + "volume": '0.10901605', + "side": 'Ask', + "messageType": CONSTANTS.TRADE_EVENT_TYPE + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + try: + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(msg_queue.empty()) + self.assertEqual(3153171493, msg.trade_id) + self.assertEqual(1554756867.632, msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + # "marketId": self.ex_trading_pair, + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = { + "marketId": self.ex_trading_pair, + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(diff_event["snapshotId"], msg.update_id) + self.assertEqual(1578512833.986, msg.timestamp) + + # ORDER BOOK SNAPSHOT + @staticmethod + def _snapshot_response() -> Dict: + return { + "marketId": "COINALPHA-HBOT", + "snapshotId": 1567334110144000, + "bids": [ + ["50005.12", "403.0416"] + ], + "asks": [ + ["50006.34", "0.2297"] + ] + } + + @staticmethod + def _snapshot_response_processed() -> Dict: + return { + "marketId": "COINALPHA-HBOT", + "snapshotId": 1567334110144000, + "bids": [["50005.12", "403.0416"]], + "asks": [["50006.34", "0.2297"]] + } + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api, sleep_mock): + mock_response: Dict[Any] = {} + url = web_utils.public_rest_url(f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, body=json.dumps(mock_response)) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + sleep_mock.side_effect = [asyncio.CancelledError] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._snapshot_response(), asyncio.CancelledError] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = [asyncio.CancelledError] + url = web_utils.public_rest_url(f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, exception=Exception) + + try: + self.async_run_with_timeout(self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_successful_rest(self, mock_api, _): + self._setup_time_mock(mock_api) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.TimeoutError + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(snapshot_data["snapshotId"]), msg.update_id) + self.assertEqual(1567334110144000.0, msg.timestamp) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful_ws(self, mock_api): + mock_queue = AsyncMock() + snapshot_event = { + "marketId": self.ex_trading_pair, + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.SNAPSHOT_EVENT_TYPE + } + mock_queue.get.side_effect = [snapshot_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + url = web_utils.public_rest_url(f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get(), timeout=6) + + self.assertEqual(snapshot_event["snapshotId"], msg.update_id) + self.assertEqual(1578512833.986, msg.timestamp) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_order_book_snapshot_exception(self, mock_api, sleep_mock): + self._setup_time_mock(mock_api) + + url = web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, exception=Exception) + + self.async_run_with_timeout(self.data_source._order_book_snapshot(self.trading_pair)) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_order_book_snapshot(self, mock_api): + self._setup_time_mock(mock_api) + + url = web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + orderbook_message = self.async_run_with_timeout(self.data_source._order_book_snapshot(self.trading_pair)) + + self.assertEqual(orderbook_message.type, OrderBookMessageType.SNAPSHOT) + self.assertEqual(orderbook_message.trading_pair, self.trading_pair) + self.assertEqual(orderbook_message.content["snapshotId"], snapshot_data["snapshotId"]) + + @aioresponses() + def test_get_snapshot(self, mock_api): + self._setup_time_mock(mock_api) + + url = web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{self.ex_trading_pair}/orderbook") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + snapshot_response = self.async_run_with_timeout(self.data_source.get_snapshot(self.trading_pair)) + + self.assertEqual(snapshot_response, snapshot_data) diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_user_stream_data_source.py new file mode 100644 index 0000000..13a301a --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_api_user_stream_data_source.py @@ -0,0 +1,394 @@ +import asyncio +import base64 +import hashlib +import hmac +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.btc_markets import btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets.btc_markets_api_user_stream_data_source import ( + BtcMarketsAPIUserStreamDataSource, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_auth import BtcMarketsAuth +from hummingbot.connector.exchange.btc_markets.btc_markets_exchange import BtcMarketsExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class BtcMarketsAPIUserStreamDataSourceTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + cls.api_key = "someKey" + cls.api_secret_key = "XXXX" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = BtcMarketsAuth( + self.api_key, + self.api_secret_key, + time_provider=self.mock_time_provider) + + self.connector = BtcMarketsExchange( + client_config_map=self.client_config_map, + btc_markets_api_key="", + btc_markets_api_secret="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = BtcMarketsAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map( + bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _raise_exception(self, exception_class): + raise exception_class + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_error_when_login_fails(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + erroneous_login_response = {"messageType": "error", "code": 1, "message": "authentication failed. invalid key"} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(erroneous_login_response)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(0, output_queue.qsize()) + + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_invalid_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + event_with_invalid_messageType = { + "messageType": "Invalid message type" + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(event_with_invalid_messageType)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("hummingbot.connector.exchange.btc_markets.btc_markets_auth.BtcMarketsAuth._time") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribe_events(self, ws_connect_mock, auth_time_mock): + auth_time_mock.side_effect = [1000] + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "event": "subscribe", + "topic": CONSTANTS.ORDER_CHANGE_EVENT_TYPE, + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + + now = int((self.mock_time_provider.time.return_value) * 1e3) + strToSign = f"/users/self/subscribe\n{now}" + signature = base64.b64encode(hmac.new( + base64.b64decode(self.api_secret_key), strToSign.encode("utf8"), digestmod=hashlib.sha512).digest()).decode('utf8') + auth_subscription = { + "signature": signature, + "key": self.api_key, + "marketIds": [self.ex_trading_pair], + "timestamp": str(now), + "messageType": 'subscribe', + "channels": [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.FUND_CHANGE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + self.assertEqual(auth_subscription, sent_subscription_messages[0]) + + self.assertTrue(self._is_logged("INFO", "Subscribed to private account and orders channels...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_heartbeat_payload(self, mock_ws): + + mock_pong = { + "messageType": CONSTANTS.HEARTBEAT + } + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_pong)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + try: + with self.assertRaises(Exception): + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_initialization(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(messages)) + self.ev_loop.run_until_complete(self.listening_task) + + @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_authentication(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(messages)) + self.ev_loop.run_until_complete(self.listening_task) + + def test_subscribe_channels_raises_cancel_exception(self): + ws_assistant = AsyncMock() + ws_assistant.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source._subscribe_channels(ws_assistant)) + self.ev_loop.run_until_complete(self.listening_task) + + # @patch('aiohttp.ClientSession.ws_connect', new_callable=AsyncMock) + # @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + # def test_listening_process_logs_exception_during_events_subscription(self, sleep_mock, mock_ws): + # self.connector._set_trading_pair_symbol_map({}) + + # messages = asyncio.Queue() + # sleep_mock.side_effect = asyncio.CancelledError + # mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + # self.mocking_assistant.add_websocket_aiohttp_message( + # mock_ws.return_value, + # json.dumps({'messageType': 'subscribe'})) + + # self.listening_task = self.ev_loop.create_task( + # self.data_source.listen_for_user_stream(messages)) + + # try: + # self.async_run_with_timeout(self.listening_task, timeout=3) + # except asyncio.CancelledError: + # pass + + # self.assertTrue(self._is_logged( + # "ERROR", + # "Unexpected error occurred subscribing to order book trading and delta streams...")) + # self.assertTrue(self._is_logged( + # "ERROR", + # "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_order_change_fund_change(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subscription_result = { + "messageType": "subscribe", + "marketIds": [self.trading_pair], + "channels": [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.FUND_CHANGE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(subscription_result)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_order_change_subscription = { + "messageType": "subscribe", + "marketIds": [self.ex_trading_pair], + "channels": [CONSTANTS.ORDER_CHANGE_EVENT_TYPE, CONSTANTS.FUND_CHANGE_EVENT_TYPE, CONSTANTS.HEARTBEAT] + } + self.assertEqual(expected_order_change_subscription["channels"], sent_subscription_messages[0]["channels"]) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to private account and orders channels...")) + + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(asyncio.Queue())) + + try: + with self.assertRaises(Exception): + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to private account and orders channels ...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_order_event(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + order_event = { + "orderId": 79003, + "marketId": 'BTC-AUD', + "side": 'Bid', + "type": 'Limit', + "openVolume": '1', + "status": 'Placed', + "triggerStatus": '', + "trades": [], + "timestamp": '2019-04-08T20:41:19.339Z', + "messageType": 'orderChange' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(1, msg_queue.qsize()) + order_event_message = msg_queue.get_nowait() + self.assertEqual(order_event, order_event_message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_logs_details_for_order_event_with_errors(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + order_event = { + "messageType": 'error', + "code": 3, + "message": 'invalid marketIds' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=mock_ws.return_value, + message=json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + self.assertTrue(self._is_logged("ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_auth.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_auth.py new file mode 100644 index 0000000..278557c --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_auth.py @@ -0,0 +1,138 @@ +import asyncio +import hashlib +import hmac +from collections import OrderedDict +from typing import Any, Awaitable, Dict, Mapping, Optional +from unittest import TestCase +from unittest.mock import MagicMock +from urllib.parse import urlencode + +from hummingbot.connector.exchange.btc_markets import btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets.btc_markets_auth import BtcMarketsAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class BtcMarketsAuthTest(TestCase): + + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.secret_key = "XXXX" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = BtcMarketsAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _get_request(self, params: Dict[str, Any]) -> RESTRequest: + return RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + params=params, + throttler_limit_id="/api/endpoint" + ) + + def test_add_auth_params_to_get_request_without_params(self): + request = self._get_request({}) + params_expected = self._params_expected(request.params) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("BM-AUTH-TIMESTAMP", params_expected) + self.assertEqual(self.api_key, params_expected["BM-AUTH-APIKEY"]) + self.assertIn("BM-AUTH-SIGNATURE", params_expected) + + def test_add_auth_params_to_get_request_with_params(self): + params = { + "param_z": "value_param_z", + "param_a": "value_param_a" + } + request = self._get_request(params) + + params_expected = self._params_expected(request.params) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("BM-AUTH-TIMESTAMP", params_expected) + self.assertEqual(self.api_key, params_expected["BM-AUTH-APIKEY"]) + self.assertIn("BM-AUTH-SIGNATURE", params_expected) + self.assertEqual(params_expected['param_z'], request.params["param_z"]) + self.assertEqual(params_expected['param_a'], request.params["param_a"]) + + def test_add_auth_params_to_post_request(self): + params = {"param_z": "value_param_z", "param_a": "value_param_a"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=params, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + params_auth = self._params_expected(request.params) + params_request = self._params_expected(request.data) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertIn("BM-AUTH-TIMESTAMP", params_auth) + self.assertEqual(self.api_key, params_auth["BM-AUTH-APIKEY"]) + self.assertIn("BM-AUTH-SIGNATURE", params_auth) + self.assertEqual(params_request['param_z'], request.data["param_z"]) + self.assertEqual(params_request['param_a'], request.data["param_a"]) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + self.assertEqual(payload, request.payload) + + def _generate_signature(self, params: Dict[str, Any]) -> str: + encoded_params_str = urlencode(params) + digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() + return digest + + def _params_expected(self, request_params: Optional[Mapping[str, str]]) -> Dict: + request_params = request_params if request_params else {} + params = { + 'BM-AUTH-TIMESTAMP': 1000000, + 'BM-AUTH-APIKEY': self.api_key, + } + params.update(request_params) + params = OrderedDict(sorted(params.items(), key=lambda t: t[0])) + params['BM-AUTH-SIGNATURE'] = self._generate_signature(params=params) + return params + + def test_get_referral_code_headers(self): + referer = { + "referer": CONSTANTS.HBOT_BROKER_ID + } + response = self.auth.get_referral_code_headers() + self.assertEqual(response, referer) + + def test_generate_auth_headers(self): + headers = { + "Accept": "application/json", + "Accept-Charset": "UTF-8", + "Content-Type": "application/json", + "BM-AUTH-APIKEY": self.api_key, + "BM-AUTH-TIMESTAMP": "123", + "BM-AUTH-SIGNATURE": "sig" + } + + response = self.auth._generate_auth_headers(123, 'sig') + self.assertEqual(response, headers) + + def test_generate_auth_dict_ws(self): + payload = "/users/self/subscribe" + "\n" + "123" + response = self.auth._generate_auth_dict_ws(123) + expected_response = self.auth._generate_signature(payload) + self.assertEqual(response, expected_response) diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_exchange.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_exchange.py new file mode 100644 index 0000000..3b0bda7 --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_exchange.py @@ -0,0 +1,1030 @@ +import json +import math +import re +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.btc_markets import ( + btc_markets_constants as CONSTANTS, + btc_markets_web_utils as web_utils, +) +from hummingbot.connector.exchange.btc_markets.btc_markets_exchange import BtcMarketsExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class BtcMarketsExchangeTest(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(CONSTANTS.MARKETS_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def latest_prices_url(self): + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + url = web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{trading_pair}/ticker") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def trading_rules_url(self): + url = web_utils.public_rest_url(CONSTANTS.MARKETS_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.ORDERS_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def trade_url(self): + url = web_utils.private_rest_url(CONSTANTS.TRADES_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.") + r"\?.*") + return regex_url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.BALANCE_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def all_symbols_request_mock_response(self): + return [ + { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": self.base_asset, + "quoteAssetName": self.quote_asset, + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + } + ] + + @property + def latest_prices_request_mock_response(self): + return { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "bestBid": "0.2612", + "bestAsk": "0.2677", + "lastPrice": str(self.expected_latest_price), + "volume24h": "6392.34930418", + "volumeQte24h": "1.39", + "price24h": "130", + "pricePct24h": "0.002", + "low24h": "0.2621", + "high24h": "0.2708", + "timestamp": "2019-09-01T10:35:04.940000Z" + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = [ + { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": self.base_asset, + "quoteAssetName": self.quote_asset, + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + }, + { + "marketId": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "baseAssetName": self.base_asset, + "quoteAssetName": self.quote_asset, + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + } + ] + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return { + "timestamp": "2019-09-01T18:34:27.045000Z" + } + + @property + def trading_rules_request_mock_response(self): + return [ + { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": self.base_asset, + "quoteAssetName": self.quote_asset, + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + } + ] + + @property + def trading_rules_request_erroneous_mock_response(self): + return [ + { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": "BTC", + "quoteAssetName": "AUD", + # "minOrderAmount": "0.0001", + # "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + } + ] + + @property + def order_creation_request_successful_mock_response(self): + return { + "orderId": self.expected_exchange_order_id, + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "clientOrderId": "11", + "creationTime": "2019-09-01T17:38:17.404000Z", + "price": "20000", + "triggerPrice": "20000", + "amount": "100", + "openAmount": "100", + "targetAmount": "100", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "Bid", + "postOnly": False + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return [ + { + "assetName": self.base_asset, + "balance": "15", + "available": "10", + "locked": "0" + }, + { + "assetName": self.quote_asset, + "balance": "2000", + "available": "2000", + "locked": "0" + } + ] + + @property + def balance_request_mock_response_only_base(self): + return [ + { + "assetName": self.base_asset, + "balance": "15", + "available": "10", + "locked": "0" + } + ] + + @property + def balance_event_websocket_update(self): + return { + "fundtransferId": 276811, + "type": 'Deposit', + "status": 'Complete', + "timestamp": '2019-04-16T01:38:02.931Z', + "amount": '5.00', + "currency": 'AUD', + "fee": '0', + "messageType": 'fundChange' + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.MARKET, OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + price_decimals = Decimal(str( + self.trading_rules_request_mock_response[0]["priceDecimals"])) + # E.g. a price decimal of 2 means 0.01 incremental. + price_step = Decimal("1") / Decimal(str(math.pow(10, price_decimals))) + amount_decimal = Decimal(str( + self.trading_rules_request_mock_response[0]["amountDecimals"])) + amount_step = Decimal("1") / Decimal(str(math.pow(10, amount_decimal))) + return TradingRule( + trading_pair=self.trading_pair, + min_order_size = Decimal(self.trading_rules_request_mock_response[0]["minOrderAmount"]), + max_order_size = Decimal(self.trading_rules_request_mock_response[0]["maxOrderAmount"]), + # min_order_value = Decimal(self.trading_rules_request_mock_response[0]["minOrderAmount"]), + min_base_amount_increment = amount_step, + min_price_increment = price_step, + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 1736871726781 + + @property + def expected_exchange_trade_id(self): + return 31727 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return True + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return 30000 + + def private_url_with_param(self, url, param = "", seperator = '?'): + if param != "": + url = f"{web_utils.private_rest_url(url)}{seperator}{param}" + else: + url = web_utils.private_rest_url(url) + + return re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + def public_url_with_param(self, url, param = "", seperator = '?'): + if param != "": + url = f"{web_utils.public_rest_url(url)}{seperator}{param}" + else: + url = web_utils.public_rest_url(url) + + return re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for record in self.log_records + ) + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return base_token + "-" + quote_token + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return BtcMarketsExchange( + client_config_map=client_config_map, + btc_markets_api_key="testAPIKey", + btc_markets_api_secret="XXXX", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn("Content-Type", request_headers) + self.assertIn("BM-AUTH-APIKEY", request_headers) + self.assertIn("BM-AUTH-SIGNATURE", request_headers) + self.assertIn("BM-AUTH-TIMESTAMP", request_headers) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["marketId"]) + self.assertEqual("Limit", request_data["type"]) + self.assertIn(request_data["side"], ["Ask", "Bid"]) + self.assertEqual(Decimal("100"), Decimal(request_data["amount"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["clientOrderId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + # Client order id is passed in url with Http DELETE rather than POST with body + self.assertTrue(True) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + # Client order id is passed in url with Http GET rather than GET with body + self.assertTrue(True) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(order.exchange_order_id, request_params["orderId"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.exchange_order_id, '/') + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.exchange_order_id, '/') + response = { + "code": CONSTANTS.INVALID_ORDERID, + "message": "In valid Order", + } + mock_api.delete(url, status=400, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = { + "code": CONSTANTS.ORDER_NOT_FOUND, + "message": "Order not found", + } + mock_api.delete(self.order_creation_url, status=404, body=json.dumps(response), callback=callback) + return self.order_creation_url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + response = { + "code": CONSTANTS.ORDER_NOT_FOUND, + "message": "Order not found", + } + mock_api.get(self.order_creation_url, body=json.dumps(response), status=404, callback=callback) + return [self.order_creation_url] + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(self.order_creation_url, body=json.dumps(response), callback=callback) + return self.order_creation_url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.exchange_order_id, '/') + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.exchange_order_id, '/') + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = [] + mock_api.get(self.trade_url, body=json.dumps(response), callback=callback) + + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.exchange_order_id, '/') + mock_api.get(url, status=401, callback=callback) + + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(self.order_creation_url, body=json.dumps(response), callback=callback) + + self.configure_open_order_status_response(order, mock_api, callback) + + return self.order_creation_url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(self.trade_url, body=json.dumps(response), callback=callback) + + self.configure_open_order_status_response(order, mock_api, callback) + + return self.trade_url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + mock_api.get(self.trade_url, status=400, callback=callback) + + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, order.client_order_id, '/') + mock_api.get(url, status=400, callback=callback) + + return self.trade_url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(self.trade_url, body=json.dumps(response), callback=callback) + + self.configure_open_order_status_response(order, mock_api, callback) + + return self.trade_url + + # https://docs.btcmarkets.net/v3/#section/Order-Life-Cycle-Events + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orderId": self.expected_exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "openVolume": "1", + "status": "Placed", + "triggerStatus": "", + "trades": [], + "timestamp": "2019-04-08T20:41:19.339Z", + "messageType": "orderChange" + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orderId": self.expected_exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "openVolume": "1", + "status": "Cancelled", + "triggerStatus": "", + "trades": [], + "timestamp": "2019-04-08T20:41:41.857Z", + "messageType": "orderChange" + } + + # https://docs.btcmarkets.net/v3/#section/Order-Life-Cycle-Events + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderId": self.expected_exchange_order_id, + # "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "openVolume": "0", + "status": "Fully Matched", + "triggerStatus": "", + "timestamp": "2019-04-08T20:50:39.658Z", + "trades": [ + { + "tradeId": self.expected_exchange_trade_id, + "price": str(order.price), + "volume": str(order.amount), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "liquidityType": 'Taker', + "valueInQuoteAsset": Decimal(order.amount) * Decimal(order.price) + } + ], + "messageType": 'orderChange' + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "tradeId": self.expected_exchange_trade_id, + # "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "price": str(order.price), + "volume": str(order.amount), + "timestamp": "2019-04-08T20:50:39.658Z", + "messageType": 'trade' + } + + def test_time_synchronizer_related_request_error_detection(self): + exception = IOError("Error executing request POST https://api.btcm.ngin.io/v3/order. HTTP status is 400. " + 'Error: {"code":InvalidTimestamp,"message":"BM-AUTH-TIMESTAMP range. Within a minute"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.btcm.ngin.io/v3/order. HTTP status is 400. " + 'Error: {"code":InvalidAuthTimestamp,"message":"BM-AUTH-TIMESTAMP invalid format"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.btcm.ngin.io/v3/order. HTTP status is 400. " + 'Error: {"code":InvalidTimeWindow,"message":"BM-AUTH-TIMESTAMP range. Within a minute"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.btcm.ngin.io/v3/order. HTTP status is 400. " + 'Error: {"code":InvalidTimeInForceOption,"message":"Other message"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.btcm.ngin.io/v3/order. HTTP status is 400. " + 'Error: {"code":TradeNotFound,"message":"Other message"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "orderId": self.expected_exchange_order_id, + "clientOrderId": order.client_order_id + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return { + "orderId": exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-30T11:08:21.956000Z", + "price": str(order.price), + "amount": str(order.amount), + "openAmount": "1.034", + "status": "Cancelled" + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return { + "orderId": exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-30T11:08:21.956000Z", + "price": str(order.price), + "amount": str(order.amount), + "openAmount": "1.034", + "status": "Fully Matched" + } + + # https://docs.btcmarkets.net/v3/#tag/Trade-APIs + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return [ + { + "id": "36014819", + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "timestamp": "2019-06-25T16:01:02.977000Z", + "price": str(order.price), + "amount": str(order.amount), + "side": "Bid", + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "orderId": exchange_order_id, + "liquidityType": "Taker", + "clientOrderId": order.client_order_id + } + ] + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return { + "orderId": exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-30T11:08:21.956000Z", + "price": str(order.price), + "amount": str(order.amount), + "openAmount": "1.034", + "status": "Placed" + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return { + "orderId": exchange_order_id, + "clientOrderId": order.client_order_id, # leave this property here as it is being asserted in the the tests + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-30T11:08:21.956000Z", + "price": str(order.price), + "amount": str(order.amount), + "openAmount": "1.034", + "status": "Partially Matched" + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + exchange_order_id = order.exchange_order_id or self.expected_exchange_order_id + return [ + { + "id": "36014819", + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "timestamp": "2019-06-25T16:01:02.977000Z", + "price": str(self.expected_partial_fill_price), + "amount": str(self.expected_partial_fill_amount), + "side": "Bid", + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "orderId": exchange_order_id, + "liquidityType": "Taker", + "clientOrderId": order.client_order_id + } + ] + + @aioresponses() + def test_place_cancel(self, mock_api): + order = InFlightOrder( + client_order_id = 123, + exchange_order_id = 11223344, + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + trade_type = TradeType.BUY, + order_type = OrderType.LIMIT, + creation_timestamp = 123456789, + price = str(9999.0), + amount = str(10.0), + initial_state = OrderState.OPEN + ) + + orderId = "123456789" + + response = { + "clientOrderId": "123456789" + } + + url = self.private_url_with_param(CONSTANTS.ORDERS_URL, 11223344, '/') + + mock_api.delete(url, body=json.dumps(response)) + + cancelled = self.async_run_with_timeout(self.exchange._place_cancel(orderId, order)) + + self.assertTrue(cancelled) + + def test_get_fee(self): + expected_limit_order_fee = AddedToCostTradeFee(percent=self.exchange.estimate_fee_pct(True)) + + limit_order_fee = self.exchange._get_fee(self.base_asset, self.quote_asset, OrderType.LIMIT, TradeType.BUY, 1, 2) + self.assertEqual(limit_order_fee, expected_limit_order_fee) + + expected_market_order_fee = AddedToCostTradeFee(percent=self.exchange.estimate_fee_pct(False)) + market_order_fee = self.exchange._get_fee(self.base_asset, self.quote_asset, OrderType.MARKET, TradeType.BUY, 1, 2) + self.assertEqual(market_order_fee, expected_market_order_fee) + + def test_is_request_exception_related_to_time_synchronizer(self): + result = self.exchange._is_request_exception_related_to_time_synchronizer(Exception("InvalidTimeWindow")) + self.assertTrue(result) + + result = self.exchange._is_request_exception_related_to_time_synchronizer(Exception("InvalidAuthTimestamp")) + self.assertTrue(result) + + result = self.exchange._is_request_exception_related_to_time_synchronizer(Exception("InvalidAuthSignature")) + self.assertTrue(result) + + result = self.exchange._is_request_exception_related_to_time_synchronizer(Exception("RadomException")) + self.assertFalse(result) + + @aioresponses() + def test_request_order_fills(self, mock_api): + order = InFlightOrder( + client_order_id = 123, + exchange_order_id = 36014819, + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + trade_type = TradeType.BUY, + order_type = OrderType.LIMIT, + creation_timestamp = 123456789, + price = str(9999.0), + amount = str(10.0), + initial_state = OrderState.OPEN + ) + + response = [ + { + "id": "36014819", + "marketId": "XRP-AUD", + "timestamp": "2019-06-25T16:01:02.977000Z", + "price": "0.67", + "amount": "1.50533262", + "side": "Ask", + "fee": "0.00857285", + "orderId": "3648306", + "liquidityType": "Taker", + "clientOrderId": "48", + "valueInQuoteAsset": "0.44508" + } + ] + + url = self.public_url_with_param(CONSTANTS.TRADES_URL, f"orderId={order.exchange_order_id}") + + mock_api.get(url, body=json.dumps(response)) + + order_response = self.async_run_with_timeout(self.exchange._request_order_fills(order)) + + self.assertEqual(order_response[0]["id"], response[0]["id"]) + + @aioresponses() + def test_place_order(self, mock_api): + order = InFlightOrder( + client_order_id = 123, + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + trade_type = TradeType.BUY, + order_type = OrderType.LIMIT, + creation_timestamp = 123456789, + price = str(9999.0), + amount = str(10.0), + initial_state = OrderState.OPEN + ) + + response = { + "orderId": "123456789", + "marketId": "BTC-AUD", + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-30T11:08:21.956000Z", + "price": "100.12", + "amount": "1.034", + "openAmount": "1.034", + "status": "Accepted" + } + + mock_api.post(self.order_creation_url, body=json.dumps(response)) + + order_response = self.async_run_with_timeout(self.exchange._place_order( + order.client_order_id, order.trading_pair, 10.0, order.trade_type, order.order_type, 9999.9)) + + self.assertEqual(order_response[0], response["orderId"]) + + def test_format_trading_rules(self): + exchange_info = [ + { + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": "BTC", + "quoteAssetName": "AUD", + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + }, + { + "marketId": "LTC-AUD", + "baseAssetName": "LTC", + "quoteAssetName": "AUD", + "minOrderAmount": "0.001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Post Only" + } + ] + + trade_rules = self.async_run_with_timeout(self.exchange._format_trading_rules(exchange_info)) + + self.assertEqual(trade_rules[0].trading_pair, self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)) + self.assertEqual(trade_rules[0].min_order_size, Decimal(str(0.0001))) + self.assertEqual(trade_rules[0].max_order_size, Decimal(str(1000000))) + self.assertEqual(trade_rules[0].min_price_increment, Decimal("1") / Decimal(str(math.pow(10, 2)))) + self.assertEqual(trade_rules[0].min_base_amount_increment, Decimal("1") / Decimal(str(math.pow(10, 8)))) + + def test_format_trading_rules_exception(self): + exchange_info = [ + { + # "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "baseAssetName": "BTC", + "quoteAssetName": "AUD", + "minOrderAmount": "0.0001", + "maxOrderAmount": "1000000", + "amountDecimals": "8", + "priceDecimals": "2", + "status": "Online" + } + ] + + self.async_run_with_timeout(self.exchange._format_trading_rules(exchange_info)) + + self.assertTrue( + self._is_logged("ERROR", f"Error parsing the trading pair rule {exchange_info[0]}. Skipping.")) + + def test_create_order_fill_updates(self): + inflight_order = InFlightOrder( + client_order_id = 123, + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + trade_type = TradeType.BUY, + order_type = OrderType.LIMIT, + creation_timestamp = 123456789, + price = str(9999), + amount = str(10), + initial_state = OrderState.OPEN + ) + + order_update = [ + { + "id": "1", + "orderId": 123, + "clientOrderId": 6789, + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "timestamp": "2019-08-3T11:11:21.956000Z", + "price": str(9999), + "amount": str(10), + "openAmount": "1.034", + "fee": "77.77", + "status": "Fully Matched" + } + ] + + trade_updates = self.exchange._create_order_fill_updates(inflight_order, order_update) + + self.assertEqual(trade_updates[0].trade_id, order_update[0]["id"]) + self.assertEqual(trade_updates[0].trading_pair, self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)) + + def test_create_order_update(self): + inflight_order = InFlightOrder( + client_order_id = 123, + trading_pair = self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + trade_type = TradeType.BUY, + order_type = OrderType.LIMIT, + creation_timestamp = 123456789, + price = str(9999), + amount = str(10), + initial_state = OrderState.OPEN + ) + + order_update = { + "orderId": 123, + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "Bid", + "type": "Limit", + "creationTime": "2019-08-3T11:11:21.956000Z", + "price": str(9999), + "amount": str(10), + "openAmount": "1.034", + "status": "Fully Matched" + } + + order = self.exchange._create_order_update(inflight_order, order_update) + + self.assertEqual(order.new_state, CONSTANTS.ORDER_STATE[order_update["status"]]) + self.assertEqual(order.trading_pair, self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)) + + @aioresponses() + def test_update_balances(self, mock_api): + response = [ + { + "assetName": self.base_asset, + "available": 900, + "balance": 1000 + } + ] + + mock_api.get(self.balance_url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_balances()) + + self.assertEqual(self.exchange._account_available_balances[self.base_asset], 900) + self.assertEqual(self.exchange._account_balances[self.base_asset], 1000) + + @aioresponses() + def test_get_last_traded_price(self, mock_api): + response = { + "lastPrice": "9999.00" + } + + url = web_utils.public_rest_url(path_url=f"{CONSTANTS.MARKETS_URL}/{self.trading_pair}/ticker") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(response)) + + lastprice_response = self.async_run_with_timeout(self.exchange._get_last_traded_price(self.trading_pair)) + + self.assertEqual(lastprice_response, 9999.00) + + @aioresponses() + def test_get_fee_returns_fee_from_exchange_if_available_and_default_if_not(self, mocked_api): + url = web_utils.private_rest_url(CONSTANTS.FEES_URL) + regex_url = re.compile(f"^{url}") + resp = { + "volume30Day": "0.0098275", + "feeByMarkets": [ + { + "makerFeeRate": "0.002", + "takerFeeRate": "0.005", + "marketId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + } + ] + } + mocked_api.get(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + # Maker fee + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT_MAKER, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.002"), fee.percent) + + # Taker fee + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.005"), fee.percent) + + # Default maker fee + fee = self.exchange.get_fee( + base_currency="SOME", + quote_currency="OTHER", + order_type=OrderType.LIMIT_MAKER, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.0085"), fee.percent) # default maker fee + + # Default taker fee + fee = self.exchange.get_fee( + base_currency="SOME", + quote_currency="OTHER", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.0085"), fee.percent) # default maker fee diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_order_book.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_order_book.py new file mode 100644 index 0000000..19080d9 --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_order_book.py @@ -0,0 +1,149 @@ +from typing import Optional +from unittest import TestCase + +from hummingbot.connector.exchange.btc_markets import btc_markets_constants as CONSTANTS +from hummingbot.connector.exchange.btc_markets.btc_markets_order_book import BtcMarketsOrderBook +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType, OrderBookRow + + +class TestOrderbook(TestCase): + def test_snapshot_message_from_exchange_websocket(self): + diff_event = { + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + + diff_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.snapshot_message_from_exchange_websocket( + diff_event, diff_event["timestamp"], {"marketId": "BAT-AUD"} + ) + + self.assertEqual(diff_message.type, OrderBookMessageType.SNAPSHOT) + self.assertEqual(diff_message.trading_pair, "BAT-AUD") + self.assertEqual(diff_message.update_id, diff_event["snapshotId"]) + self.assertEqual(diff_message.bids[0], OrderBookRow(float(diff_event["bids"][0][0]), float(diff_event["bids"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.asks[0], OrderBookRow(float(diff_event["asks"][0][0]), float(diff_event["asks"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.content["snapshotId"], diff_event["snapshotId"]) + + def test_snapshot_message_from_exchange_rest(self): + diff_event = { + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + + diff_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.snapshot_message_from_exchange_rest( + diff_event, diff_event["timestamp"], {"marketId": "BAT-AUD"} + ) + + self.assertEqual(diff_message.type, OrderBookMessageType.SNAPSHOT) + self.assertEqual(diff_message.trading_pair, "BAT-AUD") + self.assertEqual(diff_message.update_id, diff_event["snapshotId"]) + self.assertEqual(diff_message.bids[0], OrderBookRow(float(diff_event["bids"][0][0]), float(diff_event["bids"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.asks[0], OrderBookRow(float(diff_event["asks"][0][0]), float(diff_event["asks"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.content["snapshotId"], diff_event["snapshotId"]) + + def test_diff_message_from_exchange(self): + diff_event = { + "snapshot": True, + "snapshotId": 1578512833978000, + "timestamp": '2020-01-08T19:47:13.986Z', + "bids": [ + ['99.57', '0.55', 1], + ['97.62', '3.20', 2], + ['97.07', '0.9', 1], + ['96.7', '1.9', 1], + ['95.8', '7.0', 1] + ], + "asks": [ + ['100', '3.79', 3], + ['101', '6.32', 2] + ], + "messageType": CONSTANTS.DIFF_EVENT_TYPE + } + + diff_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.diff_message_from_exchange( + diff_event, diff_event["timestamp"], {"marketId": "BAT-AUD"} + ) + + self.assertEqual(diff_message.type, OrderBookMessageType.DIFF) + self.assertEqual(diff_message.trading_pair, "BAT-AUD") + self.assertEqual(diff_message.update_id, diff_event["snapshotId"]) + self.assertEqual(diff_message.bids[0], OrderBookRow(float(diff_event["bids"][0][0]), float(diff_event["bids"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.asks[0], OrderBookRow(float(diff_event["asks"][0][0]), float(diff_event["asks"][0][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.bids[1], OrderBookRow(float(diff_event["bids"][1][0]), float(diff_event["bids"][1][1]), update_id=1578512833978000)) + self.assertEqual(diff_message.content["snapshotId"], diff_event["snapshotId"]) + + def test_sell_trade_message_from_exchange(self): + trade_event = { + "marketId": "BAT-AUD", + "timestamp": '2019-04-08T20:54:27.632Z', + "tradeId": 3153171493, + "price": '7370.11', + "volume": '0.10901605', + "side": 'Ask', + "messageType": CONSTANTS.TRADE_EVENT_TYPE + } + + trade_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.trade_message_from_exchange( + trade_event, trade_event["timestamp"], {"marketId": "BAT-AUD"} + ) + + self.assertEqual(trade_message.type, OrderBookMessageType.TRADE) + self.assertEqual(trade_message.trading_pair, "BAT-AUD") + self.assertEqual(trade_message.trade_id, 3153171493) + self.assertEqual(trade_message.content["price"], "7370.11") + self.assertEqual(trade_message.content["amount"], "0.10901605") + self.assertEqual(trade_message.content["trade_type"], float(TradeType.SELL.value)) + + def test_buy_trade_message_from_exchange(self): + trade_event = { + "marketId": "BAT-AUD", + "timestamp": '2019-04-08T20:54:27.632Z', + "tradeId": 3153171493, + "price": '7370.11', + "volume": '0.10901605', + "side": 'Bid', + "messageType": CONSTANTS.TRADE_EVENT_TYPE + } + + trade_message: Optional[OrderBookMessage] = BtcMarketsOrderBook.trade_message_from_exchange( + trade_event, trade_event["timestamp"], {"marketId": "BAT-AUD"} + ) + + self.assertEqual(trade_message.type, OrderBookMessageType.TRADE) + self.assertEqual(trade_message.trading_pair, "BAT-AUD") + self.assertEqual(trade_message.trade_id, 3153171493) + self.assertEqual(trade_message.content["price"], "7370.11") + self.assertEqual(trade_message.content["amount"], "0.10901605") + self.assertEqual(trade_message.content["trade_type"], float(TradeType.BUY.value)) diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_utils.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_utils.py new file mode 100644 index 0000000..e2b93ac --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_utils.py @@ -0,0 +1,24 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.btc_markets import btc_markets_utils as utils + + +class UtilsTest(TestCase): + def test_is_exchange_information_valid(self): + exchange_info = { + "status": "Online" + } + valid = utils.is_exchange_information_valid(exchange_info=exchange_info) + self.assertTrue(valid) + + exchange_info = { + "status": "Post Only" + } + valid = utils.is_exchange_information_valid(exchange_info=exchange_info) + self.assertTrue(valid) + + exchange_info = { + "status": "Limit Only" + } + valid = utils.is_exchange_information_valid(exchange_info=exchange_info) + self.assertTrue(valid) diff --git a/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_web_utils.py b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_web_utils.py new file mode 100644 index 0000000..66f0e0f --- /dev/null +++ b/test/hummingbot/connector/exchange/btc_markets/test_btc_markets_web_utils.py @@ -0,0 +1,20 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.btc_markets import ( + btc_markets_constants as CONSTANTS, + btc_markets_web_utils as web_utils, +) + + +class WebUtilsTest(TestCase): + def test_public_rest_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TRADES_URL, domain=CONSTANTS.DEFAULT_DOMAIN) + self.assertEqual('https://api.btcmarkets.net/v3/trades', url) + + def test_private_rest_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.TRADES_URL) + self.assertEqual('https://api.btcmarkets.net/v3/trades', url) + + def test_get_path_from_url(self): + url = web_utils.get_path_from_url('https://api.btcmarkets.net/v3/trades') + self.assertEqual('v3/trades', url) diff --git a/test/hummingbot/connector/exchange/bybit/__init__.py b/test/hummingbot/connector/exchange/bybit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_api_order_book_data_source.py b/test/hummingbot/connector/exchange/bybit/test_bybit_api_order_book_data_source.py new file mode 100644 index 0000000..50e7653 --- /dev/null +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_api_order_book_data_source.py @@ -0,0 +1,679 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bybit import bybit_constants as CONSTANTS, bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_api_order_book_data_source import BybitAPIOrderBookDataSource +from hummingbot.connector.exchange.bybit.bybit_exchange import BybitExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class TestBybitAPIOrderBookDataSource(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.async_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = BybitExchange( + client_config_map=client_config_map, + bybit_api_key="", + bybit_api_secret="", + trading_pairs=[self.trading_pair]) + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.time_synchronnizer = TimeSynchronizer() + self.time_synchronnizer.add_time_offset_ms_sample(1000) + self.ob_data_source = BybitAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + throttler=self.throttler, + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + time_synchronizer=self.time_synchronnizer) + + self._original_full_order_book_reset_time = self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.ob_data_source.logger().setLevel(1) + self.ob_data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.async_task and self.async_task.cancel() + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_exchange_rules_mock(self) -> Dict: + exchange_rules = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": [ + { + "name": self.ex_trading_pair, + "alias": self.ex_trading_pair, + "baseCurrency": "COINALPHA", + "quoteCurrency": "HBOT", + "basePrecision": "0.000001", + "quotePrecision": "0.01", + "minTradeQuantity": "0.0001", + "minTradeAmount": "10", + "minPricePrecision": "0.01", + "maxTradeQuantity": "2", + "maxTradeAmount": "200", + "category": 1, + "showStatus": True + }, + ] + } + return exchange_rules + + # ORDER BOOK SNAPSHOT + @staticmethod + def _snapshot_response() -> Dict: + snapshot = { + "ret_code": 0, + "ret_msg": None, + "result": { + "time": 1620886105740, + "bids": [ + [ + "50005.12", + "403.0416" + ] + ], + "asks": [ + [ + "50006.34", + "0.2297" + ] + ] + }, + "ext_code": None, + "ext_info": None + } + return snapshot + + @staticmethod + def _snapshot_response_processed() -> Dict: + snapshot_processed = { + "time": 1620886105740, + "bids": [ + [ + "50005.12", + "403.0416" + ] + ], + "asks": [ + [ + "50006.34", + "0.2297" + ] + ] + } + return snapshot_processed + + @aioresponses() + def test_request_order_book_snapshot(self, mock_api): + url = web_utils.rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + tradingrule_resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(tradingrule_resp)) + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + ret = self.async_run_with_timeout( + coroutine=self.ob_data_source._request_order_book_snapshot(self.trading_pair) + ) + + self.assertEqual(ret, self._snapshot_response_processed()) # shallow comparison ok + + @aioresponses() + def test_get_snapshot_raises(self, mock_api): + url = web_utils.rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + tradingrule_resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(tradingrule_resp)) + mock_api.get(regex_url, status=500) + + with self.assertRaises(IOError): + self.async_run_with_timeout( + coroutine=self.ob_data_source._order_book_snapshot(self.trading_pair) + ) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.ob_data_source.get_new_order_book(self.trading_pair)) + bid_entries = list(ret.bid_entries()) + ask_entries = list(ret.ask_entries()) + self.assertEqual(1, len(bid_entries)) + self.assertEqual(50005.12, bid_entries[0].price) + self.assertEqual(403.0416, bid_entries[0].amount) + self.assertEqual(int(resp["result"]["time"]), bid_entries[0].update_id) + self.assertEqual(1, len(ask_entries)) + self.assertEqual(50006.34, ask_entries[0].price) + self.assertEqual(0.2297, ask_entries[0].amount) + self.assertEqual(int(resp["result"]["time"]), ask_entries[0].update_id) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_depth(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + 'topic': 'trade', + 'event': 'sub', + 'symbol': self.ex_trading_pair, + 'params': { + 'binary': 'false', + 'symbolName': self.ex_trading_pair}, + 'code': '0', + 'msg': 'Success'} + + result_subscribe_depth = { + 'topic': 'depth', + 'event': 'sub', + 'symbol': self.ex_trading_pair, + 'params': { + 'binary': 'false', + 'symbolName': self.ex_trading_pair}, + 'code': '0', + 'msg': 'Success'} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_depth)) + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "topic": "trade", + "event": "sub", + "symbol": self.ex_trading_pair, + "params": { + "binary": False + } + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "topic": "diffDepth", + "event": "sub", + "symbol": self.ex_trading_pair, + "params": { + "binary": False + } + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + f"Subscribed to public order book and trade channels of {self.trading_pair}..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.bybit.bybit_api_order_book_data_source.BybitAPIOrderBookDataSource._time") + def test_listen_for_subscriptions_sends_ping_message_before_ping_interval_finishes( + self, + time_mock, + ws_connect_mock): + + time_mock.side_effect = [1000, 1100, 1101, 1102] # Simulate first ping interval is already due + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + 'topic': 'trade', + 'event': 'sub', + 'symbol': self.ex_trading_pair, + 'params': { + 'binary': 'false', + 'symbolName': self.ex_trading_pair}, + 'code': '0', + 'msg': 'Success'} + + result_subscribe_depth = { + 'topic': 'depth', + 'event': 'sub', + 'symbol': self.ex_trading_pair, + 'params': { + 'binary': 'false', + 'symbolName': self.ex_trading_pair}, + 'code': '0', + 'msg': 'Success'} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_depth)) + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = { + "ping": int(1101 * 1e3) + } + self.assertEqual(expected_ping_message, sent_messages[-1]) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_raises_cancel_exception(self, _, ws_connect_mock): + ws_connect_mock.side_effect = asyncio.CancelledError + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_logs_exception_details(self, sleep_mock, ws_connect_mock): + sleep_mock.side_effect = asyncio.CancelledError + ws_connect_mock.side_effect = Exception("TEST ERROR.") + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "topic": "trade", + "params": { + "symbol": self.ex_trading_pair, + "binary": "false", + "symbolName": self.ex_trading_pair + }, + "data": { + "v": "564265886622695424", + # "t": 1582001735462, + "p": "9787.5", + "q": "0.195009", + "m": True + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "topic": "trade", + "params": { + "realtimeInterval": "24h", + "binary": "false" + }, + "data": [ + { + "v": "929681067596857345", + "t": 1625562619577, + "p": "34924.15", + "q": "0.00027", + "m": True + } + ], + "f": True, + "sendTime": 1626249138535, + "shared": False + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(trade_event["data"][0]["t"], msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + # "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "topic": "diffDepth", + "params": { + "realtimeInterval": "24h", + "binary": "false" + }, + "data": [{ + "e": 301, + "s": self.ex_trading_pair, + "t": 1565600357643, + "v": "112801745_18", + "b": [ + ["11371.49", "0.0014"], + ["11371.12", "0.2"], + ["11369.97", "0.3523"], + ["11369.96", "0.5"], + ["11369.95", "0.0934"], + ["11369.94", "1.6809"], + ["11369.6", "0.0047"], + ["11369.17", "0.3"], + ["11369.16", "0.2"], + ["11369.04", "1.3203"]], + "a": [ + ["11375.41", "0.0053"], + ["11375.42", "0.0043"], + ["11375.48", "0.0052"], + ["11375.58", "0.0541"], + ["11375.7", "0.0386"], + ["11375.71", "2"], + ["11377", "2.0691"], + ["11377.01", "0.0167"], + ["11377.12", "1.5"], + ["11377.61", "0.3"] + ], + "o": 0 + }], + "f": False, + "sendTime": 1626253839401, + "shared": False + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = { + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "topic": "diffDepth", + "params": { + "realtimeInterval": "24h", + "binary": "false" + }, + "data": [{ + "e": 301, + "s": self.ex_trading_pair, + "t": 1565600357643, + "v": "112801745_18", + "b": [ + ["11371.49", "0.0014"], + ["11371.12", "0.2"], + ["11369.97", "0.3523"], + ["11369.96", "0.5"], + ["11369.95", "0.0934"], + ["11369.94", "1.6809"], + ["11369.6", "0.0047"], + ["11369.17", "0.3"], + ["11369.16", "0.2"], + ["11369.04", "1.3203"]], + "a": [ + ["11375.41", "0.0053"], + ["11375.42", "0.0043"], + ["11375.48", "0.0052"], + ["11375.58", "0.0541"], + ["11375.7", "0.0386"], + ["11375.71", "2"], + ["11377", "2.0691"], + ["11377.01", "0.0167"], + ["11377.12", "1.5"], + ["11377.61", "0.3"] + ], + "o": 0 + }], + "f": False, + "sendTime": 1626253839401, + "shared": False + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(diff_event["data"][0]["t"], msg.update_id) + + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[CONSTANTS.SNAPSHOT_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + mock_queue = AsyncMock() + mock_queue.get.side_effect = ['ERROR', asyncio.CancelledError] + self.ob_data_source._message_queue[CONSTANTS.SNAPSHOT_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = [asyncio.CancelledError] + url = web_utils.rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, exception=Exception) + + try: + self.async_run_with_timeout(self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_successful_rest(self, mock_api, _): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.TimeoutError + self.ob_data_source._message_queue[CONSTANTS.SNAPSHOT_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + snapshot_data = self._snapshot_response() + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(snapshot_data["result"]["time"]), msg.update_id) + + def test_listen_for_order_book_snapshots_successful_ws(self): + mock_queue = AsyncMock() + snapshot_event = { + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "topic": "diffDepth", + "params": { + "realtimeInterval": "24h", + "binary": "false" + }, + "data": [{ + "e": 301, + "s": self.ex_trading_pair, + "t": 1565600357643, + "v": "112801745_18", + "b": [ + ["11371.49", "0.0014"], + ["11371.12", "0.2"], + ["11369.97", "0.3523"], + ["11369.96", "0.5"], + ["11369.95", "0.0934"], + ["11369.94", "1.6809"], + ["11369.6", "0.0047"], + ["11369.17", "0.3"], + ["11369.16", "0.2"], + ["11369.04", "1.3203"]], + "a": [ + ["11375.41", "0.0053"], + ["11375.42", "0.0043"], + ["11375.48", "0.0052"], + ["11375.58", "0.0541"], + ["11375.7", "0.0386"], + ["11375.71", "2"], + ["11377", "2.0691"], + ["11377.01", "0.0167"], + ["11377.12", "1.5"], + ["11377.61", "0.3"] + ], + "o": 0 + }], + "f": True, + "sendTime": 1626253839401, + "shared": False + } + mock_queue.get.side_effect = [snapshot_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get(), + timeout=6) + + self.assertTrue(snapshot_event["data"][0]["t"], msg.update_id) diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/bybit/test_bybit_api_user_stream_data_source.py new file mode 100644 index 0000000..2e2922d --- /dev/null +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_api_user_stream_data_source.py @@ -0,0 +1,243 @@ +import asyncio +import hashlib +import hmac +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.connector.exchange.bybit import bybit_constants as CONSTANTS, bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_api_user_stream_data_source import BybitAPIUserStreamDataSource +from hummingbot.connector.exchange.bybit.bybit_auth import BybitAuth +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestBybitAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.DEFAULT_DOMAIN + cls.api_key = "someKey" + cls.api_passphrase = "somePassPhrase" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + # self.time_synchronizer = TimeSynchronizer() + # self.time_synchronizer.add_time_offset_ms_sample(0) + self.auth = BybitAuth( + self.api_key, + self.api_secret_key, + time_provider=self.mock_time_provider) + + self.api_factory = web_utils.build_api_factory( + throttler=self.throttler, + time_synchronizer=self.mock_time_provider, + auth=self.auth) + + self.data_source = BybitAPIUserStreamDataSource( + auth=self.auth, + domain=self.domain, + api_factory=self.api_factory, + throttler=self.throttler, + time_synchronizer=self.mock_time_provider) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_last_recv_time(self): + # Initial last_recv_time + self.assertEqual(0, self.data_source.last_recv_time) + + ws_assistant = self.async_run_with_timeout(self.data_source._get_ws_assistant()) + ws_assistant._connection._last_recv_time = 1000 + self.assertEqual(1000, self.data_source.last_recv_time) + + @patch("hummingbot.connector.exchange.bybit.bybit_auth.BybitAuth._time") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_auth(self, ws_connect_mock, auth_time_mock): + auth_time_mock.side_effect = [1000] + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {'auth': 'success', 'userId': 24068148} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_auth)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + + expires = int((1000 + 10) * 1000) + _val = f'GET/realtime{expires}' + signature = hmac.new(self.api_secret_key.encode("utf8"), + _val.encode("utf8"), hashlib.sha256).hexdigest() + auth_subscription = { + "op": "auth", + "args": [self.api_key, expires, signature] + } + + self.assertEqual(auth_subscription, sent_subscription_messages[0]) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_pong_payload(self, mock_ws): + + mock_pong = { + "pong": "1545910590801" + } + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_pong)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_ticket_info(self, mock_ws): + + ticket_info = [ + { + "e": "ticketInfo", + "E": "1621912542359", + "s": "BTCUSDT", + "q": "0.001639", + "t": "1621912542314", + "p": "61000.0", + "T": "899062000267837441", + "o": "899048013515737344", + "c": "1621910874883", + "O": "899062000118679808", + "a": "10043", + "A": "10024", + "m": True + } + ] + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(ticket_info)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("hummingbot.connector.exchange.bybit.bybit_auth.BybitAuth._time") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_auth_failed_throws_exception(self, ws_connect_mock, auth_time_mock): + auth_time_mock.side_effect = [100] + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {'auth': 'fail', 'userId': 24068148} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_auth)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, sleep_mock, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.bybit.bybit_api_user_stream_data_source.BybitAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_sends_ping_message_before_ping_interval_finishes( + self, + time_mock, + ws_connect_mock): + + time_mock.side_effect = [1000, 1100, 1101, 1102] # Simulate first ping interval is already due + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_auth = {'auth': 'success', 'userId': 24068148} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_auth)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = { + "ping": 1101 * 1e3, + } + self.assertEqual(expected_ping_message, sent_messages[-1]) diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_auth.py b/test/hummingbot/connector/exchange/bybit/test_bybit_auth.py new file mode 100644 index 0000000..42800a2 --- /dev/null +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_auth.py @@ -0,0 +1,112 @@ +import asyncio +import hashlib +import hmac +from collections import OrderedDict +from typing import Any, Awaitable, Dict, Mapping, Optional +from unittest import TestCase +from unittest.mock import MagicMock +from urllib.parse import urlencode + +from hummingbot.connector.exchange.bybit.bybit_auth import BybitAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class BybitAuthTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.passphrase = "testPassphrase" + self.secret_key = "testSecretKey" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = BybitAuth( + api_key=self.api_key, + secret_key=self.secret_key, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_add_auth_params_to_get_request_without_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + params_expected = self._params_expected(request.params) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual(params_expected['api_key'], request.params["api_key"]) + self.assertEqual(params_expected['timestamp'], request.params["timestamp"]) + self.assertEqual(params_expected['sign'], request.params["sign"]) + + def test_add_auth_params_to_get_request_with_params(self): + params = { + "param_z": "value_param_z", + "param_a": "value_param_a" + } + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params=params, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + params_expected = self._params_expected(request.params) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual(params_expected['api_key'], request.params["api_key"]) + self.assertEqual(params_expected['timestamp'], request.params["timestamp"]) + self.assertEqual(params_expected['sign'], request.params["sign"]) + self.assertEqual(params_expected['param_z'], request.params["param_z"]) + self.assertEqual(params_expected['param_a'], request.params["param_a"]) + + def test_add_auth_params_to_post_request(self): + params = {"param_z": "value_param_z", "param_a": "value_param_a"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=params, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + params_auth = self._params_expected(request.params) + params_request = self._params_expected(request.data) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + self.assertEqual(params_auth['api_key'], request.params["api_key"]) + self.assertEqual(params_auth['timestamp'], request.params["timestamp"]) + self.assertEqual(params_auth['sign'], request.params["sign"]) + self.assertEqual(params_request['param_z'], request.data["param_z"]) + self.assertEqual(params_request['param_a'], request.data["param_a"]) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + self.assertEqual(payload, request.payload) + + def _generate_signature(self, params: Dict[str, Any]) -> str: + encoded_params_str = urlencode(params) + digest = hmac.new(self.secret_key.encode("utf8"), encoded_params_str.encode("utf8"), hashlib.sha256).hexdigest() + return digest + + def _params_expected(self, request_params: Optional[Mapping[str, str]]) -> Dict: + request_params = request_params if request_params else {} + params = { + 'timestamp': 1000000, + 'api_key': self.api_key, + } + params.update(request_params) + params = OrderedDict(sorted(params.items(), key=lambda t: t[0])) + params['sign'] = self._generate_signature(params=params) + return params diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py b/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py new file mode 100644 index 0000000..4f07d32 --- /dev/null +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py @@ -0,0 +1,1603 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Awaitable, Dict, NamedTuple, Optional +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.bybit import bybit_constants as CONSTANTS, bybit_web_utils as web_utils +from hummingbot.connector.exchange.bybit.bybit_api_order_book_data_source import BybitAPIOrderBookDataSource +from hummingbot.connector.exchange.bybit.bybit_exchange import BybitExchange +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class TestBybitExchange(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.api_key = "someKey" + cls.api_passphrase = "somePassPhrase" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.test_task: Optional[asyncio.Task] = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = BybitExchange( + self.client_config_map, + self.api_key, + self.api_secret_key, + trading_pairs=[self.trading_pair] + ) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + + self._initialize_event_loggers() + + BybitAPIOrderBookDataSource._trading_pair_symbol_map = { + CONSTANTS.DEFAULT_DOMAIN: bidict( + {self.ex_trading_pair: self.trading_pair}) + } + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + BybitAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_exchange_rules_mock(self) -> Dict: + exchange_rules = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": [ + { + "name": self.ex_trading_pair, + "alias": self.ex_trading_pair, + "baseCurrency": "COINALPHA", + "quoteCurrency": "USDT", + "basePrecision": "0.000001", + "quotePrecision": "0.01", + "minTradeQuantity": "0.0001", + "minTradeAmount": "10", + "minPricePrecision": "0.01", + "maxTradeQuantity": "2", + "maxTradeAmount": "200", + "category": 1, + "showStatus": True + }, + ] + } + return exchange_rules + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def _validate_auth_credentials_present(self, request_call_tuple: NamedTuple): + request_headers = request_call_tuple.kwargs["headers"] + request_params = request_call_tuple.kwargs["params"] + self.assertIn("Content-Type", request_headers) + self.assertEqual("application/x-www-form-urlencoded", request_headers["Content-Type"]) + self.assertIn("api_key", request_params) + self.assertIn("sign", request_params) + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertIn(OrderType.MARKET, supported_types) + self.assertIn(OrderType.LIMIT, supported_types) + self.assertIn(OrderType.LIMIT_MAKER, supported_types) + + @aioresponses() + def test_check_network_success(self, mock_api): + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + resp = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "serverTime": 1625799317787 + } + } + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, ret) + + @aioresponses() + def test_check_network_failure(self, mock_api): + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + mock_api.get(url, status=500) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + + mock_api.get(url, exception=asyncio.CancelledError) + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + + resp = self.get_exchange_rules_mock() + mock_api.get(url, body=json.dumps(resp)) + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertTrue(self.trading_pair in self.exchange._trading_rules) + + @aioresponses() + def test_update_trading_rules_ignores_rule_with_error(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + exchange_rules = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": [ + { + "name": self.ex_trading_pair, + "alias": self.ex_trading_pair, + "baseCurrency": "COINALPHA", + "quoteCurrency": "USDT", + "maxTradeAmount": "200", + "category": 1 + }, + ] + } + mock_api.get(url, body=json.dumps(exchange_rules)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange._trading_rules)) + self.assertTrue( + self._is_logged("ERROR", f"Error parsing the trading pair rule {self.ex_trading_pair}. Skipping.") + ) + + def test_initial_status_dict(self): + BybitAPIOrderBookDataSource._trading_pair_symbol_map = {} + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + def test_get_fee_returns_fee_from_exchange_if_available_and_default_if_not(self): + fee = self.exchange.get_fee( + base_currency="SOME", + quote_currency="OTHER", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.001"), fee.percent) # default fee + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 9 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN + ) + + self.assertEqual(result, expected_client_order_id) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.exchange.in_flight_orders) + self.assertNotIn("OID2", self.exchange.in_flight_orders) + self.assertNotIn("OID3", self.exchange.in_flight_orders) + self.assertNotIn("OID4", self.exchange.in_flight_orders) + + @aioresponses() + def test_create_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + creation_response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1", + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "orderLinkId": "162073788655749", + "orderId": "889208273689997824", + "transactTime": "1620737886573", + "price": "20000", + "origQty": "10", + "executedQty": "0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY" + } + } + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_params["symbol"]) + self.assertEqual("BUY", request_params["side"]) + self.assertEqual("LIMIT", request_params["type"]) + self.assertEqual(Decimal("100"), Decimal(request_params["qty"])) + self.assertEqual(Decimal("10000"), Decimal(request_params["price"])) + self.assertEqual("OID1", request_params["orderLinkId"]) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["result"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created LIMIT BUY order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_limit_maker_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + creation_response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1", + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "orderLinkId": "162073788655749", + "orderId": "889208273689997824", + "transactTime": "1620737886573", + "price": "20000", + "origQty": "10", + "executedQty": "0", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT_MAKER", + "side": "BUY" + } + } + + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT_MAKER, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_data = order_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_data["symbol"]) + self.assertEqual(TradeType.BUY.name, request_data["side"]) + self.assertEqual("LIMIT_MAKER", request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["qty"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual("OID1", request_data["orderLinkId"]) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT_MAKER, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["result"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created LIMIT_MAKER BUY order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.bybit.bybit_exchange.BybitExchange.get_price") + def test_create_market_order_successfully(self, mock_api, get_price_mock): + get_price_mock.return_value = Decimal(1000) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + creation_response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1", + "symbol": self.ex_trading_pair, + "symbolName": self.ex_trading_pair, + "orderLinkId": "162073788655749", + "orderId": "889208273689997824", + "transactTime": "1620737886573", + "price": "20000", + "origQty": "10", + "executedQty": "0", + "status": "NEW", + "timeInForce": "GTC", + "type": "MARKET", + "side": "SELL" + } + } + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.SELL, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.MARKET)) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_data = order_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_data["symbol"]) + self.assertEqual(TradeType.SELL.name, request_data["side"]) + self.assertEqual("MARKET", request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["qty"])) + self.assertEqual("OID1", request_data["orderLinkId"]) + self.assertNotIn("price", request_data) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["result"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created MARKET SELL order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertNotIn("OID1", self.exchange.in_flight_orders) + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("OID1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Order OID1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='OID1', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + tradingrule_url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("0.0001"), + order_type=OrderType.LIMIT, + price=Decimal("0.0001"))) + # The second order is used only to have the event triggered and avoid using timeouts for tests + asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID2", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn("OID1", self.exchange.in_flight_orders) + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("OID1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self._is_logged( + "INFO", + f"Order OID1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + "client_order_id='OID1', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "10054", + "symbol": self.ex_trading_pair, + "orderLinkId": "OID1", + "orderId": "4", + "transactTime": "1620811601728", + "price": "10000", + "origQty": "100", + "executedQty": "0", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY" + } + } + + mock_api.delete(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(client_order_id="OID1", trading_pair=self.trading_pair) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(cancel_request[1][0]) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(client_order_id="OID1", trading_pair=self.trading_pair) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(cancel_request[1][0]) + + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue( + self._is_logged( + "ERROR", + f"Failed to cancel order {order.client_order_id}" + ) + ) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["OID1"] + + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID2", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["OID2"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "10054", + "symbol": self.ex_trading_pair, + "orderLinkId": order1.client_order_id, + "orderId": order1.exchange_order_id, + "transactTime": "1620811601728", + "price": float(order1.price), + "origQty": float(order1.amount), + "executedQty": "0", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY" + } + } + + mock_api.delete(regex_url, body=json.dumps(response)) + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, status=400) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {order1.client_order_id}." + ) + ) + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + seconds_counter_mock.side_effect = [0, 0, 0] + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "serverTime": 1625799317787 + } + } + + mock_api.get(regex_url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + self.assertEqual(response["result"]['serverTime'] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "code": "-1", + "msg": "error" + } + + mock_api.get(regex_url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertTrue(self._is_logged("NETWORK", "Error getting server time.")) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, self.exchange._update_time_synchronizer()) + + @aioresponses() + def test_update_balances(self, mock_api): + url = web_utils.rest_url(CONSTANTS.ACCOUNTS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "balances": [ + { + "coin": "COINALPHA", + "coinId": "COINALPHA", + "coinName": "COINALPHA", + "total": "15", + "free": "10", + "locked": "0" + }, + { + "coin": "USDT", + "coinId": "USDT", + "coinName": "USDT", + "total": "2000", + "free": "2000", + "locked": "0" + } + ] + } + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("10"), available_balances["COINALPHA"]) + self.assertEqual(Decimal("2000"), available_balances["USDT"]) + self.assertEqual(Decimal("15"), total_balances["COINALPHA"]) + self.assertEqual(Decimal("2000"), total_balances["USDT"]) + + response = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "balances": [ + { + "coin": "COINALPHA", + "coinId": "COINALPHA", + "coinName": "COINALPHA", + "total": "15", + "free": "10", + "locked": "0" + }, + ] + } + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn("USDT", available_balances) + self.assertNotIn("USDT", total_balances) + self.assertEqual(Decimal("10"), available_balances["COINALPHA"]) + self.assertEqual(Decimal("15"), total_balances["COINALPHA"]) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + 10 - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1054", + "exchangeId": "301", + "symbol": self.trading_pair, + "symbolName": "ETHUSDT", + "orderLinkId": order.client_order_id, + "orderId": order.exchange_order_id, + "price": "20000", + "origQty": "1", + "executedQty": "1", + "cummulativeQuoteQty": "1", + "avgPrice": "1000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": order.trade_type.name, + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": "1620811601728", + "updateTime": "1620811601743", + "isWorking": True + } + } + + mock_api.get(regex_url, body=json.dumps(order_status)) + + # Simulate the order has been filled with a TradeUpdate + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_update_order_status_when_cancelled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + 10 - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1054", + "exchangeId": "301", + "symbol": self.trading_pair, + "symbolName": "ETHUSDT", + "orderLinkId": order.client_order_id, + "orderId": order.exchange_order_id, + "price": "10000", + "origQty": "1", + "executedQty": "1", + "cummulativeQuoteQty": "1", + "avgPrice": "1000", + "status": "CANCELED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": order.trade_type.name, + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": "1620811601728", + "updateTime": "1620811601743", + "isWorking": True + } + } + + mock_api.get(regex_url, body=json.dumps(order_status)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + 10 - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "ret_code": 0, + "ret_msg": "", + "ext_code": None, + "ext_info": None, + "result": { + "accountId": "1054", + "exchangeId": "301", + "symbol": self.trading_pair, + "symbolName": "ETHUSDT", + "orderLinkId": order.client_order_id, + "orderId": order.exchange_order_id, + "price": "10000", + "origQty": "1", + "executedQty": "1", + "cummulativeQuoteQty": "1", + "avgPrice": "1000", + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": order.trade_type.name, + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": "1620811601728", + "updateTime": "1620811601743", + "isWorking": True + } + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + 10 - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=404) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + def test_user_stream_update_for_new_order_does_not_update_status(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "e": "executionReport", + "E": "1499405658658", + "s": order.trading_pair, + "c": order.client_order_id, + "S": order.trade_type.name, + "o": "LIMIT", + "f": "GTC", + "q": "1.00000000", + "p": "0.10264410", + "X": "NEW", + "i": order.exchange_order_id, + "M": "0", + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": "COINALPHA", + "u": True, + "w": True, + "m": False, + "O": "1499405658657", + "Z": "473.199", + "A": "0", + "C": False, + "v": "0" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created {order.order_type.name.upper()} {order.trade_type.name.upper()} order " + f"{order.client_order_id} for {order.amount} {order.trading_pair}." + ) + ) + + def test_user_stream_update_for_cancelled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "e": "executionReport", + "E": "1499405658658", + "s": order.trading_pair, + "c": order.client_order_id, + "S": order.trade_type.name, + "o": "LIMIT", + "f": "GTC", + "q": "1.00000000", + "p": "0.10264410", + "X": "CANCELED", + "i": order.exchange_order_id, + "M": "0", + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": "COINALPHA", + "u": True, + "w": True, + "m": False, + "O": "1499405658657", + "Z": "473.199", + "A": "0", + "C": False, + "v": "0" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_user_stream_update_for_order_partial_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "e": "executionReport", + "t": "1499405658658", + "E": "1499405658658", + "s": order.trading_pair, + "c": order.client_order_id, + "S": order.trade_type.name, + "o": "LIMIT", + "f": "GTC", + "q": order.amount, + "p": order.price, + "X": "PARTIALLY_FILLED", + "i": order.exchange_order_id, + "M": "0", + "l": "0.50000000", + "z": "0.50000000", + "L": "0.10250000", + "n": "0.003", + "N": self.base_asset, + "u": True, + "w": True, + "m": False, + "O": "1499405658657", + "Z": "473.199", + "A": "0", + "C": False, + "v": "0" + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(event_message["L"]), fill_event.price) + self.assertEqual(Decimal(event_message["l"]), fill_event.amount) + + self.assertEqual([TokenAmount(amount=Decimal(event_message["n"]), token=(event_message["N"]))], + fill_event.trade_fee.flat_fees) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + self.assertTrue( + self._is_logged("INFO", f"The {order.trade_type.name} order {order.client_order_id} amounting to " + f"{fill_event.amount}/{order.amount} {order.base_asset} has been filled.") + ) + + def test_user_stream_update_for_order_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "e": "executionReport", + "t": "1499405658658", + "E": "1499405658658", + "s": order.trading_pair, + "c": order.client_order_id, + "S": order.trade_type.name, + "o": "LIMIT", + "f": "GTC", + "q": order.amount, + "p": order.price, + "X": "FILLED", + "i": order.exchange_order_id, + "M": "0", + "l": order.amount, + "z": "0.50000000", + "L": order.price, + "n": "0.003", + "N": self.base_asset, + "u": True, + "w": True, + "m": False, + "O": "1499405658657", + "Z": "473.199", + "A": "0", + "C": False, + "v": "0" + } + + filled_event = { + "e": "ticketInfo", + "E": "1621912542359", + "s": self.ex_trading_pair, + "q": "0.001639", + "t": "1621912542314", + "p": "61000.0", + "T": "899062000267837441", + "o": "899048013515737344", + "c": "1621910874883", + "O": "899062000118679808", + "a": "10043", + "A": "10024", + "m": True + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, filled_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + match_price = Decimal(event_message["L"]) + match_size = Decimal(event_message["l"]) + self.assertEqual(match_price, fill_event.price) + self.assertEqual(match_size, fill_event.amount) + self.assertEqual([TokenAmount(amount=Decimal(event_message["n"]), token=(event_message["N"]))], + fill_event.trade_fee.flat_fees) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * match_price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_balance_update(self): + self.exchange._set_current_timestamp(1640780000) + + event_message = { + "e": "outboundAccountInfo", + "E": "1629969654753", + "T": True, + "W": True, + "D": True, + "B": [ + { + "a": self.base_asset, + "f": "10000", + "l": "500" + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10000"), self.exchange.available_balances["COINALPHA"]) + self.assertEqual(Decimal("10500"), self.exchange.get_balance("COINALPHA")) + + def test_user_stream_raises_cancel_exception(self): + self.exchange._set_current_timestamp(1640780000) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._user_stream_tracker._user_stream = mock_queue + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, + self.exchange._user_stream_event_listener()) + + @patch("hummingbot.connector.exchange.bybit.bybit_exchange.BybitExchange._sleep") + def test_user_stream_logs_errors(self, _): + self.exchange._set_current_timestamp(1640780000) + + incomplete_event = { + "e": "outboundAccountInfo", + "E": "1629969654753", + "T": True, + "W": True, + "D": True, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error in user stream listener loop." + ) + ) diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_web_utils.py b/test/hummingbot/connector/exchange/bybit/test_bybit_web_utils.py new file mode 100644 index 0000000..f4759e2 --- /dev/null +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_web_utils.py @@ -0,0 +1,11 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.bybit import bybit_constants as CONSTANTS, bybit_web_utils as web_utils + + +class WebUtilsTests(TestCase): + def test_rest_url(self): + url = web_utils.rest_url(path_url=CONSTANTS.LAST_TRADED_PRICE_PATH, domain=CONSTANTS.DEFAULT_DOMAIN) + self.assertEqual('https://api.bybit.com/spot/quote/v1/ticker/price', url) + url = web_utils.rest_url(path_url=CONSTANTS.LAST_TRADED_PRICE_PATH, domain='bybit_testnet') + self.assertEqual('https://api-testnet.bybit.com/spot/quote/v1/ticker/price', url) diff --git a/test/hummingbot/connector/exchange/coinbase_pro/__init__.py b/test/hummingbot/connector/exchange/coinbase_pro/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_order_book_data_source.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_order_book_data_source.py new file mode 100644 index 0000000..1ac4d86 --- /dev/null +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_order_book_data_source.py @@ -0,0 +1,480 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable, Dict, List, Optional +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_order_book_data_source import ( + CoinbaseProAPIOrderBookDataSource, +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_auth import CoinbaseProAuth +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_order_book_tracker_entry import ( + CoinbaseProOrderBookTrackerEntry, +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_utils import build_coinbase_pro_web_assistant_factory +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook + + +class CoinbaseProAPIOrderBookDataSourceTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + auth = CoinbaseProAuth(api_key="SomeAPIKey", secret_key="SomeSecretKey", passphrase="SomePassPhrase") + web_assistants_factory = build_coinbase_pro_web_assistant_factory(auth) + self.data_source = CoinbaseProAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], web_assistants_factory=web_assistants_factory + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_products_ticker_response_mock(price: float) -> Dict: + products_ticker_mock = { + "trade_id": 86326522, + "price": str(price), + "size": "0.00698254", + "time": "2020-03-20T00:22:57.833897Z", + "bid": "6265.15", + "ask": "6267.71", + "volume": "53602.03940154" + } + return products_ticker_mock + + def get_products_response_mock(self, other_pair: str) -> List: + products_mock = [ + { + "id": self.trading_pair, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "base_min_size": "0.00100000", + "base_max_size": "280.00000000", + "quote_increment": "0.01000000", + "base_increment": "0.00000001", + "display_name": f"{self.base_asset}/{self.quote_asset}", + "min_market_funds": "10", + "max_market_funds": "1000000", + "margin_enabled": False, + "post_only": False, + "limit_only": False, + "cancel_only": False, + "status": "online", + "status_message": "", + "auction_mode": True, + }, + { + "id": other_pair, + "base_currency": other_pair.split("-")[0], + "quote_currency": other_pair.split("-")[1], + "base_min_size": "0.00100000", + "base_max_size": "280.00000000", + "quote_increment": "0.01000000", + "base_increment": "0.00000001", + "display_name": other_pair.replace("-", "/"), + "min_market_funds": "10", + "max_market_funds": "1000000", + "margin_enabled": False, + "post_only": False, + "limit_only": False, + "cancel_only": False, + "status": "online", + "status_message": "", + "auction_mode": True, + } + ] + return products_mock + + @staticmethod + def get_products_book_response_mock( + bids: Optional[List[List[str]]] = None, asks: Optional[List[List[str]]] = None + ) -> Dict: + bids = bids or [["1", "2", "3"]] + asks = asks or [["4", "5", "6"]] + products_book_mock = { + "sequence": 13051505638, + "bids": bids, + "asks": asks, + } + return products_book_mock + + def get_ws_open_message_mock(self) -> Dict: + message = { + "type": "open", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "sequence": 10, + "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", + "price": "200.2", + "remaining_size": "1.00", + "side": "sell" + } + return message + + def get_ws_match_message_mock(self) -> Dict: + message = { + "type": "match", + "trade_id": 10, + "sequence": 50, + "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "size": "5.23512", + "price": "400.23", + "side": "sell" + } + return message + + def get_ws_change_message_mock(self) -> Dict: + message = { + "type": "change", + "time": "2014-11-07T08:19:27.028459Z", + "sequence": 80, + "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", + "product_id": self.trading_pair, + "new_size": "5.23512", + "old_size": "12.234412", + "price": "400.23", + "side": "sell" + } + return message + + def get_ws_done_message_mock(self) -> Dict: + message = { + "type": "done", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "sequence": 10, + "price": "200.2", + "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", + "reason": "filled", + "side": "sell", + "remaining_size": "0" + } + return message + + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + alt_pair = "BTC-USDT" + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/ticker" + alt_url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{alt_pair}/ticker" + price = 10.0 + alt_price = 15.0 + resp = self.get_products_ticker_response_mock(price=price) + alt_resp = self.get_products_ticker_response_mock(price=alt_price) + mock_api.get(url, body=json.dumps(resp)) + mock_api.get(alt_url, body=json.dumps(alt_resp)) + + trading_pairs = [self.trading_pair, alt_pair] + ret = self.async_run_with_timeout( + coroutine=CoinbaseProAPIOrderBookDataSource.get_last_traded_prices(trading_pairs) + ) + + self.assertEqual(ret[self.trading_pair], Decimal(resp["price"])) + self.assertEqual(ret[alt_pair], Decimal(alt_resp["price"])) + + # @aioresponses() + # def test_fetch_trading_pairs(self, mock_api): + # url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}" + # alt_pair = "BTC-USDT" + # resp = self.get_products_response_mock(alt_pair) + # mock_api.get(url, body=json.dumps(resp)) + # + # ret = self.async_run_with_timeout(coroutine=CoinbaseProAPIOrderBookDataSource.fetch_trading_pairs()) + # + # self.assertIn(self.trading_pair, ret) + # self.assertIn(alt_pair, ret) + + @aioresponses() + def test_get_snapshot(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + resp = self.get_products_book_response_mock() + mock_api.get(url, body=json.dumps(resp)) + + rest_assistant = self.ev_loop.run_until_complete( + build_coinbase_pro_web_assistant_factory().get_rest_assistant() + ) + ret = self.async_run_with_timeout( + coroutine=CoinbaseProAPIOrderBookDataSource.get_snapshot(rest_assistant, self.trading_pair) + ) + + self.assertEqual(resp, ret) # shallow comparison ok + + @aioresponses() + def test_get_snapshot_raises_on_status_code(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + resp = self.get_products_book_response_mock() + mock_api.get(url, body=json.dumps(resp), status=401) + + rest_assistant = self.ev_loop.run_until_complete( + build_coinbase_pro_web_assistant_factory().get_rest_assistant() + ) + with self.assertRaises(IOError): + self.async_run_with_timeout( + coroutine=CoinbaseProAPIOrderBookDataSource.get_snapshot(rest_assistant, self.trading_pair) + ) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + resp = self.get_products_book_response_mock(bids=[["1", "2", "3"]], asks=[["4", "5", "6"]]) + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + self.assertIsInstance(ret, OrderBook) + + bid_entries = list(ret.bid_entries()) + ask_entries = list(ret.ask_entries()) + + self.assertEqual(1, len(bid_entries)) + self.assertEqual(1, len(ask_entries)) + + bid_entry = bid_entries[0] + ask_entry = ask_entries[0] + + self.assertEqual(1, bid_entry.price) + self.assertEqual(4, ask_entry.price) + + @aioresponses() + def test_get_tracking_pairs(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + resp = self.get_products_book_response_mock(bids=[["1", "2", "3"]]) + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(self.data_source.get_tracking_pairs()) + + self.assertEqual(1, len(ret)) + + tracker_entry = ret[self.trading_pair] + + self.assertIsInstance(tracker_entry, CoinbaseProOrderBookTrackerEntry) + self.assertEqual(1, list(tracker_entry.order_book.bid_entries())[0].price) + + @aioresponses() + def test_get_tracking_pairs_logs_io_error(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + mock_api.get(url, exception=IOError) + + ret = self.async_run_with_timeout(self.data_source.get_tracking_pairs()) + + self.assertEqual(0, len(ret)) + self.assertTrue(self._is_logged( + log_level="NETWORK", message=f"Error getting snapshot for {self.trading_pair}.") + ) + + @aioresponses() + def test_get_tracking_pairs_logs_other_exceptions(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}/{self.trading_pair}/book?level=3" + mock_api.get(url, exception=RuntimeError) + + ret = self.async_run_with_timeout(self.data_source.get_tracking_pairs()) + + self.assertEqual(0, len(ret)) + self.assertTrue(self._is_logged( + log_level="ERROR", message=f"Error initializing order book for {self.trading_pair}. ") + ) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_processes_open_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_open_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + ob_message = output_queue.get_nowait() + + self.assertEqual(resp, ob_message.content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_processes_match_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_match_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + ob_message = output_queue.get_nowait() + + self.assertEqual(resp, ob_message.content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_processes_change_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_change_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + ob_message = output_queue.get_nowait() + + self.assertEqual(resp, ob_message.content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_processes_done_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_done_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + ob_message = output_queue.get_nowait() + + self.assertEqual(resp, ob_message.content) # shallow comparison is ok + + @patch( + "hummingbot.connector.exchange.coinbase_pro" + ".coinbase_pro_api_order_book_data_source.CoinbaseProAPIOrderBookDataSource._sleep" + ) + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_raises_on_no_type(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged(log_level="NETWORK", message="Unexpected error with WebSocket connection.") + ) + self.assertTrue(output_queue.empty()) + + @patch( + "hummingbot.connector.exchange.coinbase_pro" + ".coinbase_pro_api_order_book_data_source.CoinbaseProAPIOrderBookDataSource._sleep" + ) + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_raises_on_error_msg(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {"type": "error", "message": "some error"} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged(log_level="NETWORK", message="Unexpected error with WebSocket connection.") + ) + self.assertTrue(output_queue.empty()) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_ignores_irrelevant_messages(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "received"}) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "activate"}) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "subscriptions"}) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) + + @patch( + "hummingbot.connector.exchange.coinbase_pro" + ".coinbase_pro_api_order_book_data_source.CoinbaseProAPIOrderBookDataSource._sleep" + ) + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_raises_on_unrecognized_message(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {"type": "some-new-message-type"} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged(log_level="NETWORK", message="Unexpected error with WebSocket connection.") + ) + self.assertTrue(output_queue.empty()) diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py new file mode 100644 index 0000000..07312e1 --- /dev/null +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py @@ -0,0 +1,258 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_user_stream_data_source import ( + CoinbaseProAPIUserStreamDataSource, +) +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_auth import CoinbaseProAuth +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_utils import build_coinbase_pro_web_assistant_factory +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class TestCoinbaseProAPIUserStreamDataSource(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + auth = CoinbaseProAuth(api_key="SomeAPIKey", secret_key="shht", passphrase="SomePassPhrase") + self.mocking_assistant = NetworkMockingAssistant() + web_assistants_factory = build_coinbase_pro_web_assistant_factory(auth) + self.data_source = CoinbaseProAPIUserStreamDataSource( + trading_pairs=[self.trading_pair], web_assistants_factory=web_assistants_factory + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_ws_open_message_mock(self) -> Dict: + message = { + "type": "open", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "sequence": 10, + "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", + "price": "200.2", + "remaining_size": "1.00", + "side": "sell" + } + return message + + def get_ws_match_message_mock(self) -> Dict: + message = { + "type": "match", + "trade_id": 10, + "sequence": 50, + "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "size": "5.23512", + "price": "400.23", + "side": "sell" + } + return message + + def get_ws_change_message_mock(self) -> Dict: + message = { + "type": "change", + "time": "2014-11-07T08:19:27.028459Z", + "sequence": 80, + "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", + "product_id": self.trading_pair, + "new_size": "5.23512", + "old_size": "12.234412", + "price": "400.23", + "side": "sell" + } + return message + + def get_ws_done_message_mock(self) -> Dict: + message = { + "type": "done", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": self.trading_pair, + "sequence": 10, + "price": "200.2", + "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", + "reason": "filled", + "side": "sell", + "remaining_size": "0" + } + return message + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_open_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_open_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + content = output_queue.get_nowait() + + self.assertEqual(resp, content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_match_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_match_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + content = output_queue.get_nowait() + + self.assertEqual(resp, content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_change_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_change_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + content = output_queue.get_nowait() + + self.assertEqual(resp, content) # shallow comparison is ok + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_processes_done_message(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_ws_done_message_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertFalse(output_queue.empty()) + + content = output_queue.get_nowait() + + self.assertEqual(resp, content) # shallow comparison is ok + + @patch( + "hummingbot.connector.exchange.coinbase_pro" + ".coinbase_pro_api_user_stream_data_source.CoinbaseProAPIUserStreamDataSource._sleep" + ) + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_raises_on_no_type(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged(log_level="NETWORK", message="Unexpected error with WebSocket connection.") + ) + self.assertTrue(output_queue.empty()) + + @patch( + "hummingbot.connector.exchange.coinbase_pro" + ".coinbase_pro_api_user_stream_data_source.CoinbaseProAPIUserStreamDataSource._sleep" + ) + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_raises_on_error_message(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {"type": "error", "message": "some error"} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged(log_level="NETWORK", message="Unexpected error with WebSocket connection.") + ) + self.assertTrue(output_queue.empty()) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_ignores_irrelevant_messages(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "received"}) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "activate"}) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps({"type": "subscriptions"}) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + self.async_tasks.append(t) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_exchange.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_exchange.py new file mode 100644 index 0000000..7d58949 --- /dev/null +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_exchange.py @@ -0,0 +1,427 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Awaitable, Dict, List + +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.coinbase_pro import coinbase_pro_constants as CONSTANTS +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_exchange import CoinbaseProExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class TestCoinbaseProExchange(unittest.TestCase): + # logging.Level required to receive logs from the exchange + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_secret = "shht" + cls.api_passphrase = "somePhrase" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.mocking_assistant = NetworkMockingAssistant() + self.async_tasks: List[asyncio.Task] = [] + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = CoinbaseProExchange( + client_config_map=self.client_config_map, + coinbase_pro_api_key=self.api_key, + coinbase_pro_secret_key=self.api_secret, + coinbase_pro_passphrase=self.api_passphrase, + trading_pairs=[self.trading_pair] + ) + self.event_listener = EventLogger() + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def simulate_trading_rules_initialization(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}" + alt_pair = "BTC-USDT" + resp = self.get_products_response_mock(alt_pair) + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + def simulate_execute_buy_order(self, mock_api, order_id): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_orders_response_mock(order_id) + mock_api.post(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout( + self.exchange.execute_sell( + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + ) + + def get_account_mock( + self, base_balance: float, base_available: float, quote_balance: float, quote_available: float + ) -> List: + account_mock = [ + { + "id": "7fd0abc0-e5ad-4cbb-8d54-f2b3f43364da", + "currency": self.base_asset, + "balance": str(base_balance), + "available": str(base_available), + "hold": "0.0000000000000000", + "profile_id": "8058d771-2d88-4f0f-ab6e-299c153d4308", + "trading_enabled": True + }, + { + "id": "7fd0abc0-e5ad-4cbb-8d54-f2b3f43364da", + "currency": self.quote_asset, + "balance": str(quote_balance), + "available": str(quote_available), + "hold": "0.0000000000000000", + "profile_id": "8058d771-2d88-4f0f-ab6e-299c153d4308", + "trading_enabled": True + } + ] + return account_mock + + def get_products_response_mock(self, other_pair: str) -> List: + products_mock = [ + { + "id": self.trading_pair, + "base_currency": self.base_asset, + "quote_currency": self.quote_asset, + "quote_increment": "0.01000000", + "base_increment": "0.00000001", + "display_name": f"{self.base_asset}/{self.quote_asset}", + "min_market_funds": "10", + "margin_enabled": False, + "post_only": False, + "limit_only": False, + "cancel_only": False, + "status": "online", + "status_message": "", + "auction_mode": True, + }, + { + "id": other_pair, + "base_currency": other_pair.split("-")[0], + "quote_currency": other_pair.split("-")[1], + "quote_increment": "0.01000000", + "base_increment": "0.00000001", + "display_name": other_pair.replace("-", "/"), + "min_market_funds": "10", + "margin_enabled": False, + "post_only": False, + "limit_only": False, + "cancel_only": False, + "status": "online", + "status_message": "", + "auction_mode": True, + } + ] + return products_mock + + def get_orders_response_mock(self, order_id: str) -> Dict: + orders_mock = { + "id": order_id, + "price": "10.00000000", + "size": "1.00000000", + "product_id": self.trading_pair, + "profile_id": "8058d771-2d88-4f0f-ab6e-299c153d4308", + "side": "buy", + "type": "limit", + "time_in_force": "GTC", + "post_only": True, + "created_at": "2020-03-11T20:48:46.622052Z", + "fill_fees": "0.0000000000000000", + "filled_size": "0.00000000", + "executed_value": "0.0000000000000000", + "status": "open", + "settled": False + } + return orders_mock + + @aioresponses() + def test_check_network_not_connected(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.TIME_PATH_URL}" + resp = "" + mock_api.get(url, status=500, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.TIME_PATH_URL}" + resp = {} + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.CONNECTED) + + @aioresponses() + def test_update_fee_percentage(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.FEES_PATH_URL}" + resp = { + "maker_fee_rate": "0.0050", + "taker_fee_rate": "0.0050", + "usd_volume": "43806.92" + } + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_fee_percentage()) + + self.assertEqual(Decimal(resp["maker_fee_rate"]), self.exchange.maker_fee_percentage) + self.assertEqual(Decimal(resp["taker_fee_rate"]), self.exchange.taker_fee_percentage) + + @aioresponses() + def test_update_balances(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ACCOUNTS_PATH_URL}" + resp = self.get_account_mock( + base_balance=2, + base_available=1, + quote_balance=4, + quote_available=3, + ) + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_balances()) + + expected_available_balances = {self.base_asset: Decimal("1"), self.quote_asset: Decimal("3")} + self.assertEqual(expected_available_balances, self.exchange.available_balances) + expected_balances = {self.base_asset: Decimal("2"), self.quote_asset: Decimal("4")} + self.assertEqual(expected_balances, self.exchange.get_all_balances()) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + url = f"{CONSTANTS.REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}" + alt_pair = "BTC-USDT" + resp = self.get_products_response_mock(alt_pair) + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + trading_rules = self.exchange.trading_rules + + self.assertEqual(2, len(trading_rules)) + self.assertIn(self.trading_pair, trading_rules) + self.assertIn(alt_pair, trading_rules) + self.assertIsInstance(trading_rules[self.trading_pair], TradingRule) + self.assertIsInstance(trading_rules[alt_pair], TradingRule) + + @aioresponses() + def test_execute_buy(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + + some_order_id = "someID" + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_orders_response_mock(some_order_id) + mock_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.add_listener(MarketEvent.BuyOrderCreated, self.event_listener) + self.exchange.add_listener(MarketEvent.OrderFilled, self.event_listener) + + self.async_run_with_timeout( + self.exchange.execute_buy( + order_id=some_order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + ) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, BuyOrderCreatedEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_buy_handles_errors(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.post(regex_url, exception=RuntimeError) + + self.exchange.add_listener(MarketEvent.BuyOrderCreated, self.event_listener) + self.exchange.add_listener(MarketEvent.OrderFailure, self.event_listener) + + some_order_id = "someID" + self.async_run_with_timeout( + self.exchange.execute_buy( + order_id=some_order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + ) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, MarketOrderFailureEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertNotIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_sell(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + + some_order_id = "someID" + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_orders_response_mock(some_order_id) + mock_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.add_listener(MarketEvent.SellOrderCreated, self.event_listener) + self.exchange.add_listener(MarketEvent.OrderFilled, self.event_listener) + + self.async_run_with_timeout( + self.exchange.execute_sell( + order_id=some_order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + ) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, SellOrderCreatedEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_sell_handles_errors(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.post(regex_url, exception=RuntimeError) + + self.exchange.add_listener(MarketEvent.SellOrderCreated, self.event_listener) + self.exchange.add_listener(MarketEvent.OrderFailure, self.event_listener) + + some_order_id = "someID" + self.async_run_with_timeout( + self.exchange.execute_sell( + order_id=some_order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + ) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, MarketOrderFailureEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertNotIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_cancel(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + some_order_id = "someID" + self.simulate_execute_buy_order(mock_api, some_order_id) + + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}/{some_order_id}" + resp = some_order_id + mock_api.delete(url, body=json.dumps(resp)) + + self.exchange.add_listener(MarketEvent.OrderCancelled, self.event_listener) + + self.async_run_with_timeout(self.exchange.execute_cancel(self.trading_pair, some_order_id)) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, OrderCancelledEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertNotIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_cancel_order_does_not_exist(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + some_order_id = "someID" + self.simulate_execute_buy_order(mock_api, some_order_id) + + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}/{some_order_id}" + mock_api.delete(url, exception=IOError("order not found")) + + self.exchange.add_listener(MarketEvent.OrderCancelled, self.event_listener) + + self.async_run_with_timeout(self.exchange.execute_cancel(self.trading_pair, some_order_id)) + + self.assertEqual(1, len(self.event_listener.event_log)) + + event = self.event_listener.event_log[0] + + self.assertIsInstance(event, OrderCancelledEvent) + self.assertEqual(some_order_id, event.order_id) + self.assertNotIn(some_order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_get_order(self, mock_api): + self.simulate_trading_rules_initialization(mock_api) + some_order_id = "someID" + self.simulate_execute_buy_order(mock_api, some_order_id) + + url = f"{CONSTANTS.REST_URL}{CONSTANTS.ORDERS_PATH_URL}/{some_order_id}" + resp = self.get_orders_response_mock(some_order_id) + mock_api.get(url, body=json.dumps(resp)) diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_in_flight_order.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_in_flight_order.py new file mode 100644 index 0000000..133d58b --- /dev/null +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_in_flight_order.py @@ -0,0 +1,194 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_in_flight_order import CoinbaseProInFlightOrder +from hummingbot.core.data_type.common import OrderType, TradeType + + +class CoinbaseProInFlightOrderTests(TestCase): + + def setUp(self): + super().setUp() + self.base_token = "BTC" + self.quote_token = "USDT" + self.trading_pair = f"{self.base_token}-{self.quote_token}" + + def test_update_with_partial_trade_event(self): + order = CoinbaseProInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "type": "match", + "trade_id": 1, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.1", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.005" + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertFalse(order.is_done) + self.assertEqual("open", order.last_state) + self.assertEqual(Decimal(str(trade_event_info["size"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["size"])) * Decimal(str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["taker_fee_rate"]) * expected_executed_quote_amount, order.fee_paid) + self.assertEqual(order.quote_asset, order.fee_asset) + + def test_update_with_full_fill_trade_event(self): + order = CoinbaseProInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "type": "match", + "trade_id": 1, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.1", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.005" + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + self.assertFalse(order.is_done) + self.assertEqual("open", order.last_state) + self.assertEqual(Decimal(str(trade_event_info["size"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["size"])) * Decimal( + str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + expected_partial_event_fee = (Decimal(trade_event_info["taker_fee_rate"]) * + expected_executed_quote_amount) + self.assertEqual(expected_partial_event_fee, order.fee_paid) + + complete_event_info = { + "type": "match", + "trade_id": 2, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.9", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.001" + } + + update_result = order.update_with_trade_update(complete_event_info) + + self.assertTrue(update_result) + # orders are marked as done with the done event + self.assertFalse(order.is_done) + self.assertEqual("open", order.last_state) + self.assertEqual(order.amount, order.executed_amount_base) + expected_executed_quote_amount += Decimal(str(complete_event_info["size"])) * Decimal( + str(complete_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + expected_complete_event_fee = (Decimal(complete_event_info["taker_fee_rate"]) * + Decimal(str(complete_event_info["size"])) * + Decimal(str(complete_event_info["price"]))) + self.assertEqual(expected_partial_event_fee + expected_complete_event_fee, order.fee_paid) + + def test_update_with_repeated_trade_id_is_ignored(self): + order = CoinbaseProInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10000), + amount=Decimal(1), + creation_timestamp=1640001112.0 + ) + + trade_event_info = { + "type": "match", + "trade_id": 1, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.1", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.005" + } + + update_result = order.update_with_trade_update(trade_event_info) + + self.assertTrue(update_result) + + complete_event_info = { + "type": "match", + "trade_id": 1, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.9", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.001" + } + + update_result = order.update_with_trade_update(complete_event_info) + + self.assertFalse(update_result) + self.assertFalse(order.is_done) + self.assertEqual("open", order.last_state) + self.assertEqual(Decimal(str(trade_event_info["size"])), order.executed_amount_base) + expected_executed_quote_amount = Decimal(str(trade_event_info["size"])) * Decimal( + str(trade_event_info["price"])) + self.assertEqual(expected_executed_quote_amount, order.executed_amount_quote) + self.assertEqual(Decimal(trade_event_info["taker_fee_rate"]) * expected_executed_quote_amount, order.fee_paid) diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coingbase_pro_exchange.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coingbase_pro_exchange.py new file mode 100644 index 0000000..63c52be --- /dev/null +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coingbase_pro_exchange.py @@ -0,0 +1,184 @@ +import asyncio +import functools +from decimal import Decimal +from typing import Awaitable, Callable, Optional +from unittest import TestCase +from unittest.mock import AsyncMock + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_exchange import CoinbaseProExchange +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent + + +class BitfinexExchangeTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.symbol = f"{cls.base_asset}{cls.quote_asset}" + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.test_task: Optional[asyncio.Task] = None + self.resume_test_event = asyncio.Event() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = CoinbaseProExchange( + client_config_map=self.client_config_map, + coinbase_pro_api_key="testAPIKey", + coinbase_pro_secret_key="testSecret", + coinbase_pro_passphrase="testPassphrase", + trading_pairs=[self.trading_pair] + ) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + + self._initialize_event_loggers() + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.order_filled_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.OrderFilled, self.order_filled_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _return_calculation_and_set_done_event(self, calculation: Callable, *args, **kwargs): + if self.resume_test_event.is_set(): + raise asyncio.CancelledError + self.resume_test_event.set() + return calculation(*args, **kwargs) + + def test_order_fill_event_takes_fee_from_update_event(self): + self.exchange.start_tracking_order( + order_id="OID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + order = self.exchange.in_flight_orders.get("OID1") + order.update_exchange_order_id("EOID1") + + partial_fill = { + "type": "match", + "trade_id": 1, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.1", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.005" + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: partial_fill) + + self.exchange.user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + expected_executed_quote_amount = Decimal(str(partial_fill["size"])) * Decimal(str(partial_fill["price"])) + expected_partial_event_fee = (Decimal(partial_fill["taker_fee_rate"]) * + expected_executed_quote_amount) + + self.assertEqual(expected_partial_event_fee, order.fee_paid) + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(Decimal("0.005"), fill_event.trade_fee.percent) + self.assertEqual([], fill_event.trade_fee.flat_fees) + self.assertTrue(self._is_logged( + "INFO", + f"Filled {Decimal(partial_fill['size'])} out of {order.amount} of the " + f"{order.order_type_description} order {order.client_order_id}" + )) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + complete_fill = { + "type": "match", + "trade_id": 2, + "sequence": 50, + "maker_order_id": "EOID1", + "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", + "time": "2014-11-07T08:19:27.028459Z", + "product_id": "BTC-USDT", + "size": "0.9", + "price": "10050.0", + "side": "buy", + "taker_user_id": "5844eceecf7e803e259d0365", + "user_id": "5844eceecf7e803e259d0365", + "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", + "taker_fee_rate": "0.001" + } + + self.resume_test_event = asyncio.Event() + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: complete_fill) + + self.exchange.user_stream_tracker._user_stream = mock_user_stream + + self.test_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + self.async_run_with_timeout(self.resume_test_event.wait()) + + expected_executed_quote_amount = Decimal(str(complete_fill["size"])) * Decimal(str(complete_fill["price"])) + expected_partial_event_fee += Decimal(complete_fill["taker_fee_rate"]) * expected_executed_quote_amount + + self.assertEqual(expected_partial_event_fee, order.fee_paid) + + self.assertEqual(2, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(Decimal("0.001"), fill_event.trade_fee.percent) + self.assertEqual([], fill_event.trade_fee.flat_fees) + + # The order should be marked as complete only when the "done" event arrives, not with the fill event + self.assertFalse(self._is_logged( + "INFO", + f"The market buy order {order.client_order_id} has completed according to Coinbase Pro user stream." + )) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) diff --git a/test/hummingbot/connector/exchange/foxbit/__init__.py b/test/hummingbot/connector/exchange/foxbit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py new file mode 100644 index 0000000..30addcb --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py @@ -0,0 +1,508 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.foxbit import foxbit_constants as CONSTANTS, foxbit_web_utils as web_utils +from hummingbot.connector.exchange.foxbit.foxbit_api_order_book_data_source import FoxbitAPIOrderBookDataSource +from hummingbot.connector.exchange.foxbit.foxbit_exchange import FoxbitExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class FoxbitAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.domain = CONSTANTS.DEFAULT_DOMAIN + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = FoxbitExchange( + client_config_map=client_config_map, + foxbit_api_key="", + foxbit_api_secret="", + foxbit_user_id="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.data_source = FoxbitAPIOrderBookDataSource(trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + self.data_source._live_stream_connected[1] = True + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + self.connector._set_trading_pair_instrument_id_map(bidict({1: self.ex_trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + def _trade_update_event(self): + return {'m': 3, 'i': 10, 'n': 'TradeDataUpdateEvent', 'o': '[[194,1,"0.1","8432.0",787704,792085,1661952966311,0,0,false,0]]'} + + def _order_diff_event(self): + return {'m': 3, 'i': 8, 'n': 'Level2UpdateEvent', 'o': '[[187,0,1661952966257,1,8432,0,8432,1,7.6,1]]'} + + def _snapshot_response(self): + resp = { + "sequence_id": 1, + "asks": [ + [ + "145901.0", + "8.65827849" + ], + [ + "145902.0", + "10.0" + ], + [ + "145903.0", + "10.0" + ] + ], + "bids": [ + [ + "145899.0", + "2.33928943" + ], + [ + "145898.0", + "9.96927011" + ], + [ + "145897.0", + "10.0" + ], + [ + "145896.0", + "10.0" + ] + ] + } + return resp + + def _level_1_response(self): + return [ + { + "OMSId": 1, + "InstrumentId": 4, + "MarketId": "ethbrl", + "BestBid": 112824.303, + "BestOffer": 113339.6599, + "LastTradedPx": 112794.1036, + "LastTradedQty": 0.00443286, + "LastTradeTime": 1658841244, + "SessionOpen": 119437.9079, + "SessionHigh": 115329.8396, + "SessionLow": 112697.42, + "SessionClose": 113410.0483, + "Volume": 0.00443286, + "CurrentDayVolume": 91.4129, + "CurrentDayNumTrades": 1269, + "CurrentDayPxChange": -1764.6783, + "Rolling24HrVolume": 103.5911, + "Rolling24NumTrades": 3354, + "Rolling24HrPxChange": -5.0469, + "TimeStamp": 1658841286 + } + ] + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL.format(self.trading_pair), domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) + + order_book: OrderBook = self.async_run_with_timeout( + coroutine=self.data_source.get_new_order_book(self.trading_pair), + timeout=2000 + ) + + expected_update_id = order_book.snapshot_uid + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(4, len(bids)) + self.assertEqual(145899, bids[0].price) + self.assertEqual(2.33928943, bids[0].amount) + self.assertEqual(3, len(asks)) + self.assertEqual(145901, asks[0].price) + self.assertEqual(8.65827849, asks[0].amount) + + @patch("hummingbot.connector.exchange.foxbit.foxbit_api_order_book_data_source.FoxbitAPIOrderBookDataSource._ORDER_BOOK_INTERVAL", 0.0) + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL.format(self.trading_pair), domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + coroutine=self.data_source.get_new_order_book(self.trading_pair), + timeout=2000 + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + result_subscribe_trades = { + "result": None, + "id": 1 + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + + result_subscribe_diffs = { + 'm': 0, + 'i': 2, + 'n': 'SubscribeLevel2', + 'o': '[[1,0,1667228256347,0,8454,0,8435.1564,1,0.001,0],[2,0,1667228256347,0,8454,0,8418,1,13.61149632,0],[3,0,1667228256347,0,8454,0,8417,1,10,0],[4,0,1667228256347,0,8454,0,8416,1,10,0],[5,0,1667228256347,0,8454,0,8415,1,10,0],[6,0,1667228256347,0,8454,0,8454,1,6.44410902,1],[7,0,1667228256347,0,8454,0,8455,1,10,1],[8,0,1667228256347,0,8454,0,8456,1,10,1],[9,0,1667228256347,0,8454,0,8457,1,10,1],[10,0,1667228256347,0,8454,0,8458,1,10,1]]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + 'Content-Type': 'application/json', + 'User-Agent': 'HBOT', + 'm': 0, + 'i': 2, + 'n': 'GetInstruments', + 'o': '{"OMSId": 1, "InstrumentId": 1, "Depth": 10}' + } + self.assertEqual(expected_trade_subscription['o'], sent_subscription_messages[0]['o']) + + expected_diff_subscription = { + 'Content-Type': 'application/json', + 'User-Agent': 'HBOT', + 'm': 0, + 'i': 2, + 'n': 'SubscribeLevel2', + 'o': '{"InstrumentId": 1}' + } + self.assertEqual(expected_diff_subscription['o'], sent_subscription_messages[1]['o']) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book channel..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_raises_cancel_exception(self, ws_connect_mock, _: AsyncMock): + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_subscribe_channels_raises_cancel_exception(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_subscribe_channels_raises_exception_and_logs_error(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue["trade"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue["trade"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_trades_successful(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue["trade"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(194, msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue["order_book_diff"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue["order_book_diff"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_successful(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + mock_queue = AsyncMock() + diff_event = self._order_diff_event() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue["order_book_diff"] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + expected_id = eval(diff_event["o"])[0][0] + self.assertEqual(expected_id, msg.update_id) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_auth.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_auth.py new file mode 100644 index 0000000..d3e7f73 --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_auth.py @@ -0,0 +1,71 @@ +import asyncio +import hashlib +import hmac +from unittest import TestCase +from unittest.mock import MagicMock + +from typing_extensions import Awaitable + +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils as utils, + foxbit_web_utils as web_utils, +) +from hummingbot.connector.exchange.foxbit.foxbit_auth import FoxbitAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class FoxbitAuthTests(TestCase): + + def setUp(self) -> None: + self._api_key = "testApiKey" + self._secret = "testSecret" + self._user_id = "testUserId" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_authenticate(self): + now = 1234567890.000 + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + + params = { + "symbol": "COINALPHAHBOT", + "side": "BUY", + "type": "LIMIT", + "timeInForce": "GTC", + "quantity": 1, + "price": "0.1", + } + + auth = FoxbitAuth(api_key=self._api_key, secret_key=self._secret, user_id=self._user_id, time_provider=mock_time_provider) + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + endpoint_url = web_utils.rest_endpoint_url(url) + request = RESTRequest(url=url, endpoint_url=endpoint_url, method=RESTMethod.GET, data=params, is_auth_required=True) + configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) + + timestamp = configured_request.headers['X-FB-ACCESS-TIMESTAMP'] + payload = '{}{}{}{}'.format(timestamp, + request.method, + request.endpoint_url, + params) + expected_signature = hmac.new(self._secret.encode("utf8"), payload.encode("utf8"), hashlib.sha256).digest().hex() + self.assertEqual(self._api_key, configured_request.headers['X-FB-ACCESS-KEY']) + self.assertEqual(expected_signature, configured_request.headers['X-FB-ACCESS-SIGNATURE']) + + def test_ws_authenticate(self): + now = 1234567890.000 + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + + auth = FoxbitAuth(api_key=self._api_key, secret_key=self._secret, user_id=self._user_id, time_provider=mock_time_provider) + header = utils.get_ws_message_frame( + endpoint=CONSTANTS.WS_AUTHENTICATE_USER, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Request"], + payload=auth.get_ws_authenticate_payload(), + ) + subscribe_request: WSJSONRequest = WSJSONRequest(payload=web_utils.format_ws_header(header), is_auth_required=True) + retValue = self.async_run_with_timeout(auth.ws_authenticate(subscribe_request)) + self.assertIsNotNone(retValue) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py new file mode 100644 index 0000000..d0650d3 --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py @@ -0,0 +1,1194 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils as utils, + foxbit_web_utils as web_utils, +) +from hummingbot.connector.exchange.foxbit.foxbit_exchange import FoxbitExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase + + +class FoxbitExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + mapping = bidict() + mapping[1] = self.trading_pair + self.exchange._trading_pair_instrument_id_map = mapping + + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=self.exchange._domain) + url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" + return url + + @property + def network_status_url(self): + url = web_utils.private_rest_url(CONSTANTS.PING_PATH_URL, domain=self.exchange._domain) + return url + + @property + def trading_rules_url(self): + url = web_utils.private_rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL, domain=self.exchange._domain) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL, domain=self.exchange._domain) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "data": [ + { + "symbol": '{}{}'.format(self.base_asset.lower(), self.quote_asset.lower()), + "quantity_min": "0.00002", + "quantity_increment": "0.00001", + "price_min": "1.0", + "price_increment": "0.0001", + "base": { + "symbol": self.base_asset.lower(), + "name": "Bitcoin", + "type": "CRYPTO" + }, + "quote": { + "symbol": self.quote_asset.lower(), + "name": "Bitcoin", + "type": "CRYPTO" + } + } + ] + } + + @property + def latest_prices_request_mock_response(self): + return { + "OMSId": 1, + "InstrumentId": 1, + "BestBid": 0.00, + "BestOffer": 0.00, + "LastTradedPx": 0.00, + "LastTradedQty": 0.00, + "LastTradeTime": 635872032000000000, + "SessionOpen": 0.00, + "SessionHigh": 0.00, + "SessionLow": 0.00, + "SessionClose": 0.00, + "Volume": 0.00, + "CurrentDayVolume": 0.00, + "CurrentDayNumTrades": 0, + "CurrentDayPxChange": 0.0, + "Rolling24HrVolume": 0.0, + "Rolling24NumTrades": 0.0, + "Rolling24HrPxChange": 0.0, + "TimeStamp": 635872032000000000, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "TRADING", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + { + "symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "status": "TRADING", + "baseAsset": "INVALID", + "baseAssetPrecision": 8, + "quoteAsset": "PAIR", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + ] + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "data": [ + { + "symbol": '{}{}'.format(self.base_asset, self.quote_asset), + "quantity_min": "0.00002", + "quantity_increment": "0.00001", + "price_min": "1.0", + "price_increment": "0.0001", + "base": { + "symbol": self.base_asset, + "name": "Bitcoin", + "type": "CRYPTO" + }, + "quote": { + "symbol": self.quote_asset, + "name": "Bitcoin", + "type": "CRYPTO" + } + } + ] + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "data": [ + { + "symbol": '{}'.format(self.base_asset), + "quantity_min": "0.00002", + "quantity_increment": "0.00001", + "price_min": "1.0", + "price_increment": "0.0001", + "base": { + "symbol": self.base_asset, + "name": "Bitcoin", + "type": "CRYPTO" + }, + "quote": { + "symbol": self.quote_asset, + "name": "Bitcoin", + "type": "CRYPTO" + } + } + ] + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "id": self.expected_exchange_order_id, + "sn": "OKMAKSDHRVVREK" + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "data": [ + { + "currency_symbol": self.base_asset, + "balance": "15.0", + "balance_available": "10.0", + "balance_locked": "0.0" + }, + { + "currency_symbol": self.quote_asset, + "balance": "2000.0", + "balance_available": "2000.0", + "balance_locked": "0.0" + } + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "data": [ + { + "currency_symbol": self.base_asset, + "balance": "15.0", + "balance_available": "10.0", + "balance_locked": "0.0" + } + ] + } + + @property + def balance_event_websocket_update(self): + return { + "n": "AccountPositionEvent", + "o": '{"ProductSymbol":"' + self.base_asset + '","Hold":"5.0","Amount": "15.0"}' + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["quantity_min"]), + min_price_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["price_increment"]), + min_base_amount_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["quantity_increment"]), + min_notional_size=Decimal(self.trading_rules_request_mock_response["data"][0]["price_min"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0]["symbol"] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 28 + + @property + def is_cancel_request_executed_synchronously_by_server(self) -> bool: + return True + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return 30000 + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return FoxbitExchange( + client_config_map=client_config_map, + foxbit_api_key="testAPIKey", + foxbit_api_secret="testSecret", + foxbit_user_id="testUserId", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument( + request_call_tuple=request_call, + params=request_call.kwargs["params"] or request_call.kwargs["data"] + ) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = eval(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["market_symbol"]) + self.assertEqual(order.trade_type.name.upper(), request_data["side"]) + self.assertEqual(FoxbitExchange.foxbit_order_type(OrderType.LIMIT), request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["quantity"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["client_order_id"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = eval(request_call.kwargs["data"]) + self.assertEqual(order.client_order_id, request_data["client_order_id"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.exchange_order_id, str(request_params["orderId"])) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.put(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.put(regex_url, status=400, callback=callback) + return url + + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2011, "msg": "Unknown order sent."} + mock_api.put(regex_url, status=400, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_CLIENT_ID.format(order.client_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + # Trade fills not requested during status update in this connector + pass + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2013, "msg": "Order does not exist."} + mock_api.get(regex_url, body=json.dumps(response), status=400, callback=callback) + return [url] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "ACTIVE", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": "0.0", + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "0", + "remark": "A remarkable note for the order." + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "CANCELLED", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": "0.0", + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "0", + "remark": "A remarkable note for the order." + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "n": "OrderStateEvent", + "o": "{'Side': 'Buy'," + + "'OrderId': " + order.client_order_id + "1'," + + "'Price': " + str(order.price) + "," + + "'Quantity': " + str(order.amount) + "," + + "'OrderType': 'Limit'," + + "'ClientOrderId': " + order.client_order_id + "," + + "'OrderState': 1," + + "'OrigQuantity': " + str(order.amount) + "," + + "'QuantityExecuted': " + str(order.amount) + "," + + "'AvgPrice': " + str(order.price) + "," + + "'ChangeReason': 'Fill'," + + "'Instrument': 1}" + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "n": "OrderTradeEvent", + "o": "{'InstrumentId': 1," + + "'OrderType': 'Limit'," + + "'OrderId': " + order.client_order_id + "1," + + "'ClientOrderId': " + order.client_order_id + "," + + "'Price': " + str(order.price) + "," + + "'Value': " + str(order.price) + "," + + "'Quantity': " + str(order.amount) + "," + + "'RemainingQuantity': 0.00," + + "'Side': 'Buy'," + + "'TradeId': 1," + + "'TradeTimeMS': 1640780000}" + } + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_all_trading_pairs(self, mock_api, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + self.exchange._set_trading_pair_symbol_map(None) + url = self.all_symbols_url + + response = self.all_symbols_request_mock_response + mock_api.get(url, body=json.dumps(response)) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertEqual(1, len(all_trading_pairs)) + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + request_sent_event = asyncio.Event() + seconds_counter_mock.side_effect = [0, 0, 0] + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"timestamp": 1640000003000} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertEqual(response["timestamp"] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + request_sent_event = asyncio.Event() + + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"code": -1121, "msg": "Dummy error"} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + get_error = False + + try: + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + get_error = True + except Exception: + get_error = True + + self.assertTrue(get_error) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, + exception=asyncio.CancelledError) + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, self.exchange._update_time_synchronizer()) + + @aioresponses() + def test_update_order_fills_from_trades_triggers_filled_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = 0 + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = '{}{}{}'.format(web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL), 'market_symbol=', self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill = { + "data": { + "id": 28457, + "sn": "TC5JZVW2LLJ3IW", + "order_id": int(order.exchange_order_id), + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "price": "9999", + "quantity": "1", + "fee": "10.10", + "fee_currency_symbol": self.quote_asset, + "created_at": "2021-02-15T22:06:32.999Z" + } + } + + trade_fill_non_tracked_order = { + "data": { + "id": 3000, + "sn": "AB5JQAW9TLJKJ0", + "order_id": int(order.exchange_order_id), + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "price": "9999", + "quantity": "1", + "fee": "10.10", + "fee_currency_symbol": self.quote_asset, + "created_at": "2021-02-15T22:06:33.999Z" + } + } + + mock_response = [trade_fill, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order['data']["order_id"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL))[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["market_symbol"]) + + @aioresponses() + def test_update_order_fills_request_parameters(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = 0 + + url = '{}{}{}'.format(web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL), 'market_symbol=', self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = [] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL))[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["market_symbol"]) + + @aioresponses() + def test_update_order_fills_from_trades_with_repeated_fill_triggers_only_one_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = 0 + + url = '{}{}{}'.format(web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL), 'market_symbol=', self.trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill_non_tracked_order = { + "data": { + "id": 3000, + "sn": "AB5JQAW9TLJKJ0", + "order_id": 9999, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "price": "9999", + "quantity": "1", + "fee": "10.10", + "fee_currency_symbol": self.quote_asset, + "created_at": "2021-02-15T22:06:33.999Z" + } + } + + mock_response = [trade_fill_non_tracked_order, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order['data']["order_id"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL))[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["market_symbol"]) + + @aioresponses() + def test_update_order_status_when_failed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = 0 + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "CANCELED", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": "0.0", + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "1", + "remark": "A remarkable note for the order." + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + request = self._all_executed_requests(mock_api, web_utils.private_rest_url(CONSTANTS.GET_ORDER_BY_ID.format(order.exchange_order_id))) + self.assertEqual([], request) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + url = self.configure_erroneous_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="11") + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue(any(log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records)) + + def test_client_order_id_on_order(self): + self.exchange._set_current_timestamp(1640780000) + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = utils.get_client_order_id( + is_buy=True, + ) + + self.assertEqual(result[:12], expected_client_order_id[:12]) + self.assertEqual(result[:2], self.exchange.client_order_id_prefix) + self.assertLess(len(expected_client_order_id), self.exchange.client_order_id_max_length) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = utils.get_client_order_id( + is_buy=False, + ) + + self.assertEqual(result[:12], expected_client_order_id[:12]) + + def test_create_order(self): + self._simulate_trading_rules_initialized() + _order = self.async_run_with_timeout(self.exchange._create_order(TradeType.BUY, + '551100', + self.trading_pair, + Decimal(1.01), + OrderType.LIMIT, + Decimal(22354.01))) + self.assertIsNone(_order) + + @aioresponses() + def test_create_limit_buy_order_raises_error(self, mock_api): + self._simulate_trading_rules_initialized() + try: + self.async_run_with_timeout(self.exchange._create_order(TradeType.BUY, + '551100', + self.trading_pair, + Decimal(1.01), + OrderType.LIMIT, + Decimal(22354.01))) + except Exception as err: + self.assertEqual('', err.args[0]) + + @aioresponses() + def test_create_limit_sell_order_raises_error(self, mock_api): + self._simulate_trading_rules_initialized() + try: + self.async_run_with_timeout(self.exchange._create_order(TradeType.SELL, + '551100', + self.trading_pair, + Decimal(1.01), + OrderType.LIMIT, + Decimal(22354.01))) + except Exception as err: + self.assertEqual('', err.args[0]) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "instruments_mapping_initialized": True, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_get_last_trade_prices(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_response = { + 'm': 0, + 'i': 1, + 'n': + 'SubscribeLevel1', + 'o': '{"OMSId":1,"InstrumentId":1,"MarketId":"coinalphahbot","BestBid":145899,"BestOffer":145901,"LastTradedPx":145899,"LastTradedQty":0.0009,"LastTradeTime":1662663925,"SessionOpen":145899,"SessionHigh":145901,"SessionLow":145899,"SessionClose":145901,"Volume":0.0009,"CurrentDayVolume":0.008,"CurrentDayNumTrades":17,"CurrentDayPxChange":2,"Rolling24HrVolume":0.008,"Rolling24NumTrades":17,"Rolling24HrPxChange":0.0014,"TimeStamp":1662736972}' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_response)) + + expected_value = 145899.0 + ret_value = self.async_run_with_timeout(self.exchange._get_last_traded_price(self.trading_pair)) + + self.assertEqual(expected_value, ret_value) + + def _validate_auth_credentials_taking_parameters_from_argument(self, + request_call_tuple: RequestCall, + params: Dict[str, Any]): + request_headers = request_call_tuple.kwargs["headers"] + self.assertIn("X-FB-ACCESS-SIGNATURE", request_headers) + self.assertEqual("testAPIKey", request_headers["X-FB-ACCESS-KEY"]) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "data": [ + { + "sn": "OKMAKSDHRVVREK", + "id": "21" + } + ] + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "FILLED", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": str(order.amount), + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "3", + "remark": "A remarkable note for the order." + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "CANCELED", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": "0.0", + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "1", + "remark": "A remarkable note for the order." + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "ACTIVE", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": "0.0", + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "0", + "remark": "A remarkable note for the order." + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "id": order.exchange_order_id, + "sn": "OKMAKSDHRVVREK", + "client_order_id": order.client_order_id, + "market_symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "type": "LIMIT", + "state": "PARTIALLY_FILLED", + "price": str(order.price), + "price_avg": str(order.price), + "quantity": str(order.amount), + "quantity_executed": str(order.amount / 2), + "instant_amount": "0.0", + "instant_amount_executed": "0.0", + "created_at": "2022-09-08T17:06:32.999Z", + "trades_count": "2", + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "n": "OrderTradeEvent", + "o": "{'InstrumentId': 1," + + "'OrderType': 'Limit'," + + "'OrderId': " + order.client_order_id + "1," + + "'ClientOrderId': " + order.client_order_id + "," + + "'Price': " + str(order.price) + "," + + "'Value': " + str(order.price) + "," + + "'Quantity': " + str(order.amount) + "," + + "'RemainingQuantity': 0.00," + + "'Side': 'Buy'," + + "'TradeId': 1," + + "'TradeTimeMS': 1640780000}" + } + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_exchange_properties_and_commons(self, ws_connect_mock): + self.assertEqual(CONSTANTS.EXCHANGE_INFO_PATH_URL, self.exchange.trading_rules_request_path) + self.assertEqual(CONSTANTS.EXCHANGE_INFO_PATH_URL, self.exchange.trading_pairs_request_path) + self.assertEqual(CONSTANTS.PING_PATH_URL, self.exchange.check_network_request_path) + self.assertTrue(self.exchange.is_cancel_request_in_exchange_synchronous) + self.assertTrue(self.exchange.is_trading_required) + self.assertEqual('1', self.exchange.convert_from_exchange_instrument_id('1')) + self.assertEqual('1', self.exchange.convert_to_exchange_instrument_id('1')) + self.assertEqual('MARKET', self.exchange.foxbit_order_type(OrderType.MARKET)) + try: + self.exchange.foxbit_order_type(OrderType.LIMIT_MAKER) + except Exception as err: + self.assertEqual('Order type not supported by Foxbit.', err.args[0]) + + self.assertEqual(OrderType.MARKET, self.exchange.to_hb_order_type('MARKET')) + self.assertEqual([OrderType.LIMIT, OrderType.MARKET], self.exchange.supported_order_types()) + self.assertTrue(self.exchange.trading_pair_instrument_id_map_ready) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ixm_config = { + 'm': 0, + 'i': 1, + 'n': 'GetInstruments', + 'o': '[{"OMSId":1,"InstrumentId":1,"Symbol":"COINALPHA/HBOT","Product1":1,"Product1Symbol":"COINALPHA","Product2":2,"Product2Symbol":"HBOT","InstrumentType":"Standard","VenueInstrumentId":1,"VenueId":1,"SortIndex":0,"SessionStatus":"Running","PreviousSessionStatus":"Paused","SessionStatusDateTime":"2020-07-11T01:27:02.851Z","SelfTradePrevention":true,"QuantityIncrement":1e-8,"PriceIncrement":0.01,"MinimumQuantity":1e-8,"MinimumPrice":0.01,"VenueSymbol":"BTC/BRL","IsDisable":false,"MasterDataId":0,"PriceCollarThreshold":0,"PriceCollarPercent":0,"PriceCollarEnabled":false,"PriceFloorLimit":0,"PriceFloorLimitEnabled":false,"PriceCeilingLimit":0,"PriceCeilingLimitEnabled":false,"CreateWithMarketRunning":true,"AllowOnlyMarketMakerCounterParty":false}]' + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(ixm_config)) + _currentTP = self.async_run_with_timeout(self.exchange.trading_pair_instrument_id_map()) + self.assertIsNotNone(_currentTP) + self.assertEqual(self.trading_pair, _currentTP[1]) + _currentTP = self.async_run_with_timeout(self.exchange.exchange_instrument_id_associated_to_pair('COINALPHA-HBOT')) + self.assertEqual(1, _currentTP) + + self.assertIsNotNone(self.exchange.get_fee('COINALPHA', 'BOT', OrderType.MARKET, TradeType.BUY, 1.0, 22500.011, False)) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + pass + + @aioresponses() + def test_update_order_status_when_canceled(self, mock_api): + pass + + @aioresponses() + def test_update_order_status_when_order_has_not_changed(self, mock_api): + pass + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + pass + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + pass + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self, mock_api): + pass + + @aioresponses() + def test_update_order_status_when_filled_correctly_processed_even_when_trade_fill_update_fails(self, mock_api): + pass + + def test_user_stream_update_for_new_order(self): + pass + + def test_user_stream_update_for_canceled_order(self): + pass + + def test_user_stream_raises_cancel_exception(self): + pass + + def test_user_stream_logs_errors(self): + pass + + @aioresponses() + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self, mock_api): + pass + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + pass + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + pass diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_order_book.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_order_book.py new file mode 100644 index 0000000..401e494 --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_order_book.py @@ -0,0 +1,385 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.foxbit.foxbit_order_book import FoxbitOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class FoxbitOrderBookTests(TestCase): + + def test_snapshot_message_from_exchange(self): + snapshot_message = FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [ + ["0.0024", "100.1"], + ["0.0023", "100.11"], + ["0.0022", "100.12"], + ["0.0021", "100.13"], + ["0.0020", "100.14"], + ["0.0019", "100.15"], + ["0.0018", "100.16"], + ["0.0017", "100.17"], + ["0.0016", "100.18"], + ["0.0015", "100.19"], + ["0.0014", "100.2"], + ["0.0013", "100.21"] + ], + "asks": [ + ["0.0026", "100.2"], + ["0.0027", "100.21"], + ["0.0028", "100.22"], + ["0.0029", "100.23"], + ["0.0030", "100.24"], + ["0.0031", "100.25"], + ["0.0032", "100.26"], + ["0.0033", "100.27"], + ["0.0034", "100.28"], + ["0.0035", "100.29"], + ["0.0036", "100.3"], + ["0.0037", "100.31"] + ] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", snapshot_message.trading_pair) + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type) + self.assertEqual(1640000000.0, snapshot_message.timestamp) + self.assertEqual(1, snapshot_message.update_id) + self.assertEqual(-1, snapshot_message.first_update_id) + self.assertEqual(-1, snapshot_message.trade_id) + self.assertEqual(10, len(snapshot_message.bids)) + self.assertEqual(0.0024, snapshot_message.bids[0].price) + self.assertEqual(100.1, snapshot_message.bids[0].amount) + self.assertEqual(0.0015, snapshot_message.bids[9].price) + self.assertEqual(100.19, snapshot_message.bids[9].amount) + self.assertEqual(10, len(snapshot_message.asks)) + self.assertEqual(0.0026, snapshot_message.asks[0].price) + self.assertEqual(100.2, snapshot_message.asks[0].amount) + self.assertEqual(0.0035, snapshot_message.asks[9].price) + self.assertEqual(100.29, snapshot_message.asks[9].amount) + + def test_diff_message_from_exchange_new_bid(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 0, + 145901, + 0, + 0.0025, + 1, + 10.3, + 0 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0, len(diff_msg.asks)) + self.assertEqual(0.0025, diff_msg.bids[0].price) + self.assertEqual(10.3, diff_msg.bids[0].amount) + + def test_diff_message_from_exchange_new_ask(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 0, + 145901, + 0, + 0.00255, + 1, + 23.7, + 1 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(0, len(diff_msg.bids)) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.00255, diff_msg.asks[0].price) + self.assertEqual(23.7, diff_msg.asks[0].amount) + + def test_diff_message_from_exchange_update_bid(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 1, + 145901, + 0, + 0.0025, + 1, + 54.9, + 0 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0, len(diff_msg.asks)) + self.assertEqual(0.0025, diff_msg.bids[0].price) + self.assertEqual(54.9, diff_msg.bids[0].amount) + + def test_diff_message_from_exchange_update_ask(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 1, + 145901, + 0, + 0.00255, + 1, + 4.5, + 1 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(0, len(diff_msg.bids)) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.00255, diff_msg.asks[0].price) + self.assertEqual(4.5, diff_msg.asks[0].amount) + + def test_diff_message_from_exchange_deletion_bid(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 0, + 145901, + 0, + 0.0025, + 1, + 10.3, + 0 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0, len(diff_msg.asks)) + self.assertEqual(0.0025, diff_msg.bids[0].price) + self.assertEqual(10.3, diff_msg.bids[0].amount) + + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[3, + 0, + 1660844469114, + 2, + 145901, + 0, + 0.0025, + 1, + 0, + 0 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(3, diff_msg.update_id) + self.assertEqual(3, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0, len(diff_msg.asks)) + self.assertEqual(0.0025, diff_msg.bids[0].price) + self.assertEqual(0.0, diff_msg.bids[0].amount) + + def test_diff_message_from_exchange_deletion_ask(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[2, + 0, + 1660844469114, + 1, + 145901, + 0, + 0.00255, + 1, + 23.7, + 1 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(2, diff_msg.update_id) + self.assertEqual(2, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(0, len(diff_msg.bids)) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.00255, diff_msg.asks[0].price) + self.assertEqual(23.7, diff_msg.asks[0].amount) + + diff_msg = FoxbitOrderBook.diff_message_from_exchange( + msg=[3, + 0, + 1660844469114, + 2, + 145901, + 0, + 0.00255, + 1, + 23.7, + 1 + ], + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1660844469114.0, diff_msg.timestamp) + self.assertEqual(3, diff_msg.update_id) + self.assertEqual(3, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(0, len(diff_msg.bids)) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.00255, diff_msg.asks[0].price) + self.assertEqual(0.0, diff_msg.asks[0].amount) + + def test_trade_message_from_exchange(self): + FoxbitOrderBook.snapshot_message_from_exchange( + msg={ + "instrumentId": "COINALPHA-HBOT", + "sequence_id": 1, + "timestamp": 2, + "bids": [["0.0024", "100.1"]], + "asks": [["0.0026", "100.2"]] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + trade_update = [194, + 4, + "0.1", + "8432.0", + 787704, + 792085, + 1661952966311, + 0, + 0, + False, + 0] + + trade_message = FoxbitOrderBook.trade_message_from_exchange( + msg=trade_update, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", trade_message.trading_pair) + self.assertEqual(OrderBookMessageType.TRADE, trade_message.type) + self.assertEqual(1661952966.311, trade_message.timestamp) + self.assertEqual(-1, trade_message.update_id) + self.assertEqual(-1, trade_message.first_update_id) + self.assertEqual(194, trade_message.trade_id) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py new file mode 100644 index 0000000..b011d3e --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py @@ -0,0 +1,137 @@ +import asyncio +import json +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import MagicMock + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.foxbit import foxbit_constants as CONSTANTS +from hummingbot.connector.exchange.foxbit.foxbit_api_user_stream_data_source import FoxbitAPIUserStreamDataSource +from hummingbot.connector.exchange.foxbit.foxbit_auth import FoxbitAuth +from hummingbot.connector.exchange.foxbit.foxbit_exchange import FoxbitExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + + +class FoxbitUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self._api_key = "testApiKey" + self._secret = "testSecret" + self._user_id = "testUserId" + self.auth = FoxbitAuth(api_key=self._api_key, secret_key=self._secret, user_id=self._user_id, time_provider=self.mock_time_provider) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = FoxbitExchange( + client_config_map=client_config_map, + foxbit_api_key="testAPIKey", + foxbit_api_secret="testSecret", + foxbit_user_id="testUserId", + trading_pairs=[self.trading_pair], + ) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = FoxbitAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _error_response(self) -> Dict[str, Any]: + resp = { + "code": "ERROR CODE", + "msg": "ERROR MESSAGE" + } + + return resp + + def _user_update_event(self): + # Balance Update + resp = { + "e": "balanceUpdate", + "E": 1573200697110, + "a": "BTC", + "d": "100.00000000", + "T": 1573200697068 + } + return json.dumps(resp) + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + def test_user_stream_properties(self): + self.assertEqual(self.data_source.ready, self.data_source._user_stream_data_source_initialized) + + async def test_run_ws_assistant(self): + ws: WSAssistant = await self.data_source._connected_websocket_assistant() + self.assertIsNotNone(ws) + await self.data_source._subscribe_channels(ws) + await self.data_source._on_user_stream_interruption(ws) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_utils.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_utils.py new file mode 100644 index 0000000..ba0b080 --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_utils.py @@ -0,0 +1,112 @@ +import unittest +from datetime import datetime +from decimal import Decimal +from unittest.mock import MagicMock + +from hummingbot.connector.exchange.foxbit import foxbit_utils as utils +from hummingbot.core.data_type.in_flight_order import OrderState + + +class FoxbitUtilTestCases(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hb_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def test_is_exchange_information_valid(self): + valid_info = { + "status": "TRADING", + "permissions": ["SPOT"], + } + self.assertTrue(utils.is_exchange_information_valid(valid_info)) + + def test_get_client_order_id(self): + now = 1234567890.000 + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + + retValue = utils.get_client_order_id(True) + self.assertLess(retValue, utils.get_client_order_id(True)) + retValue = utils.get_client_order_id(False) + self.assertLess(retValue, utils.get_client_order_id(False)) + + def test_get_ws_message_frame(self): + _msg_A = utils.get_ws_message_frame('endpoint_A') + _msg_B = utils.get_ws_message_frame('endpoint_B') + self.assertEqual(_msg_A['m'], _msg_B['m']) + self.assertNotEqual(_msg_A['n'], _msg_B['n']) + self.assertLess(_msg_A['i'], _msg_B['i']) + + def test_ws_data_to_dict(self): + _expectedValue = [{'Key': 'field0', 'Value': 'Google'}, {'Key': 'field2', 'Value': None}, {'Key': 'field3', 'Value': 'São Paulo'}, {'Key': 'field4', 'Value': False}, {'Key': 'field5', 'Value': 'SAO PAULO'}, {'Key': 'field6', 'Value': '00000001'}, {'Key': 'field7', 'Value': True}] + _msg = '[{"Key":"field0","Value":"Google"},{"Key":"field2","Value":null},{"Key":"field3","Value":"São Paulo"},{"Key":"field4","Value":false},{"Key":"field5","Value":"SAO PAULO"},{"Key":"field6","Value":"00000001"},{"Key":"field7","Value":true}]' + _retValue = utils.ws_data_to_dict(_msg) + self.assertEqual(_expectedValue, _retValue) + + def test_datetime_val_or_now(self): + self.assertIsNone(utils.datetime_val_or_now('NotValidDate', '', False)) + self.assertLessEqual(datetime.now(), utils.datetime_val_or_now('NotValidDate', '', True)) + self.assertLessEqual(datetime.now(), utils.datetime_val_or_now('NotValidDate', '')) + _now = '2023-04-19T18:53:17.981Z' + _fNow = datetime.strptime(_now, '%Y-%m-%dT%H:%M:%S.%fZ') + self.assertEqual(_fNow, utils.datetime_val_or_now(_now)) + + def test_decimal_val_or_none(self): + self.assertIsNone(utils.decimal_val_or_none('NotValidDecimal')) + self.assertIsNone(utils.decimal_val_or_none('NotValidDecimal', True)) + self.assertEqual(0, utils.decimal_val_or_none('NotValidDecimal', False)) + _dec = '2023.0419' + self.assertEqual(Decimal(_dec), utils.decimal_val_or_none(_dec)) + + def test_int_val_or_none(self): + self.assertIsNone(utils.int_val_or_none('NotValidInt')) + self.assertIsNone(utils.int_val_or_none('NotValidInt', True)) + self.assertEqual(0, utils.int_val_or_none('NotValidInt', False)) + _dec = '2023' + self.assertEqual(2023, utils.int_val_or_none(_dec)) + + def test_get_order_state(self): + self.assertIsNone(utils.get_order_state('NotValidOrderState')) + self.assertIsNone(utils.get_order_state('NotValidOrderState', False)) + self.assertEqual(OrderState.FAILED, utils.get_order_state('NotValidOrderState', True)) + self.assertEqual(OrderState.PENDING_CREATE, utils.get_order_state('PENDING')) + self.assertEqual(OrderState.OPEN, utils.get_order_state('ACTIVE')) + self.assertEqual(OrderState.OPEN, utils.get_order_state('NEW')) + self.assertEqual(OrderState.FILLED, utils.get_order_state('FILLED')) + self.assertEqual(OrderState.PARTIALLY_FILLED, utils.get_order_state('PARTIALLY_FILLED')) + self.assertEqual(OrderState.OPEN, utils.get_order_state('PENDING_CANCEL')) + self.assertEqual(OrderState.CANCELED, utils.get_order_state('CANCELED')) + self.assertEqual(OrderState.PARTIALLY_FILLED, utils.get_order_state('PARTIALLY_CANCELED')) + self.assertEqual(OrderState.FAILED, utils.get_order_state('REJECTED')) + self.assertEqual(OrderState.FAILED, utils.get_order_state('EXPIRED')) + self.assertEqual(OrderState.PENDING_CREATE, utils.get_order_state('Unknown')) + self.assertEqual(OrderState.OPEN, utils.get_order_state('Working')) + self.assertEqual(OrderState.FAILED, utils.get_order_state('Rejected')) + self.assertEqual(OrderState.CANCELED, utils.get_order_state('Canceled')) + self.assertEqual(OrderState.FAILED, utils.get_order_state('Expired')) + self.assertEqual(OrderState.FILLED, utils.get_order_state('FullyExecuted')) + + def test_get_base_quote_from_trading_pair(self): + base, quote = utils.get_base_quote_from_trading_pair('') + self.assertEqual('', base) + self.assertEqual('', quote) + base, quote = utils.get_base_quote_from_trading_pair('ALPHACOIN') + self.assertEqual('', base) + self.assertEqual('', quote) + base, quote = utils.get_base_quote_from_trading_pair('ALPHA_COIN') + self.assertEqual('', base) + self.assertEqual('', quote) + base, quote = utils.get_base_quote_from_trading_pair('ALPHA/COIN') + self.assertEqual('', base) + self.assertEqual('', quote) + base, quote = utils.get_base_quote_from_trading_pair('alpha-coin') + self.assertEqual('ALPHA', base) + self.assertEqual('COIN', quote) + base, quote = utils.get_base_quote_from_trading_pair('ALPHA-COIN') + self.assertEqual('ALPHA', base) + self.assertEqual('COIN', quote) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_web_utils.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_web_utils.py new file mode 100644 index 0000000..88c93b1 --- /dev/null +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_web_utils.py @@ -0,0 +1,46 @@ +import unittest + +from hummingbot.connector.exchange.foxbit import ( + foxbit_constants as CONSTANTS, + foxbit_utils as utils, + foxbit_web_utils as web_utils, +) + + +class FoxbitUtilTestCases(unittest.TestCase): + + def test_public_rest_url(self): + path_url = "TEST_PATH" + domain = "com.br" + expected_url = f"https://{CONSTANTS.REST_URL}/rest/{CONSTANTS.PUBLIC_API_VERSION}/{path_url}" + self.assertEqual(expected_url, web_utils.public_rest_url(path_url, domain)) + + def test_private_rest_url(self): + path_url = "TEST_PATH" + domain = "com.br" + expected_url = f"https://{CONSTANTS.REST_URL}/rest/{CONSTANTS.PRIVATE_API_VERSION}/{path_url}" + self.assertEqual(expected_url, web_utils.private_rest_url(path_url, domain)) + + def test_rest_endpoint_url(self): + path_url = "TEST_PATH" + domain = "com.br" + expected_url = f"/rest/{CONSTANTS.PRIVATE_API_VERSION}/{path_url}" + public_url = web_utils.public_rest_url(path_url, domain) + private_url = web_utils.private_rest_url(path_url, domain) + self.assertEqual(expected_url, web_utils.rest_endpoint_url(public_url)) + self.assertEqual(expected_url, web_utils.rest_endpoint_url(private_url)) + + def test_websocket_url(self): + expected_url = f"wss://{CONSTANTS.WSS_URL}/" + self.assertEqual(expected_url, web_utils.websocket_url()) + + def test_format_ws_header(self): + header = utils.get_ws_message_frame( + endpoint=CONSTANTS.WS_AUTHENTICATE_USER, + msg_type=CONSTANTS.WS_MESSAGE_FRAME_TYPE["Request"] + ) + retValue = web_utils.format_ws_header(header) + self.assertEqual(retValue, web_utils.format_ws_header(header)) + + def test_create_throttler(self): + self.assertIsNotNone(web_utils.create_throttler()) diff --git a/test/hummingbot/connector/exchange/gate_io/__init__.py b/test/hummingbot/connector/exchange/gate_io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_order_book_data_source.py b/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_order_book_data_source.py new file mode 100644 index 0000000..6d022d9 --- /dev/null +++ b/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_order_book_data_source.py @@ -0,0 +1,446 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source import GateIoAPIOrderBookDataSource +from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook, OrderBookMessage + + +class TestGateIoAPIOrderBookDataSource(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + self.mocking_assistant = NetworkMockingAssistant() + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = GateIoExchange( + client_config_map=client_config_map, + gate_io_api_key="", + gate_io_secret_key="", + trading_pairs=[], + trading_required=False) + + self.data_source = GateIoAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_order_book_data_mock() -> Dict: + order_book_data = { + "id": 1890172054, + "current": 1630644717528, + "update": 1630644716786, + "asks": [ + ["0.298705", "5020"] + ], + "bids": [ + ["0.298642", "2703.17"] + ] + } + return order_book_data + + def get_trade_data_mock(self) -> Dict: + trade_data = { + "time": 1606292218, + "channel": "spot.trades", + "event": "update", + "result": { + "id": 309143071, + "create_time": 1606292218, + "create_time_ms": "1606292218213.4578", + "side": "sell", + "currency_pair": self.ex_trading_pair, + "amount": "16.4700000000", + "price": "0.4705000000" + } + } + return trade_data + + def get_order_book_update_mock(self) -> Dict: + ob_update = { + "time": 1606294781, + "channel": "spot.order_book_update", + "event": "update", + "result": { + "t": 1606294781123, + "e": "depthUpdate", + "E": 1606294781, + "s": self.ex_trading_pair, + "U": 48776301, + "u": 48776306, + "b": [ + [ + "19137.74", + "0.0001" + ], + ], + "a": [ + [ + "19137.75", + "0.6135" + ] + ] + } + } + return ob_update + + def get_order_book_diff_mock(self, asks: List[str], bids: List[str]) -> Dict: + ob_snapshot = { + "time": 1606295412, + "channel": "spot.order_book_update", + "event": "update", + "result": { + "t": 1606295412123, + "e": "depthUpdate", + "E": 1606295412, + "s": self.ex_trading_pair, + "U": 48791820, + "u": 48791830, + "b": [bids], + "a": [asks], + } + } + return ob_snapshot + + @aioresponses() + def test_get_order_book_data_raises(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = "" + for _ in range(CONSTANTS.API_MAX_RETRIES): + mock_api.get(regex_url, body=json.dumps(resp), status=500) + + with self.assertRaises(IOError): + self.async_run_with_timeout( + coroutine=self.data_source.get_new_order_book(self.trading_pair) + ) + + @aioresponses() + def test_get_order_book_data(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_book_data_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout( + coroutine=self.data_source.get_new_order_book(self.trading_pair) + ) + + bid_entries = list(ret.bid_entries()) + ask_entries = list(ret.ask_entries()) + self.assertEqual(1, len(bid_entries)) + self.assertEqual(float(resp["bids"][0][0]), bid_entries[0].price) + self.assertEqual(float(resp["bids"][0][1]), bid_entries[0].amount) + self.assertEqual(int(resp["id"]), bid_entries[0].update_id) + self.assertEqual(1, len(ask_entries)) + self.assertEqual(float(resp["asks"][0][0]), ask_entries[0].price) + self.assertEqual(float(resp["asks"][0][1]), ask_entries[0].amount) + self.assertEqual(int(resp["id"]), ask_entries[0].update_id) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_book_data_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.data_source.get_new_order_book(self.trading_pair)) + + self.assertTrue(isinstance(ret, OrderBook)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_trades(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_trade_data_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue(not output_queue.empty()) + self.assertTrue(isinstance(output_queue.get_nowait(), OrderBookMessage)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_trades_skips_subscribe_unsubscribe_messages(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp1 = {"time": 1632223851, "channel": CONSTANTS.TRADES_ENDPOINT_NAME, "event": "subscribe", "result": {"status": "success"}} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp1) + ) + resp2 = { + "time": 1632223851, "channel": CONSTANTS.TRADES_ENDPOINT_NAME, "event": "unsubscribe", "result": {"status": "success"} + } + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp2) + ) + + output_queue = asyncio.Queue() + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) + self.assertFalse( + self._is_logged( + "ERROR", + f"Unexpected error while parsing ws trades message {resp1}." + ) + ) + self.assertFalse( + self._is_logged( + "ERROR", + f"Unexpected error while parsing ws trades message {resp2}." + ) + ) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source.GateIoAPIOrderBookDataSource._sleep") + def test_listen_for_trades_logs_error_when_exception_happens(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + incomplete_response = { + "time": 1606292218, + "channel": "spot.trades", + "event": "update", + "result": { + "id": 309143071, + "currency_pair": f"{self.base_asset}_{self.quote_asset}", + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(incomplete_response) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error when processing public trade updates from exchange" + )) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_update(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_order_book_update_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue(not output_queue.empty()) + self.assertTrue(isinstance(output_queue.get_nowait(), OrderBookMessage)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source.GateIoAPIOrderBookDataSource._sleep", + new_callable=AsyncMock) + def test_listen_for_order_book_diffs_update_logs_error_when_exception_happens(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + incomplete_response = { + "time": 1606294781, + "channel": "spot.order_book_update", + "event": "update", + "result": {} + } + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(incomplete_response) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error when processing public order book updates from exchange" + )) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_snapshot(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + asks = ["19080.24", "0.1638"] + bids = ["19079.55", "0.0195"] + resp = self.get_order_book_diff_mock(asks=asks, bids=bids) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue(not output_queue.empty()) + + msg = output_queue.get_nowait() + + self.assertTrue(isinstance(msg, OrderBookMessage)) + self.assertEqual(asks, msg.content["asks"][0], msg=f"{msg}") + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_snapshot_skips_subscribe_unsubscribe_messages(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {"time": 1632223851, "channel": "spot.usertrades", "event": "subscribe", "result": {"status": "success"}} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + resp = { + "time": 1632223851, "channel": "spot.usertrades", "event": "unsubscribe", "result": {"status": "success"} + } + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, json.dumps(resp) + ) + + output_queue = asyncio.Queue() + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_diffs(self.ev_loop, output_queue)) + self.async_tasks.append(t) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) + + @aioresponses() + def test_listen_for_order_book_snapshots(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_book_data_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + output_queue = asyncio.Queue() + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_snapshots(self.ev_loop, output_queue)) + self.async_tasks.append(t) + ret = self.async_run_with_timeout(coroutine=output_queue.get()) + + self.assertTrue(isinstance(ret, OrderBookMessage)) + + @aioresponses() + @patch( + "hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source.GateIoAPIOrderBookDataSource._sleep", + new_callable=AsyncMock) + def test_listen_for_order_book_snapshots_logs_error_when_exception_happens( + self, + mock_api, + sleep_mock): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, exception=Exception("Test Error")) + output_queue = asyncio.Queue() + sleep_mock.side_effect = asyncio.CancelledError + + t = self.ev_loop.create_task(self.data_source.listen_for_order_book_snapshots(self.ev_loop, output_queue)) + self.async_tasks.append(t) + + try: + self.async_run_with_timeout(t) + except asyncio.CancelledError: + # Ignore the CancelledError raised by the mocked _sleep + pass + + self.assertTrue( + self._is_logged( + "ERROR", + f"Unexpected error fetching order book snapshot for {self.trading_pair}." + ) + ) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.gate_io.gate_io_api_order_book_data_source.GateIoAPIOrderBookDataSource._sleep", + new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_error_when_exception_happens(self, sleep_mock, ws_connect_mock): + # ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.side_effect = Exception("Test Error") + sleep_mock.side_effect = asyncio.CancelledError + + t = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_tasks.append(t) + + try: + self.async_run_with_timeout(t) + except asyncio.CancelledError: + # Ignore the CancelledError raised by the mocked _sleep + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." + )) diff --git a/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_user_stream_data_source.py new file mode 100644 index 0000000..d28dd1d --- /dev/null +++ b/test/hummingbot/connector/exchange/gate_io/test_gate_io_api_user_stream_data_source.py @@ -0,0 +1,289 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source import GateIoAPIUserStreamDataSource +from hummingbot.connector.exchange.gate_io.gate_io_auth import GateIoAuth +from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestGateIoAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = GateIoAuth( + api_key=self.api_key, + secret_key=self.api_secret_key, + time_provider=self.mock_time_provider) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = GateIoExchange( + client_config_map=client_config_map, + gate_io_api_key="", + gate_io_secret_key="", + trading_pairs=[], + trading_required=False) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = GateIoAPIUserStreamDataSource( + self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source.GateIoAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, time_mock, ws_connect_mock): + time_mock.return_value = 1000 + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "time": 1611541000, + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_trades = { + "time": 1611541000, + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_balance = { + "time": 1611541000, + "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_balance)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(3, len(sent_subscription_messages)) + expected_orders_subscription = { + "time": int(self.mock_time_provider.time()), + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "payload": [self.ex_trading_pair], + "auth": { + "KEY": self.api_key, + "SIGN": '005d2e6996fa7783459453d36ff871d8d5cfe225a098f37ac234543811c79e3c' # noqa: mock + 'db8f41684f3ad9491f65c15ed880ce7baee81f402eb1df56b1bba188c0e7838c', # noqa: mock + "method": "api_key"}, + } + self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) + expected_trades_subscription = { + "time": int(self.mock_time_provider.time()), + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "payload": [self.ex_trading_pair], + "auth": { + "KEY": self.api_key, + "SIGN": '0f34bf79558905d2b5bc7790febf1099d38ff1aa39525a077db32bcbf9135268' # noqa: mock + 'caf23cdf2d62315841500962f788f7c5f4c3f4b8a057b2184366687b1f74af69', # noqa: mock + "method": "api_key"} + } + self.assertEqual(expected_trades_subscription, sent_subscription_messages[1]) + expected_balances_subscription = { + "time": int(self.mock_time_provider.time()), + "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + "event": "subscribe", + "auth": { + "KEY": self.api_key, + "SIGN": '90f5e732fc586d09c4a1b7de13f65b668c7ce90678b30da87aa137364bac0b97' # noqa: mock + '16b34219b689fb754e821872933a0e12b1d415867b9fbb8ec441bc86e77fb79c', # noqa: mock + "method": "api_key"} + } + self.assertEqual(expected_balances_subscription, sent_subscription_messages[2]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private order changes and balance updates channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source.GateIoAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_skips_subscribe_unsubscribe_messages(self, time_mock, ws_connect_mock): + time_mock.return_value = 1000 + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_orders = { + "time": 1611541000, + "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_trades = { + "time": 1611541000, + "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + result_subscribe_balance = { + "time": 1611541000, + "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, + "event": "subscribe", + "error": None, + "result": { + "status": "success" + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_balance)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(output_queue.empty()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_pong_payload(self, mock_ws): + mock_pong = { + "time": 1545404023, + "channel": CONSTANTS.PONG_CHANNEL_NAME, + "event": "", + "error": None, + "result": None + } + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_pong)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, sleep_mock, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/exchange/gate_io/test_gate_io_exchange.py b/test/hummingbot/connector/exchange/gate_io/test_gate_io_exchange.py new file mode 100644 index 0000000..507cb38 --- /dev/null +++ b/test/hummingbot/connector/exchange/gate_io/test_gate_io_exchange.py @@ -0,0 +1,1804 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.exchange.gate_io.gate_io_exchange import GateIoExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class TestGateIoExchange(unittest.TestCase): + # logging.Level required to receive logs from the exchange + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}_{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_secret = "someSecret" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.mocking_assistant = NetworkMockingAssistant() + self.async_tasks: List[asyncio.Task] = [] + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = GateIoExchange( + client_config_map=self.client_config_map, + gate_io_api_key=self.api_key, + gate_io_secret_key=self.api_secret, + trading_pairs=[self.trading_pair]) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + + self._initialize_event_loggers() + + self.exchange._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_currency_data_mock() -> List: + currency_data = [ + { + "currency": "GT", + "delisted": False, + "withdraw_disabled": False, + "withdraw_delayed": False, + "deposit_disabled": False, + "trade_disabled": False, + } + ] + return currency_data + + def get_trading_rules_mock(self) -> List: + trading_rules = [ + { + "id": f"{self.base_asset}_{self.quote_asset}", + "base": self.base_asset, + "quote": self.quote_asset, + "fee": "0.2", + "min_base_amount": "0.001", + "min_quote_amount": "1.0", + "amount_precision": 3, + "precision": 6, + "trade_status": "tradable", + "sell_start": 1516378650, + "buy_start": 1516378650, + } + ] + return trading_rules + + def get_order_create_response_mock(self, cancelled: bool = False, exchange_order_id: str = "someExchId") -> Dict: + order_create_resp_mock = { + "id": exchange_order_id, + "text": "t-123456", + "create_time": "1548000000", + "update_time": "1548000100", + "create_time_ms": 1548000000123, + "update_time_ms": 1548000100123, + "currency_pair": f"{self.base_asset}_{self.quote_asset}", + "status": "cancelled" if cancelled else "open", + "type": "limit", + "account": "spot", + "side": "buy", + "iceberg": "0", + "amount": "1", + "price": "5.00032", + "time_in_force": "gtc", + "left": "0.5", + "filled_total": "2.50016", + "fee": "0.005", + "fee_currency": "ETH", + "point_fee": "0", + "gt_fee": "0", + "gt_discount": False, + "rebated_fee": "0", + "rebated_fee_currency": "BTC", + } + return order_create_resp_mock + + def get_in_flight_order(self, client_order_id: str, exchange_order_id: str = "someExchId") -> InFlightOrder: + order = InFlightOrder( + client_order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("5.1"), + amount=Decimal("1"), + creation_timestamp=1640001112.0 + ) + return order + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET, OrderType.LIMIT_MAKER] + + def get_user_balances_mock(self) -> List: + user_balances = [ + { + "currency": self.base_asset, + "available": "968.8", + "locked": "0", + }, + { + "currency": self.quote_asset, + "available": "543.9", + "locked": "0", + }, + ] + return user_balances + + def get_open_order_mock(self, exchange_order_id: str = "someExchId") -> List: + open_orders = [ + { + "currency_pair": f"{self.base_asset}_{self.quote_asset}", + "total": 1, + "orders": [ + { + "id": exchange_order_id, + "text": f"{CONSTANTS.HBOT_ORDER_ID}-{exchange_order_id}", + "create_time": "1548000000", + "update_time": "1548000100", + "currency_pair": f"{self.base_asset}_{self.quote_asset}", + "status": "open", + "type": "limit", + "account": "spot", + "side": "buy", + "amount": "1", + "price": "5.00032", + "time_in_force": "gtc", + "left": "0.5", + "filled_total": "2.50016", + "fee": "0.005", + "fee_currency": "ETH", + "point_fee": "0", + "gt_fee": "0", + "gt_discount": False, + "rebated_fee": "0", + "rebated_fee_currency": "BTC", + } + ], + } + ] + return open_orders + + def get_order_trade_response( + self, order: InFlightOrder, is_completely_filled: bool = False + ) -> Dict[str, Any]: + order_amount = order.amount + if not is_completely_filled: + order_amount = float(Decimal("0.5") * order_amount) + base_asset, quote_asset = order.trading_pair.split("-")[0], order.trading_pair.split("-")[1] + return [ + { + "id": 5736713, + "user_id": 1000001, + "order_id": order.exchange_order_id, + "currency_pair": order.trading_pair, + "create_time": 1605176741, + "create_time_ms": "1605176741123.456", + "side": "buy" if order.trade_type == TradeType.BUY else "sell", + "amount": str(order_amount), + "role": "maker", + "price": str(order.price), + "fee": "0.00200000000000", + "fee_currency": base_asset if order.trade_type == TradeType.BUY else quote_asset, + "point_fee": "0", + "gt_fee": "0", + "text": order.client_order_id, + } + ] + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertEqual(self.expected_supported_order_types, supported_types) + + @aioresponses() + def test_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + resp = [ + { + "id": f"{self.base_asset}_{self.quote_asset}", + "base": self.base_asset, + "quote": self.quote_asset, + "fee": "0.2", + "min_base_amount": "0.001", + "min_quote_amount": "1.0", + "amount_precision": 3, + "precision": 6, + "trade_status": "tradable", + "sell_start": 1516378650, + "buy_start": 1516378650 + }, + { + "id": "SOME_PAIR", + "base": "SOME", + "quote": "PAIR", + "fee": "0.2", + "min_base_amount": "0.001", + "min_quote_amount": "1.0", + "amount_precision": 3, + "precision": 6, + "trade_status": "untradable", + "sell_start": 1516378650, + "buy_start": 1516378650 + } + ] + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertEqual(1, len(ret)) + self.assertIn(self.trading_pair, ret) + self.assertNotIn("SOME-PAIR", ret) + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + result: Dict[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs()) + + self.assertEqual(0, len(result)) + + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.TICKER_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = [ + { + "currency_pair": f"{self.base_asset}_{self.quote_asset}", + "last": "0.2959", + "lowest_ask": "0.295918", + "highest_bid": "0.295898", + "change_percentage": "-1.72", + "base_volume": "78497066.828007", + "quote_volume": "23432064.936692", + "high_24h": "0.309372", + "low_24h": "0.286827", + } + ] + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout( + coroutine=self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + ticker_requests = [(key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url)] + + request_params = ticker_requests[0][1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_params["currency_pair"]) + + self.assertEqual(ret[self.trading_pair], float(resp[0]["last"])) + + @aioresponses() + def test_check_network_not_connected(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.NETWORK_CHECK_PATH_URL}" + resp = "" + for i in range(CONSTANTS.API_MAX_RETRIES + 1): + mock_api.get(url, status=500, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED, msg=f"{ret}") + + @aioresponses() + def test_check_network(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.NETWORK_CHECK_PATH_URL}" + resp = self.get_currency_data_mock() + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.CONNECTED) + + @aioresponses() + def test_update_trading_rules_polling_loop(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + resp = self.get_trading_rules_mock() + called_event = asyncio.Event() + mock_api.get(url, body=json.dumps(resp), callback=lambda *args, **kwargs: called_event.set()) + + self.ev_loop.create_task(self.exchange._trading_rules_polling_loop()) + self.async_run_with_timeout(called_event.wait()) + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + + @aioresponses() + def test_update_trading_rules_ignores_invalid(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + resp = [ + { + "id": f"{self.base_asset}_{self.quote_asset}", + "base": self.base_asset, + "quote": self.quote_asset, + "fee": "0.2", + "min_base_amount": "0.001", + "min_quote_amount": "1.0", + "amount_precision": 3, + "precision": 6, + "trade_status": "untradable", + "sell_start": 1516378650, + "buy_start": 1516378650, + } + ] + called_event = asyncio.Event() + mock_api.get(url, body=json.dumps(resp), callback=lambda *args, **kwargs: called_event.set()) + + self.ev_loop.create_task(self.exchange._trading_rules_polling_loop()) + self.async_run_with_timeout(called_event.wait()) + + self.assertEqual(0, len(self.exchange.trading_rules)) + + @aioresponses() + def test_update_trading_rules_ignores_rule_with_error(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + resp = [ + { + "id": f"{self.base_asset}_{self.quote_asset}", + "base": self.base_asset, + "quote": self.quote_asset, + "fee": "0.2", + "trade_status": "tradable", + } + ] + called_event = asyncio.Event() + mock_api.get(url, body=json.dumps(resp), callback=lambda *args, **kwargs: called_event.set()) + + self.ev_loop.create_task(self.exchange._trading_rules_polling_loop()) + self.async_run_with_timeout(called_event.wait()) + + self.assertEqual(0, len(self.exchange.trading_rules)) + self.assertTrue( + self._is_logged("ERROR", f"Error parsing the trading pair rule {resp[0]}. Skipping.") + ) + + @aioresponses() + def test_create_order(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + mock_api.post(regex_url, body=json.dumps(resp), status=201) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("5.1"), + ) + ) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.ex_trading_pair, request_data["currency_pair"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("limit", request_data["type"]) + self.assertEqual(Decimal("1"), Decimal(request_data["amount"])) + self.assertEqual(Decimal("5.1"), Decimal(request_data["price"])) + self.assertEqual(order_id, request_data["text"]) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("1"), create_event.amount) + self.assertEqual(Decimal("5.1"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(resp["id"], create_event.exchange_order_id) + + @aioresponses() + def test_create_limit_maker_order(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + mock_api.post(regex_url, body=json.dumps(resp), status=201) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT_MAKER, + price=Decimal("5.1"), + ) + ) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.ex_trading_pair, request_data["currency_pair"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("limit", request_data["type"]) + self.assertEqual(Decimal("1"), Decimal(request_data["amount"])) + self.assertEqual(Decimal("5.1"), Decimal(request_data["price"])) + self.assertEqual(order_id, request_data["text"]) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT_MAKER, create_event.type) + self.assertEqual(Decimal("1"), create_event.amount) + self.assertEqual(Decimal("5.1"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(resp["id"], create_event.exchange_order_id) + + @aioresponses() + @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange.get_price") + def test_create_market_order(self, mock_api, get_price_mock): + get_price_mock.return_value = Decimal(5.1) + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + mock_api.post(regex_url, body=json.dumps(resp), status=201) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.MARKET, + price=Decimal("5.1"), + ) + ) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.ex_trading_pair, request_data["currency_pair"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("market", request_data["type"]) + self.assertEqual(Decimal("1") * Decimal("5.1"), Decimal(request_data["amount"])) + self.assertEqual(order_id, request_data["text"]) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("1"), create_event.amount) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(resp["id"], create_event.exchange_order_id) + + @aioresponses() + @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange.get_price") + @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange.get_price_for_volume") + def test_create_market_order_price_is_nan(self, mock_api, get_price_mock, get_price_for_volume_mock): + get_price_mock.return_value = None + get_price_for_volume_mock.return_value = Decimal("5.1") + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + mock_api.post(regex_url, body=json.dumps(resp), status=201) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.MARKET, + price=Decimal("5.1"), + ) + ) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.ex_trading_pair, request_data["currency_pair"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("market", request_data["type"]) + self.assertEqual(Decimal("1") * Decimal("5.1"), Decimal(request_data["amount"])) + self.assertEqual(order_id, request_data["text"]) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("1"), create_event.amount) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(resp["id"], create_event.exchange_order_id) + + @aioresponses() + @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange.get_price") + # @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange.get_price_for_volume") + def test_place_order_price_is_nan(self, mock_api, get_price_mock): + get_price_mock.return_value = None + # get_price_for_volume_mock.return_value = Decimal("5.1") + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + mock_api.post(regex_url, body=json.dumps(resp), status=201) + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[], + asks=[OrderBookRow(price=5.1, amount=20, update_id=1)], + update_id=1, + ) + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._place_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.MARKET, + price=Decimal("nan"), + ) + ) + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(Decimal("1") * Decimal("5.1"), Decimal(request_data["amount"])) + + @aioresponses() + def test_create_order_when_order_is_instantly_closed(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock() + resp["status"] = "closed" + mock_api.post(regex_url, body=json.dumps(resp)) + + event_logger = EventLogger() + self.exchange.add_listener(MarketEvent.BuyOrderCreated, event_logger) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("5.1"), + ) + ) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("1"), create_event.amount) + self.assertEqual(Decimal("5.1"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(resp["id"], create_event.exchange_order_id) + + @aioresponses() + def test_order_with_less_amount_than_allowed_is_not_created(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.post(regex_url, exception=Exception("The request should never happen")) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("0.0001"), + order_type=OrderType.LIMIT, + price=Decimal("5.1"), + ) + ) + + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + self.assertEqual(1, len(self.order_failure_logger.event_log)) + self.assertTrue( + self._is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @aioresponses() + def test_create_order_fails(self, _, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock(cancelled=True) + mock_api.post(regex_url, body=json.dumps(resp)) + + order_id = "someId" + self.async_run_with_timeout( + coroutine=self.exchange._create_order( + trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("5.1"), + ) + ) + + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + self.assertEqual(1, len(self.order_failure_logger.event_log)) + + @aioresponses() + def test_create_order_request_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_CREATE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, status=400) + + order_id = "OID1" + self.async_run_with_timeout( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id=order_id, + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + + self.assertNotIn("OID1", self.exchange.in_flight_orders) + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("OID1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Order OID1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + "client_order_id='OID1', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_execute_cancel(self, mock_api): + self._simulate_trading_rules_initialized() + self.exchange._set_current_timestamp(1640780000) + client_order_id = "someId" + exchange_order_id = "someExchId" + self.exchange.start_tracking_order( + order_id=client_order_id, + exchange_order_id=exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_DELETE_PATH_URL.format(order_id=exchange_order_id)}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_create_response_mock(cancelled=True) + mock_api.delete(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._execute_cancel(self.trading_pair, client_order_id)) + + cancel_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = cancel_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_params["currency_pair"]) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {client_order_id}." + ) + ) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_DELETE_PATH_URL.format(order_id=order.exchange_order_id)}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="OID1") + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue( + self._is_logged( + "ERROR", + f"Failed to cancel order {order.client_order_id}" + ) + ) + + def test_cancel_order_without_exchange_order_id_marks_order_as_fail_after_retries(self): + update_event = MagicMock() + update_event.wait.side_effect = asyncio.TimeoutError + + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id=None, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + order.exchange_order_id_update_event = update_event + + self.async_run_with_timeout(self.exchange._execute_cancel( + trading_pair=order.trading_pair, + order_id=order.client_order_id, + )) + + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue( + self._is_logged( + "WARNING", + f"Failed to cancel the order {order.client_order_id} because it does not have an exchange order id yet" + ) + ) + + # After the fourth time not finding the exchange order id the order should be marked as failed + for i in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout(self.exchange._execute_cancel( + trading_pair=order.trading_pair, + order_id=order.client_order_id, + )) + + self.assertTrue(order.is_failure) + + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(order.client_order_id, failure_event.order_id) + self.assertEqual(order.order_type, failure_event.order_type) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["OID1"] + + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID2", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["OID2"] + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_DELETE_PATH_URL.format(order_id=order1.exchange_order_id)}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self.get_order_create_response_mock(cancelled=True, exchange_order_id=order1.exchange_order_id) + + mock_api.delete(regex_url, body=json.dumps(response)) + + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_DELETE_PATH_URL.format(order_id=order2.exchange_order_id)}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, status=400) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {order1.client_order_id}." + ) + ) + + @aioresponses() + def test_update_balances(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.USER_BALANCES_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = [ + { + "currency": self.base_asset, + "available": "968.8", + "locked": "0", + }, + { + "currency": self.quote_asset, + "available": "500", + "locked": "300", + }, + ] + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("968.8"), available_balances[self.base_asset]) + self.assertEqual(Decimal("500"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("968.8"), total_balances[self.base_asset]) + self.assertEqual(Decimal("800"), total_balances[self.quote_asset]) + + response = [ + { + "currency": self.base_asset, + "available": "968.8", + "locked": "0", + }, + ] + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(Decimal("968.8"), available_balances[self.base_asset]) + self.assertEqual(Decimal("968.8"), total_balances[self.base_asset]) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + # Order Trade Updates + order_trade_updates_url = f"{CONSTANTS.REST_URL}/{CONSTANTS.MY_TRADES_PATH_URL}" + regex_order_trade_updates_url = re.compile( + f"^{order_trade_updates_url}".replace(".", r"\.").replace("?", r"\?")) + order_trade_updates_resp = [] + mock_api.get( + regex_order_trade_updates_url, + body=json.dumps(order_trade_updates_resp) + ) + + # Order Status Updates + order_status_url = (f"{CONSTANTS.REST_URL}/" + f"{CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=order.exchange_order_id)}") + regex_order_status_url = re.compile(f"^{order_status_url}".replace(".", r"\.").replace("?", r"\?")) + order_status_resp = self.get_order_create_response_mock( + cancelled=False, + exchange_order_id=order.exchange_order_id) + order_status_resp["text"] = order.client_order_id + order_status_resp["status"] = "closed" + order_status_resp["left"] = "0" + order_status_resp["finish_as"] = "filled" + mock_api.get( + regex_order_status_url, + body=json.dumps(order_status_resp), + ) + + # Simulate the order has been filled with a TradeUpdate + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(order_status_url))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_params["currency_pair"]) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_update_order_status_when_cancelled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + # Order Status Updates + order_status_url = (f"{CONSTANTS.REST_URL}/" + f"{CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=order.exchange_order_id)}") + regex_order_status_url = re.compile(f"^{order_status_url}".replace(".", r"\.").replace("?", r"\?")) + order_status_resp = self.get_order_create_response_mock( + cancelled=False, + exchange_order_id=order.exchange_order_id) + order_status_resp["text"] = order.client_order_id + order_status_resp["status"] = "closed" + order_status_resp["finish_as"] = "cancelled" + mock_api.get( + regex_order_status_url, + body=json.dumps(order_status_resp), + ) + # Simulate the order has been cancelled + self.async_run_with_timeout(self.exchange._update_order_status()) + # self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_done) + + @aioresponses() + def test_update_order_status_when_partilly_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + # Order Status Updates + order_status_url = (f"{CONSTANTS.REST_URL}/" + f"{CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=order.exchange_order_id)}") + regex_order_status_url = re.compile(f"^{order_status_url}".replace(".", r"\.").replace("?", r"\?")) + order_status_resp = self.get_order_create_response_mock( + cancelled=False, + exchange_order_id=order.exchange_order_id) + order_status_resp["text"] = order.client_order_id + order_status_resp["status"] = "closed" + order_status_resp["filled_total"] = "0.5" + order_status_resp["finish_as"] = "open" + mock_api.get( + regex_order_status_url, + body=json.dumps(order_status_resp), + ) + # Simulate the order has been cancelled + self.async_run_with_timeout(self.exchange._update_order_status()) + self.assertTrue(order.is_open) + + @aioresponses() + def test_update_order_status_registers_order_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + # Order Trade Updates + order_trade_updates_url = f"{CONSTANTS.REST_URL}/{CONSTANTS.MY_TRADES_PATH_URL}" + regex_order_trade_updates_url = re.compile( + f"^{order_trade_updates_url}".replace(".", r"\.").replace("?", r"\?")) + order_trade_updates_resp = [] + mock_api.get( + regex_order_trade_updates_url, + body=json.dumps(order_trade_updates_resp) + ) + + # Order Status Updates + order_status_url = (f"{CONSTANTS.REST_URL}/" + f"{CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=order.exchange_order_id)}") + regex_order_status_url = re.compile(f"^{order_status_url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_order_status_url, status=404) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + self.assertTrue( + self._is_logged( + "WARNING", + f"Error fetching status update for the active order {order.client_order_id}: Error executing request GET " + f"{order_status_url}. HTTP status is 404. Error: ." + ) + ) + + @aioresponses() + def test_update_order_status_processes_trade_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + # Order Trade Updates + order_trade_updates_url = f"{CONSTANTS.REST_URL}/{CONSTANTS.MY_TRADES_PATH_URL}" + regex_order_trade_updates_url = re.compile( + f"^{order_trade_updates_url}".replace(".", r"\.").replace("?", r"\?")) + order_trade_updates_resp = self.get_order_trade_response(order=order, is_completely_filled=True) + mock_api.get( + regex_order_trade_updates_url, + body=json.dumps(order_trade_updates_resp) + ) + + # Order Status Updates + order_status_url = (f"{CONSTANTS.REST_URL}/" + f"{CONSTANTS.ORDER_STATUS_PATH_URL.format(order_id=order.exchange_order_id)}") + regex_order_status_url = re.compile(f"^{order_status_url}".replace(".", r"\.").replace("?", r"\?")) + order_status_resp = self.get_order_create_response_mock( + cancelled=False, + exchange_order_id=order.exchange_order_id) + order_status_resp["text"] = order.client_order_id + order_status_resp["status"] = "open" + order_status_resp["left"] = "0" + mock_api.get( + regex_order_status_url, + body=json.dumps(order_status_resp), + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + self.assertTrue(order.completely_filled_event.is_set()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(order_trade_updates_url))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual(self.ex_trading_pair, request_params["currency_pair"]) + self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + self.assertTrue(order.is_open) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(self.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(order_trade_updates_resp[0]["amount"]), fill_event.amount) + self.assertEqual(Decimal(order_trade_updates_resp[0]["price"]), fill_event.price) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount( + order_trade_updates_resp[0]["fee_currency"], + Decimal(order_trade_updates_resp[0]["fee"]))], + fill_event.trade_fee.flat_fees) + self.assertEqual(str(order_trade_updates_resp[0]["id"]), fill_event.exchange_trade_id) + self.assertEqual(1, fill_event.leverage) + self.assertEqual(PositionAction.NIL.value, fill_event.position) + self.assertTrue( + self._is_logged( + "INFO", + f"The {order.trade_type.name.upper()} order {order.client_order_id} " + f"amounting to {order.executed_amount_base}/{order.amount} " + f"{order.base_asset} has been filled." + ) + ) + + def test_update_order_status_marks_order_with_no_exchange_id_as_not_found(self): + update_event = MagicMock() + update_event.wait.side_effect = asyncio.TimeoutError + + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id=None, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + order.exchange_order_id_update_event = update_event + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 7 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID, + max_id_len=CONSTANTS.MAX_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID, + max_id_len=CONSTANTS.MAX_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + def test_user_stream_update_for_new_order_does_not_update_status(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "time": 1605175506, + "channel": "spot.orders", + "event": "update", + "result": [ + { + "id": order.exchange_order_id, + "user": 123456, + "text": order.client_order_id, + "create_time": "1605175506", + "create_time_ms": "1605175506123", + "update_time": "1605175506", + "update_time_ms": "1605175506123", + "event": "put", + "currency_pair": self.ex_trading_pair, + "type": order.order_type.name.lower(), + "account": "spot", + "side": order.trade_type.name.lower(), + "amount": str(order.amount), + "price": str(order.price), + "time_in_force": "gtc", + "left": str(order.amount), + "filled_total": "0", + "fee": "0", + "fee_currency": "USDT", + "point_fee": "0", + "gt_fee": "0", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT" + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + self.assertTrue(order.is_open) + + def test_user_stream_update_for_cancelled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "time": 1605175506, + "channel": "spot.orders", + "event": "update", + "result": [ + { + "id": order.exchange_order_id, + "user": 123456, + "text": order.client_order_id, + "create_time": "1605175506", + "create_time_ms": "1605175506123", + "update_time": "1605175506", + "update_time_ms": "1605175506123", + "event": "finish", + "currency_pair": self.ex_trading_pair, + "type": order.order_type.name.lower(), + "account": "spot", + "side": order.trade_type.name.lower(), + "amount": str(order.amount), + "price": str(order.price), + "time_in_force": "gtc", + "left": str(order.amount), + "filled_total": "0", + "fee": "0", + "fee_currency": "USDT", + "point_fee": "0", + "gt_fee": "0", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT", + "finish_as": "cancelled", + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_user_stream_update_for_order_partial_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + order.current_state = OrderState.OPEN + + event_message = { + "time": 1605176741, + "channel": "spot.usertrades", + "event": "update", + "result": [ + { + "id": 5736713, + "user_id": 1000001, + "order_id": order.exchange_order_id, + "currency_pair": self.ex_trading_pair, + "create_time": 1605176741, + "create_time_ms": "1605176741123.456", + "side": order.trade_type.name.lower(), + "amount": "0.5", + "role": "taker", + "price": "10000.00000000", + "fee": "0.00200000000000", + "fee_currency": self.quote_asset, + "point_fee": "0", + "gt_fee": "0", + "text": order.client_order_id + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.OPEN, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(event_message["result"][0]["price"]), fill_event.price) + self.assertEqual(Decimal(event_message["result"][0]["amount"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount( + event_message["result"][0]["fee_currency"], + Decimal(event_message["result"][0]["fee"]))], + fill_event.trade_fee.flat_fees) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + self.assertTrue( + self._is_logged("INFO", f"The {order.trade_type.name} order {order.client_order_id} amounting to " + f"0.5/{order.amount} {order.base_asset} has been filled.") + ) + + def test_user_stream_update_for_order_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + order_event_message = { + "time": 1605175506, + "channel": "spot.orders", + "event": "update", + "result": [ + { + "id": order.exchange_order_id, + "user": 123456, + "text": order.client_order_id, + "create_time": "1605175506", + "create_time_ms": "1605175506123", + "update_time": "1605175506", + "update_time_ms": "1605175506123", + "event": "finish", + "currency_pair": self.ex_trading_pair, + "type": order.order_type.name.lower(), + "account": "spot", + "side": order.trade_type.name.lower(), + "amount": str(order.amount), + "price": str(order.price), + "time_in_force": "gtc", + "left": "0", + "filled_total": str(order.amount), + "fee": "0.00200000000000", + "fee_currency": self.quote_asset, + "point_fee": "0", + "gt_fee": "0", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT", + "finish_as": "filled", + } + ] + } + + filled_event_message = { + "time": 1605176741, + "channel": "spot.usertrades", + "event": "update", + "result": [ + { + "id": 5736713, + "user_id": 1000001, + "order_id": order.exchange_order_id, + "currency_pair": self.ex_trading_pair, + "create_time": 1605176741, + "create_time_ms": "1605176741123.456", + "side": order.trade_type.name.lower(), + "amount": str(order.amount), + "role": "taker", + "price": "10035.00000000", + "fee": "0.00200000000000", + "fee_currency": self.quote_asset, + "point_fee": "0", + "gt_fee": "0", + "text": order.client_order_id + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [order_event_message, filled_event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(filled_event_message["result"][0]["price"]), fill_event.price) + self.assertEqual(Decimal(filled_event_message["result"][0]["amount"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount( + filled_event_message["result"][0]["fee_currency"], + Decimal(filled_event_message["result"][0]["fee"]))], + fill_event.trade_fee.flat_fees) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * Decimal(filled_event_message["result"][0]["price"]), + buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_update_for_order_partially_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + order.current_state = OrderState.OPEN + + event_message = { + "time": 1605175506, + "channel": "spot.orders", + "event": "update", + "result": [ + { + "id": order.exchange_order_id, + "user": 123456, + "text": order.client_order_id, + "create_time": "1605175506", + "create_time_ms": "1605175506123", + "update_time": "1605175506", + "update_time_ms": "1605175506123", + "event": "finish", + "currency_pair": self.ex_trading_pair, + "type": order.order_type.name.lower(), + "account": "spot", + "side": order.trade_type.name.lower(), + "amount": str(order.amount), + "price": str(order.price), + "time_in_force": "gtc", + "left": "0", + "filled_total": "0.5", + "fee": "0.00200000000000", + "fee_currency": self.quote_asset, + "point_fee": "0", + "gt_fee": "0", + "gt_discount": True, + "rebated_fee": "0", + "rebated_fee_currency": "USDT", + "finish_as": "filled", + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.OPEN, order.current_state) + + def test_user_stream_balance_update(self): + self.exchange._set_current_timestamp(1640780000) + + event_message = { + "time": 1605248616, + "channel": "spot.balances", + "event": "update", + "result": [ + { + "timestamp": "1605248616", + "timestamp_ms": "1605248616123", + "user": "1000001", + "currency": self.base_asset, + "change": "100", + "total": "10500", + "available": "10000" + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10000"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("10500"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_raises_cancel_exception(self): + self.exchange._set_current_timestamp(1640780000) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._user_stream_tracker._user_stream = mock_queue + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, + self.exchange._user_stream_event_listener()) + + @patch("hummingbot.connector.exchange.gate_io.gate_io_exchange.GateIoExchange._sleep") + def test_user_stream_logs_errors(self, sleep_mock): + self.exchange._set_current_timestamp(1640780000) + + incomplete_event = { + "time": 1605248616, + "channel": "spot.balances", + "event": "update", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error in user stream listener loop." + ) + ) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) diff --git a/test/hummingbot/connector/exchange/hitbtc/__init__.py b/test/hummingbot/connector/exchange/hitbtc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/hitbtc/test_hitbtc_api_order_book_data_source.py b/test/hummingbot/connector/exchange/hitbtc/test_hitbtc_api_order_book_data_source.py new file mode 100644 index 0000000..5d7c102 --- /dev/null +++ b/test/hummingbot/connector/exchange/hitbtc/test_hitbtc_api_order_book_data_source.py @@ -0,0 +1,81 @@ +import asyncio +import json +from typing import Awaitable + +from unittest import TestCase + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.hitbtc.hitbtc_api_order_book_data_source import HitbtcAPIOrderBookDataSource +from hummingbot.connector.exchange.hitbtc.hitbtc_constants import Constants as CONSTANTS + + +class HitbtcAPIOrderBookDataSourceTests(TestCase): + + def tearDown(self) -> None: + HitbtcAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_trading_pairs_initialization(self, mock_api): + url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ENDPOINT['SYMBOL']}" + resp = [ + { + "id": "BTCUSD", + "baseCurrency": "BTC", + "quoteCurrency": "USD", + "quantityIncrement": "0.001", + "tickSize": "0.000001", + "takeLiquidityRate": "0.001", + "provideLiquidityRate": "-0.0001", + "feeCurrency": "USDT" + }, + { + "id": "ETHBTC", + "baseCurrency": "ETH", + "quoteCurrency": "BTC", + "quantityIncrement": "0.001", + "tickSize": "0.000001", + "takeLiquidityRate": "0.001", + "provideLiquidityRate": "-0.0001", + "feeCurrency": "BTC", + "marginTrading": True, + "maxInitialLeverage": "10.00" + } + ] + mock_api.get(url, body=json.dumps(resp)) + + map = self.async_run_with_timeout(HitbtcAPIOrderBookDataSource.trading_pair_symbol_map()) + + self.assertIn("BTCUSD", map) + self.assertEqual("BTC-USDT", map["BTCUSD"]) + self.assertIn("ETHBTC", map) + self.assertEqual("ETH-BTC", map["ETHBTC"]) + + def test_fetch_trading_pairs(self): + HitbtcAPIOrderBookDataSource._trading_pair_symbol_map = {"BTCUSDT": "BTC-USDT", "ETHUSDT": "ETH-USDT"} + trading_pairs = self.async_run_with_timeout(HitbtcAPIOrderBookDataSource.fetch_trading_pairs()) + self.assertEqual(["BTC-USDT", "ETH-USDT"], trading_pairs) + + def test_exchange_symbol_associated_to_pair(self): + HitbtcAPIOrderBookDataSource._trading_pair_symbol_map = {"BTCUSDT": "BTC-USDT", "ETHUSDT": "ETH-USDT"} + symbol = self.async_run_with_timeout( + HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair("BTC-USDT")) + self.assertEqual("BTCUSDT", symbol) + + def test_exchange_symbol_associated_to_pair_raises_error_when_pair_not_found(self): + HitbtcAPIOrderBookDataSource._trading_pair_symbol_map = {"BTCUSDT": "BTC-USDT", "ETHUSDT": "ETH-USDT"} + with self.assertRaises(ValueError) as exception: + self.async_run_with_timeout( + HitbtcAPIOrderBookDataSource.exchange_symbol_associated_to_pair("NOT-VALID")) + self.assertEqual("There is no symbol mapping for trading pair NOT-VALID", str(exception)) + + def test_trading_pair_associated_to_exchange_symbol(self): + HitbtcAPIOrderBookDataSource._trading_pair_symbol_map = {"BTCUSDT": "BTC-USDT", "ETHUSDT": "ETH-USDT"} + symbol = self.async_run_with_timeout( + HitbtcAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol("BTCUSDT")) + self.assertEqual("BTC-USDT", symbol) diff --git a/test/hummingbot/connector/exchange/huobi/__init__.py b/test/hummingbot/connector/exchange/huobi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_api_order_book_data_source.py b/test/hummingbot/connector/exchange/huobi/test_huobi_api_order_book_data_source.py new file mode 100644 index 0000000..e5efe68 --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_api_order_book_data_source.py @@ -0,0 +1,292 @@ +import asyncio +import gzip +import json +import re +import unittest +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +import aiohttp +import ujson +from aioresponses.core import aioresponses + +import hummingbot.connector.exchange.huobi.huobi_constants as CONSTANTS +from hummingbot.connector.exchange.huobi.huobi_api_order_book_data_source import HuobiAPIOrderBookDataSource +from hummingbot.connector.exchange.huobi.huobi_web_utils import build_api_factory +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook + + +class HuobiAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}".lower() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.async_tasks: List[asyncio.Task] = [] + self.connector = AsyncMock() + self.connector.exchange_symbol_associated_to_pair.return_value = self.ex_trading_pair + self.connector.trading_pair_associated_to_exchange_symbol.return_value = self.trading_pair + self.data_source = HuobiAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], connector=self.connector, api_factory=build_api_factory() + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + # self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _compress(self, message: Dict[str, Any]) -> bytes: + return gzip.compress(json.dumps(message).encode()) + + def _successfully_subscribed_event(self): + snapshot_resp = { + "id": self.ex_trading_pair, + "status": "ok", + "subbed": f"market.{self.ex_trading_pair}.depth.step0", + "ts": 1637333566824, + } + + trade_resp = { + "id": self.ex_trading_pair, + "status": "ok", + "subbed": f"market.{self.ex_trading_pair}.trade.detail", + "ts": 1637333566737, + } + + return trade_resp, snapshot_resp + + def _trade_update_event(self): + resp = { + "ch": f"market.{self.ex_trading_pair}.trade.detail", + "ts": 1630994963175, + "tick": { + "id": 137005445109, + "ts": 1630994963173, + "data": [ + { + "id": 137005445109359286410323766, + "ts": 1630994963173, + "tradeId": 102523573486, + "amount": 0.006754, + "price": 52648.62, + "direction": "buy", + } + ], + }, + } + return resp + + def _snapshot_response(self): + resp = { + "ch": f"market.{self.ex_trading_pair}.depth.step0", + "ts": 1637255180894, + "tick": { + "bids": [ + [57069.57, 0.05], + ], + "asks": [ + [57057.73, 0.007019], + ], + "version": 141982962388, + "ts": 1637255180700, + }, + } + return resp + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = CONSTANTS.REST_URL + CONSTANTS.DEPTH_URL + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = self._snapshot_response() + + mock_api.get(regex_url, body=ujson.dumps(mock_response)) + + result = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + self.assertIsInstance(result, OrderBook) + self.assertEqual(1637255180700, result.snapshot_uid) + self.assertEqual(1, len(list(result.bid_entries()))) + self.assertEqual(1, len(list(result.ask_entries()))) + self.assertEqual(57069.57, list(result.bid_entries())[0].price) + self.assertEqual(0.05, list(result.bid_entries())[0].amount) + self.assertEqual(57057.73, list(result.ask_entries())[0].price) + self.assertEqual(0.007019, list(result.ask_entries())[0].amount) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = CONSTANTS.REST_URL + CONSTANTS.DEPTH_URL + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_when_subscribing_raised_cancelled(self, ws_connect_mock): + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.huobi.huobi_api_order_book_data_source.HuobiAPIOrderBookDataSource._sleep") + def test_listen_for_subscriptions_raises_logs_exception(self, sleep_mock, ws_connect_mock): + sleep_mock.side_effect = lambda *_: ( + # Allows listen_for_subscriptions to yield control over thread + self.ev_loop.run_until_complete(asyncio.sleep(0.0)) + ) + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = lambda *_: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR") + ) + self.async_tasks.append(self.ev_loop.create_task(self.data_source.listen_for_subscriptions())) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successful_subbed(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subbed_message_snapshot = self._successfully_subscribed_event()[1] + subbed_message_trade = self._successfully_subscribed_event()[0] + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + message=self._compress(subbed_message_snapshot), + message_type=aiohttp.WSMsgType.BINARY, + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + message=self._compress(subbed_message_trade), + message_type=aiohttp.WSMsgType.BINARY, + ) + + self.async_tasks.append(self.ev_loop.create_task(self.data_source.listen_for_subscriptions())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(0, self.data_source._message_queue[CONSTANTS.TRADE_CHANNEL_SUFFIX].qsize()) + self.assertEqual(0, self.data_source._message_queue[CONSTANTS.ORDERBOOK_CHANNEL_SUFFIX].qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_successfully_append_trade_and_orderbook_messages(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + trade_message = self._trade_update_event() + orderbook_message = self._snapshot_response() + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=self._compress(trade_message), message_type=aiohttp.WSMsgType.BINARY + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + message=self._compress(orderbook_message), + message_type=aiohttp.WSMsgType.BINARY, + ) + + self.async_tasks.append(self.ev_loop.create_task(self.data_source.listen_for_subscriptions())) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(1, self.data_source._message_queue[CONSTANTS.TRADE_CHANNEL_SUFFIX].qsize()) + self.assertEqual(1, self.data_source._message_queue[CONSTANTS.ORDERBOOK_CHANNEL_SUFFIX].qsize()) + + def test_listen_for_trades_logs_exception(self): + + trade_message = {"ch": f"market.{self.ex_trading_pair}.trade.detail", "err": "INCOMPLETE MESSAGE"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = [trade_message, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_CHANNEL_SUFFIX] = mock_queue + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + self.assertTrue(self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_CHANNEL_SUFFIX] = mock_queue + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(137005445109359286410323766, msg.trade_id) + + def test_listen_for_order_book_diffs_logs_exception(self): + + orderbook_message = {"ch": f"market.{self.ex_trading_pair}.depth.step0", "err": "INCOMPLETE MESSAGE"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = [orderbook_message, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.ORDERBOOK_CHANNEL_SUFFIX] = mock_queue + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + orderbook_message = self._snapshot_response() + mock_queue = AsyncMock() + mock_queue.get.side_effect = [orderbook_message, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.ORDERBOOK_CHANNEL_SUFFIX] = mock_queue + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1637255180700, msg.update_id) diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/huobi/test_huobi_api_user_stream_data_source.py new file mode 100644 index 0000000..b92ced5 --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_api_user_stream_data_source.py @@ -0,0 +1,355 @@ +import asyncio +import json +import unittest +from typing import Awaitable, List +from unittest.mock import AsyncMock, MagicMock, patch + +import aiohttp + +from hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source import HuobiAPIUserStreamDataSource +from hummingbot.connector.exchange.huobi.huobi_auth import HuobiAuth +from hummingbot.connector.exchange.huobi.huobi_web_utils import build_api_factory +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class HuobiAPIUserStreamDataSourceTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}".lower() + for task in asyncio.all_tasks(loop=cls.ev_loop): + task.cancel() + + @classmethod + def tearDownClass(cls) -> None: + for task in asyncio.all_tasks(loop=cls.ev_loop): + task.cancel() + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.time_synchronizer = MagicMock() + self.time_synchronizer.time.return_value = 1640001112.223 + self.connector = AsyncMock() + self.connector.exchange_symbol_associated_to_pair.return_value = self.ex_trading_pair + self.connector.trading_pair_associated_to_exchange_symbol.return_value = self.trading_pair + self.auth = HuobiAuth(api_key="somKey", + secret_key="someSecretKey", + time_provider=self.time_synchronizer) + self.api_factory = build_api_factory() + self.data_source = HuobiAPIUserStreamDataSource(huobi_auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.api_factory,) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_authenticate_client_raises_cancelled(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = asyncio.CancelledError + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source._authenticate_client(ws)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_authenticate_client_logs_exception(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + + ws_connect_mock.return_value.send_json.side_effect = Exception("TEST ERROR") + + with self.assertRaisesRegex(Exception, "TEST ERROR"): + self.async_run_with_timeout(self.data_source._authenticate_client(ws)) + + self._is_logged("ERROR", "Error occurred authenticating websocket connection... Error: TEST ERROR") + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_authenticate_client_failed(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + + error_auth_response = {"action": "req", "code": 0, "TEST_ERROR": "ERROR WITH AUTHENTICATION"} + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(error_auth_response) + ) + + with self.assertRaisesRegex(ValueError, "User Stream Authentication Fail!"): + self.async_run_with_timeout(self.data_source._authenticate_client(ws)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_authenticate_client_successful(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + + successful_auth_response = {"action": "req", "code": 200, "ch": "auth", "data": {}} + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_auth_response) + ) + + result = self.async_run_with_timeout(self.data_source._authenticate_client(ws)) + + self.assertIsNone(result) + self._is_logged("INFO", "Successfully authenticated to user...") + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_subscribe_channels_raises_cancelled(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = asyncio.CancelledError + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source._subscribe_channels(websocket_assistant=ws)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_subscribe_channels_successful(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + # Initialise WSAssistant and assume connected to websocket server + ws = self.async_run_with_timeout(self.data_source._connected_websocket_assistant()) + successful_auth_response = {"action": "req", "code": 200, "ch": "auth", "data": {}} + successful_sub_trades_response = {"action": "sub", "code": 200, "ch": "trade.clearing#*", "data": {}} + successful_sub_order_response = {"action": "sub", "code": 200, "ch": "orders#*", "data": {}} + successful_sub_account_response = {"action": "sub", "code": 200, "ch": "accounts.update#2", "data": {}} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_auth_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_trades_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_order_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_account_response) + ) + + result = self.async_run_with_timeout( + self.data_source._subscribe_channels(websocket_assistant=ws)) + + self.assertIsNone(result) + + subscription_requests_sent = self.mocking_assistant.json_messages_sent_through_websocket( + ws_connect_mock.return_value) + + expected_orders_channel_subscription = {"action": "sub", "ch": f"orders#{self.ex_trading_pair}"} + self.assertIn(expected_orders_channel_subscription, subscription_requests_sent) + expected_accounts_channel_subscription = {"action": "sub", "ch": "accounts.update#2"} + self.assertIn(expected_accounts_channel_subscription, subscription_requests_sent) + expected_trades_channel_subscription = {"action": "sub", "ch": f"trade.clearing#{self.ex_trading_pair}#0"} + self.assertIn(expected_trades_channel_subscription, subscription_requests_sent) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source.HuobiAPIUserStreamDataSource._sleep") + def test_listen_for_user_stream_raises_cancelled_error(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.side_effect = asyncio.CancelledError + + msg_queue = asyncio.Queue() + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source.HuobiAPIUserStreamDataSource._sleep") + def test_listen_for_user_stream_logs_exception(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + successful_auth_response = {"action": "req", "code": 200, "ch": "auth", "data": {}} + successful_sub_trades_response = {"action": "sub", "code": 200, "ch": "trade.clearing#*", "data": {}} + successful_sub_order_response = {"action": "sub", "code": 200, "ch": "orders#*", "data": {}} + successful_sub_account_response = {"action": "sub", "code": 200, "ch": "accounts.update#2", "data": {}} + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_auth_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_trades_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_order_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_account_response) + ) + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.CLOSE + ) + msg_queue = asyncio.Queue() + + self.async_tasks.append( + self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(0, msg_queue.qsize()) + self._is_logged("ERROR", "Unexpected error with Huobi WebSocket connection. Retrying after 30 seconds...") + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source.HuobiAPIUserStreamDataSource._sleep") + def test_listen_for_user_stream_handle_ping(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + successful_auth_response = {"action": "req", "code": 200, "ch": "auth", "data": {}} + successful_sub_trades_response = {"action": "sub", "code": 200, "ch": "trade.clearing#*", "data": {}} + successful_sub_order_response = {"action": "sub", "code": 200, "ch": "orders#*", "data": {}} + successful_sub_account_response = {"action": "sub", "code": 200, "ch": "accounts.update#2", "data": {}} + + ping_response = {"action": "ping", "data": {"ts": 1637553193021}} + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_auth_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_trades_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_order_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_account_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(ping_response) + ) + + msg_queue = asyncio.Queue() + + self.async_tasks.append( + self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(0, msg_queue.qsize()) + sent_json = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + + self.assertTrue(any(["pong" in str(payload) for payload in sent_json])) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.huobi.huobi_api_user_stream_data_source.HuobiAPIUserStreamDataSource._sleep") + def test_listen_for_user_stream_enqueues_updates(self, _, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + successful_auth_response = {"action": "req", "code": 200, "ch": "auth", "data": {}} + successful_sub_trades_response = {"action": "sub", "code": 200, "ch": "trade.clearing#*", "data": {}} + successful_sub_order_response = {"action": "sub", "code": 200, "ch": "orders#*", "data": {}} + successful_sub_account_response = {"action": "sub", "code": 200, "ch": "accounts.update#2", "data": {}} + + ping_response = {"action": "ping", "data": {"ts": 1637553193021}} + + order_update_response = { + "action": "push", + "ch": "orders#", + "data": { + "execAmt": "0", + "lastActTime": 1637553210074, + "orderSource": "spot-api", + "remainAmt": "0.005", + "orderPrice": "4122.62", + "orderSize": "0.005", + "symbol": "ethusdt", + "orderId": 414497810678464, + "orderStatus": "canceled", + "eventType": "cancellation", + "clientOrderId": "AAc484720a-buy-ETH-USDT-1637553180003697", + "type": "buy-limit-maker", + }, + } + + account_update_response = { + "action": "push", + "ch": "accounts.update#2", + "data": { + "currency": "usdt", + "accountId": 15026496, + "balance": "100", + "available": "100", + "changeType": "order.cancel", + "accountType": "trade", + "seqNum": 117, + "changeTime": 1637553210076, + }, + } + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_auth_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_trades_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_order_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(successful_sub_account_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(ping_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(order_update_response) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(account_update_response) + ) + + msg_queue = asyncio.Queue() + + self.async_tasks.append( + self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(2, msg_queue.qsize()) diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_auth.py b/test/hummingbot/connector/exchange/huobi/test_huobi_auth.py new file mode 100644 index 0000000..a9de025 --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_auth.py @@ -0,0 +1,57 @@ +import asyncio +import base64 +import hashlib +import hmac +import time +import unittest +from copy import copy +from datetime import datetime +from unittest.mock import MagicMock +from urllib.parse import urlencode + +from typing_extensions import Awaitable + +from hummingbot.connector.exchange.huobi.huobi_auth import HuobiAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + + +class HuobiAuthTests(unittest.TestCase): + + def setUp(self): + self._api_key = "testApiKey" + self._secret = "testSecret" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_authenticate(self): + now = time.time() + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + now = datetime.utcfromtimestamp(now).strftime("%Y-%m-%dT%H:%M:%S") + test_url = "https://api.huobi.pro/v1/order/openOrders" + params = { + "order-id": "EO1D1", + } + full_params = copy(params) + + auth = HuobiAuth(api_key=self._api_key, secret_key=self._secret, time_provider=mock_time_provider) + request = RESTRequest(method=RESTMethod.GET, url=test_url, params=params, is_auth_required=True) + configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) + + full_params.update({"Timestamp": now, + "AccessKeyId": self._api_key, + "SignatureMethod": "HmacSHA256", + "SignatureVersion": "2" + }) + full_params = HuobiAuth.keysort(full_params) + encoded_params = urlencode(full_params) + payload = "\n".join(["GET", "api.huobi.pro", "/v1/order/openOrders", encoded_params]) + test_digest = hmac.new( + self._secret.encode("utf8"), + payload.encode("utf8"), + hashlib.sha256).digest() + expected_signature = base64.b64encode(test_digest).decode() + self.assertEqual(now, configured_request.params["Timestamp"]) + self.assertEqual(expected_signature, configured_request.params["Signature"]) diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_exchange.py b/test/hummingbot/connector/exchange/huobi/test_huobi_exchange.py new file mode 100644 index 0000000..0ba5844 --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_exchange.py @@ -0,0 +1,915 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from unittest.mock import patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.huobi import huobi_constants as CONSTANTS, huobi_web_utils as web_utils +from hummingbot.connector.exchange.huobi.huobi_exchange import HuobiExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import MarketOrderFailureEvent + + +class HuobiExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.TRADE_INFO_URL) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.MOST_RECENT_TRADE_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_URL) + return url + + @property + def trading_rules_url(self): + url = web_utils.private_rest_url(CONSTANTS.TRADE_INFO_URL) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.PLACE_ORDER_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNT_BALANCE_URL) + url = url.format(self.get_dummy_account_id()) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def all_symbols_request_mock_response(self): + return { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "state": "online", + "bc": "coinalpha", + "qc": "hbot", + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities", + } + ], + "ts": "1641880897191", + "full": 1, + } + + @property + def latest_prices_request_mock_response(self): + return { + "ch": f"market.{self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}.trade.detail", + "status": "ok", + "ts": 1629792192037, + "tick": { + "id": 136107843051, + "ts": 1629792191928, + "data": [ + { + "id": 136107843051348400221001656, + "ts": 1629792191928, + "trade-id": 102517374388, + "amount": 0.028416, + "price": self.expected_latest_price, + "direction": "buy", + }, + { + "id": 136107843051348400229813302, + "ts": 1629792191928, + "trade-id": 102517374387, + "amount": 0.025794, + "price": 49806.0, + "direction": "buy", + }, + ], + }, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "state": "online", + "bc": self.base_asset.lower(), + "qc": self.quote_asset.lower(), + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities", + }, + { + "symbol": self.exchange_symbol_for_tokens("invalid", "pair"), + "state": "offline", + "bc": "invalid", + "qc": "pair", + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities", + }, + ], + "ts": "1641880897191", + "full": 1, + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "state": "online", + "bc": self.base_asset, + "qc": self.quote_asset, + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities", + } + ], + "ts": "1565246363776", + "full": 1, + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "state": "online", + "bc": self.base_asset, + "qc": self.quote_asset, + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities", + } + ], + "ts": "1565246363776", + "full": 1, + } + + @property + def order_creation_request_successful_mock_response(self): + return {"status": "ok", "data": "356501383558845"} + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "status": "ok", + "data": { + "id": 1000001, + "type": "spot", + "state": "working", + "list": [ + {"currency": self.base_asset, "type": "trade", "balance": "10.0", "seq-num": "477"}, + {"currency": self.quote_asset, "type": "trade", "balance": "2000.0", "seq-num": "477"}, + ], + }, + } + + @property + def balance_request_mock_response_only_base(self): + return { + "status": "ok", + "data": { + "id": 1000001, + "type": "spot", + "state": "working", + "list": [ + { + "currency": self.base_asset.lower(), + "type": "trade", + "balance": "91.850043797676510303", + "seq-num": "477", + }, + ], + }, + } + + @property + def balance_event_websocket_update(self): + return { + "action": "push", + "ch": "accounts.update#2", + "data": { + "currency": "COINALPHA", + "accountId": 123456, + "balance": "15.0", + "available": "10.0", + "changeType": "transfer", + "accountType": "trade", + "seqNum": "86872993928", + "changeTime": 1568601800000, + }, + } + + @property + def expected_latest_price(self): + return 0.00468 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + price_precision = self.trading_rules_request_mock_response["data"][0]["pp"] + amount_precision = self.trading_rules_request_mock_response["data"][0]["ap"] + value_precision = self.trading_rules_request_mock_response["data"][0]["vp"] + + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["minoa"]), + max_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["maxoa"]), + min_price_increment=Decimal(str(10**-price_precision)), + min_base_amount_increment=Decimal(str(10**-amount_precision)), + min_quote_amount_increment=Decimal(str(10**-value_precision)), + min_notional_size=Decimal(self.trading_rules_request_mock_response["data"][0]["minov"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 356501383558845 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + return self.expected_fill_fee + + @property + def expected_fill_trade_id(self) -> str: + return "30000" + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token.lower()}{quote_token.lower()}" + + def get_dummy_account_id(self): + return "100001" + + def create_exchange_instance(self): + + instance = HuobiExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + huobi_api_key="testAPIKey", + huobi_secret_key="testSecret", + trading_pairs=[self.trading_pair], + ) + instance._account_id = self.get_dummy_account_id() + + return instance + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument( + request_call_tuple=request_call, params=request_call.kwargs["params"] or request_call.kwargs["data"] + ) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + test_order_type = f"{order.trade_type.name.lower()}-limit" + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(test_order_type, request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["amount"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["client-order-id"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["params"]) + self.assertEqual(order.exchange_order_id, request_data["order-id"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + expected_params_keys = ["AccessKeyId", "SignatureMethod", "SignatureVersion", "Timestamp", "Signature"] + self.assertEqual(expected_params_keys, list(request_params.keys())) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + request_data = request_call.kwargs["data"] + expected_params_keys = ["AccessKeyId", "SignatureMethod", "SignatureVersion", "Timestamp", "Signature"] + self.assertEqual(expected_params_keys, list(request_params.keys())) + self.assertIsNone(request_data) + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_URL) + url = url.format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.post(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.CANCEL_ORDER_URL) + url = url.format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.post(regex_url, status=400, callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_DETAIL_URL.format(order.exchange_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + url = web_utils.private_rest_url(CONSTANTS.ORDER_DETAIL_URL).format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + r"\?.*") + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return regex_url + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.ORDER_DETAIL_URL.format(order.exchange_order_id)) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.ORDER_DETAIL_URL).format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return [url] + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_DETAIL_URL).format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + r"\?.*") + mock_api.get(regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_DETAIL_URL).format(order.exchange_order_id) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + r"\?.*") + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.ORDER_MATCHES_URL.format(order.exchange_order_id)) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.ORDER_MATCHES_URL.format(order.exchange_order_id)) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + test_type = f"{order.trade_type.name.lower()}-limit" + return { + "action": "push", + "ch": f"orders#{self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}", + "data": { + "orderSize": str(order.amount), + "orderCreateTime": 1640780000, + "accountld": 10001, + "orderPrice": str(order.price), + "type": test_type, + "orderId": order.exchange_order_id, + "clientOrderId": order.client_order_id, + "orderSource": "spot-api", + "orderStatus": "submitted", + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "eventType": "creation", + }, + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + test_type = f"{order.trade_type.name.lower()}-limit" + return { + "action": "push", + "ch": "orders#btcusdt", + "data": { + "lastActTime": 1583853475406, + "remainAmt": str(order.amount), + "execAmt": "2", + "orderId": order.exchange_order_id, + "type": test_type, + "clientOrderId": order.client_order_id, + "orderSource": "spot-api", + "orderPrice": str(order.price), + "orderSize": str(order.amount), + "orderStatus": "canceled", + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "eventType": "cancellation", + }, + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + test_type = f"{order.trade_type.name.lower()}-limit" + return { + "action": "push", + "ch": f"orders#{self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}", + "data": { + "tradePrice": "10000.0", + "tradeVolume": "1.0", + "tradeId": 301, + "tradeTime": 1583854188883, + "aggressor": True, + "remainAmt": "0.0", + "execAmt": "1.0", + "orderId": order.exchange_order_id, + "type": test_type, + "clientOrderId": order.client_order_id, + "orderSource": "spot-api", + "orderPrice": "10000.0", + "orderSize": "1.0", + "orderStatus": "filled", + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "eventType": "trade", + }, + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "ch": f"trade.clearing#{self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}#0", + "data": { + "eventType": "trade", + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": int(order.exchange_order_id), + "tradePrice": str(order.price), + "tradeVolume": str(order.amount), + "orderSide": "buy", + "aggressor": True, + "tradeId": 919219323232, + "tradeTime": 998787897878, + "transactFee": str(self.expected_fill_fee.flat_fees[0].amount), + "feeDeduct": "0", + "feeDeductType": "", + "feeCurrency": self.expected_fill_fee.flat_fees[0].token.lower(), + "accountId": 9912791, + "source": "spot-api", + "orderPrice": str(order.price), + "orderSize": str(order.amount), + "clientOrderId": order.client_order_id, + "orderCreateTime": 998787897878, + "orderStatus": "filled", + }, + } + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + seconds_counter_mock.side_effect = [0, 0, 0] + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"status": "ok", "data": 1640000003000} + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + self.assertEqual(response["data"] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"status": "fail"} + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + self.assertTrue(self.is_logged("NETWORK", "Error getting server time.")) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, exception=asyncio.CancelledError) + self.assertRaises( + asyncio.CancelledError, self.async_run_with_timeout, self.exchange._update_time_synchronizer() + ) + + @aioresponses() + def test_update_balances(self, mock_api): + url = self.balance_url + response = self.balance_request_mock_response_for_base_and_quote + mock_api.get(url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("10"), total_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + @aioresponses() + def test_create_order_fails_if_response_does_not_include_exchange_order_id(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = self.order_creation_url + + creation_response = {"status": "ok", "data": None} + + mock_api.post( + url, body=json.dumps(creation_response), callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id = self.exchange.buy( + trading_pair=self.trading_pair, amount=Decimal("100"), order_type=OrderType.LIMIT, price=Decimal("10000") + ) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(order_request) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "NETWORK", + f"Error submitting {TradeType.BUY.name.lower()} {OrderType.LIMIT.name} order to " + f"{self.exchange.name_cap} for {Decimal('100.000000')} {self.trading_pair} {Decimal('10000.0000')}.", + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "status": "ok", + "data": { + "id": order.exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "account-id": 10001, + "client-order-id": order.client_order_id, + "amount": "5.000000000000000000", + "price": "1.000000000000000000", + "created-at": 1640780000, + "type": "buy-limit-maker", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 0, + "source": "spot-api", + "state": "partial-filled", + "canceled-at": 0, + }, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "status": "ok", + "data": { + "id": order.exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "account-id": 10001, + "client-order-id": order.client_order_id, + "amount": "5.000000000000000000", + "price": "1.000000000000000000", + "created-at": 1640780000, + "type": "buy-limit-maker", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 0, + "source": "spot-api", + "state": "submitted", + "canceled-at": 0, + }, + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "status": "ok", + "data": { + "id": order.exchange_order_id, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "account-id": 10001, + "client-order-id": order.client_order_id, + "amount": "5.000000000000000000", + "price": "1.000000000000000000", + "created-at": 1640780000, + "type": "buy-limit-maker", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 0, + "source": "spot-api", + "state": "canceled", + "canceled-at": 0, + }, + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "status": "ok", + "data": { + "id": 357632718898331, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "account-id": 10001, + "client-order-id": order.client_order_id, + "amount": "5.000000000000000000", + "price": "1.000000000000000000", + "created-at": 1640780000, + "type": "buy-limit-maker", + "field-amount": "0.0", + "field-cash-amount": "0.0", + "field-fees": "0.0", + "finished-at": 0, + "source": "spot-api", + "state": "filled", + "canceled-at": 0, + }, + } + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "status": "ok", + "data": order.exchange_order_id, + } + + def _validate_auth_credentials_taking_parameters_from_argument( + self, request_call_tuple: RequestCall, params: Dict[str, Any] + ): + self.assertIn("Timestamp", params) + self.assertIn("Signature", params) + self.assertEqual("testAPIKey", params["AccessKeyId"]) + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(base_token=self.base_asset, quote_token=self.quote_asset), + "fee-currency": self.expected_fill_fee.flat_fees[0].token.lower(), + "source": "spot-web", + "order-id": int(order.exchange_order_id), + "price": str(self.expected_partial_fill_price), + "created-at": 1629443051839, + "role": "taker", + "match-id": 5014, + "filled-amount": str(self.expected_partial_fill_amount), + "filled-fees": str(self.expected_fill_fee.flat_fees[0].amount), + "filled-points": "0.1", + "fee-deduct-currency": "hbpoint", + "fee-deduct-state": "done", + "trade-id": int(self.expected_fill_trade_id), + "id": 313288753120940, + "type": "buy-market", + } + ], + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "status": "ok", + "data": [ + { + "symbol": self.exchange_symbol_for_tokens(base_token=self.base_asset, quote_token=self.quote_asset), + "fee-currency": self.expected_fill_fee.flat_fees[0].token.lower(), + "source": "spot-web", + "order-id": int(order.exchange_order_id), + "price": str(order.price), + "created-at": 1629443051839, + "role": "taker", + "match-id": 5014, + "filled-amount": str(order.amount), + "filled-fees": str(self.expected_fill_fee.flat_fees[0].amount), + "filled-points": "0.1", + "fee-deduct-currency": "hbpoint", + "fee-deduct-state": "done", + "trade-id": int(self.expected_fill_trade_id), + "id": 313288753120940, + "type": "buy-market", + } + ], + } diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_utility_functions.py b/test/hummingbot/connector/exchange/huobi/test_huobi_utility_functions.py new file mode 100644 index 0000000..067ed96 --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_utility_functions.py @@ -0,0 +1,106 @@ +import unittest + +from hummingbot.connector.exchange.huobi import ( + huobi_constants as CONSTANTS, + huobi_utils as func_utils, + huobi_web_utils as web_utils, +) + + +class HuobiUtilsTestCases(unittest.TestCase): + + def test_public_rest_url(self): + path_url = CONSTANTS.SERVER_TIME_URL + expected_url = CONSTANTS.REST_URL + path_url + self.assertEqual(expected_url, web_utils.public_rest_url(path_url)) + + def test_private_rest_url(self): + path_url = CONSTANTS.SERVER_TIME_URL + expected_url = CONSTANTS.REST_URL + path_url + self.assertEqual(expected_url, web_utils.private_rest_url(path_url)) + + def test_is_exchange_information_valid(self): + invalid_info = { + "status": "ok", + "data": [ + { + "symbol": "btc3lusdt", + "state": "offline", + "bc": "btc3l", + "qc": "usdt", + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities" + } + ], + "ts": "1641880897191", + "full": 1 + } + + self.assertFalse(func_utils.is_exchange_information_valid(invalid_info["data"][0])) + + valid_info = { + "status": "ok", + "data": [ + { + "symbol": "btc3lusdt", + "state": "online", + "bc": "btc3l", + "qc": "usdt", + "pp": 4, + "ap": 4, + "sp": "main", + "vp": 8, + "minoa": 0.01, + "maxoa": 199.0515, + "minov": 5, + "lominoa": 0.01, + "lomaxoa": 199.0515, + "lomaxba": 199.0515, + "lomaxsa": 199.0515, + "smminoa": 0.01, + "blmlt": 1.1, + "slmgt": 0.9, + "smmaxoa": 199.0515, + "bmmaxov": 2500, + "msormlt": 0.1, + "mbormlt": 0.1, + "maxov": 2500, + "u": "btcusdt", + "mfr": 0.035, + "ct": "23:55:00", + "rt": "00:00:00", + "rthr": 4, + "in": 16.3568, + "at": "enabled", + "tags": "etp,nav,holdinglimit,activities" + } + ], + "ts": "1641880897191", + "full": 1 + } + self.assertTrue(func_utils.is_exchange_information_valid(valid_info["data"][0])) diff --git a/test/hummingbot/connector/exchange/huobi/test_huobi_ws_post_processor.py b/test/hummingbot/connector/exchange/huobi/test_huobi_ws_post_processor.py new file mode 100644 index 0000000..e81694f --- /dev/null +++ b/test/hummingbot/connector/exchange/huobi/test_huobi_ws_post_processor.py @@ -0,0 +1,79 @@ +import asyncio +import gzip +import json +import unittest +from typing import Any, Awaitable, Dict + +from hummingbot.connector.utils import GZipCompressionWSPostProcessor +from hummingbot.core.web_assistant.connections.data_types import WSResponse + + +class HuobiWSPostProcessorTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}".lower() + + def setUp(self) -> None: + super().setUp() + + self.post_processor = GZipCompressionWSPostProcessor() + + def _compress(self, message: Dict[str, Any]) -> bytes: + return gzip.compress(json.dumps(message).encode()) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_post_process(self): + + # Only Market data is compressed by GZIP + orderbook_message: bytes = self._compress( + message={ + "ch": f"market.{self.ex_trading_pair}.depth.step0", + "ts": 1630983549503, + "tick": { + "bids": [[52690.69, 0.36281], [52690.68, 0.2]], + "asks": [[52690.7, 0.372591], [52691.26, 0.13]], + "version": 136998124622, + "ts": 1630983549500, + }, + } + ) + + orderbook_response: WSResponse = WSResponse(data=orderbook_message) + + result_response: WSResponse = self.async_run_with_timeout(self.post_processor.post_process(orderbook_response)) + + self.assertIsInstance(result_response.data, Dict) + self.assertIn(self.ex_trading_pair, str(result_response.data)) + + # User stream message is NOT compressed by GZIP + account_message = { + "action": "push", + "ch": "accounts.update#2", + "data": { + "currency": self.quote_asset, + "accountId": 15026496, + "balance": "100.0", + "available": "10.0", + "changeType": None, + "accountType": "trade", + "changeTime": None, + "seqNum": 804, + }, + } + + account_response: WSResponse = WSResponse(data=account_message) + + result_response: WSResponse = self.async_run_with_timeout(self.post_processor.post_process(account_response)) + + self.assertIsInstance(result_response.data, Dict) + self.assertIn(self.quote_asset, str(result_response.data)) + self.assertEqual("100.0", result_response.data["data"]["balance"]) + self.assertEqual("10.0", result_response.data["data"]["available"]) diff --git a/test/hummingbot/connector/exchange/injective_v2/__init__.py b/test/hummingbot/connector/exchange/injective_v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/injective_v2/data_sources/__init__.py b/test/hummingbot/connector/exchange/injective_v2/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/injective_v2/data_sources/test_injective_data_source.py b/test/hummingbot/connector/exchange/injective_v2/data_sources/test_injective_data_source.py new file mode 100644 index 0000000..0986539 --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/data_sources/test_injective_data_source.py @@ -0,0 +1,592 @@ +import asyncio +import json +import re +from decimal import Decimal +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Awaitable, Optional, Union +from unittest import TestCase +from unittest.mock import patch + +from pyinjective.composer import Composer +from pyinjective.core.network import Network +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source import ( + InjectiveGranteeDataSource, +) +from hummingbot.connector.exchange.injective_v2.data_sources.injective_vaults_data_source import ( + InjectiveVaultsDataSource, +) +from hummingbot.connector.exchange.injective_v2.injective_market import InjectiveSpotMarket +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.data_type.common import OrderType, TradeType + + +class InjectiveGranteeDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher.fetch_all") + def setUp(self, _) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + self.async_tasks = [] + asyncio.set_event_loop(self.async_loop) + + _, grantee_private_key = PrivateKey.generate() + _, granter_private_key = PrivateKey.generate() + + self.data_source = InjectiveGranteeDataSource( + private_key=grantee_private_key.to_hex(), + subaccount_index=0, + granter_address=Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())).to_acc_bech32(), + granter_subaccount_index=0, + network=Network.testnet(node="sentry"), + rate_limits=CONSTANTS.PUBLIC_NODE_RATE_LIMITS, + ) + + self.query_executor = ProgrammableQueryExecutor() + self.data_source._query_executor = self.query_executor + + self.log_records = [] + self._logs_event: Optional[asyncio.Event] = None + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + def tearDown(self) -> None: + self.async_run_with_timeout(self.data_source.stop()) + for task in self.async_tasks: + task.cancel() + self.async_loop.stop() + # self.async_loop.close() + # Since the event loop will change we need to remove the logs event created in the old event loop + self._logs_event = None + asyncio.set_event_loop(self._original_async_loop) + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.async_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def create_task(self, coroutine: Awaitable) -> asyncio.Task: + task = self.async_loop.create_task(coroutine) + self.async_tasks.append(task) + return task + + def handle(self, record): + self.log_records.append(record) + if self._logs_event is not None: + self._logs_event.set() + + def is_logged(self, log_level: str, message: Union[str, re.Pattern]) -> bool: + expression = ( + re.compile( + f"^{message}$" + .replace(".", r"\.") + .replace("?", r"\?") + .replace("/", r"\/") + .replace("(", r"\(") + .replace(")", r"\)") + .replace("[", r"\[") + .replace("]", r"\]") + ) + if isinstance(message, str) + else message + ) + return any( + record.levelname == log_level and expression.match(record.getMessage()) is not None + for record in self.log_records + ) + + def test_market_and_tokens_construction(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + market_info = self._inj_usdt_market_info() + inj_usdt_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(market_info["marketId"]) + ) + inj_token = inj_usdt_market.base_token + usdt_token = inj_usdt_market.quote_token + + self.assertEqual(market_info["marketId"], inj_usdt_market.market_id) + self.assertEqual(market_info, inj_usdt_market.market_info) + self.assertEqual(f"{inj_token.unique_symbol}-{usdt_token.unique_symbol}", inj_usdt_market.trading_pair()) + self.assertEqual(market_info["baseDenom"], inj_token.denom) + self.assertEqual(market_info["baseTokenMeta"]["symbol"], inj_token.symbol) + self.assertEqual(inj_token.symbol, inj_token.unique_symbol) + self.assertEqual(market_info["baseTokenMeta"]["name"], inj_token.name) + self.assertEqual(market_info["baseTokenMeta"]["decimals"], inj_token.decimals) + self.assertEqual(market_info["quoteDenom"], usdt_token.denom) + self.assertEqual(market_info["quoteTokenMeta"]["symbol"], usdt_token.symbol) + self.assertEqual(usdt_token.symbol, usdt_token.unique_symbol) + self.assertEqual(market_info["quoteTokenMeta"]["name"], usdt_token.name) + self.assertEqual(market_info["quoteTokenMeta"]["decimals"], usdt_token.decimals) + + market_info = self._usdc_solana_usdc_eth_market_info() + usdc_solana_usdc_eth_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(market_info["marketId"]) + ) + usdc_solana_token = usdc_solana_usdc_eth_market.base_token + usdc_eth_token = usdc_solana_usdc_eth_market.quote_token + + self.assertEqual(market_info["marketId"], usdc_solana_usdc_eth_market.market_id) + self.assertEqual(market_info, usdc_solana_usdc_eth_market.market_info) + self.assertEqual(f"{usdc_solana_token.unique_symbol}-{usdc_eth_token.unique_symbol}", usdc_solana_usdc_eth_market.trading_pair()) + self.assertEqual(market_info["baseDenom"], usdc_solana_token.denom) + self.assertEqual(market_info["baseTokenMeta"]["symbol"], usdc_solana_token.symbol) + self.assertEqual(market_info["ticker"].split("/")[0], usdc_solana_token.unique_symbol) + self.assertEqual(market_info["baseTokenMeta"]["name"], usdc_solana_token.name) + self.assertEqual(market_info["baseTokenMeta"]["decimals"], usdc_solana_token.decimals) + self.assertEqual(market_info["quoteDenom"], usdc_eth_token.denom) + self.assertEqual(market_info["quoteTokenMeta"]["symbol"], usdc_eth_token.symbol) + self.assertEqual(usdc_eth_token.name, usdc_eth_token.unique_symbol) + self.assertEqual(market_info["quoteTokenMeta"]["name"], usdc_eth_token.name) + self.assertEqual(market_info["quoteTokenMeta"]["decimals"], usdc_eth_token.decimals) + + def test_markets_initialization_generates_unique_trading_pairs_for_tokens_with_same_symbol(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + inj_usdt_trading_pair = self.async_run_with_timeout( + self.data_source.trading_pair_for_market(market_id=self._inj_usdt_market_info()["marketId"]) + ) + self.assertEqual("INJ-USDT", inj_usdt_trading_pair) + usdt_usdc_trading_pair = self.async_run_with_timeout( + self.data_source.trading_pair_for_market(market_id=self._usdt_usdc_market_info()["marketId"]) + ) + self.assertEqual("USDT-USDC", usdt_usdc_trading_pair) + usdt_usdc_eth_trading_pair = self.async_run_with_timeout( + self.data_source.trading_pair_for_market(market_id=self._usdt_usdc_eth_market_info()["marketId"]) + ) + self.assertEqual("USDT-USC Coin (Wormhole from Ethereum)", usdt_usdc_eth_trading_pair) + usdc_solana_usdc_eth_trading_pair = self.async_run_with_timeout( + self.data_source.trading_pair_for_market(market_id=self._usdc_solana_usdc_eth_market_info()["marketId"]) + ) + self.assertEqual("USDCso-USC Coin (Wormhole from Ethereum)", usdc_solana_usdc_eth_trading_pair) + + def test_markets_initialization_adds_different_tokens_having_same_symbol(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + self.async_run_with_timeout(self.data_source.update_markets()) + + inj_usdt_market_info = self._inj_usdt_market_info() + self.assertIn(inj_usdt_market_info["baseDenom"], self.data_source._tokens_map) + self.assertEqual( + inj_usdt_market_info["baseDenom"], + self.data_source._token_symbol_symbol_and_denom_map[inj_usdt_market_info["baseTokenMeta"]["symbol"]] + ) + self.assertIn(inj_usdt_market_info["quoteDenom"], self.data_source._tokens_map) + self.assertEqual( + inj_usdt_market_info["quoteDenom"], + self.data_source._token_symbol_symbol_and_denom_map[inj_usdt_market_info["quoteTokenMeta"]["symbol"]] + ) + + usdt_usdc_market_info = self._usdt_usdc_market_info() + self.assertIn(usdt_usdc_market_info["quoteDenom"], self.data_source._tokens_map) + self.assertEqual( + usdt_usdc_market_info["quoteDenom"], + self.data_source._token_symbol_symbol_and_denom_map[usdt_usdc_market_info["quoteTokenMeta"]["symbol"]] + ) + + usdt_usdc_eth_market_info = self._usdt_usdc_eth_market_info() + self.assertIn(usdt_usdc_eth_market_info["quoteDenom"], self.data_source._tokens_map) + self.assertEqual( + usdt_usdc_eth_market_info["quoteDenom"], + self.data_source._token_symbol_symbol_and_denom_map[usdt_usdc_eth_market_info["quoteTokenMeta"]["name"]] + ) + + usdc_solana_usdc_eth_market_info = self._usdc_solana_usdc_eth_market_info() + expected_usdc_solana_unique_symbol = usdc_solana_usdc_eth_market_info["ticker"].split("/")[0] + self.assertIn(usdc_solana_usdc_eth_market_info["baseDenom"], self.data_source._tokens_map) + self.assertEqual( + usdc_solana_usdc_eth_market_info["baseDenom"], + self.data_source._token_symbol_symbol_and_denom_map[expected_usdc_solana_unique_symbol] + ) + + def test_markets_initialization_creates_one_instance_per_token(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + inj_usdt_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(self._inj_usdt_market_info()["marketId"]) + ) + usdt_usdc_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(self._usdt_usdc_market_info()["marketId"]) + ) + usdt_usdc_eth_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(self._usdt_usdc_eth_market_info()["marketId"]) + ) + usdc_solana_usdc_eth_market: InjectiveSpotMarket = self.async_run_with_timeout( + self.data_source.spot_market_info_for_id(self._usdc_solana_usdc_eth_market_info()["marketId"]) + ) + + self.assertEqual(inj_usdt_market.quote_token, usdt_usdc_market.base_token) + self.assertEqual(inj_usdt_market.quote_token, usdt_usdc_eth_market.base_token) + + self.assertNotEqual(usdt_usdc_market.quote_token, usdt_usdc_eth_market.quote_token) + self.assertNotEqual(usdt_usdc_market.quote_token, usdc_solana_usdc_eth_market.base_token) + + self.assertEqual(usdt_usdc_eth_market.quote_token, usdc_solana_usdc_eth_market.quote_token) + self.assertNotEqual(usdt_usdc_eth_market.quote_token, usdc_solana_usdc_eth_market.base_token) + + def _spot_markets_response(self): + return [ + self._inj_usdt_market_info(), + self._usdt_usdc_market_info(), + self._usdt_usdc_eth_market_info(), + self._usdc_solana_usdc_eth_market_info() + ] + + def _usdc_solana_usdc_eth_market_info(self): + return { + "marketId": "0xb825e2e4dbe369446e454e21c16e041cbc4d95d73f025c369f92210e82d2106f", # noqa: mock + "marketStatus": "active", + "ticker": "USDCso/USDCet", + "baseDenom": "factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj12pwnhtv7yat2s30xuf4gdk9qm85v4j3e60dgvu", # noqa: mock + "baseTokenMeta": { + "name": "USD Coin (Wormhole from Solana)", + "address": "0x0000000000000000000000000000000000000000", + "symbol": "USDC", + "logo": "https://static.alchemyapi.io/images/assets/3408.png", + "decimals": 6, + "updatedAt": "1685371052880", + }, + "quoteDenom": "factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1q6zlut7gtkzknkk773jecujwsdkgq882akqksk", # noqa: mock + "quoteTokenMeta": { + "name": "USC Coin (Wormhole from Ethereum)", + "address": "0x0000000000000000000000000000000000000000", + "symbol": "USDC", + "logo": "https://static.alchemyapi.io/images/assets/3408.png", + "decimals": 6, + "updatedAt": "1685371052880", + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.0001", + "minQuantityTickSize": "100", + } + + def _usdt_usdc_eth_market_info(self): + return { + "marketId": "0xda0bb7a7d8361d17a9d2327ed161748f33ecbf02738b45a7dd1d812735d1531c", # noqa: mock + "marketStatus": "active", + "ticker": "USDT/USDC", + "baseDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", + "baseTokenMeta": { + "name": "Tether", + "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1685371052879", + }, + "quoteDenom": "factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1q6zlut7gtkzknkk773jecujwsdkgq882akqksk", # noqa: mock + "quoteTokenMeta": { + "name": "USC Coin (Wormhole from Ethereum)", + "address": "0x0000000000000000000000000000000000000000", + "symbol": "USDC", + "logo": "https://static.alchemyapi.io/images/assets/3408.png", + "decimals": 6, + "updatedAt": "1685371052880" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.0001", + "minQuantityTickSize": "100", + } + + def _usdt_usdc_market_info(self): + return { + "marketId": "0x8b1a4d3e8f6b559e30e40922ee3662dd78edf7042330d4d620d188699d1a9715", # noqa: mock + "marketStatus": "active", + "ticker": "USDT/USDC", + "baseDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", + "baseTokenMeta": { + "name": "Tether", + "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1685371052879" + }, + "quoteDenom": "peggy0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "quoteTokenMeta": { + "name": "USD Coin", + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "symbol": "USDC", + "logo": "https://static.alchemyapi.io/images/assets/3408.png", + "decimals": 6, + "updatedAt": "1685371052879" + }, + "makerFeeRate": "0.001", + "takerFeeRate": "0.002", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.0001", + "minQuantityTickSize": "100", + } + + def _inj_usdt_market_info(self): + return { + "marketId": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0", # noqa: mock + "marketStatus": "active", + "ticker": "INJ/USDT", + "baseDenom": "inj", + "baseTokenMeta": { + "name": "Injective Protocol", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + "symbol": "INJ", + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1685371052879" + }, + "quoteDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", + "quoteTokenMeta": { + "name": "Tether", + "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1685371052879" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + } + + +class InjectiveVaultsDataSourceTests(TestCase): + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher.fetch_all") + def setUp(self, _) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + self.async_tasks = [] + asyncio.set_event_loop(self.async_loop) + + _, self._grantee_private_key = PrivateKey.generate() + self._vault_address = "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp" + + self.data_source = InjectiveVaultsDataSource( + private_key=self._grantee_private_key.to_hex(), + subaccount_index=0, + vault_contract_address=self._vault_address, + vault_subaccount_index=1, + network=Network.testnet(node="sentry"), + use_secure_connection=True, + rate_limits=CONSTANTS.PUBLIC_NODE_RATE_LIMITS, + ) + + self.query_executor = ProgrammableQueryExecutor() + self.data_source._query_executor = self.query_executor + + self.data_source._composer = Composer(network=self.data_source.network_name) + + def tearDown(self) -> None: + self.async_run_with_timeout(self.data_source.stop()) + for task in self.async_tasks: + task.cancel() + self.async_loop.stop() + # self.async_loop.close() + # Since the event loop will change we need to remove the logs event created in the old event loop + self._logs_event = None + asyncio.set_event_loop(self._original_async_loop) + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.async_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def create_task(self, coroutine: Awaitable) -> asyncio.Task: + task = self.async_loop.create_task(coroutine) + self.async_tasks.append(task) + return task + + def test_order_creation_message_generation(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + orders = [] + order = GatewayInFlightOrder( + client_order_id="someOrderIDCreate", + trading_pair="INJ-USDT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=123123123, + amount=Decimal("10"), + price=Decimal("100"), + ) + orders.append(order) + + messages, spot_order_hashes, derivative_order_hashes = self.async_run_with_timeout( + self.data_source._order_creation_messages( + spot_orders_to_create=orders, + derivative_orders_to_create=[], + ) + ) + + pub_key = self._grantee_private_key.to_public_key() + address = pub_key.to_address() + + self.assertEqual(0, len(spot_order_hashes)) + self.assertEqual(address.to_acc_bech32(), messages[0].sender) + self.assertEqual(self._vault_address, messages[0].contract) + + market = self._inj_usdt_market_info() + base_token_decimals = market["baseTokenMeta"]["decimals"] + quote_token_meta = market["quoteTokenMeta"]["decimals"] + message_data = json.loads(messages[0].msg.decode()) + + message_price = (order.price * Decimal(f"1e{quote_token_meta-base_token_decimals}")).normalize() + message_quantity = (order.amount * Decimal(f"1e{base_token_decimals}")).normalize() + + expected_data = { + "admin_execute_message": { + "injective_message": { + "custom": { + "route": "exchange", + "msg_data": { + "batch_update_orders": { + "sender": self._vault_address, + "spot_orders_to_create": [ + { + "market_id": market["marketId"], + "order_info": { + "fee_recipient": self._vault_address, + "subaccount_id": "1", + "price": f"{message_price:f}", + "quantity": f"{message_quantity:f}" + }, + "order_type": 1, + "trigger_price": "0", + } + ], + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "derivative_orders_to_create": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_cancel": [], + "binary_options_orders_to_create": [], + } + } + } + } + } + } + + self.assertEqual(expected_data, message_data) + + def test_order_cancel_message_generation(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + market = self._inj_usdt_market_info() + + orders_data = [] + composer = asyncio.get_event_loop().run_until_complete(self.data_source.composer()) + order_data = composer.OrderData( + market_id=market["marketId"], + subaccount_id="1", + order_hash="0xba954bc613a81cd712b9ec0a3afbfc94206cf2ff8c60d1868e031d59ea82bf27", # noqa: mock" + order_direction="buy", + order_type="limit", + ) + orders_data.append(order_data) + + message = self.async_run_with_timeout( + self.data_source._order_cancel_message( + spot_orders_to_cancel=orders_data, + derivative_orders_to_cancel=[], + ) + ) + + pub_key = self._grantee_private_key.to_public_key() + address = pub_key.to_address() + + self.assertEqual(address.to_acc_bech32(), message.sender) + self.assertEqual(self._vault_address, message.contract) + + message_data = json.loads(message.msg.decode()) + + expected_data = { + "admin_execute_message": { + "injective_message": { + "custom": { + "route": "exchange", + "msg_data": { + "batch_update_orders": { + "sender": self._vault_address, + "spot_orders_to_create": [], + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [ + { + "market_id": market["marketId"], + "subaccount_id": "1", + "order_hash": "0xba954bc613a81cd712b9ec0a3afbfc94206cf2ff8c60d1868e031d59ea82bf27", # noqa: mock" + "order_mask": 74, + } + ], + "derivative_orders_to_cancel": [], + "derivative_orders_to_create": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_cancel": [], + "binary_options_orders_to_create": [], + } + } + } + } + } + } + + self.assertEqual(expected_data, message_data) + + def _spot_markets_response(self): + return [ + self._inj_usdt_market_info(), + ] + + def _inj_usdt_market_info(self): + return { + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "marketStatus": "active", + "ticker": "INJ/USDT", + "baseDenom": "inj", + "baseTokenMeta": { + "name": "Injective Protocol", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + "symbol": "INJ", + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1685371052879" + }, + "quoteDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", + "quoteTokenMeta": { + "name": "Tether", + "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1685371052879" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + } diff --git a/test/hummingbot/connector/exchange/injective_v2/programmable_query_executor.py b/test/hummingbot/connector/exchange/injective_v2/programmable_query_executor.py new file mode 100644 index 0000000..0474ddf --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/programmable_query_executor.py @@ -0,0 +1,201 @@ +import asyncio +from typing import Any, Dict, List, Optional + +from hummingbot.connector.exchange.injective_v2.injective_query_executor import BaseInjectiveQueryExecutor + + +class ProgrammableQueryExecutor(BaseInjectiveQueryExecutor): + + def __init__(self): + self._ping_responses = asyncio.Queue() + self._spot_markets_responses = asyncio.Queue() + self._derivative_market_responses = asyncio.Queue() + self._derivative_markets_responses = asyncio.Queue() + self._spot_order_book_responses = asyncio.Queue() + self._derivative_order_book_responses = asyncio.Queue() + self._transaction_by_hash_responses = asyncio.Queue() + self._account_portfolio_responses = asyncio.Queue() + self._simulate_transaction_responses = asyncio.Queue() + self._send_transaction_responses = asyncio.Queue() + self._spot_trades_responses = asyncio.Queue() + self._derivative_trades_responses = asyncio.Queue() + self._historical_spot_orders_responses = asyncio.Queue() + self._historical_derivative_orders_responses = asyncio.Queue() + self._transaction_block_height_responses = asyncio.Queue() + self._funding_rates_responses = asyncio.Queue() + self._oracle_prices_responses = asyncio.Queue() + self._funding_payments_responses = asyncio.Queue() + self._derivative_positions_responses = asyncio.Queue() + + self._spot_order_book_updates = asyncio.Queue() + self._public_spot_trade_updates = asyncio.Queue() + self._derivative_order_book_updates = asyncio.Queue() + self._public_derivative_trade_updates = asyncio.Queue() + self._oracle_prices_updates = asyncio.Queue() + self._subaccount_positions_events = asyncio.Queue() + self._subaccount_balance_events = asyncio.Queue() + self._historical_spot_order_events = asyncio.Queue() + self._historical_derivative_order_events = asyncio.Queue() + self._transaction_events = asyncio.Queue() + + async def ping(self): + response = await self._ping_responses.get() + return response + + async def spot_markets(self, status: str) -> Dict[str, Any]: + response = await self._spot_markets_responses.get() + return response + + async def derivative_markets(self, status: str) -> Dict[str, Any]: + response = await self._derivative_markets_responses.get() + return response + + async def derivative_market(self, market_id: str) -> Dict[str, Any]: + response = await self._derivative_market_responses.get() + return response + + async def get_spot_orderbook(self, market_id: str) -> Dict[str, Any]: + response = await self._spot_order_book_responses.get() + return response + + async def get_derivative_orderbook(self, market_id: str) -> Dict[str, Any]: + response = await self._derivative_order_book_responses.get() + return response + + async def get_tx_by_hash(self, tx_hash: str) -> Dict[str, Any]: + response = await self._transaction_by_hash_responses.get() + return response + + async def get_tx_block_height(self, tx_hash: str) -> int: + response = await self._transaction_block_height_responses.get() + return response + + async def account_portfolio(self, account_address: str) -> Dict[str, Any]: + response = await self._account_portfolio_responses.get() + return response + + async def simulate_tx(self, tx_byte: bytes) -> Dict[str, Any]: + response = await self._simulate_transaction_responses.get() + return response + + async def send_tx_sync_mode(self, tx_byte: bytes) -> Dict[str, Any]: + response = await self._send_transaction_responses.get() + return response + + async def get_spot_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: + response = await self._spot_trades_responses.get() + return response + + async def get_derivative_trades( + self, + market_ids: List[str], + subaccount_id: Optional[str] = None, + start_time: Optional[int] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + ) -> Dict[str, Any]: + response = await self._derivative_trades_responses.get() + return response + + async def get_historical_spot_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: + response = await self._historical_spot_orders_responses.get() + return response + + async def get_historical_derivative_orders( + self, + market_ids: List[str], + subaccount_id: str, + start_time: int, + skip: int, + ) -> Dict[str, Any]: + response = await self._historical_derivative_orders_responses.get() + return response + + async def get_funding_rates(self, market_id: str, limit: int) -> Dict[str, Any]: + response = await self._funding_rates_responses.get() + return response + + async def get_funding_payments(self, subaccount_id: str, market_id: str, limit: int) -> Dict[str, Any]: + response = await self._funding_payments_responses.get() + return response + + async def get_derivative_positions(self, subaccount_id: str, skip: int) -> Dict[str, Any]: + response = await self._derivative_positions_responses.get() + return response + + async def get_oracle_prices( + self, + base_symbol: str, + quote_symbol: str, + oracle_type: str, + oracle_scale_factor: int, + ) -> Dict[str, Any]: + response = await self._oracle_prices_responses.get() + return response + + async def spot_order_book_updates_stream(self, market_ids: List[str]): + while True: + next_ob_update = await self._spot_order_book_updates.get() + yield next_ob_update + + async def public_spot_trades_stream(self, market_ids: List[str]): + while True: + next_trade = await self._public_spot_trade_updates.get() + yield next_trade + + async def derivative_order_book_updates_stream(self, market_ids: List[str]): + while True: + next_ob_update = await self._derivative_order_book_updates.get() + yield next_ob_update + + async def public_derivative_trades_stream(self, market_ids: List[str]): + while True: + next_trade = await self._public_derivative_trade_updates.get() + yield next_trade + + async def oracle_prices_stream(self, oracle_base: str, oracle_quote: str, oracle_type: str): + while True: + next_update = await self._oracle_prices_updates.get() + yield next_update + + async def subaccount_positions_stream(self, subaccount_id: str): + while True: + next_event = await self._subaccount_positions_events.get() + yield next_event + + async def subaccount_balance_stream(self, subaccount_id: str): + while True: + next_event = await self._subaccount_balance_events.get() + yield next_event + + async def subaccount_historical_spot_orders_stream( + self, market_id: str, subaccount_id: str + ): + while True: + next_event = await self._historical_spot_order_events.get() + yield next_event + + async def subaccount_historical_derivative_orders_stream( + self, market_id: str, subaccount_id: str + ): + while True: + next_event = await self._historical_derivative_order_events.get() + yield next_event + + async def transactions_stream(self,): + while True: + next_event = await self._transaction_events.get() + yield next_event diff --git a/test/hummingbot/connector/exchange/injective_v2/test_injective_market.py b/test/hummingbot/connector/exchange/injective_v2/test_injective_market.py new file mode 100644 index 0000000..41cb0e8 --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/test_injective_market.py @@ -0,0 +1,212 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.exchange.injective_v2.injective_market import ( + InjectiveDerivativeMarket, + InjectiveSpotMarket, + InjectiveToken, +) + + +class InjectiveSpotMarketTests(TestCase): + + def setUp(self) -> None: + super().setUp() + + self._inj_token = InjectiveToken( + denom="inj", + symbol="INJ", + unique_symbol="INJ", + name="Injective Protocol", + decimals=18, + ) + self._usdt_token = InjectiveToken( + denom="peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", # noqa: mock + symbol="USDT", + unique_symbol="USDT", + name="Tether", + decimals=6, + ) + + self._inj_usdt_market = InjectiveSpotMarket( + market_id="0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0", # noqa: mock + base_token=self._inj_token, + quote_token=self._usdt_token, + market_info={ + "marketId": "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0", # noqa: mock + "marketStatus": "active", + "ticker": "INJ/USDT", + "baseDenom": "inj", + "baseTokenMeta": { + "name": "Injective Protocol", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + "symbol": "INJ", + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1685371052879" + }, + "quoteDenom": "peggy0xdAC17F958D2ee523a2206206994597C13D831ec7", + "quoteTokenMeta": { + "name": "Tether", + "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", # noqa: mock + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1685371052879" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + } + ) + + def test_trading_pair(self): + self.assertEqual("INJ-USDT", self._inj_usdt_market.trading_pair()) + + def test_convert_quantity_from_chain_format(self): + expected_quantity = Decimal("1234") + chain_quantity = expected_quantity * Decimal(f"1e{self._inj_token.decimals}") + converted_quantity = self._inj_usdt_market.quantity_from_chain_format(chain_quantity=chain_quantity) + + self.assertEqual(expected_quantity, converted_quantity) + + def test_convert_price_from_chain_format(self): + expected_price = Decimal("15.43") + chain_price = expected_price * Decimal(f"1e{self._usdt_token.decimals}") / Decimal(f"1e{self._inj_token.decimals}") + converted_price = self._inj_usdt_market.price_from_chain_format(chain_price=chain_price) + + self.assertEqual(expected_price, converted_price) + + def test_min_price_tick_size(self): + market = self._inj_usdt_market + expected_value = market.price_from_chain_format(chain_price=Decimal(market.market_info["minPriceTickSize"])) + + self.assertEqual(expected_value, market.min_price_tick_size()) + + def test_min_quantity_tick_size(self): + market = self._inj_usdt_market + expected_value = market.quantity_from_chain_format( + chain_quantity=Decimal(market.market_info["minQuantityTickSize"]) + ) + + self.assertEqual(expected_value, market.min_quantity_tick_size()) + + +class InjectiveDerivativeMarketTests(TestCase): + + def setUp(self) -> None: + super().setUp() + + self._usdt_token = InjectiveToken( + denom="peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", # noqa: mock + symbol="USDT", + unique_symbol="USDT", + name="Tether", + decimals=6, + ) + + self._inj_usdt_derivative_market = InjectiveDerivativeMarket( + market_id="0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", # noqa: mock + quote_token=self._usdt_token, + market_info={ + "marketId": "0x17ef48032cb24375ba7c2e39f384e56433bcab20cbee9a7357e4cba2eb00abe6", # noqa: mock + "marketStatus": "active", + "ticker": "INJ/USDT PERP", + "oracleBase": "0x2d9315a88f3019f8efa88dfe9c0f0843712da0bac814461e27733f6b83eb51b3", # noqa: mock + "oracleQuote": "0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588", # noqa: mock + "oracleType": "pyth", + "oracleScaleFactor": 6, + "initialMarginRatio": "0.195", + "maintenanceMarginRatio": "0.05", + "quoteDenom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", + "quoteTokenMeta": { + "name": "Testnet Tether USDT", + "address": "0x0000000000000000000000000000000000000000", + "symbol": "USDT", + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0003", + "takerFeeRate": "0.003", + "serviceProviderFee": "0.4", + "isPerpetual": True, + "minPriceTickSize": "100", + "minQuantityTickSize": "0.0001", + "perpetualMarketInfo": { + "hourlyFundingRateCap": "0.000625", + "hourlyInterestRate": "0.00000416666", + "nextFundingTimestamp": "1690318800", + "fundingInterval": "3600" + }, + "perpetualMarketFunding": { + "cumulativeFunding": "81363.592243119007273334", + "cumulativePrice": "1.432536051546776736", + "lastTimestamp": "1689423842" + } + } + ) + + def test_trading_pair(self): + self.assertEqual("INJ-USDT", self._inj_usdt_derivative_market.trading_pair()) + + def test_convert_quantity_from_chain_format(self): + expected_quantity = Decimal("1234") + chain_quantity = expected_quantity + converted_quantity = self._inj_usdt_derivative_market.quantity_from_chain_format(chain_quantity=chain_quantity) + + self.assertEqual(expected_quantity, converted_quantity) + + def test_convert_price_from_chain_format(self): + expected_price = Decimal("15.43") + chain_price = expected_price * Decimal(f"1e{self._usdt_token.decimals}") + converted_price = self._inj_usdt_derivative_market.price_from_chain_format(chain_price=chain_price) + + self.assertEqual(expected_price, converted_price) + + def test_min_price_tick_size(self): + market = self._inj_usdt_derivative_market + expected_value = market.price_from_chain_format(chain_price=Decimal(market.market_info["minPriceTickSize"])) + + self.assertEqual(expected_value, market.min_price_tick_size()) + + def test_min_quantity_tick_size(self): + market = self._inj_usdt_derivative_market + expected_value = market.quantity_from_chain_format( + chain_quantity=Decimal(market.market_info["minQuantityTickSize"]) + ) + + self.assertEqual(expected_value, market.min_quantity_tick_size()) + + def test_get_oracle_info(self): + market = self._inj_usdt_derivative_market + + self.assertEqual(market.market_info["oracleBase"], market.oracle_base()) + self.assertEqual(market.market_info["oracleQuote"], market.oracle_quote()) + self.assertEqual(market.market_info["oracleType"], market.oracle_type()) + + def test_next_funding_timestamp(self): + market = self._inj_usdt_derivative_market + + self.assertEqual( + int(market.market_info["perpetualMarketInfo"]["nextFundingTimestamp"]), + market.next_funding_timestamp() + ) + + +class InjectiveTokenTests(TestCase): + + def test_convert_value_from_chain_format(self): + token = InjectiveToken( + denom="inj", + symbol="INJ", + unique_symbol="INJ", + name="Injective Protocol", + decimals=18, + ) + + converted_value = token.value_from_chain_format(chain_value=Decimal("100_000_000_000_000_000_000")) + + self.assertEqual(Decimal("100"), converted_value) diff --git a/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_api_order_book_data_source.py b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_api_order_book_data_source.py new file mode 100644 index 0000000..869648f --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_api_order_book_data_source.py @@ -0,0 +1,419 @@ +import asyncio +import re +from decimal import Decimal +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Awaitable, Optional, Union +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.injective_v2.injective_v2_api_order_book_data_source import ( + InjectiveV2APIOrderBookDataSource, +) +from hummingbot.connector.exchange.injective_v2.injective_v2_exchange import InjectiveV2Exchange +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveDelegatedAccountMode, + InjectiveTestnetNetworkMode, +) +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class InjectiveV2APIOrderBookDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}/{cls.quote_asset}" + cls.market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" # noqa: mock + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher.fetch_all") + def setUp(self, _) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + self.async_tasks = [] + asyncio.set_event_loop(self.async_loop) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + _, grantee_private_key = PrivateKey.generate() + _, granter_private_key = PrivateKey.generate() + + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=grantee_private_key.to_hex(), + subaccount_index=0, + granter_address=Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())).to_acc_bech32(), + granter_subaccount_index=0, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + self.connector = InjectiveV2Exchange( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + self.data_source = InjectiveV2APIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + data_source=self.connector._data_source, + ) + + self.initialize_trading_account_patch = patch( + "hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source" + ".InjectiveGranteeDataSource.initialize_trading_account" + ) + self.initialize_trading_account_patch.start() + + self.query_executor = ProgrammableQueryExecutor() + self.connector._data_source._query_executor = self.query_executor + + self.log_records = [] + self._logs_event: Optional[asyncio.Event] = None + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + self.data_source._data_source.logger().setLevel(1) + self.data_source._data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.market_id: self.trading_pair})) + + def tearDown(self) -> None: + self.async_run_with_timeout(self.data_source._data_source.stop()) + self.initialize_trading_account_patch.stop() + for task in self.async_tasks: + task.cancel() + self.async_loop.stop() + # self.async_loop.close() + # Since the event loop will change we need to remove the logs event created in the old event loop + self._logs_event = None + asyncio.set_event_loop(self._original_async_loop) + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.async_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def create_task(self, coroutine: Awaitable) -> asyncio.Task: + task = self.async_loop.create_task(coroutine) + self.async_tasks.append(task) + return task + + def handle(self, record): + self.log_records.append(record) + if self._logs_event is not None: + self._logs_event.set() + + def is_logged(self, log_level: str, message: Union[str, re.Pattern]) -> bool: + expression = ( + re.compile( + f"^{message}$" + .replace(".", r"\.") + .replace("?", r"\?") + .replace("/", r"\/") + .replace("(", r"\(") + .replace(")", r"\)") + .replace("[", r"\[") + .replace("]", r"\]") + ) + if isinstance(message, str) + else message + ) + return any( + record.levelname == log_level and expression.match(record.getMessage()) is not None + for record in self.log_records + ) + + def test_get_new_order_book_successful(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + base_decimals = spot_markets_response[0]["baseTokenMeta"]["decimals"] + quote_decimals = spot_markets_response[0]["quoteTokenMeta"]["decimals"] + + order_book_snapshot = { + "buys": [(Decimal("9487") * Decimal(f"1e{quote_decimals-base_decimals}"), + Decimal("336241") * Decimal(f"1e{base_decimals}"), + 1640001112223)], + "sells": [(Decimal("9487.5") * Decimal(f"1e{quote_decimals-base_decimals}"), + Decimal("522147") * Decimal(f"1e{base_decimals}"), + 1640001112224)], + "sequence": 512, + "timestamp": 1650001112223, + } + + self.query_executor._spot_order_book_responses.put_nowait(order_book_snapshot) + + order_book = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = order_book_snapshot["sequence"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + def test_listen_for_trades_logs_exception(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + self.query_executor._public_spot_trade_updates.put_nowait({}) + trade_data = { + "orderHash": "0x070e2eb3d361c8b26eae510f481bed513a1fb89c0869463a387cfa7995a27043", # noqa: mock + "subaccountId": "0x7998ca45575408f8b4fa354fe615abf3435cf1a7000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": "sell", + "price": { + "price": "0.000000000007701", + "quantity": "324600000000000000000", + "timestamp": "1687878089569" + }, + "fee": "-249974.46", + "executedAt": "1687878089569", + "feeRecipient": "inj10xvv532h2sy03d86x487v9dt7dp4eud8fe2qv5", # noqa: mock + "tradeId": "37120120_60_0", + "executionSide": "maker" + } + self.query_executor._public_spot_trade_updates.put_nowait(trade_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions(), timeout=2) + + msg_queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue( + self.is_logged( + "WARNING", re.compile(r"^Invalid public spot trade event format \(.*") + ) + ) + + def test_listen_for_trades_successful(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + base_decimals = spot_markets_response[0]["baseTokenMeta"]["decimals"] + quote_decimals = spot_markets_response[0]["quoteTokenMeta"]["decimals"] + + trade_data = { + "orderHash": "0x070e2eb3d361c8b26eae510f481bed513a1fb89c0869463a387cfa7995a27043", # noqa: mock + "subaccountId": "0x7998ca45575408f8b4fa354fe615abf3435cf1a7000000000000000000000000", # noqa: mock + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": "sell", + "price": { + "price": "0.000000000007701", + "quantity": "324600000000000000000", + "timestamp": "1687878089569" + }, + "fee": "-249974.46", + "executedAt": "1687878089569", + "feeRecipient": "inj10xvv532h2sy03d86x487v9dt7dp4eud8fe2qv5", # noqa: mock + "tradeId": "37120120_60_0", + "executionSide": "maker" + } + self.query_executor._public_spot_trade_updates.put_nowait(trade_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_data["tradeId"], msg.trade_id) + self.assertEqual(int(trade_data["executedAt"]) * 1e-3, msg.timestamp) + expected_price = Decimal(trade_data["price"]["price"]) * Decimal(f"1e{base_decimals-quote_decimals}") + expected_amount = Decimal(trade_data["price"]["quantity"]) * Decimal(f"1e{-base_decimals}") + self.assertEqual(expected_amount, msg.content["amount"]) + self.assertEqual(expected_price, msg.content["price"]) + self.assertEqual(self.trading_pair, msg.content["trading_pair"]) + self.assertEqual(float(TradeType.SELL.value), msg.content["trade_type"]) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + def test_listen_for_order_book_diffs_logs_exception(self): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + + self.query_executor._spot_order_book_updates.put_nowait({}) + order_book_data = { + "marketId": self.market_id, + "sequence": "7734169", + "buys": [ + { + "price": "0.000000000007684", + "quantity": "4578787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + }, + { + "price": "0.000000000007685", + "quantity": "4412340000000000000000", + "isActive": True, + "timestamp": "1687889316000" + } + ], + "sells": [ + { + "price": "0.000000000007723", + "quantity": "3478787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + } + ], + "updatedAt": "1687889315683", + } + self.query_executor._spot_order_book_updates.put_nowait(order_book_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions(), timeout=5) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue( + self.is_logged( + "WARNING", re.compile(r"^Invalid spot order book event format \(.*") + ) + ) + + @patch("hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source.InjectiveGranteeDataSource._initialize_timeout_height") + def test_listen_for_order_book_diffs_successful(self, _): + spot_markets_response = self._spot_markets_response() + self.query_executor._spot_markets_responses.put_nowait(spot_markets_response) + self.query_executor._derivative_markets_responses.put_nowait([]) + base_decimals = spot_markets_response[0]["baseTokenMeta"]["decimals"] + quote_decimals = spot_markets_response[0]["quoteTokenMeta"]["decimals"] + + order_book_data = { + "marketId": self.market_id, + "sequence": "7734169", + "buys": [ + { + "price": "0.000000000007684", + "quantity": "4578787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + }, + { + "price": "0.000000000007685", + "quantity": "4412340000000000000000", + "isActive": True, + "timestamp": "1687889316000" + } + ], + "sells": [ + { + "price": "0.000000000007723", + "quantity": "3478787000000000000000", + "isActive": True, + "timestamp": "1687889315683" + } + ], + "updatedAt": "1687889315683", + } + self.query_executor._spot_order_book_updates.put_nowait(order_book_data) + + self.async_run_with_timeout(self.data_source.listen_for_subscriptions()) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.create_task(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(order_book_data["updatedAt"]) * 1e-3, msg.timestamp) + expected_update_id = int(order_book_data["sequence"]) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + first_bid_price = Decimal(order_book_data["buys"][0]["price"]) * Decimal(f"1e{base_decimals-quote_decimals}") + first_bid_quantity = Decimal(order_book_data["buys"][0]["quantity"]) * Decimal(f"1e{-base_decimals}") + self.assertEqual(float(first_bid_price), bids[0].price) + self.assertEqual(float(first_bid_quantity), bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + first_ask_price = Decimal(order_book_data["sells"][0]["price"]) * Decimal(f"1e{base_decimals - quote_decimals}") + first_ask_quantity = Decimal(order_book_data["sells"][0]["quantity"]) * Decimal(f"1e{-base_decimals}") + self.assertEqual(float(first_ask_price), asks[0].price) + self.assertEqual(float(first_ask_quantity), asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def _spot_markets_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": self.ex_trading_pair, + "baseDenom": "inj", + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1687190809715" + }, + "quoteDenom": "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5", # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] diff --git a/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_delegated_account.py b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_delegated_account.py new file mode 100644 index 0000000..cdf5852 --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_delegated_account.py @@ -0,0 +1,2333 @@ +import asyncio +import base64 +import json +from collections import OrderedDict +from decimal import Decimal +from functools import partial +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from unittest.mock import AsyncMock, MagicMock + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict +from grpc import RpcError +from pyinjective.composer import Composer +from pyinjective.orderhash import OrderHashManager, OrderHashResponse +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.injective_v2.injective_v2_exchange import InjectiveV2Exchange +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveDelegatedAccountMode, + InjectiveTestnetNetworkMode, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_gather + + +class InjectiveV2ExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.base_asset_denom = "inj" + cls.quote_asset_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" # noqa: mock + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" # noqa: mock + + _, grantee_private_key = PrivateKey.generate() + cls.trading_account_private_key = grantee_private_key.to_hex() + cls.trading_account_subaccount_index = 0 + _, granter_private_key = PrivateKey.generate() + granter_address = Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())) + cls.portfolio_account_injective_address = granter_address.to_acc_bech32() + cls.portfolio_account_subaccount_index = 0 + portfolio_adderss = Address.from_acc_bech32(cls.portfolio_account_injective_address) + cls.portfolio_account_subaccount_id = portfolio_adderss.get_subaccount_id( + index=cls.portfolio_account_subaccount_index + ) + cls.base_decimals = 18 + cls.quote_decimals = 6 + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + self._logs_event: Optional[asyncio.Event] = None + self.exchange._data_source.logger().setLevel(1) + self.exchange._data_source.logger().addHandler(self) + + self.exchange._orders_processing_delta_time = 0.1 + self.async_tasks.append(self.async_loop.create_task(self.exchange._process_queued_orders())) + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + self._logs_event = None + + def handle(self, record): + super().handle(record=record) + if self._logs_event is not None: + self._logs_event.set() + + def reset_log_event(self): + if self._logs_event is not None: + self._logs_event.clear() + + async def wait_for_a_log(self): + if self._logs_event is not None: + await self._logs_event.wait() + + @property + def all_symbols_url(self): + raise NotImplementedError + + @property + def latest_prices_url(self): + raise NotImplementedError + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + raise NotImplementedError + + @property + def order_creation_url(self): + raise NotImplementedError + + @property + def balance_url(self): + raise NotImplementedError + + @property + def all_symbols_request_mock_response(self): + raise NotImplementedError + + @property + def latest_prices_request_mock_response(self): + return { + "trades": [ + { + "orderHash": "0x9ffe4301b24785f09cb529c1b5748198098b17bd6df8fe2744d923a574179229", # noqa: mock + "subaccountId": "0xa73ad39eab064051fb468a5965ee48ca87ab66d4000000000000000000000000", # noqa: mock + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": "sell", + "price": { + "price": str(Decimal(str(self.expected_latest_price)) * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": "142000000000000000000", + "timestamp": "1688734042063" + }, + "fee": "-112393", + "executedAt": "1688734042063", + "feeRecipient": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock + "tradeId": "13374245_801_0", + "executionSide": "maker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = self.all_markets_mock_response + response.append({ + "marketId": "invalid_market_id", + "marketStatus": "active", + "ticker": "INVALID/MARKET", + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }) + + return ("INVALID_MARKET", response) + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + raise NotImplementedError + + @property + def trading_rules_request_erroneous_mock_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + }] + + @property + def order_creation_request_successful_mock_response(self): + return {"txhash": "017C130E3602A48E5C9D661CAC657BF1B79262D4B71D5C25B1DA62DE2338DA0E", "rawLog": "[]"} # noqa: mock + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "accountAddress": self.portfolio_account_injective_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + { + "denom": self.quote_asset_denom, + "amount": str(Decimal(1000) * Decimal(1e6)) + } + ], + "subaccounts": [ + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.quote_asset_denom, + "deposit": { + "totalBalance": str(Decimal(1000) * Decimal(1e6)), + "availableBalance": str(Decimal(1000) * Decimal(1e6)) + } + }, + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(10) * Decimal(1e18)), + "availableBalance": str(Decimal(5) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "accountAddress": self.portfolio_account_injective_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + ], + "subaccounts": [ + { + "subaccountId": self.portfolio_account_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(10) * Decimal(1e18)), + "availableBalance": str(Decimal(5) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_event_websocket_update(self): + return { + "balance": { + "subaccountId": self.portfolio_account_subaccount_id, + "accountAddress": self.portfolio_account_injective_address, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)), + } + }, + "timestamp": "1688659208000" + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + market_info = self.all_markets_mock_response[0] + min_price_tick_size = (Decimal(market_info["minPriceTickSize"]) + * Decimal(f"1e{market_info['baseTokenMeta']['decimals']-market_info['quoteTokenMeta']['decimals']}")) + min_quantity_tick_size = Decimal(market_info["minQuantityTickSize"]) * Decimal( + f"1e{-market_info['baseTokenMeta']['decimals']}") + trading_rule = TradingRule( + trading_pair=self.trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + + return trading_rule + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "0x3870fbdd91f07d54425147b1bb96404f4f043ba6335b422a6d494d285b387f00" # noqa: mock + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "10414162_22_33" + + @property + def all_markets_mock_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return self.market_id + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + granter_address=self.portfolio_account_injective_address, + granter_subaccount_index=self.portfolio_account_subaccount_index, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange = InjectiveV2Exchange( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange._data_source._query_executor = ProgrammableQueryExecutor() + exchange._data_source._spot_market_and_trading_pair_map = bidict({self.market_id: self.trading_pair}) + exchange._data_source._derivative_market_and_trading_pair_map = bidict() + + exchange._data_source._composer = Composer(network=exchange._data_source.network_name) + + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def configure_all_symbols_response( + self, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + all_markets_mock_response = self.all_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + return "" + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.configure_all_symbols_response(mock_api=mock_api, callback=callback) + return "" + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + response = self.trading_rules_request_erroneous_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + return "" + + def configure_successful_cancelation_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_erroneous_cancelation_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_erroneous_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + raise NotImplementedError + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses + ) -> List[str]: + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait({"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_canceled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_open_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_open_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_http_error_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for historical orders responses") + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return None + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return None + + def configure_order_not_found_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_not_found_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_partial_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return None + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return None + + def configure_full_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return [] + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1687878089569" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1687878089569", + "feeRecipient": self.portfolio_account_injective_address, # noqa: mock + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + } + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._spot_market_and_trading_pair_map = None + queue_mock = AsyncMock() + queue_mock.get.side_effect = Exception("Test error") + self.exchange._data_source._query_executor._spot_markets_responses = queue_mock + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs(), timeout=10) + + self.assertEqual(0, len(result)) + + def test_batch_order_create(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1", "hash2"], derivative=[] + ) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("11"), + quantity=Decimal("3"), + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="hash1", + creation_transaction_hash=response["txhash"] + ) + sell_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[1].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=orders[1].price, + amount=orders[1].quantity, + exchange_order_id="hash2", + creation_transaction_hash=response["txhash"] + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + def test_batch_order_create_with_one_market_order(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1", "hash2"], derivative=[] + ) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[OrderBookRow(price=5000, amount=20, update_id=1)], + asks=[], + update_id=1, + ) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = MarketOrder( + order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_asset=self.base_asset, + quote_asset=self.quote_asset, + amount=3, + timestamp=self.exchange.current_timestamp, + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=True, + volume=Decimal(str(sell_order_to_create.amount)), + ).result_price + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="hash1", + creation_transaction_hash=response["txhash"] + ) + sell_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[1].order_id, + trading_pair=self.trading_pair, + order_type=OrderType.MARKET, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=expected_price_for_volume, + amount=orders[1].quantity, + exchange_order_id="hash2", + creation_transaction_hash=response["txhash"] + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_buy_market_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[], + asks=[OrderBookRow(price=5000, amount=20, update_id=1)], + update_id=1, + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_amount = Decimal(1) + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=True, + volume=order_amount + ).result_price + + order_id = self.place_buy_order(amount=order_amount, price=None, order_type=OrderType.MARKET) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + self.assertEqual(expected_price_for_volume, order.price) + + @aioresponses() + def test_create_sell_market_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + order_book = OrderBook() + self.exchange.order_book_tracker._order_books[self.trading_pair] = order_book + order_book.apply_snapshot( + bids=[OrderBookRow(price=5000, amount=20, update_id=1)], + asks=[], + update_id=1, + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_amount = Decimal(1) + expected_price_for_volume = self.exchange.get_price_for_volume( + trading_pair=self.trading_pair, + is_buy=False, + volume=order_amount + ).result_price + + order_id = self.place_sell_order(amount=order_amount, price=None, order_type=OrderType.MARKET) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual("hash1", order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + self.assertEqual(expected_price_for_volume, order.price) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + self.exchange._data_source._order_hash_manager = MagicMock(spec=OrderHashManager) + self.exchange._data_source._order_hash_manager.compute_order_hashes.return_value = OrderHashResponse( + spot=["hash1"], derivative=[] + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order size 0.01. The order will not be created, " + "increase the amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + def test_batch_order_cancel(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id=self.expected_exchange_order_id + "2", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("110"), + order_type=OrderType.LIMIT, + ) + + buy_order_to_cancel: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + sell_order_to_cancel: GatewayInFlightOrder = self.exchange.in_flight_orders["12"] + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait(transaction_simulation_response) + + response = self._order_cancelation_request_successful_mock_response(order=buy_order_to_cancel) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + self.exchange.batch_order_cancel(orders_to_cancel=orders_to_cancel) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], buy_order_to_cancel.cancel_tx_hash) + self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], sell_order_to_cancel.cancel_tx_hash) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + def test_order_not_found_in_its_creating_transaction_marked_as_failed_during_order_creation_check(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="0x9f94598b4842ab66037eaa7c64ec10ae16dcf196e61db8522921628522c0f62e", # noqa: mock + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: GatewayInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + transaction_data = (b'\x12\xd1\x01\n8/injective.exchange.v1beta1.MsgBatchUpdateOrdersResponse' + b'\x12\x94\x01\n\x02\x00\x00\x12\x02\x00\x00\x1aB' + b'0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1' # noqa: mock + b'\x1aB' + b'0x115975551b4f86188eee6b93d789fcc78df6e89e40011b929299b6e142f53515' # noqa: mock + b'"\x00"\x00') + transaction_messages = [ + { + "type": "/cosmos.authz.v1beta1.MsgExec", + "value": { + "grantee": PrivateKey.from_hex(self.trading_account_private_key).to_public_key().to_acc_bech32(), + "msgs": [ + { + "@type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "sender": self.portfolio_account_injective_address, + "subaccount_id": "", + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "spot_orders_to_create": [ + { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str((order.amount + Decimal(1)) * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": order.trade_type.name, + "trigger_price": "0.000000000000000000" + } + ], + "derivative_orders_to_create": [], + "binary_options_orders_to_cancel": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_create": [] + } + ] + } + } + ] + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "13302254", + "blockTimestamp": "2023-07-05 13:55:09.94 +0000 UTC", + "hash": "0x66a360da2fd6884b53b5c019f1a2b5bed7c7c8fc07e83a9c36ad3362ede096ae", # noqa: mock + "data": base64.b64encode(transaction_data).decode(), + "gasWanted": "168306", + "gasUsed": "167769", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "84153000000000" + } + ], + "gasLimit": "168306", + "payer": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" # noqa: mock + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(transaction_messages).encode()).decode(), + "signatures": [ + { + "pubkey": "035ddc4d5642b9383e2f087b2ee88b7207f6286ebc9f310e9df1406eccc2c31813", # noqa: mock + "address": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "sequence": "16450", + "signature": "S9atCwiVg9+8vTpbciuwErh54pJOAry3wHvbHT2fG8IumoE+7vfuoP7mAGDy2w9am+HHa1yv60VSWo3cRhWC9g==" + } + ], + "txNumber": "13182", + "blockUnixTimestamp": "1688565309940", + "logs": "W3sibXNnX2luZGV4IjowLCJldmVudHMiOlt7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5IjoiYWN0aW9uIiwidmFsdWUiOiIvaW5qZWN0aXZlLmV4Y2hhbmdlLnYxYmV0YTEuTXNnQmF0Y2hVcGRhdGVPcmRlcnMifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJtb2R1bGUiLCJ2YWx1ZSI6ImV4Y2hhbmdlIn1dfSx7InR5cGUiOiJjb2luX3NwZW50IiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic3BlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjE2NTE2NTAwMHBlZ2d5MHg4N2FCM0I0Qzg2NjFlMDdENjM3MjM2MTIxMUI5NmVkNERjMzZCMUI1In1dfSx7InR5cGUiOiJjb2luX3JlY2VpdmVkIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjZWl2ZXIiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxNjUxNjUwMDBwZWdneTB4ODdhQjNCNEM4NjYxZTA3RDYzNzIzNjEyMTFCOTZlZDREYzM2QjFCNSJ9XX0seyJ0eXBlIjoidHJhbnNmZXIiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNpcGllbnQiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMTY1MTY1MDAwcGVnZ3kweDg3YUIzQjRDODY2MWUwN0Q2MzcyMzYxMjExQjk2ZWQ0RGMzNkIxQjUifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiI1NTAwMDAwMDAwMDAwMDAwMDAwMGluaiJ9XX0seyJ0eXBlIjoiY29pbl9yZWNlaXZlZCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2VpdmVyIiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiNTUwMDAwMDAwMDAwMDAwMDAwMDBpbmoifV19LHsidHlwZSI6InRyYW5zZmVyIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjaXBpZW50IiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjU1MDAwMDAwMDAwMDAwMDAwMDAwaW5qIn1dfSx7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifV19XX1d" # noqa: mock + } + } + self.exchange._data_source._query_executor._transaction_by_hash_responses.put_nowait(transaction_response) + + original_order_hash_manager = self.exchange._data_source.order_hash_manager + + self.async_run_with_timeout(self.exchange._check_orders_creation_transactions()) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order.client_order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order.client_order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + self.assertNotEqual(original_order_hash_manager, self.exchange._data_source._order_hash_manager) + + def test_order_creation_check_waits_for_originating_transaction_to_be_mined(self): + request_sent_event = asyncio.Event() + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id="hash1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "2", + exchange_order_id="hash2", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("20000"), + amount=Decimal("200"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + + hash_not_matching_order: GatewayInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + hash_not_matching_order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + no_mined_tx_order: GatewayInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "2"] + no_mined_tx_order.update_creation_transaction_hash( + creation_transaction_hash="HHHHHHHHHHHHHHH") + + transaction_data = (b'\x12\xd1\x01\n8/injective.exchange.v1beta1.MsgBatchUpdateOrdersResponse' + b'\x12\x94\x01\n\x02\x00\x00\x12\x02\x00\x00\x1aB' + b'0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1' # noqa: mock + b'\x1aB' + b'0x115975551b4f86188eee6b93d789fcc78df6e89e40011b929299b6e142f53515' # noqa: mock + b'"\x00"\x00') + transaction_messages = [ + { + "type": "/cosmos.authz.v1beta1.MsgExec", + "value": { + "grantee": PrivateKey.from_hex(self.trading_account_private_key).to_public_key().to_acc_bech32(), + "msgs": [ + { + "@type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "sender": self.portfolio_account_injective_address, + "subaccount_id": "", + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "spot_orders_to_create": [ + { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str( + hash_not_matching_order.price * Decimal( + f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str( + hash_not_matching_order.amount * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": hash_not_matching_order.trade_type.name, + "trigger_price": "0.000000000000000000" + } + ], + "derivative_orders_to_create": [], + "binary_options_orders_to_cancel": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_create": [] + } + ] + } + } + ] + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "13302254", + "blockTimestamp": "2023-07-05 13:55:09.94 +0000 UTC", + "hash": "0x66a360da2fd6884b53b5c019f1a2b5bed7c7c8fc07e83a9c36ad3362ede096ae", # noqa: mock + "data": base64.b64encode(transaction_data).decode(), + "gasWanted": "168306", + "gasUsed": "167769", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "84153000000000" + } + ], + "gasLimit": "168306", + "payer": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" # noqa: mock + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(transaction_messages).encode()).decode(), + "signatures": [ + { + "pubkey": "035ddc4d5642b9383e2f087b2ee88b7207f6286ebc9f310e9df1406eccc2c31813", # noqa: mock + "address": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "sequence": "16450", + "signature": "S9atCwiVg9+8vTpbciuwErh54pJOAry3wHvbHT2fG8IumoE+7vfuoP7mAGDy2w9am+HHa1yv60VSWo3cRhWC9g==" + } + ], + "txNumber": "13182", + "blockUnixTimestamp": "1688565309940", + "logs": "W3sibXNnX2luZGV4IjowLCJldmVudHMiOlt7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5IjoiYWN0aW9uIiwidmFsdWUiOiIvaW5qZWN0aXZlLmV4Y2hhbmdlLnYxYmV0YTEuTXNnQmF0Y2hVcGRhdGVPcmRlcnMifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJtb2R1bGUiLCJ2YWx1ZSI6ImV4Y2hhbmdlIn1dfSx7InR5cGUiOiJjb2luX3NwZW50IiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic3BlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjE2NTE2NTAwMHBlZ2d5MHg4N2FCM0I0Qzg2NjFlMDdENjM3MjM2MTIxMUI5NmVkNERjMzZCMUI1In1dfSx7InR5cGUiOiJjb2luX3JlY2VpdmVkIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjZWl2ZXIiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxNjUxNjUwMDBwZWdneTB4ODdhQjNCNEM4NjYxZTA3RDYzNzIzNjEyMTFCOTZlZDREYzM2QjFCNSJ9XX0seyJ0eXBlIjoidHJhbnNmZXIiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNpcGllbnQiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMTY1MTY1MDAwcGVnZ3kweDg3YUIzQjRDODY2MWUwN0Q2MzcyMzYxMjExQjk2ZWQ0RGMzNkIxQjUifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiI1NTAwMDAwMDAwMDAwMDAwMDAwMGluaiJ9XX0seyJ0eXBlIjoiY29pbl9yZWNlaXZlZCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2VpdmVyIiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiNTUwMDAwMDAwMDAwMDAwMDAwMDBpbmoifV19LHsidHlwZSI6InRyYW5zZmVyIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjaXBpZW50IiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjU1MDAwMDAwMDAwMDAwMDAwMDAwaW5qIn1dfSx7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifV19XX1d" # noqa: mock + } + } + mock_tx_by_hash_queue = AsyncMock() + mock_tx_by_hash_queue.get.side_effect = [transaction_response, ValueError("Transaction not found in a block")] + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_tx_by_hash_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=13302254 + ) + self.exchange._data_source._query_executor._transaction_block_height_responses = mock_queue + + original_order_hash_manager = self.exchange._data_source.order_hash_manager + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._check_orders_creation_transactions() + ) + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotEqual(original_order_hash_manager, self.exchange._data_source._order_hash_manager) + + mock_queue.get.assert_called() + + def test_order_creating_transactions_identify_correctly_market_orders(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=None, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "2", + exchange_order_id=None, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("4500"), + amount=Decimal("20"), + order_type=OrderType.MARKET, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + limit_order: GatewayInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + market_order: GatewayInFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "2"] + limit_order.update_creation_transaction_hash(creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + market_order.update_creation_transaction_hash( + creation_transaction_hash="66A360DA2FD6884B53B5C019F1A2B5BED7C7C8FC07E83A9C36AD3362EDE096AE") # noqa: mock + + expected_hash_1 = "0xc5d66f56942e1ae407c01eedccd0471deb8e202a514cde3bae56a8307e376cd1" # noqa: mock + expected_hash_2 = "0x115975551b4f86188eee6b93d789fcc78df6e89e40011b929299b6e142f53515" # noqa: mock + + transaction_data = ('\x12\xd1\x01\n8/injective.exchange.v1beta1.MsgBatchUpdateOrdersResponse' + '\x12\x94\x01\n\x02\x00\x00\x12\x02\x00\x00\x1aB' + f'{expected_hash_1}' + '\x1aB' + f'{expected_hash_2}' + f'"\x00"\x00').encode() + transaction_messages = [ + { + "type": "/cosmos.authz.v1beta1.MsgExec", + "value": { + "grantee": PrivateKey.from_hex(self.trading_account_private_key).to_public_key().to_acc_bech32(), + "msgs": [ + { + "@type": "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder", + "sender": self.portfolio_account_injective_address, + "order": { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str( + market_order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(market_order.amount * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": "BUY", + "trigger_price": "0.000000000000000000" + } + }, + { + "@type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "sender": self.portfolio_account_injective_address, + "subaccount_id": "", + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "spot_orders_to_create": [ + { + "market_id": self.market_id, + "order_info": { + "subaccount_id": self.portfolio_account_subaccount_id, + "fee_recipient": self.portfolio_account_injective_address, + "price": str(limit_order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(limit_order.amount * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": limit_order.trade_type.name, + "trigger_price": "0.000000000000000000" + } + ], + "derivative_orders_to_create": [], + "binary_options_orders_to_cancel": [], + "binary_options_market_ids_to_cancel_all": [], + "binary_options_orders_to_create": [] + } + ] + } + } + ] + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "13302254", + "blockTimestamp": "2023-07-05 13:55:09.94 +0000 UTC", + "hash": "0x66a360da2fd6884b53b5c019f1a2b5bed7c7c8fc07e83a9c36ad3362ede096ae", # noqa: mock + "data": base64.b64encode(transaction_data).decode(), + "gasWanted": "168306", + "gasUsed": "167769", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "84153000000000" + } + ], + "gasLimit": "168306", + "payer": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r" # noqa: mock + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(transaction_messages).encode()).decode(), + "signatures": [ + { + "pubkey": "035ddc4d5642b9383e2f087b2ee88b7207f6286ebc9f310e9df1406eccc2c31813", # noqa: mock + "address": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "sequence": "16450", + "signature": "S9atCwiVg9+8vTpbciuwErh54pJOAry3wHvbHT2fG8IumoE+7vfuoP7mAGDy2w9am+HHa1yv60VSWo3cRhWC9g==" + } + ], + "txNumber": "13182", + "blockUnixTimestamp": "1688565309940", + "logs": "W3sibXNnX2luZGV4IjowLCJldmVudHMiOlt7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5IjoiYWN0aW9uIiwidmFsdWUiOiIvaW5qZWN0aXZlLmV4Y2hhbmdlLnYxYmV0YTEuTXNnQmF0Y2hVcGRhdGVPcmRlcnMifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJtb2R1bGUiLCJ2YWx1ZSI6ImV4Y2hhbmdlIn1dfSx7InR5cGUiOiJjb2luX3NwZW50IiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic3BlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjE2NTE2NTAwMHBlZ2d5MHg4N2FCM0I0Qzg2NjFlMDdENjM3MjM2MTIxMUI5NmVkNERjMzZCMUI1In1dfSx7InR5cGUiOiJjb2luX3JlY2VpdmVkIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjZWl2ZXIiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxNjUxNjUwMDBwZWdneTB4ODdhQjNCNEM4NjYxZTA3RDYzNzIzNjEyMTFCOTZlZDREYzM2QjFCNSJ9XX0seyJ0eXBlIjoidHJhbnNmZXIiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNpcGllbnQiLCJ2YWx1ZSI6ImluajE0dm5tdzJ3ZWUzeHRyc3FmdnBjcWczNWpnOXY3ajJ2ZHB6eDBrayJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMTY1MTY1MDAwcGVnZ3kweDg3YUIzQjRDODY2MWUwN0Q2MzcyMzYxMjExQjk2ZWQ0RGMzNkIxQjUifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6ImluajFoa2hkYWoyYTJjbG1xNWpxNm1zcHNnZ3FzMzJ2eW5wazIyOHEzciJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiI1NTAwMDAwMDAwMDAwMDAwMDAwMGluaiJ9XX0seyJ0eXBlIjoiY29pbl9yZWNlaXZlZCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2VpdmVyIiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiNTUwMDAwMDAwMDAwMDAwMDAwMDBpbmoifV19LHsidHlwZSI6InRyYW5zZmVyIiwiYXR0cmlidXRlcyI6W3sia2V5IjoicmVjaXBpZW50IiwidmFsdWUiOiJpbmoxNHZubXcyd2VlM3h0cnNxZnZwY3FnMzVqZzl2N2oydmRwengwa2sifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoiaW5qMWhraGRhajJhMmNsbXE1anE2bXNwc2dncXMzMnZ5bnBrMjI4cTNyIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjU1MDAwMDAwMDAwMDAwMDAwMDAwaW5qIn1dfSx7InR5cGUiOiJtZXNzYWdlIiwiYXR0cmlidXRlcyI6W3sia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJpbmoxaGtoZGFqMmEyY2xtcTVqcTZtc3BzZ2dxczMydnlucGsyMjhxM3IifV19XX1d" # noqa: mock + } + } + self.exchange._data_source._query_executor._transaction_by_hash_responses.put_nowait(transaction_response) + + self.async_run_with_timeout(self.exchange._check_orders_creation_transactions()) + + self.assertEquals(2, len(self.buy_order_created_logger.event_log)) + self.assertEquals(0, len(self.order_failure_logger.event_log)) + + self.assertEquals(expected_hash_1, market_order.exchange_order_id) + self.assertEquals(expected_hash_2, limit_order.exchange_order_id) + + def test_user_stream_balance_update(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveDelegatedAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + granter_address=self.portfolio_account_injective_address, + granter_subaccount_index=1, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange_with_non_default_subaccount = InjectiveV2Exchange( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange_with_non_default_subaccount._data_source._query_executor = self.exchange._data_source._query_executor + self.exchange = exchange_with_non_default_subaccount + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_balance_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_account_balance_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_spot_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_spot_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_spot_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_logs_errors(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_user_stream_raises_cancel_exception(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_spot_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_spot_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_spot_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_check_network_success(self, mock_api): + response = self.network_status_request_successful_mock_response + self.exchange._data_source._query_executor._ping_responses.put_nowait(response) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=10) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = RpcError("Test Error") + self.exchange._data_source._query_executor._ping_responses = mock_queue + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.exchange._data_source._query_executor._ping_responses = mock_queue + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + self.configure_all_symbols_response(mock_api=mock_api) + response = self.latest_prices_request_mock_response + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait(response) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_get_fee(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + maker_fee_rate = Decimal(self.all_markets_mock_response[0]["makerFeeRate"]) + taker_fee_rate = Decimal(self.all_markets_mock_response[0]["takerFeeRate"]) + + maker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=True + ) + + self.assertEqual(maker_fee_rate, maker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + taker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=False, + ) + + self.assertEqual(taker_fee_rate, taker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "2", + exchange_order_id=self.exchange_order_id_prefix + "2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "3", + exchange_order_id=self.exchange_order_id_prefix + "3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "4", + exchange_order_id=self.exchange_order_id_prefix + "4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "3", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "4", self.exchange.in_flight_orders) + + def _expected_initial_status_dict(self) -> Dict[str, bool]: + status_dict = super()._expected_initial_status_dict() + status_dict["data_source_initialized"] = False + return status_dict + + @staticmethod + def _callback_wrapper_with_response(callback: Callable, response: Any, *args, **kwargs): + callback(args, kwargs) + if isinstance(response, Exception): + raise response + else: + return response + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + all_markets_mock_response = self.all_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + self.exchange._data_source._query_executor._account_portfolio_responses.put_nowait(response) + return "" + + def _msg_exec_simulation_mock_response(self) -> Any: + return { + "gasInfo": { + "gasWanted": "50000000", + "gasUsed": "90749" + }, + "result": { + "data": "Em8KJS9jb3Ntb3MuYXV0aHoudjFiZXRhMS5Nc2dFeGVjUmVzcG9uc2USRgpECkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA=", + "log": "", + "events": [], + "msgResponses": [ + OrderedDict([ + ("@type", "/cosmos.authz.v1beta1.MsgExecResponse"), + ("results", [ + "CkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA="]) + ]) + ] + } + } + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "[]"} # noqa: mock + + def _order_cancelation_request_erroneous_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "Error"} # noqa: mock + + def _order_status_request_open_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_partially_filled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(self.expected_partial_fill_amount * Decimal(f"1e{self.base_decimals}")), + "state": "partial_filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_completely_filled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_canceled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.portfolio_account_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_not_found_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [], + "paging": { + "total": "0" + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(self.expected_partial_fill_price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(self.expected_partial_fill_amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1681735786785" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.portfolio_account_injective_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _order_fills_request_full_fill_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.portfolio_account_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1681735786785" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.portfolio_account_injective_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } diff --git a/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_offchain_vault.py b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_offchain_vault.py new file mode 100644 index 0000000..1fc5422 --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_exchange_for_offchain_vault.py @@ -0,0 +1,1889 @@ +import asyncio +import base64 +import json +from collections import OrderedDict +from decimal import Decimal +from functools import partial +from test.hummingbot.connector.exchange.injective_v2.programmable_query_executor import ProgrammableQueryExecutor +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from unittest.mock import AsyncMock + +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict +from grpc import RpcError +from pyinjective.composer import Composer +from pyinjective.wallet import Address, PrivateKey + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.injective_v2.injective_v2_exchange import InjectiveV2Exchange +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveTestnetNetworkMode, + InjectiveVaultAccountMode, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_gather + + +class InjectiveV2ExchangeForOffChainVaultTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "INJ" + cls.quote_asset = "USDT" + cls.base_asset_denom = "inj" + cls.quote_asset_denom = "peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5" # noqa: mock + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe" # noqa: mock + + _, grantee_private_key = PrivateKey.generate() + cls.trading_account_private_key = grantee_private_key.to_hex() + cls.trading_account_public_key = grantee_private_key.to_public_key().to_address().to_acc_bech32() + cls.trading_account_subaccount_index = 0 + cls.vault_contract_address = "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp" # noqa: mock" + cls.vault_contract_subaccount_index = 1 + vault_address = Address.from_acc_bech32(cls.vault_contract_address) + cls.vault_contract_subaccount_id = vault_address.get_subaccount_id( + index=cls.vault_contract_subaccount_index + ) + cls.base_decimals = 18 + cls.quote_decimals = 6 + + cls._transaction_hash = "017C130E3602A48E5C9D661CAC657BF1B79262D4B71D5C25B1DA62DE2338DA0E" # noqa: mock" + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + self._logs_event: Optional[asyncio.Event] = None + self.exchange._data_source.logger().setLevel(1) + self.exchange._data_source.logger().addHandler(self) + + self.exchange._orders_processing_delta_time = 0.1 + self.async_tasks.append(self.async_loop.create_task(self.exchange._process_queued_orders())) + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + self._logs_event = None + + def handle(self, record): + super().handle(record=record) + if self._logs_event is not None: + self._logs_event.set() + + def reset_log_event(self): + if self._logs_event is not None: + self._logs_event.clear() + + async def wait_for_a_log(self): + if self._logs_event is not None: + await self._logs_event.wait() + + @property + def all_symbols_url(self): + raise NotImplementedError + + @property + def latest_prices_url(self): + raise NotImplementedError + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + raise NotImplementedError + + @property + def order_creation_url(self): + raise NotImplementedError + + @property + def balance_url(self): + raise NotImplementedError + + @property + def all_symbols_request_mock_response(self): + raise NotImplementedError + + @property + def latest_prices_request_mock_response(self): + return { + "trades": [ + { + "orderHash": "0x9ffe4301b24785f09cb529c1b5748198098b17bd6df8fe2744d923a574179229", # noqa: mock + "subaccountId": "0xa73ad39eab064051fb468a5965ee48ca87ab66d4000000000000000000000000", # noqa: mock + "marketId": "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe", # noqa: mock + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": "sell", + "price": { + "price": str(Decimal(str(self.expected_latest_price)) * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": "142000000000000000000", + "timestamp": "1688734042063" + }, + "fee": "-112393", + "executedAt": "1688734042063", + "feeRecipient": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock + "tradeId": "13374245_801_0", + "executionSide": "maker" + } + ], + "paging": { + "total": "1000", + "from": 1, + "to": 1 + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = self.all_markets_mock_response + response.append({ + "marketId": "invalid_market_id", + "marketStatus": "active", + "ticker": "INVALID/MARKET", + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }) + + return ("INVALID_MARKET", response) + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + raise NotImplementedError + + @property + def trading_rules_request_erroneous_mock_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": 18, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": 6, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + }] + + @property + def order_creation_request_successful_mock_response(self): + return {"txhash": self._transaction_hash, "rawLog": "[]"} # noqa: mock + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "accountAddress": self.vault_contract_address, + "bankBalances": [ + { + "denom": self.base_asset_denom, + "amount": str(Decimal(5) * Decimal(1e18)) + }, + { + "denom": self.quote_asset_denom, + "amount": str(Decimal(1000) * Decimal(1e6)) + } + ], + "subaccounts": [ + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.quote_asset_denom, + "deposit": { + "totalBalance": str(Decimal(2000) * Decimal(1e6)), + "availableBalance": str(Decimal(2000) * Decimal(1e6)) + } + }, + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "accountAddress": self.vault_contract_address, + "bankBalances": [], + "subaccounts": [ + { + "subaccountId": self.vault_contract_subaccount_id, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)) + } + }, + ] + } + + @property + def balance_event_websocket_update(self): + return { + "balance": { + "subaccountId": self.vault_contract_subaccount_id, + "accountAddress": self.vault_contract_address, + "denom": self.base_asset_denom, + "deposit": { + "totalBalance": str(Decimal(15) * Decimal(1e18)), + "availableBalance": str(Decimal(10) * Decimal(1e18)), + } + }, + "timestamp": "1688659208000" + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + market_info = self.all_markets_mock_response[0] + min_price_tick_size = (Decimal(market_info["minPriceTickSize"]) + * Decimal(f"1e{market_info['baseTokenMeta']['decimals']-market_info['quoteTokenMeta']['decimals']}")) + min_quantity_tick_size = Decimal(market_info["minQuantityTickSize"]) * Decimal( + f"1e{-market_info['baseTokenMeta']['decimals']}") + trading_rule = TradingRule( + trading_pair=self.trading_pair, + min_order_size=min_quantity_tick_size, + min_price_increment=min_price_tick_size, + min_base_amount_increment=min_quantity_tick_size, + min_quote_amount_increment=min_price_tick_size, + ) + + return trading_rule + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response[0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "0x3870fbdd91f07d54425147b1bb96404f4f043ba6335b422a6d494d285b387f00" # noqa: mock + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + raise NotImplementedError + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("100") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("10") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return "10414162_22_33" + + @property + def all_markets_mock_response(self): + return [{ + "marketId": self.market_id, + "marketStatus": "active", + "ticker": f"{self.base_asset}/{self.quote_asset}", + "baseDenom": self.base_asset_denom, + "baseTokenMeta": { + "name": "Base Asset", + "address": "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + "symbol": self.base_asset, + "logo": "https://static.alchemyapi.io/images/assets/7226.png", + "decimals": self.base_decimals, + "updatedAt": "1687190809715" + }, + "quoteDenom": self.quote_asset_denom, # noqa: mock + "quoteTokenMeta": { + "name": "Quote Asset", + "address": "0x0000000000000000000000000000000000000000", # noqa: mock + "symbol": self.quote_asset, + "logo": "https://static.alchemyapi.io/images/assets/825.png", + "decimals": self.quote_decimals, + "updatedAt": "1687190809716" + }, + "makerFeeRate": "-0.0001", + "takerFeeRate": "0.001", + "serviceProviderFee": "0.4", + "minPriceTickSize": "0.000000000000001", + "minQuantityTickSize": "1000000000000000" + }] + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return self.market_id + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + account_config = InjectiveVaultAccountMode( + private_key=self.trading_account_private_key, + subaccount_index=self.trading_account_subaccount_index, + vault_contract_address=self.vault_contract_address, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + exchange = InjectiveV2Exchange( + client_config_map=client_config_map, + connector_configuration=injective_config, + trading_pairs=[self.trading_pair], + ) + + exchange._data_source._query_executor = ProgrammableQueryExecutor() + exchange._data_source._spot_market_and_trading_pair_map = bidict({self.market_id: self.trading_pair}) + exchange._data_source._derivative_market_and_trading_pair_map = bidict() + + exchange._data_source._composer = Composer(network=exchange._data_source.network_name) + + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def configure_all_symbols_response( + self, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + all_markets_mock_response = self.all_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + return "" + + def configure_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + self.configure_all_symbols_response(mock_api=mock_api, callback=callback) + return "" + + def configure_erroneous_trading_rules_response( + self, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + + response = self.trading_rules_request_erroneous_mock_response + self.exchange._data_source._query_executor._spot_markets_responses = asyncio.Queue() + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + return "" + + def configure_successful_cancelation_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_erroneous_cancelation_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + response = self._order_cancelation_request_erroneous_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + return "" + + def configure_order_not_found_error_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + raise NotImplementedError + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses + ) -> List[str]: + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> Union[str, List[str]]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait({"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_canceled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_open_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait( + {"trades": [], "paging": {"total": "0"}}) + + response = self._order_status_request_open_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_http_error_order_status_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for historical orders responses") + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return None + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return None + + def configure_order_not_found_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_all_symbols_response(mock_api=mock_api) + response = self._order_status_request_not_found_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._historical_spot_orders_responses = mock_queue + return [] + + def configure_partial_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return None + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test error for trades responses") + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return None + + def configure_full_fill_trade_response(self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._spot_trades_responses = mock_queue + return [] + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock" + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688667498756", + "updatedAt": "1688667498756", + "direction": order.trade_type.name.lower(), + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000000" # noqa: mock + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitMatchRestingOrder", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1687878089569" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1687878089569", + "feeRecipient": self.vault_contract_address, # noqa: mock + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + } + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._spot_market_and_trading_pair_map = None + queue_mock = AsyncMock() + queue_mock.get.side_effect = Exception("Test error") + self.exchange._data_source._query_executor._spot_markets_responses = queue_mock + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs(), timeout=10) + + self.assertEqual(0, len(result)) + + def test_batch_order_create(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + # Configure all symbols response to initialize the trading rules + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_rules()) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("11"), + quantity=Decimal("3"), + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1640780000, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944", # noqa: mock" + creation_transaction_hash=response["txhash"] + ) + sell_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[1].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=1640780000, + price=orders[1].price, + amount=orders[1].quantity, + exchange_order_id="0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e945", # noqa: mock" + creation_transaction_hash=response["txhash"] + ) + + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + + expected_order_hashes = [ + buy_order_to_create_in_flight.exchange_order_id, + sell_order_to_create_in_flight.exchange_order_id, + ] + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response( + orders=[buy_order_to_create_in_flight, sell_order_to_create_in_flight], + order_hashes=[expected_order_hashes] + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(2, len(orders)) + self.assertEqual(2, len(self.exchange.in_flight_orders)) + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + self.assertEqual( + buy_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + buy_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[buy_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + self.assertEqual( + sell_order_to_create_in_flight.exchange_order_id, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].exchange_order_id + ) + self.assertEqual( + sell_order_to_create_in_flight.creation_transaction_hash, + self.exchange.in_flight_orders[sell_order_to_create_in_flight.client_order_id].creation_transaction_hash + ) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + self.configure_all_symbols_response(mock_api=None) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response(orders=[order], order_hashes=[expected_order_hash]) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + order = self.exchange.in_flight_orders[order_id] + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self.configure_all_symbols_response(mock_api=None) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + request_sent_event.clear() + order = self.exchange.in_flight_orders[order_id] + + expected_order_hash = "0x05536de7e0a41f0bfb493c980c1137afd3e548ae7e740e2662503f940a80e944" # noqa: mock" + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_chain_transactions() + ) + ) + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + full_transaction_response = self._orders_creation_transaction_response( + orders=[order], + order_hashes=[expected_order_hash] + ) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=full_transaction_response + ) + self.exchange._data_source._query_executor._transaction_by_hash_responses = mock_queue + + transaction_event = self._orders_creation_transaction_event() + self.exchange._data_source._query_executor._transaction_events.put_nowait(transaction_event) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + self.assertEqual(expected_order_hash, order.exchange_order_id) + self.assertEqual(response["txhash"], order.creation_transaction_hash) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait( + transaction_simulation_response) + + response = {"txhash": "", "rawLog": "Error"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order size 0.01. The order will not be created, " + "increase the amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + def test_batch_order_cancel(self): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id=self.expected_exchange_order_id + "2", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("110"), + order_type=OrderType.LIMIT, + ) + + buy_order_to_cancel: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + sell_order_to_cancel: GatewayInFlightOrder = self.exchange.in_flight_orders["12"] + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + + transaction_simulation_response = self._msg_exec_simulation_mock_response() + self.exchange._data_source._query_executor._simulate_transaction_responses.put_nowait(transaction_simulation_response) + + response = self._order_cancelation_request_successful_mock_response(order=buy_order_to_cancel) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, + callback=lambda args, kwargs: request_sent_event.set(), + response=response + ) + self.exchange._data_source._query_executor._send_transaction_responses = mock_queue + + self.exchange.batch_order_cancel(orders_to_cancel=orders_to_cancel) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], buy_order_to_cancel.cancel_tx_hash) + self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + self.assertEqual(response["txhash"], sell_order_to_cancel.cancel_tx_hash) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + # This tests does not apply for Injective. The batch orders update message used for cancelations will not + # detect if the orders exists or not. That will happen when the transaction is executed. + pass + + def test_user_stream_balance_update(self): + self.configure_all_symbols_response(mock_api=None) + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [balance_event, asyncio.CancelledError] + self.exchange._data_source._query_executor._subaccount_balance_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout(self.exchange._data_source._listen_to_account_balance_updates()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_spot_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_spot_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_spot_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_logs_errors(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_user_stream_raises_cancel_exception(self): + # This test does not apply to Injective because it handles private events in its own data source + pass + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + mock_queue = AsyncMock() + event_messages = [order_event, asyncio.CancelledError] + mock_queue.get.side_effect = event_messages + self.exchange._data_source._query_executor._historical_spot_order_events = mock_queue + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + try: + self.async_run_with_timeout( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + except asyncio.CancelledError: + pass + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_all_symbols_response(mock_api=None) + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + orders_queue_mock = AsyncMock() + trades_queue_mock = AsyncMock() + orders_messages = [] + trades_messages = [] + if trade_event: + trades_messages.append(trade_event) + if order_event: + orders_messages.append(order_event) + orders_messages.append(asyncio.CancelledError) + trades_messages.append(asyncio.CancelledError) + + orders_queue_mock.get.side_effect = orders_messages + trades_queue_mock.get.side_effect = trades_messages + self.exchange._data_source._query_executor._historical_spot_order_events = orders_queue_mock + self.exchange._data_source._query_executor._public_spot_trade_updates = trades_queue_mock + + self.async_tasks.append( + asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener() + ) + ) + + tasks = [ + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_public_spot_trades(market_ids=[self.market_id]) + ), + asyncio.get_event_loop().create_task( + self.exchange._data_source._listen_to_subaccount_spot_order_updates(market_id=self.market_id) + ) + ] + try: + self.async_run_with_timeout(safe_gather(*tasks)) + except asyncio.CancelledError: + pass + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(response) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_check_network_success(self, mock_api): + response = self.network_status_request_successful_mock_response + self.exchange._data_source._query_executor._ping_responses.put_nowait(response) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=10) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = RpcError("Test Error") + self.exchange._data_source._query_executor._ping_responses = mock_queue + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.exchange._data_source._query_executor._ping_responses = mock_queue + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + self.configure_all_symbols_response(mock_api=mock_api) + response = self.latest_prices_request_mock_response + self.exchange._data_source._query_executor._spot_trades_responses.put_nowait(response) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_get_fee(self): + self.exchange._data_source._spot_market_and_trading_pair_map = None + self.exchange._data_source._derivative_market_and_trading_pair_map = None + self.configure_all_symbols_response(mock_api=None) + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + maker_fee_rate = Decimal(self.all_markets_mock_response[0]["makerFeeRate"]) + taker_fee_rate = Decimal(self.all_markets_mock_response[0]["takerFeeRate"]) + + maker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=True + ) + + self.assertEqual(maker_fee_rate, maker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + taker_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("1000"), + price=Decimal("5"), + is_maker=False, + ) + + self.assertEqual(taker_fee_rate, taker_fee.percent) + self.assertEqual(self.quote_asset, maker_fee.percent_token) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "2", + exchange_order_id=self.exchange_order_id_prefix + "2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "3", + exchange_order_id=self.exchange_order_id_prefix + "3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(GatewayInFlightOrder( + client_order_id=self.client_order_id_prefix + "4", + exchange_order_id=self.exchange_order_id_prefix + "4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "2", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "3", self.exchange.in_flight_orders) + self.assertNotIn(self.client_order_id_prefix + "4", self.exchange.in_flight_orders) + + def _expected_initial_status_dict(self) -> Dict[str, bool]: + status_dict = super()._expected_initial_status_dict() + status_dict["data_source_initialized"] = False + return status_dict + + @staticmethod + def _callback_wrapper_with_response(callback: Callable, response: Any, *args, **kwargs): + callback(args, kwargs) + if isinstance(response, Exception): + raise response + else: + return response + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + all_markets_mock_response = self.all_markets_mock_response + self.exchange._data_source._query_executor._spot_markets_responses.put_nowait(all_markets_mock_response) + self.exchange._data_source._query_executor._derivative_markets_responses.put_nowait([]) + self.exchange._data_source._query_executor._account_portfolio_responses.put_nowait(response) + return "" + + def _msg_exec_simulation_mock_response(self) -> Any: + return { + "gasInfo": { + "gasWanted": "50000000", + "gasUsed": "90749" + }, + "result": { + "data": "Em8KJS9jb3Ntb3MuYXV0aHoudjFiZXRhMS5Nc2dFeGVjUmVzcG9uc2USRgpECkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA=", # noqa: mock" + "log": "", + "events": [], + "msgResponses": [ + OrderedDict([ + ("@type", "/cosmos.authz.v1beta1.MsgExecResponse"), + ("results", [ + "CkIweGYxNGU5NGMxZmQ0MjE0M2I3ZGRhZjA4ZDE3ZWMxNzAzZGMzNzZlOWU2YWI0YjY0MjBhMzNkZTBhZmFlYzJjMTA="]) # noqa: mock" + ]) + ] + } + } + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "[]"} # noqa: mock + + def _order_cancelation_request_erroneous_mock_response(self, order: InFlightOrder) -> Dict[str, Any]: + return {"txhash": "79DBF373DE9C534EE2DC9D009F32B850DA8D0C73833FAA0FD52C6AE8989EC659", "rawLog": "Error"} # noqa: mock + + def _order_status_request_open_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "booked", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_partially_filled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(self.expected_partial_fill_amount * Decimal(f"1e{self.base_decimals}")), + "state": "partial_filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_completely_filled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "state": "filled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_canceled_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [ + { + "orderHash": order.exchange_order_id, + "marketId": self.market_id, + "isActive": True, + "subaccountId": self.vault_contract_subaccount_id, + "executionType": "market" if order.order_type == OrderType.MARKET else "limit", + "orderType": order.trade_type.name.lower(), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "triggerPrice": "0", + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "filledQuantity": "0", + "state": "canceled", + "createdAt": "1688476825015", + "updatedAt": "1688476825015", + "direction": order.trade_type.name.lower(), + "txHash": order.creation_transaction_hash + }, + ], + "paging": { + "total": "1" + }, + } + + def _order_status_request_not_found_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "orders": [], + "paging": { + "total": "0" + }, + } + + def _order_fills_request_partial_fill_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(self.expected_partial_fill_price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(self.expected_partial_fill_amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1681735786785" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.vault_contract_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _order_fills_request_full_fill_mock_response(self, order: GatewayInFlightOrder) -> Dict[str, Any]: + return { + "trades": [ + { + "orderHash": order.exchange_order_id, + "subaccountId": self.vault_contract_subaccount_id, + "marketId": self.market_id, + "tradeExecutionType": "limitFill", + "tradeDirection": order.trade_type.name.lower(), + "price": { + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")), + "timestamp": "1681735786785" + }, + "fee": str(self.expected_fill_fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}")), + "executedAt": "1681735786785", + "feeRecipient": self.vault_contract_address, + "tradeId": self.expected_fill_trade_id, + "executionSide": "maker" + }, + ], + "paging": { + "total": "1", + "from": 1, + "to": 1 + } + } + + def _orders_creation_transaction_event(self) -> Dict[str, Any]: + return { + 'blockNumber': '44237', + 'blockTimestamp': '2023-07-18 20:25:43.518 +0000 UTC', + 'hash': self._transaction_hash, + 'messages': '[{"type":"/cosmwasm.wasm.v1.MsgExecuteContract","value":{"sender":"inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa","contract":"inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp","msg":{"admin_execute_message":{"injective_message":{"custom":{"route":"exchange","msg_data":{"batch_update_orders":{"sender":"inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp","spot_orders_to_create":[{"market_id":"0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0","order_info":{"subaccount_id":"1","price":"0.000000000002559000","quantity":"10000000000000000000.000000000000000000"},"order_type":1,"trigger_price":"0"}],"spot_market_ids_to_cancel_all":[],"derivative_market_ids_to_cancel_all":[],"spot_orders_to_cancel":[],"derivative_orders_to_cancel":[],"derivative_orders_to_create":[]}}}}}},"funds":[]}}]', # noqa: mock" + 'txNumber': '122692' + } + + def _orders_creation_transaction_response(self, orders: List[GatewayInFlightOrder], order_hashes: List[str]): + spot_orders = [] + for order in orders: + order_creation_message = { + "market_id": self.market_id, + "order_info": { + "subaccount_id": str(self.vault_contract_subaccount_index), + "price": str(order.price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + "quantity": str(order.amount * Decimal(f"1e{self.base_decimals}")) + }, + "order_type": 1 if order.trade_type == TradeType.BUY else 2, + "trigger_price": "0" + } + spot_orders.append(order_creation_message) + messages = [ + { + "type": "/cosmwasm.wasm.v1.MsgExecuteContract", + "value": { + "sender": self.trading_account_public_key, + "contract": self.vault_contract_address, + "msg": { + "admin_execute_message": { + "injective_message": { + "custom": { + "route": "exchange", + "msg_data": { + "batch_update_orders": { + "sender": self.vault_contract_address, + "spot_orders_to_create": spot_orders, + "spot_market_ids_to_cancel_all": [], + "derivative_market_ids_to_cancel_all": [], + "spot_orders_to_cancel": [], + "derivative_orders_to_cancel": [], + "derivative_orders_to_create": []}}}}}}, + "funds": []}}] + + logs = [{ + "msg_index": 0, + "events": [ + { + "type": "message", + "attributes": [{"key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract"}, + {"key": "sender", "value": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa"}, # noqa: mock" + {"key": "module", "value": "wasm"}]}, + { + "type": "execute", + "attributes": [ + {"key": "_contract_address", "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}]}, # noqa: mock" + { + "type": "reply", + "attributes": [ + {"key": "_contract_address", "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}]}, # noqa: mock" + { + "type": "wasm", + "attributes": [ + { + "key": "_contract_address", + "value": "inj1zlwdkv49rmsug0pnwu6fmwnl267lfr34yvhwgp"}, # noqa: mock" + { + "key": "method", + "value": "instantiate"}, + { + "key": "reply_id", + "value": "1"}, + { + "key": "batch_update_orders_response", + "value": f'MsgBatchUpdateOrdersResponse {{ spot_cancel_success: [], derivative_cancel_success: [], spot_order_hashes: {order_hashes}}}, derivative_order_hashes: [], binary_options_cancel_success: [], binary_options_order_hashes: [], unknown_fields: UnknownFields {{ fields: None }}, cached_size: CachedSize {{ size: 0 }} }}' + } + ] + } + ] + }] + + transaction_response = { + "s": "ok", + "data": { + "blockNumber": "30159", + "blockTimestamp": "2023-07-19 15:39:21.798 +0000 UTC", + "hash": self._transaction_hash, + "data": "Ei4KLC9jb3Ntd2FzbS53YXNtLnYxLk1zZ0V4ZWN1dGVDb250cmFjdFJlc3BvbnNl", # noqa: mock" + "gasWanted": "163571", + "gasUsed": "162984", + "gasFee": { + "amount": [ + { + "denom": "inj", + "amount": "81785500000000"}], "gasLimit": "163571", + "payer": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa" # noqa: mock" + }, + "txType": "injective", + "messages": base64.b64encode(json.dumps(messages).encode()).decode(), + "signatures": [ + { + "pubkey": "0382e03bf4b0ad77bef5f756a717a1a54d3c444b250b4ce097acb578aa80f58aab", # noqa: mock" + "address": "inj15uad884tqeq9r76x3fvktmjge2r6kek55c2zpa", # noqa: mock" + "sequence": "2", + "signature": "mF+KepSndvbu5UznsqfSl3rS9HkQQkDIcwBM3UIEzlF/SORCoI2fLue5okALWX5ZzfZXmwJGdjLqfjHDcJ3uEg==" + } + ], + "txNumber": "5", + "blockUnixTimestamp": "1689781161798", + "logs": base64.b64encode(json.dumps(logs).encode()).decode(), + } + } + + return transaction_response diff --git a/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_utils.py b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_utils.py new file mode 100644 index 0000000..e4441a7 --- /dev/null +++ b/test/hummingbot/connector/exchange/injective_v2/test_injective_v2_utils.py @@ -0,0 +1,136 @@ +from unittest import TestCase + +from pyinjective import Address, PrivateKey +from pyinjective.core.network import Network + +from hummingbot.connector.exchange.injective_v2 import injective_constants as CONSTANTS +from hummingbot.connector.exchange.injective_v2.data_sources.injective_grantee_data_source import ( + InjectiveGranteeDataSource, +) +from hummingbot.connector.exchange.injective_v2.data_sources.injective_vaults_data_source import ( + InjectiveVaultsDataSource, +) +from hummingbot.connector.exchange.injective_v2.injective_v2_utils import ( + InjectiveConfigMap, + InjectiveCustomNetworkMode, + InjectiveDelegatedAccountMode, + InjectiveMainnetNetworkMode, + InjectiveTestnetNetworkMode, + InjectiveVaultAccountMode, +) + + +class InjectiveConfigMapTests(TestCase): + + def test_mainnet_network_config_creation(self): + network_config = InjectiveMainnetNetworkMode() + + network = network_config.network() + expected_network = Network.mainnet(node="lb") + + self.assertEqual(expected_network.string(), network.string()) + self.assertEqual(expected_network.lcd_endpoint, network.lcd_endpoint) + self.assertTrue(network_config.use_secure_connection()) + + def test_testnet_network_config_creation(self): + network_config = InjectiveTestnetNetworkMode(testnet_node="sentry") + + network = network_config.network() + expected_network = Network.testnet(node="sentry") + + self.assertEqual(expected_network.string(), network.string()) + self.assertEqual(expected_network.lcd_endpoint, network.lcd_endpoint) + self.assertTrue(network_config.use_secure_connection()) + + def test_custom_network_config_creation(self): + network_config = InjectiveCustomNetworkMode( + lcd_endpoint='https://devnet.lcd.injective.dev', + tm_websocket_endpoint='wss://devnet.tm.injective.dev/websocket', + grpc_endpoint='devnet.injective.dev:9900', + grpc_exchange_endpoint='devnet.injective.dev:9910', + grpc_explorer_endpoint='devnet.injective.dev:9911', + chain_id='injective-777', + env='devnet', + secure_connection=False, + ) + + network = network_config.network() + expected_network = Network.custom( + lcd_endpoint='https://devnet.lcd.injective.dev', + tm_websocket_endpoint='wss://devnet.tm.injective.dev/websocket', + grpc_endpoint='devnet.injective.dev:9900', + grpc_exchange_endpoint='devnet.injective.dev:9910', + grpc_explorer_endpoint='devnet.injective.dev:9911', + chain_id='injective-777', + env='devnet' + ) + + self.assertEqual(expected_network.string(), network.string()) + self.assertEqual(expected_network.lcd_endpoint, network.lcd_endpoint) + self.assertEqual(expected_network.tm_websocket_endpoint, network.tm_websocket_endpoint) + self.assertEqual(expected_network.grpc_endpoint, network.grpc_endpoint) + self.assertEqual(expected_network.grpc_exchange_endpoint, network.grpc_exchange_endpoint) + self.assertEqual(expected_network.grpc_explorer_endpoint, network.grpc_explorer_endpoint) + self.assertEqual(expected_network.chain_id, network.chain_id) + self.assertEqual(expected_network.fee_denom, network.fee_denom) + self.assertEqual(expected_network.env, network.env) + self.assertFalse(network_config.use_secure_connection()) + + def test_injective_delegate_account_config_creation(self): + _, grantee_private_key = PrivateKey.generate() + _, granter_private_key = PrivateKey.generate() + + config = InjectiveDelegatedAccountMode( + private_key=granter_private_key.to_hex(), + subaccount_index=0, + granter_address=Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())).to_acc_bech32(), + granter_subaccount_index=0, + ) + + data_source = config.create_data_source( + network=Network.testnet(node="sentry"), + use_secure_connection=True, + rate_limits=CONSTANTS.PUBLIC_NODE_RATE_LIMITS, + ) + + self.assertEqual(InjectiveGranteeDataSource, type(data_source)) + + def test_injective_vault_account_config_creation(self): + _, private_key = PrivateKey.generate() + + config = InjectiveVaultAccountMode( + private_key=private_key.to_hex(), + subaccount_index=0, + vault_contract_address=Address( + bytes.fromhex(private_key.to_public_key().to_hex())).to_acc_bech32(), + ) + + data_source = config.create_data_source( + network=Network.testnet(node="sentry"), + use_secure_connection=True, + rate_limits=CONSTANTS.PUBLIC_NODE_RATE_LIMITS, + ) + + self.assertEqual(InjectiveVaultsDataSource, type(data_source)) + + def test_injective_config_creation(self): + network_config = InjectiveMainnetNetworkMode() + + _, grantee_private_key = PrivateKey.generate() + _, granter_private_key = PrivateKey.generate() + + account_config = InjectiveDelegatedAccountMode( + private_key=granter_private_key.to_hex(), + subaccount_index=0, + granter_address=Address(bytes.fromhex(granter_private_key.to_public_key().to_hex())).to_acc_bech32(), + granter_subaccount_index=0, + ) + + injective_config = InjectiveConfigMap( + network=network_config, + account_type=account_config, + ) + + data_source = injective_config.create_data_source() + + self.assertEqual(InjectiveGranteeDataSource, type(data_source)) diff --git a/test/hummingbot/connector/exchange/kraken/__init__.py b/test/hummingbot/connector/exchange/kraken/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py b/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py new file mode 100644 index 0000000..5553889 --- /dev/null +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py @@ -0,0 +1,271 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_api_order_book_data_source import KrakenAPIOrderBookDataSource +from hummingbot.connector.exchange.kraken.kraken_constants import KrakenAPITier +from hummingbot.connector.exchange.kraken.kraken_utils import build_rate_limits_by_tier +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook, OrderBookMessage + + +class KrakenAPIOrderBookDataSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.api_tier = KrakenAPITier.STARTER + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.throttler = AsyncThrottler(build_rate_limits_by_tier(self.api_tier)) + self.data_source = KrakenAPIOrderBookDataSource(self.throttler, trading_pairs=[self.trading_pair]) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_last_traded_prices_mock(self, last_trade_close: Decimal) -> Dict: + last_traded_prices = { + "error": [], + "result": { + f"X{self.base_asset}{self.quote_asset}": { + "a": [ + "52609.60000", + "1", + "1.000" + ], + "b": [ + "52609.50000", + "1", + "1.000" + ], + "c": [ + str(last_trade_close), + "0.00080000" + ], + "v": [ + "1920.83610601", + "7954.00219674" + ], + "p": [ + "52389.94668", + "54022.90683" + ], + "t": [ + 23329, + 80463 + ], + "l": [ + "51513.90000", + "51513.90000" + ], + "h": [ + "53219.90000", + "57200.00000" + ], + "o": "52280.40000" + } + } + } + return last_traded_prices + + def get_depth_mock(self) -> Dict: + depth = { + "error": [], + "result": { + f"X{self.base_asset}{self.quote_asset}": { + "asks": [ + [ + "52523.00000", + "1.199", + 1616663113 + ], + [ + "52536.00000", + "0.300", + 1616663112 + ] + ], + "bids": [ + [ + "52522.90000", + "0.753", + 1616663112 + ], + [ + "52522.80000", + "0.006", + 1616663109 + ] + ] + } + } + } + return depth + + def get_public_asset_pair_mock(self) -> Dict: + asset_pairs = { + "error": [], + "result": { + f"X{self.base_asset}{self.quote_asset}": { + "altname": f"{self.base_asset}{self.quote_asset}", + "wsname": f"{self.base_asset}/{self.quote_asset}", + "aclass_base": "currency", + "base": self.base_asset, + "aclass_quote": "currency", + "quote": self.quote_asset, + "lot": "unit", + "pair_decimals": 5, + "lot_decimals": 8, + "lot_multiplier": 1, + "leverage_buy": [ + 2, + 3, + 4, + 5 + ], + "leverage_sell": [ + 2, + 3, + 4, + 5 + ], + "fees": [ + [ + 0, + 0.26 + ], + [ + 50000, + 0.24 + ], + ], + "fees_maker": [ + [ + 0, + 0.16 + ], + [ + 50000, + 0.14 + ], + ], + "fee_volume_currency": "ZUSD", + "margin_call": 80, + "margin_stop": 40, + "ordermin": "0.005" + }, + } + } + return asset_pairs + + def get_trade_data_mock(self) -> List: + trade_data = [ + 0, + [ + [ + "5541.20000", + "0.15850568", + "1534614057.321597", + "s", + "l", + "" + ], + [ + "6060.00000", + "0.02455000", + "1534614057.324998", + "b", + "l", + "" + ] + ], + "trade", + f"{self.base_asset}/{self.quote_asset}" + ] + return trade_data + + @aioresponses() + def test_get_last_traded_prices(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TICKER_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + last_traded_price = Decimal("52641.10000") + resp = self.get_last_traded_prices_mock(last_trade_close=last_traded_price) + mocked_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout( + KrakenAPIOrderBookDataSource.get_last_traded_prices( + trading_pairs=[self.trading_pair], throttler=self.throttler + ) + ) + + self.assertIn(self.trading_pair, ret) + self.assertEqual(float(last_traded_price), ret[self.trading_pair]) + + @aioresponses() + def test_get_new_order_book(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SNAPSHOT_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_depth_mock() + mocked_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + self.assertTrue(isinstance(ret, OrderBook)) + + bids_df, asks_df = ret.snapshot + pair_data = resp["result"][f"X{self.base_asset}{self.quote_asset}"] + first_bid_price = float(pair_data["bids"][0][0]) + first_ask_price = float(pair_data["asks"][0][0]) + + self.assertEqual(first_bid_price, bids_df.iloc[0]["price"]) + self.assertEqual(first_ask_price, asks_df.iloc[0]["price"]) + + # @aioresponses() + # def test_fetch_trading_pairs(self, mocked_api): + # url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + # regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + # resp = self.get_public_asset_pair_mock() + # mocked_api.get(regex_url, body=json.dumps(resp)) + # + # resp = self.async_run_with_timeout(KrakenAPIOrderBookDataSource.fetch_trading_pairs(), 2) + # + # self.assertTrue(len(resp) == 1) + # self.assertIn(self.trading_pair, resp) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_trades(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = self.get_trade_data_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) + ) + output_queue = asyncio.Queue() + + self.ev_loop.create_task(self.data_source.listen_for_trades(self.ev_loop, output_queue)) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=ws_connect_mock.return_value) + + self.assertTrue(not output_queue.empty()) + msg = output_queue.get_nowait() + self.assertTrue(isinstance(msg, OrderBookMessage)) + first_trade_price = resp[1][0][0] + self.assertEqual(msg.content["price"], first_trade_price) + + self.assertTrue(not output_queue.empty()) + msg = output_queue.get_nowait() + self.assertTrue(isinstance(msg, OrderBookMessage)) + second_trade_price = resp[1][1][0] + self.assertEqual(msg.content["price"], second_trade_price) diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py new file mode 100644 index 0000000..7786f2f --- /dev/null +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_api_user_stream_data_source.py @@ -0,0 +1,138 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_api_user_stream_data_source import KrakenAPIUserStreamDataSource +from hummingbot.connector.exchange.kraken.kraken_auth import KrakenAuth +from hummingbot.connector.exchange.kraken.kraken_constants import KrakenAPITier +from hummingbot.connector.exchange.kraken.kraken_utils import build_rate_limits_by_tier +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class KrakenAPIUserStreamDataSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.api_tier = KrakenAPITier.STARTER + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.throttler = AsyncThrottler(build_rate_limits_by_tier(self.api_tier)) + not_a_real_secret = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==" + kraken_auth = KrakenAuth(api_key="someKey", secret_key=not_a_real_secret) + self.data_source = KrakenAPIUserStreamDataSource(self.throttler, kraken_auth) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_auth_response_mock() -> Dict: + auth_resp = { + "error": [], + "result": { + "token": "1Dwc4lzSwNWOAwkMdqhssNNFhs1ed606d1WcF3XfEMw", + "expires": 900 + } + } + return auth_resp + + @staticmethod + def get_open_orders_mock() -> List: + open_orders = [ + [ + { + "OGTT3Y-C6I3P-XRI6HX": { + "status": "closed" + } + }, + { + "OGTT3Y-C6I3P-XRI6HX": { + "status": "closed" + } + } + ], + "openOrders", + { + "sequence": 59342 + } + ] + return open_orders + + @staticmethod + def get_own_trades_mock() -> List: + own_trades = [ + [ + { + "TDLH43-DVQXD-2KHVYY": { + "cost": "1000000.00000", + "fee": "1600.00000", + "margin": "0.00000", + "ordertxid": "TDLH43-DVQXD-2KHVYY", + "ordertype": "limit", + "pair": "XBT/EUR", + "postxid": "OGTT3Y-C6I3P-XRI6HX", + "price": "100000.00000", + "time": "1560516023.070651", + "type": "sell", + "vol": "1000000000.00000000" + } + }, + ], + "ownTrades", + { + "sequence": 2948 + } + ] + return own_trades + + @aioresponses() + def test_get_auth_token(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.GET_TOKEN_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_auth_response_mock() + mocked_api.post(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(self.data_source.get_auth_token()) + + self.assertEqual(ret, resp["result"]["token"]) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream(self, mocked_api, ws_connect_mock): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.GET_TOKEN_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_auth_response_mock() + mocked_api.post(regex_url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + output_queue = asyncio.Queue() + self.ev_loop.create_task(self.data_source.listen_for_user_stream(output_queue)) + + resp = self.get_open_orders_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) + ) + ret = self.async_run_with_timeout(coroutine=output_queue.get()) + + self.assertEqual(ret, resp) + + resp = self.get_own_trades_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) + ) + ret = self.async_run_with_timeout(coroutine=output_queue.get()) + + self.assertEqual(ret, resp) diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_exchange.py b/test/hummingbot/connector/exchange/kraken/test_kraken_exchange.py new file mode 100644 index 0000000..6096c57 --- /dev/null +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_exchange.py @@ -0,0 +1,454 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from functools import partial +from typing import Awaitable, Dict + +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.kraken import kraken_constants as CONSTANTS +from hummingbot.connector.exchange.kraken.kraken_exchange import KrakenExchange +from hummingbot.connector.exchange.kraken.kraken_in_flight_order import KrakenInFlightOrderNotCreated +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderCancelledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class KrakenExchangeTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.event_listener = EventLogger() + not_a_real_secret = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==" + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = KrakenExchange( + client_config_map=self.client_config_map, + kraken_api_key="someKey", + kraken_secret_key=not_a_real_secret, + trading_pairs=[self.trading_pair], + ) + self.start_time = 1 + self.clock = Clock(clock_mode=ClockMode.BACKTEST, start_time=self.start_time) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def simulate_trading_rules_initialized(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + resp = self.get_asset_pairs_mock() + mocked_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_rules(), timeout=2) + + @staticmethod + def register_sent_request(requests_list, url, **kwargs): + requests_list.append((url, kwargs)) + + def get_asset_pairs_mock(self) -> Dict: + asset_pairs = { + "error": [], + "result": { + f"X{self.base_asset}{self.quote_asset}": { + "altname": f"{self.base_asset}{self.quote_asset}", + "wsname": f"{self.base_asset}/{self.quote_asset}", + "aclass_base": "currency", + "base": f"{self.base_asset}", + "aclass_quote": "currency", + "quote": f"{self.quote_asset}", + "lot": "unit", + "pair_decimals": 5, + "lot_decimals": 8, + "lot_multiplier": 1, + "leverage_buy": [ + 2, + 3, + ], + "leverage_sell": [ + 2, + 3, + ], + "fees": [ + [ + 0, + 0.26 + ], + [ + 50000, + 0.24 + ], + ], + "fees_maker": [ + [ + 0, + 0.16 + ], + [ + 50000, + 0.14 + ], + ], + "fee_volume_currency": "ZUSD", + "margin_call": 80, + "margin_stop": 40, + "ordermin": "0.005" + }, + } + } + return asset_pairs + + def get_balances_mock(self, base_asset_balance: float, quote_asset_balance: float) -> Dict: + balances = { + "error": [], + "result": { + self.base_asset: str(base_asset_balance), + self.quote_asset: str(quote_asset_balance), + "USDT": "171288.6158", + } + } + return balances + + def get_open_orders_mock(self, quantity: float, price: float, order_type: str) -> Dict: + open_orders = { + "error": [], + "result": { + "open": { + "OQCLML-BW3P3-BUCMWZ": self.get_order_status_mock(quantity, price, order_type, status="open"), + } + } + } + return open_orders + + def get_query_orders_mock( + self, exchange_id: str, quantity: float, price: float, order_type: str, status: str + ) -> Dict: + query_orders = { + "error": [], + "result": { + exchange_id: self.get_order_status_mock(quantity, price, order_type, status) + } + } + return query_orders + + def get_order_status_mock(self, quantity: float, price: float, order_type: str, status: str) -> Dict: + order_status = { + "refid": None, + "userref": 0, + "status": status, + "opentm": 1616666559.8974, + "starttm": 0, + "expiretm": 0, + "descr": { + "pair": f"{self.base_asset}{self.quote_asset}", + "type": order_type, + "ordertype": "limit", + "price": str(price), + "price2": "0", + "leverage": "none", + "order": f"buy {quantity} {self.base_asset}{self.quote_asset} @ limit {price}", + "close": "" + }, + "vol": str(quantity), + "vol_exec": "0", + "cost": str(price * quantity), + "fee": "0.00000", + "price": str(price), + "stopprice": "0.00000", + "limitprice": "0.00000", + "misc": "", + "oflags": "fciq", + "trades": [ + "TCCCTY-WE2O6-P3NB37" + ] + } + return order_status + + def get_order_placed_mock(self, exchange_id: str, quantity: float, price: float, order_type: str) -> Dict: + order_placed = { + "error": [], + "result": { + "descr": { + "order": f"{order_type} {quantity} {self.base_asset}{self.quote_asset}" + f" @ limit {price} with 2:1 leverage", + }, + "txid": [ + exchange_id + ] + } + } + return order_placed + + @aioresponses() + def test_get_asset_pairs(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + resp = self.get_asset_pairs_mock() + mocked_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(self.exchange.get_asset_pairs()) + + self.assertIn(self.trading_pair, ret) + self.assertEqual( + ret[self.trading_pair], resp["result"][f"X{self.base_asset}{self.quote_asset}"] # shallow comparison is ok + ) + + @aioresponses() + def test_update_balances(self, mocked_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" + resp = self.get_asset_pairs_mock() + mocked_api.get(url, body=json.dumps(resp)) + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.BALANCE_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_balances_mock(base_asset_balance=10, quote_asset_balance=20) + mocked_api.post(regex_url, body=json.dumps(resp)) + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.OPEN_ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_open_orders_mock(quantity=1, price=2, order_type="buy") + mocked_api.post(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_balances()) + + self.assertEqual(self.exchange.available_balances[self.quote_asset], Decimal("18")) + + @aioresponses() + def test_update_order_status_order_closed(self, mocked_api): + order_id = "someId" + exchange_id = "someExchangeId" + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.QUERY_ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_query_orders_mock(exchange_id, quantity=1, price=2, order_type="buy", status="closed") + mocked_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.start_tracking_order( + order_id=order_id, + exchange_order_id=exchange_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=2, + amount=1, + order_type=OrderType.LIMIT, + userref=1, + ) + self.exchange.add_listener(MarketEvent.BuyOrderCompleted, self.event_listener) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertEqual(len(self.event_listener.event_log), 1) + self.assertTrue(isinstance(self.event_listener.event_log[0], BuyOrderCompletedEvent)) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_check_network_success(self, mock_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}" + resp = {"status": 200, "result": []} + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.CONNECTED) + + @aioresponses() + def test_check_network_raises_cancelled_error(self, mock_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}" + mock_api.get(url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + @aioresponses() + def test_check_network_not_connected_for_error_status(self, mock_api): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}" + resp = {"status": 405, "result": []} + mock_api.get(url, status=405, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_get_open_orders_with_userref(self, mocked_api): + sent_messages = [] + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.OPEN_ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_open_orders_mock(quantity=1, price=2, order_type="buy") + mocked_api.post(regex_url, body=json.dumps(resp), callback=partial(self.register_sent_request, sent_messages)) + userref = 1 + + ret = self.async_run_with_timeout(self.exchange.get_open_orders_with_userref(userref)) + + self.assertEqual(len(sent_messages), 1) + + sent_message = sent_messages[0][1]["data"] + + self.assertEqual(sent_message["userref"], userref) + self.assertEqual(ret, resp["result"]) # shallow comparison ok + + @aioresponses() + def test_get_order(self, mocked_api): + sent_messages = [] + order_id = "someId" + exchange_id = "someExchangeId" + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.QUERY_ORDERS_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_query_orders_mock(exchange_id, quantity=1, price=2, order_type="buy", status="closed") + mocked_api.post(regex_url, body=json.dumps(resp), callback=partial(self.register_sent_request, sent_messages)) + + self.exchange.start_tracking_order( + order_id=order_id, + exchange_order_id=exchange_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=2, + amount=1, + order_type=OrderType.LIMIT, + userref=1, + ) + ret = self.async_run_with_timeout(self.exchange.get_order(client_order_id=order_id)) + + self.assertEqual(len(sent_messages), 1) + + sent_message = sent_messages[0][1]["data"] + + self.assertEqual(sent_message["txid"], exchange_id) + self.assertEqual(ret, resp["result"]) # shallow comparison ok + + @aioresponses() + def test_execute_buy(self, mocked_api): + self.exchange.start(self.clock, self.start_time) + self.simulate_trading_rules_initialized(mocked_api) + + order_id = "someId" + exchange_id = "someExchangeId" + userref = 1 + quantity = 1 + price = 2 + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ADD_ORDER_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_placed_mock(exchange_id, quantity, price, order_type="buy") + mocked_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.add_listener(MarketEvent.BuyOrderCreated, self.event_listener) + self.async_run_with_timeout( + self.exchange.execute_buy( + order_id, + self.trading_pair, + amount=Decimal(quantity), + order_type=OrderType.LIMIT, + price=Decimal(price), + userref=userref, + ) + ) + + self.assertEqual(len(self.event_listener.event_log), 1) + self.assertTrue(isinstance(self.event_listener.event_log[0], BuyOrderCreatedEvent)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_sell(self, mocked_api): + order_id = "someId" + exchange_id = "someExchangeId" + userref = 1 + quantity = 1 + price = 2 + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ADD_ORDER_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_order_placed_mock(exchange_id, quantity, price, order_type="sell") + mocked_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.start(self.clock, self.start_time) + self.simulate_trading_rules_initialized(mocked_api) + self.exchange.add_listener(MarketEvent.SellOrderCreated, self.event_listener) + self.async_run_with_timeout( + self.exchange.execute_sell( + order_id, + self.trading_pair, + amount=Decimal(quantity), + order_type=OrderType.LIMIT, + price=Decimal(price), + userref=userref, + ) + ) + + self.assertEqual(len(self.event_listener.event_log), 1) + self.assertTrue(isinstance(self.event_listener.event_log[0], SellOrderCreatedEvent)) + self.assertIn(order_id, self.exchange.in_flight_orders) + + @aioresponses() + def test_execute_cancel(self, mocked_api): + order_id = "someId" + exchange_id = "someExchangeId" + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.CANCEL_ORDER_PATH_URL}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = { + "error": [], + "result": { + "count": 1 + } + } + mocked_api.post(regex_url, body=json.dumps(resp)) + + self.exchange.start_tracking_order( + order_id=order_id, + exchange_order_id=exchange_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=2, + amount=1, + order_type=OrderType.LIMIT, + userref=1, + ) + self.exchange.in_flight_orders[order_id].update_exchange_order_id(exchange_id) + self.exchange.in_flight_orders[order_id].last_state = "pending" + self.exchange.add_listener(MarketEvent.OrderCancelled, self.event_listener) + ret = self.async_run_with_timeout(self.exchange.execute_cancel(self.trading_pair, order_id)) + + self.assertEqual(len(self.event_listener.event_log), 1) + self.assertTrue(isinstance(self.event_listener.event_log[0], OrderCancelledEvent)) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + self.assertEqual(ret["origClientOrderId"], order_id) + + def test_execute_cancel_ignores_local_orders(self): + order_id = "someId" + exchange_id = "someExchangeId" + + self.exchange.start_tracking_order( + order_id=order_id, + exchange_order_id=exchange_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=2, + amount=1, + order_type=OrderType.LIMIT, + userref=1, + ) + + with self.assertRaises(KrakenInFlightOrderNotCreated): + self.async_run_with_timeout(self.exchange.execute_cancel(self.trading_pair, order_id)) diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py b/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py new file mode 100644 index 0000000..6de2297 --- /dev/null +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py @@ -0,0 +1,90 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.exchange.kraken.kraken_in_flight_order import KrakenInFlightOrder +from hummingbot.core.data_type.common import OrderType, TradeType + + +class KrakenInFlightOrderTests(TestCase): + def test_order_is_local_after_creation(self): + order = KrakenInFlightOrder( + client_order_id="someId", + exchange_order_id=None, + trading_pair="BTC-USDT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(45000), + amount=Decimal(1), + creation_timestamp=1640001112.0, + userref=1, + ) + + self.assertTrue(order.is_local) + + def test_serialize_order_to_json(self): + order = KrakenInFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(1000), + amount=Decimal(1), + creation_timestamp=1640001112.0, + userref=2, + initial_state="OPEN", + ) + + expected_json = { + "client_order_id": order.client_order_id, + "exchange_order_id": order.exchange_order_id, + "trading_pair": order.trading_pair, + "order_type": order.order_type.name, + "trade_type": order.trade_type.name, + "price": str(order.price), + "amount": str(order.amount), + "last_state": order.last_state, + "executed_amount_base": str(order.executed_amount_base), + "executed_amount_quote": str(order.executed_amount_quote), + "fee_asset": order.fee_asset, + "fee_paid": str(order.fee_paid), + "creation_timestamp": 1640001112.0, + "userref": order.userref, + } + + self.assertEqual(expected_json, order.to_json()) + + def test_deserialize_order_from_json(self): + json = { + "client_order_id": "OID1", + "exchange_order_id": "EOID1", + "trading_pair": "COINALPHA-HBOT", + "order_type": OrderType.LIMIT.name, + "trade_type": TradeType.BUY.name, + "price": "1000", + "amount": "1", + "last_state": "OPEN", + "executed_amount_base": "0.1", + "executed_amount_quote": "110", + "fee_asset": "BNB", + "fee_paid": "10", + "creation_timestamp": 1640001112.0, + "userref": 2, + } + + order: KrakenInFlightOrder = KrakenInFlightOrder.from_json(json) + + self.assertEqual(json["client_order_id"], order.client_order_id) + self.assertEqual(json["exchange_order_id"], order.exchange_order_id) + self.assertEqual(json["trading_pair"], order.trading_pair) + self.assertEqual(OrderType.LIMIT, order.order_type) + self.assertEqual(TradeType.BUY, order.trade_type) + self.assertEqual(Decimal(json["price"]), order.price) + self.assertEqual(Decimal(json["amount"]), order.amount) + self.assertEqual(Decimal(json["executed_amount_base"]), order.executed_amount_base) + self.assertEqual(Decimal(json["executed_amount_quote"]), order.executed_amount_quote) + self.assertEqual(json["fee_asset"], order.fee_asset) + self.assertEqual(Decimal(json["fee_paid"]), order.fee_paid) + self.assertEqual(json["last_state"], order.last_state) + self.assertEqual(json["creation_timestamp"], order.creation_timestamp) + self.assertEqual(json["userref"], order.userref) diff --git a/test/hummingbot/connector/exchange/kucoin/__init__.py b/test/hummingbot/connector/exchange/kucoin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_order_book_data_source.py b/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_order_book_data_source.py new file mode 100644 index 0000000..e9fe3b5 --- /dev/null +++ b/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_order_book_data_source.py @@ -0,0 +1,507 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS, kucoin_web_utils as web_utils +from hummingbot.connector.exchange.kucoin.kucoin_api_order_book_data_source import KucoinAPIOrderBookDataSource +from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class TestKucoinAPIOrderBookDataSource(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ws_endpoint = "ws://someEndpoint" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.async_task = None + + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = KucoinExchange( + client_config_map=client_config_map, + kucoin_api_key="", + kucoin_passphrase="", + kucoin_secret_key="", + trading_pairs=[], + trading_required=False) + + self.ob_data_source = KucoinAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self._original_full_order_book_reset_time = self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.ob_data_source.logger().setLevel(1) + self.ob_data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.async_task and self.async_task.cancel() + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_snapshot_mock() -> Dict: + snapshot = { + "code": "200000", + "data": { + "time": 1630556205455, + "sequence": "1630556205456", + "bids": [["0.3003", "4146.5645"]], + "asks": [["0.3004", "1553.6412"]] + } + } + return snapshot + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + resp = self.get_snapshot_mock() + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.ob_data_source.get_new_order_book(self.trading_pair)) + bid_entries = list(ret.bid_entries()) + ask_entries = list(ret.ask_entries()) + self.assertEqual(1, len(bid_entries)) + self.assertEqual(0.3003, bid_entries[0].price) + self.assertEqual(4146.5645, bid_entries[0].amount) + self.assertEqual(int(resp["data"]["sequence"]), bid_entries[0].update_id) + self.assertEqual(1, len(ask_entries)) + self.assertEqual(0.3004, ask_entries[0].price) + self.assertEqual(1553.6412, ask_entries[0].amount) + self.assertEqual(int(resp["data"]["sequence"]), ask_entries[0].update_id) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.kucoin.kucoin_web_utils.next_message_id") + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, mock_api, id_mock, ws_connect_mock): + id_mock.side_effect = [1, 2] + url = web_utils.public_rest_url(path_url=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "type": "ack", + "id": 1 + } + result_subscribe_diffs = { + "type": "ack", + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "id": 1, + "type": "subscribe", + "topic": f"/market/match:{self.trading_pair}", + "privateChannel": False, + "response": False + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "id": 2, + "type": "subscribe", + "topic": f"/market/level2:{self.trading_pair}", + "privateChannel": False, + "response": False + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.kucoin.kucoin_web_utils.next_message_id") + @patch("hummingbot.connector.exchange.kucoin.kucoin_api_order_book_data_source.KucoinAPIOrderBookDataSource._time") + def test_listen_for_subscriptions_sends_ping_message_before_ping_interval_finishes( + self, + mock_api, + time_mock, + id_mock, + ws_connect_mock): + + id_mock.side_effect = [1, 2, 3, 4] + time_mock.side_effect = [1000, 1100, 1101, 1102] # Simulate first ping interval is already due + url = web_utils.public_rest_url(path_url=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 20000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "type": "ack", + "id": 1 + } + result_subscribe_diffs = { + "type": "ack", + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = { + "id": 3, + "type": "ping", + } + self.assertEqual(expected_ping_message, sent_messages[-1]) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_api, _, ws_connect_mock): + url = web_utils.public_rest_url(path_url=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_logs_exception_details(self, mock_api, sleep_mock, ws_connect_mock): + url = web_utils.public_rest_url(path_url=CONSTANTS.PUBLIC_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + sleep_mock.side_effect = asyncio.CancelledError + ws_connect_mock.side_effect = Exception("TEST ERROR.") + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "type": "message", + "topic": f"/market/match:{self.trading_pair}", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[self.ob_data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "type": "message", + "topic": f"/market/match:{self.trading_pair}", + "subject": "trade.l3match", + "data": { + "sequence": "1545896669145", + "type": "match", + "symbol": self.trading_pair, + "side": "buy", + "price": "0.08200000000000000000", + "size": "0.01022222000000000000", + "tradeId": "5c24c5da03aa673885cd67aa", + "takerOrderId": "5c24c5d903aa6772d55b371e", + "makerOrderId": "5c2187d003aa677bd09d5c93", + "time": "1545913818099033203" + } + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[self.ob_data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(trade_event["data"]["tradeId"], msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "type": "message", + "topic": f"/market/level2:{self.trading_pair}", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[self.ob_data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = { + "type": "message", + "topic": "/market/level2:BTC-USDT", + "subject": "trade.l2update", + "data": { + + "sequenceStart": 1545896669105, + "sequenceEnd": 1545896669106, + "symbol": f"{self.trading_pair}", + "changes": { + + "asks": [["6", "1", "1545896669105"]], + "bids": [["4", "1", "1545896669106"]] + } + } + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[self.ob_data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(diff_event["data"]["sequenceEnd"], msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.kucoin.kucoin_api_order_book_data_source" + ".KucoinAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = asyncio.CancelledError + + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + try: + self.async_run_with_timeout(self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api, ): + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_NO_AUTH_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + snapshot_data = { + "code": "200000", + "data": { + "sequence": "3262786978", + "time": 1550653727731, + "bids": [["6500.12", "0.45054140"], + ["6500.11", "0.45054140"]], + "asks": [["6500.16", "0.57753524"], + ["6500.15", "0.57753524"]] + } + } + + mock_api.get(regex_url, body=json.dumps(snapshot_data)) + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(snapshot_data["data"]["sequence"]), msg.update_id) diff --git a/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_user_stream_data_source.py new file mode 100644 index 0000000..93dd43e --- /dev/null +++ b/test/hummingbot/connector/exchange/kucoin/test_kucoin_api_user_stream_data_source.py @@ -0,0 +1,357 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS, kucoin_web_utils as web_utils +from hummingbot.connector.exchange.kucoin.kucoin_api_user_stream_data_source import KucoinAPIUserStreamDataSource +from hummingbot.connector.exchange.kucoin.kucoin_auth import KucoinAuth +from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestKucoinAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.api_key = "someKey" + cls.api_passphrase = "somePassPhrase" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = KucoinAuth( + self.api_key, + self.api_passphrase, + self.api_secret_key, + time_provider=self.mock_time_provider) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = KucoinExchange( + client_config_map=client_config_map, + kucoin_api_key="", + kucoin_passphrase="", + kucoin_secret_key="", + trading_pairs=[], + trading_required=False) + + self.data_source = KucoinAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + def get_listen_key_mock(): + listen_key = { + "code": "200000", + "data": { + "token": "someToken", + "instanceServers": [ + { + "endpoint": "wss://someEndpoint", + "encrypt": True, + "protocol": "websocket", + "pingInterval": 18000, + "pingTimeout": 10000, + } + ] + } + } + return listen_key + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.kucoin.kucoin_web_utils.next_message_id") + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, mock_api, id_mock, ws_connect_mock): + id_mock.side_effect = [1, 2] + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "type": "ack", + "id": 1 + } + result_subscribe_diffs = { + "type": "ack", + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_orders_subscription = { + "id": 1, + "type": "subscribe", + "topic": "/spotMarket/tradeOrders", + "privateChannel": True, + "response": False + } + self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) + expected_balances_subscription = { + "id": 2, + "type": "subscribe", + "topic": "/account/balance", + "privateChannel": True, + "response": False + } + self.assertEqual(expected_balances_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private order changes and balance updates channels..." + )) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = self.get_listen_key_mock() + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + order_event = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + + "symbol": "KCS-USDT", + "orderType": "limit", + "side": "buy", + "orderId": "5efab07953bdea00089965d2", + "type": "open", + "orderTime": 1593487481683297666, + "size": "0.1", + "filledSize": "0", + "price": "0.937", + "clientOid": "1593487481000906", + "remainSize": "0.1", + "status": "open", + "ts": 1593487481683297666 + } + } + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(order_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertEqual(order_event, msg) + mock_ws.return_value.ping.assert_called() + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_pong_payload(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = self.get_listen_key_mock() + + mock_pong = { + "id": "1545910590801", + "type": "pong" + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(mock_pong)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_connection_failed(self, mock_api, sleep_mock, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = self.get_listen_key_mock() + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + msg_queue = asyncio.Queue() + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, sleep_mock, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = self.get_listen_key_mock() + mock_api.post(regex_url, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.kucoin.kucoin_web_utils.next_message_id") + @patch("hummingbot.connector.exchange.kucoin.kucoin_api_user_stream_data_source.KucoinAPIUserStreamDataSource" + "._time") + def test_listen_for_user_stream_sends_ping_message_before_ping_interval_finishes( + self, + mock_api, + time_mock, + id_mock, + ws_connect_mock): + + id_mock.side_effect = [1, 2, 3, 4] + time_mock.side_effect = [1000, 1100, 1101, 1102] # Simulate first ping interval is already due + url = web_utils.private_rest_url(path_url=CONSTANTS.PRIVATE_WS_DATA_PATH_URL) + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 20000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "type": "ack", + "id": 1 + } + result_subscribe_diffs = { + "type": "ack", + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = { + "id": 3, + "type": "ping", + } + self.assertEqual(expected_ping_message, sent_messages[-1]) diff --git a/test/hummingbot/connector/exchange/kucoin/test_kucoin_auth.py b/test/hummingbot/connector/exchange/kucoin/test_kucoin_auth.py new file mode 100644 index 0000000..d3235b2 --- /dev/null +++ b/test/hummingbot/connector/exchange/kucoin/test_kucoin_auth.py @@ -0,0 +1,125 @@ +import asyncio +import base64 +import hashlib +import hmac +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS +from hummingbot.connector.exchange.kucoin.kucoin_auth import KucoinAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class KucoinAuthTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.passphrase = "testPassphrase" + self.secret_key = "testSecretKey" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = KucoinAuth( + api_key=self.api_key, + passphrase=self.passphrase, + secret_key=self.secret_key, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _sign(self, message: str, key: str) -> str: + signed_message = base64.b64encode( + hmac.new( + key.encode("utf-8"), + message.encode("utf-8"), + hashlib.sha256).digest()) + return signed_message.decode("utf-8") + + def test_add_auth_headers_to_get_request_without_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + expected_signature = self._sign("1000000" + "GET" + request.throttler_limit_id, key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_add_auth_headers_to_get_request_with_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params={"param_z": "value_param_z", "param_a": "value_param_a"}, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + full_endpoint = f"{request.throttler_limit_id}?param_a=value_param_a¶m_z=value_param_z" + expected_signature = self._sign("1000000" + "GET" + full_endpoint, key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_add_auth_headers_to_post_request(self): + body = {"param_z": "value_param_z", "param_a": "value_param_a"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=json.dumps(body), + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) + self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) + self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) + expected_signature = self._sign("1000000" + "POST" + request.throttler_limit_id + json.dumps(body), + key=self.secret_key) + self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) + expected_passphrase = self._sign(self.passphrase, key=self.secret_key) + self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) + + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) + expected_partner_signature = self._sign("1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, + key=CONSTANTS.HB_PARTNER_KEY) + self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"]) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) diff --git a/test/hummingbot/connector/exchange/kucoin/test_kucoin_exchange.py b/test/hummingbot/connector/exchange/kucoin/test_kucoin_exchange.py new file mode 100644 index 0000000..5dfe3e7 --- /dev/null +++ b/test/hummingbot/connector/exchange/kucoin/test_kucoin_exchange.py @@ -0,0 +1,2629 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Awaitable, Dict, List, NamedTuple, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS, kucoin_web_utils as web_utils +from hummingbot.connector.exchange.kucoin.kucoin_exchange import KucoinExchange +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, TradeUpdate +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeBase, TradeFeeSchema +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class KucoinExchangeTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.exchange_trading_pair = cls.trading_pair + cls.api_key = "someKey" + cls.api_passphrase = "somePassPhrase" + cls.api_secret_key = "someSecretKey" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.test_task: Optional[asyncio.Task] = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = KucoinExchange( + client_config_map=self.client_config_map, + kucoin_api_key=self.api_key, + kucoin_passphrase=self.api_passphrase, + kucoin_secret_key=self.api_secret_key, + trading_pairs=[self.trading_pair] + ) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + + self._initialize_event_loggers() + + self.exchange._set_trading_pair_symbol_map(bidict({self.trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_exchange_rules_mock(self) -> Dict: + exchange_rules = { + "code": "200000", + "data": [ + { + "symbol": self.trading_pair, + "name": self.trading_pair, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "feeCurrency": self.quote_asset, + "market": "ALTS", + "baseMinSize": "1", + "quoteMinSize": "0.1", + "baseMaxSize": "10000000000", + "quoteMaxSize": "99999999", + "baseIncrement": "0.1", + "quoteIncrement": "0.01", + "priceIncrement": "0.01", + "priceLimitRate": "0.1", + "isMarginEnabled": False, + "enableTrading": True, + }, + ], + } + return exchange_rules + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def _validate_auth_credentials_present(self, request_call_tuple: NamedTuple): + request_headers = request_call_tuple.kwargs["headers"] + self.assertIn("KC-API-PARTNER", request_headers) + self.assertEqual(CONSTANTS.HB_PARTNER_ID, request_headers["KC-API-PARTNER"]) + self.assertIn("KC-API-PARTNER-SIGN", request_headers) + self.assertIn("KC-API-KEY", request_headers) + self.assertEqual(self.api_key, request_headers["KC-API-KEY"]) + self.assertIn("KC-API-TIMESTAMP", request_headers) + self.assertIn("KC-API-KEY-VERSION", request_headers) + self.assertEqual("2", request_headers["KC-API-KEY-VERSION"]) + self.assertIn("KC-API-SIGN", request_headers) + self.assertIn("KC-API-PASSPHRASE", request_headers) + + @aioresponses() + def test_all_trading_pairs(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + url = web_utils.public_rest_url(path_url=CONSTANTS.SYMBOLS_PATH_URL) + + resp = { + "data": [ + { + "symbol": self.trading_pair, + "name": self.trading_pair, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "enableTrading": True, + }, + { + "symbol": "SOME-PAIR", + "name": "SOME-PAIR", + "baseCurrency": "SOME", + "quoteCurrency": "PAIR", + "enableTrading": False, + } + ] + } + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertEqual(1, len(ret)) + self.assertIn(self.trading_pair, ret) + self.assertNotIn("SOME-PAIR", ret) + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + + url = web_utils.public_rest_url(path_url=CONSTANTS.SYMBOLS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs()) + + self.assertEqual(0, len(result)) + + @aioresponses() + def test_get_last_traded_prices(self, mock_api): + map = self.async_run_with_timeout(self.exchange.trading_pair_symbol_map()) + map["TKN1-TKN2"] = "TKN1-TKN2" + self.exchange._set_trading_pair_symbol_map(map) + + url1 = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, + domain=CONSTANTS.DEFAULT_DOMAIN) + url1 = f"{url1}?symbol={self.trading_pair}" + regex_url = re.compile(f"^{url1}".replace(".", r"\.").replace("?", r"\?")) + resp = { + "code": "200000", + "data": { + "sequence": "1550467636704", + "bestAsk": "0.03715004", + "size": "0.17", + "price": "100", + "bestBidSize": "3.803", + "bestBid": "0.03710768", + "bestAskSize": "1.788", + "time": 1550653727731 + } + } + mock_api.get(regex_url, body=json.dumps(resp)) + + url2 = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, + domain=CONSTANTS.DEFAULT_DOMAIN) + url2 = f"{url2}?symbol=TKN1-TKN2" + regex_url = re.compile(f"^{url2}".replace(".", r"\.").replace("?", r"\?")) + resp = { + "code": "200000", + "data": { + "sequence": "1550467636704", + "bestAsk": "0.03715004", + "size": "0.17", + "price": "200", + "bestBidSize": "3.803", + "bestBid": "0.03710768", + "bestAskSize": "1.788", + "time": 1550653727731 + } + } + mock_api.get(regex_url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout( + coroutine=self.exchange.get_last_traded_prices([self.trading_pair, "TKN1-TKN2"]) + ) + + ticker_requests = [(key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url1) or key[1].human_repr().startswith(url2)] + + request_params = ticker_requests[0][1][0].kwargs["params"] + self.assertEqual(f"{self.base_asset}-{self.quote_asset}", request_params["symbol"]) + request_params = ticker_requests[1][1][0].kwargs["params"] + self.assertEqual("TKN1-TKN2", request_params["symbol"]) + + self.assertEqual(ret[self.trading_pair], 100) + self.assertEqual(ret["TKN1-TKN2"], 200) + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertIn(OrderType.MARKET, supported_types) + self.assertIn(OrderType.LIMIT, supported_types) + self.assertIn(OrderType.LIMIT_MAKER, supported_types) + + @aioresponses() + def test_check_network_success(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + resp = { + "code": "200000", + "msg": "success", + "data": 1640001112223 + } + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, ret) + + @aioresponses() + def test_check_network_failure(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + mock_api.get(url, status=500) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + + mock_api.get(url, exception=asyncio.CancelledError) + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = web_utils.public_rest_url(CONSTANTS.SYMBOLS_PATH_URL) + resp = self.get_exchange_rules_mock() + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertTrue(self.trading_pair in self.exchange._trading_rules) + + @aioresponses() + def test_update_trading_rules_ignores_rule_with_error(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = web_utils.public_rest_url(CONSTANTS.SYMBOLS_PATH_URL) + resp = { + "code": "200000", + "data": [ + { + "symbol": self.trading_pair, + "name": self.trading_pair, + "baseCurrency": self.base_asset, + "quoteCurrency": self.quote_asset, + "enableTrading": True, + }, + ], + } + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange._trading_rules)) + self.assertTrue( + self._is_logged("ERROR", f"Error parsing the trading pair rule {resp['data'][0]}. Skipping.") + ) + + @aioresponses() + def test_get_fee_returns_fee_from_exchange_if_available_and_default_if_not(self, mocked_api): + url = web_utils.public_rest_url(CONSTANTS.FEE_PATH_URL) + regex_url = re.compile(f"^{url}") + resp = {"data": [ + {"symbol": self.trading_pair, + "makerFeeRate": "0.002", + "takerFeeRate": "0.002"}]} + mocked_api.get(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.002"), fee.percent) + + fee = self.exchange.get_fee( + base_currency="SOME", + quote_currency="OTHER", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.001"), fee.percent) # default fee + + @aioresponses() + def test_fee_request_for_multiple_pairs(self, mocked_api): + self.exchange = KucoinExchange( + self.client_config_map, + self.api_key, + self.api_passphrase, + self.api_secret_key, + trading_pairs=[self.trading_pair, "BTC-USDT"] + ) + + self.exchange._set_trading_pair_symbol_map( + bidict({ + self.trading_pair: self.trading_pair, + "BTC-USDT": "BTC-USDT"})) + + url = web_utils.public_rest_url(CONSTANTS.FEE_PATH_URL) + regex_url = re.compile(f"^{url}") + resp = {"data": [ + {"symbol": self.trading_pair, + "makerFeeRate": "0.002", + "takerFeeRate": "0.002"}, + {"symbol": "BTC-USDT", + "makerFeeRate": "0.01", + "takerFeeRate": "0.01"}, + ]} + mocked_api.get(regex_url, body=json.dumps(resp)) + + self.async_run_with_timeout(self.exchange._update_trading_fees()) + + order_request = next(((key, value) for key, value in mocked_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_params = order_request[1][0].kwargs["params"] + + self.assertEqual(f"{self.exchange_trading_pair},BTC-USDT", request_params["symbols"]) + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.002"), fee.percent) + + fee = self.exchange.get_fee( + base_currency="BTC", + quote_currency="USDT", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.01"), fee.percent) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 9 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, trading_pair=self.trading_pair + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, trading_pair=self.trading_pair + ) + + self.assertEqual(result, expected_client_order_id) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.exchange.in_flight_orders) + self.assertNotIn("OID2", self.exchange.in_flight_orders) + self.assertNotIn("OID3", self.exchange.in_flight_orders) + self.assertNotIn("OID4", self.exchange.in_flight_orders) + + @aioresponses() + def test_create_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + creation_response = { + "data": { + "orderId": "5bd6e9286d99522a52e458de" + }} + + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("limit", request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["size"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual("OID1", request_data["clientOid"]) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["data"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created LIMIT BUY order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_limit_maker_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + creation_response = { + "data": { + "orderId": "5bd6e9286d99522a52e458de" + }} + + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT_MAKER, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(TradeType.BUY.name.lower(), request_data["side"]) + self.assertEqual("limit", request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["size"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual("OID1", request_data["clientOid"]) + self.assertTrue(request_data["postOnly"]) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT_MAKER, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["data"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created LIMIT_MAKER BUY order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.kucoin.kucoin_exchange.KucoinExchange.get_price") + def test_create_market_order_successfully(self, mock_api, get_price_mock): + get_price_mock.return_value = Decimal(1000) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + creation_response = { + "data": { + "orderId": "5bd6e9286d99522a52e458de" + }} + + mock_api.post(regex_url, + body=json.dumps(creation_response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.SELL, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.MARKET)) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + request_data = json.loads(order_request[1][0].kwargs["data"]) + self.assertEqual(self.exchange_trading_pair, request_data["symbol"]) + self.assertEqual(TradeType.SELL.name.lower(), request_data["side"]) + self.assertEqual("market", request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["size"])) + self.assertEqual("OID1", request_data["clientOid"]) + self.assertNotIn("price", request_data) + + self.assertIn("OID1", self.exchange.in_flight_orders) + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertIsNone(create_event.price) + self.assertEqual("OID1", create_event.order_id) + self.assertEqual(creation_response["data"]["orderId"], create_event.exchange_order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created MARKET SELL order OID1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertNotIn("OID1", self.exchange.in_flight_orders) + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("OID1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Order OID1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + "client_order_id='OID1', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID1", + trading_pair=self.trading_pair, + amount=Decimal("0.0001"), + order_type=OrderType.LIMIT, + price=Decimal("0.0001"))) + # The second order is used only to have the event triggered and avoid using timeouts for tests + asyncio.get_event_loop().create_task( + self.exchange._create_order(trade_type=TradeType.BUY, + order_id="OID2", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"))) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn("OID1", self.exchange.in_flight_orders) + self.assertEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("OID1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self._is_logged( + "INFO", + f"Order OID1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + "client_order_id='OID1', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "data": {"cancelledOrderIds": [order.exchange_order_id]} + } + + mock_api.delete(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="OID1") + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(cancel_request[1][0]) + request_params = cancel_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, + status=400, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="OID1") + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + self._validate_auth_credentials_present(cancel_request[1][0]) + + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue( + self._is_logged( + "ERROR", + f"Failed to cancel order {order.client_order_id}" + ) + ) + + def test_cancel_order_without_exchange_order_id_marks_order_as_fail_after_retries(self): + update_event = MagicMock() + update_event.wait.side_effect = asyncio.TimeoutError + + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id=None, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["OID1"] + order.exchange_order_id_update_event = update_event + + self.async_run_with_timeout(self.exchange._execute_cancel( + trading_pair=order.trading_pair, + order_id=order.client_order_id, + )) + + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue( + self._is_logged( + "WARNING", + f"Failed to cancel the order {order.client_order_id} because it does not have an exchange order id yet" + ) + ) + + # After the fourth time not finding the exchange order id the order should be marked as failed + for i in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout(self.exchange._execute_cancel( + trading_pair=order.trading_pair, + order_id=order.client_order_id, + )) + + self.assertTrue(order.is_failure) + + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(order.client_order_id, failure_event.order_id) + self.assertEqual(order.order_type, failure_event.order_type) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["OID1"] + + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("OID2", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["OID2"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order1.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "data": {"cancelledOrderIds": [order1.exchange_order_id]} + } + + mock_api.delete(regex_url, body=json.dumps(response)) + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order2.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.delete(regex_url, status=400) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Successfully canceled order {order1.client_order_id}." + ) + ) + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + seconds_counter_mock.side_effect = [0, 0, 0] + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "code": "200000", + "msg": "success", + "data": 1640000003000 + } + + mock_api.get(regex_url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertEqual(response["data"] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "code": "-1", + "msg": "error" + } + + mock_api.get(regex_url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertTrue(self._is_logged("NETWORK", "Error getting server time.")) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, self.exchange._update_time_synchronizer()) + + @aioresponses() + def test_update_balances(self, mock_api): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = { + "code": "200000", + "data": [ + { + "id": "5bd6e9286d99522a52e458de", + "currency": "BTC", + "type": "trade", + "balance": "15.0", + "available": "10.0", + "holds": "0" + }, + { + "id": "5bd6e9216d99522a52e458d6", + "currency": "LTC", + "type": "trade", + "balance": "2000", + "available": "2000", + "holds": "0" + }] + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("10"), available_balances["BTC"]) + self.assertEqual(Decimal("2000"), available_balances["LTC"]) + self.assertEqual(Decimal("15"), total_balances["BTC"]) + self.assertEqual(Decimal("2000"), total_balances["LTC"]) + + response = { + "code": "200000", + "data": [ + { + "id": "5bd6e9286d99522a52e458de", + "currency": "BTC", + "type": "trade", + "balance": "15.0", + "available": "10.0", + "holds": "0" + }] + } + + mock_api.get(regex_url, body=json.dumps(response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn("LTC", available_balances) + self.assertNotIn("LTC", total_balances) + self.assertEqual(Decimal("10"), available_balances["BTC"]) + self.assertEqual(Decimal("15"), total_balances["BTC"]) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "code": "200000", + "data": { + "id": order.exchange_order_id, + "symbol": self.trading_pair, + "opType": "DEAL", + "type": "limit", + "side": order.trade_type.name.lower(), + "price": "10000", + "size": "1", + "funds": "0", + "dealFunds": "0.166", + "dealSize": "1", + "fee": "0", + "feeCurrency": self.quote_asset, + "stp": "", + "stop": "", + "stopTriggered": False, + "stopPrice": "0", + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "visibleSize": "0", + "cancelAfter": 0, + "channel": "IOS", + "clientOid": "", + "remark": "", + "tags": "", + "isActive": False, + "cancelExist": False, + "createdAt": 1547026471000, + "tradeType": "TRADE" + } + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + # Simulate the order has been filled with a TradeUpdate + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = order_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_update_order_status_when_cancelled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "code": "200000", + "data": { + "id": order.exchange_order_id, + "symbol": self.trading_pair, + "opType": "CANCEL", + "type": "limit", + "side": order.trade_type.name.lower(), + "price": "10000", + "size": "1", + "funds": "0", + "dealFunds": "0.166", + "dealSize": "1", + "fee": "0", + "feeCurrency": self.quote_asset, + "stp": "", + "stop": "", + "stopTriggered": False, + "stopPrice": "0", + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "visibleSize": "0", + "cancelAfter": 0, + "channel": "IOS", + "clientOid": "", + "remark": "", + "tags": "", + "isActive": False, + "cancelExist": True, + "createdAt": 1547026471000, + "tradeType": "TRADE" + } + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = order_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "code": "200000", + "data": { + "id": order.exchange_order_id, + "symbol": self.trading_pair, + "opType": "DEAL", + "type": "limit", + "side": order.trade_type.name.lower(), + "price": "10000", + "size": "1", + "funds": "0", + "dealFunds": "0.166", + "dealSize": "1", + "fee": "0", + "feeCurrency": self.quote_asset, + "stp": "", + "stop": "", + "stopTriggered": False, + "stopPrice": "0", + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "visibleSize": "0", + "cancelAfter": 0, + "channel": "IOS", + "clientOid": "", + "remark": "", + "tags": "", + "isActive": True, + "cancelExist": False, + "createdAt": 1547026471000, + "tradeType": "TRADE" + } + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.assertTrue(order.is_open) + + list_updates = self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = order_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + self._validate_auth_credentials_present(order_request[1][0]) + self.assertIsNone(list_updates) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + # ---- Testing the _update_orders_fills() method overwritten from the ExchangePyBase + def test__update_orders_fills_raises_asyncio(self): + orders: List[InFlightOrder] = [InFlightOrder(client_order_id="COID1-1", + exchange_order_id="EOID1-1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + creation_timestamp=1234567890, + )] + + # Simulate the order has been filled with a TradeUpdate + self.assertEqual(0., self.exchange._last_order_fill_ts_s) + + with patch.object(ClientOrderTracker, "process_trade_update") as mock_tracker: + with patch.object(KucoinExchange, "_all_trades_updates") as mock_updates: + mock_updates.side_effect = [asyncio.CancelledError] + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.exchange._update_orders_fills(orders)) + mock_tracker.assert_not_called() + + self.assertEqual(0, self.exchange._last_order_fill_ts_s) + + def test__update_orders_fills_calls_on_orders(self): + orders: List[InFlightOrder] = [InFlightOrder(client_order_id="COID1-1", + exchange_order_id="EOID1-1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + creation_timestamp=1234567890, + ), + InFlightOrder(client_order_id="COID1-2", + exchange_order_id="EOID1-2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + creation_timestamp=1234567890, + ) + ] + fee: TradeFeeBase = TradeFeeBase.new_spot_fee( + fee_schema=TradeFeeSchema(), + trade_type=TradeType.BUY, + percent_token="USDT", + flat_fees=[TokenAmount(amount=Decimal("0"), token="USDT")] + ) + trade: TradeUpdate = TradeUpdate(client_order_id="COID1-1", + exchange_order_id="EOID1-1", + trading_pair=self.trading_pair, + trade_id="0", + fill_timestamp=1234567890, + fill_price=Decimal("0"), + fill_base_amount=Decimal("0"), + fill_quote_amount=Decimal("0"), + fee=fee) + # Simulate the order has been filled with a TradeUpdate + self.assertEqual(0., self.exchange._last_order_fill_ts_s) + + with patch.object(ClientOrderTracker, "process_trade_update") as mock_tracker: + with patch.object(KucoinExchange, "_all_trades_updates") as mock_updates: + mock_updates.side_effect = [[trade]] + self.async_run_with_timeout(self.exchange._update_orders_fills(orders)) + mock_tracker.assert_called_with(trade) + mock_updates.assert_called_once_with(orders) + + def test__update_orders_fills_handles_exception(self): + orders: List[InFlightOrder] = [InFlightOrder(client_order_id="COID1-1", + exchange_order_id="EOID1-1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + creation_timestamp=1234567890, + )] + + with patch.object(ClientOrderTracker, "process_trade_update") as mock_tracker: + with patch.object(KucoinExchange, "_all_trades_updates") as mock_updates: + mock_updates.side_effect = [Exception("test")] + self.async_run_with_timeout(self.exchange._update_orders_fills(orders)) + # Empty trade updates due to exception + mock_tracker.assert_not_called() + + print(self.log_records) + self.assertTrue(self._is_logged("WARNING", "Failed to fetch trade updates. Error: test")) + + @aioresponses() + def test__all_trades_updates_empty_orders(self, mock_api): + orders: List[InFlightOrder] = [] + + # Simulate the order has been filled with a TradeUpdate + # Updating with only the oldest order(fee called once) + self.assertEqual(0., self.exchange._last_order_fill_ts_s) + with patch.object(TradeFeeBase, "new_spot_fee") as mock_fee: + trades = self.async_run_with_timeout(self.exchange._all_trades_updates(orders)) + mock_fee.assert_not_called() + self.assertEqual(0, self.exchange._last_order_fill_ts_s) + + self.assertEqual(0, len(trades)) + + def test__update_orders_fills_empty_orders(self): + orders: List[InFlightOrder] = [] + + with patch.object(ClientOrderTracker, "process_trade_update") as mock_tracker: + mock_tracker.return_value = None + with patch.object(KucoinExchange, "_all_trades_updates") as mock_exception: + mock_exception.side_effect = [asyncio.CancelledError, Exception] + with patch.object(TradeFeeBase, "new_spot_fee") as mock_fee: + self.async_run_with_timeout(self.exchange._update_orders_fills(orders)) + mock_fee.assert_not_called() + mock_exception.assert_not_called() + mock_tracker.assert_not_called() + + self.assertEqual(0, self.exchange._last_order_fill_ts_s) + + @aioresponses() + def test__all_trades_updates_last_fill(self, mock_api): + orders: List[InFlightOrder] = [] + order_fills_status: Dict = { + "currentPage": 1, + "pageSize": 500, + "totalNum": 251915, + "totalPage": 251915, + "items": []} + base_amount = Decimal("0.8424304") + quote_amount = Decimal("0.0699217232") + for i in range(5): + self.exchange._set_current_timestamp(1640780000 + i) + self.exchange.start_tracking_order( + order_id=f"OID1-{i}", + exchange_order_id=f"EOID1-{i}", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders.append(self.exchange.in_flight_orders[f"OID1-{i}"]) + + order_fills_status["items"].append({ + "symbol": self.trading_pair, + "tradeId": f"5c35c02709e4f67d5266954e-{i}", # trade id + "orderId": orders[-1].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[-1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": str(base_amount), # order quantity + "funds": str(quote_amount), # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[-1].creation_timestamp * 1000, + "tradeType": "TRADE" + }) + + mock_response = order_fills_status + for i in range(5): + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int((1640780004 - i) * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url_fills, body=json.dumps(mock_response), repeat=True) + + # Simulate the order has been filled with a TradeUpdate + # Updating with only the oldest order(fee called once) + self.assertEqual(0., self.exchange._last_order_fill_ts_s) + with patch.object(TradeFeeBase, "new_spot_fee") as mock_fee: + trades = self.async_run_with_timeout(self.exchange._all_trades_updates([orders[0]])) + mock_fee.assert_called_once() + self.assertEqual(1640780000.0, self.exchange._last_order_fill_ts_s) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith( + web_utils.private_rest_url(f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt=")))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual({'pageSize': 500, 'startAt': 1640780000000}, request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertEqual(1, len(trades)) + + # Updating with only the second-oldest order(fee called once) + mock_api.requests = {} + with patch.object(TradeFeeBase, "new_spot_fee") as mock_fee: + trades = self.async_run_with_timeout(self.exchange._all_trades_updates([orders[3]])) + mock_fee.assert_called_once() + self.assertEqual(1640780003.0, self.exchange._last_order_fill_ts_s) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith( + web_utils.private_rest_url(f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt=")))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual({'pageSize': 500, 'startAt': 1640780003000}, request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertEqual(1, len(trades)) + + # Updating with 5 orders (fee called 5 times) + mock_api.requests = {} + with patch.object(TradeFeeBase, "new_spot_fee") as mock_fee: + trades = self.async_run_with_timeout(self.exchange._all_trades_updates(orders)) + mock_fee.assert_called() + self.assertEqual(1640780004.0, self.exchange._last_order_fill_ts_s) + + self.assertEqual(5, len(trades)) + + @aioresponses() + def test_update_order_status_when_filled_using_fills(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders: List[InFlightOrder] = [self.exchange.in_flight_orders["OID1"], + self.exchange.in_flight_orders["OID2"]] + + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int(orders[0].creation_timestamp * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{orders[0].exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + base_amount = Decimal("0.8424304") + quote_amount = Decimal("0.0699217232") + order_fills_status = { + "currentPage": 1, + "pageSize": 1, + "totalNum": 251915, + "totalPage": 251915, + "items": [ + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[0].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": str(base_amount), # order quantity + "funds": str(quote_amount), # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": str(base_amount), # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[1].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": str(base_amount), # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": "XCAD-HBOT", + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67dxxxxxxxx", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + ] + } + order_status = { + "code": "200000", + "data": { + "id": orders[0].exchange_order_id, + "symbol": self.trading_pair, + "opType": "DEAL", + "type": "limit", + "side": orders[0].trade_type.name.lower(), + "price": "10000", + "size": "1", + "funds": "0", + "dealFunds": "0.166", + "dealSize": "1", + "fee": "0", + "feeCurrency": self.quote_asset, + "stp": "", + "stop": "", + "stopTriggered": False, + "stopPrice": "0", + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "visibleSize": "0", + "cancelAfter": 0, + "channel": "IOS", + "clientOid": "", + "remark": "", + "tags": "", + "isActive": False, + "cancelExist": False, + "createdAt": 1547026471000, + "tradeType": "TRADE" + } + } + + mock_response = order_fills_status + mock_api.get(regex_url_fills, body=json.dumps(mock_response)) + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + # Simulate the order has been filled with a TradeUpdate + orders[0].completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + self.async_run_with_timeout(orders[0].wait_until_completely_filled()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = order_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(orders[0].is_filled) + self.assertTrue(orders[0].is_done) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(orders[0].client_order_id, buy_event.order_id) + self.assertEqual(orders[0].base_asset, buy_event.base_asset) + self.assertEqual(orders[0].quote_asset, buy_event.quote_asset) + self.assertEqual(base_amount, buy_event.base_asset_amount) + self.assertEqual(quote_amount, buy_event.quote_asset_amount) + self.assertEqual(orders[0].order_type, buy_event.order_type) + self.assertEqual(orders[0].exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(orders[0].client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {orders[0].client_order_id} completely filled." + ) + ) + + @aioresponses() + def test_update_order_status_when_cancelled_using_fills(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders: List[InFlightOrder] = [self.exchange.in_flight_orders["OID1"], + self.exchange.in_flight_orders["OID2"]] + + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int(orders[0].creation_timestamp * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{orders[0].exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_fills_status = { + "currentPage": 1, + "pageSize": 1, + "totalNum": 251915, + "totalPage": 251915, + "items": [ + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[0].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[1].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": "XCAD-HBOT", + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67dxxxxxxxx", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + ] + } + order_status = { + "code": "200000", + "data": { + "id": orders[0].exchange_order_id, + "symbol": self.trading_pair, + "opType": "CANCEL", + "type": "limit", + "side": orders[0].trade_type.name.lower(), + "price": "10000", + "size": "1", + "funds": "0", + "dealFunds": "0.166", + "dealSize": "1", + "fee": "0", + "feeCurrency": self.quote_asset, + "stp": "", + "stop": "", + "stopTriggered": False, + "stopPrice": "0", + "timeInForce": "GTC", + "postOnly": False, + "hidden": False, + "iceberg": False, + "visibleSize": "0", + "cancelAfter": 0, + "channel": "IOS", + "clientOid": "", + "remark": "", + "tags": "", + "isActive": False, + "cancelExist": True, + "createdAt": 1547026471000, + "tradeType": "TRADE" + } + } + + mock_response = order_fills_status + mock_api.get(regex_url_fills, body=json.dumps(mock_response)) + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url_fills))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual({'pageSize': 500, 'startAt': 1640780000000}, request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(orders[0].client_order_id, cancel_event.order_id) + self.assertEqual(orders[0].exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(orders[0].client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {orders[0].client_order_id}.") + ) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed_using_fills(self, mock_api): + self.exchange._set_current_timestamp(1640780000) # Seconds + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders: List[InFlightOrder] = [self.exchange.in_flight_orders["OID1"], + self.exchange.in_flight_orders["OID2"]] + + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int(orders[0].creation_timestamp * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + + order_fills_status = { + "currentPage": 1, + "pageSize": 1, + "totalNum": 251915, + "totalPage": 251915, + "items": [ + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[0].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": orders[1].exchange_order_id, + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": self.trading_pair, + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67d5266954e", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[1].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[1].creation_timestamp, + "tradeType": "TRADE" + }, + { + "symbol": "XCAD-HBOT", + "tradeId": "5c35c02709e4f67d5266954e", # trade id + "orderId": "5c35c02709e4f67dxxxxxxxx", + "counterOrderId": "5c1ab46003aa676e487fa8e3", # counter order id + "side": orders[0].trade_type.name.lower(), + "liquidity": "taker", # include taker and maker + "forceTaker": True, # forced to become taker + "price": "0.083", # order price + "size": "0.8424304", # order quantity + "funds": "0.0699217232", # order funds + "fee": "0", # fee + "feeRate": "0", # fee rate + "feeCurrency": self.quote_asset, + "stop": "", # stop type + "type": "limit", # order type,e.g. limit,market,stop_limit. + "createdAt": orders[0].creation_timestamp, + "tradeType": "TRADE" + }, + ] + } + + mock_response = order_fills_status + mock_api.get(regex_url_fills, body=json.dumps(mock_response)) + + self.assertTrue(orders[0].is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url_fills))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual({'pageSize': 500, 'startAt': 1640780000000}, request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(orders[0].is_open) + self.assertFalse(orders[0].is_filled) + self.assertFalse(orders[0].is_done) + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found_using_fills(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders: List[InFlightOrder] = [self.exchange.in_flight_orders["OID1"], + self.exchange.in_flight_orders["OID2"]] + + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int(orders[0].creation_timestamp * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url_fills, status=404) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url_fills))) + request_params = order_request[1][0].kwargs["params"] + self.assertEqual({'pageSize': 500, 'startAt': 1640780000000}, request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(orders[0].is_open) + self.assertFalse(orders[0].is_filled) + self.assertFalse(orders[0].is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[orders[0].client_order_id]) + + @aioresponses() + def test_update_order_status_marks_order_with_no_exchange_id_as_not_found_using_fills(self, mock_api): + update_event = MagicMock() + update_event.wait.side_effect = asyncio.TimeoutError + + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + self.exchange.start_tracking_order( + order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + orders: List[InFlightOrder] = [self.exchange.in_flight_orders["OID1"], + self.exchange.in_flight_orders["OID2"]] + orders[0].exchange_order_id_update_event = update_event + + url_fills = web_utils.private_rest_url( + f"{CONSTANTS.FILLS_PATH_URL}?pageSize=500&startAt={int(orders[0].creation_timestamp * 1000)}") + regex_url_fills = re.compile(f"^{url_fills}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url_fills, status=404) + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(orders[0].is_open) + self.assertFalse(orders[0].is_filled) + self.assertFalse(orders[0].is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[orders[0].client_order_id]) + + # ---- End of testing for _update_orders_fills() method overwritten from the ExchangePyBase + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(f"{CONSTANTS.ORDERS_PATH_URL}/{order.exchange_order_id}") + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=404) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + order_request = next(((key, value) for key, value in mock_api.requests.items() + if key[1].human_repr().startswith(url))) + request_params = order_request[1][0].kwargs["params"] + self.assertIsNone(request_params) + self._validate_auth_credentials_present(order_request[1][0]) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + def test_update_order_status_marks_order_with_no_exchange_id_as_not_found(self): + update_event = MagicMock() + update_event.wait.side_effect = asyncio.TimeoutError + + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id=None, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders["OID1"] + order.exchange_order_id_update_event = update_event + + self.async_run_with_timeout(self.exchange._update_order_status(), timeout=2) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + def test_user_stream_update_for_new_order_does_not_update_status(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "symbol": order.trading_pair, + "orderType": "limit", + "side": order.trade_type.name.lower(), + "orderId": order.exchange_order_id, + "type": "open", + "orderTime": 1593487481683297666, + "size": "1", + "filledSize": "0", + "price": "10000.0", + "clientOid": order.client_order_id, + "remainSize": "1", + "status": "open", + "ts": 1593487481683297666 + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + self.assertTrue( + self._is_logged( + "INFO", + f"Created {order.order_type.name.upper()} {order.trade_type.name.upper()} order " + f"{order.client_order_id} for {order.amount} {order.trading_pair}." + ) + ) + + def test_user_stream_update_for_cancelled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "symbol": order.trading_pair, + "orderType": "limit", + "side": order.trade_type.name.lower(), + "orderId": order.exchange_order_id, + "type": "canceled", + "orderTime": 1593487481683297666, + "size": "1.0", + "filledSize": "0", + "price": "10000.0", + "clientOid": order.client_order_id, + "remainSize": "0", + "status": "done", + "ts": 1593487481893140844 + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_user_stream_update_for_order_partial_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + event_message = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "symbol": order.trading_pair, + "orderType": "limit", + "side": order.trade_type.name.lower(), + "orderId": order.exchange_order_id, + "liquidity": "taker", + "type": "match", + "orderTime": 1593487482038606180, + "size": "1", + "filledSize": "0.1", + "price": "10000", + "matchPrice": "10010.5", + "matchSize": "0.1", + "tradeId": "5efab07a4ee4c7000a82d6d9", + "clientOid": order.client_order_id, + "remainSize": "0.9", + "status": "match", + "ts": 1593487482038606180 + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(event_message["data"]["matchPrice"]), fill_event.price) + self.assertEqual(Decimal(event_message["data"]["matchSize"]), fill_event.amount) + expected_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=order.order_type, + order_side=order.trade_type, + amount=Decimal(event_message["data"]["matchSize"]), + price=Decimal(event_message["data"]["matchPrice"]), + is_maker=False, + ) + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + self.assertTrue( + self._is_logged("INFO", f"The {order.trade_type.name} order {order.client_order_id} amounting to " + f"0.1/{order.amount} {order.base_asset} has been filled.") + ) + + def test_user_stream_update_for_order_fill(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + match_event = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + "symbol": order.trading_pair, + "orderType": "limit", + "side": order.trade_type.name.lower(), + "orderId": order.exchange_order_id, + "liquidity": "taker", + "type": "match", + "orderTime": 1593487482038606180, + "size": "1", + "filledSize": "1", + "price": "10000", + "matchPrice": "10010.5", + "matchSize": "1", + "tradeId": "5efab07a4ee4c7000a82d6d9", + "clientOid": order.client_order_id, + "remainSize": "0", + "status": "match", + "ts": 1593487482038606180 + } + } + + filled_event = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + "data": { + + "symbol": order.trading_pair, + "orderType": "limit", + "side": order.trade_type.name.lower(), + "orderId": order.exchange_order_id, + "type": "filled", + "orderTime": 1593487482038606180, + "size": "1", + "filledSize": "1", + "price": "10000", + "clientOid": order.client_order_id, + "remainSize": "0", + "status": "done", + "ts": 1593487482038606180 + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [match_event, filled_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + match_price = Decimal(match_event["data"]["matchPrice"]) + match_size = Decimal(match_event["data"]["matchSize"]) + self.assertEqual(match_price, fill_event.price) + self.assertEqual(match_size, fill_event.amount) + expected_fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=order.order_type, + order_side=order.trade_type, + amount=match_size, + price=match_price, + is_maker=False, + ) + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * match_price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self._is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_balance_update(self): + self.exchange._set_current_timestamp(1640780000) + + event_message = { + "type": "message", + "topic": "/account/balance", + "subject": "account.balance", + "channelType": "private", + "data": { + "total": "10500", + "available": "10000", + "availableChange": "0", + "currency": self.base_asset, + "hold": "0", + "holdChange": "0", + "relationEvent": "trade.setted", + "relationEventId": "5c21e80303aa677bd09d7dff", + "relationContext": { + "symbol": self.trading_pair, + "tradeId": "5e6a5dca9e16882a7d83b7a4", + "orderId": "5ea10479415e2f0009949d54" + }, + "time": "1545743136994" + } + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10000"), self.exchange.available_balances["COINALPHA"]) + self.assertEqual(Decimal("10500"), self.exchange.get_balance("COINALPHA")) + + def test_user_stream_raises_cancel_exception(self): + self.exchange._set_current_timestamp(1640780000) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._user_stream_tracker._user_stream = mock_queue + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, + self.exchange._user_stream_event_listener()) + + @patch("hummingbot.connector.exchange.kucoin.kucoin_exchange.KucoinExchange._sleep") + def test_user_stream_logs_errors(self, sleep_mock): + self.exchange._set_current_timestamp(1640780000) + + incomplete_event = { + "type": "message", + "topic": "/spotMarket/tradeOrders", + "subject": "orderChange", + "channelType": "private", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_event, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error in user stream listener loop." + ) + ) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) diff --git a/test/hummingbot/connector/exchange/mexc/__init__.py b/test/hummingbot/connector/exchange/mexc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py new file mode 100644 index 0000000..25360f1 --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py @@ -0,0 +1,415 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source import MexcAPIOrderBookDataSource +from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class MexcAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = MexcExchange( + client_config_map=client_config_map, + mexc_api_key="", + mexc_api_secret="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.data_source = MexcAPIOrderBookDataSource(trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _successfully_subscribed_event(self): + resp = { + "code": None, + "id": 1 + } + return resp + + def _trade_update_event(self): + resp = { + "c": "spot@public.deals.v3.api@BTCUSDT", + "d": { + "deals": [{ + "S": 2, + "p": "0.001", + "t": 1661927587825, + "v": "100"}], + "e": "spot@public.deals.v3.api"}, + "s": self.ex_trading_pair, + "t": 1661927587836 + } + return resp + + def _order_diff_event(self): + resp = { + "c": "spot@public.increase.depth.v3.api@BTCUSDT", + "d": { + "asks": [{ + "p": "0.0026", + "v": "100"}], + "bids": [{ + "p": "0.0024", + "v": "10"}], + "e": "spot@public.increase.depth.v3.api", + "r": "3407459756"}, + "s": self.ex_trading_pair, + "t": 1661932660144 + } + return resp + + def _snapshot_response(self): + resp = { + "lastUpdateId": 1027024, + "bids": [ + [ + "4.00000000", + "431.00000000" + ] + ], + "asks": [ + [ + "4.00000200", + "12.00000000" + ] + ] + } + return resp + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self._snapshot_response() + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = resp["lastUpdateId"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(4, bids[0].price) + self.assertEqual(431, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(4.000002, asks[0].price) + self.assertEqual(12, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "code": None, + "id": 1 + } + result_subscribe_diffs = { + "code": None, + "id": 2 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "method": "SUBSCRIPTION", + "params": [f"spot@public.deals.v3.api@{self.ex_trading_pair}"], + "id": 1} + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "method": "SUBSCRIPTION", + "params": [f"spot@public.increase.depth.v3.api@{self.ex_trading_pair}"], + "id": 2} + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1661927587825, msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self._order_diff_event() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(diff_event["d"]["r"]), msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError, repeat=True) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.mexc.mexc_api_order_book_data_source" + ".MexcAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception, repeat=True) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api, ): + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1027024, msg.update_id) diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py b/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py new file mode 100644 index 0000000..3f543d9 --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_auth.py @@ -0,0 +1,51 @@ +import asyncio +import hashlib +import hmac +from copy import copy +from unittest import TestCase +from unittest.mock import MagicMock + +from typing_extensions import Awaitable + +from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + + +class MexcAuthTests(TestCase): + + def setUp(self) -> None: + self._api_key = "testApiKey" + self._secret = "testSecret" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_authenticate(self): + now = 1234567890.000 + mock_time_provider = MagicMock() + mock_time_provider.time.return_value = now + + params = { + "symbol": "LTCBTC", + "side": "BUY", + "type": "LIMIT", + "timeInForce": "GTC", + "quantity": 1, + "price": "0.1", + } + full_params = copy(params) + + auth = MexcAuth(api_key=self._api_key, secret_key=self._secret, time_provider=mock_time_provider) + request = RESTRequest(method=RESTMethod.GET, params=params, is_auth_required=True) + configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) + + full_params.update({"timestamp": 1234567890000}) + encoded_params = "&".join([f"{key}={value}" for key, value in full_params.items()]) + expected_signature = hmac.new( + self._secret.encode("utf-8"), + encoded_params.encode("utf-8"), + hashlib.sha256).hexdigest() + self.assertEqual(now * 1e3, configured_request.params["timestamp"]) + self.assertEqual(expected_signature, configured_request.params["signature"]) + self.assertEqual({"X-MEXC-APIKEY": self._api_key}, configured_request.headers) diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py new file mode 100644 index 0000000..f7947ff --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py @@ -0,0 +1,1279 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import MarketOrderFailureEvent, OrderFilledEvent + + +class MexcExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=self.exchange._domain) + url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" + return url + + @property + def network_status_url(self): + url = web_utils.private_rest_url(CONSTANTS.PING_PATH_URL, domain=self.exchange._domain) + return url + + @property + def trading_rules_url(self): + url = web_utils.private_rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + return url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL, domain=self.exchange._domain) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL, domain=self.exchange._domain) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "ENABLED", + "baseAsset": self.base_asset, + "baseSizePrecision": 1e-8, + "quotePrecision": 8, + "baseAssetPrecision": 8, + "quoteAmountPrecision": 8, + "quoteAsset": self.quote_asset, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "SPOT", + "MARGIN" + ] + }, + ] + } + + @property + def latest_prices_request_mock_response(self): + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "priceChange": "-94.99999800", + "priceChangePercent": "-95.960", + "weightedAvgPrice": "0.29628482", + "prevClosePrice": "0.10002000", + "lastPrice": str(self.expected_latest_price), + "lastQty": "200.00000000", + "bidPrice": "4.00000000", + "bidQty": "100.00000000", + "askPrice": "4.00000200", + "askQty": "100.00000000", + "openPrice": "99.00000000", + "highPrice": "100.00000000", + "lowPrice": "0.10000000", + "volume": "8913.30000000", + "quoteVolume": "15.30000000", + "openTime": 1499783499040, + "closeTime": 1499869899040, + "firstId": 28385, + "lastId": 28460, + "count": 76, + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "ENABLED", + "baseAsset": self.base_asset, + "baseSizePrecision": 1e-8, + "quotePrecision": 8, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteAmountPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + { + "symbol": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "status": "ENABLED", + "baseAsset": "INVALID", + "baseSizePrecision": 1e-8, + "quotePrecision": 8, + "baseAssetPrecision": 8, + "quoteAmountPrecision": 8, + "quoteAsset": "PAIR", + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + ] + } + + return "INVALID-PAIR", response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1565246363776, + "rateLimits": [{}], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "ENABLED", + "baseAsset": self.base_asset, + "baseSizePrecision": 1e-8, + "quotePrecision": 8, + "baseAssetPrecision": 8, + "quoteAmountPrecision": 8, + "quoteAsset": self.quote_asset, + "quoteAssetPrecision": 8, + "orderTypes": ["LIMIT", "LIMIT_MAKER"], + "icebergAllowed": True, + "ocoAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "200000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00200000" + } + ], + "permissions": [ + "SPOT", + "MARGIN" + ] + } + ] + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "timezone": "UTC", + "serverTime": 1565246363776, + "rateLimits": [{}], + "exchangeFilters": [], + "symbols": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "ENABLED", + "baseAsset": self.base_asset, + "baseAssetPrecision": 8, + "quoteAsset": self.quote_asset, + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "orderTypes": ["LIMIT", "LIMIT_MAKER"], + "icebergAllowed": True, + "ocoAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "permissions": [ + "SPOT", + "MARGIN" + ] + } + ] + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": self.expected_exchange_order_id, + "orderListId": -1, + "clientOrderId": "OID1", + "transactTime": 1507725176595 + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "makerCommission": 15, + "takerCommission": 15, + "buyerCommission": 0, + "sellerCommission": 0, + "canTrade": True, + "canWithdraw": True, + "canDeposit": True, + "updateTime": 123456789, + "accountType": "SPOT", + "balances": [ + { + "asset": self.base_asset, + "free": "10.0", + "locked": "5.0" + }, + { + "asset": self.quote_asset, + "free": "2000", + "locked": "0.00000000" + } + ], + "permissions": [ + "SPOT" + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "makerCommission": 15, + "takerCommission": 15, + "buyerCommission": 0, + "sellerCommission": 0, + "canTrade": True, + "canWithdraw": True, + "canDeposit": True, + "updateTime": 123456789, + "accountType": "SPOT", + "balances": [{"asset": self.base_asset, "free": "10.0", "locked": "5.0"}], + "permissions": ["SPOT"], + } + + @property + def balance_event_websocket_update(self): + return { + "c": "spot@private.account.v3.api", + "d": { + "a": self.base_asset, + "c": 1564034571105, + "f": "10", + "fd": "-4.990689704", + "l": "5", + "ld": "4.990689704", + "o": "ENTRUST_PLACE" + }, + "t": 1564034571073 + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["symbols"][0]["baseSizePrecision"]), + min_price_increment=Decimal( + f'1e-{self.trading_rules_request_mock_response["symbols"][0]["quotePrecision"]}'), + min_base_amount_increment=Decimal( + f'1e-{self.trading_rules_request_mock_response["symbols"][0]["baseAssetPrecision"]}'), + min_notional_size=Decimal(self.trading_rules_request_mock_response["symbols"][0]["quoteAmountPrecision"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["symbols"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 28 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return str(30000) + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return MexcExchange( + client_config_map=client_config_map, + mexc_api_key="testAPIKey", + mexc_api_secret="testSecret", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument( + request_call_tuple=request_call, + params=request_call.kwargs["params"] or request_call.kwargs["data"] + ) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(order.trade_type.name.upper(), request_data["side"]) + self.assertEqual(MexcExchange.mexc_order_type(OrderType.LIMIT), request_data["type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["quantity"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) + self.assertEqual(order.client_order_id, request_data["newClientOrderId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["params"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["symbol"]) + self.assertEqual(order.client_order_id, request_data["origClientOrderId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["symbol"]) + self.assertEqual(order.exchange_order_id, str(request_params["orderId"])) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_api.delete(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.delete(regex_url, status=400, callback=callback) + return url + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2011, "msg": "Unknown order sent."} + mock_api.delete(regex_url, status=400, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2013, "msg": "Order does not exist."} + mock_api.get(regex_url, body=json.dumps(response), status=400, callback=callback) + return [url] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "c": "spot@private.orders.v3.api", + "d": { + "A": 8.0, + "O": 1661938138000, + "S": 1, + "V": 10, + "a": 8, + "c": order.client_order_id, + "i": order.exchange_order_id, + "m": 0, + "o": 1, + "p": order.price, + "s": 1, + "v": order.amount, + "ap": 0, + "cv": 0, + "ca": 0 + }, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "t": 1499405658657 + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "c": "spot@private.orders.v3.api", + "d": { + "A": 8.0, + "O": 1661938138000, + "S": 1, + "V": 10, + "a": 8, + "c": order.client_order_id, + "i": order.exchange_order_id, + "m": 0, + "o": 1, + "p": order.price, + "s": 4, + "v": order.amount, + "ap": 0, + "cv": 0, + "ca": 0 + }, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "t": 1499405658657 + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "c": "spot@private.orders.v3.api", + "d": { + "A": 8.0, + "O": 1661938138000, + "S": 1, + "V": 10, + "a": 8, + "c": order.client_order_id, + "i": order.exchange_order_id, + "m": 0, + "o": 1, + "p": order.price, + "s": 2, + "v": order.amount, + "ap": 0, + "cv": 0, + "ca": 0 + }, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "t": 1499405658657 + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "c": "spot@private.deals.v3.api", + "d": { + "p": order.price, + "v": order.amount, + "a": order.price * order.amount, + "S": 1, + "T": 1678901086198, + "t": "5bbb6ad8b4474570b155610e3960cd", + "c": order.client_order_id, + "i": order.exchange_order_id, + "m": 0, + "st": 0, + "n": Decimal(self.expected_fill_fee.flat_fees[0].amount), + "N": self.quote_asset + }, + "s": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "t": 1661938980285 + } + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + request_sent_event = asyncio.Event() + seconds_counter_mock.side_effect = [0, 0, 0] + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"serverTime": 1640000003000} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertEqual(response["serverTime"] * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + request_sent_event = asyncio.Event() + + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = {"code": -1121, "msg": "Dummy error"} + + mock_api.get(regex_url, + body=json.dumps(response), + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + + self.assertTrue(self.is_logged("NETWORK", "Error getting server time.")) + + @aioresponses() + def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): + url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, + exception=asyncio.CancelledError) + + self.assertRaises( + asyncio.CancelledError, + self.async_run_with_timeout, self.exchange._update_time_synchronizer()) + + @aioresponses() + def test_update_order_fills_from_trades_triggers_filled_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 28457, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": "9999", + "qty": "1", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": self.quote_asset, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + trade_fill_non_tracked_order = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 30000, + "orderId": 99999, + "orderListId": -1, + "price": "4.00000100", + "qty": "12.00000000", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": "BNB", + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + mock_response = [trade_fill, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal(trade_fill["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([TokenAmount(trade_fill["commissionAsset"], Decimal(trade_fill["commission"]))], + fill_event.trade_fee.flat_fees) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[1] + self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) + self.assertEqual("OID99", fill_event.order_id) + self.assertEqual(self.trading_pair, fill_event.trading_pair) + self.assertEqual(TradeType.BUY, fill_event.trade_type) + self.assertEqual(OrderType.LIMIT, fill_event.order_type) + self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount( + trade_fill_non_tracked_order["commissionAsset"], + Decimal(trade_fill_non_tracked_order["commission"]))], + fill_event.trade_fee.flat_fees) + self.assertTrue(self.is_logged( + "INFO", + f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" + )) + + @aioresponses() + def test_update_order_fills_request_parameters(self, mock_api): + self.exchange._set_current_timestamp(0) + self.exchange._last_poll_timestamp = -1 + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = [] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertNotIn("startTime", request_params) + + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + self.exchange._last_trades_poll_mexc_timestamp = 10 + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[1] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertEqual(10 * 1e3, request_params["startTime"]) + + @aioresponses() + def test_update_order_fills_from_trades_with_repeated_fill_triggers_only_one_event(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + trade_fill_non_tracked_order = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "id": 30000, + "orderId": 99999, + "orderListId": -1, + "price": "4.00000100", + "qty": "12.00000000", + "quoteQty": "48.000012", + "commission": "10.10000000", + "commissionAsset": "BNB", + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + + mock_response = [trade_fill_non_tracked_order, trade_fill_non_tracked_order] + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.exchange.add_exchange_order_ids_from_market_recorder( + {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) + + self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) + self.assertEqual("OID99", fill_event.order_id) + self.assertEqual(self.trading_pair, fill_event.trading_pair) + self.assertEqual(TradeType.BUY, fill_event.trade_type) + self.assertEqual(OrderType.LIMIT, fill_event.order_type) + self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) + self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) + self.assertEqual(0.0, fill_event.trade_fee.percent) + self.assertEqual([ + TokenAmount(trade_fill_non_tracked_order["commissionAsset"], + Decimal(trade_fill_non_tracked_order["commission"]))], + fill_event.trade_fee.flat_fees) + self.assertTrue(self.is_logged( + "INFO", + f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" + )) + + @aioresponses() + def test_update_order_status_when_failed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + self.exchange.start_tracking_order( + order_id="OID1", + exchange_order_id="100234", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders["OID1"] + + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + order_status = { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": "10000.0", + "origQty": "1.0", + "executedQty": "0.0", + "cummulativeQuoteQty": "0.0", + "status": "REJECTED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": "10000.000000" + } + + mock_response = order_status + mock_api.get(regex_url, body=json.dumps(mock_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(request) + request_params = request.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) + self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) + + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(order.client_order_id, failure_event.order_id) + self.assertEqual(order.order_type, failure_event.order_type) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}'," + f" update_timestamp={order_status['updateTime'] * 1e-3}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order.client_order_id}', exchange_order_id='{order.exchange_order_id}', " + "misc_updates=None)") + ) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 7 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_ORDER_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + def test_time_synchronizer_related_request_error_detection(self): + exception = IOError("Error executing request POST https://api.mexc.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Timestamp for this request is outside of the recvWindow.'}") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.mexc.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Timestamp for this request was 1000ms ahead of the server's " + "time.'}") + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.mexc.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1022,'msg':'Timestamp for this request was 1000ms ahead of the server's " + "time.'}") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://api.mexc.com/api/v3/order. HTTP status is 400. " + "Error: {'code':-1021,'msg':'Other error.'}") + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + @aioresponses() + def test_place_order_manage_server_overloaded_error_unkown_order(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = {"code": -1003, "msg": "Unknown error, please check your request or try again later."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + o_id, transact_time = self.async_run_with_timeout(self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + self.assertEqual(o_id, "UNKNOWN") + + @aioresponses() + def test_place_order_manage_server_overloaded_error_failure(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - + self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) + + url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response = {"code": -1003, "msg": "Service Unavailable."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + self.assertRaises( + IOError, + self.async_run_with_timeout, + self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + + mock_response = {"code": -1003, "msg": "Internal error; unable to process your request. Please try again."} + mock_api.post(regex_url, body=json.dumps(mock_response), status=503) + + self.assertRaises( + IOError, + self.async_run_with_timeout, + self.exchange._place_order( + order_id="test_order_id", + trading_pair=self.trading_pair, + amount=Decimal("1"), + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("2"), + )) + + def test_format_trading_rules__min_notional_present(self): + trading_rules = [{ + "symbol": "COINALPHAHBOT", + "baseSizePrecision": 1e-8, + "quotePrecision": 8, + "baseAssetPrecision": 8, + "status": "ENABLED", + "quoteAmountPrecision": "0.001", + "orderTypes": ["LIMIT", "MARKET"], + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00300000" + } + ], + "permissions": [ + "SPOT" + ] + }] + exchange_info = {"symbols": trading_rules} + + result = self.async_run_with_timeout(self.exchange._format_trading_rules(exchange_info)) + + self.assertEqual(result[0].min_notional_size, Decimal("0.00100000")) + + def _validate_auth_credentials_taking_parameters_from_argument(self, + request_call_tuple: RequestCall, + params: Dict[str, Any]): + self.assertIn("timestamp", params) + self.assertIn("signature", params) + request_headers = request_call_tuple.kwargs["headers"] + self.assertIn("X-MEXC-APIKEY", request_headers) + self.assertEqual("testAPIKey", request_headers["X-MEXC-APIKEY"]) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "origClientOrderId": order.exchange_order_id or "dummyOrdId", + "orderId": 4, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(Decimal("0")), + "cummulativeQuoteQty": str(Decimal("0")), + "status": "NEW", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY" + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(order.amount), + "cummulativeQuoteQty": str(order.price + Decimal(2)), + "status": "FILLED", + "timeInForce": "GTC", + "type": "LIMIT", + "side": "BUY", + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": "0.0", + "cummulativeQuoteQty": "10000.0", + "status": "CANCELED", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": "0.0", + "cummulativeQuoteQty": "10000.0", + "status": "NEW", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "orderId": order.exchange_order_id, + "orderListId": -1, + "clientOrderId": order.client_order_id, + "price": str(order.price), + "origQty": str(order.amount), + "executedQty": str(order.amount), + "cummulativeQuoteQty": str(self.expected_partial_fill_amount * order.price), + "status": "PARTIALLY_FILLED", + "timeInForce": "GTC", + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "stopPrice": "0.0", + "icebergQty": "0.0", + "time": 1499827319559, + "updateTime": 1499827319559, + "isWorking": True, + "origQuoteOrderQty": str(order.price * order.amount) + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return [ + { + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "id": self.expected_fill_trade_id, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": str(self.expected_partial_fill_price), + "qty": str(self.expected_partial_fill_amount), + "quoteQty": str(self.expected_partial_fill_amount * self.expected_partial_fill_price), + "commission": str(self.expected_fill_fee.flat_fees[0].amount), + "commissionAsset": self.expected_fill_fee.flat_fees[0].token, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + ] + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return [ + { + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "id": self.expected_fill_trade_id, + "orderId": int(order.exchange_order_id), + "orderListId": -1, + "price": str(order.price), + "qty": str(order.amount), + "quoteQty": str(order.amount * order.price), + "commission": str(self.expected_fill_fee.flat_fees[0].amount), + "commissionAsset": self.expected_fill_fee.flat_fees[0].token, + "time": 1499865549590, + "isBuyer": True, + "isMaker": False, + "isBestMatch": True + } + ] diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_order_book.py b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book.py new file mode 100644 index 0000000..2ccf88f --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book.py @@ -0,0 +1,90 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.mexc.mexc_order_book import MexcOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class MexcOrderBookTests(TestCase): + + def test_snapshot_message_from_exchange(self): + snapshot_message = MexcOrderBook.snapshot_message_from_exchange( + msg={ + "lastUpdateId": 1, + "bids": [ + ["4.00000000", "431.00000000"] + ], + "asks": [ + ["4.00000200", "12.00000000"] + ] + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", snapshot_message.trading_pair) + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type) + self.assertEqual(1640000000.0, snapshot_message.timestamp) + self.assertEqual(1, snapshot_message.update_id) + self.assertEqual(-1, snapshot_message.trade_id) + self.assertEqual(1, len(snapshot_message.bids)) + self.assertEqual(4.0, snapshot_message.bids[0].price) + self.assertEqual(431.0, snapshot_message.bids[0].amount) + self.assertEqual(1, snapshot_message.bids[0].update_id) + self.assertEqual(1, len(snapshot_message.asks)) + self.assertEqual(4.000002, snapshot_message.asks[0].price) + self.assertEqual(12.0, snapshot_message.asks[0].amount) + self.assertEqual(1, snapshot_message.asks[0].update_id) + + def test_diff_message_from_exchange(self): + diff_msg = MexcOrderBook.diff_message_from_exchange( + msg={ + "c": "spot@public.increase.depth.v3.api@BTCUSDT", + "d": { + "asks": [{ + "p": "0.0026", + "v": "100"}], + "bids": [{ + "p": "0.0024", + "v": "10"}], + "e": "spot@public.increase.depth.v3.api", + "r": "3407459756"}, + "s": "COINALPHAHBOT", + "t": 1661932660144 + }, + timestamp=1640000000000, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1640000000.0, diff_msg.timestamp) + self.assertEqual(3407459756, diff_msg.update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(0.0024, diff_msg.bids[0].price) + self.assertEqual(10.0, diff_msg.bids[0].amount) + self.assertEqual(3407459756, diff_msg.bids[0].update_id) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(0.0026, diff_msg.asks[0].price) + self.assertEqual(100.0, diff_msg.asks[0].amount) + self.assertEqual(3407459756, diff_msg.asks[0].update_id) + + def test_trade_message_from_exchange(self): + trade_update = { + "S": 2, + "p": "0.001", + "t": 1661927587825, + "v": "100" + } + + trade_message = MexcOrderBook.trade_message_from_exchange( + msg=trade_update, + metadata={"trading_pair": "COINALPHA-HBOT"}, + timestamp=1661927587836 + ) + + self.assertEqual("COINALPHA-HBOT", trade_message.trading_pair) + self.assertEqual(OrderBookMessageType.TRADE, trade_message.type) + self.assertEqual(1661927587.836, trade_message.timestamp) + self.assertEqual(-1, trade_message.update_id) + self.assertEqual(1661927587825, trade_message.trade_id) diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_data_source.py new file mode 100644 index 0000000..adc2b59 --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_data_source.py @@ -0,0 +1,318 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.mexc import mexc_constants as CONSTANTS, mexc_web_utils as web_utils +from hummingbot.connector.exchange.mexc.mexc_api_user_stream_data_source import MexcAPIUserStreamDataSource +from hummingbot.connector.exchange.mexc.mexc_auth import MexcAuth +from hummingbot.connector.exchange.mexc.mexc_exchange import MexcExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class MexcUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "com" + + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = MexcAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET", time_provider=self.mock_time_provider) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = MexcExchange( + client_config_map=client_config_map, + mexc_api_key="", + mexc_api_secret="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = MexcAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _error_response(self) -> Dict[str, Any]: + resp = { + "code": "ERROR CODE", + "msg": "ERROR MESSAGE" + } + + return resp + + def _user_update_event(self): + # Balance Update + resp = { + "c": "spot@private.account.v3.api", + "d": { + "a": "BTC", + "c": 1678185928428, + "f": "302.185113007893322435", + "fd": "-4.990689704", + "l": "4.990689704", + "ld": "4.990689704", + "o": "ENTRUST_PLACE" + }, + "t": 1678185928435 + } + return json.dumps(resp) + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + @aioresponses() + def test_get_listen_key_log_exception(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.post(regex_url, status=400, body=json.dumps(self._error_response())) + + with self.assertRaises(IOError): + self.async_run_with_timeout(self.data_source._get_listen_key()) + + @aioresponses() + def test_get_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + result: str = self.async_run_with_timeout(self.data_source._get_listen_key()) + + self.assertEqual(self.listen_key, result) + + @aioresponses() + def test_ping_listen_key_log_exception(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.put(regex_url, status=400, body=json.dumps(self._error_response())) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + + self.assertTrue(self._is_logged("WARNING", f"Failed to refresh the listen key {self.listen_key}: " + f"{self._error_response()}")) + self.assertFalse(result) + + @aioresponses() + def test_ping_listen_key_successful(self, mock_api): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.put(regex_url, body=json.dumps({})) + + self.data_source._current_listen_key = self.listen_key + result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) + self.assertTrue(result) + + @patch("hummingbot.connector.exchange.mexc.mexc_api_user_stream_data_source.MexcAPIUserStreamDataSource" + "._ping_listen_key", + new_callable=AsyncMock) + def test_manage_listen_key_task_loop_keep_alive_failed(self, mock_ping_listen_key): + mock_ping_listen_key.side_effect = (lambda *args, **kwargs: + self._create_return_value_and_unlock_test_with_event(False)) + + self.data_source._current_listen_key = self.listen_key + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged("ERROR", "Error occurred renewing listen key ...")) + self.assertIsNone(self.data_source._current_listen_key) + self.assertFalse(self.data_source._listen_key_initialized_event.is_set()) + + @patch("hummingbot.connector.exchange.mexc.mexc_api_user_stream_data_source.MexcAPIUserStreamDataSource." + "_ping_listen_key", + new_callable=AsyncMock) + def test_manage_listen_key_task_loop_keep_alive_successful(self, mock_ping_listen_key): + mock_ping_listen_key.side_effect = (lambda *args, **kwargs: + self._create_return_value_and_unlock_test_with_event(True)) + + # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached + self.data_source._current_listen_key = self.listen_key + self.data_source._listen_key_initialized_event.set() + self.data_source._last_listen_key_ping_ts = 0 + + self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged("INFO", f"Refreshed listen key {self.listen_key}.")) + self.assertGreater(self.data_source._last_listen_key_ping_ts, 0) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._user_update_event()) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + msg = self.async_run_with_timeout(msg_queue.get()) + self.assertEqual(json.loads(self._user_update_event()), msg) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, mock_ws): + url = web_utils.private_rest_url(path_url=CONSTANTS.MEXC_USER_STREAM_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_response = { + "listenKey": self.listen_key + } + mock_api.post(regex_url, body=json.dumps(mock_response)) + + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = (lambda *args, **kwargs: + self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR"))) + mock_ws.close.return_value = None + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_utils.py b/test/hummingbot/connector/exchange/mexc/test_mexc_utils.py new file mode 100644 index 0000000..0c8632c --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_utils.py @@ -0,0 +1,44 @@ +import unittest + +from hummingbot.connector.exchange.mexc import mexc_utils as utils + + +class MexcUtilTestCases(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hb_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def test_is_exchange_information_valid(self): + invalid_info_1 = { + "status": "BREAK", + "permissions": ["MARGIN"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_1)) + + invalid_info_2 = { + "status": "BREAK", + "permissions": ["SPOT"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_2)) + + invalid_info_3 = { + "status": "ENABLED", + "permissions": ["MARGIN"], + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_3)) + + invalid_info_4 = { + "status": "ENABLED", + "permissions": ["SPOT"], + } + + self.assertTrue(utils.is_exchange_information_valid(invalid_info_4)) diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_web_utils.py b/test/hummingbot/connector/exchange/mexc/test_mexc_web_utils.py new file mode 100644 index 0000000..51ba434 --- /dev/null +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_web_utils.py @@ -0,0 +1,19 @@ +import unittest + +import hummingbot.connector.exchange.mexc.mexc_constants as CONSTANTS +from hummingbot.connector.exchange.mexc import mexc_web_utils as web_utils + + +class MexcUtilTestCases(unittest.TestCase): + + def test_public_rest_url(self): + path_url = "/TEST_PATH" + domain = "com" + expected_url = CONSTANTS.REST_URL.format(domain) + CONSTANTS.PUBLIC_API_VERSION + path_url + self.assertEqual(expected_url, web_utils.public_rest_url(path_url, domain)) + + def test_private_rest_url(self): + path_url = "/TEST_PATH" + domain = "com" + expected_url = CONSTANTS.REST_URL.format(domain) + CONSTANTS.PRIVATE_API_VERSION + path_url + self.assertEqual(expected_url, web_utils.private_rest_url(path_url, domain)) diff --git a/test/hummingbot/connector/exchange/ndax/__init__.py b/test/hummingbot/connector/exchange/ndax/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_api_order_book_data_source.py b/test/hummingbot/connector/exchange/ndax/test_ndax_api_order_book_data_source.py new file mode 100644 index 0000000..6f7c189 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_api_order_book_data_source.py @@ -0,0 +1,408 @@ +import asyncio +import unittest +from collections import deque +from typing import Any, Awaitable, Dict, List +from unittest.mock import AsyncMock, patch + +import ujson + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source import NdaxAPIOrderBookDataSource +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookEntry +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class NdaxAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.instrument_id = 1 + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.data_source = NdaxAPIOrderBookDataSource(throttler=self.throttler, trading_pairs=[self.trading_pair]) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + self.data_source._trading_pair_id_map.clear() + + self.mocking_assistant = NetworkMockingAssistant() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def simulate_trading_pair_ids_initialized(self): + self.data_source._trading_pair_id_map.update({self.trading_pair: self.instrument_id}) + + def _raise_exception(self, exception_class): + raise exception_class + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _subscribe_level_2_response(self): + resp = { + "m": 1, + "i": 2, + "n": "SubscribeLevel2", + "o": "[[93617617, 1, 1626788175000, 0, 37800.0, 1, 37750.0, 1, 0.015, 0],[93617617, 1, 1626788175000, 0, 37800.0, 1, 37751.0, 1, 0.015, 1]]" + } + return ujson.dumps(resp) + + def _orderbook_update_event(self): + resp = { + "m": 3, + "i": 3, + "n": "Level2UpdateEvent", + "o": "[[93617618, 1, 1626788175001, 0, 37800.0, 1, 37740.0, 1, 0.015, 0]]" + } + return ujson.dumps(resp) + + @patch("aiohttp.ClientSession.get") + def test_init_trading_pair_ids(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + + mock_response: List[Any] = [ + { + "Product1Symbol": self.base_asset, + "Product2Symbol": self.quote_asset, + "InstrumentId": self.instrument_id, + "SessionStatus": "Running" + }, + { + "Product1Symbol": "ANOTHER_ACTIVE", + "Product2Symbol": "MARKET", + "InstrumentId": 2, + "SessionStatus": "Running" + }, + { + "Product1Symbol": "NOT_ACTIVE", + "Product2Symbol": "MARKET", + "InstrumentId": 3, + "SessionStatus": "Stopped" + } + ] + + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + self.ev_loop.run_until_complete(self.data_source.init_trading_pair_ids()) + self.assertEqual(2, len(self.data_source._trading_pair_id_map)) + self.assertEqual(1, self.data_source._trading_pair_id_map[self.trading_pair]) + self.assertEqual(2, self.data_source._trading_pair_id_map["ANOTHER_ACTIVE-MARKET"]) + + @patch("aiohttp.ClientSession.get") + def test_get_last_traded_prices(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + self.simulate_trading_pair_ids_initialized() + mock_response: Dict[Any] = { + "LastTradedPx": 1.0 + } + + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + results = self.ev_loop.run_until_complete( + asyncio.gather(self.data_source.get_last_traded_prices([self.trading_pair]))) + results: Dict[str, Any] = results[0] + + self.assertEqual(results[self.trading_pair], mock_response["LastTradedPx"]) + + @patch("aiohttp.ClientSession.get") + def test_fetch_trading_pairs(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + + self.simulate_trading_pair_ids_initialized() + + mock_response: List[Any] = [ + { + "Product1Symbol": self.base_asset, + "Product2Symbol": self.quote_asset, + "InstrumentId": self.instrument_id, + "SessionStatus": "Running" + }, + { + "Product1Symbol": "ANOTHER_ACTIVE", + "Product2Symbol": "MARKET", + "InstrumentId": 2, + "SessionStatus": "Running" + }, + { + "Product1Symbol": "NOT_ACTIVE", + "Product2Symbol": "MARKET", + "InstrumentId": 3, + "SessionStatus": "Stopped" + } + ] + + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + results: List[str] = self.ev_loop.run_until_complete(self.data_source.fetch_trading_pairs()) + self.assertTrue(self.trading_pair in results) + self.assertTrue("ANOTHER_ACTIVE-MARKET" in results) + self.assertFalse("NOT_ACTIVE-MARKET" in results) + + @patch("aiohttp.ClientSession.get") + def test_fetch_trading_pairs_with_error_status_in_response(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + mock_response = {} + self.mocking_assistant.add_http_response(mock_api, 100, mock_response) + + result = self.ev_loop.run_until_complete(self.data_source.fetch_trading_pairs()) + self.assertEqual(0, len(result)) + + @patch("aiohttp.ClientSession.get") + def test_get_order_book_data(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + self.simulate_trading_pair_ids_initialized() + mock_response: List[List[Any]] = [ + # mdUpdateId, accountId, actionDateTime, actionType, lastTradePrice, orderId, price, productPairCode, quantity, side + [93617617, 1, 1626788175416, 0, 37813.22, 1, 37750.6, 1, 0.014698, 0] + ] + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + results = self.ev_loop.run_until_complete( + asyncio.gather(self.data_source.get_order_book_data(self.trading_pair))) + result = results[0] + + self.assertTrue("data" in result) + self.assertGreaterEqual(len(result["data"]), 0) + self.assertEqual(NdaxOrderBookEntry(*mock_response[0]), result["data"][0]) + + @patch("aiohttp.ClientSession.get") + def test_get_order_book_data_raises_exception_when_response_has_error_code(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + + self.simulate_trading_pair_ids_initialized() + mock_response = {"Erroneous response"} + self.mocking_assistant.add_http_response(mock_api, 100, mock_response) + + with self.assertRaises(IOError) as context: + self.ev_loop.run_until_complete(self.data_source.get_order_book_data(self.trading_pair)) + + self.assertEqual(str(context.exception), f"Error fetching OrderBook for {self.trading_pair} " + f"at {CONSTANTS.ORDER_BOOK_URL}. " + f"HTTP {100}. Response: {mock_response}") + + @patch("aiohttp.ClientSession.get") + def test_get_new_order_book(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + + self.simulate_trading_pair_ids_initialized() + + mock_response: List[List[Any]] = [ + # mdUpdateId, accountId, actionDateTime, actionType, lastTradePrice, orderId, price, productPairCode, quantity, side + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37750.0, 1, 0.015, 0], + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37751.0, 1, 0.015, 1] + ] + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + results = self.ev_loop.run_until_complete( + asyncio.gather(self.data_source.get_new_order_book(self.trading_pair))) + result: OrderBook = results[0] + + self.assertTrue(type(result) == OrderBook) + self.assertEqual(result.snapshot_uid, 0) + + @patch("aiohttp.ClientSession.get") + def test_get_instrument_ids(self, mock_api): + self.mocking_assistant.configure_http_request_mock(mock_api) + + mock_response: List[Any] = [{ + "Product1Symbol": self.base_asset, + "Product2Symbol": self.quote_asset, + "InstrumentId": self.instrument_id, + "SessionStatus": "Running", + }] + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + + results = self.ev_loop.run_until_complete(asyncio.gather(self.data_source.get_instrument_ids())) + result: Dict[str, Any] = results[0] + + self.assertEqual(1, self.data_source._trading_pair_id_map[self.trading_pair]) + self.assertEqual(result[self.trading_pair], self.instrument_id) + + @patch("hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource._sleep") + @patch("aiohttp.ClientSession.get") + def test_listen_for_snapshots_cancelled_when_fetching_snapshot(self, mock_api, mock_sleep): + mock_api.side_effect = asyncio.CancelledError + self.simulate_trading_pair_ids_initialized() + + msg_queue: asyncio.Queue = asyncio.Queue() + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.ev_loop.run_until_complete(self.listening_task) + + self.assertEqual(msg_queue.qsize(), 0) + + @patch("hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource._sleep") + @patch("aiohttp.ClientSession.get") + def test_listen_for_snapshots_logs_exception_when_fetching_snapshot(self, mock_api, mock_sleep): + # the queue and the division by zero error are used just to synchronize the test + sync_queue = deque() + sync_queue.append(1) + + self.simulate_trading_pair_ids_initialized() + + mock_api.side_effect = Exception + mock_sleep.side_effect = lambda delay: 1 / 0 if len(sync_queue) == 0 else sync_queue.pop() + + msg_queue: asyncio.Queue = asyncio.Queue() + with self.assertRaises(ZeroDivisionError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + self.ev_loop.run_until_complete(self.listening_task) + + self.assertEqual(msg_queue.qsize(), 0) + self.assertTrue(self._is_logged("ERROR", + "Unexpected error occured listening for orderbook snapshots. Retrying in 5 secs...")) + + @patch("hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource._sleep") + @patch("aiohttp.ClientSession.get") + def test_listen_for_snapshots_successful(self, mock_api, mock_sleep): + self.mocking_assistant.configure_http_request_mock(mock_api) + + # the queue and the division by zero error are used just to synchronize the test + sync_queue = deque() + sync_queue.append(1) + + mock_response: List[List[Any]] = [ + # mdUpdateId, accountId, actionDateTime, actionType, lastTradePrice, orderId, price, productPairCode, quantity, side + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37750.0, 1, 0.015, 0], + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37751.0, 1, 0.015, 1], + ] + self.mocking_assistant.add_http_response(mock_api, 200, mock_response) + self.simulate_trading_pair_ids_initialized() + + mock_sleep.side_effect = lambda delay: 1 / 0 if len(sync_queue) == 0 else sync_queue.pop() + + msg_queue: asyncio.Queue = asyncio.Queue() + with self.assertRaises(ZeroDivisionError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + self.ev_loop.run_until_complete(self.listening_task) + + self.assertEqual(msg_queue.qsize(), 1) + + snapshot_msg: OrderBookMessage = msg_queue.get_nowait() + self.assertEqual(snapshot_msg.update_id, 0) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_cancelled_when_subscribing(self, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.send_json.side_effect = asyncio.CancelledError() + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._subscribe_level_2_response()) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._orderbook_update_event()) + + self.simulate_trading_pair_ids_initialized() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.ev_loop.run_until_complete(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_cancelled_when_listening(self, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = lambda: ( + self._raise_exception(asyncio.CancelledError) + ) + + self.simulate_trading_pair_ids_initialized() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.ev_loop.run_until_complete(self.listening_task) + + self.assertEqual(msg_queue.qsize(), 0) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @patch("hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_logs_exception(self, mock_api, mock_ws, *_): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.close.return_value = None + + incomplete_resp = { + "m": 1, + "i": 2, + } + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, ujson.dumps(incomplete_resp)) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._orderbook_update_event()) + + self.simulate_trading_pair_ids_initialized() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + self.ev_loop.run_until_complete(msg_queue.get()) + + self.assertTrue(self._is_logged("NETWORK", "Unexpected error with WebSocket connection.")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_order_book_diffs_successful(self, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.send_json.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._subscribe_level_2_response()) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._orderbook_update_event()) + + self.simulate_trading_pair_ids_initialized() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + first_msg = self.ev_loop.run_until_complete(msg_queue.get()) + second_msg = self.ev_loop.run_until_complete(msg_queue.get()) + + self.assertTrue(first_msg.type == OrderBookMessageType.SNAPSHOT) + self.assertTrue(second_msg.type == OrderBookMessageType.DIFF) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_websocket_connection_creation_raises_cancel_exception(self, mock_ws): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source._create_websocket_connection()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_websocket_connection_creation_raises_exception_after_loging(self, mock_ws): + mock_ws.side_effect = Exception + + with self.assertRaises(Exception): + self.async_run_with_timeout(self.data_source._create_websocket_connection()) + + self.assertTrue(self._is_logged("NETWORK", "Unexpected error occurred during ndax WebSocket Connection ()")) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/ndax/test_ndax_api_user_stream_data_source.py new file mode 100644 index 0000000..f4f8c37 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_api_user_stream_data_source.py @@ -0,0 +1,241 @@ +import asyncio +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.connector.exchange.ndax.ndax_api_user_stream_data_source import NdaxAPIUserStreamDataSource +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth +from hummingbot.connector.exchange.ndax.ndax_websocket_adaptor import NdaxWebSocketAdaptor +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class NdaxAPIUserStreamDataSourceTests(TestCase): + # the level is required to receive logs from the data source loger + level = 0 + + def setUp(self) -> None: + super().setUp() + self.uid = '001' + self.api_key = 'testAPIKey' + self.secret = 'testSecret' + self.account_id = 528 + self.username = 'hbot' + self.oms_id = 1 + self.log_records = [] + self.listening_task = None + + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + auth_assistant = NdaxAuth(uid=self.uid, + api_key=self.api_key, + secret_key=self.secret, + account_name=self.username) + self.data_source = NdaxAPIUserStreamDataSource(throttler, auth_assistant) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.mocking_assistant = NetworkMockingAssistant() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _authentication_response(self, authenticated: bool) -> str: + user = {"UserId": 492, + "UserName": "hbot", + "Email": "hbot@mailinator.com", + "EmailVerified": True, + "AccountId": self.account_id, + "OMSId": self.oms_id, + "Use2FA": True} + payload = {"Authenticated": authenticated, + "SessionToken": "74e7c5b0-26b1-4ca5-b852-79b796b0e599", + "User": user, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None} + message = {"m": 1, + "i": 1, + "n": CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, + "o": json.dumps(payload)} + + return json.dumps(message) + + def _raise_exception(self, exception_class): + raise exception_class + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + initial_last_recv_time = self.data_source.last_recv_time + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(True)) + # Add a dummy message for the websocket to read and include in the "messages" queue + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps('dummyMessage')) + + first_received_message = self.async_run_with_timeout(messages.get()) + + self.assertEqual('dummyMessage', first_received_message) + + self.assertTrue(self._is_logged('INFO', "Authenticating to User Stream...")) + self.assertTrue(self._is_logged('INFO', "Successfully authenticated to User Stream.")) + self.assertTrue(self._is_logged('INFO', "Successfully subscribed to user events.")) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + self.assertEqual(2, len(sent_messages)) + authentication_request = sent_messages[0] + subscription_request = sent_messages[1] + self.assertEqual(CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, + NdaxWebSocketAdaptor.endpoint_from_raw_message(json.dumps(authentication_request))) + self.assertEqual(CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME, + NdaxWebSocketAdaptor.endpoint_from_raw_message(json.dumps(subscription_request))) + subscription_payload = NdaxWebSocketAdaptor.payload_from_raw_message(json.dumps(subscription_request)) + expected_payload = {"AccountId": self.account_id, + "OMSId": self.oms_id} + self.assertEqual(expected_payload, subscription_payload) + + self.assertGreater(self.data_source.last_recv_time, initial_last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_fails_when_authentication_fails(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + # Make the close function raise an exception to finish the execution + ws_connect_mock.return_value.close.side_effect = lambda: self._raise_exception(Exception) + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(False)) + + try: + self.async_run_with_timeout(self.listening_task) + except Exception: + pass + + self.assertTrue(self._is_logged("ERROR", "Error occurred when authenticating to user stream " + "(Could not authenticate websocket connection with NDAX)")) + self.assertTrue(self._is_logged("ERROR", + "Unexpected error with NDAX WebSocket connection. Retrying in 30 seconds. " + "(Could not authenticate websocket connection with NDAX)")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_initialization(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_authentication(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.send_json.side_effect = lambda sent_message: ( + self._raise_exception(asyncio.CancelledError) + if CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME in sent_message['n'] + else self.mocking_assistant._sent_websocket_json_messages[ws_connect_mock.return_value].append(sent_message)) + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_when_cancel_exception_during_events_subscription(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.send_json.side_effect = lambda sent_message: ( + self._raise_exception(asyncio.CancelledError) + if CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME in sent_message['n'] + else self.mocking_assistant._sent_websocket_json_messages[ws_connect_mock.return_value].append(sent_message)) + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(True)) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_logs_exception_details_during_initialization(self, ws_connect_mock): + ws_connect_mock.side_effect = Exception + + with self.assertRaises(Exception): + self.listening_task = asyncio.get_event_loop().create_task(self.data_source._init_websocket_connection()) + self.async_run_with_timeout(self.listening_task) + self.assertTrue(self._is_logged("NETWORK", "Unexpected error occurred during ndax WebSocket Connection ()")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_logs_exception_details_during_authentication(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.send_json.side_effect = lambda sent_message: ( + self._raise_exception(Exception) + if CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME in sent_message['n'] + else self.mocking_assistant._sent_websocket_json_messages[ws_connect_mock.return_value].append(sent_message)) + # Make the close function raise an exception to finish the execution + ws_connect_mock.return_value.close.side_effect = lambda: self._raise_exception(Exception) + + try: + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + self.async_run_with_timeout(self.listening_task) + except Exception: + pass + + self.assertTrue(self._is_logged("ERROR", "Error occurred when authenticating to user stream ()")) + self.assertTrue(self._is_logged("ERROR", + "Unexpected error with NDAX WebSocket connection. Retrying in 30 seconds. ()")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_logs_exception_during_events_subscription(self, ws_connect_mock): + messages = asyncio.Queue() + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.send_json.side_effect = lambda sent_message: ( + CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME in sent_message['n'] and self._raise_exception(Exception)) + # Make the close function raise an exception to finish the execution + ws_connect_mock.return_value.close.side_effect = lambda: self._raise_exception(Exception) + + try: + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages)) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(True)) + self.async_run_with_timeout(self.listening_task) + except Exception: + pass + + self.assertTrue(self._is_logged("ERROR", "Error occurred subscribing to ndax private channels ()")) + self.assertTrue(self._is_logged("ERROR", + "Unexpected error with NDAX WebSocket connection. Retrying in 30 seconds. ()")) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_auth.py b/test/hummingbot/connector/exchange/ndax/test_ndax_auth.py new file mode 100644 index 0000000..7cc5861 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_auth.py @@ -0,0 +1,67 @@ +import hashlib +import hmac +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth + + +class NdaxAuthTests(TestCase): + + @property + def uid(self): + return '001' + + @property + def api_key(self): + return 'test_api_key' + + @property + def secret_key(self): + return 'test_secret_key' + + def test_no_authentication_headers(self): + auth = NdaxAuth(uid=self.uid, api_key=self.api_key, secret_key=self.secret_key, account_name="hbot") + headers = auth.get_headers() + + self.assertEqual(1, len(headers)) + self.assertEqual('application/json', headers.get('Content-Type')) + + def test_authentication_headers(self): + auth = NdaxAuth(uid=self.uid, api_key=self.api_key, secret_key=self.secret_key, account_name="hbot") + nonce = '1234567890' + + with patch('hummingbot.connector.exchange.ndax.ndax_auth.get_tracking_nonce_low_res') as generate_nonce_mock: + generate_nonce_mock.return_value = nonce + headers = auth.get_auth_headers() + + raw_signature = nonce + self.uid + self.api_key + expected_signature = hmac.new(self.secret_key.encode('utf-8'), + raw_signature.encode('utf-8'), + hashlib.sha256).hexdigest() + + self.assertEqual(5, len(headers)) + self.assertEqual('application/json', headers.get("Content-Type")) + self.assertEqual('001', headers.get('UserId')) + self.assertEqual('test_api_key', headers.get('APIKey')) + self.assertEqual('1234567890', headers.get('Nonce')) + self.assertEqual(expected_signature, headers.get('Signature')) + + def test_ws_auth_payload(self): + auth = NdaxAuth(uid=self.uid, api_key=self.api_key, secret_key=self.secret_key, account_name="hbot") + nonce = '1234567890' + + with patch('hummingbot.connector.exchange.ndax.ndax_auth.get_tracking_nonce_low_res') as generate_nonce_mock: + generate_nonce_mock.return_value = nonce + auth_info = auth.get_ws_auth_payload() + + raw_signature = nonce + self.uid + self.api_key + expected_signature = hmac.new(self.secret_key.encode('utf-8'), + raw_signature.encode('utf-8'), + hashlib.sha256).hexdigest() + + self.assertEqual(4, len(auth_info)) + self.assertEqual('001', auth_info.get('UserId')) + self.assertEqual('test_api_key', auth_info.get('APIKey')) + self.assertEqual('1234567890', auth_info.get('Nonce')) + self.assertEqual(expected_signature, auth_info.get('Signature')) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_exchange.py b/test/hummingbot/connector/exchange/ndax/test_ndax_exchange.py new file mode 100644 index 0000000..568d713 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_exchange.py @@ -0,0 +1,1836 @@ +import asyncio +import functools +import json +import re +import time +from decimal import Decimal +from typing import Any, Awaitable, Callable, Dict, List +from unittest import TestCase +from unittest.mock import AsyncMock, PropertyMock, patch + +import pandas as pd +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS, ndax_utils +from hummingbot.connector.exchange.ndax.ndax_exchange import NdaxExchange +from hummingbot.connector.exchange.ndax.ndax_in_flight_order import ( + WORKING_LOCAL_STATUS, + NdaxInFlightOrder, + NdaxInFlightOrderNotCreated, +) +from hummingbot.connector.exchange.ndax.ndax_order_book import NdaxOrderBook +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, MarketOrderFailureEvent, OrderCancelledEvent, OrderFilledEvent +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future + + +class NdaxExchangeTests(TestCase): + # the level is required to receive logs from the data source loger + level = 0 + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + + self.tracker_task = None + self.exchange_task = None + self.log_records = [] + self.resume_test_event = asyncio.Event() + self._account_name = "hbot" + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.exchange = NdaxExchange(client_config_map=self.client_config_map, + ndax_uid='001', + ndax_api_key='testAPIKey', + ndax_secret_key='testSecret', + ndax_account_name=self._account_name, + trading_pairs=[self.trading_pair]) + + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._account_id = 1 + + self.order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.buy_order_completed_logger: EventLogger = EventLogger() + self.sell_order_completed_logger: EventLogger = EventLogger() + self.order_failure_logger: EventLogger = EventLogger() + + self.exchange.add_listener(MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger) + self.exchange.add_listener(MarketEvent.SellOrderCompleted, self.sell_order_completed_logger) + self.exchange.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.exchange.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + self.exchange.add_listener(MarketEvent.OrderFailure, self.order_failure_logger) + + self.mocking_assistant = NetworkMockingAssistant() + self.mock_done_event = asyncio.Event() + + def tearDown(self) -> None: + self.tracker_task and self.tracker_task.cancel() + self.exchange_task and self.exchange_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _authentication_response(self, authenticated: bool) -> str: + user = {"UserId": 492, + "UserName": "hbot", + "Email": "hbot@mailinator.com", + "EmailVerified": True, + "AccountId": 528, + "OMSId": 1, + "Use2FA": True} + payload = {"Authenticated": authenticated, + "SessionToken": "74e7c5b0-26b1-4ca5-b852-79b796b0e599", + "User": user, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None} + message = {"m": 1, + "i": 1, + "n": CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, + "o": json.dumps(payload)} + + return json.dumps(message) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _mock_responses_done_callback(self, *_, **__): + self.mock_done_event.set() + + def _return_calculation_and_set_done_event(self, calculation: Callable, *args, **kwargs): + if self.resume_test_event.is_set(): + raise asyncio.CancelledError + self.resume_test_event.set() + return calculation(*args, **kwargs) + + def _simulate_reset_poll_notifier(self): + self.exchange._poll_notifier.clear() + + def _simulate_ws_message_received(self, timestamp: float): + self.exchange._user_stream_tracker._data_source._last_recv_time = timestamp + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def _simulate_create_order(self, + trade_type: TradeType, + order_id: str, + trading_pair: str, + amount: Decimal, + price: Decimal = Decimal("0"), + order_type: OrderType = OrderType.MARKET): + future = safe_ensure_future( + self.exchange._create_order(trade_type, order_id, trading_pair, amount, price, order_type) + ) + self.exchange.start_tracking_order( + order_id, None, self.trading_pair, TradeType.BUY, Decimal(10.0), Decimal(1.0), OrderType.LIMIT + ) + return future + + @patch("aiohttp.ClientSession.ws_connect") + def test_user_event_queue_error_is_logged(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_text_message( + ws_connect_mock.return_value, + self._authentication_response(True)) + + dummy_user_stream = AsyncMock() + dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + Exception("Dummy test error")) + self.exchange._user_stream_tracker._user_stream = dummy_user_stream + + # Add a dummy message for the websocket to read and include in the "messages" queue + self.mocking_assistant.add_websocket_text_message(ws_connect_mock, json.dumps('dummyMessage')) + self.async_run_with_timeout(self.resume_test_event.wait()) + self.resume_test_event.clear() + + try: + self.exchange_task.cancel() + self.async_run_with_timeout(self.exchange_task) + except asyncio.CancelledError: + pass + except Exception: + pass + + self.assertTrue(self._is_logged('NETWORK', "Unknown error. Retrying after 1 seconds.")) + + def test_user_event_queue_notifies_cancellations(self): + self.tracker_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + dummy_user_stream = AsyncMock() + dummy_user_stream.get.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + self.exchange._user_stream_tracker._user_stream = dummy_user_stream + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.tracker_task) + + def test_exchange_logs_unknown_event_message(self): + payload = {} + message = {"m": 3, + "i": 99, + "n": 'UnknownEndpoint', + "o": json.dumps(payload)} + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue(self._is_logged('DEBUG', f"Unknown event received from the connector ({message})")) + + def test_account_position_event_updates_account_balances(self): + payload = {"OMSId": 1, + "AccountId": 5, + "ProductSymbol": "BTC", + "ProductId": 1, + "Amount": 10499.1, + "Hold": 2.1, + "PendingDeposits": 10, + "PendingWithdraws": 20, + "TotalDayDeposits": 30, + "TotalDayWithdraws": 40} + message = {"m": 3, + "i": 2, + "n": CONSTANTS.ACCOUNT_POSITION_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload)} + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(Decimal('10499.1'), self.exchange.get_balance('BTC')) + self.assertEqual(Decimal('10499.1') - Decimal('2.1'), self.exchange.available_balances['BTC']) + + def test_order_event_with_cancel_status_cancels_in_flight_order(self): + payload = { + "Side": "Sell", + "OrderId": 9849, + "Price": 35000, + "Quantity": 1, + "Instrument": 1, + "Account": 4, + "OrderType": "Limit", + "ClientOrderId": 3, + "OrderState": "Canceled", + "ReceiveTime": 0, + "OrigQuantity": 1, + "QuantityExecuted": 0, + "AvgPrice": 0, + "ChangeReason": "NewInputAccepted" + } + message = {"m": 3, + "i": 2, + "n": CONSTANTS.ORDER_STATE_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload)} + + self.exchange.start_tracking_order(order_id="3", + exchange_order_id="9849", + trading_pair="BTC-USD", + trade_type=TradeType.SELL, + price=Decimal("35000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT) + + inflight_order = self.exchange.in_flight_orders["3"] + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual("Canceled", inflight_order.last_state) + self.assertTrue(inflight_order.is_cancelled) + self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {inflight_order.client_order_id}")) + self.assertEqual(1, len(self.cancel_order_logger.event_log)) + cancel_event = self.cancel_order_logger.event_log[0] + self.assertEqual(OrderCancelledEvent, type(cancel_event)) + self.assertEqual(inflight_order.client_order_id, cancel_event.order_id) + + def test_order_event_with_rejected_status_makes_in_flight_order_fail(self): + payload = { + "Side": "Sell", + "OrderId": 9849, + "Price": 35000, + "Quantity": 1, + "Instrument": 1, + "Account": 4, + "OrderType": "Limit", + "ClientOrderId": 3, + "OrderState": "Rejected", + "ReceiveTime": 0, + "OrigQuantity": 1, + "QuantityExecuted": 0, + "AvgPrice": 0, + "ChangeReason": "OtherRejected" + } + message = {"m": 3, + "i": 2, + "n": CONSTANTS.ORDER_STATE_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload)} + + self.exchange.start_tracking_order(order_id="3", + exchange_order_id="9849", + trading_pair="BTC-USD", + trade_type=TradeType.SELL, + price=Decimal("35000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT) + + inflight_order = self.exchange.in_flight_orders["3"] + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual("Rejected", inflight_order.last_state) + self.assertTrue(inflight_order.is_failure) + self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", + f"The market order {inflight_order.client_order_id} " + f"has failed according to order status event. " + f"Reason: {payload['ChangeReason']}")) + self.assertEqual(1, len(self.order_failure_logger.event_log)) + failure_event = self.order_failure_logger.event_log[0] + self.assertEqual(MarketOrderFailureEvent, type(failure_event)) + self.assertEqual(inflight_order.client_order_id, failure_event.order_id) + + def test_trade_event_fills_and_completes_buy_in_flight_order(self): + payload = { + "OMSId": 1, + "TradeId": 213, + "OrderId": 9848, + "AccountId": 4, + "ClientOrderId": 3, + "InstrumentId": 1, + "Side": "Buy", + "Quantity": 1, + "Price": 35000, + "Value": 35000, + "TradeTime": 635978008210426109, + "ContraAcctId": 3, + "OrderTradeRevision": 1, + "Direction": "NoChange" + } + message = {"m": 3, + "i": 2, + "n": CONSTANTS.ORDER_TRADE_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload)} + + self.exchange.start_tracking_order(order_id="3", + exchange_order_id="9848", + trading_pair="BTC-USD", + trade_type=TradeType.BUY, + price=Decimal("35000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT) + + inflight_order = self.exchange.in_flight_orders["3"] + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual("FullyExecuted", inflight_order.last_state) + self.assertIn(payload["TradeId"], inflight_order.trade_id_set) + self.assertEqual(Decimal(1), inflight_order.executed_amount_base) + self.assertEqual(Decimal(35000), inflight_order.executed_amount_quote) + self.assertEqual(inflight_order.executed_amount_base * Decimal("0.002"), inflight_order.fee_paid) + + self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", f"The {inflight_order.trade_type.name} order " + f"{inflight_order.client_order_id} has completed " + f"according to order status API")) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + fill_event = self.order_fill_logger.event_log[0] + self.assertEqual(OrderFilledEvent, type(fill_event)) + self.assertEqual(inflight_order.client_order_id, fill_event.order_id) + self.assertEqual(inflight_order.trading_pair, fill_event.trading_pair) + self.assertEqual(inflight_order.trade_type, fill_event.trade_type) + self.assertEqual(inflight_order.order_type, fill_event.order_type) + self.assertEqual(Decimal(35000), fill_event.price) + self.assertEqual(Decimal(1), fill_event.amount) + self.assertEqual(Decimal("0.002"), fill_event.trade_fee.percent) + self.assertEqual(0, len(fill_event.trade_fee.flat_fees)) + self.assertEqual("213", fill_event.exchange_trade_id) + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) + buy_event = self.buy_order_completed_logger.event_log[0] + self.assertEqual(inflight_order.client_order_id, buy_event.order_id) + self.assertEqual(inflight_order.base_asset, buy_event.base_asset) + self.assertEqual(inflight_order.quote_asset, buy_event.quote_asset) + self.assertEqual(inflight_order.executed_amount_base, buy_event.base_asset_amount) + self.assertEqual(inflight_order.executed_amount_quote, buy_event.quote_asset_amount) + self.assertEqual(inflight_order.order_type, buy_event.order_type) + self.assertEqual(inflight_order.exchange_order_id, buy_event.exchange_order_id) + + def test_trade_event_fills_and_completes_sell_in_flight_order(self): + payload = { + "OMSId": 1, + "TradeId": 213, + "OrderId": 9848, + "AccountId": 4, + "ClientOrderId": 3, + "InstrumentId": 1, + "Side": "Sell", + "Quantity": 1, + "Price": 35000, + "Value": 35000, + "TradeTime": 635978008210426109, + "ContraAcctId": 3, + "OrderTradeRevision": 1, + "Direction": "NoChange" + } + message = {"m": 3, + "i": 2, + "n": CONSTANTS.ORDER_TRADE_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload)} + + self.exchange.start_tracking_order(order_id="3", + exchange_order_id="9848", + trading_pair="BTC-USD", + trade_type=TradeType.SELL, + price=Decimal("35000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT) + + inflight_order = self.exchange.in_flight_orders["3"] + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._user_stream_event_listener()) + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual("FullyExecuted", inflight_order.last_state) + self.assertIn(payload["TradeId"], inflight_order.trade_id_set) + self.assertEqual(Decimal(1), inflight_order.executed_amount_base) + self.assertEqual(Decimal(35000), inflight_order.executed_amount_quote) + self.assertEqual(inflight_order.executed_amount_base * inflight_order.executed_amount_quote * Decimal("0.002"), + inflight_order.fee_paid) + + self.assertFalse(inflight_order.client_order_id in self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", f"The {inflight_order.trade_type.name} order " + f"{inflight_order.client_order_id} has completed " + f"according to order status API")) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + fill_event = self.order_fill_logger.event_log[0] + self.assertEqual(OrderFilledEvent, type(fill_event)) + self.assertEqual(inflight_order.client_order_id, fill_event.order_id) + self.assertEqual(inflight_order.trading_pair, fill_event.trading_pair) + self.assertEqual(inflight_order.trade_type, fill_event.trade_type) + self.assertEqual(inflight_order.order_type, fill_event.order_type) + self.assertEqual(Decimal(35000), fill_event.price) + self.assertEqual(Decimal(1), fill_event.amount) + self.assertEqual(Decimal("0.002"), fill_event.trade_fee.percent) + self.assertEqual(0, len(fill_event.trade_fee.flat_fees)) + self.assertEqual("213", fill_event.exchange_trade_id) + self.assertEqual(1, len(self.sell_order_completed_logger.event_log)) + buy_event = self.sell_order_completed_logger.event_log[0] + self.assertEqual(inflight_order.client_order_id, buy_event.order_id) + self.assertEqual(inflight_order.base_asset, buy_event.base_asset) + self.assertEqual(inflight_order.quote_asset, buy_event.quote_asset) + self.assertEqual(inflight_order.executed_amount_base, buy_event.base_asset_amount) + self.assertEqual(inflight_order.executed_amount_quote, buy_event.quote_asset_amount) + self.assertEqual(inflight_order.order_type, buy_event.order_type) + self.assertEqual(inflight_order.exchange_order_id, buy_event.exchange_order_id) + + def test_tick_initial_tick_successful(self): + start_ts: float = time.time() * 1e3 + + self.exchange.tick(start_ts) + self.assertEqual(start_ts, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + @patch("time.time") + def test_tick_subsequent_tick_within_short_poll_interval(self, mock_ts): + # Assumes user stream tracker has NOT been receiving messages, Hence SHORT_POLL_INTERVAL in use + start_ts: float = self.start_timestamp + next_tick: float = start_ts + (self.exchange.SHORT_POLL_INTERVAL - 1) + + mock_ts.return_value = start_ts + self.exchange.tick(start_ts) + self.assertEqual(start_ts, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + self._simulate_reset_poll_notifier() + + mock_ts.return_value = next_tick + self.exchange.tick(next_tick) + self.assertEqual(next_tick, self.exchange._last_timestamp) + self.assertFalse(self.exchange._poll_notifier.is_set()) + + @patch("time.time") + def test_tick_subsequent_tick_exceed_short_poll_interval(self, mock_ts): + # Assumes user stream tracker has NOT been receiving messages, Hence SHORT_POLL_INTERVAL in use + start_ts: float = self.start_timestamp + next_tick: float = start_ts + (self.exchange.SHORT_POLL_INTERVAL + 1) + + mock_ts.return_value = start_ts + self.exchange.tick(start_ts) + self.assertEqual(start_ts, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + self._simulate_reset_poll_notifier() + + mock_ts.return_value = next_tick + self.exchange.tick(next_tick) + self.assertEqual(next_tick, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + @patch("time.time") + def test_tick_subsequent_tick_within_long_poll_interval(self, mock_time): + + start_ts: float = self.start_timestamp + next_tick: float = start_ts + (self.exchange.LONG_POLL_INTERVAL - 1) + + mock_time.return_value = start_ts + self.exchange.tick(start_ts) + self.assertEqual(start_ts, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + # Simulate last message received 1 sec ago + self._simulate_ws_message_received(next_tick - 1) + self._simulate_reset_poll_notifier() + + mock_time.return_value = next_tick + self.exchange.tick(next_tick) + self.assertEqual(next_tick, self.exchange._last_timestamp) + self.assertFalse(self.exchange._poll_notifier.is_set()) + + @patch("time.time") + def test_tick_subsequent_tick_exceed_long_poll_interval(self, mock_time): + # Assumes user stream tracker has been receiving messages, Hence LONG_POLL_INTERVAL in use + start_ts: float = self.start_timestamp + next_tick: float = start_ts + (self.exchange.LONG_POLL_INTERVAL - 1) + + mock_time.return_value = start_ts + self.exchange.tick(start_ts) + self.assertEqual(start_ts, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + self._simulate_ws_message_received(start_ts) + self._simulate_reset_poll_notifier() + + mock_time.return_value = next_tick + self.exchange.tick(next_tick) + self.assertEqual(next_tick, self.exchange._last_timestamp) + self.assertTrue(self.exchange._poll_notifier.is_set()) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_get_account_id(self, mock_api): + account_id = 1 + mock_response = [{'OMSID': '1', + 'AccountId': str(account_id), + 'AccountName': self._account_name, + 'AccountHandle': None, + 'FirmId': None, + 'FirmName': None, + 'AccountType': 'Asset', + 'FeeGroupId': '0', + 'ParentID': '0', + 'RiskType': 'Normal', + 'VerificationLevel': '2', + 'CreditTier': '0', + 'FeeProductType': 'BaseProduct', + 'FeeProduct': '0', + 'RefererId': '328', + 'LoyaltyProductId': '0', + 'LoyaltyEnabled': False, + 'PriceTier': '0', + 'Frozen': False}] + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + task = asyncio.get_event_loop().create_task( + self.exchange._get_account_id() + ) + resp = self.async_run_with_timeout(task) + self.assertEqual(resp, account_id) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_get_account_id_when_account_does_not_exist(self, mock_api): + account_id = 1 + mock_response = [{'OMSID': '1', + 'AccountId': str(account_id), + 'AccountName': 'unexistent_name', + 'AccountHandle': None, + 'FirmId': None, + 'FirmName': None, + 'AccountType': 'Asset', + 'FeeGroupId': '0', + 'ParentID': '0', + 'RiskType': 'Normal', + 'VerificationLevel': '2', + 'CreditTier': '0', + 'FeeProductType': 'BaseProduct', + 'FeeProduct': '0', + 'RefererId': '328', + 'LoyaltyProductId': '0', + 'LoyaltyEnabled': False, + 'PriceTier': '0', + 'Frozen': False}] + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + task = asyncio.get_event_loop().create_task( + self.exchange._get_account_id() + ) + resp = self.async_run_with_timeout(task) + + self.assertTrue(self._is_logged('ERROR', f"There is no account named {self._account_name} " + f"associated with the current NDAX user")) + self.assertIsNone(resp) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_update_balances(self, mock_api): + self.assertEqual(0, len(self.exchange._account_balances)) + self.assertEqual(0, len(self.exchange._account_available_balances)) + + # We force the account_id to avoid the account id resolution request + self.exchange._account_id = 1 + + mock_response: List[Dict[str, Any]] = [ + { + "ProductSymbol": self.base_asset, + "Amount": 10.0, + "Hold": 5.0 + }, + ] + + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._update_balances() + ) + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(Decimal(str(10.0)), self.exchange.get_balance(self.base_asset)) + + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_update_order_status(self, mock_order_status, mock_trade_history): + + # Simulates order being tracked + order: NdaxInFlightOrder = NdaxInFlightOrder( + "0", + "2628", + self.trading_pair, + OrderType.LIMIT, + TradeType.SELL, + Decimal(str(41720.83)), + Decimal("1"), + 1640001112.0, + "Working", + ) + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + self.assertTrue(1, len(self.exchange.in_flight_orders)) + + # Add FullyExecuted GetOrderStatus API Response + self.mocking_assistant.configure_http_request_mock(mock_order_status) + self.mocking_assistant.add_http_response( + mock_order_status, + 200, + { + "Side": "Sell", + "OrderId": 2628, + "Price": 41720.830000000000000000000000, + "Quantity": 0.0000000000000000000000000000, + "DisplayQuantity": 0.0000000000000000000000000000, + "Instrument": 5, + "Account": 528, + "AccountName": "hbot", + "OrderType": "Limit", + "ClientOrderId": 0, + "OrderState": "FullyExecuted", + "ReceiveTime": 1627380780887, + "ReceiveTimeTicks": 637629775808866338, + "LastUpdatedTime": 1627380783860, + "LastUpdatedTimeTicks": 637629775838598558, + "OrigQuantity": 1.0000000000000000000000000000, + "QuantityExecuted": 1.0000000000000000000000000000, + "GrossValueExecuted": 41720.830000000000000000000000, + "ExecutableValue": 0.0000000000000000000000000000, + "AvgPrice": 41720.830000000000000000000000, + "CounterPartyId": 0, + "ChangeReason": "Trade", + "OrigOrderId": 2628, + "OrigClOrdId": 0, + "EnteredBy": 492, + "UserName": "hbot", + "IsQuote": False, + "InsideAsk": 41720.830000000000000000000000, + "InsideAskSize": 0.9329960000000000000000000000, + "InsideBid": 41718.340000000000000000000000, + "InsideBidSize": 0.0632560000000000000000000000, + "LastTradePrice": 41720.830000000000000000000000, + "RejectReason": "", + "IsLockedIn": False, + "CancelReason": "", + "OrderFlag": "AddedToBook, RemovedFromBook", + "UseMargin": False, + "StopPrice": 0.0000000000000000000000000000, + "PegPriceType": "Last", + "PegOffset": 0.0000000000000000000000000000, + "PegLimitOffset": 0.0000000000000000000000000000, + "IpAddress": "103.6.151.12", + "ClientOrderIdUuid": None, + "OMSId": 1 + }, + "") + + # Add TradeHistory API Response + self.mocking_assistant.configure_http_request_mock(mock_trade_history) + self.mocking_assistant.add_http_response( + mock_trade_history, + 200, + [{ + "OMSId": 1, + "ExecutionId": 245936, + "TradeId": 252851, + "OrderId": 2628, + "AccountId": 528, + "AccountName": "hbot", + "SubAccountId": 0, + "ClientOrderId": 0, + "InstrumentId": 5, + "Side": "Sell", + "OrderType": "Limit", + "Quantity": 1.0000000000000000000000000000, + "RemainingQuantity": 0.0000000000000000000000000000, + "Price": 41720.830000000000000000000000, + "Value": 41720.830000000000000000000000, + "CounterParty": "0", + "OrderTradeRevision": 1, + "Direction": "NoChange", + "IsBlockTrade": False, + "Fee": 834.4, + "FeeProductId": 5, + "OrderOriginator": 492, + "UserName": "hbot", + "TradeTimeMS": 1627380783859, + "MakerTaker": "Maker", + "AdapterTradeId": 0, + "InsideBid": 41718.340000000000000000000000, + "InsideBidSize": 0.0632560000000000000000000000, + "InsideAsk": 41720.830000000000000000000000, + "InsideAskSize": 0.9329960000000000000000000000, + "IsQuote": False, + "CounterPartyClientUserId": 0, + "NotionalProductId": 2, + "NotionalRate": 0.7953538608862469000000000000, + "NotionalValue": 480.28818328452511800486539800, + "NotionalHoldAmount": 0, + "TradeTime": 637629775838593249 + }], + "") + + # Simulate _trading_pair_id_map initialized. + self.exchange.order_book_tracker.data_source._trading_pair_id_map.update({ + self.trading_pair: 5 + }) + + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status()) + self.async_run_with_timeout(self.exchange_task) + self.assertEqual(0, len(self.exchange.in_flight_orders)) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_update_order_status_error_response(self, mock_api): + + # Simulates order being tracked + order: NdaxInFlightOrder = NdaxInFlightOrder( + "0", + "2628", + self.trading_pair, + OrderType.LIMIT, + TradeType.SELL, + Decimal(str(41720.83)), + Decimal("1"), + creation_timestamp=1640001112.0) + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + self.assertTrue(1, len(self.exchange.in_flight_orders)) + + # Add FullyExecuted GetOrderStatus API Response + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response( + mock_api, + 200, + { + "Side": "Sell", + "OrderId": 2628, + "Price": 41720.830000000000000000000000, + "Quantity": 0.0000000000000000000000000000, + "DisplayQuantity": 0.0000000000000000000000000000, + "Instrument": 5, + "Account": 528, + "AccountName": "hbot", + "OrderType": "Limit", + "ClientOrderId": 0, + "OrderState": "Working", + "ReceiveTime": 1627380780887, + "ReceiveTimeTicks": 637629775808866338, + "LastUpdatedTime": 1627380783860, + "LastUpdatedTimeTicks": 637629775838598558, + "OrigQuantity": 1.0000000000000000000000000000, + "QuantityExecuted": 1.0000000000000000000000000000, + "GrossValueExecuted": 41720.830000000000000000000000, + "ExecutableValue": 0.0000000000000000000000000000, + "AvgPrice": 41720.830000000000000000000000, + "CounterPartyId": 0, + "ChangeReason": "Trade", + "OrigOrderId": 2628, + "OrigClOrdId": 0, + "EnteredBy": 492, + "UserName": "hbot", + "IsQuote": False, + "InsideAsk": 41720.830000000000000000000000, + "InsideAskSize": 0.9329960000000000000000000000, + "InsideBid": 41718.340000000000000000000000, + "InsideBidSize": 0.0632560000000000000000000000, + "LastTradePrice": 41720.830000000000000000000000, + "RejectReason": "", + "IsLockedIn": False, + "CancelReason": "", + "OrderFlag": "AddedToBook, RemovedFromBook", + "UseMargin": False, + "StopPrice": 0.0000000000000000000000000000, + "PegPriceType": "Last", + "PegOffset": 0.0000000000000000000000000000, + "PegLimitOffset": 0.0000000000000000000000000000, + "IpAddress": "103.6.151.12", + "ClientOrderIdUuid": None, + "OMSId": 1 + }, + "") + + # Add TradeHistory API Response + self.mocking_assistant.add_http_response( + mock_api, + 200, + { + "result": False, + "errormsg": "Invalid Request", + "errorcode": 100, + "detail": None + }, + "") + + # Simulate _trading_pair_id_map initialized. + self.exchange.order_book_tracker.data_source._trading_pair_id_map.update({ + self.trading_pair: 5 + }) + + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status()) + self.async_run_with_timeout(self.exchange_task) + self.assertEqual(1, len(self.exchange.in_flight_orders)) + + self.assertEqual(0, len(self.exchange.in_flight_orders[order.client_order_id].trade_id_set)) + + @patch("hummingbot.connector.in_flight_order_base.GET_EX_ORDER_ID_TIMEOUT", 0.1) + def test_update_order_status_exchange_order_id_not_found(self): + + # Simulates order being tracked + order: NdaxInFlightOrder = NdaxInFlightOrder( + "0", + None, + self.trading_pair, + OrderType.LIMIT, + TradeType.SELL, + Decimal(str(41720.83)), + Decimal("1"), + 1640001112.0, + "Working" + ) + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + # Simulate _trading_pair_id_map initialized. + self.exchange.order_book_tracker.data_source._trading_pair_id_map.update({ + self.trading_pair: 5 + }) + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status()) + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(1, len(self.exchange._in_flight_orders)) + self.assertEqual(1, self.exchange._order_not_found_records[order.client_order_id]) + + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._update_order_status()) + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(0, len(self.exchange._in_flight_orders)) + self.assertEqual(2, self.exchange._order_not_found_records[order.client_order_id]) + self.assertTrue(self._is_logged("INFO", "Order 0 does not seem to be active, will stop tracking order...")) + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_balances", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_order_status", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange.current_timestamp", new_callable=PropertyMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._reset_poll_notifier") + def test_status_polling_loop(self, _, mock_ts, mock_update_order_status, mock_balances): + mock_balances.return_value = None + mock_update_order_status.return_value = None + + ts: float = time.time() + mock_ts.return_value = ts + self.exchange._current_timestamp = ts + + with self.assertRaises(asyncio.TimeoutError): + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._status_polling_loop() + ) + + self.exchange._poll_notifier.set() + + self.async_run_with_timeout(self.exchange_task, 2.0) + + self.assertEqual(ts, self.exchange._last_poll_timestamp) + + @patch("aiohttp.ClientSession.get") + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange.current_timestamp", new_callable=PropertyMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._reset_poll_notifier") + def test_status_polling_loop_cancels(self, _, mock_ts, mock_api): + mock_api.side_effect = asyncio.CancelledError + + ts: float = time.time() + mock_ts.return_value = ts + self.exchange._current_timestamp = ts + + with self.assertRaises(asyncio.CancelledError): + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._status_polling_loop() + ) + self.exchange._poll_notifier.set() + + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(0, self.exchange._last_poll_timestamp) + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_balances", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_order_status", new_callable=AsyncMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange.current_timestamp", new_callable=PropertyMock) + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._reset_poll_notifier") + def test_status_polling_loop_exception_raised(self, _, mock_ts, mock_update_order_status, mock_balances): + mock_balances.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + Exception("Dummy test error")) + mock_update_order_status.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + Exception("Dummy test error")) + + ts: float = time.time() + mock_ts.return_value = ts + self.exchange._current_timestamp = ts + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._status_polling_loop() + ) + + self.exchange._poll_notifier.set() + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(0, self.exchange._last_poll_timestamp) + self._is_logged("ERROR", "Unexpected error while in status polling loop. Error: ") + + def test_format_trading_rules_success(self): + instrument_info: List[Dict[str, Any]] = [{ + "Product1Symbol": self.base_asset, + "Product2Symbol": self.quote_asset, + "QuantityIncrement": 0.0000010000000000000000000000, + "MinimumQuantity": 0.0001000000000000000000000000, + "MinimumPrice": 15000.000000000000000000000000, + "PriceIncrement": 0.0001, + } + ] + + result: Dict[str, TradingRule] = self.exchange._format_trading_rules(instrument_info) + self.assertTrue(self.trading_pair in result) + + def test_format_trading_rules_failure(self): + # Simulate invalid API response + instrument_info: List[Dict[str, Any]] = [{}] + + result: Dict[str, TradingRule] = self.exchange._format_trading_rules(instrument_info) + self.assertTrue(self.trading_pair not in result) + self.assertTrue(self._is_logged("ERROR", "Error parsing the trading pair rule: {}. Skipping...")) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_update_trading_rules(self, mock_api): + mock_response: List[Dict[str, Any]] = [ + { + "Product1Symbol": self.base_asset, + "Product2Symbol": self.quote_asset, + "QuantityIncrement": 0.01, + "MinimumQuantity": 0.0001, + "MinimumPrice": 15000.0, + "PriceIncrement": 0.0001, + } + ] + + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + task = asyncio.get_event_loop().create_task( + self.exchange._update_trading_rules() + ) + self.async_run_with_timeout(task) + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + + trading_rule: TradingRule = self.exchange.trading_rules[self.trading_pair] + self.assertEqual(trading_rule.min_order_size, Decimal(str(mock_response[0]["MinimumQuantity"]))) + self.assertEqual(trading_rule.min_price_increment, Decimal(str(mock_response[0]["PriceIncrement"]))) + self.assertEqual(trading_rule.min_base_amount_increment, Decimal(str(mock_response[0]["QuantityIncrement"]))) + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_trading_rules", + new_callable=AsyncMock) + def test_trading_rules_polling_loop(self, mock_update): + # No Side Effects expected + mock_update.return_value = None + with self.assertRaises(asyncio.TimeoutError): + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._trading_rules_polling_loop()) + + self.async_run_with_timeout( + asyncio.wait_for(self.exchange_task, 1.0) + ) + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_trading_rules", + new_callable=AsyncMock) + def test_trading_rules_polling_loop_cancels(self, mock_update): + mock_update.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._trading_rules_polling_loop() + ) + + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(0, self.exchange._last_poll_timestamp) + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._update_trading_rules", + new_callable=AsyncMock) + def test_trading_rules_polling_loop_exception_raised(self, mock_update): + mock_update.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + Exception("Dummy test error")) + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._trading_rules_polling_loop() + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self._is_logged("ERROR", "Unexpected error while fetching trading rules. Error: ") + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_check_network_succeeds_when_ping_replies_pong(self, mock_api): + mock_response = {"msg": "PONG"} + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + result = self.async_run_with_timeout(self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, result) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_check_network_fails_when_ping_does_not_reply_pong(self, mock_api): + mock_response = {"msg": "NOT-PONG"} + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + result = self.async_run_with_timeout(self.exchange.check_network()) + self.assertEqual(NetworkStatus.NOT_CONNECTED, result) + + mock_response = {} + self.mocking_assistant.add_http_response(mock_api, 200, mock_response, "") + + result = self.async_run_with_timeout(self.exchange.check_network()) + self.assertEqual(NetworkStatus.NOT_CONNECTED, result) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_check_network_fails_when_ping_returns_error_code(self, mock_api): + mock_response = {"msg": "PONG"} + self.mocking_assistant.configure_http_request_mock(mock_api) + self.mocking_assistant.add_http_response(mock_api, 404, mock_response, "") + + result = self.async_run_with_timeout(self.exchange.check_network()) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, result) + + def test_get_order_book_for_valid_trading_pair(self): + dummy_order_book = NdaxOrderBook() + self.exchange.order_book_tracker.order_books["BTC-USDT"] = dummy_order_book + self.assertEqual(dummy_order_book, self.exchange.get_order_book("BTC-USDT")) + + def test_get_order_book_for_invalid_trading_pair_raises_error(self): + self.assertRaisesRegex(ValueError, + "No order book exists for 'BTC-USDT'", + self.exchange.get_order_book, + "BTC-USDT") + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._create_order", new_callable=AsyncMock) + def test_buy(self, mock_create): + mock_create.side_effect = None + order_details = [ + self.trading_pair, + Decimal(1.0), + Decimal(10.0), + OrderType.LIMIT, + ] + + # Note: BUY simply returns immediately with the client order id. + order_id: str = self.exchange.buy(*order_details) + + # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec + self.assertTrue((int(time.time() * 1e3) - int(order_id)) < 1 * 1e3) + + def test_sell(self): + order_details = [ + self.trading_pair, + Decimal(1.0), + Decimal(10.0), + OrderType.LIMIT, + ] + + # Note: SELL simply returns immediately with the client order id. + order_id: str = self.exchange.buy(*order_details) + + # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec + self.assertTrue((int(time.time() * 1e3) - int(order_id)) < 1 * 1e3) + + @patch( + "hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource.get_instrument_ids", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_create_limit_order(self, mock_post, mock_get_instrument_ids): + mock_get_instrument_ids.return_value = { + self.trading_pair: 5 + } + + expected_response = { + "status": "Accepted", + "errormsg": "", + "OrderId": 123 + } + + self.mocking_assistant.configure_http_request_mock(mock_post) + self.mocking_assistant.add_http_response(mock_post, 200, expected_response, "") + + self._simulate_trading_rules_initialized() + + order_details = [ + TradeType.BUY, + str(1), + self.trading_pair, + Decimal(1.0), + Decimal(10.0), + OrderType.LIMIT, + ] + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + future = self._simulate_create_order(*order_details) + self.async_run_with_timeout(future) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self._is_logged("INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {123} for {Decimal(1.0)} {self.trading_pair}") + + tracked_order: NdaxInFlightOrder = self.exchange.in_flight_orders["1"] + self.assertEqual(tracked_order.client_order_id, "1") + self.assertEqual(tracked_order.exchange_order_id, "123") + self.assertEqual(tracked_order.last_state, WORKING_LOCAL_STATUS) + self.assertEqual(tracked_order.trading_pair, self.trading_pair) + self.assertEqual(tracked_order.price, Decimal(10.0)) + self.assertEqual(tracked_order.amount, Decimal(1.0)) + self.assertEqual(tracked_order.trade_type, TradeType.BUY) + + @patch( + "hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource.get_instrument_ids", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_create_market_order(self, mock_post, mock_get_instrument_ids): + mock_get_instrument_ids.return_value = { + self.trading_pair: 5 + } + + expected_response = { + "status": "Accepted", + "errormsg": "", + "OrderId": 123 + } + + self.mocking_assistant.configure_http_request_mock(mock_post) + self.mocking_assistant.add_http_response(mock_post, 200, expected_response, "") + + self._simulate_trading_rules_initialized() + + order_details = [ + TradeType.BUY, + str(1), + self.trading_pair, + Decimal(1.0), + None, + OrderType.MARKET, + ] + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + future = self._simulate_create_order(*order_details) + self.async_run_with_timeout(future) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self._is_logged("INFO", + f"Created {OrderType.MARKET.name} {TradeType.BUY.name} order {123} for {Decimal(1.0)} {self.trading_pair}") + + tracked_order: NdaxInFlightOrder = self.exchange.in_flight_orders["1"] + self.assertEqual(tracked_order.client_order_id, "1") + self.assertEqual(tracked_order.exchange_order_id, "123") + self.assertEqual(tracked_order.last_state, WORKING_LOCAL_STATUS) + self.assertEqual(tracked_order.trading_pair, self.trading_pair) + self.assertEqual(tracked_order.amount, Decimal(1.0)) + self.assertEqual(tracked_order.trade_type, TradeType.BUY) + + def test_detect_created_order_server_acknowledgement(self): + self.exchange.start_tracking_order( + order_id="3", + exchange_order_id="9849", + trading_pair="BTC-USD", + trade_type=TradeType.SELL, + price=Decimal("35000"), + amount=Decimal("1"), + order_type=OrderType.LIMIT, + ) + + payload = { + "Side": "Sell", + "OrderId": 9849, + "Price": 35000, + "Quantity": 1, + "Instrument": 1, + "Account": 4, + "OrderType": "Limit", + "ClientOrderId": 3, + "OrderState": "Working", + "ReceiveTime": 0, + "OrigQuantity": 1, + "QuantityExecuted": 0, + "AvgPrice": 0, + "ChangeReason": "NewInputAccepted" + } + message = { + "m": 3, + "i": 2, + "n": CONSTANTS.ORDER_STATE_EVENT_ENDPOINT_NAME, + "o": json.dumps(payload), + } + + mock_user_stream = AsyncMock() + mock_user_stream.get.side_effect = functools.partial(self._return_calculation_and_set_done_event, + lambda: message) + + self.exchange._user_stream_tracker._user_stream = mock_user_stream + + self.exchange_task = asyncio.get_event_loop().create_task(self.exchange._user_stream_event_listener()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + tracked_order: NdaxInFlightOrder = self.exchange.in_flight_orders["3"] + self.assertEqual(tracked_order.last_state, "Working") + + @patch( + "hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource.get_instrument_ids", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_create_order_cancels(self, mock_post, mock_get_instrument_ids): + mock_get_instrument_ids.return_value = { + self.trading_pair: 5 + } + + mock_post.side_effect = asyncio.CancelledError + + self._simulate_trading_rules_initialized() + + order_details = [ + TradeType.BUY, + str(1), + self.trading_pair, + Decimal(1.0), + Decimal(10.0), + OrderType.LIMIT, + ] + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + future = self._simulate_create_order(*order_details) + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(future) + + # InFlightOrder is still 1 since we do not know exactly where did the Cancel occur. + self.assertEqual(1, len(self.exchange.in_flight_orders)) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @patch( + "hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource.get_instrument_ids", + new_callable=AsyncMock) + def test_create_order_below_min_order_size_exception_raised(self, mock_get_instrument_ids, mock_main_app): + mock_get_instrument_ids.return_value = { + self.trading_pair: 5 + } + + self._simulate_trading_rules_initialized() + + order_details = [ + TradeType.BUY, + str(1), + self.trading_pair, + Decimal(str(0.0000001)), + Decimal(10.0), + OrderType.LIMIT, + ] + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + + self.exchange_task = asyncio.get_event_loop().create_task( + self.exchange._create_order(*order_details) + ) + + self.async_run_with_timeout(self.exchange_task) + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + self._is_logged("NETWORK", f"Error submitting {TradeType.BUY.name} {OrderType.LIMIT.name} order to NDAX") + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @patch( + "hummingbot.connector.exchange.ndax.ndax_api_order_book_data_source.NdaxAPIOrderBookDataSource.get_instrument_ids", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_create_order_api_returns_error_exception_raised(self, mock_post, mock_get_instrument_ids, _): + mock_get_instrument_ids.return_value = { + self.trading_pair: 5 + } + + expected_response = { + "status": "Rejected", + "errormsg": "Some Error Msg", + "OrderId": 123 + } + + self.mocking_assistant.configure_http_request_mock(mock_post) + self.mocking_assistant.add_http_response(mock_post, 200, expected_response, "") + + self._simulate_trading_rules_initialized() + + order_details = [ + TradeType.BUY, + str(1), + self.trading_pair, + Decimal(1.0), + Decimal(10.0), + OrderType.LIMIT, + ] + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + self.async_run_with_timeout( + self.exchange._create_order(*order_details) + ) + + self.assertEqual(0, len(self.exchange.in_flight_orders)) + self._is_logged("NETWORK", + f"Error submitting {TradeType.BUY.name} {OrderType.LIMIT.name} order to NDAX") + + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_execute_cancel_success(self, mock_cancel): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="Working", + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + mock_response = { + "result": True, + "errormsg": None, + "errorcode": 0, + "detail": None + } + + self.mocking_assistant.configure_http_request_mock(mock_cancel) + self.mocking_assistant.add_http_response(mock_cancel, 200, mock_response, "") + + result = asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order.client_order_id) + ) + + self.assertEqual(result, order.client_order_id) + + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_execute_cancel_all_success(self, mock_get_request, mock_post_request): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + # Simulate _trading_pair_id_map initialized. + self.exchange.order_book_tracker.data_source._trading_pair_id_map.update({ + self.trading_pair: 5 + }) + + mock_open_orders_response = [ + { + "Side": "Buy", + "OrderId": 123, + "Price": 10.0, + "Quantity": 1.0, + "DisplayQuantity": 1.0, + "Instrument": 5, + "Account": 1, + "OrderType": "Limit", + "ClientOrderId": 0, + "OrderState": "Working", + "ReceiveTime": 0, + "ReceiveTimeTicks": 0, + "OrigQuantity": 1.0, + "QuantityExecuted": 0.0, + "AvgPrice": 0.0, + "CounterPartyId": 0, + "ChangeReason": "Unknown", + "OrigOrderId": 0, + "OrigClOrdId": 0, + "EnteredBy": 0, + "IsQuote": False, + "InsideAsk": 0.0, + "InsideAskSize": 0.0, + "InsideBid": 0.0, + "InsideBidSize": 0.0, + "LastTradePrice": 0.0, + "RejectReason": "", + "IsLockedIn": False, + "CancelReason": "", + "OMSId": 1 + }, + ] + self.mocking_assistant.configure_http_request_mock(mock_get_request) + self.mocking_assistant.add_http_response(mock_get_request, 200, mock_open_orders_response, "") + + mock_response = { + "result": True, + "errormsg": None, + "errorcode": 0, + "detail": None + } + self.mocking_assistant.configure_http_request_mock(mock_post_request) + self.mocking_assistant.add_http_response(mock_post_request, 200, mock_response, "") + + cancellation_results = asyncio.new_event_loop().run_until_complete( + self.exchange.cancel_all(10) + ) + + self.assertEqual(1, len(cancellation_results)) + self.assertEqual("0", cancellation_results[0].order_id) + self.assertTrue(cancellation_results[0].success) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_execute_cancel_fail(self, mock_cancel, mock_main_app): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="Working", + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + # Regardless of Response, the method returns the client_order_id + # Actual cancellation verification is done using WebSocket message or REST API. + mock_response = { + "result": False, + "errormsg": "Invalid Request", + "errorcode": 100, + "detail": None + } + + self.mocking_assistant.configure_http_request_mock(mock_cancel) + self.mocking_assistant.add_http_response(mock_cancel, 200, mock_response, "") + + result = asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order.client_order_id) + ) + + self.assertIsNone(result) + + @patch("aiohttp.ClientSession.post", new_callable=AsyncMock) + def test_execute_cancel_cancels(self, mock_cancel): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="Working", + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + mock_cancel.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order.client_order_id) + ) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._api_request", new_callable=AsyncMock) + def test_execute_cancel_exception_raised(self, mock_request, mock_main_app): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="Working", + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + mock_request.side_effect = lambda: self._create_exception_and_unlock_test_with_event( + Exception("Dummy test error")) + + asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order.client_order_id) + ) + + self._is_logged("ERROR", f"Failed to cancel order {order.client_order_id}") + + @aioresponses() + def test_execute_cancel_exception_raised_stop_tracking_order(self, mock_api): + path_url = CONSTANTS.CANCEL_ORDER_PATH_URL + url = ndax_utils.rest_api_url(None) + path_url + regex_url = re.compile(f"^{url}") + + mock_response = { + "result": False, + "errormsg": "Resource Not Found", + "errorcode": 104, + "detail": None + } + + mock_api.post(regex_url, body=json.dumps(mock_response), callback=self._mock_responses_done_callback) + + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="Working", + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order.client_order_id) + ) + + self._is_logged("WARNING", + f"Order {order.client_order_id} does not seem to be active, will stop tracking order...") + + @patch("hummingbot.connector.exchange.ndax.ndax_exchange.NdaxExchange._execute_cancel", new_callable=AsyncMock) + def test_cancel(self, mock_cancel): + mock_cancel.return_value = None + + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + # Note: BUY simply returns immediately with the client order id. + return_val: str = self.exchange.cancel(self.trading_pair, order.client_order_id) + + # Order ID is simply a timestamp. The assertion below checks if it is created within 1 sec + self.assertTrue(order.client_order_id, return_val) + + def test_cancel_rate_limited_order(self): + order_id = str(1) + order_details = [ + TradeType.BUY, + order_id, + self.trading_pair, + Decimal(1.0), + None, + OrderType.MARKET, + ] + self._simulate_create_order(*order_details) + with self.assertRaises(NdaxInFlightOrderNotCreated): + asyncio.new_event_loop().run_until_complete( + self.exchange._execute_cancel(self.trading_pair, order_id) + ) + + def test_ready_trading_required_all_ready(self): + self.exchange._trading_required = True + + # Simulate all components initialized + self.exchange._account_id = 1 + self.exchange.order_book_tracker._order_books_initialized.set() + self.exchange._account_balances = { + self.base_asset: Decimal(str(10.0)) + } + self._simulate_trading_rules_initialized() + self.exchange._user_stream_tracker.data_source._last_recv_time = 1 + + self.assertTrue(self.exchange.ready) + + def test_ready_trading_required_not_ready(self): + self.exchange._trading_required = True + + # Simulate all components but account_id not initialized + self.exchange._account_id = None + self.exchange.order_book_tracker._order_books_initialized.set() + self.exchange._account_balances = {} + self._simulate_trading_rules_initialized() + self.exchange._user_stream_tracker.data_source._last_recv_time = 0 + + self.assertFalse(self.exchange.ready) + + def test_ready_trading_not_required_ready(self): + self.exchange._trading_required = False + + # Simulate all components but account_id not initialized + self.exchange._account_id = None + self.exchange.order_book_tracker._order_books_initialized.set() + self.exchange._account_balances = {} + self._simulate_trading_rules_initialized() + self.exchange._user_stream_tracker.data_source._last_recv_time = 0 + + self.assertTrue(self.exchange.ready) + + def test_ready_trading_not_required_not_ready(self): + self.exchange._trading_required = False + self.assertFalse(self.exchange.ready) + + def test_limit_orders(self): + self.assertEqual(0, len(self.exchange.limit_orders)) + + # Simulate orders being placed and tracked + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + self.assertEqual(1, len(self.exchange.limit_orders)) + + def test_tracking_states_order_not_done(self): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0) + + order_json = order.to_json() + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + self.assertEqual(1, len(self.exchange.tracking_states)) + self.assertEqual(order_json, self.exchange.tracking_states[order.client_order_id]) + + def test_tracking_states_order_done(self): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0, + initial_state="FullyExecuted" + ) + + self.exchange._in_flight_orders.update({ + order.client_order_id: order + }) + + self.assertEqual(0, len(self.exchange.tracking_states)) + + def test_restore_tracking_states(self): + order: NdaxInFlightOrder = NdaxInFlightOrder( + client_order_id="0", + exchange_order_id="123", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(10.0), + amount=Decimal(1.0), + creation_timestamp=1640001112.0) + + order_json = order.to_json() + + self.exchange.restore_tracking_states({order.client_order_id: order_json}) + + self.assertEqual(1, len(self.exchange.in_flight_orders)) + self.assertEqual(str(self.exchange.in_flight_orders[order.client_order_id]), str(order)) + + @patch("aiohttp.ClientSession.get", new_callable=AsyncMock) + def test_rest_api_limit_reached_error(self, mock_get_request): + self.mocking_assistant.configure_http_request_mock(mock_get_request) + self.mocking_assistant.add_http_response(mock_get_request, 200, {}, CONSTANTS.API_LIMIT_REACHED_ERROR_MESSAGE) + + with self.assertRaises(IOError) as exception_context: + asyncio.new_event_loop().run_until_complete( + self.exchange._api_request("GET", CONSTANTS.MARKETS_URL) + ) + + self.assertTrue("Error: The exchange API request limit has been reached (original error 'TOO MANY REQUESTS')" + in f"{exception_context.exception}") + + def test_start_network_warning_is_logged(self): + self.async_run_with_timeout(self.exchange.start_network()) + + self.assertTrue(self._is_logged('WARNING', "This exchange connector does not provide trades feed. " + "Strategies which depend on it will not work properly.")) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_in_flight_order.py b/test/hummingbot/connector/exchange/ndax/test_ndax_in_flight_order.py new file mode 100644 index 0000000..018d59c --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_in_flight_order.py @@ -0,0 +1,154 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.connector.exchange.ndax.ndax_in_flight_order import NdaxInFlightOrder, WORKING_LOCAL_STATUS +from hummingbot.core.data_type.common import OrderType, TradeType + + +class NdaxInFlightOrderTests(TestCase): + + def _example_json(self): + return {"client_order_id": "C1", + "exchange_order_id": "1", + "trading_pair": "BTC-USDT", + "order_type": "LIMIT", + "trade_type": "BUY", + "price": "35000", + "amount": "1.1", + "creation_timestamp": 1640001112.0, + "last_state": "Working", + "executed_amount_base": "0.5", + "executed_amount_quote": "15000", + "fee_asset": "BTC", + "fee_paid": "0"} + + def test_instance_creation(self): + order = NdaxInFlightOrder(client_order_id="C1", + exchange_order_id="1", + trading_pair="BTC-USDT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("35000"), + amount=Decimal("1.1"), + creation_timestamp=1640001112.0) + + self.assertEqual("C1", order.client_order_id) + self.assertEqual("1", order.exchange_order_id) + self.assertEqual("BTC-USDT", order.trading_pair) + self.assertEqual(OrderType.LIMIT, order.order_type) + self.assertEqual(TradeType.SELL, order.trade_type) + self.assertEqual(Decimal("35000"), order.price) + self.assertEqual(Decimal("1.1"), order.amount) + self.assertEqual(Decimal("0"), order.executed_amount_base) + self.assertEqual(Decimal("0"), order.executed_amount_quote) + self.assertEqual(order.quote_asset, order.fee_asset) + self.assertEqual(Decimal("0"), order.fee_paid) + self.assertEqual(WORKING_LOCAL_STATUS, order.last_state) + + def test_create_from_json(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + self.assertEqual("C1", order.client_order_id) + self.assertEqual("1", order.exchange_order_id) + self.assertEqual("BTC-USDT", order.trading_pair) + self.assertEqual(OrderType.LIMIT, order.order_type) + self.assertEqual(TradeType.BUY, order.trade_type) + self.assertEqual(Decimal("35000"), order.price) + self.assertEqual(Decimal("1.1"), order.amount) + self.assertEqual(Decimal("0.5"), order.executed_amount_base) + self.assertEqual(Decimal("15000"), order.executed_amount_quote) + self.assertEqual(order.base_asset, order.fee_asset) + self.assertEqual(Decimal("0"), order.fee_paid) + self.assertEqual("Working", order.last_state) + + def test_is_done(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + self.assertFalse(order.is_done) + + for status in ["FullyExecuted", "Canceled", "Rejected", "Expired"]: + order.last_state = status + self.assertTrue(order.is_done) + + def test_is_failure(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + for status in ["Working", "FullyExecuted", "Canceled", "Expired"]: + order.last_state = status + self.assertFalse(order.is_failure) + + order.last_state = "Rejected" + self.assertTrue(order.is_failure) + + def test_is_cancelled(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + for status in ["Working", "FullyExecuted", "Rejected"]: + order.last_state = status + self.assertFalse(order.is_cancelled) + + for status in ["Canceled", "Expired"]: + order.last_state = status + self.assertTrue(order.is_cancelled) + + def test_mark_as_filled(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + order.mark_as_filled() + self.assertEqual("FullyExecuted", order.last_state) + + def test_to_json(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + self.assertEqual(self._example_json(), order.to_json()) + + def test_update_with_trade_update(self): + order = NdaxInFlightOrder.from_json(self._example_json()) + + trade_update_for_different_order_id = { + "OMSId": 1, + "TradeId": 213, + "OrderId": 5, + "AccountId": 4, + "ClientOrderId": 0, + "InstrumentId": 1, + "Side": "Buy", + "Quantity": 0.01, + "Price": 95, + "Value": 0.95, + "TradeTime": 635978008210426109, + "ContraAcctId": 3, + "OrderTradeRevision": 1, + "Direction": "NoChange" + } + + update_result = order.update_with_trade_update(trade_update_for_different_order_id) + self.assertFalse(update_result) + + valid_trade_update = { + "OMSId": 1, + "TradeId": 213, + "OrderId": 1, + "AccountId": 4, + "ClientOrderId": 0, + "InstrumentId": 1, + "Side": "Buy", + "Quantity": 0.1, + "Price": 35000, + "Value": 3500, + "TradeTime": 635978008210426109, + "ContraAcctId": 3, + "OrderTradeRevision": 1, + "Direction": "NoChange" + } + + update_result = order.update_with_trade_update(valid_trade_update) + self.assertTrue(update_result) + self.assertEqual(Decimal("0.1") + Decimal(self._example_json()["executed_amount_base"]), + order.executed_amount_base) + self.assertEqual(Decimal("3500") + Decimal(self._example_json()["executed_amount_quote"]), + order.executed_amount_quote) + + repeated_trade_update = valid_trade_update + update_result = order.update_with_trade_update(repeated_trade_update) + self.assertFalse(update_result) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_message.py b/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_message.py new file mode 100644 index 0000000..f5acb8f --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_message.py @@ -0,0 +1,82 @@ +import time + +from unittest import TestCase + +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookMessage, NdaxOrderBookEntry +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class NdaxOrderBookMessageTests(TestCase): + + def test_equality_based_on_type_and_timestamp(self): + message = NdaxOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT, + content={"data": []}, + timestamp=10000000) + equal_message = NdaxOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT, + content={"data": []}, + timestamp=10000000) + message_with_different_type = NdaxOrderBookMessage(message_type=OrderBookMessageType.DIFF, + content={"data": []}, + timestamp=10000000) + message_with_different_timestamp = NdaxOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT, + content={"data": []}, + timestamp=90000000) + + self.assertEqual(message, message) + self.assertEqual(message, equal_message) + self.assertNotEqual(message, message_with_different_type) + self.assertNotEqual(message, message_with_different_timestamp) + + def test_equal_messages_have_equal_hash(self): + message = NdaxOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT, + content={"data": []}, + timestamp=10000000) + equal_message = NdaxOrderBookMessage(message_type=OrderBookMessageType.SNAPSHOT, + content={"data": []}, + timestamp=10000000) + + self.assertEqual(hash(message), hash(equal_message)) + + def test_delete_buy_order_book_entry_always_has_zero_amount(self): + entries = [NdaxOrderBookEntry(mdUpdateId=1, + accountId=1, + actionDateTime=1627935956059, + actionType=2, + lastTradePrice=42211.51, + orderId=1, + price=41508.19, + productPairCode=5, + quantity=1.5, + side=0)] + content = {"data": entries} + message = NdaxOrderBookMessage(message_type=OrderBookMessageType.DIFF, + content=content, + timestamp=time.time()) + bids = message.bids + + self.assertEqual(1, len(bids)) + self.assertEqual(41508.19, bids[0].price) + self.assertEqual(0.0, bids[0].amount) + self.assertEqual(1, bids[0].update_id) + + def test_delete_sell_order_book_entry_always_has_zero_amount(self): + entries = [NdaxOrderBookEntry(mdUpdateId=1, + accountId=1, + actionDateTime=1627935956059, + actionType=2, + lastTradePrice=42211.51, + orderId=1, + price=41508.19, + productPairCode=5, + quantity=1.5, + side=1)] + content = {"data": entries} + message = NdaxOrderBookMessage(message_type=OrderBookMessageType.DIFF, + content=content, + timestamp=time.time()) + asks = message.asks + + self.assertEqual(1, len(asks)) + self.assertEqual(41508.19, asks[0].price) + self.assertEqual(0.0, asks[0].amount) + self.assertEqual(1, asks[0].update_id) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_tracker.py b/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_tracker.py new file mode 100644 index 0000000..bbdcef7 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_order_book_tracker.py @@ -0,0 +1,134 @@ +import unittest +import asyncio +from decimal import Decimal +from typing import Any +from unittest.mock import patch, AsyncMock + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.connector.exchange.ndax.ndax_order_book import NdaxOrderBook +from hummingbot.connector.exchange.ndax.ndax_order_book_message import NdaxOrderBookEntry, NdaxOrderBookMessage +from hummingbot.connector.exchange.ndax.ndax_order_book_tracker import NdaxOrderBookTracker +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class NdaxOrderBookTrackerUnitTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.instrument_id = 1 + + cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + def setUp(self) -> None: + super().setUp() + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.tracker: NdaxOrderBookTracker = NdaxOrderBookTracker(throttler=throttler, trading_pairs=[self.trading_pair]) + self.tracking_task = None + + # Simulate start() + self.tracker._order_books[self.trading_pair] = NdaxOrderBook() + self.tracker._tracking_message_queues[self.trading_pair] = asyncio.Queue() + self.tracker._order_books_initialized.set() + + def tearDown(self) -> None: + self.tracking_task and self.tracking_task.cancel() + if len(self.tracker._tracking_tasks) > 0: + for task in self.tracker._tracking_tasks.values(): + task.cancel() + super().tearDown() + + def simulate_trading_pair_ids_initialized(self): + self.tracker._data_source._trading_pair_id_map.update({self.trading_pair: self.instrument_id}) + + @staticmethod + def set_mock_response(mock_api, status: int, json_data: Any): + mock_api.return_value.__aenter__.return_value.status = status + mock_api.return_value.__aenter__.return_value.json = AsyncMock(return_value=json_data) + + def simulate_queue_order_book_messages(self, message: NdaxOrderBookMessage): + message_queue = self.tracker._tracking_message_queues[self.trading_pair] + message_queue.put_nowait(message) + + def simulate_saving_order_book_diff_messages(self, message: NdaxOrderBookMessage): + message_queue = self.tracker._saved_message_queues[self.trading_pair] + message_queue.put_nowait(message) + + def test_exchange_name(self): + self.assertEqual(self.tracker.exchange_name, CONSTANTS.EXCHANGE_NAME) + + def test_track_single_book_apply_snapshot(self): + snapshot_data = [ + NdaxOrderBookEntry(*[93617617, 1, 1626788175000, 0, 37800.0, 1, 37750.0, 1, 0.015, 0]), + NdaxOrderBookEntry(*[93617617, 1, 1626788175000, 0, 37800.0, 1, 37751.0, 1, 0.015, 1]) + ] + snapshot_msg = NdaxOrderBook.snapshot_message_from_exchange( + msg={"data": snapshot_data}, + timestamp=1626788175000, + metadata={"trading_pair": self.trading_pair, "instrument_id": self.instrument_id} + ) + self.simulate_queue_order_book_messages(snapshot_msg) + + with self.assertRaises(asyncio.TimeoutError): + # Allow 5 seconds for tracker to process some messages. + self.tracking_task = self.ev_loop.create_task(asyncio.wait_for( + self.tracker._track_single_book(self.trading_pair), + 2.0 + )) + self.ev_loop.run_until_complete(self.tracking_task) + + self.assertEqual(0, self.tracker.order_books[self.trading_pair].snapshot_uid) + + @patch("aiohttp.ClientSession.get") + def test_init_order_books(self, mock_api): + self.simulate_trading_pair_ids_initialized() + mock_response = [ + # mdUpdateId, accountId, actionDateTime, actionType, lastTradePrice, orderId, price, productPairCode, quantity, side + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37750.0, 1, 0.015, 0], + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37751.0, 1, 0.015, 1] + ] + self.set_mock_response(mock_api, 200, mock_response) + + self.tracker._order_books_initialized.clear() + self.tracker._tracking_message_queues.clear() + self.tracker._tracking_tasks.clear() + self.tracker._order_books.clear() + + self.assertEqual(0, len(self.tracker.order_books)) + self.assertEqual(0, len(self.tracker._tracking_message_queues)) + self.assertEqual(0, len(self.tracker._tracking_tasks)) + self.assertFalse(self.tracker._order_books_initialized.is_set()) + + init_order_books_task = self.ev_loop.create_task( + self.tracker._init_order_books() + ) + + self.ev_loop.run_until_complete(init_order_books_task) + + self.assertIsInstance(self.tracker.order_books[self.trading_pair], OrderBook) + self.assertTrue(self.tracker._order_books_initialized.is_set()) + + @patch("aiohttp.ClientSession.get") + def test_can_get_price_after_order_book_init(self, mock_api): + self.simulate_trading_pair_ids_initialized() + mock_response = [ + # mdUpdateId, accountId, actionDateTime, actionType, lastTradePrice, orderId, price, productPairCode, quantity, side + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37750.0, 1, 0.015, 0], + [93617617, 1, 1626788175416, 0, 37800.0, 1, 37751.0, 1, 0.015, 1] + ] + self.set_mock_response(mock_api, 200, mock_response) + + init_order_books_task = self.ev_loop.create_task( + self.tracker._init_order_books() + ) + self.ev_loop.run_until_complete(init_order_books_task) + + ob = self.tracker.order_books[self.trading_pair] + ask_price = ob.get_price(True) + + self.assertEqual(Decimal("37751.0"), ask_price) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_user_stream_tracker.py b/test/hummingbot/connector/exchange/ndax/test_ndax_user_stream_tracker.py new file mode 100644 index 0000000..fb904aa --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_user_stream_tracker.py @@ -0,0 +1,78 @@ +import asyncio +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, patch + +import hummingbot.connector.exchange.ndax.ndax_constants as CONSTANTS +from hummingbot.connector.exchange.ndax.ndax_auth import NdaxAuth +from hummingbot.connector.exchange.ndax.ndax_user_stream_tracker import NdaxUserStreamTracker +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class NdaxUserStreamTrackerTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.ws_sent_messages = [] + self.ws_incoming_messages = asyncio.Queue() + self.listening_task = None + + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + auth_assistant = NdaxAuth(uid='001', + api_key='testAPIKey', + secret_key='testSecret', + account_name="hbot") + self.tracker = NdaxUserStreamTracker(throttler=throttler, auth_assistant=auth_assistant) + + self.mocking_assistant = NetworkMockingAssistant() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _authentication_response(self, authenticated: bool) -> str: + user = {"UserId": 492, + "UserName": "hbot", + "Email": "hbot@mailinator.com", + "EmailVerified": True, + "AccountId": 528, + "OMSId": 1, + "Use2FA": True} + payload = {"Authenticated": authenticated, + "SessionToken": "74e7c5b0-26b1-4ca5-b852-79b796b0e599", + "User": user, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None} + message = {"m": 1, + "i": 1, + "n": CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, + "o": json.dumps(payload)} + + return json.dumps(message) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.listening_task = asyncio.get_event_loop().create_task( + self.tracker.start()) + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(True)) + # Add a dummy message for the websocket to read and include in the "messages" queue + self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps('dummyMessage')) + + first_received_message = self.async_run_with_timeout(self.tracker.user_stream.get()) + + self.assertEqual('dummyMessage', first_received_message) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_utils.py b/test/hummingbot/connector/exchange/ndax/test_ndax_utils.py new file mode 100644 index 0000000..64f16bd --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_utils.py @@ -0,0 +1,36 @@ +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS, ndax_utils as utils + + +class NdaxUtilsTests(TestCase): + + def test_trading_pair_convertion(self): + trading_pair = "BTC-USDT" + self.assertEqual("BTCUSDT", utils.convert_to_exchange_trading_pair(trading_pair)) + + @patch('hummingbot.connector.exchange.ndax.ndax_utils.get_tracking_nonce') + def test_client_order_id_creation(self, nonce_provider_mock): + nonce_provider_mock.return_value = 1000 + self.assertEqual(f"{utils.HUMMINGBOT_ID_PREFIX}{1000}", utils.get_new_client_order_id(True, "BTC-USDT")) + + def test_rest_api_url(self): + url = utils.rest_api_url(None) + self.assertEqual(CONSTANTS.REST_URLS.get("ndax_main"), url) + + url = utils.rest_api_url("ndax_main") + self.assertEqual(CONSTANTS.REST_URLS.get("ndax_main"), url) + + url = utils.rest_api_url("ndax_testnet") + self.assertEqual(CONSTANTS.REST_URLS.get("ndax_testnet"), url) + + def test_wss_url(self): + url = utils.wss_url(None) + self.assertEqual(CONSTANTS.WSS_URLS.get("ndax_main"), url) + + url = utils.wss_url("ndax_main") + self.assertEqual(CONSTANTS.WSS_URLS.get("ndax_main"), url) + + url = utils.wss_url("ndax_testnet") + self.assertEqual(CONSTANTS.WSS_URLS.get("ndax_testnet"), url) diff --git a/test/hummingbot/connector/exchange/ndax/test_ndax_websocket_adaptor.py b/test/hummingbot/connector/exchange/ndax/test_ndax_websocket_adaptor.py new file mode 100644 index 0000000..d8fdff8 --- /dev/null +++ b/test/hummingbot/connector/exchange/ndax/test_ndax_websocket_adaptor.py @@ -0,0 +1,124 @@ +import asyncio +import json +from typing import Awaitable +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.connector.exchange.ndax import ndax_constants as CONSTANTS +from hummingbot.connector.exchange.ndax.ndax_websocket_adaptor import NdaxWebSocketAdaptor +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class NdaxWebSocketAdaptorTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect") + def test_sending_messages_increment_message_number(self, mock_ws): + sent_messages = [] + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.send_json.side_effect = lambda sent_message: sent_messages.append(sent_message) + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + payload = {} + self.async_run_with_timeout(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, + payload=payload, + limit_id=CONSTANTS.WS_PING_ID)) + self.async_run_with_timeout(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, + payload=payload, + limit_id=CONSTANTS.WS_PING_ID)) + self.async_run_with_timeout(adaptor.send_request(endpoint_name=CONSTANTS.WS_ORDER_BOOK_CHANNEL, + payload=payload)) + self.assertEqual(3, len(sent_messages)) + + message = sent_messages[0] + self.assertEqual(1, message.get('i')) + message = sent_messages[1] + self.assertEqual(2, message.get('i')) + message = sent_messages[2] + self.assertEqual(3, message.get('i')) + + @patch("aiohttp.ClientSession.ws_connect") + def test_request_message_structure(self, mock_ws): + sent_messages = [] + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.send_json.side_effect = lambda sent_message: sent_messages.append(sent_message) + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + payload = {"TestElement1": "Value1", "TestElement2": "Value2"} + self.async_run_with_timeout(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, + payload=payload, + limit_id=CONSTANTS.WS_PING_ID)) + + self.assertEqual(1, len(sent_messages)) + message = sent_messages[0] + + self.assertEqual(0, message.get('m')) + self.assertEqual(1, message.get('i')) + self.assertEqual(CONSTANTS.WS_PING_REQUEST, message.get('n')) + message_payload = json.loads(message.get('o')) + self.assertEqual(payload, message_payload) + + @patch("aiohttp.ClientSession.ws_connect") + def test_receive_message(self, mock_ws): + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, 'test message') + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + received_message = self.async_run_with_timeout(adaptor.receive()) + + self.assertEqual('test message', received_message.data) + + @patch("aiohttp.ClientSession.ws_connect") + def test_close(self, mock_ws): + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + self.async_run_with_timeout(adaptor.close()) + + self.assertEquals(1, mock_ws.return_value.close.await_count) + + @patch("aiohttp.ClientSession.ws_connect") + def test_get_payload_from_raw_received_message(self, mock_ws): + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + payload = {"Key1": True, + "Key2": "Value2"} + message = {"m": 1, + "i": 1, + "n": "Endpoint", + "o": json.dumps(payload)} + raw_message = json.dumps(message) + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + extracted_payload = adaptor.payload_from_raw_message(raw_message=raw_message) + + self.assertEqual(payload, extracted_payload) + + @patch("aiohttp.ClientSession.ws_connect") + def test_get_endpoint_from_raw_received_message(self, mock_ws): + throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + payload = {"Key1": True, + "Key2": "Value2"} + message = {"m": 1, + "i": 1, + "n": "Endpoint", + "o": json.dumps(payload)} + raw_message = json.dumps(message) + + adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) + extracted_endpoint = adaptor.endpoint_from_raw_message(raw_message=raw_message) + + self.assertEqual("Endpoint", extracted_endpoint) diff --git a/test/hummingbot/connector/exchange/okx/__init__.py b/test/hummingbot/connector/exchange/okx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/okx/test_okx_api_order_book_data_source.py b/test/hummingbot/connector/exchange/okx/test_okx_api_order_book_data_source.py new file mode 100644 index 0000000..c51ef0c --- /dev/null +++ b/test/hummingbot/connector/exchange/okx/test_okx_api_order_book_data_source.py @@ -0,0 +1,624 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +import hummingbot.connector.exchange.okx.okx_constants as CONSTANTS +import hummingbot.connector.exchange.okx.okx_web_utils as web_utils +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.okx.okx_api_order_book_data_source import OkxAPIOrderBookDataSource +from hummingbot.connector.exchange.okx.okx_exchange import OkxExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class OkxAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = OkxExchange( + client_config_map=client_config_map, + okx_api_key="", + okx_secret_key="", + okx_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.data_source = OkxAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map( + bidict({f"{self.base_asset}-{self.quote_asset}": self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = { + "code": "0", + "msg": "", + "data": [ + { + "asks": [ + [ + "41006.8", + "0.60038921", + "0", + "1" + ] + ], + "bids": [ + [ + "41006.3", + "0.30178218", + "0", + "2" + ] + ], + "ts": "1629966436396" + } + ] + } + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = int(int(resp["data"][0]["ts"]) * 1e-3) + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(41006.3, bids[0].price) + self.assertEqual(0.30178218, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(41006.8, asks[0].price) + self.assertEqual(0.60038921, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "event": "subscribe", + "args": { + "channel": "trades", + "instId": self.trading_pair + } + } + result_subscribe_diffs = { + "event": "subscribe", + "arg": { + "channel": "books", + "instId": self.trading_pair + } + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs)) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = { + "op": "subscribe", + "args": [ + { + "channel": "trades", + "instId": self.trading_pair + } + ] + } + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = { + "op": "subscribe", + "args": [ + { + "channel": "books", + "instId": self.trading_pair + } + ] + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_sends_ping_message_before_ping_interval_finishes(self, ws_connect_mock): + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = [asyncio.TimeoutError("Test timeiout"), + asyncio.CancelledError] + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + sent_messages = self.mocking_assistant.text_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = "ping" + self.assertEqual(expected_ping_message, sent_messages[0]) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "arg": { + "channel": "trades", + "instId": "BTC-USDT" + }, + "data": [ + { + "instId": "BTC-USDT", + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "arg": { + "channel": "trades", + "instId": self.trading_pair + }, + "data": [ + { + "instId": self.trading_pair, + "tradeId": "130639474", + "px": "42219.9", + "sz": "0.12060306", + "side": "buy", + "ts": "1630048897897" + } + ] + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(trade_event["data"][0]["tradeId"], msg.trade_id) + self.assertEqual(int(trade_event["data"][0]["ts"]) * 1e-3, msg.timestamp) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "update", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "update", + "data": [ + { + "asks": [ + ["8476.98", "415", "0", "13"], + ["8477", "7", "0", "2"], + ["8477.34", "85", "0", "1"], + ], + "bids": [ + ["8476.97", "256", "0", "12"], + ["8475.55", "101", "0", "1"], + ], + "ts": "1597026383085", + "checksum": -855196043 + } + ] + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(diff_event["data"][0]["ts"]) * 1e-3, msg.timestamp) + expected_update_id = int(int(diff_event["data"][0]["ts"]) * 1e-3) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(8476.97, bids[0].price) + self.assertEqual(256, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(3, len(asks)) + self.assertEqual(8476.98, asks[0].price) + self.assertEqual(415, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_order_book_snapshots_websocket_successful(self): + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = 1 + mock_queue = AsyncMock() + snapshot_event = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "snapshot", + "data": [ + { + "asks": [ + ["8476.98", "415", "0", "13"], + ["8477", "7", "0", "2"], + ["8477.34", "85", "0", "1"], + ], + "bids": [ + ["8476.97", "256", "0", "12"], + ["8475.55", "101", "0", "1"], + ], + "ts": "1597026383085", + "checksum": -855196043 + } + ] + } + mock_queue.get.side_effect = [snapshot_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._snapshot_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(snapshot_event["data"][0]["ts"]) * 1e-3, msg.timestamp) + expected_update_id = int(int(snapshot_event["data"][0]["ts"]) * 1e-3) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(8476.97, bids[0].price) + self.assertEqual(256, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(3, len(asks)) + self.assertEqual(8476.98, asks[0].price) + self.assertEqual(415, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.okx.okx_api_order_book_data_source" + ".OkxAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_api_successful(self, mock_api, ): + msg_queue: asyncio.Queue = asyncio.Queue() + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_ORDER_BOOK_PATH) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = { + "code": "0", + "msg": "", + "data": [ + { + "asks": [ + [ + "41006.8", + "0.60038921", + "0", + "1" + ] + ], + "bids": [ + [ + "41006.3", + "0.30178218", + "0", + "2" + ] + ], + "ts": "1629966436396" + } + ] + } + + mock_api.get(regex_url, body=json.dumps(resp)) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(int(resp["data"][0]["ts"]) * 1e-3, msg.timestamp) + expected_update_id = int(int(resp["data"][0]["ts"]) * 1e-3) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(41006.3, bids[0].price) + self.assertEqual(0.30178218, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(41006.8, asks[0].price) + self.assertEqual(0.60038921, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_channel_originating_message_snapshot_queue(self): + event_message = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "snapshot", + "data": [ + { + "asks": [ + ["8476.98", "415", "0", "13"], + ], + "bids": [ + ["8476.97", "256", "0", "12"], + ], + "ts": "1597026383085", + "checksum": -855196043 + } + ] + } + channel_result = self.data_source._channel_originating_message(event_message) + self.assertEqual(channel_result, self.data_source._snapshot_messages_queue_key) + + def test_channel_originating_message_diff_queue(self): + event_message = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "update", + "data": [ + { + "asks": [ + ["8476.98", "415", "0", "13"], + ], + "bids": [ + ["8476.97", "256", "0", "12"], + ], + "ts": "1597026383085", + "checksum": -855196043 + } + ] + } + channel_result = self.data_source._channel_originating_message(event_message) + self.assertEqual(channel_result, self.data_source._diff_messages_queue_key) diff --git a/test/hummingbot/connector/exchange/okx/test_okx_auth.py b/test/hummingbot/connector/exchange/okx/test_okx_auth.py new file mode 100644 index 0000000..a864cd6 --- /dev/null +++ b/test/hummingbot/connector/exchange/okx/test_okx_auth.py @@ -0,0 +1,112 @@ +import asyncio +import base64 +import hashlib +import hmac +import json +from datetime import datetime +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.connector.exchange.okx.okx_auth import OkxAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class OkxAuthTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.api_key = "testApiKey" + self.passphrase = "testPassphrase" + self.secret_key = "testSecretKey" + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.auth = OkxAuth( + api_key=self.api_key, + secret_key=self.secret_key, + passphrase=self.passphrase, + time_provider=self.mock_time_provider, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _sign(self, message: str, key: str) -> str: + signed_message = base64.b64encode( + hmac.new( + key.encode("utf-8"), + message.encode("utf-8"), + hashlib.sha256).digest()) + return signed_message.decode("utf-8") + + def _format_timestamp(self, timestamp: int) -> str: + return datetime.utcfromtimestamp(timestamp).isoformat(timespec="milliseconds") + 'Z' + + def test_add_auth_headers_to_get_request_without_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + expected_timestamp = self._format_timestamp(timestamp=1000) + self.assertEqual(self.api_key, request.headers["OK-ACCESS-KEY"]) + self.assertEqual(expected_timestamp, request.headers["OK-ACCESS-TIMESTAMP"]) + expected_signature = self._sign(expected_timestamp + "GET" + request.throttler_limit_id, key=self.secret_key) + self.assertEqual(expected_signature, request.headers["OK-ACCESS-SIGN"]) + expected_passphrase = self.passphrase + self.assertEqual(expected_passphrase, request.headers["OK-ACCESS-PASSPHRASE"]) + + def test_add_auth_headers_to_get_request_with_params(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + params = {'ordId': '123', 'instId': 'BTC-USDT'}, + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + expected_timestamp = self._format_timestamp(timestamp=1000) + self.assertEqual(self.api_key, request.headers["OK-ACCESS-KEY"]) + self.assertEqual(expected_timestamp, request.headers["OK-ACCESS-TIMESTAMP"]) + expected_signature = self._sign(expected_timestamp + "GET" + f"{request.throttler_limit_id}?ordId=123&instId=BTC-USDT", key=self.secret_key) + self.assertEqual(expected_signature, request.headers["OK-ACCESS-SIGN"]) + expected_passphrase = self.passphrase + self.assertEqual(expected_passphrase, request.headers["OK-ACCESS-PASSPHRASE"]) + + def test_add_auth_headers_to_post_request(self): + body = {"param_z": "value_param_z", "param_a": "value_param_a"} + request = RESTRequest( + method=RESTMethod.POST, + url="https://test.url/api/endpoint", + data=json.dumps(body), + is_auth_required=True, + throttler_limit_id="/api/endpoint" + ) + + self.async_run_with_timeout(self.auth.rest_authenticate(request)) + + expected_timestamp = self._format_timestamp(timestamp=1000) + self.assertEqual(self.api_key, request.headers["OK-ACCESS-KEY"]) + self.assertEqual(expected_timestamp, request.headers["OK-ACCESS-TIMESTAMP"]) + expected_signature = self._sign(expected_timestamp + "POST" + request.throttler_limit_id + json.dumps(body), + key=self.secret_key) + self.assertEqual(expected_signature, request.headers["OK-ACCESS-SIGN"]) + expected_passphrase = self.passphrase + self.assertEqual(expected_passphrase, request.headers["OK-ACCESS-PASSPHRASE"]) + + def test_no_auth_added_to_wsrequest(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=True) + + self.async_run_with_timeout(self.auth.ws_authenticate(request)) + + self.assertEqual(payload, request.payload) diff --git a/test/hummingbot/connector/exchange/okx/test_okx_exchange.py b/test/hummingbot/connector/exchange/okx/test_okx_exchange.py new file mode 100644 index 0000000..772a753 --- /dev/null +++ b/test/hummingbot/connector/exchange/okx/test_okx_exchange.py @@ -0,0 +1,1184 @@ +import asyncio +import json +import re +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.okx import okx_constants as CONSTANTS, okx_web_utils as web_utils +from hummingbot.connector.exchange.okx.okx_exchange import OkxExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import OrderCancelledEvent, OrderType, TradeType + + +class OkxExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.api_key = "someKey" + cls.api_passphrase = "somePassPhrase" + cls.api_secret_key = "someSecretKey" + + @property + def all_symbols_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_INSTRUMENTS_PATH) + url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?") + ".*") + return url + + @property + def latest_prices_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_TICKER_PATH) + url = f"{url}?instId={self.base_asset}-{self.quote_asset}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + return regex_url + + @property + def network_status_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.OKX_SERVER_TIME_PATH) + return url + + @property + def trading_rules_url(self): + return self.all_symbols_url + + @property + def order_creation_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_PLACE_ORDER_PATH) + return url + + @property + def balance_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_BALANCE_PATH) + return url + + @property + def all_symbols_request_mock_response(self): + return { + "code": "0", + "data": [ + { + "alias": "", + "baseCcy": self.base_asset, + "category": "1", + "ctMult": "", + "ctType": "", + "ctVal": "", + "ctValCcy": "", + "expTime": "", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "instType": "SPOT", + "lever": "10", + "listTime": "1548133413000", + "lotSz": "0.00000001", + "minSz": "0.00001", + "optType": "", + "quoteCcy": self.quote_asset, + "settleCcy": "", + "state": "live", + "stk": "", + "tickSz": "0.1", + "uly": "" + }, + ] + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "code": "0", + "data": [ + { + "alias": "", + "baseCcy": "INVALID", + "category": "1", + "ctMult": "", + "ctType": "", + "ctVal": "", + "ctValCcy": "", + "expTime": "", + "instId": self.exchange_symbol_for_tokens("INVALID", "PAIR"), + "instType": "OPTION", + "lever": "10", + "listTime": "1548133413000", + "lotSz": "0.000001", + "minSz": "0.1", + "optType": "", + "quoteCcy": "PAIR", + "settleCcy": "", + "state": "live", + "stk": "", + "tickSz": "0.001", + "uly": "" + }, + ] + } + + return "INVALID-PAIR", response + + @property + def latest_prices_request_mock_response(self): + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.trading_pair, + "last": str(self.expected_latest_price), + "lastSz": "0.1", + "askPx": "9999.99", + "askSz": "11", + "bidPx": "8888.88", + "bidSz": "5", + "open24h": "9000", + "high24h": "10000", + "low24h": "8888.88", + "volCcy24h": "2222", + "vol24h": "2222", + "sodUtc0": "2222", + "sodUtc8": "2222", + "ts": "1597026383085" + } + ] + } + + @property + def network_status_request_successful_mock_response(self): + return { + "code": "0", + "msg": "", + "data": [ + { + "ts": "1597026383085" + } + ] + } + + @property + def trading_rules_request_mock_response(self): + response = { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "uly": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "category": "1", + "baseCcy": self.base_asset, + "quoteCcy": self.quote_asset, + "settleCcy": "LTC", + "ctVal": "10", + "ctMult": "1", + "ctValCcy": "USD", + "optType": "C", + "stk": "", + "listTime": "1597026383085", + "expTime": "1597026383085", + "lever": "10", + "tickSz": "0.01", + "lotSz": "1", + "minSz": "1", + "ctType": "inverse", + "alias": "this_week", + "state": "live" + } + ] + } + return response + + @property + def trading_rules_request_erroneous_mock_response(self): + response = { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "uly": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "category": "1", + "baseCcy": self.base_asset, + "quoteCcy": self.quote_asset, + } + ] + } + return response + + @property + def order_creation_request_successful_mock_response(self): + return { + "code": "0", + "msg": "", + "data": [ + { + "clOrdId": "oktswap6", + "ordId": self.expected_exchange_order_id, + "tag": "", + "sCode": "0", + "sMsg": "" + } + ] + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "code": "0", + "data": [ + { + "adjEq": "10679688.0460531643092577", + "details": [ + { + "availBal": "10", + "availEq": "", + "cashBal": "", + "ccy": self.base_asset, + "crossLiab": "0", + "disEq": "9439737.0772999514", + "eq": "", + "eqUsd": "9933041.196999946", + "frozenBal": "5", + "interest": "0", + "isoEq": "0", + "isoLiab": "0", + "isoUpl": "0", + "liab": "0", + "maxLoan": "10000", + "mgnRatio": "", + "notionalLever": "", + "ordFrozen": "0", + "twap": "0", + "uTime": "1620722938250", + "upl": "0", + "uplLiab": "0", + "stgyEq": "0" + }, + { + "availBal": "", + "availEq": "2000", + "cashBal": "", + "ccy": self.quote_asset, + "crossLiab": "0", + "disEq": "1239950.9687532129092577", + "eq": "2000", + "eqUsd": "1239950.9687532129092577", + "frozenBal": "0.0918492093160816", + "interest": "0", + "isoEq": "0", + "isoLiab": "0", + "isoUpl": "0", + "liab": "0", + "maxLoan": "1453.92289531493594", + "mgnRatio": "", + "notionalLever": "", + "ordFrozen": "0", + "twap": "0", + "uTime": "1620722938250", + "upl": "0.570822125136023", + "uplLiab": "0", + "stgyEq": "0" + } + ], + "imr": "3372.2942371050594217", + "isoEq": "0", + "mgnRatio": "70375.35408747017", + "mmr": "134.8917694842024", + "notionalUsd": "33722.9423710505978888", + "ordFroz": "0", + "totalEq": "11172992.1657531589092577", + "uTime": "1623392334718" + } + ] + } + + @property + def balance_request_mock_response_only_base(self): + return { + "code": "0", + "data": [ + { + "adjEq": "10679688.0460531643092577", + "details": [ + { + "availBal": "10", + "availEq": "", + "cashBal": "", + "ccy": self.base_asset, + "crossLiab": "0", + "disEq": "9439737.0772999514", + "eq": "", + "eqUsd": "9933041.196999946", + "frozenBal": "5", + "interest": "0", + "isoEq": "0", + "isoLiab": "0", + "isoUpl": "0", + "liab": "0", + "maxLoan": "10000", + "mgnRatio": "", + "notionalLever": "", + "ordFrozen": "0", + "twap": "0", + "uTime": "1620722938250", + "upl": "0", + "uplLiab": "0", + "stgyEq": "0" + }, + ], + "imr": "3372.2942371050594217", + "isoEq": "0", + "mgnRatio": "70375.35408747017", + "mmr": "134.8917694842024", + "notionalUsd": "33722.9423710505978888", + "ordFroz": "0", + "totalEq": "11172992.1657531589092577", + "uTime": "1623392334718" + } + ], + "msg": "" + } + + @property + def balance_event_websocket_update(self): + return { + "arg": { + "channel": "account", + "ccy": "BTC" + }, + "data": [ + { + "uTime": "1597026383085", + "totalEq": "41624.32", + "isoEq": "3624.32", + "adjEq": "41624.32", + "ordFroz": "0", + "imr": "4162.33", + "mmr": "4", + "notionalUsd": "", + "mgnRatio": "41624.32", + "details": [ + { + "availBal": "10", + "availEq": "", + "ccy": self.base_asset, + "cashBal": "", + "uTime": "1617279471503", + "disEq": "50559.01", + "eq": "", + "eqUsd": "45078.3790756226851775", + "frozenBal": "5", + "interest": "0", + "isoEq": "0", + "liab": "0", + "maxLoan": "", + "mgnRatio": "", + "notionalLever": "0.0022195262185864", + "ordFrozen": "0", + "upl": "0", + "uplLiab": "0", + "crossLiab": "0", + "isoLiab": "0", + "coinUsdPrice": "60000", + "stgyEq": "0", + "isoUpl": "" + } + ] + } + ] + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(self.trading_rules_request_mock_response["data"][0]["minSz"]), + min_price_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["tickSz"]), + min_base_amount_increment=Decimal(self.trading_rules_request_mock_response["data"][0]["lotSz"]), + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["data"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return "312269865356374016" + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))]) + + @property + def expected_fill_trade_id(self) -> str: + return "TrID1" + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}-{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + return OkxExchange( + client_config_map, + self.api_key, + self.api_secret_key, + self.api_passphrase, + trading_pairs=[self.trading_pair] + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + request_headers = request_call.kwargs["headers"] + self.assertIn("OK-ACCESS-KEY", request_headers) + self.assertEqual(self.api_key, request_headers["OK-ACCESS-KEY"]) + self.assertIn("OK-ACCESS-TIMESTAMP", request_headers) + self.assertIn("OK-ACCESS-SIGN", request_headers) + self.assertIn("OK-ACCESS-PASSPHRASE", request_headers) + self.assertEqual(self.api_passphrase, request_headers["OK-ACCESS-PASSPHRASE"]) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["instId"]) + self.assertEqual("cash", request_data["tdMode"]) + self.assertEqual(order.trade_type.name.lower(), request_data["side"]) + self.assertEqual(order.order_type.name.lower(), request_data["ordType"]) + self.assertEqual(Decimal("100"), Decimal(request_data["sz"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["px"])) + self.assertEqual(order.client_order_id, request_data["clOrdId"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = json.loads(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["instId"]) + self.assertEqual(order.client_order_id, request_data["clOrdId"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["instId"]) + self.assertEqual(order.client_order_id, request_params["clOrdId"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + request_params = request_call.kwargs["params"] + self.assertEqual("SPOT", request_params["instType"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_params["instId"]) + self.assertEqual(order.exchange_order_id, request_params["ordId"]) + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + response_scode: int = 0, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_CANCEL_PATH) + response = self._order_cancelation_request_successful_mock_response(response_scode=response_scode, order=order) + mock_api.post(url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_CANCEL_PATH) + response = { + "code": "0", + "msg": "", + "data": [ + { + "clOrdId": order.client_order_id, + "ordId": order.exchange_order_id or "dummyExchangeOrderId", + "sCode": "1", + "sMsg": "Error" + } + ] + } + mock_api.post(url, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + # Implement the expected not found response when enabling test_cancel_order_not_found_in_the_exchange + raise NotImplementedError + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + # Implement the expected not found response when enabling + # test_lost_order_removed_if_not_found_during_order_status_update + raise NotImplementedError + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_canceled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_open_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=404, callback=callback) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_ORDER_DETAILS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_TRADE_FILLS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_partial_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_TRADE_FILLS_PATH) + regex_url = re.compile(url + r"\?.*") + response = self._order_fills_request_full_fill_mock_response(order=order) + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.private_rest_url(path_url=CONSTANTS.OKX_TRADE_FILLS_PATH) + regex_url = re.compile(url + r"\?.*") + mock_api.get(regex_url, status=400, callback=callback) + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": "orders", + "uid": "77982378738415879", + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset) + }, + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "ccy": "BTC", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "", + "ordType": "limit", + "side": order.trade_type.name.lower(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": "", + "fillSz": "0", + "fillPx": "0", + "tradeId": "0", + "accFillSz": "323", + "fillNotionalUsd": "", + "fillTime": "0", + "fillFee": "0", + "fillFeeCcy": "", + "execType": "T", + "state": "live", + "avgPx": "0", + "lever": "20", + "tpTriggerPx": "0", + "tpTriggerPxType": "last", + "tpOrdPx": "20", + "slTriggerPx": "0", + "slTriggerPxType": "last", + "slOrdPx": "20", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "source": "", + "pnl": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085", + "reqId": "", + "amendResult": "", + "code": "0", + "msg": "" + } + ] + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": "orders", + "uid": "77982378738415879", + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset) + }, + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "ccy": "BTC", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "", + "ordType": "limit", + "side": order.trade_type.name.lower(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": "", + "fillSz": "0", + "fillPx": "0", + "tradeId": "0", + "accFillSz": "323", + "fillNotionalUsd": "", + "fillTime": "0", + "fillFee": "0", + "fillFeeCcy": "", + "execType": "T", + "state": "canceled", + "avgPx": "0", + "lever": "20", + "tpTriggerPx": "0", + "tpTriggerPxType": "last", + "tpOrdPx": "20", + "slTriggerPx": "0", + "slTriggerPxType": "last", + "slOrdPx": "20", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "source": "", + "pnl": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085", + "reqId": "", + "amendResult": "", + "code": "0", + "msg": "" + } + ] + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "arg": { + "channel": "orders", + "uid": "77982378738415879", + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset) + }, + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "ccy": "BTC", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "notionalUsd": "", + "ordType": "limit", + "side": order.trade_type.name.lower(), + "posSide": "long", + "tdMode": "cross", + "tgtCcy": "", + "fillSz": str(order.amount), + "fillPx": str(order.price), + "tradeId": self.expected_fill_trade_id, + "accFillSz": "323", + "fillNotionalUsd": "", + "fillTime": "0", + "fillFee": str(self.expected_fill_fee.flat_fees[0].amount), + "fillFeeCcy": self.expected_fill_fee.flat_fees[0].token, + "execType": "T", + "state": "filled", + "avgPx": "0", + "lever": "20", + "tpTriggerPx": "0", + "tpTriggerPxType": "last", + "tpOrdPx": "20", + "slTriggerPx": "0", + "slTriggerPxType": "last", + "slOrdPx": "20", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "source": "", + "pnl": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085", + "reqId": "", + "amendResult": "", + "code": "0", + "msg": "" + } + ] + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return {} + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 9 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.CLIENT_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.CLIENT_ID_PREFIX, + max_id_len=CONSTANTS.MAX_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + def test_time_synchronizer_related_request_error_detection(self): + exception = IOError("Error executing request POST https://okx.com/api/v3/order. HTTP status is 401. " + 'Error: {"code":"50113","msg":"message"}') + self.assertTrue(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + exception = IOError("Error executing request POST https://okx.com/api/v3/order. HTTP status is 401. " + 'Error: {"code":"50114","msg":"message"}') + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(exception)) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + def _order_cancelation_request_successful_mock_response(self, response_scode: int, order: InFlightOrder) -> Any: + return { + "code": "0", + "msg": "", + "data": [ + { + "clOrdId": order.client_order_id, + "ordId": order.exchange_order_id or "dummyOrdId", + "sCode": str(response_scode), + "sMsg": "" + } + ] + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "ccy": "", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "pnl": "5", + "ordType": "limit", + "side": order.trade_type.name.lower(), + "posSide": "long", + "tdMode": "isolated", + "accFillSz": "0", + "fillPx": "0", + "tradeId": "1", + "fillSz": str(order.amount), + "fillTime": "0", + "state": "filled", + "avgPx": str(order.price + Decimal(2)), + "lever": "20", + "tpTriggerPx": "", + "tpTriggerPxType": "last", + "tpOrdPx": "", + "slTriggerPx": "", + "slTriggerPxType": "last", + "slOrdPx": "", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085" + } + ] + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "ccy": "", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "pnl": "5", + "ordType": "limit", + "side": order.order_type.name.lower(), + "posSide": "long", + "tdMode": "isolated", + "accFillSz": "0", + "fillPx": "0", + "tradeId": "1", + "fillSz": "0", + "fillTime": "0", + "state": "canceled", + "avgPx": "0", + "lever": "20", + "tpTriggerPx": "", + "tpTriggerPxType": "last", + "tpOrdPx": "", + "slTriggerPx": "", + "slTriggerPxType": "last", + "slOrdPx": "", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085" + } + ] + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "ccy": "", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "pnl": "5", + "ordType": "limit", + "side": order.order_type.name.lower(), + "posSide": "long", + "tdMode": "isolated", + "accFillSz": "0", + "fillPx": "0", + "tradeId": "1", + "fillSz": "0", + "fillTime": "0", + "state": "live", + "avgPx": "0", + "lever": "20", + "tpTriggerPx": "", + "tpTriggerPxType": "last", + "tpOrdPx": "", + "slTriggerPx": "", + "slTriggerPxType": "last", + "slOrdPx": "", + "feeCcy": "", + "fee": "", + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085" + } + ] + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "ccy": "", + "ordId": order.exchange_order_id or "EOID1", + "clOrdId": order.client_order_id, + "tag": "", + "px": str(order.price), + "sz": str(order.amount), + "pnl": "5", + "ordType": "limit", + "side": order.trade_type.name.lower(), + "posSide": "long", + "tdMode": "isolated", + "accFillSz": "0", + "fillPx": str(self.expected_partial_fill_price), + "tradeId": "1", + "fillSz": str(self.expected_partial_fill_amount), + "fillTime": "0", + "state": "partially_filled", + "avgPx": str(order.price + Decimal(2)), + "lever": "20", + "tpTriggerPx": "", + "tpTriggerPxType": "last", + "tpOrdPx": "", + "slTriggerPx": "", + "slTriggerPxType": "last", + "slOrdPx": "", + "feeCcy": self.expected_fill_fee.flat_fees[0].token, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "rebateCcy": "", + "rebate": "", + "tgtCcy": "", + "category": "", + "uTime": "1597026383085", + "cTime": "1597026383085" + } + ] + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "tradeId": self.expected_fill_trade_id, + "ordId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "billId": "1111", + "tag": "", + "fillPx": str(self.expected_partial_fill_price), + "fillSz": str(self.expected_partial_fill_amount), + "side": order.order_type.name.lower(), + "posSide": "long", + "execType": "M", + "feeCcy": self.expected_fill_fee.flat_fees[0].token, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "ts": "1597026383085" + }, + ] + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "code": "0", + "msg": "", + "data": [ + { + "instType": "SPOT", + "instId": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "tradeId": self.expected_fill_trade_id, + "ordId": order.exchange_order_id, + "clOrdId": order.client_order_id, + "billId": "1111", + "tag": "", + "fillPx": str(order.price), + "fillSz": str(order.amount), + "side": order.order_type.name.lower(), + "posSide": "long", + "execType": "M", + "feeCcy": self.expected_fill_fee.flat_fees[0].token, + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "ts": "1597026383085" + }, + ] + } + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id="4", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + for response_scode in (0, 51400, 51401): + url = self.configure_successful_cancelation_response( + order=order, + mock_api=mock_api, + response_scode=response_scode, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_request = self._all_executed_requests(mock_api, url)[0] + self.validate_auth_credentials_present(cancel_request) + self.validate_order_cancelation_request( + order=order, + request_call=cancel_request) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + else: + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) diff --git a/test/hummingbot/connector/exchange/okx/test_okx_user_stream_data_source.py b/test/hummingbot/connector/exchange/okx/test_okx_user_stream_data_source.py new file mode 100644 index 0000000..7272723 --- /dev/null +++ b/test/hummingbot/connector/exchange/okx/test_okx_user_stream_data_source.py @@ -0,0 +1,266 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aiohttp import WSMessage, WSMsgType + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.okx.okx_api_user_stream_data_source import OkxAPIUserStreamDataSource +from hummingbot.connector.exchange.okx.okx_auth import OkxAuth +from hummingbot.connector.exchange.okx.okx_exchange import OkxExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant + + +class OkxUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + self.time_synchronizer = MagicMock() + self.time_synchronizer.time.return_value = 1640001112.223 + + self.auth = OkxAuth( + api_key="TEST_API_KEY", + secret_key="TEST_SECRET", + passphrase="TEST_PASSPHRASE", + time_provider=self.time_synchronizer) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = OkxExchange( + client_config_map=client_config_map, + okx_api_key="", + okx_secret_key="", + okx_passphrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = OkxAPIUserStreamDataSource( + auth=self.auth, + connector=self.connector, + api_factory=self.connector._web_assistants_factory + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + successful_login_response = { + "event": "login", + "code": "0", + "msg": "" + } + result_subscribe_orders = { + "event": "subscribe", + "arg": { + "channel": "account" + } + } + result_subscribe_account = { + "event": "subscribe", + "arg": { + "channel": "orders", + "instType": "SPOT", + } + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(successful_login_response)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_orders)) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_account)) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(3, len(sent_messages)) + expected_login = { + "op": "login", + "args": [ + { + "apiKey": self.auth.api_key, + "passphrase": self.auth.passphrase, + 'timestamp': '1640001112', + 'sign': 'wEhbGLkjM+fzAclpjd67vGUzbRpxPe4AlLyh6/wVwL4=', + } + ] + } + self.assertEqual(expected_login, sent_messages[0]) + expected_account_subscription = { + "op": "subscribe", + "args": [ + { + "channel": "account" + } + ] + } + self.assertEqual(expected_account_subscription, sent_messages[1]) + expected_orders_subscription = { + "op": "subscribe", + "args": [ + { + "channel": "orders", + "instType": "SPOT", + } + ] + } + self.assertEqual(expected_orders_subscription, sent_messages[2]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to private account and orders channels..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_authentication_failure(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + login_response = { + "event": "error", + "code": "60009", + "msg": "Login failed." + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(login_response)) + + output_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue(self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + )) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + successful_login_response = { + "event": "login", + "code": "0", + "msg": "" + } + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, + json.dumps(successful_login_response)) + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_ws): + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.")) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds...")) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_sends_ping_message_before_ping_interval_finishes( + self, + ws_connect_mock): + + successful_login_response = { + "event": "login", + "code": "0", + "msg": "" + } + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = [ + WSMessage(type=WSMsgType.TEXT, data=json.dumps(successful_login_response), extra=None), + asyncio.TimeoutError("Test timeout"), + asyncio.CancelledError] + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + sent_messages = self.mocking_assistant.text_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = "ping" + self.assertEqual(expected_ping_message, sent_messages[0]) diff --git a/test/hummingbot/connector/exchange/paper_trade/__init__.py b/test/hummingbot/connector/exchange/paper_trade/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/paper_trade/test_paper_trade_exchange.py b/test/hummingbot/connector/exchange/paper_trade/test_paper_trade_exchange.py new file mode 100644 index 0000000..9e9c4d3 --- /dev/null +++ b/test/hummingbot/connector/exchange/paper_trade/test_paper_trade_exchange.py @@ -0,0 +1,31 @@ +from unittest import TestCase + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.binance.binance_api_order_book_data_source import BinanceAPIOrderBookDataSource +from hummingbot.connector.exchange.kucoin.kucoin_api_order_book_data_source import KucoinAPIOrderBookDataSource +from hummingbot.connector.exchange.paper_trade import create_paper_trade_market, get_order_book_tracker +from hummingbot.core.data_type.order_book_tracker import OrderBookTracker + + +class PaperTradeExchangeTests(TestCase): + + def test_get_order_book_tracker_for_connector_using_generic_tracker(self): + tracker = get_order_book_tracker(connector_name="binance", trading_pairs=["COINALPHA-HBOT"]) + self.assertEqual(OrderBookTracker, type(tracker)) + + tracker = get_order_book_tracker(connector_name="kucoin", trading_pairs=["COINALPHA-HBOT"]) + self.assertEqual(OrderBookTracker, type(tracker)) + + def test_create_paper_trade_market_for_connector_using_generic_tracker(self): + paper_exchange = create_paper_trade_market( + exchange_name="binance", + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trading_pairs=["COINALPHA-HBOT"]) + self.assertEqual(BinanceAPIOrderBookDataSource, type(paper_exchange.order_book_tracker.data_source)) + + paper_exchange = create_paper_trade_market( + exchange_name="kucoin", + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trading_pairs=["COINALPHA-HBOT"]) + self.assertEqual(KucoinAPIOrderBookDataSource, type(paper_exchange.order_book_tracker.data_source)) diff --git a/test/hummingbot/connector/exchange/polkadex/__init__.py b/test/hummingbot/connector/exchange/polkadex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/polkadex/programmable_query_executor.py b/test/hummingbot/connector/exchange/polkadex/programmable_query_executor.py new file mode 100644 index 0000000..e0ea098 --- /dev/null +++ b/test/hummingbot/connector/exchange/polkadex/programmable_query_executor.py @@ -0,0 +1,97 @@ +import asyncio +from typing import Any, Callable, Dict + +from hummingbot.connector.exchange.polkadex.polkadex_query_executor import BaseQueryExecutor + + +class ProgrammableQueryExecutor(BaseQueryExecutor): + def __init__(self): + self._main_account = None + self._all_assets_responses = asyncio.Queue() + self._all_markets_responses = asyncio.Queue() + self._order_book_snapshots = asyncio.Queue() + self._recent_trades_responses = asyncio.Queue() + self._balances_responses = asyncio.Queue() + self._place_order_responses = asyncio.Queue() + self._cancel_order_responses = asyncio.Queue() + self._order_history_responses = asyncio.Queue() + self._order_responses = asyncio.Queue() + self._list_orders_responses = asyncio.Queue() + self._order_fills_responses = asyncio.Queue() + + self._order_book_update_events = asyncio.Queue() + self._public_trades_update_events = asyncio.Queue() + self._private_events = asyncio.Queue() + + async def all_assets(self): + response = await self._all_assets_responses.get() + return response + + async def all_markets(self): + response = await self._all_markets_responses.get() + return response + + async def get_orderbook(self, market_symbol: str) -> Dict[str, Any]: + snapshot = await self._order_book_snapshots.get() + return snapshot + + async def main_account_from_proxy(self, proxy_account=str) -> str: + return self._main_account + + async def recent_trades(self, market_symbol: str, limit: int) -> Dict[str, Any]: + response = await self._recent_trades_responses.get() + return response + + async def get_all_balances_by_main_account(self, main_account: str) -> Dict[str, Any]: + response = await self._balances_responses.get() + return response + + async def place_order(self, polkadex_order: Dict[str, Any], signature: Dict[str, Any]) -> Dict[str, Any]: + response = await self._place_order_responses.get() + return response + + async def cancel_order( + self, + order_id: str, + market_symbol: str, + main_address: str, + proxy_address: str, + signature: Dict[str, Any], + ) -> Dict[str, Any]: + response = await self._cancel_order_responses.get() + return response + + async def list_order_history_by_account( + self, main_account: str, from_time: float, to_time: float + ) -> Dict[str, Any]: + response = await self._order_history_responses.get() + return response + + async def find_order_by_main_account(self, main_account: str, market_symbol: str, order_id: str) -> Dict[str, Any]: + response = await self._order_responses.get() + return response + + async def list_open_orders_by_main_account(self, main_account: str) -> Dict[str, Any]: + response = await self._list_orders_responses.get() + return response + + async def get_order_fills_by_main_account( + self, from_timestamp: float, to_timestamp: float, main_account: str + ) -> Dict[str, Any]: + response = await self._order_fills_responses.get() + return response + + async def listen_to_orderbook_updates(self, events_handler: Callable, market_symbol: str): + while True: + event = await self._order_book_update_events.get() + events_handler(event=event, market_symbol=market_symbol) + + async def listen_to_public_trades(self, events_handler: Callable, market_symbol: str): + while True: + event = await self._public_trades_update_events.get() + events_handler(event=event) + + async def listen_to_private_events(self, events_handler: Callable, address: str): + while True: + event = await self._private_events.get() + events_handler(event=event) diff --git a/test/hummingbot/connector/exchange/polkadex/test_polkadex_api_order_book_data_source.py b/test/hummingbot/connector/exchange/polkadex/test_polkadex_api_order_book_data_source.py new file mode 100644 index 0000000..099fc0f --- /dev/null +++ b/test/hummingbot/connector/exchange/polkadex/test_polkadex_api_order_book_data_source.py @@ -0,0 +1,266 @@ +import asyncio +import json +import re +from decimal import Decimal +from test.hummingbot.connector.exchange.polkadex.programmable_query_executor import ProgrammableQueryExecutor +from typing import Awaitable, Optional, Union +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.polkadex.polkadex_api_order_book_data_source import PolkadexAPIOrderBookDataSource +from hummingbot.connector.exchange.polkadex.polkadex_exchange import PolkadexExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType + + +class PolkadexAPIOrderBookDataSourceTests(TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-1" + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + self.async_tasks = [] + asyncio.set_event_loop(self.async_loop) + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = PolkadexExchange( + client_config_map=client_config_map, + polkadex_seed_phrase="", + trading_pairs=[self.trading_pair], + trading_required=False, + ) + self.connector._data_source._query_executor = ProgrammableQueryExecutor() + + self.data_source = PolkadexAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + data_source=self.connector._data_source, + ) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.log_records = [] + self._logs_event: Optional[asyncio.Event] = None + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + self.async_run_with_timeout(self.data_source._data_source.stop()) + for task in self.async_tasks: + task.cancel() + self.async_loop.stop() + self.async_loop.close() + # Since the event loop will change we need to remove the logs event created in the old event loop + self._logs_event = None + asyncio.set_event_loop(self._original_async_loop) + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.async_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def create_task(self, coroutine: Awaitable) -> asyncio.Task: + task = self.async_loop.create_task(coroutine) + self.async_tasks.append(task) + return task + + def handle(self, record): + self.log_records.append(record) + if self._logs_event is not None: + self._logs_event.set() + + def is_logged(self, log_level: str, message: Union[str, re.Pattern]) -> bool: + expression = ( + re.compile( + f"^{message}$" + .replace(".", r"\.") + .replace("?", r"\?") + .replace("/", r"\/") + .replace("(", r"\(") + .replace(")", r"\)") + .replace("[", r"\[") + .replace("]", r"\]") + ) + if isinstance(message, str) + else message + ) + return any( + record.levelname == log_level and expression.match(record.getMessage()) is not None + for record in self.log_records + ) + + def test_get_new_order_book_successful(self): + data = [ + {"side": "Ask", "p": 9487.5, "q": 522147, "s": "Ask", "stid": 1}, + {"side": "Bid", "p": 9487, "q": 336241, "s": "Bid", "stid": 1}, + ] + order_book_snapshot = {"getOrderbook": {"items": data}} + self.data_source._data_source._query_executor._order_book_snapshots.put_nowait(order_book_snapshot) + + order_book = self.async_run_with_timeout(self.data_source.get_new_order_book(self.trading_pair)) + + expected_update_id = 1 + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(9487, bids[0].price) + self.assertEqual(336241, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(9487.5, asks[0].price) + self.assertEqual(522147, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + def test_listen_for_trades_logs_exception(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [Exception("some error"), asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.async_run_with_timeout(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self.is_logged( + "ERROR", "Unexpected error when processing public trade updates from exchange" + ) + ) + + def test_listen_for_trades_successful(self): + expected_trade_id = "1664193952989" + trade_data = { + "type": "TradeFormat", + "m": self.ex_trading_pair, + "m_side": "Ask", + "trade_id": expected_trade_id, + "p": "1718.5", + "q": "10", + "t": 1664193952989, + "stid": "16", + } + trade_event = {"websocket_streams": {"data": json.dumps(trade_data)}} + + self.data_source._data_source._query_executor._public_trades_update_events.put_nowait(trade_event) + + msg_queue = asyncio.Queue() + self.async_run_with_timeout(self.data_source._data_source.start(market_symbols=[self.ex_trading_pair])) + + self.create_task(self.data_source.listen_for_trades(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(expected_trade_id, msg.trade_id) + self.assertEqual(trade_data["t"] * 1e-3, msg.timestamp) + expected_price = Decimal(trade_data["p"]) + expected_amount = Decimal(trade_data["q"]) + self.assertEqual(expected_amount, msg.content["amount"]) + self.assertEqual(expected_price, msg.content["price"]) + self.assertEqual(self.trading_pair, msg.content["trading_pair"]) + self.assertEqual(float(TradeType.SELL.value), msg.content["trade_type"]) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + def test_listen_for_order_book_diffs_logs_exception(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [Exception("some error"), asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.async_run_with_timeout(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self.is_logged( + "ERROR", "Unexpected error when processing public order book updates from exchange" + ) + ) + + @patch("hummingbot.connector.exchange.polkadex.polkadex_data_source.PolkadexDataSource._time") + def test_listen_for_order_book_diffs_successful(self, time_mock): + time_mock.return_value = 1640001112.223 + + order_book_data = { + "i": 1, + "a": { + "3001": "0", + }, + "b": { + "2999": "8", + "1.671": "52.952", + }, + } + order_book_event = {"websocket_streams": {"data": json.dumps(order_book_data)}} + + self.data_source._data_source._query_executor._order_book_update_events.put_nowait(order_book_event) + + msg_queue: asyncio.Queue = asyncio.Queue() + self.async_run_with_timeout( + self.data_source._data_source.start(market_symbols=[self.ex_trading_pair]) + ) + self.create_task(self.data_source.listen_for_order_book_diffs(self.async_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(time_mock.return_value, msg.timestamp) + expected_update_id = 1 + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(2, len(bids)) + self.assertEqual(2999.0, bids[0].price) + self.assertEqual(8, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(3001, asks[0].price) + self.assertEqual(0, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) diff --git a/test/hummingbot/connector/exchange/polkadex/test_polkadex_exchange.py b/test/hummingbot/connector/exchange/polkadex/test_polkadex_exchange.py new file mode 100644 index 0000000..ea07f2e --- /dev/null +++ b/test/hummingbot/connector/exchange/polkadex/test_polkadex_exchange.py @@ -0,0 +1,1749 @@ +import asyncio +import json +from functools import partial +from test.hummingbot.connector.exchange.polkadex.programmable_query_executor import ProgrammableQueryExecutor +from typing import Any, Callable, Dict, List, Optional, Tuple +from unittest.mock import AsyncMock, MagicMock, patch + +from _decimal import Decimal +from aioresponses import aioresponses +from aioresponses.core import RequestCall +from bidict import bidict +from gql.transport.exceptions import TransportQueryError +from substrateinterface import SubstrateInterface + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.polkadex.polkadex_exchange import PolkadexExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class PolkadexExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + client_order_id_prefix = "0x" + exchange_order_id_prefix = "0x" + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls._seed_phrase = ( + "hollow crack grain grab equal rally ceiling manage goddess grass negative canal" # noqa: mock + ) + + def setUp(self) -> None: + super().setUp() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + self._logs_event: Optional[asyncio.Event] = None + self.exchange._data_source.logger().setLevel(1) + self.exchange._data_source.logger().addHandler(self) + self.exchange._set_trading_pair_symbol_map(bidict({self.exchange_trading_pair: self.trading_pair})) + exchange_base, exchange_quote = self.trading_pair.split("-") + self.exchange._data_source._assets_map = {exchange_base: self.base_asset, "1": self.quote_asset} + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + self._logs_event = None + + def handle(self, record): + super().handle(record=record) + if self._logs_event is not None: + self._logs_event.set() + + def reset_log_event(self): + if self._logs_event is not None: + self._logs_event.clear() + + async def wait_for_a_log(self): + if self._logs_event is not None: + await self._logs_event.wait() + + @property + def all_symbols_url(self): + raise NotImplementedError + + @property + def latest_prices_url(self): + raise NotImplementedError + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + raise NotImplementedError + + @property + def order_creation_url(self): + raise NotImplementedError + + @property + def balance_url(self): + raise NotImplementedError + + @property + def all_assets_mock_response(self): + return { + "getAllAssets": { + "items": [ + {"asset_id": "1", "name": self.quote_asset}, + {"asset_id": self.base_asset, "name": self.base_asset}, + ] + } + } + + @property + def all_symbols_request_mock_response(self): + return { + "getAllMarkets": { + "items": [ + { + "base_asset_precision": "8", + "market": self.exchange_trading_pair, + "max_order_price": "10000", + "max_order_qty": "20000", + "min_order_price": "2.0E-4", + "min_order_qty": "0.001", + "price_tick_size": "1.0E-4", + "qty_step_size": "0.0001", + "quote_asset_precision": "8", + } + ] + } + } + + @property + def latest_prices_request_mock_response(self): + return { + "getRecentTrades": { + "items": [ + { + "isReverted": None, + "m": None, + "p": str(self.expected_latest_price), + "q": "1", + "t": "1668606574722", + "sid": 896, + }, + ] + } + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + response = { + "getAllMarkets": { + "items": [ + { + "base_asset_precision": "8", + "market": self.exchange_trading_pair, + "max_order_price": "10000", + "max_order_qty": "10000", + "min_order_price": "1.0E-4", + "min_order_qty": "0.001", + "price_tick_size": "1.0E-4", + "qty_step_size": "0.001", + "quote_asset_precision": "8", + }, + { + "base_asset_precision": "8", + "market": "INVALID-1", + "max_order_price": "10000", + "max_order_qty": "10000", + "min_order_price": "1.0E-4", + "min_order_qty": "0.001", + "price_tick_size": "1.0E-4", + "qty_step_size": "0.001", + "quote_asset_precision": "8", + }, + ] + } + } + + return "INVALID-1", response + + @property + def network_status_request_successful_mock_response(self): + raise NotImplementedError + + @property + def trading_rules_request_mock_response(self): + return self.all_symbols_request_mock_response + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "getAllMarkets": { + "items": [ + { + "base_asset_precision": "8", + "market": self.exchange_trading_pair, + } + ] + } + } + + @property + def order_creation_request_successful_mock_response(self): + return {"place_order": json.dumps({"is_success": True, "body": self.expected_exchange_order_id})} + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "getAllBalancesByMainAccount": { + "items": [ + { + "a": self.base_asset, + "f": "10.0000", + "r": "5", + }, + { + "a": "1", + "f": "2000", + "r": "0", + }, + ] + } + } + + @property + def balance_request_mock_response_only_base(self): + return { + "getAllBalancesByMainAccount": { + "items": [ + { + "a": self.base_asset, + "f": "10.0000", + "r": "5", + }, + ] + } + } + + @property + def balance_event_websocket_update(self): + data = { + "type": "SetBalance", + "snapshot_number": 50133, + "event_id": 4300053, + "user": "5C5ZpV7Hunb7yG2CwDtnhxaYc3aug4UTLxRvu6HERxJqrtJY", + "asset": {"asset": self.base_asset}, + "free": "10", + "pending_withdrawal": "0", + "reserved": "5", + } + + return { + "websocket_streams": { + "data": json.dumps(data), + } + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.MARKET] + + @property + def expected_trading_rule(self): + market_info = self.all_symbols_request_mock_response["getAllMarkets"]["items"][0] + trading_pair = self.trading_pair + min_order_size = Decimal(market_info["min_order_qty"]) + max_order_size = Decimal(market_info["max_order_qty"]) + min_order_price = Decimal(market_info["min_order_price"]) + amount_increment = Decimal(market_info["qty_step_size"]) + price_increment = Decimal(market_info["price_tick_size"]) + trading_rule = TradingRule( + trading_pair=trading_pair, + min_order_size=min_order_size, + max_order_size=max_order_size, + min_price_increment=price_increment, + min_base_amount_increment=amount_increment, + min_quote_amount_increment=price_increment, + min_notional_size=min_order_size * min_order_price, + min_order_value=min_order_size * min_order_price, + ) + + return trading_rule + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["getAllMarkets"]["items"][0] + return f"Error parsing the trading pair rule: {erroneous_rule}. Skipping..." + + @property + def expected_exchange_order_id(self): + return "0x1b99cba5555ad0ba890756fe16e499cb884b46a165b89bdce77ee8913b55ffff" # noqa: mock + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + raise NotImplementedError + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal("10500") + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[ + TokenAmount( + token=self.quote_asset, + amount=Decimal("0"), # according to Polkadex team, fees will be zero for the foreseeable future + ), + ], + ) + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return AddedToCostTradeFee( + percent_token=self.quote_asset, + flat_fees=[ + TokenAmount( + token=self.quote_asset, + amount=Decimal("0"), # according to Polkadex team, fees will be zero for the foreseeable future + ), + ], + ) + + @property + def expected_fill_trade_id(self) -> str: + return "9999" + + @property + def exchange_trading_pair(self) -> str: + return self.exchange_symbol_for_tokens(self.base_asset, "1") + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"{base_token}-{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + with patch("hummingbot.connector.exchange.polkadex.polkadex_data_source.SubstrateInterface.connect_websocket"): + exchange = PolkadexExchange( + client_config_map=client_config_map, + polkadex_seed_phrase=self._seed_phrase, + trading_pairs=[self.trading_pair], + ) + encode_mock = MagicMock( + return_value="0x1b99cba5555ad0ba890756fe16e499cb884b46a165b89bdce77ee8913b55fff1" # noqa: mock + ) + exchange._data_source._substrate_interface = MagicMock( + spec=SubstrateInterface, spec_sec=SubstrateInterface, autospec=True + ) + exchange._data_source._substrate_interface.create_scale_object.return_value.encode = encode_mock + + exchange._data_source._query_executor = ProgrammableQueryExecutor() + return exchange + + def validate_auth_credentials_present(self, request_call: RequestCall): + raise NotImplementedError + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + raise NotImplementedError + + def configure_successful_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = self._order_cancelation_request_successful_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._cancel_order_responses = mock_queue + return "" + + def configure_erroneous_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + response = {} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._cancel_order_responses = mock_queue + return "" + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + not_found_error = { + "path": ["cancel_order"], + "data": None, + "errorType": "Lambda:Unhandled", + "errorInfo": None, + "locations": [{"line": 2, "column": 3, "sourceName": None}], + "message": '{"errorMessage":"{\\"code\\":-32000,\\"message\\":\\"Order not found : ' + "0x1b99cba5555ad0ba890756fe16e499cb884b46a165b89bdce77ee8913b55ffff" # noqa: mock + '\\"}","errorType":"Lambda:Handled"}', + } + not_found_exception = TransportQueryError(str(not_found_error)) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, callback=callback, response=not_found_exception + ) + self.exchange._data_source._query_executor._cancel_order_responses = mock_queue + return "" + + def configure_order_not_active_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + not_found_error = { + "path": ["cancel_order"], + "data": None, + "errorType": "Lambda:Unhandled", + "errorInfo": None, + "locations": [{"line": 2, "column": 3, "sourceName": None}], + "message": '{"errorMessage":"{\\"code\\":-32000,\\"message\\":\\"Order is not active: ' + "0x1b99cba5555ad0ba890756fe16e499cb884b46a165b89bdce77ee8913b55ffff" # noqa: mock + '\\"}","errorType":"Lambda:Handled"}', + } + not_found_exception = TransportQueryError(str(not_found_error)) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, callback=callback, response=not_found_exception + ) + self.exchange._data_source._query_executor._cancel_order_responses = mock_queue + return "" + + def configure_one_successful_one_erroneous_cancel_all_response( + self, successful_order: InFlightOrder, erroneous_order: InFlightOrder, mock_api: aioresponses + ) -> List[str]: + response = self._order_cancelation_request_successful_mock_response(order=successful_order) + self.exchange._data_source._query_executor._cancel_order_responses.put_nowait(response) + self.exchange._data_source._query_executor._cancel_order_responses.put_nowait({}) + return [] + + def configure_completely_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + response = self._order_status_request_completely_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_responses = mock_queue + return [] + + def configure_canceled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_no_fills_trade_response() + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + response = self._order_status_request_canceled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_responses = mock_queue + return [] + + def configure_open_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_no_fills_trade_response() + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + response = self._order_status_request_open_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_responses = mock_queue + return [] + + def configure_http_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + self.configure_no_fills_trade_response() + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + mock_queue = AsyncMock() + mock_queue.get.side_effect = IOError("Test failure") + self.exchange._data_source._query_executor._order_responses = mock_queue + return "" + + def configure_partially_filled_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + response = self._order_status_request_partially_filled_mock_response(order=order) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_responses = mock_queue + return [] + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + self.configure_no_fills_trade_response() + order_history_response = {"listOrderHistorybyMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_history_responses.put_nowait(order_history_response) + response = {"findOrderByMainAccount": None} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_responses = mock_queue + return [] + + def configure_partial_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + order_fills_response = { + "listTradesByMainAccount": { + "items": [ + { + "m": self.exchange_trading_pair, + "p": str(self.expected_partial_fill_price), + "q": str(self.expected_partial_fill_amount), + "m_id": order.exchange_order_id, + "trade_id": self.expected_fill_trade_id, + "t": str(int(self.exchange.current_timestamp * 1e3)), + } + ] + } + } + self.exchange._data_source._query_executor._order_fills_responses.put_nowait(order_fills_response) + return "" + + def configure_erroneous_http_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + error = { + "path": ["listTradesByMainAccount"], + "data": None, + "errorType": "DynamoDB:DynamoDbException", + "errorInfo": None, + "locations": [{"line": 2, "column": 3, "sourceName": None}], + "message": ( + "Invalid KeyConditionExpression: The BETWEEN operator requires upper bound to be greater than or" + " equal to lower bound; lower bound operand: AttributeValue: {N:1691691033195}, upper bound operand:" + " AttributeValue: {N:1691691023195} (Service: DynamoDb, Status Code: 400, Request ID:" + " F314JNSTC7U56DMFAFEPAGCM9VVV4KQNSO5AEMVJF66Q9ASUAAJG)" + ), + } + response = TransportQueryError(error) + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial(self._callback_wrapper_with_response, callback=callback, response=response) + self.exchange._data_source._query_executor._order_fills_responses = mock_queue + return "" + + def configure_full_fill_trade_response( + self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + order_fills_response = { + "listTradesByMainAccount": { + "items": [ + { + "m": self.exchange_trading_pair, + "p": str(order.price), + "q": str(order.amount), + "m_id": order.exchange_order_id, + "trade_id": self.expected_fill_trade_id, + "t": str(int(self.exchange.current_timestamp * 1e3)), + } + ] + } + } + self.exchange._data_source._query_executor._order_fills_responses.put_nowait(order_fills_response) + return "" + + def configure_no_fills_trade_response(self): + order_fills_response = {"listTradesByMainAccount": {"items": []}} + self.exchange._data_source._query_executor._order_fills_responses.put_nowait(order_fills_response) + + def configure_all_symbols_response( + self, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + response = self.all_symbols_request_mock_response + self.exchange._data_source._query_executor._all_markets_responses.put_nowait(response) + return "" + + def configure_successful_creation_order_status_response( + self, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + creation_response = self.order_creation_request_successful_mock_response + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, callback=callback, response=creation_response + ) + self.exchange._data_source._query_executor._place_order_responses = mock_queue + return "" + + def configure_erroneous_creation_order_status_response( + self, callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + creation_response = {"place_order": json.dumps({"is_success": False, "error": "some error"})} + mock_queue = AsyncMock() + mock_queue.get.side_effect = partial( + self._callback_wrapper_with_response, callback=callback, response=creation_response + ) + self.exchange._data_source._query_executor._place_order_responses = mock_queue + return "" + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + data = self.build_order_event_websocket_update( + order=order, + filled_quantity=Decimal("0"), + filled_price=Decimal("0"), + fee=Decimal("0"), + status="OPEN", + ) + return data + + def order_event_for_partially_filled_websocket_update(self, order: InFlightOrder): + data = self.build_order_event_websocket_update( + order=order, + filled_quantity=self.expected_partial_fill_amount, + filled_price=self.expected_partial_fill_price, + fee=Decimal("0"), + status="OPEN", + ) + return data + + def order_event_for_partially_canceled_websocket_update(self, order: InFlightOrder): + data = self.build_order_event_websocket_update( + order=order, + filled_quantity=self.expected_partial_fill_amount, + filled_price=self.expected_partial_fill_price, + fee=Decimal("0"), + status="CANCELLED", + ) + return data + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + data = self.build_order_event_websocket_update( + order=order, + filled_quantity=Decimal("0"), + filled_price=Decimal("0"), + fee=Decimal("0"), + status="CANCELLED", + ) + return data + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + data = self.build_order_event_websocket_update( + order=order, + filled_quantity=order.amount, + filled_price=order.price, + fee=Decimal("0"), + status="CLOSED", + ) + return data + + def build_order_event_websocket_update( + self, + order: InFlightOrder, + filled_quantity: Decimal, + filled_price: Decimal, + fee: Decimal, + status: str, + ): + data = { + "type": "Order", + "stid": 50133, + "client_order_id": order.client_order_id, + "avg_filled_price": str(filled_price), + "fee": str(fee), + "filled_quantity": str(filled_quantity), + "status": status, + "id": order.exchange_order_id, + "user": "5EqHNNKJWA4U6dyZDvUSkKPQCt6PGgrAxiSBRvC6wqz2xKXU", # noqa: mock + "pair": {"base": {"asset": self.base_asset}, "quote": {"asset": "1"}}, + "side": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "order_type": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "qty": str(order.amount), + "price": str(order.price), + "timestamp": 1682480373, + } + + return {"websocket_streams": {"data": json.dumps(data)}} + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + data = self.build_trade_event_websocket_update( + order=order, + filled_quantity=order.amount, + filled_price=order.price, + ) + return data + + def trade_event_for_partial_fill_websocket_update(self, order: InFlightOrder): + data = self.build_trade_event_websocket_update( + order=order, + filled_quantity=self.expected_partial_fill_amount, + filled_price=self.expected_partial_fill_price, + ) + return data + + def build_trade_event_websocket_update( + self, + order: InFlightOrder, + filled_quantity: Decimal, + filled_price: Decimal, + ) -> Dict[str, Any]: + data = { + "type": "TradeFormat", + "stid": 50133, + "p": str(filled_price), + "q": str(filled_quantity), + "m": self.exchange_trading_pair, + "t": str(self.exchange.current_timestamp), + "cid": str(order.client_order_id), + "order_id": str(order.exchange_order_id), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "trade_id": self.expected_fill_trade_id, + } + + return {"websocket_streams": {"data": json.dumps(data)}} + + @aioresponses() + def test_check_network_success(self, mock_api): + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + @aioresponses() + def test_check_network_failure(self, mock_api): + all_assets_mock_response = {"getAllAssets": {"items": []}} + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._data_source._query_executor._all_assets_responses = mock_queue + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_get_last_trade_prices(self, mock_api): + response = self.latest_prices_request_mock_response + self.exchange._data_source._query_executor._recent_trades_responses.put_nowait(response) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + @aioresponses() + def test_invalid_trading_pair_not_in_all_trading_pairs(self, mock_api): + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + invalid_pair, response = self.all_symbols_including_invalid_pair_mock_response + self.exchange._data_source._query_executor._all_markets_responses.put_nowait(response) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertNotIn(invalid_pair, all_trading_pairs) + + @aioresponses() + def test_all_trading_pairs_does_not_raise_exception(self, mock_api): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._assets_map = None + queue_mock = AsyncMock() + queue_mock.get.side_effect = Exception + self.exchange._data_source._query_executor._all_assets_responses = queue_mock + + result: List[str] = self.async_run_with_timeout(self.exchange.all_trading_pairs()) + + self.assertEqual(0, len(result)) + + @aioresponses() + def test_create_buy_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.configure_successful_creation_order_status_response( + callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000000')} {self.trading_pair}.", + ) + ) + + @aioresponses() + def test_create_sell_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.configure_successful_creation_order_status_response( + callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id = self.place_sell_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000000')} {self.trading_pair}.", + ) + ) + + def test_create_order_fails_and_raises_failure_event(self): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.configure_erroneous_creation_order_status_response( + callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)", + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.configure_erroneous_creation_order_status_response( + callback=lambda *args, **kwargs: request_sent_event.set() + ) + + order_id_for_invalid_order = self.place_buy_order( + amount=Decimal("0.0001"), price=Decimal("0.0001") + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + order_id = self.place_buy_order() + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_successful_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) + + @aioresponses() + @patch("hummingbot.connector.exchange.polkadex.polkadex_data_source.PolkadexDataSource._build_substrate_interface") + @patch("hummingbot.connector.exchange.polkadex.polkadex_data_source.Keypair.sign") + def test_cancel_order_retries_on_substrate_broken_pipe( + self, mock_api: aioresponses, sign_mock: MagicMock, _: MagicMock + ): + sign_mock.hex.return_value = "0x1234adf" + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + create_scale_object_mock = MagicMock( + "substrateinterface.base.SubstrateInterface.create_scale_object", autospec=True + ) + self.exchange._data_source._substrate_interface.create_scale_object.return_value = create_scale_object_mock + create_scale_object_mock.encode.side_effect = [ + BrokenPipeError, + "0x1b99cba5555ad0ba890756fe16e499cb884b46a165b89bdce77ee8913b55fff1", # noqa: mock + ] + self.configure_successful_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) + self.assertTrue(self.is_logged(log_level="ERROR", message="Rebuilding the substrate interface.")) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_erroneous_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id=self.client_order_id_prefix + "1") + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue( + any( + log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records + ) + ) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_order_not_found_error_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id=self.client_order_id_prefix + "1") + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertFalse(order.is_done) + self.assertFalse(order.is_failure) + self.assertFalse(order.is_cancelled) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_updatable_orders) + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @aioresponses() + def test_cancel_order_no_longer_active(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_order_not_active_error_cancelation_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id=self.client_order_id_prefix + "1") + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertFalse(order.is_failure) + self.assertTrue(order.is_cancelled) + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_updatable_orders) + + @aioresponses() + def test_update_balances(self, mock_api): + response = self.balance_request_mock_response_for_base_and_quote + self._configure_balance_response(response=response, mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), available_balances[self.quote_asset]) + self.assertEqual(Decimal("15"), total_balances[self.base_asset]) + self.assertEqual(Decimal("2000"), total_balances[self.quote_asset]) + + response = self.balance_request_mock_response_only_base + + self._configure_balance_response(response=response, mock_api=mock_api) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(Decimal("10"), available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), total_balances[self.base_asset]) + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + self.configure_http_error_order_status_response( + order=order, + mock_api=mock_api) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @aioresponses() + def test_cancel_lost_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_successful_cancelation_response( + order=order, + mock_api=mock_api, + callback=lambda *args, **kwargs: request_sent_event.set()) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_cancel_lost_order_raises_failure_event_when_request_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=self.exchange_order_id_prefix + "1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn(self.client_order_id_prefix + "1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.exchange._data_source._query_executor._cancel_order_responses.put_nowait({}) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue( + any( + log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records + ) + ) + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + self.reset_log_event() + + self.exchange._data_source._process_private_event(event=order_event) + self.async_run_with_timeout(self.wait_for_a_log()) + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + @aioresponses() + def test_lost_order_user_stream_full_fill_events_are_processed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + self.reset_log_event() + + self.exchange._data_source._process_private_event(event=order_event) + self.exchange._data_source._process_private_event(event=trade_event) + self.async_run_with_timeout(self.wait_for_a_log()) + + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + def test_user_stream_balance_update(self): + self.exchange._set_current_timestamp(1640780000) + + balance_event = self.balance_event_websocket_update + + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + + self.async_run_with_timeout( + self.exchange._data_source._process_balance_event( + event=json.loads(balance_event["websocket_streams"]["data"]) + ) + ) + + self.assertEqual(Decimal("10"), self.exchange.available_balances[self.base_asset]) + self.assertEqual(Decimal("15"), self.exchange.get_balance(self.base_asset)) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_new_order_websocket_update(order=order) + + self.reset_log_event() + self.exchange._data_source._process_private_event(event=order_event) + self.async_run_with_timeout(self.wait_for_a_log()) + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_canceled_order_websocket_update(order=order) + + self.reset_log_event() + self.exchange._data_source._process_private_event(event=order_event) + self.async_run_with_timeout(self.wait_for_a_log()) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue(self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + + @aioresponses() + def test_user_stream_update_for_order_full_fill(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + order_event = self.order_event_for_full_fill_websocket_update(order=order) + trade_event = self.trade_event_for_full_fill_websocket_update(order=order) + + self.reset_log_event() + self.exchange._data_source._process_private_event(event=order_event) + self.exchange._data_source._process_private_event(event=trade_event) + self.async_run_with_timeout(self.wait_for_a_log()) + + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"BUY order {order.client_order_id} completely filled.") + ) + + def test_user_stream_logs_errors(self): + # This test does not apply to Polkadex because it handles private events in its own data source + pass + + def test_user_stream_raises_cancel_exception(self): + # This test does not apply to Polkadex because it handles private events in its own data source + pass + + @aioresponses() + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + request_sent_event = asyncio.Event() + + self.exchange.start_tracking_order( + order_id=self.client_order_id_prefix + "1", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[self.client_order_id_prefix + "1"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.configure_full_fill_trade_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse(self.is_logged("INFO", f"BUY order {order.client_order_id} completely filled.")) + + request_sent_event.clear() + + self.configure_full_fill_trade_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.configure_completely_filled_order_status_response( + order=order, mock_api=mock_api, callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + if self.is_order_fill_http_update_included_in_status_update: + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse(self.is_logged("INFO", f"BUY order {order.client_order_id} completely filled.")) + + def test_initial_status_dict(self): + self.exchange._set_trading_pair_symbol_map(None) + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + response = self.trading_rules_request_mock_response + self.exchange._data_source._query_executor._all_markets_responses.put_nowait(response) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + trading_rule: TradingRule = self.exchange.trading_rules[self.trading_pair] + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(trading_rule)) + + trading_rule_with_default_values = TradingRule(trading_pair=self.trading_pair) + + # The following element can't be left with the default value because that breaks quantization in Cython + self.assertNotEqual(trading_rule_with_default_values.min_base_amount_increment, + trading_rule.min_base_amount_increment) + self.assertNotEqual(trading_rule_with_default_values.min_price_increment, + trading_rule.min_price_increment) + + @aioresponses() + def test_update_trading_rules_ignores_rule_with_error(self, mock_api): + self.exchange._set_current_timestamp(1000) + + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + response = self.trading_rules_request_erroneous_mock_response + self.exchange._data_source._query_executor._all_markets_responses.put_nowait(response) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertEqual(0, len(self.exchange._trading_rules)) + self.assertTrue( + self.is_logged("ERROR", self.expected_logged_error_for_erroneous_trading_rule) + ) + + def test_user_stream_status_is_based_on_listening_tasks(self): + self.exchange._set_trading_pair_symbol_map(None) + self.exchange._data_source._events_listening_tasks.append(self.async_loop.create_task(asyncio.sleep(120))) + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": True, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + def test_is_exception_related_to_time_synchronizer_returns_false(self): + self.assertFalse(self.exchange._is_request_exception_related_to_time_synchronizer(request_exception=None)) + + def test_create_user_stream_tracker_task(self): + self.assertIsNone(self.exchange._create_user_stream_tracker_task()) + + def _configure_balance_response( + self, + response: Dict[str, Any], + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None, + ) -> str: + all_assets_mock_response = self.all_assets_mock_response + self.exchange._data_source._query_executor._all_assets_responses.put_nowait(all_assets_mock_response) + self.exchange._data_source._query_executor._balances_responses.put_nowait(response) + return "" + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return {"cancel_order": True} + + def _all_trading_pairs_mock_response(self, orders_count: int, symbol: str) -> Any: + return { + "listOpenOrdersByMainAccount": { + "items": [ + { + "afp": "0", + "cid": f"0x48424f544250584354356663383135646636666166313531306165623366376{i}", + "fee": "0", + "fq": "0", + "id": f"0x541a3a1be1ad69cc0d325103ca54e4e12c8035d9474a96539af3323cae681fa{i}", + "m": symbol, + "ot": "LIMIT", + "p": f"1.51{i}", + "q": f"0.06{i}", + "s": "Bid", + "st": "OPEN", + "t": self.exchange.current_timestamp, + } + for i in range(orders_count) + ], + }, + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return {"findOrderByMainAccount": self._orders_status_response(order=order)} + + def _orders_status_response(self, order: InFlightOrder) -> Any: + return { + "afp": "0", + "cid": "0x" + order.client_order_id.encode("utf-8").hex(), + "fee": "0", + "fq": "0", + "id": order.exchange_order_id, + "isReverted": False, + "m": self.exchange_trading_pair, + "ot": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "p": str(order.price), + "q": str(order.amount), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "sid": 1, + "st": "OPEN", + "t": 160001112.223, + "u": "", + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "findOrderByMainAccount": { + "afp": "0", + "cid": "0x" + order.client_order_id.encode("utf-8").hex(), + "fee": "0", + "fq": "0", + "id": order.exchange_order_id, + "isReverted": False, + "m": self.exchange_trading_pair, + "ot": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "p": str(order.price), + "q": str(order.amount), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "sid": 1, + "st": "CANCELLED", + "t": 160001112.223, + "u": "", + } + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "findOrderByMainAccount": { + "afp": str(order.price), + "cid": "0x" + order.client_order_id.encode("utf-8").hex(), + "fee": str(self.expected_fill_fee.flat_fees[0].amount), + "fq": str(order.amount), + "id": order.exchange_order_id, + "isReverted": False, + "m": self.exchange_trading_pair, + "ot": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "p": str(order.price), + "q": str(order.amount), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "sid": int(self.expected_fill_trade_id), + "st": "CLOSED", + "t": 160001112.223, + "u": "", + } + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "findOrderByMainAccount": { + "afp": str(self.expected_partial_fill_price), + "cid": "0x" + order.client_order_id.encode("utf-8").hex(), + "fee": str(self.expected_partial_fill_fee.flat_fees[0].amount), + "fq": str(self.expected_partial_fill_amount), + "id": order.exchange_order_id, + "isReverted": False, + "m": self.exchange_trading_pair, + "ot": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "p": str(order.price), + "q": str(order.amount), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "sid": int(self.expected_fill_trade_id), + "st": "OPEN", + "t": 160001112.223, + "u": "", + } + } + + def _order_status_request_partially_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "findOrderByMainAccount": { + "afp": str(self.expected_partial_fill_price), + "cid": "0x" + order.client_order_id.encode("utf-8").hex(), + "fee": str(self.expected_partial_fill_fee.flat_fees[0].amount), + "fq": str(self.expected_partial_fill_amount), + "id": order.exchange_order_id, + "isReverted": False, + "m": self.exchange_trading_pair, + "ot": "MARKET" if order.order_type == OrderType.MARKET else "LIMIT", + "p": str(order.price), + "q": str(order.amount), + "s": "Bid" if order.trade_type == TradeType.BUY else "Ask", + "sid": int(self.expected_fill_trade_id), + "st": "CANCELLED", + "t": 160001112.223, + "u": "", + } + } + + @staticmethod + def _callback_wrapper_with_response(callback: Callable, response: Any, *args, **kwargs): + callback(args, kwargs) + if isinstance(response, Exception): + raise response + else: + return response + + def _exchange_order_id(self, order_number: int) -> str: + template_exchange_id = self.expected_exchange_order_id + digits = len(str(order_number)) + prefix = template_exchange_id[:-digits] + return f"{prefix}{order_number}" diff --git a/test/hummingbot/connector/exchange/test_dummy_test_to_trigger_pycharm_menu.py b/test/hummingbot/connector/exchange/test_dummy_test_to_trigger_pycharm_menu.py new file mode 100644 index 0000000..9021607 --- /dev/null +++ b/test/hummingbot/connector/exchange/test_dummy_test_to_trigger_pycharm_menu.py @@ -0,0 +1,5 @@ +import unittest + + +class TestDummy(unittest.TestCase): + pass diff --git a/test/hummingbot/connector/exchange/vertex/__init__.py b/test/hummingbot/connector/exchange/vertex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_api_order_book_data_source.py b/test/hummingbot/connector/exchange/vertex/test_vertex_api_order_book_data_source.py new file mode 100644 index 0000000..25b793a --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_api_order_book_data_source.py @@ -0,0 +1,534 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.vertex import vertex_constants as CONSTANTS +from hummingbot.connector.exchange.vertex.vertex_api_order_book_data_source import VertexAPIOrderBookDataSource +from hummingbot.connector.exchange.vertex.vertex_exchange import VertexExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.data_type.order_book_message import OrderBookMessage + +# QUEUE KEYS FOR WEBSOCKET DATA PROCESSING +TRADE_KEY = "trade" +ORDER_BOOK_DIFF_KEY = "order_book_diff" +ORDER_BOOK_SNAPSHOT_KEY = "order_book_snapshot" + + +class TestVertexAPIOrderBookDataSource(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "wBTC" + cls.quote_asset = "USDC" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.TESTNET_DOMAIN + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_task = None + self.mocking_assistant = NetworkMockingAssistant() + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + # NOTE: RANDOM KEYS GENERATED JUST FOR UNIT TESTS + self.connector = VertexExchange( + client_config_map, + "0x2162Db26939B9EAF0C5404217774d166056d31B5", + "5500eb16bf3692840e04fb6a63547b9a80b75d9cbb36b43ca5662127d4c19c83", # noqa: mock + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.time_synchronnizer = TimeSynchronizer() + self.time_synchronnizer.add_time_offset_ms_sample(1000) + self.ob_data_source = VertexAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain, + throttler=self.throttler, + ) + + self.connector._exchange_market_info = {self.domain: self.get_exchange_market_info_mock()} + + self._original_full_order_book_reset_time = self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.ob_data_source.logger().setLevel(1) + self.ob_data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.async_task and self.async_task.cancel() + self.ob_data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_exchange_market_info_mock(self) -> Dict: + exchange_rules = { + 1: { + "product_id": 1, + "oracle_price_x18": "26377830075239748635916", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001494499342736176", + "cumulative_borrows_multiplier_x18": "1005427534505418441", + "total_deposits_normalized": "336222763183987406404281", + "total_borrows_normalized": "106663044719707335242158", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "91404440604308224485238211", + "last_cumulative_multiplier_x18": "1000000008185212765", + }, + "base": { + "amount": "3531841597039580133389", + "last_cumulative_multiplier_x18": "1001494499342736176", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "56936143536016463686263", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + } + return exchange_rules + + def get_exchange_rules_mock(self) -> Dict: + exchange_rules = { + "status": "success", + "data": { + "spot_products": [ + { + "product_id": 1, + "oracle_price_x18": "26377830075239748635916", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001494499342736176", + "cumulative_borrows_multiplier_x18": "1005427534505418441", + "total_deposits_normalized": "336222763183987406404281", + "total_borrows_normalized": "106663044719707335242158", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "91404440604308224485238211", + "last_cumulative_multiplier_x18": "1000000008185212765", + }, + "base": { + "amount": "3531841597039580133389", + "last_cumulative_multiplier_x18": "1001494499342736176", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "56936143536016463686263", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + ], + }, + } + return exchange_rules + + # ORDER BOOK SNAPSHOT + @staticmethod + def _snapshot_response() -> Dict: + snapshot = { + "status": "success", + "data": { + "bids": [ + ["25100000000000000000000", "1000000000000000000"], + ["25000000000000000000000", "2000000000000000000"], + ], + "asks": [ + ["26000000000000000000000", "3000000000000000000"], + ["26100000000000000000000", "4000000000000000000"], + ], + "timestamp": "1686272064612415825", + }, + } + return snapshot + + @aioresponses() + def test_request_order_book_snapshot(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + snapshot_data = self._snapshot_response() + tradingrule_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?type={CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE}" + tradingrule_resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(tradingrule_resp)) + mock_api.get(url, body=json.dumps(snapshot_data)) + + ret = self.async_run_with_timeout(coroutine=self.ob_data_source._request_order_book_snapshot(self.trading_pair)) + + self.assertEqual(snapshot_data, ret) + + @aioresponses() + def test_get_snapshot_raises(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + tradingrule_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?type={CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE}" + tradingrule_resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(tradingrule_resp)) + mock_api.get(url, status=500) + + with self.assertRaises(IOError): + self.async_run_with_timeout(coroutine=self.ob_data_source._order_book_snapshot(self.trading_pair)) + + @aioresponses() + def test_get_new_order_book(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + resp = self._snapshot_response() + tradingrule_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?type={CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE}" + tradingrule_resp = self.get_exchange_rules_mock() + mock_api.get(tradingrule_url, body=json.dumps(tradingrule_resp)) + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.ob_data_source.get_new_order_book(self.trading_pair)) + bid_entries = list(ret.bid_entries()) + ask_entries = list(ret.ask_entries()) + self.assertEqual(2, len(bid_entries)) + self.assertEqual(25100, bid_entries[0].price) + self.assertEqual(1, bid_entries[0].amount) + self.assertEqual(25000, bid_entries[1].price) + self.assertEqual(2, bid_entries[1].amount) + + self.assertEqual(int(resp["data"]["timestamp"]), bid_entries[0].update_id) + self.assertEqual(2, len(ask_entries)) + self.assertEqual(26000, ask_entries[0].price) + self.assertEqual(3, ask_entries[0].amount) + self.assertEqual(26100, ask_entries[1].price) + self.assertEqual(4, ask_entries[1].amount) + self.assertEqual(int(resp["data"]["timestamp"]), ask_entries[0].update_id) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_depth(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = {"method": "subscribe", "stream": {"type": "trade", "product_id": 1}, "id": 1} + + result_subscribe_depth = {"method": "subscribe", "stream": {"type": "book_depth", "product_id": 1}, "id": 1} + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_depth) + ) + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_trade_subscription = {"method": "subscribe", "stream": {"type": "trade", "product_id": 1}, "id": 1} + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + expected_diff_subscription = {"method": "subscribe", "stream": {"type": "book_depth", "product_id": 1}, "id": 1} + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue( + self._is_logged( + "INFO", f"Subscribed to public trade and order book diff channels of {self.trading_pair}..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_raises_cancel_exception(self, _, ws_connect_mock): + ws_connect_mock.side_effect = asyncio.CancelledError + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_subscriptions_logs_exception_details(self, sleep_mock, ws_connect_mock): + sleep_mock.side_effect = asyncio.CancelledError + ws_connect_mock.side_effect = Exception("TEST ERROR.") + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged( + "ERROR", "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." + ) + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "type": "trade", + "timestamp": 1676151190656903000, + "product_id": 1, + "taker_qty": "1000000000000000000", + "maker_qty": "1000000000000000000", + "is_taker_buyer": True, + "is_maker_amm": True, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue(self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + trade_event = { + "type": "trade", + "timestamp": 1676151190656903000, + "product_id": 1, + "price": "26000000000000000000000", + "taker_qty": "1000000000000000000", + "maker_qty": "1000000000000000000", + "is_taker_buyer": True, + "is_maker_amm": True, + } + mock_queue.get.side_effect = [trade_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(trade_event["timestamp"], msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[ORDER_BOOK_DIFF_KEY] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "type": "book_depth", + "min_timestamp": "1683805381879572835", + "max_timestamp": "1683805381879572835", + "last_max_timestamp": "1683805381771464799", + "product_id": 1, + "bids": [["26000000000000000000000", "1000000000000000000"]], + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.ob_data_source._message_queue[ORDER_BOOK_DIFF_KEY] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange") + ) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = { + "type": "book_depth", + "min_timestamp": "1683805381879572835", + "max_timestamp": "1683805381879572835", + "last_max_timestamp": "1683805381771464799", + "product_id": 1, + "bids": [["26000000000000000000000", "1000000000000000000"]], + "asks": [], + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.ob_data_source._message_queue[ORDER_BOOK_DIFF_KEY] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + try: + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + except asyncio.CancelledError: + pass + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(diff_event["last_max_timestamp"], msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + mock_api.get(url, exception=asyncio.CancelledError) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = asyncio.CancelledError + + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + mock_api.get(url, exception=Exception) + + try: + self.async_run_with_timeout(self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.") + ) + + @aioresponses() + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + def test_listen_for_order_book_snapshots_successful(self, mock_api, _): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.TimeoutError + self.ob_data_source._message_queue[ORDER_BOOK_SNAPSHOT_KEY] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?depth={CONSTANTS.ORDER_BOOK_DEPTH}&product_id=1&type={CONSTANTS.MARKET_LIQUIDITY_REQUEST_TYPE}" + snapshot_data = self._snapshot_response() + mock_api.get(url, body=json.dumps(snapshot_data)) + + self.listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(int(snapshot_data["data"]["timestamp"]), msg.update_id) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.ob_data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.ob_data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to trading and order book stream...") + ) diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/vertex/test_vertex_api_user_stream_data_source.py new file mode 100644 index 0000000..2290f96 --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_api_user_stream_data_source.py @@ -0,0 +1,223 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Dict, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.vertex import vertex_constants as CONSTANTS, vertex_web_utils as web_utils +from hummingbot.connector.exchange.vertex.vertex_api_user_stream_data_source import VertexAPIUserStreamDataSource +from hummingbot.connector.exchange.vertex.vertex_auth import VertexAuth +from hummingbot.connector.exchange.vertex.vertex_exchange import VertexExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class TestVertexAPIUserStreamDataSource(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "wBTC" + cls.quote_asset = "USDC" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.TESTNET_DOMAIN + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + + # NOTE: RANDOM KEYS GENERATED JUST FOR UNIT TESTS + self.auth = VertexAuth( + "0x2162Db26939B9EAF0C5404217774d166056d31B5", # noqa: mock + "5500eb16bf3692840e04fb6a63547b9a80b75d9cbb36b43ca5662127d4c19c83", # noqa: mock + ) + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = VertexExchange( + client_config_map, + "0x2162Db26939B9EAF0C5404217774d166056d31B5", # noqa: mock + "5500eb16bf3692840e04fb6a63547b9a80b75d9cbb36b43ca5662127d4c19c83", # noqa: mock + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + + self.connector._exchange_market_info = {self.domain: self.get_exchange_market_info_mock()} + + self.api_factory = web_utils.build_api_factory(throttler=self.throttler, auth=self.auth) + + self.data_source = VertexAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + domain=self.domain, + api_factory=self.api_factory, + throttler=self.throttler, + connector=self.connector, + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_exchange_market_info_mock(self) -> Dict: + exchange_rules = { + 1: { + "product_id": 1, + "oracle_price_x18": "26377830075239748635916", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001494499342736176", + "cumulative_borrows_multiplier_x18": "1005427534505418441", + "total_deposits_normalized": "336222763183987406404281", + "total_borrows_normalized": "106663044719707335242158", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "91404440604308224485238211", + "last_cumulative_multiplier_x18": "1000000008185212765", + }, + "base": { + "amount": "3531841597039580133389", + "last_cumulative_multiplier_x18": "1001494499342736176", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "56936143536016463686263", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + } + return exchange_rules + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_unknown_event(self, mock_ws): + unknown_event = [{"type": "unknown_event"}] + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, json.dumps(unknown_event)) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("hummingbot.connector.exchange.vertex.vertex_auth.VertexAuth._time") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_failure_logs_error(self, ws_connect_mock, auth_time_mock): + auth_time_mock.side_effect = [100] + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + unknown_event = {"type": "unknown_event"} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(unknown_event) + ) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch("hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep") + def test_listen_for_user_stream_iter_message_throws_exception(self, sleep_mock, mock_ws): + msg_queue: asyncio.Queue = asyncio.Queue() + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") + sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution + + try: + self.async_run_with_timeout(self.data_source.listen_for_user_stream(msg_queue)) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds...") + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + @patch( + "hummingbot.connector.exchange.vertex.vertex_api_user_stream_data_source.VertexAPIUserStreamDataSource" "._time" + ) + def test_listen_for_user_stream_subscribe_message(self, time_mock, ws_connect_mock): + time_mock.side_effect = [1000, 1100, 1101, 1102] # Simulate first ping interval is already due + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps({}) + ) + + output_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + expected_message = { + "id": 1, + "method": "subscribe", + "stream": { + "product_id": 1, + "subaccount": "0x2162Db26939B9EAF0C5404217774d166056d31B5", + "type": "fill", + }, # noqa: mock + } + self.assertEqual(expected_message, sent_messages[-2]) + + # TODO: Need to assert that we send a ws.ping() frame on 30 s... diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_auth.py b/test/hummingbot/connector/exchange/vertex/test_vertex_auth.py new file mode 100644 index 0000000..ae3ca90 --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_auth.py @@ -0,0 +1,68 @@ +import asyncio +from typing import Awaitable +from unittest import TestCase + +import hummingbot.connector.exchange.vertex.vertex_constants as CONSTANTS +from hummingbot.connector.exchange.vertex.vertex_auth import VertexAuth +from hummingbot.connector.exchange.vertex.vertex_eip712_structs import Order +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, WSJSONRequest + + +class VertexAuthTests(TestCase): + def setUp(self) -> None: + super().setUp() + # NOTE: RANDOM KEYS GENERATED JUST FOR UNIT TESTS + self.sender_address = "0x2162Db26939B9EAF0C5404217774d166056d31B5" # noqa: mock + self.private_key = "5500eb16bf3692840e04fb6a63547b9a80b75d9cbb36b43ca5662127d4c19c83" # noqa: mock + + self.auth = VertexAuth( + vertex_arbitrum_address=self.sender_address, + vertex_arbitrum_private_key=self.private_key, + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_authenticate(self): + request = RESTRequest( + method=RESTMethod.GET, + url="https://test.url/api/endpoint", + is_auth_required=True, + throttler_limit_id="/api/endpoint", + ) + ret = self.async_run_with_timeout(self.auth.rest_authenticate(request)) + self.assertEqual(request, ret) + + def test_ws_authenticate(self): + payload = {"param1": "value_param_1"} + request = WSJSONRequest(payload=payload, is_auth_required=False) + ret = self.async_run_with_timeout(self.auth.ws_authenticate(request)) + self.assertEqual(payload, request.payload) + self.assertEqual(request, ret) + + def test_get_referral_code_headers(self): + headers = {"referer": CONSTANTS.HBOT_BROKER_ID} + self.assertEqual(headers, self.auth.get_referral_code_headers()) + + def test_sign_payload(self): + order = Order( + sender="0x2162Db26939B9EAF0C5404217774d166056d31B5", # noqa: mock + priceX18=26383000000000000000000, + amount=2292000000000000000, + expiration=1685989016166771694, + nonce=1767924162661187978, + ) + contract = "0xbf16e41fb4ac9922545bfc1500f67064dc2dcc3b" # noqa: mock + chain_id = "421613" + expected_signature = "0x458cb49f9c20f3f2c8f57d229ca9f33fd23556b3d5c87dbe9366e9e09ef00c43632ef996f67434f55350a9241f4bff62da7055aaa889237d33e403b482e8abab1b" # noqa: mock + expected_digest = "0xaa4dadc6a1ed641eb46a22b1b58fd702e60392b8593e3fb29a5218f7f4010e69" # noqa: mock + signature, digest = self.auth.sign_payload(order, contract, chain_id) + self.assertEqual(expected_signature, signature) + self.assertEqual(expected_digest, digest) + + def test_generate_digest(self): + signable_bytes = b"\x19\x01\xb0_\xd0\xc1Co\xf9K\xb2C$*S\x8f\xd78\xac\xc3\xdcdu\xf0\xfcY\x9d9\xac\xe7\xff/\xa6)\x1fp-\xfcL\x9d\xdf\xe8\xbb\xffe\x0bJIl\x14\x94\x89\xc9{\x9af\x97\xad2\x13\x8a1\xca\x89\xfa\xd3" # noqa: mock + expected_digest = "0xaa4dadc6a1ed641eb46a22b1b58fd702e60392b8593e3fb29a5218f7f4010e69" # noqa: mock + digest = self.auth.generate_digest(signable_bytes) + self.assertEqual(expected_digest, digest) diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_exchange.py b/test/hummingbot/connector/exchange/vertex/test_vertex_exchange.py new file mode 100644 index 0000000..7e6e7d3 --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_exchange.py @@ -0,0 +1,1540 @@ +import asyncio +import json +import unittest +from collections.abc import Awaitable +from decimal import Decimal +from typing import Any, Dict, List, Optional +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.vertex import vertex_constants as CONSTANTS, vertex_web_utils as web_utils +from hummingbot.connector.exchange.vertex.vertex_api_order_book_data_source import VertexAPIOrderBookDataSource +from hummingbot.connector.exchange.vertex.vertex_exchange import VertexExchange +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import get_new_client_order_id +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class TestVertexExchange(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "wBTC" + cls.quote_asset = "USDC" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = CONSTANTS.TESTNET_DOMAIN + cls.trading_fees = {cls.trading_pair: {"maker": Decimal("0.0"), "taker": Decimal("0.0002")}} + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.test_task: Optional[asyncio.Task] = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + # NOTE: RANDOM KEYS GENERATED JUST FOR UNIT TESTS + self.exchange = VertexExchange( + self.client_config_map, + "0x2162Db26939B9EAF0C5404217774d166056d31B5", # noqa: mock + "5500eb16bf3692840e04fb6a63547b9a80b75d9cbb36b43ca5662127d4c19c83", # noqa: mock + trading_pairs=[self.trading_pair], + domain=self.domain, + ) + self.exchange._trading_fees = self.trading_fees + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._time_synchronizer.add_time_offset_ms_sample(0) + self.exchange._time_synchronizer.logger().setLevel(1) + self.exchange._time_synchronizer.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + self.exchange._exchange_market_info = {self.domain: self.get_exchange_market_info_mock()} + + self._initialize_event_loggers() + + VertexAPIOrderBookDataSource._trading_pair_symbol_map = { + CONSTANTS.DEFAULT_DOMAIN: bidict({self.ex_trading_pair: self.trading_pair}) + } + + def tearDown(self) -> None: + self.test_task and self.test_task.cancel() + VertexAPIOrderBookDataSource._trading_pair_symbol_map = {} + super().tearDown() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger), + ] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_query_url(self, path: str, endpoint: str) -> str: + return f"{CONSTANTS.BASE_URLS[self.domain]}{path}?type={endpoint}" + + def get_exchange_symbols_mock(self) -> List[Dict[str, Any]]: + exchange_symbols = [ + {"product_id": 0, "symbol": "USDC"}, + {"product_id": 1, "symbol": "BTC"}, + {"product_id": 2, "symbol": "BTC-PERP"}, + {"product_id": 3, "symbol": "ETH"}, + {"product_id": 4, "symbol": "ETH-PERP"}, + {"product_id": 5, "symbol": "ARB"}, + {"product_id": 6, "symbol": "ARB-PERP"}, + {"product_id": 8, "symbol": "BNB-PERP"}, + {"product_id": 10, "symbol": "XRP-PERP"}, + {"product_id": 12, "symbol": "SOL-PERP"}, + {"product_id": 14, "symbol": "MATIC-PERP"}, + ] + return exchange_symbols + + def get_exchange_contracts_mock(self) -> Dict: + exchange_contracts = { + "status": "success", + "data": { + "chain_id": "421613", + "endpoint_addr": "0x5956d6f55011678b2cab217cd21626f7668ba6c5", # noqa: mock + "book_addrs": [ + "0x0000000000000000000000000000000000000000", # noqa: mock + "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + "0x291b578ff99bfef1706a2018d9dfdd98773e4f3e", # noqa: mock + "0x4008c7b762d7000034207bdef628a798065c3dcc", # noqa: mock + "0xe5106c497f8398ee8d1d6d246f08c125245d19ff", # noqa: mock + "0x49eff6d3de555be7a039d0b86471e3cb454b35de", # noqa: mock + "0xc5f223f12d091fba16141d4eeb5d39c5e0e2577c", # noqa: mock + "0xe65a493369bc41acebbc1ef7c78b2c12a972184d", # noqa: mock + "0x0897fc0e6f293da5e7da70cd296daff588fdbe55", # noqa: mock + "0x7a6eb01e393d9e32f4733ffa68c63363894a36bc", # noqa: mock + "0xcba84e5d703f604adac66f605383fc1f87a45be8", # noqa: mock + "0x5516479d3c4189bdfd0e98282779242068b08c1f", # noqa: mock + "0xc5ee375688580a72970eefd7f52e1100bcda3927", # noqa: mock + "0x38bafd8d005fe2cbde0761b3cf1fdba25d835fd8", # noqa: mock + "0x7c5953ce20d82caf70f00e4ecf9f0e67df3174d0", # noqa: mock + ], + }, + "request_type": "query_contracts", + } + return exchange_contracts + + def get_exchange_market_info_mock(self) -> Dict: + exchange_rules = { + 1: { + "product_id": 1, + "oracle_price_x18": "26377830075239748635916", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001494499342736176", + "cumulative_borrows_multiplier_x18": "1005427534505418441", + "total_deposits_normalized": "336222763183987406404281", + "total_borrows_normalized": "106663044719707335242158", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "91404440604308224485238211", + "last_cumulative_multiplier_x18": "1000000008185212765", + }, + "base": { + "amount": "3531841597039580133389", + "last_cumulative_multiplier_x18": "1001494499342736176", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "56936143536016463686263", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + } + return exchange_rules + + def get_balances_mock(self) -> Dict: + balances = { + "status": "success", + "data": { + "spot_balances": [ + { + "product_id": 0, + "lp_balance": {"amount": "0"}, + "balance": { + "amount": "1000000000000000000000000", + "last_cumulative_multiplier_x18": "1001518877793429853", + }, + }, + { + "product_id": 1, + "lp_balance": {"amount": "0"}, + "balance": { + "amount": "1000000000000000000", + "last_cumulative_multiplier_x18": "1001518877793429853", + }, + }, + ], + "perp_balances": [ + { + "product_id": 2, + "lp_balance": {"amount": "0", "last_cumulative_funding_x18": "-1001518877793429853"}, + "balance": { + "amount": "-100000000000000000", + "v_quote_balance": "100000000000000000", + "last_cumulative_funding_x18": "1000000000000000000", + }, + } + ], + "spot_products": [ + { + "product_id": 0, + "oracle_price_x18": "1000000000000000000", + "risk": { + "long_weight_initial_x18": "1000000000000000000", + "short_weight_initial_x18": "1000000000000000000", + "long_weight_maintenance_x18": "1000000000000000000", + "short_weight_maintenance_x18": "1000000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x179522635726710dd7d2035a81d856de4aa7836c", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1000000008204437687", + "cumulative_borrows_multiplier_x18": "1003084641724797461", + "total_deposits_normalized": "852001296830654324383510453917856", + "total_borrows_normalized": "553883896490779110607466353", + }, + "lp_state": { + "supply": "0", + "quote": {"amount": "0", "last_cumulative_multiplier_x18": "0"}, + "base": {"amount": "0", "last_cumulative_multiplier_x18": "0"}, + }, + "book_info": { + "size_increment": "0", + "price_increment_x18": "0", + "min_size": "0", + "collected_fees": "0", + "lp_spread_x18": "0", + }, + "symbol": "USDC", + "market": "USDC/USDC", + "contract": "0x0000000000000000000000000000000000000000", # noqa: mock + }, + { + "product_id": 1, + "oracle_price_x18": "26424265624966947277660", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001518877793429853", + "cumulative_borrows_multiplier_x18": "1005523562130424749", + "total_deposits_normalized": "336282930030016053702710", + "total_borrows_normalized": "106703872127542542861581", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "92286370346647961348638227", + "last_cumulative_multiplier_x18": "1000000008204437687", + }, + "base": { + "amount": "3498727249394376645114", + "last_cumulative_multiplier_x18": "1001518877793429853", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "499223396588563365634", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + ], + "perp_products": [ + { + "product_id": 2, + "oracle_price_x18": "26419259351173115090673", + "risk": { + "long_weight_initial_x18": "950000000000000000", + "short_weight_initial_x18": "1050000000000000000", + "long_weight_maintenance_x18": "970000000000000000", + "short_weight_maintenance_x18": "1030000000000000000", + "large_position_penalty_x18": "0", + }, + "state": { + "cumulative_funding_long_x18": "6662728756561469018660", + "cumulative_funding_short_x18": "6662728756561469018660", + "available_settle": "110946828757089326230869901", + "open_interest": "63094032706802833576317", + }, + "lp_state": { + "supply": "66703229552073603222444341", + "last_cumulative_funding_x18": "6662728756561469018660", + "cumulative_funding_per_lp_x18": "-298632181758973913", + "base": "3458209000000000000000", + "quote": "91332362183122498758162711", + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "387217901265039386486", + "lp_spread_x18": "3000000000000000", + }, + } + ], + }, + } + return balances + + def get_matches_filled_mock(self) -> Dict: + matches = { + "matches": [ + { + "digest": "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092", # noqa: mock + "order": { + "sender": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "priceX18": "25000000000000000000000", + "amount": "1000000000000000000", + "expiration": "4611687704073609553", + "nonce": "1767528267032559689", + }, + "base_filled": "1000000000000000000", + "quote_filled": "-250000000000000000000000000", + "fee": "424291087326197859", + "cumulative_fee": "424291087326197859", + "cumulative_base_filled": "1000000000000000000", + "cumulative_quote_filled": "-250000000000000000000000000", + "submission_idx": "1352436", + } + ], + "txs": [ + { + "tx": { + "match_orders": { + "product_id": 1, + "amm": False, + "taker": { + "order": { + "sender": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "price_x18": "25000000000000000000000", + "amount": "1000000000000000000", + "expiration": 4611687704073609553, + "nonce": 1767528267032559689, + }, + "signature": "0x", # noqa: mock + }, + "maker": { + "order": { + "sender": "0xf8d240d9514c9a4715d66268d7af3b53d619642564656661756c740000000000", # noqa: mock + "price_x18": "25000000000000000000000", + "amount": "-1000000000000000000", + "expiration": 1685649491, + "nonce": 1767527837317726208, + }, + "signature": "0x", # noqa: mock + }, + } + }, + "submission_idx": "1352436", + "timestamp": "1685646226", + } + ], + } + return matches + + def get_matches_unfilled_mock(self) -> Dict: + matches = { + "matches": [ + { + "digest": "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092", # noqa: mock + "order": { + "sender": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "priceX18": "25000000000000000000000", + "amount": "1000000000000000000", + "expiration": "4611687704073609553", + "nonce": "1767528267032559689", + }, + "base_filled": "0", + "quote_filled": "0", + "fee": "0", + "cumulative_fee": "0", + "cumulative_base_filled": "0", + "cumulative_quote_filled": "0", + "submission_idx": "1352436", + } + ], + "txs": [], + } + return matches + + def get_order_status_mock(self) -> Dict: + order_status = { + "status": "success", + "data": { + "product_id": 1, + "sender": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "price_x18": "25000000000000000000000", + "amount": "1000000000000000000", + "expiration": "1686250284884", + "order_type": "default", + "nonce": "1768161672830124761", + "unfilled_amount": "0", + "digest": "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092", # noqa: mock + "placed_at": 1686250288, + }, + } + return order_status + + def get_order_status_canceled_mock(self) -> Dict: + order_status = { + "status": "failure", + "data": "Order with the provided digest (0x20ebb4ed9285ded32381c2e41258a4db33d5ffbad23c1ee609e90b37aa58f3b7) could not be found. Please verify the order digest and try again.", # noqa: mock + "error_code": 2020, + } + return order_status + + def get_partial_fill_event_mock(self) -> Dict: + event = { + "type": "fill", + "timestamp": "1686256556393346680", + "product_id": 1, + "subaccount": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "order_digest": "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092", # noqa: mock + "filled_qty": "500000000000000000", + "remaining_qty": "500000000000000000", + "original_qty": "1000000000000000000", + "price": "25000000000000000000000", + "is_taker": False, + "is_bid": True, + "is_against_amm": False, + } + return event + + def get_fill_event_mock(self) -> Dict: + event = { + "type": "fill", + "timestamp": "1686256556393346680", + "product_id": 1, + "subaccount": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "order_digest": "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092", # noqa: mock + "filled_qty": "1000000000000000000", + "remaining_qty": "0", + "original_qty": "1000000000000000000", + "price": "25000000000000000000000", + "is_taker": False, + "is_bid": True, + "is_against_amm": False, + } + return event + + def get_position_change_event_mock(self) -> Dict: + event = { + "type": "position_change", + "timestamp": "1686256783298303728", + "product_id": 1, + "is_lp": False, + "subaccount": "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", # noqa: mock + "amount": "1000000000000000000", + "v_quote_amount": "0", + } + return event + + def get_max_withdrawable_mock(self) -> Dict: + event = { + "status": "success", + "data": {"max_withdrawable": "1000000000000000000"}, + "request_type": "query_max_withdrawable", + } + return event + + def _simulate_trading_rules_initialized(self): + self.exchange._trading_rules = { + self.trading_pair: TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(0.01)), + min_price_increment=Decimal(str(0.0001)), + min_base_amount_increment=Decimal(str(0.000001)), + ) + } + + def mock_balance_updates(self, mock_api) -> None: + bal_url = ( + f"{CONSTANTS.BASE_URLS[self.domain]}/query?subaccount={self.exchange.sender_address}&type=subaccount_info" + ) + bal_response = self.get_balances_mock() + mock_api.get(bal_url, body=json.dumps(bal_response)) + + for i in [0, 1]: + max_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?product_id={i}&sender={self.exchange.sender_address}&spot_leverage=false&type=max_withdrawable" + max_response = self.get_max_withdrawable_mock() + mock_api.get(max_url, body=json.dumps(max_response)) + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertIn(OrderType.MARKET, supported_types) + self.assertIn(OrderType.LIMIT, supported_types) + self.assertIn(OrderType.LIMIT_MAKER, supported_types) + + @aioresponses() + def test_check_network_success(self, mock_api): + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.STATUS_REQUEST_TYPE) + resp = {"status": "success", "data": "active"} + mock_api.get(url, body=json.dumps(resp)) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(NetworkStatus.CONNECTED, ret) + + @aioresponses() + def test_check_network_failure(self, mock_api): + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.STATUS_REQUEST_TYPE) + mock_api.get(url, status=500) + + ret = self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + self.assertEqual(ret, NetworkStatus.NOT_CONNECTED) + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.STATUS_REQUEST_TYPE) + + mock_api.get(url, exception=asyncio.CancelledError) + + self.assertRaises(asyncio.CancelledError, self.async_run_with_timeout, self.exchange.check_network()) + + @aioresponses() + def test_update_trading_rules(self, mock_api): + self.exchange._set_current_timestamp(1000) + + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + + resp = self.get_exchange_market_info_mock() + mock_api.get(url, body=json.dumps(resp)) + + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + self.assertTrue(self.trading_pair in self.exchange._trading_rules) + + def test_initial_status_dict(self): + VertexAPIOrderBookDataSource._trading_pair_symbol_map = {} + + status_dict = self.exchange.status_dict + + expected_initial_dict = { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + } + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(self.exchange.ready) + + def test_get_fee_returns_fee_from_exchange_if_available_and_default_if_not(self): + fee = self.exchange.get_fee( + base_currency="wBTC", + quote_currency="USDC", + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("20"), + ) + + self.assertEqual(Decimal("0.0002"), fee.percent) # default fee + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_client_order_id_on_order(self, mocked_nonce): + mocked_nonce.return_value = 9 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + expected_client_order_id = get_new_client_order_id( + is_buy=False, + trading_pair=self.trading_pair, + hbot_order_id_prefix=CONSTANTS.HBOT_BROKER_ID, + max_id_len=CONSTANTS.MAX_ORDER_ID_LEN, + ) + + self.assertEqual(result, expected_client_order_id) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append( + InFlightOrder( + client_order_id="ABC1", + exchange_order_id="EABC1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + ) + ) + orders.append( + InFlightOrder( + client_order_id="ABC2", + exchange_order_id="EABC2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED, + ) + ) + orders.append( + InFlightOrder( + client_order_id="ABC3", + exchange_order_id="EABC3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED, + ) + ) + orders.append( + InFlightOrder( + client_order_id="ABC4", + exchange_order_id="EABC4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED, + ) + ) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + self.assertNotIn("ABC2", self.exchange.in_flight_orders) + self.assertNotIn("ABC3", self.exchange.in_flight_orders) + self.assertNotIn("ABC4", self.exchange.in_flight_orders) + + @aioresponses() + def test_create_limit_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + creation_response = {"status": "success", "error": None} + + tradingrule_url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + resp = self.get_exchange_market_info_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post( + url, body=json.dumps(creation_response), callback=lambda *args, **kwargs: request_sent_event.set() + ) + self.mock_balance_updates(mock_api) + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="ABC1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"), + ) + ) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next( + ((key, value) for key, value in mock_api.requests.items() if key[1].human_repr().startswith(url)) + ) + request_data = json.loads(order_request[1][0].kwargs["data"])["place_order"]["order"] + self.assertEqual( + "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", request_data["sender"] # noqa: mock + ) + self.assertEqual("10000000000000000000000", request_data["priceX18"]) + self.assertEqual("100000000000000000000", request_data["amount"]) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("ABC1", create_event.order_id) + + self.assertTrue( + self._is_logged("INFO", f"Created LIMIT BUY order ABC1 for {Decimal('100.000000')} {self.trading_pair}.") + ) + + @aioresponses() + def test_create_limit_maker_order_successfully(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + creation_response = {"status": "success", "error": None} + + tradingrule_url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + resp = self.get_exchange_market_info_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post( + url, body=json.dumps(creation_response), callback=lambda *args, **kwargs: request_sent_event.set() + ) + + self.mock_balance_updates(mock_api) + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="ABC1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT_MAKER, + price=Decimal("10000"), + ) + ) + self.async_run_with_timeout(request_sent_event.wait()) + + order_request = next( + ((key, value) for key, value in mock_api.requests.items() if key[1].human_repr().startswith(url)) + ) + request_data = json.loads(order_request[1][0].kwargs["data"])["place_order"]["order"] + self.assertEqual( + "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", request_data["sender"] # noqa: mock + ) + self.assertEqual("10000000000000000000000", request_data["priceX18"]) + self.assertEqual("100000000000000000000", request_data["amount"]) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT_MAKER, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual("ABC1", create_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", f"Created LIMIT_MAKER BUY order ABC1 for {Decimal('100.000000')} {self.trading_pair}." + ) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.vertex.vertex_exchange.VertexExchange.get_price") + def test_create_market_order_successfully(self, mock_api, get_price_mock): + get_price_mock.return_value = Decimal(1000) + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + creation_response = {"status": "success", "error": None} + + tradingrule_url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + resp = self.get_exchange_market_info_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post( + url, body=json.dumps(creation_response), callback=lambda *args, **kwargs: request_sent_event.set() + ) + self.mock_balance_updates(mock_api) + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.SELL, + order_id="ABC1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.MARKET, + price=Decimal("10000"), + ) + ) + self.async_run_with_timeout(request_sent_event.wait(), 10) + + order_request = next( + ((key, value) for key, value in mock_api.requests.items() if key[1].human_repr().startswith(url)) + ) + request_data = json.loads(order_request[1][0].kwargs["data"])["place_order"]["order"] + self.assertEqual( + "0x2162Db26939B9EAF0C5404217774d166056d31B564656661756c740000000000", request_data["sender"] # noqa: mock + ) + self.assertEqual("10000000000000000000000", request_data["priceX18"]) + self.assertEqual("-100000000000000000000", request_data["amount"]) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.MARKET, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual("ABC1", create_event.order_id) + + self.assertTrue( + self._is_logged("INFO", f"Created MARKET SELL order ABC1 for {Decimal('100.000000')} {self.trading_pair}.") + ) + + @aioresponses() + def test_create_order_fails_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + tradingrule_url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + resp = self.get_exchange_market_info_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(url, status=400, callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="ABC1", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"), + ) + ) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn("ABC1", self.exchange.in_flight_orders) + self.assertAlmostEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("ABC1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "INFO", + f"Order ABC1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='ABC1', exchange_order_id=None, misc_updates=None)", + ) + ) + + @aioresponses() + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self, mock_api): + self._simulate_trading_rules_initialized() + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + tradingrule_url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.ALL_PRODUCTS_REQUEST_TYPE) + resp = self.get_exchange_market_info_mock() + mock_api.get(tradingrule_url, body=json.dumps(resp)) + mock_api.post(url, status=400, callback=lambda *args, **kwargs: request_sent_event.set()) + + self.test_task = asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="ABC1", + trading_pair=self.trading_pair, + amount=Decimal("0.0001"), + order_type=OrderType.LIMIT, + price=Decimal("0.0001"), + ) + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + asyncio.get_event_loop().create_task( + self.exchange._create_order( + trade_type=TradeType.BUY, + order_id="ABC2", + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("10000"), + ) + ) + + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertNotIn("ABC1", self.exchange.in_flight_orders) + self.assertAlmostEqual(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual("ABC1", failure_event.order_id) + + self.assertTrue( + self._is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.01. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self._is_logged( + "INFO", + f"Order ABC1 has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + "client_order_id='ABC1', exchange_order_id=None, misc_updates=None)", + ) + ) + + @aioresponses() + def test_cancel_order_successfully(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="ABC1", + exchange_order_id="ABC1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["ABC1"] + + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + response = {"status": "success", "error": None} + + mock_api.post(url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) + + self.mock_balance_updates(mock_api) + self.exchange.cancel(client_order_id="ABC1", trading_pair=self.trading_pair) + self.async_run_with_timeout(request_sent_event.wait()) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + + @aioresponses() + def test_cancel_order_raises_failure_event_when_request_fails(self, mock_api): + request_sent_event = asyncio.Event() + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="ABC1", + exchange_order_id="ABC1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["ABC1"] + + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + + mock_api.post(url, status=400, callback=lambda *args, **kwargs: request_sent_event.set()) + + self.exchange.cancel(client_order_id="ABC1", trading_pair=self.trading_pair) + self.async_run_with_timeout(request_sent_event.wait()) + + self.assertAlmostEqual(0, len(self.order_cancelled_logger.event_log)) + + self.assertTrue(self._is_logged("ERROR", f"Failed to cancel order {order.client_order_id}")) + + @aioresponses() + def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + + self.exchange.start_tracking_order( + order_id="ABC1", + exchange_order_id="ABC1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=Decimal("10000"), + amount=Decimal("100"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("ABC1", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["ABC1"] + + self.exchange.start_tracking_order( + order_id="ABC2", + exchange_order_id="ABC2", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("ABC2", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["ABC2"] + + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + + response = {"status": "success", "error": None} + + mock_api.post(url, body=json.dumps(response)) + self.mock_balance_updates(mock_api) + url = web_utils.public_rest_url(CONSTANTS.POST_PATH_URL, domain=self.domain) + + mock_api.post(url, status=400) + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertEqual(CancellationResult(order1.client_order_id, True), cancellation_results[0]) + self.assertEqual(CancellationResult(order2.client_order_id, False), cancellation_results[1]) + + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order1.client_order_id, cancel_event.order_id) + + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {order1.client_order_id}.")) + + @aioresponses() + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): + seconds_counter_mock.side_effect = [0, 0, 0] + self.exchange._set_current_timestamp(1640780000) + + self.exchange._time_synchronizer.clear_time_offset_ms_samples() + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.STATUS_REQUEST_TYPE) + + response = {"status": "success", "data": "active"} + + mock_api.get(url, body=json.dumps(response)) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + self.assertLess(1640780000 * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_time_synchronizer_failure_is_logged(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + url = self.get_query_url(CONSTANTS.QUERY_PATH_URL, CONSTANTS.STATUS_REQUEST_TYPE) + + response = {"status": "success", "data": "failed"} + + mock_api.get(url, body=json.dumps(response), status=400) + + self.async_run_with_timeout(self.exchange._update_time_synchronizer()) + self.assertGreater(1640780000 * 1e-3, self.exchange._time_synchronizer.time()) + + @aioresponses() + def test_update_balances(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?subaccount={self.exchange.sender_address}&type=subaccount_info" + response = self.get_balances_mock() + + mock_api.get(url, body=json.dumps(response)) + for i in [0, 1]: + max_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?product_id={i}&sender={self.exchange.sender_address}&spot_leverage=false&type=max_withdrawable" + max_response = self.get_max_withdrawable_mock() + mock_api.get(max_url, body=json.dumps(max_response)) + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + + self.assertEqual(Decimal("1"), available_balances["wBTC"]) + + @aioresponses() + def test_update_order_status_when_filled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = self.exchange.current_timestamp - 10 - 1 + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + matches_url = web_utils.public_rest_url(CONSTANTS.INDEXER_PATH_URL, domain=self.domain) + matches_response = self.get_matches_filled_mock() + mock_api.post(matches_url, body=json.dumps(matches_response)) + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + orders_response = self.get_order_status_mock() + mock_api.get(orders_url, body=json.dumps(orders_response)) + + # Simulate the order has been filled with a TradeUpdate + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal("1"), buy_event.base_asset_amount) + self.assertEqual(Decimal("25000"), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", f"BUY order {order.client_order_id} completely filled.")) + + @aioresponses() + def test_update_order_status_when_cancelled(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = self.exchange.current_timestamp - 10 - 1 + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + matches_url = web_utils.public_rest_url(CONSTANTS.INDEXER_PATH_URL, domain=self.domain) + matches_response = self.get_matches_unfilled_mock() + mock_api.post(matches_url, body=json.dumps(matches_response)) + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + orders_response = self.get_order_status_canceled_mock() + mock_api.get(orders_url, body=json.dumps(orders_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + + @aioresponses() + def test_update_order_status_when_order_has_not_changed(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = self.exchange.current_timestamp - 10 - 1 + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + matches_url = web_utils.public_rest_url(CONSTANTS.INDEXER_PATH_URL, domain=self.domain) + matches_response = self.get_matches_unfilled_mock() + mock_api.post(matches_url, body=json.dumps(matches_response)) + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + orders_response = self.get_order_status_mock() + mock_api.get(orders_url, body=json.dumps(orders_response)) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + @aioresponses() + def test_update_order_status_when_request_fails_marks_order_as_not_found(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + self.exchange._last_poll_timestamp = self.exchange.current_timestamp - 10 - 1 + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + mock_api.get(orders_url, status=404) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + @aioresponses() + def test_user_stream_update_for_new_order_does_not_update_status(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + matches_url = web_utils.public_rest_url(CONSTANTS.INDEXER_PATH_URL, domain=self.domain) + matches_response = self.get_matches_unfilled_mock() + mock_api.post(matches_url, body=json.dumps(matches_response)) + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + orders_response = self.get_order_status_mock() + mock_api.get(orders_url, body=json.dumps(orders_response)) + self.async_run_with_timeout(self.exchange._update_order_status()) + + event_message = {"type": "nonexistent_vertex_event"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + @aioresponses() + def test_user_stream_update_for_cancelled_order(self, mock_api): + self.exchange._set_current_timestamp(1640780000) + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + matches_url = web_utils.public_rest_url(CONSTANTS.INDEXER_PATH_URL, domain=self.domain) + matches_response = self.get_matches_unfilled_mock() + mock_api.post(matches_url, body=json.dumps(matches_response)) + + orders_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?digest={digest}&product_id=1&type=order" + orders_response = self.get_order_status_canceled_mock() + mock_api.get(orders_url, body=json.dumps(orders_response)) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + event_message = {"type": "nonexistent_vertex_event"} + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + + def test_user_stream_update_for_order_partial_fill(self): + self.exchange._set_current_timestamp(1640780000) + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + event_message = self.get_partial_fill_event_mock() + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal("25000"), fill_event.price) + self.assertEqual(Decimal("0.5"), fill_event.amount) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + self.assertTrue( + self._is_logged( + "INFO", + f"The {order.trade_type.name} order {order.client_order_id} amounting to " + f"{fill_event.amount}/{order.amount} {order.base_asset} has been filled.", + ) + ) + + def test_user_stream_update_for_order_fill(self): + self.exchange._set_current_timestamp(1640780000) + digest = "0x7b76413f438b5dd83550901304d8afed47720358acbd923890cd9431a58d3092" # noqa: mock + + self.exchange.start_tracking_order( + order_id=digest, + exchange_order_id=digest, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("25000"), + amount=Decimal("1"), + ) + order: InFlightOrder = self.exchange.in_flight_orders[digest] + + event_message = self.get_fill_event_mock() + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + self.exchange._user_stream_tracker._user_stream = mock_queue + + try: + self.async_run_with_timeout(self.exchange._user_stream_event_listener()) + except asyncio.CancelledError: + pass + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(Decimal("25000"), fill_event.price) + self.assertEqual(Decimal("1"), fill_event.amount) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(Decimal("25000"), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue(self._is_logged("INFO", f"BUY order {order.client_order_id} completely filled.")) + + # def test_user_stream_balance_update(self): + # self.exchange._set_current_timestamp(1640780000) + + # event_message = self.get_position_change_event_mock() + # mock_queue = AsyncMock() + # mock_queue.get.side_effect = [event_message, asyncio.CancelledError] + # self.exchange._user_stream_tracker._user_stream = mock_queue + + # try: + # self.async_run_with_timeout(coroutine=self.exchange._user_stream_event_listener(), timeout=2) + # except asyncio.CancelledError: + # pass + + # self.assertEqual(Decimal("1"), self.exchange.available_balances["wBTC"]) + # self.assertEqual(Decimal("1"), self.exchange.get_balance("wBTC")) + + @aioresponses() + def test_get_account_max_withdrawable(self, mock_api): + for i in [0, 1]: + max_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?product_id={i}&sender={self.exchange.sender_address}&spot_leverage=false&type=max_withdrawable" + max_response = self.get_max_withdrawable_mock() + mock_api.get(max_url, body=json.dumps(max_response)) + res = self.async_run_with_timeout(self.exchange._get_account_max_withdrawable()) + self.assertEqual(Decimal("1"), res[0]) + + def test_user_stream_raises_cancel_exception(self): + self.exchange._set_current_timestamp(1640780000) + + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError + self.exchange._user_stream_tracker._user_stream = mock_queue + + self.assertRaises( + asyncio.CancelledError, self.async_run_with_timeout, self.exchange._user_stream_event_listener() + ) + + @aioresponses() + def test_get_account(self, mock_api): + url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?subaccount={self.exchange.sender_address}&type=subaccount_info" + response = self.get_balances_mock() + + mock_api.get(url, body=json.dumps(response)) + + try: + self.async_run_with_timeout(coroutine=self.exchange._get_account(), timeout=2) + except asyncio.CancelledError: + pass + + self.assertTrue(response["status"] == "success") + + @aioresponses() + def test_get_symbols(self, mock_api): + symbols_response = self.get_exchange_symbols_mock() + symbols_url = f"{CONSTANTS.BASE_URLS[self.domain]}{CONSTANTS.SYMBOLS_PATH_URL}" + mock_api.get(symbols_url, body=json.dumps(symbols_response)) + + try: + self.async_run_with_timeout(self.exchange._get_symbols(), timeout=1) + except asyncio.CancelledError: + pass + except asyncio.TimeoutError: + pass + + self.assertEqual(int(0), self.exchange._symbols[0]["product_id"]) + self.assertEqual("USDC", self.exchange._symbols[0]["symbol"]) + + @aioresponses() + def test_contracts(self, mock_api): + contracts_response = self.get_exchange_contracts_mock() + contracts_url = f"{CONSTANTS.BASE_URLS[self.domain]}/query?type=contracts" + mock_api.get(contracts_url, body=json.dumps(contracts_response)) + + try: + self.async_run_with_timeout(self.exchange._get_contracts(), timeout=2) + except asyncio.CancelledError: + pass + + self.assertEqual("0x0000000000000000000000000000000000000000", self.exchange._contracts[0]) # noqa: mock diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_order_book.py b/test/hummingbot/connector/exchange/vertex/test_vertex_order_book.py new file mode 100644 index 0000000..ce1e246 --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_order_book.py @@ -0,0 +1,76 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.vertex.vertex_order_book import VertexOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class VertexOrderBookTests(TestCase): + def test_snapshot_message_from_exchange(self): + snapshot_message = VertexOrderBook.snapshot_message_from_exchange_rest( + msg={ + "data": { + "timestamp": 1640000000.0, + "bids": [["4000000000000000000", "431000000000000000000"]], + "asks": [["4000002000000000000", "12000000000000000000"]], + } + }, + timestamp=1640000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"}, + ) + self.assertEqual("COINALPHA-HBOT", snapshot_message.trading_pair) + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type) + self.assertEqual(1640000000.0, snapshot_message.timestamp) + self.assertEqual(1640000000, snapshot_message.update_id) + self.assertEqual(1, len(snapshot_message.bids)) + self.assertEqual(4.0, snapshot_message.bids[0].price) + self.assertEqual(431.0, snapshot_message.bids[0].amount) + self.assertEqual(1, len(snapshot_message.asks)) + self.assertEqual(4.000002, snapshot_message.asks[0].price) + self.assertEqual(12.0, snapshot_message.asks[0].amount) + + def test_diff_message_from_exchange(self): + diff_msg = VertexOrderBook.diff_message_from_exchange( + msg={ + "last_max_timestamp": 1640000000000000000.0, + "product_id": 1, + "bids": [["4000000000000000000", "431000000000000000000"]], + "asks": [["4000002000000000000", "12000000000000000000"]], + }, + timestamp=1640000000000000000.0, + metadata={"trading_pair": "COINALPHA-HBOT"}, + ) + + self.assertEqual("COINALPHA-HBOT", diff_msg.trading_pair) + self.assertEqual(OrderBookMessageType.DIFF, diff_msg.type) + self.assertEqual(1640000000.0, diff_msg.timestamp) + self.assertEqual(1640000000000000000, diff_msg.update_id) + self.assertEqual(1640000000000000000, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(4.0, diff_msg.bids[0].price) + self.assertEqual(431, diff_msg.bids[0].amount) + self.assertEqual(1640000000000000000, diff_msg.bids[0].update_id) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(4.000002, diff_msg.asks[0].price) + self.assertEqual(12.0, diff_msg.asks[0].amount) + self.assertEqual(1640000000000000000, diff_msg.asks[0].update_id) + + def test_trade_message_from_exchange(self): + trade_update = { + "product_id": 2, + "is_taker_buyer": False, + "timestamp": 1640000000000000000.0, + "price": "1000000000000000", + "taker_qty": "100000000000000000000", + } + + trade_message = VertexOrderBook.trade_message_from_exchange( + msg=trade_update, metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual("COINALPHA-HBOT", trade_message.trading_pair) + self.assertEqual(OrderBookMessageType.TRADE, trade_message.type) + self.assertEqual(1640000000, trade_message.timestamp) + self.assertEqual(-1, trade_message.update_id) + self.assertEqual(-1, trade_message.first_update_id) + self.assertEqual(1640000000000000000, trade_message.trade_id) diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_utils.py b/test/hummingbot/connector/exchange/vertex/test_vertex_utils.py new file mode 100644 index 0000000..15f140a --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_utils.py @@ -0,0 +1,143 @@ +import random +from decimal import Decimal +from typing import Dict +from unittest import TestCase + +import hummingbot.connector.exchange.vertex.vertex_constants as CONSTANTS +from hummingbot.connector.exchange.vertex import vertex_utils + + +class VertexUtilTestCases(TestCase): + def get_exchange_market_info_mock(self) -> Dict: + exchange_market_info = { + 1: { + "product_id": 1, + "oracle_price_x18": "26377830075239748635916", + "risk": { + "long_weight_initial_x18": "900000000000000000", + "short_weight_initial_x18": "1100000000000000000", + "long_weight_maintenance_x18": "950000000000000000", + "short_weight_maintenance_x18": "1050000000000000000", + "large_position_penalty_x18": "0", + }, + "config": { + "token": "0x5cc7c91690b2cbaee19a513473d73403e13fb431", # noqa: mock + "interest_inflection_util_x18": "800000000000000000", + "interest_floor_x18": "10000000000000000", + "interest_small_cap_x18": "40000000000000000", + "interest_large_cap_x18": "1000000000000000000", + }, + "state": { + "cumulative_deposits_multiplier_x18": "1001494499342736176", + "cumulative_borrows_multiplier_x18": "1005427534505418441", + "total_deposits_normalized": "336222763183987406404281", + "total_borrows_normalized": "106663044719707335242158", + }, + "lp_state": { + "supply": "62619418496845923388438072", + "quote": { + "amount": "91404440604308224485238211", + "last_cumulative_multiplier_x18": "1000000008185212765", + }, + "base": { + "amount": "3531841597039580133389", + "last_cumulative_multiplier_x18": "1001494499342736176", + }, + }, + "book_info": { + "size_increment": "1000000000000000", + "price_increment_x18": "1000000000000000000", + "min_size": "10000000000000000", + "collected_fees": "56936143536016463686263", + "lp_spread_x18": "3000000000000000", + }, + "symbol": "wBTC", + "market": "wBTC/USDC", + "contract": "0x939b0915f9c3b657b9e9a095269a0078dd587491", # noqa: mock + }, + } + return exchange_market_info + + def test_hex_to_bytes32(self): + hex_string = "0x5cc7c91690b2cbaee19a513473d73403e13fb431" # noqa: mock + expected_bytes = b"\\\xc7\xc9\x16\x90\xb2\xcb\xae\xe1\x9aQ4s\xd74\x03\xe1?\xb41\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # noqa: mock + self.assertEqual(expected_bytes, vertex_utils.hex_to_bytes32(hex_string)) + + def test_convert_timestamp(self): + timestamp = 1685989014506281744 + expected_ts = 1685989014506281744 / 1e9 + self.assertEqual(expected_ts, vertex_utils.convert_timestamp(timestamp)) + + def test_trading_pair_to_product_id(self): + trading_pair = "wBTC-USDC" + expected_id = 1 + exchange_info = self.get_exchange_market_info_mock() + self.assertEqual(expected_id, vertex_utils.trading_pair_to_product_id(trading_pair, exchange_info)) + missing_trading_pair = "ABC-XYZ" + expected_missing_id = -1 + self.assertEqual( + expected_missing_id, vertex_utils.trading_pair_to_product_id(missing_trading_pair, exchange_info) + ) + + def test_market_to_trading_pair(self): + market = "wBTC/USDC" + expected_trading_pair = "wBTC-USDC" + self.assertEqual(expected_trading_pair, vertex_utils.market_to_trading_pair(market)) + + def test_convert_from_x18(self): + data_numeric = 26369000000000000000000 + expected_numeric = "26369" + self.assertEqual(expected_numeric, vertex_utils.convert_from_x18(data_numeric)) + + data_dict = { + "bids": [["26369000000000000000000", "294000000000000000"]], + "asks": [["26370000000000000000000", "551000000000000000"]], + } + expected_dict = { + "bids": [["26369", "0.294"]], + "asks": [["26370", "0.551"]], + } + self.assertEqual(expected_dict, vertex_utils.convert_from_x18(data_dict)) + + def test_convert_to_x18(self): + data_numeric = 26369.123 + expected_numeric = "26369000000000000000000" + self.assertEqual(expected_numeric, vertex_utils.convert_to_x18(data_numeric, Decimal("1"))) + + data_dict = { + "bids": [[26369.0, 0.294]], + "asks": [[26370.0, 0.551]], + } + expected_dict = { + "bids": [["26369000000000000000000", "294000000000000000"]], + "asks": [["26370000000000000000000", "551000000000000000"]], + } + self.assertEqual(expected_dict, vertex_utils.convert_to_x18(data_dict)) + + def test_generate_expiration(self): + timestamp = 1685989011.1215873 + expected_gtc = "1686075411" + expected_ioc = "4611686020113463315" + expected_fok = "9223372038540851219" + expected_postonly = "13835058056968239123" + self.assertEqual(expected_gtc, vertex_utils.generate_expiration(timestamp, CONSTANTS.TIME_IN_FORCE_GTC)) + self.assertEqual(expected_ioc, vertex_utils.generate_expiration(timestamp, CONSTANTS.TIME_IN_FORCE_IOC)) + self.assertEqual(expected_fok, vertex_utils.generate_expiration(timestamp, CONSTANTS.TIME_IN_FORCE_FOK)) + self.assertEqual( + expected_postonly, vertex_utils.generate_expiration(timestamp, CONSTANTS.TIME_IN_FORCE_POSTONLY) + ) + + def test_generate_nonce(self): + timestamp = 1685989011.1215873 + expiry_ms = 90 + expected_nonce = 1767887707697054351 + random.seed(42) + self.assertEqual(expected_nonce, vertex_utils.generate_nonce(timestamp, expiry_ms)) + + def test_convert_address_to_sender(self): + address = "0xbbee07b3e8121227afcfe1e2b82772246226128e" # noqa: mock + expected_sender = "0xbbee07b3e8121227afcfe1e2b82772246226128e64656661756c740000000000" # noqa: mock + self.assertEqual(expected_sender, vertex_utils.convert_address_to_sender(address)) + + def test_is_exchange_information_valid(self): + self.assertTrue(vertex_utils.is_exchange_information_valid({})) diff --git a/test/hummingbot/connector/exchange/vertex/test_vertex_web_utils.py b/test/hummingbot/connector/exchange/vertex/test_vertex_web_utils.py new file mode 100644 index 0000000..455e848 --- /dev/null +++ b/test/hummingbot/connector/exchange/vertex/test_vertex_web_utils.py @@ -0,0 +1,33 @@ +import asyncio +import time +from unittest import TestCase + +from hummingbot.connector.exchange.vertex import vertex_constants as CONSTANTS, vertex_web_utils as web_utils + + +class WebUtilsTests(TestCase): + def test_public_rest_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.QUERY_PATH_URL, domain=CONSTANTS.DEFAULT_DOMAIN) + self.assertEqual("https://prod.vertexprotocol-backend.com/query", url) + url = web_utils.public_rest_url(path_url=CONSTANTS.QUERY_PATH_URL, domain=CONSTANTS.TESTNET_DOMAIN) + self.assertEqual("https://test.vertexprotocol-backend.com/query", url) + + def test_private_rest_url(self): + url = web_utils.private_rest_url(path_url=CONSTANTS.QUERY_PATH_URL, domain=CONSTANTS.DEFAULT_DOMAIN) + self.assertEqual("https://prod.vertexprotocol-backend.com/query", url) + url = web_utils.private_rest_url(path_url=CONSTANTS.QUERY_PATH_URL, domain=CONSTANTS.TESTNET_DOMAIN) + self.assertEqual("https://test.vertexprotocol-backend.com/query", url) + + def test_build_api_factory(self): + self.assertIsNotNone(web_utils.build_api_factory()) + + def test_create_throttler(self): + self.assertIsNotNone(web_utils.create_throttler()) + + def test_get_current_server_time(self): + loop = asyncio.get_event_loop() + recent_timestamp = time.time() - 1.0 + server_timestamp = loop.run_until_complete( + asyncio.wait_for(web_utils.get_current_server_time(web_utils.create_throttler()), 1) + ) + self.assertLess(recent_timestamp, server_timestamp) diff --git a/test/hummingbot/connector/exchange/woo_x/__init__.py b/test/hummingbot/connector/exchange/woo_x/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_order_book_data_source.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_order_book_data_source.py new file mode 100644 index 0000000..7973769 --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_order_book_data_source.py @@ -0,0 +1,464 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses.core import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_web_utils as web_utils +from hummingbot.connector.exchange.woo_x.woo_x_api_order_book_data_source import WooXAPIOrderBookDataSource +from hummingbot.connector.exchange.woo_x.woo_x_exchange import WooXExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage + + +class WooXAPIOrderBookDataSourceUnitTests(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"SPOT_{cls.base_asset}_{cls.quote_asset}" + cls.domain = "woo_x" + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + + self.listening_task = None + + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + self.connector = WooXExchange( + client_config_map=client_config_map, + public_api_key="", + secret_api_key="", + application_id="", + trading_pairs=[], + trading_required=False, + domain=self.domain + ) + + self.data_source = WooXAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + + self.data_source.logger().setLevel(1) + + self.data_source.logger().addHandler(self) + + self._original_full_order_book_reset_time = self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS + + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = -1 + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + + self.data_source.FULL_ORDER_BOOK_RESET_DELTA_SECONDS = self._original_full_order_book_reset_time + + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + def _trade_update_event(self): + resp = { + "topic": "SPOT_BTC_USDT@trade", + "ts": 1618820361552, + "data": { + "symbol": "SPOT_BTC_USDT", + "price": 56749.15, + "size": 3.92864, + "side": "BUY", + "source": 0 + } + } + + return resp + + def _order_diff_event(self): + resp = { + "topic": "SPOT_BTC_USDT@orderbookupdate", + "ts": 1618826337580, + "data": { + "symbol": "SPOT_BTC_USDT", + "prevTs": 1618826337380, + "asks": [ + [ + 56749.15, + 3.92864 + ], + [ + 56749.8, + 0 + ], + ], + "bids": [ + [ + 56745.2, + 1.03895025 + ], + [ + 56744.6, + 1.0807 + ], + ] + } + } + + return resp + + def _snapshot_response(self): + return { + "success": True, + "bids": [ + { + "price": 4, + "quantity": 431 + } + ], + "asks": [ + { + "price": 4.000002, + "quantity": 12 + } + ], + "timestamp": 1686211049066 + } + + @aioresponses() + def test_get_new_order_book_successful(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + resp = self._snapshot_response() + + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + expected_update_id = resp["timestamp"] + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(4, bids[0].price) + self.assertEqual(431, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(4.000002, asks[0].price) + self.assertEqual(12, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, domain=self.domain) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_trades = { + "id": "0", + "event": "subscribe", + "success": True, + "ts": 1609924478533 + } + + result_subscribe_diffs = { + "id": "1", + "event": "subscribe", + "success": True, + "ts": 1609924478533 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_trades) + ) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_diffs) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_subscription_messages)) + + expected_trade_subscription = { + "id": "0", + "topic": f"{self.ex_trading_pair}@trade", + "event": "subscribe", + } + + self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) + + expected_diff_subscription = { + "id": "1", + "topic": f"{self.ex_trading_pair}@orderbookupdate", + "event": "subscribe", + } + + self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) + + self.assertTrue(self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + )) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange")) + + def test_listen_for_trades_successful(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = [self._trade_update_event(), asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.TRADE_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1618820361552, msg.trade_id) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "m": 1, + "i": 2, + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange")) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + diff_event = self._order_diff_event() + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue)) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(diff_event["ts"], msg.update_id) + + @aioresponses() + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self, mock_api): + url = web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, domain=self.domain) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=asyncio.CancelledError, repeat=True) + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + @aioresponses() + @patch("hummingbot.connector.exchange.woo_x.woo_x_api_order_book_data_source" + ".WooXAPIOrderBookDataSource._sleep") + def test_listen_for_order_book_snapshots_log_exception(self, mock_api, sleep_mock): + msg_queue: asyncio.Queue = asyncio.Queue() + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + url = web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, domain=self.domain) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, exception=Exception, repeat=True) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged("ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}.")) + + @aioresponses() + def test_listen_for_order_book_snapshots_successful(self, mock_api, ): + msg_queue: asyncio.Queue = asyncio.Queue() + + url = web_utils.public_rest_url(path_url=CONSTANTS.ORDERBOOK_SNAPSHOT_PATH_URL, domain=self.domain) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(1686211049066, msg.update_id) diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_user_stream_data_source.py new file mode 100644 index 0000000..4308017 --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_api_user_stream_data_source.py @@ -0,0 +1,273 @@ +import asyncio +import json +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS +from hummingbot.connector.exchange.woo_x.woo_x_api_user_stream_data_source import WooXAPIUserStreamDataSource +from hummingbot.connector.exchange.woo_x.woo_x_auth import WooXAuth +from hummingbot.connector.exchange.woo_x.woo_x_exchange import WooXExchange +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.time_synchronizer import TimeSynchronizer +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler + + +class WooXUserStreamDataSourceUnitTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + cls.domain = "woo_x" + + cls.listen_key = "TEST_LISTEN_KEY" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) + self.mock_time_provider = MagicMock() + self.mock_time_provider.time.return_value = 1000 + self.auth = WooXAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET", time_provider=self.mock_time_provider) + self.time_synchronizer = TimeSynchronizer() + self.time_synchronizer.add_time_offset_ms_sample(0) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.connector = WooXExchange( + client_config_map=client_config_map, + public_api_key="", + secret_api_key="", + application_id="", + trading_pairs=[], + trading_required=False, + domain=self.domain) + self.connector._web_assistants_factory._auth = self.auth + + self.data_source = WooXAPIUserStreamDataSource( + auth=self.auth, + trading_pairs=[self.trading_pair], + connector=self.connector, + api_factory=self.connector._web_assistants_factory, + domain=self.domain + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def _raise_exception(self, exception_class): + raise exception_class + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def _create_return_value_and_unlock_test_with_event(self, value): + self.resume_test_event.set() + return value + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def _error_response(self) -> Dict[str, Any]: + resp = { + "code": "ERROR CODE", + "msg": "ERROR MESSAGE" + } + + return resp + + def _user_update_event(self): + # Balance Update + resp = { + "e": "balanceUpdate", + "E": 1573200697110, + "a": "BTC", + "d": "100.00000000", + "T": 1573200697068 + } + return json.dumps(resp) + + def _successfully_subscribed_event(self): + resp = { + "result": None, + "id": 1 + } + return resp + + def _authentication_response(self, success: bool) -> str: + return json.dumps({ + "id": "auth", + "event": "auth", + "success": success, + "ts": 1686526749230, + **({} if success else {"errorMsg": "sample error message"}) + }) + + def _subscription_response(self, success: bool, channel: str) -> str: + return json.dumps({ + 'id': channel, + 'event': 'subscribe', + 'success': success, + 'ts': 1686527628871 + }) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_authenticates_and_subscribes_to_events(self, ws_connect_mock): + messages = asyncio.Queue() + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + initial_last_recv_time = self.data_source.last_recv_time + + # Add the authentication response for the websocket + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(True) + ) + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response( + True, + 'executionreport' + ) + ) + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._subscription_response( + True, + 'balance' + ) + ) + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged("INFO", "Subscribed to private account and orders channels...") + ) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(ws_connect_mock.return_value) + + self.assertEqual(3, len(sent_messages)) + + for n, id in enumerate(['auth', 'executionreport', 'balance']): + self.assertEqual(sent_messages[n]['id'], id) + + self.assertGreater(self.data_source.last_recv_time, initial_last_recv_time) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_authentication_failure(self, ws_connect_mock): + messages = asyncio.Queue() + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, + self._authentication_response(False) + ) + + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertTrue( + self._is_logged( + "ERROR", + f"Error authenticating the private websocket connection: {self._authentication_response(False)}" + ) + ) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_ws): + mock_ws.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + mock_ws.return_value, "" + ) + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) + + self.assertEqual(0, msg_queue.qsize()) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_connection_failed(self, mock_ws): + mock_ws.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( + Exception("TEST ERROR.") + ) + + msg_queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_user_stream(msg_queue) + ) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error while listening to user stream. Retrying after 5 seconds..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listening_process_canceled_on_cancel_exception(self, mock_ws): + messages = asyncio.Queue() + + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = asyncio.get_event_loop().create_task( + self.data_source.listen_for_user_stream(messages) + ) + + self.async_run_with_timeout(self.listening_task) diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_auth.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_auth.py new file mode 100644 index 0000000..26bbc8c --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_auth.py @@ -0,0 +1,67 @@ +import asyncio +import hashlib +import hmac +import json +from unittest import TestCase +from unittest.mock import MagicMock + +from typing_extensions import Awaitable + +from hummingbot.connector.exchange.woo_x.woo_x_auth import WooXAuth +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + + +class WooXAuthTests(TestCase): + def setUp(self) -> None: + self._api_key = "testApiKey" + self._secret = "testSecret" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + + return ret + + def test_rest_authenticate(self): + mock_time_provider = MagicMock() + + mock_time_provider.time.return_value = 1686452155.0 + + data = { + "symbol": "SPOT_BTC_USDT", + "order_type": "LIMIT", + "side": "BUY", + "order_price": 20000, + "order_quantity": 1, + } + + timestamp = str(int(mock_time_provider.time.return_value * 1e3)) + + auth = WooXAuth(api_key=self._api_key, secret_key=self._secret, time_provider=mock_time_provider) + + request = RESTRequest(method=RESTMethod.POST, data=json.dumps(data), is_auth_required=True) + + configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) + + signable = '&'.join([f"{key}={value}" for key, value in sorted(data.items())]) + f"|{timestamp}" + + signature = ( + hmac.new( + bytes(self._secret, "utf-8"), + bytes(signable, "utf-8"), + hashlib.sha256 + ).hexdigest().upper() + ) + + headers = { + 'x-api-key': self._api_key, + 'x-api-signature': signature, + 'x-api-timestamp': timestamp, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + } + + self.assertEqual(timestamp, configured_request.headers['x-api-timestamp']) + + self.assertEqual(signature, configured_request.headers['x-api-signature']) + + self.assertEqual(headers, configured_request.headers) diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_exchange.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_exchange.py new file mode 100644 index 0000000..64852d7 --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_exchange.py @@ -0,0 +1,920 @@ +import json +import logging +import re +import secrets +from decimal import Decimal +from typing import Any, Callable, List, Optional, Tuple +from unittest.mock import patch + +from aioresponses import aioresponses +from aioresponses.core import RequestCall + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_web_utils as web_utils +from hummingbot.connector.exchange.woo_x.woo_x_exchange import WooXExchange +from hummingbot.connector.test_support.exchange_connector_test import AbstractExchangeConnectorTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import DeductedFromReturnsTradeFee, TokenAmount, TradeFeeBase + + +class WooXExchangeTests(AbstractExchangeConnectorTests.ExchangeConnectorTests): + @property + def all_symbols_url(self): + return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + + @property + def latest_prices_url(self): + params = { + 'symbol': self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + } + + query = ('?' + '&'.join([f"{key}={value}" for key, value in sorted(params.items())])) if len( + params) != 0 else '' + + url = web_utils.public_rest_url(path_url=CONSTANTS.MARKET_TRADES_PATH, domain=self.exchange._domain) + query + + return url + + @property + def network_status_url(self): + raise NotImplementedError + + @property + def trading_rules_url(self): + return web_utils.public_rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) + + @property + def order_creation_url(self): + return web_utils.public_rest_url(CONSTANTS.ORDER_PATH_URL, domain=self.exchange._domain) + + @property + def balance_url(self): + return web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL, domain=self.exchange._domain) + + @property + def all_symbols_request_mock_response(self): + return { + "rows": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "quote_min": 0, + "quote_max": 200000, + "quote_tick": 0.01, + "base_min": 0.00001, + "base_max": 300, + "base_tick": 0.00000001, + "min_notional": 1, + "price_range": 0.1, + "price_scope": None, + "created_time": "1571824137.000", + "updated_time": "1686530374.000", + "is_stable": 0, + "precisions": [ + 1, + 10, + 100, + 500, + 1000, + 10000 + ] + } + ], + "success": True + } + + @property + def latest_prices_request_mock_response(self): + return { + "success": True, + "rows": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "side": "BUY", + "source": 0, + "executed_price": self.expected_latest_price, + "executed_quantity": 0.00025, + "executed_timestamp": "1567411795.000" + } + ] + } + + @property + def all_symbols_including_invalid_pair_mock_response(self) -> Tuple[str, Any]: + mock_response = self.all_symbols_request_mock_response + + return None, mock_response + + @property + def network_status_request_successful_mock_response(self): + return {} + + @property + def trading_rules_request_mock_response(self): + return { + "rows": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "quote_min": 0, + "quote_max": 200000, + "quote_tick": 0.01, + "base_min": 0.00001, + "base_max": 300, + "base_tick": 0.00000001, + "min_notional": 1, + "price_range": 0.1, + "price_scope": None, + "created_time": "1571824137.000", + "updated_time": "1686530374.000", + "is_stable": 0, + "precisions": [ + 1, + 10, + 100, + 500, + 1000, + 10000 + ] + } + ], + "success": None + } + + @property + def trading_rules_request_erroneous_mock_response(self): + return { + "rows": [ + { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "min_notional": 1, + "price_range": 0.1, + "price_scope": None, + "created_time": "1571824137.000", + "updated_time": "1686530374.000", + "is_stable": 0, + "precisions": [ + 1, + 10, + 100, + 500, + 1000, + 10000 + ] + } + ], + "success": None + } + + @property + def order_creation_request_successful_mock_response(self): + return { + "success": True, + "timestamp": "1686537643.701", + "order_id": self.expected_exchange_order_id, + "order_type": "LIMIT", + "order_price": 20000, + "order_quantity": 0.001, + "order_amount": None, + "client_order_id": 0 + } + + @property + def balance_request_mock_response_for_base_and_quote(self): + return { + "holding": [{ + "token": self.base_asset, + "holding": 10, + "frozen": 5, + "interest": 0.0, + "outstanding_holding": -0.00080, + "pending_exposure": 0.0, + "opening_cost": -126.36839957, + "holding_cost": -125.69703515, + "realised_pnl": 73572.86125165, + "settled_pnl": 73573.5326161, + "fee_24_h": 0.01432411, + "settled_pnl_24_h": 0.67528081, + "updated_time": "1675220398" + }, { + "token": self.quote_asset, + "holding": 2000, + "frozen": 0, + "interest": 0.0, + "outstanding_holding": -0.00080, + "pending_exposure": 0.0, + "opening_cost": -126.36839957, + "holding_cost": -125.69703515, + "realised_pnl": 73572.86125165, + "settled_pnl": 73573.5326161, + "fee_24_h": 0.01432411, + "settled_pnl_24_h": 0.67528081, + "updated_time": "1675220398" + }], + "success": True + } + + @property + def balance_request_mock_response_only_base(self): + return { + "holding": [{ + "token": self.base_asset, + "holding": 10, + "frozen": 5, + "interest": 0.0, + "outstanding_holding": -0.00080, + "pending_exposure": 0.0, + "opening_cost": -126.36839957, + "holding_cost": -125.69703515, + "realised_pnl": 73572.86125165, + "settled_pnl": 73573.5326161, + "fee_24_h": 0.01432411, + "settled_pnl_24_h": 0.67528081, + "updated_time": "1675220398" + }], + "success": True + } + + @property + def balance_event_websocket_update(self): + return { + "topic": "balance", + "ts": 1686539285351, + "data": { + "balances": { + self.base_asset: { + "holding": 10, + "frozen": 5, + "interest": 0.0, + "pendingShortQty": 0.0, + "pendingExposure": 0.0, + "pendingLongQty": 0.004, + "pendingLongExposure": 0.0, + "version": 9, + "staked": 0.0, + "unbonding": 0.0, + "vault": 0.0, + "averageOpenPrice": 0.0, + "pnl24H": 0.0, + "fee24H": 0.00773214, + "markPrice": 25772.05, + "pnl24HPercentage": 0.0 + } + } + } + } + + @property + def expected_latest_price(self): + return 9999.9 + + @property + def expected_supported_order_types(self): + return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET] + + @property + def expected_trading_rule(self): + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=Decimal(str(self.trading_rules_request_mock_response["rows"][0]["base_min"])), + min_price_increment=Decimal(str(self.trading_rules_request_mock_response["rows"][0]["quote_tick"])), + min_base_amount_increment=Decimal(str(self.trading_rules_request_mock_response["rows"][0]['base_tick'])), + min_notional_size=Decimal(str(self.trading_rules_request_mock_response["rows"][0]["min_notional"])) + ) + + @property + def expected_logged_error_for_erroneous_trading_rule(self): + erroneous_rule = self.trading_rules_request_erroneous_mock_response["rows"][0] + return f"Error parsing the trading pair rule {erroneous_rule}. Skipping." + + @property + def expected_exchange_order_id(self): + return 28 + + @property + def is_order_fill_http_update_included_in_status_update(self) -> bool: + return True + + @property + def is_order_fill_http_update_executed_during_websocket_order_event_processing(self) -> bool: + return False + + @property + def expected_partial_fill_price(self) -> Decimal: + return Decimal(10500) + + @property + def expected_partial_fill_amount(self) -> Decimal: + return Decimal("0.5") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + percent_token=self.quote_asset, + flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("30"))] + ) + + @property + def expected_fill_trade_id(self) -> str: + return str(30000) + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + return f"SPOT_{base_token}_{quote_token}" + + def create_exchange_instance(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + + return WooXExchange( + client_config_map=client_config_map, + public_api_key="testAPIKey", + secret_api_key="testSecret", + application_id="applicationId", + trading_pairs=[self.trading_pair], + ) + + def validate_auth_credentials_present(self, request_call: RequestCall): + self._validate_auth_credentials_taking_parameters_from_argument(request_call) + + def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["data"]) + self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) + self.assertEqual(order.trade_type.name.upper(), request_data["side"]) + self.assertEqual(WooXExchange.woo_x_order_type(OrderType.LIMIT), request_data["order_type"]) + self.assertEqual(Decimal("100"), Decimal(request_data["order_quantity"])) + self.assertEqual(Decimal("10000"), Decimal(request_data["order_price"])) + self.assertEqual(order.client_order_id, request_data["client_order_id"]) + + def validate_order_cancelation_request(self, order: InFlightOrder, request_call: RequestCall): + request_data = dict(request_call.kwargs["params"]) + + self.assertEqual( + self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + request_data["symbol"] + ) + + self.assertEqual(order.client_order_id, request_data["client_order_id"]) + + def validate_order_status_request(self, order: InFlightOrder, request_call: RequestCall): + return True + # request_params = request_call.kwargs["params"] + # + # + # logging.info(f"request params: {request_params}") + # logging.info(f"request: {request_call}") + # + # self.assertEqual(order.exchange_order_id, request_params["order_id"]) + + def validate_trades_request(self, order: InFlightOrder, request_call: RequestCall): + return True + + def configure_successful_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + params = { + "client_order_id": order.client_order_id, + 'symbol': self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + } + + query = ('?' + '&'.join([f"{key}={value}" for key, value in sorted(params.items())])) if len( + params) != 0 else '' + + url = web_utils.public_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + query + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_cancelation_request_successful_mock_response(order=order) + + mock_api.delete(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_erroneous_cancelation_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + params = { + "client_order_id": order.client_order_id, + 'symbol': self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset) + } + + query = ('?' + '&'.join([f"{key}={value}" for key, value in sorted(params.items())])) if len( + params) != 0 else '' + + url = web_utils.public_rest_url(CONSTANTS.CANCEL_ORDER_PATH_URL) + query + + response = {"status": "CANCEL_FAILED"} + + mock_api.delete(url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_order_not_found_error_cancelation_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2011, "msg": "Unknown order sent."} + mock_api.delete(regex_url, status=400, body=json.dumps(response), callback=callback) + return url + + def configure_one_successful_one_erroneous_cancel_all_response( + self, + successful_order: InFlightOrder, + erroneous_order: InFlightOrder, + mock_api: aioresponses) -> List[str]: + """ + :return: a list of all configured URLs for the cancelations + """ + all_urls = [] + url = self.configure_successful_cancelation_response(order=successful_order, mock_api=mock_api) + all_urls.append(url) + url = self.configure_erroneous_cancelation_response(order=erroneous_order, mock_api=mock_api) + all_urls.append(url) + return all_urls + + def configure_completely_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.public_rest_url(CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_completely_filled_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_canceled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url(CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_canceled_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_erroneous_http_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(url + r"\?.*") + + mock_api.get(regex_url, status=400, callback=callback) + + return url + + def configure_open_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + """ + :return: the URL configured + """ + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_open_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_http_error_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get(regex_url, status=401, callback=callback, repeat=True) + return url + + def configure_partially_filled_order_status_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + response = self._order_status_request_partially_filled_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def configure_order_not_found_error_order_status_response( + self, order: InFlightOrder, mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> List[str]: + url = web_utils.public_rest_url(CONSTANTS.ORDER_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + response = {"code": -2013, "msg": "Order does not exist."} + mock_api.get(regex_url, body=json.dumps(response), status=400, callback=callback) + return [url] + + def configure_partial_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(url + r"\?.*") + + response = self._order_fills_request_partial_fill_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback) + + return url + + def configure_full_fill_trade_response( + self, + order: InFlightOrder, + mock_api: aioresponses, + callback: Optional[Callable] = lambda *args, **kwargs: None + ) -> str: + url = web_utils.public_rest_url( + path_url=CONSTANTS.GET_ORDER_BY_CLIENT_ORDER_ID_PATH.format(order.client_order_id)) + + regex_url = re.compile(url + r"\?.*") + + response = self._order_fills_request_full_fill_mock_response(order=order) + + mock_api.get(regex_url, body=json.dumps(response), callback=callback, repeat=True) + + return url + + def order_event_for_new_order_websocket_update(self, order: InFlightOrder): + return { + "topic": "executionreport", + "ts": 1686588154387, + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "clientOrderId": int(order.client_order_id), + "orderId": int(order.exchange_order_id), + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "quantity": float(order.amount), + "price": float(order.price), + "tradeId": 0, + "executedPrice": 0.0, + "executedQuantity": 0.0, + "fee": 0.0, + "feeAsset": "BTC", + "totalExecutedQuantity": 0.0, + "status": "NEW", + "reason": "", + "orderTag": "default", + "totalFee": 0.0, + "visible": 0.001, + "timestamp": 1686588154387, + "reduceOnly": False, + "maker": False + } + } + + def order_event_for_canceled_order_websocket_update(self, order: InFlightOrder): + return { + "topic": "executionreport", + "ts": 1686588270140, + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "clientOrderId": int(order.client_order_id), + "orderId": int(order.exchange_order_id), + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "quantity": float(order.amount), + "price": float(order.price), + "tradeId": 0, + "executedPrice": 0.0, + "executedQuantity": 0.0, + "fee": 0.0, + "feeAsset": "BTC", + "totalExecutedQuantity": 0.0, + "status": "CANCELLED", + "reason": "", + "orderTag": "default", + "totalFee": 0.0, + "visible": 0.001, + "timestamp": 1686588270140, + "reduceOnly": False, + "maker": False + } + } + + def order_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return { + "topic": "executionreport", + "ts": 1686588450683, + "data": { + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "clientOrderId": int(order.client_order_id), + "orderId": 199270655, + "type": order.order_type.name.upper(), + "side": order.trade_type.name.upper(), + "quantity": float(order.amount), + "price": float(order.price), + "tradeId": 250106703, + "executedPrice": float(order.price), + "executedQuantity": float(order.amount), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "feeAsset": self.expected_fill_fee.flat_fees[0].token, + "totalExecutedQuantity": float(order.amount), + "avgPrice": float(order.price), + "status": "FILLED", + "reason": "", + "orderTag": "default", + "totalFee": 0.00000030, + "visible": 0.001, + "timestamp": 1686588450683, + "reduceOnly": False, + "maker": True + } + } + + def trade_event_for_full_fill_websocket_update(self, order: InFlightOrder): + return None + + @patch("secrets.randbelow") + def test_client_order_id_on_order(self, mocked_secret): + mocked_secret.return_value = 10 + + result = self.exchange.buy( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + + expected_client_order_id = str(secrets.randbelow(9223372036854775807)) + + logging.error(expected_client_order_id) + + self.assertEqual(result, expected_client_order_id) + + mocked_secret.return_value = 20 + + expected_client_order_id = str(secrets.randbelow(9223372036854775807)) + + result = self.exchange.sell( + trading_pair=self.trading_pair, + amount=Decimal("1"), + order_type=OrderType.LIMIT, + price=Decimal("2"), + ) + + self.assertEqual(result, expected_client_order_id) + + @aioresponses() + def test_cancel_order_not_found_in_the_exchange(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during cancellation (check _is_order_not_found_during_cancelation_error) + pass + + @aioresponses() + def test_lost_order_removed_if_not_found_during_order_status_update(self, mock_api): + # Disabling this test because the connector has not been updated yet to validate + # order not found during status update (check _is_order_not_found_during_status_update_error) + pass + + @aioresponses() + def test_check_network_failure(self, mock_api): + # Disabling this test because Woo X does not have an endpoint to check health. + pass + + @aioresponses() + def test_check_network_raises_cancel_exception(self, mock_api): + # Disabling this test because Woo X does not have an endpoint to check health. + pass + + @aioresponses() + def test_check_network_success(self, mock_api): + # Disabling this test because Woo X does not have an endpoint to check health. + pass + + @aioresponses() + def test_update_order_status_when_filled_correctly_processed_even_when_trade_fill_update_fails(self, mock_api): + pass + + def _validate_auth_credentials_taking_parameters_from_argument(self, request_call: RequestCall): + headers = request_call.kwargs["headers"] + + self.assertIn("x-api-key", headers) + self.assertIn("x-api-signature", headers) + self.assertIn("x-api-timestamp", headers) + + self.assertEqual("testAPIKey", headers["x-api-key"]) + + def _order_cancelation_request_successful_mock_response(self, order: InFlightOrder) -> Any: + return { + "success": True, + "status": "CANCEL_SENT" + } + + def _order_status_request_completely_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "success": True, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "FILLED", + "side": "BUY", + "created_time": "1686558570.495", + "order_id": int(order.exchange_order_id), + "order_tag": "default", + "price": float(order.price), + "type": "LIMIT", + "quantity": float(order.amount), + "amount": None, + "visible": float(order.amount), + "executed": float(order.amount), + "total_fee": 3e-07, + "fee_asset": "BTC", + "client_order_id": int(order.client_order_id), + "reduce_only": False, + "realized_pnl": None, + "average_executed_price": 10500, + "Transactions": [ + { + "id": self.expected_fill_trade_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "order_id": int(order.exchange_order_id), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "side": "BUY", + "executed_timestamp": "1686558583.434", + "executed_price": float(order.price), + "executed_quantity": float(order.amount), + "fee_asset": self.expected_fill_fee.flat_fees[0].token, + "is_maker": 1, + "realized_pnl": None + } + ] + } + + def _order_status_request_canceled_mock_response(self, order: InFlightOrder) -> Any: + return { + "success": True, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "CANCELLED", + "side": order.trade_type.name.upper(), + "created_time": "1686558863.782", + "order_id": int(order.exchange_order_id), + "order_tag": "default", + "price": float(order.price), + "type": order.order_type.name.upper(), + "quantity": float(order.amount), + "amount": None, + "visible": float(order.amount), + "executed": 0, + "total_fee": 0, + "fee_asset": "BTC", + "client_order_id": int(order.client_order_id), + "reduce_only": False, + "realized_pnl": None, + "average_executed_price": None, + "Transactions": [] + } + + def _order_status_request_open_mock_response(self, order: InFlightOrder) -> Any: + return { + "success": True, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "NEW", + "side": order.trade_type.name.upper(), + "created_time": "1686559699.983", + "order_id": int(order.exchange_order_id), + "order_tag": "default", + "price": float(order.price), + "type": order.order_type.name.upper(), + "quantity": float(order.amount), + "amount": None, + "visible": float(order.amount), + "executed": 0, + "total_fee": 0, + "fee_asset": "BTC", + "client_order_id": int(order.client_order_id), + "reduce_only": False, + "realized_pnl": None, + "average_executed_price": None, + "Transactions": [] + } + + def _order_status_request_partially_filled_mock_response(self, order: InFlightOrder) -> Any: + return { + "success": True, + "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), + "status": "PARTIAL_FILLED", + "side": "BUY", + "created_time": "1686558570.495", + "order_id": order.exchange_order_id, + "order_tag": "default", + "price": float(order.price), + "type": "LIMIT", + "quantity": float(order.amount), + "amount": None, + "visible": float(order.amount), + "executed": float(order.amount), + "total_fee": 3e-07, + "fee_asset": "BTC", + "client_order_id": order.client_order_id, + "reduce_only": False, + "realized_pnl": None, + "average_executed_price": 10500, + "Transactions": [ + { + "id": self.expected_fill_trade_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "order_id": int(order.exchange_order_id), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "side": "BUY", + "executed_timestamp": "1686558583.434", + "executed_price": float(self.expected_partial_fill_price), + "executed_quantity": float(self.expected_partial_fill_amount), + "fee_asset": self.expected_fill_fee.flat_fees[0].token, + "is_maker": 1, + "realized_pnl": None + } + ] + } + + def _order_fills_request_partial_fill_mock_response(self, order: InFlightOrder): + return { + "success": True, + "meta": { + "total": 65, + "records_per_page": 100, + "current_page": 1 + }, + "rows": [ + { + "id": self.expected_fill_trade_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "side": "BUY", + "executed_timestamp": "1686585723.908", + "order_id": int(order.exchange_order_id), + "order_tag": "default", + "executed_price": float(self.expected_partial_fill_price), + "executed_quantity": float(self.expected_partial_fill_amount), + "fee_asset": self.expected_fill_fee.flat_fees[0].token, + "is_maker": 0, + "realized_pnl": None + } + ] + } + + def _order_fills_request_full_fill_mock_response(self, order: InFlightOrder): + return { + "success": True, + "meta": { + "total": 65, + "records_per_page": 100, + "current_page": 1 + }, + "rows": [ + { + "id": self.expected_fill_trade_id, + "symbol": self.exchange_symbol_for_tokens(order.base_asset, order.quote_asset), + "fee": float(self.expected_fill_fee.flat_fees[0].amount), + "side": "BUY", + "executed_timestamp": "1686585723.908", + "order_id": int(order.exchange_order_id), + "order_tag": "default", + "executed_price": float(order.price), + "executed_quantity": float(order.amount), + "fee_asset": self.expected_fill_fee.flat_fees[0].token, + "is_maker": 0, + "realized_pnl": None + } + ] + } diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_order_book.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_order_book.py new file mode 100644 index 0000000..0d61e32 --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_order_book.py @@ -0,0 +1,105 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.woo_x.woo_x_order_book import WooXOrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class WooXOrderBookTests(TestCase): + + def test_snapshot_message_from_exchange(self): + snapshot_message = WooXOrderBook.snapshot_message_from_exchange( + msg={ + "success": True, + "asks": [ + { + "price": 10669.4, + "quantity": 1.56263218 + }, + ], + "bids": [ + { + "price": 10669.3, + "quantity": 0.88159988 + }, + ], + "timestamp": 1564710591905 + }, + timestamp=1564710591905, + metadata={"trading_pair": "COINALPHA-HBOT"} + ) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_message.type) + self.assertEqual(1564710591905, snapshot_message.timestamp) + self.assertEqual(1564710591905, snapshot_message.update_id) + self.assertEqual(-1, snapshot_message.trade_id) + self.assertEqual(1, len(snapshot_message.bids)) + self.assertEqual(10669.3, snapshot_message.bids[0].price) + self.assertEqual(0.88159988, snapshot_message.bids[0].amount) + self.assertEqual(1564710591905, snapshot_message.bids[0].update_id) + self.assertEqual(1, len(snapshot_message.asks)) + self.assertEqual(10669.4, snapshot_message.asks[0].price) + self.assertEqual(1.56263218, snapshot_message.asks[0].amount) + self.assertEqual(1564710591905, snapshot_message.asks[0].update_id) + + def test_diff_message_from_exchange(self): + diff_msg = WooXOrderBook.diff_message_from_exchange( + msg={ + "topic": "SPOT_BTC_USDT@orderbookupdate", + "ts": 1618826337580, + "data": { + "symbol": "SPOT_BTC_USDT", + "prevTs": 1618826337380, + "asks": [ + [ + 56749.15, + 3.92864 + ], + ], + "bids": [ + [ + 56745.2, + 1.03895025 + ], + ] + } + }, + metadata={"trading_pair": "BTC-USDT"} + ) + + self.assertEqual(1618826337580, diff_msg.timestamp) + self.assertEqual(1618826337580, diff_msg.update_id) + self.assertEqual(1618826337580, diff_msg.first_update_id) + self.assertEqual(-1, diff_msg.trade_id) + self.assertEqual(1, len(diff_msg.bids)) + self.assertEqual(56745.2, diff_msg.bids[0].price) + self.assertEqual(1.03895025, diff_msg.bids[0].amount) + self.assertEqual(1618826337580, diff_msg.bids[0].update_id) + self.assertEqual(1, len(diff_msg.asks)) + self.assertEqual(56749.15, diff_msg.asks[0].price) + self.assertEqual(3.92864, diff_msg.asks[0].amount) + self.assertEqual(1618826337580, diff_msg.asks[0].update_id) + + def test_trade_message_from_exchange(self): + trade_update = { + "topic": "SPOT_ADA_USDT@trade", + "ts": 1618820361552, + "data": { + "symbol": "SPOT_ADA_USDT", + "price": 1.27988, + "size": 300, + "side": "BUY", + "source": 0 + } + } + + trade_message = WooXOrderBook.trade_message_from_exchange( + msg=trade_update, + metadata={"trading_pair": "ADA-USDT"} + ) + + self.assertEqual("ADA-USDT", trade_message.trading_pair) + self.assertEqual(OrderBookMessageType.TRADE, trade_message.type) + self.assertEqual(1618820361.552, trade_message.timestamp) + self.assertEqual(-1, trade_message.update_id) + self.assertEqual(-1, trade_message.first_update_id) + self.assertEqual(1618820361552, trade_message.trade_id) diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_utils.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_utils.py new file mode 100644 index 0000000..b658fbf --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_utils.py @@ -0,0 +1,40 @@ +import unittest + +from hummingbot.connector.exchange.woo_x import woo_x_utils as utils + + +class WooXUtilTestCases(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hb_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def test_is_exchange_information_valid(self): + invalid_info_1 = { + "symbol": "MARGIN_BTC_USDT", + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_1)) + + invalid_info_2 = { + "symbol": "PERP_BTC_ETH", + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_2)) + + invalid_info_3 = { + "symbol": "BTC-USDT", + } + + self.assertFalse(utils.is_exchange_information_valid(invalid_info_3)) + + valid_info_4 = { + "symbol": f"SPOT_{self.base_asset}_{self.quote_asset}", + } + + self.assertTrue(utils.is_exchange_information_valid(valid_info_4)) diff --git a/test/hummingbot/connector/exchange/woo_x/test_woo_x_web_utils.py b/test/hummingbot/connector/exchange/woo_x/test_woo_x_web_utils.py new file mode 100644 index 0000000..4f06717 --- /dev/null +++ b/test/hummingbot/connector/exchange/woo_x/test_woo_x_web_utils.py @@ -0,0 +1,11 @@ +from unittest import TestCase + +from hummingbot.connector.exchange.woo_x import woo_x_constants as CONSTANTS, woo_x_web_utils as web_utils + + +class WebUtilsTests(TestCase): + def test_rest_url(self): + url = web_utils.public_rest_url(path_url=CONSTANTS.MARKET_TRADES_PATH, domain=CONSTANTS.DEFAULT_DOMAIN) + self.assertEqual('https://api.woo.org/v1/public/market_trades', url) + url = web_utils.public_rest_url(path_url=CONSTANTS.MARKET_TRADES_PATH, domain='woo_x_testnet') + self.assertEqual('https://api.staging.woo.org/v1/public/market_trades', url) diff --git a/test/hummingbot/connector/gateway/__init__.py b/test/hummingbot/connector/gateway/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/amm/__init__.py b/test/hummingbot/connector/gateway/amm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/amm/debug_gateway_cancel.py b/test/hummingbot/connector/gateway/amm/debug_gateway_cancel.py new file mode 100755 index 0000000..1b53cc0 --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/debug_gateway_cancel.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +import asyncio +import time +from contextlib import asynccontextmanager +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpRecorder +from typing import Generator, Optional + +from bin import path_util # noqa: F401 +from hummingbot.client.config.config_helpers import read_system_configs_from_yml +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + MarketEvent, + OrderCancelledEvent, + TokenApprovalCancelledEvent, + TokenApprovalEvent, + TradeType, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +WALLET_ADDRESS = "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92" +NETWORK = "ropsten" +TRADING_PAIR = "WETH-DAI" +MAX_FEE_PER_GAS = 2000 +MAX_PRIORITY_FEE_PER_GAS = 200 +gateway_http_client: GatewayHttpClient = GatewayHttpClient.get_instance() + + +class GatewayCancelDataCollector: + fixture_path: str = realpath(join(__file__, "../fixtures/gateway_cancel_fixture.db")) + + def __init__(self): + self._clock: Clock = Clock(ClockMode.REALTIME) + self._connector: GatewayEVMAMM = GatewayEVMAMM( + "uniswap", + "ethereum", + NETWORK, + WALLET_ADDRESS, + trading_pairs=[TRADING_PAIR], + trading_required=True + ) + self._clock.add_iterator(self._connector) + self._clock_task: Optional[asyncio.Task] = None + self._http_recorder: HttpRecorder = HttpRecorder(self.fixture_path) + + async def main(self): + await self.load_configs() + with self._clock: + with self._http_recorder.patch_aiohttp_client(): + await self.wait_til_ready() + await self.collect_testing_data() + + @staticmethod + async def load_configs(): + await read_system_configs_from_yml() + gateway_http_client.base_url = "https://localhost:5000" + + @asynccontextmanager + async def run_clock(self) -> Generator[Clock, None, None]: + self._clock_task = safe_ensure_future(self._clock.run()) + try: + yield self._clock + finally: + self._clock_task.cancel() + try: + await self._clock_task + except asyncio.CancelledError: + pass + self._clock_task = None + + async def wait_til_ready(self): + print("Waiting til ready...\t\t", end="", flush=True) + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if self._connector.ready: + break + else: + await self._clock.run_til(next_iteration + 0.1) + await asyncio.sleep(1.0) + print("done") + + async def collect_testing_data(self): + await self.collect_cancel_order() + await self.collect_cancel_approval() + + async def collect_cancel_order(self): + print("Creating and then canceling Uniswap order...\t\t", end="", flush=True) + connector: GatewayEVMAMM = self._connector + event_logger: EventLogger = EventLogger() + connector.add_listener(MarketEvent.OrderCancelled, event_logger) + try: + async with self.run_clock(): + amount: Decimal = Decimal("0.001") + buy_price: Decimal = await connector.get_order_price(TRADING_PAIR, True, amount) * Decimal("1.02") + sell_price: Decimal = await connector.get_order_price(TRADING_PAIR, False, amount) * Decimal("0.98") + await connector._create_order( + TradeType.BUY, + GatewayEVMAMM.create_market_order_id(TradeType.BUY, TRADING_PAIR), + TRADING_PAIR, + amount, + buy_price, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + await connector._create_order( + TradeType.SELL, + GatewayEVMAMM.create_market_order_id(TradeType.SELL, TRADING_PAIR), + TRADING_PAIR, + amount, + sell_price, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + for in_flight_order in connector._in_flight_orders.values(): + in_flight_order._creation_timestamp = connector.current_timestamp - 86400 + safe_ensure_future(connector.cancel_outdated_orders(600)) + while len(event_logger.event_log) < 2: + await event_logger.wait_for(OrderCancelledEvent) + finally: + connector.remove_listener(MarketEvent.OrderCancelled, event_logger) + print("done") + + async def collect_cancel_approval(self): + print("Creating and then canceling token approval...\t\t", end="", flush=True) + connector: GatewayEVMAMM = self._connector + event_logger: EventLogger = EventLogger() + connector.add_listener(TokenApprovalEvent.ApprovalCancelled, event_logger) + try: + async with self.run_clock(): + tracked_order_1: GatewayInFlightOrder = await connector.approve_token( + "DAI", + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + tracked_order_2: GatewayInFlightOrder = await connector.approve_token( + "WETH", + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + tracked_order_1._creation_timestamp = connector.current_timestamp - 86400 + tracked_order_2._creation_timestamp = connector.current_timestamp - 86400 + safe_ensure_future(connector.cancel_outdated_orders(600)) + while len(event_logger.event_log) < 2: + await event_logger.wait_for(TokenApprovalCancelledEvent) + finally: + connector.remove_listener(TokenApprovalEvent.ApprovalCancelled, event_logger) + print("done") + + +if __name__ == "__main__": + data_collector: GatewayCancelDataCollector = GatewayCancelDataCollector() + try: + asyncio.run(data_collector.main()) + except KeyboardInterrupt: + pass diff --git a/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm.py b/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm.py new file mode 100755 index 0000000..96b1321 --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python + +""" +Fixture data collection script for GatewayEVMAMM unit test cases. + +This is included for record only - if you need to run this to collect another batch of fixture data, you'll need to +change the wallet address and transaction hashes. +""" + +import asyncio +import time +from contextlib import asynccontextmanager +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpRecorder +from typing import Generator, List, Optional + +from bin import path_util # noqa: F401 +from hummingbot.client.config.config_helpers import read_system_configs_from_yml +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCreatedEvent, + MarketEvent, + OrderFilledEvent, + OrderType, + SellOrderCreatedEvent, + TradeType, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +s_decimal_0 = Decimal(0) +gateway_http_client: GatewayHttpClient = GatewayHttpClient.get_instance() + + +class GatewayEVMAMMDataCollector: + fixture_path: str = realpath(join(__file__, "../fixtures/gateway_evm_amm_fixture.db")) + + def __init__(self): + self._clock: Clock = Clock(ClockMode.REALTIME) + self._connector: GatewayEVMAMM = GatewayEVMAMM( + "uniswap", + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + trading_pairs=["DAI-WETH"], + trading_required=True + ) + self._clock.add_iterator(self._connector) + self._clock_task: Optional[asyncio.Task] = None + self._http_recorder: HttpRecorder = HttpRecorder(self.fixture_path) + + async def main(self): + await self.load_configs() + with self._clock: + with self._http_recorder.patch_aiohttp_client(): + await self.wait_til_ready() + await self.collect_testing_data() + + @staticmethod + async def load_configs(): + await read_system_configs_from_yml() + gateway_http_client.base_url = "https://localhost:5000" + + async def wait_til_ready(self): + print("Waiting til ready...\t\t", end="", flush=True) + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if self._connector.ready: + break + else: + await self._clock.run_til(next_iteration + 0.1) + await asyncio.sleep(1.0) + print("done") + + @asynccontextmanager + async def run_clock(self) -> Generator[Clock, None, None]: + self._clock_task = safe_ensure_future(self._clock.run()) + try: + yield self._clock + finally: + self._clock_task.cancel() + try: + await self._clock_task + except asyncio.CancelledError: + pass + self._clock_task = None + + async def collect_testing_data(self): + await self.collect_update_balances() + await self.collect_get_allowances() + await self.collect_get_chain_info() + await self.collect_approval_status() + await self.collect_order_status() + await self.collect_get_price() + await self.collect_approve_token() + await self.collect_buy_order() + await self.collect_sell_order() + + async def collect_update_balances(self): + print("Updating balances...\t\t", end="", flush=True) + await self._connector.update_balances(on_interval=False) + print("done") + + async def collect_get_allowances(self): + print("Getting token allowances...\t\t", end="", flush=True) + await self._connector.get_allowances() + print("done") + + async def collect_get_chain_info(self): + print("Getting chain info...\t\t", end="", flush=True) + await self._connector.get_chain_info() + print("done") + + async def collect_approval_status(self): + def create_approval_record(token_symbol: str, tx_hash: str) -> GatewayInFlightOrder: + return GatewayInFlightOrder( + client_order_id=self._connector.create_approval_order_id(token_symbol), + exchange_order_id=tx_hash, + trading_pair=token_symbol, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=s_decimal_0, + amount=s_decimal_0, + gas_price=s_decimal_0, + creation_timestamp=self._connector.current_timestamp + ) + print("Getting token approval status...\t\t", end="", flush=True) + successful_records: List[GatewayInFlightOrder] = [ + create_approval_record( + "WETH", + "0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff9" # noqa: mock + ), + create_approval_record( + "DAI", + "0x4f81aa904fcb16a8938c0e0a76bf848df32ce6378e9e0060f7afc4b2955de405" # noqa: mock + ), + ] + await self._connector.update_token_approval_status(successful_records) + fake_records: List[GatewayInFlightOrder] = [ + create_approval_record( + "WETH", + "0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff8" # noqa: mock + ), + create_approval_record( + "DAI", + "0x4f81aa904fcb16a8938c0e0a76bf848df32ce6378e9e0060f7afc4b2955de404" # noqa: mock + ), + ] + await self._connector.update_token_approval_status(fake_records) + print("done") + + async def collect_order_status(self): + def create_order_record( + trading_pair: str, + trade_type: TradeType, + tx_hash: str, + price: Decimal, + amount: Decimal, + gas_price: Decimal) -> GatewayInFlightOrder: + return GatewayInFlightOrder( + client_order_id=self._connector.create_market_order_id(trade_type, trading_pair), + exchange_order_id=tx_hash, + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=trade_type, + price=price, + amount=amount, + gas_price=gas_price, + creation_timestamp=self._connector.current_timestamp + ) + print("Getting uniswap order status...\t\t", end="", flush=True) + successful_records: List[GatewayInFlightOrder] = [ + create_order_record( + "DAI-WETH", + TradeType.BUY, + "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + Decimal("0.00267589"), + Decimal("1000"), + Decimal("29") + ) + ] + await self._connector.update_order_status(successful_records) + fake_records: List[GatewayInFlightOrder] = [ + create_order_record( + "DAI-WETH", + TradeType.BUY, + "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e17", # noqa: mock + Decimal("0.00267589"), + Decimal("1000"), + Decimal("29") + ) + ] + await self._connector.update_order_status(fake_records) + print("done") + + async def collect_get_price(self): + print("Getting current prices...\t\t", end="", flush=True) + await self._connector.get_quote_price("DAI-WETH", True, Decimal(1000)) + await self._connector.get_quote_price("DAI-WETH", False, Decimal(1000)) + print("done") + + async def collect_approve_token(self): + print("Approving tokens...") + weth_in_flight_order: GatewayInFlightOrder = await self._connector.approve_token("WETH") + dai_in_flight_order: GatewayInFlightOrder = await self._connector.approve_token("DAI") + print(f"\tSent WETH approval with txHash: {weth_in_flight_order.exchange_order_id}") + print(f"\tSent DAI approval with txHash: {dai_in_flight_order.exchange_order_id}") + while len(self._connector.approval_orders) > 0: + await asyncio.sleep(5) + await self._connector.update_token_approval_status(self._connector.approval_orders) + print("\tdone") + + async def collect_buy_order(self): + print("Buying DAI tokens...") + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.BuyOrderCreated, event_logger) + self._connector.add_listener(MarketEvent.OrderFilled, event_logger) + async with self.run_clock(): + try: + price: Decimal = await self._connector.get_quote_price("DAI-WETH", True, Decimal(100)) + price *= Decimal("1.005") + self._connector.buy("DAI-WETH", Decimal(100), OrderType.LIMIT, price) + buy_order_event: BuyOrderCreatedEvent = await event_logger.wait_for(BuyOrderCreatedEvent) + print(f"\tSent buy order with txHash: {buy_order_event.exchange_order_id}") + await event_logger.wait_for(OrderFilledEvent, timeout_seconds=600) + finally: + self._connector.remove_listener(MarketEvent.BuyOrderCreated, event_logger) + self._connector.remove_listener(MarketEvent.OrderFilled, event_logger) + print("\tdone") + + async def collect_sell_order(self): + print("Selling DAI tokens...") + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.SellOrderCreated, event_logger) + self._connector.add_listener(MarketEvent.OrderFilled, event_logger) + async with self.run_clock(): + try: + price: Decimal = await self._connector.get_quote_price("DAI-WETH", False, Decimal(100)) + price *= Decimal("0.995") + self._connector.sell("DAI-WETH", Decimal(100), OrderType.LIMIT, price) + sell_order_event: SellOrderCreatedEvent = await event_logger.wait_for(SellOrderCreatedEvent) + print(f"\tSent sell order with txHash: {sell_order_event.exchange_order_id}") + await event_logger.wait_for(OrderFilledEvent, timeout_seconds=600) + finally: + self._connector.remove_listener(MarketEvent.SellOrderCreated, event_logger) + self._connector.remove_listener(MarketEvent.OrderFilled, event_logger) + print("\tdone") + + +if __name__ == "__main__": + data_collector: GatewayEVMAMMDataCollector = GatewayEVMAMMDataCollector() + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + try: + ev_loop.run_until_complete(data_collector.main()) + except KeyboardInterrupt: + pass diff --git a/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm_lp.py b/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm_lp.py new file mode 100755 index 0000000..da3c80c --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/debug_gateway_evm_amm_lp.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python + +""" +Fixture data collection script for GatewayEVMAMMLP unit test cases. + +This is included for record only - if you need to run this to collect another batch of fixture data, you'll need to +change the wallet address and transaction hashes. +""" + +import asyncio +import time +from contextlib import asynccontextmanager +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpRecorder +from typing import Generator, List, Optional + +from bin import path_util # noqa: F401 +from hummingbot.client.config.config_helpers import read_system_configs_from_yml +from hummingbot.connector.gateway.amm_lp.gateway_evm_amm_lp import GatewayEVMAMMLP +from hummingbot.connector.gateway.amm_lp.gateway_in_flight_lp_order import GatewayInFlightLPOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + LPType, + MarketEvent, + RangePositionFeeCollectedEvent, + RangePositionLiquidityAddedEvent, + RangePositionLiquidityRemovedEvent, + RangePositionUpdateEvent, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +s_decimal_0 = Decimal(0) +gateway_http_client: GatewayHttpClient = GatewayHttpClient.get_instance() + + +class GatewayEVMAMMLPDataCollector: + fixture_path: str = realpath(join(__file__, "../fixtures/gateway_evm_amm_lp_fixture.db")) + + def __init__(self): + self._clock: Clock = Clock(ClockMode.REALTIME) + self._connector: GatewayEVMAMMLP = GatewayEVMAMMLP( + "uniswapLP", + "ethereum", + "kovan", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + trading_pairs=["COIN1-COIN3"], + trading_required=True + ) + self._clock.add_iterator(self._connector) + self._clock_task: Optional[asyncio.Task] = None + self._http_recorder: HttpRecorder = HttpRecorder(self.fixture_path) + + async def main(self): + await self.load_configs() + with self._clock: + with self._http_recorder.patch_aiohttp_client(): + await self.wait_til_ready() + await self.collect_testing_data() + + @staticmethod + async def load_configs(): + await read_system_configs_from_yml() + gateway_http_client.base_url = "https://localhost:5000" + + async def wait_til_ready(self): + print("Waiting til ready...\t\t", end="", flush=True) + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if self._connector.ready: + break + else: + await self._clock.run_til(next_iteration + 0.1) + await asyncio.sleep(1.0) + print("done") + + @asynccontextmanager + async def run_clock(self) -> Generator[Clock, None, None]: + self._clock_task = safe_ensure_future(self._clock.run()) + try: + yield self._clock + finally: + self._clock_task.cancel() + try: + await self._clock_task + except asyncio.CancelledError: + pass + self._clock_task = None + + async def collect_testing_data(self): + await self.collect_update_balances() + await self.collect_get_allowances() + await self.collect_get_chain_info() + await self.collect_approval_status() + await self.collect_get_price() + await self.collect_approve_token() + await self.collect_add_liquidity() + await self.collect_fee_collect() + await self.collect_remove_liquidity() + + async def collect_update_balances(self): + print("Updating balances...\t\t", end="", flush=True) + await self._connector.update_balances(on_interval=False) + print("done") + + async def collect_get_allowances(self): + print("Getting token allowances...\t\t", end="", flush=True) + await self._connector.get_allowances() + print("done") + + async def collect_get_chain_info(self): + print("Getting chain info...\t\t", end="", flush=True) + await self._connector.get_chain_info() + print("done") + + async def collect_approval_status(self): + def create_approval_record(token_symbol: str, tx_hash: str) -> GatewayInFlightLPOrder: + return GatewayInFlightLPOrder( + client_order_id=self._connector.create_approval_order_id(token_symbol), + exchange_order_id=tx_hash, + trading_pair=token_symbol, + lp_type = LPType.ADD, + lower_price = s_decimal_0, + upper_price = s_decimal_0, + amount_0 = s_decimal_0, + amount_1 = s_decimal_0, + token_id = 0, + gas_price=s_decimal_0, + creation_timestamp=self._connector.current_timestamp + ) + print("Getting token approval status...\t\t", end="", flush=True) + successful_records: List[GatewayInFlightLPOrder] = [ + create_approval_record( + "COIN1", + "0x273a720fdc92554c47f409f4f74d3c262937451ccdbaddfd8d0185a9e3c64dd2" # noqa: mock + ), + create_approval_record( + "COIN3", + "0x27d7a7156bd0afc73092602da67774aa3319adbc72213122d65480e482ce0a8b" # noqa: mock + ), + ] + await self._connector.update_token_approval_status(successful_records) + fake_records: List[GatewayInFlightLPOrder] = [ + create_approval_record( + "COIN1", + "0x273a720fdc92554c47f409f4f74d3c262937451ccdbaddfd8d0185a9e3c64dd1" # noqa: mock + ), + create_approval_record( + "COIN3", + "0x27d7a7156bd0afc73092602da67774aa3319adbc72213122d65480e482ce0a8a" # noqa: mock + ), + ] + await self._connector.update_token_approval_status(fake_records) + print("done") + + async def collect_get_price(self): + print("Getting current pool price...\t\t", end="", flush=True) + await self._connector.get_price("COIN1-COIN3", "LOW") + print("done") + + async def collect_approve_token(self): + print("Approving tokens...") + coin1_in_flight_order: GatewayInFlightLPOrder = await self._connector.approve_token("COIN1") + coin3_in_flight_order: GatewayInFlightLPOrder = await self._connector.approve_token("COIN3") + print(f"\tSent COIN1 approval with txHash: {coin1_in_flight_order.exchange_order_id}") + print(f"\tSent COIN3 approval with txHash: {coin3_in_flight_order.exchange_order_id}") + while len(self._connector.approval_orders) > 0: + await asyncio.sleep(5) + await self._connector.update_token_approval_status(self._connector.approval_orders) + print("\tdone") + + async def collect_add_liquidity(self): + print("Adding liquidity in LOW pool...") + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionLiquidityAdded, event_logger) + async with self.run_clock(): + try: + self._connector.add_liquidity("COIN1-COIN3", Decimal("1"), Decimal("1"), Decimal("1"), Decimal("5"), "LOW") + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for(RangePositionUpdateEvent) + print(f"\tAdd liquidity sent with txHash: {pos_update_event.exchange_order_id}") + await event_logger.wait_for(RangePositionLiquidityAddedEvent, timeout_seconds=600) + finally: + self._connector.remove_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.remove_listener(MarketEvent.RangePositionLiquidityAdded, event_logger) + print("\tdone") + + async def collect_remove_liquidity(self): + print("Removing liquidity in LOW pool...") + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionLiquidityRemoved, event_logger) + async with self.run_clock(): + try: + self._connector.remove_liquidity("COIN1-COIN3", 11840) + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for(RangePositionUpdateEvent) + print(f"\tRemove liquidity sent with txHash: {pos_update_event.exchange_order_id}") + await event_logger.wait_for(RangePositionLiquidityRemovedEvent, timeout_seconds=600) + finally: + self._connector.remove_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.remove_listener(MarketEvent.RangePositionLiquidityRemoved, event_logger) + print("\tdone") + + async def collect_fee_collect(self): + print("Collect earned fees...") + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionFeeCollected, event_logger) + async with self.run_clock(): + try: + self._connector.collect_fees("COIN1-COIN3", 11840) + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for(RangePositionUpdateEvent) + print(f"\tCollect fees request sent with txHash: {pos_update_event.exchange_order_id}") + await event_logger.wait_for(RangePositionFeeCollectedEvent, timeout_seconds=600) + finally: + self._connector.remove_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.remove_listener(MarketEvent.RangePositionFeeCollected, event_logger) + print("\tdone") + + +if __name__ == "__main__": + data_collector: GatewayEVMAMMLPDataCollector = GatewayEVMAMMLPDataCollector() + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + try: + ev_loop.run_until_complete(data_collector.main()) + except KeyboardInterrupt: + pass diff --git a/test/hummingbot/connector/gateway/amm/fixtures/gateway_cancel_fixture.db b/test/hummingbot/connector/gateway/amm/fixtures/gateway_cancel_fixture.db new file mode 100644 index 0000000..b3bcd26 Binary files /dev/null and b/test/hummingbot/connector/gateway/amm/fixtures/gateway_cancel_fixture.db differ diff --git a/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_fixture.db b/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_fixture.db new file mode 100644 index 0000000..72bea89 Binary files /dev/null and b/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_fixture.db differ diff --git a/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_lp_fixture.db b/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_lp_fixture.db new file mode 100644 index 0000000..71cdd32 Binary files /dev/null and b/test/hummingbot/connector/gateway/amm/fixtures/gateway_evm_amm_lp_fixture.db differ diff --git a/test/hummingbot/connector/gateway/amm/test_gateway_cancel.py b/test/hummingbot/connector/gateway/amm/test_gateway_cancel.py new file mode 100644 index 0000000..5722831 --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/test_gateway_cancel.py @@ -0,0 +1,222 @@ +import asyncio +import time +import unittest +from contextlib import ExitStack, asynccontextmanager +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Generator, Optional, Set +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test +from async_timeout import timeout + +from bin import path_util # noqa: F401 +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + MarketEvent, + OrderCancelledEvent, + TokenApprovalCancelledEvent, + TokenApprovalEvent, + TradeType, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +WALLET_ADDRESS = "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92" +NETWORK = "ropsten" +TRADING_PAIR = "WETH-DAI" +MAX_FEE_PER_GAS = 2000 +MAX_PRIORITY_FEE_PER_GAS = 200 + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + +class GatewayCancelUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + _clock: Clock + _clock_task: Optional[asyncio.Task] + _connector: GatewayEVMAMM + + @classmethod + def setUpClass(cls) -> None: + cls._db_path = realpath(join(__file__, "../fixtures/gateway_cancel_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._clock: Clock = Clock(ClockMode.REALTIME) + cls._client_config_map = ClientConfigAdapter(ClientConfigMap()) + cls._connector: GatewayEVMAMM = GatewayEVMAMM( + client_config_map=cls._client_config_map, + connector_name="uniswap", + chain="ethereum", + network=NETWORK, + address=WALLET_ADDRESS, + trading_pairs=[TRADING_PAIR], + trading_required=True + ) + cls._connector._amount_quantum_dict = {"WETH": Decimal(str(1e-15)), "DAI": Decimal(str(1e-15))} + cls._clock.add_iterator(cls._connector) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch( + "hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", + return_value=ClientSession(), + ) + ) + cls._patch_stack.enter_context(cls._clock) + GatewayHttpClient.get_instance().base_url = "https://localhost:5000" + ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + + def tearDown(self) -> None: + self._connector._order_tracker.all_orders.clear() + + @classmethod + async def wait_til_ready(cls): + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if cls._connector.ready: + break + else: + await cls._clock.run_til(next_iteration + 0.1) + + @asynccontextmanager + async def run_clock(self) -> Generator[Clock, None, None]: + self._clock_task = safe_ensure_future(self._clock.run()) + try: + yield self._clock + finally: + self._clock_task.cancel() + try: + await self._clock_task + except asyncio.CancelledError: + pass + self._clock_task = None + + @async_test(loop=ev_loop) + async def test_cancel_order(self): + amount: Decimal = Decimal("0.001") + connector: GatewayEVMAMM = self._connector + event_logger: EventLogger = EventLogger() + connector.add_listener(MarketEvent.OrderCancelled, event_logger) + + expected_order_tx_hash_set: Set[str] = { + "0x08f410a0d5cd42446fef3faffc14251ccfa3e4388d83f75f3730c05bcba1c5ab", # noqa: mock + "0xe09a9d9593e7ca19205edd3a4ddd1ab1f348dad3ea922ad0ef8efc5c00e3abfb", # noqa: mock + } + expected_cancel_tx_hash_set: Set[str] = { + "0xcd03a16f309a01239b8f7c036865f1c413768f2809fd0355400e7595a3860988", # noqa: mock + "0x044eb2c220ec160e157949b0f18f7ba5e36c6e7b115a36e976f92b469f45cab5", # noqa: mock + } + + try: + async with self.run_clock(): + self._http_player.replay_timestamp_ms = 1648503302272 + buy_price: Decimal = await connector.get_order_price(TRADING_PAIR, True, amount) * Decimal("1.02") + sell_price: Decimal = await connector.get_order_price(TRADING_PAIR, False, amount) * Decimal("0.98") + + self._http_player.replay_timestamp_ms = 1648503304951 + await connector._create_order( + trade_type=TradeType.BUY, + order_id=GatewayEVMAMM.create_market_order_id(TradeType.BUY, TRADING_PAIR), + trading_pair=TRADING_PAIR, + amount=amount, + price=buy_price, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + self._http_player.replay_timestamp_ms = 1648503309059 + await connector._create_order( + TradeType.SELL, + GatewayEVMAMM.create_market_order_id(TradeType.SELL, TRADING_PAIR), + TRADING_PAIR, + amount, + sell_price, + max_fee_per_gas=MAX_FEE_PER_GAS, + max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS, + ) + + self._http_player.replay_timestamp_ms = 1648503311238 + await connector.update_order_status(connector.amm_orders) + + self.assertEqual(2, len(connector.amm_orders)) + self.assertEqual(expected_order_tx_hash_set, set(o.exchange_order_id for o in connector.amm_orders)) + + for in_flight_order in connector.amm_orders: + in_flight_order.creation_timestamp = connector.current_timestamp - 86400 + + self._http_player.replay_timestamp_ms = 1648503313675 + await connector.cancel_outdated_orders(600) + + self._http_player.replay_timestamp_ms = 1648503331511 + await connector.update_canceling_transactions(connector.amm_orders) + # self._http_player.replay_timestamp_ms = 1648503331520 + # await connector.update_canceling_transactions(connector.amm_orders) + self.assertEqual(2, len(connector.amm_orders)) + self.assertEqual(expected_cancel_tx_hash_set, set(o.cancel_tx_hash for o in connector.amm_orders)) + + async with timeout(10): + while len(event_logger.event_log) < 2: + await event_logger.wait_for(OrderCancelledEvent) + self.assertEqual(0, len(connector.amm_orders)) + finally: + connector.remove_listener(MarketEvent.OrderCancelled, event_logger) + + @async_test(loop=ev_loop) + async def test_cancel_approval(self): + connector: GatewayEVMAMM = self._connector + event_logger: EventLogger = EventLogger() + connector.add_listener(TokenApprovalEvent.ApprovalCancelled, event_logger) + + expected_evm_approve_tx_hash_set: Set[str] = { + "0x7666bb5ba3ecec828e323f20685dfd03a067e7b2830b217363293b166b48a679", # noqa: mock + "0x7291d26447e300bd37260add7ac7db9a745f64c7ee10854695b0a70b0897456f", # noqa: mock + } + expected_cancel_tx_hash_set: Set[str] = { + "0x21b4d0e956241a497cf50d9c5dcefea4ec9fb225a1d11f80477ca434caab30ff", # noqa: mock + "0x7ac85d5a77f28e9317127218c06eb3d70f4c68924a4b5b743fe8faef6d011d11", # noqa: mock + } + + try: + async with self.run_clock(): + self._http_player.replay_timestamp_ms = 1648503333290 + tracked_order_1: GatewayInFlightOrder = await connector.approve_token( + "DAI", max_fee_per_gas=MAX_FEE_PER_GAS, max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + self._http_player.replay_timestamp_ms = 1648503337964 + tracked_order_2: GatewayInFlightOrder = await connector.approve_token( + "WETH", max_fee_per_gas=MAX_FEE_PER_GAS, max_priority_fee_per_gas=MAX_PRIORITY_FEE_PER_GAS + ) + self.assertEqual(2, len(connector.approval_orders)) + self.assertEqual( + expected_evm_approve_tx_hash_set, set(o.exchange_order_id for o in connector.approval_orders) + ) + + self._http_player.replay_timestamp_ms = 1648503342513 + tracked_order_1.creation_timestamp = connector.current_timestamp - 86400 + tracked_order_2.creation_timestamp = connector.current_timestamp - 86400 + await connector.cancel_outdated_orders(600) + self.assertEqual(2, len(connector.approval_orders)) + self.assertEqual(expected_cancel_tx_hash_set, set(o.cancel_tx_hash for o in connector.approval_orders)) + + self._http_player.replay_timestamp_ms = 1648503385484 + async with timeout(10): + while len(event_logger.event_log) < 2: + await event_logger.wait_for(TokenApprovalCancelledEvent) + cancelled_approval_symbols = [e.token_symbol for e in event_logger.event_log] + self.assertIn("DAI", cancelled_approval_symbols) + self.assertIn("WETH", cancelled_approval_symbols) + finally: + connector.remove_listener(TokenApprovalEvent.ApprovalCancelled, event_logger) diff --git a/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm.py b/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm.py new file mode 100644 index 0000000..b28e2de --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm.py @@ -0,0 +1,388 @@ +import asyncio +import time +import unittest +from contextlib import ExitStack +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Dict, List +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test +from async_timeout import timeout + +from bin import path_util # noqa: F401 +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.amm.gateway_evm_amm import GatewayEVMAMM +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCreatedEvent, + MarketEvent, + OrderFilledEvent, + OrderType, + SellOrderCreatedEvent, + TokenApprovalEvent, + TokenApprovalSuccessEvent, + TradeType, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +s_decimal_0: Decimal = Decimal(0) + + +class GatewayEVMAMMConnectorUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + _clock: Clock + _connector: GatewayEVMAMM + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + GatewayHttpClient.__instance = None + cls._db_path = realpath(join(__file__, "../fixtures/gateway_evm_amm_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._clock: Clock = Clock(ClockMode.REALTIME) + cls._client_config_map = ClientConfigAdapter(ClientConfigMap()) + cls._connector: GatewayEVMAMM = GatewayEVMAMM( + client_config_map=cls._client_config_map, + connector_name="uniswap", + chain="ethereum", + network="ropsten", + address="0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + trading_pairs=["DAI-WETH"], + trading_required=True + ) + cls._connector._amount_quantum_dict = {"WETH": Decimal(str(1e-15)), "DAI": Decimal(str(1e-15))} + cls._clock.add_iterator(cls._connector) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch( + "hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", + return_value=ClientSession() + ) + ) + cls._patch_stack.enter_context(cls._clock) + GatewayHttpClient.get_instance(client_config_map=cls._client_config_map).base_url = "https://localhost:5000" + ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + GatewayHttpClient.__instance = None + super().tearDownClass() + + def setUp(self) -> None: + super().setUp() + self._http_player.replay_timestamp_ms = None + + @classmethod + async def wait_til_ready(cls): + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if cls._connector.ready: + break + else: + await cls._clock.run_til(next_iteration + 0.1) + + async def run_clock(self): + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration + 0.1) + + @async_test(loop=ev_loop) + async def test_update_balances(self): + self._connector._account_balances.clear() + self.assertEqual(0, len(self._connector.get_all_balances())) + await self._connector.update_balances(on_interval=False) + self.assertEqual(3, len(self._connector.get_all_balances())) + self.assertAlmostEqual(Decimal("58.903990239981237338"), self._connector.get_balance("ETH")) + self.assertAlmostEqual(Decimal("1015.242427495432379422"), self._connector.get_balance("DAI")) + + @async_test(loop=ev_loop) + async def test_get_allowances(self): + big_num: Decimal = Decimal("1000000000000000000000000000") + allowances: Dict[str, Decimal] = await self._connector.get_allowances() + self.assertEqual(2, len(allowances)) + self.assertGreater(allowances.get("WETH"), big_num) + self.assertGreater(allowances.get("DAI"), big_num) + + @async_test(loop=ev_loop) + async def test_get_chain_info(self): + self._connector._chain_info.clear() + await self._connector.get_chain_info() + self.assertGreater(len(self._connector._chain_info), 2) + self.assertEqual("ETH", self._connector._chain_info.get("nativeCurrency")) + + @async_test(loop=ev_loop) + async def test_update_approval_status(self): + def create_approval_record(token_symbol: str, tx_hash: str) -> GatewayInFlightOrder: + return GatewayInFlightOrder( + client_order_id=self._connector.create_approval_order_id(token_symbol), + exchange_order_id=tx_hash, + trading_pair=token_symbol, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=s_decimal_0, + amount=s_decimal_0, + gas_price=s_decimal_0, + creation_timestamp=self._connector.current_timestamp + ) + successful_records: List[GatewayInFlightOrder] = [ + create_approval_record( + "WETH", + "0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff9" # noqa: mock + ), + create_approval_record( + "DAI", + "0x4f81aa904fcb16a8938c0e0a76bf848df32ce6378e9e0060f7afc4b2955de405" # noqa: mock + ), + ] + fake_records: List[GatewayInFlightOrder] = [ + create_approval_record( + "WETH", + "0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff8" # noqa: mock + ), + create_approval_record( + "DAI", + "0x4f81aa904fcb16a8938c0e0a76bf848df32ce6378e9e0060f7afc4b2955de404" # noqa: mock + ), + ] + + event_logger: EventLogger = EventLogger() + self._connector.add_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + self._connector.add_listener(TokenApprovalEvent.ApprovalFailed, event_logger) + + try: + await self._connector.update_token_approval_status(successful_records + fake_records) + self.assertEqual(2, len(event_logger.event_log)) + self.assertEqual( + {"WETH", "DAI"}, + set(e.token_symbol for e in event_logger.event_log) + ) + finally: + self._connector.remove_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + self._connector.remove_listener(TokenApprovalEvent.ApprovalFailed, event_logger) + + @async_test(loop=ev_loop) + async def test_update_order_status(self): + def create_order_record( + trading_pair: str, + trade_type: TradeType, + tx_hash: str, + price: Decimal, + amount: Decimal, + gas_price: Decimal) -> GatewayInFlightOrder: + order: GatewayInFlightOrder = GatewayInFlightOrder( + client_order_id=self._connector.create_market_order_id(trade_type, trading_pair), + exchange_order_id=tx_hash, + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=trade_type, + price=price, + amount=amount, + gas_price=gas_price, + creation_timestamp=self._connector.current_timestamp + ) + order.fee_asset = self._connector._native_currency + self._connector._order_tracker.start_tracking_order(order) + return order + successful_records: List[GatewayInFlightOrder] = [ + create_order_record( + "DAI-WETH", + TradeType.BUY, + "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + Decimal("0.00267589"), + Decimal("1000"), + Decimal("29") + ) + ] + fake_records: List[GatewayInFlightOrder] = [ + create_order_record( + "DAI-WETH", + TradeType.BUY, + "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e17", # noqa: mock + Decimal("0.00267589"), + Decimal("1000"), + Decimal("29") + ) + ] + + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.OrderFilled, event_logger) + + try: + await self._connector.update_order_status(successful_records + fake_records) + async with timeout(10): + while len(event_logger.event_log) < 1: + await event_logger.wait_for(OrderFilledEvent) + filled_event: OrderFilledEvent = event_logger.event_log[0] + self.assertEqual( + "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + filled_event.exchange_trade_id) + finally: + self._connector.remove_listener(MarketEvent.OrderFilled, event_logger) + + @async_test(loop=ev_loop) + async def test_get_quote_price(self): + buy_price: Decimal = await self._connector.get_quote_price("DAI-WETH", True, Decimal(1000)) + sell_price: Decimal = await self._connector.get_quote_price("DAI-WETH", False, Decimal(1000)) + self.assertEqual(Decimal("0.002684496"), buy_price) + self.assertEqual(Decimal("0.002684496"), sell_price) + + @async_test(loop=ev_loop) + async def test_approve_token(self): + self._http_player.replay_timestamp_ms = 1648499867736 + weth_in_flight_order: GatewayInFlightOrder = await self._connector.approve_token("WETH") + self._http_player.replay_timestamp_ms = 1648499871595 + dai_in_flight_order: GatewayInFlightOrder = await self._connector.approve_token("DAI") + + self.assertEqual( + "0x6c975ba8c1d35e8542ffd05956d9ec227c1ac234ae4d5f69819aa24bae784321", # noqa: mock + weth_in_flight_order.exchange_order_id + ) + self.assertEqual( + "0x919438daa20dc2f5381fdf25b6b89a189d055b3a1d6d848c44e53dc10f49168c", # noqa: mock + dai_in_flight_order.exchange_order_id + ) + + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + + self._http_player.replay_timestamp_ms = 1648500060232 + try: + async with timeout(10): + while len(event_logger.event_log) < 2: + await event_logger.wait_for(TokenApprovalSuccessEvent) + self.assertEqual(2, len(event_logger.event_log)) + self.assertEqual( + {"WETH", "DAI"}, + set(e.token_symbol for e in event_logger.event_log) + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_buy_order(self): + self._http_player.replay_timestamp_ms = 1648500060561 + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.BuyOrderCreated, event_logger) + self._connector.add_listener(MarketEvent.OrderFilled, event_logger) + + try: + self._connector.buy("DAI-WETH", Decimal(100), OrderType.LIMIT, Decimal("0.002861464039500")) + order_created_event: BuyOrderCreatedEvent = await event_logger.wait_for( + BuyOrderCreatedEvent, + timeout_seconds=5 + ) + self.assertEqual( + "0xc3d3166e6142c479b26c21e007b68e2b7fb1d28c1954ab344b45d7390139654f", # noqa: mock + order_created_event.exchange_order_id + ) + self._http_player.replay_timestamp_ms = 1648500097569 + order_filled_event: OrderFilledEvent = await event_logger.wait_for(OrderFilledEvent, timeout_seconds=5) + self.assertEqual( + "0xc3d3166e6142c479b26c21e007b68e2b7fb1d28c1954ab344b45d7390139654f", # noqa: mock + order_filled_event.exchange_trade_id + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_sell_order(self): + self._http_player.replay_timestamp_ms = 1648500097825 + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.SellOrderCreated, event_logger) + self._connector.add_listener(MarketEvent.OrderFilled, event_logger) + + try: + self._connector.sell("DAI-WETH", Decimal(100), OrderType.LIMIT, Decimal("0.002816023229500")) + order_created_event: SellOrderCreatedEvent = await event_logger.wait_for( + SellOrderCreatedEvent, + timeout_seconds=5 + ) + self.assertEqual( + "0x63c7ffaf8dcede44c51cc2ea7ab3a5c0ea4915c9dab57dfcb432ea92ad174391", # noqa: mock + order_created_event.exchange_order_id + ) + self._http_player.replay_timestamp_ms = 1648500133889 + order_filled_event: OrderFilledEvent = await event_logger.wait_for(OrderFilledEvent, timeout_seconds=5) + self.assertEqual( + "0x63c7ffaf8dcede44c51cc2ea7ab3a5c0ea4915c9dab57dfcb432ea92ad174391", # noqa: mock + order_filled_event.exchange_trade_id + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + def test_order_restoration(self): + self._connector.restore_tracking_states( + saved_states={ + "sell-WETH-USDT-1680822551019999": { + "client_order_id": "sell-WETH-USDT-1680822551019999", + "exchange_order_id": "0xcf31a0b408fb162de7c842d164292d604347d79d0d3c5be3a36a785c123c322a", # noqa: mock + "trading_pair": "WETH-USDT", + "order_type": "LIMIT", + "trade_type": "SELL", + "price": "1848.189431481628356", + "amount": "0.010000", + "executed_amount_base": "0.010000", + "executed_amount_quote": "18.481894314816283560000", + "last_state": "5", + "leverage": "1", + "position": "NIL", + "creation_timestamp": 1680822551.0, + "last_update_timestamp": 1680822555.0, + "order_fills": { + "0xcf31a0b408fb162de7c842d164292d604347d79d0d3c5be3a36a785c123c322a": { # noqa: mock + "trade_id": "0xcf31a0b408fb162de7c842d164292d604347d79d0d3c5be3a36a785c123c322a", # noqa: mock + "client_order_id": "sell-WETH-USDT-1680822551019999", + "exchange_order_id": "0xcf31a0b408fb162de7c842d164292d604347d79d0d3c5be3a36a785c123c322a", # noqa: mock + "trading_pair": "WETH-USDT", + "fill_timestamp": 1680822555.0, + "fill_price": "1848.189431481628356", + "fill_base_amount": "0.010000", + "fill_quote_amount": "18.481894314816283560000", + "fee": { + "fee_type": "AddedToCost", + "percent": "0", + "percent_token": None, + "flat_fees": [{"token": "ETH", "amount": "0.0001152116000000000063955285512"}], + }, + "is_taker": True, + } + }, + "nonce": None, + "cancel_tx_hash": None, + "creation_transaction_hash": None, + "gas_price": "None", + } + } + ) + inflight_orders = self._connector.in_flight_orders + self.assertEqual(len(inflight_orders), 1) + # more asserts diff --git a/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm_lp.py b/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm_lp.py new file mode 100644 index 0000000..0dd415a --- /dev/null +++ b/test/hummingbot/connector/gateway/amm/test_gateway_evm_amm_lp.py @@ -0,0 +1,306 @@ +import asyncio +import time +import unittest +from contextlib import ExitStack +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Dict, List +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test +from async_timeout import timeout + +from bin import path_util # noqa: F401 +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.gateway.amm_lp.gateway_evm_amm_lp import GatewayEVMAMMLP, GatewayInFlightLPOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + LPType, + MarketEvent, + RangePositionFeeCollectedEvent, + RangePositionLiquidityAddedEvent, + RangePositionLiquidityRemovedEvent, + RangePositionUpdateEvent, + TokenApprovalEvent, + TokenApprovalSuccessEvent, +) +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.utils.async_utils import safe_ensure_future + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +s_decimal_0: Decimal = Decimal(0) + + +class GatewayEVMAMMLPConnectorUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + _clock: Clock + _connector: GatewayEVMAMMLP + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls._db_path = realpath(join(__file__, "../fixtures/gateway_evm_amm_lp_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._clock: Clock = Clock(ClockMode.REALTIME) + cls._client_config_map = ClientConfigAdapter(ClientConfigMap()) + cls._connector: GatewayEVMAMMLP = GatewayEVMAMMLP( + client_config_map=cls._client_config_map, + connector_name="uniswapLP", + chain="ethereum", + network="kovan", + address="0xefb7be8631d154d4c0ad8676fec0897b2894fe8f", + trading_pairs=["COIN1-COIN3"], + trading_required=True + ) + cls._clock.add_iterator(cls._connector) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch( + "hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", + return_value=ClientSession() + ) + ) + cls._patch_stack.enter_context(cls._clock) + GatewayHttpClient.get_instance(client_config_map=cls._client_config_map).base_url = "https://localhost:5000" + ev_loop.run_until_complete(cls.wait_til_ready()) + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + + def setUp(self) -> None: + self._http_player.replay_timestamp_ms = None + + @classmethod + async def wait_til_ready(cls): + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + if cls._connector.ready: + break + else: + await cls._clock.run_til(next_iteration + 0.1) + + async def run_clock(self): + while True: + now: float = time.time() + next_iteration = now // 1.0 + 1 + await self._clock.run_til(next_iteration + 0.1) + + @async_test(loop=ev_loop) + async def test_update_balances(self): + self._connector._account_balances.clear() + self.assertEqual(0, len(self._connector.get_all_balances())) + await self._connector.update_balances(on_interval=False) + self.assertEqual(3, len(self._connector.get_all_balances())) + self.assertAlmostEqual(Decimal("299914.137497713523375729"), self._connector.get_balance("COIN1")) + self.assertAlmostEqual(Decimal("599007.076157878187323412"), self._connector.get_balance("COIN3")) + + @async_test(loop=ev_loop) + async def test_get_allowances(self): + big_num: Decimal = Decimal("1000000000000000000000000000") + allowances: Dict[str, Decimal] = await self._connector.get_allowances() + self.assertEqual(2, len(allowances)) + self.assertGreater(allowances.get("uniswapLP_COIN1"), big_num) + self.assertGreater(allowances.get("uniswapLP_COIN3"), big_num) + + @async_test(loop=ev_loop) + async def test_get_chain_info(self): + self._connector._chain_info.clear() + await self._connector.get_chain_info() + self.assertGreater(len(self._connector._chain_info), 2) + self.assertEqual("ETH", self._connector._chain_info.get("nativeCurrency")) + + @async_test(loop=ev_loop) + async def test_update_approval_status(self): + def create_approval_record(token_symbol: str, tx_hash: str) -> GatewayInFlightLPOrder: + return GatewayInFlightLPOrder( + client_order_id=self._connector.create_approval_order_id("uniswapLP", token_symbol), + exchange_order_id=tx_hash, + trading_pair=token_symbol, + lp_type = LPType.ADD, + lower_price = s_decimal_0, + upper_price = s_decimal_0, + amount_0 = s_decimal_0, + amount_1 = s_decimal_0, + token_id = 0, + gas_price=s_decimal_0, + creation_timestamp=self._connector.current_timestamp + ) + successful_records: List[GatewayInFlightLPOrder] = [ + create_approval_record( + "COIN1", + "0x273a720fdc92554c47f409f4f74d3c262937451ccdbaddfd8d0185a9e3c64dd2" # noqa: mock + ), + create_approval_record( + "COIN3", + "0x27d7a7156bd0afc73092602da67774aa3319adbc72213122d65480e482ce0a8b" # noqa: mock + ), + ] + """fake_records: List[GatewayInFlightLPOrder] = [ + create_approval_record( + "COIN1", + "0x273a720fdc92554c47f409f4f74d3c262937451ccdbaddfd8d0185a9e3c64dd1" # noqa: mock + ), + create_approval_record( + "COIN3", + "0x27d7a7156bd0afc73092602da67774aa3319adbc72213122d65480e482ce0a8a" # noqa: mock + ), + ]""" + + event_logger: EventLogger = EventLogger() + self._connector.add_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + self._connector.add_listener(TokenApprovalEvent.ApprovalFailed, event_logger) + + try: + await self._connector.update_token_approval_status(successful_records) + self.assertEqual(2, len(event_logger.event_log)) + self.assertEqual( + {"uniswapLP_COIN1", "uniswapLP_COIN3"}, + set(e.token_symbol for e in event_logger.event_log) + ) + finally: + self._connector.remove_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + self._connector.remove_listener(TokenApprovalEvent.ApprovalFailed, event_logger) + + @async_test(loop=ev_loop) + async def test_get_price(self): + pool_price: Decimal = await self._connector.get_price("COIN1-COIN3", "LOW") + self.assertEqual([Decimal("0.66141134")], pool_price) + + @async_test(loop=ev_loop) + async def test_approve_token(self): + self._http_player.replay_timestamp_ms = 1652728282963 + coin1_in_flight_order: GatewayInFlightLPOrder = await self._connector.approve_token("uniswapLP", "COIN1") + self._http_player.replay_timestamp_ms = 1652728286030 + coin3_in_flight_order: GatewayInFlightLPOrder = await self._connector.approve_token("uniswapLP", "COIN3") + + self.assertEqual( + "0x65ef330422dc9892460e3ea67338013b9ca619270f960c4531f47a1812cb7677", # noqa: mock + coin1_in_flight_order.exchange_order_id + ) + self.assertEqual( + "0x551970f039ed4190b00a7277bf7e952aec371ac562853e89b54bbeea82c9ed86", # noqa: mock + coin3_in_flight_order.exchange_order_id + ) + + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(TokenApprovalEvent.ApprovalSuccessful, event_logger) + + self._http_player.replay_timestamp_ms = 1652728338292 + try: + async with timeout(5): + while len(event_logger.event_log) < 2: + await event_logger.wait_for(TokenApprovalSuccessEvent) + self.assertEqual(2, len(event_logger.event_log)) + self.assertEqual( + {"uniswapLP_COIN1", "uniswapLP_COIN3"}, + set(e.token_symbol for e in event_logger.event_log) + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_add_liquidity(self): + self._http_player.replay_timestamp_ms = 1652728316475 + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionLiquidityAdded, event_logger) + + try: + self._connector.add_liquidity("COIN1-COIN3", Decimal("1"), Decimal("1"), Decimal("1"), Decimal("5"), "LOW") + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for( + RangePositionUpdateEvent, + timeout_seconds=5 + ) + self.assertEqual( + "0x0b6145277c7a6eefff5db3f595a3563da77df7f6face91122fd117fb6345ba0f", # noqa: mock + pos_update_event.exchange_order_id + ) + self._http_player.replay_timestamp_ms = 1652728320503 + liquidity_added_event: RangePositionLiquidityAddedEvent = await event_logger.wait_for(RangePositionLiquidityAddedEvent, timeout_seconds=5) + self.assertEqual( + "0x0b6145277c7a6eefff5db3f595a3563da77df7f6face91122fd117fb6345ba0f", # noqa: mock + liquidity_added_event.exchange_order_id + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_remove_liquidity(self): + self._http_player.replay_timestamp_ms = 1652728331046 + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionLiquidityRemoved, event_logger) + + try: + self._connector.remove_liquidity("COIN1-COIN3", 1000) + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for( + RangePositionUpdateEvent, + timeout_seconds=5 + ) + self.assertEqual( + "0x84c2e5633ea6f5aaa8fe19464a65b2a30d1fce66513db4b11439c09288962e7a", # noqa: mock + pos_update_event.exchange_order_id + ) + self._http_player.replay_timestamp_ms = 1652728338292 + liquidity_removed_event: RangePositionLiquidityRemovedEvent = await event_logger.wait_for(RangePositionLiquidityRemovedEvent, timeout_seconds=5) + self.assertEqual( + "0x84c2e5633ea6f5aaa8fe19464a65b2a30d1fce66513db4b11439c09288962e7a", # noqa: mock + liquidity_removed_event.exchange_order_id + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_collect_fees(self): + self._http_player.replay_timestamp_ms = 1652728322607 + clock_task: asyncio.Task = safe_ensure_future(self.run_clock()) + event_logger: EventLogger = EventLogger() + self._connector.add_listener(MarketEvent.RangePositionUpdate, event_logger) + self._connector.add_listener(MarketEvent.RangePositionFeeCollected, event_logger) + + try: + self._connector.collect_fees("COIN1-COIN3", 1001) + pos_update_event: RangePositionUpdateEvent = await event_logger.wait_for( + RangePositionUpdateEvent, + timeout_seconds=5 + ) + self.assertEqual( + "0x74ae9ae03e8bee4d6954dfb4944c87dae5de5f63f4932be3b16dfc93601f0fdc", # noqa: mock + pos_update_event.exchange_order_id + ) + self._http_player.replay_timestamp_ms = 1652728328486 + fees_collected_event: RangePositionFeeCollectedEvent = await event_logger.wait_for(RangePositionFeeCollectedEvent, timeout_seconds=5) + self.assertEqual( + "0x74ae9ae03e8bee4d6954dfb4944c87dae5de5f63f4932be3b16dfc93601f0fdc", # noqa: mock + fees_collected_event.exchange_order_id + ) + finally: + clock_task.cancel() + try: + await clock_task + except asyncio.CancelledError: + pass diff --git a/test/hummingbot/connector/gateway/clob_perp/__init__.py b/test/hummingbot/connector/gateway/clob_perp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/__init__.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/__init__.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py new file mode 100644 index 0000000..02e32c0 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/injective_perpetual_mock_utils.py @@ -0,0 +1,1416 @@ +import asyncio +import json +from decimal import Decimal +from typing import Any, List, Optional, Tuple, Type +from unittest.mock import AsyncMock, patch + +import grpc +import pandas as pd +from pyinjective.orderhash import OrderHashResponse +from pyinjective.proto.exchange.injective_accounts_rpc_pb2 import ( + StreamSubaccountBalanceResponse, + SubaccountBalance, + SubaccountDeposit as AccountSubaccountDeposit, +) +from pyinjective.proto.exchange.injective_derivative_exchange_rpc_pb2 import ( + DerivativeLimitOrderbookV2, + DerivativeMarketInfo, + DerivativeOrderHistory, + DerivativePosition, + DerivativeTrade, + FundingPayment, + FundingPaymentsResponse, + FundingRate, + FundingRatesResponse, + MarketResponse, + MarketsResponse, + OrderbooksV2Response, + OrdersHistoryResponse, + Paging, + PerpetualMarketFunding, + PerpetualMarketInfo, + PositionDelta, + PositionsResponse, + PriceLevel, + SingleDerivativeLimitOrderbookV2, + StreamOrderbookV2Response, + StreamOrdersHistoryResponse, + StreamPositionsResponse, + StreamTradesResponse, + TokenMeta, + TradesResponse, +) +from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import ( + CosmosCoin, + GasFee, + GetTxByTxHashResponse, + StreamTxsResponse, + TxDetailData, +) +from pyinjective.proto.exchange.injective_oracle_rpc_pb2 import PriceResponse, StreamPricesResponse +from pyinjective.proto.exchange.injective_portfolio_rpc_pb2 import ( + AccountPortfolioResponse, + Coin, + Portfolio, + StreamAccountPortfolioResponse, + SubaccountBalanceV2, + SubaccountDeposit, +) +from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2 import ( + MarketsResponse as SpotMarketsResponse, + SpotMarketInfo, + TokenMeta as SpotTokenMeta, +) + +from hummingbot.connector.constants import s_decimal_0 +from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_constants import ( + BASE_GAS, + DERIVATIVE_CANCEL_ORDER_GAS, + DERIVATIVE_SUBMIT_ORDER_GAS, + GAS_BUFFER, +) +from hummingbot.core.data_type.common import OrderType, PositionSide, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase + + +class StreamMock: + def __init__(self): + self.queue = asyncio.Queue() + + def add(self, item: Any): + self.queue.put_nowait(item=item) + + def run_until_all_items_delivered(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete(asyncio.wait_for(fut=self.queue.join(), timeout=timeout)) + + def cancel(self): + pass + + def __aiter__(self): + return self + + async def __anext__(self): + el = await self.queue.get() + self.queue.task_done() + return el + + +class InjectivePerpetualClientMock: + def __init__( + self, + initial_timestamp: float, + sub_account_id: str, + base: str, + quote: str, + ): + self.initial_timestamp = initial_timestamp + self.base = base + self.base_coin_address = "someBaseCoinAddress" + self.base_denom = self.base_coin_address + self.base_decimals = 18 + self.quote = quote + self.quote_coin_address = "someQuoteCoinAddress" + self.quote_denom = self.quote_coin_address + self.quote_decimals = 8 # usually set to 6, but for the sake of differing minimum price/size increments + self.oracle_scale_factor = 6 # usually same as quote_decimals but differentiating here for the sake of tests + self.market_id = "someMarketId" + self.inj_base = "INJ" + self.inj_market_id = "anotherMarketId" + self.sub_account_id = sub_account_id + self.service_provider_fee = Decimal("0.4") + self.order_creation_gas_estimate = Decimal("0.0000825") + self.order_cancelation_gas_estimate = Decimal("0.0000725") + self.order_gas_estimate = Decimal("0.000155") # gas to both submit and cancel an order in INJ + + self.injective_async_client_mock_patch = patch( + target=( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source.AsyncClient" + ), + autospec=True, + ) + self.injective_async_client_mock: Optional[AsyncMock] = None + self.gateway_instance_mock_patch = patch( + target=( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual" + ".injective_perpetual_api_data_source.GatewayHttpClient" + ), + autospec=True, + ) + self.gateway_instance_mock: Optional[AsyncMock] = None + self.injective_order_hash_manager_start_patch = patch( + target=( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source" + ".OrderHashManager.start" + ), + autospec=True, + ) + self.injective_composer_patch = patch( + target="hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source" + ".Composer", + autospec=True, + ) + self.injective_compute_order_hashes_patch = patch( + target=( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source" + ".OrderHashManager.compute_order_hashes" + ), + autospec=True, + ) + self.injective_compute_order_hashes_mock: Optional[AsyncMock] = None + + self.place_order_called_event = asyncio.Event() + self.cancel_order_called_event = asyncio.Event() + + @property + def min_quantity_tick_size(self) -> Decimal: + return Decimal("0.001") + + @property + def min_price_tick_size(self) -> Decimal: + return Decimal("0.00001") + + @property + def maker_fee_rate(self) -> Decimal: + return Decimal("-0.0001") + + @property + def taker_fee_rate(self) -> Decimal: + return Decimal("0.001") + + @property + def exchange_trading_pair(self) -> str: + return self.market_id + + def start(self): + self.injective_async_client_mock = self.injective_async_client_mock_patch.start() + self.injective_async_client_mock.return_value = self.injective_async_client_mock + self.gateway_instance_mock = self.gateway_instance_mock_patch.start() + self.gateway_instance_mock.get_instance.return_value = self.gateway_instance_mock + self.injective_order_hash_manager_start_patch.start() + self.injective_composer_patch.start() + self.injective_compute_order_hashes_mock = self.injective_compute_order_hashes_patch.start() + + self.injective_async_client_mock.stream_derivative_trades.return_value = StreamMock() + self.injective_async_client_mock.stream_historical_derivative_orders.return_value = StreamMock() + self.injective_async_client_mock.stream_derivative_orderbook_snapshot.return_value = StreamMock() + self.injective_async_client_mock.stream_account_portfolio.return_value = StreamMock() + self.injective_async_client_mock.stream_subaccount_balance.return_value = StreamMock() + self.injective_async_client_mock.stream_derivative_positions.return_value = StreamMock() + self.injective_async_client_mock.stream_txs.return_value = StreamMock() + self.injective_async_client_mock.stream_oracle_prices.return_value = StreamMock() + self.injective_async_client_mock.stream_derivative_positions.return_value = StreamMock() + + self.configure_active_derivative_markets_response(timestamp=self.initial_timestamp) + self.configure_get_funding_info_response( + index_price=Decimal("200"), + mark_price=Decimal("202"), + next_funding_time=self.initial_timestamp + 8 * 60 * 60, + funding_rate=Decimal("0.0002"), + ) + + def stop(self): + self.injective_async_client_mock_patch.stop() + self.gateway_instance_mock_patch.stop() + self.injective_order_hash_manager_start_patch.stop() + self.injective_composer_patch.stop() + self.injective_compute_order_hashes_patch.stop() + + def run_until_all_items_delivered(self, timeout: float = 1): + self.injective_async_client_mock.stream_derivative_trades.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_historical_derivative_orders.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_derivative_orderbook_snapshot.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_account_portfolio.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_txs.return_value.run_until_all_items_delivered(timeout=timeout) + self.injective_async_client_mock.stream_oracle_prices.return_value.run_until_all_items_delivered( + timeout=timeout) + + def run_until_place_order_called(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete( + asyncio.wait_for(fut=self.place_order_called_event.wait(), timeout=timeout) + ) + + def run_until_cancel_order_called(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete( + asyncio.wait_for(fut=self.cancel_order_called_event.wait(), timeout=timeout) + ) + + def configure_batch_order_create_response( + self, + timestamp: int, + transaction_hash: str, + created_orders: List[InFlightOrder], + ): + def update_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_perp_batch_order_modify.side_effect = update_and_return + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, success=True, order_hashes=[order.exchange_order_id for order in created_orders] + ) + for order in created_orders: + self.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + ) + self.injective_compute_order_hashes_mock.return_value = OrderHashResponse( + spot=[], derivative=[order.exchange_order_id for order in created_orders] + ) + + def configure_batch_order_cancel_response( + self, + timestamp: int, + transaction_hash: str, + canceled_orders: List[InFlightOrder], + ): + def update_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_perp_batch_order_modify.side_effect = update_and_return + for order in canceled_orders: + self.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + is_canceled=True, + ) + + def configure_place_order_response( + self, + timestamp: int, + transaction_hash: str, + exchange_order_id: str, + trade_type: TradeType, + price: Decimal, + size: Decimal, + ): + def place_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash[2:].lower(), + } + + self.gateway_instance_mock.clob_perp_place_order.side_effect = place_and_return + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, success=True, order_hashes=[exchange_order_id] + ) + self.configure_get_historical_perp_orders_response( + timestamp=timestamp, + order_hash=exchange_order_id, + state="booked", + execution_type="limit", + order_type="buy" if trade_type == TradeType.BUY else "sell", + price=price, + size=size, + filled_size=Decimal("0"), + direction="buy" if trade_type == TradeType.BUY else "sell", + leverage=Decimal(1), + ) + self.injective_compute_order_hashes_mock.return_value = OrderHashResponse( + spot=[], derivative=[exchange_order_id] + ) + + def configure_place_order_fails_response(self, exception: Exception): + def place_and_raise(*_, **__): + self.place_order_called_event.set() + raise exception + + self.gateway_instance_mock.clob_perp_place_order.side_effect = place_and_raise + + def configure_cancel_order_response(self, timestamp: int, transaction_hash: str): + def cancel_and_return(*_, **__): + self.cancel_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_perp_cancel_order.side_effect = cancel_and_return + + def configure_cancel_order_fails_response(self, exception: Exception): + def cancel_and_raise(*_, **__): + self.cancel_order_called_event.set() + raise exception + + self.gateway_instance_mock.clob_perp_cancel_order.side_effect = cancel_and_raise + + def configure_one_success_one_failure_order_cancelation_responses( + self, + success_timestamp: int, + success_transaction_hash: str, + failure_exception: Exception, + ): + called_once = False + + def cancel_and_return(*_, **__): + nonlocal called_once + if called_once: + self.cancel_order_called_event.set() + raise failure_exception + called_once = True + return { + "network": "injective", + "timestamp": success_timestamp, + "latency": 2, + "txHash": success_transaction_hash, + } + + self.gateway_instance_mock.clob_perp_cancel_order.side_effect = cancel_and_return + + def configure_check_network_success(self): + self.injective_async_client_mock.ping.side_effect = None + + def configure_check_network_failure(self, exc: Type[Exception] = grpc.RpcError): + self.injective_async_client_mock.ping.side_effect = exc + + def configure_order_status_update_response( + self, + timestamp: int, + order: InFlightOrder, + creation_transaction_hash: Optional[str] = None, + creation_transaction_success: bool = True, + cancelation_transaction_hash: Optional[str] = None, + filled_size: Decimal = s_decimal_0, + is_canceled: bool = False, + is_failed: bool = False, + ): + exchange_order_id = order.exchange_order_id + if creation_transaction_hash is not None: + if creation_transaction_success: + self.configure_creation_transaction_stream_event( + timestamp=timestamp, transaction_hash=creation_transaction_hash + ) + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, + success=creation_transaction_success, + order_hashes=[exchange_order_id], + transaction_hash=creation_transaction_hash, + is_order_failed=is_failed, + ) + if cancelation_transaction_hash is not None: + self.configure_cancelation_transaction_stream_event( + timestamp=timestamp, + transaction_hash=cancelation_transaction_hash, + order_hash=exchange_order_id, + ) + self.configure_get_tx_by_hash_cancelation_response( + timestamp=timestamp, + order_hash=exchange_order_id, + transaction_hash=cancelation_transaction_hash, + ) + if is_failed: + self.configure_get_historical_derivative_orders_empty_response() + elif not is_canceled: + self.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + order_hash=exchange_order_id, + filled_size=filled_size, + ) + else: + self.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=timestamp, in_flight_order=order, is_canceled=True + ) + + def configure_trades_response_with_exchange_order_id( + self, + timestamp: float, + exchange_order_id: str, + price: Decimal, + size: Decimal, + fee: TradeFeeBase, + trade_id: str, + ): + """This method appends mocks if previously queued mocks already exist.""" + timestamp_ms = int(timestamp * 1e3) + scaled_price = price * Decimal(f"1e{self.quote_decimals}") + scaled_fee = fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}") + position_delta = PositionDelta( + trade_direction="buy", + execution_price=str(scaled_price), + execution_quantity=str(size), + execution_margin=str(scaled_price * size), + ) + trade = DerivativeTrade( + executed_at=timestamp_ms, + position_delta=position_delta, + subaccount_id=self.sub_account_id, + trade_execution_type="limitMatchNewOrder", + fee=str(scaled_fee), + is_liquidation=False, + market_id=self.market_id, + order_hash=exchange_order_id, + payout="0", + fee_recipient="anotherRecipientAddress", + trade_id=trade_id, + execution_side="taker", + ) + trades = TradesResponse() + trades.trades.append(trade) + + if self.injective_async_client_mock.get_derivative_trades.side_effect is None: + self.injective_async_client_mock.get_derivative_trades.side_effect = [trades, TradesResponse()] + else: + self.injective_async_client_mock.get_derivative_trades.side_effect = list( + self.injective_async_client_mock.get_derivative_trades.side_effect + ) + [trades, TradesResponse()] + + def configure_trades_response_fails(self): + self.injective_async_client_mock.get_derivative_trades.side_effect = RuntimeError + + def configure_order_stream_event_for_in_flight_order( + self, + timestamp: float, + in_flight_order: InFlightOrder, + filled_size: Decimal = Decimal("0"), + is_canceled: bool = False, + ): + if is_canceled: + state = "canceled" + elif filled_size == Decimal("0"): + state = "booked" + elif filled_size == in_flight_order.amount: + state = "filled" + else: + state = "partial_filled" + self.configure_order_stream_event( + timestamp=timestamp, + order_hash=in_flight_order.exchange_order_id, + state=state, + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=filled_size, + direction=in_flight_order.trade_type.name.lower(), + leverage=in_flight_order.leverage, + ) + + def configure_trade_stream_event( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: TradeFeeBase, + taker_fee: TradeFeeBase, + exchange_order_id: str = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7", # noqa: mock + taker_trade_id: str = "19889401_someTradeId", + ): + """The taker is a buy.""" + maker_trade, taker_trade = self.get_maker_taker_trades_pair( + timestamp=timestamp, + price=price, + size=size, + maker_fee=maker_fee.flat_fees[0].amount, + taker_fee=taker_fee.flat_fees[0].amount, + order_hash=exchange_order_id, + taker_trade_id=taker_trade_id, + ) + maker_trade_response = StreamTradesResponse(trade=maker_trade) + taker_trade_response = StreamTradesResponse(trade=taker_trade) + self.injective_async_client_mock.stream_derivative_trades.return_value.add(maker_trade_response) + self.injective_async_client_mock.stream_derivative_trades.return_value.add(taker_trade_response) + + def configure_bank_account_portfolio_balance_stream_event(self, token: str, amount: Decimal): + assert token in (self.base, self.quote) + if token == self.base: + denom = self.base_coin_address + scaled_amount = amount * Decimal(f"1e{self.base_decimals}") + self.configure_get_account_balances_response(base_bank_balance=amount) + else: + denom = self.quote_coin_address + scaled_amount = amount * Decimal(f"1e{self.quote_decimals}") + self.configure_get_account_balances_response(quote_bank_balance=amount) + balance_event = StreamAccountPortfolioResponse(type="bank", denom=denom, amount=str(scaled_amount)) + self.injective_async_client_mock.stream_account_portfolio.return_value.add(balance_event) + + def configure_funding_info_stream_event( + self, index_price: Decimal, mark_price: Decimal, next_funding_time: float, funding_rate: Decimal + ): + self.configure_get_funding_info_response( + index_price=index_price, + mark_price=mark_price, + next_funding_time=next_funding_time, + funding_rate=funding_rate, + ) + stream_prices_event = StreamPricesResponse( + price=str(mark_price), timestamp=int(next_funding_time - 1000) + ) + self.injective_async_client_mock.stream_oracle_prices.return_value.add(stream_prices_event) + + def configure_account_quote_balance_stream_event( + self, timestamp: float, total_balance: Decimal, available_balance: Decimal + ): + timestamp_ms = int(timestamp * 1e3) + deposit = AccountSubaccountDeposit( + total_balance=str(total_balance * Decimal(f"1e{self.quote_decimals}")), + available_balance=str(available_balance * Decimal(f"1e{self.quote_decimals}")), + ) + balance = SubaccountBalance( + subaccount_id=self.sub_account_id, + account_address="someAccountAddress", + denom=self.quote_denom, + deposit=deposit, + ) + balance_event = StreamSubaccountBalanceResponse( + balance=balance, + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.add(balance_event) + + self.configure_get_account_balances_response( + quote_total_balance=total_balance, quote_available_balance=available_balance, + ) + + def configure_faulty_base_balance_stream_event(self, timestamp: float): + timestamp_ms = int(timestamp * 1e3) + deposit = AccountSubaccountDeposit( + total_balance="", + available_balance="", + ) + balance = SubaccountBalance( + subaccount_id=self.sub_account_id, + account_address="someAccountAddress", + denom="wrongCoinAddress", + deposit=deposit, + ) + balance_event = StreamSubaccountBalanceResponse( + balance=balance, + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.add(balance_event) + + def configure_perp_trades_response_to_request_without_exchange_order_id( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: TradeFeeBase, + taker_fee: TradeFeeBase, + ): + """The taker is a buy.""" + maker_trade, taker_trade = self.get_maker_taker_trades_pair( + timestamp=timestamp, + price=price, + size=size, + maker_fee=maker_fee.flat_fees[0].amount, + taker_fee=taker_fee.flat_fees[0].amount, + ) + trades = TradesResponse() + trades.trades.append(maker_trade) + trades.trades.append(taker_trade) + + self.injective_async_client_mock.get_derivative_trades.return_value = trades + + def configure_empty_perp_trades_responses(self): + self.injective_async_client_mock.get_derivative_trades.return_value = TradesResponse() + + def configure_orderbook_snapshot( + self, + timestamp: float, + bids: List[Tuple[float, float]], + asks: List[Tuple[float, float]], + ): + timestamp_ms = int(timestamp * 1e3) + orderbook = self.create_orderbook_mock(timestamp_ms=timestamp_ms, bids=bids, asks=asks) + single_orderbook = SingleDerivativeLimitOrderbookV2(market_id=self.market_id, orderbook=orderbook) + orderbook_response = OrderbooksV2Response() + orderbook_response.orderbooks.append(single_orderbook) + + self.injective_async_client_mock.get_derivative_orderbooksV2.return_value = orderbook_response + + def configure_orderbook_snapshot_stream_event( + self, timestamp: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]] + ): + timestamp_ms = int(timestamp * 1e3) + orderbook = self.create_orderbook_mock(timestamp_ms=timestamp_ms, bids=bids, asks=asks) + orderbook_response = StreamOrderbookV2Response( + orderbook=orderbook, + operation_type="update", + timestamp=timestamp_ms, + market_id=self.market_id, + ) + + self.injective_async_client_mock.stream_derivative_orderbook_snapshot.return_value.add(orderbook_response) + + def create_orderbook_mock( + self, timestamp_ms: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]] + ) -> DerivativeLimitOrderbookV2: + orderbook = DerivativeLimitOrderbookV2() + + for price, size in bids: + scaled_price = price * Decimal(f"1e{self.quote_decimals}") + bid = PriceLevel(price=str(scaled_price), quantity=str(size), timestamp=timestamp_ms) + orderbook.buys.append(bid) + + for price, size in asks: + scaled_price = price * Decimal(f"1e{self.quote_decimals}") + ask = PriceLevel(price=str(scaled_price), quantity=str(size), timestamp=timestamp_ms) + orderbook.sells.append(ask) + + return orderbook + + def get_maker_taker_trades_pair( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: Decimal, + taker_fee: Decimal, + order_hash: str = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7", # noqa: mock + taker_trade_id: str = "19889401_someTradeId", + ) -> Tuple[DerivativeTrade, DerivativeTrade]: + """The taker is a buy.""" + timestamp_ms = int(timestamp * 1e3) + scaled_price = price * Decimal(f"1e{self.quote_decimals}") + scaled_maker_fee = maker_fee * Decimal(f"1e{self.quote_decimals}") + scaled_taker_fee = taker_fee * Decimal(f"1e{self.quote_decimals}") + assert len(taker_trade_id.split("_")) == 2 + trade_id_prefix = taker_trade_id.split("_")[0] + + taker_position_delta = PositionDelta( + trade_direction="buy", + execution_price=str(scaled_price), + execution_quantity=str(size), + execution_margin=str(scaled_price * size), + ) + + maker_position_delta = PositionDelta( + trade_direction="sell", + execution_price=str(scaled_price), + execution_quantity=str(size), + execution_margin=str(scaled_price * size), + ) + + taker_trade = DerivativeTrade( + order_hash=order_hash, + subaccount_id="sumSubAccountId", + market_id=self.market_id, + trade_execution_type="limitMatchNewOrder", + is_liquidation=False, + position_delta=taker_position_delta, + payout="", + fee=str(scaled_taker_fee), + executed_at=timestamp_ms, + fee_recipient="anotherRecipientAddress", + trade_id=taker_trade_id, + execution_side="taker", + ) + maker_trade = DerivativeTrade( + order_hash="anotherOrderHash", + subaccount_id="anotherSubAccountId", + market_id=self.market_id, + trade_execution_type="limitMatchRestingOrder", + is_liquidation=False, + position_delta=maker_position_delta, + payout="", + fee=str(scaled_maker_fee), + executed_at=timestamp_ms, + fee_recipient="someRecipientAddress", + trade_id=f"{trade_id_prefix}_anotherTradeId", # trade IDs for each side have same prefix, different suffix + execution_side="maker", + ) + return maker_trade, taker_trade + + def configure_order_stream_event( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + leverage: Decimal, + ): + """ + order { + order_hash: "0xfb526d72b85e9ffb4426c37bf332403fb6fb48709fb5d7ca3be7b8232cd10292" # noqa: documentation + market_id: "0x90e662193fa29a3a7e6c07be4407c94833e762d9ee82136a2cc712d6b87d7de3" # noqa: documentation + is_active: true + subaccount_id: "0xc6fe5d33615a1c52c08018c47e8bc53646a0e101000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell_po" + price: "274310000" + trigger_price: "0" + quantity: "144" + filled_quantity: "0" + state: "booked" + created_at: 1665487076373 + updated_at: 1665487076373 + direction: "sell" + margin: "3950170000" + } + operation_type: "update" + timestamp: 1669198784000 + """ + order = self.get_derivative_order_history( + timestamp=timestamp, + order_hash=order_hash, + state=state, + execution_type=execution_type, + order_type=order_type, + price=price, + size=size, + filled_size=filled_size, + direction=direction, + leverage=leverage, + ) + timestamp_ms = int(timestamp * 1e3) + order_response = StreamOrdersHistoryResponse( + order=order, + operation_type="update", + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_historical_derivative_orders.return_value.add(order_response) + + def configure_get_historical_perp_orders_response_for_in_flight_order( + self, + timestamp: float, + in_flight_order: InFlightOrder, + filled_size: Decimal = Decimal("0"), + is_canceled: bool = False, + order_hash: Optional[str] = None, # overwrites in_flight_order.exchange_order_id + ): + """ + orders { + order_hash: "0x10c4cd0c744c08d38920d063ad5f811b97fd9f5d59224814ad9a02bdffb4c0bd" # noqa: documentation + market_id: "0x90e662193fa29a3a7e6c07be4407c94833e762d9ee82136a2cc712d6b87d7de3" # noqa: documentation + subaccount_id: "0x295639d56c987f0e24d21bb167872b3542a6e05a000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell" + price: "21876100000" + trigger_price: "0" + quantity: "0.001" + filled_quantity: "0" + state: "canceled" + created_at: 1676268856766 + updated_at: 1676268924613 + direction: "sell" + margin: "21900000" + } + paging { + total: 32 + } + """ + if is_canceled: + state = "canceled" + elif filled_size == Decimal("0"): + state = "booked" + elif filled_size == in_flight_order.amount: + state = "filled" + else: + state = "partial_filled" + self.configure_get_historical_perp_orders_response( + timestamp=timestamp, + order_hash=order_hash or in_flight_order.exchange_order_id, + state=state, + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=filled_size, + direction=in_flight_order.trade_type.name.lower(), + leverage=in_flight_order.leverage, + ) + + def configure_get_historical_derivative_orders_empty_response(self): + paging = Paging(total=1) + mock_response = OrdersHistoryResponse(paging=paging) + self.injective_async_client_mock.get_historical_derivative_orders.return_value = mock_response + + def configure_get_historical_perp_orders_response( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + leverage: Decimal, + ): + """ + orders { + order_hash: "0x06a9b81441b4fd38bc9da9b928007286b340407481f41398daab291cde2bd6dc" # noqa: documentation + market_id: "0x90e662193fa29a3a7e6c07be4407c94833e762d9ee82136a2cc712d6b87d7de3" # noqa: documentation + subaccount_id: "0x295639d56c987f0e24d21bb167872b3542a6e05a000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell" + price: "21805600000" + trigger_price: "0" + quantity: "0.001" + filled_quantity: "0.001" + state: "filled" + created_at: 1676269001530 + updated_at: 1676269001530 + direction: "sell" + margin: "21800000" + } + paging { + total: 1000 + } + """ + order = self.get_derivative_order_history( + timestamp=timestamp, + order_hash=order_hash, + state=state, + execution_type=execution_type, + order_type=order_type, + price=price, + size=size, + filled_size=filled_size, + direction=direction, + leverage=leverage, + ) + paging = Paging(total=1) + mock_response = OrdersHistoryResponse(paging=paging) + mock_response.orders.append(order) + self.injective_async_client_mock.get_historical_derivative_orders.return_value = mock_response + + def configure_get_funding_info_response( + self, index_price: Decimal, mark_price: Decimal, next_funding_time: float, funding_rate: Decimal + ): + self.configure_get_funding_rates_response( + funding_rate=funding_rate, timestamp=next_funding_time - 100000 + ) + + oracle_price = PriceResponse(price=str(mark_price)) + self.injective_async_client_mock.get_oracle_prices.return_value = oracle_price + + self.configure_perp_trades_response_to_request_without_exchange_order_id( + timestamp=next_funding_time - 1000, + price=index_price, + size=Decimal("1"), + maker_fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0"))]), + taker_fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0"))]), + ) + + derivative_market_info = self._get_derivative_market_info( + market_id=self.market_id, + base_token=self.base, + next_funding_time=next_funding_time, + ) + market_info_response = MarketResponse(market=derivative_market_info) + self.injective_async_client_mock.get_derivative_market.return_value = market_info_response + + def configure_get_funding_payments_response( + self, timestamp: float, funding_rate: Decimal, amount: Decimal + ): + self.configure_fetch_last_fee_payment_response( + amount=amount, funding_rate=funding_rate, timestamp=timestamp + ) + + def get_derivative_order_history( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + leverage: Decimal, + ) -> DerivativeOrderHistory: + timestamp_ms = int(timestamp * 1e3) + order = DerivativeOrderHistory( + order_hash=order_hash, + market_id=self.market_id, + subaccount_id="someSubAccountId", + execution_type=execution_type, + order_type=order_type, + price=str(price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + trigger_price="0", + quantity=str(size * Decimal(f"1e{self.base_decimals}")), + filled_quantity=str(filled_size * Decimal(f"1e{self.base_decimals}")), + state=state, + created_at=timestamp_ms, + updated_at=timestamp_ms, + direction=direction, + margin=str(leverage * size * price), + ) + return order + + def configure_get_account_balances_response( + self, + base_bank_balance: Decimal = s_decimal_0, + quote_bank_balance: Decimal = s_decimal_0, + base_total_balance: Decimal = s_decimal_0, + base_available_balance: Decimal = s_decimal_0, + quote_total_balance: Decimal = s_decimal_0, + quote_available_balance: Decimal = s_decimal_0, + sub_account_id: Optional[str] = None, + ): + sub_account_id = sub_account_id or self.sub_account_id + subaccount_list = [] + bank_coin_list = [] + + if base_total_balance != s_decimal_0: + base_deposit = SubaccountDeposit( + total_balance=str(base_total_balance * Decimal(f"1e{self.base_decimals}")), + available_balance=str(base_available_balance * Decimal(f"1e{self.base_decimals}")), + ) + base_balance = SubaccountBalanceV2( + subaccount_id=sub_account_id, + denom=self.base_denom, + deposit=base_deposit + ) + subaccount_list.append(base_balance) + + if quote_total_balance != s_decimal_0: + quote_deposit = SubaccountDeposit( + total_balance=str(quote_total_balance * Decimal(f"1e{self.quote_decimals}")), + available_balance=str(quote_available_balance * Decimal(f"1e{self.quote_decimals}")), + ) + quote_balance = SubaccountBalanceV2( + subaccount_id=sub_account_id, + denom=self.quote_denom, + deposit=quote_deposit, + ) + subaccount_list.append(quote_balance) + + if base_bank_balance != s_decimal_0: + base_scaled_amount = str(base_bank_balance * Decimal(f"1e{self.base_decimals}")) + coin = Coin(amount=base_scaled_amount, denom=self.base_denom) + bank_coin_list.append(coin) + + if quote_bank_balance != s_decimal_0: + quote_scaled_amount = str(quote_bank_balance * Decimal(f"1e{self.quote_decimals}")) + coin = Coin(amount=quote_scaled_amount, denom=self.quote_denom) + bank_coin_list.append(coin) + + portfolio = Portfolio(account_address="someAccountAddress", bank_balances=bank_coin_list, + subaccounts=subaccount_list) + + self.injective_async_client_mock.get_account_portfolio.return_value = AccountPortfolioResponse( + portfolio=portfolio) + + def configure_get_tx_by_hash_creation_response( + self, + timestamp: float, + success: bool, + order_hashes: Optional[List[str]] = None, + transaction_hash: str = "", + trade_type: TradeType = TradeType.BUY, + is_order_failed: bool = False, + ): + order_hashes = order_hashes or [] + data_data = "\n\275\001\n0/injective.exchange.v1beta1.MsgBatchUpdateOrders" + if success and not is_order_failed: + data_data += "\022\210\001\032B" + "\032B".join(order_hashes) + gas_wanted = int(BASE_GAS + DERIVATIVE_SUBMIT_ORDER_GAS + GAS_BUFFER) + gas_amount_scaled = self.order_creation_gas_estimate * Decimal("1e18") + gas_amount = CosmosCoin(denom="inj", amount=str(int(gas_amount_scaled))) + gas_fee = GasFee(gas_limit=gas_wanted, payer="") + gas_fee.amount.append(gas_amount) + messages_data = [ + { + "type": "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "value": { + "order": { + "market_id": self.market_id, + "order_info": { + "fee_recipient": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "price": "0.000000000007523000", + "quantity": "10000000000000000.000000000000000000", + "subaccount_id": self.sub_account_id, + }, + "order_type": "BUY" if trade_type == TradeType.BUY else "SELL", + "trigger_price": "0.000000000000000000", + }, + "sender": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + }, + } + ] + data = TxDetailData( + hash=transaction_hash, + data=data_data.encode(), + gas_wanted=gas_wanted, + gas_used=int(gas_wanted * Decimal("0.9")), + gas_fee=gas_fee, + code=0 if success else 6, + block_unix_timestamp=int(timestamp * 1e3), + messages=json.dumps(messages_data).encode(), + ) + self.injective_async_client_mock.get_tx_by_hash.return_value = GetTxByTxHashResponse(data=data) + + def configure_get_tx_by_hash_cancelation_response( + self, + timestamp: float, + order_hash: str = "", + transaction_hash: str = "", + ): + data_data = "\n0\n./injective.exchange.v1beta1.MsgCancelDerivativeOrder" + gas_wanted = int(BASE_GAS + DERIVATIVE_CANCEL_ORDER_GAS + GAS_BUFFER) + gas_amount_scaled = self.order_cancelation_gas_estimate * Decimal("1e18") + gas_amount = CosmosCoin(denom="inj", amount=str(int(gas_amount_scaled))) + gas_fee = GasFee(gas_limit=gas_wanted, payer="") + gas_fee.amount.append(gas_amount) + messages_data = [ + { + "type": "/injective.exchange.v1beta1.MsgCancelDerivativeOrder", + "value": { + "market_id": self.market_id, + "order_hash": order_hash, + "sender": "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r", # noqa: mock + "subaccount_id": self.sub_account_id, + }, + } + ] + data = TxDetailData( + hash=transaction_hash, + data=data_data.encode(), + gas_wanted=gas_wanted, + gas_used=int(gas_wanted * Decimal("0.9")), + gas_fee=gas_fee, + code=0, + block_unix_timestamp=int(timestamp * 1e3), + messages=json.dumps(messages_data).encode(), + ) + self.injective_async_client_mock.get_tx_by_hash.return_value = GetTxByTxHashResponse(data=data) + + def configure_creation_transaction_stream_event( + self, timestamp: float, transaction_hash: str, trade_type: TradeType = TradeType.BUY + ): + """ + block_number: 29339622 + block_timestamp: "2023-03-23 12:32:36.4 +0000 UTC" + hash: "0xfdb58d83b16caf9a64e8818b31eb27f1cadebd2590c98178e4452ed255fd10de" # noqa: mock + messages: "[{\"type\":\"/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder\",\"value\":{\"order\":{\"margin\":\"8000000.000000000000000000\",\"market_id\":\"0x9b9980167ecc3645ff1a5517886652d94a0825e54a77d2057cbbe3ebee015963\",\"order_info\":{\"fee_recipient\":\"inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh\",\"price\":\"4000000.000000000000000000\",\"quantity\":\"2.000000000000000000\",\"subaccount_id\":\"0x72b52e007d01cc5ac36349288f24ce1bd912cedf000000000000000000000000\"},\"order_type\":\"BUY\",\"trigger_price\":\"0.000000000000000000\"},\"sender\":\"inj1w26juqraq8x94smrfy5g7fxwr0v39nkluxrq07\"}}]" # noqa: documentation + tx_number: 185354499 + """ + message = [ + { + "type": "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder", + "value": { + "order": { + "margin": "8000000.000000000000000000", + "market_id": self.market_id, + "order_info": { + "fee_recipient": "inj1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3t5qxqh", # noqa: mock + "price": "4000000.000000000000000000", + "quantity": "2.000000000000000000", + "subaccount_id": self.sub_account_id, + }, + "order_type": "BUY" if trade_type == TradeType.BUY else "SELL", + "trigger_price": "0.000000000000000000", + }, + "sender": "inj1w26juqraq8x94smrfy5g7fxwr0v39nkluxrq07", # noqa: mock + }, + }, + ] + transaction_event = StreamTxsResponse( + block_number=29339622, + block_timestamp=f"{pd.Timestamp.utcfromtimestamp(timestamp / 1e3).strftime('%Y-%m-%d %H:%M:%S.%f')} +0000 UTC", + hash=transaction_hash, + messages=json.dumps(message), + tx_number=185354499, + ) + self.injective_async_client_mock.stream_txs.return_value.add(transaction_event) + + def configure_cancelation_transaction_stream_event(self, timestamp: float, transaction_hash: str, order_hash: str): + """ + block_number: 29339532 + block_timestamp: "2023-03-23 12:30:56.689 +0000 UTC" + hash: "0xac84bc1734e49b0a1a2e7e5b3eb020ef5ee16429fbd7b9012b9a3b3c8e5d27a5" # noqa: mock + messages: "[{\"type\":\"/injective.exchange.v1beta1.MsgCancelDerivativeOrder\",\"value\":{\"market_id\":\"0x9b9980167ecc3645ff1a5517886652d94a0825e54a77d2057cbbe3ebee015963\",\"order_hash\":\"0x9118645b214027341d1f5be3af9edd5dc25bd12505e30e10f1323dd6e4532976\",\"order_mask\":1,\"sender\":\"inj1w26juqraq8x94smrfy5g7fxwr0v39nkluxrq07\",\"subaccount_id\":\"0x72b52e007d01cc5ac36349288f24ce1bd912cedf000000000000000000000000\"}}]" # noqa: documentation + tx_number: 185353585 + """ + message = [ + { + "type": "/injective.exchange.v1beta1.MsgCancelDerivativeOrder", + "value": { + "market_id": "0x9b9980167ecc3645ff1a5517886652d94a0825e54a77d2057cbbe3ebee015963", # noqa: mock + "order_hash": "0x9118645b214027341d1f5be3af9edd5dc25bd12505e30e10f1323dd6e4532976", # noqa: mock + "order_mask": 1, + "sender": "inj1w26juqraq8x94smrfy5g7fxwr0v39nkluxrq07", # noqa: mock + "subaccount_id": "0x72b52e007d01cc5ac36349288f24ce1bd912cedf000000000000000000000000", # noqa: mock + }, + } + ] + transaction_event = StreamTxsResponse( + block_number=29339532, + block_timestamp=f"{pd.Timestamp.utcfromtimestamp(timestamp / 1e3).strftime('%Y-%m-%d %H:%M:%S.%f')} +0000 UTC", + hash=transaction_hash, + messages=json.dumps(message), + tx_number=185353585, + ) + self.injective_async_client_mock.stream_txs.return_value.add(transaction_event) + + def configure_active_derivative_markets_response(self, timestamp: float): + custom_derivative_market_info = self._get_derivative_market_info( + market_id=self.market_id, base_token=self.base + ) + + inj_derivative_market_info = self._get_derivative_market_info( + market_id=self.inj_market_id, base_token=self.inj_base + ) + perp_markets = MarketsResponse() + perp_markets.markets.append(custom_derivative_market_info) + perp_markets.markets.append(inj_derivative_market_info) + + self.injective_async_client_mock.get_derivative_markets.return_value = perp_markets + + min_spot_price_tick_size = str( + self.min_price_tick_size * Decimal(f"1e{self.quote_decimals - self.base_decimals}")) + min_spot_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) + inj_spot_pair_min_price_tick_size = str(self.min_price_tick_size * Decimal(f"1e{18 - self.base_decimals}")) + inj_spot_pair_min_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) + base_token_meta = SpotTokenMeta( + name="Coin", + address=self.base_coin_address, + symbol=self.base, + decimals=self.base_decimals, + updated_at=int(timestamp * 1e3), + ) + quote_token_meta = SpotTokenMeta( + name="Alpha", + address=self.quote_coin_address, + symbol=self.quote, + decimals=self.quote_decimals, + updated_at=int(timestamp * 1e3), + ) + inj_token_meta = SpotTokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + symbol=self.inj_base, + decimals=18, + updated_at=int(timestamp * 1e3), + ) + custom_spot_market_info = SpotMarketInfo( + market_id=self.market_id, + market_status="active", + ticker=f"{self.base}/{self.quote}", + base_denom=self.base_denom, + base_token_meta=base_token_meta, + quote_denom=self.quote_denom, + quote_token_meta=quote_token_meta, + maker_fee_rate=str(self.maker_fee_rate), + taker_fee_rate=str(self.taker_fee_rate), + service_provider_fee="0.4", + min_price_tick_size=min_spot_price_tick_size, + min_quantity_tick_size=min_spot_quantity_tick_size, + ) + inj_spot_market_info = SpotMarketInfo( + market_id=self.inj_market_id, + market_status="active", + ticker=f"{self.inj_base}/{self.quote}", + base_denom="inj", + base_token_meta=inj_token_meta, + quote_denom=self.quote_denom, + quote_token_meta=quote_token_meta, + maker_fee_rate=str(self.maker_fee_rate), + taker_fee_rate=str(self.taker_fee_rate), + service_provider_fee="0.4", + min_price_tick_size=inj_spot_pair_min_price_tick_size, + min_quantity_tick_size=inj_spot_pair_min_quantity_tick_size, + ) + spot_markets = SpotMarketsResponse() + spot_markets.markets.append(custom_spot_market_info) + spot_markets.markets.append(inj_spot_market_info) + + self.injective_async_client_mock.get_spot_markets.return_value = spot_markets + + def configure_get_derivative_positions_response( + self, + main_position_size: Decimal, + main_position_price: Decimal, + main_position_mark_price: Decimal, + main_position_side: PositionSide, + main_position_leverage: Decimal, + inj_position_size: Decimal, + inj_position_price: Decimal, + inj_position_mark_price: Decimal, + inj_position_side: PositionSide, + inj_position_leverage: Decimal, + ): + """Mocks a response for a fetch-positions call. + + Allows to set one arbitrary trading-pair position and one positions for INJ/{QUOTE}. If the size of + either of those two positions is zero, the position is not added to the response.""" + positions = PositionsResponse() + + if main_position_size != 0: + margin = ( + main_position_price * Decimal(f"1e{self.quote_decimals}") + / main_position_leverage + * abs(main_position_size) + ) + positions.positions.append( + DerivativePosition( + ticker=f"{self.base}/{self.quote} PERP", + market_id=self.market_id, + subaccount_id=self.sub_account_id, + direction="long" if main_position_side == PositionSide.LONG else "short", + quantity=str(abs(main_position_size)), + entry_price=str(main_position_price * Decimal(f"1e{self.quote_decimals}")), + margin=str(margin), + liquidation_price="0", + mark_price=str(main_position_mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + aggregate_reduce_only_quantity="0", + updated_at=1680511486496, + created_at=-62135596800000, + ) + ) + if inj_position_size != 0: + margin = ( + inj_position_price * Decimal(f"1e{self.quote_decimals}") + / inj_position_leverage + * abs(inj_position_size) + ) + positions.positions.append( + DerivativePosition( + ticker=f"{self.inj_base}/{self.quote} PERP", + market_id=self.inj_market_id, + subaccount_id=self.sub_account_id, + direction="long" if inj_position_side == PositionSide.LONG else "short", + quantity=str(abs(inj_position_size)), + entry_price=str(inj_position_price * Decimal(f"1e{self.quote_decimals}")), + margin=str(margin), + liquidation_price="0", + mark_price=str(inj_position_mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + aggregate_reduce_only_quantity="0", + updated_at=1680511486496, + created_at=-62135596800000, + ) + ) + + self.injective_async_client_mock.get_derivative_positions.return_value = positions + + def configure_position_event( + self, + size: Decimal, + side: PositionSide, + unrealized_pnl: Decimal, + entry_price: Decimal, + leverage: Decimal, + ): + mark_price = 1 / ((1 / entry_price) - (unrealized_pnl / size)) + margin = entry_price / leverage * size + position = DerivativePosition( + ticker=f"{self.base}/{self.quote} PERP", + market_id=self.market_id, + direction="long" if side == PositionSide.LONG else "short", + subaccount_id=self.sub_account_id, + quantity=str(size), + mark_price=str(mark_price * Decimal(f"1e{self.oracle_scale_factor}")), + entry_price=str(entry_price * Decimal(f"1e{self.quote_decimals}")), + margin=str(margin * Decimal(f"1e{self.quote_decimals}")), + aggregate_reduce_only_quantity="0", + updated_at=1680511486496, + created_at=-62135596800000, + ) + position_event = StreamPositionsResponse(position=position) + self.injective_async_client_mock.stream_derivative_positions.return_value.add(position_event) + + def _get_derivative_market_info( + self, + market_id: str, + base_token: str, + next_funding_time: float = 123123123 + ): + quote_token_meta = TokenMeta( + name="Alpha", + address=self.quote_coin_address, + symbol=self.quote, + decimals=self.quote_decimals, + updated_at=int(self.initial_timestamp * 1e3), + ) + min_perpetual_price_tick_size = str(self.min_price_tick_size * Decimal(f"1e{self.quote_decimals}")) + min_perpetual_quantity_tick_size = str(self.min_quantity_tick_size) + perpetual_market_info = PerpetualMarketInfo( + hourly_funding_rate_cap="0.0000625", + hourly_interest_rate="0.00000416666", + next_funding_timestamp=int(next_funding_time * 1e3), + funding_interval=3600, + ) + perpetual_market_funding = PerpetualMarketFunding( + cumulative_funding="6749828879.286921884648585187", + cumulative_price="1.502338165156193724", + last_timestamp=1677660809, + ) + derivative_market_info = DerivativeMarketInfo( + market_id=market_id, + market_status="active", + ticker=f"{base_token}/{self.quote} PERP", + oracle_base=base_token, + oracle_quote=self.quote, + oracle_type="bandibc", + oracle_scale_factor=self.oracle_scale_factor, + initial_margin_ratio="0.095", + maintenance_margin_ratio="0.05", + quote_denom=self.quote_coin_address, + quote_token_meta=quote_token_meta, + maker_fee_rate=str(self.maker_fee_rate), + taker_fee_rate=str(self.taker_fee_rate), + service_provider_fee="0.4", + is_perpetual=True, + min_price_tick_size=min_perpetual_price_tick_size, + min_quantity_tick_size=min_perpetual_quantity_tick_size, + perpetual_market_info=perpetual_market_info, + perpetual_market_funding=perpetual_market_funding, + ) + return derivative_market_info + + def configure_fetch_last_fee_payment_response( + self, amount: Decimal, funding_rate: Decimal, timestamp: float + ): + funding_payments_response = FundingPaymentsResponse() + funding_payment = FundingPayment( + market_id=self.market_id, + subaccount_id=self.sub_account_id, + amount=str(amount * Decimal(f"1e{self.quote_decimals}")), + timestamp=int(timestamp * 1e3), + ) + funding_payments_response.payments.append(funding_payment) + self.injective_async_client_mock.get_funding_payments.return_value = funding_payments_response + self.configure_get_funding_rates_response(funding_rate=funding_rate, timestamp=timestamp) + + def configure_get_funding_rates_response(self, funding_rate: Decimal, timestamp: float): + funding_rate = FundingRate( + market_id=self.exchange_trading_pair, + rate=str(funding_rate), + timestamp=int(timestamp * 1e3), + ) + funding_rates_response = FundingRatesResponse() + funding_rates_response.funding_rates.append(funding_rate) + self.injective_async_client_mock.get_funding_rates.return_value = funding_rates_response diff --git a/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py new file mode 100644 index 0000000..448b84c --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_perp/data_sources/injective_perpetual/test_injective_perpetual_api_data_source.py @@ -0,0 +1,1129 @@ +import asyncio +import unittest +from decimal import Decimal +from test.hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_mock_utils import ( + InjectivePerpetualClientMock, +) +from typing import Awaitable, List +from unittest.mock import AsyncMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source import ( + InjectivePerpetualAPIDataSource, +) +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, PositionSide, TradeType +from hummingbot.core.data_type.funding_info import FundingInfoUpdate +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, MakerTakerExchangeFeeRates, TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + AccountEvent, + BalanceUpdateEvent, + MarketEvent, + OrderBookDataSourceEvent, + PositionUpdateEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class MockExchange(ExchangeBase): + pass + + +class InjectivePerpetualAPIDataSourceTest(unittest.TestCase): + base: str + quote: str + trading_pair: str + inj_trading_pair: str + sub_account_id: str + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base = "COIN" + cls.quote = "ALPHA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.inj_trading_pair = combine_to_hb_trading_pair(base="INJ", quote=cls.quote) + cls.sub_account_id = "0x72B52e007d01cc5aC36349288F24CE1Bd912CEDf000000000000000000000000" # noqa: mock + + def setUp(self) -> None: + super().setUp() + self.initial_timestamp = 1669100347689 + self.injective_async_client_mock = InjectivePerpetualClientMock( + initial_timestamp=self.initial_timestamp, + sub_account_id=self.sub_account_id, + base=self.base, + quote=self.quote, + ) + self.injective_async_client_mock.start() + + client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) + + self.connector = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.tracker = GatewayOrderTracker(connector=self.connector) + connector_spec = { + "chain": "injective", + "network": "mainnet", + "wallet_address": self.sub_account_id, + } + self.data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + self.data_source.gateway_order_tracker = self.tracker + + self.trades_logger = EventLogger() + self.order_updates_logger = EventLogger() + self.trade_updates_logger = EventLogger() + self.snapshots_logger = EventLogger() + self.balance_logger = EventLogger() + self.funding_info_logger = EventLogger() + self.position_event_logger = EventLogger() + + self.data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=self.balance_logger) + self.data_source.add_listener(event_tag=AccountEvent.PositionUpdate, listener=self.position_event_logger) + self.data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=self.order_updates_logger) + self.data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=self.trade_updates_logger) + self.data_source.add_listener(event_tag=MarketEvent.FundingInfo, listener=self.funding_info_logger) + self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=self.trades_logger) + self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=self.snapshots_logger) + + self.async_run_with_timeout(coro=self.data_source.start()) + + @staticmethod + def async_run_with_timeout(coro: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coro, timeout)) + return ret + + def tearDown(self) -> None: + self.injective_async_client_mock.stop() + self.async_run_with_timeout(coro=self.data_source.stop()) + super().tearDown() + + def test_place_order(self): + expected_exchange_order_id = "someEOID" + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + self.injective_async_client_mock.configure_place_order_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + exchange_order_id=expected_exchange_order_id, + trade_type=TradeType.BUY, + price=Decimal("10"), + size=Decimal("2"), + ) + order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("2"), + ) + exchange_order_id, misc_updates = self.async_run_with_timeout(coro=self.data_source.place_order(order=order)) + + self.assertEqual(expected_exchange_order_id, exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, misc_updates) + + def test_batch_order_create(self): + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + buy_expected_exchange_order_id = ( + "0x7df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc8" # noqa: mock + ) + sell_expected_exchange_order_id = ( + "0x8df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc9" # noqa: mock + ) + buy_order_to_create = GatewayInFlightOrder( + client_order_id="someCOIDCancelCreate", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("2"), + exchange_order_id=buy_expected_exchange_order_id, + ) + sell_order_to_create = GatewayInFlightOrder( + client_order_id="someCOIDCancelCreate", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("11"), + amount=Decimal("3"), + exchange_order_id=sell_expected_exchange_order_id, + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + self.injective_async_client_mock.configure_batch_order_create_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + created_orders=orders_to_create, + ) + + result: List[PlaceOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_create(orders_to_create=orders_to_create) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(buy_expected_exchange_order_id, result[0].exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(sell_expected_exchange_order_id, result[1].exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, result[1].misc_updates) + + def test_cancel_order(self): + creation_transaction_hash = "0x8f6g4552091a69125d5dfcb7b8c2659029395ceg" # noqa: mock + expected_client_order_id = "someCOID" + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + expected_exchange_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + order = GatewayInFlightOrder( + client_order_id=expected_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10"), + amount=Decimal("1"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash, + ) + order.order_fills[creation_transaction_hash] = None # to prevent requesting creation transaction + self.injective_async_client_mock.configure_cancel_order_response( + timestamp=self.initial_timestamp, transaction_hash=expected_transaction_hash + ) + self.injective_async_client_mock.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=self.initial_timestamp, + in_flight_order=order, + order_hash=expected_exchange_order_id, + is_canceled=True, + ) + cancelation_success, misc_updates = self.async_run_with_timeout(coro=self.data_source.cancel_order(order=order)) + + self.assertTrue(cancelation_success) + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, misc_updates) + + self.injective_async_client_mock.run_until_all_items_delivered() + + def test_batch_order_cancel(self): + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + buy_expected_exchange_order_id = ( + "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + ) + sell_expected_exchange_order_id = ( + "0x7df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc8" # noqa: mock + ) + creation_transaction_hash_for_cancel = "0x8f6g4552091a69125d5dfcb7b8c2659029395ceg" # noqa: mock + buy_order_to_cancel = GatewayInFlightOrder( + client_order_id="someCOIDCancel", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10"), + amount=Decimal("1"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=buy_expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash_for_cancel, + ) + sell_order_to_cancel = GatewayInFlightOrder( + client_order_id="someCOIDCancel", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("11"), + amount=Decimal("2"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=sell_expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash_for_cancel, + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=buy_order_to_cancel) + self.data_source.gateway_order_tracker.start_tracking_order(order=sell_order_to_cancel) + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + self.injective_async_client_mock.configure_batch_order_cancel_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + canceled_orders=orders_to_cancel, + ) + + result: List[CancelOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_cancel(orders_to_cancel=orders_to_cancel) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(buy_order_to_cancel.client_order_id, result[0].client_order_id) + self.assertIsNone(result[0].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(sell_order_to_cancel.client_order_id, result[1].client_order_id) + self.assertIsNone(result[1].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, result[1].misc_updates) + + def test_get_trading_rules(self): + trading_rules = self.async_run_with_timeout(coro=self.data_source.get_trading_rules()) + + self.assertEqual(2, len(trading_rules)) + self.assertIn(self.trading_pair, trading_rules) + self.assertIn(self.inj_trading_pair, trading_rules) + + trading_rule: TradingRule = trading_rules[self.trading_pair] + + self.assertEqual(self.trading_pair, trading_rule.trading_pair) + self.assertEqual(Decimal("0.00001"), trading_rule.min_price_increment) + self.assertEqual(Decimal("0.00001"), trading_rule.min_quote_amount_increment) + self.assertEqual(Decimal("0.001"), trading_rule.min_base_amount_increment) + + def test_get_symbol_map(self): + symbol_map = self.async_run_with_timeout(coro=self.data_source.get_symbol_map()) + + self.assertIsInstance(symbol_map, bidict) + self.assertEqual(2, len(symbol_map)) + self.assertIn(self.injective_async_client_mock.market_id, symbol_map) + self.assertIn(self.trading_pair, symbol_map.inverse) + self.assertIn(self.inj_trading_pair, symbol_map.inverse) + + def test_get_last_traded_price(self): + target_price = Decimal("1.157") + target_maker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + self.injective_async_client_mock.configure_perp_trades_response_to_request_without_exchange_order_id( + timestamp=self.initial_timestamp, + price=target_price, + size=Decimal("0.001"), + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + ) + price = self.async_run_with_timeout(coro=self.data_source.get_last_traded_price(trading_pair=self.trading_pair)) + + self.assertEqual(target_price, price) + + def test_get_order_book_snapshot(self): + self.injective_async_client_mock.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + order_book_snapshot: OrderBookMessage = self.async_run_with_timeout( + coro=self.data_source.get_order_book_snapshot(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.initial_timestamp, order_book_snapshot.timestamp) + self.assertEqual(2, len(order_book_snapshot.bids)) + self.assertEqual(9, order_book_snapshot.bids[0].price) + self.assertEqual(1, order_book_snapshot.bids[0].amount) + self.assertEqual(1, len(order_book_snapshot.asks)) + self.assertEqual(11, order_book_snapshot.asks[0].price) + self.assertEqual(3, order_book_snapshot.asks[0].amount) + + def test_delivers_trade_events(self): + target_price = Decimal("1.157") + target_size = Decimal("0.001") + target_maker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + target_exchange_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + target_trade_id = "19889401_someTradeId" + self.injective_async_client_mock.configure_trade_stream_event( + timestamp=self.initial_timestamp, + price=target_price, + size=target_size, + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + exchange_order_id=target_exchange_order_id, + taker_trade_id=target_trade_id, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.trades_logger.event_log)) + self.assertEqual(2, len(self.trade_updates_logger.event_log)) + + first_trade_event: OrderBookMessage = self.trades_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, first_trade_event.timestamp) + self.assertEqual(self.trading_pair, first_trade_event.content["trading_pair"]) + self.assertEqual(TradeType.SELL, first_trade_event.content["trade_type"]) + self.assertEqual(target_price, first_trade_event.content["price"]) + self.assertEqual(target_size, first_trade_event.content["amount"]) + self.assertFalse(first_trade_event.content["is_taker"]) + + second_trade_event: OrderBookMessage = self.trades_logger.event_log[1] + + self.assertEqual(self.initial_timestamp, second_trade_event.timestamp) + self.assertEqual(self.trading_pair, second_trade_event.content["trading_pair"]) + self.assertEqual(TradeType.BUY, second_trade_event.content["trade_type"]) + self.assertEqual(target_price, second_trade_event.content["price"]) + self.assertEqual(target_size, second_trade_event.content["amount"]) + self.assertTrue(second_trade_event.content["is_taker"]) + + first_trade_update: TradeUpdate = self.trade_updates_logger.event_log[0] + + self.assertEqual(self.trading_pair, first_trade_update.trading_pair) + self.assertEqual(self.initial_timestamp, first_trade_update.fill_timestamp) + self.assertEqual(target_price, first_trade_update.fill_price) + self.assertEqual(target_size, first_trade_update.fill_base_amount) + self.assertEqual(target_price * target_size, first_trade_update.fill_quote_amount) + self.assertEqual(target_maker_fee, first_trade_update.fee) + + second_order_event: TradeUpdate = self.trade_updates_logger.event_log[1] + + self.assertEqual(target_trade_id, second_order_event.trade_id) + self.assertEqual(target_exchange_order_id, second_order_event.exchange_order_id) + self.assertEqual(self.trading_pair, second_order_event.trading_pair) + self.assertEqual(self.initial_timestamp, second_order_event.fill_timestamp) + self.assertEqual(target_price, second_order_event.fill_price) + self.assertEqual(target_size, second_order_event.fill_base_amount) + self.assertEqual(target_price * target_size, second_order_event.fill_quote_amount) + self.assertEqual(target_taker_fee, second_order_event.fee) + + def test_delivers_order_created_events(self): + target_order_id = "someOrderHash" + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="buy_po", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="buy", + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[0] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.OPEN, order_event.new_state) + + target_order_id = "anotherOrderHash" + target_price = Decimal("50") + target_size = Decimal("1") + order = GatewayInFlightOrder( + client_order_id="someOtherOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="sell", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="sell", + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.OPEN, order_event.new_state) + + def test_delivers_order_fully_filled_events(self): + target_order_id = "someOrderHash" + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="filled", + execution_type="limit", + order_type="buy", + price=target_price, + size=target_size, + filled_size=target_size, + direction="buy", + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.FILLED, order_event.new_state) + + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="filled", + execution_type="limit", + order_type="sell_po", + price=target_price, + size=target_size, + filled_size=target_size, + direction="sell", + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(4, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[3] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.FILLED, order_event.new_state) + + def test_delivers_order_canceled_events(self): + target_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="canceled", + execution_type="limit", + order_type="buy", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="buy", + leverage=Decimal("3"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.CANCELED, order_event.new_state) + + def test_delivers_order_book_snapshots(self): + self.injective_async_client_mock.configure_orderbook_snapshot_stream_event( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.snapshots_logger.event_log)) + + snapshot_event: OrderBookMessage = self.snapshots_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, snapshot_event.timestamp) + self.assertEqual(2, len(snapshot_event.bids)) + self.assertEqual(9, snapshot_event.bids[0].price) + self.assertEqual(1, snapshot_event.bids[0].amount) + self.assertEqual(1, len(snapshot_event.asks)) + self.assertEqual(11, snapshot_event.asks[0].price) + self.assertEqual(3, snapshot_event.asks[0].amount) + + def test_get_account_balances_using_default_account(self): + base_bank_balance = Decimal("75") + base_total_balance = Decimal("10") + base_available_balance = Decimal("9") + quote_total_balance = Decimal("200") + quote_available_balance = Decimal("150") + expected_base_total_balance = base_bank_balance + base_total_balance + expected_base_available_balance = base_bank_balance + base_available_balance + self.injective_async_client_mock.configure_get_account_balances_response( + base_bank_balance=base_bank_balance, + quote_bank_balance=Decimal("0"), + base_total_balance=base_total_balance, + base_available_balance=base_available_balance, + quote_total_balance=quote_total_balance, + quote_available_balance=quote_available_balance, + ) + + sub_account_balances = self.async_run_with_timeout(coro=self.data_source.get_account_balances()) + + self.assertEqual(expected_base_total_balance, sub_account_balances[self.base]["total_balance"]) + self.assertEqual(expected_base_available_balance, sub_account_balances[self.base]["available_balance"]) + self.assertEqual(quote_total_balance, sub_account_balances[self.quote]["total_balance"]) + self.assertEqual(quote_available_balance, sub_account_balances[self.quote]["available_balance"]) + + def test_get_account_balances_using_non_default_account(self): + sub_account_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + connector_spec = { + "chain": "injective", + "network": "mainnet", + "wallet_address": sub_account_id, + } + data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=ClientConfigAdapter(hb_config=ClientConfigMap()), + ) + data_source.gateway_order_tracker = self.tracker + + self.async_run_with_timeout(coro=data_source.start()) + + base_bank_balance = Decimal("75") + base_total_balance = Decimal("10") + base_available_balance = Decimal("9") + quote_total_balance = Decimal("200") + quote_available_balance = Decimal("150") + self.injective_async_client_mock.configure_get_account_balances_response( + base_bank_balance=base_bank_balance, + quote_bank_balance=Decimal("0"), + base_total_balance=base_total_balance, + base_available_balance=base_available_balance, + quote_total_balance=quote_total_balance, + quote_available_balance=quote_available_balance, + sub_account_id=sub_account_id, + ) + + sub_account_balances = self.async_run_with_timeout(coro=data_source.get_account_balances()) + + self.assertEqual(base_total_balance, sub_account_balances[self.base]["total_balance"]) + self.assertEqual(base_available_balance, sub_account_balances[self.base]["available_balance"]) + self.assertEqual(quote_total_balance, sub_account_balances[self.quote]["total_balance"]) + self.assertEqual(quote_available_balance, sub_account_balances[self.quote]["available_balance"]) + + def test_get_order_status_update_success(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.injective_async_client_mock.configure_get_historical_perp_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="booked", + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + leverage=Decimal("1"), + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.OPEN, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) + self.assertIn("creation_transaction_hash", status_update.misc_updates) + self.assertEqual(creation_transaction_hash, status_update.misc_updates["creation_transaction_hash"]) + + def test_get_all_order_fills_no_fills(self): + self.injective_async_client_mock.configure_empty_perp_trades_responses() + target_order_id = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + self.injective_async_client_mock.configure_get_historical_perp_orders_response( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="sell", + price=Decimal("10"), + size=Decimal("2"), + filled_size=Decimal("0"), + direction="sell", + leverage=Decimal("1"), + ) + in_flight_order = GatewayInFlightOrder( + client_order_id="someOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=Decimal("10"), + amount=Decimal("2"), + exchange_order_id=target_order_id, + ) + + trade_updates = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(0, len(trade_updates)) + + def test_get_all_order_fills(self): + target_client_order_id = "someOrderId" + target_exchange_order_id = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + target_trade_id = "someTradeHash" + target_price = Decimal("10") + target_size = Decimal("2") + target_trade_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.01"))]) + target_partial_fill_size = target_size / 2 + target_fill_ts = self.initial_timestamp + 10 + self.injective_async_client_mock.configure_get_historical_perp_orders_response( + timestamp=self.initial_timestamp, + order_hash=target_exchange_order_id, + state="partial_filled", + execution_type="limit", + order_type="sell", + price=target_price, + size=target_size, + filled_size=target_partial_fill_size, + direction="sell", + leverage=Decimal("1"), + ) + self.injective_async_client_mock.configure_trades_response_with_exchange_order_id( + timestamp=target_fill_ts, + exchange_order_id=target_exchange_order_id, + price=target_price, + size=target_partial_fill_size, + fee=target_trade_fee, + trade_id=target_trade_id, + ) + in_flight_order = GatewayInFlightOrder( + client_order_id=target_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=target_price, + amount=target_size, + exchange_order_id=target_exchange_order_id, + ) + + trade_updates: List[TradeUpdate] = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(1, len(trade_updates)) + + trade_update = trade_updates[0] + + self.assertEqual(target_trade_id, trade_update.trade_id) + self.assertEqual(target_client_order_id, trade_update.client_order_id) + self.assertEqual(target_exchange_order_id, trade_update.exchange_order_id) + self.assertEqual(self.trading_pair, trade_update.trading_pair) + self.assertEqual(target_fill_ts, trade_update.fill_timestamp) + self.assertEqual(target_price, trade_update.fill_price) + self.assertEqual(target_partial_fill_size, trade_update.fill_base_amount) + self.assertEqual(target_partial_fill_size * target_price, trade_update.fill_quote_amount) + self.assertEqual(target_trade_fee, trade_update.fee) + + def test_check_network_status(self): + self.injective_async_client_mock.configure_check_network_failure() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, status) + + self.injective_async_client_mock.configure_check_network_success() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.CONNECTED, status) + + def test_get_trading_fees(self): + all_trading_fees = self.async_run_with_timeout(coro=self.data_source.get_trading_fees()) + + self.assertIn(self.trading_pair, all_trading_fees) + + pair_trading_fees: MakerTakerExchangeFeeRates = all_trading_fees[self.trading_pair] + + service_provider_rebate = Decimal("1") - self.injective_async_client_mock.service_provider_fee + expected_maker_fee = self.injective_async_client_mock.maker_fee_rate * service_provider_rebate + expected_taker_fee = self.injective_async_client_mock.taker_fee_rate * service_provider_rebate + self.assertEqual(expected_maker_fee, pair_trading_fees.maker) + self.assertEqual(expected_taker_fee, pair_trading_fees.taker) + + def test_delivers_balance_events(self): + target_total_balance = Decimal("20") + target_available_balance = Decimal("19") + self.injective_async_client_mock.configure_account_quote_balance_stream_event( + timestamp=self.initial_timestamp, + total_balance=target_total_balance, + available_balance=target_available_balance, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.balance_logger.event_log)) + + balance_event: BalanceUpdateEvent = self.balance_logger.event_log[0] + + self.assertEqual(self.quote, balance_event.asset_name) + self.assertEqual(target_total_balance, balance_event.total_balance) + self.assertEqual(target_available_balance, balance_event.available_balance) + + def test_delivers_bank_balance_events(self): + target_available_balance = Decimal("20") + self.injective_async_client_mock.configure_bank_account_portfolio_balance_stream_event( + token=self.quote, amount=target_available_balance + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.balance_logger.event_log)) + + balance_event: BalanceUpdateEvent = self.balance_logger.event_log[0] + + self.assertEqual(self.quote, balance_event.asset_name) + self.assertEqual(target_available_balance, balance_event.available_balance) + + def test_non_default_account_ignores_bank_balance_events(self): + sub_account_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + connector_spec = { + "chain": "injective", + "network": "mainnet", + "wallet_address": sub_account_id, + } + data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=ClientConfigAdapter(hb_config=ClientConfigMap()), + ) + data_source.gateway_order_tracker = self.tracker + self.data_source.remove_listener(event_tag=AccountEvent.BalanceEvent, listener=self.balance_logger) + data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=self.balance_logger) + + self.async_run_with_timeout(coro=data_source.start()) + + self.injective_async_client_mock.configure_bank_account_portfolio_balance_stream_event( + token=self.quote, amount=Decimal("20") + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(0, len(self.balance_logger.event_log)) + + def test_delivers_funding_info_events(self): + target_index_price = Decimal("100") + target_mark_price = Decimal("101") + next_funding_time = 123123123 + target_rate = Decimal("0.0001") + self.injective_async_client_mock.configure_funding_info_stream_event( + index_price=target_index_price, + mark_price=target_mark_price, + next_funding_time=next_funding_time, + funding_rate=target_rate, + ) + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.funding_info_logger.event_log)) + + funding_info_event: FundingInfoUpdate = self.funding_info_logger.event_log[0] + + self.assertEqual(self.trading_pair, funding_info_event.trading_pair) + self.assertEqual(target_index_price, funding_info_event.index_price) + self.assertEqual(target_mark_price, funding_info_event.mark_price) + self.assertEqual(next_funding_time, funding_info_event.next_funding_utc_timestamp) + self.assertEqual(target_rate, funding_info_event.rate) + + def test_parses_transaction_event_for_order_creation_success(self): + creation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.tracker.start_tracking_order(order=in_flight_order) + self.injective_async_client_mock.configure_creation_transaction_stream_event( + timestamp=self.initial_timestamp + 1, transaction_hash=creation_transaction_hash + ) + self.injective_async_client_mock.configure_get_historical_perp_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="booked", + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[0] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.OPEN, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) + + @patch( + "hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source" + ".InjectivePerpetualAPIDataSource._update_account_address_and_create_order_hash_manager", + new_callable=AsyncMock, + ) + def test_parses_transaction_event_for_order_creation_failure(self, _: AsyncMock): + creation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.tracker.start_tracking_order(order=in_flight_order) + self.injective_async_client_mock.configure_order_status_update_response( + timestamp=self.initial_timestamp, + order=in_flight_order, + creation_transaction_hash=creation_transaction_hash, + is_failed=True, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[1] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp, status_update.update_timestamp) + self.assertEqual(OrderState.FAILED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(0, len(self.trade_updates_logger.event_log)) + + def test_parses_transaction_event_for_order_cancelation(self): + cancelation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash="someHash", + exchange_order_id=target_order_hash, + ) + in_flight_order.order_fills["someHash"] = None # to prevent order creation transaction request + self.tracker.start_tracking_order(order=in_flight_order) + in_flight_order.cancel_tx_hash = cancelation_transaction_hash + self.injective_async_client_mock.configure_cancelation_transaction_stream_event( + timestamp=self.initial_timestamp + 1, + transaction_hash=cancelation_transaction_hash, + order_hash=target_order_hash, + ) + self.injective_async_client_mock.configure_get_historical_perp_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="canceled", + execution_type="limit", + order_type=in_flight_order.trade_type.name.lower(), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + leverage=Decimal("1"), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[1] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.CANCELED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) + + def test_parse_position_event(self): + expected_size = Decimal("1") + expected_side = PositionSide.LONG + expecte_unrealized_pnl = Decimal("2") + expected_entry_price = Decimal("1") + expected_leverage = Decimal("3") + + self.injective_async_client_mock.configure_position_event( + size=expected_size, + side=expected_side, + unrealized_pnl=expecte_unrealized_pnl, + entry_price=expected_entry_price, + leverage=expected_leverage, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.position_event_logger.event_log)) + + position_event: PositionUpdateEvent = self.position_event_logger.event_log[0] + + self.assertEqual(self.trading_pair, position_event.trading_pair) + self.assertEqual(expected_size, position_event.amount) + self.assertEqual(expected_side, position_event.position_side) + self.assertEqual(expecte_unrealized_pnl, position_event.unrealized_pnl) + self.assertEqual(expected_entry_price, position_event.entry_price) + self.assertEqual(expected_leverage, position_event.leverage) + + def test_fetch_positions(self): + main_position_expected_size = Decimal("1") + main_position_expected_price = Decimal("10") + main_position_expected_mark_price = Decimal("20") + main_position_expected_pnl = ( + main_position_expected_size * ( + (1 / main_position_expected_price) - (1 / main_position_expected_mark_price) + ) + ) + main_position_expected_side = PositionSide.LONG + main_position_expected_leverage = Decimal("2") + inj_position_expected_size = Decimal("-2") + inj_position_expected_price = Decimal("20") + inj_position_expected_mark_price = Decimal("25") + inj_position_expected_pnl = ( + inj_position_expected_size * ( + (1 / inj_position_expected_price) - (1 / inj_position_expected_mark_price) + ) + ) + inj_position_expected_side = PositionSide.SHORT + inj_position_expected_leverage = Decimal("3") + + self.injective_async_client_mock.configure_get_derivative_positions_response( + main_position_size=main_position_expected_size, + main_position_price=main_position_expected_price, + main_position_mark_price=main_position_expected_mark_price, + main_position_side=main_position_expected_side, + main_position_leverage=main_position_expected_leverage, + inj_position_size=inj_position_expected_size, + inj_position_price=inj_position_expected_price, + inj_position_mark_price=inj_position_expected_mark_price, + inj_position_side=inj_position_expected_side, + inj_position_leverage=inj_position_expected_leverage, + ) + + positions: List[Position] = self.async_run_with_timeout(coro=self.data_source.fetch_positions()) + + self.assertEqual(2, len(positions)) + + main_position = positions[0] + + self.assertEqual(self.trading_pair, main_position.trading_pair) + self.assertEqual(main_position_expected_side, main_position.position_side) + self.assertEqual(main_position_expected_pnl, main_position.unrealized_pnl) + self.assertEqual(main_position_expected_price, main_position.entry_price) + self.assertEqual(main_position_expected_size, main_position.amount) + self.assertEqual(main_position_expected_leverage, main_position.leverage) + + inj_position = positions[1] + + self.assertEqual(self.inj_trading_pair, inj_position.trading_pair) + self.assertEqual(inj_position_expected_side, inj_position.position_side) + self.assertEqual(inj_position_expected_pnl, inj_position.unrealized_pnl) + self.assertEqual(inj_position_expected_price, inj_position.entry_price) + self.assertEqual(inj_position_expected_size, inj_position.amount) + self.assertEqual(inj_position_expected_leverage, inj_position.leverage) + + def test_get_funding_info(self): + target_index_price = Decimal("100") + target_mark_price = Decimal("101") + next_funding_time = 123123123 + target_rate = Decimal("0.0001") + + self.injective_async_client_mock.configure_get_funding_info_response( + index_price=target_index_price, + mark_price=target_mark_price, + next_funding_time=next_funding_time, + funding_rate=target_rate, + ) + + funding_info = self.async_run_with_timeout( + coro=self.data_source.get_funding_info(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.trading_pair, funding_info.trading_pair) + self.assertEqual(target_index_price, funding_info.index_price) + self.assertEqual(target_mark_price, funding_info.mark_price) + self.assertEqual(next_funding_time, funding_info.next_funding_utc_timestamp) + self.assertEqual(target_rate, funding_info.rate) + + def test_fetch_last_fee_payment(self): + expected_funding_timestamp = self.initial_timestamp + 1000 + expected_funding_rate = Decimal("0.0001") + expected_payment = Decimal("0.02") + + self.injective_async_client_mock.configure_fetch_last_fee_payment_response( + amount=expected_payment, + funding_rate=expected_funding_rate, + timestamp=expected_funding_timestamp, + ) + + timestamp, funding_rate, payment = self.async_run_with_timeout( + coro=self.data_source.fetch_last_fee_payment(trading_pair=self.trading_pair) + ) + + self.assertEqual(expected_funding_timestamp, timestamp) + self.assertEqual(expected_funding_rate, funding_rate) + self.assertEqual(expected_payment, payment) diff --git a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py new file mode 100644 index 0000000..70b4e0a --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp.py @@ -0,0 +1,1845 @@ +import asyncio +import unittest +from decimal import Decimal +from test.hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_mock_utils import ( + InjectivePerpetualClientMock, +) +from typing import Awaitable, Dict, List, Mapping +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.fee_overrides_config_map import init_fee_overrides_config +from hummingbot.client.config.trade_fee_schema_loader import TradeFeeSchemaLoader +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source import ( + InjectivePerpetualAPIDataSource, +) +from hummingbot.connector.gateway.clob_perp.gateway_clob_perp import GatewayCLOBPerp +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class GatewayCLOBPERPTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + base_asset: str + quote_asset: str + trading_pair: str + inj_trading_pair: str + wallet_address: str + clock_tick_size: float + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base_asset, quote=cls.quote_asset) + cls.inj_trading_pair = combine_to_hb_trading_pair(base="INJ", quote=cls.quote_asset) + cls.wallet_address = "someWalletAddress" + cls.clock_tick_size = 1 + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + self.start_timestamp = 1669100347689 + self.clob_data_source_mock = InjectivePerpetualClientMock( + initial_timestamp=self.start_timestamp, + sub_account_id=self.wallet_address, + base=self.base_asset, + quote=self.quote_asset, + ) + self.clob_data_source_mock.start() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + self.exchange = GatewayCLOBPerp( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="injective", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + self.end_timestamp = self.start_timestamp + self.exchange.LONG_POLL_INTERVAL + 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(iterator=self.exchange) + + api_data_source.logger().setLevel(1) + api_data_source.logger().addHandler(self) + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + + self.initialize_event_loggers() + + self.async_run_with_timeout(coroutine=self.exchange.start_network()) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + self.clob_data_source_mock.stop() + self.async_run_with_timeout(coroutine=self.exchange.stop_network()) + super().tearDown() + + @property + def expected_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_exchange_order_id(self) -> str: + return "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + + @property + def expected_transaction_hash(self) -> str: + return "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18" # noqa: mock + + @property + def expected_trade_id(self) -> str: + return "19889401_someTradeId" + + @property + def expected_latest_price(self) -> float: + return 100 + + @property + def expected_trading_rule(self) -> TradingRule: + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=self.clob_data_source_mock.min_quantity_tick_size, + min_price_increment=self.clob_data_source_mock.min_price_tick_size, + min_base_amount_increment=self.clob_data_source_mock.min_quantity_tick_size, + min_quote_amount_increment=self.clob_data_source_mock.min_price_tick_size, + ) + + @property + def expected_order_price(self) -> Decimal: + return Decimal("10_000") + + @property + def expected_order_size(self) -> Decimal: + return Decimal("2") + + @property + def expected_partial_fill_size(self) -> Decimal: + return self.expected_order_size / 2 + + @property + def expected_full_fill_fee(self) -> TradeFeeBase: + expected_fee = self.clob_data_source_mock.maker_fee_rate * self.expected_order_price + return AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=expected_fee)] + ) + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + expected_fee = self.clob_data_source_mock.maker_fee_rate * self.expected_partial_fill_size + return AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=expected_fee)] + ) + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def is_logged_that_starts_with(self, log_level: str, message_starts_with: str): + return any( + record.levelname == log_level and record.getMessage().startswith(message_starts_with) + for record in self.log_records + ) + + @staticmethod + def async_run_with_timeout(coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + self.funding_payment_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger), + (MarketEvent.FundingPaymentCompleted, self.funding_payment_logger), + ] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + @staticmethod + def expected_initial_status_dict() -> Dict[str, bool]: + return { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + "api_data_source_initialized": False, + "funding_info": False, + } + + @staticmethod + def expected_initialized_status_dict() -> Dict[str, bool]: + return { + "symbols_mapping_initialized": True, + "order_books_initialized": True, + "account_balance": True, + "trading_rule_initialized": True, + "user_stream_initialized": True, + "api_data_source_initialized": True, + "funding_info": True, + } + + def place_buy_order( + self, + size: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=size, + order_type=OrderType.LIMIT, + price=price, + position_action=position_action, + ) + return order_id + + def place_sell_order( + self, + size: Decimal = Decimal("100"), + price: Decimal = Decimal("10_000"), + position_action: PositionAction = PositionAction.OPEN, + ): + order_id = self.exchange.sell( + trading_pair=self.trading_pair, + amount=size, + order_type=OrderType.LIMIT, + price=price, + position_action=position_action, + ) + return order_id + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertEqual(self.expected_supported_order_types, supported_types) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayInFlightOrder( + client_order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + position=PositionAction.OPEN, + )) + orders.append(GatewayInFlightOrder( + client_order_id="12", + exchange_order_id="22", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED, + position=PositionAction.OPEN, + )) + orders.append(GatewayInFlightOrder( + client_order_id="13", + exchange_order_id="23", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED, + position=PositionAction.CLOSE, + )) + orders.append(GatewayInFlightOrder( + client_order_id="14", + exchange_order_id="24", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED, + position=PositionAction.CLOSE, + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("11", self.exchange.in_flight_orders) + self.assertNotIn("12", self.exchange.in_flight_orders) + self.assertNotIn("13", self.exchange.in_flight_orders) + self.assertNotIn("14", self.exchange.in_flight_orders) + + def test_all_trading_pairs(self): + self.exchange._set_trading_pair_symbol_map(None) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertEqual(2, len(all_trading_pairs)) + self.assertIn(self.trading_pair, all_trading_pairs) + self.assertIn(self.inj_trading_pair, all_trading_pairs) + + def test_get_last_trade_prices(self): + fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.001"))]) + self.clob_data_source_mock.configure_perp_trades_response_to_request_without_exchange_order_id( + timestamp=self.start_timestamp, + price=Decimal(self.expected_latest_price), + size=Decimal("2"), + maker_fee=fee, + taker_fee=fee, + ) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_check_network_success(self): + self.clob_data_source_mock.configure_check_network_success() + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=2) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + def test_check_network_failure(self): + self.clob_data_source_mock.configure_check_network_failure() + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=2) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, network_status) + + def test_check_network_raises_cancel_exception(self): + self.clob_data_source_mock.configure_check_network_failure(exc=asyncio.CancelledError) + + with self.assertRaises(expected_exception=asyncio.CancelledError): + self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + def test_init_trading_pair_symbol_map(self): + symbol_map = self.async_run_with_timeout(coroutine=self.exchange.trading_pair_symbol_map()) + + self.assertIsInstance(symbol_map, Mapping) + self.assertEqual(2, len(symbol_map)) + self.assertIn(self.clob_data_source_mock.exchange_trading_pair, symbol_map) + self.assertIn(self.trading_pair, symbol_map.inverse) + self.assertIn(self.inj_trading_pair, symbol_map.inverse) + + def test_initial_status_dict(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + exchange = GatewayCLOBPerp( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="cosmos", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + status_dict = exchange.status_dict + + expected_initial_dict = self.expected_initial_status_dict() + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(exchange.ready) + + @patch("hummingbot.core.data_type.order_book_tracker.OrderBookTracker._sleep") + def test_full_initialization_and_de_initialization(self, _: AsyncMock): + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=Decimal("10"), + base_available_balance=Decimal("9"), + quote_total_balance=Decimal("200"), + quote_available_balance=Decimal("150"), + ) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + exchange = GatewayCLOBPerp( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="cosmos", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + self.assertEqual(0, len(exchange.trading_fees)) + + self.async_run_with_timeout(coroutine=exchange.start_network()) + + self.clock.add_iterator(exchange) + self.clock.backtest_til(self.start_timestamp + exchange.SHORT_POLL_INTERVAL) + self.clob_data_source_mock.run_until_all_items_delivered() + + status_dict = exchange.status_dict + + expected_initial_dict = self.expected_initialized_status_dict() + + self.assertEqual(expected_initial_dict, status_dict) + self.assertTrue(exchange.ready) + self.assertNotEqual(0, len(exchange.trading_fees)) + self.assertIn(self.trading_pair, exchange.trading_fees) + + trading_fees_data = exchange.trading_fees[self.trading_pair] + service_provider_rebate = Decimal("1") - self.clob_data_source_mock.service_provider_fee + expected_maker_fee = self.clob_data_source_mock.maker_fee_rate * service_provider_rebate + expected_taker_fee = self.clob_data_source_mock.taker_fee_rate * service_provider_rebate + + self.assertEqual(expected_maker_fee, trading_fees_data.maker) + self.assertEqual(expected_taker_fee, trading_fees_data.taker) + + def test_update_trading_rules(self): + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + trading_rule: TradingRule = self.exchange.trading_rules[self.trading_pair] + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(trading_rule)) + + trading_rule_with_default_values = TradingRule(trading_pair=self.trading_pair) + + # The following element can't be left with the default value because that breaks quantization in Cython + self.assertNotEqual(trading_rule_with_default_values.min_base_amount_increment, + trading_rule.min_base_amount_increment) + self.assertNotEqual(trading_rule_with_default_values.min_price_increment, + trading_rule.min_price_increment) + + def test_create_buy_limit_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.clob_data_source_mock.configure_place_order_response( + timestamp=self.start_timestamp, + transaction_hash=self.expected_transaction_hash, + exchange_order_id=self.expected_exchange_order_id, + trade_type=TradeType.BUY, + price=self.expected_order_price, + size=self.expected_order_size, + ) + + order_id = self.place_buy_order() + self.clob_data_source_mock.run_until_all_items_delivered() + order = self.exchange._order_tracker.active_orders[order_id] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + creation_transaction_hash=self.expected_transaction_hash, + order=order, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000')} to {PositionAction.OPEN.name} a {self.trading_pair} position." + ) + ) + + def test_create_sell_limit_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.clob_data_source_mock.configure_place_order_response( + timestamp=self.start_timestamp, + transaction_hash=self.expected_transaction_hash, + exchange_order_id=self.expected_exchange_order_id, + trade_type=TradeType.BUY, + price=self.expected_order_price, + size=self.expected_order_size, + ) + + order_id = self.place_sell_order() + self.clob_data_source_mock.run_until_all_items_delivered() + order = self.exchange._order_tracker.active_orders[order_id] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + creation_transaction_hash=self.expected_transaction_hash, + order=order, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000')} to {PositionAction.OPEN.name} a {self.trading_pair} position." + ) + ) + + def test_create_order_fails_and_raises_failure_event(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.clob_data_source_mock.configure_place_order_fails_response(exception=RuntimeError("some error")) + + order_id = self.place_buy_order( + size=self.expected_order_size, price=self.expected_order_price + ) + + self.clob_data_source_mock.run_until_place_order_called() + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + expr=self.is_logged_that_starts_with( + log_level="NETWORK", + message_starts_with=( + f"Error submitting buy LIMIT order to {self.exchange.name_cap} for" + ) + ) + ) + self.assertTrue( + expr=self.is_logged( + log_level="INFO", + message=( + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + ) + + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.clob_data_source_mock.configure_place_order_fails_response(exception=RuntimeError("some error")) + + order_id_for_invalid_order = self.place_buy_order( + size=Decimal("0.0001"), price=Decimal("0.0000001") + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + order_id = self.place_buy_order() + self.clob_data_source_mock.run_until_place_order_called() + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.001. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + def test_cancel_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_cancel_order_response( + timestamp=self.start_timestamp, transaction_hash=self.expected_transaction_hash + ) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.clob_data_source_mock.run_until_cancel_order_called() + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + else: + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) + + self.assertEqual(self.expected_transaction_hash, order.cancel_tx_hash) + + def test_cancel_order_raises_failure_event_when_request_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_cancel_order_fails_response(exception=RuntimeError("some error")) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="11") + self.clob_data_source_mock.run_until_cancel_order_called() + + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue(any(log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records)) + + def test_cancel_two_orders_with_cancel_all_and_one_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["11"] + + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("12", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["12"] + + self.clob_data_source_mock.configure_one_success_one_failure_order_cancelation_responses( + success_timestamp=self.start_timestamp, + success_transaction_hash=self.expected_transaction_hash, + failure_exception=RuntimeError("some error"), + ) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertIn(CancellationResult(order1.client_order_id, True), cancellation_results) + self.assertIn(CancellationResult(order2.client_order_id, False), cancellation_results) + + # def test_batch_order_cancel(self): + # self.exchange._set_current_timestamp(self.start_timestamp) + # + # self.exchange.start_tracking_order( + # order_id="11", + # exchange_order_id=self.expected_exchange_order_id, + # trading_pair=self.trading_pair, + # trade_type=TradeType.BUY, + # price=self.expected_order_price, + # amount=self.expected_order_size, + # order_type=OrderType.LIMIT, + # ) + # self.exchange.start_tracking_order( + # order_id="12", + # exchange_order_id=self.expected_exchange_order_id, + # trading_pair=self.trading_pair, + # trade_type=TradeType.SELL, + # price=self.expected_order_price, + # amount=self.expected_order_size, + # order_type=OrderType.LIMIT, + # ) + # + # buy_order_to_cancel: InFlightOrder = self.exchange.in_flight_orders["11"] + # sell_order_to_cancel: InFlightOrder = self.exchange.in_flight_orders["12"] + # orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + # + # self.clob_data_source_mock.configure_batch_order_cancel_response( + # timestamp=self.start_timestamp, + # transaction_hash="somehash", + # canceled_orders=orders_to_cancel, + # ) + # + # self.exchange.batch_order_cancel(orders_to_cancel=self.exchange.limit_orders) + # + # self.clob_data_source_mock.run_until_all_items_delivered() + # + # self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + # self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + # self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + # self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + + # def test_batch_order_create(self): + # self.exchange._set_current_timestamp(self.start_timestamp) + # + # buy_order_to_create = LimitOrder( + # client_order_id="", + # trading_pair=self.trading_pair, + # is_buy=True, + # base_currency=self.base_asset, + # quote_currency=self.quote_asset, + # price=Decimal("10"), + # quantity=Decimal("2"), + # ) + # sell_order_to_create = LimitOrder( + # client_order_id="", + # trading_pair=self.trading_pair, + # is_buy=False, + # base_currency=self.base_asset, + # quote_currency=self.quote_asset, + # price=Decimal("11"), + # quantity=Decimal("3"), + # ) + # orders_to_create = [buy_order_to_create, sell_order_to_create] + # + # orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + # + # buy_order_to_create_in_flight = GatewayInFlightOrder( + # client_order_id=orders[0].client_order_id, + # trading_pair=self.trading_pair, + # order_type=OrderType.LIMIT, + # trade_type=TradeType.BUY, + # creation_timestamp=self.start_timestamp, + # price=orders[0].price, + # amount=orders[0].quantity, + # exchange_order_id="someEOID0", + # ) + # sell_order_to_create_in_flight = GatewayInFlightOrder( + # client_order_id=orders[1].client_order_id, + # trading_pair=self.trading_pair, + # order_type=OrderType.LIMIT, + # trade_type=TradeType.SELL, + # creation_timestamp=self.start_timestamp, + # price=orders[1].price, + # amount=orders[1].quantity, + # exchange_order_id="someEOID1", + # ) + # orders_to_create_in_flight = [buy_order_to_create_in_flight, sell_order_to_create_in_flight] + # self.clob_data_source_mock.configure_batch_order_create_response( + # timestamp=self.start_timestamp, + # transaction_hash="somehash", + # created_orders=orders_to_create_in_flight, + # ) + # + # self.assertEqual(2, len(orders)) + # + # self.clob_data_source_mock.run_until_all_items_delivered() + # + # self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + # self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + # + # buy_create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + # self.assertEqual(self.exchange.current_timestamp, buy_create_event.timestamp) + # self.assertEqual(self.trading_pair, buy_create_event.trading_pair) + # self.assertEqual(OrderType.LIMIT, buy_create_event.type) + # self.assertEqual(buy_order_to_create_in_flight.amount, buy_create_event.amount) + # self.assertEqual(buy_order_to_create_in_flight.price, buy_create_event.price) + # self.assertEqual(buy_order_to_create_in_flight.client_order_id, buy_create_event.order_id) + # self.assertEqual(buy_order_to_create_in_flight.exchange_order_id, buy_create_event.exchange_order_id) + # self.assertTrue( + # self.is_logged( + # "INFO", + # f"Created {OrderType.LIMIT.name} {TradeType.BUY.name}" + # f" order {buy_order_to_create_in_flight.client_order_id} for " + # f"{buy_create_event.amount} {self.trading_pair}." + # ) + # ) + + def test_update_balances(self): + expected_base_total_balance = Decimal("100") + expected_base_available_balance = Decimal("90") + expected_quote_total_balance = Decimal("10") + expected_quote_available_balance = Decimal("8") + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=expected_base_total_balance, + base_available_balance=expected_base_available_balance, + quote_total_balance=expected_quote_total_balance, + quote_available_balance=expected_quote_available_balance, + ) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(expected_base_available_balance, available_balances[self.base_asset]) + self.assertEqual(expected_quote_available_balance, available_balances[self.quote_asset]) + self.assertEqual(expected_base_total_balance, total_balances[self.base_asset]) + self.assertEqual(expected_quote_total_balance, total_balances[self.quote_asset]) + + expected_base_total_balance = Decimal("100") + expected_base_available_balance = Decimal("90") + expected_quote_total_balance = Decimal("0") + expected_quote_available_balance = Decimal("0") + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=expected_base_total_balance, + base_available_balance=expected_base_available_balance, + quote_total_balance=expected_quote_total_balance, + quote_available_balance=expected_quote_available_balance, + ) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(expected_base_available_balance, available_balances[self.base_asset]) + self.assertEqual(expected_base_total_balance, total_balances[self.base_asset]) + + def test_update_order_status_when_filled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + creation_transaction_hash=creation_transaction_hash, + order=order, + filled_size=self.expected_order_size, + ) + + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.clob_data_source_mock.run_until_all_items_delivered() + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + + self.assertTrue(order.is_filled) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[-1] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_full_fill_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * order.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_update_order_status_when_canceled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_empty_perp_trades_responses() + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, is_canceled=True + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_update_order_status_when_cancel_transaction_minted(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.cancel_tx_hash = self.expected_transaction_hash + + self.assertEqual(OrderState.PENDING_CREATE, order.current_state) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + order=order, + creation_transaction_hash=creation_transaction_hash, + cancelation_transaction_hash=self.expected_transaction_hash, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(OrderState.CANCELED, order.current_state) + self.assertEqual(self.expected_transaction_hash, order.cancel_tx_hash) + + buy_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual("11", buy_event.order_id) + self.assertEqual(self.expected_exchange_order_id, buy_event.exchange_order_id) + + def test_update_order_status_when_order_has_not_changed(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_empty_perp_trades_responses() + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=Decimal("0"), + ) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + def test_update_order_status_when_request_fails_marks_order_as_not_found(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_empty_perp_trades_responses() + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, is_failed=True + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.order_fills[creation_transaction_hash] = None # tod prevent creation transaction request + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp + 1, + exchange_order_id=order.exchange_order_id, + price=order.price, + size=self.expected_partial_fill_size, + fee=self.expected_partial_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(self.expected_order_price, fill_event.price) + self.assertEqual(self.expected_partial_fill_size, fill_event.amount) + self.assertEqual(self.expected_partial_fill_fee, fill_event.trade_fee) + + def test_update_order_status_when_filled_correctly_processed_even_when_trade_fill_update_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + creation_transaction_hash=creation_transaction_hash, + filled_size=order.amount, + ) + self.clob_data_source_mock.configure_trades_response_fails() + + # Since the trade fill update will fail we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertEqual(0, len(self.order_filled_logger.event_log)) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_update_order_status_when_order_partially_filled_and_cancelled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.order_fills[creation_transaction_hash] = None # to prevent creation transaction request + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=order.exchange_order_id, + price=order.price, + size=self.expected_partial_fill_size, + fee=self.expected_partial_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.assertTrue(order.is_open) + + self.clock.backtest_til(self.start_timestamp + self.clock.tick_size * 1) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + order_partially_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(order.client_order_id, order_partially_filled_event.order_id) + self.assertEqual(order.trading_pair, order_partially_filled_event.trading_pair) + self.assertEqual(order.trade_type, order_partially_filled_event.trade_type) + self.assertEqual(order.order_type, order_partially_filled_event.order_type) + self.assertEqual(self.expected_trade_id, order_partially_filled_event.exchange_trade_id) + self.assertEqual(self.expected_order_price, order_partially_filled_event.price) + self.assertEqual(self.expected_partial_fill_size, order_partially_filled_event.amount) + self.assertEqual(self.expected_partial_fill_fee, order_partially_filled_event.trade_fee) + self.assertEqual(self.exchange.current_timestamp, order_partially_filled_event.timestamp) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=self.expected_partial_fill_size, + is_canceled=True, + ) + + self.async_run_with_timeout(self.exchange._update_order_status(), timeout=2) + + self.assertTrue(order.is_cancelled) + self.assertEqual(OrderState.CANCELED, order.current_state) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order, filled_size=Decimal("0"), + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order, is_canceled=True + ) + self.clob_data_source_mock.run_until_all_items_delivered() + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_user_stream_update_for_order_full_fill(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp, + price=self.expected_order_price, + size=self.expected_order_size, + maker_fee=self.expected_full_fill_fee, + taker_fee=self.expected_full_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_full_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_update_for_partially_cancelled_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order + ) + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp + 1, in_flight_order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp + 1, + price=self.expected_order_price, + size=self.expected_partial_fill_size, + maker_fee=self.expected_partial_fill_fee, + taker_fee=self.expected_partial_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp + 2, + in_flight_order=order, + filled_size=self.expected_partial_fill_size, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + order_created_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, order_created_event.timestamp) + self.assertEqual(order.order_type, order_created_event.type) + self.assertEqual(order.trading_pair, order_created_event.trading_pair) + self.assertEqual(order.amount, order_created_event.amount) + self.assertEqual(order.price, order_created_event.price) + self.assertEqual(order.client_order_id, order_created_event.order_id) + self.assertEqual(order.exchange_order_id, order_created_event.exchange_order_id) + + self.assertTrue(self.is_logged("INFO", order.build_order_created_message())) + + order_partially_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(order.order_type, order_partially_filled_event.order_type) + self.assertEqual(order.trading_pair, order_partially_filled_event.trading_pair) + self.assertEqual(self.expected_trade_id, order_partially_filled_event.exchange_trade_id) + self.assertEqual(self.expected_order_price, order_partially_filled_event.price) + self.assertEqual(self.expected_partial_fill_size, order_partially_filled_event.amount) + self.assertEqual(order.client_order_id, order_partially_filled_event.order_id) + + self.assertTrue( + self.is_logged_that_starts_with( + log_level="INFO", + message_starts_with=f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to ", + ) + ) + + order_partially_cancelled_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(order.client_order_id, order_partially_cancelled_event.order_id) + self.assertEqual(order.exchange_order_id, order_partially_cancelled_event.exchange_order_id) + self.assertEqual(self.exchange.current_timestamp, order_partially_cancelled_event.timestamp) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + + self.assertTrue(order.is_cancelled) + + def test_user_stream_balance_update(self): + if self.exchange.real_time_balance_update: + target_total_balance = Decimal("15") + target_available_balance = Decimal("10") + self.clob_data_source_mock.configure_account_quote_balance_stream_event( + timestamp=self.start_timestamp, + total_balance=target_total_balance, + available_balance=target_available_balance, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(target_total_balance, self.exchange.get_balance(self.quote_asset)) + self.assertEqual(target_available_balance, self.exchange.available_balances[self.quote_asset]) + + def test_user_stream_logs_errors(self): + self.clob_data_source_mock.configure_faulty_base_balance_stream_event(timestamp=self.start_timestamp) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertTrue(self.is_logged("INFO", "Restarting account balances stream.")) + + def test_funding_info_update(self): + initial_funding_info = self.exchange.get_funding_info(self.trading_pair) + + update_target_index_price = initial_funding_info.index_price + 1 + update_target_mark_price = initial_funding_info.mark_price + 2 + update_target_next_funding_time = initial_funding_info.next_funding_utc_timestamp * 2 + update_target_funding_rate = initial_funding_info.rate + Decimal("0.0003") + + self.clob_data_source_mock.configure_funding_info_stream_event( + index_price=update_target_index_price, + mark_price=update_target_mark_price, + next_funding_time=update_target_next_funding_time, + funding_rate=update_target_funding_rate, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + updated_funding_info = self.exchange.get_funding_info(trading_pair=self.trading_pair) + + self.assertEqual(update_target_index_price, updated_funding_info.index_price) + self.assertEqual(update_target_mark_price, updated_funding_info.mark_price) + self.assertEqual(update_target_next_funding_time, updated_funding_info.next_funding_utc_timestamp) + self.assertEqual(update_target_funding_rate, updated_funding_info.rate) + + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + creation_transaction_hash=self.expected_transaction_hash, + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.clock.backtest_til(self.start_timestamp + self.exchange.SHORT_POLL_INTERVAL) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[-1] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_full_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + # Configure again the response to the order fills request since it is required by lost orders update logic + self.clob_data_source_mock.configure_get_historical_perp_orders_response_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_cancel_lost_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_cancel_order_response( + timestamp=self.start_timestamp, transaction_hash=self.expected_transaction_hash + ) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + else: + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_failure) + + self.assertFalse( + self.is_logged_that_starts_with(log_level="WARNING", message_starts_with="Failed to cancel the order ") + ) + self.assertFalse( + self.is_logged_that_starts_with(log_level="ERROR", message_starts_with="Failed to cancel order ") + ) + + def test_cancel_lost_order_raises_failure_event_when_request_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_cancel_order_fails_response(exception=RuntimeError("some error")) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue(any(log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records)) + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + def test_lost_order_user_stream_full_fill_events_are_processed(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp, + price=self.expected_order_price, + size=self.expected_order_size, + maker_fee=self.expected_full_fill_fee, + taker_fee=self.expected_full_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=self.expected_order_size, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_full_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.load") + def test_estimated_fee_calculation(self, gateway_settings_load_mock: MagicMock): + AllConnectorSettings.all_connector_settings = {} + gateway_settings_load_mock.return_value = [ + { + "connector": "injective", + "chain": "injective", + "network": "mainnet", + "trading_type": "CLOB_PERP", + "wallet_address": "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + "additional_spenders": [], + }, + ] + init_fee_overrides_config() + fee_schema = TradeFeeSchemaLoader.configured_schema_for_exchange(exchange_name=self.exchange.name) + + self.assertFalse(fee_schema.buy_percent_fee_deducted_from_returns) + self.assertEqual(0, len(fee_schema.maker_fixed_fees)) + self.assertEqual(0, fee_schema.maker_percent_fee_decimal) + self.assertIsNone(fee_schema.percent_fee_token) + self.assertEqual(0, len(fee_schema.taker_fixed_fees)) + self.assertEqual(0, fee_schema.taker_percent_fee_decimal) + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal(100), + price=Decimal(1000), + is_maker=True, + position_action=PositionAction.OPEN, + ) + + self.assertEqual(self.quote_asset, fee.percent_token) + self.assertEqual(Decimal("-0.00006"), fee.percent) # factoring in Injective service-provider rebate + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal(100), + price=Decimal(1000), + is_maker=False, + position_action=PositionAction.OPEN, + ) + + self.assertEqual(self.quote_asset, fee.percent_token) + self.assertEqual(Decimal("0.0006"), fee.percent) + + def test_funding_info_continuously_requested(self): + first_amount = Decimal("2.1") + funding_rate = Decimal("0.001") + timestamp = self.start_timestamp + 1 + self.clob_data_source_mock.configure_get_funding_payments_response( + timestamp=timestamp, + funding_rate=funding_rate, + amount=first_amount, + ) + + self.clock.backtest_til(self.start_timestamp + 1) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.funding_payment_logger.event_log)) + + second_amount = Decimal("1.9") + self.clob_data_source_mock.configure_get_funding_payments_response( + timestamp=timestamp + 60, + funding_rate=funding_rate, + amount=second_amount, + ) + + self.clock.backtest_til(self.start_timestamp + 1 + self.exchange.funding_fee_poll_interval + 1) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.funding_payment_logger.event_log)) diff --git a/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py new file mode 100644 index 0000000..3bc1729 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_perp/test_gateway_clob_perp_api_order_book_data_source.py @@ -0,0 +1,255 @@ +import asyncio +import unittest +from decimal import Decimal +from test.hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_mock_utils import ( + InjectivePerpetualClientMock, +) +from typing import Awaitable +from unittest.mock import MagicMock + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.clob_perp.data_sources.injective_perpetual.injective_perpetual_api_data_source import ( + InjectivePerpetualAPIDataSource, +) +from hummingbot.connector.gateway.clob_perp.gateway_clob_perp_api_order_book_data_source import ( + GatewayCLOBPerpAPIOrderBookDataSource, +) +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.funding_info import FundingInfo +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount + + +class MockExchange(ExchangeBase): + pass + + +class GatewayCLOBPerpAPIOrderBookDataSourceTest(unittest.TestCase): + ev_loop: asyncio.AbstractEventLoop + base: str + quote: str + trading_pair: str + sub_account_id: str + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base = "COIN" + cls.quote = "ALPHA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.sub_account_id = "someSubAccountId" + + def setUp(self) -> None: + super().setUp() + self.listening_tasks = [] + + self.initial_timestamp = 1669100347689 + self.injective_async_client_mock = InjectivePerpetualClientMock( + initial_timestamp=self.initial_timestamp, + sub_account_id=self.sub_account_id, + base=self.base, + quote=self.quote, + ) + self.injective_async_client_mock.start() + + client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) + self.api_data_source = InjectivePerpetualAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec={ + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.sub_account_id, + }, + client_config_map=client_config_map, + ) + self.connector = MockExchange(client_config_map=client_config_map) + self.tracker = GatewayOrderTracker(connector=self.connector) + self.api_data_source.gateway_order_tracker = self.tracker + self.ob_data_source = GatewayCLOBPerpAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], api_data_source=self.api_data_source + ) + self.async_run_with_timeout(coro=self.api_data_source.start()) + + def tearDown(self) -> None: + self.injective_async_client_mock.stop() + self.async_run_with_timeout(coro=self.api_data_source.stop()) + for task in self.listening_tasks: + task.cancel() + super().tearDown() + + @staticmethod + def async_run_with_timeout(coro: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coro, timeout)) + return ret + + def test_get_new_order_book_successful(self): + self.injective_async_client_mock.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + order_book: OrderBook = self.async_run_with_timeout( + self.ob_data_source.get_new_order_book(self.trading_pair) + ) + + self.assertEqual(self.initial_timestamp * 1e3, order_book.snapshot_uid) + + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + + self.assertEqual(2, len(bids)) + self.assertEqual(9, bids[0].price) + self.assertEqual(1, bids[0].amount) + self.assertEqual(1, len(asks)) + self.assertEqual(11, asks[0].price) + self.assertEqual(3, asks[0].amount) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._trade_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, asyncio.Queue()) + ) + self.listening_tasks.append(listening_task) + self.async_run_with_timeout(listening_task) + + def test_listen_for_trades_successful(self): + target_price = Decimal("1.157") + target_size = Decimal("0.001") + target_maker_fee = Decimal("0.0001157") + target_taker_fee = Decimal("0.00024") + target_maker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + target_trade_id = "19889401_someTradeId" + + def configure_trade(): + self.injective_async_client_mock.configure_trade_stream_event( + timestamp=self.initial_timestamp, + price=target_price, + size=target_size, + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + taker_trade_id=target_trade_id, + ) + + msg_queue: asyncio.Queue = asyncio.Queue() + + subs_listening_task = self.ev_loop.create_task(coro=self.ob_data_source.listen_for_subscriptions()) + self.listening_tasks.append(subs_listening_task) + trades_listening_task = self.ev_loop.create_task( + coro=self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.listening_tasks.append(trades_listening_task) + self.ev_loop.call_soon(callback=configure_trade) + self.injective_async_client_mock.run_until_all_items_delivered() + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(msg_queue.empty()) # only the taker update was forwarded by the ob data source + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(target_trade_id, msg.trade_id) + self.assertEqual(self.initial_timestamp, msg.timestamp) + + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._snapshot_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + def test_listen_for_order_book_snapshots_successful(self): + def configure_snapshot(): + self.injective_async_client_mock.configure_orderbook_snapshot_stream_event( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + + subs_listening_task = self.ev_loop.create_task(coro=self.ob_data_source.listen_for_subscriptions()) + self.listening_tasks.append(subs_listening_task) + msg_queue: asyncio.Queue = asyncio.Queue() + listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.listening_tasks.append(listening_task) + self.ev_loop.call_soon(callback=configure_snapshot) + + snapshot_msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_msg.type) + self.assertEqual(self.initial_timestamp, snapshot_msg.timestamp) + self.assertEqual(2, len(snapshot_msg.bids)) + self.assertEqual(9, snapshot_msg.bids[0].price) + self.assertEqual(1, snapshot_msg.bids[0].amount) + self.assertEqual(1, len(snapshot_msg.asks)) + self.assertEqual(11, snapshot_msg.asks[0].price) + self.assertEqual(3, snapshot_msg.asks[0].amount) + + def test_listen_for_funding_info_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._funding_info_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_funding_info(asyncio.Queue()) + ) + self.listening_tasks.append(listening_task) + self.async_run_with_timeout(listening_task) + + def test_listen_for_funding_info_successful(self): + initial_funding_info = self.async_run_with_timeout( + coro=self.ob_data_source.get_funding_info(self.trading_pair) + ) + + update_target_index_price = initial_funding_info.index_price + 1 + update_target_mark_price = initial_funding_info.mark_price + 2 + update_target_next_funding_time = initial_funding_info.next_funding_utc_timestamp * 2 + update_target_funding_rate = initial_funding_info.rate + Decimal("0.0003") + + self.injective_async_client_mock.configure_funding_info_stream_event( + index_price=update_target_index_price, + mark_price=update_target_mark_price, + next_funding_time=update_target_next_funding_time, + funding_rate=update_target_funding_rate, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + updated_funding_info = self.async_run_with_timeout( + coro=self.ob_data_source.get_funding_info(self.trading_pair) + ) + + self.assertEqual(update_target_index_price, updated_funding_info.index_price) + self.assertEqual(update_target_mark_price, updated_funding_info.mark_price) + self.assertEqual(update_target_next_funding_time, updated_funding_info.next_funding_utc_timestamp) + self.assertEqual(update_target_funding_rate, updated_funding_info.rate) + + def test_get_funding_info(self): + expected_index_price = Decimal("10") + expected_mark_price = Decimal("10.1") + expected_next_funding_time = 1610000000 + expected_funding_rate = Decimal("0.0009") + + self.injective_async_client_mock.configure_get_funding_info_response( + index_price=expected_index_price, + mark_price=expected_mark_price, + next_funding_time=expected_next_funding_time, + funding_rate=expected_funding_rate, + ) + + funding_rate: FundingInfo = self.async_run_with_timeout( + coro=self.ob_data_source.get_funding_info(trading_pair=self.trading_pair) + ) + + self.assertEqual(expected_index_price, funding_rate.index_price) + self.assertEqual(expected_mark_price, funding_rate.mark_price) + self.assertEqual(expected_next_funding_time, funding_rate.next_funding_utc_timestamp) + self.assertEqual(expected_funding_rate, funding_rate.rate) diff --git a/test/hummingbot/connector/gateway/clob_spot/__init__.py b/test/hummingbot/connector/gateway/clob_spot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/__init__.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/__init__.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_api_data_source.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_api_data_source.py new file mode 100644 index 0000000..9bd388c --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_api_data_source.py @@ -0,0 +1,731 @@ +import asyncio +import json +import math +import re +from decimal import Decimal +from typing import Any, Dict, List, Union +from unittest.mock import AsyncMock, MagicMock, patch + +import pandas as pd +from aioresponses import aioresponses + +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot import dexalot_constants as CONSTANTS +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_api_data_source import DexalotAPIDataSource +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_constants import HB_TO_DEXALOT_STATUS_MAP +from hummingbot.connector.gateway.common_types import PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.test_support.gateway_clob_api_data_source_test import AbstractGatewayCLOBAPIDataSourceTests +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utils import split_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import TradeFeeBase +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import OrderBookDataSourceEvent + + +class DexalotAPIDataSourceTest(AbstractGatewayCLOBAPIDataSourceTests.GatewayCLOBAPIDataSourceTests): + @property + def expected_buy_exchange_order_id(self) -> str: + return "0x0000000000000000000000000000000000000000000000000000000063cd59f3" # noqa: mock + + @property + def expected_sell_exchange_order_id(self) -> str: + return "0x0000000000000000000000000000000000000000000000000000000063cd59f4" # noqa: mock + + @property + def expected_fill_trade_id(self) -> Union[str, int]: + return 12340 + + @property + def exchange_base(self) -> str: + return self.hb_token_to_exchange_token(hb_token=self.base) + + @property + def exchange_quote(self) -> str: + return self.hb_token_to_exchange_token(hb_token=self.quote) + + @property + def expected_quote_decimals(self) -> int: + return 6 + + @property + def expected_base_decimals(self) -> int: + return 18 + + @property + def expected_event_counts_per_new_order(self) -> int: + return 2 + + def setUp(self) -> None: + self.domain = "dexalot" + self.mock_api = aioresponses() + self.mock_api.start() + self.api_key_mock = "someAPIKey" + self.wallet_sign_mock = "DD5113FEDED638E5500E65779613BDD3BDDBEB8EB5D86CDD3370E629B02E92CD" # noqa: mock + + self.mocking_assistant = NetworkMockingAssistant() + self.ws_connect_patch = patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + self.ws_connect_mock = self.ws_connect_patch.start() + super().setUp() + self.configure_signature_response() + self.configure_ws_auth_response() + + def tearDown(self) -> None: + self.mock_api.stop() + self.ws_connect_patch.stop() + super().tearDown() + + def configure_signature_response(self): + response = { + "signature": self.wallet_sign_mock, + } + self.gateway_instance_mock.wallet_sign.return_value = response + + def configure_ws_auth_response(self): + response = {"token": "someToken"} + url = CONSTANTS.BASE_PATH_URL[self.domain] + CONSTANTS.WS_AUTH_PATH + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + self.mock_api.get(regex_url, body=json.dumps(response)) + + def build_api_data_source(self, with_api_key: bool = True) -> DexalotAPIDataSource: + api_key = self.api_key_mock if with_api_key else "" + connector_spec = { + "api_key": api_key, + "chain": "avalanche", + "network": "dexalot", + "wallet_address": self.account_id, + "additional_prompt_values": {"api_key": api_key}, + } + data_source = DexalotAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=self.client_config_map, + ) + return data_source + + def exchange_symbol_for_tokens(self, base_token: str, quote_token: str) -> str: + exchange_base_token = self.hb_token_to_exchange_token(hb_token=base_token) + exchange_quote_token = self.hb_token_to_exchange_token(hb_token=quote_token) + exchange_trading_pair = f"{exchange_base_token}/{exchange_quote_token}" + return exchange_trading_pair + + @staticmethod + def hb_token_to_exchange_token(hb_token: str) -> str: + """To simulate cases with differing symbols (e.g. USDt).""" + return hb_token[:-1] + hb_token[-1].lower() + + def get_trading_pairs_info_response(self) -> List[Dict[str, Any]]: + return [ + { + "baseSymbol": self.exchange_base, + "quoteSymbol": self.exchange_quote, + "buyBookId": "someId", + "sellBookId": "anotherId", + "minTradeAmount": 5000000, + "maxTradeAmount": 50000000000, + "auctionPrice": 6, + "auctionMode": 1, + "makerRate": float(self.expected_maker_taker_fee_rates.maker), + "takerRate": float(self.expected_maker_taker_fee_rates.taker), + "baseDecimals": self.expected_base_decimals, + "baseDisplayDecimals": 3, + "quoteDecimals": self.expected_quote_decimals, + "quoteDisplayDecimals": int(math.log10(1 / self.expected_min_price_increment)), + "allowedSlippagePercent": 1, + "addOrderPaused": False, + "pairPaused": False, + "postOnly": False, + }, + { + "baseSymbol": "ANOTHER", + "quoteSymbol": "PAIR", + "buyBookId": "someId", + "sellBookId": "anotherId", + "minTradeAmount": 5000000, + "maxTradeAmount": 50000000000, + "auctionPrice": 6, + "auctionMode": 1, + "makerRate": float(self.expected_maker_taker_fee_rates.maker), + "takerRate": float(self.expected_maker_taker_fee_rates.taker), + "baseDecimals": 18, + "baseDisplayDecimals": 6, + "quoteDecimals": 16, + "quoteDisplayDecimals": 4, + "allowedSlippagePercent": 2, + "addOrderPaused": False, + "pairPaused": False, + "postOnly": False, + } + ] + + def get_clob_ticker_response(self, trading_pair: str, last_traded_price: Decimal) -> List[Dict[str, Any]]: + raise NotImplementedError + + def configure_place_order_response( + self, + timestamp: float, + transaction_hash: str, + exchange_order_id: str, + trade_type: TradeType, + price: Decimal, + size: Decimal, + ): + super().configure_batch_order_create_response( + timestamp=timestamp, + transaction_hash=transaction_hash, + created_orders=[ + GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=trade_type, + creation_timestamp=timestamp, + price=price, + amount=size, + exchange_order_id=exchange_order_id, + creation_transaction_hash=transaction_hash, + ) + ] + ) + + def configure_place_order_failure_response(self): + self.gateway_instance_mock.clob_batch_order_modify.return_value = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "txHash": None, + } + + def configure_cancel_order_response(self, timestamp: float, transaction_hash: str): + super().configure_batch_order_cancel_response( + timestamp=timestamp, transaction_hash=transaction_hash, canceled_orders=[] + ) + + def configure_cancel_order_failure_response(self): + self.gateway_instance_mock.clob_batch_order_modify.return_value = { + "network": self.data_source.network, + "timestamp": self.initial_timestamp, + "latency": 2, + "txHash": None, + } + + def configure_account_balances_response( + self, + base_total_balance: Decimal, + base_available_balance: Decimal, + quote_total_balance: Decimal, + quote_available_balance: Decimal, + ): + response = { + "balances": { + "available": { + self.exchange_base: self.expected_base_available_balance, + self.exchange_quote: self.expected_quote_available_balance, + }, + "total": { + self.exchange_base: self.expected_base_total_balance, + self.exchange_quote: self.expected_quote_total_balance, + }, + } + } + self.gateway_instance_mock.get_balances.return_value = response + + def configure_empty_order_fills_response(self): + url = CONSTANTS.BASE_PATH_URL[self.domain] + CONSTANTS.EXECUTIONS_PATH + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + markets_resp = [] + self.mock_api.get(regex_url, body=json.dumps(markets_resp)) + + def configure_trade_fill_response( + self, + timestamp: float, + exchange_order_id: str, + price: Decimal, + size: Decimal, + fee: TradeFeeBase, + trade_id: Union[str, int], + is_taker: bool, + ): + url = CONSTANTS.BASE_PATH_URL[self.domain] + CONSTANTS.EXECUTIONS_PATH + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + fee_token = self.hb_token_to_exchange_token(hb_token=fee.flat_fees[0].token) + markets_resp = [ + { + "env": "production-multi-subnet", + "execid": trade_id, + "fee": str(fee.flat_fees[0].amount), + "feeunit": fee_token, + "orderid": exchange_order_id, + "pair": self.exchange_trading_pair, + "price": str(price), + "quantity": str(size), + "side": 0, + "traderaddress": self.account_id, + "ts": self.data_source._timestamp_to_dexalot_timestamp(timestamp=timestamp), + "tx": "0xe7c7a9b32607d0bf3c9aa4f39069f5a31b1dc9fdde6dbe95f8dbee255e4869dc", # noqa: mock + "type": "T" if is_taker else "M", + } + ] + self.mock_api.get(regex_url, body=json.dumps(markets_resp)) + + def configure_orderbook_snapshot_event( + self, bids: List[List[float]], asks: List[List[float]], latency: float = 0 + ): + self.ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + price_scaler = Decimal(f"1e{self.expected_quote_decimals}") + size_scaler = Decimal(f"1e{self.expected_base_decimals}") + resp = { # abbreviated response + "data": { + "buyBook": [ + { + "prices": ",".join([str(int(price * price_scaler)) for price, _ in bids]), + "quantities": ",".join([str(int(size * size_scaler)) for _, size in bids]), + } + ], + "sellBook": [ + { + "prices": ",".join([str(int(price * price_scaler)) for price, _ in asks]), + "quantities": ",".join([str(int(size * size_scaler)) for _, size in asks]), + } + ], + }, + "pair": self.exchange_trading_pair, + "type": "orderBooks", + } + self.mocking_assistant.add_websocket_aiohttp_message( + self.ws_connect_mock.return_value, json.dumps(resp) + ) + + def configure_last_traded_price(self, trading_pair: str, last_traded_price: Decimal): + self.ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + resp = {"data": [{"execId": 1675046315, + "price": str(self.expected_last_traded_price), + "quantity": "951.14", + "takerSide": 1, + "ts": "2023-03-07T14:17:52.000Z"}], + "pair": self.exchange_trading_pair, + "type": "lastTrade"} + self.mocking_assistant.add_websocket_aiohttp_message( + self.ws_connect_mock.return_value, json.dumps(resp) + ) + + def enqueue_order_status_response( + self, + timestamp: float, + trading_pair: str, + exchange_order_id: str, + client_order_id: str, + status: OrderState, + ) -> asyncio.Event: + order = GatewayInFlightOrder( + client_order_id=client_order_id, + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + exchange_order_id=exchange_order_id, + creation_transaction_hash=self.expected_transaction_hash, + ) + return self.enqueue_order_status_responses_for_batch_order_create( + timestamp=timestamp, orders=[order], statuses=[status] + ) + + def enqueue_order_status_responses_for_batch_order_create( + self, timestamp: float, orders: List[GatewayInFlightOrder], statuses: List[OrderState] + ) -> asyncio.Event: + update_delivered_event = asyncio.Event() + base_url = CONSTANTS.BASE_PATH_URL[self.domain] + CONSTANTS.ORDERS_PATH + for order, status, i in zip(orders, statuses, range(len(orders))): + if status == OrderState.OPEN: # order is being created, and it doesn't have an exchange order ID + response = self.get_transaction_status_update( + timestamp=timestamp, orders=orders, statuses=statuses + ) + self.gateway_instance_mock.get_transaction_status.return_value = response + regex_url = re.compile( + f"^{base_url}/{order.exchange_order_id}".replace(".", r"\.").replace("?", r"\?") + ) + response = self.get_order_status_response( + timestamp=timestamp, + trading_pair=order.trading_pair, + exchange_order_id=order.exchange_order_id or "", + client_order_id=order.client_order_id, + status=status, + ) + + callback = None if i != len(orders) - 1 else lambda *_, **__: update_delivered_event.set() + self.mock_api.get(regex_url, body=json.dumps(response), callback=callback) + + return update_delivered_event + + def get_transaction_status_update( + self, timestamp: float, orders: List[GatewayInFlightOrder], statuses: List[OrderState] + ): + return { + "txStatus": 1, + "timestamp": int(timestamp * 1e3), + "txHash": self.expected_transaction_hash, + "txData": {}, + "txReceipt": { + "transactionHash": self.expected_transaction_hash, + "logs": [ + { + "name": "OrderStatusChanged", + "events": [ + {}, # version + {}, # traderaddress + {}, # pair + { + "name": "orderId", + "type": "bytes32", + "value": order.exchange_order_id, + }, + { + "name": "clientOrderId", + "type": "bytes32", + "value": order.client_order_id, + }, + { + "name": "price", + "type": "uint256", + "value": "15000000" + }, + {}, # totalamount + { + "name": "quantity", + "type": "uint256", + "value": "500000000000000000" + }, + {}, # side + {}, # type1 + {}, # type2 + { + "name": "status", + "type": "uint8", + "value": str(CONSTANTS.HB_TO_DEXALOT_NUMERIC_STATUS_MAP[status]) + }, + {}, # quantityfilled + {}, # totalfee + {}, # code + ], + "address": "0x09383137C1eEe3E1A8bc781228E4199f6b4A9bbf" + } for order, status in zip(orders, statuses) + ], + "status": 1, + }, + } + + def get_order_status_response( + self, + timestamp: float, + trading_pair: str, + exchange_order_id: str, + client_order_id: str, + status: OrderState, + ) -> Dict[str, Any]: + """ + Example response: + + { "id": "0x0000000000000000000000000000000000000000000000000000000063d4909e", # noqa: mock + "price": "22.000000000000000000", + "quantity": "0.800000000000000000", + "quantityFilled": "0.000000000000000000", + "side": "SELL", + "status": "CANCELED", + "timestamp": "2023-02-27T04:49:40.000Z", + "totalAmount": "0.000000000000000000", + "totalFee": "0.000000000000000000", + "tradePair": "AVAX/USDC", + "tx": "0xc9468f344cfe9917e692afecb2b81e89b807394a0ac873db9ee1417297a5f869", # noqa: mock + "type1": "LIMIT", + "type2": "GTC", + "updateTs": "2023-02-27T05:04:25.000Z"} + """ + dexalot_timestamp_str = pd.Timestamp.utcfromtimestamp(timestamp).strftime(format="%Y-%m-%dT%H:%M:%S") + ".000Z" + response = { + "id": exchange_order_id, + "price": "22.000000000000000000", + "quantity": "0.800000000000000000", + "quantityFilled": "0.000000000000000000", + "side": "SELL", + "status": HB_TO_DEXALOT_STATUS_MAP[status], + "timestamp": dexalot_timestamp_str, + "totalAmount": "0.000000000000000000", + "totalFee": "0.000000000000000000", + "tradePair": self.exchange_trading_pair_from_hb_trading_pair(trading_pair=trading_pair), + "tx": "0xc9468f344cfe9917e692afecb2b81e89b807394a0ac873db9ee1417297a5f869", # noqa: mock + "type1": "LIMIT", + "type2": "GTC", + "updateTs": dexalot_timestamp_str, + } + return response + + @staticmethod + def exchange_trading_pair_from_hb_trading_pair(trading_pair: str) -> str: + base, quote = split_hb_trading_pair(trading_pair=trading_pair) + return f"{base[:-1] + base[-1].lower()}/{quote[:-1] + quote[-1].lower()}" + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_api_data_source" + ".DexalotAPIDataSource._time" + ) + def test_delivers_order_book_snapshot_events(self, time_mock: MagicMock): + self.async_run_with_timeout(self.data_source.stop()) + + data_source = self.build_api_data_source() + self.additional_data_sources_to_stop_on_tear_down.append(data_source) + data_source.min_snapshots_update_interval = 0 + data_source.max_snapshots_update_interval = 0 + + snapshots_logger = EventLogger() + + data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=snapshots_logger + ) + + self.configure_orderbook_snapshot_event(bids=[[9, 1], [8, 2]], asks=[[11, 3]]) + time_mock.return_value = self.initial_timestamp + data_source.gateway_order_tracker = self.tracker + + task = asyncio.get_event_loop().create_task(coro=snapshots_logger.wait_for(event_type=OrderBookMessage)) + self.async_tasks.append(task) + self.async_run_with_timeout(coro=data_source.start()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=self.ws_connect_mock.return_value + ) + self.async_run_with_timeout(coro=task) + + self.assertEqual(1, len(snapshots_logger.event_log)) + + snapshot_event: OrderBookMessage = snapshots_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, snapshot_event.timestamp) + self.assertEqual(2, len(snapshot_event.bids)) + self.assertEqual(9, snapshot_event.bids[0].price) + self.assertEqual(1, snapshot_event.bids[0].amount) + self.assertEqual(1, len(snapshot_event.asks)) + self.assertEqual(11, snapshot_event.asks[0].price) + self.assertEqual(3, snapshot_event.asks[0].amount) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_api_data_source" + ".DexalotAPIDataSource._time" + ) + def test_delivers_order_book_snapshot_events_without_api_key(self, time_mock: MagicMock): + self.async_run_with_timeout(self.data_source.stop()) + + data_source = self.build_api_data_source(with_api_key=False) + self.additional_data_sources_to_stop_on_tear_down.append(data_source) + data_source.min_snapshots_update_interval = 0 + data_source.max_snapshots_update_interval = 0 + + snapshots_logger = EventLogger() + + data_source.add_listener( + event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=snapshots_logger + ) + + self.configure_orderbook_snapshot_event(bids=[[9, 1], [8, 2]], asks=[[11, 3]]) + time_mock.return_value = self.initial_timestamp + data_source.gateway_order_tracker = self.tracker + + task = asyncio.get_event_loop().create_task(coro=snapshots_logger.wait_for(event_type=OrderBookMessage)) + self.async_tasks.append(task) + self.async_run_with_timeout(coro=data_source.start()) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=self.ws_connect_mock.return_value + ) + self.async_run_with_timeout(coro=task) + + self.assertEqual(1, len(snapshots_logger.event_log)) + + snapshot_event: OrderBookMessage = snapshots_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, snapshot_event.timestamp) + self.assertEqual(2, len(snapshot_event.bids)) + self.assertEqual(9, snapshot_event.bids[0].price) + self.assertEqual(1, snapshot_event.bids[0].amount) + self.assertEqual(1, len(snapshot_event.asks)) + self.assertEqual(11, snapshot_event.asks[0].price) + self.assertEqual(3, snapshot_event.asks[0].amount) + + def test_minimum_delay_between_requests_for_snapshot_events(self): + pass # Dexalot streams the snapshots + + def test_maximum_delay_between_requests_for_snapshot_events(self): + pass # Dexalot streams the snapshots + + def test_get_last_traded_price(self): + self.configure_last_traded_price( + trading_pair=self.trading_pair, last_traded_price=self.expected_last_traded_price, + ) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered( + websocket_mock=self.ws_connect_mock.return_value, + ) + last_trade_price = self.async_run_with_timeout( + coro=self.data_source.get_last_traded_price(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.expected_last_traded_price, last_trade_price) + + def test_get_order_status_update_from_closed_order(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.FILLED, + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.FILLED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(self.expected_buy_exchange_order_id, status_update.exchange_order_id) + + def test_get_order_status_update_transaction_not_found_raises(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + creation_transaction_hash=creation_transaction_hash, + ) + self.gateway_instance_mock.get_transaction_status.return_value = {"txStatus": -1} + + expected_error = f"No update found for order {in_flight_order.client_order_id}" + with self.assertRaisesRegex(expected_exception=ValueError, expected_regex=expected_error): + self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_batch_order_create_splits_order_by_max_order_create_per_batch(self, sleep_mock: AsyncMock): + mock_max_order_create_per_batch = 2 + + def sleep_mock_side_effect(delay): + raise Exception + + sleep_mock.side_effect = sleep_mock_side_effect + + orders_to_create = [] + for i in range(mock_max_order_create_per_batch + 1): + orders_to_create.append( + GatewayInFlightOrder( + client_order_id=f"someCOID{i}", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price + Decimal("1") * i, + amount=self.expected_buy_order_size, + exchange_order_id=hex(int(self.expected_buy_exchange_order_id, 16) + i), + ) + ) + self.configure_batch_order_create_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + created_orders=orders_to_create, + ) + + for order in orders_to_create: + order.exchange_order_id = None # the orders are new + + CONSTANTS.MAX_ORDER_CREATIONS_PER_BATCH = mock_max_order_create_per_batch + result: List[PlaceOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_create(orders_to_create=orders_to_create) + ) + + self.assertEqual(len(orders_to_create), len(result)) + self.assertEqual( + math.ceil(len(orders_to_create) / mock_max_order_create_per_batch), + len(self.gateway_instance_mock.clob_batch_order_modify.mock_calls), + ) + + first_call = self.gateway_instance_mock.clob_batch_order_modify.mock_calls[0] + second_call = self.gateway_instance_mock.clob_batch_order_modify.mock_calls[1] + + self.assertEqual( + orders_to_create[:mock_max_order_create_per_batch], first_call.kwargs["orders_to_create"] + ) + self.assertEqual( + orders_to_create[mock_max_order_create_per_batch:], second_call.kwargs["orders_to_create"] + ) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.gateway_clob_api_data_source_base" + ".GatewayCLOBAPIDataSourceBase._sleep", + new_callable=AsyncMock, + ) + def test_batch_order_cancel_splits_order_by_max_order_cancel_per_batch(self, sleep_mock: AsyncMock): + mock_max_order_cancelations_per_batch = 2 + + orders_to_cancel = [] + for i in range(mock_max_order_cancelations_per_batch + 1): + orders_to_cancel.append( + GatewayInFlightOrder( + client_order_id=f"someCOID{i}", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price + Decimal("1") * i, + amount=self.expected_buy_order_size, + exchange_order_id=hex(int(self.expected_buy_exchange_order_id, 16) + i), + creation_transaction_hash=f"someCreationHash{i}" + ) + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=orders_to_cancel[-1]) + self.configure_batch_order_cancel_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + canceled_orders=orders_to_cancel, + ) + + CONSTANTS.MAX_ORDER_CANCELATIONS_PER_BATCH = mock_max_order_cancelations_per_batch + result: List[PlaceOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_cancel(orders_to_cancel=orders_to_cancel) + ) + + self.assertEqual(len(orders_to_cancel), len(result)) + self.assertEqual( + math.ceil(len(orders_to_cancel) / mock_max_order_cancelations_per_batch), + len(self.gateway_instance_mock.clob_batch_order_modify.mock_calls), + ) + + first_call = self.gateway_instance_mock.clob_batch_order_modify.mock_calls[0] + second_call = self.gateway_instance_mock.clob_batch_order_modify.mock_calls[1] + + self.assertEqual( + orders_to_cancel[:mock_max_order_cancelations_per_batch], first_call.kwargs["orders_to_cancel"] + ) + self.assertEqual( + orders_to_cancel[mock_max_order_cancelations_per_batch:], second_call.kwargs["orders_to_cancel"] + ) diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_auth.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_auth.py new file mode 100644 index 0000000..e5af82d --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/dexalot/test_dexalot_auth.py @@ -0,0 +1,66 @@ +import asyncio +import unittest +from typing import Awaitable, TypeVar +from unittest.mock import MagicMock, patch + +from hummingbot.connector.gateway.clob_spot.data_sources.dexalot.dexalot_auth import DexalotAuth, WalletSigner +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest + +T = TypeVar("T") + + +class DexalotAuthTest(unittest.TestCase): + wallet_address: str + wallet_private_key: str + + @classmethod + def setUpClass(cls) -> None: + cls.wallet_address = "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18" # noqa: mock + + @staticmethod + def async_run_with_timeout(coroutine: Awaitable[T], timeout: int = 1) -> T: + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.wallet_sign") + def test_rest_authenticate(self, wallet_sign_mock: MagicMock): + signature = "someSignature" + wallet_sign_mock.return_value = {"signature": signature} + + gateway_instance = GatewayHttpClient() + signer = WalletSigner( + chain="avalanche", + network="dexalot", + address=self.wallet_address, + gateway_instance=gateway_instance, + ) + auth = DexalotAuth(signer=signer, address=self.wallet_address) + + request = RESTRequest( + method=RESTMethod.GET, + url="https://some.url.com", + ) + authenticated_request = self.async_run_with_timeout( + coroutine=auth.rest_authenticate(request=request) + ) + + expected_auth_headers = {"x-signature": f"{self.wallet_address}:{signature}"} + + self.assertEqual(expected_auth_headers, authenticated_request.headers) + + request = RESTRequest( + method=RESTMethod.GET, + url="https://some.url.com", + headers={"Content-Type": "application/json"}, + ) + authenticated_request = self.async_run_with_timeout( + coroutine=auth.rest_authenticate(request=request) + ) + + expected_auth_headers = { + "Content-Type": "application/json", + "x-signature": f"{self.wallet_address}:{signature}", + } + + self.assertEqual(expected_auth_headers, authenticated_request.headers) diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/__init__.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_mock_utils.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_mock_utils.py new file mode 100644 index 0000000..315e208 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/injective_mock_utils.py @@ -0,0 +1,1156 @@ +import asyncio +import json +from decimal import Decimal +from typing import Any, List, Optional, Tuple, Type +from unittest.mock import AsyncMock, patch + +import grpc +import pandas as pd +from pyinjective.orderhash import OrderHashResponse +from pyinjective.proto.exchange.injective_accounts_rpc_pb2 import ( + StreamSubaccountBalanceResponse, + SubaccountBalance, + SubaccountDeposit as Account_SubaccountDeposit, +) +from pyinjective.proto.exchange.injective_explorer_rpc_pb2 import ( + CosmosCoin, + GasFee, + GetTxByTxHashResponse, + StreamTxsResponse, + TxDetailData, +) +from pyinjective.proto.exchange.injective_portfolio_rpc_pb2 import ( + AccountPortfolioResponse, + Coin, + Portfolio, + SubaccountBalanceV2, + SubaccountDeposit, +) +from pyinjective.proto.exchange.injective_spot_exchange_rpc_pb2 import ( + MarketsResponse, + OrderbooksV2Response, + OrdersHistoryResponse, + Paging, + PriceLevel, + SingleSpotLimitOrderbookV2, + SpotLimitOrderbookV2, + SpotMarketInfo, + SpotOrderHistory, + SpotTrade, + StreamOrderbookV2Response, + StreamOrdersHistoryResponse, + StreamTradesResponse, + TokenMeta, + TradesResponse, +) + +from hummingbot.connector.constants import s_decimal_0 +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_constants import ( + BASE_GAS, + GAS_BUFFER, + SPOT_CANCEL_ORDER_GAS, + SPOT_SUBMIT_ORDER_GAS, +) +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import TradeFeeBase + + +class StreamMock: + def __init__(self): + self.queue = asyncio.Queue() + + def add(self, item: Any): + self.queue.put_nowait(item=item) + + def run_until_all_items_delivered(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete(asyncio.wait_for(fut=self.queue.join(), timeout=timeout)) + + def cancel(self): + pass + + def __aiter__(self): + return self + + async def __anext__(self): + el = await self.queue.get() + self.queue.task_done() + return el + + +class InjectiveClientMock: + def __init__( + self, initial_timestamp: float, sub_account_id: str, base: str, quote: str, + ): + self.initial_timestamp = initial_timestamp + self.base = base + self.base_coin_address = "someBaseCoinAddress" + self.base_denom = self.base_coin_address + self.base_decimals = 18 + self.quote = quote + self.quote_coin_address = "someQuoteCoinAddress" + self.quote_denom = self.quote_coin_address + self.quote_decimals = 8 # usually set to 6, but for the sake of differing minimum price/size increments + self.market_id = "someMarketId" + self.sub_account_id = sub_account_id + self.service_provider_fee = Decimal("0.4") + self.order_creation_gas_estimate = Decimal("0.0000825") + self.order_cancelation_gas_estimate = Decimal("0.0000725") + self.order_gas_estimate = Decimal("0.000155") # gas to both submit and cancel an order in INJ + + self.injective_async_client_mock_patch = patch( + target=( + "hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source.AsyncClient" + ), + autospec=True, + ) + self.injective_async_client_mock: Optional[AsyncMock] = None + self.gateway_instance_mock_patch = patch( + target=( + "hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source" + ".GatewayHttpClient" + ), + autospec=True, + ) + self.gateway_instance_mock: Optional[AsyncMock] = None + self.injective_order_hash_manager_start_patch = patch( + target=( + "hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source" + ".OrderHashManager.start" + ), + autospec=True, + ) + self.injective_composer_patch = patch( + target="hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source" + ".ProtoMsgComposer", + autospec=True, + ) + self.injective_compute_order_hashes_patch = patch( + target=( + "hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source" + ".OrderHashManager.compute_order_hashes" + ), + autospec=True, + ) + self.injective_compute_order_hashes_mock: Optional[AsyncMock] = None + + self.place_order_called_event = asyncio.Event() + self.cancel_order_called_event = asyncio.Event() + + @property + def min_quantity_tick_size(self) -> Decimal: + return Decimal("0.001") + + @property + def min_price_tick_size(self) -> Decimal: + return Decimal("0.00001") + + @property + def maker_fee_rate(self) -> Decimal: + return Decimal("-0.0001") + + @property + def taker_fee_rate(self) -> Decimal: + return Decimal("0.001") + + @property + def exchange_trading_pair(self) -> str: + return self.market_id + + def start(self): + self.injective_async_client_mock = self.injective_async_client_mock_patch.start() + self.injective_async_client_mock.return_value = self.injective_async_client_mock + self.gateway_instance_mock = self.gateway_instance_mock_patch.start() + self.gateway_instance_mock.get_instance.return_value = self.gateway_instance_mock + self.injective_order_hash_manager_start_patch.start() + self.injective_composer_patch.start() + self.injective_compute_order_hashes_mock = self.injective_compute_order_hashes_patch.start() + + self.injective_async_client_mock.stream_spot_trades.return_value = StreamMock() + self.injective_async_client_mock.stream_historical_spot_orders.return_value = StreamMock() + self.injective_async_client_mock.stream_spot_orderbook_snapshot.return_value = StreamMock() + self.injective_async_client_mock.stream_account_portfolio.return_value = StreamMock() + self.injective_async_client_mock.stream_subaccount_balance.return_value = StreamMock() + self.injective_async_client_mock.stream_txs.return_value = StreamMock() + + self.configure_active_spot_markets_response(timestamp=self.initial_timestamp) + + def stop(self): + self.injective_async_client_mock_patch.stop() + self.gateway_instance_mock_patch.stop() + self.injective_order_hash_manager_start_patch.stop() + self.injective_composer_patch.stop() + self.injective_compute_order_hashes_patch.stop() + + def run_until_all_items_delivered(self, timeout: float = 1): + self.injective_async_client_mock.stream_spot_trades.return_value.run_until_all_items_delivered(timeout=timeout) + self.injective_async_client_mock.stream_historical_spot_orders.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_spot_orderbook_snapshot.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.run_until_all_items_delivered( + timeout=timeout + ) + self.injective_async_client_mock.stream_txs.return_value.run_until_all_items_delivered( + timeout=timeout + ) + + def run_until_place_order_called(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete( + asyncio.wait_for(fut=self.place_order_called_event.wait(), timeout=timeout) + ) + + def run_until_cancel_order_called(self, timeout: float = 1): + asyncio.get_event_loop().run_until_complete( + asyncio.wait_for(fut=self.cancel_order_called_event.wait(), timeout=timeout) + ) + + def configure_batch_order_create_response( + self, + timestamp: int, + transaction_hash: str, + created_orders: List[InFlightOrder], + ): + def update_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_batch_order_modify.side_effect = update_and_return + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, success=True, order_hashes=[order.exchange_order_id for order in created_orders] + ) + for order in created_orders: + self.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + ) + self.injective_compute_order_hashes_mock.return_value = OrderHashResponse( + spot=[order.exchange_order_id for order in created_orders], derivative=[] + ) + + def configure_batch_order_cancel_response( + self, + timestamp: int, + transaction_hash: str, + canceled_orders: List[InFlightOrder], + ): + def update_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_batch_order_modify.side_effect = update_and_return + for order in canceled_orders: + self.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + is_canceled=True, + ) + + def configure_place_order_response( + self, + timestamp: int, + transaction_hash: str, + exchange_order_id: str, + trade_type: TradeType, + price: Decimal, + size: Decimal, + ): + + def place_and_return(*_, **__): + self.place_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash[2:].lower(), + } + + self.gateway_instance_mock.clob_place_order.side_effect = place_and_return + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, success=True, order_hashes=[exchange_order_id] + ) + self.configure_get_historical_spot_orders_response( + timestamp=timestamp, + order_hash=exchange_order_id, + state="booked", + execution_type="limit", + order_type="buy" if trade_type == TradeType.BUY else "sell", + price=price, + size=size, + filled_size=Decimal("0"), + direction="buy" if trade_type == TradeType.BUY else "sell", + ) + self.injective_compute_order_hashes_mock.return_value = OrderHashResponse( + spot=[exchange_order_id], derivative=[] + ) + + def configure_place_order_fails_response(self, exception: Exception): + + def place_and_raise(*_, **__): + self.place_order_called_event.set() + raise exception + + self.gateway_instance_mock.clob_place_order.side_effect = place_and_raise + + def configure_cancel_order_response(self, timestamp: int, transaction_hash: str): + + def cancel_and_return(*_, **__): + self.cancel_order_called_event.set() + return { + "network": "injective", + "timestamp": timestamp, + "latency": 2, + "txHash": transaction_hash if not transaction_hash.startswith("0x") else transaction_hash[2:], + } + + self.gateway_instance_mock.clob_cancel_order.side_effect = cancel_and_return + + def configure_cancel_order_fails_response(self, exception: Exception): + + def cancel_and_raise(*_, **__): + self.cancel_order_called_event.set() + raise exception + + self.gateway_instance_mock.clob_cancel_order.side_effect = cancel_and_raise + + def configure_one_success_one_failure_order_cancelation_responses( + self, success_timestamp: int, success_transaction_hash: str, failure_exception: Exception, + ): + called_once = False + + def cancel_and_return(*_, **__): + nonlocal called_once + if called_once: + self.cancel_order_called_event.set() + raise failure_exception + called_once = True + return { + "network": "injective", + "timestamp": success_timestamp, + "latency": 2, + "txHash": success_transaction_hash, + } + + self.gateway_instance_mock.clob_cancel_order.side_effect = cancel_and_return + + def configure_check_network_success(self): + self.injective_async_client_mock.ping.side_effect = None + + def configure_check_network_failure(self, exc: Type[Exception] = grpc.RpcError): + self.injective_async_client_mock.ping.side_effect = exc + + def configure_order_status_update_response( + self, + timestamp: int, + order: InFlightOrder, + creation_transaction_hash: Optional[str] = None, + creation_transaction_success: bool = True, + cancelation_transaction_hash: Optional[str] = None, + filled_size: Decimal = s_decimal_0, + is_canceled: bool = False, + is_failed: bool = False, + ): + exchange_order_id = order.exchange_order_id + if creation_transaction_hash is not None: + if creation_transaction_success: + self.configure_creation_transaction_stream_event( + timestamp=timestamp, transaction_hash=creation_transaction_hash + ) + self.configure_get_tx_by_hash_creation_response( + timestamp=timestamp, + success=creation_transaction_success, + order_hashes=[exchange_order_id], + transaction_hash=creation_transaction_hash, + is_order_failed=is_failed, + ) + if cancelation_transaction_hash is not None: + self.configure_cancelation_transaction_stream_event( + timestamp=timestamp, + transaction_hash=cancelation_transaction_hash, + order_hash=exchange_order_id, + ) + self.configure_get_tx_by_hash_cancelation_response( + timestamp=timestamp, + order_hash=exchange_order_id, + transaction_hash=cancelation_transaction_hash, + ) + if is_failed: + self.configure_get_historical_spot_orders_empty_response() + elif not is_canceled: + self.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=timestamp, + in_flight_order=order, + order_hash=exchange_order_id, + filled_size=filled_size, + ) + else: + self.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=timestamp, in_flight_order=order, is_canceled=True + ) + + def configure_trades_response_with_exchange_order_id( + self, + timestamp: float, + exchange_order_id: str, + price: Decimal, + size: Decimal, + fee: TradeFeeBase, + trade_id: str, + ): + """This method appends mocks if previously queued mocks already exist.""" + timestamp_ms = int(timestamp * 1e3) + scaled_price = price * Decimal(f"1e{self.quote_decimals - self.base_decimals}") + scaled_size = size * Decimal(f"1e{self.base_decimals}") + price_level = PriceLevel( + price=str(scaled_price), quantity=str(scaled_size), timestamp=timestamp_ms + ) + scaled_fee = fee.flat_fees[0].amount * Decimal(f"1e{self.quote_decimals}") + trade = SpotTrade( + order_hash=exchange_order_id, + subaccount_id=self.sub_account_id, + market_id=self.market_id, + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price_level, + fee=str(scaled_fee), + executed_at=timestamp_ms, + fee_recipient="anotherRecipientAddress", + trade_id=trade_id, + execution_side="taker", + ) + trades = TradesResponse() + trades.trades.append(trade) + + if self.injective_async_client_mock.get_spot_trades.side_effect is None: + self.injective_async_client_mock.get_spot_trades.side_effect = [trades, TradesResponse()] + else: + self.injective_async_client_mock.get_spot_trades.side_effect = ( + list(self.injective_async_client_mock.get_spot_trades.side_effect) + [trades, TradesResponse()] + ) + + def configure_trades_response_no_trades(self): + """This method appends mocks if previously queued mocks already exist.""" + trades = TradesResponse() + + if self.injective_async_client_mock.get_spot_trades.side_effect is None: + self.injective_async_client_mock.get_spot_trades.side_effect = [trades, TradesResponse()] + else: + self.injective_async_client_mock.get_spot_trades.side_effect = ( + list(self.injective_async_client_mock.get_spot_trades.side_effect) + [trades, TradesResponse()] + ) + + def configure_trades_response_fails(self): + self.injective_async_client_mock.get_spot_trades.side_effect = RuntimeError + + def configure_order_stream_event_for_in_flight_order( + self, + timestamp: float, + in_flight_order: InFlightOrder, + filled_size: Decimal = Decimal("0"), + is_canceled: bool = False, + ): + if is_canceled: + state = "canceled" + elif filled_size == Decimal("0"): + state = "booked" + elif filled_size == in_flight_order.amount: + state = "filled" + else: + state = "partial_filled" + self.configure_order_stream_event( + timestamp=timestamp, + order_hash=in_flight_order.exchange_order_id, + state=state, + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=filled_size, + direction=in_flight_order.trade_type.name.lower(), + ) + + def configure_trade_stream_event( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: TradeFeeBase, + taker_fee: TradeFeeBase, + exchange_order_id: str = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7", # noqa: mock + taker_trade_id: str = "19889401_someTradeId", + ): + """The taker is a buy.""" + maker_trade, taker_trade = self.get_maker_taker_trades_pair( + timestamp=timestamp, + price=price, + size=size, + maker_fee=maker_fee.flat_fees[0].amount, + taker_fee=taker_fee.flat_fees[0].amount, + order_hash=exchange_order_id, + taker_trade_id=taker_trade_id, + ) + maker_trade_response = StreamTradesResponse(trade=maker_trade) + taker_trade_response = StreamTradesResponse(trade=taker_trade) + self.injective_async_client_mock.stream_spot_trades.return_value.add(maker_trade_response) + self.injective_async_client_mock.stream_spot_trades.return_value.add(taker_trade_response) + + def configure_account_base_balance_stream_event( + self, timestamp: float, total_balance: Decimal, available_balance: Decimal + ): + timestamp_ms = int(timestamp * 1e3) + deposit = Account_SubaccountDeposit( + total_balance=str(total_balance * Decimal(f"1e{self.base_decimals}")), + available_balance=str(available_balance * Decimal(f"1e{self.base_decimals}")), + ) + balance = SubaccountBalance( + subaccount_id=self.sub_account_id, + account_address="someAccountAddress", + denom=self.base_denom, + deposit=deposit, + ) + balance_event = StreamSubaccountBalanceResponse( + balance=balance, + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.add(balance_event) + + self.configure_get_account_balances_response( + quote_total_balance=total_balance, quote_available_balance=available_balance, + ) + + def configure_faulty_base_balance_stream_event(self, timestamp: float): + timestamp_ms = int(timestamp * 1e3) + deposit = Account_SubaccountDeposit( + total_balance="", + available_balance="", + ) + balance = SubaccountBalance( + subaccount_id=self.sub_account_id, + account_address="someAccountAddress", + denom="wrongCoinAddress", + deposit=deposit, + ) + balance_event = StreamSubaccountBalanceResponse( + balance=balance, + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.add(balance_event) + + def configure_spot_trades_response_to_request_without_exchange_order_id( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: TradeFeeBase, + taker_fee: TradeFeeBase, + ): + """The taker is a buy.""" + maker_trade, taker_trade = self.get_maker_taker_trades_pair( + timestamp=timestamp, + price=price, + size=size, + maker_fee=maker_fee.flat_fees[0].amount, + taker_fee=taker_fee.flat_fees[0].amount, + ) + trades = TradesResponse() + trades.trades.append(maker_trade) + trades.trades.append(taker_trade) + + self.injective_async_client_mock.get_spot_trades.return_value = trades + + def configure_orderbook_snapshot( + self, timestamp: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]], + ): + timestamp_ms = int(timestamp * 1e3) + orderbook = self.create_orderbook_mock(timestamp_ms=timestamp_ms, bids=bids, asks=asks) + single_orderbook = SingleSpotLimitOrderbookV2(market_id=self.market_id, orderbook=orderbook) + orderbook_response = OrderbooksV2Response() + orderbook_response.orderbooks.append(single_orderbook) + + self.injective_async_client_mock.get_spot_orderbooksV2.return_value = orderbook_response + + def configure_orderbook_snapshot_stream_event( + self, timestamp: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]] + ): + timestamp_ms = int(timestamp * 1e3) + orderbook = self.create_orderbook_mock(timestamp_ms=timestamp_ms, bids=bids, asks=asks) + orderbook_response = StreamOrderbookV2Response( + orderbook=orderbook, + operation_type="update", + timestamp=timestamp_ms, + market_id=self.market_id, + ) + + self.injective_async_client_mock.stream_spot_orderbook_snapshot.return_value.add(orderbook_response) + + def create_orderbook_mock( + self, timestamp_ms: float, bids: List[Tuple[float, float]], asks: List[Tuple[float, float]] + ) -> SpotLimitOrderbookV2: + orderbook = SpotLimitOrderbookV2() + + for price, size in bids: + scaled_price = price * Decimal(f"1e{self.quote_decimals - self.base_decimals}") + scaled_size = size * Decimal(f"1e{self.base_decimals}") + bid = PriceLevel( + price=str(scaled_price), quantity=str(scaled_size), timestamp=timestamp_ms + ) + orderbook.buys.append(bid) + + for price, size in asks: + scaled_price = price * Decimal(f"1e{self.quote_decimals - self.base_decimals}") + scaled_size = size * Decimal(f"1e{self.base_decimals}") + ask = PriceLevel( + price=str(scaled_price), quantity=str(scaled_size), timestamp=timestamp_ms + ) + orderbook.sells.append(ask) + + return orderbook + + def get_maker_taker_trades_pair( + self, + timestamp: float, + price: Decimal, + size: Decimal, + maker_fee: Decimal, + taker_fee: Decimal, + order_hash: str = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7", # noqa: mock + taker_trade_id: str = "19889401_someTradeId", + ) -> Tuple[SpotTrade, SpotTrade]: + """The taker is a buy.""" + timestamp_ms = int(timestamp * 1e3) + scaled_price = price * Decimal(f"1e{self.quote_decimals - self.base_decimals}") + scaled_size = size * Decimal(f"1e{self.base_decimals}") + price_level = PriceLevel( + price=str(scaled_price), quantity=str(scaled_size), timestamp=timestamp_ms + ) + scaled_maker_fee = maker_fee * Decimal(f"1e{self.quote_decimals}") + scaled_taker_fee = taker_fee * Decimal(f"1e{self.quote_decimals}") + assert len(taker_trade_id.split("_")) == 2 + trade_id_prefix = taker_trade_id.split("_")[0] + taker_trade = SpotTrade( + order_hash=order_hash, + subaccount_id="sumSubAccountId", + market_id=self.market_id, + trade_execution_type="limitMatchNewOrder", + trade_direction="buy", + price=price_level, + fee=str(scaled_taker_fee), + executed_at=timestamp_ms, + fee_recipient="anotherRecipientAddress", + trade_id=taker_trade_id, + execution_side="taker", + ) + maker_trade = SpotTrade( + order_hash="anotherOrderHash", + subaccount_id="anotherSubAccountId", + market_id=self.market_id, + trade_execution_type="limitMatchRestingOrder", + trade_direction="sell", + price=price_level, + fee=str(scaled_maker_fee), + executed_at=timestamp_ms, + fee_recipient="someRecipientAddress", + trade_id=f"{trade_id_prefix}_anotherTradeId", # trade IDs for each side have same prefix, different suffix + execution_side="maker", + ) + return maker_trade, taker_trade + + def configure_order_stream_event( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + ): + """ + order { + order_hash: "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: documentation + market_id: "0xd1956e20d74eeb1febe31cd37060781ff1cb266f49e0512b446a5fafa9a16034" # noqa: documentation + subaccount_id: "0x32b16783ea9a08602dc792f24c3d78bba6e333d3000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "buy_po" + price: "0.00000000116023" + trigger_price: "0" + quantity: "2000000000000000" + filled_quantity: "0" + state: "canceled" + created_at: 1669198777253 + updated_at: 1669198783253 + direction: "buy" + } + operation_type: "update" + timestamp: 1669198784000 + """ + order = self.get_spot_order_history( + timestamp=timestamp, + order_hash=order_hash, + state=state, + execution_type=execution_type, + order_type=order_type, + price=price, + size=size, + filled_size=filled_size, + direction=direction, + ) + timestamp_ms = int(timestamp * 1e3) + order_response = StreamOrdersHistoryResponse( + order=order, + operation_type="update", + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_historical_spot_orders.return_value.add(order_response) + + def configure_get_historical_spot_orders_response_for_in_flight_order( + self, + timestamp: float, + in_flight_order: InFlightOrder, + filled_size: Decimal = Decimal("0"), + is_canceled: bool = False, + order_hash: Optional[str] = None, # overwrites in_flight_order.exchange_order_id + ): + """ + orders { + order_hash: "0x0f62edfb64644762c20490d9573034c2f319e87e857401c41eea1fe373045dd7" # noqa: documentation + market_id: "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0" # noqa: documentation + subaccount_id: "0x1b99514e320ae0087be7f87b1e3057853c43b799000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell_po" + price: "1887550000" + trigger_price: "0" + quantity: "14.66" + filled_quantity: "0" + state: "canceled" + created_at: 1660245368028 + updated_at: 1660245374789 + direction: "sell" + } + paging { + total: 1000 + } + """ + if is_canceled: + state = "canceled" + elif filled_size == Decimal("0"): + state = "booked" + elif filled_size == in_flight_order.amount: + state = "filled" + else: + state = "partial_filled" + self.configure_get_historical_spot_orders_response( + timestamp=timestamp, + order_hash=order_hash or in_flight_order.exchange_order_id, + state=state, + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=filled_size, + direction=in_flight_order.trade_type.name.lower(), + ) + + def configure_get_historical_spot_orders_empty_response(self): + paging = Paging(total=1) + mock_response = OrdersHistoryResponse(paging=paging) + self.injective_async_client_mock.get_historical_spot_orders.return_value = mock_response + + def configure_get_historical_spot_orders_response( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + ): + """ + orders { + order_hash: "0x0f62edfb64644762c20490d9573034c2f319e87e857401c41eea1fe373045dd7" # noqa: documentation + market_id: "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0" # noqa: documentation + subaccount_id: "0x1b99514e320ae0087be7f87b1e3057853c43b799000000000000000000000000" # noqa: documentation + execution_type: "limit" + order_type: "sell_po" + price: "1887550000" + trigger_price: "0" + quantity: "14.66" + filled_quantity: "0" + state: "canceled" + created_at: 1660245368028 + updated_at: 1660245374789 + direction: "sell" + } + paging { + total: 1000 + } + """ + order = self.get_spot_order_history( + timestamp=timestamp, + order_hash=order_hash, + state=state, + execution_type=execution_type, + order_type=order_type, + price=price, + size=size, + filled_size=filled_size, + direction=direction, + ) + paging = Paging(total=1) + mock_response = OrdersHistoryResponse(paging=paging) + mock_response.orders.append(order) + self.injective_async_client_mock.get_historical_spot_orders.return_value = mock_response + + def configure_account_quote_balance_stream_event( + self, timestamp: float, total_balance: Decimal, available_balance: Decimal + ): + timestamp_ms = int(timestamp * 1e3) + deposit = Account_SubaccountDeposit( + total_balance=str(total_balance * Decimal(f"1e{self.quote_decimals}")), + available_balance=str(available_balance * Decimal(f"1e{self.quote_decimals}")), + ) + balance = SubaccountBalance( + subaccount_id=self.sub_account_id, + account_address="someAccountAddress", + denom=self.quote_denom, + deposit=deposit, + ) + balance_event = StreamSubaccountBalanceResponse( + balance=balance, + timestamp=timestamp_ms, + ) + self.injective_async_client_mock.stream_subaccount_balance.return_value.add(balance_event) + + self.configure_get_account_balances_response( + quote_total_balance=total_balance, quote_available_balance=available_balance, + ) + + def get_spot_order_history( + self, + timestamp: float, + order_hash: str, + state: str, + execution_type: str, + order_type: str, + price: Decimal, + size: Decimal, + filled_size: Decimal, + direction: str, + ) -> SpotOrderHistory: + timestamp_ms = int(timestamp * 1e3) + order = SpotOrderHistory( + order_hash=order_hash, + market_id=self.market_id, + subaccount_id="someSubAccountId", + execution_type=execution_type, + order_type=order_type, + price=str(price * Decimal(f"1e{self.quote_decimals - self.base_decimals}")), + trigger_price="0", + quantity=str(size * Decimal(f"1e{self.base_decimals}")), + filled_quantity=str(filled_size * Decimal(f"1e{self.base_decimals}")), + state=state, + created_at=timestamp_ms, + updated_at=timestamp_ms, + direction=direction, + ) + return order + + def configure_get_account_portfolio_response( + self, + base_total_balance: Decimal, + base_available_balance: Decimal, + quote_total_balance: Decimal, + quote_available_balance: Decimal, + ): + pass + + def configure_get_account_balances_response( + self, + base_bank_balance: Decimal = s_decimal_0, + quote_bank_balance: Decimal = s_decimal_0, + base_total_balance: Decimal = s_decimal_0, + base_available_balance: Decimal = s_decimal_0, + quote_total_balance: Decimal = s_decimal_0, + quote_available_balance: Decimal = s_decimal_0, + sub_account_id: Optional[str] = None, + ): + sub_account_id = sub_account_id or self.sub_account_id + subaccount_list = [] + bank_coin_list = [] + + if base_total_balance != s_decimal_0: + base_deposit = SubaccountDeposit( + total_balance=str(base_total_balance * Decimal(f"1e{self.base_decimals}")), + available_balance=str(base_available_balance * Decimal(f"1e{self.base_decimals}")), + ) + base_balance = SubaccountBalanceV2( + subaccount_id=sub_account_id, + denom=self.base_denom, + deposit=base_deposit + ) + subaccount_list.append(base_balance) + + if quote_total_balance != s_decimal_0: + quote_deposit = SubaccountDeposit( + total_balance=str(quote_total_balance * Decimal(f"1e{self.quote_decimals}")), + available_balance=str(quote_available_balance * Decimal(f"1e{self.quote_decimals}")), + ) + quote_balance = SubaccountBalanceV2( + subaccount_id=sub_account_id, + denom=self.quote_denom, + deposit=quote_deposit, + ) + subaccount_list.append(quote_balance) + + if base_bank_balance != s_decimal_0: + base_scaled_amount = str(base_bank_balance * Decimal(f"1e{self.base_decimals}")) + coin = Coin(amount=base_scaled_amount, denom=self.base_denom) + bank_coin_list.append(coin) + + if quote_bank_balance != s_decimal_0: + quote_scaled_amount = str(quote_bank_balance * Decimal(f"1e{self.quote_decimals}")) + coin = Coin(amount=quote_scaled_amount, denom=self.quote_denom) + bank_coin_list.append(coin) + + portfolio = Portfolio(account_address="someAccountAddress", bank_balances=bank_coin_list, + subaccounts=subaccount_list) + + self.injective_async_client_mock.get_account_portfolio.return_value = AccountPortfolioResponse( + portfolio=portfolio) + + def configure_get_tx_by_hash_creation_response( + self, + timestamp: float, + success: bool, + order_hashes: Optional[List[str]] = None, + transaction_hash: str = "", + trade_type: TradeType = TradeType.BUY, + is_order_failed: bool = False, + ): + order_hashes = order_hashes or [] + data_data = "\n\275\001\n0/injective.exchange.v1beta1.MsgBatchUpdateOrders" + if success and not is_order_failed: + data_data += "\022\210\001\032B" + "\032B".join(order_hashes) + gas_wanted = int(BASE_GAS + SPOT_SUBMIT_ORDER_GAS + GAS_BUFFER) + gas_amount_scaled = self.order_creation_gas_estimate * Decimal("1e18") + gas_amount = CosmosCoin(denom="inj", amount=str(int(gas_amount_scaled))) + gas_fee = GasFee(gas_limit=gas_wanted, payer="") + gas_fee.amount.append(gas_amount) + messages_data = [ + { + 'type': '/injective.exchange.v1beta1.MsgBatchUpdateOrders', + 'value': { + 'order': { + 'market_id': self.market_id, + 'order_info': { + 'fee_recipient': 'inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r', + 'price': '0.000000000007523000', + 'quantity': '10000000000000000.000000000000000000', + 'subaccount_id': self.sub_account_id, + }, + 'order_type': "BUY" if trade_type == TradeType.BUY else "SELL", + 'trigger_price': '0.000000000000000000', + }, + 'sender': 'inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r', + }, + } + ] + data = TxDetailData( + hash=transaction_hash, + data=data_data.encode(), + gas_wanted=gas_wanted, + gas_used=int(gas_wanted * Decimal("0.9")), + gas_fee=gas_fee, + code=0 if success else 6, + block_unix_timestamp=int(timestamp * 1e3), + messages=json.dumps(messages_data).encode(), + ) + self.injective_async_client_mock.get_tx_by_hash.return_value = GetTxByTxHashResponse(data=data) + + def configure_get_tx_by_hash_cancelation_response( + self, + timestamp: float, + order_hash: str = "", + transaction_hash: str = "", + ): + data_data = "\n0\n./injective.exchange.v1beta1.MsgCancelSpotOrder" + gas_wanted = int(BASE_GAS + SPOT_CANCEL_ORDER_GAS + GAS_BUFFER) + gas_amount_scaled = self.order_cancelation_gas_estimate * Decimal("1e18") + gas_amount = CosmosCoin(denom="inj", amount=str(int(gas_amount_scaled))) + gas_fee = GasFee(gas_limit=gas_wanted, payer="") + gas_fee.amount.append(gas_amount) + messages_data = [ + { + 'type': '/injective.exchange.v1beta1.MsgCancelSpotOrder', + 'value': { + 'market_id': self.market_id, + "order_hash": order_hash, + 'sender': 'inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r', + 'subaccount_id': self.sub_account_id, + }, + } + ] + data = TxDetailData( + hash=transaction_hash, + data=data_data.encode(), + gas_wanted=gas_wanted, + gas_used=int(gas_wanted * Decimal("0.9")), + gas_fee=gas_fee, + code=0, + block_unix_timestamp=int(timestamp * 1e3), + messages=json.dumps(messages_data).encode(), + ) + self.injective_async_client_mock.get_tx_by_hash.return_value = GetTxByTxHashResponse(data=data) + + def configure_creation_transaction_stream_event( + self, timestamp: float, transaction_hash: str, trade_type: TradeType = TradeType.BUY + ): + """ + block_number: 21394573 + block_timestamp: "2022-12-12 06:15:31.072 +0000 UTC" + hash: "0x2956ee8cf58f2b19646d00d794bfa6a857fcb7a58f6cd0879de5b91e849f60bb" # noqa: documentation + messages: "[{\"type\":\"/injective.exchange.v1beta1.MsgCreateSpotLimitOrder\",\"value\":{\"order\":{\"market_id\":\"0x572f05fd93a6c2c4611b2eba1a0a36e102b6a592781956f0128a27662d84f112\",\"order_info\":{\"fee_recipient\":\"inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx\",\"price\":\"0.000000000004500000\",\"quantity\":\"51110000000000000000.000000000000000000\",\"subaccount_id\":\"0x20b6c8f178dbcc6af6f09fbe4cb9e213641a8bce000000000000000000000000\"},\"order_type\":\"SELL_PO\",\"trigger_price\":null},\"sender\":\"inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx\"}}]" # noqa: documentation + tx_number: 135730075 + """ + message = [ + { + "type": "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder", + "value": { + "order": { + "market_id": self.market_id, + "order_info": { + "fee_recipient": "inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx", + "price": "0.000000000004500000", + "quantity": "51110000000000000000.000000000000000000", + "subaccount_id": self.sub_account_id, + }, + "order_type": "BUY" if trade_type == TradeType.BUY else "SELL", + "trigger_price": None, + }, + "sender": "inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx", + }, + }, + ] + transaction_event = StreamTxsResponse( + block_number=21393769, + block_timestamp=f"{pd.Timestamp.utcfromtimestamp(timestamp / 1e3).strftime('%Y-%m-%d %H:%M:%S.%f')} +0000 UTC", + hash=transaction_hash, + messages=json.dumps(message), + tx_number=135726991, + ) + self.injective_async_client_mock.stream_txs.return_value.add(transaction_event) + + def configure_cancelation_transaction_stream_event(self, timestamp: float, transaction_hash: str, order_hash: str): + """ + block_number: 21393769 + block_timestamp: "2022-12-12 06:00:22.878 +0000 UTC" + hash: "0xa3dbf1340278ef5c9443b88c992e715cc72140a79c6a961a2513a9ed8774afb8" # noqa: documentation + messages: "[{\"type\":\"/injective.exchange.v1beta1.MsgCancelSpotOrder\",\"value\":{\"market_id\":\"0xd1956e20d74eeb1febe31cd37060781ff1cb266f49e0512b446a5fafa9a16034\",\"order_hash\":\"0x0b7c4b6753c938e6ea994d77d6b2fa40b60bd949317e8f5f7a8f290e1925d303\",\"sender\":\"inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx\",\"subaccount_id\":\"0x20b6c8f178dbcc6af6f09fbe4cb9e213641a8bce000000000000000000000000\"}}]" # noqa: documentation + tx_number: 135726991 + """ + message = [ + { + "type": "/injective.exchange.v1beta1.MsgCancelSpotOrder", + "value": { + "market_id": self.market_id, + "order_hash": order_hash, + "sender": "inj1yzmv3utcm0xx4ahsn7lyew0zzdjp4z7wlx44vx", + "subaccount_id": self.sub_account_id, + }, + }, + ] + transaction_event = StreamTxsResponse( + block_number=21393769, + block_timestamp=f"{pd.Timestamp.utcfromtimestamp(timestamp / 1e3).strftime('%Y-%m-%d %H:%M:%S.%f')} +0000 UTC", + hash=transaction_hash, + messages=json.dumps(message), + tx_number=135726991, + ) + self.injective_async_client_mock.stream_txs.return_value.add(transaction_event) + + def configure_active_spot_markets_response(self, timestamp: float): + base_token_meta = TokenMeta( + name="Coin", + address=self.base_coin_address, + symbol=self.base, + decimals=self.base_decimals, + updated_at=int(timestamp * 1e3), + ) + quote_token_meta = TokenMeta( + name="Alpha", + address=self.quote_coin_address, + symbol=self.quote, + decimals=self.quote_decimals, + updated_at=int(timestamp * 1e3), + ) + inj_token_meta = TokenMeta( + name="Injective Protocol", + address="0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", # noqa: mock + symbol="INJ", + decimals=18, + updated_at=int(timestamp * 1e3), + ) + min_price_tick_size = str( + self.min_price_tick_size * Decimal(f"1e{self.quote_decimals - self.base_decimals}") + ) + min_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) + market = SpotMarketInfo( + market_id=self.market_id, + market_status="active", + ticker=f"{self.base}/{self.quote}", + base_denom=self.base_denom, + base_token_meta=base_token_meta, + quote_denom=self.quote_denom, + quote_token_meta=quote_token_meta, + maker_fee_rate=str(self.maker_fee_rate), + taker_fee_rate=str(self.taker_fee_rate), + service_provider_fee="0.4", + min_price_tick_size=min_price_tick_size, + min_quantity_tick_size=min_quantity_tick_size, + ) + inj_pair_min_price_tick_size = str( + self.min_price_tick_size * Decimal(f"1e{18 - self.base_decimals}") + ) + inj_pair_min_quantity_tick_size = str(self.min_quantity_tick_size * Decimal(f"1e{self.base_decimals}")) + inj_pair_market = SpotMarketInfo( + market_id="anotherMarketId", + market_status="active", + ticker=f"INJ/{self.quote}", + base_denom="inj", + base_token_meta=inj_token_meta, + quote_denom=self.quote_denom, + quote_token_meta=quote_token_meta, + maker_fee_rate=str(self.maker_fee_rate), + taker_fee_rate=str(self.taker_fee_rate), + service_provider_fee="0.4", + min_price_tick_size=inj_pair_min_price_tick_size, + min_quantity_tick_size=inj_pair_min_quantity_tick_size, + ) + markets = MarketsResponse() + markets.markets.append(market) + markets.markets.append(inj_pair_market) + + self.injective_async_client_mock.get_spot_markets.return_value = markets diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_api_data_source.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_api_data_source.py new file mode 100644 index 0000000..a8ac214 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_api_data_source.py @@ -0,0 +1,883 @@ +import asyncio +import unittest +from contextlib import ExitStack +from decimal import Decimal +from pathlib import Path +from test.hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_mock_utils import InjectiveClientMock +from test.mock.http_recorder import HttpPlayer +from typing import Awaitable, List +from unittest.mock import AsyncMock, patch + +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source import ( + InjectiveAPIDataSource, +) +from hummingbot.connector.gateway.common_types import CancelOrderResult, PlaceOrderResult +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import ( + AddedToCostTradeFee, + DeductedFromReturnsTradeFee, + MakerTakerExchangeFeeRates, + TokenAmount, +) +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import AccountEvent, BalanceUpdateEvent, MarketEvent, OrderBookDataSourceEvent +from hummingbot.core.network_iterator import NetworkStatus + + +class MockExchange(ExchangeBase): + pass + + +class InjectiveAPIDataSourceTest(unittest.TestCase): + base: str + quote: str + trading_pair: str + sub_account_id: str + db_path: Path + http_player: HttpPlayer + patch_stack: ExitStack + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base = "COIN" + cls.quote = "ALPHA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.inj_trading_pair = combine_to_hb_trading_pair(base="INJ", quote=cls.quote) + cls.sub_account_id = "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18" # noqa: mock + + def setUp(self) -> None: + super().setUp() + self.initial_timestamp = 1669100347689 + self.injective_async_client_mock = InjectiveClientMock( + initial_timestamp=self.initial_timestamp, + sub_account_id=self.sub_account_id, + base=self.base, + quote=self.quote, + ) + self.injective_async_client_mock.start() + + client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) + + self.connector = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.tracker = GatewayOrderTracker(connector=self.connector) + connector_spec = { + "chain": "injective", + "network": "mainnet", + "wallet_address": self.sub_account_id + } + self.data_source = InjectiveAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + self.data_source.gateway_order_tracker = self.tracker + + self.trades_logger = EventLogger() + self.order_updates_logger = EventLogger() + self.trade_updates_logger = EventLogger() + self.snapshots_logger = EventLogger() + self.balance_logger = EventLogger() + + self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.TRADE_EVENT, listener=self.trades_logger) + self.data_source.add_listener(event_tag=MarketEvent.OrderUpdate, listener=self.order_updates_logger) + self.data_source.add_listener(event_tag=MarketEvent.TradeUpdate, listener=self.trade_updates_logger) + self.data_source.add_listener(event_tag=OrderBookDataSourceEvent.SNAPSHOT_EVENT, listener=self.snapshots_logger) + self.data_source.add_listener(event_tag=AccountEvent.BalanceEvent, listener=self.balance_logger) + + self.async_run_with_timeout(coro=self.data_source.start()) + + @staticmethod + def async_run_with_timeout(coro: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coro, timeout)) + return ret + + def tearDown(self) -> None: + self.injective_async_client_mock.stop() + self.async_run_with_timeout(coro=self.data_source.stop()) + super().tearDown() + + def test_place_order(self): + expected_exchange_order_id = "someEOID" + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + self.injective_async_client_mock.configure_place_order_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + exchange_order_id=expected_exchange_order_id, + trade_type=TradeType.BUY, + price=Decimal("10"), + size=Decimal("2"), + ) + order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("2"), + ) + exchange_order_id, misc_updates = self.async_run_with_timeout(coro=self.data_source.place_order(order=order)) + + self.assertEqual(expected_exchange_order_id, exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, misc_updates) + + def test_batch_order_create(self): + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + buy_expected_exchange_order_id = ( + "0x7df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc8" # noqa: mock + ) + sell_expected_exchange_order_id = ( + "0x8df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc9" # noqa: mock + ) + buy_order_to_create = GatewayInFlightOrder( + client_order_id="someCOIDCancelCreate", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("2"), + exchange_order_id=buy_expected_exchange_order_id, + ) + sell_order_to_create = GatewayInFlightOrder( + client_order_id="someCOIDCancelCreate", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("11"), + amount=Decimal("3"), + exchange_order_id=sell_expected_exchange_order_id, + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + self.injective_async_client_mock.configure_batch_order_create_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + created_orders=orders_to_create, + ) + + result: List[PlaceOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_create(orders_to_create=orders_to_create) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(buy_expected_exchange_order_id, result[0].exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(sell_expected_exchange_order_id, result[1].exchange_order_id) + self.assertEqual({"creation_transaction_hash": expected_transaction_hash}, result[1].misc_updates) + + def test_cancel_order(self): + creation_transaction_hash = "0x8f6g4552091a69125d5dfcb7b8c2659029395ceg" # noqa: mock + expected_client_order_id = "someCOID" + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + expected_exchange_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + order = GatewayInFlightOrder( + client_order_id=expected_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10"), + amount=Decimal("1"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash, + ) + order.order_fills[creation_transaction_hash] = None # to prevent requesting creation transaction + self.injective_async_client_mock.configure_cancel_order_response( + timestamp=self.initial_timestamp, transaction_hash=expected_transaction_hash + ) + self.injective_async_client_mock.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=self.initial_timestamp, + in_flight_order=order, + order_hash=expected_exchange_order_id, + is_canceled=True, + ) + cancelation_success, misc_updates = self.async_run_with_timeout(coro=self.data_source.cancel_order(order=order)) + + self.assertTrue(cancelation_success) + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, misc_updates) + + self.injective_async_client_mock.run_until_all_items_delivered() + + def test_batch_order_cancel(self): + expected_transaction_hash = "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf" # noqa: mock + buy_expected_exchange_order_id = ( + "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + ) + sell_expected_exchange_order_id = ( + "0x7df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc8" # noqa: mock + ) + creation_transaction_hash_for_cancel = "0x8f6g4552091a69125d5dfcb7b8c2659029395ceg" # noqa: mock + buy_order_to_cancel = GatewayInFlightOrder( + client_order_id="someCOIDCancel", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("10"), + amount=Decimal("1"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=buy_expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash_for_cancel, + ) + sell_order_to_cancel = GatewayInFlightOrder( + client_order_id="someCOIDCancel", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("11"), + amount=Decimal("2"), + creation_timestamp=self.initial_timestamp, + exchange_order_id=sell_expected_exchange_order_id, + creation_transaction_hash=creation_transaction_hash_for_cancel, + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=buy_order_to_cancel) + self.data_source.gateway_order_tracker.start_tracking_order(order=sell_order_to_cancel) + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + self.injective_async_client_mock.configure_batch_order_cancel_response( + timestamp=self.initial_timestamp, + transaction_hash=expected_transaction_hash, + canceled_orders=orders_to_cancel, + ) + + result: List[CancelOrderResult] = self.async_run_with_timeout( + coro=self.data_source.batch_order_cancel(orders_to_cancel=orders_to_cancel) + ) + + self.assertEqual(2, len(result)) + self.assertEqual(buy_order_to_cancel.client_order_id, result[0].client_order_id) + self.assertIsNone(result[0].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, result[0].misc_updates) + self.assertEqual(sell_order_to_cancel.client_order_id, result[1].client_order_id) + self.assertIsNone(result[1].exception) # i.e. success + self.assertEqual({"cancelation_transaction_hash": expected_transaction_hash}, result[1].misc_updates) + + def test_get_trading_rules(self): + trading_rules = self.async_run_with_timeout(coro=self.data_source.get_trading_rules()) + + self.assertEqual(2, len(trading_rules)) + self.assertIn(self.trading_pair, trading_rules) + self.assertIn(self.inj_trading_pair, trading_rules) + + trading_rule: TradingRule = trading_rules[self.trading_pair] + + self.assertEqual(self.trading_pair, trading_rule.trading_pair) + self.assertEqual(Decimal("0.00001"), trading_rule.min_price_increment) + self.assertEqual(Decimal("0.00001"), trading_rule.min_quote_amount_increment) + self.assertEqual(Decimal("0.001"), trading_rule.min_base_amount_increment) + + def test_get_symbol_map(self): + symbol_map = self.async_run_with_timeout(coro=self.data_source.get_symbol_map()) + + self.assertIsInstance(symbol_map, bidict) + self.assertEqual(2, len(symbol_map)) + self.assertIn(self.injective_async_client_mock.market_id, symbol_map) + self.assertIn(self.trading_pair, symbol_map.inverse) + self.assertIn(self.inj_trading_pair, symbol_map.inverse) + + def test_get_last_traded_price(self): + target_price = Decimal("1.157") + target_maker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + self.injective_async_client_mock.configure_spot_trades_response_to_request_without_exchange_order_id( + timestamp=self.initial_timestamp, + price=target_price, + size=Decimal("0.001"), + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + ) + price = self.async_run_with_timeout(coro=self.data_source.get_last_traded_price(trading_pair=self.trading_pair)) + + self.assertEqual(target_price, price) + + def test_get_order_book_snapshot(self): + self.injective_async_client_mock.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + order_book_snapshot: OrderBookMessage = self.async_run_with_timeout( + coro=self.data_source.get_order_book_snapshot(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.initial_timestamp, order_book_snapshot.timestamp) + self.assertEqual(2, len(order_book_snapshot.bids)) + self.assertEqual(9, order_book_snapshot.bids[0].price) + self.assertEqual(1, order_book_snapshot.bids[0].amount) + self.assertEqual(1, len(order_book_snapshot.asks)) + self.assertEqual(11, order_book_snapshot.asks[0].price) + self.assertEqual(3, order_book_snapshot.asks[0].amount) + + def test_delivers_trade_events(self): + target_price = Decimal("1.157") + target_size = Decimal("0.001") + target_maker_fee = DeductedFromReturnsTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + target_exchange_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + target_trade_id = "19889401_someTradeId" + self.injective_async_client_mock.configure_trade_stream_event( + timestamp=self.initial_timestamp, + price=target_price, + size=target_size, + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + exchange_order_id=target_exchange_order_id, + taker_trade_id=target_trade_id, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.trades_logger.event_log)) + self.assertEqual(2, len(self.trade_updates_logger.event_log)) + + first_trade_event: OrderBookMessage = self.trades_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, first_trade_event.timestamp) + self.assertEqual(self.trading_pair, first_trade_event.content["trading_pair"]) + self.assertEqual(TradeType.SELL, first_trade_event.content["trade_type"]) + self.assertEqual(target_price, first_trade_event.content["price"]) + self.assertEqual(target_size, first_trade_event.content["amount"]) + self.assertFalse(first_trade_event.content["is_taker"]) + + second_trade_event: OrderBookMessage = self.trades_logger.event_log[1] + + self.assertEqual(self.initial_timestamp, second_trade_event.timestamp) + self.assertEqual(self.trading_pair, second_trade_event.content["trading_pair"]) + self.assertEqual(TradeType.BUY, second_trade_event.content["trade_type"]) + self.assertEqual(target_price, second_trade_event.content["price"]) + self.assertEqual(target_size, second_trade_event.content["amount"]) + self.assertTrue(second_trade_event.content["is_taker"]) + + first_trade_update: TradeUpdate = self.trade_updates_logger.event_log[0] + + self.assertEqual(self.trading_pair, first_trade_update.trading_pair) + self.assertEqual(self.initial_timestamp, first_trade_update.fill_timestamp) + self.assertEqual(target_price, first_trade_update.fill_price) + self.assertEqual(target_size, first_trade_update.fill_base_amount) + self.assertEqual(target_price * target_size, first_trade_update.fill_quote_amount) + self.assertEqual(target_maker_fee, first_trade_update.fee) + + second_order_event: TradeUpdate = self.trade_updates_logger.event_log[1] + + self.assertEqual(target_trade_id, second_order_event.trade_id) + self.assertEqual(target_exchange_order_id, second_order_event.exchange_order_id) + self.assertEqual(self.trading_pair, second_order_event.trading_pair) + self.assertEqual(self.initial_timestamp, second_order_event.fill_timestamp) + self.assertEqual(target_price, second_order_event.fill_price) + self.assertEqual(target_size, second_order_event.fill_base_amount) + self.assertEqual(target_price * target_size, second_order_event.fill_quote_amount) + self.assertEqual(target_taker_fee, second_order_event.fee) + + def test_delivers_order_created_events(self): + target_order_id = "someOrderHash" + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="buy_po", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="buy", + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[0] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.OPEN, order_event.new_state) + + target_order_id = "anotherOrderHash" + target_price = Decimal("50") + target_size = Decimal("1") + order = GatewayInFlightOrder( + client_order_id="someOtherOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="sell", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="sell", + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.OPEN, order_event.new_state) + + def test_delivers_order_fully_filled_events(self): + target_order_id = "someOrderHash" + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="filled", + execution_type="limit", + order_type="buy", + price=target_price, + size=target_size, + filled_size=target_size, + direction="buy", + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.FILLED, order_event.new_state) + + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="filled", + execution_type="limit", + order_type="sell_po", + price=target_price, + size=target_size, + filled_size=target_size, + direction="sell", + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(4, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[3] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.FILLED, order_event.new_state) + + def test_delivers_order_canceled_events(self): + target_order_id = "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + target_price = Decimal("100") + target_size = Decimal("2") + order = GatewayInFlightOrder( + client_order_id="someOrderCID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + exchange_order_id=target_order_id, + ) + self.tracker.start_tracking_order(order=order) + self.injective_async_client_mock.configure_order_stream_event( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="canceled", + execution_type="limit", + order_type="buy", + price=target_price, + size=target_size, + filled_size=Decimal("0"), + direction="buy", + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(2, len(self.order_updates_logger.event_log)) + + order_event: OrderUpdate = self.order_updates_logger.event_log[1] + + self.assertIsInstance(order_event, OrderUpdate) + self.assertEqual(self.initial_timestamp, order_event.update_timestamp) + self.assertEqual(target_order_id, order_event.exchange_order_id) + self.assertEqual(OrderState.CANCELED, order_event.new_state) + + def test_delivers_order_book_snapshots(self): + self.injective_async_client_mock.configure_orderbook_snapshot_stream_event( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.snapshots_logger.event_log)) + + snapshot_event: OrderBookMessage = self.snapshots_logger.event_log[0] + + self.assertEqual(self.initial_timestamp, snapshot_event.timestamp) + self.assertEqual(2, len(snapshot_event.bids)) + self.assertEqual(9, snapshot_event.bids[0].price) + self.assertEqual(1, snapshot_event.bids[0].amount) + self.assertEqual(1, len(snapshot_event.asks)) + self.assertEqual(11, snapshot_event.asks[0].price) + self.assertEqual(3, snapshot_event.asks[0].amount) + + def test_get_account_balances(self): + base_bank_balance = Decimal("75") + base_total_balance = Decimal("10") + base_available_balance = Decimal("9") + quote_total_balance = Decimal("200") + quote_available_balance = Decimal("150") + self.injective_async_client_mock.configure_get_account_balances_response( + base_bank_balance=base_bank_balance, + quote_bank_balance=Decimal("0"), + base_total_balance=base_total_balance, + base_available_balance=base_available_balance, + quote_total_balance=quote_total_balance, + quote_available_balance=quote_available_balance, + ) + + subaccount_balances = self.async_run_with_timeout(coro=self.data_source.get_account_balances()) + + self.assertEqual(base_total_balance, subaccount_balances[self.base]["total_balance"]) + self.assertEqual(base_available_balance, subaccount_balances[self.base]["available_balance"]) + self.assertEqual(quote_total_balance, subaccount_balances[self.quote]["total_balance"]) + self.assertEqual(quote_available_balance, subaccount_balances[self.quote]["available_balance"]) + + def test_get_order_status_update_success(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.injective_async_client_mock.configure_get_historical_spot_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="booked", + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.OPEN, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) + self.assertIn("creation_transaction_hash", status_update.misc_updates) + self.assertEqual(creation_transaction_hash, status_update.misc_updates["creation_transaction_hash"]) + + def test_get_all_order_fills_no_fills(self): + target_order_id = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + self.injective_async_client_mock.configure_get_historical_spot_orders_response( + timestamp=self.initial_timestamp, + order_hash=target_order_id, + state="booked", + execution_type="limit", + order_type="sell", + price=Decimal("10"), + size=Decimal("2"), + filled_size=Decimal("0"), + direction="sell", + ) + in_flight_order = GatewayInFlightOrder( + client_order_id="someOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=Decimal("10"), + amount=Decimal("2"), + exchange_order_id=target_order_id, + ) + + trade_updates = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(0, len(trade_updates)) + + def test_get_all_order_fills(self): + target_client_order_id = "someOrderId" + target_exchange_order_id = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + target_trade_id = "someTradeHash" + target_price = Decimal("10") + target_size = Decimal("2") + target_trade_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.01"))]) + target_partial_fill_size = target_size / 2 + target_fill_ts = self.initial_timestamp + 10 + self.injective_async_client_mock.configure_get_historical_spot_orders_response( + timestamp=self.initial_timestamp, + order_hash=target_exchange_order_id, + state="partial_filled", + execution_type="limit", + order_type="sell", + price=target_price, + size=target_size, + filled_size=target_partial_fill_size, + direction="sell", + ) + self.injective_async_client_mock.configure_trades_response_with_exchange_order_id( + timestamp=target_fill_ts, + exchange_order_id=target_exchange_order_id, + price=target_price, + size=target_partial_fill_size, + fee=target_trade_fee, + trade_id=target_trade_id, + ) + in_flight_order = GatewayInFlightOrder( + client_order_id=target_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=target_price, + amount=target_size, + exchange_order_id=target_exchange_order_id, + ) + + trade_updates: List[TradeUpdate] = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order) + ) + + self.assertEqual(1, len(trade_updates)) + + trade_update = trade_updates[0] + + self.assertEqual(target_trade_id, trade_update.trade_id) + self.assertEqual(target_client_order_id, trade_update.client_order_id) + self.assertEqual(target_exchange_order_id, trade_update.exchange_order_id) + self.assertEqual(self.trading_pair, trade_update.trading_pair) + self.assertEqual(target_fill_ts, trade_update.fill_timestamp) + self.assertEqual(target_price, trade_update.fill_price) + self.assertEqual(target_partial_fill_size, trade_update.fill_base_amount) + self.assertEqual(target_partial_fill_size * target_price, trade_update.fill_quote_amount) + self.assertEqual(target_trade_fee, trade_update.fee) + + def test_check_network_status(self): + self.injective_async_client_mock.configure_check_network_failure() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, status) + + self.injective_async_client_mock.configure_check_network_success() + + status = self.async_run_with_timeout(coro=self.data_source.check_network_status()) + + self.assertEqual(NetworkStatus.CONNECTED, status) + + def test_get_trading_fees(self): + all_trading_fees = self.async_run_with_timeout(coro=self.data_source.get_trading_fees()) + + self.assertIn(self.trading_pair, all_trading_fees) + + pair_trading_fees: MakerTakerExchangeFeeRates = all_trading_fees[self.trading_pair] + + service_provider_rebate = Decimal("1") - self.injective_async_client_mock.service_provider_fee + expected_maker_fee = self.injective_async_client_mock.maker_fee_rate * service_provider_rebate + expected_taker_fee = self.injective_async_client_mock.taker_fee_rate * service_provider_rebate + self.assertEqual(expected_maker_fee, pair_trading_fees.maker) + self.assertEqual(expected_taker_fee, pair_trading_fees.taker) + + def test_delivers_balance_events(self): + target_total_balance = Decimal("20") + target_available_balance = Decimal("19") + self.injective_async_client_mock.configure_account_quote_balance_stream_event( + timestamp=self.initial_timestamp, + total_balance=target_total_balance, + available_balance=target_available_balance, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + self.assertEqual(1, len(self.balance_logger.event_log)) + + balance_event: BalanceUpdateEvent = self.balance_logger.event_log[0] + + self.assertEqual(self.quote, balance_event.asset_name) + self.assertEqual(target_total_balance, balance_event.total_balance) + self.assertEqual(target_available_balance, balance_event.available_balance) + + def test_parses_transaction_event_for_order_creation_success(self): + creation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.tracker.start_tracking_order(order=in_flight_order) + self.injective_async_client_mock.configure_creation_transaction_stream_event( + timestamp=self.initial_timestamp + 1, transaction_hash=creation_transaction_hash + ) + self.injective_async_client_mock.configure_get_historical_spot_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="booked", + execution_type="market" if in_flight_order.order_type == OrderType.MARKET else "limit", + order_type=( + in_flight_order.trade_type.name.lower() + + ("_po" if in_flight_order.order_type == OrderType.LIMIT_MAKER else "") + ), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[0] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.OPEN, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) + + @patch( + "hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source" + ".InjectiveAPIDataSource._update_account_address_and_create_order_hash_manager", + new_callable=AsyncMock, + ) + def test_parses_transaction_event_for_order_creation_failure(self, _: AsyncMock): + creation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=target_order_hash, + ) + self.tracker.start_tracking_order(order=in_flight_order) + self.injective_async_client_mock.configure_order_status_update_response( + timestamp=self.initial_timestamp, + order=in_flight_order, + creation_transaction_hash=creation_transaction_hash, + is_failed=True, + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[1] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp, status_update.update_timestamp) + self.assertEqual(OrderState.FAILED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(0, len(self.trade_updates_logger.event_log)) + + def test_parses_transaction_event_for_order_cancelation(self): + cancelation_transaction_hash = "0x7cb1eafc389349f86da901cdcbfd9119435a2ea84d61c17b6ded778b6fd2f81d" # noqa: mock + target_order_hash = "0x6ba1eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2f70c" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id="someClientOrderID", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp, + price=Decimal("10"), + amount=Decimal("1"), + creation_transaction_hash="someHash", + exchange_order_id=target_order_hash, + ) + in_flight_order.order_fills["someHash"] = None # to prevent order creation transaction request + self.tracker.start_tracking_order(order=in_flight_order) + in_flight_order.cancel_tx_hash = cancelation_transaction_hash + self.injective_async_client_mock.configure_cancelation_transaction_stream_event( + timestamp=self.initial_timestamp + 1, + transaction_hash=cancelation_transaction_hash, + order_hash=target_order_hash, + ) + self.injective_async_client_mock.configure_get_historical_spot_orders_response( + timestamp=self.initial_timestamp + 1, + order_hash=target_order_hash, + state="canceled", + execution_type="limit", + order_type=in_flight_order.trade_type.name.lower(), + price=in_flight_order.price, + size=in_flight_order.amount, + filled_size=Decimal("0"), + direction=in_flight_order.trade_type.name.lower(), + ) + + self.injective_async_client_mock.run_until_all_items_delivered() + + status_update = self.order_updates_logger.event_log[1] + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertEqual(self.initial_timestamp + 1, status_update.update_timestamp) + self.assertEqual(OrderState.CANCELED, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(target_order_hash, status_update.exchange_order_id) diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_utils.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_utils.py new file mode 100644 index 0000000..44e189d --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/injective/test_injective_utils.py @@ -0,0 +1,48 @@ +from decimal import Decimal +from unittest import TestCase + +from pyinjective.constant import Denom + +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_utils import ( + derivative_price_to_backend, + derivative_quantity_to_backend, + floor_to, +) + + +class InjectiveUtilsTests(TestCase): + + def test_floor_to_utility_method(self): + original_value = Decimal("123.0123456789") + + result = floor_to(value=original_value, target=Decimal("0.001")) + self.assertEqual(Decimal("123.012"), result) + + result = floor_to(value=original_value, target=Decimal("1")) + self.assertEqual(Decimal("123"), result) + + def test_derivative_quantity_to_backend_utility_method(self): + denom = Denom( + description="Fixed denom", + base=2, + quote=6, + min_price_tick_size=1000, + min_quantity_tick_size=100, + ) + + backend_quantity = derivative_quantity_to_backend(quantity=Decimal("1"), denom=denom) + + self.assertEqual(100000000000000000000, backend_quantity) + + def test_derivative_price_to_backend_utility_method(self): + denom = Denom( + description="Fixed denom", + base=2, + quote=6, + min_price_tick_size=1000, + min_quantity_tick_size=100, + ) + + backend_quantity = derivative_price_to_backend(price=Decimal("123.45"), denom=denom) + + self.assertEqual(123450000000000000000000000, backend_quantity) diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/kujira/__init__.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/kujira/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/gateway/clob_spot/data_sources/kujira/test_kujira_api_data_source.py b/test/hummingbot/connector/gateway/clob_spot/data_sources/kujira/test_kujira_api_data_source.py new file mode 100644 index 0000000..ac9eb55 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/data_sources/kujira/test_kujira_api_data_source.py @@ -0,0 +1,716 @@ +import asyncio +from typing import Any, Dict, List, Union +from unittest.mock import patch + +from _decimal import Decimal +from bidict import bidict +from dotmap import DotMap + +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_api_data_source import KujiraAPIDataSource +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_helpers import ( + convert_hb_trading_pair_to_market_name, + convert_market_name_to_hb_trading_pair, + generate_hash, +) +from hummingbot.connector.gateway.clob_spot.data_sources.kujira.kujira_types import ( + OrderSide as KujiraOrderSide, + OrderStatus as KujiraOrderStatus, + OrderType as KujiraOrderType, +) +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.test_support.gateway_clob_api_data_source_test import AbstractGatewayCLOBAPIDataSourceTests +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.trade_fee import ( + DeductedFromReturnsTradeFee, + MakerTakerExchangeFeeRates, + TokenAmount, + TradeFeeBase, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class KujiraAPIDataSourceTest(AbstractGatewayCLOBAPIDataSourceTests.GatewayCLOBAPIDataSourceTests): + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.chain = "kujira" # noqa: mock + cls.network = "mainnet" + cls.base = "KUJI" # noqa: mock + cls.quote = "USK" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.owner_address = "kujira1yrensec9gzl7y3t3duz44efzgwj21b2arayr1w" # noqa: mock + + def setUp(self) -> None: + super().setUp() + + self.configure_asyncio_sleep() + self.data_source._gateway = self.gateway_instance_mock + self.configure_async_functions_with_decorator() + self.configure_get_market() + + def tearDown(self) -> None: + super().tearDown() + + @property + def expected_buy_client_order_id(self) -> str: + return "03719e91d18db65ec3bf5554d678e5b4" + + @property + def expected_sell_client_order_id(self) -> str: + return "02719e91d18db65ec3bf5554d678e5b2" + + @property + def expected_buy_exchange_order_id(self) -> str: + return "1" + + @property + def expected_sell_exchange_order_id(self) -> str: + return "2" + + @property + def exchange_base(self) -> str: + return self.base + + @property + def exchange_quote(self) -> str: + return self.quote + + @property + def expected_quote_decimals(self) -> int: + return 6 + + @property + def expected_base_decimals(self) -> int: + return 6 + + @property + def expected_maker_taker_fee_rates(self) -> MakerTakerExchangeFeeRates: + return MakerTakerExchangeFeeRates( + maker=Decimal("0.075"), + taker=Decimal("0.15"), + maker_flat_fees=[], + taker_flat_fees=[], + ) + + @property + def expected_min_price_increment(self): + return Decimal("0.001") + + @property + def expected_last_traded_price(self) -> Decimal: + return Decimal("0.641") + + @property + def expected_base_total_balance(self) -> Decimal: + return Decimal("6.355439") + + @property + def expected_base_available_balance(self) -> Decimal: + return Decimal("6.355439") + + @property + def expected_quote_total_balance(self) -> Decimal: + return Decimal("3.522325") + + @property + def expected_quote_available_balance(self) -> Decimal: + return Decimal("3.522325") + + @property + def expected_fill_price(self) -> Decimal: + return Decimal("11") + + @property + def expected_fill_size(self) -> Decimal: + return Decimal("3") + + @property + def expected_fill_fee_amount(self) -> Decimal: + return Decimal("0.15") + + @property + def expected_fill_fee(self) -> TradeFeeBase: + return DeductedFromReturnsTradeFee( + flat_fees=[TokenAmount(token=self.expected_fill_fee_token, amount=self.expected_fill_fee_amount)] + ) + + def build_api_data_source(self, with_api_key: bool = True) -> Any: + connector_spec = { + "chain": self.chain, + "network": self.network, + "wallet_address": self.owner_address, + } + + data_source = KujiraAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=self.client_config_map, + ) + + return data_source + + @staticmethod + def configure_asyncio_sleep(): + async def sleep(*_args, **_kwargs): + pass + + patch.object(asyncio, "sleep", new_callable=sleep) + + def configure_async_functions_with_decorator(self): + def wrapper(object, function): + async def closure(*args, **kwargs): + return await function(object, *args, **kwargs) + + return closure + + self.data_source._gateway_ping_gateway = wrapper(self.data_source, self.data_source._gateway_ping_gateway.original) + self.data_source._gateway_get_clob_markets = wrapper(self.data_source, self.data_source._gateway_get_clob_markets.original) + self.data_source._gateway_get_clob_orderbook_snapshot = wrapper(self.data_source, self.data_source._gateway_get_clob_orderbook_snapshot.original) + self.data_source._gateway_get_clob_ticker = wrapper(self.data_source, self.data_source._gateway_get_clob_ticker.original) + self.data_source._gateway_get_balances = wrapper(self.data_source, self.data_source._gateway_get_balances.original) + self.data_source._gateway_clob_place_order = wrapper(self.data_source, self.data_source._gateway_clob_place_order.original) + self.data_source._gateway_clob_cancel_order = wrapper(self.data_source, self.data_source._gateway_clob_cancel_order.original) + self.data_source._gateway_clob_batch_order_modify = wrapper(self.data_source, self.data_source._gateway_clob_batch_order_modify.original) + self.data_source._gateway_get_clob_order_status_updates = wrapper(self.data_source, self.data_source._gateway_get_clob_order_status_updates.original) + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.get_clob_markets") + def configure_get_market(self, *_args): + self.data_source._gateway.get_clob_markets.return_value = self.configure_gateway_get_clob_markets_response() + + def configure_place_order_response( + self, + timestamp: float, + transaction_hash: str, + exchange_order_id: str, + trade_type: TradeType, + price: Decimal, + size: Decimal, + ): + super().configure_place_order_response( + timestamp, + transaction_hash, + exchange_order_id, + trade_type, + price, + size, + ) + self.gateway_instance_mock.clob_place_order.return_value["id"] = "1" + + def configure_place_order_failure_response(self): + super().configure_place_order_failure_response() + self.gateway_instance_mock.clob_place_order.return_value["id"] = "1" + + def configure_batch_order_create_response( + self, + timestamp: float, + transaction_hash: str, + created_orders: List[GatewayInFlightOrder], + ): + super().configure_batch_order_create_response( + timestamp=self.initial_timestamp, + transaction_hash=self.expected_transaction_hash, + created_orders=created_orders, + ) + self.gateway_instance_mock.clob_batch_order_modify.return_value["ids"] = ["1", "2"] + + def get_trading_pairs_info_response(self) -> List[Dict[str, Any]]: + response = self.configure_gateway_get_clob_markets_response() + + market = response.markets[list(response.markets.keys())[0]] + + market_name = convert_market_name_to_hb_trading_pair(market.name) + + return [{"market_name": market_name, "market": market}] + + def get_order_status_response( + self, + timestamp: float, + trading_pair: str, + exchange_order_id: str, + client_order_id: str, + status: OrderState + ) -> List[Dict[str, Any]]: + return [DotMap({ + "id": exchange_order_id, + "orderHash": "", + "marketId": "kujira193dzcmy7lwuj4eda3zpwwt9ejal00xva0vawcvhgsyyp5cfh6jyq66wfrf", # noqa: mock + "active": "", + "subaccountId": "", # noqa: mock + "executionType": "", + "orderType": "LIMIT", + "price": "0.616", + "triggerPrice": "", + "quantity": "0.24777", + "filledQuantity": "", + "state": KujiraOrderStatus.from_hummingbot(status).name, + "createdAt": timestamp, + "updatedAt": "", + "direction": "BUY" + })] + + def get_clob_ticker_response( + self, + trading_pair: str, + last_traded_price: Decimal + ) -> Dict[str, Any]: + market = ( + self.configure_gateway_get_clob_markets_response() + ).markets[trading_pair] + + return { + "KUJI-USK": { # noqa: mock + "market": market, + "ticker": { + "price": "0.641" + }, + "price": "0.641", + "timestamp": 1694631135095 + } + } + + def configure_account_balances_response( + self, + base_total_balance: Decimal, + base_available_balance: Decimal, + quote_total_balance: Decimal, + quote_available_balance: Decimal + ): + self.gateway_instance_mock.get_balances.return_value = self.configure_gateway_get_balances_response() + + def configure_empty_order_fills_response(self): + pass + + def configure_trade_fill_response( + self, + timestamp: float, + exchange_order_id: str, + price: Decimal, + size: Decimal, + fee: TradeFeeBase, trade_id: Union[str, int], is_taker: bool + ): + pass + + @staticmethod + def configure_gateway_get_clob_markets_response(): + return DotMap({ + "network": "mainnet", + "timestamp": 1694561843115, + "latency": 0.001, + "markets": { + "KUJI-USK": { # noqa: mock + "id": "kujira193dzcmy7lwuj4eda3zpwwt9ejal00xva0vawcvhgsyyp5cfh6jyq66wfrf", # noqa: mock + "name": "KUJI/USK", # noqa: mock + "baseToken": { + "id": "ukuji", # noqa: mock + "name": "KUJI", # noqa: mock + "symbol": "KUJI", # noqa: mock + "decimals": 6 + }, + "quoteToken": { + "id": "factory/kujira1qk00h5atutpsv900x202pxx42npjr9thg58dnqpa72f2p7m2luase444a7/uusk", + # noqa: mock + "name": "USK", + "symbol": "USK", + "decimals": 6 + }, + "precision": 3, + "minimumOrderSize": "0.001", + "minimumPriceIncrement": "0.001", + "minimumBaseAmountIncrement": "0.001", + "minimumQuoteAmountIncrement": "0.001", + "fees": { + "maker": "0.075", + "taker": "0.15", + "serviceProvider": "0" + }, + "deprecated": False, + "connectorMarket": { + "address": "kujira193dzcmy7lwuj4eda3zpwwt9ejal00xva0vawcvhgsyyp5cfh6jyq66wfrf", # noqa: mock + "denoms": [ # noqa: mock + { + "reference": "ukuji", # noqa: mock + "decimals": 6, + "symbol": "KUJI" # noqa: mock + }, + { + "reference": "factory/kujira1qk00h5atutpsv900x202pxx42npjr9thg58dnqpa72f2p7m2luase444a7/uusk", + # noqa: mock + "decimals": 6, + "symbol": "USK" + } + ], + "precision": { + "decimal_places": 3 + }, + "decimalDelta": 0, + "multiswap": True, # noqa: mock + "pool": "kujira1g9xcvvh48jlckgzw8ajl6dkvhsuqgsx2g8u3v0a6fx69h7f8hffqaqu36t", # noqa: mock + "calc": "kujira1e6fjnq7q20sh9cca76wdkfg69esha5zn53jjewrtjgm4nktk824stzyysu" # noqa: mock + } + } + } + }, _dynamic=False) + + def configure_gateway_get_balances_response(self): + return { + "balances": { + "USK": "3.522325", + "axlUSDC": "1.999921", + "KUJI": "6.355439" + } + } + + def exchange_symbol_for_tokens( + self, + base_token: str, + quote_token: str + ) -> str: + return f"{base_token}-{quote_token}" + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.ping_gateway") + def test_gateway_ping_gateway(self, *_args): + self.data_source._gateway.ping_gateway.return_value = True + + result = self.async_run_with_timeout( + coro=self.data_source._gateway_ping_gateway() + ) + + expected = True + + self.assertEqual(expected, result) + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.ping_gateway") + def test_check_network_status_with_gateway_connected(self, *_args): + self.data_source._gateway.ping_gateway.return_value = True + + result = self.async_run_with_timeout( + coro=self.data_source.check_network_status() + ) + + expected = NetworkStatus.CONNECTED + + self.assertEqual(expected, result) + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.ping_gateway") + def test_check_network_status_with_gateway_not_connected(self, *_args): + self.data_source._gateway.ping_gateway.return_value = False + + result = self.async_run_with_timeout( + coro=self.data_source.check_network_status() + ) + + expected = NetworkStatus.NOT_CONNECTED + + self.assertEqual(expected, result) + + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.ping_gateway") + def test_check_network_status_with_gateway_exception(self, *_args): + self.configure_asyncio_sleep() + self.data_source._gateway.ping_gateway.side_effect = RuntimeError("Unknown error") + + result = self.async_run_with_timeout( + coro=self.data_source.check_network_status() + ) + + expected = NetworkStatus.NOT_CONNECTED + + self.assertEqual(expected, result) + + def test_batch_order_cancel(self): + super().test_batch_order_cancel() + + def test_batch_order_create(self): + super().test_batch_order_create() + + def test_cancel_order(self): + super().test_cancel_order() + + def test_cancel_order_transaction_fails(self): + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_timestamp=self.initial_timestamp, + exchange_order_id=self.expected_buy_exchange_order_id, + creation_transaction_hash="someCreationHash", + ) + self.data_source.gateway_order_tracker.start_tracking_order(order=order) + self.configure_cancel_order_failure_response() + + result = self.async_run_with_timeout(coro=self.data_source.cancel_order(order=order)) + + self.assertEqual(False, result[0]) + self.assertEqual(DotMap({}), result[1]) + + def test_check_network_status(self): + super().test_check_network_status() + + def test_delivers_balance_events(self): + super().test_delivers_balance_events() + + def test_delivers_order_book_snapshot_events(self): + pass + + def test_get_account_balances(self): + super().test_get_account_balances() + + def test_get_all_order_fills(self): + asyncio.get_event_loop().run_until_complete( + self.data_source._update_markets() + ) + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + initial_state=OrderState.PENDING_CREATE, + client_order_id=self.expected_sell_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.initial_timestamp - 10, + price=self.expected_sell_order_price, + amount=self.expected_sell_order_size, + exchange_order_id=self.expected_sell_exchange_order_id, + ) + self.data_source.gateway_order_tracker.active_orders[in_flight_order.client_order_id] = in_flight_order + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.FILLED, + ) + + trade_updates: List[TradeUpdate] = self.async_run_with_timeout( + coro=self.data_source.get_all_order_fills(in_flight_order=in_flight_order), + ) + + self.assertEqual(1, len(trade_updates)) + + trade_update = trade_updates[0] + + self.assertIsNotNone(trade_update.trade_id) + self.assertEqual(self.expected_sell_client_order_id, trade_update.client_order_id) + self.assertEqual(self.expected_sell_exchange_order_id, trade_update.exchange_order_id) + self.assertEqual(self.trading_pair, trade_update.trading_pair) + self.assertLess(float(0), trade_update.fill_timestamp) + self.assertEqual(self.expected_fill_price, trade_update.fill_price) + self.assertEqual(self.expected_fill_size, trade_update.fill_base_amount) + self.assertEqual(self.expected_fill_size * self.expected_fill_price, trade_update.fill_quote_amount) + self.assertEqual(self.expected_fill_fee, trade_update.fee) + self.assertTrue(trade_update.is_taker) + + def test_get_all_order_fills_no_fills(self): + super().test_get_all_order_fills_no_fills() + + def test_get_last_traded_price(self): + self.configure_last_traded_price( + trading_pair=self.trading_pair, last_traded_price=self.expected_last_traded_price + ) + last_trade_price = self.async_run_with_timeout( + coro=self.data_source.get_last_traded_price(trading_pair=self.trading_pair) + ) + + self.assertEqual(self.expected_last_traded_price, last_trade_price) + + def test_get_order_book_snapshot(self): + self.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[[9, 1], [8, 2]], asks=[[11, 3]] + ) + order_book_snapshot: OrderBookMessage = self.async_run_with_timeout( + coro=self.data_source.get_order_book_snapshot(trading_pair=self.trading_pair) + ) + + self.assertLess(float(0), order_book_snapshot.timestamp) + self.assertEqual(2, len(order_book_snapshot.bids)) + self.assertEqual(9, order_book_snapshot.bids[0].price) + self.assertEqual(1, order_book_snapshot.bids[0].amount) + self.assertEqual(1, len(order_book_snapshot.asks)) + self.assertEqual(11, order_book_snapshot.asks[0].price) + self.assertEqual(3, order_book_snapshot.asks[0].amount) + + def test_get_order_status_update(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + self.data_source.gateway_order_tracker.active_orders[in_flight_order.client_order_id] = in_flight_order + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.PENDING_CREATE, + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertLess(self.initial_timestamp, status_update.update_timestamp) + self.assertEqual(OrderState.PENDING_CREATE, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(self.expected_buy_exchange_order_id, status_update.exchange_order_id) + + def test_get_order_status_update_with_no_update(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.PENDING_CREATE, + ) + + status_update: OrderUpdate = self.async_run_with_timeout( + coro=self.data_source.get_order_status_update(in_flight_order=in_flight_order) + ) + + self.assertEqual(self.trading_pair, status_update.trading_pair) + self.assertLess(self.initial_timestamp, status_update.update_timestamp) + self.assertEqual(OrderState.PENDING_CREATE, status_update.new_state) + self.assertEqual(in_flight_order.client_order_id, status_update.client_order_id) + self.assertEqual(self.expected_buy_exchange_order_id, status_update.exchange_order_id) + + def test_update_order_status(self): + creation_transaction_hash = "0x7cb2eafc389349f86da901cdcbfd9119425a2ea84d61c17b6ded778b6fd2g81d" # noqa: mock + in_flight_order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + creation_transaction_hash=creation_transaction_hash, + exchange_order_id=self.expected_buy_exchange_order_id, + ) + self.data_source.gateway_order_tracker.active_orders[in_flight_order.client_order_id] = in_flight_order + self.enqueue_order_status_response( + timestamp=self.initial_timestamp + 1, + trading_pair=in_flight_order.trading_pair, + exchange_order_id=self.expected_buy_exchange_order_id, + client_order_id=in_flight_order.client_order_id, + status=OrderState.PENDING_CREATE, + ) + + self.async_run_with_timeout( + coro=self.data_source._update_order_status() + ) + + def test_get_symbol_map(self): + symbol_map = self.async_run_with_timeout(coro=self.data_source.get_symbol_map()) + + self.assertIsInstance(symbol_map, bidict) + self.assertEqual(1, len(symbol_map)) + self.assertIn(self.exchange_trading_pair, symbol_map.inverse) + + def test_get_trading_fees(self): + super().test_get_trading_fees() + + def test_get_trading_rules(self): + trading_rules = self.async_run_with_timeout(coro=self.data_source.get_trading_rules()) + + self.assertEqual(1, len(trading_rules)) + self.assertIn(self.trading_pair, trading_rules) + + trading_rule: TradingRule = trading_rules[self.trading_pair] + + self.assertEqual(self.trading_pair, trading_rule.trading_pair) + self.assertEqual(self.expected_min_price_increment, trading_rule.min_price_increment) + + def test_maximum_delay_between_requests_for_snapshot_events(self): + pass + + def test_minimum_delay_between_requests_for_snapshot_events(self): + pass + + def test_place_order(self): + super().test_place_order() + + def test_place_order_transaction_fails(self): + self.configure_place_order_failure_response() + + order = GatewayInFlightOrder( + client_order_id=self.expected_buy_client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.initial_timestamp, + price=self.expected_buy_order_price, + amount=self.expected_buy_order_size, + ) + + with self.assertRaises(Exception): + self.async_run_with_timeout( + coro=self.data_source.place_order(order=order) + ) + + def test_generate_hash(self): + actual = generate_hash("test") + + self.assertIsNotNone(actual) + + def test_convert_hb_trading_pair_to_market_name(self): + expected = "KUJI/USK" + + actual = convert_hb_trading_pair_to_market_name("KUJI-USK") + + self.assertEqual(expected, actual) + + def test_order_status_methods(self): + for item in KujiraOrderStatus: + if item == KujiraOrderStatus.UNKNOWN: + continue + + hummingbot_status = KujiraOrderStatus.to_hummingbot(item) + kujira_status = KujiraOrderStatus.from_hummingbot(hummingbot_status) + kujira_status_from_name = KujiraOrderStatus.from_name(kujira_status.name) + + self.assertEqual(item, kujira_status) + self.assertEqual(item, kujira_status_from_name) + + def test_order_sides(self): + for item in KujiraOrderSide: + hummingbot_side = KujiraOrderSide.to_hummingbot(item) + kujira_side = KujiraOrderSide.from_hummingbot(hummingbot_side) + kujira_side_from_name = KujiraOrderSide.from_name(kujira_side.name) + + self.assertEqual(item, kujira_side) + self.assertEqual(item, kujira_side_from_name) + + def test_order_types(self): + for item in KujiraOrderType: + hummingbot_type = KujiraOrderType.to_hummingbot(item) + kujira_type = KujiraOrderType.from_hummingbot(hummingbot_type) + kujira_type_from_name = KujiraOrderType.from_name(kujira_type.name) + + self.assertEqual(item, kujira_type) + self.assertEqual(item, kujira_type_from_name) diff --git a/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot.py b/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot.py new file mode 100644 index 0000000..8b2f7c8 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot.py @@ -0,0 +1,1769 @@ +import asyncio +import unittest +from decimal import Decimal +from test.hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_mock_utils import InjectiveClientMock +from typing import Awaitable, Dict, List, Mapping +from unittest.mock import AsyncMock, MagicMock, patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.fee_overrides_config_map import init_fee_overrides_config +from hummingbot.client.config.trade_fee_schema_loader import TradeFeeSchemaLoader +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source import ( + InjectiveAPIDataSource, +) +from hummingbot.connector.gateway.clob_spot.gateway_clob_spot import GatewayCLOBSPOT +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.trading_rule import TradingRule +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus + + +class GatewayCLOBSPOTTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + base_asset: str + quote_asset: str + trading_pair: str + inj_trading_pair: str + wallet_address: str + clock_tick_size: float + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base_asset, quote=cls.quote_asset) + cls.inj_trading_pair = combine_to_hb_trading_pair(base="INJ", quote=cls.quote_asset) + cls.wallet_address = "someWalletAddress" + cls.clock_tick_size = 1 + + def setUp(self) -> None: + super().setUp() + + self.log_records = [] + self.async_tasks: List[asyncio.Task] = [] + + self.start_timestamp = 1669100347689 + self.clob_data_source_mock = InjectiveClientMock( + initial_timestamp=self.start_timestamp, + sub_account_id=self.wallet_address, + base=self.base_asset, + quote=self.quote_asset, + ) + self.clob_data_source_mock.start() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectiveAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + self.exchange = GatewayCLOBSPOT( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="injective", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + self.end_timestamp = self.start_timestamp + self.exchange.LONG_POLL_INTERVAL + 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(iterator=self.exchange) + + api_data_source.logger().setLevel(1) + api_data_source.logger().addHandler(self) + self.exchange.logger().setLevel(1) + self.exchange.logger().addHandler(self) + self.exchange._order_tracker.logger().setLevel(1) + self.exchange._order_tracker.logger().addHandler(self) + + self.initialize_event_loggers() + + self.async_run_with_timeout(coroutine=self.exchange.start_network()) + + def tearDown(self) -> None: + for task in self.async_tasks: + task.cancel() + self.clob_data_source_mock.stop() + self.async_run_with_timeout(coroutine=self.exchange.stop_network()) + super().tearDown() + + @property + def expected_supported_order_types(self) -> List[OrderType]: + return [OrderType.LIMIT, OrderType.LIMIT_MAKER] + + @property + def expected_exchange_order_id(self) -> str: + return "0x6df823e0adc0d4811e8d25d7380c1b45e43b16b0eea6f109cc1fb31d31aeddc7" # noqa: mock + + @property + def expected_transaction_hash(self) -> str: + return "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18" # noqa: mock + + @property + def expected_trade_id(self) -> str: + return "19889401_someTradeId" + + @property + def expected_latest_price(self) -> float: + return 100 + + @property + def expected_trading_rule(self) -> TradingRule: + return TradingRule( + trading_pair=self.trading_pair, + min_order_size=self.clob_data_source_mock.min_quantity_tick_size, + min_price_increment=self.clob_data_source_mock.min_price_tick_size, + min_base_amount_increment=self.clob_data_source_mock.min_quantity_tick_size, + min_quote_amount_increment=self.clob_data_source_mock.min_price_tick_size, + ) + + @property + def expected_order_price(self) -> Decimal: + return Decimal("10_000") + + @property + def expected_order_size(self) -> Decimal: + return Decimal("2") + + @property + def expected_partial_fill_size(self) -> Decimal: + return self.expected_order_size / 2 + + @property + def expected_full_fill_fee(self) -> TradeFeeBase: + expected_fee = self.clob_data_source_mock.maker_fee_rate * self.expected_order_price + return AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=expected_fee)] + ) + + @property + def expected_partial_fill_fee(self) -> TradeFeeBase: + expected_fee = self.clob_data_source_mock.maker_fee_rate * self.expected_partial_fill_size + return AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=expected_fee)] + ) + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def is_logged_that_starts_with(self, log_level: str, message_starts_with: str): + return any( + record.levelname == log_level and record.getMessage().startswith(message_starts_with) + for record in self.log_records + ) + + @staticmethod + def async_run_with_timeout(coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.exchange.add_listener(event, logger) + + @staticmethod + def expected_initial_status_dict() -> Dict[str, bool]: + return { + "symbols_mapping_initialized": False, + "order_books_initialized": False, + "account_balance": False, + "trading_rule_initialized": False, + "user_stream_initialized": False, + "api_data_source_initialized": False, + } + + @staticmethod + def expected_initialized_status_dict() -> Dict[str, bool]: + return { + "symbols_mapping_initialized": True, + "order_books_initialized": True, + "account_balance": True, + "trading_rule_initialized": True, + "user_stream_initialized": True, + "api_data_source_initialized": True, + } + + def place_buy_order(self, size: Decimal = Decimal("100"), price: Decimal = Decimal("10_000")): + order_id = self.exchange.buy( + trading_pair=self.trading_pair, + amount=size, + order_type=OrderType.LIMIT, + price=price, + ) + return order_id + + def place_sell_order(self, size: Decimal = Decimal("100"), price: Decimal = Decimal("10_000")): + order_id = self.exchange.sell( + trading_pair=self.trading_pair, + amount=size, + order_type=OrderType.LIMIT, + price=price, + ) + return order_id + + def test_supported_order_types(self): + supported_types = self.exchange.supported_order_types() + self.assertEqual(self.expected_supported_order_types, supported_types) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(GatewayInFlightOrder( + client_order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + )) + orders.append(GatewayInFlightOrder( + client_order_id="12", + exchange_order_id="22", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.CANCELED + )) + orders.append(GatewayInFlightOrder( + client_order_id="13", + exchange_order_id="23", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(GatewayInFlightOrder( + client_order_id="14", + exchange_order_id="24", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.exchange.restore_tracking_states(tracking_states) + + self.assertIn("11", self.exchange.in_flight_orders) + self.assertNotIn("12", self.exchange.in_flight_orders) + self.assertNotIn("13", self.exchange.in_flight_orders) + self.assertNotIn("14", self.exchange.in_flight_orders) + + def test_all_trading_pairs(self): + self.exchange._set_trading_pair_symbol_map(None) + + all_trading_pairs = self.async_run_with_timeout(coroutine=self.exchange.all_trading_pairs()) + + self.assertEqual(2, len(all_trading_pairs)) + self.assertIn(self.trading_pair, all_trading_pairs) + self.assertIn(self.inj_trading_pair, all_trading_pairs) + + def test_get_last_trade_prices(self): + fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("0.001"))]) + self.clob_data_source_mock.configure_spot_trades_response_to_request_without_exchange_order_id( + timestamp=self.start_timestamp, + price=Decimal(self.expected_latest_price), + size=Decimal("2"), + maker_fee=fee, + taker_fee=fee, + ) + + latest_prices: Dict[str, float] = self.async_run_with_timeout( + self.exchange.get_last_traded_prices(trading_pairs=[self.trading_pair]) + ) + + self.assertEqual(1, len(latest_prices)) + self.assertEqual(self.expected_latest_price, latest_prices[self.trading_pair]) + + def test_check_network_success(self): + self.clob_data_source_mock.configure_check_network_success() + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=2) + + self.assertEqual(NetworkStatus.CONNECTED, network_status) + + def test_check_network_failure(self): + self.clob_data_source_mock.configure_check_network_failure() + + network_status = self.async_run_with_timeout(coroutine=self.exchange.check_network(), timeout=2) + + self.assertEqual(NetworkStatus.NOT_CONNECTED, network_status) + + def test_check_network_raises_cancel_exception(self): + self.clob_data_source_mock.configure_check_network_failure(exc=asyncio.CancelledError) + + with self.assertRaises(expected_exception=asyncio.CancelledError): + self.async_run_with_timeout(coroutine=self.exchange.check_network()) + + def test_init_trading_pair_symbol_map(self): + symbol_map = self.async_run_with_timeout(coroutine=self.exchange.trading_pair_symbol_map()) + + self.assertIsInstance(symbol_map, Mapping) + self.assertEqual(2, len(symbol_map)) + self.assertIn(self.clob_data_source_mock.exchange_trading_pair, symbol_map) + self.assertIn(self.trading_pair, symbol_map.inverse) + self.assertIn(self.inj_trading_pair, symbol_map.inverse) + + def test_initial_status_dict(self): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectiveAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + exchange = GatewayCLOBSPOT( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="cosmos", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + status_dict = exchange.status_dict + + expected_initial_dict = self.expected_initial_status_dict() + + self.assertEqual(expected_initial_dict, status_dict) + self.assertFalse(exchange.ready) + + @patch("hummingbot.core.data_type.order_book_tracker.OrderBookTracker._sleep") + def test_full_initialization_and_de_initialization(self, _: AsyncMock): + self.clob_data_source_mock.configure_trades_response_no_trades() + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=Decimal("10"), + base_available_balance=Decimal("9"), + quote_total_balance=Decimal("200"), + quote_available_balance=Decimal("150"), + ) + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector_spec = { + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.wallet_address, + } + api_data_source = InjectiveAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec=connector_spec, + client_config_map=client_config_map, + ) + exchange = GatewayCLOBSPOT( + client_config_map=client_config_map, + api_data_source=api_data_source, + connector_name="injective", + chain="cosmos", + network="mainnet", + address=self.wallet_address, + trading_pairs=[self.trading_pair], + ) + + self.assertEqual(0, len(exchange.trading_fees)) + + self.async_run_with_timeout(coroutine=exchange.start_network()) + + self.clock.add_iterator(exchange) + self.clock.backtest_til(self.start_timestamp + exchange.SHORT_POLL_INTERVAL) + self.clob_data_source_mock.run_until_all_items_delivered() + + status_dict = exchange.status_dict + + expected_initial_dict = self.expected_initialized_status_dict() + + self.assertEqual(expected_initial_dict, status_dict) + self.assertTrue(exchange.ready) + self.assertNotEqual(0, len(exchange.trading_fees)) + self.assertIn(self.trading_pair, exchange.trading_fees) + + trading_fees_data = exchange.trading_fees[self.trading_pair] + service_provider_rebate = Decimal("1") - self.clob_data_source_mock.service_provider_fee + expected_maker_fee = self.clob_data_source_mock.maker_fee_rate * service_provider_rebate + expected_taker_fee = self.clob_data_source_mock.taker_fee_rate * service_provider_rebate + + self.assertEqual(expected_maker_fee, trading_fees_data.maker) + self.assertEqual(expected_taker_fee, trading_fees_data.taker) + + def test_update_trading_rules(self): + self.async_run_with_timeout(coroutine=self.exchange._update_trading_rules()) + + trading_rule: TradingRule = self.exchange.trading_rules[self.trading_pair] + + self.assertTrue(self.trading_pair in self.exchange.trading_rules) + self.assertEqual(repr(self.expected_trading_rule), repr(trading_rule)) + + trading_rule_with_default_values = TradingRule(trading_pair=self.trading_pair) + + # The following element can't be left with the default value because that breaks quantization in Cython + self.assertNotEqual(trading_rule_with_default_values.min_base_amount_increment, + trading_rule.min_base_amount_increment) + self.assertNotEqual(trading_rule_with_default_values.min_price_increment, + trading_rule.min_price_increment) + + def test_create_buy_limit_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.clob_data_source_mock.configure_place_order_response( + timestamp=self.start_timestamp, + transaction_hash=self.expected_transaction_hash, + exchange_order_id=self.expected_exchange_order_id, + trade_type=TradeType.BUY, + price=self.expected_order_price, + size=self.expected_order_size, + ) + + order_id = self.place_buy_order() + self.clob_data_source_mock.run_until_all_items_delivered() + order = self.exchange._order_tracker.active_orders[order_id] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + creation_transaction_hash=self.expected_transaction_hash, + order=order, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name} order {order_id} for " + f"{Decimal('100.000')} {self.trading_pair}." + ) + ) + + def test_create_sell_limit_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.clob_data_source_mock.configure_place_order_response( + timestamp=self.start_timestamp, + transaction_hash=self.expected_transaction_hash, + exchange_order_id=self.expected_exchange_order_id, + trade_type=TradeType.BUY, + price=self.expected_order_price, + size=self.expected_order_size, + ) + + order_id = self.place_sell_order() + self.clob_data_source_mock.run_until_all_items_delivered() + order = self.exchange._order_tracker.active_orders[order_id] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + creation_transaction_hash=self.expected_transaction_hash, + order=order, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(order_id, self.exchange.in_flight_orders) + + create_event: SellOrderCreatedEvent = self.sell_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, create_event.timestamp) + self.assertEqual(self.trading_pair, create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, create_event.type) + self.assertEqual(Decimal("100"), create_event.amount) + self.assertEqual(Decimal("10000"), create_event.price) + self.assertEqual(order_id, create_event.order_id) + self.assertEqual(str(self.expected_exchange_order_id), create_event.exchange_order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.SELL.name} order {order_id} for " + f"{Decimal('100.000')} {self.trading_pair}." + ) + ) + + def test_create_order_fails_and_raises_failure_event(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.clob_data_source_mock.configure_place_order_fails_response(exception=RuntimeError("some error")) + + order_id = self.place_buy_order( + size=self.expected_order_size, price=self.expected_order_price + ) + + self.clob_data_source_mock.run_until_place_order_called() + + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id, failure_event.order_id) + + self.assertTrue( + expr=self.is_logged_that_starts_with( + log_level="NETWORK", + message_starts_with=( + f"Error submitting buy LIMIT order to {self.exchange.name_cap} for" + ) + ) + ) + self.assertTrue( + expr=self.is_logged( + log_level="INFO", + message=( + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + ) + + def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.clob_data_source_mock.configure_place_order_fails_response(exception=RuntimeError("some error")) + + order_id_for_invalid_order = self.place_buy_order( + size=Decimal("0.0001"), price=Decimal("0.0001") + ) + # The second order is used only to have the event triggered and avoid using timeouts for tests + order_id = self.place_buy_order() + self.clob_data_source_mock.run_until_place_order_called() + + self.assertNotIn(order_id_for_invalid_order, self.exchange.in_flight_orders) + self.assertNotIn(order_id, self.exchange.in_flight_orders) + + self.assertEquals(0, len(self.buy_order_created_logger.event_log)) + failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) + self.assertEqual(OrderType.LIMIT, failure_event.order_type) + self.assertEqual(order_id_for_invalid_order, failure_event.order_id) + + self.assertTrue( + self.is_logged( + "WARNING", + "Buy order amount 0.0001 is lower than the minimum order " + "size 0.001. The order will not be created, increase the " + "amount to be higher than the minimum order size." + ) + ) + self.assertTrue( + self.is_logged( + "INFO", + f"Order {order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}', " + f"update_timestamp={self.exchange.current_timestamp}, new_state={repr(OrderState.FAILED)}, " + f"client_order_id='{order_id}', exchange_order_id=None, misc_updates=None)" + ) + ) + + def test_cancel_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_cancel_order_response( + timestamp=self.start_timestamp, transaction_hash=self.expected_transaction_hash + ) + + self.exchange.cancel(trading_pair=order.trading_pair, client_order_id=order.client_order_id) + self.clob_data_source_mock.run_until_cancel_order_called() + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + else: + self.assertIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_pending_cancel_confirmation) + + self.assertEqual(self.expected_transaction_hash, order.cancel_tx_hash) + + def test_cancel_order_raises_failure_event_when_request_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_cancel_order_fails_response(exception=RuntimeError("some error")) + + self.exchange.cancel(trading_pair=self.trading_pair, client_order_id="11") + self.clob_data_source_mock.run_until_cancel_order_called() + + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue(any(log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records)) + + def test_cancel_two_orders_with_cancel_all_and_one_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order1 = self.exchange.in_flight_orders["11"] + + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id="5", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=Decimal("11000"), + amount=Decimal("90"), + order_type=OrderType.LIMIT, + ) + + self.assertIn("12", self.exchange.in_flight_orders) + order2 = self.exchange.in_flight_orders["12"] + + self.clob_data_source_mock.configure_one_success_one_failure_order_cancelation_responses( + success_timestamp=self.start_timestamp, + success_transaction_hash=self.expected_transaction_hash, + failure_exception=RuntimeError("some error"), + ) + + cancellation_results = self.async_run_with_timeout(self.exchange.cancel_all(10)) + + self.assertEqual(2, len(cancellation_results)) + self.assertIn(CancellationResult(order1.client_order_id, True), cancellation_results) + self.assertIn(CancellationResult(order2.client_order_id, False), cancellation_results) + + def test_batch_order_cancel(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + self.exchange.start_tracking_order( + order_id="12", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + buy_order_to_cancel: InFlightOrder = self.exchange.in_flight_orders["11"] + sell_order_to_cancel: InFlightOrder = self.exchange.in_flight_orders["12"] + orders_to_cancel = [buy_order_to_cancel, sell_order_to_cancel] + + self.clob_data_source_mock.configure_batch_order_cancel_response( + timestamp=self.start_timestamp, + transaction_hash="somehash", + canceled_orders=orders_to_cancel, + ) + + self.exchange.batch_order_cancel(orders_to_cancel=self.exchange.limit_orders) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(buy_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_cancel.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(buy_order_to_cancel.is_pending_cancel_confirmation) + self.assertTrue(sell_order_to_cancel.is_pending_cancel_confirmation) + + def test_batch_order_create(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + buy_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("10"), + quantity=Decimal("2"), + ) + sell_order_to_create = LimitOrder( + client_order_id="", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("11"), + quantity=Decimal("3"), + ) + orders_to_create = [buy_order_to_create, sell_order_to_create] + + orders: List[LimitOrder] = self.exchange.batch_order_create(orders_to_create=orders_to_create) + + buy_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[0].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=self.start_timestamp, + price=orders[0].price, + amount=orders[0].quantity, + exchange_order_id="someEOID0", + ) + sell_order_to_create_in_flight = GatewayInFlightOrder( + client_order_id=orders[1].client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=self.start_timestamp, + price=orders[1].price, + amount=orders[1].quantity, + exchange_order_id="someEOID1", + ) + orders_to_create_in_flight = [buy_order_to_create_in_flight, sell_order_to_create_in_flight] + self.clob_data_source_mock.configure_batch_order_create_response( + timestamp=self.start_timestamp, + transaction_hash="somehash", + created_orders=orders_to_create_in_flight, + ) + + self.assertEqual(2, len(orders)) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertIn(buy_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + self.assertIn(sell_order_to_create_in_flight.client_order_id, self.exchange.in_flight_orders) + + buy_create_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_create_event.timestamp) + self.assertEqual(self.trading_pair, buy_create_event.trading_pair) + self.assertEqual(OrderType.LIMIT, buy_create_event.type) + self.assertEqual(buy_order_to_create_in_flight.amount, buy_create_event.amount) + self.assertEqual(buy_order_to_create_in_flight.price, buy_create_event.price) + self.assertEqual(buy_order_to_create_in_flight.client_order_id, buy_create_event.order_id) + self.assertEqual(buy_order_to_create_in_flight.exchange_order_id, buy_create_event.exchange_order_id) + self.assertTrue( + self.is_logged( + "INFO", + f"Created {OrderType.LIMIT.name} {TradeType.BUY.name}" + f" order {buy_order_to_create_in_flight.client_order_id} for " + f"{buy_create_event.amount} {self.trading_pair}." + ) + ) + + def test_update_balances(self): + expected_base_total_balance = Decimal("100") + expected_base_available_balance = Decimal("90") + expected_quote_total_balance = Decimal("10") + expected_quote_available_balance = Decimal("8") + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=expected_base_total_balance, + base_available_balance=expected_base_available_balance, + quote_total_balance=expected_quote_total_balance, + quote_available_balance=expected_quote_available_balance, + ) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertEqual(expected_base_available_balance, available_balances[self.base_asset]) + self.assertEqual(expected_quote_available_balance, available_balances[self.quote_asset]) + self.assertEqual(expected_base_total_balance, total_balances[self.base_asset]) + self.assertEqual(expected_quote_total_balance, total_balances[self.quote_asset]) + + expected_base_total_balance = Decimal("100") + expected_base_available_balance = Decimal("90") + expected_quote_total_balance = Decimal("0") + expected_quote_available_balance = Decimal("0") + self.clob_data_source_mock.configure_get_account_balances_response( + base_total_balance=expected_base_total_balance, + base_available_balance=expected_base_available_balance, + quote_total_balance=expected_quote_total_balance, + quote_available_balance=expected_quote_available_balance, + ) + + self.async_run_with_timeout(self.exchange._update_balances()) + + available_balances = self.exchange.available_balances + total_balances = self.exchange.get_all_balances() + + self.assertNotIn(self.quote_asset, available_balances) + self.assertNotIn(self.quote_asset, total_balances) + self.assertEqual(expected_base_available_balance, available_balances[self.base_asset]) + self.assertEqual(expected_base_total_balance, total_balances[self.base_asset]) + + def test_update_order_status_when_filled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + creation_transaction_hash=creation_transaction_hash, + order=order, + filled_size=self.expected_order_size, + ) + + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.clob_data_source_mock.run_until_all_items_delivered() + + self.async_run_with_timeout(order.wait_until_completely_filled()) + self.assertTrue(order.is_done) + + self.assertTrue(order.is_filled) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[-1] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_full_fill_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * order.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_update_order_status_when_canceled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, is_canceled=True + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_update_order_status_when_cancel_transaction_minted(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.cancel_tx_hash = self.expected_transaction_hash + + self.assertEqual(OrderState.PENDING_CREATE, order.current_state) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp + 1, + order=order, + creation_transaction_hash=creation_transaction_hash, + cancelation_transaction_hash=self.expected_transaction_hash, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(OrderState.CANCELED, order.current_state) + self.assertEqual(self.expected_transaction_hash, order.cancel_tx_hash) + + buy_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual("11", buy_event.order_id) + self.assertEqual(self.expected_exchange_order_id, buy_event.exchange_order_id) + + def test_update_order_status_when_order_has_not_changed(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=Decimal("0"), + ) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + def test_update_order_status_when_request_fails_marks_order_as_not_found(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, is_failed=True + ) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertFalse(order.is_filled) + self.assertFalse(order.is_done) + + self.assertEqual(1, self.exchange._order_tracker._order_not_found_records[order.client_order_id]) + + def test_update_order_status_when_order_has_not_changed_and_one_partial_fill(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.order_fills[creation_transaction_hash] = None # tod prevent creation transaction request + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp + 1, + exchange_order_id=order.exchange_order_id, + price=order.price, + size=self.expected_partial_fill_size, + fee=self.expected_partial_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.assertTrue(order.is_open) + + self.async_run_with_timeout(self.exchange._update_order_status()) + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(self.expected_order_price, fill_event.price) + self.assertEqual(self.expected_partial_fill_size, fill_event.amount) + self.assertEqual(self.expected_partial_fill_fee, fill_event.trade_fee) + + def test_update_order_status_when_filled_correctly_processed_even_when_trade_fill_update_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + creation_transaction_hash=creation_transaction_hash, + filled_size=order.amount, + ) + self.clob_data_source_mock.configure_trades_response_fails() + + # Since the trade fill update will fail we need to manually set the event + # to allow the ClientOrderTracker to process the last status update + order.completely_filled_event.set() + self.async_run_with_timeout(self.exchange._update_order_status()) + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertEqual(0, len(self.order_filled_logger.event_log)) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(Decimal(0), buy_event.base_asset_amount) + self.assertEqual(Decimal(0), buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_update_order_status_when_order_partially_filled_and_cancelled(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + creation_transaction_hash = "someHash" + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: GatewayInFlightOrder = self.exchange.in_flight_orders["11"] + order.creation_transaction_hash = creation_transaction_hash + order.order_fills[creation_transaction_hash] = None # to prevent creation transaction request + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=order.exchange_order_id, + price=order.price, + size=self.expected_partial_fill_size, + fee=self.expected_partial_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.assertTrue(order.is_open) + + self.clock.backtest_til(self.start_timestamp + self.clock.tick_size * 1) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertTrue(order.is_open) + self.assertEqual(OrderState.PARTIALLY_FILLED, order.current_state) + + order_partially_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(order.client_order_id, order_partially_filled_event.order_id) + self.assertEqual(order.trading_pair, order_partially_filled_event.trading_pair) + self.assertEqual(order.trade_type, order_partially_filled_event.trade_type) + self.assertEqual(order.order_type, order_partially_filled_event.order_type) + self.assertEqual(self.expected_trade_id, order_partially_filled_event.exchange_trade_id) + self.assertEqual(self.expected_order_price, order_partially_filled_event.price) + self.assertEqual(self.expected_partial_fill_size, order_partially_filled_event.amount) + self.assertEqual(self.expected_partial_fill_fee, order_partially_filled_event.trade_fee) + self.assertEqual(self.exchange.current_timestamp, order_partially_filled_event.timestamp) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=self.expected_partial_fill_size, + is_canceled=True, + ) + + self.async_run_with_timeout(self.exchange._update_order_status(), timeout=2) + + self.assertTrue(order.is_cancelled) + self.assertEqual(OrderState.CANCELED, order.current_state) + + def test_user_stream_update_for_new_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order, filled_size=Decimal("0"), + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, event.timestamp) + self.assertEqual(order.order_type, event.type) + self.assertEqual(order.trading_pair, event.trading_pair) + self.assertEqual(order.amount, event.amount) + self.assertEqual(order.price, event.price) + self.assertEqual(order.client_order_id, event.order_id) + self.assertEqual(order.exchange_order_id, event.exchange_order_id) + self.assertTrue(order.is_open) + + tracked_order: InFlightOrder = list(self.exchange.in_flight_orders.values())[0] + + self.assertTrue(self.is_logged("INFO", tracked_order.build_order_created_message())) + + def test_user_stream_update_for_canceled_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order, is_canceled=True + ) + self.clob_data_source_mock.run_until_all_items_delivered() + + cancel_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, cancel_event.timestamp) + self.assertEqual(order.client_order_id, cancel_event.order_id) + self.assertEqual(order.exchange_order_id, cancel_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_cancelled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged("INFO", f"Successfully canceled order {order.client_order_id}.") + ) + + def test_user_stream_update_for_order_full_fill(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp, + price=self.expected_order_price, + size=self.expected_order_size, + maker_fee=self.expected_full_fill_fee, + taker_fee=self.expected_full_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_full_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + buy_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, buy_event.timestamp) + self.assertEqual(order.client_order_id, buy_event.order_id) + self.assertEqual(order.base_asset, buy_event.base_asset) + self.assertEqual(order.quote_asset, buy_event.quote_asset) + self.assertEqual(order.amount, buy_event.base_asset_amount) + self.assertEqual(order.amount * fill_event.price, buy_event.quote_asset_amount) + self.assertEqual(order.order_type, buy_event.order_type) + self.assertEqual(order.exchange_order_id, buy_event.exchange_order_id) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_done) + + self.assertTrue( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_user_stream_update_for_partially_cancelled_order(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, in_flight_order=order + ) + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp + 1, in_flight_order=order, filled_size=self.expected_partial_fill_size + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp + 1, + price=self.expected_order_price, + size=self.expected_partial_fill_size, + maker_fee=self.expected_partial_fill_fee, + taker_fee=self.expected_partial_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp + 2, + in_flight_order=order, + filled_size=self.expected_partial_fill_size, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + order_created_event: BuyOrderCreatedEvent = self.buy_order_created_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, order_created_event.timestamp) + self.assertEqual(order.order_type, order_created_event.type) + self.assertEqual(order.trading_pair, order_created_event.trading_pair) + self.assertEqual(order.amount, order_created_event.amount) + self.assertEqual(order.price, order_created_event.price) + self.assertEqual(order.client_order_id, order_created_event.order_id) + self.assertEqual(order.exchange_order_id, order_created_event.exchange_order_id) + + self.assertTrue(self.is_logged("INFO", order.build_order_created_message())) + + order_partially_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(order.order_type, order_partially_filled_event.order_type) + self.assertEqual(order.trading_pair, order_partially_filled_event.trading_pair) + self.assertEqual(self.expected_trade_id, order_partially_filled_event.exchange_trade_id) + self.assertEqual(self.expected_order_price, order_partially_filled_event.price) + self.assertEqual(self.expected_partial_fill_size, order_partially_filled_event.amount) + self.assertEqual(order.client_order_id, order_partially_filled_event.order_id) + + self.assertTrue( + self.is_logged_that_starts_with( + log_level="INFO", + message_starts_with=f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to ", + ) + ) + + order_partially_cancelled_event: OrderCancelledEvent = self.order_cancelled_logger.event_log[0] + self.assertEqual(order.client_order_id, order_partially_cancelled_event.order_id) + self.assertEqual(order.exchange_order_id, order_partially_cancelled_event.exchange_order_id) + self.assertEqual(self.exchange.current_timestamp, order_partially_cancelled_event.timestamp) + + self.assertTrue( + self.is_logged( + "INFO", + f"Successfully canceled order {order.client_order_id}." + ) + ) + + self.assertTrue(order.is_cancelled) + + def test_user_stream_balance_update(self): + if self.exchange.real_time_balance_update: + target_total_balance = Decimal("15") + target_available_balance = Decimal("10") + self.clob_data_source_mock.configure_account_quote_balance_stream_event( + timestamp=self.start_timestamp, + total_balance=target_total_balance, + available_balance=target_available_balance, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertEqual(target_total_balance, self.exchange.get_balance(self.quote_asset)) + self.assertEqual(target_available_balance, self.exchange.available_balances[self.quote_asset]) + + def test_user_stream_logs_errors(self): + self.clob_data_source_mock.configure_faulty_base_balance_stream_event(timestamp=self.start_timestamp) + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertTrue(self.is_logged("INFO", "Restarting account balances stream.")) + + def test_lost_order_included_in_order_fills_update_and_not_in_order_status_update(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id) + ) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + creation_transaction_hash=self.expected_transaction_hash, + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.clock.backtest_til(self.start_timestamp + self.exchange.SHORT_POLL_INTERVAL) + self.async_run_with_timeout(order.wait_until_completely_filled()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[-1] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + self.assertEqual(self.expected_full_fill_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + # Configure again the response to the order fills request since it is required by lost orders update logic + self.clob_data_source_mock.configure_get_historical_spot_orders_response_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trades_response_with_exchange_order_id( + timestamp=self.start_timestamp, + exchange_order_id=self.expected_exchange_order_id, + price=self.expected_order_price, + size=self.expected_order_size, + fee=self.expected_full_fill_fee, + trade_id=self.expected_trade_id, + ) + + self.async_run_with_timeout(self.exchange._update_lost_orders_status()) + + self.assertTrue(order.is_done) + self.assertTrue(order.is_failure) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.all_fillable_orders) + self.assertFalse( + self.is_logged( + "INFO", + f"BUY order {order.client_order_id} completely filled." + ) + ) + + def test_cancel_lost_order_successfully(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order: InFlightOrder = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_cancel_order_response( + timestamp=self.start_timestamp, transaction_hash=self.expected_transaction_hash + ) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + + if self.exchange.is_cancel_request_in_exchange_synchronous: + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + else: + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_failure) + + self.assertFalse( + self.is_logged_that_starts_with(log_level="WARNING", message_starts_with="Failed to cancel the order ") + ) + self.assertFalse( + self.is_logged_that_starts_with(log_level="ERROR", message_starts_with="Failed to cancel order ") + ) + + def test_cancel_lost_order_raises_failure_event_when_request_fails(self): + self.exchange._set_current_timestamp(self.start_timestamp) + + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=self.expected_exchange_order_id, + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + order_type=OrderType.LIMIT, + ) + + self.assertIn("11", self.exchange.in_flight_orders) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_cancel_order_fails_response(exception=RuntimeError("some error")) + + self.async_run_with_timeout(self.exchange._cancel_lost_orders()) + + self.assertIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEquals(0, len(self.order_cancelled_logger.event_log)) + self.assertTrue(any(log.msg.startswith(f"Failed to cancel order {order.client_order_id}") + for log in self.log_records)) + + def test_lost_order_removed_after_cancel_status_user_event_received(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + is_canceled=True, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertEqual(0, len(self.order_cancelled_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertFalse(order.is_cancelled) + self.assertTrue(order.is_failure) + + def test_lost_order_user_stream_full_fill_events_are_processed(self): + self.exchange._set_current_timestamp(self.start_timestamp) + self.exchange.start_tracking_order( + order_id="11", + exchange_order_id=str(self.expected_exchange_order_id), + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=self.expected_order_price, + amount=self.expected_order_size, + ) + order = self.exchange.in_flight_orders["11"] + + for _ in range(self.exchange._order_tracker._lost_order_count_limit + 1): + self.async_run_with_timeout( + self.exchange._order_tracker.process_order_not_found(client_order_id=order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + + self.clob_data_source_mock.configure_order_stream_event_for_in_flight_order( + timestamp=self.start_timestamp, + in_flight_order=order, + filled_size=self.expected_order_size, + ) + self.clob_data_source_mock.configure_trade_stream_event( + timestamp=self.start_timestamp, + price=self.expected_order_price, + size=self.expected_order_size, + maker_fee=self.expected_full_fill_fee, + taker_fee=self.expected_full_fill_fee, + exchange_order_id=self.expected_exchange_order_id, + taker_trade_id=self.expected_trade_id, + ) + self.clob_data_source_mock.configure_order_status_update_response( + timestamp=self.start_timestamp, + order=order, + filled_size=self.expected_order_size, + ) + + self.clob_data_source_mock.run_until_all_items_delivered() + # Execute one more synchronization to ensure the async task that processes the update is finished + self.async_run_with_timeout(order.wait_until_completely_filled()) + + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(self.exchange.current_timestamp, fill_event.timestamp) + self.assertEqual(order.client_order_id, fill_event.order_id) + self.assertEqual(order.trading_pair, fill_event.trading_pair) + self.assertEqual(order.trade_type, fill_event.trade_type) + self.assertEqual(order.order_type, fill_event.order_type) + self.assertEqual(order.price, fill_event.price) + self.assertEqual(order.amount, fill_event.amount) + expected_fee = self.expected_full_fill_fee + self.assertEqual(expected_fee, fill_event.trade_fee) + + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) + self.assertNotIn(order.client_order_id, self.exchange._order_tracker.lost_orders) + self.assertTrue(order.is_filled) + self.assertTrue(order.is_failure) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.load") + def test_estimated_fee_calculation(self, gateway_settings_load_mock: MagicMock): + AllConnectorSettings.all_connector_settings = {} + gateway_settings_load_mock.return_value = [ + { + "connector": "injective", + "chain": "injective", + "network": "mainnet", + "trading_type": "CLOB_SPOT", + "wallet_address": "0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + "additional_spenders": [], + }, + ] + init_fee_overrides_config() + fee_schema = TradeFeeSchemaLoader.configured_schema_for_exchange(exchange_name=self.exchange.name) + + self.assertFalse(fee_schema.buy_percent_fee_deducted_from_returns) + self.assertEqual(0, len(fee_schema.maker_fixed_fees)) + self.assertEqual(0, fee_schema.maker_percent_fee_decimal) + self.assertIsNone(fee_schema.percent_fee_token) + self.assertEqual(0, len(fee_schema.taker_fixed_fees)) + self.assertEqual(0, fee_schema.taker_percent_fee_decimal) + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal(100), + price=Decimal(1000), + is_maker=True + ) + + self.assertEqual(self.quote_asset, fee.percent_token) + self.assertEqual(Decimal("-0.00006"), fee.percent) # factoring in Injective service-provider rebate + + fee = self.exchange.get_fee( + base_currency=self.base_asset, + quote_currency=self.quote_asset, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal(100), + price=Decimal(1000), + is_maker=False + ) + + self.assertEqual(self.quote_asset, fee.percent_token) + self.assertEqual(Decimal("0.0006"), fee.percent) diff --git a/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot_api_order_book_data_source.py b/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot_api_order_book_data_source.py new file mode 100644 index 0000000..3e0c9a4 --- /dev/null +++ b/test/hummingbot/connector/gateway/clob_spot/test_gateway_clob_spot_api_order_book_data_source.py @@ -0,0 +1,184 @@ +import asyncio +import unittest +from decimal import Decimal +from test.hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_mock_utils import InjectiveClientMock +from typing import Awaitable +from unittest.mock import MagicMock + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.clob_spot.data_sources.injective.injective_api_data_source import ( + InjectiveAPIDataSource, +) +from hummingbot.connector.gateway.clob_spot.gateway_clob_api_order_book_data_source import ( + GatewayCLOBSPOTAPIOrderBookDataSource, +) +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage, OrderBookMessageType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount + + +class MockExchange(ExchangeBase): + pass + + +class GatewayCLOBSPOTAPIOrderBookDataSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base = "COIN" + cls.quote = "ALPHA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.base, quote=cls.quote) + cls.sub_account_id = "someSubAccountId" + + def setUp(self) -> None: + super().setUp() + self.listening_tasks = [] + + self.initial_timestamp = 1669100347689 + self.injective_async_client_mock = InjectiveClientMock( + initial_timestamp=self.initial_timestamp, + sub_account_id=self.sub_account_id, + base=self.base, + quote=self.quote, + ) + self.injective_async_client_mock.start() + + client_config_map = ClientConfigAdapter(hb_config=ClientConfigMap()) + self.api_data_source = InjectiveAPIDataSource( + trading_pairs=[self.trading_pair], + connector_spec={ + "chain": "someChain", + "network": "mainnet", + "wallet_address": self.sub_account_id, + }, + client_config_map=client_config_map, + ) + self.connector = MockExchange(client_config_map=client_config_map) + self.tracker = GatewayOrderTracker(connector=self.connector) + self.api_data_source.gateway_order_tracker = self.tracker + self.ob_data_source = GatewayCLOBSPOTAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], api_data_source=self.api_data_source + ) + self.async_run_with_timeout(coro=self.api_data_source.start()) + + def tearDown(self) -> None: + self.injective_async_client_mock.stop() + self.async_run_with_timeout(coro=self.api_data_source.stop()) + for task in self.listening_tasks: + task.cancel() + super().tearDown() + + @staticmethod + def async_run_with_timeout(coro: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coro, timeout)) + return ret + + def test_get_new_order_book_successful(self): + self.injective_async_client_mock.configure_orderbook_snapshot( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + order_book: OrderBook = self.async_run_with_timeout( + self.ob_data_source.get_new_order_book(self.trading_pair) + ) + + self.assertEqual(self.initial_timestamp * 1e3, order_book.snapshot_uid) + + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + + self.assertEqual(2, len(bids)) + self.assertEqual(9, bids[0].price) + self.assertEqual(1, bids[0].amount) + self.assertEqual(1, len(asks)) + self.assertEqual(11, asks[0].price) + self.assertEqual(3, asks[0].amount) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._trade_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_trades(self.ev_loop, asyncio.Queue()) + ) + self.listening_tasks.append(listening_task) + self.async_run_with_timeout(listening_task) + + def test_listen_for_trades_successful(self): + target_price = Decimal("1.157") + target_size = Decimal("0.001") + target_maker_fee = Decimal("0.0001157") + target_taker_fee = Decimal("0.00024") + target_maker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.0001157"))]) + target_taker_fee = AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote, amount=Decimal("0.00024"))]) + target_trade_id = "19889401_someTradeId" + + def configure_trade(): + self.injective_async_client_mock.configure_trade_stream_event( + timestamp=self.initial_timestamp, + price=target_price, + size=target_size, + maker_fee=target_maker_fee, + taker_fee=target_taker_fee, + taker_trade_id=target_trade_id, + ) + + msg_queue: asyncio.Queue = asyncio.Queue() + + subs_listening_task = self.ev_loop.create_task(coro=self.ob_data_source.listen_for_subscriptions()) + self.listening_tasks.append(subs_listening_task) + trades_listening_task = self.ev_loop.create_task( + coro=self.ob_data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.listening_tasks.append(trades_listening_task) + self.ev_loop.call_soon(callback=configure_trade) + self.injective_async_client_mock.run_until_all_items_delivered() + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertTrue(msg_queue.empty()) # only the taker update was forwarded by the ob data source + self.assertEqual(OrderBookMessageType.TRADE, msg.type) + self.assertEqual(target_trade_id, msg.trade_id) + self.assertEqual(self.initial_timestamp, msg.timestamp) + + def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.ob_data_source._message_queue[self.ob_data_source._snapshot_messages_queue_key] = mock_queue + + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, asyncio.Queue()) + ) + + def test_listen_for_order_book_snapshots_successful(self): + def configure_snapshot(): + self.injective_async_client_mock.configure_orderbook_snapshot_stream_event( + timestamp=self.initial_timestamp, bids=[(9, 1), (8, 2)], asks=[(11, 3)] + ) + + subs_listening_task = self.ev_loop.create_task(coro=self.ob_data_source.listen_for_subscriptions()) + self.listening_tasks.append(subs_listening_task) + msg_queue: asyncio.Queue = asyncio.Queue() + listening_task = self.ev_loop.create_task( + self.ob_data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) + ) + self.listening_tasks.append(listening_task) + self.ev_loop.call_soon(callback=configure_snapshot) + + snapshot_msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.SNAPSHOT, snapshot_msg.type) + self.assertEqual(self.initial_timestamp, snapshot_msg.timestamp) + self.assertEqual(2, len(snapshot_msg.bids)) + self.assertEqual(9, snapshot_msg.bids[0].price) + self.assertEqual(1, snapshot_msg.bids[0].amount) + self.assertEqual(1, len(snapshot_msg.asks)) + self.assertEqual(11, snapshot_msg.asks[0].price) + self.assertEqual(3, snapshot_msg.asks[0].amount) diff --git a/test/hummingbot/connector/gateway/test_gateway_in_flight_order.py b/test/hummingbot/connector/gateway/test_gateway_in_flight_order.py new file mode 100644 index 0000000..9e3b824 --- /dev/null +++ b/test/hummingbot/connector/gateway/test_gateway_in_flight_order.py @@ -0,0 +1,149 @@ +import asyncio +import unittest +from decimal import Decimal + +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import OrderState, OrderUpdate + +s_decimal_0 = Decimal("0") + + +class GatewayInFlightOrderUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + cls.client_order_id = "someClientOrderId" + cls.exchange_order_id = "someTxHash" + cls.nonce = 1 + + def test_order_life_cycle_of_token_approval_requests(self): + order: GatewayInFlightOrder = GatewayInFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.quote_asset, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1652324823, + initial_state=OrderState.PENDING_APPROVAL, + ) + # Assert that order is in fact a Approval Request + self.assertTrue(order.is_approval_request) + + self.assertTrue(order.is_pending_approval) + + order_update: OrderUpdate = OrderUpdate( + trading_pair=order.trading_pair, + update_timestamp=1652324824, + new_state=OrderState.APPROVED, + client_order_id=order.client_order_id, + exchange_order_id=self.exchange_order_id, + ) + + order.update_with_order_update(order_update=order_update) + + self.assertFalse(order.is_pending_approval) + + def test_order_life_cycle_of_trade_orders(self): + order: GatewayInFlightOrder = GatewayInFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.quote_asset, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("1"), + amount=Decimal("1000"), + creation_timestamp=1652324823, + initial_state=OrderState.PENDING_CREATE, + ) + + # Nonce is not provided upon creation + self.assertEqual(order.nonce, -1) + + # Exchange Order Id for GatewayInFlightOrder is only assigned after a TradeUpdate + self.assertIsNone(order.exchange_order_id) + + # CancelTxHash is not initialized on creation + + self.assertIsNone(order.cancel_tx_hash) + + def test_update_creation_transaction_hash_with_order_update(self): + order: GatewayInFlightOrder = GatewayInFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.quote_asset, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("1"), + amount=Decimal("1000"), + creation_timestamp=1652324823, + initial_state=OrderState.PENDING_CREATE, + creation_transaction_hash=None, + ) + + self.assertIsNone(order.creation_transaction_hash) + + desired_creation_transaction_hash = "someTransactionHash" + order_update = OrderUpdate( + trading_pair=self.trading_pair, + update_timestamp=1652324823 + 1, + new_state=OrderState.OPEN, + client_order_id=self.client_order_id, + exchange_order_id="someExchangeOrderID", + misc_updates={ + "creation_transaction_hash": desired_creation_transaction_hash, + } + ) + order.update_with_order_update(order_update=order_update) + + self.assertEqual(desired_creation_transaction_hash, order.creation_transaction_hash) + + def test_update_cancelation_transaction_hash_with_order_update(self): + order: GatewayInFlightOrder = GatewayInFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.quote_asset, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("1"), + amount=Decimal("1000"), + creation_timestamp=1652324823, + initial_state=OrderState.PENDING_CREATE, + ) + + self.assertIsNone(order.creation_transaction_hash) + + desired_cancelation_transaction_hash = "someTransactionHash" + order_update = OrderUpdate( + trading_pair=self.trading_pair, + update_timestamp=1652324823 + 1, + new_state=OrderState.OPEN, + client_order_id=self.client_order_id, + exchange_order_id="someExchangeOrderID", + misc_updates={ + "cancelation_transaction_hash": desired_cancelation_transaction_hash, + } + ) + order.update_with_order_update(order_update=order_update) + + self.assertEqual(desired_cancelation_transaction_hash, order.cancel_tx_hash) + + def test_to_and_from_json(self): + base_order = GatewayInFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.quote_asset, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("1"), + amount=Decimal("1000"), + creation_timestamp=1652324823, + initial_state=OrderState.PENDING_CREATE, + ) + base_order.last_update_timestamp = 1652324824 + + order_json = base_order.to_json() + derived_order = GatewayInFlightOrder.from_json(order_json) + + self.assertEqual(base_order, derived_order) diff --git a/test/hummingbot/connector/gateway/test_gateway_order_tracker.py b/test/hummingbot/connector/gateway/test_gateway_order_tracker.py new file mode 100644 index 0000000..d18382e --- /dev/null +++ b/test/hummingbot/connector/gateway/test_gateway_order_tracker.py @@ -0,0 +1,83 @@ +import unittest +from decimal import Decimal + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.gateway.gateway_order_tracker import GatewayOrderTracker +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType + + +class MockExchange(ExchangeBase): + pass + + +class GatewayOrderTrackerTest(unittest.TestCase): + trading_pair: str + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.trading_pair = combine_to_hb_trading_pair(base="COIN", quote="ALPHA") + + def setUp(self) -> None: + super().setUp() + + self.connector = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.connector._set_current_timestamp(1640000000.0) + self.tracker = GatewayOrderTracker(connector=self.connector) + + def test_all_fillable_orders_by_hash(self): + self.tracker.start_tracking_order( + order=GatewayInFlightOrder( + client_order_id="1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1231233123, + price=Decimal("1"), + amount=Decimal("2"), + exchange_order_id="asdf", + creation_transaction_hash=None, + ) + ) + first_creation_hash = "someHash" + self.tracker.start_tracking_order( + order=GatewayInFlightOrder( + client_order_id="2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1231233123, + price=Decimal("1"), + amount=Decimal("2"), + exchange_order_id="asdg", + creation_transaction_hash=first_creation_hash, + ) + ) + second_creation_hash = "anotherHash" + cancelation_hash = "yetAnotherHash" + self.tracker.start_tracking_order( + order=GatewayInFlightOrder( + client_order_id="3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=1231233123, + price=Decimal("1"), + amount=Decimal("2"), + exchange_order_id="asde", + creation_transaction_hash=second_creation_hash, + ) + ) + order = self.tracker.all_orders["3"] + order.cancel_tx_hash = cancelation_hash + + orders_by_hashes = self.tracker.all_fillable_orders_by_hash + + self.assertEqual(3, len(orders_by_hashes)) + self.assertIn(first_creation_hash, orders_by_hashes) + self.assertIn(second_creation_hash, orders_by_hashes) + self.assertIn(cancelation_hash, orders_by_hashes) diff --git a/test/hummingbot/connector/test_budget_checker.py b/test/hummingbot/connector/test_budget_checker.py new file mode 100644 index 0000000..62c6461 --- /dev/null +++ b/test/hummingbot/connector/test_budget_checker.py @@ -0,0 +1,582 @@ +import unittest +from decimal import Decimal + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.budget_checker import BudgetChecker +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeSchema + + +class BudgetCheckerTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.base_asset = "COINALPHA" + self.quote_asset = "HBOT" + self.trading_pair = f"{self.base_asset}-{self.quote_asset}" + + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.01"), taker_percent_fee_decimal=Decimal("0.02") + ) + self.exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + self.budget_checker: BudgetChecker = self.exchange.budget_checker + + def test_populate_collateral_fields_buy_order(self): + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("10"), populated_candidate.potential_returns.amount) + + def test_populate_collateral_fields_taker_buy_order(self): + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=False, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("10"), populated_candidate.potential_returns.amount) + + def test_populate_collateral_fields_buy_order_percent_fee_from_returns(self): + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.01"), + taker_percent_fee_decimal=Decimal("0.01"), + buy_percent_fee_deducted_from_returns=True, + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + budget_checker: BudgetChecker = exchange.budget_checker + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertIsNone(populated_candidate.percent_fee_collateral) + self.assertEqual(self.base_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.1"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("9.90"), populated_candidate.potential_returns.amount) + + def test_populate_collateral_fields_sell_order(self): + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = self.budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.base_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), populated_candidate.order_collateral.amount) + self.assertIsNone(populated_candidate.percent_fee_collateral) + self.assertEqual(self.quote_asset, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.quote_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("19.8"), populated_candidate.potential_returns.amount) + + def test_populate_collateral_fields_percent_fees_in_third_token(self): + pfc_token = "PFC" + trade_fee_schema = TradeFeeSchema( + percent_fee_token=pfc_token, + maker_percent_fee_decimal=Decimal("0.01"), + taker_percent_fee_decimal=Decimal("0.01"), + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + pfc_quote_pair = combine_to_hb_trading_pair(self.quote_asset, pfc_token) + exchange.set_balanced_order_book( # the quote to pfc price will be 1:2 + trading_pair=pfc_quote_pair, + mid_price=1.5, + min_price=1, + max_price=2, + price_step_size=1, + volume_step_size=1, + ) + budget_checker: BudgetChecker = exchange.budget_checker + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertEqual(pfc_token, populated_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_collateral.amount) + self.assertEqual(pfc_token, populated_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.4"), populated_candidate.percent_fee_value.amount) + self.assertEqual(0, len(populated_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("10"), populated_candidate.potential_returns.amount) + + def test_populate_collateral_fields_fixed_fees_in_quote_token(self): + trade_fee_schema = TradeFeeSchema( + maker_fixed_fees=[TokenAmount(self.quote_asset, Decimal("1"))], + taker_fixed_fees=[TokenAmount(self.base_asset, Decimal("2"))], + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + budget_checker: BudgetChecker = exchange.budget_checker + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + populated_candidate = budget_checker.populate_collateral_entries(order_candidate) + + self.assertEqual(self.quote_asset, populated_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), populated_candidate.order_collateral.amount) + self.assertIsNone(populated_candidate.percent_fee_collateral) + self.assertIsNone(populated_candidate.percent_fee_value) + self.assertEqual(1, len(populated_candidate.fixed_fee_collaterals)) + + fixed_fee_collateral = populated_candidate.fixed_fee_collaterals[0] + + self.assertEqual(self.quote_asset, fixed_fee_collateral.token) + self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) + self.assertEqual(self.base_asset, populated_candidate.potential_returns.token) + self.assertEqual(Decimal("10"), populated_candidate.potential_returns.amount) + + def test_adjust_candidate_sufficient_funds(self): + self.exchange.set_balance(self.quote_asset, Decimal("100")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate) + + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("20"), adjusted_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_value.amount) + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("10"), adjusted_candidate.potential_returns.amount) + + def test_adjust_candidate_insufficient_funds_all_or_none(self): + self.exchange.set_balance(self.quote_asset, Decimal("10")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate, all_or_none=True) + + self.assertEqual(0, adjusted_candidate.amount) + self.assertIsNone(adjusted_candidate.order_collateral) + self.assertIsNone(adjusted_candidate.percent_fee_collateral) + self.assertIsNone(adjusted_candidate.percent_fee_value) + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertIsNone(adjusted_candidate.potential_returns) + + def test_adjust_candidate_buy_insufficient_funds_partial_adjustment_allowed(self): + q_params = QuantizationParams( + trading_pair=self.trading_pair, + price_precision=8, + price_decimals=2, + order_size_precision=8, + order_size_decimals=2, + ) + self.exchange.set_quantization_param(q_params) + self.exchange.set_balance(self.quote_asset, Decimal("10")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + # order amount quantized to two decimal places + self.assertEqual(Decimal("4.95"), adjusted_candidate.amount) # 5 * .99 + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("9.9"), adjusted_candidate.order_collateral.amount) # 4.95 * 2 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_collateral.amount) # 9.9 * 0.01 + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.099"), adjusted_candidate.percent_fee_value.amount) # 9.9 * 0.01 + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("4.95"), adjusted_candidate.potential_returns.amount) + + def test_adjust_candidate_sell_insufficient_funds_partial_adjustment_allowed(self): + self.exchange.set_balance(self.base_asset, Decimal("5")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = self.budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("5"), adjusted_candidate.amount) + self.assertEqual(self.base_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("5"), adjusted_candidate.order_collateral.amount) + self.assertIsNone(adjusted_candidate.percent_fee_collateral) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.1"), adjusted_candidate.percent_fee_value.amount) # 10 * 0.01 + self.assertEqual(0, len(adjusted_candidate.fixed_fee_collaterals)) + self.assertEqual(self.quote_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("9.9"), adjusted_candidate.potential_returns.amount) # 10 * 0.99 + + def test_adjust_candidate_insufficient_funds_for_flat_fees_same_token(self): + trade_fee_schema = TradeFeeSchema( + maker_fixed_fees=[TokenAmount(self.quote_asset, Decimal("1"))], + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + budget_checker: BudgetChecker = exchange.budget_checker + exchange.set_balance(self.quote_asset, Decimal("11")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("5"), adjusted_candidate.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), adjusted_candidate.order_collateral.amount) + self.assertIsNone(adjusted_candidate.percent_fee_collateral) + self.assertIsNone(adjusted_candidate.percent_fee_value) + self.assertEqual(1, len(adjusted_candidate.fixed_fee_collaterals)) + + fixed_fee_collateral = adjusted_candidate.fixed_fee_collaterals[0] + + self.assertEqual(self.quote_asset, fixed_fee_collateral.token) + self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) + self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("5"), adjusted_candidate.potential_returns.amount) + + def test_adjust_candidate_insufficient_funds_for_flat_fees_third_token(self): + fee_asset = "FEE" + trade_fee_schema = TradeFeeSchema( + maker_fixed_fees=[TokenAmount(fee_asset, Decimal("11"))], + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + budget_checker: BudgetChecker = exchange.budget_checker + exchange.set_balance(self.quote_asset, Decimal("100")) + exchange.set_balance(fee_asset, Decimal("10")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertTrue(adjusted_candidate.is_zero_order) + + def test_adjust_candidate_insufficient_funds_for_flat_fees_and_percent_fees(self): + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.1"), + maker_fixed_fees=[TokenAmount(self.quote_asset, Decimal("1"))], + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + budget_checker: BudgetChecker = exchange.budget_checker + exchange.set_balance(self.quote_asset, Decimal("12")) + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("5"), adjusted_candidate.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), adjusted_candidate.order_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("1"), adjusted_candidate.percent_fee_collateral.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("1"), adjusted_candidate.percent_fee_value.amount) + self.assertEqual(1, len(adjusted_candidate.fixed_fee_collaterals)) + + fixed_fee_collateral = adjusted_candidate.fixed_fee_collaterals[0] + + self.assertEqual(self.quote_asset, fixed_fee_collateral.token) + self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) + self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("5"), adjusted_candidate.potential_returns.amount) + + def test_adjust_candidate_insufficient_funds_for_flat_fees_and_percent_fees_third_token(self): + fc_token = "PFC" + trade_fee_schema = TradeFeeSchema( + percent_fee_token=fc_token, + maker_percent_fee_decimal=Decimal("0.01"), + taker_percent_fee_decimal=Decimal("0.01"), + maker_fixed_fees=[TokenAmount(fc_token, Decimal("1"))] + ) + exchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + pfc_quote_pair = combine_to_hb_trading_pair(self.quote_asset, fc_token) + exchange.set_balanced_order_book( # the quote to pfc price will be 1:2 + trading_pair=pfc_quote_pair, + mid_price=1.5, + min_price=1, + max_price=2, + price_step_size=1, + volume_step_size=1, + ) + budget_checker: BudgetChecker = exchange.budget_checker + exchange.set_balance(self.quote_asset, Decimal("20")) + exchange.set_balance(fc_token, Decimal("1.2")) # 0.2 less than required; will result in 50% order reduction + + order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.BUY, + amount=Decimal("10"), + price=Decimal("2"), + ) + adjusted_candidate = budget_checker.adjust_candidate(order_candidate, all_or_none=False) + + self.assertEqual(Decimal("5"), adjusted_candidate.amount) + self.assertEqual(self.quote_asset, adjusted_candidate.order_collateral.token) + self.assertEqual(Decimal("10"), adjusted_candidate.order_collateral.amount) + self.assertEqual(fc_token, adjusted_candidate.percent_fee_collateral.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_collateral.amount) + self.assertEqual(fc_token, adjusted_candidate.percent_fee_value.token) + self.assertEqual(Decimal("0.2"), adjusted_candidate.percent_fee_value.amount) + self.assertEqual(1, len(adjusted_candidate.fixed_fee_collaterals)) + + fixed_fee_collateral = adjusted_candidate.fixed_fee_collaterals[0] + + self.assertEqual(fc_token, fixed_fee_collateral.token) + self.assertEqual(Decimal("1"), fixed_fee_collateral.amount) + self.assertEqual(self.base_asset, adjusted_candidate.potential_returns.token) + self.assertEqual(Decimal("5"), adjusted_candidate.potential_returns.amount) + + def test_adjust_candidate_and_lock_available_collateral(self): + self.exchange.set_balance(self.base_asset, Decimal("10")) + + first_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("7"), + price=Decimal("2"), + ) + first_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + first_order_candidate, all_or_none=False + ) + second_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + second_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + second_order_candidate, all_or_none=False + ) + third_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + third_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + third_order_candidate, all_or_none=False + ) + + self.assertEqual(Decimal("7"), first_adjusted_candidate.amount) + self.assertEqual(Decimal("3"), second_adjusted_candidate.amount) + self.assertEqual(Decimal("3"), second_adjusted_candidate.order_collateral.amount) + self.assertEqual(Decimal("0"), third_adjusted_candidate.amount) + self.assertIsNone(third_adjusted_candidate.order_collateral) + + def test_reset_locked_collateral(self): + self.exchange.set_balance(self.base_asset, Decimal("10")) + + first_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("7"), + price=Decimal("2"), + ) + first_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + first_order_candidate, all_or_none=False + ) + + self.budget_checker.reset_locked_collateral() + + second_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + second_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + second_order_candidate, all_or_none=False + ) + + self.assertEqual(Decimal("7"), first_adjusted_candidate.amount) + self.assertEqual(Decimal("5"), second_adjusted_candidate.amount) + + def test_adjust_candidates(self): + self.exchange.set_balance(self.base_asset, Decimal("10")) + + first_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("7"), + price=Decimal("2"), + ) + second_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + third_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + + first_adjusted_candidate, second_adjusted_candidate, third_adjusted_candidate = ( + self.budget_checker.adjust_candidates( + [first_order_candidate, second_order_candidate, third_order_candidate], all_or_none=False + ) + ) + + self.assertEqual(Decimal("7"), first_adjusted_candidate.amount) + self.assertEqual(Decimal("3"), second_adjusted_candidate.amount) + self.assertEqual(Decimal("3"), second_adjusted_candidate.order_collateral.amount) + self.assertEqual(Decimal("0"), third_adjusted_candidate.amount) + self.assertIsNone(third_adjusted_candidate.order_collateral) + + def test_adjust_candidates_resets_locked_collateral(self): + self.exchange.set_balance(self.base_asset, Decimal("10")) + + first_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("7"), + price=Decimal("2"), + ) + first_adjusted_candidate, = self.budget_checker.adjust_candidates( + [first_order_candidate], all_or_none=False + ) + + second_order_candidate = OrderCandidate( + trading_pair=self.trading_pair, + is_maker=True, + order_type=OrderType.LIMIT, + order_side=TradeType.SELL, + amount=Decimal("5"), + price=Decimal("2"), + ) + second_adjusted_candidate = self.budget_checker.adjust_candidate_and_lock_available_collateral( + second_order_candidate, all_or_none=False + ) + + self.assertEqual(Decimal("7"), first_adjusted_candidate.amount) + self.assertEqual(Decimal("5"), second_adjusted_candidate.amount) diff --git a/test/hummingbot/connector/test_client_order_tracker.py b/test/hummingbot/connector/test_client_order_tracker.py new file mode 100644 index 0000000..d37f271 --- /dev/null +++ b/test/hummingbot/connector/test_client_order_tracker.py @@ -0,0 +1,1093 @@ +import asyncio +import unittest +from decimal import Decimal +from typing import Awaitable, Dict +from unittest.mock import patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.client_order_tracker import ClientOrderTracker +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + AddedToCostTradeFee, + BuyOrderCompletedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) + + +class MockExchange(ExchangeBase): + + @property + def order_books(self) -> Dict[str, OrderBook]: + return dict() + + +class ClientOrderTrackerUnitTest(unittest.TestCase): + # logging.Level required to receive logs from the exchange + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.trade_fee_percent = Decimal("0.001") + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + + self.connector = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.connector._set_current_timestamp(1640000000.0) + self.tracker = ClientOrderTracker(connector=self.connector) + + self.tracker.logger().setLevel(1) + self.tracker.logger().addHandler(self) + + self._initialize_event_loggers() + + def _initialize_event_loggers(self): + self.buy_order_completed_logger = EventLogger() + self.buy_order_created_logger = EventLogger() + self.order_cancelled_logger = EventLogger() + self.order_failure_logger = EventLogger() + self.order_filled_logger = EventLogger() + self.sell_order_completed_logger = EventLogger() + self.sell_order_created_logger = EventLogger() + + events_and_loggers = [ + (MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger), + (MarketEvent.BuyOrderCreated, self.buy_order_created_logger), + (MarketEvent.OrderCancelled, self.order_cancelled_logger), + (MarketEvent.OrderFailure, self.order_failure_logger), + (MarketEvent.OrderFilled, self.order_filled_logger), + (MarketEvent.SellOrderCompleted, self.sell_order_completed_logger), + (MarketEvent.SellOrderCreated, self.sell_order_created_logger)] + + for event, logger in events_and_loggers: + self.connector.add_listener(event, logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_start_tracking_order(self): + self.assertEqual(0, len(self.tracker.active_orders)) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + self.tracker.start_tracking_order(order) + + self.assertEqual(1, len(self.tracker.active_orders)) + + def test_stop_tracking_order(self): + self.assertEqual(0, len(self.tracker.active_orders)) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + self.assertEqual(1, len(self.tracker.active_orders)) + + self.tracker.stop_tracking_order(order.client_order_id) + + self.assertEqual(0, len(self.tracker.active_orders)) + self.assertEqual(1, len(self.tracker.cached_orders)) + + def test_cached_order_max_cache_size(self): + for i in range(ClientOrderTracker.MAX_CACHE_SIZE + 1): + order: InFlightOrder = InFlightOrder( + client_order_id=f"someClientOrderId_{i}", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker._cached_orders[order.client_order_id] = order + + self.assertEqual(ClientOrderTracker.MAX_CACHE_SIZE, len(self.tracker.cached_orders)) + + # First entry gets removed when the no. of cached order exceeds MAX_CACHE_SIZE + self.assertNotIn("someClientOrderId_0", self.tracker._cached_orders) + + def test_cached_order_ttl_not_exceeded(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker._cached_orders[order.client_order_id] = order + + self.assertIn(order.client_order_id, self.tracker._cached_orders) + + @patch("hummingbot.connector.client_order_tracker.ClientOrderTracker.CACHED_ORDER_TTL", 0.1) + def test_cached_order_ttl_exceeded(self): + tracker = ClientOrderTracker(self.connector) + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + tracker._cached_orders[order.client_order_id] = order + + self.ev_loop.run_until_complete(asyncio.sleep(0.2)) + + self.assertNotIn(order.client_order_id, tracker.cached_orders) + + def test_fetch_tracked_order_not_found(self): + self.assertIsNone(self.tracker.fetch_tracked_order("someNonExistantOrderId")) + + def test_fetch_tracked_order(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + self.assertEqual(1, len(self.tracker.active_orders)) + + fetched_order: InFlightOrder = self.tracker.fetch_tracked_order(order.client_order_id) + + self.assertTrue(fetched_order == order) + + def test_fetch_cached_order_not_found(self): + self.assertIsNone(self.tracker.fetch_cached_order("someNonExistantOrderId")) + + def test_fetch_cached_order(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker._cached_orders[order.client_order_id] = order + self.assertEqual(1, len(self.tracker.cached_orders)) + + fetched_order: InFlightOrder = self.tracker.fetch_cached_order(order.client_order_id) + + self.assertTrue(fetched_order == order) + + def test_fetch_order_by_client_order_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + self.assertEqual(1, len(self.tracker.active_orders)) + + fetched_order: InFlightOrder = self.tracker.fetch_order(order.client_order_id) + + self.assertTrue(fetched_order == order) + + def test_fetch_order_by_exchange_order_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + self.assertEqual(1, len(self.tracker.active_orders)) + + fetched_order: InFlightOrder = self.tracker.fetch_order(exchange_order_id=order.exchange_order_id) + + self.assertTrue(fetched_order == order) + + def test_fetch_order_does_not_match_orders_with_undefined_exchange_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + self.assertEqual(1, len(self.tracker.active_orders)) + + fetched_order = self.tracker.fetch_order("invalid_order_id") + + self.assertIsNone(fetched_order) + + def test_process_order_update_invalid_order_update(self): + + order_creation_update: OrderUpdate = OrderUpdate( + # client_order_id="someClientOrderId", # client_order_id intentionally omitted + # exchange_order_id="someExchangeOrderId", # client_order_id intentionally omitted + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(update_future) + + self.assertTrue( + self._is_logged( + "ERROR", + "OrderUpdate does not contain any client_order_id or exchange_order_id", + ) + ) + + def test_process_order_update_order_not_found(self): + + order_creation_update: OrderUpdate = OrderUpdate( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(update_future) + + self.assertTrue( + self._is_logged( + "DEBUG", + f"Order is not/no longer being tracked ({order_creation_update})", + ) + ) + + def test_process_order_update_trigger_order_creation_event(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + + order_creation_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(update_future) + + updated_order: InFlightOrder = self.tracker.fetch_tracked_order(order.client_order_id) + + # Check order update has been successfully applied + self.assertEqual(updated_order.exchange_order_id, order_creation_update.exchange_order_id) + self.assertTrue(updated_order.exchange_order_id_update_event.is_set()) + self.assertEqual(updated_order.current_state, order_creation_update.new_state) + self.assertTrue(updated_order.is_open) + + # Check that Logger has logged the correct log + self.assertTrue( + self._is_logged( + "INFO", + f"Created {order.order_type.name} {order.trade_type.name} order {order.client_order_id} for " + f"{order.amount} {order.trading_pair}.", + ) + ) + + # Check that Buy/SellOrderCreatedEvent has been triggered. + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + event_logged = self.buy_order_created_logger.event_log[0] + self.assertEqual(event_logged.amount, order.amount) + self.assertEqual(event_logged.exchange_order_id, order_creation_update.exchange_order_id) + self.assertEqual(event_logged.order_id, order.client_order_id) + self.assertEqual(event_logged.price, order.price) + self.assertEqual(event_logged.trading_pair, order.trading_pair) + self.assertEqual(event_logged.type, order.order_type) + + def test_process_order_update_trigger_order_creation_event_without_client_order_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", # exchange_order_id is provided when initialized. See AscendEx. + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + + order_creation_update: OrderUpdate = OrderUpdate( + # client_order_id=order.client_order_id, # client_order_id purposefully ommited + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(update_future) + + updated_order: InFlightOrder = self.tracker.fetch_tracked_order(order.client_order_id) + + # Check order update has been successfully applied + self.assertEqual(updated_order.exchange_order_id, order_creation_update.exchange_order_id) + self.assertTrue(updated_order.exchange_order_id_update_event.is_set()) + self.assertEqual(updated_order.current_state, order_creation_update.new_state) + self.assertTrue(updated_order.is_open) + + # Check that Logger has logged the correct log + self.assertTrue( + self._is_logged( + "INFO", + f"Created {order.order_type.name} {order.trade_type.name} order {order.client_order_id} for " + f"{order.amount} {order.trading_pair}.", + ) + ) + + # Check that Buy/SellOrderCreatedEvent has been triggered. + self.assertEqual(1, len(self.buy_order_created_logger.event_log)) + event_logged = self.buy_order_created_logger.event_log[0] + + self.assertEqual(event_logged.amount, order.amount) + self.assertEqual(event_logged.exchange_order_id, order_creation_update.exchange_order_id) + self.assertEqual(event_logged.order_id, order.client_order_id) + self.assertEqual(event_logged.price, order.price) + self.assertEqual(event_logged.trading_pair, order.trading_pair) + self.assertEqual(event_logged.type, order.order_type) + + def test_process_order_update_trigger_order_cancelled_event(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + + self.tracker.start_tracking_order(order) + + order_cancelled_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.CANCELED, + ) + + update_future = self.tracker.process_order_update(order_cancelled_update) + self.async_run_with_timeout(update_future) + + self.assertTrue(self._is_logged("INFO", f"Successfully canceled order {order.client_order_id}.")) + self.assertEqual(0, len(self.tracker.active_orders)) + self.assertEqual(1, len(self.tracker.cached_orders)) + self.assertEqual(1, len(self.order_cancelled_logger.event_log)) + + event_triggered = self.order_cancelled_logger.event_log[0] + self.assertIsInstance(event_triggered, OrderCancelledEvent) + self.assertEqual(event_triggered.exchange_order_id, order.exchange_order_id) + self.assertEqual(event_triggered.order_id, order.client_order_id) + + def test_process_order_update_trigger_order_failure_event(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + + self.tracker.start_tracking_order(order) + + order_failure_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.FAILED, + ) + + update_future = self.tracker.process_order_update(order_failure_update) + self.async_run_with_timeout(update_future) + + self.assertTrue( + self._is_logged("INFO", f"Order {order.client_order_id} has failed. Order Update: {order_failure_update}") + ) + self.assertEqual(0, len(self.tracker.active_orders)) + self.assertEqual(1, len(self.tracker.cached_orders)) + self.assertEqual(1, len(self.order_failure_logger.event_log)) + + event_triggered = self.order_failure_logger.event_log[0] + self.assertIsInstance(event_triggered, MarketOrderFailureEvent) + self.assertEqual(event_triggered.order_id, order.client_order_id) + self.assertEqual(event_triggered.order_type, order.order_type) + + def test_process_order_update_trigger_completed_event_and_not_fill_event(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + initial_order_filled_amount = order.amount / Decimal("2.0") + order_update_1: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.PARTIALLY_FILLED, + ) + + update_future = self.tracker.process_order_update(order_update_1) + self.async_run_with_timeout(update_future) + + # Check order update has been successfully applied + updated_order: InFlightOrder = self.tracker.fetch_tracked_order(order.client_order_id) + self.assertEqual(updated_order.exchange_order_id, order_update_1.exchange_order_id) + self.assertTrue(updated_order.exchange_order_id_update_event.is_set()) + self.assertEqual(updated_order.current_state, order_update_1.new_state) + self.assertTrue(updated_order.is_open) + + subsequent_order_filled_amount = order.amount - initial_order_filled_amount + order_update_2: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.FILLED, + ) + + # Force order to not wait for filled events from TradeUpdate objects + order.completely_filled_event.set() + update_future = self.tracker.process_order_update(order_update_2) + self.async_run_with_timeout(update_future) + + # Check order is not longer being actively tracked + self.assertIsNone(self.tracker.fetch_tracked_order(order.client_order_id)) + + cached_order: InFlightOrder = self.tracker.fetch_cached_order(order.client_order_id) + self.assertEqual(cached_order.current_state, order_update_2.new_state) + self.assertTrue(cached_order.is_done) + + # Check that Logger has logged the appropriate logs + self.assertFalse( + self._is_logged( + "INFO", + f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to " + f"{initial_order_filled_amount}/{order.amount} {order.base_asset} has been filled.", + ) + ) + self.assertFalse( + self._is_logged( + "INFO", + f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to " + f"{initial_order_filled_amount + subsequent_order_filled_amount}/{order.amount} {order.base_asset} " + f"has been filled.", + ) + ) + self.assertTrue( + self._is_logged( + "INFO", f"{order.trade_type.name.upper()} order {order.client_order_id} completely filled." + ) + ) + + self.assertEqual(0, len(self.order_filled_logger.event_log)) + self.assertEqual(1, len(self.buy_order_completed_logger.event_log)) + + def test_process_trade_update_trigger_filled_event_flat_fee(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + trade_filled_price: Decimal = Decimal("0.5") + trade_filled_amount: Decimal = order.amount / Decimal("2.0") + fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount + trade_update: TradeUpdate = TradeUpdate( + trade_id=1, + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fill_price=trade_filled_price, + fill_base_amount=trade_filled_amount, + fill_quote_amount=trade_filled_price * trade_filled_amount, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=fee_paid)]), + fill_timestamp=1, + ) + + self.tracker.process_trade_update(trade_update) + + self.assertTrue( + self._is_logged( + "INFO", + f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to " + f"{trade_filled_amount}/{order.amount} {order.base_asset} has been filled.", + ) + ) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + order_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + + self.assertEqual(order_filled_event.order_id, order.client_order_id) + self.assertEqual(order_filled_event.price, trade_update.fill_price) + self.assertEqual(order_filled_event.amount, trade_update.fill_base_amount) + self.assertEqual( + order_filled_event.trade_fee, AddedToCostTradeFee(flat_fees=[TokenAmount(self.quote_asset, fee_paid)]) + ) + + def test_process_trade_update_does_not_trigger_filled_event_update_status_when_completely_filled(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + fee_paid: Decimal = self.trade_fee_percent * order.amount + trade_update: TradeUpdate = TradeUpdate( + trade_id=1, + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fill_price=order.price, + fill_base_amount=order.amount, + fill_quote_amount=order.price * order.amount, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=fee_paid)]), + fill_timestamp=1, + ) + + self.tracker.process_trade_update(trade_update) + + fetched_order: InFlightOrder = self.tracker.fetch_order(order.client_order_id) + self.assertTrue(fetched_order.is_filled) + self.assertIn(fetched_order.client_order_id, self.tracker.active_orders) + self.assertNotIn(fetched_order.client_order_id, self.tracker.cached_orders) + + self.assertTrue( + self._is_logged( + "INFO", + f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to " + f"{order.amount}/{order.amount} {order.base_asset} has been filled.", + ) + ) + + self.assertEqual(1, len(self.order_filled_logger.event_log)) + self.assertEqual(0, len(self.buy_order_completed_logger.event_log)) + + order_filled_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertIsNotNone(order_filled_event) + + self.assertEqual(order_filled_event.order_id, order.client_order_id) + self.assertEqual(order_filled_event.price, trade_update.fill_price) + self.assertEqual(order_filled_event.amount, trade_update.fill_base_amount) + self.assertEqual( + order_filled_event.trade_fee, AddedToCostTradeFee(flat_fees=[TokenAmount(self.quote_asset, fee_paid)]) + ) + + def test_updating_order_states_with_both_process_order_update_and_process_trade_update(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.tracker.start_tracking_order(order) + + order_creation_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(update_future) + + open_order: InFlightOrder = self.tracker.fetch_tracked_order(order.client_order_id) + + # Check order_creation_update has been successfully applied + self.assertEqual(open_order.exchange_order_id, order_creation_update.exchange_order_id) + self.assertTrue(open_order.exchange_order_id_update_event.is_set()) + self.assertEqual(open_order.current_state, order_creation_update.new_state) + self.assertTrue(open_order.is_open) + self.assertEqual(0, len(open_order.order_fills)) + + trade_filled_price: Decimal = order.price + trade_filled_amount: Decimal = order.amount + fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount + trade_update: TradeUpdate = TradeUpdate( + trade_id=1, + client_order_id=order.client_order_id, + exchange_order_id=order.exchange_order_id, + trading_pair=order.trading_pair, + fill_price=trade_filled_price, + fill_base_amount=trade_filled_amount, + fill_quote_amount=trade_filled_price * trade_filled_amount, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=fee_paid)]), + fill_timestamp=2, + ) + + self.tracker.process_trade_update(trade_update) + self.assertEqual(1, len(self.tracker.active_orders)) + self.assertEqual(0, len(self.tracker.cached_orders)) + + def test_process_order_not_found_invalid_order(self): + self.assertEqual(0, len(self.tracker.active_orders)) + + unknown_order_id = "UNKNOWN_ORDER_ID" + self.async_run_with_timeout(self.tracker.process_order_not_found(unknown_order_id)) + + self._is_logged("DEBUG", f"Order is not/no longer being tracked ({unknown_order_id})") + + def test_process_order_not_found_does_not_exceed_limit(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.active_orders) + self.assertIn(order.client_order_id, self.tracker._order_not_found_records) + self.assertEqual(1, self.tracker._order_not_found_records[order.client_order_id]) + + def test_process_order_not_found_exceeded_limit(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertNotIn(order.client_order_id, self.tracker.active_orders) + + def test_restore_tracking_states_only_registers_open_orders(self): + orders = [] + orders.append(InFlightOrder( + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.223, + price=Decimal("1.0"), + )) + orders.append(InFlightOrder( + client_order_id="OID2", + exchange_order_id="EOID2", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.223, + price=Decimal("1.0"), + initial_state=OrderState.CANCELED + )) + orders.append(InFlightOrder( + client_order_id="OID3", + exchange_order_id="EOID3", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + )) + orders.append(InFlightOrder( + client_order_id="OID4", + exchange_order_id="EOID4", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + initial_state=OrderState.FAILED + )) + + tracking_states = {order.client_order_id: order.to_json() for order in orders} + + self.tracker.restore_tracking_states(tracking_states) + + self.assertIn("OID1", self.tracker.active_orders) + self.assertNotIn("OID2", self.tracker.all_orders) + self.assertNotIn("OID3", self.tracker.all_orders) + self.assertNotIn("OID4", self.tracker.all_orders) + + def test_update_to_close_order_is_not_processed_until_order_completelly_filled(self): + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + price=Decimal("1.0"), + creation_timestamp=1640001112.223, + ) + self.tracker.start_tracking_order(order) + + order_creation_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id="1", + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=order.trading_pair, + fill_price=Decimal("1100"), + fill_base_amount=order.amount, + fill_quote_amount=order.amount * Decimal("1100"), + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token=self.quote_asset, amount=Decimal("10"))]), + fill_timestamp=10, + ) + + order_completion_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.FILLED, + ) + + # We invert the orders update processing on purpose, to force the test scenario without using sleeps + self.connector._set_current_timestamp(1640001100) + completion_update_future = self.tracker.process_order_update(order_completion_update) + + self.connector._set_current_timestamp(1640001105) + creation_update_future = self.tracker.process_order_update(order_creation_update) + self.async_run_with_timeout(creation_update_future) + + order: InFlightOrder = self.tracker.fetch_order(client_order_id=order.client_order_id) + + # Check order_creation_update has been successfully applied + self.assertFalse(order.is_done) + self.assertFalse(order.is_filled) + self.assertFalse(order.completely_filled_event.is_set()) + + fill_timetamp = 1640001115 + self.connector._set_current_timestamp(fill_timetamp) + self.tracker.process_trade_update(trade_update) + self.assertTrue(order.completely_filled_event.is_set()) + + self.connector._set_current_timestamp(1640001120) + self.async_run_with_timeout(completion_update_future) + + self.assertTrue(order.is_filled) + fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] + self.assertEqual(fill_timetamp, fill_event.timestamp) + + complete_event: BuyOrderCompletedEvent = self.buy_order_completed_logger.event_log[0] + self.assertGreaterEqual(complete_event.timestamp, 1640001120) + + def test_access_lost_orders(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertEqual(0, len(self.tracker.lost_orders)) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertEqual(1, len(self.tracker.lost_orders)) + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + def test_lost_orders_returned_in_all_fillable_orders(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.all_fillable_orders) + self.assertNotIn(order.client_order_id, self.tracker.cached_orders) + + def test_lost_orders_returned_in_all_updatable_orders(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.all_updatable_orders) + self.assertNotIn(order.client_order_id, self.tracker.cached_orders) + + def test_lost_order_removed_when_fully_filled(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + order_completion_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.FILLED, + ) + + self.async_run_with_timeout(self.tracker.process_order_update(order_update=order_completion_update)) + + self.assertTrue(order.is_failure) + self.assertNotIn(order.client_order_id, self.tracker.lost_orders) + + def test_lost_order_removed_when_canceled(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + order_completion_update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.CANCELED, + ) + + self.async_run_with_timeout(self.tracker.process_order_update(order_update=order_completion_update)) + + self.assertTrue(order.is_failure) + self.assertNotIn(order.client_order_id, self.tracker.lost_orders) + + def test_lost_order_not_removed_when_updated_with_non_final_states(self): + self.tracker = ClientOrderTracker(connector=self.connector, lost_order_count_limit=1) + + order: InFlightOrder = InFlightOrder( + client_order_id="someClientOrderId", + exchange_order_id="someExchangeOrderId", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + initial_state=OrderState.OPEN, + ) + self.tracker.start_tracking_order(order) + + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + self.async_run_with_timeout(self.tracker.process_order_not_found(order.client_order_id)) + + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.OPEN, + ) + + self.async_run_with_timeout(self.tracker.process_order_update(order_update=update)) + + self.assertTrue(order.is_failure) + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=self.trading_pair, + update_timestamp=3, + new_state=OrderState.PARTIALLY_FILLED, + ) + + self.async_run_with_timeout(self.tracker.process_order_update(order_update=update)) + + self.assertTrue(order.is_failure) + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + update: OrderUpdate = OrderUpdate( + client_order_id=order.client_order_id, + trading_pair=self.trading_pair, + update_timestamp=3, + new_state=OrderState.PENDING_CANCEL, + ) + + self.async_run_with_timeout(self.tracker.process_order_update(order_update=update)) + + self.assertTrue(order.is_failure) + self.assertIn(order.client_order_id, self.tracker.lost_orders) + + def test_setting_lost_order_count_limit(self): + self.tracker.lost_order_count_limit = 1 + + self.assertEqual(1, self.tracker.lost_order_count_limit) + + self.tracker.lost_order_count_limit = 2 + + self.assertEqual(2, self.tracker.lost_order_count_limit) diff --git a/test/hummingbot/connector/test_connector_base.py b/test/hummingbot/connector/test_connector_base.py new file mode 100644 index 0000000..dc8444b --- /dev/null +++ b/test/hummingbot/connector/test_connector_base.py @@ -0,0 +1,572 @@ +import copy +import unittest +import unittest.mock +from decimal import Decimal +from typing import Dict, List + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.connector_base import ConnectorBase, OrderFilledEvent +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee + + +class InFightOrderTest(InFlightOrderBase): + @property + def is_done(self) -> bool: + return False + + @property + def is_cancelled(self) -> bool: + return False + + @property + def is_failure(self) -> bool: + return False + + +class MockTestConnector(ConnectorBase): + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + self._in_flight_orders = {} + self._event_logs = [] + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrder]: + return self._in_flight_orders + + @property + def event_logs(self) -> List: + return self._event_logs + + +class ConnectorBaseUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls._patcher = unittest.mock.patch("hummingbot.connector.connector_base.estimate_fee") + cls._url_mock = cls._patcher.start() + cls._url_mock.return_value = AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]) + + @classmethod + def tearDownClass(cls) -> None: + cls._patcher.stop() + + def test_in_flight_asset_balances(self): + connector = ConnectorBase(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + orders = { + "1": InFightOrderTest("1", "A", "HBOT-USDT", OrderType.LIMIT, TradeType.BUY, 100, 1, 1640001112.0, "live"), + "2": InFightOrderTest("2", "B", "HBOT-USDT", OrderType.LIMIT, TradeType.BUY, 100, 2, 1640001112.0, "live"), + "3": InFightOrderTest("3", "C", "HBOT-USDT", OrderType.LIMIT, TradeType.SELL, 110, + Decimal("1.5"), 1640001112.0, "live") + } + bals = connector.in_flight_asset_balances(orders) + self.assertEqual(Decimal("300"), bals["USDT"]) + self.assertEqual(Decimal("1.5"), bals["HBOT"]) + + def test_estimated_available_balance_with_no_order_during_snapshot_is_the_registered_available_balance(self): + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_balance = Decimal("1000") + estimated_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_balance) + + self.assertEqual(initial_balance, estimated_balance) + + def test_estimated_available_balance_with_unfilled_orders_during_snapshot_and_no_current_orders(self): + # Considers the case where the balance update was done when two orders were alive + # The orders were then cancelled and the available balance is calculated after the cancellation + # but before the new balance update + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + initial_buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + initial_sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {order.client_order_id: order for order + in [initial_buy_order, initial_sell_order]} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + self.assertEqual(initial_coinalpha_balance + initial_sell_order.amount, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance + (initial_buy_order.amount * initial_buy_order.price), + estimated_hbot_balance) + + def test_estimated_available_balance_with_no_orders_during_snapshot_and_two_current_orders(self): + # Considers the case where the balance update was done when no orders were alive + # At the moment of calculating the available balance there are two live orders + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + connector._in_flight_orders = {order.client_order_id: order for order in [buy_order, sell_order]} + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + self.assertEqual(initial_coinalpha_balance - sell_order.amount, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance - (buy_order.amount * buy_order.price), + estimated_hbot_balance) + + def test_estimated_available_balance_with_unfilled_orders_during_snapshot_that_are_still_alive(self): + # Considers the case where the balance update was done when two orders were alive + # The orders are still alive when calculating the available balance + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + initial_buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + initial_sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {order.client_order_id: order for order + in [initial_buy_order, initial_sell_order]} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + connector._in_flight_orders = {order.client_order_id: order for order + in [copy.copy(initial_buy_order), copy.copy(initial_sell_order)]} + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + self.assertEqual(initial_coinalpha_balance, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance, estimated_hbot_balance) + + def test_estimated_available_balance_with_no_orders_during_snapshot_no_alive_orders_and_a_fill_event(self): + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + connector.in_flight_orders_snapshot = {} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + connector._in_flight_orders = {} + + fill_event = OrderFilledEvent( + timestamp=1640000002, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1050), + amount=Decimal(2), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(fill_event) + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + self.assertEqual(initial_coinalpha_balance + fill_event.amount, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance - (fill_event.amount * fill_event.price), + estimated_hbot_balance) + + def test_fill_event_previous_to_balance_updated_is_ignored_for_estimated_available_balance(self): + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + connector.in_flight_orders_snapshot = {} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + connector._in_flight_orders = {} + + fill_event = OrderFilledEvent( + timestamp=1630000999, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1050), + amount=Decimal(2), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(fill_event) + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + self.assertEqual(initial_coinalpha_balance, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance, estimated_hbot_balance) + + def test_estimated_available_balance_with_partially_filled_orders_during_snapshot_and_no_current_orders(self): + # Considers the case where the balance update was done when two orders were alive and partially filled + # The orders were then cancelled and the available balance is calculated after the cancellation + # but before the new balance update + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + initial_buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + initial_sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {order.client_order_id: order for order + in [initial_buy_order, initial_sell_order]} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + + buy_fill_event = OrderFilledEvent( + timestamp=1630000999, + order_id=initial_buy_order.client_order_id, + trading_pair=initial_buy_order.trading_pair, + trade_type=initial_buy_order.trade_type, + order_type=initial_buy_order.order_type, + price=Decimal(950), + amount=Decimal("0.5"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(buy_fill_event) + initial_buy_order.executed_amount_base = buy_fill_event.amount + initial_buy_order.executed_amount_quote = buy_fill_event.amount * buy_fill_event.price + + sell_fill_event = OrderFilledEvent( + timestamp=1630000999, + order_id=initial_sell_order.client_order_id, + trading_pair=initial_sell_order.trading_pair, + trade_type=initial_sell_order.trade_type, + order_type=initial_sell_order.order_type, + price=Decimal(1120), + amount=Decimal("0.1"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(sell_fill_event) + initial_sell_order.executed_amount_base = sell_fill_event.amount + initial_sell_order.executed_amount_quote = sell_fill_event.amount * sell_fill_event.price + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + # The partial fills prior to the balance update are already impacted in the balance + # Only the unfilled part of the orders should be recovered once they are gone + self.assertEqual(initial_coinalpha_balance + initial_sell_order.amount - sell_fill_event.amount, + estimated_coinalpha_balance) + expected_hbot_amount = (initial_hbot_balance + + (initial_buy_order.amount - initial_buy_order.executed_amount_base) * initial_buy_order.price) + self.assertEqual(expected_hbot_amount, estimated_hbot_balance) + + def test_estimated_available_balance_with_partially_filled_orders_during_snapshot_that_are_still_alive(self): + # Considers the case where the balance update was done when two orders were alive and partially filled + # The orders are still alive with no more fills + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + initial_buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + initial_sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {order.client_order_id: order for order + in [initial_buy_order, initial_sell_order]} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + + buy_fill_event = OrderFilledEvent( + timestamp=1630000999, + order_id=initial_buy_order.client_order_id, + trading_pair=initial_buy_order.trading_pair, + trade_type=initial_buy_order.trade_type, + order_type=initial_buy_order.order_type, + price=Decimal(950), + amount=Decimal("0.5"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(buy_fill_event) + initial_buy_order.executed_amount_base = buy_fill_event.amount + initial_buy_order.executed_amount_quote = buy_fill_event.amount * buy_fill_event.price + + sell_fill_event = OrderFilledEvent( + timestamp=1630000999, + order_id=initial_sell_order.client_order_id, + trading_pair=initial_sell_order.trading_pair, + trade_type=initial_sell_order.trade_type, + order_type=initial_sell_order.order_type, + price=Decimal(1120), + amount=Decimal("0.1"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(sell_fill_event) + initial_sell_order.executed_amount_base = sell_fill_event.amount + initial_sell_order.executed_amount_quote = sell_fill_event.amount * sell_fill_event.price + + connector._in_flight_orders = {order.client_order_id: order for order + in [copy.copy(initial_buy_order), copy.copy(initial_sell_order)]} + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + # The partial fills prior to the balance update are already impacted in the balance + self.assertEqual(initial_coinalpha_balance, estimated_coinalpha_balance) + self.assertEqual(initial_hbot_balance, estimated_hbot_balance) + + def test_estimated_available_balance_with_unfilled_orders_during_snapshot_two_current_partial_filled_and_extra_fill(self): + # Considers the case where the balance update was done when two orders were alive + # Currently those initial orders are gone, and there are two new partially filled orders + # There is an extra fill event for an order no longer present + + connector = MockTestConnector(client_config_map=ClientConfigAdapter(ClientConfigMap())) + connector.real_time_balance_update = True + connector.in_flight_orders_snapshot = {} + + initial_coinalpha_balance = Decimal("10") + initial_hbot_balance = Decimal("100000") + + initial_buy_order = InFlightOrder( + client_order_id="OID1", + exchange_order_id="1234", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640000000 + ) + initial_sell_order = InFlightOrder( + client_order_id="OID2", + exchange_order_id="1235", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640000000 + ) + + connector.in_flight_orders_snapshot = {order.client_order_id: order for order + in [initial_buy_order, initial_sell_order]} + connector.in_flight_orders_snapshot_timestamp = 1640000000 + + current_buy_order = InFlightOrder( + client_order_id="OID3", + exchange_order_id="1236", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal("900"), + amount=Decimal("1"), + creation_timestamp=1640100000 + ) + current_sell_order = InFlightOrder( + client_order_id="OID4", + exchange_order_id="1237", + trading_pair="COINALPHA-HBOT", + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + price=Decimal("1100"), + amount=Decimal("0.5"), + creation_timestamp=1640100000 + ) + + connector._in_flight_orders = {order.client_order_id: order for order + in [current_buy_order, current_sell_order]} + + buy_fill_event = OrderFilledEvent( + timestamp=1640100999, + order_id=current_buy_order.client_order_id, + trading_pair=current_buy_order.trading_pair, + trade_type=current_buy_order.trade_type, + order_type=current_buy_order.order_type, + price=Decimal(950), + amount=Decimal("0.5"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(buy_fill_event) + current_buy_order.executed_amount_base = buy_fill_event.amount + current_buy_order.executed_amount_quote = buy_fill_event.amount * buy_fill_event.price + + sell_fill_event = OrderFilledEvent( + timestamp=1640100999, + order_id=current_sell_order.client_order_id, + trading_pair=current_sell_order.trading_pair, + trade_type=current_sell_order.trade_type, + order_type=current_sell_order.order_type, + price=Decimal(1120), + amount=Decimal("0.1"), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(sell_fill_event) + current_sell_order.executed_amount_base = sell_fill_event.amount + current_sell_order.executed_amount_quote = sell_fill_event.amount * sell_fill_event.price + + extra_fill_event = OrderFilledEvent( + timestamp=1640101999, + order_id="OID99", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(3), + trade_fee=AddedToCostTradeFee(), + ) + connector._event_logs.append(extra_fill_event) + + estimated_coinalpha_balance = connector.apply_balance_update_since_snapshot( + currency="COINALPHA", + available_balance=initial_coinalpha_balance) + estimated_hbot_balance = connector.apply_balance_update_since_snapshot( + currency="HBOT", + available_balance=initial_hbot_balance) + + expected_coinalpha_amount = (initial_coinalpha_balance + + initial_sell_order.amount + + current_buy_order.executed_amount_base + - current_sell_order.amount + + extra_fill_event.amount) + self.assertEqual(expected_coinalpha_amount, estimated_coinalpha_balance) + expected_hbot_amount = (initial_hbot_balance + + (initial_buy_order.amount * initial_buy_order.price) + - ((current_buy_order.amount - current_buy_order.executed_amount_base) * current_buy_order.price) + - (current_buy_order.executed_amount_quote) + + (current_sell_order.executed_amount_quote) + - (extra_fill_event.amount * extra_fill_event.price)) + self.assertEqual(expected_hbot_amount, estimated_hbot_balance) diff --git a/test/hummingbot/connector/test_connector_metrics_collector.py b/test/hummingbot/connector/test_connector_metrics_collector.py new file mode 100644 index 0000000..712560c --- /dev/null +++ b/test/hummingbot/connector/test_connector_metrics_collector.py @@ -0,0 +1,257 @@ +import asyncio +import json +import platform +from decimal import Decimal +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, PropertyMock + +import hummingbot.connector.connector_metrics_collector +from hummingbot.connector.connector_metrics_collector import TradeVolumeMetricCollector +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.core.rate_oracle.rate_oracle import RateOracle + + +class TradeVolumeMetricCollectorTests(TestCase): + + def setUp(self) -> None: + super().setUp() + + self.metrics_collector_url = "localhost" + self.connector_name = "test_connector" + self.instance_id = "test_instance_id" + self.client_version = "0.1" + self.rate_oracle = RateOracle() + self.connector_mock = MagicMock() + type(self.connector_mock).name = PropertyMock(return_value=self.connector_name) + self.dispatcher_mock = MagicMock() + type(self.dispatcher_mock).log_server_url = PropertyMock(return_value=self.metrics_collector_url) + + self.original_client_version = hummingbot.connector.connector_metrics_collector.CLIENT_VERSION + hummingbot.connector.connector_metrics_collector.CLIENT_VERSION = self.client_version + + self.metrics_collector = TradeVolumeMetricCollector( + connector=self.connector_mock, + activation_interval=Decimal(10), + rate_provider=self.rate_oracle, + instance_id=self.instance_id) + + self.metrics_collector._dispatcher = self.dispatcher_mock + + def tearDown(self) -> None: + hummingbot.connector.connector_metrics_collector.CLIENT_VERSION = self.original_client_version + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_instance_creation_using_configuration_parameters(self): + + metrics_collector = TradeVolumeMetricCollector( + connector=self.connector_mock, + activation_interval=300, + rate_provider=self.rate_oracle, + instance_id=self.instance_id, + valuation_token="USDT") + + self.assertEqual(5 * 60, metrics_collector._activation_interval) + self.assertEqual(TradeVolumeMetricCollector.DEFAULT_METRICS_SERVER_URL, + metrics_collector._dispatcher.log_server_url) + self.assertEqual(self.instance_id, metrics_collector._instance_id) + self.assertEqual(self.client_version, metrics_collector._client_version) + self.assertEqual("USDT", metrics_collector._valuation_token) + + def test_start_and_stop_are_forwarded_to_dispatcher(self): + self.metrics_collector.start() + self.dispatcher_mock.start.assert_called() + + self.metrics_collector.stop() + self.dispatcher_mock.stop.assert_called() + + def test_start_registers_to_order_fill_event(self): + self.metrics_collector.start() + self.connector_mock.add_listener.assert_called() + self.assertEqual(MarketEvent.OrderFilled, self.connector_mock.add_listener.call_args[0][0]) + + def test_stop_unregisters_from_order_fill_event(self): + self.metrics_collector.stop() + self.connector_mock.remove_listener.assert_called() + self.assertEqual(MarketEvent.OrderFilled, self.connector_mock.remove_listener.call_args[0][0]) + + def test_process_tick_does_not_collect_metrics_if_activation_interval_not_reached(self): + event = OrderFilledEvent( + timestamp=1000, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + self.metrics_collector._register_fill_event(event) + + last_process_tick_timestamp_copy = self.metrics_collector._last_process_tick_timestamp + last_executed_collection_process = self.metrics_collector._last_executed_collection_process + + self.metrics_collector.process_tick(timestamp=5) + + self.assertEqual(last_process_tick_timestamp_copy, self.metrics_collector._last_process_tick_timestamp) + self.assertEqual(last_executed_collection_process, self.metrics_collector._last_executed_collection_process) + self.assertIn(event, self.metrics_collector._collected_events) + + def test_process_tick_starts_metrics_collection_if_activation_interval_reached(self): + event = OrderFilledEvent( + timestamp=1000, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + self.metrics_collector._register_fill_event(event) + + last_executed_collection_process = self.metrics_collector._last_executed_collection_process + + self.metrics_collector.process_tick(timestamp=15) + + self.assertEqual(15, self.metrics_collector._last_process_tick_timestamp) + self.assertNotEqual(last_executed_collection_process, self.metrics_collector._last_executed_collection_process) + self.assertNotIn(event, self.metrics_collector._collected_events) + + def test_collect_metrics_does_not_dispatch_anything_when_no_events_registered(self): + self.async_run_with_timeout(self.metrics_collector.collect_metrics([])) + self.dispatcher_mock.request.assert_not_called() + + def test_collect_metrics_for_single_event(self): + self.rate_oracle._prices = {"HBOT-USDT": Decimal("100")} + + event = OrderFilledEvent( + timestamp=1000, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + self.async_run_with_timeout(self.metrics_collector.collect_metrics([event])) + + expected_dispatch_request = { + "url": f"{self.metrics_collector_url}/client_metrics", + "method": "POST", + "request_obj": { + "headers": { + 'Content-Type': "application/json" + }, + "data": json.dumps({ + "source": "hummingbot", + "name": TradeVolumeMetricCollector.METRIC_NAME, + "instance_id": self.instance_id, + "exchange": self.connector_name, + "version": self.client_version, + "system": f"{platform.system()} {platform.release()}({platform.platform()})", + "value": str(event.amount * event.price * 100) + }), + "params": {"ddtags": f"instance_id:{self.instance_id}," + f"client_version:{self.client_version}," + f"type:metrics", + "ddsource": "hummingbot-client"} + } + } + + self.dispatcher_mock.request.assert_called() + dispatched_metric = self.dispatcher_mock.request.call_args[0][0] + + self.assertEqual(expected_dispatch_request, dispatched_metric) + + def test_metrics_not_collected_when_convertion_rate_to_volume_token_not_found(self): + mock_rate_oracle = MagicMock() + mock_rate_oracle.stored_or_live_rate = AsyncMock(return_value=None) + + local_collector = TradeVolumeMetricCollector( + connector=self.connector_mock, + activation_interval=10, + rate_provider=mock_rate_oracle, + instance_id=self.instance_id) + local_collector._dispatcher = self.dispatcher_mock + + event = OrderFilledEvent( + timestamp=1000, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + self.async_run_with_timeout(local_collector.collect_metrics([event])) + + self.dispatcher_mock.request.assert_not_called() + + def test_collect_metrics_uses_event_amount_when_only_base_token_convertion_rate_found(self): + self.rate_oracle._prices = { + "HBOT-USDT": Decimal("100"), + "COINALPHA-USDT": Decimal("200"), + } + + event_1 = OrderFilledEvent( + timestamp=1000, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(1000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + event_2 = OrderFilledEvent( + timestamp=1000, + order_id="OID2", + trading_pair="COINALPHA-ZZZ", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal(500000), + amount=Decimal(1), + trade_fee=AddedToCostTradeFee(), + ) + self.async_run_with_timeout(self.metrics_collector.collect_metrics([event_1, event_2])) + + expected_volume = Decimal("0") + expected_volume += event_1.amount * event_1.price * Decimal("100") + expected_volume += event_2.amount * Decimal("200") + + expected_dispatch_request = { + "url": f"{self.metrics_collector_url}/client_metrics", + "method": "POST", + "request_obj": { + "headers": { + 'Content-Type': "application/json" + }, + "data": json.dumps({ + "source": "hummingbot", + "name": TradeVolumeMetricCollector.METRIC_NAME, + "instance_id": self.instance_id, + "exchange": self.connector_name, + "version": self.client_version, + "system": f"{platform.system()} {platform.release()}({platform.platform()})", + "value": str(expected_volume) + }), + "params": {"ddtags": f"instance_id:{self.instance_id}," + f"client_version:{self.client_version}," + f"type:metrics", + "ddsource": "hummingbot-client"} + } + } + + self.dispatcher_mock.request.assert_called() + dispatched_metric = self.dispatcher_mock.request.call_args[0][0] + + self.assertEqual(expected_dispatch_request, dispatched_metric) diff --git a/test/hummingbot/connector/test_markets_recorder.py b/test/hummingbot/connector/test_markets_recorder.py new file mode 100644 index 0000000..1eaf8d4 --- /dev/null +++ b/test/hummingbot/connector/test_markets_recorder.py @@ -0,0 +1,494 @@ +import asyncio +import time +from decimal import Decimal +from typing import Awaitable +from unittest import TestCase +from unittest.mock import MagicMock, PropertyMock, patch + +import numpy as np +from sqlalchemy import create_engine + +from hummingbot.client.config.client_config_map import ClientConfigMap, MarketDataCollectionConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.markets_recorder import MarketsRecorder +from hummingbot.core.data_type.common import OrderType, PositionAction, PriceType, TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderFilledEvent, + SellOrderCreatedEvent, +) +from hummingbot.logger import HummingbotLogger +from hummingbot.model.market_data import MarketData +from hummingbot.model.order import Order +from hummingbot.model.sql_connection_manager import SQLConnectionManager, SQLConnectionType +from hummingbot.model.trade_fill import TradeFill +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketsRecorderTests(TestCase): + @staticmethod + def create_mock_strategy(): + market = MagicMock() + market_info = MagicMock() + market_info.market = market + + strategy = MagicMock(spec=ScriptStrategyBase) + type(strategy).market_info = PropertyMock(return_value=market_info) + type(strategy).trading_pair = PropertyMock(return_value="ETH-USDT") + strategy.buy.side_effect = ["OID-BUY-1", "OID-BUY-2", "OID-BUY-3"] + strategy.sell.side_effect = ["OID-SELL-1", "OID-SELL-2", "OID-SELL-3"] + strategy.cancel.return_value = None + strategy.connectors = { + "binance_perpetual": MagicMock(), + } + return strategy + + @staticmethod + def async_run_with_timeout(coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_price_by_type(self, trading_pair, price_type): + pass + + def get_order_book(self, trading_pair): + pass + + @patch("hummingbot.model.sql_connection_manager.create_engine") + def setUp(self, engine_mock) -> None: + super().setUp() + self.display_name = "test_market" + self.config_file_path = "test_config" + self.strategy_name = "test_strategy" + + self.symbol = "COINALPHAHBOT" + self.base = "COINALPHA" + self.quote = "HBOT" + self.trading_pair = f"{self.base}-{self.quote}" + self.ready = True + self.trading_pairs = [self.trading_pair] + + engine_mock.return_value = create_engine("sqlite:///:memory:") + self.manager = SQLConnectionManager( + ClientConfigAdapter(ClientConfigMap()), SQLConnectionType.TRADE_FILLS, db_name="test_DB" + ) + + self.tracking_states = dict() + + def add_trade_fills_from_market_recorder(self, current_trade_fills): + pass + + def add_exchange_order_ids_from_market_recorder(self, current_exchange_order_ids): + pass + + def test_properties(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + self.assertEqual(self.manager, recorder.sql_manager) + self.assertEqual(self.config_file_path, recorder.config_file_path) + self.assertEqual(self.strategy_name, recorder.strategy_name) + self.assertIsInstance(recorder.logger(), HummingbotLogger) + + def test_get_trade_for_config(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + with self.manager.get_new_session() as session: + with session.begin(): + trade_fill_record = TradeFill( + config_file_path=self.config_file_path, + strategy=self.strategy_name, + market=self.display_name, + symbol=self.symbol, + base_asset=self.base, + quote_asset=self.quote, + timestamp=int(time.time()), + order_id="OID1", + trade_type=TradeType.BUY.name, + order_type=OrderType.LIMIT.name, + price=Decimal(1000), + amount=Decimal(1), + leverage=1, + trade_fee=AddedToCostTradeFee().to_json(), + exchange_trade_id="EOID1", + position=PositionAction.NIL.value) + session.add(trade_fill_record) + + fill_id = trade_fill_record.exchange_trade_id + + trades = recorder.get_trades_for_config("test_config") + self.assertEqual(1, len(trades)) + self.assertEqual(fill_id, trades[0].exchange_trade_id) + + def test_buy_order_created_event_creates_order_record(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + event = BuyOrderCreatedEvent( + timestamp=int(time.time()), + type=OrderType.LIMIT, + trading_pair=self.trading_pair, + amount=Decimal(1), + price=Decimal(1000), + order_id="OID1", + creation_timestamp=1640001112.223, + exchange_order_id="EOID1", + ) + + recorder._did_create_order(MarketEvent.BuyOrderCreated.value, self, event) + + with self.manager.get_new_session() as session: + query = session.query(Order) + orders = query.all() + order = orders[0] + order_status = order.status + trade_fills = order.trade_fills + + self.assertEqual(1, len(orders)) + self.assertEqual(self.config_file_path, orders[0].config_file_path) + self.assertEqual(event.order_id, orders[0].id) + self.assertEqual(1640001112223, orders[0].creation_timestamp) + self.assertEqual(1, len(order_status)) + self.assertEqual(MarketEvent.BuyOrderCreated.name, order_status[0].status) + self.assertEqual(0, len(trade_fills)) + + def test_sell_order_created_event_creates_order_record(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + event = SellOrderCreatedEvent( + timestamp=int(time.time()), + type=OrderType.LIMIT, + trading_pair=self.trading_pair, + amount=Decimal(1), + price=Decimal(1000), + order_id="OID1", + creation_timestamp=1640001112.223, + exchange_order_id="EOID1", + ) + + recorder._did_create_order(MarketEvent.SellOrderCreated.value, self, event) + + with self.manager.get_new_session() as session: + query = session.query(Order) + orders = query.all() + order = orders[0] + order_status = order.status + trade_fills = order.trade_fills + + self.assertEqual(1, len(orders)) + self.assertEqual(self.config_file_path, orders[0].config_file_path) + self.assertEqual(event.order_id, orders[0].id) + self.assertEqual(1640001112223, orders[0].creation_timestamp) + self.assertEqual(1, len(order_status)) + self.assertEqual(MarketEvent.SellOrderCreated.name, order_status[0].status) + self.assertEqual(0, len(trade_fills)) + + def test_create_order_and_process_fill(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + create_event = BuyOrderCreatedEvent( + timestamp=1642010000, + type=OrderType.LIMIT, + trading_pair=self.trading_pair, + amount=Decimal(1), + price=Decimal(1000), + order_id="OID1-1642010000000000", + creation_timestamp=1640001112.223, + exchange_order_id="EOID1", + ) + + recorder._did_create_order(MarketEvent.BuyOrderCreated.value, self, create_event) + + fill_event = OrderFilledEvent( + timestamp=1642020000, + order_id=create_event.order_id, + trading_pair=create_event.trading_pair, + trade_type=TradeType.BUY, + order_type=create_event.type, + price=Decimal(1010), + amount=create_event.amount, + trade_fee=AddedToCostTradeFee(), + exchange_trade_id="TradeId1" + ) + + recorder._did_fill_order(MarketEvent.OrderFilled.value, self, fill_event) + + with self.manager.get_new_session() as session: + query = session.query(Order) + orders = query.all() + order = orders[0] + order_status = order.status + trade_fills = order.trade_fills + + self.assertEqual(1, len(orders)) + self.assertEqual(self.config_file_path, orders[0].config_file_path) + self.assertEqual(create_event.order_id, orders[0].id) + self.assertEqual(2, len(order_status)) + self.assertEqual(MarketEvent.BuyOrderCreated.name, order_status[0].status) + self.assertEqual(MarketEvent.OrderFilled.name, order_status[1].status) + self.assertEqual(1, len(trade_fills)) + self.assertEqual(self.config_file_path, trade_fills[0].config_file_path) + self.assertEqual(fill_event.order_id, trade_fills[0].order_id) + + def test_trade_fee_in_quote_not_available(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + create_event = BuyOrderCreatedEvent( + timestamp=1642010000, + type=OrderType.LIMIT, + trading_pair=self.trading_pair, + amount=Decimal(1), + price=Decimal(1000), + order_id="OID1-1642010000000000", + creation_timestamp=1640001112.223, + exchange_order_id="EOID1", + ) + + recorder._did_create_order(MarketEvent.BuyOrderCreated.value, self, create_event) + + trade_fee = MagicMock() + trade_fee.fee_amount_in_token = MagicMock(side_effect=[Exception("Fee amount in quote not available")]) + trade_fee.to_json = MagicMock(return_value={"test": "test"}) + fill_event = OrderFilledEvent( + timestamp=1642020000, + order_id=create_event.order_id, + trading_pair=create_event.trading_pair, + trade_type=TradeType.BUY, + order_type=create_event.type, + price=Decimal(1010), + amount=create_event.amount, + trade_fee=trade_fee, + exchange_trade_id="TradeId1" + ) + + recorder._did_fill_order(MarketEvent.OrderFilled.value, self, fill_event) + + with self.manager.get_new_session() as session: + query = session.query(Order) + orders = query.all() + order = orders[0] + order_status = order.status + trade_fills = order.trade_fills + + self.assertEqual(1, len(orders)) + self.assertEqual(self.config_file_path, orders[0].config_file_path) + self.assertEqual(create_event.order_id, orders[0].id) + self.assertEqual(2, len(order_status)) + self.assertEqual(MarketEvent.BuyOrderCreated.name, order_status[0].status) + self.assertEqual(MarketEvent.OrderFilled.name, order_status[1].status) + self.assertEqual(1, len(trade_fills)) + self.assertEqual(self.config_file_path, trade_fills[0].config_file_path) + self.assertEqual(fill_event.order_id, trade_fills[0].order_id) + + def test_create_order_and_completed(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + market_data_collection_interval=60, + market_data_collection_depth=20, + ), + ) + + create_event = BuyOrderCreatedEvent( + timestamp=1642010000, + type=OrderType.LIMIT, + trading_pair=self.trading_pair, + amount=Decimal(1), + price=Decimal(1000), + order_id="OID1-1642010000000000", + creation_timestamp=1640001112.223, + exchange_order_id="EOID1", + ) + + recorder._did_create_order(MarketEvent.BuyOrderCreated.value, self, create_event) + + complete_event = BuyOrderCompletedEvent( + timestamp=1642020000, + order_id=create_event.order_id, + base_asset=self.base, + quote_asset=self.quote, + base_asset_amount=create_event.amount, + quote_asset_amount=create_event.amount * create_event.price, + order_type=create_event.type) + + recorder._did_complete_order(MarketEvent.BuyOrderCompleted.value, self, complete_event) + + with self.manager.get_new_session() as session: + query = session.query(Order) + orders = query.all() + order = orders[0] + order_status = order.status + trade_fills = order.trade_fills + + self.assertEqual(1, len(orders)) + self.assertEqual(self.config_file_path, orders[0].config_file_path) + self.assertEqual(create_event.order_id, orders[0].id) + self.assertEqual(2, len(order_status)) + self.assertEqual(MarketEvent.BuyOrderCreated.name, order_status[0].status) + self.assertEqual(MarketEvent.BuyOrderCompleted.name, order_status[1].status) + self.assertEqual(0, len(trade_fills)) + + @patch("hummingbot.connector.markets_recorder.MarketsRecorder._sleep") + def test_market_data_collection_enabled(self, sleep_mock): + sleep_mock.side_effect = [0.1, asyncio.CancelledError] + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=True, + market_data_collection_interval=1, + market_data_collection_depth=20, + ), + ) + with patch.object(self, "get_price_by_type") as get_price_by_type: + # Set the side_effect function to determine return values + def side_effect(trading_pair, price_type): + if price_type == PriceType.MidPrice: + return Decimal("100") + elif price_type == PriceType.BestBid: + return Decimal("99") + elif price_type == PriceType.BestAsk: + return Decimal("101") + + # Assign the side_effect function to the mock method + get_price_by_type.side_effect = side_effect + with patch.object(self, "get_order_book") as get_order_book: + order_book = OrderBook(dex=False) + bids_array = np.array([[1, 1, 1], [2, 1, 2], [3, 1, 3]], dtype=np.float64) + asks_array = np.array([[4, 1, 1], [5, 1, 2], [6, 1, 3], [7, 1, 4]], dtype=np.float64) + order_book.apply_numpy_snapshot(bids_array, asks_array) + get_order_book.return_value = order_book + with self.assertRaises(asyncio.CancelledError): + self.async_run_with_timeout(recorder._record_market_data()) + with self.manager.get_new_session() as session: + query = session.query(MarketData) + market_data = query.all() + self.assertEqual(market_data[0].best_ask, Decimal("101")) + self.assertEqual(market_data[0].best_bid, Decimal("99")) + self.assertEqual(market_data[0].mid_price, Decimal("100")) + + def test_store_position_executor(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + ), + ) + position_config = PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.SELL, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.LIMIT, + stop_loss_order_type=OrderType.MARKET) + position_executor = PositionExecutor(self.create_mock_strategy(), position_config) + position_executor_json = position_executor.to_json() + position_executor_json["order_level"] = 1 + position_executor_json["controller_name"] = "test_controller" + recorder.store_executor(position_executor_json) + executors_in_db = recorder.get_position_executors() + position_executor_record = executors_in_db[0] + self.assertEqual(position_executor_record.timestamp, position_executor.position_config.timestamp) + + def test_store_position_executor_filtered(self): + recorder = MarketsRecorder( + sql=self.manager, + markets=[self], + config_file_path=self.config_file_path, + strategy_name=self.strategy_name, + market_data_collection=MarketDataCollectionConfigMap( + market_data_collection_enabled=False, + ), + ) + executors_in_db = recorder.get_position_executors(controller_name="test_controller") + self.assertEqual(len(executors_in_db), 0) + + position_config = PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.SELL, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.LIMIT, + stop_loss_order_type=OrderType.MARKET) + position_executor = PositionExecutor(self.create_mock_strategy(), position_config) + position_executor_json = position_executor.to_json() + position_executor_json["order_level"] = 1 + position_executor_json["controller_name"] = "test_controller" + recorder.store_executor(position_executor_json) + executors_in_db = recorder.get_position_executors(controller_name="test_controller") + self.assertEqual(len(executors_in_db), 1) + position_executor_json["controller_name"] = "test_controller_2" + recorder.store_executor(position_executor_json) + executors_in_db = recorder.get_position_executors(controller_name="test_controller") + self.assertEqual(len(executors_in_db), 1) + executors_in_db = recorder.get_position_executors(controller_name="test_controller_2") + self.assertEqual(len(executors_in_db), 1) diff --git a/test/hummingbot/connector/test_parrot.py b/test/hummingbot/connector/test_parrot.py new file mode 100644 index 0000000..0b1a240 --- /dev/null +++ b/test/hummingbot/connector/test_parrot.py @@ -0,0 +1,427 @@ +import asyncio +import json +from asyncio import CancelledError +from copy import copy +from decimal import Decimal +from unittest import TestCase +from unittest.mock import patch + +from aioresponses import aioresponses + +import hummingbot.connector.parrot as parrot + + +class ParrotConnectorUnitTest(TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + + parrot.logger().setLevel(1) + parrot.logger().addHandler(self) + + self.campaigns_get_resp = {"status": "success", "campaigns": [ + {"id": 1, "campaign_name": "zilliqa", "link": "https://zilliqa.com/index.html", "markets": [ + {"market_id": 1, "exchange_name": "binance", "base_asset": "ZIL", "quote_asset": "USDT", + "base_asset_full_name": "zilliqa", "quote_asset_full_name": "tether", "trading_pair": "ZILUSDT", + "return": 1.4600818845692998, "last_snapshot_ts": 1592263560000, + "last_snapshot_volume": 7294.967867187001, "trailing_1h_volume": 525955.48182515, + "hourly_payout_usd": 1.488095238095238, "bots": 10, "last_hour_bots": 14, "filled_24h_volume": 0.0, + "market_24h_usd_volume": 0.0}, + ]}]} + self.expected_campaign_no_markets = parrot.CampaignSummary(market_id=1, trading_pair='ZIL-USDT', + exchange_name='binance', spread_max=Decimal('0'), + payout_asset='', liquidity=Decimal('0'), + liquidity_usd=Decimal('0'), active_bots=0, + reward_per_wk=Decimal('0'), apy=Decimal('0')) + self.expected_campaign_w_markets = parrot.CampaignSummary(market_id=1, trading_pair='ZIL-USDT', + exchange_name='binance', spread_max=Decimal('0.02'), + payout_asset='ZIL', liquidity=Decimal('0'), + liquidity_usd=Decimal('0'), active_bots=15, + reward_per_wk=Decimal('205930.0'), apy=Decimal('0')) + self.expected_campaign_32_markets = { + 32: parrot.CampaignSummary(market_id=32, trading_pair='ALGO-USDT', exchange_name='binance', + spread_max=Decimal('0.015'), payout_asset='ALGO', liquidity=Decimal('0'), + liquidity_usd=Decimal('0'), active_bots=18, reward_per_wk=Decimal('341.0'), + apy=Decimal('0'))} + self.markets_get_resp = {"status": "success", "markets": [ + {"base_asset": "ZIL", + "base_asset_full_name": "zilliqa", "exchange_name": "binance", + "market_id": 1, "quote_asset": "USDT", "quote_asset_full_name": "tether", "trading_pair": "ZIL/USDT", + "base_asset_address": "", "quote_asset_address": "", + "active_bounty_periods": [ + {"bounty_period_id": 2396, "bounty_campaign_id": 38, "bounty_campaign_name": "dafi", + "bounty_campaign_link": "https://zilliqa.com/index.html", "start_timestamp": 1657584000000, + "end_timestamp": 1658188800000, "budget": {"bid": 102965.0, "ask": 102965.0}, "spread_max": 2.0, + "payout_asset": "ZIL"}], "return": 8.694721275945772, "last_snapshot_ts": 1657812180000, + "last_snapshot_volume": 3678.5291375, "trailing_1h_volume": 261185.66037849995, + "hourly_payout_usd": 4.317788244047619, "bots": 15, "last_hour_bots": 18, "filled_24h_volume": 6816.23476, + "weekly_reward_in_usd": 751.8118232323908, "weekly_reward": {"ZIL": 205735.9191468253}, + "has_user_bots": 'false', "market_24h_usd_volume": 0.0}]} + + self.get_fail = {"status": "error", "message": "ERROR message"} + + self.snapshot_get_resp = {"status": "success", "market_snapshot": {"market_id": 32, "timestamp": 1657747860000, + "last_snapshot_ts": 1657747864000, + "annualized_return": 0.4026136989303596, + "payout_summary": {"open_volume": { + "reward": { + "ask": {"ALGO": 0.01691468253968254}, + "bid": { + "ALGO": 0.01691468253968254}}, + "reward_profoma": { + "ask": {"ALGO": 0.01691468253968254}, + "bid": { + "ALGO": 0.01691468253968254}}, + "payout_asset_usd_rate": { + "ALGO": 0.30415}, + "total_hourly_payout_usd": 0.6173520833333332}, + "filled_volume": {}}, + "summary_stats": {"open_volume": { + "ask": {"accumulated_roll_over": 0}, + "bid": {"accumulated_roll_over": 0}, + "bots": 17, "oov_ask": 16274, + "oov_bid": 31099, "bots_ask": 14, + "bots_bid": 9, + "spread_ask": 0.29605111465204803, + "spread_bid": 0.33684674006707405, + "last_hour_bots": 19, + "oov_eligible_ask": 16156, + "oov_eligible_bid": 26059, + "last_hour_bots_ask": 16, + "last_hour_bots_bid": 15, + "base_asset_usd_rate": 0.30415, + "quote_asset_usd_rate": 1}, + "filled_volume": {}}}, + "user_snapshot": {"timestamp": 1657747860000, "is_default": True, + "rewards_summary": {"ask": {}, "bid": {}}, + "summary_stats": {"oov_ask": 0, "oov_bid": 0, "reward_pct": 0, + "spread_ask": -1, "spread_bid": -1, + "reward": {"ask": {}, "bid": {}}, + "reward_profoma": {"ask": {}, "bid": {}}, + "open_volume_pct": 0, "oov_eligible_ask": 0, + "oov_eligible_bid": 0}}, + "market_mid_price": 0.30415} + self.expected_snapshots_bad_timestamp = {"status": "error", + "message": "Data not available for timestamp 1657747860000."} + self.expected_snapshots_error = {"status": "error", "message": "404: Not Found"} + + self.expected_summary = { + 'ALGO-USDT': parrot.CampaignSummary(market_id=32, trading_pair='ALGO-USDT', exchange_name='binance', + spread_max=Decimal('0.015'), payout_asset='ALGO', + liquidity=Decimal('42215'), + liquidity_usd=Decimal('12839.69224999999898390035113'), active_bots=17, + reward_per_wk=Decimal('341.0'), + apy=Decimal('0.40261369893035958700266974119585938751697540283203125'))} + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + @aioresponses() + def test_get_active_campaigns_empty_markets(self, mocked_http): + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}campaigns", body=json.dumps(self.campaigns_get_resp)) + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps("")) + + campaigns = self.ev_loop.run_until_complete(parrot.get_active_campaigns("binance")) + + self.assertEqual({1: self.expected_campaign_no_markets}, campaigns) + self.assertTrue(self._is_logged("WARNING", + "Could not get active markets from Hummingbot API" + " (returned response '').")) + + @aioresponses() + def test_get_active_campaigns_failed_markets(self, mocked_http): + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}campaigns", body=json.dumps(self.campaigns_get_resp)) + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.get_fail)) + + campaigns = self.ev_loop.run_until_complete(parrot.get_active_campaigns("binance")) + + self.assertEqual({1: self.expected_campaign_no_markets}, campaigns) + self.assertTrue(self._is_logged("WARNING", + "Could not get active markets from Hummingbot API" + f" (returned response '{self.get_fail}').")) + + @aioresponses() + def test_get_active_campaigns_markets_wrong_id(self, mocked_http): + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}campaigns", body=json.dumps(self.campaigns_get_resp)) + market_wrong_id = self.markets_get_resp + market_wrong_id["markets"][0]["market_id"] = 10 + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(market_wrong_id)) + + campaigns = self.ev_loop.run_until_complete(parrot.get_active_campaigns("binance")) + + self.assertEqual({1: self.expected_campaign_no_markets}, campaigns) + self.assertFalse(self._is_logged("WARNING", + "Could not get active markets from Hummingbot API" + f" (returned response '{self.get_fail}').")) + + @aioresponses() + def test_get_active_campaigns_markets(self, mocked_http): + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}campaigns", body=json.dumps(self.campaigns_get_resp)) + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.markets_get_resp)) + + campaigns = self.ev_loop.run_until_complete(parrot.get_active_campaigns("binance", ["ZIL-USDT"])) + self.assertEqual({1: self.expected_campaign_w_markets}, campaigns) + + # def test_get_active_campaigns_markets_live(self): + # campaigns = self.ev_loop.run_until_complete(parrot.get_active_campaigns("binance", ["ALGO-USDT"])) + # self.assertEqual({1: self.expected_campaign_w_markets}, campaigns) + + @aioresponses() + def test_get_active_markets(self, mocked_http): + mocked_http.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.markets_get_resp)) + campaigns = {1: copy(self.expected_campaign_no_markets)} + campaigns = self.ev_loop.run_until_complete(parrot.get_active_markets(campaigns)) + self.assertNotEqual({1: self.expected_campaign_no_markets}, campaigns) + self.assertEqual({1: self.expected_campaign_w_markets}, campaigns) + + @aioresponses() + def test_get_market_snapshots(self, mocked_http): + market_id = 32 + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}charts/market_band?chart_interval=1&market_id={market_id}", + body=json.dumps({"status": "success", "data": [ + {"timestamp": 1662589860000, "price": 0.30005, "ask": 0.301145, "bid": 0.298362, + "spread_ask": 0.3647958323482506, "spread_bid": 0.5624147716913023, "liquidity": 32932.5255}]})) + snapshot = self.ev_loop.run_until_complete(parrot.get_market_snapshots(market_id)) + self.assertEqual({'data': [{'ask': 0.301145, + 'bid': 0.298362, + 'liquidity': 32932.5255, + 'price': 0.30005, + 'spread_ask': 0.3647958323482506, + 'spread_bid': 0.5624147716913023, + 'timestamp': 1662589860000}], + 'status': 'success'}, snapshot) + + @aioresponses() + def test_get_market_snapshots_returns_none(self, mocked_http): + market_id = 32 + # 'status' == "error" + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}charts/market_band?chart_interval=1&market_id={market_id}", + body=json.dumps({"status": "error", "data": []})) + snapshot = self.ev_loop.run_until_complete(parrot.get_market_snapshots(market_id)) + self.assertEqual(None, snapshot) + + # No 'status' field + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}charts/market_band?chart_interval=1&market_id={market_id}", + body=json.dumps({"data": []})) + snapshot = self.ev_loop.run_until_complete(parrot.get_market_snapshots(market_id)) + self.assertEqual(None, snapshot) + + # JSON resp is None + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}charts/market_band?chart_interval=1&market_id={market_id}", + body=json.dumps(None)) + snapshot = self.ev_loop.run_until_complete(parrot.get_market_snapshots(market_id)) + self.assertEqual(None, snapshot) + + @aioresponses() + def test_get_market_last_snapshot(self, mocked_http): + market_id = 32 + timestamp = 1662589860000 + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}user/single_snapshot?aggregate_period=1m&market_id={market_id}×tamp={timestamp}", + body=json.dumps(self.snapshot_get_resp)) + with patch("hummingbot.connector.parrot.get_market_snapshots") as mocked_snapshots: + mocked_snapshots.return_value = {"status": "success", "data": [{"timestamp": timestamp}]} + snapshot = self.ev_loop.run_until_complete(parrot.get_market_last_snapshot(market_id)) + self.assertEqual(self.snapshot_get_resp, snapshot) + + # This test is likely to fail with time as the data will change + # def test_get_market_last_snapshot_live(self): + # market_id = 32 + # snapshot = self.ev_loop.run_until_complete(parrot.get_market_last_snapshot(market_id)) + # self.assertEqual(self.snapshot_get_resp, snapshot) + + @aioresponses() + def test_get_campaign_summary(self, mocked_http): + timestamp = 16577478600000 + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}user/single_snapshot?aggregate_period=1m&market_id={32}×tamp={timestamp}", + body=json.dumps(self.snapshot_get_resp)) + with patch("hummingbot.connector.parrot.get_market_snapshots") as mocked_snapshots: + mocked_snapshots.return_value = {"status": "success", "data": [{"timestamp": timestamp}]} + with patch('hummingbot.connector.parrot.get_active_campaigns') as mocked_ac: + mocked_ac.return_value = self.expected_campaign_32_markets + summary = self.ev_loop.run_until_complete(parrot.get_campaign_summary("binance", ["ALGO-USDT"])) + self.assertEqual(self.expected_summary, summary) + + @aioresponses() + def test_get_campaign_summary_http_error(self, mocked_http): + timestamp = 16577478600000 + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}user/single_snapshot?market_id={32}×tamp={timestamp}&aggregate_period=1m", + body=json.dumps(self.snapshot_get_resp)) + with patch('hummingbot.connector.parrot.get_active_campaigns') as mocked_ac: + with patch("hummingbot.connector.parrot.get_market_snapshots") as mocked_snapshots: + mocked_snapshots.return_value = {"status": "success", "data": [{"timestamp": timestamp}]} + with patch('hummingbot.connector.parrot.get_market_snapshots') as mocked_ss: + mocked_ac.return_value = self.expected_campaign_32_markets + mocked_ss.return_value = self.expected_snapshots_error + summary = self.ev_loop.run_until_complete(parrot.get_campaign_summary("binance", ["ALGO-USDT"])) + # No snapshot, just dict re-arrangement + self.assertEqual({}, summary) + self.assertTrue(self._is_logged("ERROR", "Unexpected error while requesting data from Hummingbot API.")) + + @aioresponses() + def test_get_campaign_summary_exception(self, mocked_http): + mocked_http.get( + f"{parrot.PARROT_MINER_BASE_URL}user/single_snapshot?market_id={32}×tamp={-1}&aggregate_period=1m", + body=json.dumps(self.snapshot_get_resp)) + with patch('hummingbot.connector.parrot.get_active_campaigns') as mocked_ac: + with patch('hummingbot.connector.parrot.get_market_snapshots') as mocked_ss: + with self.assertRaises(CancelledError): + mocked_ac.side_effect = asyncio.CancelledError + mocked_ss.return_value = self.expected_campaign_32_markets + self.ev_loop.run_until_complete(parrot.get_campaign_summary("binance", ["ALGO-USDT"])) + self.assertTrue( + self._is_logged("ERROR", "Unexpected error while requesting data from Hummingbot API.")) + + with self.assertRaises(CancelledError): + mocked_ac.return_value = self.expected_campaign_32_markets + mocked_ss.side_effect = asyncio.CancelledError + self.ev_loop.run_until_complete(parrot.get_campaign_summary("binance", ["ALGO-USDT"])) + self.assertTrue( + self._is_logged("ERROR", "Unexpected error while requesting data from Hummingbot API.")) + + @aioresponses() + def test_retrieve_active_campaigns_error_is_logged(self, mock_api): + resp = {"status": "error", "message": "Rate limit exceeded: 10 per 1 minute"} + mock_api.get(f"{parrot.PARROT_MINER_BASE_URL}campaigns", body=json.dumps(resp)) + mock_api.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(resp)) + + campaigns = asyncio.get_event_loop().run_until_complete( + parrot.get_active_campaigns( + exchange="binance", + trading_pairs=["COINALPHA-HBOT"])) + + self.assertEqual(0, len(campaigns)) + self.assertTrue(self._is_logged("WARNING", + "Could not get active campaigns from Hummingbot API" + f" (returned response '{resp}').")) + + @aioresponses() + def test_active_campaigns_are_filtered_by_token_pair(self, mock_api): + url = f"{parrot.PARROT_MINER_BASE_URL}campaigns" + resp = { + "status": "success", + "campaigns": [{ + "id": 26, + "campaign_name": "xym", + "link": "https://symbolplatform.com/", + "markets": [{ + "market_id": 62, + "trading_pair": "XYM-BTC", + "exchange_name": "kucoin", + "base_asset": "XYM", + "base_asset_full_name": "symbol", + "quote_asset": "BTC", + "quote_asset_full_name": "bitcoin"}]}, + { + "id": 27, + "campaign_name": "test", + "link": "https://symbolplatform.com/", + "markets": [{ + "market_id": 63, + "trading_pair": "COINALPHA-HBOT", + "exchange_name": "kucoin", + "base_asset": "COINALPHA", + "base_asset_full_name": "coinalpha", + "quote_asset": "HBOT", + "quote_asset_full_name": "hbot"}]}]} + + mock_api.get(url, body=json.dumps(resp)) + mock_api.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.markets_get_resp)) + + campaigns = asyncio.get_event_loop().run_until_complete( + parrot.get_active_campaigns( + exchange="kucoin", + trading_pairs=["COINALPHA-HBOT"])) + + self.assertEqual(1, len(campaigns)) + campaign_summary: parrot.CampaignSummary = campaigns[63] + self.assertEqual("COINALPHA-HBOT", campaign_summary.trading_pair) + self.assertEqual("kucoin", campaign_summary.exchange_name) + self.assertEqual(Decimal("0"), campaign_summary.spread_max) + + @aioresponses() + def test_active_campaigns_are_filtered_by_exchange_name(self, mock_api): + url = f"{parrot.PARROT_MINER_BASE_URL}campaigns" + resp = { + "status": "success", + "campaigns": [{ + "id": 26, + "campaign_name": "xym", + "link": "https://symbolplatform.com/", + "markets": [{ + "market_id": 62, + "trading_pair": "XYM-BTC", + "exchange_name": "ascendex", + "base_asset": "XYM", + "base_asset_full_name": "symbol", + "quote_asset": "BTC", + "quote_asset_full_name": "bitcoin"}], + "bounty_periods": [{ + "id": 823, + "start_datetime": "2021-10-05T00:00:00", + "end_datetime": "2021-10-12T00:00:00", + "payout_parameters": [{ + "id": 2212, + "market_id": 62, + "bid_budget": 1371.5, + "ask_budget": 1371.5, + "exponential_decay_function_factor": 8.0, + "spread_max": 1.5, + "payout_asset": "XYM"}]}]}]} + + mock_api.get(url, body=json.dumps(resp)) + mock_api.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.markets_get_resp)) + + campaigns = asyncio.get_event_loop().run_until_complete( + parrot.get_active_campaigns( + exchange="test_exchange", + trading_pairs=["XYM-BTC"])) + + self.assertEqual(0, len(campaigns)) + + mock_api.get(url, body=json.dumps(resp)) + mock_api.get(f"{parrot.PARROT_MINER_BASE_URL}markets", body=json.dumps(self.markets_get_resp)) + + campaigns = asyncio.get_event_loop().run_until_complete( + parrot.get_active_campaigns( + exchange="ascend_ex", + trading_pairs=["XYM-BTC"])) + self.assertEqual(1, len(campaigns)) + + @aioresponses() + def test_get_campaign_summary_logs_error_if_exception_happens(self, mock_api): + url = f"{parrot.PARROT_MINER_BASE_URL}campaigns" + + mock_api.get(url, exception=Exception("Test error description")) + + campaigns = asyncio.get_event_loop().run_until_complete( + parrot.get_campaign_summary( + exchange="test_exchange", + trading_pairs=["XYM-BTC"])) + + self.assertEqual(0, len(campaigns)) + self.assertTrue(self._is_logged("ERROR", + "Unexpected error while requesting data from Hummingbot API.")) + + def test_are_same_entity(self): + self.assertTrue(parrot.are_same_entity("ascend_ex", "ascendex")) + self.assertTrue(parrot.are_same_entity("ascend_ex", "ascend_ex")) + self.assertTrue(parrot.are_same_entity("gate_io", "gateio")) + self.assertFalse(parrot.are_same_entity("gate_io", "gateios")) diff --git a/test/hummingbot/connector/test_perpetual_trading.py b/test/hummingbot/connector/test_perpetual_trading.py new file mode 100644 index 0000000..93c11d5 --- /dev/null +++ b/test/hummingbot/connector/test_perpetual_trading.py @@ -0,0 +1,163 @@ +import asyncio +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import MagicMock + +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.core.data_type.common import PositionMode, PositionSide +from hummingbot.core.data_type.funding_info import FundingInfo, FundingInfoUpdate + + +class PerpetualTest(unittest.TestCase): + # logging.Level required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.perpetual_trading = PerpetualTrading([self.trading_pair]) + self.perpetual_trading.logger().setLevel(1) + self.perpetual_trading.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + async def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_init(self): + self.assertEqual(len(self.perpetual_trading.account_positions), 0) + self.assertEqual(self.perpetual_trading.position_mode, PositionMode.ONEWAY) + self.assertEqual(self.perpetual_trading.funding_payment_span, [0, 0]) + + def test_account_positions(self): + """ + Test getting account positions by manually adding a position to the class member + """ + a_pos: Position = Position( + "market1", PositionSide.LONG, Decimal("0"), Decimal("100"), Decimal("1"), Decimal("5") + ) + self.perpetual_trading._account_positions["market1"] = a_pos + self.assertEqual(len(self.perpetual_trading.account_positions), 1) + self.assertEqual(self.perpetual_trading.account_positions["market1"], a_pos) + self.assertEqual(self.perpetual_trading.get_position("market1"), a_pos) + self.assertEqual(self.perpetual_trading.get_position("market2"), None) + + def test_position_key(self): + self.perpetual_trading.set_position_mode(PositionMode.ONEWAY) + self.assertEqual(self.perpetual_trading.position_key("market1"), "market1") + self.assertEqual(self.perpetual_trading.position_key("market1", PositionSide.LONG), "market1") + self.perpetual_trading.set_position_mode(PositionMode.HEDGE) + self.assertEqual(self.perpetual_trading.position_key("market1", PositionSide.LONG), "market1LONG") + self.assertEqual(self.perpetual_trading.position_key("market1", PositionSide.SHORT), "market1SHORT") + + def test_position_mode(self): + self.assertEqual(self.perpetual_trading.position_mode, PositionMode.ONEWAY) + self.perpetual_trading.set_position_mode(PositionMode.HEDGE) + self.assertEqual(self.perpetual_trading.position_mode, PositionMode.HEDGE) + + def test_leverage(self): + self.perpetual_trading.set_leverage("pair1", 2) + self.perpetual_trading.set_leverage("pair2", 3) + self.assertEqual(self.perpetual_trading.get_leverage("pair1"), 2) + self.assertEqual(self.perpetual_trading.get_leverage("pair2"), 3) + + def test_funding_info(self): + """ + Test getting funding infos by manually adding a funding info to the class member + """ + fInfo: FundingInfo = FundingInfo("pair1", Decimal(1), Decimal(2), 1000, Decimal(0.1)) + self.perpetual_trading._funding_info["pair1"] = fInfo + self.assertEqual(self.perpetual_trading.get_funding_info("pair1"), fInfo) + + def test_funding_info_initialization(self): + self.assertFalse(self.perpetual_trading.is_funding_info_initialized()) + + funding_info = FundingInfo( + self.trading_pair, + index_price=Decimal("1"), + mark_price=Decimal("2"), + next_funding_utc_timestamp=3, + rate=Decimal("4"), + ) + self.perpetual_trading.initialize_funding_info(funding_info) + + self.assertTrue(self.perpetual_trading.is_funding_info_initialized()) + self.assertEqual(1, len(self.perpetual_trading.funding_info)) + + def test_updating_funding_info_logs_exception(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = [ + self._create_exception_and_unlock_test_with_event( + RuntimeError("Some error") + ), + asyncio.CancelledError(), + ] + self.perpetual_trading._funding_info_stream = mock_queue + self.perpetual_trading.start() + self.listening_task = self.perpetual_trading._funding_info_updater_task + + self.async_run_with_timeout(self.resume_test_event.wait()) + self.perpetual_trading.stop() + + self.assertTrue(self._is_logged("ERROR", "Unexpected error updating funding info.")) + + def test_updating_funding_info_success(self): + self.perpetual_trading.start() + + funding_info = FundingInfo( + self.trading_pair, + index_price=Decimal("1"), + mark_price=Decimal("2"), + next_funding_utc_timestamp=3, + rate=Decimal("4"), + ) + self.perpetual_trading.initialize_funding_info(funding_info) + + self.assertEqual(Decimal("1"), self.perpetual_trading.funding_info[self.trading_pair].index_price) + + funding_info_update = FundingInfoUpdate(self.trading_pair, index_price=Decimal("10")) + + async def return_update(): + return funding_info_update + + mock_queue = MagicMock() + mock_queue.get.side_effect = [ + return_update(), + asyncio.CancelledError(), + ] + self.perpetual_trading._funding_info_stream = mock_queue + self.listening_task = self.perpetual_trading._funding_info_updater_task + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertEqual(Decimal("10"), self.perpetual_trading.funding_info[self.trading_pair].index_price) diff --git a/test/hummingbot/connector/test_time_synchronizer.py b/test/hummingbot/connector/test_time_synchronizer.py new file mode 100644 index 0000000..d3e08af --- /dev/null +++ b/test/hummingbot/connector/test_time_synchronizer.py @@ -0,0 +1,77 @@ +import asyncio +from typing import Awaitable +from unittest import TestCase +from unittest.mock import patch + +import numpy.ma + +from hummingbot.connector.time_synchronizer import TimeSynchronizer + + +class TimeSynchronizerTests(TestCase): + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @staticmethod + async def configurable_timestamp_provider(timestamp: float) -> float: + return timestamp + + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._time") + def test_time_with_registered_offsets_returns_local_time(self, time_mock, seconds_counter_mock): + now = 1640000000.0 + time_mock.side_effect = [now] + seconds_counter_mock.side_effect = [2, 3] + time_provider = TimeSynchronizer() + + synchronized_time = time_provider.time() + self.assertEqual(now + (2 - 3), synchronized_time) + + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._time") + def test_time_with_one_registered_offset(self, _, seconds_counter_mock): + now = 1640000020.0 + seconds_counter_mock.side_effect = [10, 30, 31] + + time_provider = TimeSynchronizer() + self.async_run_with_timeout( + time_provider.update_server_time_offset_with_time_provider( + time_provider=self.configurable_timestamp_provider(now * 1e3) + )) + synchronized_time = time_provider.time() + seconds_difference_getting_time = 30 - 10 + seconds_difference_when_calculating_current_time = 31 + self.assertEqual( + now - seconds_difference_getting_time + seconds_difference_when_calculating_current_time, + synchronized_time) + + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._current_seconds_counter") + @patch("hummingbot.connector.time_synchronizer.TimeSynchronizer._time") + def test_time_calculated_with_mean_of_all_offsets(self, _, seconds_counter_mock): + first_time = 1640000003.0 + second_time = 16400000010.0 + third_time = 1640000016.0 + seconds_counter_mock.side_effect = [2, 4, 6, 10, 11, 13, 25] + + time_provider = TimeSynchronizer() + for time in [first_time, second_time, third_time]: + self.async_run_with_timeout( + time_provider.update_server_time_offset_with_time_provider( + time_provider=self.configurable_timestamp_provider(time * 1e3) + )) + synchronized_time = time_provider.time() + first_expected_offset = first_time - (4 + 2) / 2 + second_expected_offset = second_time - (10 + 6) / 2 + third_expected_offset = third_time - (13 + 11) / 2 + expected_offsets = [first_expected_offset, second_expected_offset, third_expected_offset] + seconds_difference_when_calculating_current_time = 25 + + calculated_median = numpy.median(expected_offsets) + calculated_weighted_average = numpy.average( + expected_offsets, + weights=range(1, len(expected_offsets) * 2 + 1, 2)) + calculated_offset = numpy.mean([calculated_median, calculated_weighted_average]) + + self.assertEqual(calculated_offset + seconds_difference_when_calculating_current_time, synchronized_time) diff --git a/test/hummingbot/connector/test_utils.py b/test/hummingbot/connector/test_utils.py new file mode 100644 index 0000000..b843862 --- /dev/null +++ b/test/hummingbot/connector/test_utils.py @@ -0,0 +1,102 @@ +import importlib +import os +import platform +import unittest +from hashlib import md5 +from os import DirEntry, scandir +from os.path import exists, join +from typing import cast +from unittest.mock import patch + +from pydantic import SecretStr + +from hummingbot import root_path +from hummingbot.client.config.config_data_types import BaseConnectorConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.settings import CONNECTOR_SUBMODULES_THAT_ARE_NOT_CEX_TYPES +from hummingbot.connector.utils import get_new_client_order_id + + +class UtilsTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.base = "HBOT" + cls.quote = "COINALPHA" + cls.trading_pair = f"{cls.base}-{cls.quote}" + + def test_get_new_client_order_id(self): + host_prefix = "hbot" + + id0 = get_new_client_order_id(is_buy=True, trading_pair=self.trading_pair) + id1 = get_new_client_order_id(is_buy=True, trading_pair=self.trading_pair, hbot_order_id_prefix=host_prefix) + + self.assertFalse(id0.startswith(host_prefix)) + self.assertTrue(id1.startswith(host_prefix)) + + id2 = get_new_client_order_id(is_buy=True, trading_pair=self.trading_pair, max_id_len=len(id0) - 2) + + self.assertEqual(len(id0) - 2, len(id2)) + + @patch("hummingbot.connector.utils.get_tracking_nonce") + def test_get_new_client_order_id_with_max_len_less_than_required_to_include_time_hashes_it(self, nonce_mock): + nonce_mock.return_value = 1640001112223334 + host_prefix = "long-hbot-prefix" + + full_length_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=host_prefix) + shortened_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=host_prefix, + max_id_len=len(host_prefix) + 5 + 13 + 5) + extra_reduced_id = get_new_client_order_id( + is_buy=True, + trading_pair=self.trading_pair, + hbot_order_id_prefix=host_prefix, + max_id_len=len(host_prefix) + 5 + 12) + + expected_id_prefix = f"{host_prefix}B{self.base[0]}{self.base[-1]}{self.quote[0]}{self.quote[-1]}" + expected_time_text = hex(nonce_mock.return_value)[2:] + expected_client_instance_id = md5(f"{platform.uname()}_pid:{os.getpid()}_ppid:{os.getppid()}".encode("utf-8")).hexdigest() + + expected_full_length_id = f"{expected_id_prefix}{expected_time_text}{expected_client_instance_id}" + expected_shortened_id = f"{expected_id_prefix}{expected_time_text}{expected_client_instance_id[:5]}" + + extra_reduced_id_suffix = md5(f"{expected_time_text}{expected_client_instance_id}".encode()).hexdigest() + expected_extra_reduced_id = f"{expected_id_prefix}{extra_reduced_id_suffix[:12]}" + + self.assertEqual(expected_full_length_id, full_length_id) + self.assertEqual(expected_shortened_id, shortened_id) + self.assertEqual(expected_extra_reduced_id, extra_reduced_id) + + def test_connector_config_maps(self): + connector_exceptions = ["mock_paper_exchange", "mock_pure_python_paper_exchange", "paper_trade", "amm", "clob"] + + type_dirs = [ + cast(DirEntry, f) for f in + scandir(f"{root_path() / 'hummingbot' / 'connector'}") + if f.is_dir() and f.name not in CONNECTOR_SUBMODULES_THAT_ARE_NOT_CEX_TYPES + ] + for type_dir in type_dirs: + connector_dirs = [ + cast(DirEntry, f) for f in scandir(type_dir.path) + if f.is_dir() and exists(join(f.path, "__init__.py")) + ] + for connector_dir in connector_dirs: + if connector_dir.name.startswith("_") or connector_dir.name in connector_exceptions: + continue + util_module_path: str = ( + f"hummingbot.connector.{type_dir.name}.{connector_dir.name}.{connector_dir.name}_utils" + ) + util_module = importlib.import_module(util_module_path) + connector_config = getattr(util_module, "KEYS") + + self.assertIsInstance(connector_config, BaseConnectorConfigMap) + for el in ClientConfigAdapter(connector_config).traverse(): + print(el) + if el.attr == "connector": + self.assertEqual(el.value, connector_dir.name) + elif el.client_field_data.is_secure: + self.assertEqual(el.type_, SecretStr) diff --git a/test/hummingbot/connector/utilities/__init__.py b/test/hummingbot/connector/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/utilities/oms_connector/__init__.py b/test/hummingbot/connector/utilities/oms_connector/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_order_book_data_source.py b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_order_book_data_source.py new file mode 100644 index 0000000..f69c73f --- /dev/null +++ b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_order_book_data_source.py @@ -0,0 +1,429 @@ +import asyncio +import json +import re +import unittest +from typing import Any, Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses +from bidict import bidict + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_api_order_book_data_source import ( + OMSConnectorAPIOrderBookDataSource, +) +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_exchange import OMSExchange +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import ( + OMSConnectorURLCreatorBase, + build_api_factory, +) +from hummingbot.core.data_type.order_book import OrderBook, OrderBookMessage +from hummingbot.core.data_type.order_book_message import OrderBookMessageType + + +class TestURCreator(OMSConnectorURLCreatorBase): + def get_rest_url(self, path_url: str) -> str: + return "https://some.url" + + def get_ws_url(self) -> str: + return "wss://some.url" + + +class TestExchange(OMSExchange): + @property + def name(self) -> str: + return "test_exchange" + + @property + def oms_id(self) -> int: + return 1 + + @property + def domain(self): + return "" + + +class OMSConnectorAPIOrderBookDataSourceTest(unittest.TestCase): + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "someApiKey" + cls.secret = "someSecret" + cls.user_id = 20 + cls.user_name = "someUserName" + cls.oms_id = 1 + cls.account_id = 3 + cls.pair_id = 1 + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.url_provider = TestURCreator() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.listening_task = None + self.mocking_assistant = NetworkMockingAssistant() + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = TestExchange( + client_config_map, self.api_key, self.secret, self.user_id, trading_pairs=[self.trading_pair] + ) + self.auth = OMSConnectorAuth(api_key=self.api_key, secret_key=self.secret, user_id=self.user_id) + self.initialize_auth() + api_factory = build_api_factory(auth=self.auth) + self.data_source = OMSConnectorAPIOrderBookDataSource( + trading_pairs=[self.trading_pair], + connector=connector, + api_factory=api_factory, + url_provider=self.url_provider, + oms_id=self.oms_id, + ) + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + connector._set_trading_pair_symbol_map(bidict({str(self.pair_id): self.trading_pair})) + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level + and record.getMessage() == message + for record in self.log_records + ) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def initialize_auth(self): + auth_resp = self.get_auth_success_response() + self.auth.update_with_rest_response(auth_resp) + + def get_auth_success_response(self) -> Dict[str, Any]: + auth_resp = { + "Authenticated": True, + "SessionToken": "0e8bbcbc-6ada-482a-a9b4-5d9218ada3f9", + "User": { + "UserId": self.user_id, + "UserName": self.user_name, + "Email": "", + "EmailVerified": True, + "AccountId": self.account_id, + "OMSId": self.oms_id, + "Use2FA": False, + }, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None, + } + return auth_resp + + @aioresponses() + def test_get_new_order_book_success(self, mock_api): + url = self.url_provider.get_rest_url(CONSTANTS.REST_GET_L2_SNAPSHOT_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + update_ts_ms = 21288594 + expected_update_id = update_ts_ms + resp = [ + [21288594, 1, update_ts_ms, 0, 0.0617018, 1, 0.0586575, self.pair_id, 0.087, 0], + [21288594, 1, update_ts_ms, 0, 0.0617018, 1, 0.0598854, self.pair_id, 2.0, 1], + ] + mock_api.get(regex_url, body=json.dumps(resp)) + + order_book: OrderBook = self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + self.assertEqual(expected_update_id, order_book.snapshot_uid) + bids = list(order_book.bid_entries()) + asks = list(order_book.ask_entries()) + self.assertEqual(1, len(bids)) + self.assertEqual(0.0586575, bids[0].price) + self.assertEqual(0.087, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(0.0598854, asks[0].price) + self.assertEqual(2, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @aioresponses() + def test_get_new_order_book_raises_exception(self, mock_api): + url = self.url_provider.get_rest_url(CONSTANTS.REST_GET_L2_SNAPSHOT_ENDPOINT) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + + mock_api.get(regex_url, status=400) + with self.assertRaises(IOError): + self.async_run_with_timeout( + self.data_source.get_new_order_book(self.trading_pair) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + instrument_id = 1 + result_subscribe_trades = { + CONSTANTS.MSG_SEQUENCE_FIELD: 2, + CONSTANTS.MSG_TYPE_FIELD: CONSTANTS.RESP_MSG_TYPE, + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_TRADES_SUB_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: "[]", + } + l2_rows = [ + [21288594, 1, 1654877612463, 0, 0.0617018, 1, 0.0586575, self.pair_id, 0.087, 0], + [21288594, 1, 1654877612463, 0, 0.0617018, 1, 0.0598854, self.pair_id, 2.0, 1], + ] + result_subscribe_diffs = { + CONSTANTS.MSG_SEQUENCE_FIELD: 4, + CONSTANTS.MSG_TYPE_FIELD: CONSTANTS.RESP_MSG_TYPE, + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_L2_SUB_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: json.dumps(l2_rows), + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_diffs) + ) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_subscription_messages)) + req_params = { + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.INSTRUMENT_ID_FIELD: instrument_id, + CONSTANTS.DEPTH_FIELD: CONSTANTS.MAX_L2_SNAPSHOT_DEPTH, + } + expected_diff_subscription = { + CONSTANTS.MSG_SEQUENCE_FIELD: 2, + CONSTANTS.MSG_TYPE_FIELD: CONSTANTS.REQ_MSG_TYPE, + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_L2_SUB_ENDPOINT, + CONSTANTS.MSG_DATA_FIELD: json.dumps(req_params), + } + self.assertEqual(expected_diff_subscription, sent_subscription_messages[0]) + self.assertTrue( + self._is_logged( + "INFO", + "Subscribed to public order book and trade channels..." + ) + ) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self._is_logged( + "ERROR", + "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", + ) + ) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_source._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error occurred subscribing to order book trading and delta streams...") + ) + + def test_listen_for_trades_cancelled_when_listening(self): + mock_queue = MagicMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_trades_logs_exception(self): + incomplete_resp = { + "arg": { + "channel": "trades", + "instId": "BTC-USDT" + }, + "data": [ + { + "instId": "BTC-USDT", + } + ] + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._trade_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_trades(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public trade updates from exchange") + ) + + def test_listen_for_order_book_diffs_cancelled(self): + mock_queue = AsyncMock() + mock_queue.get.side_effect = asyncio.CancelledError() + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + self.async_run_with_timeout(self.listening_task) + + def test_listen_for_order_book_diffs_logs_exception(self): + incomplete_resp = { + "arg": { + "channel": "books", + "instId": self.trading_pair + }, + "action": "update", + } + + mock_queue = AsyncMock() + mock_queue.get.side_effect = [incomplete_resp, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + self.assertTrue( + self._is_logged("ERROR", "Unexpected error when processing public order book updates from exchange") + ) + + def test_listen_for_order_book_diffs_successful(self): + mock_queue = AsyncMock() + ts_ms = 1654877612463 + expected_update_id = ts_ms + diff_event = { + "m": 3, + "i": 0, + "n": "Level2UpdateEvent", + "o": [ + [21288594, 1, ts_ms, 0, 0.0617018, 1, 0.0586575, self.pair_id, 0.087, 0], + [21288594, 1, ts_ms, 0, 0.0617018, 1, 0.0598854, self.pair_id, 2.0, 1], + ] + } + mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] + self.data_source._message_queue[self.data_source._diff_messages_queue_key] = mock_queue + + msg_queue: asyncio.Queue = asyncio.Queue() + + self.listening_task = self.ev_loop.create_task( + self.data_source.listen_for_order_book_diffs(self.ev_loop, msg_queue) + ) + + msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) + + self.assertEqual(OrderBookMessageType.DIFF, msg.type) + self.assertEqual(-1, msg.trade_id) + self.assertEqual(ts_ms * 1e-3, msg.timestamp) + self.assertEqual(expected_update_id, msg.update_id) + + bids = msg.bids + asks = msg.asks + self.assertEqual(1, len(bids)) + self.assertEqual(0.0586575, bids[0].price) + self.assertEqual(0.087, bids[0].amount) + self.assertEqual(expected_update_id, bids[0].update_id) + self.assertEqual(1, len(asks)) + self.assertEqual(0.0598854, asks[0].price) + self.assertEqual(2, asks[0].amount) + self.assertEqual(expected_update_id, asks[0].update_id) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_sends_ping_message_before_ping_interval_finishes(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.receive.side_effect = [asyncio.TimeoutError("Test timeout"), + asyncio.CancelledError] + + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_subscriptions()) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + expected_ping_message = {"n": "Ping", "o": "{}", "m": 0, "i": 4} + self.assertEqual(expected_ping_message, sent_messages[-1]) diff --git a/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_user_stream_data_source.py b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_user_stream_data_source.py new file mode 100644 index 0000000..cbec9f9 --- /dev/null +++ b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_api_user_stream_data_source.py @@ -0,0 +1,261 @@ +import asyncio +import hashlib +import hmac +import json +import unittest +from typing import Any, Awaitable, Dict, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +from aiohttp import WSMessage, WSMsgType + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utilities.oms_connector import oms_connector_constants as CONSTANTS +from hummingbot.connector.utilities.oms_connector.oms_connector_api_user_stream_data_source import ( + OMSConnectorAPIUserStreamDataSource, +) +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import ( + OMSConnectorURLCreatorBase, + build_api_factory, +) + + +class TestURLCreator(OMSConnectorURLCreatorBase): + def get_rest_url(self, path_url: str) -> str: + return "https://some.url" + + def get_ws_url(self) -> str: + return "wss://some.url" + + +class OMSConnectorUserStreamDataSourceTests(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "someApiKey" + cls.secret = "someSecret" + cls.user_id = 20 + cls.time_mock = 1655283229.419752 + cls.nonce = str(int(cls.time_mock * 1e3)) + auth_concat = f"{cls.nonce}{cls.user_id}{cls.api_key}" + cls.signature = hmac.new( + key=cls.secret.encode("utf-8"), + msg=auth_concat.encode("utf-8"), + digestmod=hashlib.sha256, + ).hexdigest() + cls.user_name = "someUserName" + cls.oms_id = 1 + cls.account_id = 3 + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def setUp(self, time_mock: MagicMock) -> None: + super().setUp() + time_mock.return_value = self.time_mock + self.log_records = [] + self.listening_task: Optional[asyncio.Task] = None + self.mocking_assistant = NetworkMockingAssistant() + + self.auth = OMSConnectorAuth(api_key=self.api_key, secret_key=self.secret, user_id=self.user_id) + self.initialize_auth() + api_factory = build_api_factory(auth=self.auth) + url_provider = TestURLCreator() + + self.data_source = OMSConnectorAPIUserStreamDataSource( + api_factory=api_factory, url_provider=url_provider, oms_id=self.oms_id + ) + + self.data_source.logger().setLevel(1) + self.data_source.logger().addHandler(self) + + self.resume_test_event = asyncio.Event() + + def tearDown(self) -> None: + self.listening_task and self.listening_task.cancel() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level + and record.getMessage() == message + for record in self.log_records + ) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def initialize_auth(self): + auth_resp = self.get_auth_success_response() + self.auth.update_with_rest_response(auth_resp) + + def get_auth_success_response(self) -> Dict[str, Any]: + auth_resp = { + "Authenticated": True, + "SessionToken": "0e8bbcbc-6ada-482a-a9b4-5d9218ada3f9", + "User": { + "UserId": self.user_id, + "UserName": self.user_name, + "Email": "", + "EmailVerified": True, + "AccountId": self.account_id, + "OMSId": self.oms_id, + "Use2FA": False, + }, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None, + } + return auth_resp + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_subscribes_to_orders_and_balances_events(self, ws_mock: AsyncMock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subscribe_resp = {"m": 1, "i": 4, "n": "SubscribeAccountEvents", "o": json.dumps({"Subscribed": True})} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(subscribe_resp) + ) + + output_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_mock.return_value) + + expected_auth_message = { + CONSTANTS.MSG_TYPE_FIELD: CONSTANTS.REQ_MSG_TYPE, + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_AUTH_ENDPOINT, + CONSTANTS.MSG_SEQUENCE_FIELD: 2, + CONSTANTS.MSG_DATA_FIELD: json.dumps( + { + CONSTANTS.API_KEY_FIELD: self.api_key, + CONSTANTS.SIGNATURE_FIELD: self.signature, + CONSTANTS.USER_ID_FIELD: str(self.user_id), + CONSTANTS.NONCE_FIELD: self.nonce, + } + ), + } + expected_sub_message = { + CONSTANTS.MSG_TYPE_FIELD: CONSTANTS.REQ_MSG_TYPE, + CONSTANTS.MSG_ENDPOINT_FIELD: CONSTANTS.WS_ACC_EVENTS_ENDPOINT, + CONSTANTS.MSG_SEQUENCE_FIELD: 4, + CONSTANTS.MSG_DATA_FIELD: json.dumps( + { + CONSTANTS.ACCOUNT_ID_FIELD: self.account_id, + CONSTANTS.OMS_ID_FIELD: self.oms_id, + CONSTANTS.API_KEY_FIELD: self.api_key, + CONSTANTS.SIGNATURE_FIELD: self.signature, + CONSTANTS.USER_ID_FIELD: str(self.user_id), + CONSTANTS.NONCE_FIELD: self.nonce, + } + ), + } + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_mock.return_value + ) + + self.assertEqual(2, len(sent_messages)) + self.assertEqual(expected_auth_message, sent_messages[0]) + self.assertEqual(expected_sub_message, sent_messages[1]) + + self.assertTrue( + self._is_logged( + "INFO", + "Subscribed to private account and orders channels..." + ) + ) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_ignores_non_events(self, ws_mock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subscribe_resp = {"m": 1, "i": 4, "n": "SubscribeAccountEvents", "o": json.dumps({"Subscribed": True})} + update_data = { + "AccountId": 23, + "Amount": 5.5, + "Hold": 3, + "NotionalHoldAmount": 3, + "NotionalProductId": 0, + "NotionalProductSymbol": "ETH", + "NotionalRate": 1, + "NotionalValue": 5.5, + "OMSId": 1, + "PendingDeposits": 0, + "PendingWithdraws": 0, + "ProductId": 14, + "ProductSymbol": "ETH", + "TotalDayDepositNotional": 0, + "TotalDayDeposits": 0, + "TotalDayTransferNotional": 0, + "TotalDayWithdrawNotional": 0, + "TotalDayWithdraws": 0, + "TotalMonthDepositNotional": 0, + "TotalMonthDeposits": 0, + "TotalMonthWithdrawNotional": 0, + "TotalMonthWithdraws": 0, + "TotalYearDepositNotional": 0, + "TotalYearDeposits": 0, + "TotalYearWithdrawNotional": 0, + "TotalYearWithdraws": 0, + } + update_resp = { + "m": 3, + "i": 4, + "n": "SubscribeAccountEvents", + "o": json.dumps(update_data), + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(subscribe_resp) + ) + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_mock.return_value, message=json.dumps(update_resp) + ) + + output_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(output=output_queue)) + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_mock.return_value) + + self.assertFalse(output_queue.empty()) + + expected_update_resp = { + "m": 3, + "i": 4, + "n": "SubscribeAccountEvents", + "o": update_data, + } + update_resp_received = output_queue.get_nowait() + + self.assertTrue(output_queue.empty()) + self.assertEqual(expected_update_resp, update_resp_received) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_user_stream_sends_ping_message_before_ping_interval_finishes(self, ws_mock): + ws_mock.return_value = self.mocking_assistant.create_websocket_mock() + + subscribe_resp = {"m": 1, "i": 4, "n": "SubscribeAccountEvents", "o": json.dumps({"Subscribed": True})} + ws_mock.return_value.receive.side_effect = [ + WSMessage(type=WSMsgType.TEXT, data=json.dumps(subscribe_resp), extra=None), + asyncio.TimeoutError("Test timeout"), + asyncio.CancelledError, + ] + + msg_queue = asyncio.Queue() + self.listening_task = self.ev_loop.create_task(self.data_source.listen_for_user_stream(msg_queue)) + + try: + self.async_run_with_timeout(self.listening_task) + except asyncio.CancelledError: + pass + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket(websocket_mock=ws_mock.return_value) + + expected_ping_message = {"n": "Ping", "o": "{}", "m": 0, "i": 6} + self.assertEqual(expected_ping_message, sent_messages[-1]) diff --git a/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_auth.py b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_auth.py new file mode 100644 index 0000000..3f6adb2 --- /dev/null +++ b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_auth.py @@ -0,0 +1,110 @@ +import asyncio +import hashlib +import hmac +import unittest +from typing import Any, Awaitable +from unittest.mock import MagicMock, patch + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utilities.oms_connector.oms_connector_auth import OMSConnectorAuth +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import build_api_factory + + +class OMSConnectorAuthTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.ev_loop = asyncio.get_event_loop() + cls.api_key = "someApiKey" + cls.secret = "someSecret" + cls.user_id = 20 + cls.time_mock = 1655283229.419752 + cls.user_name = "someUserName" + cls.account_id = 3 + cls.ws_url = "ws://someUrl" + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def setUp(self, time_mock: MagicMock) -> None: + super().setUp() + time_mock.return_value = self.time_mock + self.auth = OMSConnectorAuth(self.api_key, self.secret, self.user_id) + self.api_factory = build_api_factory(auth=self.auth) + self.ws_assistant = self.async_run_with_timeout(self.api_factory.get_ws_assistant()) + self.mocking_assistant = NetworkMockingAssistant() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1) -> Any: + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_build_auth_payload(self): + nonce = str(int(self.time_mock * 1e3)) + concat = f"{nonce}{self.user_id}{self.api_key}" + signature = hmac.new( + key=self.secret.encode("utf-8"), + msg=concat.encode("utf-8"), + digestmod=hashlib.sha256, + ).hexdigest() + expected_headers = { + "APIKey": self.api_key, + "Signature": signature, + "UserId": str(self.user_id), + "Nonce": nonce, + } + + auth_payload = self.auth.get_rest_auth_headers() + + self.assertEqual(expected_headers, auth_payload) + + def test_validate_auth(self): + response_success = { + "Authenticated": True, + "SessionToken": "0e8bbcbc-6ada-482a-a9b4-5d9218ada3f9", + "User": { + "UserId": self.user_id, + "UserName": self.user_name, + "Email": "", + "EmailVerified": True, + "AccountId": self.account_id, + "OMSId": 1, + "Use2FA": False, + }, + "Locked": False, + "Requires2FA": False, + "EnforceEnable2FA": False, + "TwoFAType": None, + "TwoFAToken": None, + "errormsg": None, + } + + self.assertTrue(self.auth.validate_rest_auth(response_success)) + + self.assertNotEqual(self.user_name, self.auth.user_name) + self.assertNotEqual(self.account_id, self.auth.account_id) + self.assertFalse(self.auth.initialized) + + self.auth.update_with_rest_response(response_success) + + self.assertEqual(self.user_name, self.auth.user_name) + self.assertEqual(self.account_id, self.auth.account_id) + self.assertTrue(self.auth.initialized) + + response_failure = { + "Authenticated": False, + "EnforceEnable2FA": False, + "Locked": False, + "Requires2FA": False, + "SessionToken": None, + "TwoFAToken": None, + "TwoFAType": None, + "User": { + "AccountId": 0, + "Email": None, + "EmailVerified": False, + "OMSId": 0, + "Use2FA": False, + "UserId": 0, + "UserName": None, + }, + "errormsg": "User api key not found", + } + + self.assertFalse(self.auth.validate_rest_auth(response_failure)) diff --git a/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_web_utils.py b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_web_utils.py new file mode 100644 index 0000000..97c07b4 --- /dev/null +++ b/test/hummingbot/connector/utilities/oms_connector/test_oms_connector_web_utils.py @@ -0,0 +1,130 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, patch + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utilities.oms_connector.oms_connector_web_utils import build_api_factory +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse + + +class OMSConnectorWebUtilsTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.ws_url = "ws://someUrl" + + def setUp(self) -> None: + super().setUp() + self.api_factory = build_api_factory() + self.ws_assistant = self.async_run_with_timeout(self.api_factory.get_ws_assistant()) + self.rest_assistant = self.async_run_with_timeout(self.api_factory.get_rest_assistant()) + self.mocking_assistant = NetworkMockingAssistant() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_ws_pre_processor(self, ws_connect_mock: AsyncMock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + endpoint = "someEndpoint" + msg_data = {"someAttribute": "someValue"} + msg_payload = { + "m": 0, + "n": endpoint, + "o": msg_data, + } + msg = WSJSONRequest(payload=msg_payload) + self.async_run_with_timeout(self.ws_assistant.connect(ws_url=self.ws_url)) + self.async_run_with_timeout(self.ws_assistant.send(msg)) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(1, len(sent_messages)) + + sent_msg = sent_messages[0] + expected_payload = { + "m": 0, + "i": 2, + "n": endpoint, + "o": json.dumps(msg_data), + } + + self.assertEqual(expected_payload, sent_msg) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_ws_post_processor(self, ws_connect_mock: AsyncMock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + msg_mock = { + "m": 1, + "i": 2, + "n": "someEndpoint", + "o": json.dumps({"someAttribute": "someValue"}), + } + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(msg_mock), + ) + + self.async_run_with_timeout(self.ws_assistant.connect(ws_url=self.ws_url)) + resp: Optional[WSResponse] = self.async_run_with_timeout(self.ws_assistant.receive()) + + self.assertIsNotNone(resp) + + data = resp.data + expected_data = { + "m": 1, + "i": 2, + "n": "someEndpoint", + "o": {"someAttribute": "someValue"}, + } + + self.assertEqual(expected_data, data) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_ws_increments_msg_counter(self, ws_connect_mock: AsyncMock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + endpoint = "someEndpoint" + msg_data = {"someAttribute": "someValue"} + msg_payload = { + "m": 0, + "n": endpoint, + "o": msg_data, + } + msg = WSJSONRequest(payload=msg_payload) + self.async_run_with_timeout(self.ws_assistant.connect(ws_url=self.ws_url)) + self.async_run_with_timeout(self.ws_assistant.send(msg)) + self.async_run_with_timeout(self.ws_assistant.send(msg)) + + sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value + ) + + self.assertEqual(2, len(sent_messages)) + + first_sent_msg = sent_messages[0] + first_expected_payload = { + "m": 0, + "i": 2, + "n": endpoint, + "o": json.dumps(msg_data), + } + + self.assertEqual(first_expected_payload, first_sent_msg) + + second_sent_msg = sent_messages[1] + second_expected_payload = { + "m": 0, + "i": 4, + "n": endpoint, + "o": json.dumps(msg_data), + } + + self.assertEqual(second_expected_payload, second_sent_msg) diff --git a/test/hummingbot/core/__init__.py b/test/hummingbot/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/api_throttler/__init__.py b/test/hummingbot/core/api_throttler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/api_throttler/test_async_throttler.py b/test/hummingbot/core/api_throttler/test_async_throttler.py new file mode 100644 index 0000000..422b9bd --- /dev/null +++ b/test/hummingbot/core/api_throttler/test_async_throttler.py @@ -0,0 +1,277 @@ +import asyncio +import logging +import math +import sys +import time +import unittest +from decimal import Decimal +from typing import Dict, List +from unittest.mock import patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.core.api_throttler.async_throttler import AsyncRequestContext, AsyncThrottler +from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit, TaskLog +from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL + +TEST_PATH_URL = "/hummingbot" +TEST_POOL_ID = "TEST" +TEST_WEIGHTED_POOL_ID = "TEST_WEIGHTED" +TEST_WEIGHTED_TASK_1_ID = "/weighted_task_1" +TEST_WEIGHTED_TASK_2_ID = "/weighted_task_2" + +logging.basicConfig(level=METRICS_LOG_LEVEL) + + +class AsyncThrottlerUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + cls.rate_limits: List[RateLimit] = [ + RateLimit(limit_id=TEST_POOL_ID, limit=1, time_interval=5.0), + RateLimit(limit_id=TEST_PATH_URL, limit=1, time_interval=5.0, + linked_limits=[LinkedLimitWeightPair(TEST_POOL_ID)]), + RateLimit(limit_id=TEST_WEIGHTED_POOL_ID, limit=10, time_interval=5.0), + RateLimit(limit_id=TEST_WEIGHTED_TASK_1_ID, + limit=1000, + time_interval=5.0, + linked_limits=[LinkedLimitWeightPair(TEST_WEIGHTED_POOL_ID, 5)]), + RateLimit(limit_id=TEST_WEIGHTED_TASK_2_ID, + limit=1000, + time_interval=5.0, + linked_limits=[LinkedLimitWeightPair(TEST_WEIGHTED_POOL_ID, 1)]), + ] + + def setUp(self) -> None: + super().setUp() + self.throttler = AsyncThrottler(rate_limits=self.rate_limits) + self._req_counters: Dict[str, int] = {limit.limit_id: 0 for limit in self.rate_limits} + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + async def execute_requests(self, no_request: int, limit_id: str, throttler: AsyncThrottler): + for _ in range(no_request): + async with throttler.execute_task(limit_id=limit_id): + self._req_counters[limit_id] += 1 + + def test_init_without_rate_limits_share_pct(self): + self.assertEqual(0.1, self.throttler._retry_interval) + self.assertEqual(5, len(self.throttler._rate_limits)) + self.assertEqual(1, self.throttler._id_to_limit_map[TEST_POOL_ID].limit) + self.assertEqual(1, self.throttler._id_to_limit_map[TEST_PATH_URL].limit) + + def test_init_with_rate_limits_share_pct(self): + + rate_share_pct: Decimal = Decimal("55") + self.throttler = AsyncThrottler(rate_limits=self.rate_limits, limits_share_percentage=rate_share_pct) + + rate_limits = self.rate_limits.copy() + rate_limits.append(RateLimit(limit_id="ANOTHER_TEST", limit=10, time_interval=5)) + expected_limit = math.floor(Decimal("10") * rate_share_pct / Decimal("100")) + + throttler = AsyncThrottler(rate_limits=rate_limits, limits_share_percentage=rate_share_pct) + self.assertEqual(0.1, throttler._retry_interval) + self.assertEqual(6, len(throttler._rate_limits)) + self.assertEqual(Decimal("1"), throttler._id_to_limit_map[TEST_POOL_ID].limit) + self.assertEqual(Decimal("1"), throttler._id_to_limit_map[TEST_PATH_URL].limit) + self.assertEqual(expected_limit, throttler._id_to_limit_map["ANOTHER_TEST"].limit) + + def test_get_related_limits(self): + self.assertEqual(5, len(self.throttler._rate_limits)) + + rate_limit, related_limits = self.throttler.get_related_limits(TEST_POOL_ID) + self.assertEqual(TEST_POOL_ID, rate_limit.limit_id) + self.assertEqual(0, len(related_limits)) + + rate_limit, related_limits = self.throttler.get_related_limits(TEST_PATH_URL) + self.assertEqual(TEST_PATH_URL, rate_limit.limit_id) + self.assertEqual(1, len(related_limits)) + + def test_flush_empty_task_logs(self): + # Test: No entries in task_logs to flush + lock = asyncio.Lock() + + rate_limit = self.rate_limits[0] + self.assertEqual(0, len(self.throttler._task_logs)) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=lock, + safety_margin_pct=self.throttler._safety_margin_pct) + context.flush() + self.assertEqual(0, len(self.throttler._task_logs)) + + def test_flush_only_elapsed_tasks_are_flushed(self): + lock = asyncio.Lock() + rate_limit = self.rate_limits[0] + self.throttler._task_logs = [ + TaskLog(timestamp=1.0, rate_limit=rate_limit, weight=rate_limit.weight), + TaskLog(timestamp=time.time(), rate_limit=rate_limit, weight=rate_limit.weight) + ] + + self.assertEqual(2, len(self.throttler._task_logs)) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=lock, + safety_margin_pct=self.throttler._safety_margin_pct) + context.flush() + self.assertEqual(1, len(self.throttler._task_logs)) + + def test_within_capacity_singular_non_weighted_task_returns_false(self): + rate_limit, _ = self.throttler.get_related_limits(limit_id=TEST_POOL_ID) + self.throttler._task_logs.append( + TaskLog(timestamp=time.time(), rate_limit=rate_limit, weight=rate_limit.weight)) + + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertFalse(context.within_capacity()) + + def test_within_capacity_singular_non_weighted_task_returns_true(self): + rate_limit, _ = self.throttler.get_related_limits(limit_id=TEST_POOL_ID) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertTrue(context.within_capacity()) + + def test_within_capacity_pool_non_weighted_task_returns_false(self): + rate_limit, related_limits = self.throttler.get_related_limits(limit_id=TEST_PATH_URL) + + for linked_limit, weight in related_limits: + self.throttler._task_logs.append(TaskLog(timestamp=time.time(), rate_limit=linked_limit, weight=weight)) + + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=related_limits, + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertFalse(context.within_capacity()) + + def test_within_capacity_pool_non_weighted_task_returns_true(self): + rate_limit, related_limits = self.throttler.get_related_limits(limit_id=TEST_PATH_URL) + + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=related_limits, + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertTrue(context.within_capacity()) + + def test_within_capacity_pool_weighted_tasks(self): + task_1, task_1_related_limits = self.throttler.get_related_limits(limit_id=TEST_WEIGHTED_TASK_1_ID) + + # Simulate Weighted Task 1 and Task 2 already in task logs, resulting in a used capacity of 6/10 + for linked_limit, weight in task_1_related_limits: + self.throttler._task_logs.append(TaskLog(timestamp=time.time(), rate_limit=linked_limit, weight=weight)) + task_2, task_2_related_limits = self.throttler.get_related_limits(limit_id=TEST_WEIGHTED_TASK_2_ID) + for linked_limit, weight in task_2_related_limits: + self.throttler._task_logs.append(TaskLog(timestamp=time.time(), rate_limit=linked_limit, weight=weight)) + + # Another Task 1(weight=5) will exceed the capacity(11/10) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=task_1, + related_limits=task_1_related_limits, + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertFalse(context.within_capacity()) + + # However Task 2(weight=1) will not exceed the capacity(7/10) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=task_2, + related_limits=task_2_related_limits, + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertTrue(context.within_capacity()) + + def test_within_capacity_returns_true(self): + lock = asyncio.Lock() + rate_limit = self.rate_limits[0] + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=lock, + safety_margin_pct=self.throttler._safety_margin_pct) + self.assertTrue(context.within_capacity()) + + def test_acquire_appends_to_task_logs(self): + rate_limit = self.rate_limits[0] + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[], + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + self.ev_loop.run_until_complete(context.acquire()) + + # We acquire()'d just one rate_limit, task log should have only one entry + self.assertEqual(1, len(self.throttler._task_logs)) + + def test_acquire_awaits_when_exceed_capacity(self): + rate_limit = self.rate_limits[0] + self.throttler._task_logs.append( + TaskLog(timestamp=time.time(), rate_limit=rate_limit, weight=rate_limit.weight)) + context = AsyncRequestContext(task_logs=self.throttler._task_logs, + rate_limit=rate_limit, + related_limits=[(rate_limit, rate_limit.weight)], + lock=asyncio.Lock(), + safety_margin_pct=self.throttler._safety_margin_pct) + with self.assertRaises(asyncio.exceptions.TimeoutError): + self.ev_loop.run_until_complete( + asyncio.wait_for(context.acquire(), 1.0) + ) + + def test_within_capacity_returns_true_for_throttler_without_configured_limits(self): + throttler = AsyncThrottler(rate_limits=[]) + context = throttler.execute_task(limit_id="test_limit_id") + self.assertTrue(context.within_capacity()) + + @patch("hummingbot.core.api_throttler.async_throttler.AsyncRequestContext._time") + def test_within_capacity_for_limits_with_milliseconds_interval(self, time_mock): + per_second_limit = RateLimit(limit_id="generic_per_second", limit=3, time_interval=1) + per_millisecond_limit = RateLimit(limit_id="generic_per_millisecond", limit=2, time_interval=0.2) + specific_limit = RateLimit(limit_id="specific_limit", limit=sys.maxsize, time_interval=1, linked_limits=[ + LinkedLimitWeightPair(per_second_limit.limit_id), + LinkedLimitWeightPair(per_millisecond_limit.limit_id), + ]) + + # Scenario where one specific task was executed at 0 milliseconds + tasks_log = [] + tasks_log.append(TaskLog(timestamp=1640000000.0000, rate_limit=per_millisecond_limit, weight=1)) + tasks_log.append(TaskLog(timestamp=1640000000.0000, rate_limit=per_second_limit, weight=1)) + + context = AsyncRequestContext( + task_logs=tasks_log, + rate_limit=specific_limit, + related_limits=[(per_millisecond_limit, 1), (per_second_limit, 1), (specific_limit, 1)], + lock=asyncio.Lock(), + safety_margin_pct=0, + ) + + time_mock.return_value = 1640000000.0100 + result = context.within_capacity() + self.assertTrue(result) + + # Add one more occurrence of the same task but at millisecond 1 + tasks_log.append(TaskLog(timestamp=1640000000.1000, rate_limit=per_millisecond_limit, weight=1)) + tasks_log.append(TaskLog(timestamp=1640000000.1000, rate_limit=per_second_limit, weight=1)) + + time_mock.return_value = 1640000000.1000 + result = context.within_capacity() + self.assertFalse(result) + + time_mock.return_value = 1640000000.1900 + result = context.within_capacity() + self.assertFalse(result) + + time_mock.return_value = 1640000000.2000 + result = context.within_capacity() + self.assertFalse(result) + + time_mock.return_value = 1640000000.2100 + result = context.within_capacity() + self.assertTrue(result) diff --git a/test/hummingbot/core/data_type/__init__.py b/test/hummingbot/core/data_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/data_type/test_in_flight_order.py b/test/hummingbot/core/data_type/test_in_flight_order.py new file mode 100644 index 0000000..9ec1f70 --- /dev/null +++ b/test/hummingbot/core/data_type/test_in_flight_order.py @@ -0,0 +1,726 @@ +import asyncio +import time +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import patch + +from hummingbot.core.data_type.common import OrderType, PositionAction, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount + + +class InFlightOrderPyUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + cls.client_order_id = "someClientOrderId" + cls.exchange_order_id = "someExchangeOrderId" + cls.trade_fee_percent = Decimal("0.01") + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1): + class DesiredError(Exception): + pass + + async def run_coro_that_raises(coro: Awaitable): + try: + await coro + except asyncio.TimeoutError: + raise DesiredError + + try: + self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout) + except DesiredError: # the coroutine raised an asyncio.TimeoutError as expected + raise asyncio.TimeoutError + except asyncio.TimeoutError: # the coroutine did not finish on time + raise RuntimeError + + def _simulate_order_created(self, order: InFlightOrder): + order.current_state = OrderState.OPEN + order.update_exchange_order_id(self.exchange_order_id) + + def _simulate_cancel_order_request_sent(self, order: InFlightOrder): + order.current_state = OrderState.PENDING_CANCEL + + def _simulate_order_cancelled(self, order: InFlightOrder): + order.current_state = OrderState.CANCELED + + def _simulate_order_failed(self, order: InFlightOrder): + order.current_state = OrderState.FAILED + + def _simulate_order_partially_filled(self, order: InFlightOrder): + order.current_state = OrderState.PARTIALLY_FILLED + order.executed_amount_base = order.amount / Decimal("2") + + def _simulate_order_completely_filled(self, order: InFlightOrder): + order.current_state = OrderState.FILLED + order.executed_amount_base = order.amount + + def test_in_flight_order_states(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + self.assertTrue(order.is_open) + self.assertIsNone(order.exchange_order_id) + self.assertFalse(order.exchange_order_id_update_event.is_set()) + + # Simulate Order Created + self._simulate_order_created(order) + + self.assertTrue(order.is_open) + self.assertIsNotNone(order.exchange_order_id) + self.assertTrue(order.exchange_order_id_update_event.is_set()) + + # Simulate Order Cancellation request sent + self._simulate_cancel_order_request_sent(order) + + self.assertTrue(order.is_pending_cancel_confirmation + and order.is_open + and not order.is_cancelled + and not order.is_done) + + # Simulate Order Cancelled + self._simulate_order_cancelled(order) + + self.assertTrue(order.is_done and order.is_cancelled) + + failed_order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + # Simulate Order Failed + self._simulate_order_failed(failed_order) + + self.assertTrue(failed_order.is_failure and failed_order.is_done) + + filled_order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + # Simulate Order Partially Filled + self._simulate_order_partially_filled(filled_order) + + self.assertTrue(filled_order.is_open and not filled_order.is_done) + + # Simulate Order Completely Filled + self._simulate_order_completely_filled(filled_order) + + self.assertTrue(filled_order.is_done and filled_order.is_filled) + + def test_average_executed_price(self): + order_0: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + self.assertIsNone(order_0.average_executed_price) + + trade_update_0: TradeUpdate = TradeUpdate( + trade_id="someTradeId", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=order_0.trading_pair, + fill_price=order_0.price, + fill_base_amount=order_0.amount, + fill_quote_amount=(order_0.price * order_0.amount), + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * order_0.amount)]), + fill_timestamp=time.time(), + ) + # Order completely filled after single trade update + order_0.order_fills.update({trade_update_0.trade_id: trade_update_0}) + + self.assertEqual(order_0.price, order_0.average_executed_price) + + order_1: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + trade_update_1: TradeUpdate = TradeUpdate( + trade_id="someTradeId_1", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=order_1.trading_pair, + fill_price=Decimal("0.5"), + fill_base_amount=(order_1.amount / Decimal("2.0")), + fill_quote_amount=(order_1.price * (order_1.amount / Decimal("2.0"))), + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * (order_1.amount / Decimal("2.0")))]), + fill_timestamp=time.time(), + ) + + trade_update_2: TradeUpdate = TradeUpdate( + trade_id="someTradeId_2", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=order_1.trading_pair, + fill_price=order_1.price, + fill_base_amount=(order_1.amount / Decimal("2.0")), + fill_quote_amount=(order_1.price * (order_1.amount / Decimal("2.0"))), + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(self.base_asset, Decimal(0.01) * (order_1.amount / Decimal("2.0")))]), + fill_timestamp=time.time(), + ) + + # Order completely filled after 2 trade updates + order_1.order_fills.update( + { + trade_update_1.trade_id: trade_update_1, + trade_update_2.trade_id: trade_update_2, + } + ) + expected_average_price = ( + sum([order_fill.fill_price * order_fill.fill_base_amount for order_fill in order_1.order_fills.values()]) + / order_1.amount + ) + self.assertEqual(expected_average_price, order_1.average_executed_price) + + @patch("hummingbot.core.data_type.in_flight_order.GET_EX_ORDER_ID_TIMEOUT", 0.1) + def test_get_exchange_order_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + self.assertIsNone(order.exchange_order_id) + self.assertFalse(order.exchange_order_id_update_event.is_set()) + with self.assertRaises(asyncio.TimeoutError): + self.async_run_with_timeout_coroutine_must_raise_timeout(order.get_exchange_order_id()) + + order.update_exchange_order_id(self.exchange_order_id) + result = self.async_run_with_timeout(order.get_exchange_order_id()) + + self.assertEqual(self.exchange_order_id, result) + self.assertTrue(order.exchange_order_id_update_event.is_set()) + + def test_from_json(self): + fee = AddedToCostTradeFee( + percent=Decimal("0.5"), + percent_token=self.quote_asset + ) + trade_update = TradeUpdate( + trade_id="12345", + client_order_id=self.client_order_id, + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + fill_timestamp=1640001112, + fill_price=Decimal("1000.11"), + fill_base_amount=Decimal("2"), + fill_quote_amount=Decimal("2000.22"), + fee=fee, + ) + + order_json = { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": OrderType.LIMIT.name, + "trade_type": TradeType.BUY.name, + "price": "1.0", + "amount": "1000.0", + "executed_amount_base": "0", + "executed_amount_quote": "0", + "fee_asset": None, + "fee_paid": "0", + "last_state": "0", + "leverage": "1", + "position": "NIL", + "creation_timestamp": 1640001112.0, + "last_update_timestamp": 1640001113.0, + "order_fills": {"1": trade_update.to_json()} + } + + expected_order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + expected_order.last_update_timestamp = 1640001113.0 + + order_from_json = InFlightOrder.from_json(order_json) + self.assertEqual(expected_order, order_from_json) + self.assertFalse(order_from_json.completely_filled_event.is_set()) + + self.assertIn("1", order_from_json.order_fills) + self.assertEqual(trade_update, order_from_json.order_fills["1"]) + self.assertEqual(1640001113.0, order_from_json.last_update_timestamp) + + def test_from_json_does_not_fail_when_order_fills_not_present(self): + order_json = { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": OrderType.LIMIT.name, + "trade_type": TradeType.BUY.name, + "price": "1.0", + "amount": "1000.0", + "executed_amount_base": "0", + "executed_amount_quote": "0", + "fee_asset": None, + "fee_paid": "0", + "last_state": "0", + "leverage": "1", + "position": PositionAction.NIL.value, + "creation_timestamp": 1640001112 + } + + expected_order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + order_from_json = InFlightOrder.from_json(order_json) + self.assertEqual(expected_order, order_from_json) + self.assertFalse(order_from_json.completely_filled_event.is_set()) + + def test_completed_order_recovered_from_json_has_completed_event_updated(self): + order_json = { + "client_order_id": self.client_order_id, + "exchange_order_id": self.exchange_order_id, + "trading_pair": self.trading_pair, + "order_type": OrderType.LIMIT.name, + "trade_type": TradeType.BUY.name, + "price": "1.0", + "amount": "1000.0", + "executed_amount_base": "1000.0", + "executed_amount_quote": "1100.0", + "fee_asset": None, + "fee_paid": "0", + "last_state": "0", + "leverage": "1", + "position": "NIL", + "creation_timestamp": 1640001112.0, + } + + expected_order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + expected_order.executed_amount_base = Decimal("1000") + expected_order.executed_amount_quote = Decimal("1100") + + order_from_json = InFlightOrder.from_json(order_json) + self.assertEqual(expected_order, order_from_json) + self.assertTrue(order_from_json.completely_filled_event.is_set()) + + def test_to_json(self): + fee = AddedToCostTradeFee( + percent=Decimal("0.5"), + percent_token=self.quote_asset + ) + trade_update = TradeUpdate( + trade_id="12345", + client_order_id=self.client_order_id, + exchange_order_id="EOID1", + trading_pair=self.trading_pair, + fill_timestamp=1640001112, + fill_price=Decimal("1000.11"), + fill_base_amount=Decimal("2"), + fill_quote_amount=Decimal("2000.22"), + fee=fee, + ) + + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + order.order_fills["1"] = trade_update + + order_json = order.to_json() + + self.assertIsInstance(order_json, dict) + + self.assertEqual(order_json["client_order_id"], order.client_order_id) + self.assertEqual(order_json["exchange_order_id"], order.exchange_order_id) + self.assertEqual(order_json["trading_pair"], order.trading_pair) + self.assertEqual(order_json["order_type"], order.order_type.name) + self.assertEqual(order_json["trade_type"], order.trade_type.name) + self.assertEqual(order_json["price"], str(order.price)) + self.assertEqual(order_json["amount"], str(order.amount)) + self.assertEqual(order_json["executed_amount_base"], str(order.executed_amount_base)) + self.assertEqual(order_json["executed_amount_quote"], str(order.executed_amount_quote)) + self.assertEqual(order_json["last_state"], str(order.current_state.value)) + self.assertEqual(order_json["leverage"], str(order.leverage)) + self.assertEqual(order_json["position"], order.position.value) + self.assertEqual(order_json["creation_timestamp"], order.creation_timestamp) + self.assertEqual(order_json["last_update_timestamp"], order.last_update_timestamp) + self.assertEqual(order_json["order_fills"], {"1": trade_update.to_json()}) + + def test_to_limit_order(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.223334, + price=Decimal("1.0"), + ) + + expected_limit_order: LimitOrder = LimitOrder( + client_order_id=order.client_order_id, + trading_pair=order.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("1.0"), + quantity=Decimal("1000.0"), + filled_quantity=Decimal("0"), + creation_timestamp=1640001112223334 + ) + + limit_order = order.to_limit_order() + + self.assertIsInstance(limit_order, LimitOrder) + + self.assertEqual(limit_order.client_order_id, expected_limit_order.client_order_id) + self.assertEqual(limit_order.trading_pair, expected_limit_order.trading_pair) + self.assertEqual(limit_order.is_buy, expected_limit_order.is_buy) + self.assertEqual(limit_order.base_currency, expected_limit_order.base_currency) + self.assertEqual(limit_order.quote_currency, expected_limit_order.quote_currency) + self.assertEqual(limit_order.price, expected_limit_order.price) + self.assertEqual(limit_order.quantity, expected_limit_order.quantity) + self.assertEqual(limit_order.filled_quantity, expected_limit_order.filled_quantity) + self.assertEqual(limit_order.creation_timestamp, expected_limit_order.creation_timestamp) + self.assertEqual(limit_order.status, expected_limit_order.status) + + def test_update_with_order_update_client_order_id_mismatch(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + mismatch_order_update: OrderUpdate = OrderUpdate( + client_order_id="mismatchClientOrderId", + exchange_order_id="mismatchExchangeOrderId", + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + self.assertFalse(order.update_with_order_update(mismatch_order_update)) + self.assertEqual(Decimal("0"), order.executed_amount_base) + self.assertEqual(Decimal("0"), order.executed_amount_quote) + self.assertEqual(order.creation_timestamp, order.last_update_timestamp) + + def test_update_with_order_update_open_order(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + open_order_update: OrderUpdate = OrderUpdate( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + self.assertTrue(order.update_with_order_update(open_order_update)) + self.assertEqual(Decimal("0"), order.executed_amount_base) + self.assertEqual(Decimal("0"), order.executed_amount_quote) + self.assertEqual(1, order.last_update_timestamp) + + def test_update_with_order_update_multiple_order_updates(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + order_update_1: OrderUpdate = OrderUpdate( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.PARTIALLY_FILLED, + ) + + self.assertTrue(order.update_with_order_update(order_update_1)) + # Order updates should not modify executed values + self.assertEqual(Decimal(0), order.executed_amount_base) + self.assertEqual(Decimal(0), order.executed_amount_quote) + self.assertEqual(order.last_update_timestamp, 1) + self.assertEqual(0, len(order.order_fills)) + self.assertTrue(order.is_open) + + order_update_2: OrderUpdate = OrderUpdate( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=2, + new_state=OrderState.FILLED, + ) + + self.assertTrue(order.update_with_order_update(order_update_2)) + # Order updates should not modify executed values + self.assertEqual(Decimal(0), order.executed_amount_base) + self.assertEqual(Decimal(0), order.executed_amount_quote) + self.assertEqual(order.last_update_timestamp, 2) + self.assertEqual(0, len(order.order_fills)) + self.assertTrue(order.is_done) + + def test_update_exchange_id_with_order_update(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + order_update: OrderUpdate = OrderUpdate( + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + update_timestamp=1, + new_state=OrderState.OPEN, + ) + + result = order.update_with_order_update(order_update) + self.assertTrue(result) + self.assertEqual(self.exchange_order_id, order.exchange_order_id) + self.assertTrue(order.exchange_order_id_update_event.is_set()) + self.assertEqual(0, len(order.order_fills)) + + def test_update_with_trade_update_trade_update_with_trade_fee_percent(self): + + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id="someTradeId", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + fill_price=Decimal("1.0"), + fill_base_amount=Decimal("500.0"), + fill_quote_amount=Decimal("500.0"), + fee=AddedToCostTradeFee(percent=self.trade_fee_percent, percent_token=self.quote_asset), + fill_timestamp=1, + ) + + self.assertTrue(order.update_with_trade_update(trade_update)) + self.assertEqual(order.executed_amount_base, trade_update.fill_base_amount) + self.assertEqual(order.executed_amount_quote, trade_update.fill_quote_amount) + self.assertEqual(order.last_update_timestamp, trade_update.fill_timestamp) + self.assertEqual(1, len(order.order_fills)) + self.assertIn(trade_update.trade_id, order.order_fills) + + def test_update_with_trade_update_duplicate_trade_update(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id="someTradeId", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + fill_price=Decimal("1.0"), + fill_base_amount=Decimal("500.0"), + fill_quote_amount=Decimal("500.0"), + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * Decimal("500.0"))]), + fill_timestamp=1, + ) + + self.assertTrue(order.update_with_trade_update(trade_update)) + self.assertEqual(order.executed_amount_base, trade_update.fill_base_amount) + self.assertEqual(order.executed_amount_quote, trade_update.fill_quote_amount) + self.assertEqual(order.last_update_timestamp, trade_update.fill_timestamp) + self.assertEqual(1, len(order.order_fills)) + self.assertIn(trade_update.trade_id, order.order_fills) + + # Ignores duplicate trade update + self.assertFalse(order.update_with_trade_update(trade_update)) + + def test_update_with_trade_update_multiple_trade_updates(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + initial_fill_price: Decimal = Decimal("0.5") + initial_fill_amount: Decimal = Decimal("500.0") + trade_update_1: TradeUpdate = TradeUpdate( + trade_id="someTradeId_1", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + fill_price=initial_fill_price, + fill_base_amount=initial_fill_amount, + fill_quote_amount=initial_fill_price * initial_fill_amount, + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * initial_fill_amount)]), + fill_timestamp=1, + ) + + subsequent_fill_price: Decimal = Decimal("1.0") + subsequent_fill_amount: Decimal = Decimal("500.0") + trade_update_2: TradeUpdate = TradeUpdate( + trade_id="someTradeId_2", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + fill_price=subsequent_fill_price, + fill_base_amount=subsequent_fill_amount, + fill_quote_amount=subsequent_fill_price * subsequent_fill_amount, + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * subsequent_fill_amount)]), + fill_timestamp=2, + ) + + self.assertTrue(order.update_with_trade_update(trade_update_1)) + self.assertIn(trade_update_1.trade_id, order.order_fills) + self.assertEqual(order.executed_amount_base, trade_update_1.fill_base_amount) + self.assertEqual(order.executed_amount_quote, trade_update_1.fill_quote_amount) + self.assertEqual(order.last_update_timestamp, trade_update_1.fill_timestamp) + self.assertEqual(1, len(order.order_fills)) + + self.assertTrue(order.is_open) + + self.assertTrue(order.update_with_trade_update(trade_update_2)) + self.assertIn(trade_update_2.trade_id, order.order_fills) + self.assertEqual(order.executed_amount_base, order.amount) + self.assertEqual( + order.executed_amount_quote, trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount + ) + self.assertEqual(order.last_update_timestamp, trade_update_2.fill_timestamp) + self.assertEqual(2, len(order.order_fills)) + self.assertEqual( + order.average_executed_price, + (trade_update_1.fill_quote_amount + trade_update_2.fill_quote_amount) / order.amount, + ) + + self.assertTrue(order.is_filled) + self.assertEqual(order.current_state, OrderState.PENDING_CREATE) + + def test_trade_update_does_not_change_exchange_order_id(self): + order: InFlightOrder = InFlightOrder( + client_order_id=self.client_order_id, + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("1000.0"), + creation_timestamp=1640001112.0, + price=Decimal("1.0"), + ) + + trade_update: TradeUpdate = TradeUpdate( + trade_id="someTradeId", + client_order_id=self.client_order_id, + exchange_order_id=self.exchange_order_id, + trading_pair=self.trading_pair, + fill_price=Decimal("1.0"), + fill_base_amount=Decimal("500.0"), + fill_quote_amount=Decimal("500.0"), + fee=AddedToCostTradeFee( + flat_fees=[TokenAmount(token=self.quote_asset, amount=self.trade_fee_percent * Decimal("500.0"))]), + fill_timestamp=1, + ) + + self.assertTrue(order.update_with_trade_update(trade_update)) + self.assertIsNone(order.exchange_order_id) + self.assertFalse(order.exchange_order_id_update_event.is_set()) diff --git a/test/hummingbot/core/data_type/test_limit_order.py b/test/hummingbot/core/data_type/test_limit_order.py new file mode 100644 index 0000000..a22abbc --- /dev/null +++ b/test/hummingbot/core/data_type/test_limit_order.py @@ -0,0 +1,101 @@ +import unittest +from decimal import Decimal +import time +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.event.events import LimitOrderStatus + + +class LimitOrderUnitTest(unittest.TestCase): + def test_order_creation_with_default_values(self): + order = LimitOrder(client_order_id="HBOT_1", + trading_pair="HBOT-USDT", + is_buy=False, + base_currency="HBOT", + quote_currency="USDT", + price=Decimal("100"), + quantity=Decimal("1.5") + ) + self.assertEqual("HBOT_1", order.client_order_id) + self.assertEqual("HBOT-USDT", order.trading_pair) + self.assertEqual(False, order.is_buy) + self.assertEqual("HBOT", order.base_currency) + self.assertEqual("USDT", order.quote_currency) + self.assertEqual(Decimal("100"), order.price) + self.assertEqual(Decimal("1.5"), order.quantity) + self.assertTrue(Decimal.is_nan(order.filled_quantity)) + self.assertEqual(0, order.creation_timestamp) + self.assertEqual(LimitOrderStatus.UNKNOWN, order.status) + self.assertEqual(-1, order.age()) + + def test_order_creation_with_all_values(self): + created = int((time.time() - 100.) * 1e6) + order = LimitOrder(client_order_id="HBOT_1", + trading_pair="HBOT-USDT", + is_buy=False, + base_currency="HBOT", + quote_currency="USDT", + price=Decimal("100"), + quantity=Decimal("1.5"), + filled_quantity=Decimal("0.5"), + creation_timestamp=created, + status=LimitOrderStatus.OPEN + ) + self.assertEqual(Decimal("0.5"), order.filled_quantity) + self.assertEqual(created, order.creation_timestamp) + self.assertEqual(LimitOrderStatus.OPEN, order.status) + self.assertEqual(100, order.age()) + end_time = created + (50 * 1e6) + self.assertEqual(50, order.age_til(end_time)) + end_time = created - (50 * 1e6) + self.assertEqual(-1, order.age_til(end_time)) + + def test_to_pandas(self): + # Fix the timestamp here so that we can test order age accurately + created = 1625835199511442 + now_ts = created + 100 * 1e6 + self.maxDiff = None + orders = [ + LimitOrder("HBOT_1", "A-B", True, "A", "B", Decimal("1"), Decimal("1.5")), + LimitOrder(f"HBOT_{str(created)}", "C-D", True, "C", "D", Decimal("1"), Decimal("1")), + LimitOrder("HBOT_2", "A-B ", False, "A", "B", Decimal("2.5"), Decimal("1"), Decimal("0"), created, LimitOrderStatus.OPEN), + LimitOrder(f"HBOT_{str(created)}", "A-B ", False, "A", "B", Decimal("2"), Decimal("1"), Decimal(0), created, LimitOrderStatus.CANCELED), + ] + df = LimitOrder.to_pandas(orders, 1.5, end_time_order_age=now_ts) + # Except df output is as below + + # Order ID Type Price Spread Amount Age Hang + # HBOT_2 sell 2.5 66.67% 1.0 00:01:40 n/a + # ...1442 sell 2.0 33.33% 1.0 00:01:40 n/a + # HBOT_1 buy 1.0 33.33% 1.5 n/a n/a + # ...1442 buy 1.0 33.33% 1.0 00:01:40 n/a + # we can't compare the text output directly as for some weird reason the test file passes when run individually + # but will fail under coverage run -m nose test.hummingbot + # self.assertEqual(expect_txt, df.to_string(index=False, max_colwidth=50)) + self.assertEqual("HBOT_2", df["Order ID"][0]) + self.assertEqual("sell", df["Type"][0]) + self.assertAlmostEqual(2.5, df["Price"][0]) + self.assertEqual("66.67%", df["Spread"][0]) + self.assertAlmostEqual(1., df["Amount"][0]) + self.assertEqual("00:01:40", df["Age"][0]) + self.assertEqual("n/a", df["Hang"][0]) + + # Test to see if hanging orders are displayed correctly + df = LimitOrder.to_pandas(orders, 1.5, ["HBOT_1", "HBOT_2"], end_time_order_age=now_ts) + # Except df output is as below + # Order ID Type Price Spread Amount Age Hang + # HBOT_2 sell 2.5 66.67% 1.0 00:01:40 yes + # ...1442 sell 2.0 33.33% 1.0 00:01:40 no + # HBOT_1 buy 1.0 33.33% 1.5 n/a yes + # ...1442 buy 1.0 33.33% 1.0 00:01:40 no + + self.assertEqual("HBOT_2", df["Order ID"][0]) + self.assertEqual("sell", df["Type"][0]) + self.assertAlmostEqual(2.5, df["Price"][0]) + self.assertEqual("66.67%", df["Spread"][0]) + self.assertAlmostEqual(1., df["Amount"][0]) + self.assertEqual("00:01:40", df["Age"][0]) + self.assertEqual("yes", df["Hang"][0]) + # Test to see if df is created and order age is calculated + df = LimitOrder.to_pandas(orders, 1.5, []) + self.assertAlmostEqual(2.5, df["Price"][0]) + self.assertTrue(":" in df["Age"][0]) diff --git a/test/hummingbot/core/data_type/test_order_book.py b/test/hummingbot/core/data_type/test_order_book.py new file mode 100644 index 0000000..98eb44d --- /dev/null +++ b/test/hummingbot/core/data_type/test_order_book.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +import logging +import unittest +from hummingbot.core.data_type.order_book import OrderBook +import numpy as np + + +class OrderBookUnitTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.order_book_dex = OrderBook(dex=True) + cls.order_book_cex = OrderBook(dex=False) + + def test_truncate_overlap_entries_dex(self): + bids_array = np.array([[1, 1, 1], [2, 1, 2], [3, 1, 3], [50, 0.01, 4]], dtype=np.float64) + asks_array = np.array([[4, 1, 1], [5, 1, 2], [6, 1, 3], [7, 1, 4]], dtype=np.float64) + self.order_book_dex.apply_numpy_snapshot(bids_array, asks_array) + bids, asks = self.order_book_dex.snapshot + best_bid = bids.iloc[0].tolist() + best_ask = asks.iloc[0].tolist() + self.assertEqual(best_bid, [3., 1., 3.]) + self.assertEqual(best_ask, [4., 1., 1.]) + + new_ask = np.array([[2, 0.1, 5]]) + new_bid = np.array([[3.5, 1, 5]]) + self.order_book_dex.apply_numpy_diffs(new_bid, new_ask) + bids, asks = self.order_book_dex.snapshot + best_bid = bids.iloc[0].tolist() + best_ask = asks.iloc[0].tolist() + self.assertEqual(best_bid, [3.5, 1., 5.]) + self.assertEqual(best_ask, [4., 1., 1.]) + + def test_truncate_overlap_entries_cex(self): + bids_array = np.array([[1, 1, 1], [2, 1, 2], [3, 1, 3]], dtype=np.float64) + asks_array = np.array([[4, 1, 1], [5, 1, 2], [6, 1, 3], [7, 1, 4]], dtype=np.float64) + self.order_book_cex.apply_numpy_snapshot(bids_array, asks_array) + bids, asks = self.order_book_cex.snapshot + best_bid = bids.iloc[0].tolist() + best_ask = asks.iloc[0].tolist() + self.assertEqual(best_bid, [3., 1., 3.]) + self.assertEqual(best_ask, [4., 1., 1.]) + + new_ask = np.array([[2, 0.1, 5]]) + new_bid = np.array([[50, 0.01, 6]]) + self.order_book_cex.apply_numpy_diffs(new_bid, new_ask) + bids, asks = self.order_book_cex.snapshot + best_bid = len(bids) and bids.iloc[0].tolist() + best_ask = len(asks) and asks.iloc[0].tolist() + self.assertEqual(best_bid, [50., 0.01, 6.]) + self.assertEqual(best_ask, 0) + + +def main(): + logging.basicConfig(level=logging.INFO) + unittest.main() + + +if __name__ == "__main__": + main() diff --git a/test/hummingbot/core/data_type/test_order_book_message.py b/test/hummingbot/core/data_type/test_order_book_message.py new file mode 100644 index 0000000..9638fcd --- /dev/null +++ b/test/hummingbot/core/data_type/test_order_book_message.py @@ -0,0 +1,250 @@ +import time +import unittest + +from hummingbot.core.data_type.order_book_message import OrderBookMessage, \ + OrderBookMessageType +from hummingbot.core.data_type.order_book_row import OrderBookRow + + +class OrderBookMessageTest(unittest.TestCase): + def test_update_id(self): + update_id = "someId" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": update_id}, + timestamp=time.time(), + ) + self.assertEqual(update_id, msg.update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": update_id}, + timestamp=time.time(), + ) + self.assertEqual(update_id, msg.update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertEqual(-1, msg.update_id) + + def test_first_update_id(self): + first_update_id = "firstUpdateId" + update_id = "someId" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertEqual(-1, msg.first_update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={ + "update_id": update_id, + "first_update_id": first_update_id, + }, + timestamp=time.time(), + ) + self.assertEqual(first_update_id, msg.first_update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertEqual(-1, msg.first_update_id) + + def test_trade_id(self): + trade_id = "someTradeId" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertEqual(-1, msg.trade_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertEqual(-1, msg.trade_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": trade_id}, + timestamp=time.time(), + ) + self.assertEqual(trade_id, msg.trade_id) + + def test_trading_pair(self): + trading_pair = "BTC-USDT" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"trading_pair": trading_pair}, + timestamp=time.time(), + ) + self.assertEqual(trading_pair, msg.trading_pair) + + def test_bids_and_asks(self): + update_id = "someId" + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={ + "update_id": update_id, + "asks": [(1, 2), (3, 4)], + "bids": [(5, 6), (7, 8)], + }, + timestamp=time.time(), + ) + + asks = msg.asks + self.assertEqual(2, len(asks)) + self.assertTrue(isinstance(asks[0], OrderBookRow)) + self.assertEqual(1, asks[0].price) + self.assertEqual(2, asks[0].amount) + self.assertEqual(update_id, asks[0].update_id) + + bids = msg.bids + self.assertEqual(2, len(bids)) + self.assertTrue(isinstance(bids[0], OrderBookRow)) + self.assertEqual(5, bids[0].price) + self.assertEqual(6, bids[0].amount) + self.assertEqual(update_id, bids[0].update_id) + + def test_has_update_id(self): + update_id = "someId" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": update_id}, + timestamp=time.time(), + ) + self.assertTrue(msg.has_update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": update_id}, + timestamp=time.time(), + ) + self.assertTrue(msg.has_update_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertFalse(msg.has_update_id) + + def test_has_trade_id(self): + trade_id = "someTradeId" + + msg = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertFalse(msg.has_trade_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"someKey": "someValue"}, + timestamp=time.time(), + ) + self.assertFalse(msg.has_trade_id) + + msg = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": trade_id}, + timestamp=time.time(), + ) + self.assertTrue(msg.has_trade_id) + + def test_equality(self): + trade_id = "someTradeId" + update_id = "someId" + + snapshot1 = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": update_id}, + timestamp=time.time(), + ) + snapshot2 = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": update_id}, + timestamp=time.time(), + ) + diff1 = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": update_id}, + timestamp=time.time(), + ) + diff2 = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": update_id}, + timestamp=time.time(), + ) + trade1 = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": trade_id}, + timestamp=time.time(), + ) + trade2 = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": trade_id}, + timestamp=time.time(), + ) + + self.assertNotEqual(snapshot1, diff1) + self.assertNotEqual(snapshot1, trade1) + self.assertNotEqual(diff1, trade1) + self.assertEqual(snapshot1, snapshot2) + self.assertEqual(diff1, diff2) + self.assertEqual(trade1, trade2) + + def test_larger_than(self): + t = time.time() + trade1 = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": 5}, + timestamp=t, + ) + trade2 = OrderBookMessage( + message_type=OrderBookMessageType.TRADE, + content={"trade_id": 6}, + timestamp=time.time() + 1, + ) + snapshot1 = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": 1}, + timestamp=time.time() + 2, + ) + snapshot2 = OrderBookMessage( + message_type=OrderBookMessageType.SNAPSHOT, + content={"update_id": 3}, + timestamp=time.time() + 3, + ) + diff1 = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": 2}, + timestamp=time.time() + 4, + ) + diff2 = OrderBookMessage( + message_type=OrderBookMessageType.DIFF, + content={"update_id": 2}, + timestamp=t, + ) + + self.assertTrue(trade1 < trade2) # based on id + self.assertTrue(snapshot1 < snapshot2) # based on id + self.assertTrue(snapshot1 < diff1) # based on id + self.assertTrue(diff1 < snapshot2) # based on id + self.assertTrue(trade1 < snapshot1) # based on timestamp + self.assertTrue(diff2 < trade1) # if same ts, ob messages < trade messages diff --git a/test/hummingbot/core/data_type/test_trade_fee.py b/test/hummingbot/core/data_type/test_trade_fee.py new file mode 100644 index 0000000..3cae973 --- /dev/null +++ b/test/hummingbot/core/data_type/test_trade_fee.py @@ -0,0 +1,320 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.core.data_type.common import TradeType, PositionAction +from hummingbot.core.data_type.in_flight_order import TradeUpdate +from hummingbot.core.data_type.trade_fee import ( + AddedToCostTradeFee, + DeductedFromReturnsTradeFee, + TokenAmount, + TradeFeeBase, + TradeFeeSchema, +) + + +class TradeFeeTests(TestCase): + + def test_added_to_cost_spot_fee_created_for_buy_and_fee_not_deducted_from_return(self): + + schema = TradeFeeSchema( + percent_fee_token="HBOT", + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=False, + ) + + fee = TradeFeeBase.new_spot_fee( + fee_schema=schema, + trade_type=TradeType.BUY, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(AddedToCostTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + def test_deducted_from_return_spot_fee_created_for_buy_and_fee_deducted_from_return(self): + + schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=True, + ) + + fee = TradeFeeBase.new_spot_fee( + fee_schema=schema, + trade_type=TradeType.BUY, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(DeductedFromReturnsTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + def test_deducted_from_return_spot_fee_created_for_sell(self): + + schema = TradeFeeSchema( + percent_fee_token="HBOT", + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=False, + ) + + fee = TradeFeeBase.new_spot_fee( + fee_schema=schema, + trade_type=TradeType.SELL, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(DeductedFromReturnsTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + schema.percent_fee_token = None + schema.buy_percent_fee_deducted_from_returns = True + + fee = TradeFeeBase.new_spot_fee( + fee_schema=schema, + trade_type=TradeType.SELL, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(DeductedFromReturnsTradeFee, type(fee)) + + def test_added_to_cost_perpetual_fee_created_when_opening_positions(self): + + schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=False, + ) + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=schema, + position_action=PositionAction.OPEN, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(AddedToCostTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + schema.percent_fee_token = "HBOT" + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=schema, + position_action=PositionAction.OPEN, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(AddedToCostTradeFee, type(fee)) + + def test_added_to_cost_perpetual_fee_created_when_closing_position_but_schema_has_percent_fee_token(self): + + schema = TradeFeeSchema( + percent_fee_token="HBOT", + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=False, + ) + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=schema, + position_action=PositionAction.CLOSE, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(AddedToCostTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + def test_deducted_from_returns_perpetual_fee_created_when_closing_position_and_no_percent_fee_token(self): + + schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("1"), + taker_percent_fee_decimal=Decimal("1"), + buy_percent_fee_deducted_from_returns=False, + ) + + fee = TradeFeeBase.new_perpetual_fee( + fee_schema=schema, + position_action=PositionAction.CLOSE, + percent=Decimal("1.1"), + percent_token="HBOT", + flat_fees=[TokenAmount(token="COINALPHA", amount=Decimal("20"))] + ) + + self.assertEqual(DeductedFromReturnsTradeFee, type(fee)) + self.assertEqual(Decimal("1.1"), fee.percent) + self.assertEqual("HBOT", fee.percent_token) + self.assertEqual([TokenAmount(token="COINALPHA", amount=Decimal("20"))], fee.flat_fees) + + def test_added_to_cost_json_serialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = AddedToCostTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + + expected_json = { + "fee_type": AddedToCostTradeFee.type_descriptor_for_json(), + "percent": "0.5", + "percent_token": "COINALPHA", + "flat_fees": [token_amount.to_json()] + } + + self.assertEqual(expected_json, fee.to_json()) + + def test_added_to_cost_json_deserialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = AddedToCostTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + + self.assertEqual(fee, TradeFeeBase.from_json(fee.to_json())) + + def test_deducted_from_returns_json_serialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = DeductedFromReturnsTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + + expected_json = { + "fee_type": DeductedFromReturnsTradeFee.type_descriptor_for_json(), + "percent": "0.5", + "percent_token": "COINALPHA", + "flat_fees": [token_amount.to_json()] + } + + self.assertEqual(expected_json, fee.to_json()) + + def test_deducted_from_returns_json_deserialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = DeductedFromReturnsTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + + self.assertEqual(fee, TradeFeeBase.from_json(fee.to_json())) + + def test_added_to_cost_fee_amount_in_token_does_not_look_for_convertion_rate_when_percentage_zero(self): + # Configure fee to use a percent token different from the token used to request the fee value + # That forces the logic to need the convertion rate if the fee amount is calculated + fee = AddedToCostTradeFee(percent=Decimal("0"), percent_token="COINALPHA") + + fee_amount = fee.fee_amount_in_token( + trading_pair="HBOT-COINALPHA", + price=Decimal("1000"), + order_amount=Decimal("1"), + token="BNB") + + self.assertEqual(Decimal("0"), fee_amount) + + def test_deducted_from_returns_fee_amount_in_token_does_not_look_for_convertion_rate_when_percentage_zero(self): + # Configure fee to use a percent token different from the token used to request the fee value + # That forces the logic to need the convertion rate if the fee amount is calculated + fee = DeductedFromReturnsTradeFee(percent=Decimal("0"), percent_token="COINALPHA") + + fee_amount = fee.fee_amount_in_token( + trading_pair="HBOT-COINALPHA", + price=Decimal("1000"), + order_amount=Decimal("1"), + token="BNB") + + self.assertEqual(Decimal("0"), fee_amount) + + +class TokenAmountTests(TestCase): + + def test_json_serialization(self): + amount = TokenAmount(token="HBOT-COINALPHA", amount=Decimal("1000.50")) + + expected_json = { + "token": "HBOT-COINALPHA", + "amount": "1000.50", + } + + self.assertEqual(expected_json, amount.to_json()) + + def test_json_deserialization(self): + amount = TokenAmount(token="HBOT-COINALPHA", amount=Decimal("1000.50")) + + self.assertEqual(amount, TokenAmount.from_json(amount.to_json())) + + +class TradeUpdateTests(TestCase): + + def test_json_serialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = DeductedFromReturnsTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + trade_update = TradeUpdate( + trade_id="12345", + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="HBOT-COINALPHA", + fill_timestamp=1640001112, + fill_price=Decimal("1000.11"), + fill_base_amount=Decimal("2"), + fill_quote_amount=Decimal("2000.22"), + fee=fee, + ) + + expected_json = trade_update._asdict() + expected_json.update({ + "fill_price": "1000.11", + "fill_base_amount": "2", + "fill_quote_amount": "2000.22", + "fee": fee.to_json(), + }) + + self.assertEqual(expected_json, trade_update.to_json()) + + def test_json_deserialization(self): + token_amount = TokenAmount(token="COINALPHA", amount=Decimal("20.6")) + fee = DeductedFromReturnsTradeFee( + percent=Decimal("0.5"), + percent_token="COINALPHA", + flat_fees=[token_amount] + ) + trade_update = TradeUpdate( + trade_id="12345", + client_order_id="OID1", + exchange_order_id="EOID1", + trading_pair="HBOT-COINALPHA", + fill_timestamp=1640001112, + fill_price=Decimal("1000.11"), + fill_base_amount=Decimal("2"), + fill_quote_amount=Decimal("2000.22"), + fee=fee, + ) + + self.assertEqual(trade_update, TradeUpdate.from_json(trade_update.to_json())) diff --git a/test/hummingbot/core/gateway/__init__.py b/test/hummingbot/core/gateway/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/gateway/debug_collect_gatewy_perp_test_samples.py b/test/hummingbot/core/gateway/debug_collect_gatewy_perp_test_samples.py new file mode 100755 index 0000000..3304e9c --- /dev/null +++ b/test/hummingbot/core/gateway/debug_collect_gatewy_perp_test_samples.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +""" +Script for collecting the gateway http client fixture data. + +This is included for record purpose only. If you need to collect another batch of fixture data, you'll need to modify +the ETH address and nonce numbers. +""" + +import asyncio +from decimal import Decimal +from os.path import join, realpath + +from bin import path_util # noqa: F401 +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + load_client_config_map_from_file, + read_system_configs_from_yml, +) +from hummingbot.core.data_type.common import PositionSide +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + + +async def main(): + from test.mock.http_recorder import HttpRecorder + client_config_map: ClientConfigAdapter = load_client_config_map_from_file() + await read_system_configs_from_yml() + + fixture_db_path: str = realpath(join(__file__, "../fixtures/gateway_perp_http_client_fixture.db")) + http_recorder: HttpRecorder = HttpRecorder(fixture_db_path) + with http_recorder.patch_aiohttp_client(): + gateway_http_client: GatewayHttpClient = GatewayHttpClient.get_instance(client_config_map=client_config_map) + + print("ping gateway:", await gateway_http_client.ping_gateway()) + print("gateway status:", await gateway_http_client.get_gateway_status()) + print("add wallet:", + await gateway_http_client.add_wallet( + "ethereum", + "optimism", + "0000000000000000000000000000000000000000000000000000000000000001" # noqa: mock + )) + print("get wallets:", await gateway_http_client.get_wallets()) + print("get connectors:", await gateway_http_client.get_connectors()) + print("get market list:", + await gateway_http_client.get_perp_markets( + "ethereum", + "optimism", + "perp", + )) + print("get market status:", + await gateway_http_client.get_perp_market_status( + "ethereum", + "optimism", + "perp", + "AAVE", + "USD", + )) + print("get market price:", + await gateway_http_client.get_perp_market_price( + "ethereum", + "optimism", + "perp", + "AAVE", + "USD", + Decimal("0.1"), + PositionSide.LONG, + )) + print("get USD balance:", + await gateway_http_client.amm_perp_balance( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + )) + print("open long position:", + await gateway_http_client.amm_perp_open( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + PositionSide.LONG, + Decimal("0.1"), + Decimal("63"), + )) + print("get active position:", + await gateway_http_client.get_perp_position( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + )) + print("close long position:", + await gateway_http_client.amm_perp_close( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + )) + + +if __name__ == "__main__": + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + try: + ev_loop.run_until_complete(main()) + except KeyboardInterrupt: + pass diff --git a/test/hummingbot/core/gateway/debug_collect_gatewy_test_samples.py b/test/hummingbot/core/gateway/debug_collect_gatewy_test_samples.py new file mode 100755 index 0000000..b366438 --- /dev/null +++ b/test/hummingbot/core/gateway/debug_collect_gatewy_test_samples.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +""" +Script for collecting the gateway http client fixture data. + +This is included for record purpose only. If you need to collect another batch of fixture data, you'll need to modify +the ETH address and nonce numbers. +""" + +import asyncio +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpRecorder + +from bin import path_util # noqa: F401 +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + load_client_config_map_from_file, + read_system_configs_from_yml, +) +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + + +async def main(): + client_config_map: ClientConfigAdapter = load_client_config_map_from_file() + await read_system_configs_from_yml() + client_config_map.gateway.gateway_api_port = 5000 + + fixture_db_path: str = realpath(join(__file__, "../fixtures/gateway_http_client_fixture.db")) + http_recorder: HttpRecorder = HttpRecorder(fixture_db_path) + with http_recorder.patch_aiohttp_client(): + gateway_http_client: GatewayHttpClient = GatewayHttpClient() + + print("ping gateway:", await gateway_http_client.ping_gateway()) + print("gateway status:", await gateway_http_client.get_gateway_status()) + print("add wallet:", + await gateway_http_client.add_wallet( + "ethereum", + "ropsten", + "0000000000000000000000000000000000000000000000000000000000000001" # noqa: mock + )) + print("get wallets:", await gateway_http_client.get_wallets()) + print("get connectors:", await gateway_http_client.get_connectors()) + print("set configuration:", await gateway_http_client.update_config("telemetry.enabled", False)) + print("get configuration:", await gateway_http_client.get_configuration()) + print("get tokens:", await gateway_http_client.get_tokens("ethereum", "ropsten")) + print("get network status:", await gateway_http_client.get_network_status("ethereum", "ropsten")) + print("get price:", + await gateway_http_client.get_price( + "ethereum", + "ropsten", + "uniswap", + "DAI", + "WETH", + Decimal(1000), + TradeType.BUY + )) + print("get balances:", + await gateway_http_client.get_balances( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + ["WETH", "DAI"], + )) + print("get transaction status:", + await gateway_http_client.get_transaction_status( + "ethereum", + "ropsten", + "0xa8d428627dc7f453be79a32129dc18ea29d1a715249a4a5762ca6273da5d96e3" # noqa: mock + )) + print("get transaction status:", + await gateway_http_client.get_transaction_status( + "ethereum", + "ropsten", + "0xa8d428627dc7f453be79a32129dc18ea29d1a715249a4a5762ca6273da5d96e1" # noqa: mock + )) + print("get evm nonce:", + await gateway_http_client.get_evm_nonce( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92" + )) + print("approve WETH:", + await gateway_http_client.approve_token( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + "WETH", + "uniswap", + 2 + )) + print("approve DAI:", + await gateway_http_client.approve_token( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + "DAI", + "uniswap", + 3 + )) + print("get WETH, DAI allowance:", + await gateway_http_client.get_allowances( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + ["WETH", "DAI"], + "uniswap" + )) + print("buy DAI with WETH:", + await gateway_http_client.amm_trade( + "ethereum", + "ropsten", + "uniswap", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + "DAI", + "WETH", + TradeType.BUY, + Decimal(1000), + Decimal("0.00266"), + 4 + )) + print("get signature:", + await gateway_http_client.wallet_sign( + chain="avalanche", + network="dexalot", + address="0x010216bB52E46807a07d0101Bb828bA547534F37", # noqa: mock + message="dexalot", + )) + + +if __name__ == "__main__": + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + try: + ev_loop.run_until_complete(main()) + except KeyboardInterrupt: + pass diff --git a/test/hummingbot/core/gateway/fixtures/gateway_http_client_clob_fixture.db b/test/hummingbot/core/gateway/fixtures/gateway_http_client_clob_fixture.db new file mode 100644 index 0000000..574f14e Binary files /dev/null and b/test/hummingbot/core/gateway/fixtures/gateway_http_client_clob_fixture.db differ diff --git a/test/hummingbot/core/gateway/fixtures/gateway_http_client_fixture.db b/test/hummingbot/core/gateway/fixtures/gateway_http_client_fixture.db new file mode 100644 index 0000000..763903b Binary files /dev/null and b/test/hummingbot/core/gateway/fixtures/gateway_http_client_fixture.db differ diff --git a/test/hummingbot/core/gateway/fixtures/gateway_perp_http_client_fixture.db b/test/hummingbot/core/gateway/fixtures/gateway_perp_http_client_fixture.db new file mode 100644 index 0000000..5f1eb8a Binary files /dev/null and b/test/hummingbot/core/gateway/fixtures/gateway_perp_http_client_fixture.db differ diff --git a/test/hummingbot/core/gateway/test_gateway_http_client.py b/test/hummingbot/core/gateway/test_gateway_http_client.py new file mode 100644 index 0000000..cf14a0e --- /dev/null +++ b/test/hummingbot/core/gateway/test_gateway_http_client.py @@ -0,0 +1,224 @@ +import asyncio +import unittest +from contextlib import ExitStack +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Any, Dict, List +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test + +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + +class GatewayHttpClientUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls._db_path = realpath(join(__file__, "../fixtures/gateway_http_client_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", return_value=ClientSession()) + ) + GatewayHttpClient.get_instance().base_url = "https://localhost:5000" + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + + @async_test(loop=ev_loop) + async def test_ping_gateway(self): + result: bool = await GatewayHttpClient.get_instance().ping_gateway() + self.assertTrue(result) + + @async_test(loop=ev_loop) + async def test_get_gateway_status(self): + result: List[Dict[str, Any]] = await GatewayHttpClient.get_instance().get_gateway_status() + self.assertIsInstance(result, list) + self.assertEqual(1, len(result)) + + first_entry: Dict[str, Any] = result[0] + self.assertEqual(3, first_entry["chainId"]) + + @async_test(loop=ev_loop) + async def test_add_wallet(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().add_wallet( + "ethereum", + "ropsten", + "0000000000000000000000000000000000000000000000000000000000000001" # noqa: mock + ) + self.assertTrue(isinstance(result, dict)) + self.assertEqual("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", result["address"]) + + @async_test(loop=ev_loop) + async def test_get_wallets(self): + result: List[Dict[str, Any]] = await GatewayHttpClient.get_instance().get_wallets() + self.assertIsInstance(result, list) + + first_entry: Dict[str, Any] = result[0] + self.assertIn("chain", first_entry) + self.assertIn("walletAddresses", first_entry) + + @async_test(loop=ev_loop) + async def test_get_connectors(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_connectors() + self.assertIn("connectors", result) + + uniswap: Dict[str, Any] = result["connectors"][0] + self.assertEqual("uniswap", uniswap["name"]) + self.assertEqual(["AMM"], uniswap["trading_type"]) + + @async_test(loop=ev_loop) + async def test_get_configuration(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_configuration() + self.assertIn("avalanche", result) + self.assertIn("ethereum", result) + + @async_test(loop=ev_loop) + async def test_update_configuration(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().update_config("telemetry.enabled", False) + self.assertIn("message", result) + + @async_test(loop=ev_loop) + async def test_get_tokens(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_tokens("ethereum", "ropsten") + self.assertIn("tokens", result) + self.assertIsInstance(result["tokens"], list) + self.assertEqual("WETH", result["tokens"][0]["symbol"]) + self.assertEqual("DAI", result["tokens"][1]["symbol"]) + + @async_test(loop=ev_loop) + async def test_get_network_status(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_network_status("ethereum", "ropsten") + self.assertEqual(3, result["chainId"]) + self.assertEqual(12067035, result["currentBlockNumber"]) + + @async_test(loop=ev_loop) + async def test_get_price(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_price( + "ethereum", + "ropsten", + "uniswap", + "DAI", + "WETH", + Decimal(1000), + TradeType.BUY + ) + self.assertEqual("1000.000000000000000000", result["amount"]) + self.assertEqual("1000000000000000000000", result["rawAmount"]) + self.assertEqual("0.00262343", result["price"]) + + @async_test(loop=ev_loop) + async def test_get_balances(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_balances( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + ["WETH", "DAI"], + ) + self.assertIn("balances", result) + self.assertEqual("21.000000000000000000", result["balances"]["WETH"]) + self.assertEqual("0.000000000000000000", result["balances"]["DAI"]) + + @async_test(loop=ev_loop) + async def test_successful_get_transaction(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_transaction_status( + "ethereum", + "ropsten", + "0xa8d428627dc7f453be79a32129dc18ea29d1a715249a4a5762ca6273da5d96e3" # noqa: mock + ) + self.assertEqual(1, result["txStatus"]) + self.assertIsNotNone(result["txData"]) + + @async_test(loop=ev_loop) + async def test_failed_get_transaction(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_transaction_status( + "ethereum", + "ropsten", + "0xa8d428627dc7f453be79a32129dc18ea29d1a715249a4a5762ca6273da5d96e1" # noqa: mock + ) + self.assertEqual(-1, result["txStatus"]) + self.assertIsNone(result["txData"]) + + @async_test(loop=ev_loop) + async def test_get_evm_nonce(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_evm_nonce( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92" + ) + self.assertEqual(2, result["nonce"]) + + @async_test(loop=ev_loop) + async def test_approve_token(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().approve_token( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + "WETH", + "uniswap", + 2 + ) + self.assertEqual("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", result["spender"]) + self.assertEqual("0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff9", # noqa: mock + result["approval"]["hash"]) + + @async_test(loop=ev_loop) + async def test_get_allowances(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_allowances( + "ethereum", + "ropsten", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + ["WETH", "DAI"], + "uniswap" + ) + self.assertIn("approvals", result) + self.assertEqual("115792089237316195423570985008687907853269984665640564039457.584007913129639935", + result["approvals"]["DAI"]) + self.assertEqual("115792089237316195423570985008687907853269984665640564039457.584007913129639935", + result["approvals"]["WETH"]) + + @async_test(loop=ev_loop) + async def test_amm_trade(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().amm_trade( + "ethereum", + "ropsten", + "uniswap", + "0x5821715133bB451bDE2d5BC6a4cE3430a4fdAF92", + "DAI", + "WETH", + TradeType.BUY, + Decimal(1000), + Decimal("0.00266"), + 4 + ) + self.assertEqual("1000.000000000000000000", result["amount"]) + self.assertEqual("1000000000000000000000", result["rawAmount"]) + self.assertEqual("0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + result["txHash"]) + + @async_test(loop=ev_loop) + async def test_successful_wallet_sign(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().wallet_sign( + chain="avalanche", + network="dexalot", + address="0x010216bB52E46807a07d0101Bb828bA547534F37", # noqa: mock + message="dexalot", + ) + self.assertIn("signature", result) + self.assertEqual( + result["signature"], + "0x7e26cf881393f098dd8ff1adf459c01414c28e30fb05e6f2b2d5d0e2e284234b5964d4134cab95affc2219" # noqa: mock + "55a4956ce8f5ed3c5a80e94143808063b26e7774421f", + ) # noqa: mock diff --git a/test/hummingbot/core/gateway/test_gateway_http_client_clob.py b/test/hummingbot/core/gateway/test_gateway_http_client_clob.py new file mode 100644 index 0000000..ab47c8e --- /dev/null +++ b/test/hummingbot/core/gateway/test_gateway_http_client_clob.py @@ -0,0 +1,201 @@ +import asyncio +import unittest +from contextlib import ExitStack +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Any, Dict +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test + +from hummingbot.connector.gateway.gateway_in_flight_order import GatewayInFlightOrder +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + +class GatewayHttpClientUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls._db_path = realpath(join(__file__, "../fixtures/gateway_http_client_clob_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch( + "hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", + return_value=ClientSession(), + ) + ) + GatewayHttpClient.get_instance().base_url = "https://localhost:5000" + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + + @async_test(loop=ev_loop) + async def test_clob_place_order(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().clob_place_order( + connector="injective", + chain="injective", + network="mainnet", + trading_pair=combine_to_hb_trading_pair(base="COIN", quote="ALPHA"), + address="0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("10"), + size=Decimal("2"), + ) + + self.assertEqual("mainnet", result["network"]) + self.assertEqual(1647066435595, result["timestamp"]) + self.assertEqual(2, result["latency"]) + self.assertEqual("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", result["txHash"]) # noqa: mock + + @async_test(loop=ev_loop) + async def test_clob_cancel_order(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().clob_cancel_order( + connector="injective", + chain="injective", + network="mainnet", + trading_pair=combine_to_hb_trading_pair(base="COIN", quote="ALPHA"), + address="0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + exchange_order_id="0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff9", # noqa: mock + ) + + self.assertEqual("mainnet", result["network"]) + self.assertEqual(1647066436595, result["timestamp"]) + self.assertEqual(2, result["latency"]) + self.assertEqual("0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", result["txHash"]) # noqa: mock + + @async_test(loop=ev_loop) + async def test_clob_order_status_updates(self): + result = await GatewayHttpClient.get_instance().get_clob_order_status_updates( + trading_pair="COIN-ALPHA", + chain="injective", + network="mainnet", + connector="injective", + address="0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + ) + + self.assertEqual(2, len(result["orders"])) + self.assertEqual("EOID1", result["orders"][0]["exchangeID"]) + self.assertEqual("EOID2", result["orders"][1]["exchangeID"]) + + result = await GatewayHttpClient.get_instance().get_clob_order_status_updates( + trading_pair="COIN-ALPHA", + chain="injective", + network="mainnet", + connector="injective", + address="0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + exchange_order_id="0x66b533792f45780fc38573bfd60d6043ab266471607848fb71284cd0d9eecff9", # noqa: mock + ) + + self.assertEqual(1, len(result["orders"])) + self.assertEqual("EOID1", result["orders"][0]["exchangeID"]) + + @async_test(loop=ev_loop) + async def test_get_clob_all_markets(self): + result = await GatewayHttpClient.get_instance().get_clob_markets( + connector="dexalot", chain="avalanche", network="mainnet" + ) + + self.assertEqual(2, len(result["markets"])) + self.assertEqual("COIN-ALPHA", result["markets"][1]["tradingPair"]) + + @async_test(loop=ev_loop) + async def test_get_clob_single_market(self): + result = await GatewayHttpClient.get_instance().get_clob_markets( + connector="dexalot", chain="avalanche", network="mainnet", trading_pair="COIN-ALPHA" + ) + + self.assertEqual(1, len(result["markets"])) + self.assertEqual("COIN-ALPHA", result["markets"][0]["tradingPair"]) + + @async_test(loop=ev_loop) + async def test_get_clob_orderbook(self): + result = await GatewayHttpClient.get_instance().get_clob_orderbook_snapshot( + trading_pair="COIN-ALPHA", connector="dexalot", chain="avalanche", network="mainnet" + ) + + expected_orderbook = { + "bids": [[1, 2], [3, 4]], + "asks": [[5, 6]], + } + self.assertEqual(expected_orderbook, result["orderbook"]) + + @async_test(loop=ev_loop) + async def test_get_clob_ticker(self): + result = await GatewayHttpClient.get_instance().get_clob_ticker( + connector="dexalot", chain="avalanche", network="mainnet" + ) + expected_markets = [ + { + "pair": "COIN-ALPHA", + "lastPrice": 9, + }, + { + "pair": "BTC-USDT", + "lastPrice": 10, + } + ] + + self.assertEqual(expected_markets, result["markets"]) + + result = await GatewayHttpClient.get_instance().get_clob_ticker( + connector="dexalot", chain="avalanche", network="mainnet", trading_pair="COIN-ALPHA" + ) + expected_markets = [ + { + "pair": "COIN-ALPHA", + "lastPrice": 9, + }, + ] + + self.assertEqual(expected_markets, result["markets"]) + + @async_test(loop=ev_loop) + async def test_clob_batch_order_update(self): + trading_pair = combine_to_hb_trading_pair(base="COIN", quote="ALPHA") + order_to_create = GatewayInFlightOrder( + client_order_id="someOrderIDCreate", + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + creation_timestamp=123123123, + amount=Decimal("10"), + price=Decimal("100"), + ) + order_to_cancel = GatewayInFlightOrder( + client_order_id="someOrderIDCancel", + trading_pair=trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.SELL, + creation_timestamp=123123123, + price=Decimal("90"), + amount=Decimal("9"), + exchange_order_id="someExchangeOrderID", + ) + result: Dict[str, Any] = await GatewayHttpClient.get_instance().clob_batch_order_modify( + connector="injective", + chain="injective", + network="mainnet", + address="0xc7287236f64484b476cfbec0fd21bc49d85f8850c8885665003928a122041e18", # noqa: mock + orders_to_create=[order_to_create], + orders_to_cancel=[order_to_cancel], + ) + + self.assertEqual("mainnet", result["network"]) + self.assertEqual(1647066456595, result["timestamp"]) + self.assertEqual(3, result["latency"]) + self.assertEqual("0x7E5F4552091A69125d5DfCb7b8C2659029395Ceg", result["txHash"]) # noqa: mock diff --git a/test/hummingbot/core/gateway/test_gateway_perp_http_endpoints.py b/test/hummingbot/core/gateway/test_gateway_perp_http_endpoints.py new file mode 100644 index 0000000..f3d27af --- /dev/null +++ b/test/hummingbot/core/gateway/test_gateway_perp_http_endpoints.py @@ -0,0 +1,130 @@ +import asyncio +import unittest +from contextlib import ExitStack +from decimal import Decimal +from os.path import join, realpath +from test.mock.http_recorder import HttpPlayer +from typing import Any, Dict +from unittest.mock import patch + +from aiohttp import ClientSession +from aiounittest import async_test + +from hummingbot.core.data_type.common import PositionSide +from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + +class GatewayHttpClientUnitTest(unittest.TestCase): + _db_path: str + _http_player: HttpPlayer + _patch_stack: ExitStack + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls._db_path = realpath(join(__file__, "../fixtures/gateway_perp_http_client_fixture.db")) + cls._http_player = HttpPlayer(cls._db_path) + cls._patch_stack = ExitStack() + cls._patch_stack.enter_context(cls._http_player.patch_aiohttp_client()) + cls._patch_stack.enter_context( + patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient._http_client", return_value=ClientSession()) + ) + GatewayHttpClient.get_instance().base_url = "https://localhost:5000" + + @classmethod + def tearDownClass(cls) -> None: + cls._patch_stack.close() + + @async_test(loop=ev_loop) + async def test_gateway_list(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_perp_markets( + "ethereum", + "optimism", + "perp", + ) + self.assertTrue(isinstance(result["pairs"], list)) + + @async_test(loop=ev_loop) + async def test_gateway_status(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_perp_market_status( + "ethereum", + "optimism", + "perp", + "AAVE", + "USD", + ) + self.assertTrue(isinstance(result, dict)) + self.assertTrue(result["isActive"]) + + @async_test(loop=ev_loop) + async def test_gateway_price(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_perp_market_price( + "ethereum", + "optimism", + "perp", + "AAVE", + "USD", + Decimal("0.1"), + PositionSide.LONG, + ) + self.assertTrue(isinstance(result, dict)) + self.assertEqual("72.3961573502110110555952936205735574981841", result["markPrice"]) + self.assertEqual("72.46", result["indexPrice"]) + self.assertEqual("72.71790773", result["indexTwapPrice"]) + + @async_test(loop=ev_loop) + async def test_perp_balance(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().amm_perp_balance( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + ) + self.assertEqual("209.992983", result["balance"]) + + @async_test(loop=ev_loop) + async def test_perp_open(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().amm_perp_open( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + PositionSide.LONG, + Decimal("0.1"), + Decimal("63"), + ) + self.assertEqual("0.100000000000000000", result["amount"]) + self.assertEqual("0x48e65c8888282f268154146aa810a0da9bf10a3ffc2f98a298cf48a0fee864ac", # noqa: mock + result["txHash"]) + + @async_test(loop=ev_loop) + async def test_gateway_perp_position(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().get_perp_position( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + ) + self.assertTrue(isinstance(result, dict)) + self.assertEqual("LONG", result["positionSide"]) + self.assertEqual("AAVEUSD", result["tickerSymbol"]) + self.assertEqual("72.54886162448166764354", result["entryPrice"]) + + @async_test(loop=ev_loop) + async def test_perp_close(self): + result: Dict[str, Any] = await GatewayHttpClient.get_instance().amm_perp_close( + "ethereum", + "optimism", + "perp", + "0xefB7Be8631d154d4C0ad8676FEC0897B2894FE8F", + "AAVE", + "USD", + ) + self.assertEqual("0x4ad832e07778d077af763e96395b4924ae84634a30b296f6f450d7c54e599da5", # noqa: mock + result["txHash"]) diff --git a/test/hummingbot/core/gateway/test_gateway_utils.py b/test/hummingbot/core/gateway/test_gateway_utils.py new file mode 100644 index 0000000..297b4fa --- /dev/null +++ b/test/hummingbot/core/gateway/test_gateway_utils.py @@ -0,0 +1,14 @@ +import unittest + +from hummingbot.core.gateway.utils import unwrap_token_symbol + + +class GatewayUtilsTest(unittest.TestCase): + def test_unwrap_token_symbols(self): + self.assertEqual("ETH", unwrap_token_symbol("WETH")) + self.assertEqual("ETH", unwrap_token_symbol("WETH.e")) + self.assertEqual("AVAX", unwrap_token_symbol("WAVAX")) + self.assertEqual("DAI", unwrap_token_symbol("DAI.e")) + self.assertEqual("DAI", unwrap_token_symbol("DAI.E")) + self.assertEqual("WAVE", unwrap_token_symbol("WAVE")) + self.assertEqual("stETH", unwrap_token_symbol("wstETH")) diff --git a/test/hummingbot/core/mock_api/__init__.py b/test/hummingbot/core/mock_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/mock_api/test_mock_web_server.py b/test/hummingbot/core/mock_api/test_mock_web_server.py new file mode 100644 index 0000000..8bffc5d --- /dev/null +++ b/test/hummingbot/core/mock_api/test_mock_web_server.py @@ -0,0 +1,73 @@ +import asyncio +from aiohttp import ClientSession +import unittest.mock +import requests +import json +from hummingbot.core.mock_api.mock_web_server import MockWebServer + + +class MockWebServerTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + cls.web_app: MockWebServer = MockWebServer.get_instance() + cls.host = "www.google.com" + cls.web_app.add_host_to_mock(cls.host) + cls.web_app.start() + cls.ev_loop.run_until_complete(cls.web_app.wait_til_started()) + cls._patcher = unittest.mock.patch("aiohttp.client.URL") + cls._url_mock = cls._patcher.start() + cls._url_mock.side_effect = MockWebServer.reroute_local + + cls._req_patcher = unittest.mock.patch.object(requests.Session, "request", autospec=True) + cls._req_url_mock = cls._req_patcher.start() + cls._req_url_mock.side_effect = MockWebServer.reroute_request + + @classmethod + def tearDownClass(cls) -> None: + cls.web_app.stop() + cls._patcher.stop() + cls._req_patcher.stop() + + async def _test_web_app_response(self): + self.web_app.clear_responses() + self.web_app.update_response("get", self.host, "/", data=self.web_app.TEST_RESPONSE, is_json=False) + async with ClientSession() as client: + async with client.get("http://www.google.com/") as resp: + text: str = await resp.text() + print(text) + self.assertEqual(self.web_app.TEST_RESPONSE, text) + + def test_web_app_response(self): + self.ev_loop.run_until_complete(asyncio.wait_for(self._test_web_app_response(), 20)) + + def test_get_request_response(self): + self.web_app.clear_responses() + self.web_app.update_response("get", self.host, "/", data=self.web_app.TEST_RESPONSE, is_json=False) + r = requests.get("http://www.google.com/") + self.assertEqual(self.web_app.TEST_RESPONSE, r.text) + + def test_update_response(self): + self.web_app.clear_responses() + self.web_app.update_response('get', 'www.google.com', '/', {"a": 1, "b": 2}) + r = requests.get("http://www.google.com/") + r_json = json.loads(r.text) + self.assertEqual(r_json["a"], 1) + + self.web_app.update_response('post', 'www.google.com', '/', "default") + self.web_app.update_response('post', 'www.google.com', '/', {"a": 1, "b": 2}, params={"para_a": '11'}) + r = requests.post("http://www.google.com/", data={"para_a": 11, "para_b": 22}) + r_json = json.loads(r.text) + self.assertEqual(r_json["a"], 1) + + def test_query_string(self): + self.web_app.clear_responses() + self.web_app.update_response('get', 'www.google.com', '/', "default") + self.web_app.update_response('get', 'www.google.com', '/', {"a": 1}, params={"qs1": "1"}) + r = requests.get("http://www.google.com/?qs1=1") + r_json = json.loads(r.text) + self.assertEqual(r_json["a"], 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/hummingbot/core/mock_api/test_mock_web_socket_server.py b/test/hummingbot/core/mock_api/test_mock_web_socket_server.py new file mode 100644 index 0000000..972e967 --- /dev/null +++ b/test/hummingbot/core/mock_api/test_mock_web_socket_server.py @@ -0,0 +1,59 @@ +import aiohttp +from aiounittest import async_test +import asyncio +import unittest.mock +import json + +from hummingbot.core.mock_api.mock_web_socket_server import MockWebSocketServerFactory + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + +class MockWebSocketServerFactoryTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.ws_server = MockWebSocketServerFactory.start_new_server("wss://www.google.com/ws/") + cls._patcher = unittest.mock.patch("aiohttp.client.ClientSession.ws_connect", autospec=True) + cls._mock = cls._patcher.start() + cls._mock.side_effect = MockWebSocketServerFactory.reroute_ws_connect + # need to wait a bit for the server to be available + ev_loop.run_until_complete(asyncio.wait_for(cls.ws_server.wait_til_started(), 1)) + + @classmethod + def tearDownClass(cls) -> None: + cls.ws_server.stop() + cls._patcher.stop() + + @async_test(loop=ev_loop) + async def test_web_socket(self): + uri = "wss://www.google.com/ws/" + + # Retry up to 3 times if there is any error connecting to the mock server address and port + async with aiohttp.ClientSession() as client: + for retry_attempt in range(3): + try: + async with client.ws_connect(uri) as websocket: + await MockWebSocketServerFactory.send_str(uri, "aaa") + answer = await websocket.receive_str() + self.assertEqual("aaa", answer) + + await MockWebSocketServerFactory.send_json(uri, data={"foo": "bar"}) + answer = await websocket.receive_str() + answer = json.loads(answer) + self.assertEqual(answer["foo"], "bar") + + await self.ws_server.websocket.send_str("xxx") + answer = await websocket.receive_str() + self.assertEqual("xxx", answer) + except OSError: + if retry_attempt == 2: + raise + # Continue retrying + continue + + # Stop the retries cycle + break + + +if __name__ == '__main__': + unittest.main() diff --git a/test/hummingbot/core/rate_oracle/__init__.py b/test/hummingbot/core/rate_oracle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/rate_oracle/sources/__init__.py b/test/hummingbot/core/rate_oracle/sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/rate_oracle/sources/test_ascend_ex_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_ascend_ex_rate_source.py new file mode 100644 index 0000000..cb0d2a5 --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_ascend_ex_rate_source.py @@ -0,0 +1,66 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.ascend_ex import ascend_ex_constants as CONSTANTS +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.ascend_ex_rate_source import AscendExRateSource + + +class AscendExRateSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.ignored_trading_pair = combine_to_hb_trading_pair(base="SOME", quote="PAIR") + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def setup_ascend_ex_responses(self, mock_api, expected_rate: Decimal): + symbols_url = f"{CONSTANTS.PUBLIC_REST_URL}{CONSTANTS.PRODUCTS_PATH_URL}" + symbols_response = { # truncated response + "code": 0, + "data": [ + { + "symbol": f"{self.target_token}/{self.global_token}", + "baseAsset": self.target_token, + "quoteAsset": self.global_token, + "statusCode": "Normal", + }, + {"symbol": "SOME/PAIR", "baseAsset": "SOME", "quoteAsset": "PAIR", "statusCode": "Normal"}, + ], + } + mock_api.get(url=symbols_url, body=json.dumps(symbols_response)) + prices_url = f"{CONSTANTS.PUBLIC_REST_URL}{CONSTANTS.TICKER_PATH_URL}" + prices_response = { # truncated response + "code": 0, + "data": [ + { + "symbol": f"{self.target_token}/{self.global_token}", + "ask": [str(expected_rate + Decimal("0.1")), "43641"], + "bid": [str(expected_rate - Decimal("0.1")), "443"], + } + ], + } + mock_api.get(url=prices_url, body=json.dumps(prices_response)) + + @aioresponses() + def test_get_prices(self, mock_api): + expected_rate = Decimal("10") + self.setup_ascend_ex_responses(mock_api=mock_api, expected_rate=expected_rate) + + rate_source = AscendExRateSource() + prices = self.async_run_with_timeout(rate_source.get_prices()) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + self.assertNotIn(self.ignored_trading_pair, prices) diff --git a/test/hummingbot/core/rate_oracle/sources/test_binance_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_binance_rate_source.py new file mode 100644 index 0000000..091816d --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_binance_rate_source.py @@ -0,0 +1,109 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils as web_utils +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.binance_rate_source import BinanceRateSource + + +class BinanceRateSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.binance_pair = f"{cls.target_token}{cls.global_token}" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.binance_us_pair = f"{cls.target_token}USD" + cls.us_trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote="USD") + cls.binance_ignored_pair = "SOMEPAIR" + cls.ignored_trading_pair = combine_to_hb_trading_pair(base="SOME", quote="PAIR") + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def setup_binance_responses(self, mock_api, expected_rate: Decimal): + pairs_us_url = web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain="us") + pairs_url = web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL) + symbols_response = { # truncated + "symbols": [ + { + "symbol": self.binance_pair, + "status": "TRADING", + "baseAsset": self.target_token, + "quoteAsset": self.global_token, + "permissions": [ + "SPOT", + ], + }, + { + "symbol": self.binance_us_pair, + "status": "TRADING", + "baseAsset": self.target_token, + "quoteAsset": "USD", + "permissions": [ + "SPOT", + ], + }, + { + "symbol": self.binance_ignored_pair, + "status": "PAUSED", + "baseAsset": "SOME", + "quoteAsset": "PAIR", + "permissions": [ + "SPOT", + ], + }, + ] + } + binance_prices_us_url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_BOOK_PATH_URL, domain="us") + binance_prices_us_response = [ + { + "symbol": self.binance_us_pair, + "bidPrice": "20862.0000", + "bidQty": "0.50000000", + "askPrice": "20865.6100", + "askQty": "0.14500000", + }, + { + "symbol": self.binance_ignored_pair, + "bidPrice": "0", + "bidQty": "0", + "askPrice": "0", + "askQty": "0", + } + ] + binance_prices_global_url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_BOOK_PATH_URL) + binance_prices_global_response = [ + { + "symbol": self.binance_pair, + "bidPrice": str(expected_rate - Decimal("0.1")), + "bidQty": "0.50000000", + "askPrice": str(expected_rate + Decimal("0.1")), + "askQty": "0.14500000", + } + ] + mock_api.get(pairs_us_url, body=json.dumps(symbols_response)) + mock_api.get(pairs_url, body=json.dumps(symbols_response)) + mock_api.get(binance_prices_us_url, body=json.dumps(binance_prices_us_response)) + mock_api.get(binance_prices_global_url, body=json.dumps(binance_prices_global_response)) + + @aioresponses() + def test_get_binance_prices(self, mock_api): + expected_rate = Decimal("10") + self.setup_binance_responses(mock_api=mock_api, expected_rate=expected_rate) + + rate_source = BinanceRateSource() + prices = self.async_run_with_timeout(rate_source.get_prices()) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + self.assertIn(self.us_trading_pair, prices) + self.assertNotIn(self.ignored_trading_pair, prices) diff --git a/test/hummingbot/core/rate_oracle/sources/test_coin_cap_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_coin_cap_rate_source.py new file mode 100644 index 0000000..c01fc1e --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_coin_cap_rate_source.py @@ -0,0 +1,274 @@ +import asyncio +import json +import re +import unittest +from decimal import Decimal +from typing import Awaitable, Optional +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.rate_oracle.sources.coin_cap_rate_source import CoinCapRateSource +from hummingbot.data_feed.coin_cap_data_feed import coin_cap_constants as CONSTANTS + + +class CoinCapRateSourceTest(unittest.TestCase): + level = 0 + target_token: str + target_asset_id: str + global_token: str + trading_pair: str + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.target_token = "COINALPHA" + cls.target_asset_id = "some CoinAlpha ID" + cls.global_token = CONSTANTS.UNIVERSAL_QUOTE_TOKEN + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.rate_source = CoinCapRateSource(assets_map={}, api_key="") + self.rate_source._coin_cap_data_feed.logger().setLevel(1) + self.rate_source._coin_cap_data_feed.logger().addHandler(self) + self.mocking_assistant = NetworkMockingAssistant() + self.rate_source._coin_cap_data_feed._get_api_factory() + self._web_socket_mock = self.mocking_assistant.configure_web_assistants_factory( + web_assistants_factory=self.rate_source._coin_cap_data_feed._api_factory + ) + + def handle(self, record): + self.log_records.append(record) + + @staticmethod + def async_run_with_timeout(coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_coin_cap_assets_data_mock( + self, + asset_symbol: str, + asset_price: Decimal, + asset_id: Optional[str] = None, + ): + data = { + "data": [ + { + "id": asset_id or self.target_asset_id, + "rank": "1", + "symbol": asset_symbol, + "name": "Bitcoin", + "supply": "19351375.0000000000000000", + "maxSupply": "21000000.0000000000000000", + "marketCapUsd": "560124156928.7894433300126125", + "volumeUsd24Hr": "8809682089.3591086933779149", + "priceUsd": str(asset_price), + "changePercent24Hr": "-3.7368339984395858", + "vwap24Hr": "29321.6954689987292113", + "explorer": "https://blockchain.info/", + }, + { + "id": "bitcoin-bep2", + "rank": "36", + "symbol": "BTCB", + "name": "Bitcoin BEP2", + "supply": "53076.5813160500000000", + "maxSupply": None, + "marketCapUsd": "1535042933.7400446414478907", + "volumeUsd24Hr": "545107668.1789385958198549", + "priceUsd": "28921.2849749962704851", + "changePercent24Hr": "-3.6734367191141411", + "vwap24Hr": "29306.9911285134523131", + "explorer": "https://explorer.binance.org/asset/BTCB-1DE", + }, + ], + "timestamp": 1681975911184, + } + return data + + @aioresponses() + def test_get_prices(self, mock_api: aioresponses): + expected_rate = Decimal("20") + + data = self.get_coin_cap_assets_data_mock(asset_symbol=self.target_token, asset_price=expected_rate) + url = f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.ALL_ASSETS_ENDPOINT}" + url_regex = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get( + url=url_regex, + body=json.dumps(data), + headers={ + "X-Ratelimit-Remaining": str(CONSTANTS.NO_KEY_LIMIT - 1), + "X-Ratelimit-Limit": str(CONSTANTS.NO_KEY_LIMIT), + }, + ) + + prices = self.async_run_with_timeout(self.rate_source.get_prices(quote_token="SOMETOKEN")) + + self.assertEqual(prices, {}) + + prices = self.async_run_with_timeout( + coroutine=self.rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + + @aioresponses() + def test_check_network(self, mock_api: aioresponses): + url = f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.HEALTH_CHECK_ENDPOINT}" + mock_api.get(url, exception=Exception()) + + status = self.async_run_with_timeout(coroutine=self.rate_source.check_network()) + self.assertEqual(NetworkStatus.NOT_CONNECTED, status) + + mock_api.get( + url, + body=json.dumps({}), + headers={ + "X-Ratelimit-Remaining": str(CONSTANTS.NO_KEY_LIMIT - 1), + "X-Ratelimit-Limit": str(CONSTANTS.NO_KEY_LIMIT), + }, + ) + + status = self.async_run_with_timeout(coroutine=self.rate_source.check_network()) + self.assertEqual(NetworkStatus.CONNECTED, status) + + @aioresponses() + def test_ws_stream_prices(self, mock_api: aioresponses): + # initial request + rest_rate = Decimal("20") + data = self.get_coin_cap_assets_data_mock(asset_symbol=self.target_token, asset_price=rest_rate) + assets_map = { + asset_data["symbol"]: asset_data["id"] for asset_data in data["data"] + } + rate_source = CoinCapRateSource(assets_map=assets_map, api_key="") + rate_source._coin_cap_data_feed._get_api_factory() + web_socket_mock = self.mocking_assistant.configure_web_assistants_factory( + web_assistants_factory=rate_source._coin_cap_data_feed._api_factory + ) + url = f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.ALL_ASSETS_ENDPOINT}" + url_regex = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get( + url=url_regex, + body=json.dumps(data), + headers={ + "X-Ratelimit-Remaining": str(CONSTANTS.NO_KEY_LIMIT - 1), + "X-Ratelimit-Limit": str(CONSTANTS.NO_KEY_LIMIT), + }, + repeat=True, + ) + + self.async_run_with_timeout(coroutine=rate_source.start_network()) + + prices = self.async_run_with_timeout( + coroutine=rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(rest_rate, prices[self.trading_pair]) + + streamed_rate = rest_rate + Decimal("1") + stream_response = {self.target_asset_id: str(streamed_rate)} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=web_socket_mock, message=json.dumps(stream_response) + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=web_socket_mock) + + prices = self.async_run_with_timeout( + coroutine=rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(streamed_rate, prices[self.trading_pair]) + + self.async_run_with_timeout(coroutine=rate_source.stop_network()) + + prices = self.async_run_with_timeout( + coroutine=rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(rest_rate, prices[self.trading_pair]) # rest requests are used once again + + @aioresponses() + @patch("hummingbot.data_feed.coin_cap_data_feed.coin_cap_data_feed.CoinCapDataFeed._sleep") + def test_ws_stream_logs_exceptions_and_restarts(self, mock_api: aioresponses, sleep_mock: AsyncMock): + continue_event = asyncio.Event() + + async def _continue_event_wait(*_, **__): + await continue_event.wait() + continue_event.clear() + + sleep_mock.side_effect = _continue_event_wait + + # initial request + rest_rate = Decimal("20") + data = self.get_coin_cap_assets_data_mock(asset_symbol=self.target_token, asset_price=rest_rate) + assets_map = { + asset_data["symbol"]: asset_data["id"] for asset_data in data["data"] + } + rate_source = CoinCapRateSource(assets_map=assets_map, api_key="") + rate_source._coin_cap_data_feed._get_api_factory() + web_socket_mock = self.mocking_assistant.configure_web_assistants_factory( + web_assistants_factory=rate_source._coin_cap_data_feed._api_factory + ) + url = f"{CONSTANTS.BASE_REST_URL}{CONSTANTS.ALL_ASSETS_ENDPOINT}" + url_regex = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_api.get( + url=url_regex, + body=json.dumps(data), + headers={ + "X-Ratelimit-Remaining": str(CONSTANTS.NO_KEY_LIMIT - 1), + "X-Ratelimit-Limit": str(CONSTANTS.NO_KEY_LIMIT), + }, + repeat=True, + ) + + self.async_run_with_timeout(coroutine=rate_source.start_network()) + + streamed_rate = rest_rate + Decimal("1") + stream_response = {self.target_asset_id: str(streamed_rate)} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=web_socket_mock, message=json.dumps(stream_response) + ) + self.mocking_assistant.add_websocket_aiohttp_exception( + websocket_mock=web_socket_mock, exception=Exception("test exception") + ) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=web_socket_mock) + + prices = self.async_run_with_timeout( + coroutine=rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(streamed_rate, prices[self.trading_pair]) + log_level = "NETWORK" + message = "Unexpected error while streaming prices. Restarting the stream." + any( + record.levelname == log_level and message == record.getMessage() is not None + for record in self.log_records + ) + + streamed_rate = rest_rate + Decimal("2") + stream_response = {self.target_asset_id: str(streamed_rate)} + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=web_socket_mock, message=json.dumps(stream_response) + ) + + continue_event.set() + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(websocket_mock=web_socket_mock) + + prices = self.async_run_with_timeout( + coroutine=rate_source.get_prices(quote_token=self.global_token) + ) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(streamed_rate, prices[self.trading_pair]) diff --git a/test/hummingbot/core/rate_oracle/sources/test_coin_gecko_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_coin_gecko_rate_source.py new file mode 100644 index 0000000..ef9bdf1 --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_coin_gecko_rate_source.py @@ -0,0 +1,263 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import AsyncMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.coin_gecko_rate_source import CoinGeckoRateSource +from hummingbot.data_feed.coin_gecko_data_feed import coin_gecko_constants as CONSTANTS +from hummingbot.data_feed.coin_gecko_data_feed.coin_gecko_constants import COOLOFF_AFTER_BAN + + +class CoinGeckoRateSourceTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.extra_token = "EXTRA" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.extra_trading_pair = combine_to_hb_trading_pair(base=cls.extra_token, quote=cls.global_token) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 300): + try: + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + except asyncio.TimeoutError: + self.fail("Test timed out") + except Exception: + raise + return ret + + def get_coin_markets_data_mock(self, price: float): + data = [ + { + "id": self.target_token.lower(), + "symbol": self.target_token.lower(), + "name": self.target_token.title(), + "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", + "current_price": price, + "market_cap": 451469235435, + "market_cap_rank": 1, + "fully_diluted_valuation": 496425271642, + "total_volume": 50599610665, + "high_24h": 23655, + "low_24h": 21746, + "price_change_24h": 1640.38, + "price_change_percentage_24h": 7.45665, + "market_cap_change_24h": 31187048611, + "market_cap_change_percentage_24h": 7.4205, + "circulating_supply": 19098250, + "total_supply": 21000000, + "max_supply": 21000000, + "ath": 69045, + "ath_change_percentage": -65.90618, + "ath_date": "2021-11-10T14:24:11.849Z", + "atl": 67.81, + "atl_change_percentage": 34615.15839, + "atl_date": "2013-07-06T00:00:00.000Z", + "roi": None, + "last_updated": "2022-07-20T06:30:40.123Z" + }, + ] + return data + + def get_extra_token_data_mock(self, price: float): + data = [ + { + "id": self.extra_token.lower(), + "symbol": self.extra_token.lower(), + "name": self.extra_token.title(), + "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", + "current_price": price, + "market_cap": 451469235435, + "market_cap_rank": 1, + "fully_diluted_valuation": 496425271642, + "total_volume": 50599610665, + "high_24h": 23655, + "low_24h": 21746, + "price_change_24h": 1640.38, + "price_change_percentage_24h": 7.45665, + "market_cap_change_24h": 31187048611, + "market_cap_change_percentage_24h": 7.4205, + "circulating_supply": 19098250, + "total_supply": 21000000, + "max_supply": 21000000, + "ath": 69045, + "ath_change_percentage": -65.90618, + "ath_date": "2021-11-10T14:24:11.849Z", + "atl": 67.81, + "atl_change_percentage": 34615.15839, + "atl_date": "2013-07-06T00:00:00.000Z", + "roi": None, + "last_updated": "2022-07-20T06:30:40.123Z" + }, + ] + return data + + @staticmethod + def get_prices_by_page_url(page_no, vs_currency): + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?order=market_cap_desc&page={page_no}" + f"&per_page=250&sparkline=false&vs_currency={vs_currency}" + ) + return url + + @staticmethod + def get_prices_by_page_with_category_url(category, page_no, vs_currency): + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?category={category}&order=market_cap_desc&page={page_no}" + f"&per_page=250&sparkline=false&vs_currency={vs_currency}" + ) + return url + + def setup_responses(self, mock_api: aioresponses, expected_rate: Decimal): + # setup supported tokens response + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SUPPORTED_VS_TOKENS_REST_ENDPOINT}" + data = [self.global_token.lower(), self.extra_token.lower()] + mock_api.get(url=url, body=json.dumps(data)) + + # setup prices by page responses + initial_data_set = False + for page_no in range(1, 11): + url = self.get_prices_by_page_url(page_no=page_no, vs_currency=self.global_token.lower()) + data = self.get_coin_markets_data_mock(price=float(expected_rate)) if not initial_data_set else [] + initial_data_set = True + mock_api.get(url=url, body=json.dumps(data)) + + # setup prices by page with category responses + initial_data_set = False + for page_no in range(1, 3): + for category in CONSTANTS.TOKEN_CATEGORIES: + url = self.get_prices_by_page_with_category_url( + category=category, page_no=page_no, vs_currency=self.global_token.lower() + ) + data = self.get_coin_markets_data_mock(price=float(expected_rate)) if not initial_data_set else [] + initial_data_set = True + mock_api.get(url=url, body=json.dumps(data)) + + # setup extra token price response + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?ids={self.extra_token.lower()}&vs_currency={self.global_token.lower()}" + ) + data = self.get_extra_token_data_mock(price=20.0) + mock_api.get(url=url, body=json.dumps(data)) + + def setup_responses_with_exception(self, mock_api: aioresponses, expected_rate: Decimal, exception=Exception): + # setup supported tokens response + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SUPPORTED_VS_TOKENS_REST_ENDPOINT}" + data = [self.global_token.lower(), self.extra_token.lower()] + mock_api.get(url=url, body=json.dumps(data)) + + # setup prices by page responses + initial_data_set = False + for page_no in range(1, 11): + url = self.get_prices_by_page_url(page_no=page_no, vs_currency=self.global_token.lower()) + data = self.get_coin_markets_data_mock(price=float(expected_rate)) if not initial_data_set else [] + initial_data_set = True + if page_no == 7: + mock_api.get(url=url, body=None, exception=exception) + else: + mock_api.get(url=url, body=json.dumps(data)) + + # setup prices by page with category responses + initial_data_set = False + for page_no in range(1, 3): + for category in CONSTANTS.TOKEN_CATEGORIES: + url = self.get_prices_by_page_with_category_url( + category=category, page_no=page_no, vs_currency=self.global_token.lower() + ) + data = self.get_coin_markets_data_mock(price=float(expected_rate)) if not initial_data_set else [] + initial_data_set = True + + # We want to test the IOError that does nothing and 'finally' to a asyncio.sleep() + # So first test the IOError, then follow with Exception that gets out of the loop - M. Clunky strikes again + if page_no == 1: + mock_api.get(url=url, body=None, exception=exception) + elif page_no == 2: + mock_api.get(url=url, body=None, exception=Exception) + else: + mock_api.get(url=url, body=json.dumps(data)) + + # setup extra token price response + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?ids={self.extra_token.lower()}&vs_currency={self.global_token.lower()}" + ) + data = self.get_extra_token_data_mock(price=20.0) + mock_api.get(url=url, body=json.dumps(data)) + + @aioresponses() + def test_get_prices_no_extra_tokens(self, mock_api: aioresponses): + print("test_get_prices_no_extra_tokens() - mock_api.responses", mock_api) + expected_rate = Decimal("10") + self.setup_responses(mock_api=mock_api, expected_rate=expected_rate) + print(mock_api.requests) + + rate_source = CoinGeckoRateSource(extra_token_ids=[]) + + prices = self.async_run_with_timeout(rate_source.get_prices(quote_token=self.global_token)) + + self.assertIn(self.trading_pair, prices) + self.assertNotIn(self.extra_trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + + @aioresponses() + def test_get_prices_with_extra_tokens(self, mock_api: aioresponses): + expected_rate = Decimal("10") + self.setup_responses(mock_api=mock_api, expected_rate=expected_rate) + + rate_source = CoinGeckoRateSource(extra_token_ids=[self.extra_token]) + + prices = self.async_run_with_timeout(rate_source.get_prices(quote_token=self.global_token)) + + self.assertIn(self.trading_pair, prices) + self.assertIn(self.extra_trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + + @aioresponses() + def test_get_prices_raises_IOError_cooloff(self, mock_api: aioresponses): + # setup supported tokens response + expected_rate = Decimal("10") + # IOError exception on page 7 (hardcoded to page 7 + self.setup_responses_with_exception(mock_api=mock_api, expected_rate=expected_rate, exception=IOError) + + rate_source = CoinGeckoRateSource(extra_token_ids=[]) + + # The test creates a response with an exception, but the exception is caught and the code continues + # So we need to test that the code continues and the prices are returned, but for the particular page + # that the exception was raised, the prices are not returned and the mocked API returns ClientConnectionError + with patch.object(CoinGeckoRateSource, "_sleep", new_callable=AsyncMock) as mock_sleep: + with self.assertRaises(Exception) as e: + self.async_run_with_timeout(rate_source.get_prices(quote_token=self.global_token)) + self.assertIn("page=7", e.exception.args[0]) + # Exception is caught and the code continues, so the sleep is called + mock_sleep.assert_called_with(COOLOFF_AFTER_BAN) + mock_sleep.assert_awaited() + + @aioresponses() + def test_get_prices_raises_non_IOError_no_cooloff(self, mock_api: aioresponses): + # setup supported tokens response + expected_rate = Decimal("10") + # IOError exception on page 7 (hardcoded to page 7 + self.setup_responses_with_exception(mock_api=mock_api, expected_rate=expected_rate, exception=Exception) + + rate_source = CoinGeckoRateSource(extra_token_ids=[]) + + # The test creates a response with an exception, but the exception is caught and the code continues + # So we need to test that the code continues and the prices are returned, but for the particular page + # that the exception was raised, the prices are not returned and the mocked API returns ClientConnectionError + with patch.object(CoinGeckoRateSource, "_sleep", new_callable=AsyncMock) as mock_sleep: + # The exception is not an IOError, so the code does not sleep + with self.assertRaises(Exception) as e: + self.async_run_with_timeout(rate_source.get_prices(quote_token=self.global_token)) + self.assertIn("Unhandled error in CoinGecko rate source", e.exception.args[0]) + # Exception is caught and the code continues, so the sleep is called + mock_sleep.assert_not_called() diff --git a/test/hummingbot/core/rate_oracle/sources/test_gate_io_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_gate_io_rate_source.py new file mode 100644 index 0000000..5f16d3e --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_gate_io_rate_source.py @@ -0,0 +1,112 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.gate_io import gate_io_constants as CONSTANTS +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.gate_io_rate_source import GateIoRateSource + + +class GateIoRateSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.ignored_trading_pair = combine_to_hb_trading_pair(base="SOME", quote="PAIR") + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def setup_gate_io_responses(self, mock_api, expected_rate: Decimal): + symbols_url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" + symbols_response = [ + { + "id": self.trading_pair, + "base": "COINALPHA", + "quote": "HBOT", + "fee": "0.2", + "trade_status": "tradable", + }, + { + "id": self.ignored_trading_pair, + "base": "SOME", + "quote": "PAIR", + "fee": "0.2", + "trade_status": "non-tradable", + }, + { + "id": "FAKE_BTC", + "base": "FAKE", + "quote": "BTC", + "fee": "0.2", + "trade_status": "tradable", + } + ] + mock_api.get(url=symbols_url, body=json.dumps(symbols_response)) + prices_url = f"{CONSTANTS.REST_URL}/{CONSTANTS.TICKER_PATH_URL}" + prices_response = [ + { + "currency_pair": self.trading_pair, + "last": "0.49876", + "high_24h": "0.52271", + "low_24h": "0.48598", + "base_volume": "122140", + "quote_volume": "122140", + "lowest_ask": str(expected_rate - Decimal("0.1")), + "highest_bid": str(expected_rate + Decimal("0.1")), + "change_percentage": "-2.05", + "etf_net_value": "2.46316141", + "etf_pre_net_value": "2.43201848", + "etf_pre_timestamp": 1611244800, + "etf_leverage": "2.2803019447281203" + }, + { + "currency_pair": "KCS_BTC", + "last": "0.0001816", + "high_24h": "0.00018315", + "low_24h": "0.0001658", + "base_volume": "14595.7", + "quote_volume": "14595.7", + "lowest_ask": "", + "highest_bid": "", + "etf_net_value": "2.46316141", + "etf_pre_net_value": "2.43201848", + "etf_pre_timestamp": 1611244800, + "etf_leverage": "2.2803019447281203" + }, + { + "currency_pair": self.ignored_trading_pair, + "last": "0.0001816", + "high_24h": "0.00018315", + "low_24h": "0.0001658", + "base_volume": "14595.7", + "quote_volume": "14595.7", + "lowest_ask": str(expected_rate - Decimal("0.1")), + "highest_bid": str(expected_rate + Decimal("0.1")), + "etf_net_value": "2.46316141", + "etf_pre_net_value": "2.43201848", + "etf_pre_timestamp": 1611244800, + "etf_leverage": "2.2803019447281203" + }, + ] + mock_api.get(url=prices_url, body=json.dumps(prices_response)) + + @aioresponses() + def test_get_prices(self, mock_api): + expected_rate = Decimal("10") + self.setup_gate_io_responses(mock_api=mock_api, expected_rate=expected_rate) + + rate_source = GateIoRateSource() + prices = self.async_run_with_timeout(rate_source.get_prices()) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + self.assertNotIn(self.ignored_trading_pair, prices) diff --git a/test/hummingbot/core/rate_oracle/sources/test_kucoin_rate_source.py b/test/hummingbot/core/rate_oracle/sources/test_kucoin_rate_source.py new file mode 100644 index 0000000..8226a17 --- /dev/null +++ b/test/hummingbot/core/rate_oracle/sources/test_kucoin_rate_source.py @@ -0,0 +1,79 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Awaitable + +from aioresponses import aioresponses + +from hummingbot.connector.exchange.kucoin import kucoin_constants as CONSTANTS +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.sources.kucoin_rate_source import KucoinRateSource + + +class KucoinRateSourceTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + cls.ignored_trading_pair = combine_to_hb_trading_pair(base="SOME", quote="PAIR") + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def setup_kucoin_responses(self, mock_api, expected_rate: Decimal): + symbols_url = f"{CONSTANTS.BASE_PATH_URL['main']}{CONSTANTS.SYMBOLS_PATH_URL}" + symbols_response = { # truncated response + "data": [ + { + "symbol": self.trading_pair, + "baseCurrency": self.target_token, + "quoteCurrency": self.global_token, + "enableTrading": True, + }, + { + "symbol": self.ignored_trading_pair, + "baseCurrency": "SOME", + "quoteCurrency": "PAIR", + "enableTrading": False, + }, + ], + } + mock_api.get(url=symbols_url, body=json.dumps(symbols_response)) + prices_url = f"{CONSTANTS.BASE_PATH_URL['main']}{CONSTANTS.ALL_TICKERS_PATH_URL}" + prices_response = { # truncated response + "data": { + "time": 1602832092060, + "ticker": [ + { + "symbol": self.trading_pair, + "symbolName": self.trading_pair, + "buy": str(expected_rate - Decimal("0.1")), + "sell": str(expected_rate + Decimal("0.1")), + }, + { + "symbol": self.ignored_trading_pair, + "symbolName": self.ignored_trading_pair, + "buy": str(expected_rate - Decimal("0.1")), + "sell": str(expected_rate + Decimal("0.1")), + } + ], + }, + } + mock_api.get(url=prices_url, body=json.dumps(prices_response)) + + @aioresponses() + def test_get_prices(self, mock_api): + expected_rate = Decimal("10") + self.setup_kucoin_responses(mock_api=mock_api, expected_rate=expected_rate) + + rate_source = KucoinRateSource() + prices = self.async_run_with_timeout(rate_source.get_prices()) + + self.assertIn(self.trading_pair, prices) + self.assertEqual(expected_rate, prices[self.trading_pair]) + self.assertNotIn(self.ignored_trading_pair, prices) diff --git a/test/hummingbot/core/rate_oracle/test_rate_oracle.py b/test/hummingbot/core/rate_oracle/test_rate_oracle.py new file mode 100644 index 0000000..4914fef --- /dev/null +++ b/test/hummingbot/core/rate_oracle/test_rate_oracle.py @@ -0,0 +1,103 @@ +import asyncio +import unittest +from copy import deepcopy +from decimal import Decimal +from typing import Awaitable, Dict, Optional + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.core.rate_oracle.sources.coin_gecko_rate_source import CoinGeckoRateSource +from hummingbot.core.rate_oracle.sources.rate_source_base import RateSourceBase +from hummingbot.core.rate_oracle.utils import find_rate + + +class DummyRateSource(RateSourceBase): + def __init__(self, price_dict: Dict[str, Decimal]): + self._price_dict = price_dict + + @property + def name(self): + return "dummy_rate_source" + + async def get_prices(self, quote_token: Optional[str] = None) -> Dict[str, Decimal]: + return deepcopy(self._price_dict) + + +class RateOracleTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.target_token = "COINALPHA" + cls.global_token = "HBOT" + cls.trading_pair = combine_to_hb_trading_pair(base=cls.target_token, quote=cls.global_token) + + def setUp(self) -> None: + if RateOracle._shared_instance is not None: + RateOracle._shared_instance.stop() + RateOracle._shared_instance = None + + def tearDown(self) -> None: + RateOracle._shared_instance = None + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_find_rate_from_source(self): + expected_rate = Decimal("10") + rate_oracle = RateOracle(source=DummyRateSource(price_dict={self.trading_pair: expected_rate})) + + rate = self.async_run_with_timeout(rate_oracle.rate_async(self.trading_pair)) + self.assertEqual(expected_rate, rate) + + def test_rate_oracle_network(self): + expected_rate = Decimal("10") + rate_oracle = RateOracle(source=DummyRateSource(price_dict={self.trading_pair: expected_rate})) + + rate_oracle.start() + self.async_run_with_timeout(rate_oracle.get_ready()) + self.assertGreater(len(rate_oracle.prices), 0) + rate = rate_oracle.get_pair_rate(self.trading_pair) + self.assertEqual(expected_rate, rate) + + self.async_run_with_timeout(rate_oracle.stop_network()) + + self.assertEqual(0, len(rate_oracle.prices)) + + def test_find_rate(self): + prices = {"HBOT-USDT": Decimal("100"), "AAVE-USDT": Decimal("50"), "USDT-GBP": Decimal("0.75")} + rate = find_rate(prices, "HBOT-USDT") + self.assertEqual(rate, Decimal("100")) + rate = find_rate(prices, "ZBOT-USDT") + self.assertEqual(rate, None) + rate = find_rate(prices, "USDT-HBOT") + self.assertEqual(rate, Decimal("0.01")) + rate = find_rate(prices, "HBOT-AAVE") + self.assertEqual(rate, Decimal("2")) + rate = find_rate(prices, "AAVE-HBOT") + self.assertEqual(rate, Decimal("0.5")) + rate = find_rate(prices, "HBOT-GBP") + self.assertEqual(rate, Decimal("75")) + + def test_rate_oracle_single_instance_rate_source_reset_after_configuration_change(self): + config_map = ClientConfigAdapter(ClientConfigMap()) + config_map.rate_oracle_source = "binance" + rate_oracle = RateOracle.get_instance() + config_map.rate_oracle_source = "coin_gecko" + self.assertEqual(type(rate_oracle.source), CoinGeckoRateSource) + + def test_rate_oracle_single_instance_prices_reset_after_global_token_change(self): + config_map = ClientConfigAdapter(ClientConfigMap()) + + rate_oracle = RateOracle.get_instance() + + self.assertEqual(0, len(rate_oracle.prices)) + + rate_oracle._prices = {"BTC-USD": Decimal("20000")} + + config_map.global_token.global_token_name = "EUR" + + self.assertEqual(0, len(rate_oracle.prices)) diff --git a/test/hummingbot/core/test_clock.py b/test/hummingbot/core/test_clock.py new file mode 100644 index 0000000..0d44d74 --- /dev/null +++ b/test/hummingbot/core/test_clock.py @@ -0,0 +1,126 @@ +import unittest +import asyncio +import pandas as pd +import time + +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.time_iterator import TimeIterator + + +class ClockUnitTest(unittest.TestCase): + + backtest_start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + backtest_end_timestamp: float = pd.Timestamp("2021-01-01 01:00:00", tz="UTC").timestamp() + tick_size: int = 1 + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + + def setUp(self): + super().setUp() + self.realtime_start_timestamp = int(time.time()) + self.realtime_end_timestamp = self.realtime_start_timestamp + 2.0 # + self.clock_realtime = Clock(ClockMode.REALTIME, self.tick_size, self.realtime_start_timestamp, self.realtime_end_timestamp) + self.clock_backtest = Clock(ClockMode.BACKTEST, self.tick_size, self.backtest_start_timestamp, self.backtest_end_timestamp) + + def test_clock_mode(self): + self.assertEqual(ClockMode.REALTIME, self.clock_realtime.clock_mode) + self.assertEqual(ClockMode.BACKTEST, self.clock_backtest.clock_mode) + + def test_start_time(self): + self.assertEqual(self.realtime_start_timestamp, self.clock_realtime.start_time) + self.assertEqual(self.backtest_start_timestamp, self.clock_backtest.start_time) + + def test_tick_time(self): + self.assertEqual(self.tick_size, self.clock_realtime.tick_size) + self.assertEqual(self.tick_size, self.clock_backtest.tick_size) + + def test_child_iterators(self): + # Tests child_iterators property after initialization. See also test_add_iterator + self.assertEqual(0, len(self.clock_realtime.child_iterators)) + self.assertEqual(0, len(self.clock_backtest.child_iterators)) + + def test_current_timestamp(self): + self.assertEqual(self.backtest_start_timestamp, self.clock_backtest.current_timestamp) + self.assertAlmostEqual((self.realtime_start_timestamp // self.tick_size) * self.tick_size, self.clock_realtime.current_timestamp) + + self.clock_backtest.backtest() + self.clock_realtime.backtest() + + self.assertEqual(self.backtest_end_timestamp, self.clock_backtest.current_timestamp) + self.assertLessEqual(self.realtime_end_timestamp, self.clock_realtime.current_timestamp) + + def test_add_iterator(self): + self.assertEqual(0, len(self.clock_realtime.child_iterators)) + self.assertEqual(0, len(self.clock_backtest.child_iterators)) + + time_iterator: TimeIterator = TimeIterator() + self.clock_realtime.add_iterator(time_iterator) + self.clock_backtest.add_iterator(time_iterator) + + self.assertEqual(1, len(self.clock_realtime.child_iterators)) + self.assertEqual(time_iterator, self.clock_realtime.child_iterators[0]) + self.assertEqual(1, len(self.clock_backtest.child_iterators)) + self.assertEqual(time_iterator, self.clock_backtest.child_iterators[0]) + + def test_remove_iterator(self): + self.assertEqual(0, len(self.clock_realtime.child_iterators)) + self.assertEqual(0, len(self.clock_backtest.child_iterators)) + + time_iterator: TimeIterator = TimeIterator() + self.clock_realtime.add_iterator(time_iterator) + self.clock_backtest.add_iterator(time_iterator) + + self.assertEqual(1, len(self.clock_realtime.child_iterators)) + self.assertEqual(time_iterator, self.clock_realtime.child_iterators[0]) + self.assertEqual(1, len(self.clock_backtest.child_iterators)) + self.assertEqual(time_iterator, self.clock_backtest.child_iterators[0]) + + self.clock_realtime.remove_iterator(time_iterator) + self.clock_backtest.remove_iterator(time_iterator) + + self.assertEqual(0, len(self.clock_realtime.child_iterators)) + self.assertEqual(0, len(self.clock_backtest.child_iterators)) + + def test_run(self): + # Note: Technically you do not execute `run()` when in BACKTEST mode + + # Tests EnvironmentError raised when not runnning within a context + with self.assertRaises(EnvironmentError): + self.ev_loop.run_until_complete(self.clock_realtime.run()) + + # Note: run() will essentially run indefinitely hence the enforced timeout. + with self.assertRaises(asyncio.TimeoutError), self.clock_realtime: + self.ev_loop.run_until_complete(asyncio.wait_for(self.clock_realtime.run(), 1)) + + self.assertLess(self.realtime_start_timestamp, self.clock_realtime.current_timestamp) + + def test_run_til(self): + # Note: Technically you do not execute `run_til()` when in BACKTEST mode + + # Tests EnvironmentError raised when not runnning within a context + with self.assertRaises(EnvironmentError): + self.ev_loop.run_until_complete(self.clock_realtime.run_til(self.realtime_end_timestamp)) + + with self.clock_realtime: + self.ev_loop.run_until_complete(self.clock_realtime.run_til(self.realtime_end_timestamp)) + + self.assertGreaterEqual(self.clock_realtime.current_timestamp, self.realtime_end_timestamp) + + def test_backtest(self): + # Note: Technically you do not execute `backtest()` when in REALTIME mode + + self.clock_backtest.backtest() + self.assertGreaterEqual(self.clock_backtest.current_timestamp, self.backtest_end_timestamp) + + def test_backtest_til(self): + # Note: Technically you do not execute `backtest_til()` when in REALTIME mode + + self.clock_backtest.backtest_til(self.backtest_start_timestamp + self.tick_size) + self.assertGreater(self.clock_backtest.current_timestamp, self.clock_backtest.start_time) + self.assertLess(self.clock_backtest.current_timestamp, self.backtest_end_timestamp) diff --git a/test/hummingbot/core/test_events.py b/test/hummingbot/core/test_events.py new file mode 100644 index 0000000..dbf6030 --- /dev/null +++ b/test/hummingbot/core/test_events.py @@ -0,0 +1,24 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import OrderFilledEvent, OrderType, TradeType + + +class OrderFilledEventTests(TestCase): + + def test_fill_events_created_from_order_book_rows_have_unique_trade_ids(self): + rows = [OrderBookRow(Decimal(1000), Decimal(1), 1), OrderBookRow(Decimal(1001), Decimal(2), 2)] + fill_events = OrderFilledEvent.order_filled_events_from_order_book_rows( + timestamp=1640001112.223, + order_id="OID1", + trading_pair="COINALPHA-HBOT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + trade_fee=AddedToCostTradeFee(), + order_book_rows=rows + ) + + self.assertEqual("OID1_0", fill_events[0].exchange_trade_id) + self.assertEqual("OID1_1", fill_events[1].exchange_trade_id) diff --git a/test/hummingbot/core/test_network_base.py b/test/hummingbot/core/test_network_base.py new file mode 100644 index 0000000..ff8adfb --- /dev/null +++ b/test/hummingbot/core/test_network_base.py @@ -0,0 +1,85 @@ +import asyncio +from hummingbot.core.network_base import NetworkBase +from hummingbot.core.network_iterator import NetworkStatus +import unittest + + +class SampleNetwork(NetworkBase): + async def check_network(self) -> NetworkStatus: + "Override check_network to always return connected" + return NetworkStatus.CONNECTED + + +class NetworkBaseTest(unittest.TestCase): + def test_init(self): + """ + This lets us know if the initial values have changed and extends + code coverage to the class's properties. + """ + nb = NetworkBase() + + self.assertEqual(nb.network_status, NetworkStatus.STOPPED) + self.assertEqual(nb.check_network_task, None) + self.assertEqual(nb.check_network_interval, 60.0) + self.assertEqual(nb.network_error_wait_time, 60.0) + self.assertEqual(nb.check_network_timeout, 60.0) + self.assertEqual(nb.started, False) + + # test that setters work + nb.check_network_interval = 15.0 + self.assertEqual(nb.check_network_interval, 15.0) + + nb.network_error_wait_time = 25.0 + self.assertEqual(nb.network_error_wait_time, 25.0) + + nb.check_network_timeout = 45.0 + self.assertEqual(nb.check_network_timeout, 45.0) + + def test_network(self): + """ + NetworkBase has a couple of method sketches that do not do anything + but are used by child classes. + """ + + nb = NetworkBase() + + self.assertEqual(asyncio.get_event_loop().run_until_complete(nb.start_network()), None) + + self.assertEqual(asyncio.get_event_loop().run_until_complete(nb.stop_network()), None) + + self.assertEqual(asyncio.get_event_loop().run_until_complete(nb.check_network()), NetworkStatus.NOT_CONNECTED) + + def test_start_and_stop_network(self): + """ + Assert that start and stop update the started property. + """ + + nb = NetworkBase() + + nb.start() + self.assertEqual(nb.started, True) + + nb.stop() + self.assertEqual(nb.started, False) + + def test_update_network_status(self): + """ + Use SampleNetwork to test that the network status gets updated + """ + sample = SampleNetwork() + + self.assertEqual(sample.network_status, NetworkStatus.STOPPED) + + sample.check_network_interval = 0.1 + sample.network_error_wait_time = 0.1 + sample.check_network_timeout = 0.1 + + sample.start() + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.2)) + + self.assertEqual(sample.network_status, NetworkStatus.CONNECTED) + + sample.stop() + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.2)) + + self.assertEqual(sample.started, False) diff --git a/test/hummingbot/core/test_network_iterator.py b/test/hummingbot/core/test_network_iterator.py new file mode 100644 index 0000000..54f9d1c --- /dev/null +++ b/test/hummingbot/core/test_network_iterator.py @@ -0,0 +1,129 @@ +import unittest +import asyncio +import pandas as pd + +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.network_iterator import ( + NetworkIterator, + NetworkStatus, +) + + +class MockNetworkIterator(NetworkIterator): + + def __init__(self): + super().__init__() + self._start_network_event = asyncio.Event() + self._stop_network_event = asyncio.Event() + + async def start_network(self): + self._start_network_event.set() + self._stop_network_event = asyncio.Event() + + async def stop_network(self): + self._stop_network_event.set() + + self._network_status = NetworkStatus.STOPPED + self._start_network_event = asyncio.Event() + + async def check_network(self): + if self.network_status != NetworkStatus.CONNECTED: + self.last_connected_timestamp = self.current_timestamp + return NetworkStatus.CONNECTED + else: + return NetworkStatus.NOT_CONNECTED + + +class NetworkIteratorUnitTest(unittest.TestCase): + + start: pd.Timestamp = pd.Timestamp("2021-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2022-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + clock_tick_size = 10 + + @classmethod + def setUpClass(cls): + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + return super().setUpClass() + + def setUp(self): + self.network_iterator = MockNetworkIterator() + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.network_iterator) + return super().setUp() + + def test_network_status(self): + # This test technically tests the _check_network_loop() and all its paths. + self.assertEqual(NetworkStatus.STOPPED, self.network_iterator.network_status) + + self.network_iterator.check_network_interval = 0.5 + + self.clock.backtest_til(self.start_timestamp) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(NetworkStatus.CONNECTED, self.network_iterator.network_status) + self.assertTrue(self.network_iterator._start_network_event.is_set()) + + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(NetworkStatus.NOT_CONNECTED, self.network_iterator.network_status) + self.assertTrue(self.network_iterator._stop_network_event.is_set()) + + def test_last_connected_timestamp(self): + self.clock.backtest_til(self.start_timestamp) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(self.start_timestamp, self.network_iterator.last_connected_timestamp) + + def test_check_network_task(self): + self.clock.backtest_til(self.start_timestamp) + self.assertIsNotNone(self.network_iterator.check_network_task) + + def test_check_network_interval(self): + # Default interval + self.assertEqual(10.0, self.network_iterator.check_network_interval) + + def test_network_error_wait_time(self): + # Default wait time + self.assertEqual(60.0, self.network_iterator.network_error_wait_time) + + def test_check_network_timeout(self): + # Default timeout + self.assertEqual(5.0, self.network_iterator.check_network_timeout) + + def test_start_network(self): + self.assertFalse(self.network_iterator._start_network_event.is_set()) + self.assertFalse(self.network_iterator._stop_network_event.is_set()) + + self.ev_loop.run_until_complete(self.network_iterator.start_network()) + self.assertTrue(self.network_iterator._start_network_event.is_set()) + self.assertFalse(self.network_iterator._stop_network_event.is_set()) + + def test_stop_network(self): + self.assertFalse(self.network_iterator._start_network_event.is_set()) + self.assertFalse(self.network_iterator._stop_network_event.is_set()) + + self.ev_loop.run_until_complete(self.network_iterator.stop_network()) + self.assertFalse(self.network_iterator._start_network_event.is_set()) + self.assertTrue(self.network_iterator._stop_network_event.is_set()) + self.assertEqual(NetworkStatus.STOPPED, self.network_iterator.network_status) + + def test_start(self): + self.assertEqual(NetworkStatus.STOPPED, self.network_iterator.network_status) + + self.network_iterator.start(self.clock, self.clock.current_timestamp) + + self.assertIsNotNone(self.network_iterator.check_network_task) + self.assertEqual(NetworkStatus.NOT_CONNECTED, self.network_iterator.network_status) + + def test_stop(self): + self.assertEqual(NetworkStatus.STOPPED, self.network_iterator.network_status) + + self.network_iterator.start(self.clock, self.clock.current_timestamp) + self.assertEqual(NetworkStatus.NOT_CONNECTED, self.network_iterator.network_status) + + self.network_iterator.stop(self.clock) + + self.assertEqual(NetworkStatus.STOPPED, self.network_iterator.network_status) + self.assertIsNone(self.network_iterator.check_network_task) diff --git a/test/hummingbot/core/test_pubsub.py b/test/hummingbot/core/test_pubsub.py new file mode 100644 index 0000000..56e9bba --- /dev/null +++ b/test/hummingbot/core/test_pubsub.py @@ -0,0 +1,94 @@ +import unittest +import gc +import weakref + +from hummingbot.core.pubsub import PubSub +from hummingbot.core.event.event_logger import EventLogger + +from test.mock.mock_events import MockEventType, MockEvent + + +class PubSubTest(unittest.TestCase): + def setUp(self) -> None: + self.pubsub = PubSub() + self.listener_zero = EventLogger() + self.listener_one = EventLogger() + self.event_tag_zero = MockEventType.EVENT_ZERO + self.event_tag_one = MockEventType.EVENT_ONE + self.event = MockEvent(payload=1) + + def test_get_listeners_no_listeners(self): + listeners_count = len(self.pubsub.get_listeners(self.event_tag_zero)) + self.assertEqual(0, listeners_count) + + def test_add_listeners(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + listeners = self.pubsub.get_listeners(self.event_tag_zero) + self.assertEqual(1, len(listeners)) + self.assertIn(self.listener_zero, listeners) + + self.pubsub.add_listener(self.event_tag_zero, self.listener_one) + listeners = self.pubsub.get_listeners(self.event_tag_zero) + self.assertEqual(2, len(listeners)) + self.assertIn(self.listener_zero, listeners) + self.assertIn(self.listener_one, listeners) + + def test_add_listener_twice(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + listeners_count = len(self.pubsub.get_listeners(self.event_tag_zero)) + self.assertEqual(1, listeners_count) + + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + listeners_count = len(self.pubsub.get_listeners(self.event_tag_zero)) + self.assertEqual(1, listeners_count) + + def test_remove_listener(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + self.pubsub.add_listener(self.event_tag_zero, self.listener_one) + + self.pubsub.remove_listener(self.event_tag_zero, self.listener_zero) + listeners = self.pubsub.get_listeners(self.event_tag_zero) + self.assertNotIn(self.listener_zero, listeners) + self.assertIn(self.listener_one, listeners) + + def test_add_listeners_to_separate_events(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + self.pubsub.add_listener(self.event_tag_one, self.listener_one) + + listeners_zero = self.pubsub.get_listeners(self.event_tag_zero) + listeners_one = self.pubsub.get_listeners(self.event_tag_one) + self.assertEqual(1, len(listeners_zero)) + self.assertEqual(1, len(listeners_one)) + + def test_trigger_event(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + self.pubsub.add_listener(self.event_tag_one, self.listener_one) + self.pubsub.trigger_event(self.event_tag_zero, self.event) + self.assertEqual(1, len(self.listener_zero.event_log)) + self.assertEqual(self.event, self.listener_zero.event_log[0]) + self.assertEqual(0, len(self.listener_one.event_log)) + + def test_lapsed_listener_remove_on_get_listeners(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + self.listener_zero = None # remove strong reference + gc.collect() + listeners = self.pubsub.get_listeners(self.event_tag_zero) + self.assertEqual(0, len(listeners)) + + def test_lapsed_listener_remove_on_remove_listener(self): + self.pubsub.add_listener(self.event_tag_zero, self.listener_zero) + self.pubsub.add_listener(self.event_tag_zero, self.listener_one) + listener_zero_weakref = weakref.ref(self.listener_zero) + listener_one_weakref = weakref.ref(self.listener_one) + listeners = None + self.listener_zero = None # remove strong reference + gc.collect() + self.pubsub.remove_listener(self.event_tag_zero, self.listener_one) + self.assertEqual(None, listener_zero_weakref()) + self.assertNotEqual(None, listener_one_weakref()) + listeners = self.pubsub.get_listeners(self.event_tag_zero) + self.assertEqual(0, len(listeners)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/hummingbot/core/test_py_time_iterator.py b/test/hummingbot/core/test_py_time_iterator.py new file mode 100644 index 0000000..7308c34 --- /dev/null +++ b/test/hummingbot/core/test_py_time_iterator.py @@ -0,0 +1,76 @@ +import unittest +import math +import pandas as pd + +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.py_time_iterator import PyTimeIterator + +NaN = float("nan") + + +class MockPyTimeIterator(PyTimeIterator): + + def __init__(self): + super().__init__() + self._mock_variable = None + + @property + def mock_variable(self): + return self._mock_variable + + def tick(self, timestamp: float): + self._mock_variable = timestamp + + +class PyTimeIteratorUnitTest(unittest.TestCase): + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + end_timestamp: float = pd.Timestamp("2022-01-01 01:00:00", tz="UTC").timestamp() + tick_size: int = 10 + + def setUp(self): + self.py_time_iterator = MockPyTimeIterator() + self.clock = Clock(ClockMode.BACKTEST, self.tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.py_time_iterator) + + def test_current_timestamp(self): + # On initialization, current_timestamp should be NaN + self.assertTrue(math.isnan(self.py_time_iterator.current_timestamp)) + + self.py_time_iterator.start(self.clock) + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp) + + def test_clock(self): + # On initialization, clock should be None + self.assertTrue(self.py_time_iterator.clock is None) + + self.py_time_iterator.start(self.clock) + self.assertEqual(self.clock, self.py_time_iterator.clock) + + def test_start(self): + self.py_time_iterator.start(self.clock) + self.assertEqual(self.clock, self.py_time_iterator.clock) + self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp) + + def test_stop(self): + self.py_time_iterator.start(self.clock) + self.assertEqual(self.clock, self.py_time_iterator.clock) + self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp) + + self.py_time_iterator.stop(self.clock) + self.assertTrue(math.isnan(self.py_time_iterator.current_timestamp)) + self.assertTrue(self.py_time_iterator.clock is None) + + def test_tick(self): + self.py_time_iterator.start(self.clock) + self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp) + + # c_tick is called within Clock + self.clock.backtest_til(self.start_timestamp + self.tick_size) + self.assertEqual(self.start_timestamp + self.tick_size, self.py_time_iterator.current_timestamp) + + self.assertEqual(self.start_timestamp + self.tick_size, self.py_time_iterator.mock_variable) diff --git a/test/hummingbot/core/test_time_iterator.py b/test/hummingbot/core/test_time_iterator.py new file mode 100644 index 0000000..9e96476 --- /dev/null +++ b/test/hummingbot/core/test_time_iterator.py @@ -0,0 +1,60 @@ +import unittest +import math +import pandas as pd + +from hummingbot.core.clock import ( + Clock, + ClockMode +) +from hummingbot.core.time_iterator import TimeIterator + +NaN = float("nan") + + +class TimeIteratorUnitTest(unittest.TestCase): + + start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp() + end_timestamp: float = pd.Timestamp("2022-01-01 01:00:00", tz="UTC").timestamp() + tick_size: int = 10 + + def setUp(self): + self.time_iterator = TimeIterator() + self.clock = Clock(ClockMode.BACKTEST, self.tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.time_iterator) + + def test_current_timestamp(self): + # On initialization, current_timestamp should be NaN + self.assertTrue(math.isnan(self.time_iterator.current_timestamp)) + + self.time_iterator.start(self.clock) + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(self.start_timestamp, self.time_iterator.current_timestamp) + + def test_clock(self): + # On initialization, clock should be None + self.assertTrue(self.time_iterator.clock is None) + + self.time_iterator.start(self.clock) + self.assertEqual(self.clock, self.time_iterator.clock) + + def test_start(self): + self.time_iterator.start(self.clock) + self.assertEqual(self.clock, self.time_iterator.clock) + self.assertEqual(self.start_timestamp, self.time_iterator.current_timestamp) + + def test_stop(self): + self.time_iterator.start(self.clock) + self.assertEqual(self.clock, self.time_iterator.clock) + self.assertEqual(self.start_timestamp, self.time_iterator.current_timestamp) + + self.time_iterator.stop(self.clock) + self.assertTrue(math.isnan(self.time_iterator.current_timestamp)) + self.assertTrue(self.time_iterator.clock is None) + + def test_tick(self): + self.time_iterator.start(self.clock) + self.assertEqual(self.start_timestamp, self.time_iterator.current_timestamp) + + # c_tick is called within Clock + self.clock.backtest_til(self.start_timestamp + self.tick_size) + self.assertEqual(self.start_timestamp + self.tick_size, self.time_iterator.current_timestamp) diff --git a/test/hummingbot/core/utils/__init__.py b/test/hummingbot/core/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/utils/test_async_retry.py b/test/hummingbot/core/utils/test_async_retry.py new file mode 100644 index 0000000..e5c5389 --- /dev/null +++ b/test/hummingbot/core/utils/test_async_retry.py @@ -0,0 +1,80 @@ +""" +Unit tests for hummingbot.core.utils.async_retry +""" + +import asyncio +from hummingbot.core.utils.async_retry import AllTriesFailedException, async_retry +import unittest + + +class FooException(Exception): + """ + foo_three_times throws this exception, and we set async_retry to use this to trigger a retry + """ + pass + + +class BarException(Exception): + """ + bar_three_times throws this exception, but we do not set async_retry to use this to trigger a retry + """ + pass + + +class AsyncRetryTest(unittest.TestCase): + def setUp(self): + super(AsyncRetryTest, self).setUp() + self.foo_counter = 0 + self.bar_counter = 0 + + @async_retry(3, exception_types=[FooException], raise_exp=True, retry_interval=0) + async def foo_three_times(self, target): + """ + This function runs three times. It raises FooException when the target is not met. + When FooException is called, async_retry increments a counter and logs the error, + if the counter is three and foo_three_times still fails, then it will raise + AllTriesFailedException. + """ + if self.foo_counter >= target: + return self.foo_counter + else: + self.foo_counter += 1 + raise FooException + + @async_retry(3, raise_exp=False, retry_interval=0) + async def bar_three_times(self, target): + """ + This function runs three times. It raises BarException when the target is not met. + When FooException is called, async_retry increments a counter, if the counter is + three and foo_three_times still fails, then it will raise AllTriesFailedException. + """ + if self.bar_counter >= target: + return self.bar_counter + else: + self.bar_counter += 1 + raise BarException + + def test_async_retry(self): + """ + Unit tests for async_retry. + """ + # run foo_three_times successfully + self.foo_counter = 0 + foo_result = asyncio.get_event_loop().run_until_complete(self.foo_three_times(2)) + self.assertEqual(foo_result, 2) + + # pass a target to foo_three_times that it won't reach. This should raise the error from async_retry. + self.foo_counter = 0 + self.assertRaises(AllTriesFailedException, asyncio.get_event_loop().run_until_complete, self.foo_three_times(5)) + + # bar_three_times has raise_exp=False on async_retry. It will return None instead of raising an exception if + # it fails to meet its condition in the three tries. + # First run it in a case that passes. + self.bar_counter = 0 + bar_result = asyncio.get_event_loop().run_until_complete(self.bar_three_times(2)) + self.assertEqual(bar_result, 2) + + # run bar_three_times so that it does not meet its expected conditions. It will not raise an error. + self.bar_counter = 0 + bar_result = asyncio.get_event_loop().run_until_complete(self.bar_three_times(5)) + self.assertEqual(bar_result, None) diff --git a/test/hummingbot/core/utils/test_async_ttl_cache.py b/test/hummingbot/core/utils/test_async_ttl_cache.py new file mode 100644 index 0000000..de06163 --- /dev/null +++ b/test/hummingbot/core/utils/test_async_ttl_cache.py @@ -0,0 +1,23 @@ +import unittest +import asyncio +import time + +from hummingbot.core.utils import async_ttl_cache + + +class AsyncTTLCacheUnitTest(unittest.TestCase): + + @async_ttl_cache(ttl=3, maxsize=1) + async def get_timestamp(self): + return time.time() + + def test_async_ttl_cache(self): + ret_1 = asyncio.get_event_loop().run_until_complete(self.get_timestamp()) + ret_2 = asyncio.get_event_loop().run_until_complete(self.get_timestamp()) + self.assertEqual(ret_1, ret_2) + time.sleep(2) + ret_3 = asyncio.get_event_loop().run_until_complete(self.get_timestamp()) + self.assertEqual(ret_2, ret_3) + time.sleep(2) + ret_4 = asyncio.get_event_loop().run_until_complete(self.get_timestamp()) + self.assertGreater(ret_4, ret_3) diff --git a/test/hummingbot/core/utils/test_estimate_fee.py b/test/hummingbot/core/utils/test_estimate_fee.py new file mode 100644 index 0000000..04ba065 --- /dev/null +++ b/test/hummingbot/core/utils/test_estimate_fee.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +""" +unit tests for hummingbot.core.utils.estimate_fee +""" + +import unittest +from decimal import Decimal + +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, DeductedFromReturnsTradeFee +from hummingbot.core.utils.estimate_fee import estimate_fee + + +class EstimateFeeTest(unittest.TestCase): + + def test_estimate_fee(self): + """ + test the estimate_fee function + """ + + # test against centralized exchanges + self.assertEqual(estimate_fee("kucoin", True), AddedToCostTradeFee(percent=Decimal('0.001'), flat_fees=[])) + self.assertEqual(estimate_fee("kucoin", False), AddedToCostTradeFee(percent=Decimal('0.001'), flat_fees=[])) + self.assertEqual(estimate_fee("binance", True), DeductedFromReturnsTradeFee(percent=Decimal('0.001'), flat_fees=[])) + self.assertEqual(estimate_fee("binance", False), DeductedFromReturnsTradeFee(percent=Decimal('0.001'), flat_fees=[])) + + # test against exchanges that do not exist in hummingbot.client.settings.CONNECTOR_SETTINGS + self.assertRaisesRegex(Exception, "^Invalid connector", estimate_fee, "does_not_exist", True) + self.assertRaisesRegex(Exception, "Invalid connector", estimate_fee, "does_not_exist", False) diff --git a/test/hummingbot/core/utils/test_fixed_rate_source.py b/test/hummingbot/core/utils/test_fixed_rate_source.py new file mode 100644 index 0000000..897a2d6 --- /dev/null +++ b/test/hummingbot/core/utils/test_fixed_rate_source.py @@ -0,0 +1,26 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.core.utils.fixed_rate_source import FixedRateSource + + +class FixedRateSourceTests(TestCase): + + def test_look_for_unconfigured_pair_rate(self): + rate_source = FixedRateSource() + self.assertIsNone(rate_source.get_pair_rate("BTC-USDT")) + + def test_get_rate(self): + rate_source = FixedRateSource() + rate_source.add_rate("BTC-USDT", Decimal(40000)) + + self.assertEqual(rate_source.get_pair_rate("BTC-USDT"), Decimal(40000)) + + def test_get_rate_when_inverted_pair_is_configured(self): + rate_source = FixedRateSource() + rate_source.add_rate("BTC-USDT", Decimal(40000)) + + self.assertEqual(rate_source.get_pair_rate("USDT-BTC"), Decimal(1) / Decimal(40000)) + + def test_string_representation(self): + self.assertEqual(str(FixedRateSource()), "fixed rates") diff --git a/test/hummingbot/core/utils/test_gateway_config_utils.py b/test/hummingbot/core/utils/test_gateway_config_utils.py new file mode 100644 index 0000000..47a3002 --- /dev/null +++ b/test/hummingbot/core/utils/test_gateway_config_utils.py @@ -0,0 +1,87 @@ +from typing import List +from unittest import TestCase +import hummingbot.core.utils.gateway_config_utils as utils + + +class GatewayConfigUtilsTest(TestCase): + + config_dict = { + "a": 1, + "b": { + "ba": 21, + "bb": 22, + "bc": { + "bca": 231, + "bcb": 232 + } + }, + "c": 3 + } + + def test_build_config_dict_display(self): + lines: List[str] = [] + utils.build_config_dict_display(lines, self.config_dict) + self.assertEqual(8, len(lines)) + self.assertEqual('a: 1', lines[0]) + self.assertEqual('b:', lines[1]) + self.assertEqual(' ba: 21', lines[2]) + self.assertEqual(' bb: 22', lines[3]) + self.assertEqual(' bc:', lines[4]) + self.assertEqual(' bca: 231', lines[5]) + self.assertEqual(' bcb: 232', lines[6]) + self.assertEqual('c: 3', lines[7]) + + def test_build_config_namespace_keys(self): + keys = [] + utils.build_config_namespace_keys(keys, self.config_dict) + self.assertEqual(["a", "b", "b.ba", "b.bb", "b.bc", "b.bc.bca", "b.bc.bcb", "c"], keys) + + def test_sear(self): + result = utils.search_configs(self.config_dict, "a") + self.assertEqual({"a": 1}, result) + result = utils.search_configs(self.config_dict, "A") + self.assertEqual(None, result) + result = utils.search_configs(self.config_dict, "b") + self.assertEqual({ + "b": { + "ba": 21, + "bb": 22, + "bc": { + "bca": 231, + "bcb": 232 + } + } + }, result) + result = utils.search_configs(self.config_dict, "b.bb") + self.assertEqual({ + "b": { + "bb": 22 + } + }, result) + result = utils.search_configs(self.config_dict, "b.bc") + self.assertEqual({ + "b": { + "bc": { + "bca": 231, + "bcb": 232 + } + } + }, result) + result = utils.search_configs(self.config_dict, "b.bc.bcb") + self.assertEqual({ + "b": { + "bc": { + "bcb": 232 + } + } + }, result) + result = utils.search_configs(self.config_dict, "b.BC.bCb") + self.assertEqual(None, result) + result = utils.search_configs(self.config_dict, "b.BC.bCb") + self.assertEqual(None, result) + result = utils.search_configs(self.config_dict, "d") + self.assertEqual(None, result) + result = utils.search_configs(self.config_dict, "b.xyz") + self.assertEqual(None, result) + result = utils.search_configs(self.config_dict, "b.bb.xyz") + self.assertEqual(None, result) diff --git a/test/hummingbot/core/utils/test_gateway_transaction_exceptions.py b/test/hummingbot/core/utils/test_gateway_transaction_exceptions.py new file mode 100644 index 0000000..0ae364e --- /dev/null +++ b/test/hummingbot/core/utils/test_gateway_transaction_exceptions.py @@ -0,0 +1,49 @@ +""" +Unit tests for test_check_transaction_exceptions +""" + +from decimal import Decimal +from typing import Dict, Any +import unittest.mock + +from hummingbot.core.event.events import TradeType +from hummingbot.core.gateway import check_transaction_exceptions + + +class CheckTransactionExceptionsTest(unittest.TestCase): + def test_check_transaction_exceptions(self): + """ + Unit tests for hummingbot.core.gateway.check_transaction_exceptions + """ + + # create transactions data that should result in no warnings + transaction_args: Dict[Any] = { + "allowances": {"WBTC": Decimal(1000)}, + "balances": {"ETH": Decimal(1000)}, + "base_asset": "ETH", + "quote_asset": "WBTC", + "amount": Decimal(1000), + "side": TradeType.BUY, + "gas_limit": 22000, + "gas_cost": Decimal(90), + "gas_asset": "ETH", + "swaps_count": 2 + } + self.assertEqual(check_transaction_exceptions(**transaction_args), []) + + # ETH balance less than gas_cost + invalid_transaction_1 = transaction_args.copy() + invalid_transaction_1["balances"] = {"ETH": Decimal(10)} + self.assertRegexpMatches( + check_transaction_exceptions(**invalid_transaction_1)[0], r"^Insufficient ETH balance to cover gas" + ) + + # Gas limit set too low, gas_limit is less than 21000 + invalid_transaction_2 = transaction_args.copy() + invalid_transaction_2["gas_limit"] = 10000 + self.assertRegexpMatches(check_transaction_exceptions(**invalid_transaction_2)[0], r"^Gas limit") + + # Insufficient token allowance, allowance of quote less than amount + invalid_transaction_3 = transaction_args.copy() + invalid_transaction_3["allowances"] = {"WBTC": Decimal(500)} + self.assertRegexpMatches(check_transaction_exceptions(**invalid_transaction_3)[0], r"^Insufficient") diff --git a/test/hummingbot/core/utils/test_map_df_to_str.py b/test/hummingbot/core/utils/test_map_df_to_str.py new file mode 100644 index 0000000..d5881e8 --- /dev/null +++ b/test/hummingbot/core/utils/test_map_df_to_str.py @@ -0,0 +1,16 @@ +import unittest +import pandas as pd +from hummingbot.core.utils import map_df_to_str + + +class MapDfToStrTest(unittest.TestCase): + + def test_map_df_to_str(self): + df = pd.DataFrame(data=[0.2, 0, 1, 100., 1.00]) + df = map_df_to_str(df) + self.assertEqual(df.to_string(), " 0\n" + "0 0.2\n" + "1 0\n" + "2 1\n" + "3 100\n" + "4 1") diff --git a/test/hummingbot/core/utils/test_market_price.py b/test/hummingbot/core/utils/test_market_price.py new file mode 100644 index 0000000..4b087ea --- /dev/null +++ b/test/hummingbot/core/utils/test_market_price.py @@ -0,0 +1,62 @@ +import asyncio +import re +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict +from unittest.mock import patch + +import ujson +from aioresponses import aioresponses +from bidict import bidict + +import hummingbot.connector.exchange.binance.binance_constants as CONSTANTS +import hummingbot.connector.exchange.binance.binance_web_utils as web_utils +import hummingbot.core.utils.market_price as market_price +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange + + +class MarketPriceUnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.binance_ex_trading_pair = f"{cls.base_asset}{cls.quote_asset}" + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + @patch("hummingbot.client.settings.ConnectorSetting.non_trading_connector_instance_with_default_configuration") + def test_get_last_price(self, mock_api, connector_creator_mock): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + connector = BinanceExchange( + client_config_map, + binance_api_key="", + binance_api_secret="", + trading_pairs=[], + trading_required=False) + connector._set_trading_pair_symbol_map(bidict({f"{self.binance_ex_trading_pair}": self.trading_pair})) + connector_creator_mock.return_value = connector + + url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + mock_response: Dict[str, Any] = { + # truncated response + "symbol": self.binance_ex_trading_pair, + "lastPrice": "1", + } + mock_api.get(regex_url, body=ujson.dumps(mock_response)) + + result = self.async_run_with_timeout(market_price.get_last_price( + exchange="binance", + trading_pair=self.trading_pair, + )) + + self.assertEqual(result, Decimal("1.0")) diff --git a/test/hummingbot/core/utils/test_nonce_creator.py b/test/hummingbot/core/utils/test_nonce_creator.py new file mode 100644 index 0000000..a61b00d --- /dev/null +++ b/test/hummingbot/core/utils/test_nonce_creator.py @@ -0,0 +1,68 @@ +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.core.utils.tracking_nonce import NonceCreator + + +class NonceCreatorTests(TestCase): + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def test_create_seconds_precision_nonce_from_machine_time(self, time_mock): + time_mock.return_value = 1112223334.445556 + nonce_creator = NonceCreator.for_seconds() + + nonce = nonce_creator.get_tracking_nonce() + self.assertEqual(int(time_mock.return_value), nonce) + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def test_create_milliseconds_precision_nonce_from_machine_time(self, time_mock): + time_mock.return_value = 1112223334.445556 + nonce_creator = NonceCreator.for_milliseconds() + + nonce = nonce_creator.get_tracking_nonce() + self.assertEqual(int(time_mock.return_value * 1e3), nonce) + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def test_create_microseconds_precision_nonce_from_machine_time(self, time_mock): + time_mock.return_value = 1112223334.445556 + nonce_creator = NonceCreator.for_microseconds() + + nonce = nonce_creator.get_tracking_nonce() + self.assertEqual(int(time_mock.return_value * 1e6), nonce) + + def test_create_seconds_precision_nonce_from_parameter(self): + timestamp = 1112223334.445556 + nonce_creator = NonceCreator.for_seconds() + + nonce = nonce_creator.get_tracking_nonce(timestamp=timestamp) + self.assertEqual(int(timestamp), nonce) + + def test_create_milliseconds_precision_nonce_from_parameter(self): + timestamp = 1112223334.445556 + nonce_creator = NonceCreator.for_milliseconds() + + nonce = nonce_creator.get_tracking_nonce(timestamp=timestamp) + self.assertEqual(int(timestamp * 1e3), nonce) + + def test_create_microseconds_precision_nonce_from_parameter(self): + timestamp = 1112223334.445556 + nonce_creator = NonceCreator.for_microseconds() + + nonce = nonce_creator.get_tracking_nonce(timestamp=timestamp) + self.assertEqual(int(timestamp * 1e6), nonce) + + @patch("hummingbot.core.utils.tracking_nonce.NonceCreator._time") + def test_nonce_from_machine_time_is_not_repeated(self, time_mock): + time_mock.return_value = 1112223334.445556 + nonce_creator = NonceCreator.for_seconds() + + first_nonce = nonce_creator.get_tracking_nonce() + second_nonce = nonce_creator.get_tracking_nonce() + self.assertEqual(second_nonce, first_nonce + 1) + + def test_nonce_from_same_base_number_is_not_repeated(self): + nonce_creator = NonceCreator.for_seconds() + + first_nonce = nonce_creator.get_tracking_nonce(timestamp=1234567890) + second_nonce = nonce_creator.get_tracking_nonce(timestamp=1234567890) + self.assertEqual(second_nonce, first_nonce + 1) diff --git a/test/hummingbot/core/utils/test_ssl_cert.py b/test/hummingbot/core/utils/test_ssl_cert.py new file mode 100644 index 0000000..81b9640 --- /dev/null +++ b/test/hummingbot/core/utils/test_ssl_cert.py @@ -0,0 +1,127 @@ +""" +Unit tests for hummingbot.core.utils.ssl_cert +""" + +import os +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.core.gateway import GatewayPaths +from hummingbot.core.utils.ssl_cert import ( + certs_files_exist, + create_self_sign_certs, + generate_csr, + generate_private_key, + generate_public_key, + sign_csr, +) + + +class SslCertTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + + def test_generate_private_key(self): + """ + Unit tests for generate_private_key + """ + # Assert that it generates a file + temp_dir = tempfile.gettempdir() + private_key_file_path = temp_dir + "/private_key_test" + generate_private_key("topsecret", private_key_file_path) + self.assertEqual(os.path.exists(private_key_file_path), True) + + def test_generate_public_key(self): + """ + Unit tests for generate_public_key + """ + + # create a private key + temp_dir = tempfile.gettempdir() + private_key_file_path = temp_dir + "/private_key_test" + private_key = generate_private_key("topsecret", private_key_file_path) + + # create the public key, assert that the file exists + public_key_file_path = temp_dir + "/public_key_test" + generate_public_key(private_key, public_key_file_path) + self.assertEqual(os.path.exists(public_key_file_path), True) + + def test_generate_csr(self): + """ + Unit tests for generate_csr + """ + + # create a private key + temp_dir = tempfile.gettempdir() + private_key_file_path = temp_dir + "/private_key_test" + private_key = generate_private_key("topsecret", private_key_file_path) + + # create a csr and assert that it exists + csr_file_path = temp_dir + "/csr_test" + generate_csr(private_key, csr_file_path) + self.assertEqual(os.path.exists(csr_file_path), True) + + def test_sign_csr(self): + """ + Unit tests for sign_csr + """ + + # create a private key + temp_dir = tempfile.gettempdir() + private_key_file_path = temp_dir + "/private_key_test" + private_key = generate_private_key("topsecret", private_key_file_path) + + # create a public key + public_key_file_path = temp_dir + "/public_key_test" + public_key = generate_public_key(private_key, public_key_file_path) + + # create a csr + csr_file_path = temp_dir + "/csr_test" + csr = generate_csr(private_key, csr_file_path) + + # create a verified public key + verified_public_key_file_path = temp_dir + "/verified_public_key" + sign_csr(csr, public_key, private_key, verified_public_key_file_path) + self.assertEqual(os.path.exists(verified_public_key_file_path), True) + + # try to create a verified public key with the wrong private key + # x509 does not stop you from doing this and will still output a file + # so we just do a simple check that the outputs are not the same + private_key_file_path2 = temp_dir + "/private_key_test2" + private_key2 = generate_private_key("topsecret2", private_key_file_path2) + verified_public_key_file_path2 = temp_dir + "/verified_public_key2" + sign_csr(csr, public_key, private_key2, verified_public_key_file_path2) + + with open(verified_public_key_file_path, "rb") as verified_public_key: + with open(verified_public_key_file_path2, "rb") as verified_public_key2: + self.assertNotEqual(verified_public_key, verified_public_key2) + + def test_create_self_sign_certs(self): + """ + Unit tests for create_self_sign_certs and certs_files_exist + """ + + # setup global cert_path and make sure it is empty + with tempfile.TemporaryDirectory() as tempdir: + temppath: Path = Path(tempdir) + mock_gateway_paths: GatewayPaths = GatewayPaths( + local_conf_path=temppath.joinpath("conf"), + local_certs_path=temppath.joinpath("certs"), + local_logs_path=temppath.joinpath("logs"), + mount_conf_path=temppath.joinpath("conf"), + mount_certs_path=temppath.joinpath("certs"), + mount_logs_path=temppath.joinpath("logs"), + ) + + with patch("hummingbot.core.utils.ssl_cert.get_gateway_paths", return_value=mock_gateway_paths): + self.assertEqual(certs_files_exist(client_config_map=self.client_config_map), False) + + # generate all necessary certs then confirm they exist in the expected place + create_self_sign_certs("abc123", cert_path=mock_gateway_paths.local_certs_path.as_posix()) + self.assertEqual(certs_files_exist(client_config_map=self.client_config_map), True) diff --git a/test/hummingbot/core/utils/test_tracking_nonce.py b/test/hummingbot/core/utils/test_tracking_nonce.py new file mode 100644 index 0000000..e47bdcc --- /dev/null +++ b/test/hummingbot/core/utils/test_tracking_nonce.py @@ -0,0 +1,33 @@ +from unittest import TestCase +import asyncio + +import hummingbot.core.utils.tracking_nonce as tracking_nonce + + +class TrackingNonceTest(TestCase): + + def test_get_tracking_nonce(self): + nonce = tracking_nonce.get_tracking_nonce() + self.assertIsNotNone(nonce) + new_nonce = tracking_nonce.get_tracking_nonce() + self.assertGreater(new_nonce, nonce) + + def test_get_low_res_tracking_nonce(self): + nonce = tracking_nonce.get_tracking_nonce_low_res() + self.assertIsNotNone(nonce) + new_nonce = tracking_nonce.get_tracking_nonce_low_res() + self.assertGreater(new_nonce, nonce) + + def test_get_concurrent_nonce_in_low_res(self): + async def task(): + return tracking_nonce.get_tracking_nonce_low_res() + tasks = [task(), task()] + ret = asyncio.get_event_loop().run_until_complete(asyncio.gather(*tasks)) + self.assertGreaterEqual(ret[1], ret[0]) + + def test_get_concurrent_nonce_in_high_res(self): + async def task(): + return tracking_nonce.get_tracking_nonce() + tasks = [task(), task()] + ret = asyncio.get_event_loop().run_until_complete(asyncio.gather(*tasks)) + self.assertGreaterEqual(ret[1], ret[0]) diff --git a/test/hummingbot/core/utils/test_trading_pair_fetcher.py b/test/hummingbot/core/utils/test_trading_pair_fetcher.py new file mode 100644 index 0000000..4e4df83 --- /dev/null +++ b/test/hummingbot/core/utils/test_trading_pair_fetcher.py @@ -0,0 +1,283 @@ +import asyncio +import json +import unittest +from decimal import Decimal +from typing import Any, Awaitable, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.config.security import Security +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.connector.exchange.binance import binance_constants as CONSTANTS, binance_web_utils +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher + + +class TestTradingPairFetcher(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.ev_loop = asyncio.get_event_loop() + + @classmethod + async def wait_until_trading_pair_fetcher_ready(cls, tpf): + while True: + if tpf.ready: + break + else: + await asyncio.sleep(0) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + class MockConnectorSetting(MagicMock): + def __init__(self, name, parent_name=None, connector=None, *args, **kwargs) -> None: + super().__init__(name, *args, **kwargs) + self._name = name + self._parent_name = parent_name + self._connector = connector + + @property + def name(self) -> str: + return self._name + + @property + def parent_name(self) -> str: + return self._parent_name + + def base_name(self) -> str: + return self.name + + def connector_connected(self) -> bool: + return True + + def add_domain_parameter(*_, **__) -> Dict[str, Any]: + return {} + + def uses_gateway_generic_connector(self) -> bool: + return False + + def non_trading_connector_instance_with_default_configuration(self, trading_pairs = None): + return self._connector + + @classmethod + def tearDownClass(cls) -> None: + # Need to reset TradingPairFetcher module so next time it gets imported it works as expected + TradingPairFetcher._sf_shared_instance = None + + def test_trading_pair_fetcher_returns_same_instance_when_get_new_instance_once_initialized(self): + instance = TradingPairFetcher.get_instance() + self.assertIs(instance, TradingPairFetcher.get_instance()) + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher._all_connector_settings") + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher._sf_shared_instance") + def test_fetched_connector_trading_pairs(self, _, mock_connector_settings): + connector = AsyncMock() + connector.all_trading_pairs.return_value = ["MOCK-HBOT"] + mock_connector_settings.return_value = { + "mock_exchange_1": self.MockConnectorSetting(name="mockConnector", connector=connector), + "mock_paper_trade": self.MockConnectorSetting(name="mock_paper_trade", parent_name="mock_exchange_1") + } + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + client_config_map.fetch_pairs_from_all_exchanges = True + trading_pair_fetcher = TradingPairFetcher(client_config_map) + self.async_run_with_timeout(self.wait_until_trading_pair_fetcher_ready(trading_pair_fetcher), 1.0) + trading_pairs = trading_pair_fetcher.trading_pairs + self.assertEqual(2, len(trading_pairs)) + self.assertEqual({"mockConnector": ["MOCK-HBOT"], "mock_paper_trade": ["MOCK-HBOT"]}, trading_pairs) + + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher._all_connector_settings") + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher._sf_shared_instance") + @patch("hummingbot.client.config.security.Security.connector_config_file_exists") + @patch("hummingbot.client.config.security.Security.wait_til_decryption_done") + def test_fetched_connected_trading_pairs(self, _, __: MagicMock, ___: AsyncMock, mock_connector_settings): + connector = AsyncMock() + connector.all_trading_pairs.return_value = ["MOCK-HBOT"] + mock_connector_settings.return_value = { + "mock_exchange_1": self.MockConnectorSetting(name="binance", connector=connector), + "mock_paper_trade": self.MockConnectorSetting(name="mock_paper_trade", parent_name="mock_exchange_1") + } + + client_config_map = ClientConfigAdapter(ClientConfigMap()) + client_config_map.fetch_pairs_from_all_exchanges = False + self.assertTrue(Security.connector_config_file_exists("binance")) + trading_pair_fetcher = TradingPairFetcher(client_config_map) + self.async_run_with_timeout(self.wait_until_trading_pair_fetcher_ready(trading_pair_fetcher), 1.0) + trading_pairs = trading_pair_fetcher.trading_pairs + self.assertEqual(2, len(trading_pairs)) + self.assertEqual({"binance": ["MOCK-HBOT"], "mock_paper_trade": ["MOCK-HBOT"]}, trading_pairs) + + @aioresponses() + @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher._all_connector_settings") + @patch("hummingbot.core.gateway.gateway_http_client.GatewayHttpClient.get_perp_markets") + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + def test_fetch_all(self, mock_api, con_spec_mock, perp_market_mock, all_connector_settings_mock, ): + client_config_map = ClientConfigAdapter(ClientConfigMap()) + client_config_map.fetch_pairs_from_all_exchanges = True + all_connector_settings_mock.return_value = { + "binance": ConnectorSetting( + name='binance', + type=ConnectorType.Exchange, + example_pair='ZRX-ETH', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'binance_api_key': ConfigVar(key='binance_api_key', prompt=""), + 'binance_api_secret': ConfigVar(key='binance_api_secret', prompt="")}, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False), + "perp_ethereum_optimism": ConnectorSetting( + name='perp_ethereum_optimism', + type=ConnectorType.AMM_Perpetual, + example_pair='ZRX-ETH', + centralised=False, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys=None, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + } + + url = binance_web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL) + mock_response: Dict[str, Any] = { + "timezone": "UTC", + "serverTime": 1639598493658, + "rateLimits": [], + "exchangeFilters": [], + "symbols": [ + { + "symbol": "ETHBTC", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "SPOT", + "MARGIN" + ] + }, + { + "symbol": "LTCBTC", + "status": "TRADING", + "baseAsset": "LTC", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "SPOT", + "MARGIN" + ] + }, + { + "symbol": "BNBBTC", + "status": "TRADING", + "baseAsset": "BNB", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "quoteAssetPrecision": 8, + "baseCommissionPrecision": 8, + "quoteCommissionPrecision": 8, + "orderTypes": [ + "LIMIT", + "LIMIT_MAKER", + "MARKET", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT_LIMIT" + ], + "icebergAllowed": True, + "ocoAllowed": True, + "quoteOrderQtyMarketAllowed": True, + "isSpotTradingAllowed": True, + "isMarginTradingAllowed": True, + "filters": [], + "permissions": [ + "MARGIN" + ] + }, + ] + } + + mock_api.get(url, body=json.dumps(mock_response)) + perp_market_mock.return_value = {"pairs": ["ABCUSD"]} + con_spec_mock.return_value = { + "connector": "perp", + "chain": "optimism", + "network": "ethereum", + "trading_type": "AMM_Perpetual", + "chain_type": "EVM", + "wallet_address": "0x..." + } + + fetcher = TradingPairFetcher(client_config_map) + asyncio.get_event_loop().run_until_complete(fetcher._fetch_task) + trading_pairs = fetcher.trading_pairs + + self.assertEqual(2, len(trading_pairs.keys())) + self.assertIn("binance", trading_pairs) + binance_pairs = trading_pairs["binance"] + self.assertEqual(2, len(binance_pairs)) + self.assertIn("ETH-BTC", binance_pairs) + self.assertIn("LTC-BTC", binance_pairs) + self.assertNotIn("BNB-BTC", binance_pairs) + perp_pairs = trading_pairs["perp_ethereum_optimism"] + self.assertEqual(1, len(perp_pairs)) + self.assertIn("ABC-USD", perp_pairs) + self.assertNotIn("WETH-USDT", perp_pairs) diff --git a/test/hummingbot/core/web_assistant/__init__.py b/test/hummingbot/core/web_assistant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/web_assistant/connections/__init__.py b/test/hummingbot/core/web_assistant/connections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/core/web_assistant/connections/test_connections_factory.py b/test/hummingbot/core/web_assistant/connections/test_connections_factory.py new file mode 100644 index 0000000..859de56 --- /dev/null +++ b/test/hummingbot/core/web_assistant/connections/test_connections_factory.py @@ -0,0 +1,36 @@ +import asyncio +import unittest +from typing import Awaitable + +from hummingbot.core.web_assistant.connections.connections_factory import ( + ConnectionsFactory +) +from hummingbot.core.web_assistant.connections.rest_connection import ( + RESTConnection +) +from hummingbot.core.web_assistant.connections.ws_connection import WSConnection + + +class ConnectionsFactoryTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_get_rest_connection(self): + factory = ConnectionsFactory() + + rest_connection = self.async_run_with_timeout(factory.get_rest_connection()) + + self.assertIsInstance(rest_connection, RESTConnection) + + def test_get_ws_connection(self): + factory = ConnectionsFactory() + + rest_connection = self.async_run_with_timeout(factory.get_ws_connection()) + + self.assertIsInstance(rest_connection, WSConnection) diff --git a/test/hummingbot/core/web_assistant/connections/test_data_types.py b/test/hummingbot/core/web_assistant/connections/test_data_types.py new file mode 100644 index 0000000..99ae601 --- /dev/null +++ b/test/hummingbot/core/web_assistant/connections/test_data_types.py @@ -0,0 +1,129 @@ +import asyncio +import json +import unittest +from typing import Awaitable + +import aiohttp +from aioresponses import aioresponses + +from hummingbot.core.web_assistant.connections.data_types import ( + RESTMethod, RESTResponse, EndpointRESTRequest +) + + +class DataTypesTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_rest_method_to_str(self): + method = RESTMethod.GET + method_str = str(method) + + self.assertEqual("GET", method_str) + + @aioresponses() + def test_rest_response_properties(self, mocked_api): + url = "https://some.url" + body = {"one": 1} + body_str = json.dumps(body) + headers = {"content-type": "application/json"} + mocked_api.get(url=url, body=body_str, headers=headers) + aiohttp_response = self.async_run_with_timeout(aiohttp.ClientSession().get(url)) + + response = RESTResponse(aiohttp_response) + + self.assertEqual(url, response.url) + self.assertEqual(RESTMethod.GET, response.method) + self.assertEqual(200, response.status) + self.assertEqual(headers, response.headers) + + json_ = self.async_run_with_timeout(response.json()) + + self.assertEqual(body, json_) + + text = self.async_run_with_timeout(response.text()) + + self.assertEqual(body_str, text) + + @aioresponses() + def test_rest_response_repr(self, mocked_api): + url = "https://some.url" + body = {"one": 1} + body_str = json.dumps(body) + headers = {"content-type": "application/json"} + mocked_api.get(url=url, body=body_str, headers=headers) + aiohttp_response = self.async_run_with_timeout(aiohttp.ClientSession().get(url)) + + response = RESTResponse(aiohttp_response) + + expected = ( + f"RESTResponse(url='{url}', method={RESTMethod.GET}, status=200, headers={aiohttp_response.headers})" + ) + actual = str(response) + + self.assertEqual(expected, actual) + + +class EndpointRESTRequestDummy(EndpointRESTRequest): + @property + def base_url(self) -> str: + return "https://some.url" + + +class EndpointRESTRequestTest(unittest.TestCase): + def test_constructs_url_from_endpoint(self): + endpoint = "some/endpoint" + alt_endpoint = "/some/endpoint" + request = EndpointRESTRequestDummy(method=RESTMethod.GET, endpoint=endpoint) + alt_request = EndpointRESTRequestDummy(method=RESTMethod.GET, endpoint=alt_endpoint) + + url = request.url + alt_url = alt_request.url + + self.assertEqual(f"{request.base_url}/{endpoint}", url) + self.assertEqual(url, alt_url) + + def test_raises_on_no_url_and_no_endpoint(self): + with self.assertRaises(ValueError): + EndpointRESTRequestDummy(method=RESTMethod.GET) + + def test_raises_on_params_supplied_to_post_request(self): + endpoint = "some/endpoint" + params = {"one": 1} + + with self.assertRaises(ValueError): + EndpointRESTRequestDummy( + method=RESTMethod.POST, + endpoint=endpoint, + params=params, + ) + + def test_data_to_str(self): + endpoint = "some/endpoint" + data = {"one": 1} + + request = EndpointRESTRequestDummy( + method=RESTMethod.POST, + endpoint=endpoint, + data=data, + ) + + self.assertIsInstance(request.data, str) + self.assertEqual(data, json.loads(request.data)) + + def test_raises_on_data_supplied_to_non_post_request(self): + endpoint = "some/endpoint" + data = {"one": 1} + + with self.assertRaises(ValueError): + EndpointRESTRequestDummy( + method=RESTMethod.GET, + endpoint=endpoint, + data=data, + ) diff --git a/test/hummingbot/core/web_assistant/connections/test_rest_connection.py b/test/hummingbot/core/web_assistant/connections/test_rest_connection.py new file mode 100644 index 0000000..48b53e3 --- /dev/null +++ b/test/hummingbot/core/web_assistant/connections/test_rest_connection.py @@ -0,0 +1,41 @@ +import asyncio +import json +import unittest +from typing import Awaitable + +import aiohttp +from aioresponses import aioresponses + +from hummingbot.core.web_assistant.connections.rest_connection import RESTConnection +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, RESTResponse + + +class RESTConnectionTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_rest_connection_call(self, mocked_api): + url = "https://www.test.com/url" + resp = {"one": 1} + mocked_api.get(url, body=json.dumps(resp).encode()) + + client_session = aiohttp.ClientSession() + connection = RESTConnection(client_session) + request = RESTRequest(method=RESTMethod.GET, url=url) + + ret = self.async_run_with_timeout(connection.call(request)) + + self.assertIsInstance(ret, RESTResponse) + self.assertEqual(url, ret.url) + self.assertEqual(200, ret.status) + + j = self.async_run_with_timeout(ret.json()) + + self.assertEqual(resp, j) diff --git a/test/hummingbot/core/web_assistant/connections/test_ws_connection.py b/test/hummingbot/core/web_assistant/connections/test_ws_connection.py new file mode 100644 index 0000000..3c88bf5 --- /dev/null +++ b/test/hummingbot/core/web_assistant/connections/test_ws_connection.py @@ -0,0 +1,250 @@ +import asyncio +import json +import unittest +from typing import Awaitable, List +from unittest.mock import AsyncMock, patch + +import aiohttp + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.web_assistant.connections.data_types import WSJSONRequest, WSResponse +from hummingbot.core.web_assistant.connections.ws_connection import WSConnection + + +class WSConnectionTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.ws_url = "ws://some/url" + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.client_session = aiohttp.ClientSession() + self.ws_connection = WSConnection(self.client_session) + self.async_tasks: List[asyncio.Task] = [] + + def tearDown(self) -> None: + self.ws_connection.disconnect() + self.client_session.close() + for task in self.async_tasks: + task.cancel() + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_connect_and_disconnect(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.assertFalse(self.ws_connection.connected) + + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + + self.assertTrue(self.ws_connection.connected) + + self.async_run_with_timeout(self.ws_connection.disconnect()) + + self.assertFalse(self.ws_connection.connected) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_attempt_to_connect_second_time_raises(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + + with self.assertRaises(RuntimeError) as e: + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + + self.assertEqual("WS is connected.", str(e.exception)) + + def test_send_when_disconnected_raises(self): + request = WSJSONRequest(payload={"one": 1}) + + with self.assertRaises(RuntimeError) as e: + self.async_run_with_timeout(self.ws_connection.send(request)) + + self.assertEqual("WS is not connected.", str(e.exception)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_send(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + request = WSJSONRequest(payload={"one": 1}) + + self.async_run_with_timeout(self.ws_connection.send(request)) + + json_msgs = self.mocking_assistant.json_messages_sent_through_websocket( + ws_connect_mock.return_value + ) + + self.assertEqual(1, len(json_msgs)) + self.assertEqual(request.payload, json_msgs[0]) + + def test_receive_when_disconnected_raises(self): + with self.assertRaises(RuntimeError) as e: + self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual("WS is not connected.", str(e.exception)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_raises_on_timeout(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + def raise_timeout(*_, **__): + raise asyncio.TimeoutError + + ws_connect_mock.return_value.receive.side_effect = raise_timeout + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + + with self.assertRaises(asyncio.TimeoutError) as e: + self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual("Message receive timed out.", str(e.exception)) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + data = {"one": 1} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(data) + ) + + self.assertEqual(0, self.ws_connection.last_recv_time) + + response = self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertIsInstance(response, WSResponse) + self.assertEqual(data, response.data) + self.assertNotEqual(0, self.ws_connection.last_recv_time) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_disconnects_and_raises_on_aiohttp_closed(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.close_code = 1111 + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.CLOSED + ) + + with self.assertRaises(ConnectionError) as e: + self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual("The WS connection was closed unexpectedly. Close code = 1111 msg data: ", str(e.exception)) + self.assertFalse(self.ws_connection.connected) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_disconnects_and_raises_on_aiohttp_close(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + ws_connect_mock.return_value.close_code = 1111 + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.CLOSE + ) + + with self.assertRaises(ConnectionError) as e: + self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual("The WS connection was closed unexpectedly. Close code = 1111 msg data: ", str(e.exception)) + self.assertFalse(self.ws_connection.connected) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_ignores_aiohttp_close_msg_if_disconnect_called(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.CLOSED + ) + prev_side_effect = ws_connect_mock.return_value.receive.side_effect + + async def disconnect_on_side_effect(*args, **kwargs): + await self.ws_connection.disconnect() + return await prev_side_effect(*args, **kwargs) + + ws_connect_mock.return_value.receive.side_effect = disconnect_on_side_effect + + response = self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertFalse(self.ws_connection.connected) + self.assertIsNone(response) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_ignores_ping(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.PING + ) + data = {"one": 1} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(data) + ) + + response = self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual(data, response.data) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_sends_pong_on_ping(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.PING + ) + receive_task = self.ev_loop.create_task(self.ws_connection.receive()) + self.async_tasks.append(receive_task) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + ws_connect_mock.return_value.pong.assert_called() + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_ping_updates_last_recv_time(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.PING + ) + receive_task = self.ev_loop.create_task(self.ws_connection.receive()) + self.async_tasks.append(receive_task) + + self.assertEqual(0, self.ws_connection.last_recv_time) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertNotEqual(0, self.ws_connection.last_recv_time) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_ignores_pong(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.PONG + ) + data = {"one": 1} + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message=json.dumps(data) + ) + + response = self.async_run_with_timeout(self.ws_connection.receive()) + + self.assertEqual(data, response.data) + + @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_pong_updates_last_recv_time(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.async_run_with_timeout(self.ws_connection.connect(self.ws_url)) + self.mocking_assistant.add_websocket_aiohttp_message( + ws_connect_mock.return_value, message="", message_type=aiohttp.WSMsgType.PONG + ) + receive_task = self.ev_loop.create_task(self.ws_connection.receive()) + self.async_tasks.append(receive_task) + + self.assertEqual(0, self.ws_connection.last_recv_time) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertNotEqual(0, self.ws_connection.last_recv_time) diff --git a/test/hummingbot/core/web_assistant/test_rest_assistant.py b/test/hummingbot/core/web_assistant/test_rest_assistant.py new file mode 100644 index 0000000..d37644f --- /dev/null +++ b/test/hummingbot/core/web_assistant/test_rest_assistant.py @@ -0,0 +1,102 @@ +import asyncio +import json +import unittest +from typing import Awaitable, Optional +from unittest.mock import patch + +import aiohttp +from aioresponses import aioresponses + +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest, RESTResponse, WSRequest +from hummingbot.core.web_assistant.connections.rest_connection import RESTConnection +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.rest_post_processors import RESTPostProcessorBase +from hummingbot.core.web_assistant.rest_pre_processors import RESTPreProcessorBase + + +class RESTAssistantTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @aioresponses() + def test_rest_assistant_call_with_pre_and_post_processing(self, mocked_api): + url = "https://www.test.com/url" + resp = {"one": 1} + pre_processor_ran = False + post_processor_ran = False + mocked_api.get(url, body=json.dumps(resp).encode()) + + class PreProcessor(RESTPreProcessorBase): + async def pre_process(self, request: RESTRequest) -> RESTRequest: + nonlocal pre_processor_ran + pre_processor_ran = True + return request + + class PostProcessor(RESTPostProcessorBase): + async def post_process(self, response: RESTResponse) -> RESTResponse: + nonlocal post_processor_ran + post_processor_ran = True + return response + + pre_processors = [PreProcessor()] + post_processors = [PostProcessor()] + connection = RESTConnection(aiohttp.ClientSession()) + assistant = RESTAssistant( + connection=connection, + throttler=AsyncThrottler(rate_limits=[]), + rest_pre_processors=pre_processors, + rest_post_processors=post_processors) + req = RESTRequest(method=RESTMethod.GET, url=url) + + ret = self.async_run_with_timeout(assistant.call(req)) + ret_json = self.async_run_with_timeout(ret.json()) + + self.assertEqual(resp, ret_json) + self.assertTrue(pre_processor_ran) + self.assertTrue(post_processor_ran) + + @patch("hummingbot.core.web_assistant.connections.rest_connection.RESTConnection.call") + def test_rest_assistant_authenticates(self, mocked_call): + url = "https://www.test.com/url" + resp = {"one": 1} + call_request: Optional[RESTRequest] = None + auth_header = {"authenticated": True} + + async def register_request_and_return(request: RESTRequest): + nonlocal call_request + call_request = request + return resp + + mocked_call.side_effect = register_request_and_return + + class AuthDummy(AuthBase): + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + request.headers = auth_header + return request + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + pass + + connection = RESTConnection(aiohttp.ClientSession()) + assistant = RESTAssistant(connection, throttler=AsyncThrottler(rate_limits=[]), auth=AuthDummy()) + req = RESTRequest(method=RESTMethod.GET, url=url) + auth_req = RESTRequest(method=RESTMethod.GET, url=url, is_auth_required=True) + + self.async_run_with_timeout(assistant.call(req)) + + self.assertIsNotNone(call_request) + self.assertIsNone(call_request.headers) + + self.async_run_with_timeout(assistant.call(auth_req)) + + self.assertIsNotNone(call_request) + self.assertIsNotNone(call_request.headers) + self.assertEqual(call_request.headers, auth_header) diff --git a/test/hummingbot/core/web_assistant/test_web_assistants_factory.py b/test/hummingbot/core/web_assistant/test_web_assistants_factory.py new file mode 100644 index 0000000..8886f4f --- /dev/null +++ b/test/hummingbot/core/web_assistant/test_web_assistants_factory.py @@ -0,0 +1,33 @@ +import asyncio +import unittest +from typing import Awaitable + +from hummingbot.core.api_throttler.async_throttler import AsyncThrottler +from hummingbot.core.web_assistant.rest_assistant import RESTAssistant +from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory +from hummingbot.core.web_assistant.ws_assistant import WSAssistant + + +class WebAssistantsFactoryTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def test_get_rest_assistant(self): + factory = WebAssistantsFactory(throttler=AsyncThrottler(rate_limits=[])) + + rest_assistant = self.async_run_with_timeout(factory.get_rest_assistant()) + + self.assertIsInstance(rest_assistant, RESTAssistant) + + def test_get_ws_assistant(self): + factory = WebAssistantsFactory(throttler=AsyncThrottler(rate_limits=[])) + + ws_assistant = self.async_run_with_timeout(factory.get_ws_assistant()) + + self.assertIsInstance(ws_assistant, WSAssistant) diff --git a/test/hummingbot/core/web_assistant/test_ws_assistant.py b/test/hummingbot/core/web_assistant/test_ws_assistant.py new file mode 100644 index 0000000..9d13e9e --- /dev/null +++ b/test/hummingbot/core/web_assistant/test_ws_assistant.py @@ -0,0 +1,202 @@ +import asyncio +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, PropertyMock, patch + +import aiohttp + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.core.web_assistant.auth import AuthBase +from hummingbot.core.web_assistant.connections.data_types import RESTRequest, WSJSONRequest, WSRequest, WSResponse +from hummingbot.core.web_assistant.connections.ws_connection import WSConnection +from hummingbot.core.web_assistant.ws_assistant import WSAssistant +from hummingbot.core.web_assistant.ws_post_processors import WSPostProcessorBase +from hummingbot.core.web_assistant.ws_pre_processors import WSPreProcessorBase + + +class WSAssistantTest(unittest.TestCase): + ev_loop: asyncio.AbstractEventLoop + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cls.ev_loop): + task.cancel() + + def setUp(self) -> None: + super().setUp() + aiohttp_client_session = aiohttp.ClientSession() + self.ws_connection = WSConnection(aiohttp_client_session) + self.ws_assistant = WSAssistant(self.ws_connection) + self.mocking_assistant = NetworkMockingAssistant() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.connect") + def test_connect(self, connect_mock): + ws_url = "ws://some.url" + ping_timeout = 10 + message_timeout = 20 + + self.async_run_with_timeout( + self.ws_assistant.connect(ws_url, ping_timeout=ping_timeout, message_timeout=message_timeout) + ) + + connect_mock.assert_called_with(ws_url=ws_url, + ws_headers={}, + ping_timeout=ping_timeout, + message_timeout=message_timeout) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.disconnect") + def test_disconnect(self, disconnect_mock): + self.async_run_with_timeout(self.ws_assistant.disconnect()) + + disconnect_mock.assert_called() + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.send") + def test_send(self, send_mock): + sent_requests = [] + send_mock.side_effect = lambda r: sent_requests.append(r) + payload = {"one": 1} + request = WSJSONRequest(payload) + + self.async_run_with_timeout(self.ws_assistant.send(request)) + + self.assertEqual(1, len(sent_requests)) + + sent_request = sent_requests[0] + + self.assertNotEqual(id(request), id(sent_request)) # has been cloned + self.assertEqual(request, sent_request) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.send") + def test_send_pre_processes(self, send_mock): + class SomePreProcessor(WSPreProcessorBase): + async def pre_process(self, request_: RESTRequest) -> RESTRequest: + request_.payload["two"] = 2 + return request_ + + ws_assistant = WSAssistant( + connection=self.ws_connection, ws_pre_processors=[SomePreProcessor()] + ) + sent_requests = [] + send_mock.side_effect = lambda r: sent_requests.append(r) + payload = {"one": 1} + request = WSJSONRequest(payload) + + self.async_run_with_timeout(ws_assistant.send(request)) + + sent_request = sent_requests[0] + expected = {"one": 1, "two": 2} + + self.assertEqual(expected, sent_request.payload) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.send") + def test_subscribe(self, send_mock): + sent_requests = [] + send_mock.side_effect = lambda r: sent_requests.append(r) + payload = {"one": 1} + request = WSJSONRequest(payload) + + self.async_run_with_timeout(self.ws_assistant.subscribe(request)) + + self.assertEqual(1, len(sent_requests)) + + sent_request = sent_requests[0] + + self.assertNotEqual(id(request), id(sent_request)) # has been cloned + self.assertEqual(request, sent_request) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.send") + def test_ws_assistant_authenticates(self, send_mock): + class Auth(AuthBase): + async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: + pass + + async def ws_authenticate(self, request: WSRequest) -> WSRequest: + request.payload["authenticated"] = True + return request + + ws_assistant = WSAssistant(connection=self.ws_connection, auth=Auth()) + sent_requests = [] + send_mock.side_effect = lambda r: sent_requests.append(r) + payload = {"one": 1} + req = WSJSONRequest(payload) + auth_req = WSJSONRequest(payload, is_auth_required=True) + + self.async_run_with_timeout(ws_assistant.send(req)) + self.async_run_with_timeout(ws_assistant.send(auth_req)) + + sent_request = sent_requests[0] + auth_sent_request = sent_requests[1] + expected = {"one": 1} + auth_expected = {"one": 1, "authenticated": True} + + self.assertEqual(expected, sent_request.payload) + self.assertEqual(auth_expected, auth_sent_request.payload) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.receive") + def test_receive(self, receive_mock): + data = {"one": 1} + response_mock = WSResponse(data) + receive_mock.return_value = response_mock + + response = self.async_run_with_timeout(self.ws_assistant.receive()) + + self.assertEqual(data, response.data) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_receive_plain_text(self, ws_connect_mock): + data = "pong" + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=data) + self.async_run_with_timeout(self.ws_assistant.connect(ws_url="test.url")) + response = self.async_run_with_timeout(self.ws_assistant.receive()) + + self.assertEqual(data, response.data) + + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.receive") + def test_receive_post_processes(self, receive_mock): + class SomePostProcessor(WSPostProcessorBase): + async def post_process(self, response_: WSResponse) -> WSResponse: + response_.data["two"] = 2 + return response_ + + ws_assistant = WSAssistant( + connection=self.ws_connection, ws_post_processors=[SomePostProcessor()] + ) + data = {"one": 1} + response_mock = WSResponse(data) + receive_mock.return_value = response_mock + + response = self.async_run_with_timeout(ws_assistant.receive()) + + expected = {"one": 1, "two": 2} + + self.assertEqual(expected, response.data) + + @patch( + "hummingbot.core.web_assistant.connections.ws_connection.WSConnection.connected", + new_callable=PropertyMock, + ) + @patch("hummingbot.core.web_assistant.connections.ws_connection.WSConnection.receive") + def test_iter_messages(self, receive_mock, connected_mock): + connected_mock.return_value = True + data = {"one": 1} + response_mock = WSResponse(data) + receive_mock.return_value = response_mock + iter_messages_iterator = self.ws_assistant.iter_messages() + + response = self.async_run_with_timeout(iter_messages_iterator.__anext__()) + + self.assertEqual(data, response.data) + + connected_mock.return_value = False + + with self.assertRaises(StopAsyncIteration): + self.async_run_with_timeout(iter_messages_iterator.__anext__()) diff --git a/test/hummingbot/data_feed/__init__.py b/test/hummingbot/data_feed/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/__init__.py b/test/hummingbot/data_feed/candles_feed/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/test_ascend_ex_spot_candles.py b/test/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/test_ascend_ex_spot_candles.py new file mode 100644 index 0000000..d68e57b --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/ascend_ex_spot_candles/test_ascend_ex_spot_candles.py @@ -0,0 +1,311 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.ascend_ex_spot_candles import AscendExSpotCandles, constants as CONSTANTS + + +class TestAscendExSpotCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + "/" + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = AscendExSpotCandles(trading_pair=self.trading_pair, interval=self.interval) + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 2): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = { + "code": 0, + "data": [ + { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1688973840000, + "o": "30105.52", + "c": "30099.41", + "h": "30115.58", + "l": "30098.19", + "v": "0.13736" + } + }, + { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1688973900000, + "o": "30096.84", + "c": "30097.88", + "h": "30115.67", + "l": "30096.84", + "v": "0.16625" + } + }, + { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1688973960000, + "o": "30092.53", + "c": "30087.11", + "h": "30115.97", + "l": "30087.11", + "v": "0.06992" + } + }, + { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1688974020000, + "o": "30086.51", + "c": "30102.34", + "h": "30102.34", + "l": "30082.68", + "v": "0.14145" + } + }, + { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1688974080000, + "o": "30095.93", + "c": "30085.25", + "h": "30103.04", + "l": "30077.94", + "v": "0.15819" + } + } + ] + } + return data + + def get_candles_ws_data_mock_1(self): + data = { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1575398940000, + "o": "0.04993", + "c": "0.04970", + "h": "0.04993", + "l": "0.04970", + "v": "8052" + } + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "m": "bar", + "s": "BTC/USDT", + "data": { + "i": "1", + "ts": 1575398950000, + "o": "0.04993", + "c": "0.04970", + "h": "0.04993", + "l": "0.04970", + "v": "8052" + } + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1685167200 + end_time = 1685172600 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock['data'])) + self.assertEqual(resp.shape[1], 10) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "result": None, + "id": 1 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_kline_subscription = { + "op": CONSTANTS.SUB_ENDPOINT_NAME, + "ch": f"bar:{CONSTANTS.INTERVALS[self.interval]}:{self.ex_trading_pair}" + } + self.assertEqual(expected_kline_subscription["ch"], sent_subscription_messages[0]["ch"]) + + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.AscendExSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.AscendExSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.AscendExSpotCandles.fill_historical_candles", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.AscendExSpotCandles.fill_historical_candles", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.ascend_ex_spot_candles.AscendExSpotCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/binance_perpetuals_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/binance_perpetuals_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/binance_perpetuals_candles/test_binance_perpetuals_candles.py b/test/hummingbot/data_feed/candles_feed/binance_perpetuals_candles/test_binance_perpetuals_candles.py new file mode 100644 index 0000000..27344b5 --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/binance_perpetuals_candles/test_binance_perpetuals_candles.py @@ -0,0 +1,318 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.binance_perpetual_candles import BinancePerpetualCandles, constants as CONSTANTS + + +class TestBinancePerpetualCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = BinancePerpetualCandles(trading_pair=self.trading_pair, interval=self.interval) + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = [ + [ + 1672981200000, + "16823.24000000", + "16823.63000000", + "16792.12000000", + "16810.18000000", + "6230.44034000", + 1672984799999, + "104737787.36570630", + 162086, + "3058.60695000", + "51418990.63131130", + "0" + ], + [ + 1672984800000, + "16809.74000000", + "16816.45000000", + "16779.96000000", + "16786.86000000", + "6529.22759000", + 1672988399999, + "109693209.64287010", + 175249, + "3138.11977000", + "52721850.46080600", + "0" + ], + [ + 1672988400000, + "16786.60000000", + "16802.87000000", + "16780.15000000", + "16794.06000000", + "5763.44917000", + 1672991999999, + "96775667.56265520", + 160778, + "3080.59468000", + "51727251.37008490", + "0" + ], + [ + 1672992000000, + "16794.33000000", + "16812.22000000", + "16791.47000000", + "16802.11000000", + "5475.13940000", + 1672995599999, + "92000245.54341140", + 164303, + "2761.40926000", + "46400964.30558100", + "0" + ], + ] + return data + + def get_candles_ws_data_mock_1(self): + data = { + "e": "kline", + "E": 123456789, + "s": "BTCUSDT", + "k": {"t": 123400000, + "T": 123460000, + "s": "BNBBTC", + "i": "1m", + "f": 100, + "L": 200, + "o": "0.0010", + "c": "0.0020", + "h": "0.0025", + "l": "0.0015", + "v": "1000", + "n": 100, + "x": False, + "q": "1.0000", + "V": "500", + "Q": "0.500", + "B": "123456" + } + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "e": "kline", + "E": 123516789, + "s": "BTCUSDT", + "k": {"t": 123460000, + "T": 123460000, + "s": "BNBBTC", + "i": "1m", + "f": 100, + "L": 200, + "o": "0.0010", + "c": "0.0020", + "h": "0.0025", + "l": "0.0015", + "v": "1000", + "n": 100, + "x": False, + "q": "1.0000", + "V": "500", + "Q": "0.500", + "B": "123456" + } + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1672981200000 + end_time = 1672992000000 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}?endTime={end_time}&interval={self.interval}&limit=500" \ + f"&startTime={start_time}&symbol={self.ex_trading_pair}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock)) + self.assertEqual(resp.shape[1], 10) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "result": None, + "id": 1 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_kline_subscription = { + "method": "SUBSCRIBE", + "params": [f"{self.ex_trading_pair.lower()}@kline_{self.interval}"], + "id": 1} + + self.assertEqual(expected_kline_subscription, sent_subscription_messages[0]) + + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.binance_perpetual_candles.BinancePerpetualCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.binance_perpetual_candles.BinancePerpetualCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.binance_perpetual_candles.BinancePerpetualCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.binance_perpetual_candles.BinancePerpetualCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.binance_perpetual_candles.BinancePerpetualCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/binance_spot_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/binance_spot_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/binance_spot_candles/test_binance_spot_candles.py b/test/hummingbot/data_feed/candles_feed/binance_spot_candles/test_binance_spot_candles.py new file mode 100644 index 0000000..2d3c6e3 --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/binance_spot_candles/test_binance_spot_candles.py @@ -0,0 +1,319 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.binance_spot_candles import BinanceSpotCandles, constants as CONSTANTS + + +class TestBinanceSpotCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = BinanceSpotCandles(trading_pair=self.trading_pair, interval=self.interval) + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = [ + [ + 1672981200000, + "16823.24000000", + "16823.63000000", + "16792.12000000", + "16810.18000000", + "6230.44034000", + 1672984799999, + "104737787.36570630", + 162086, + "3058.60695000", + "51418990.63131130", + "0" + ], + [ + 1672984800000, + "16809.74000000", + "16816.45000000", + "16779.96000000", + "16786.86000000", + "6529.22759000", + 1672988399999, + "109693209.64287010", + 175249, + "3138.11977000", + "52721850.46080600", + "0" + ], + [ + 1672988400000, + "16786.60000000", + "16802.87000000", + "16780.15000000", + "16794.06000000", + "5763.44917000", + 1672991999999, + "96775667.56265520", + 160778, + "3080.59468000", + "51727251.37008490", + "0" + ], + [ + 1672992000000, + "16794.33000000", + "16812.22000000", + "16791.47000000", + "16802.11000000", + "5475.13940000", + 1672995599999, + "92000245.54341140", + 164303, + "2761.40926000", + "46400964.30558100", + "0" + ], + ] + return data + + def get_candles_ws_data_mock_1(self): + data = { + "e": "kline", + "E": 123456789, + "s": "BTCUSDT", + "k": {"t": 123400000, + "T": 123460000, + "s": "BNBBTC", + "i": "1m", + "f": 100, + "L": 200, + "o": "0.0010", + "c": "0.0020", + "h": "0.0025", + "l": "0.0015", + "v": "1000", + "n": 100, + "x": False, + "q": "1.0000", + "V": "500", + "Q": "0.500", + "B": "123456" + } + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "e": "kline", + "E": 123516789, + "s": "BTCUSDT", + "k": {"t": 123460000, + "T": 123460000, + "s": "BNBBTC", + "i": "1m", + "f": 100, + "L": 200, + "o": "0.0010", + "c": "0.0020", + "h": "0.0025", + "l": "0.0015", + "v": "1000", + "n": 100, + "x": False, + "q": "1.0000", + "V": "500", + "Q": "0.500", + "B": "123456" + } + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1672981200000 + end_time = 1672992000000 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}?endTime={end_time}&interval={self.interval}&limit=500" \ + f"&startTime={start_time}&symbol={self.ex_trading_pair}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock)) + self.assertEqual(resp.shape[1], 10) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "result": None, + "id": 1 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_kline_subscription = { + "method": "SUBSCRIBE", + "params": [f"{self.ex_trading_pair.lower()}@kline_{self.interval}"], + "id": 1} + + self.assertEqual(expected_kline_subscription, sent_subscription_messages[0]) + + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.binance_spot_candles.BinanceSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.binance_spot_candles.BinanceSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.binance_spot_candles.BinanceSpotCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.binance_spot_candles.BinanceSpotCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.binance_spot_candles.BinanceSpotCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/test_gate_io_perpetual_candles.py b/test/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/test_gate_io_perpetual_candles.py new file mode 100644 index 0000000..e103853 --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/gate_io_perpetual_candles/test_gate_io_perpetual_candles.py @@ -0,0 +1,305 @@ +import asyncio +import json +import re +import time +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.gate_io_perpetual_candles import GateioPerpetualCandles, constants as CONSTANTS + + +class TestGateioPerpetualCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + "_" + cls.quote_asset + cls.quanto_multiplier = 0.0001 + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = GateioPerpetualCandles(trading_pair=self.trading_pair, interval=self.interval) + self.data_feed.quanto_multiplier = 0.0001 + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = [ + { + "t": 1685167200, + "v": 97151, + "c": "1.032", + "h": "1.032", + "l": "1.032", + "o": "1.032", + "sum": "3580" + }, { + "t": 1685167300, + "v": 97151, + "c": "1.032", + "h": "1.032", + "l": "1.032", + "o": "1.032", + "sum": "3580" + }, { + "t": 1685167400, + "v": 97151, + "c": "1.032", + "h": "1.032", + "l": "1.032", + "o": "1.032", + "sum": "3580" + }, { + "t": 1685172600, + "v": 97151, + "c": "1.032", + "h": "1.032", + "l": "1.032", + "o": "1.032", + "sum": "3580" + }, + ] + return data + + def get_exchange_trading_pair_quanto_multiplier_data_mock(self): + data = {"quanto_multiplier": 0.0001} + return data + + def get_candles_ws_data_mock_1(self): + data = { + "time": 1542162490, + "time_ms": 1542162490123, + "channel": "futures.candlesticks", + "event": "update", + "error": None, + "result": [ + { + "t": 1545129300, + "v": 27525555, + "c": "95.4", + "h": "96.9", + "l": "89.5", + "o": "94.3", + "n": "1m_BTC_USD" + } + ] + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "time": 1542162490, + "time_ms": 1542162490123, + "channel": "futures.candlesticks", + "event": "update", + "error": None, + "result": [ + { + "t": 1545139300, + "v": 27525555, + "c": "95.4", + "h": "96.9", + "l": "89.5", + "o": "94.3", + "n": "1m_BTC_USD" + } + ] + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1685167200 + end_time = 1685172600 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock)) + self.assertEqual(resp.shape[1], 10) + + @aioresponses() + def test_get_exchange_trading_pair_quanto_multiplier(self, mock_api: aioresponses): + url = CONSTANTS.REST_URL + CONSTANTS.CONTRACT_INFO_URL.format(contract=self.ex_trading_pair) + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_exchange_trading_pair_quanto_multiplier_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + resp = self.async_run_with_timeout(self.data_feed.get_exchange_trading_pair_quanto_multiplier()) + + self.assertEqual(resp, 0.0001) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "result": None, + "id": 1 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_kline_subscription = { + "time": int(time.time()), + "channel": CONSTANTS.WS_CANDLES_ENDPOINT, + "event": "subscribe", + "payload": [self.interval, self.ex_trading_pair] + } + self.assertEqual(expected_kline_subscription["channel"], sent_subscription_messages[0]["channel"]) + self.assertEqual(expected_kline_subscription["payload"], sent_subscription_messages[0]["payload"]) + + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.GateioPerpetualCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.GateioPerpetualCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.GateioPerpetualCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.GateioPerpetualCandles.fill_historical_candles", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.gate_io_perpetual_candles.GateioPerpetualCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/gate_io_spot_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/gate_io_spot_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/gate_io_spot_candles/test_gate_io_spot_candles.py b/test/hummingbot/data_feed/candles_feed/gate_io_spot_candles/test_gate_io_spot_candles.py new file mode 100644 index 0000000..df9b453 --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/gate_io_spot_candles/test_gate_io_spot_candles.py @@ -0,0 +1,256 @@ +import asyncio +import json +import re +import time +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.gate_io_spot_candles import GateioSpotCandles, constants as CONSTANTS + + +class TestGateioSpotCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = cls.base_asset + "_" + cls.quote_asset + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = GateioSpotCandles(trading_pair=self.trading_pair, interval=self.interval) + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 2): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = [ + ['1685167200', '129807.73747903012', '26718.4', '26736.1', '26718.4', '26728.1', '4.856410775'], + ['1685169000', '657338.79714685262', '26746.2', '26758.1', '26709.2', '26718.4', '24.5891110488'], + ['1685170800', '202249.7345089816', '26723.1', '26746.2', '26720', '26746.2', '7.5659923741'], + ['1685172600', '121057.96936704352', '26723.1', '26723.1', '26710.1', '26723.1', '4.5305391649'] + ] + return data + + def get_candles_ws_data_mock_1(self): + data = { + "time": 1606292600, + "time_ms": 1606292600376, + "channel": "spot.candlesticks", + "event": "update", + "result": { + "t": "1606292500", + "v": "2362.32035", + "c": "19128.1", + "h": "19128.1", + "l": "19128.1", + "o": "19128.1", + "n": "1m_BTC_USDT", + "a": "3.8283" + } + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "time": 1606292600, + "time_ms": 1606292600376, + "channel": "spot.candlesticks", + "event": "update", + "result": { + "t": "1606292580", + "v": "2362.32035", + "c": "19128.1", + "h": "19128.1", + "l": "19128.1", + "o": "19128.1", + "n": "1m_BTC_USDT", + "a": "3.8283" + } + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1685167200 + end_time = 1685172600 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock)) + self.assertEqual(resp.shape[1], 10) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "result": None, + "id": 1 + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(1, len(sent_subscription_messages)) + expected_kline_subscription = { + "time": int(time.time()), + "channel": CONSTANTS.WS_CANDLES_ENDPOINT, + "event": "subscribe", + "payload": [self.interval, self.ex_trading_pair] + } + self.assertEqual(expected_kline_subscription["channel"], sent_subscription_messages[0]["channel"]) + self.assertEqual(expected_kline_subscription["payload"], sent_subscription_messages[0]["payload"]) + + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.gate_io_spot_candles.GateioSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.gate_io_spot_candles.GateioSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.gate_io_spot_candles.GateioSpotCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.gate_io_spot_candles.GateioSpotCandles.fill_historical_candles", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.gate_io_spot_candles.GateioSpotCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/__init__.py b/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py b/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py new file mode 100644 index 0000000..5ab8695 --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py @@ -0,0 +1,307 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import AsyncMock, MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.connector.test_support.network_mocking_assistant import NetworkMockingAssistant +from hummingbot.data_feed.candles_feed.kucoin_spot_candles import KucoinSpotCandles, constants as CONSTANTS + + +class TestKucoinSpotCandles(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.interval = "1h" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.ex_trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.mocking_assistant = NetworkMockingAssistant() + self.data_feed = KucoinSpotCandles(trading_pair=self.trading_pair, interval=self.interval) + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + self.resume_test_event = asyncio.Event() + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_candles_rest_data_mock(self): + data = [ + [ + 1672981200, + "16823.24000000", + "16823.63000000", + "16792.12000000", + "16810.18000000", + "6230.44034000", + 1672984799999, + ], + [ + 1672984800, + "16809.74000000", + "16816.45000000", + "16779.96000000", + "16786.86000000", + "6529.22759000", + 1672988399999, + ], + [ + 1672988400, + "16786.60000000", + "16802.87000000", + "16780.15000000", + "16794.06000000", + "5763.44917000", + 1672991999999, + ], + [ + 1672992000, + "16794.33000000", + "16812.22000000", + "16791.47000000", + "16802.11000000", + "5475.13940000", + 1672995599999, + ], + ] + return {"data": data} + + def get_candles_ws_data_mock_1(self): + data = { + "type": "message", + "topic": "/market/candles:BTC-USDT_1hour", + "subject": "trade.candles.update", + "data": { + "symbol": "BTC-USDT", # symbol + "candles": [ + "1589968800", # Start time of the candle cycle + "9786.9", # open price + "9740.8", # close price + "9806.1", # high price + "9732", # low price + "27.45649579", # Transaction volume + "268280.09830877" # Transaction amount + ], + "time": 1589970010253893337 # now(us) + } + } + return data + + def get_candles_ws_data_mock_2(self): + data = { + "type": "message", + "topic": "/market/candles:BTC-USDT_1hour", + "subject": "trade.candles.update", + "data": { + "symbol": "BTC-USDT", # symbol + "candles": [ + "1589972400", # Start time of the candle cycle + "9786.9", # open price + "9740.8", # close price + "9806.1", # high price + "9732", # low price + "27.45649579", # Transaction volume + "268280.09830877" # Transaction amount + ], + "time": 1589970010253893337 # now(us) + } + } + return data + + @aioresponses() + def test_fetch_candles(self, mock_api: aioresponses): + start_time = 1672981200 + end_time = 1672992000 + url = f"{CONSTANTS.REST_URL}{CONSTANTS.CANDLES_ENDPOINT}?endAt={end_time}&startAt={start_time}" \ + f"&symbol={self.ex_trading_pair}&type={CONSTANTS.INTERVALS[self.interval]}" + regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) + data_mock = self.get_candles_rest_data_mock() + mock_api.get(url=regex_url, body=json.dumps(data_mock)) + + resp = self.async_run_with_timeout(self.data_feed.fetch_candles(start_time=start_time, end_time=end_time)) + + self.assertEqual(resp.shape[0], len(data_mock["data"])) + self.assertEqual(resp.shape[1], 7) + + def test_candles_empty(self): + self.assertTrue(self.data_feed.candles_df.empty) + + @aioresponses() + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_subscribes_to_klines(self, mock_api, ws_connect_mock): + url = self.data_feed.public_ws_url + + resp = { + "code": "200000", + "data": { + "instanceServers": [ + { + "endpoint": "wss://test.url/endpoint", + "protocol": "websocket", + "encrypt": True, + "pingInterval": 50000, + "pingTimeout": 10000 + } + ], + "token": "testToken" + } + } + mock_api.post(url, body=json.dumps(resp)) + + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + result_subscribe_klines = { + "id": "hQvf8jkno", + "type": "welcome" + } + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(result_subscribe_klines)) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( + websocket_mock=ws_connect_mock.return_value) + + self.assertEqual(2, len(sent_subscription_messages)) + expected_topic = f"/market/candles:{self.ex_trading_pair.upper()}_{CONSTANTS.INTERVALS[self.interval]}" + self.assertEqual(expected_topic, sent_subscription_messages[0]["topic"]) + self.assertTrue(self.is_logged( + "INFO", + "Subscribed to public klines..." + )) + + @patch("hummingbot.data_feed.candles_feed.kucoin_spot_candles.KucoinSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect") + def test_listen_for_subscriptions_raises_cancel_exception(self, mock_ws, _: AsyncMock): + mock_ws.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + self.async_run_with_timeout(self.listening_task) + + @patch("hummingbot.data_feed.candles_feed.kucoin_spot_candles.KucoinSpotCandles._sleep") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_listen_for_subscriptions_logs_exception_details(self, mock_ws, sleep_mock: AsyncMock): + mock_ws.side_effect = Exception("TEST ERROR.") + sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( + asyncio.CancelledError()) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.async_run_with_timeout(self.resume_test_event.wait(), timeout=1) + + self.assertTrue( + self.is_logged( + "ERROR", + "Unexpected error occurred when listening to public klines. Retrying in 1 seconds...")) + + def test_subscribe_channels_raises_cancel_exception(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = asyncio.CancelledError + + with self.assertRaises(asyncio.CancelledError): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + def test_subscribe_channels_raises_exception_and_logs_error(self): + mock_ws = MagicMock() + mock_ws.send.side_effect = Exception("Test Error") + + with self.assertRaises(Exception): + self.listening_task = self.ev_loop.create_task(self.data_feed._subscribe_channels(mock_ws)) + self.async_run_with_timeout(self.listening_task) + + self.assertTrue( + self.is_logged("ERROR", "Unexpected error occurred subscribing to public klines...") + ) + + @patch("hummingbot.data_feed.candles_feed.kucoin_spot_candles.KucoinSpotCandles.fill_historical_candles", new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_empty_candle(self, ws_connect_mock, fill_historical_candles_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + fill_historical_candles_mock.assert_called_once() + + @patch("hummingbot.data_feed.candles_feed.kucoin_spot_candles.KucoinSpotCandles.fill_historical_candles", + new_callable=AsyncMock) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_duplicated_candle_not_included(self, ws_connect_mock, fill_historical_candles): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + fill_historical_candles.return_value = None + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.data_feed._time = MagicMock(return_value=5) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) + + self.assertEqual(self.data_feed.candles_df.shape[0], 1) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + @patch("hummingbot.data_feed.candles_feed.kucoin_spot_candles.KucoinSpotCandles.fill_historical_candles") + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_process_websocket_messages_with_two_valid_messages(self, ws_connect_mock, _): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_1())) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self.get_candles_ws_data_mock_2())) + + self.listening_task = self.ev_loop.create_task(self.data_feed.listen_for_subscriptions()) + + self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value, timeout=2) + + self.assertEqual(self.data_feed.candles_df.shape[0], 2) + self.assertEqual(self.data_feed.candles_df.shape[1], 10) + + def _create_exception_and_unlock_test_with_event(self, exception): + self.resume_test_event.set() + raise exception diff --git a/test/hummingbot/data_feed/candles_feed/test_candles_factory.py b/test/hummingbot/data_feed/candles_feed/test_candles_factory.py new file mode 100644 index 0000000..017dfcf --- /dev/null +++ b/test/hummingbot/data_feed/candles_feed/test_candles_factory.py @@ -0,0 +1,33 @@ +import unittest + +from hummingbot.data_feed.candles_feed.binance_perpetual_candles import BinancePerpetualCandles +from hummingbot.data_feed.candles_feed.binance_spot_candles import BinanceSpotCandles +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory + + +class TestCandlesFactory(unittest.TestCase): + def test_get_binance_candles_spot(self): + candles = CandlesFactory.get_candle(CandlesConfig( + connector="binance", + trading_pair="BTC-USDT", + interval="1m" + )) + self.assertIsInstance(candles, BinanceSpotCandles) + candles.stop() + + def test_get_binance_candles_perpetuals(self): + candles = CandlesFactory.get_candle(CandlesConfig( + connector="binance_perpetual", + trading_pair="BTC-USDT", + interval="1m" + )) + self.assertIsInstance(candles, BinancePerpetualCandles) + candles.stop() + + def test_get_non_existing_candles(self): + with self.assertRaises(Exception): + CandlesFactory.get_candle(CandlesConfig( + connector="hbot", + trading_pair="BTC-USDT", + interval="1m" + )) diff --git a/test/hummingbot/data_feed/test_amm_gateway_data_feed.py b/test/hummingbot/data_feed/test_amm_gateway_data_feed.py new file mode 100644 index 0000000..90f3e0e --- /dev/null +++ b/test/hummingbot/data_feed/test_amm_gateway_data_feed.py @@ -0,0 +1,60 @@ +import asyncio +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from test.logger_mixin_for_test import LoggerMixinForTest, LogLevel +from unittest.mock import AsyncMock, patch + +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.data_feed.amm_gateway_data_feed import AmmGatewayDataFeed + + +class TestAmmGatewayDataFeed(IsolatedAsyncioWrapperTestCase, LoggerMixinForTest): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.data_feed = AmmGatewayDataFeed( + connector_chain_network="connector_chain_network", + trading_pairs={"HBOT-USDT"}, + order_amount_in_base=Decimal("1"), + ) + + def setUp(self) -> None: + super().setUp() + self.set_loggers(loggers=[self.data_feed.logger()]) + + @patch("hummingbot.data_feed.amm_gateway_data_feed.AmmGatewayDataFeed.gateway_client", new_callable=AsyncMock) + async def test_check_network_connected(self, gateway_client_mock: AsyncMock): + gateway_client_mock.ping_gateway.return_value = True + self.assertEqual(NetworkStatus.CONNECTED, await self.data_feed.check_network()) + + @patch("hummingbot.data_feed.amm_gateway_data_feed.AmmGatewayDataFeed.gateway_client", new_callable=AsyncMock) + async def test_check_network_not_connected(self, gateway_client_mock: AsyncMock): + gateway_client_mock.ping_gateway.return_value = False + self.assertEqual(NetworkStatus.NOT_CONNECTED, await self.data_feed.check_network()) + self.assertTrue(self.is_logged(log_level=LogLevel.WARNING, + message="Gateway is not online. Please check your gateway connection.", )) + + @patch("hummingbot.data_feed.amm_gateway_data_feed.AmmGatewayDataFeed._fetch_data", new_callable=AsyncMock) + async def test_fetch_data_loop_exception(self, fetch_data_mock: AsyncMock): + fetch_data_mock.side_effect = [Exception("test exception"), asyncio.CancelledError()] + try: + await self.data_feed._fetch_data_loop() + except asyncio.CancelledError: + pass + self.assertEqual(2, fetch_data_mock.call_count) + self.assertTrue( + self.is_logged(log_level=LogLevel.ERROR, + message="Error getting data from AmmDataFeed[connector_chain_network]Check network " + "connection. Error: test exception")) + + @patch("hummingbot.data_feed.amm_gateway_data_feed.AmmGatewayDataFeed.gateway_client", new_callable=AsyncMock) + async def test_fetch_data_successful(self, gateway_client_mock: AsyncMock): + gateway_client_mock.get_price.side_effect = [{"price": "1"}, {"price": "2"}] + try: + await self.data_feed._fetch_data() + except asyncio.CancelledError: + pass + self.assertEqual(2, gateway_client_mock.get_price.call_count) + self.assertEqual(Decimal("1"), self.data_feed.price_dict["HBOT-USDT"].buy_price) + self.assertEqual(Decimal("2"), self.data_feed.price_dict["HBOT-USDT"].sell_price) diff --git a/test/hummingbot/data_feed/test_coin_gecko_data_feed.py b/test/hummingbot/data_feed/test_coin_gecko_data_feed.py new file mode 100644 index 0000000..83f05e1 --- /dev/null +++ b/test/hummingbot/data_feed/test_coin_gecko_data_feed.py @@ -0,0 +1,235 @@ +import asyncio +import json +import re +import unittest +from typing import Awaitable +from unittest.mock import MagicMock, patch + +from aioresponses import aioresponses + +from hummingbot.data_feed.coin_gecko_data_feed import CoinGeckoDataFeed, coin_gecko_constants as CONSTANTS + + +class CoinGeckoDataFeedTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + def setUp(self) -> None: + super().setUp() + + self.data_feed = CoinGeckoDataFeed() + + self.log_records = [] + self.data_feed.logger().setLevel(1) + self.data_feed.logger().addHandler(self) + + def handle(self, record): + self.log_records.append(record) + + def is_logged(self, log_level: str, message: str) -> bool: + return any( + record.levelname == log_level and record.getMessage() == message for + record in self.log_records) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_coin_markets_data_mock(self, btc_price: float, eth_price: float): + data = [ + { + "id": "bitcoin", + "symbol": "btc", + "name": "Bitcoin", + "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", + "current_price": btc_price, + "market_cap": 451469235435, + "market_cap_rank": 1, + "fully_diluted_valuation": 496425271642, + "total_volume": 50599610665, + "high_24h": 23655, + "low_24h": 21746, + "price_change_24h": 1640.38, + "price_change_percentage_24h": 7.45665, + "market_cap_change_24h": 31187048611, + "market_cap_change_percentage_24h": 7.4205, + "circulating_supply": 19098250, + "total_supply": 21000000, + "max_supply": 21000000, + "ath": 69045, + "ath_change_percentage": -65.90618, + "ath_date": "2021-11-10T14:24:11.849Z", + "atl": 67.81, + "atl_change_percentage": 34615.15839, + "atl_date": "2013-07-06T00:00:00.000Z", + "roi": None, + "last_updated": "2022-07-20T06:30:40.123Z" + }, + { + "id": "ethereum", + "symbol": "eth", + "name": "Ethereum", + "image": "https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880", + "current_price": eth_price, + "market_cap": 188408028152, + "market_cap_rank": 2, + "fully_diluted_valuation": None, + "total_volume": 22416922274, + "high_24h": 1585.84, + "low_24h": 1510.73, + "price_change_24h": 43.63, + "price_change_percentage_24h": 2.85217, + "market_cap_change_24h": 5243861455, + "market_cap_change_percentage_24h": 2.86293, + "circulating_supply": 119749448.206629, + "total_supply": 119748879.331629, + "max_supply": None, + "ath": 4878.26, + "ath_change_percentage": -67.76359, + "ath_date": "2021-11-10T14:24:19.604Z", + "atl": 0.432979, + "atl_change_percentage": 363099.28971, + "atl_date": "2015-10-20T00:00:00.000Z", + "roi": { + "times": 88.0543596997439, + "currency": "btc", + "percentage": 8805.435969974389 + }, + "last_updated": "2022-07-20T06:30:15.395Z" + }, + ] + return data + + @aioresponses() + def test_get_supported_vs_tokens(self, mock_api: aioresponses): + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SUPPORTED_VS_TOKENS_REST_ENDPOINT}" + data = ["btc", "eth"] + mock_api.get(url=url, body=json.dumps(data)) + + resp = self.async_run_with_timeout(self.data_feed.get_supported_vs_tokens()) + + self.assertEqual(data, resp) + + @aioresponses() + def test_get_prices_by_page(self, mock_api: aioresponses): + vs_currency = "USD" + page_no = 0 + category = "coin" + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?category={category}&order=market_cap_desc&page={page_no}" + f"&per_page=250&sparkline=false&vs_currency={vs_currency}" + ) + data = self.get_coin_markets_data_mock(btc_price=1, eth_price=2) + mock_api.get(url=url, body=json.dumps(data)) + + resp = self.async_run_with_timeout( + self.data_feed.get_prices_by_page(vs_currency=vs_currency, page_no=page_no, category=category) + ) + + self.assertEqual(data, resp) + + @aioresponses() + def test_get_prices_by_token_id(self, mock_api: aioresponses): + vs_currency = "USD" + token_ids = ["ETH", "BTC"] + token_ids_str = ",".join(map(str.lower, token_ids)) + url = ( + f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + f"?ids={token_ids_str}&vs_currency={vs_currency}" + ) + data = self.get_coin_markets_data_mock(btc_price=1, eth_price=2) + mock_api.get(url=url, body=json.dumps(data)) + + resp = self.async_run_with_timeout( + self.data_feed.get_prices_by_token_id(vs_currency=vs_currency, token_ids=token_ids) + ) + + self.assertEqual(data, resp) + + @aioresponses() + @patch( + "hummingbot.data_feed.coin_gecko_data_feed.coin_gecko_data_feed.CoinGeckoDataFeed._async_sleep", + new_callable=MagicMock, + ) + def test_fetch_data_loop(self, mock_api: aioresponses, sleep_mock: MagicMock): + sleep_continue_event = asyncio.Event() + + async def wait_on_sleep_event(): + sleep_continue_event.clear() + await sleep_continue_event.wait() + + sleep_mock.return_value = wait_on_sleep_event() + + prices_requested_event = asyncio.Event() + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + regex_url = re.compile(f"^{url}") + data = self.get_coin_markets_data_mock(btc_price=1, eth_price=2) + first_page = data[:1] + second_page = data[1:] + + self.assertEqual({}, self.data_feed.price_dict) + self.data_feed._price_dict["SOMECOIN"] = 10 + + mock_api.get( + url=regex_url, body=json.dumps(first_page), callback=lambda *_, **__: prices_requested_event.set() + ) + self.async_run_with_timeout(self.data_feed.start_network()) + self.async_run_with_timeout(prices_requested_event.wait()) + prices_dict = self.data_feed.price_dict + + self.assertIn("BTC", prices_dict) + self.assertEqual(1, prices_dict["BTC"]) + self.assertIn("SOMECOIN", prices_dict) + self.assertNotIn("ETH", prices_dict) + self.assertFalse(self.data_feed.ready) + + prices_requested_event.clear() + mock_api.get(url=regex_url, body=json.dumps(second_page), callback=lambda *_, **__: prices_requested_event.set()) + sleep_continue_event.set() + sleep_mock.return_value = wait_on_sleep_event() + self.async_run_with_timeout(prices_requested_event.wait()) + prices_dict = self.data_feed.price_dict + + self.assertIn("BTC", prices_dict) + self.assertEqual(1, prices_dict["BTC"]) + self.assertIn("SOMECOIN", prices_dict) + self.assertIn("ETH", prices_dict) + self.assertEqual(2, prices_dict["ETH"]) + self.assertFalse(self.data_feed.ready) + + mock_api.get( + url=regex_url, body=json.dumps([]), callback=lambda *_, **__: prices_requested_event.set(), repeat=True + ) + for i in range(2): + prices_requested_event.clear() + sleep_continue_event.set() + sleep_mock.return_value = wait_on_sleep_event() + self.async_run_with_timeout(prices_requested_event.wait()) + prices_dict = self.data_feed.price_dict + + self.assertIn("BTC", prices_dict) + self.assertEqual(1, prices_dict["BTC"]) + self.assertNotIn("SOMECOIN", prices_dict) # the dict has been fully updated + self.assertIn("ETH", prices_dict) + self.assertEqual(2, prices_dict["ETH"]) + self.assertTrue(self.data_feed.ready) + + @aioresponses() + @patch( + "hummingbot.data_feed.coin_gecko_data_feed.coin_gecko_data_feed.CoinGeckoDataFeed._async_sleep", + new_callable=MagicMock, + ) + def test_fetch_data_logs_exceptions(self, mock_api, sleep_mock: MagicMock): + sleep_mock.side_effect = [asyncio.CancelledError] + + url = f"{CONSTANTS.BASE_URL}{CONSTANTS.PRICES_REST_ENDPOINT}" + regex_url = re.compile(f"^{url}") + mock_api.get(url=regex_url, exception=RuntimeError("Some error")) + + with self.assertRaises(RuntimeError): + self.async_run_with_timeout(self.data_feed._fetch_data()) + + self.assertTrue( + self.is_logged(log_level="WARNING", message="Coin Gecko API request failed. Exception: Some error") + ) diff --git a/test/hummingbot/data_feed/test_wallet_tracker_data_feed.py b/test/hummingbot/data_feed/test_wallet_tracker_data_feed.py new file mode 100644 index 0000000..c72c364 --- /dev/null +++ b/test/hummingbot/data_feed/test_wallet_tracker_data_feed.py @@ -0,0 +1,63 @@ +import asyncio +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from test.logger_mixin_for_test import LoggerMixinForTest, LogLevel +from unittest.mock import AsyncMock, patch + +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.data_feed.wallet_tracker_data_feed import WalletTrackerDataFeed + + +class TestWalletTrackerDataFeed(IsolatedAsyncioWrapperTestCase, LoggerMixinForTest): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.data_feed = WalletTrackerDataFeed( + chain="chain", + network="network", + wallets={"wallet"}, + tokens={"token1", "token2"}, + ) + + def setUp(self) -> None: + super().setUp() + self.set_loggers(loggers=[self.data_feed.logger()]) + + @patch("hummingbot.data_feed.wallet_tracker_data_feed.WalletTrackerDataFeed.gateway_client", new_callable=AsyncMock) + async def test_check_network_connected(self, gateway_client_mock: AsyncMock): + gateway_client_mock.ping_gateway.return_value = True + self.assertEqual(NetworkStatus.CONNECTED, await self.data_feed.check_network()) + + @patch("hummingbot.data_feed.wallet_tracker_data_feed.WalletTrackerDataFeed.gateway_client", new_callable=AsyncMock) + async def test_check_network_not_connected(self, gateway_client_mock: AsyncMock): + gateway_client_mock.ping_gateway.return_value = False + self.assertEqual(NetworkStatus.NOT_CONNECTED, await self.data_feed.check_network()) + self.assertTrue(self.is_logged(log_level=LogLevel.WARNING, + message="Gateway is not online. Please check your gateway connection.", )) + + @patch("hummingbot.data_feed.wallet_tracker_data_feed.WalletTrackerDataFeed._fetch_data", new_callable=AsyncMock) + async def test_fetch_data_loop_exception(self, fetch_data_mock: AsyncMock): + fetch_data_mock.side_effect = [Exception("test exception"), asyncio.CancelledError()] + try: + await self.data_feed._fetch_data_loop() + except asyncio.CancelledError: + pass + self.assertEqual(2, fetch_data_mock.call_count) + self.assertTrue( + self.is_logged(log_level=LogLevel.ERROR, + message="Error getting data from WalletTrackerDataFeed[chain-network]Check network " + "connection. Error: test exception")) + + @patch("hummingbot.data_feed.wallet_tracker_data_feed.WalletTrackerDataFeed.gateway_client", new_callable=AsyncMock) + async def test_fetch_data_successful(self, gateway_client_mock: AsyncMock): + gateway_client_mock.get_balances.return_value = {"balances": {"token": 1, "token2": 2}} + try: + await self.data_feed._fetch_data() + except asyncio.CancelledError: + pass + self.assertEqual(1, gateway_client_mock.get_balances.call_count) + self.assertEqual(Decimal("1"), self.data_feed.wallet_balances["wallet"]["token"]) + self.assertEqual(Decimal("2"), self.data_feed.wallet_balances["wallet"]["token2"]) + self.assertEqual(Decimal("1"), self.data_feed.wallet_balances_df.loc["wallet", "token"]) + self.assertEqual(Decimal("2"), self.data_feed.wallet_balances_df.loc["wallet", "token2"]) diff --git a/test/hummingbot/logger/__init__.py b/test/hummingbot/logger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/logger/test_logger_util_functions.py b/test/hummingbot/logger/test_logger_util_functions.py new file mode 100644 index 0000000..f440b3d --- /dev/null +++ b/test/hummingbot/logger/test_logger_util_functions.py @@ -0,0 +1,16 @@ +import unittest +from dataclasses import dataclass + +from hummingbot.logger import log_encoder + + +class LoggerUtilFunctionsTest(unittest.TestCase): + def test_log_encoder_encodes_dataclasses(self): + @dataclass + class DummyDataClass: + one: int + two: float + + encoded = log_encoder(DummyDataClass(one=1, two=2.0)) + + self.assertEqual({"one": 1, "two": 2.0}, encoded) diff --git a/test/hummingbot/model/__init__.py b/test/hummingbot/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/model/db_migration/__init__.py b/test/hummingbot/model/db_migration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/model/db_migration/test_transformations.py b/test/hummingbot/model/db_migration/test_transformations.py new file mode 100644 index 0000000..3d82fd7 --- /dev/null +++ b/test/hummingbot/model/db_migration/test_transformations.py @@ -0,0 +1,43 @@ +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.model.db_migration.transformations import AddTradeFeeInQuote, ConvertPriceAndAmountColumnsToBigint + + +class ConvertPriceAndAmountColumnsToBigintTests(TestCase): + + def test_name(self): + self.assertEqual("ConvertPriceAndAmountColumnsToBigint", ConvertPriceAndAmountColumnsToBigint(self).name) + + def test_to_version(self): + self.assertEqual(20220130, ConvertPriceAndAmountColumnsToBigint(self).to_version) + + def test_apply_changes_trade_fill_and_order_tables(self): + executed_queries = [] + mock = MagicMock() + mock.engine.execute.side_effect = lambda query: executed_queries.append(query) + + ConvertPriceAndAmountColumnsToBigint(migrator=self).apply(mock) + + self.assertIn("create table Order_dg_tmp", executed_queries[0]) + self.assertIn("CAST(amount * 1000000 AS INTEGER)", executed_queries[1]) + self.assertIn("CAST(price * 1000000 AS INTEGER", executed_queries[1]) + self.assertEquals('drop table "Order";', executed_queries[2]) + self.assertEquals('alter table Order_dg_tmp rename to "Order";', executed_queries[3]) + + self.assertIn("create table TradeFill_dg_tmp", executed_queries[8]) + self.assertNotIn("id INTEGER", executed_queries[8]) + self.assertIn("constraint TradeFill_pk", executed_queries[8]) + self.assertIn("primary key (market, order_id, exchange_trade_id)", executed_queries[8]) + self.assertIn("CAST(amount * 1000000 AS INTEGER)", executed_queries[9]) + self.assertIn("CAST(price * 1000000 AS INTEGER", executed_queries[9]) + self.assertEquals('drop table TradeFill;', executed_queries[10]) + self.assertEquals('alter table TradeFill_dg_tmp rename to TradeFill;', executed_queries[11]) + + +class AddTradeFeeInQuoteTests(TestCase): + def test_name(self): + self.assertEqual("AddTradeFeeInQuote", AddTradeFeeInQuote(self).name) + + def test_to_version(self): + self.assertEqual(20230516, AddTradeFeeInQuote(self).to_version) diff --git a/test/hummingbot/model/test_trade_fill.py b/test/hummingbot/model/test_trade_fill.py new file mode 100644 index 0000000..721ba8c --- /dev/null +++ b/test/hummingbot/model/test_trade_fill.py @@ -0,0 +1,39 @@ +from unittest import TestCase + +from hummingbot.model.trade_fill import TradeFill + + +class TradeFillTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.display_name = "test_market" + self.config_file_path = "test_config" + self.strategy_name = "test_strategy" + + self.symbol = "COINALPHAHBOT" + self.base = "COINALPHA" + self.quote = "HBOT" + self.trading_pair = f"{self.base}-{self.quote}" + + def test_attribute_names_for_file_export(self): + expected_attributes = [ + "exchange_trade_id", + "config_file_path", + "strategy", + "market", + "symbol", + "base_asset", + "quote_asset", + "timestamp", + "order_id", + "trade_type", + "order_type", + "price", + "amount", + "leverage", + "trade_fee", + "trade_fee_in_quote", + "position", ] + + self.assertEqual(expected_attributes, TradeFill.attribute_names_for_file_export()) diff --git a/test/hummingbot/notifier/__init__.py b/test/hummingbot/notifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/notifier/test_telegram.py b/test/hummingbot/notifier/test_telegram.py new file mode 100644 index 0000000..13bafff --- /dev/null +++ b/test/hummingbot/notifier/test_telegram.py @@ -0,0 +1,23 @@ +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from hummingbot.notifier.telegram_notifier import TelegramNotifier + + +class TelegramNotifierTests(TestCase): + + def setUp(self) -> None: + super().setUp() + self.app = MagicMock() + self.telegram = TelegramNotifier(token="000:token", chat_id="", hb=None) + + @patch("telegram.ext.Updater.stop") + @patch("telegram.ext.Updater.start_polling") + def test_start_after_stop(self, start_polling: MagicMock, stop: MagicMock): + self.telegram.start() + start_polling.assert_called_once() + start_polling.reset_mock() + self.telegram.stop() + stop.assert_called_once() + self.telegram.start() + start_polling.assert_called_once() diff --git a/test/hummingbot/pmm_script/__init__.py b/test/hummingbot/pmm_script/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/pmm_script/test_pmm_script_base.py b/test/hummingbot/pmm_script/test_pmm_script_base.py new file mode 100644 index 0000000..b2f6c13 --- /dev/null +++ b/test/hummingbot/pmm_script/test_pmm_script_base.py @@ -0,0 +1,72 @@ +import unittest +from decimal import Decimal +from statistics import mean + +from hummingbot.pmm_script.pmm_script_base import PMMScriptBase + + +class PMMScriptIteratorTests(unittest.TestCase): + + def test_avg_mid_price(self): + script_base = PMMScriptBase() + script_base.mid_prices = [Decimal("10.1"), Decimal("10.2"), Decimal("10.1"), Decimal("10.2"), Decimal("10.4"), + Decimal("10.5"), Decimal("10.3"), Decimal("10.6"), Decimal("10.7"), Decimal("10.8"), + Decimal("10.0"), Decimal("10.1"), Decimal("10.1"), Decimal("10.1"), Decimal("10.1")] + avg_price = script_base.avg_mid_price(3, 10) + # since there is not enough sample size, it should return None + self.assertTrue(avg_price is None) + # At interval of 3 and length of 5, these belows are counted as the samples + samples = [Decimal("10.1"), Decimal("10.5"), Decimal("10.7"), Decimal("10.1"), Decimal("10.1")] + self.assertEqual(mean(samples), script_base.avg_mid_price(3, 5)) + # At length of 2, only the last two should be used for the avg + samples = [Decimal("10.1"), Decimal("10.1")] + self.assertEqual(mean(samples), script_base.avg_mid_price(3, 2)) + # At 100 interval and length of 1, only the last item is counted. + avg_price = script_base.avg_mid_price(100, 1) + self.assertEqual(Decimal("10.1"), avg_price) + + def test_take_samples(self): + script_base = PMMScriptBase() + a_list = [1, 2, 3, 4, 5, 6, 7] + samples = script_base.take_samples(a_list, 3, 10) + # since there is not enough sample size, it should return None + self.assertTrue(samples is None) + # At interval of 3 and length of 2, these belows are counted as the samples + expected = [4, 7] + samples = script_base.take_samples(a_list, 3, 2) + self.assertEqual(expected, samples) + # At interval of 2 and length of 4, these belows are counted as the samples + expected = [1, 3, 5, 7] + samples = script_base.take_samples(a_list, 2, 4) + self.assertEqual(expected, samples) + # At interval of 2 and length of 1, these belows are counted as the samples + expected = [7] + samples = script_base.take_samples(a_list, 2, 1) + self.assertEqual(expected, samples) + + def test_avg_and_median_mid_price_chg(self): + script_base = PMMScriptBase() + script_base.mid_prices = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + avg_chg = script_base.avg_price_volatility(3, 10) + # since there is not enough sample size, it should return None + self.assertTrue(avg_chg is None) + avg_chg = script_base.avg_price_volatility(3, 5) + # at 5 sample size, as we need 5 +1, so this should also return None + self.assertTrue(avg_chg is None) + # At interval of 4 and length of 3, these belows are counted as the samples + # The samples are 15, 11, 7, 3 + expected_chg = [(15 - 11) / 11, (11 - 7) / 7, (7 - 3) / 3] + self.assertEqual(mean(expected_chg), script_base.avg_price_volatility(4, 3)) + # The median change is (11 - 7) / 7 + self.assertEqual((11 - 7) / 7, script_base.median_price_volatility(4, 3)) + + # At 10 interval and length of 1. + expected_chg = (15 - 5) / 5 + self.assertEqual(expected_chg, script_base.avg_price_volatility(10, 1)) + + def test_round_by_step(self): + self.assertEqual(Decimal("1.75"), PMMScriptBase.round_by_step(Decimal("1.8"), Decimal("0.25"))) + self.assertEqual(Decimal("1.75"), PMMScriptBase.round_by_step(Decimal("1.75"), Decimal("0.25"))) + self.assertEqual(Decimal("1.75"), PMMScriptBase.round_by_step(Decimal("1.7567"), Decimal("0.01"))) + self.assertEqual(Decimal("1"), PMMScriptBase.round_by_step(Decimal("1.7567"), Decimal("1"))) + self.assertEqual(Decimal("-1.75"), PMMScriptBase.round_by_step(Decimal("-1.8"), Decimal("0.25"))) diff --git a/test/hummingbot/remote_iface/__init__.py b/test/hummingbot/remote_iface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/remote_iface/test_mqtt.py b/test/hummingbot/remote_iface/test_mqtt.py new file mode 100644 index 0000000..fce0622 --- /dev/null +++ b/test/hummingbot/remote_iface/test_mqtt.py @@ -0,0 +1,1296 @@ +import asyncio +from decimal import Decimal +from typing import Awaitable +from unittest import TestCase +from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch + +from async_timeout import timeout + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.event.events import BuyOrderCreatedEvent, MarketEvent, OrderExpiredEvent, SellOrderCreatedEvent +from hummingbot.core.mock_api.mock_mqtt_server import FakeMQTTBroker +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler +from hummingbot.model.order import Order +from hummingbot.model.trade_fill import TradeFill +from hummingbot.remote_iface.mqtt import MQTTGateway, MQTTMarketEventForwarder + + +@patch("hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_HEALTH_CHECK", 0.0) +@patch("hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_RESTART_LONG", 0.0) +class RemoteIfaceMQTTTests(TestCase): + # logging.Level required to receive logs from the exchange + level = 0 + + @classmethod + def setUpClass(cls): + super().setUpClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + cls.instance_id = 'TEST_ID' + cls.fake_err_msg = "Some error" + cls.client_config_map = ClientConfigAdapter(ClientConfigMap()) + cls.hbapp = HummingbotApplication(client_config_map=cls.client_config_map) + cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() + cls.hbapp.ev_loop = cls.ev_loop + cls.client_config_map.mqtt_bridge.mqtt_port = 1888 + cls.client_config_map.mqtt_bridge.mqtt_commands = 1 + cls.client_config_map.mqtt_bridge.mqtt_events = 1 + cls.prev_instance_id = cls.client_config_map.instance_id + cls.client_config_map.instance_id = cls.instance_id + cls.command_topics = [ + 'start', + 'stop', + 'config', + 'import', + 'status', + 'history', + 'balance/limit', + 'balance/paper', + 'command_shortcuts', + ] + cls.START_URI = 'hbot/$instance_id/start' + cls.STOP_URI = 'hbot/$instance_id/stop' + cls.CONFIG_URI = 'hbot/$instance_id/config' + cls.IMPORT_URI = 'hbot/$instance_id/import' + cls.STATUS_URI = 'hbot/$instance_id/status' + cls.HISTORY_URI = 'hbot/$instance_id/history' + cls.BALANCE_LIMIT_URI = 'hbot/$instance_id/balance/limit' + cls.BALANCE_PAPER_URI = 'hbot/$instance_id/balance/paper' + cls.COMMAND_SHORTCUT_URI = 'hbot/$instance_id/command_shortcuts' + cls.fake_mqtt_broker = FakeMQTTBroker() + + @classmethod + def tearDownClass(cls) -> None: + cls.client_config_map.instance_id = cls.prev_instance_id + del cls.fake_mqtt_broker + super().tearDownClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + # self.async_run_with_timeout(read_system_configs_from_yml()) + self.gateway = MQTTGateway(self.hbapp) + self.test_market: MockPaperExchange = MockPaperExchange( + client_config_map=self.client_config_map) + self.hbapp.markets = { + "test_market_paper_trade": self.test_market + } + self.resume_test_event = asyncio.Event() + self.hbapp.logger().setLevel(1) + self.hbapp.logger().addHandler(self) + self.gateway.logger().setLevel(1) + self.gateway.logger().addHandler(self) + # Restart interval Patcher + self.restart_interval_patcher = patch( + 'hummingbot.remote_iface.mqtt.MQTTGateway._INTERVAL_RESTART_SHORT', + new_callable=PropertyMock + ) + self.addCleanup(self.restart_interval_patcher.stop) + self.restart_interval_mock = self.restart_interval_patcher.start() + self.restart_interval_mock.return_value = 0.0 + # MQTT Transport Patcher + self.mqtt_transport_patcher = patch( + 'commlib.transports.mqtt.MQTTTransport' + ) + self.addCleanup(self.mqtt_transport_patcher.stop) + self.mqtt_transport_mock = self.mqtt_transport_patcher.start() + self.mqtt_transport_mock.side_effect = self.fake_mqtt_broker.create_transport + # MQTT Patch Loggers Patcher + self.patch_loggers_patcher = patch( + 'hummingbot.remote_iface.mqtt.MQTTGateway.patch_loggers' + ) + self.addCleanup(self.patch_loggers_patcher.stop) + self.patch_loggers_mock = self.patch_loggers_patcher.start() + self.patch_loggers_mock.return_value = None + + def tearDown(self): + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.gateway.stop() + del self.gateway + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.fake_mqtt_broker.clear() + self.restart_interval_patcher.stop() + self.mqtt_transport_patcher.stop() + self.patch_loggers_patcher.stop() + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and str(record.getMessage()) == str(message) for record in self.log_records) + + async def wait_for_logged(self, log_level: str, message: str): + try: + async with timeout(3): + while not self._is_logged(log_level=log_level, message=message): + await asyncio.sleep(0.1) + except asyncio.TimeoutError as e: + print(f"Message: {message} was not logged.") + print(f"Received Logs: {[record.getMessage() for record in self.log_records]}") + raise e + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + async def _create_exception_and_unlock_test_with_event_async(self, *args, **kwargs): + self.resume_test_event.set() + raise RuntimeError(self.fake_err_msg) + + def _create_exception_and_unlock_test_with_event(self, *args, **kwargs): + self.resume_test_event.set() + raise RuntimeError(self.fake_err_msg) + + def _create_exception_and_unlock_test_with_event_not_impl(self, *args, **kwargs): + self.resume_test_event.set() + raise NotImplementedError(self.fake_err_msg) + + def is_msg_received(self, *args, **kwargs): + return self.fake_mqtt_broker.is_msg_received(*args, **kwargs) + + async def wait_for_rcv(self, topic, content=None, msg_key = 'msg'): + try: + async with timeout(3): + while not self.is_msg_received(topic=topic, content=content, msg_key = msg_key): + await asyncio.sleep(0.1) + except asyncio.TimeoutError as e: + print(f"Topic: {topic} was not received.") + print(f"Received Messages: {self.fake_mqtt_broker.received_msgs}") + raise e + + def start_mqtt(self): + self.gateway.start() + self.gateway.start_market_events_fw() + + def get_topic_for( + self, + topic + ): + return topic.replace('$instance_id', self.hbapp.instance_id) + + def build_fake_strategy( + self, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock, + invalid_strategy: bool = True, + empty_name: bool = False + ): + if empty_name: + strategy_name = '' + elif invalid_strategy: + strategy_name = "some_strategy" + else: + strategy_name = "avellaneda_market_making" + status_check_all_mock.return_value = True + strategy_conf_var = ConfigVar("strategy", None) + strategy_conf_var.value = strategy_name + load_strategy_config_map_from_file.return_value = {"strategy": strategy_conf_var} + return strategy_name + + def send_fake_import_cmd( + self, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock, + invalid_strategy: bool = True, + empty_name: bool = False + ): + import_topic = self.get_topic_for(self.IMPORT_URI) + + strategy_name = self.build_fake_strategy( + status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=invalid_strategy, + empty_name=empty_name + ) + + self.fake_mqtt_broker.publish_to_subscription(import_topic, {'strategy': strategy_name}) + + @staticmethod + def emit_order_created_event( + market: MockPaperExchange, + order: LimitOrder + ): + event_cls = BuyOrderCreatedEvent if order.is_buy else SellOrderCreatedEvent + event_tag = MarketEvent.BuyOrderCreated if order.is_buy else MarketEvent.SellOrderCreated + market.trigger_event( + event_tag, + message=event_cls( + order.creation_timestamp, + OrderType.LIMIT, + order.trading_pair, + order.quantity, + order.price, + order.client_order_id, + order.creation_timestamp * 1e-6 + ) + ) + + @staticmethod + def emit_order_expired_event(market: MockPaperExchange): + event_cls = OrderExpiredEvent + event_tag = MarketEvent.OrderExpired + market.trigger_event( + event_tag, + message=event_cls( + 1671819499, + "OID1" + ) + ) + + def build_fake_trades(self): + ts = 1671819499 + config_file_path = "some-strategy.yml" + strategy_name = "pure_market_making" + market = "binance" + symbol = "HBOT-COINALPHA" + base_asset = "HBOT" + quote_asset = "COINALPHA" + order_id = "OID1" + order = Order( + id=order_id, + config_file_path=config_file_path, + strategy=strategy_name, + market=market, + symbol=symbol, + base_asset=base_asset, + quote_asset=quote_asset, + creation_timestamp=0, + order_type="LMT", + amount=4, + leverage=0, + price=Decimal(1000), + last_status="PENDING", + last_update_timestamp=0, + ) + trades = [ + TradeFill( + config_file_path=config_file_path, + strategy=strategy_name, + market=market, + symbol=symbol, + base_asset=base_asset, + quote_asset=quote_asset, + timestamp=ts, + order_id=order_id, + trade_type=TradeType.BUY.name, + order_type=OrderType.LIMIT.name, + price=Decimal(1000), + amount=Decimal(1), + trade_fee='{}', + exchange_trade_id="EOID1", + order=order), + TradeFill( + config_file_path=config_file_path, + strategy=strategy_name, + market=market, + symbol=symbol, + base_asset=base_asset, + quote_asset=quote_asset, + timestamp=ts, + order_id=order_id, + trade_type=TradeType.BUY.name, + order_type=OrderType.LIMIT.name, + price=Decimal(1000), + amount=Decimal(1), + trade_fee='{}', + exchange_trade_id="EOID1", + order=order) + ] + trade_list = list([TradeFill.to_bounty_api_json(t) for t in trades]) + for t in trade_list: + t['trade_timestamp'] = str(t['trade_timestamp']) + return trade_list + + def test_mqtt_command_balance_limit(self): + self.start_mqtt() + + topic = self.get_topic_for(self.BALANCE_LIMIT_URI) + msg = { + 'exchange': 'binance', + 'asset': 'BTC-USD', + 'amount': '1.0', + } + + self.fake_mqtt_broker.publish_to_subscription(topic, msg) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "Limit for BTC-USD on binance exchange set to 1.0" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.balance_command.BalanceCommand.balance") + def test_mqtt_command_balance_limit_failure( + self, + balance_mock: MagicMock + ): + balance_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + msg = { + 'exchange': 'binance', + 'asset': 'BTC-USD', + 'amount': '1.0', + } + + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.BALANCE_LIMIT_URI), msg) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/balance/limit" + msg = {'status': 400, 'msg': self.fake_err_msg, 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + def test_mqtt_command_balance_paper(self): + self.start_mqtt() + + topic = self.get_topic_for(self.BALANCE_PAPER_URI) + msg = { + 'exchange': 'binance', + 'asset': 'BTC-USD', + 'amount': '1.0', + } + + self.fake_mqtt_broker.publish_to_subscription(topic, msg) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "Paper balance for BTC-USD token set to 1.0" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.balance_command.BalanceCommand.balance") + def test_mqtt_command_balance_paper_failure( + self, + balance_mock: MagicMock + ): + balance_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + msg = { + 'exchange': 'binance', + 'asset': 'BTC-USD', + 'amount': '1.0', + } + + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.BALANCE_PAPER_URI), msg) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/balance/paper" + msg = {'status': 400, 'msg': self.fake_err_msg, 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + def test_mqtt_command_command_shortcuts(self): + self.start_mqtt() + + topic = self.get_topic_for(self.COMMAND_SHORTCUT_URI) + shortcut_data = {"params": [["spreads", "4", "4"]]} + + self.fake_mqtt_broker.publish_to_subscription(topic, shortcut_data) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msgs = [ + " >>> config bid_spread 4", + " >>> config ask_spread 4", + "Invalid key, please choose from the list.", + ] + reply_topic = f"test_reply/hbot/{self.instance_id}/command_shortcuts" + reply_data = {'success': [True], 'status': 200, 'msg': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(reply_topic, reply_data, msg_key='data')) + for notify_msg in notify_msgs: + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication._handle_shortcut") + def test_mqtt_command_command_shortcuts_failure( + self, + command_shortcuts_mock: MagicMock + ): + command_shortcuts_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + topic = self.get_topic_for(self.COMMAND_SHORTCUT_URI) + shortcut_data = {"params": [["spreads", "4", "4"]]} + + self.fake_mqtt_broker.publish_to_subscription(topic, shortcut_data) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/command_shortcuts" + msg = {'success': [], 'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + def test_mqtt_command_config(self): + self.start_mqtt() + + topic = self.get_topic_for(self.CONFIG_URI) + + self.fake_mqtt_broker.publish_to_subscription(topic, {}) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_mqtt_command_config_map_changes( + self, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + self.start_mqtt() + topic = self.get_topic_for(self.CONFIG_URI) + notify_topic = f"hbot/{self.instance_id}/notify" + + self._strategy_config_map = {} + self.fake_mqtt_broker.publish_to_subscription(topic, {}) + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + self.fake_mqtt_broker.publish_to_subscription(topic, {}) + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + prev_cconfigmap = self.client_config_map + self.client_config_map = {} + self.fake_mqtt_broker.publish_to_subscription(topic, {}) + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + self.client_config_map = prev_cconfigmap + + def test_mqtt_command_config_updates_single_param(self): + self.start_mqtt() + + topic = self.get_topic_for(self.CONFIG_URI) + + config_msg = { + 'params': [ + ('instance_id', self.instance_id), + ] + } + + self.fake_mqtt_broker.publish_to_subscription(topic, config_msg) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.config_command.ConfigCommand.config") + def test_mqtt_command_config_updates_configurable_keys( + self, + config_mock: MagicMock + ): + config_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + config_msg = { + 'params': [ + ('skata', 90), + ] + } + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.CONFIG_URI), + config_msg + ) + topic = f"test_reply/hbot/{self.instance_id}/config" + msg = {'changes': [], 'config': {}, 'status': 400, 'msg': "Invalid param key(s): ['skata']"} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + def test_mqtt_command_config_updates_multiple_params(self): + self.start_mqtt() + topic = self.get_topic_for(self.CONFIG_URI) + config_msg = { + 'params': [ + ('instance_id', self.instance_id), + ('mqtt_bridge.mqtt_port', 1888), + ] + } + self.fake_mqtt_broker.publish_to_subscription(topic, config_msg) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "\nGlobal Configurations:" + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.config_command.ConfigCommand.config") + def test_mqtt_command_config_failure( + self, + config_mock: MagicMock + ): + config_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.CONFIG_URI), {}) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/config" + msg = {'changes': [], 'config': {}, 'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + @patch("hummingbot.client.command.history_command.HistoryCommand.get_history_trades_json") + def test_mqtt_command_history( + self, + get_history_trades_mock: MagicMock + ): + fake_trades = self.build_fake_trades() + get_history_trades_mock.return_value = fake_trades + self.start_mqtt() + + topic = self.get_topic_for(self.HISTORY_URI) + + self.fake_mqtt_broker.publish_to_subscription( + topic, + {"async_backend": 0} + ) + history_topic = f"test_reply/hbot/{self.instance_id}/history" + history_msg = {'status': 200, 'msg': '', 'trades': fake_trades} + self.ev_loop.run_until_complete(self.wait_for_rcv(history_topic, history_msg, msg_key='data')) + self.assertTrue(self.is_msg_received(history_topic, history_msg, msg_key='data')) + + self.fake_mqtt_broker.publish_to_subscription( + topic, + {"async_backend": 1} + ) + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "\n Please first import a strategy config file of which to show historical performance." + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + history_topic = f"test_reply/hbot/{self.instance_id}/history" + history_msg = {'status': 200, 'msg': '', 'trades': []} + self.ev_loop.run_until_complete(self.wait_for_rcv(history_topic, history_msg, msg_key='data')) + self.assertTrue(self.is_msg_received(history_topic, history_msg, msg_key='data')) + + @patch("hummingbot.client.command.history_command.HistoryCommand.history") + def test_mqtt_command_history_failure( + self, + history_mock: MagicMock + ): + history_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.HISTORY_URI), {}) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/history" + msg = {'status': 400, 'msg': self.fake_err_msg, 'trades': []} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_mqtt_command_import( + self, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + self.start_mqtt() + self.send_fake_import_cmd(status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=False) + + notify_topic = f"hbot/{self.instance_id}/notify" + start_msg = '\nEnter "start" to start market making.' + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, start_msg)) + self.assertTrue(self.is_msg_received(notify_topic, 'Configuration from avellaneda_market_making.yml file is imported.')) + self.assertTrue(self.is_msg_received(notify_topic, start_msg)) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + @patch("hummingbot.client.command.import_command.ImportCommand.import_config_file", new_callable=AsyncMock) + def test_mqtt_command_import_failure( + self, + import_mock: AsyncMock, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + import_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + self.start_mqtt() + self.send_fake_import_cmd(status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=False) + + topic = f"test_reply/hbot/{self.instance_id}/import" + msg = {'status': 400, 'msg': 'Some error'} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + @patch("hummingbot.client.command.import_command.ImportCommand.import_config_file", new_callable=AsyncMock) + def test_mqtt_command_import_empty_strategy( + self, + import_mock: AsyncMock, + status_check_all_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + import_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + topic = f"test_reply/hbot/{self.instance_id}/import" + msg = {'status': 400, 'msg': 'Empty strategy_name given!'} + self.start_mqtt() + self.send_fake_import_cmd(status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=False, + empty_name=True) + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.start_command.StartCommand._in_start_check") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_mqtt_command_start_sync( + self, + status_check_all_mock: MagicMock, + in_start_check_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + in_start_check_mock.return_value = True + self.start_mqtt() + + self.send_fake_import_cmd(status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=False) + + notify_topic = f"hbot/{self.instance_id}/notify" + + self.ev_loop.run_until_complete(self.wait_for_rcv( + notify_topic, '\nEnter "start" to start market making.')) + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'async_backend': 0} + ) + + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.start_command.StartCommand._in_start_check") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_mqtt_command_start_async( + self, + status_check_all_mock: MagicMock, + in_start_check_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock + ): + in_start_check_mock.return_value = True + self.start_mqtt() + + self.send_fake_import_cmd(status_check_all_mock=status_check_all_mock, + load_strategy_config_map_from_file=load_strategy_config_map_from_file, + invalid_strategy=False) + + notify_topic = f"hbot/{self.instance_id}/notify" + + self.ev_loop.run_until_complete(self.wait_for_rcv( + notify_topic, '\nEnter "start" to start market making.')) + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'async_backend': 1} + ) + # start_msg = 'The bot is already running - please run "stop" first' + # self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, start_msg)) + # self.assertTrue(self.is_msg_received(notify_topic, start_msg)) + + @patch("hummingbot.client.command.start_command.init_logging") + @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") + @patch("hummingbot.client.command.start_command.StartCommand.start_script_strategy") + @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all") + def test_mqtt_command_start_script( + self, + status_check_all_mock: MagicMock, + start_script_strategy_mock: MagicMock, + load_strategy_config_map_from_file: MagicMock, + mock_init_logging: MagicMock + ): + start_script_strategy_mock.side_effect = self._create_exception_and_unlock_test_with_event_not_impl + mock_init_logging.side_effect = lambda *args, **kwargs: None + self.start_mqtt() + + notify_topic = f"hbot/{self.instance_id}/notify" + notify_msg = "Invalid strategy. Start aborted." + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'script': 'format_status_example.py'} + ) + + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) + self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) + + @patch("hummingbot.client.command.start_command.StartCommand.start") + def test_mqtt_command_start_failure( + self, + start_mock: MagicMock + ): + start_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {} + ) + topic = f"test_reply/hbot/{self.instance_id}/start" + msg = {'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + self.hbapp.strategy_name = None + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'script': None} + ) + topic = f"test_reply/hbot/{self.instance_id}/start" + msg = {'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'script': 'format_status_example.py'} + ) + topic = f"test_reply/hbot/{self.instance_id}/start" + msg = {'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + prev_strategy = self.hbapp.strategy + self.hbapp.strategy = {} + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {'script': 'format_status_example.py'} + ) + topic = f"test_reply/hbot/{self.instance_id}/start" + msg = { + 'status': 400, + 'msg': 'The bot is already running - please run "stop" first' + } + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.START_URI), + {} + ) + topic = f"test_reply/hbot/{self.instance_id}/start" + msg = { + 'status': 400, + 'msg': 'Strategy check: Please import or create a strategy.' + } + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + self.hbapp.strategy = prev_strategy + + @patch("hummingbot.client.command.status_command.StatusCommand.strategy_status", new_callable=AsyncMock) + def test_mqtt_command_status_no_strategy_running( + self, + strategy_status_mock: AsyncMock + ): + strategy_status_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + self.start_mqtt() + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.STATUS_URI), + {'async_backend': 0} + ) + topic = f"test_reply/hbot/{self.instance_id}/status" + msg = {'status': 400, 'msg': 'No strategy is currently running!', 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + @patch("hummingbot.client.command.status_command.StatusCommand.strategy_status", new_callable=AsyncMock) + def test_mqtt_command_status_async( + self, + strategy_status_mock: AsyncMock + ): + strategy_status_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + self.hbapp.strategy = {} + self.start_mqtt() + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.STATUS_URI), + {'async_backend': 1} + ) + topic = f"test_reply/hbot/{self.instance_id}/status" + msg = {'status': 200, 'msg': '', 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + self.hbapp.strategy = None + self.ev_loop.run_until_complete(asyncio.sleep(0.2)) + + @patch("hummingbot.client.command.status_command.StatusCommand.strategy_status", new_callable=AsyncMock) + def test_mqtt_command_status_sync( + self, + strategy_status_mock: AsyncMock + ): + strategy_status_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + self.hbapp.strategy = {} + self.start_mqtt() + self.fake_mqtt_broker.publish_to_subscription( + self.get_topic_for(self.STATUS_URI), + {'async_backend': 0} + ) + topic = f"test_reply/hbot/{self.instance_id}/status" + msg = {'status': 400, 'msg': 'Some error', 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + self.hbapp.strategy = None + + @patch("hummingbot.client.command.status_command.StatusCommand.strategy_status", new_callable=AsyncMock) + def test_mqtt_command_status_failure( + self, + strategy_status_mock: AsyncMock + ): + strategy_status_mock.side_effect = self._create_exception_and_unlock_test_with_event_async + self.start_mqtt() + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.STATUS_URI), {}) + topic = f"test_reply/hbot/{self.instance_id}/status" + msg = {'status': 400, 'msg': 'No strategy is currently running!', 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + self.ev_loop.run_until_complete(asyncio.sleep(0.2)) + + def test_mqtt_command_stop_sync(self): + self.start_mqtt() + + topic = self.get_topic_for(self.STOP_URI) + + self.fake_mqtt_broker.publish_to_subscription( + topic, + {'async_backend': 0} + ) + notify_topic = f"hbot/{self.instance_id}/notify" + wind_down_msg = "\nWinding down..." + canceling_msg = "Canceling outstanding orders..." + stop_msg = "All outstanding orders canceled." + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, wind_down_msg)) + self.assertTrue(self.is_msg_received(notify_topic, wind_down_msg)) + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, canceling_msg)) + self.assertTrue(self.is_msg_received(notify_topic, canceling_msg)) + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, stop_msg)) + self.assertTrue(self.is_msg_received(notify_topic, stop_msg)) + + def test_mqtt_command_stop_async(self): + self.start_mqtt() + + topic = self.get_topic_for(self.STOP_URI) + self.fake_mqtt_broker.publish_to_subscription( + topic, + {'async_backend': 1} + ) + notify_topic = f"hbot/{self.instance_id}/notify" + wind_down_msg = "\nWinding down..." + canceling_msg = "Canceling outstanding orders..." + stop_msg = "All outstanding orders canceled." + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, wind_down_msg)) + self.assertTrue(self.is_msg_received(notify_topic, wind_down_msg)) + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, canceling_msg)) + self.assertTrue(self.is_msg_received(notify_topic, canceling_msg)) + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, stop_msg)) + self.assertTrue(self.is_msg_received(notify_topic, stop_msg)) + + @patch("hummingbot.client.command.stop_command.StopCommand.stop") + def test_mqtt_command_stop_failure( + self, + stop_mock: MagicMock + ): + stop_mock.side_effect = self._create_exception_and_unlock_test_with_event + self.start_mqtt() + + self.fake_mqtt_broker.publish_to_subscription(self.get_topic_for(self.STOP_URI), {}) + + self.async_run_with_timeout(self.resume_test_event.wait()) + + topic = f"test_reply/hbot/{self.instance_id}/stop" + msg = {'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) + self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + + def test_mqtt_event_buy_order_created(self): + self.start_mqtt() + + order = LimitOrder(client_order_id="HBOT_1", + trading_pair="HBOT-USDT", + is_buy=True, + base_currency="HBOT", + quote_currency="USDT", + price=Decimal("100"), + quantity=Decimal("1.5") + ) + + self.emit_order_created_event(self.test_market, order) + + events_topic = f"hbot/{self.instance_id}/events" + + evt_type = "BuyOrderCreated" + self.ev_loop.run_until_complete(self.wait_for_rcv(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, evt_type, msg_key = 'type')) + + def test_mqtt_event_sell_order_created(self): + self.start_mqtt() + + order = LimitOrder(client_order_id="HBOT_1", + trading_pair="HBOT-USDT", + is_buy=False, + base_currency="HBOT", + quote_currency="USDT", + price=Decimal("100"), + quantity=Decimal("1.5") + ) + + self.emit_order_created_event(self.test_market, order) + + events_topic = f"hbot/{self.instance_id}/events" + + evt_type = "SellOrderCreated" + self.ev_loop.run_until_complete(self.wait_for_rcv(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, evt_type, msg_key = 'type')) + + def test_mqtt_event_order_expired(self): + self.start_mqtt() + + self.emit_order_expired_event(self.test_market) + + events_topic = f"hbot/{self.instance_id}/events" + + evt_type = "OrderExpired" + self.ev_loop.run_until_complete(self.wait_for_rcv(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, evt_type, msg_key = 'type')) + + def test_mqtt_subscribed_topics(self): + self.start_mqtt() + self.assertTrue(self.gateway is not None) + subscribed_mqtt_topics = sorted(list([f"hbot/{self.instance_id}/{topic}" + for topic in (self.command_topics + ['external/event/*'])])) + self.assertEqual(subscribed_mqtt_topics, sorted(list(self.fake_mqtt_broker.subscriptions.keys()))) + + @patch("hummingbot.remote_iface.mqtt.mqtts_logger", None) + def test_mqtt_eventforwarder_logger(self): + self.assertTrue(MQTTMarketEventForwarder.logger() is not None) + self.start_mqtt() + + def test_mqtt_eventforwarder_unknown_events(self): + self.start_mqtt() + test_evt = {"unknown": "you don't know me"} + self.gateway._market_events._send_mqtt_event(event_tag=999, + pubsub=None, + event=test_evt) + + events_topic = f"hbot/{self.instance_id}/events" + + evt_type = "Unknown" + self.ev_loop.run_until_complete(self.wait_for_rcv(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, test_evt, msg_key = 'data')) + + def test_mqtt_eventforwarder_invalid_events(self): + self.start_mqtt() + self.gateway._market_events._send_mqtt_event(event_tag=999, + pubsub=None, + event="i feel empty") + + events_topic = f"hbot/{self.instance_id}/events" + + evt_type = "Unknown" + self.ev_loop.run_until_complete( + self.wait_for_rcv(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, evt_type, msg_key = 'type')) + self.assertTrue(self.is_msg_received(events_topic, {}, msg_key = 'data')) + + def test_mqtt_notifier_fakes(self): + self.start_mqtt() + self.assertEqual(self.gateway._notifier.start(), None) + self.assertEqual(self.gateway._notifier.stop(), None) + + def test_mqtt_gateway_check_health(self): + tmp = self.gateway._start_health_monitoring_loop + self.gateway._start_health_monitoring_loop = lambda: None + self.start_mqtt() + self.assertTrue(self.gateway._check_connections()) + self.gateway._rpc_services[0]._transport._connected = False + self.assertFalse(self.gateway._check_connections()) + self.gateway._rpc_services[0]._transport._connected = True + s = self.gateway.create_subscriber(topic='TEST', on_message=lambda x: {}) + s.run() + self.assertTrue(self.gateway._check_connections()) + s._transport._connected = False + self.assertFalse(self.gateway._check_connections()) + prev_pub = self.gateway._publishers + prev__sub = self.gateway._subscribers + self.gateway._publishers = [] + self.gateway._subscribers = [] + self.gateway._rpc_services[0]._transport._connected = False + self.assertFalse(self.gateway._check_connections()) + self.gateway._publishers = prev_pub + self.gateway._subscribers = prev__sub + self.gateway._start_health_monitoring_loop = tmp + + @patch("hummingbot.remote_iface.mqtt.MQTTGateway.health", new_callable=PropertyMock) + def test_mqtt_gateway_check_health_restarts( + self, + health_mock: PropertyMock + ): + health_mock.return_value = True + status_topic = f"hbot/{self.instance_id}/status_updates" + self.start_mqtt() + self.ev_loop.run_until_complete(self.wait_for_logged("DEBUG", f"Started Heartbeat Publisher ")) + self.ev_loop.run_until_complete(self.wait_for_rcv(status_topic, 'online')) + self.ev_loop.run_until_complete(self.wait_for_logged("DEBUG", "Monitoring MQTT Gateway health for disconnections.")) + self.log_records.clear() + health_mock.return_value = False + self.restart_interval_mock.return_value = None + self.ev_loop.run_until_complete(self.wait_for_logged("WARNING", "MQTT Gateway is disconnected, attempting to reconnect.")) + fake_err = "'<=' not supported between instances of 'NoneType' and 'int'" + self.ev_loop.run_until_complete(self.wait_for_logged("ERROR", f"MQTT Gateway failed to reconnect: {fake_err}. Sleeping 10 seconds before retry.")) + self.assertFalse( + self._is_logged( + "WARNING", + "MQTT Gateway successfully reconnected.", + ) + ) + self.assertTrue(self.is_msg_received(status_topic, 'offline')) + self.log_records.clear() + self.restart_interval_mock.return_value = 0.0 + self.hbapp.strategy = True + self.ev_loop.run_until_complete(self.wait_for_logged("WARNING", "MQTT Gateway is disconnected, attempting to reconnect.")) + health_mock.return_value = True + self.ev_loop.run_until_complete(self.wait_for_logged("WARNING", "MQTT Gateway successfully reconnected.")) + self.assertTrue( + self._is_logged( + "WARNING", + "MQTT Gateway successfully reconnected.", + ) + ) + + def test_mqtt_gateway_stop(self): + self.start_mqtt() + self.assertTrue(self.gateway._check_connections()) + self.gateway.stop() + self.assertFalse(self.gateway._check_connections()) + + def test_eevent_queue_factory(self): + self.start_mqtt() + from hummingbot.remote_iface.mqtt import EEventQueueFactory, ExternalEventFactory + queue = ExternalEventFactory.create_queue('test') + self.assertTrue(queue is not None) + + from collections import deque + dq = deque() + EEventQueueFactory._on_event(dq, {'a': 1}, 'testevent') + self.assertTrue(1) + + def test_eevent_listener_factory(self): + self.start_mqtt() + from hummingbot.remote_iface.mqtt import ExternalEventFactory + + def clb(msg, event_name): + pass + + ExternalEventFactory.create_async('test.a.b', clb) + ExternalEventFactory.remove_listener('test.a.b', clb) + try: + MQTTGateway._instance = None + ExternalEventFactory.create_async('test.a.b', clb) + ExternalEventFactory.remove_listener('test.a.b', clb) + except Exception: + self.assertTrue(1) + else: + self.assertTrue(0) + + def test_etopic_queue_factory(self): + self.start_mqtt() + from hummingbot.remote_iface.mqtt import ETopicQueueFactory, ExternalTopicFactory + queue = ExternalTopicFactory.create_queue('test/a/b') + self.assertTrue(queue is not None) + + from collections import deque + dq = deque() + ETopicQueueFactory._on_message(dq, {'a': 1}, 'test/external') + self.assertTrue(1) + + def test_etopic_listener_factory(self): + self.start_mqtt() + from hummingbot.remote_iface.mqtt import ExternalTopicFactory + + def clb(msg, topic): + pass + + listener = ExternalTopicFactory.create_async('test/a/b', clb) + self.assertTrue(listener is not None) + ExternalTopicFactory.remove_listener(listener) + + def test_external_events_add_remove(self): + self.start_mqtt() + from hummingbot.remote_iface.mqtt import MQTTGateway + + def clb(msg, event_name): + pass + + gw = MQTTGateway.main() + self.assertTrue(len(gw._external_events._listeners.get('*')) == 0) + gw.add_external_event_listener('*', clb) + self.assertTrue(len(gw._external_events._listeners.get('*')) == 1) + gw.remove_external_event_listener('*', clb) + self.assertTrue(len(gw._external_events._listeners.get('*')) == 0) + gw.add_external_event_listener('test.a.b', clb) + self.assertTrue(len(gw._external_events._listeners.get('test.a.b')) == 1) + gw.remove_external_event_listener('test.a.b', clb) + self.assertTrue(len(gw._external_events._listeners.get('test.a.b')) == 0) + + def test_mqtt_log_handler(self): + import logging + + from hummingbot.logger import HummingbotLogger + from hummingbot.remote_iface.mqtt import MQTTLogHandler + self.start_mqtt() + + handler = MQTTLogHandler(self.hbapp, self.gateway) + handler.emit(logging.LogRecord('', 1, '', '', '', '', '')) + self.assertTrue(1) + + logger = HummingbotLogger('testlogger') + self.gateway.add_log_handler(logger) + self.gateway.remove_log_handler(logger) + logger = self.gateway._get_root_logger() + self.assertTrue(logger is not None) + self.gateway._remove_log_handlers() + + def test_market_events(self): + self.start_mqtt() + from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, DeductedFromReturnsTradeFee + from hummingbot.remote_iface.mqtt import MQTTGateway + + gw = MQTTGateway.main() + gw._market_events._make_event_payload({ + 'a': 'a', + 'b': 1, + 'c': Decimal('1.0'), + 'd': DeductedFromReturnsTradeFee(), + 'e': AddedToCostTradeFee(), + 'f': {'a': 1}, + 'type': 'TEST', + 'order_type': 'BUY', + 'trade_type': 'LIMIT', + }) + self.assertTrue(1) + + def test_etopic_listener_class(self): + from hummingbot.remote_iface.mqtt import ETopicListener + + def clb(msg, topic): + pass + + listener = ETopicListener('test', clb, use_bot_prefix=False) + self.assertTrue(listener is not None) + listener = ETopicListener('test', clb, use_bot_prefix=True) + self.assertTrue(listener is not None) + + self.start_mqtt() + listener = ETopicListener('test', clb, use_bot_prefix=True) + self.assertTrue(listener is not None) + + prev_gw = MQTTGateway.main() + MQTTGateway._instance = None + try: + listener = ETopicListener('test', clb, use_bot_prefix=False) + except Exception: + self.assertTrue(1) + else: + self.assertFalse(1) + MQTTGateway._instance = prev_gw + + def test_eevent_queue_factory_class(self): + from hummingbot.remote_iface.mqtt import EEventQueueFactory + self.start_mqtt() + + equeue = EEventQueueFactory.create(event_name='test', queue_size=2) + self.assertTrue(equeue is not None) + + prev_gw = MQTTGateway.main() + MQTTGateway._instance = None + try: + equeue = EEventQueueFactory.create(event_name='test', queue_size=2) + except Exception: + self.assertTrue(1) + else: + self.assertFalse(1) + MQTTGateway._instance = prev_gw + + def test_eevent_listener_factory_class(self): + from hummingbot.remote_iface.mqtt import EEventListenerFactory + self.start_mqtt() + + def clb(msg, topic): + pass + + EEventListenerFactory.create(event_name='test', callback=clb) + prev_gw = MQTTGateway.main() + MQTTGateway._instance = None + try: + EEventListenerFactory.create(event_name='test', + callback=clb) + except Exception: + self.assertTrue(1) + else: + self.assertFalse(1) + try: + EEventListenerFactory.remove(event_name='test', + callback=clb) + except Exception: + self.assertTrue(1) + else: + self.assertFalse(1) + MQTTGateway._instance = prev_gw + + def test_mqtt_external_events_class(self): + from hummingbot.remote_iface.messages import ExternalEventMessage + from hummingbot.remote_iface.mqtt import MQTTExternalEvents + + self.start_mqtt() + + def clb(msg, topic): + pass + + eevents = MQTTExternalEvents(self.hbapp, self.gateway) + eevents.add_global_listener(clb) + ename = eevents._event_uri_to_name('hbot/bot1/external/event/e1') + self.assertTrue(ename == "e1") + eevents.add_listener('e1', clb) + eevents.add_listener('e1', clb) + eevents._on_event_arrived(ExternalEventMessage(), + 'hbot/bot1/external/event/e1') + self.assertTrue(len(eevents._listeners) == 2) + self.assertTrue('*' in eevents._listeners) + self.assertTrue('e1' in eevents._listeners) + self.assertTrue(ename in eevents._listeners) + + eevents._listeners = {} + eevents.add_global_listener(clb) + eevents.remove_global_listener(clb) + eevents.add_listener('test_event', clb) + eevents.remove_listener('test_event', clb) + eevents.add_listener('test_event', clb) + eevents.add_listener('test_event', clb) + eevents.remove_listener('test_event', clb) + + def test_mqtt_gateway_health(self): + health = self.gateway.health + self.assertFalse(health) + + def test_mqtt_gateway_namespace_wrong_lastchar(self): + prev_ns = self.gateway._hb_app.client_config_map.mqtt_bridge.mqtt_namespace + self.gateway._hb_app.client_config_map.mqtt_bridge.mqtt_namespace = 'test/' + gw = MQTTGateway(self.hbapp) + self.assertTrue(gw.namespace == 'test') + gw.stop() + del gw + self.gateway._hb_app.client_config_map.mqtt_bridge.mqtt_namespace = prev_ns diff --git a/test/hummingbot/smart_components/__init__.py b/test/hummingbot/smart_components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/executors/__init__.py b/test/hummingbot/smart_components/executors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/executors/arbitrage_executor/__init__.py b/test/hummingbot/smart_components/executors/arbitrage_executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/executors/arbitrage_executor/test_arbitrage_executor.py b/test/hummingbot/smart_components/executors/arbitrage_executor/test_arbitrage_executor.py new file mode 100644 index 0000000..92daf4f --- /dev/null +++ b/test/hummingbot/smart_components/executors/arbitrage_executor/test_arbitrage_executor.py @@ -0,0 +1,171 @@ +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from test.logger_mixin_for_test import LoggerMixinForTest +from unittest.mock import MagicMock, Mock, PropertyMock, patch + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.event.events import MarketOrderFailureEvent +from hummingbot.smart_components.executors.arbitrage_executor.arbitrage_executor import ArbitrageExecutor +from hummingbot.smart_components.executors.arbitrage_executor.data_types import ( + ArbitrageConfig, + ArbitrageExecutorStatus, + ExchangePair, +) +from hummingbot.smart_components.executors.position_executor.data_types import TrackedOrder +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class TestArbitrageExecutor(IsolatedAsyncioWrapperTestCase, LoggerMixinForTest): + def setUp(self): + super().setUp() + self.strategy = self.create_mock_strategy() + self.arbitrage_config = MagicMock(spec=ArbitrageConfig) + self.arbitrage_config.buying_market = ExchangePair(exchange='binance', trading_pair='MATIC-USDT') + self.arbitrage_config.selling_market = ExchangePair(exchange='uniswap_polygon_mainnet', trading_pair='WMATIC-USDT') + self.arbitrage_config.min_profitability = Decimal('0.01') + self.arbitrage_config.order_amount = Decimal('1') + self.arbitrage_config.max_retries = 3 + self.update_interval = 0.5 + self.executor = ArbitrageExecutor(self.strategy, self.arbitrage_config, self.update_interval) + self.set_loggers(loggers=[self.executor.logger()]) + + @staticmethod + def create_mock_strategy(): + market = MagicMock() + market_info = MagicMock() + market_info.market = market + + strategy = MagicMock(spec=ScriptStrategyBase) + type(strategy).market_info = PropertyMock(return_value=market_info) + type(strategy).trading_pair = PropertyMock(return_value="ETH-USDT") + strategy.buy.side_effect = ["OID-BUY-1", "OID-BUY-2", "OID-BUY-3"] + strategy.sell.side_effect = ["OID-SELL-1", "OID-SELL-2", "OID-SELL-3"] + strategy.cancel.return_value = None + strategy.connectors = { + "binance": MagicMock(spec=ConnectorBase), + } + return strategy + + def test_is_arbitrage_valid(self): + self.assertTrue(self.executor.is_arbitrage_valid('ETH-USDT', 'ETH-USDT')) + self.assertTrue(self.executor.is_arbitrage_valid('ETH-BUSD', 'ETH-USDT')) + self.assertTrue(self.executor.is_arbitrage_valid('ETH-USDT', 'WETH-USDT')) + self.assertFalse(self.executor.is_arbitrage_valid('ETH-USDT', 'BTC-USDT')) + self.assertFalse(self.executor.is_arbitrage_valid('ETH-USDT', 'ETH-BTC')) + + def test_net_pnl(self): + self.executor.arbitrage_status = ArbitrageExecutorStatus.COMPLETED + self.executor._buy_order = Mock(spec=TrackedOrder) + self.executor._sell_order = Mock(spec=TrackedOrder) + self.executor._buy_order.order.executed_amount_base = Decimal('1') + self.executor._sell_order.order.executed_amount_base = Decimal('1') + self.executor._buy_order.average_executed_price = Decimal('100') + self.executor._sell_order.average_executed_price = Decimal('200') + self.executor._buy_order.cum_fees = Decimal('1') + self.executor._sell_order.cum_fees = Decimal('1') + self.assertEqual(self.executor.net_pnl, Decimal('98')) + self.assertEqual(self.executor.net_pnl_pct, Decimal('98')) + + @patch.object(ArbitrageExecutor, 'place_order') + def test_place_buy_arbitrage_order(self, mock_place_order): + mock_place_order.return_value = 'order_id' + self.executor.place_buy_arbitrage_order() + mock_place_order.assert_called_once_with( + connector_name=self.arbitrage_config.buying_market.exchange, + trading_pair=self.arbitrage_config.buying_market.trading_pair, + order_type=OrderType.MARKET, + side=TradeType.BUY, + amount=self.arbitrage_config.order_amount, + price=self.executor._last_buy_price, + ) + + @patch.object(ArbitrageExecutor, 'place_order') + def test_place_sell_arbitrage_order(self, mock_place_order): + mock_place_order.return_value = 'order_id' + self.executor.place_sell_arbitrage_order() + mock_place_order.assert_called_once_with( + connector_name=self.arbitrage_config.selling_market.exchange, + trading_pair=self.arbitrage_config.selling_market.trading_pair, + order_type=OrderType.MARKET, + side=TradeType.SELL, + amount=self.arbitrage_config.order_amount, + price=self.executor._last_sell_price, + ) + + @patch.object(ArbitrageExecutor, "get_resulting_price_for_amount") + @patch.object(ArbitrageExecutor, "get_tx_cost_in_asset") + async def test_control_task_not_started_not_profitable(self, tx_cost_mock, resulting_price_mock): + tx_cost_mock.return_value = Decimal('0.01') + resulting_price_mock.side_effect = [Decimal('100'), Decimal('102')] + self.executor.arbitrage_status = ArbitrageExecutorStatus.NOT_STARTED + await self.executor.control_task() + self.assertEqual(self.executor.arbitrage_status, ArbitrageExecutorStatus.NOT_STARTED) + + @patch.object(ArbitrageExecutor, "place_order") + @patch.object(ArbitrageExecutor, "get_resulting_price_for_amount") + @patch.object(ArbitrageExecutor, "get_tx_cost_in_asset") + async def test_control_task_not_started_profitable(self, tx_cost_mock, resulting_price_mock, place_order_mock): + tx_cost_mock.return_value = Decimal('0.01') + resulting_price_mock.side_effect = [Decimal('100'), Decimal('104')] + place_order_mock.side_effect = ['OID-BUY', 'OID-SELL'] + self.executor.arbitrage_status = ArbitrageExecutorStatus.NOT_STARTED + await self.executor.control_task() + self.assertEqual(self.executor.arbitrage_status, ArbitrageExecutorStatus.ACTIVE_ARBITRAGE) + self.assertEqual(self.executor.buy_order.order_id, 'OID-BUY') + self.assertEqual(self.executor.sell_order.order_id, 'OID-SELL') + + async def test_control_task_active_arbitrage_max_retries(self): + self.executor.arbitrage_status = ArbitrageExecutorStatus.ACTIVE_ARBITRAGE + self.executor._cumulative_failures = 4 + await self.executor.control_task() + self.assertEqual(self.executor.arbitrage_status, ArbitrageExecutorStatus.FAILED) + + async def test_control_task_active_arbitrage_complete(self): + self.executor.arbitrage_status = ArbitrageExecutorStatus.ACTIVE_ARBITRAGE + self.executor._cumulative_failures = 0 + self.executor._buy_order = Mock(spec=TrackedOrder) + self.executor._sell_order = Mock(spec=TrackedOrder) + self.executor._buy_order.order.is_done.return_value = True + self.executor._sell_order.order.is_done.return_value = True + await self.executor.control_task() + self.assertEqual(self.executor.arbitrage_status, ArbitrageExecutorStatus.COMPLETED) + + @patch.object(ArbitrageExecutor, "get_trade_pnl_pct") + async def test_price_not_available_logs_exception(self, trade_pnl_pct_mock): + trade_pnl_pct_mock.side_effect = Exception("Price not available") + self.executor.arbitrage_status = ArbitrageExecutorStatus.NOT_STARTED + self.executor._cumulative_failures = 0 + try: + await self.executor.control_task() + except Exception: + pass + self.is_logged("ERROR", "Error calculating profitability: Price not available") + + def test_to_format_status_not_started(self): + self.executor.arbitrage_status = ArbitrageExecutorStatus.NOT_STARTED + format_status = "".join(self.executor.to_format_status()) + self.assertIn("Arbitrage Status: ArbitrageExecutorStatus.NOT_STARTED", format_status) + self.assertIn("Trade PnL (%): 0.00 % | TX Cost (%): -100.00 % | Net PnL (%): -100.00 %", format_status) + + @patch.object(ArbitrageExecutor, "place_order") + def test_process_order_failed_event_increments_cumulative_failures(self, _): + self.executor._cumulative_failures = 0 + self.executor.buy_order.order_id = "123" + self.executor.sell_order.order_id = "321" + market = MagicMock() + buy_order_failed_event = MarketOrderFailureEvent( + timestamp=123456789, + order_id=self.executor.buy_order.order_id, + order_type=OrderType.MARKET, + ) + self.executor.process_order_failed_event("102", market, buy_order_failed_event) + self.assertEqual(self.executor._cumulative_failures, 1) + + sell_order_failed_event = MarketOrderFailureEvent( + timestamp=123456789, + order_id=self.executor.sell_order.order_id, + order_type=OrderType.MARKET, + ) + self.executor.process_order_failed_event("102", market, sell_order_failed_event) + self.assertEqual(self.executor._cumulative_failures, 2) diff --git a/test/hummingbot/smart_components/executors/position_executor/__init__.py b/test/hummingbot/smart_components/executors/position_executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/executors/position_executor/test_data_types.py b/test/hummingbot/smart_components/executors/position_executor/test_data_types.py new file mode 100644 index 0000000..54bc378 --- /dev/null +++ b/test/hummingbot/smart_components/executors/position_executor/test_data_types.py @@ -0,0 +1,71 @@ +from decimal import Decimal +from unittest import TestCase + +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder +from hummingbot.smart_components.executors.position_executor.data_types import ( + CloseType, + PositionConfig, + PositionExecutorStatus, + TrackedOrder, +) + + +class TestPositionExecutorDataTypes(TestCase): + def test_position_config_model(self): + config = PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + open_order_type=OrderType.LIMIT, + side=TradeType.BUY, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60) + self.assertEqual(config.trading_pair, "ETH-USDT") + self.assertEqual(config.exchange, "binance") + self.assertEqual(config.open_order_type, OrderType.LIMIT) + self.assertEqual(config.side, TradeType.BUY) + self.assertEqual(config.entry_price, Decimal("100")) + self.assertEqual(config.amount, Decimal("1")) + self.assertEqual(config.stop_loss, Decimal("0.05")) + self.assertEqual(config.take_profit, Decimal("0.1")) + self.assertEqual(config.time_limit, 60) + + def test_position_executor_status_enum(self): + self.assertEqual(PositionExecutorStatus.NOT_STARTED.name, "NOT_STARTED") + self.assertEqual(PositionExecutorStatus.NOT_STARTED.value, 1) + self.assertEqual(PositionExecutorStatus.ACTIVE_POSITION.name, "ACTIVE_POSITION") + self.assertEqual(PositionExecutorStatus.ACTIVE_POSITION.value, 2) + self.assertEqual(PositionExecutorStatus.COMPLETED.name, "COMPLETED") + self.assertEqual(PositionExecutorStatus.COMPLETED.value, 3) + + def test_position_executor_close_types_enum(self): + self.assertEqual(CloseType.TIME_LIMIT.name, "TIME_LIMIT") + self.assertEqual(CloseType.TIME_LIMIT.value, 1) + self.assertEqual(CloseType.STOP_LOSS.name, "STOP_LOSS") + self.assertEqual(CloseType.STOP_LOSS.value, 2) + self.assertEqual(CloseType.TAKE_PROFIT.name, "TAKE_PROFIT") + self.assertEqual(CloseType.TAKE_PROFIT.value, 3) + self.assertEqual(CloseType.EXPIRED.name, "EXPIRED") + self.assertEqual(CloseType.EXPIRED.value, 4) + self.assertEqual(CloseType.EARLY_STOP.name, "EARLY_STOP") + self.assertEqual(CloseType.EARLY_STOP.value, 5) + + def test_tracked_order(self): + order = TrackedOrder() + self.assertIsNone(order.order_id) + self.assertIsNone(order.order) + + def test_tracked_order_order_id(self): + order = TrackedOrder() + order.order_id = "12345" + self.assertEqual(order.order_id, "12345") + + def test_tracked_order_order(self): + in_flight_order = InFlightOrder( + client_order_id="12345", + trading_pair="ETH/USDT", + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + amount=Decimal("100"), + creation_timestamp=12341451532, + price=Decimal("1")) + order = TrackedOrder() + order.order = in_flight_order + self.assertEqual(order.order, in_flight_order) diff --git a/test/hummingbot/smart_components/executors/position_executor/test_position_executor.py b/test/hummingbot/smart_components/executors/position_executor/test_position_executor.py new file mode 100644 index 0000000..9889aee --- /dev/null +++ b/test/hummingbot/smart_components/executors/position_executor/test_position_executor.py @@ -0,0 +1,558 @@ +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from unittest.mock import MagicMock, PropertyMock, patch + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, TradeUpdate +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.logger import HummingbotLogger +from hummingbot.smart_components.executors.position_executor.data_types import ( + CloseType, + PositionConfig, + PositionExecutorStatus, + TrailingStop, +) +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class TestPositionExecutor(IsolatedAsyncioWrapperTestCase): + def setUp(self) -> None: + super().setUp() + self.strategy = self.create_mock_strategy + + @property + def create_mock_strategy(self): + market = MagicMock() + market_info = MagicMock() + market_info.market = market + + strategy = MagicMock(spec=ScriptStrategyBase) + type(strategy).market_info = PropertyMock(return_value=market_info) + type(strategy).trading_pair = PropertyMock(return_value="ETH-USDT") + strategy.buy.side_effect = ["OID-BUY-1", "OID-BUY-2", "OID-BUY-3"] + strategy.sell.side_effect = ["OID-SELL-1", "OID-SELL-2", "OID-SELL-3"] + strategy.cancel.return_value = None + strategy.connectors = { + "binance": MagicMock(spec=ConnectorBase), + } + return strategy + + def get_position_config_trailing_stop(self): + return PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.BUY, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.LIMIT, stop_loss_order_type=OrderType.MARKET, + trailing_stop=TrailingStop( + activation_price_delta=Decimal("0.02"), + trailing_delta=Decimal("0.01"))) + + def get_position_config_market_long(self): + return PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.BUY, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.LIMIT, stop_loss_order_type=OrderType.MARKET,) + + def get_position_config_market_long_tp_market(self): + return PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.BUY, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.MARKET, stop_loss_order_type=OrderType.MARKET,) + + def get_position_config_market_short(self): + return PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.SELL, entry_price=Decimal("100"), amount=Decimal("1"), + stop_loss=Decimal("0.05"), take_profit=Decimal("0.1"), time_limit=60, + take_profit_order_type=OrderType.LIMIT, stop_loss_order_type=OrderType.MARKET,) + + def get_incomplete_position_config(self): + return PositionConfig(timestamp=1234567890, trading_pair="ETH-USDT", exchange="binance", + side=TradeType.SELL, entry_price=Decimal("100"), amount=Decimal("1"), + take_profit_order_type=OrderType.LIMIT, stop_loss_order_type=OrderType.MARKET,) + + def test_init_raises_exception(self): + position_config = self.get_incomplete_position_config() + with self.assertRaises(ValueError): + PositionExecutor(self.strategy, position_config) + + def test_properties(self): + position_config = self.get_position_config_market_short() + position_executor = PositionExecutor(self.strategy, position_config) + self.assertEqual(position_executor.trade_pnl_quote, Decimal("0")) + position_executor.executor_status = PositionExecutorStatus.COMPLETED + position_executor.close_type = CloseType.EARLY_STOP + self.assertTrue(position_executor.is_closed) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.COMPLETED) + self.assertEqual(position_executor.trading_pair, "ETH-USDT") + self.assertEqual(position_executor.exchange, "binance") + self.assertEqual(position_executor.side, TradeType.SELL) + self.assertEqual(position_executor.entry_price, Decimal("100")) + self.assertEqual(position_executor.amount, Decimal("1")) + self.assertEqual(position_executor.stop_loss_price, Decimal("105.00")) + self.assertEqual(position_executor.take_profit_price, Decimal("90.0")) + self.assertEqual(position_executor.end_time, 1234567890 + 60) + self.assertEqual(position_executor.take_profit_order_type, OrderType.LIMIT) + self.assertEqual(position_executor.stop_loss_order_type, OrderType.MARKET) + self.assertEqual(position_executor.time_limit_order_type, OrderType.MARKET) + self.assertEqual(position_executor.filled_amount, Decimal("0")) + self.assertEqual(position_executor.trailing_stop_config, None) + self.assertEqual(position_executor.close_price, None) + self.assertIsInstance(position_executor.logger(), HummingbotLogger) + position_executor.terminate_control_loop() + + async def test_control_position_not_started_create_open_order(self): + position_config = self.get_position_config_market_short() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + await position_executor.control_task() + self.assertEqual(position_executor.open_order.order_id, "OID-SELL-1") + position_executor.terminate_control_loop() + + async def test_control_position_not_started_expired(self): + position_config = self.get_position_config_market_short() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234569890) + position_executor = PositionExecutor(self.strategy, position_config) + await position_executor.control_task() + self.assertIsNone(position_executor.open_order.order_id) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.COMPLETED) + self.assertEqual(position_executor.close_type, CloseType.EXPIRED) + self.assertEqual(position_executor.trade_pnl, Decimal("0")) + position_executor.terminate_control_loop() + + async def test_control_open_order_expiration(self): + position_config = self.get_position_config_market_short() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234569890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-SELL-1" + await position_executor.control_task() + position_executor._strategy.cancel.assert_called_with( + connector_name="binance", + trading_pair="ETH-USDT", + order_id="OID-SELL-1") + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.NOT_STARTED) + self.assertEqual(position_executor.trade_pnl, Decimal("0")) + position_executor.terminate_control_loop() + + async def test_control_position_order_placed_not_cancel_open_order(self): + position_config = self.get_position_config_market_short() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-SELL-1" + await position_executor.control_task() + position_executor._strategy.cancel.assert_not_called() + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("101")) + async def test_control_position_active_position_create_take_profit(self, _): + position_config = self.get_position_config_market_short() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-SELL-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-SELL-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.SELL, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-SELL-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + await position_executor.control_task() + self.assertEqual(position_executor.take_profit_order.order_id, "OID-BUY-1") + self.assertEqual(position_executor.trade_pnl, Decimal("-0.01")) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", + return_value=Decimal("120")) + async def test_control_position_active_position_close_by_take_profit_market(self, _): + position_config = self.get_position_config_market_long_tp_market() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + await position_executor.control_task() + self.assertEqual(position_executor.close_order.order_id, "OID-SELL-1") + self.assertEqual(position_executor.close_type, CloseType.TAKE_PROFIT) + self.assertEqual(position_executor.trade_pnl, Decimal("0.2")) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("70")) + async def test_control_position_active_position_close_by_stop_loss(self, _): + position_config = self.get_position_config_market_long() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + await position_executor.control_task() + self.assertEqual(position_executor.close_order.order_id, "OID-SELL-1") + self.assertEqual(position_executor.close_type, CloseType.STOP_LOSS) + self.assertEqual(position_executor.trade_pnl, Decimal("-0.3")) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("100")) + async def test_control_position_active_position_close_by_time_limit(self, _): + position_config = self.get_position_config_market_long() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234597890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + await position_executor.control_task() + self.assertEqual(position_executor.close_order.order_id, "OID-SELL-2") + self.assertEqual(position_executor.close_type, CloseType.TIME_LIMIT) + self.assertEqual(position_executor.trade_pnl, Decimal("0.0")) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("70")) + async def test_control_position_close_placed_stop_loss_failed(self, _): + position_config = self.get_position_config_market_long() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + + position_executor.close_order.order_id = "OID-SELL-FAIL" + position_executor.close_type = CloseType.STOP_LOSS + market = MagicMock() + position_executor.process_order_failed_event( + "102", market, MarketOrderFailureEvent( + order_id="OID-SELL-FAIL", + timestamp=1640001112.223, + order_type=OrderType.MARKET) + ) + await position_executor.control_task() + self.assertEqual(position_executor.close_order.order_id, "OID-SELL-1") + self.assertEqual(position_executor.close_type, CloseType.STOP_LOSS) + position_executor.terminate_control_loop() + + def test_process_order_completed_event_open_order(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + event = BuyOrderCompletedEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + base_asset="ETH", + quote_asset="USDT", + base_asset_amount=position_config.amount, + quote_asset_amount=position_config.amount * position_config.entry_price, + order_type=position_config.open_order_type, + exchange_order_id="ED140" + ) + market = MagicMock() + position_executor.process_order_completed_event("102", market, event) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.ACTIVE_POSITION) + position_executor.terminate_control_loop() + + def test_process_order_completed_event_close_order(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.close_order.order_id = "OID-BUY-1" + position_executor.close_type = CloseType.STOP_LOSS + event = BuyOrderCompletedEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + base_asset="ETH", + quote_asset="USDT", + base_asset_amount=position_config.amount, + quote_asset_amount=position_config.amount * position_config.entry_price, + order_type=position_config.open_order_type, + exchange_order_id="ED140" + ) + market = MagicMock() + position_executor.process_order_completed_event("102", market, event) + self.assertEqual(position_executor.close_timestamp, 1234567890) + self.assertEqual(position_executor.close_type, CloseType.STOP_LOSS) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.COMPLETED) + position_executor.terminate_control_loop() + + def test_process_order_completed_event_take_profit_order(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.take_profit_order.order_id = "OID-BUY-1" + event = BuyOrderCompletedEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + base_asset="ETH", + quote_asset="USDT", + base_asset_amount=position_config.amount, + quote_asset_amount=position_config.amount * position_config.entry_price, + order_type=position_config.open_order_type, + exchange_order_id="ED140" + ) + market = MagicMock() + position_executor.process_order_completed_event("102", market, event) + self.assertEqual(position_executor.close_timestamp, 1234567890) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.COMPLETED) + self.assertEqual(position_executor.close_type, CloseType.TAKE_PROFIT) + position_executor.terminate_control_loop() + + def test_process_order_filled_event_open_order_not_started(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + event = OrderFilledEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + trading_pair="ETH-USDT", + price=position_config.entry_price, + amount=position_config.amount, + order_type=position_config.open_order_type, + trade_type=TradeType.SELL, + trade_fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]) + ) + market = MagicMock() + position_executor.process_order_filled_event("102", market, event) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.ACTIVE_POSITION) + position_executor.terminate_control_loop() + + def test_process_order_filled_event_open_order_started(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + event = OrderFilledEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + trading_pair="ETH-USDT", + price=position_config.entry_price, + amount=position_config.amount, + order_type=position_config.open_order_type, + trade_type=TradeType.SELL, + trade_fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]) + ) + market = MagicMock() + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + position_executor.process_order_filled_event("102", market, event) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.ACTIVE_POSITION) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("101")) + def test_to_format_status(self, _): + position_config = self.get_position_config_market_long() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + status = position_executor.to_format_status() + self.assertIn("Trading Pair: ETH-USDT", status[0]) + self.assertIn("PNL (%): 0.80%", status[0]) + position_executor.terminate_control_loop() + + @patch("hummingbot.smart_components.executors.position_executor.position_executor.PositionExecutor.get_price", return_value=Decimal("101")) + def test_to_format_status_is_closed(self, _): + position_config = self.get_position_config_market_long() + type(self.strategy).current_timestamp = PropertyMock(return_value=1234567890) + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + position_executor.open_order.order = InFlightOrder( + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + order_type=position_config.open_order_type, + trade_type=TradeType.BUY, + amount=position_config.amount, + price=position_config.entry_price, + creation_timestamp=1640001112.223, + initial_state=OrderState.FILLED + ) + position_executor.open_order.order.update_with_trade_update( + TradeUpdate( + trade_id="1", + client_order_id="OID-BUY-1", + exchange_order_id="EOID4", + trading_pair=position_config.trading_pair, + fill_price=position_config.entry_price, + fill_base_amount=position_config.amount, + fill_quote_amount=position_config.amount * position_config.entry_price, + fee=AddedToCostTradeFee(flat_fees=[TokenAmount(token="USDT", amount=Decimal("0.2"))]), + fill_timestamp=10, + ) + ) + position_executor.executor_status = PositionExecutorStatus.COMPLETED + type(position_executor).close_price = PropertyMock(return_value=Decimal(101)) + status = position_executor.to_format_status() + self.assertIn("Trading Pair: ETH-USDT", status[0]) + self.assertIn("PNL (%): 0.80%", status[0]) + position_executor.terminate_control_loop() + + def test_process_order_canceled_event(self): + position_config = self.get_position_config_market_long() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.open_order.order_id = "OID-BUY-1" + event = OrderCancelledEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + ) + market = MagicMock() + position_executor.process_order_canceled_event("102", market, event) + self.assertEqual(position_executor.executor_status, PositionExecutorStatus.COMPLETED) + self.assertEqual(position_executor.close_type, CloseType.EXPIRED) + position_executor.terminate_control_loop() + + def test_trailing_stop_condition(self): + position_config = self.get_position_config_trailing_stop() + position_executor = PositionExecutor(self.strategy, position_config) + position_executor.executor_status = PositionExecutorStatus.ACTIVE_POSITION + type(position_executor).close_price = PropertyMock(side_effect=[Decimal("101"), Decimal("102"), Decimal("103"), Decimal("101")]) + + # First: not activated + self.assertEqual(position_executor.trailing_stop_condition(), False) + self.assertEqual(position_executor._trailing_stop_activated, False) + + # Second: activated but not triggered + self.assertEqual(position_executor.trailing_stop_condition(), False) + self.assertEqual(position_executor._trailing_stop_activated, True) + self.assertEqual(position_executor._trailing_stop_price, Decimal("102")) + + # Third: activated and updated + self.assertEqual(position_executor.trailing_stop_condition(), False) + self.assertEqual(position_executor._trailing_stop_activated, True) + self.assertEqual(position_executor._trailing_stop_price, Decimal("102")) + + # Forth: triggered + self.assertEqual(position_executor.trailing_stop_condition(), True) + position_executor.terminate_control_loop() diff --git a/test/hummingbot/smart_components/strategy_frameworks/__init__.py b/test/hummingbot/smart_components/strategy_frameworks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/strategy_frameworks/directional_trading/__init__.py b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_backtesting_engine.py b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_backtesting_engine.py new file mode 100644 index 0000000..7f233a4 --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_backtesting_engine.py @@ -0,0 +1,95 @@ +import unittest +from datetime import datetime, timezone +from decimal import Decimal +from unittest.mock import Mock + +import pandas as pd + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_backtesting_engine import ( + DirectionalTradingBacktestingEngine, +) + + +class TestDirectionalTradingBacktestingEngine(unittest.TestCase): + def get_controller_mock_simple(self): + controller_base_mock = Mock() + controller_base_mock.config.order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + triple_barrier_conf=TripleBarrierConf(take_profit=Decimal("0.2"), + stop_loss=Decimal("0.1"), + time_limit=360)), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("10"), + triple_barrier_conf=TripleBarrierConf(take_profit=Decimal("0.2"), + stop_loss=Decimal("0.1"), + time_limit=360)) + ] + initial_date = datetime(2023, 3, 16, 0, 0, tzinfo=timezone.utc) + initial_timestamp = int(initial_date.timestamp()) + minute = 60 + timestamps = [ + initial_timestamp, + initial_timestamp + minute * 2, + initial_timestamp + minute * 4, + initial_timestamp + minute * 6, + initial_timestamp + minute * 8 + ] + + controller_base_mock.get_processed_data = Mock(return_value=pd.DataFrame({ + "timestamp": timestamps, + "close": [100, 110, 110, 130, 100], + "signal": [1, 1, -1, -1, 1] + })) + return controller_base_mock + + def get_controller_mock_with_cooldown(self): + controller_base_mock = Mock() + controller_base_mock.config.order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + cooldown_time=60, + triple_barrier_conf=TripleBarrierConf(take_profit=Decimal("0.2"), + stop_loss=Decimal("0.1"), + time_limit=360) + ), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("10"), + cooldown_time=60, + triple_barrier_conf=TripleBarrierConf(take_profit=Decimal("0.2"), + stop_loss=Decimal("0.1"), + time_limit=360)) + ] + initial_date = datetime(2023, 3, 16, 0, 0, tzinfo=timezone.utc) + initial_timestamp = int(initial_date.timestamp()) + minute = 60 + timestamps = [ + initial_timestamp, + initial_timestamp + minute * 2, + initial_timestamp + minute * 4, + initial_timestamp + minute * 6, + initial_timestamp + minute * 8 + ] + + controller_base_mock.get_processed_data = Mock(return_value=pd.DataFrame({ + "timestamp": timestamps, + "close": [100, 110, 110, 130, 100], + "signal": [1, 1, -1, -1, 1] + })) + return controller_base_mock + + def test_run_backtesting_all_positions(self): + engine = DirectionalTradingBacktestingEngine(self.get_controller_mock_simple()) + backtesting_results = engine.run_backtesting() + self.assertIsInstance(backtesting_results, dict) + processed_data = backtesting_results["processed_data"] + self.assertIn("signal", processed_data.columns) + + executors_df = backtesting_results["executors_df"] + self.assertIn("side", executors_df.columns) + self.assertEqual(4, len(executors_df)) + self.assertEqual(2, len(executors_df[executors_df["profitable"] == 1])) + + def test_run_backtesting_with_cooldown(self): + engine = DirectionalTradingBacktestingEngine(self.get_controller_mock_with_cooldown()) + backtesting_results = engine.run_backtesting() + executors_df = backtesting_results["executors_df"] + self.assertEqual(2, len(executors_df)) diff --git a/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_controller_base.py b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_controller_base.py new file mode 100644 index 0000000..91e7494 --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_controller_base.py @@ -0,0 +1,124 @@ +import unittest +from decimal import Decimal +from unittest.mock import MagicMock, patch + +import pandas as pd + +from hummingbot.core.data_type.common import TradeType +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.directional_trading import ( + DirectionalTradingControllerBase, + DirectionalTradingControllerConfigBase, +) + + +class TestDirectionalTradingControllerBase(unittest.TestCase): + + def setUp(self): + self.mock_candles_config = CandlesConfig( + connector="binance", + trading_pair="BTC-USDT", + interval="1m" + ) + # Mocking the DirectionalTradingControllerConfigBase + self.mock_controller_config = DirectionalTradingControllerConfigBase( + strategy_name="directional_strategy", + exchange="binance", + trading_pair="BTC-USDT", + candles_config=[self.mock_candles_config], + order_levels=[], + ) + + # Instantiating the DirectionalTradingControllerBase + self.controller = DirectionalTradingControllerBase( + config=self.mock_controller_config, + ) + + def test_filter_executors_df(self): + mock_df = pd.DataFrame({"trading_pair": ["BTC-USDT", "ETH-USDT"]}) + self.controller.filter_executors_df = MagicMock(return_value=mock_df[mock_df["trading_pair"] == "BTC-USDT"]) + filtered_df = self.controller.filter_executors_df(mock_df) + self.assertEqual(len(filtered_df), 1) + + def test_update_strategy_markets_dict(self): + markets_dict = {} + updated_markets_dict = self.controller.update_strategy_markets_dict(markets_dict) + self.assertEqual(updated_markets_dict, {"binance": {"BTC-USDT"}}) + + def test_is_perpetual(self): + self.controller.config.exchange = "binance_perpetual" + self.assertTrue(self.controller.is_perpetual) + + def test_get_signal(self): + mock_df = pd.DataFrame({"signal": [1, -1, 1]}) + self.controller.get_processed_data = MagicMock(return_value=mock_df) + signal = self.controller.get_signal() + self.assertEqual(signal, 1) + + def test_early_stop_condition(self): + with self.assertRaises(NotImplementedError): + self.controller.early_stop_condition(None, None) + + def test_cooldown_condition(self): + with self.assertRaises(NotImplementedError): + self.controller.cooldown_condition(None, None) + + def test_get_processed_data(self): + with self.assertRaises(NotImplementedError): + self.controller.get_processed_data() + + @patch( + "hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_controller_base.format_df_for_printout") + def test_to_format_status(self, mock_format_df_for_printout): + # Create a mock DataFrame + mock_df = pd.DataFrame({ + "timestamp": ["2021-01-01", "2021-01-02", "2021-01-03", "2021-01-04"], + "open": [1, 2, 3, 4], + "low": [1, 2, 3, 4], + "high": [1, 2, 3, 4], + "close": [1, 2, 3, 4], + "volume": [1, 2, 3, 4], + "signal": [1, -1, 1, -1] + }) + + # Mock the get_processed_data method to return the mock DataFrame + self.controller.get_processed_data = MagicMock(return_value=mock_df) + + # Mock the format_df_for_printout function to return a sample formatted string + mock_format_df_for_printout.return_value = "formatted_string" + + # Call the method and get the result + result = self.controller.to_format_status() + + # Check if the result contains the expected formatted string + self.assertIn("formatted_string", result) + + @patch("hummingbot.smart_components.strategy_frameworks.controller_base.ControllerBase.get_close_price") + def test_get_position_config(self, mock_get_closest_price): + order_level = OrderLevel( + level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + triple_barrier_conf=TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 2, + )) + mock_get_closest_price.return_value = Decimal("100") + # Create a mock DataFrame + mock_df = pd.DataFrame({ + "timestamp": ["2021-01-01", "2021-01-02", "2021-01-03", "2021-01-04"], + "open": [1, 2, 3, 4], + "low": [1, 2, 3, 4], + "high": [1, 2, 3, 4], + "close": [1, 2, 3, 4], + "volume": [1, 2, 3, 4], + "signal": [1, -1, 1, -1] + }) + + # Mock the get_processed_data method to return the mock DataFrame + self.controller.get_processed_data = MagicMock(return_value=mock_df) + position_config = self.controller.get_position_config(order_level, 1) + self.assertEqual(position_config.trading_pair, "BTC-USDT") + self.assertEqual(position_config.exchange, "binance") + self.assertEqual(position_config.side, TradeType.BUY) + self.assertEqual(position_config.amount, Decimal("0.1")) + self.assertEqual(position_config.entry_price, Decimal("100")) diff --git a/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_executor_handler.py b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_executor_handler.py new file mode 100644 index 0000000..fb4a70e --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/directional_trading/test_directional_trading_executor_handler.py @@ -0,0 +1,100 @@ +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from unittest.mock import MagicMock, patch + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionExecutorStatus +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.directional_trading import ( + DirectionalTradingControllerBase, + DirectionalTradingControllerConfigBase, + DirectionalTradingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class TestDirectionalTradingExecutorHandler(IsolatedAsyncioWrapperTestCase): + + def setUp(self): + # Mocking the necessary components + self.mock_strategy = MagicMock(spec=ScriptStrategyBase) + self.mock_controller = MagicMock(spec=DirectionalTradingControllerBase) + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 24, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + self.mock_controller.config = MagicMock(spec=DirectionalTradingControllerConfigBase) + self.mock_controller.config.exchange = "binance" + self.mock_controller.config.trading_pair = "BTC-USDT" + self.mock_controller.config.order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("100"), + spread_factor=Decimal("0.01"), triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("100"), + spread_factor=Decimal("0.01"), triple_barrier_conf=triple_barrier_conf) + ] + + # Instantiating the DirectionalTradingExecutorHandler + self.handler = DirectionalTradingExecutorHandler( + strategy=self.mock_strategy, + controller=self.mock_controller + ) + + @patch( + "hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.close_open_positions") + def test_on_stop_perpetual(self, mock_close_open_positions): + self.mock_controller.is_perpetual = True + self.handler.on_stop() + mock_close_open_positions.assert_called_once() + + @patch( + "hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.close_open_positions") + def test_on_stop_non_perpetual(self, mock_close_open_positions): + self.mock_controller.is_perpetual = False + self.handler.on_stop() + mock_close_open_positions.assert_not_called() + + @patch( + "hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_executor_handler.DirectionalTradingExecutorHandler.set_leverage_and_position_mode") + def test_on_start_perpetual(self, mock_set_leverage): + self.mock_controller.is_perpetual = True + self.handler.on_start() + mock_set_leverage.assert_called_once() + + @patch( + "hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_executor_handler.DirectionalTradingExecutorHandler.set_leverage_and_position_mode") + def test_on_start_non_perpetual(self, mock_set_leverage): + self.mock_controller.is_perpetual = False + self.handler.on_start() + mock_set_leverage.assert_not_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_all_candles_ready(self, mock_create_executor): + self.mock_controller.all_candles_ready = True + await self.handler.control_task() + mock_create_executor.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_candles_not_ready(self, mock_create_executor): + self.mock_controller.all_candles_ready = False + await self.handler.control_task() + mock_create_executor.assert_not_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.store_executor") + async def test_control_task_executor_closed_not_in_cooldown(self, mock_store_executor): + self.mock_controller.all_candles_ready = True + mock_executor = MagicMock() + mock_executor.is_closed = True + mock_executor.executor_status = PositionExecutorStatus.COMPLETED + self.handler.level_executors["BUY_1"] = mock_executor + self.handler.level_executors["SELL_1"] = mock_executor + self.mock_controller.cooldown_condition.return_value = False + await self.handler.control_task() + mock_store_executor.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_no_executor(self, mock_create_executor): + self.mock_controller.all_candles_ready = True + await self.handler.control_task() + mock_create_executor.assert_called() diff --git a/test/hummingbot/smart_components/strategy_frameworks/market_making/__init__.py b/test/hummingbot/smart_components/strategy_frameworks/market_making/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_controller_base.py b/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_controller_base.py new file mode 100644 index 0000000..550a78d --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_controller_base.py @@ -0,0 +1,75 @@ +import unittest +from unittest.mock import MagicMock + +import pandas as pd + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.strategy_frameworks.market_making import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class TestMarketMakingControllerBase(unittest.TestCase): + + def setUp(self): + # Mocking the CandlesConfig + self.mock_candles_config = CandlesConfig( + connector="binance", + trading_pair="BTC-USDT", + interval="1m" + ) + + # Mocking the MarketMakingControllerConfigBase + self.mock_controller_config = MarketMakingControllerConfigBase( + strategy_name="dman_strategy", + exchange="binance", + trading_pair="BTC-USDT", + candles_config=[self.mock_candles_config], + order_levels=[] + ) + + # Instantiating the MarketMakingControllerBase + self.controller = MarketMakingControllerBase( + config=self.mock_controller_config, + ) + + def test_get_price_and_spread_multiplier(self): + mock_candles_df = pd.DataFrame({"price_multiplier": [1.0, 2.0, 3.0], "spread_multiplier": [0.1, 0.2, 0.3]}) + self.controller.get_processed_data = MagicMock(return_value=mock_candles_df) + price_multiplier, spread_multiplier = self.controller.get_price_and_spread_multiplier() + self.assertEqual(price_multiplier, 3.0) + self.assertEqual(spread_multiplier, 0.3) + + def test_update_strategy_markets_dict(self): + markets_dict = {} + updated_markets_dict = self.controller.update_strategy_markets_dict(markets_dict) + self.assertEqual(updated_markets_dict, {"binance": {"BTC-USDT"}}) + + def test_is_perpetual_true(self): + self.controller.config.exchange = "mock_exchange_perpetual" + self.assertTrue(self.controller.is_perpetual) + + def test_is_perpetual_false(self): + self.controller.config.exchange = "mock_regular_exchange" + self.assertFalse(self.controller.is_perpetual) + + def test_refresh_order_condition(self): + with self.assertRaises(NotImplementedError): + self.controller.refresh_order_condition(None, None) + + def test_early_stop_condition(self): + with self.assertRaises(NotImplementedError): + self.controller.early_stop_condition(None, None) + + def test_cooldown_condition(self): + with self.assertRaises(NotImplementedError): + self.controller.cooldown_condition(None, None) + + def test_get_position_config(self): + with self.assertRaises(NotImplementedError): + self.controller.get_position_config(None) + + def test_get_candles_with_price_and_spread_multipliers(self): + with self.assertRaises(NotImplementedError): + self.controller.get_processed_data() diff --git a/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_executor_handler.py b/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_executor_handler.py new file mode 100644 index 0000000..2660bfb --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/market_making/test_market_making_executor_handler.py @@ -0,0 +1,110 @@ +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from unittest.mock import MagicMock, patch + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionExecutorStatus +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf +from hummingbot.smart_components.strategy_frameworks.market_making import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class TestMarketMakingExecutorHandler(IsolatedAsyncioWrapperTestCase): + + def setUp(self): + # Mocking the necessary components + self.mock_strategy = MagicMock(spec=ScriptStrategyBase) + self.mock_controller = MagicMock(spec=MarketMakingControllerBase) + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 24, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + self.mock_controller.config = MagicMock(spec=MarketMakingControllerConfigBase) + self.mock_controller.config.exchange = "binance" + self.mock_controller.config.trading_pair = "BTC-USDT" + self.mock_controller.config.order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("100"), + spread_factor=Decimal("0.01"), triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("100"), + spread_factor=Decimal("0.01"), triple_barrier_conf=triple_barrier_conf) + ] + + # Instantiating the MarketMakingExecutorHandler + self.handler = MarketMakingExecutorHandler( + strategy=self.mock_strategy, + controller=self.mock_controller + ) + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.close_open_positions") + def test_on_stop_perpetual(self, mock_close_open_positions): + self.mock_controller.is_perpetual = True + self.handler.on_stop() + mock_close_open_positions.assert_called_once() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.close_open_positions") + def test_on_stop_non_perpetual(self, mock_close_open_positions): + self.mock_controller.is_perpetual = False + self.handler.on_stop() + mock_close_open_positions.assert_not_called() + + @patch("hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler.MarketMakingExecutorHandler.set_leverage_and_position_mode") + def test_on_start_perpetual(self, mock_set_leverage): + self.mock_controller.is_perpetual = True + self.handler.on_start() + mock_set_leverage.assert_called_once() + + @patch("hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler.MarketMakingExecutorHandler.set_leverage_and_position_mode") + def test_on_start_non_perpetual(self, mock_set_leverage): + self.mock_controller.is_perpetual = False + self.handler.on_start() + mock_set_leverage.assert_not_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_all_candles_ready(self, mock_create_executor): + self.mock_controller.all_candles_ready = True + await self.handler.control_task() + mock_create_executor.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_candles_not_ready(self, mock_create_executor): + self.mock_controller.all_candles_ready = False + await self.handler.control_task() + mock_create_executor.assert_not_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.store_executor") + async def test_control_task_executor_closed_not_in_cooldown(self, mock_store_executor): + self.mock_controller.all_candles_ready = True + mock_executor = MagicMock() + mock_executor.is_closed = True + mock_executor.executor_status = PositionExecutorStatus.COMPLETED + self.handler.level_executors["BUY_1"] = mock_executor + self.handler.level_executors["SELL_1"] = mock_executor + self.mock_controller.cooldown_condition.return_value = False + + await self.handler.control_task() + mock_store_executor.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.store_executor") + async def test_control_task_executor_not_started_refresh_order(self, _): + self.mock_controller.all_candles_ready = True + mock_executor = MagicMock() + mock_executor.is_closed = False + mock_executor.executor_status = PositionExecutorStatus.NOT_STARTED + self.handler.level_executors["BUY_1"] = mock_executor + self.handler.level_executors["SELL_1"] = mock_executor + self.mock_controller.refresh_order_condition.return_value = True + + await self.handler.control_task() + mock_executor.early_stop.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.ExecutorHandlerBase.create_executor") + async def test_control_task_no_executor(self, mock_create_executor): + self.mock_controller.all_candles_ready = True + await self.handler.control_task() + mock_create_executor.assert_called() diff --git a/test/hummingbot/smart_components/strategy_frameworks/test_backtesting_engine_base.py b/test/hummingbot/smart_components/strategy_frameworks/test_backtesting_engine_base.py new file mode 100644 index 0000000..cd2b1de --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/test_backtesting_engine_base.py @@ -0,0 +1,94 @@ +import unittest +from datetime import datetime, timezone +from unittest.mock import patch + +import pandas as pd + +from hummingbot.smart_components.strategy_frameworks.backtesting_engine_base import BacktestingEngineBase + + +class TestBacktestingEngineBase(unittest.TestCase): + + @patch("hummingbot.smart_components.strategy_frameworks.controller_base.ControllerBase") + def setUp(self, MockControllerBase): + self.controller = MockControllerBase() + self.backtesting_engine = BacktestingEngineBase(self.controller) + + def test_filter_df_by_time(self): + df = pd.DataFrame({ + "timestamp": pd.date_range(start="2021-01-01", end="2021-01-05", freq="D") + }) + filtered_df = self.backtesting_engine.filter_df_by_time(df, "2021-01-02", "2021-01-04") + self.assertEqual(len(filtered_df), 3) + self.assertEqual(filtered_df["timestamp"].min(), pd.Timestamp("2021-01-02")) + self.assertEqual(filtered_df["timestamp"].max(), pd.Timestamp("2021-01-04")) + + @patch("pandas.read_csv") + def test_get_data(self, mock_read_csv): + mock_df = pd.DataFrame({ + "timestamp": pd.date_range(start="2021-01-01", end="2021-01-05", freq="D") + }) + mock_read_csv.return_value = mock_df + self.controller.get_processed_data.return_value = mock_df + + df = self.backtesting_engine.get_data("2021-01-02", "2021-01-04") + self.assertEqual(len(df), 3) + self.assertEqual(df["timestamp"].min(), pd.Timestamp("2021-01-02")) + self.assertEqual(df["timestamp"].max(), pd.Timestamp("2021-01-04")) + + def test_summarize_results(self): + initial_date = datetime(2023, 3, 16, 0, 0, tzinfo=timezone.utc) + initial_timestamp = int(initial_date.timestamp()) + minute = 60 + timestamps = [ + initial_timestamp, + initial_timestamp + minute * 2, + initial_timestamp + minute * 4, + initial_timestamp + minute * 6, + initial_timestamp + minute * 8 + ] + + # Assuming each trade closes after 60 seconds (1 minute) + close_timestamps = [timestamp + 60 for timestamp in timestamps] + executors_df = pd.DataFrame({ + "timestamp": timestamps, + "close_timestamp": close_timestamps, + "exchange": ["binance_perpetual"] * 5, + "trading_pair": ["HBOT-USDT"] * 5, + "side": ["BUY", "BUY", "SELL", "SELL", "BUY"], + "amount": [10, 20, 10, 20, 10], + "trade_pnl": [0.2, 0.1, -0.1, -0.2, 0.2], + "trade_pnl_quote": [0, 0, 0, 0, 0], + "cum_fee_quote": [0, 0, 0, 0, 0], + "net_pnl": [1, 2, -1, -2, 1], + "net_pnl_quote": [0.1, 0.2, -0.1, -0.2, 0.1], + "profitable": [1, 1, 0, 0, 1], + "signal": [1, -1, 1, 1, 1], + "executor_status": ["COMPLETED"] * 5, + "close_type": ["EXPIRED", "EXPIRED", "EXPIRED", "EXPIRED", "EXPIRED"], + "entry_price": [5, 5, 5, 5, 5], + "close_price": [5, 5, 5, 5, 5], + "sl": [0.03] * 5, + "tp": [0.02] * 5, + "tl": [86400] * 5, + "leverage": [10] * 5, + "inventory": [10, 10, 10, 10, 10], + }) + executors_df.index = pd.to_datetime(executors_df["timestamp"], unit="s") + executors_df["close_time"] = pd.to_datetime(executors_df["close_timestamp"], unit="s") + result = self.backtesting_engine.summarize_results(executors_df) + self.assertEqual(result["net_pnl"], 1) # 1 + 2 - 1 - 2 + 1 + self.assertEqual(round(result["net_pnl_quote"], 2), 0.1) # 0.1 + 0.2 - 0.1 - 0.2 + 0.1 + self.assertEqual(result["total_executors"], 5) + self.assertEqual(result["total_executors_with_position"], 5) + self.assertEqual(result["total_long"], 3) # 3 BUYs + self.assertEqual(result["total_short"], 2) # 2 SELLs + self.assertEqual(result["close_types"]["EXPIRED"], 5) # All are "EXPIRED" + self.assertEqual(result["accuracy"], 3 / 5) # 3 out of 5 trades were profitable + self.assertEqual(round(result["duration_minutes"], 3), 9) # 4 minutes between the first and last trade + self.assertEqual(round(result["avg_trading_time_minutes"], 3), 1) # Average of 1 minute between trades + + def test_summarize_results_empty(self): + result = self.backtesting_engine.summarize_results(pd.DataFrame()) + self.assertEqual(result["net_pnl"], 0) + self.assertEqual(result["net_pnl_quote"], 0) diff --git a/test/hummingbot/smart_components/strategy_frameworks/test_controller_base.py b/test/hummingbot/smart_components/strategy_frameworks/test_controller_base.py new file mode 100644 index 0000000..e728044 --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/test_controller_base.py @@ -0,0 +1,92 @@ +import unittest +from unittest.mock import MagicMock + +import pandas as pd + +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase, ControllerConfigBase + + +class TestControllerBase(unittest.TestCase): + + def setUp(self): + # Mocking the CandlesConfig + self.mock_candles_config = CandlesConfig( + connector="binance", + trading_pair="BTC-USDT", + interval="1m" + ) + + # Mocking the ControllerConfigBase + self.mock_controller_config = ControllerConfigBase( + strategy_name="dman_strategy", + exchange="binance_perpetual", + trading_pair="BTC-USDT", + candles_config=[self.mock_candles_config], + order_levels=[] + ) + + # Instantiating the ControllerBase + self.controller = ControllerBase( + config=self.mock_controller_config, + ) + + def test_initialize_candles_live_mode(self): + candles = self.controller.initialize_candles([self.mock_candles_config]) + self.assertTrue(len(candles) == 1) + + def test_initialize_candles_non_live_mode(self): + self.controller.initialize_candles([self.mock_candles_config]) + self.assertTrue(len(self.controller.candles) == 1) + + def test_get_close_price(self): + mock_candle = MagicMock() + mock_candle.name = "binance_BTC-USDT" + mock_candle._trading_pair = "BTC-USDT" + mock_candle.interval = "1m" + mock_candle.candles_df = pd.DataFrame({"close": [100.0, 200.0, 300.0], + "open": [100.0, 200.0, 300.0]}) + self.controller.candles = [mock_candle] + close_price = self.controller.get_close_price("BTC-USDT") + self.assertEqual(close_price, 300) + + def test_get_candles_by_connector_trading_pair(self): + mock_candle = MagicMock() + mock_candle.name = "binance_BTC-USDT" + mock_candle.interval = "1m" + result = self.controller.get_candles_by_connector_trading_pair("binance", "BTC-USDT") + self.assertEqual(list(result.keys()), ["1m"]) + + def test_get_candle(self): + mock_candle = MagicMock() + mock_candle.name = "binance_BTC-USDT" + mock_candle.interval = "1m" + self.controller.candles = [mock_candle] + result = self.controller.get_candle("binance", "BTC-USDT", "1m") + self.assertEqual(result, mock_candle) + + def test_all_candles_ready(self): + mock_candle = MagicMock() + mock_candle.is_ready = True + self.controller.candles = [mock_candle] + self.assertTrue(self.controller.all_candles_ready) + + def test_start(self): + mock_candle = MagicMock() + self.controller.candles = [mock_candle] + self.controller.start() + mock_candle.start.assert_called_once() + + def test_stop(self): + mock_candle = MagicMock() + self.controller.candles = [mock_candle] + self.controller.stop() + mock_candle.stop.assert_called_once() + + def test_get_csv_prefix(self): + prefix = self.controller.get_csv_prefix() + self.assertEqual(prefix, "dman_strategy") + + def test_to_format_status(self): + status = self.controller.to_format_status() + self.assertEqual(" exchange: binance_perpetual", status[1]) diff --git a/test/hummingbot/smart_components/strategy_frameworks/test_executor_handler_base.py b/test/hummingbot/smart_components/strategy_frameworks/test_executor_handler_base.py new file mode 100644 index 0000000..8f3a777 --- /dev/null +++ b/test/hummingbot/smart_components/strategy_frameworks/test_executor_handler_base.py @@ -0,0 +1,171 @@ +import random +from decimal import Decimal +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase +from unittest.mock import AsyncMock, MagicMock, patch + +import pandas as pd + +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.logger import HummingbotLogger +from hummingbot.smart_components.strategy_frameworks.controller_base import ControllerBase +from hummingbot.smart_components.strategy_frameworks.executor_handler_base import ExecutorHandlerBase + + +class TestExecutorHandlerBase(IsolatedAsyncioWrapperTestCase): + def setUp(self): + super().setUp() + self.mock_strategy = MagicMock() + self.mock_controller = MagicMock(spec=ControllerBase) + self.mock_controller.config = MagicMock() + self.mock_controller.config.strategy_name = "test_strategy" + self.mock_controller.config.order_levels = [] + self.mock_controller.get_csv_prefix = MagicMock(return_value="test_strategy") + self.executor_handler = ExecutorHandlerBase(self.mock_strategy, self.mock_controller) + + def test_initialization(self): + self.assertEqual(self.executor_handler.strategy, self.mock_strategy) + self.assertEqual(self.executor_handler.controller, self.mock_controller) + self.assertTrue(isinstance(self.executor_handler.logger(), HummingbotLogger)) + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.safe_ensure_future") + def test_start(self, mock_safe_ensure_future): + self.executor_handler.start() + self.mock_controller.start.assert_called_once() + mock_safe_ensure_future.assert_called_once() + + def test_terminate_control_loop(self): + self.executor_handler.stop() + self.assertTrue(self.executor_handler.terminated.is_set()) + + def test_on_stop(self): + self.executor_handler.on_stop() + self.mock_controller.stop.assert_called_once() + + def test_get_csv_path(self): + path = self.executor_handler.get_csv_path() + self.assertEqual(path.suffix, ".csv") + self.assertIn("test_strategy", path.name) + + @patch("hummingbot.connector.markets_recorder.MarketsRecorder", new_callable=MagicMock) + @patch("pandas.DataFrame.to_csv", new_callable=MagicMock) + def test_store_executor_removes_executor(self, _, market_recorder_mock): + market_recorder_mock.store_executor = MagicMock() + mock_executor = MagicMock() + mock_executor.to_json = MagicMock(return_value={"timestamp": 123445634, + "exchange": "binance_perpetual", + "trading_pair": "BTC-USDT", + "side": "BUY", + "amount": 100, + "trade_pnl": 0.1, + "trade_pnl_quote": 10, + "cum_fee_quote": 1, + "net_pnl_quote": 9, + "net_pnl": 0.09, + "close_timestamp": 1234156423, + "executor_status": "CLOSED", + "close_type": "TAKE_PROFIT", + "entry_price": 100, + "close_price": 110, + "sl": 0.03, + "tp": 0.05, + "tl": 0.1, + "open_order_type": "MARKET", + "take_profit_order_type": "MARKET", + "stop_loss_order_type": "MARKET", + "time_limit_order_type": "MARKET", + "leverage": 10, + }) + mock_order_level = MagicMock() + mock_order_level.level_id = "BUY_1" + self.executor_handler.store_executor(mock_executor, mock_order_level) + self.assertIsNone(self.executor_handler.level_executors[mock_order_level.level_id]) + + @patch.object(ExecutorHandlerBase, "_sleep", new_callable=AsyncMock) + @patch.object(ExecutorHandlerBase, "control_task", new_callable=AsyncMock) + async def test_control_loop(self, mock_control_task, mock_sleep): + mock_sleep.side_effect = [None, Exception] + with self.assertRaises(Exception): + await self.executor_handler.control_loop() + mock_control_task.assert_called() + + @patch("hummingbot.smart_components.strategy_frameworks.executor_handler_base.PositionExecutor") + def test_create_executor(self, mock_position_executor): + mock_position_config = MagicMock() + mock_order_level = MagicMock() + self.executor_handler.create_executor(mock_position_config, mock_order_level) + mock_position_executor.assert_called_once_with(self.mock_strategy, mock_position_config, update_interval=1.0) + self.assertIsNotNone(self.executor_handler.level_executors[mock_order_level.level_id]) + + def generate_random_data(self, num_rows): + data = { + "net_pnl": [random.uniform(-1, 1) for _ in range(num_rows)], + "net_pnl_quote": [random.uniform(0, 1000) for _ in range(num_rows)], + "amount": [random.uniform(0, 100) for _ in range(num_rows)], + "side": [random.choice(["BUY", "SELL"]) for _ in range(num_rows)], + "close_type": [random.choice(["type1", "type2", "type3"]) for _ in range(num_rows)], + "timestamp": [pd.Timestamp.now() for _ in range(num_rows)] + } + return pd.DataFrame(data) + + def test_summarize_executors_df(self): + df = self.generate_random_data(100) # Generate a DataFrame with 100 rows of random data + + summary = ExecutorHandlerBase.summarize_executors_df(df) + + # Check if the summary values match the DataFrame's values + self.assertEqual(summary["net_pnl"], df["net_pnl"].sum()) + self.assertEqual(summary["net_pnl_quote"], df["net_pnl_quote"].sum()) + self.assertEqual(summary["total_executors"], df.shape[0]) + self.assertEqual(summary["total_executors_with_position"], df[df["net_pnl"] != 0].shape[0]) + self.assertEqual(summary["total_volume"], df[df["net_pnl"] != 0]["amount"].sum() * 2) + self.assertEqual(summary["total_long"], (df[df["net_pnl"] != 0]["side"] == "BUY").sum()) + self.assertEqual(summary["total_short"], (df[df["net_pnl"] != 0]["side"] == "SELL").sum()) + + def test_close_open_positions(self): + # Mocking the connector and its methods + mock_connector = MagicMock() + mock_connector.get_mid_price.return_value = 100 # Mocking the mid price to be 100 + + # Mocking the account_positions of the connector + mock_position1 = MagicMock(trading_pair="BTC-USD", position_side=PositionSide.LONG, amount=10) + mock_position2 = MagicMock(trading_pair="BTC-USD", position_side=PositionSide.SHORT, amount=-10) + mock_connector.account_positions = { + "pos1": mock_position1, + "pos2": mock_position2 + } + + # Setting the mock connector to the strategy's connectors + self.mock_strategy.connectors = {"mock_connector": mock_connector} + + # Calling the method + self.executor_handler.close_open_positions(connector_name="mock_connector", trading_pair="BTC-USD") + + # Asserting that the strategy's sell and buy methods were called with the expected arguments + self.mock_strategy.sell.assert_called_once_with( + connector_name="mock_connector", + trading_pair="BTC-USD", + amount=10, + order_type=OrderType.MARKET, + price=100, + position_action=PositionAction.CLOSE + ) + self.mock_strategy.buy.assert_called_once_with( + connector_name="mock_connector", + trading_pair="BTC-USD", + amount=10, + order_type=OrderType.MARKET, + price=100, + position_action=PositionAction.CLOSE + ) + + def test_get_active_executors_df(self): + position_executor_mock = MagicMock() + position_executor_mock.to_json = MagicMock(return_value={"entry_price": Decimal("100"), + "amount": Decimal("10")}) + self.executor_handler.level_executors = { + "level1": position_executor_mock, + "level2": position_executor_mock, + "level3": position_executor_mock + } + active_executors_df = self.executor_handler.get_active_executors_df() + self.assertEqual(active_executors_df.shape[0], 3) diff --git a/test/hummingbot/smart_components/test_smart_component_base.py b/test/hummingbot/smart_components/test_smart_component_base.py new file mode 100644 index 0000000..03ae363 --- /dev/null +++ b/test/hummingbot/smart_components/test_smart_component_base.py @@ -0,0 +1,160 @@ +import asyncio +import unittest +from decimal import Decimal +from typing import Awaitable +from unittest.mock import MagicMock, PropertyMock + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderFilledEvent, +) +from hummingbot.smart_components.smart_component_base import SmartComponentBase, SmartComponentStatus +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class TestSmartComponentBase(unittest.TestCase): + def setUp(self): + self.strategy = self.create_mock_strategy + self.component = SmartComponentBase(self.strategy, ["connector1"], update_interval=0.5) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + @property + def create_mock_strategy(self): + market = MagicMock() + market_info = MagicMock() + market_info.market = market + + strategy = MagicMock(spec=ScriptStrategyBase) + type(strategy).market_info = PropertyMock(return_value=market_info) + type(strategy).trading_pair = PropertyMock(return_value="ETH-USDT") + strategy.buy.side_effect = ["OID-BUY-1", "OID-BUY-2", "OID-BUY-3"] + strategy.sell.side_effect = ["OID-SELL-1", "OID-SELL-2", "OID-SELL-3"] + strategy.cancel.return_value = None + strategy.connectors = { + "connector1": MagicMock(spec=ConnectorBase), + } + return strategy + + def test_constructor(self): + component = SmartComponentBase(self.strategy, ["connector1"], update_interval=0.5) + self.assertEqual(component._strategy, self.strategy) + self.assertEqual(component.update_interval, 0.5) + self.assertEqual(len(component.connectors), 1) + self.assertEqual(component._status, SmartComponentStatus.NOT_STARTED) + self.assertEqual(component._states, []) + self.assertIsInstance(component._create_buy_order_forwarder, SourceInfoEventForwarder) + + def test_control_loop(self): + self.component.control_task = MagicMock() + self.component.terminated.set() + self.async_run_with_timeout(self.component.control_loop()) + self.assertEqual(self.component._status, SmartComponentStatus.TERMINATED) + + def test_terminate_control_loop(self): + self.component.control_task = MagicMock() + self.component.terminate_control_loop() + self.async_run_with_timeout(self.component.control_loop()) + self.assertEqual(self.component.status, SmartComponentStatus.TERMINATED) + + def test_process_order_completed_event(self): + event_tag = 1 + market = MagicMock() + event = BuyOrderCompletedEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + base_asset="ETH", + quote_asset="USDT", + base_asset_amount=Decimal("1.0"), + quote_asset_amount=Decimal("1.0") * Decimal("1000.0"), + order_type=OrderType.LIMIT, + exchange_order_id="ED140" + ) + self.component.process_order_completed_event(event_tag, market, event) + self.assertIsNone(self.component.process_order_completed_event(event_tag, market, event)) + + def test_process_order_created_event(self): + event_tag = 1 + market = MagicMock() + event = BuyOrderCreatedEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + trading_pair="ETH-USDT", + amount=Decimal("1.0"), + type=OrderType.LIMIT, + price=Decimal("1000.0"), + exchange_order_id="ED140", + creation_timestamp=1234567890 + ) + self.component.process_order_created_event(event_tag, market, event) + self.assertIsNone(self.component.process_order_created_event(event_tag, market, event)) + + def test_process_order_canceled_event(self): + event_tag = 1 + market = MagicMock() + event = OrderCancelledEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + exchange_order_id="ED140", + ) + self.component.process_order_canceled_event(event_tag, market, event) + self.assertIsNone(self.component.process_order_canceled_event(event_tag, market, event)) + + def test_process_order_filled_event(self): + event_tag = 1 + market = MagicMock() + event = OrderFilledEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + exchange_order_id="ED140", + trading_pair="ETH-USDT", + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=Decimal("1000.0"), + amount=Decimal("1.0"), + trade_fee=AddedToCostTradeFee(percent=Decimal("0.001")), + ) + self.component.process_order_filled_event(event_tag, market, event) + self.assertIsNone(self.component.process_order_filled_event(event_tag, market, event)) + + def test_process_order_failed_event(self): + event_tag = 1 + market = MagicMock() + event = MarketOrderFailureEvent( + timestamp=1234567890, + order_id="OID-BUY-1", + order_type=OrderType.LIMIT, + ) + self.component.process_order_failed_event(event_tag, market, event) + self.assertIsNone(self.component.process_order_failed_event(event_tag, market, event)) + + def test_place_buy_order(self): + buy_order_id = self.component.place_order( + connector_name="connector1", + trading_pair="ETH-USDT", + order_type=OrderType.LIMIT, + side=TradeType.BUY, + price=Decimal("1000.0"), + amount=Decimal("1.0"), + ) + self.assertEqual(buy_order_id, "OID-BUY-1") + + def test_place_sell_order(self): + sell_order_id = self.component.place_order( + connector_name="connector1", + trading_pair="ETH-USDT", + order_type=OrderType.LIMIT, + side=TradeType.SELL, + price=Decimal("1000.0"), + amount=Decimal("1.0"), + ) + self.assertEqual(sell_order_id, "OID-SELL-1") diff --git a/test/hummingbot/smart_components/utils/__init__.py b/test/hummingbot/smart_components/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/smart_components/utils/test_distributions.py b/test/hummingbot/smart_components/utils/test_distributions.py new file mode 100644 index 0000000..08db687 --- /dev/null +++ b/test/hummingbot/smart_components/utils/test_distributions.py @@ -0,0 +1,45 @@ +import unittest +from decimal import Decimal + +from hummingbot.smart_components.utils.distributions import Distributions + + +class TestDistributions(unittest.TestCase): + + def test_linear(self): + result = Distributions.linear(5, 0, 10) + expected = [Decimal(x) for x in [0, 2.5, 5, 7.5, 10]] + self.assertEqual(result, expected) + + def test_fibonacci(self): + result = Distributions.fibonacci(5, 0.01) + expected = [Decimal("0.01"), Decimal("0.02"), Decimal("0.03"), Decimal("0.05"), Decimal("0.08")] + for r, e in zip(result, expected): + self.assertAlmostEqual(r, e, places=2) + + def test_fibonacci_single_level(self): + result = Distributions.fibonacci(1, 0.01) + expected = [Decimal("0.01")] + for r, e in zip(result, expected): + self.assertAlmostEqual(r, e, places=2) + + def test_logarithmic(self): + result = Distributions.logarithmic(4) + # Expected values can be computed using the formula, but here are approximated: + expected = [Decimal(x) for x in [0.4, 0.805, 1.093, 1.316]] + for r, e in zip(result, expected): + self.assertAlmostEqual(r, e, places=2) + + def test_arithmetic(self): + result = Distributions.arithmetic(5, 1, 2) + expected = [Decimal(x) for x in [1, 3, 5, 7, 9]] + self.assertEqual(result, expected) + + def test_geometric(self): + result = Distributions.geometric(5, 1, 2) + expected = [Decimal(x) for x in [1, 2, 4, 8, 16]] + self.assertEqual(result, expected) + + def test_geometric_invalid_ratio(self): + with self.assertRaises(ValueError): + Distributions.geometric(5, 1, 0.5) diff --git a/test/hummingbot/smart_components/utils/test_order_level_builder.py b/test/hummingbot/smart_components/utils/test_order_level_builder.py new file mode 100644 index 0000000..0b62ad1 --- /dev/null +++ b/test/hummingbot/smart_components/utils/test_order_level_builder.py @@ -0,0 +1,43 @@ +import unittest +from decimal import Decimal + +from hummingbot.smart_components.strategy_frameworks.data_types import TripleBarrierConf +from hummingbot.smart_components.utils.order_level_builder import OrderLevelBuilder + + +class TestOrderLevelBuilder(unittest.TestCase): + + def setUp(self): + self.builder = OrderLevelBuilder(3) + + def test_resolve_input_single_value(self): + result = self.builder.resolve_input(10.5) + self.assertEqual(result, [10.5, 10.5, 10.5]) + + def test_resolve_input_list(self): + input_list = [10.5, 20.5, 30.5] + result = self.builder.resolve_input(input_list) + self.assertEqual(result, input_list) + + def test_resolve_input_dict(self): + input_dict = {"method": "linear", "params": {"start": 0, "end": 3}} + result = self.builder.resolve_input(input_dict) + self.assertEqual(result, [Decimal(0), Decimal(1.5), Decimal(3)]) + + def test_resolve_input_invalid_list(self): + with self.assertRaises(ValueError): + self.builder.resolve_input([10.5, 20.5]) + + def test_resolve_input_invalid_dict(self): + with self.assertRaises(ValueError): + self.builder.resolve_input({"method": "unknown_method", "params": {}}) + + def test_build_order_levels(self): + amounts = [Decimal("100"), Decimal("200"), Decimal("300")] + spreads = [Decimal("0.01"), Decimal("0.02"), Decimal("0.03")] + triple_barrier_confs = TripleBarrierConf() # Assume a default instance is enough. + result = self.builder.build_order_levels(amounts, spreads, triple_barrier_confs) + + self.assertEqual(len(result), 6) # 3 levels * 2 sides + self.assertEqual(result[0].order_amount_usd, Decimal("100")) + self.assertEqual(result[0].spread_factor, Decimal("0.01")) diff --git a/test/hummingbot/strategy/__init__.py b/test/hummingbot/strategy/__init__.py new file mode 100644 index 0000000..8daf8fe --- /dev/null +++ b/test/hummingbot/strategy/__init__.py @@ -0,0 +1,7 @@ +from typing import Dict +from hummingbot.client.config.config_var import ConfigVar + + +def assign_config_default(config_map: Dict[str, ConfigVar]): + for key, value in config_map.items(): + value.value = value.default diff --git a/test/hummingbot/strategy/amm_arb/__init__.py b/test/hummingbot/strategy/amm_arb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/amm_arb/test_amm_arb.py b/test/hummingbot/strategy/amm_arb/test_amm_arb.py new file mode 100644 index 0000000..3bb6392 --- /dev/null +++ b/test/hummingbot/strategy/amm_arb/test_amm_arb.py @@ -0,0 +1,392 @@ +import asyncio +import contextlib +import unittest +from decimal import Decimal +from typing import List +from unittest.mock import patch + +from aiounittest import async_test + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeSchema +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.fixed_rate_source import FixedRateSource +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.strategy.amm_arb.amm_arb import AmmArbStrategy +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +TRADING_PAIR: str = "HBOT-USDT" +BASE_ASSET: str = TRADING_PAIR.split("-")[0] +QUOTE_ASSET: str = TRADING_PAIR.split("-")[1] + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +s_decimal_0 = Decimal(0) + + +class MockAMM(ConnectorBase): + def __init__(self, name, client_config_map: "ClientConfigAdapter"): + self._name = name + super().__init__(client_config_map) + self._buy_prices = {} + self._sell_prices = {} + self._network_transaction_fee = TokenAmount("ETH", s_decimal_0) + + @property + def name(self): + return self._name + + @property + def network_transaction_fee(self) -> TokenAmount: + return self._network_transaction_fee + + @network_transaction_fee.setter + def network_transaction_fee(self, fee: TokenAmount): + self._network_transaction_fee = fee + + @property + def connector_name(self): + return "uniswap_ethereum_mainnet" + + @property + def status_dict(self): + return {"Balance": False} + + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + if is_buy: + return self._buy_prices[trading_pair] + else: + return self._sell_prices[trading_pair] + + async def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + return await self.get_quote_price(trading_pair, is_buy, amount) + + def set_prices(self, trading_pair, is_buy, price): + if is_buy: + self._buy_prices[trading_pair] = Decimal(str(price)) + else: + self._sell_prices[trading_pair] = Decimal(str(price)) + + def set_balance(self, token, balance): + self._account_balances[token] = Decimal(str(balance)) + self._account_available_balances[token] = Decimal(str(balance)) + + def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs): + return self.place_order(True, trading_pair, amount, price) + + def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal, **kwargs): + return self.place_order(False, trading_pair, amount, price) + + def place_order(self, is_buy: bool, trading_pair: str, amount: Decimal, price: Decimal): + side = "buy" if is_buy else "sell" + order_id = f"{side}-{trading_pair}-{get_tracking_nonce()}" + event_tag = MarketEvent.BuyOrderCreated if is_buy else MarketEvent.SellOrderCreated + event_class = BuyOrderCreatedEvent if is_buy else SellOrderCreatedEvent + self.trigger_event(event_tag, + event_class( + self.current_timestamp, + OrderType.LIMIT, + trading_pair, + amount, + price, + order_id, + self.current_timestamp)) + return order_id + + def get_taker_order_type(self): + return OrderType.LIMIT + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("0.01") + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + return Decimal("0.01") + + def estimate_fee_pct(self, is_maker: bool): + return Decimal("0") + + def ready(self): + return True + + async def check_network(self) -> NetworkStatus: + return NetworkStatus.CONNECTED + + async def cancel_outdated_orders(self, _: int) -> List: + return [] + + +class AmmArbUnitTest(unittest.TestCase): + def setUp(self): + self.clock: Clock = Clock(ClockMode.REALTIME) + self.stack: contextlib.ExitStack = contextlib.ExitStack() + self.amm_1: MockAMM = MockAMM( + name="onion", + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.amm_1.set_balance(BASE_ASSET, 500) + self.amm_1.set_balance(QUOTE_ASSET, 500) + self.market_info_1 = MarketTradingPairTuple(self.amm_1, TRADING_PAIR, BASE_ASSET, QUOTE_ASSET) + + self.amm_2: MockAMM = MockAMM( + name="garlic", + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.amm_2.set_balance(BASE_ASSET, 500) + self.amm_2.set_balance(QUOTE_ASSET, 500) + self.market_info_2 = MarketTradingPairTuple(self.amm_2, TRADING_PAIR, BASE_ASSET, QUOTE_ASSET) + + # Set some default prices. + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 105) + self.amm_2.set_prices(TRADING_PAIR, False, 104) + + self.strategy = AmmArbStrategy() + self.strategy.init_params( + self.market_info_1, + self.market_info_2, + min_profitability=Decimal("0.01"), + order_amount=Decimal("1"), + market_1_slippage_buffer=Decimal("0.001"), + market_2_slippage_buffer=Decimal("0.002"), + ) + self.rate_source: FixedRateSource = FixedRateSource() + self.strategy.rate_source = self.rate_source + self.clock.add_iterator(self.amm_1) + self.clock.add_iterator(self.amm_2) + self.clock.add_iterator(self.strategy) + self.market_order_fill_logger: EventLogger = EventLogger() + self.amm_1.add_listener(MarketEvent.OrderFilled, self.market_order_fill_logger) + self.amm_2.add_listener(MarketEvent.OrderFilled, self.market_order_fill_logger) + self.rate_source.add_rate("ETH-USDT", Decimal(3000)) + + self.stack.enter_context(self.clock) + self.stack.enter_context(patch( + "hummingbot.client.config.trade_fee_schema_loader.TradeFeeSchemaLoader.configured_schema_for_exchange", + return_value=TradeFeeSchema() + )) + self.clock_task: asyncio.Task = safe_ensure_future(self.clock.run()) + + def tearDown(self) -> None: + self.stack.close() + self.clock_task.cancel() + try: + ev_loop.run_until_complete(self.clock_task) + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_arbitrage_not_profitable(self): + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 101) + self.amm_2.set_prices(TRADING_PAIR, False, 100) + await asyncio.sleep(2) + taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders + self.assertTrue(len(taker_orders) == 0) + + @async_test(loop=ev_loop) + async def test_arb_buy_amm_1_sell_amm_2(self): + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 105) + self.amm_2.set_prices(TRADING_PAIR, False, 104) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + amm_1_order = [order for market, order in placed_orders if market == self.amm_1][0] + amm_2_order = [order for market, order in placed_orders if market == self.amm_2][0] + + self.assertTrue(len(placed_orders) == 2) + # Check if the order is created as intended + self.assertEqual(Decimal("1"), amm_1_order.quantity) + self.assertEqual(True, amm_1_order.is_buy) + # The order price has to account for slippage_buffer + exp_price = self.amm_1.quantize_order_price(TRADING_PAIR, Decimal("101") * Decimal("1.001")) + self.assertEqual(exp_price, amm_1_order.price) + self.assertEqual(TRADING_PAIR, amm_1_order.trading_pair) + + self.assertEqual(Decimal("1"), amm_2_order.quantity) + self.assertEqual(False, amm_2_order.is_buy) + exp_price = self.amm_1.quantize_order_price(TRADING_PAIR, Decimal("104") * (Decimal("1") - Decimal("0.002"))) + self.assertEqual(exp_price, amm_2_order.price) + self.assertEqual(TRADING_PAIR, amm_2_order.trading_pair) + + # There are outstanding orders, the strategy is not ready to take on new arb + self.assertFalse(self.strategy.ready_for_new_arb_trades()) + await asyncio.sleep(2) + placed_orders = self.strategy.tracked_limit_orders + new_amm_1_order = [order for market, order in placed_orders if market == self.amm_1][0] + new_amm_2_order = [order for market, order in placed_orders if market == self.amm_2][0] + # Check if orders remain the same + self.assertEqual(amm_1_order.client_order_id, new_amm_1_order.client_order_id) + self.assertEqual(amm_2_order.client_order_id, new_amm_2_order.client_order_id) + + @async_test(loop=ev_loop) + async def test_arb_buy_amm_2_sell_amm_1(self): + self.amm_1.set_prices(TRADING_PAIR, True, 105) + self.amm_1.set_prices(TRADING_PAIR, False, 104) + self.amm_2.set_prices(TRADING_PAIR, True, 101) + self.amm_2.set_prices(TRADING_PAIR, False, 100) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + amm_1_order = [order for market, order in placed_orders if market == self.amm_1][0] + amm_2_order = [order for market, order in placed_orders if market == self.amm_2][0] + + self.assertTrue(len(placed_orders) == 2) + self.assertEqual(Decimal("1"), amm_1_order.quantity) + self.assertEqual(False, amm_1_order.is_buy) + exp_price = self.amm_1.quantize_order_price(TRADING_PAIR, Decimal("104") * (Decimal("1") - Decimal("0.001"))) + self.assertEqual(exp_price, amm_1_order.price) + self.assertEqual(TRADING_PAIR, amm_1_order.trading_pair) + self.assertEqual(Decimal("1"), amm_2_order.quantity) + self.assertEqual(True, amm_2_order.is_buy) + exp_price = self.amm_1.quantize_order_price(TRADING_PAIR, Decimal("101") * (Decimal("1") + Decimal("0.002"))) + self.assertEqual(exp_price, amm_2_order.price) + self.assertEqual(TRADING_PAIR, amm_2_order.trading_pair) + + @async_test(loop=ev_loop) + async def test_insufficient_balance(self): + self.amm_1.set_prices(TRADING_PAIR, True, 105) + self.amm_1.set_prices(TRADING_PAIR, False, 104) + self.amm_2.set_prices(TRADING_PAIR, True, 101) + self.amm_2.set_prices(TRADING_PAIR, False, 100) + # set base_asset to below order_amount, so not enough to sell on amm_1 + self.amm_1.set_balance(BASE_ASSET, 0.5) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + self.assertTrue(len(placed_orders) == 0) + self.amm_1.set_balance(BASE_ASSET, 10) + # set quote balance to 0 on amm_2, so not enough to buy + self.amm_2.set_balance(QUOTE_ASSET, 0) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + self.assertTrue(len(placed_orders) == 0) + + @staticmethod + def trigger_order_complete(is_buy: bool, connector: ConnectorBase, amount: Decimal, price: Decimal, + order_id: str): + # This function triggers order complete event for our mock connector, this is to simulate scenarios more + # precisely taker orders are fully filled. + event_tag = MarketEvent.BuyOrderCompleted if is_buy else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if is_buy else SellOrderCompletedEvent + connector.trigger_event(event_tag, + event_class(connector.current_timestamp, order_id, BASE_ASSET, QUOTE_ASSET, + amount, amount * price, OrderType.LIMIT)) + + @async_test(loop=ev_loop) + async def test_non_concurrent_orders_submission(self): + # On non concurrent orders submission, the second leg of the arb trade has to wait for the first leg order gets + # filled. + self.strategy = AmmArbStrategy() + self.strategy.init_params( + self.market_info_1, + self.market_info_2, + min_profitability=Decimal("0.01"), + order_amount=Decimal("1"), + concurrent_orders_submission=False + ) + self.strategy.rate_source = self.rate_source + self.clock.add_iterator(self.strategy) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + self.assertEqual(1, len(placed_orders)) + # Only one order submitted at this point, the one from amm_1 + amm_1_order = [order for market, order in placed_orders if market == self.amm_1][0] + amm_2_orders = [order for market, order in placed_orders if market == self.amm_2] + self.assertEqual(0, len(amm_2_orders)) + self.assertEqual(True, amm_1_order.is_buy) + self.trigger_order_complete(True, self.amm_1, amm_1_order.quantity, amm_1_order.price, + amm_1_order.client_order_id) + # After the first leg order completed, the second one is now submitted. + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + amm_2_orders = [order for market, order in placed_orders if market == self.amm_2] + self.assertEqual(1, len(amm_2_orders)) + amm_2_order = amm_2_orders[0] + self.assertEqual(False, amm_2_order.is_buy) + self.trigger_order_complete(False, self.amm_2, amm_2_order.quantity, amm_2_order.price, + amm_2_order.client_order_id) + await asyncio.sleep(1.5) + placed_orders = self.strategy.tracked_limit_orders + new_amm_1_order = [order for market, order in placed_orders if market == self.amm_1][0] + # Check if new order is submitted when arb opportunity still presents + self.assertNotEqual(amm_1_order.client_order_id, new_amm_1_order.client_order_id) + + @async_test(loop=ev_loop) + async def test_arb_not_profitable_from_gas_prices(self): + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 110) + self.amm_2.set_prices(TRADING_PAIR, False, 109) + self.amm_1.network_transaction_fee = TokenAmount("ETH", Decimal("0.01")) + await asyncio.sleep(2) + taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders + self.assertTrue(len(taker_orders) == 0) + + @async_test(loop=ev_loop) + async def test_arb_profitable_after_gas_prices(self): + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 105) + self.amm_2.set_prices(TRADING_PAIR, False, 104) + self.amm_1.network_transaction_fee = TokenAmount("ETH", Decimal("0.0002")) + await asyncio.sleep(2) + placed_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders + self.assertEqual(2, len(placed_orders)) + + @async_test(loop=ev_loop) + @unittest.mock.patch("hummingbot.strategy.amm_arb.amm_arb.AmmArbStrategy.apply_gateway_transaction_cancel_interval") + async def test_apply_cancel_interval(self, patched_func: unittest.mock.AsyncMock): + await asyncio.sleep(2) + patched_func.assert_awaited() + + @async_test(loop=ev_loop) + @unittest.mock.patch("hummingbot.strategy.amm_arb.amm_arb.AmmArbStrategy.is_gateway_market", return_value=True) + @unittest.mock.patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + @unittest.mock.patch.object(MockAMM, "cancel_outdated_orders") + async def test_cancel_outdated_orders( + self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + get_connector_spec_from_market_name_mock: unittest.mock.MagicMock, + _: unittest.mock.Mock + ): + get_connector_spec_from_market_name_mock.return_value = { + "connector": "uniswap", + "chain": "ethereum", + "network": "mainnet", + "trading_type": "AMM", + "chain_type": "EVM", + "wallet_address": "0xA86b66F4e7DC45a943D71a11c7DDddE341246682", # noqa: mock + "additional_spenders": [], + } + await asyncio.sleep(2) + cancel_outdated_orders_func.assert_awaited() + + @async_test(loop=ev_loop) + async def test_set_order_failed(self): + self.amm_1.set_prices(TRADING_PAIR, True, 101) + self.amm_1.set_prices(TRADING_PAIR, False, 100) + self.amm_2.set_prices(TRADING_PAIR, True, 105) + self.amm_2.set_prices(TRADING_PAIR, False, 104) + self.amm_1.network_transaction_fee = TokenAmount("ETH", Decimal("0.0002")) + await asyncio.sleep(2) + new_amm_1_order = [order for market, order in self.strategy.tracked_limit_orders if market == self.amm_1][0] + self.assertEqual(2, len(self.strategy.tracked_limit_orders)) + self.strategy.set_order_failed(new_amm_1_order.client_order_id) + self.assertEqual(2, len(self.strategy.tracked_limit_orders)) + + @async_test(loop=ev_loop) + async def test_market_ready(self): + self.amm_1.ready = False + await asyncio.sleep(10) + self.assertFalse(self.strategy._all_markets_ready) diff --git a/test/hummingbot/strategy/amm_arb/test_amm_arb_start.py b/test/hummingbot/strategy/amm_arb/test_amm_arb_start.py new file mode 100644 index 0000000..7057fbb --- /dev/null +++ b/test/hummingbot/strategy/amm_arb/test_amm_arb_start.py @@ -0,0 +1,47 @@ +from decimal import Decimal +import unittest.mock +import hummingbot.strategy.amm_arb.start as amm_arb_start +from hummingbot.strategy.amm_arb.amm_arb_config_map import amm_arb_config_map +from hummingbot.strategy.amm_arb.amm_arb import AmmArbStrategy +from test.hummingbot.strategy import assign_config_default + + +class AMMArbStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy: AmmArbStrategy = None + self.markets = {"binance": None, "balancer": None} + self.notifications = [] + self.log_errors = [] + assign_config_default(amm_arb_config_map) + amm_arb_config_map.get("strategy").value = "amm_arb" + amm_arb_config_map.get("connector_1").value = "binance" + amm_arb_config_map.get("market_1").value = "ETH-USDT" + amm_arb_config_map.get("connector_2").value = "balancer" + amm_arb_config_map.get("market_2").value = "ETH-USDT" + amm_arb_config_map.get("order_amount").value = Decimal("1") + amm_arb_config_map.get("min_profitability").value = Decimal("10") + amm_arb_config_map.get("market_1_slippage_buffer").value = Decimal("1") + amm_arb_config_map.get("market_2_slippage_buffer").value = Decimal("0") + + def _initialize_market_assets(self, market, trading_pairs): + pass + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + @unittest.mock.patch('hummingbot.strategy.amm_arb.amm_arb.AmmArbStrategy.add_markets') + def test_amm_arb_strategy_creation(self, mock): + amm_arb_start.start(self) + self.assertEqual(self.strategy._order_amount, Decimal(1)) + self.assertEqual(self.strategy._min_profitability, Decimal("10") / Decimal("100")) diff --git a/test/hummingbot/strategy/amm_arb/test_data_types.py b/test/hummingbot/strategy/amm_arb/test_data_types.py new file mode 100644 index 0000000..9cfc68d --- /dev/null +++ b/test/hummingbot/strategy/amm_arb/test_data_types.py @@ -0,0 +1,206 @@ +from decimal import Decimal +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.utils.fixed_rate_source import FixedRateSource +from hummingbot.strategy.amm_arb.data_types import ArbProposal, ArbProposalSide, TokenAmount +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class ArbProposalTests(TestCase): + + level = 0 + log_records = [] + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + self.buy_market = MagicMock() + self.sell_market = MagicMock() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def test_profit_is_zero_when_no_available_sell_to_buy_quote_rate(self): + buy_market_info = MarketTradingPairTuple(self.buy_market, "BTC-USDT", "BTC", "USDT") + sell_market_info = MarketTradingPairTuple(self.sell_market, "BTC-DAI", "BTC", "DAI") + + buy_side = ArbProposalSide( + buy_market_info, + True, + Decimal(30000), + Decimal(30000), + Decimal(10), + [] + ) + sell_side = ArbProposalSide( + sell_market_info, + False, + Decimal(32000), + Decimal(32000), + Decimal(10), + [] + ) + + proposal = ArbProposal(buy_side, sell_side) + proposal.logger().setLevel(1) + proposal.logger().addHandler(self) + + self.assertEqual(proposal.profit_pct(), Decimal(0)) + self.assertTrue(self._is_logged('WARNING', + ("The arbitrage proposal profitability could not be calculated due to" + " a missing rate (BTC-BTC=1, DAI-USDT=None)"))) + + def test_profit_without_fees_for_same_trading_pair(self): + buy_market_info = MarketTradingPairTuple(self.buy_market, "BTC-USDT", "BTC", "USDT") + sell_market_info = MarketTradingPairTuple(self.sell_market, "BTC-USDT", "BTC", "USDT") + + buy_side = ArbProposalSide( + buy_market_info, + True, + Decimal(30000), + Decimal(30000), + Decimal(10), + [] + ) + sell_side = ArbProposalSide( + sell_market_info, + False, + Decimal(32000), + Decimal(32000), + Decimal(10), + [] + ) + + proposal = ArbProposal(buy_side, sell_side) + + self.assertEqual(proposal.profit_pct(), Decimal(2000) / buy_side.quote_price) + + def test_profit_without_fees_for_different_quotes_trading_pairs(self): + buy_market_info = MarketTradingPairTuple(self.buy_market, "BTC-USDT", "BTC", "USDT") + sell_market_info = MarketTradingPairTuple(self.sell_market, "BTC-ETH", "BTC", "ETH") + + buy_side = ArbProposalSide( + buy_market_info, + True, + Decimal(30000), + Decimal(30000), + Decimal(10), + [] + ) + sell_side = ArbProposalSide( + sell_market_info, + False, + Decimal(10), + Decimal(10), + Decimal(10), + [] + ) + + proposal = ArbProposal(buy_side, sell_side) + + rate_source = FixedRateSource() + rate_source.add_rate("BTC-USDT", Decimal(30000)) + rate_source.add_rate("BTC-ETH", Decimal(10)) + rate_source.add_rate("ETH-USDT", Decimal(3000)) + + expected_sell_result = sell_side.amount * sell_side.quote_price + sell_quote_to_buy_quote_rate = rate_source.get_pair_rate("ETH-USDT") + adjusted_sell_result = expected_sell_result * sell_quote_to_buy_quote_rate + + expected_buy_result = buy_side.amount * buy_side.quote_price + + expected_profit_pct = (adjusted_sell_result - expected_buy_result) / expected_buy_result + + profit = proposal.profit_pct(account_for_fee=False, + rate_source=rate_source) + + self.assertEqual(profit, expected_profit_pct) + + def test_profit_without_fees_for_different_base_trading_pairs_and_different_amount_on_sides(self): + """ + If the amount is different on both sides and the base tokens are different, then + the profit calculation should not apply the conversion rate for the base tokens because + the different orders of magnitude for the tokens might have been considered when configuring + the arbitrage sides. + """ + buy_market_info = MarketTradingPairTuple(self.buy_market, "BTC-USDT", "BTC", "USDT") + sell_market_info = MarketTradingPairTuple(self.sell_market, "XRP-USDT", "XRP", "USDT") + + buy_side = ArbProposalSide( + buy_market_info, + True, + Decimal(30000), + Decimal(30000), + Decimal(10), + [] + ) + sell_side = ArbProposalSide( + sell_market_info, + False, + Decimal(1.1), + Decimal(1.1), + Decimal(27000), + [] + ) + + proposal = ArbProposal(buy_side, sell_side) + + rate_source = FixedRateSource() + rate_source.add_rate("BTC-USDT", Decimal(30000)) + rate_source.add_rate("BTC-XRP", Decimal(27000)) + + expected_sell_result = sell_side.amount * sell_side.quote_price + + expected_buy_result = buy_side.amount * buy_side.quote_price + expected_profit_pct = (expected_sell_result - expected_buy_result) / expected_buy_result + + profit = proposal.profit_pct(account_for_fee=False, + rate_source=rate_source) + + self.assertEqual(profit, expected_profit_pct) + + @patch("hummingbot.client.config.trade_fee_schema_loader.TradeFeeSchemaLoader.configured_schema_for_exchange", + return_value=TradeFeeSchema()) + def test_profit_with_network_fees(self, _): + buy_market_info = MarketTradingPairTuple(self.buy_market, "WETH-DAI", "WETH", "DAI") + sell_market_info = MarketTradingPairTuple(self.sell_market, "ETH-USDT", "ETH", "USDT") + + buy_side = ArbProposalSide( + buy_market_info, + True, + Decimal("3300"), + Decimal("3300"), + Decimal("1"), + [TokenAmount("ETH", Decimal("0.003"))] + ) + sell_side = ArbProposalSide( + sell_market_info, + False, + Decimal("3350"), + Decimal("3350"), + Decimal("1"), + [TokenAmount("ETH", Decimal("0.001"))] + ) + + proposal = ArbProposal(buy_side, sell_side) + + rate_source = FixedRateSource() + rate_source.add_rate("WETH-DAI", Decimal(3300)) + rate_source.add_rate("ETH-USDT", Decimal(3350)) + rate_source.add_rate("WETH-ETH", Decimal(1)) + rate_source.add_rate("USDT-DAI", Decimal(1)) + + expected_sell_result: Decimal = (sell_side.amount * sell_side.quote_price - + sell_side.extra_flat_fees[0].amount * sell_side.quote_price) + expected_buy_result: Decimal = (buy_side.amount * buy_side.quote_price + + (buy_side.extra_flat_fees[0].amount * buy_side.quote_price)) + expected_profit_pct: Decimal = (expected_sell_result - expected_buy_result) / expected_buy_result + calculated_profit: Decimal = proposal.profit_pct(account_for_fee=True, rate_source=rate_source) + + self.assertEqual(expected_profit_pct, calculated_profit) diff --git a/test/hummingbot/strategy/amm_arb/test_utils.py b/test/hummingbot/strategy/amm_arb/test_utils.py new file mode 100644 index 0000000..5712399 --- /dev/null +++ b/test/hummingbot/strategy/amm_arb/test_utils.py @@ -0,0 +1,63 @@ +import asyncio +import unittest +from decimal import Decimal + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.strategy.amm_arb import utils +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +trading_pair = "HBOT-USDT" +base = trading_pair.split("-")[0] +quote = trading_pair.split("-")[1] + + +class MockConnector1(ConnectorBase): + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + if is_buy: + return Decimal("105") + else: + return Decimal("104") + + def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + return self.get_quote_price(trading_pair, is_buy, amount) + + +class MockConnector2(ConnectorBase): + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + if is_buy: + return Decimal("103") + else: + return Decimal("100") + + def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + return self.get_quote_price(trading_pair, is_buy, amount) + + +class AmmArbUtilsUnitTest(unittest.TestCase): + + def test_create_arb_proposals(self): + asyncio.get_event_loop().run_until_complete(self._test_create_arb_proposals()) + + async def _test_create_arb_proposals(self): + market_info1 = MarketTradingPairTuple( + MockConnector1(client_config_map=ClientConfigAdapter(ClientConfigMap())), + trading_pair, + base, + quote) + market_info2 = MarketTradingPairTuple( + MockConnector2(client_config_map=ClientConfigAdapter(ClientConfigMap())), + trading_pair, + base, + quote) + arb_proposals = await utils.create_arb_proposals(market_info1, market_info2, [], [], Decimal("1")) + # there are 2 proposal combination possible - (buy_1, sell_2) and (buy_2, sell_1) + self.assertEqual(2, len(arb_proposals)) + # Each proposal has a buy and a sell proposal sides + self.assertNotEqual(arb_proposals[0].first_side.is_buy, arb_proposals[0].second_side.is_buy) + self.assertNotEqual(arb_proposals[1].first_side.is_buy, arb_proposals[1].second_side.is_buy) + buy_1_sell_2_profit_pct = (Decimal("100") - Decimal("105")) / Decimal("105") + self.assertEqual(buy_1_sell_2_profit_pct, arb_proposals[0].profit_pct()) + buy_2_sell_1_profit_pct = (Decimal("104") - Decimal("103")) / Decimal("103") + self.assertEqual(buy_2_sell_1_profit_pct, arb_proposals[1].profit_pct()) diff --git a/test/hummingbot/strategy/avellaneda_market_making/__init__.py b/test/hummingbot/strategy/avellaneda_market_making/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making.py b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making.py new file mode 100644 index 0000000..f9c2961 --- /dev/null +++ b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making.py @@ -0,0 +1,1630 @@ +import datetime +import math +import unittest +from copy import deepcopy +from decimal import Decimal +from typing import Dict, List, Tuple + +import numpy as np +import pandas as pd + +from hummingbot.client import settings +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TradeFeeSchema +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + OrderBookTradeEvent, + OrderFilledEvent, + SellOrderCompletedEvent, +) +from hummingbot.strategy.__utils__.trailing_indicators.instant_volatility import InstantVolatilityIndicator +from hummingbot.strategy.__utils__.trailing_indicators.trading_intensity import TradingIntensityIndicator +from hummingbot.strategy.avellaneda_market_making import AvellanedaMarketMakingStrategy +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, + DailyBetweenTimesModel, + InfiniteModel, + MultiOrderLevelModel, + TrackHangingOrdersModel, +) +from hummingbot.strategy.data_types import PriceSize, Proposal +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate + +s_decimal_zero = Decimal(0) +s_decimal_one = Decimal(1) +s_decimal_nan = Decimal("NaN") +s_decimal_neg_one = Decimal(-1) + + +class AvellanedaMarketMakingUnitTests(unittest.TestCase): + + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.trading_pair: str = "COINALPHA-HBOT" + cls.base_asset, cls.quote_asset = cls.trading_pair.split("-") + cls.initial_mid_price: int = 100 + + cls.clock_tick_size: int = 1 + + # Testing Constants + # Volatility as a percentage, not absolute as in instant volatility indicator + cls.low_vol: Decimal = Decimal("0.05") + cls.expected_low_vol: Decimal = Decimal("0.0501863845537047") + cls.high_vol: Decimal = Decimal("5") + cls.expected_high_vol: Decimal = Decimal("5.018622242594793") + + cls.low_liq_spread: Decimal = Decimal("10") + cls.low_liq_amount: Decimal = Decimal("1") + cls.high_liq_spread: Decimal = Decimal("0.1") + cls.high_liq_amount: Decimal = Decimal("100") + + # Strategy Initial Configuration Parameters + cls.order_amount: Decimal = Decimal("10") + cls.inventory_target_base_pct: Decimal = Decimal("50") # 50% + cls.min_spread: Decimal = Decimal("0.0") # Default strategy value + cls.risk_factor_finite: Decimal = Decimal("0.8") + cls.risk_factor_infinite: Decimal = Decimal("1") + + cls.volatility_indicator_low_vol = None + cls.volatility_indicator_high_vol = None + + cls.trading_intensity_indicator_low_liq = None + cls.trading_intensity_indicator_high_liq = None + + def setUp(self): + super().setUp() + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.25"), taker_percent_fee_decimal=Decimal("0.25") + ) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=trade_fee_schema) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.trading_pair, *self.trading_pair.split("-") + ) + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("COINALPHA", 1) + self.market.set_balance("HBOT", 500) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair.split("-")[0], 6, 6, 6, 6 + ) + ) + self._original_paper_trade_exchanges = settings.PAPER_TRADE_EXCHANGES + settings.PAPER_TRADE_EXCHANGES.append("mock_paper_exchange") + + self.price_delegate = OrderBookAssetPriceDelegate(self.market_info.market, self.trading_pair) + + config_settings = self.get_default_map() + self.config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**config_settings)) + + self.strategy: AvellanedaMarketMakingStrategy = AvellanedaMarketMakingStrategy() + self.strategy.init_params( + config_map=self.config_map, + market_info=self.market_info, + ) + + self.avg_vol_indicator: InstantVolatilityIndicator = InstantVolatilityIndicator(sampling_length=100, + processing_length=1) + + self.trading_intensity_indicator: TradingIntensityIndicator = TradingIntensityIndicator( + order_book=self.market_info.order_book, + price_delegate=self.price_delegate, + sampling_length=20) + + self.strategy.avg_vol = self.avg_vol_indicator + + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + + self.clock.add_iterator(self.market) + self.clock.add_iterator(self.strategy) + self.strategy.start(self.clock, self.start_timestamp) + + self.strategy.trading_intensity = self.trading_intensity_indicator + + self.clock.backtest_til(self.start_timestamp) + + def tearDown(self) -> None: + self.strategy.stop(self.clock) + if self._original_paper_trade_exchanges is not None: + settings.PAPER_TRADE_EXCHANGES = self._original_paper_trade_exchanges + super().tearDown() + + def get_default_map(self) -> Dict[str, str]: + config_settings = { + "exchange": self.market.name, + "market": self.trading_pair, + "execution_timeframe_mode": "infinite", + "order_amount": self.order_amount, + "order_optimization_enabled": "yes", + "min_spread": self.min_spread, + "risk_factor": self.risk_factor_finite, + "order_refresh_time": "30", + "inventory_target_base_pct": self.inventory_target_base_pct, + "add_transaction_costs": "yes", + } + return config_settings + + def simulate_low_volatility(self, strategy: AvellanedaMarketMakingStrategy): + if self.volatility_indicator_low_vol is None: + N_SAMPLES = 400 + INITIAL_RANDOM_SEED = 3141592653 + original_price = 100 + volatility = AvellanedaMarketMakingUnitTests.low_vol / Decimal("100") # Assuming 0.5% volatility + np.random.seed(INITIAL_RANDOM_SEED) # Using this hardcoded random seed we guarantee random samples generated are always the same + samples = np.random.normal(original_price, volatility * original_price, N_SAMPLES) + + # This replicates the same indicator Avellaneda uses if volatility_buffer_samples = 30 + volatility_indicator = strategy.avg_vol + + for sample in samples: + volatility_indicator.add_sample(sample) + + self.volatility_indicator_low_vol = volatility_indicator + + # Note: Current Value of volatility is ~0.5% + strategy.avg_vol = self.volatility_indicator_low_vol + + # Simulates change in mid price to reflect last sample added + strategy.market_info.market.set_balanced_order_book(trading_pair=strategy.trading_pair, + mid_price=samples[-1], + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + def simulate_high_volatility(self, strategy: AvellanedaMarketMakingStrategy): + if self.volatility_indicator_high_vol is None: + N_SAMPLES = 400 + INITIAL_RANDOM_SEED = 3141592653 + original_price = 100 + volatility = AvellanedaMarketMakingUnitTests.high_vol / Decimal("100") # Assuming 10% volatility + np.random.seed(INITIAL_RANDOM_SEED) # Using this hardcoded random seed we guarantee random samples generated are always the same + samples = np.random.normal(original_price, volatility * original_price, N_SAMPLES) + + # This replicates the same indicator Avellaneda uses if volatility_buffer_samples = 30 + volatility_indicator = strategy.avg_vol + + for sample in samples: + volatility_indicator.add_sample(sample) + + self.volatility_indicator_high_vol = volatility_indicator + + # Note: Current Value of volatility is ~5% + strategy.avg_vol = self.volatility_indicator_high_vol + + # Simulates change in mid price to reflect last sample added + strategy.market_info.market.set_balanced_order_book(trading_pair=strategy.trading_pair, + mid_price=samples[-1], + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + def simulate_low_liquidity(self, strategy: AvellanedaMarketMakingStrategy): + if self.trading_intensity_indicator_low_liq is None: + N_SAMPLES = 400 + INITIAL_RANDOM_SEED = 3141592653 + volatility = self.high_vol + original_price_mid = 100 + original_spread = AvellanedaMarketMakingUnitTests.low_liq_spread + volatility = volatility / Decimal("100") + original_amount = AvellanedaMarketMakingUnitTests.low_liq_amount + + spread_stdev = original_spread * Decimal("0.01") + amount_stdev = original_amount * Decimal("0.01") + + np.random.seed(INITIAL_RANDOM_SEED) # Using this hardcoded random seed we guarantee random samples generated are always the same + + # Generate orderbooks for all ticks + bids_df, asks_df = AvellanedaMarketMakingUnitTests.make_order_books(original_price_mid, original_spread, original_amount, volatility, spread_stdev, amount_stdev, N_SAMPLES) + trades = AvellanedaMarketMakingUnitTests.make_trades(bids_df, asks_df) + + # This replicates the same indicator Avellaneda uses for trading intensity estimation + trading_intensity_indicator = strategy.trading_intensity + + timestamp = self.start_timestamp + for bid_df, ask_df, trades_tick in zip(bids_df, asks_df, trades): + bid = bid_df["price"].iloc[0] + ask = ask_df["price"].iloc[0] + mid = (bid + ask) / 2 + for trade in trades_tick: + trading_intensity_indicator.register_trade(trade) + trading_intensity_indicator.calculate(timestamp) + trading_intensity_indicator.last_quotes = [{"timestamp": timestamp, "price": mid}] + trading_intensity_indicator.last_quotes + timestamp += 1 + + self.trading_intensity_indicator_low_liq = trading_intensity_indicator + + # Update the trading intensity indicator + strategy.trading_intensity = self.trading_intensity_indicator_low_liq + + def simulate_high_liquidity(self, strategy: AvellanedaMarketMakingStrategy): + if self.trading_intensity_indicator_high_liq is None: + N_SAMPLES = 400 + INITIAL_RANDOM_SEED = 3141592653 + volatility = self.low_vol + original_price_mid = 100 + original_spread = AvellanedaMarketMakingUnitTests.high_liq_spread + volatility = volatility / Decimal("100") + original_amount = AvellanedaMarketMakingUnitTests.high_liq_amount + + spread_stdev = original_spread * Decimal("0.01") + amount_stdev = original_amount * Decimal("0.01") + + np.random.seed(INITIAL_RANDOM_SEED) # Using this hardcoded random seed we guarantee random samples generated are always the same + + # Generate orderbooks for all ticks + bids_df, asks_df = AvellanedaMarketMakingUnitTests.make_order_books(original_price_mid, original_spread, original_amount, volatility, spread_stdev, amount_stdev, N_SAMPLES) + trades = AvellanedaMarketMakingUnitTests.make_trades(bids_df, asks_df) + + # This replicates the same indicator Avellaneda uses for trading intensity estimation + trading_intensity_indicator = strategy.trading_intensity + + timestamp = self.start_timestamp + for bid_df, ask_df, trades_tick in zip(bids_df, asks_df, trades): + bid = bid_df["price"].iloc[0] + ask = ask_df["price"].iloc[0] + mid = (bid + ask) / 2 + for trade in trades_tick: + trading_intensity_indicator.register_trade(trade) + trading_intensity_indicator.calculate(timestamp) + trading_intensity_indicator.last_quotes = [{"timestamp": timestamp, "price": mid}] + trading_intensity_indicator.last_quotes + timestamp += 1 + + self.trading_intensity_indicator_high_liq = trading_intensity_indicator + + # Update the trading intensity indicator + strategy.trading_intensity = self.trading_intensity_indicator_high_liq + + @staticmethod + def make_order_books(original_price_mid, original_spread, original_amount, volatility, spread_stdev, amount_stdev, samples): + # 0.1% quantization of prices in the orderbook + PRICE_STEP_FRACTION = 0.01 + + # Generate BBO quotes + samples_mid = np.random.normal(original_price_mid, volatility * original_price_mid, samples) + samples_spread = np.random.normal(original_spread, spread_stdev, samples) + + samples_price_bid = np.subtract(samples_mid, np.divide(samples_spread, 2)) + samples_price_ask = np.add(samples_mid, np.divide(samples_spread, 2)) + + samples_amount_bid = np.random.normal(original_amount, amount_stdev, samples) + samples_amount_ask = np.random.normal(original_amount, amount_stdev, samples) + + # A full orderbook is not necessary, only up to the BBO max deviation + price_depth_max = max(max(samples_price_bid) - min(samples_price_bid), max(samples_price_ask) - min(samples_price_ask)) + + bid_dfs = [] + ask_dfs = [] + + # Generate an orderbook for every tick + for price_bid, amount_bid, price_ask, amount_ask in zip(samples_price_bid, samples_amount_bid, samples_price_ask, samples_amount_ask): + bid_df, ask_df = AvellanedaMarketMakingUnitTests.make_order_book(price_bid, amount_bid, price_ask, amount_ask, price_depth_max, original_price_mid * PRICE_STEP_FRACTION, amount_stdev) + bid_dfs += [bid_df] + ask_dfs += [ask_df] + + return bid_dfs, ask_dfs + + @staticmethod + def make_order_book(price_bid, amount_bid, price_ask, amount_ask, price_depth, price_step, amount_stdev, ): + + prices_bid = np.linspace(price_bid, price_bid - price_depth, math.ceil(price_depth / price_step)) + amounts_bid = np.random.normal(amount_bid, amount_stdev, len(prices_bid)) + amounts_bid[0] = amount_bid + + prices_ask = np.linspace(price_ask, price_ask + price_depth, math.ceil(price_depth / price_step)) + amounts_ask = np.random.normal(amount_ask, amount_stdev, len(prices_ask)) + amounts_ask[0] = amount_ask + + data_bid = {'price': prices_bid, 'amount': amounts_bid} + bid_df = pd.DataFrame(data=data_bid) + + data_ask = {'price': prices_ask, 'amount': amounts_ask} + ask_df = pd.DataFrame(data=data_ask) + + return bid_df, ask_df + + @staticmethod + def make_trades(bids_df, asks_df): + # Estimate market orders that happened + # Assume every movement in the BBO is caused by a market order and its size is the volume differential + + bid_df_prev = None + ask_df_prev = None + bid_prev = None + ask_prev = None + price_prev = None + + trades = [] + + start = pd.Timestamp("2019-01-01", tz="UTC") + start_timestamp = start.timestamp() + timestamp = start_timestamp + + for bid_df, ask_df in zip(bids_df, asks_df): + + trades += [[]] + + bid = bid_df["price"].iloc[0] + ask = ask_df["price"].iloc[0] + + if bid_prev is not None and ask_prev is not None and price_prev is not None: + # Higher bids were filled - someone matched them - a determined seller + # Equal bids - if amount lower - partially filled + for index, row in bid_df_prev[bid_df_prev['price'] >= bid].iterrows(): + if row['price'] == bid: + if bid_df["amount"].iloc[0] < row['amount']: + amount = row['amount'] - bid_df["amount"].iloc[0] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.SELL + ) + trades[-1] += [new_trade] + else: + amount = row['amount'] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.SELL + ) + trades[-1] += [new_trade] + + # Lower asks were filled - someone matched them - a determined buyer + # Equal asks - if amount lower - partially filled + for index, row in ask_df_prev[ask_df_prev['price'] <= ask].iterrows(): + if row['price'] == ask: + if ask_df["amount"].iloc[0] < row['amount']: + amount = row['amount'] - ask_df["amount"].iloc[0] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.BUY + ) + trades[-1] += [new_trade] + else: + amount = row['amount'] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.BUY + ) + trades[-1] += [new_trade] + + # Store previous values + bid_df_prev = bid_df + ask_df_prev = ask_df + bid_prev = bid_df["price"].iloc[0] + ask_prev = ask_df["price"].iloc[0] + price_prev = (bid_prev + ask_prev) / 2 + + timestamp += 1 + + return trades + + @staticmethod + def simulate_place_limit_order(strategy: AvellanedaMarketMakingStrategy, market_info: MarketTradingPairTuple, order: LimitOrder): + strategy.set_timers() + if order.is_buy: + return strategy.buy_with_specific_market(market_trading_pair_tuple=market_info, + order_type=OrderType.LIMIT, + price=order.price, + amount=order.quantity + ) + else: + return strategy.sell_with_specific_market(market_trading_pair_tuple=market_info, + order_type=OrderType.LIMIT, + price=order.price, + amount=order.quantity) + + @staticmethod + def simulate_cancelling_all_active_orders(strategy: AvellanedaMarketMakingStrategy): + strategy.cancel_active_orders(None) + + @staticmethod + def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal("0")) + )) + market.trigger_event(MarketEvent.BuyOrderCompleted, BuyOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal("0")) + )) + market.trigger_event(MarketEvent.SellOrderCompleted, SellOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + + def test_all_markets_ready(self): + self.assertTrue(self.strategy.all_markets_ready()) + + def test_market_info(self): + self.assertEqual(self.market_info, self.strategy.market_info) + + def test_base_asset(self): + self.assertEqual(self.trading_pair.split("-")[0], self.strategy.base_asset) + + def test_quote_asset(self): + self.assertEqual(self.trading_pair.split("-")[1], self.strategy.quote_asset) + + def test_trading_pair(self): + self.assertEqual(self.trading_pair, self.strategy.trading_pair) + + def test_get_price(self): + # Avellaneda Strategy get_price is simply a wrapper for MarketTradingPairTuple.get_mid_price() + self.assertEqual(self.market_info.get_mid_price(), self.strategy.get_price()) + + def test_get_mid_price(self): + self.assertEqual(self.market_info.get_mid_price(), self.strategy.get_mid_price()) + + def test_market_info_to_active_orders(self): + order_tracker = self.strategy.order_tracker + + self.assertEqual(order_tracker.market_pair_to_active_orders, self.strategy.market_info_to_active_orders) + + # Simulate order being placed + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101.0"), + quantity=Decimal("10")) + + self.simulate_place_limit_order(self.strategy, self.market_info, limit_order) + + self.assertEqual(1, len(self.strategy.market_info_to_active_orders)) + self.assertEqual(order_tracker.market_pair_to_active_orders, self.strategy.market_info_to_active_orders) + + def test_active_orders(self): + self.assertEqual(0, len(self.strategy.active_orders)) + + # Simulate order being placed + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101.0"), + quantity=Decimal("10")) + + self.simulate_place_limit_order(self.strategy, self.market_info, limit_order) + + self.assertEqual(1, len(self.strategy.active_orders)) + # Note: The order_id created by the BacktestMarket class is as follows [buy/sell]://{trading_pair}/{random_uuid} + self.assertTrue(f"buy://{self.trading_pair}" in self.strategy.active_orders[0].client_order_id) + self.assertEqual(limit_order.price, self.strategy.active_orders[0].price) + self.assertEqual(limit_order.quantity, self.strategy.active_orders[0].quantity) + + def test_active_buys(self): + self.assertEqual(0, len(self.strategy.active_buys)) + + # Simulate order being placed + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101.0"), + quantity=Decimal("10")) + + self.simulate_place_limit_order(self.strategy, self.market_info, limit_order) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertTrue(f"buy://{self.trading_pair}" in self.strategy.active_buys[0].client_order_id) + self.assertEqual(limit_order.price, self.strategy.active_buys[0].price) + self.assertEqual(limit_order.quantity, self.strategy.active_buys[0].quantity) + + def test_active_sells(self): + self.assertEqual(0, len(self.strategy.active_sells)) + + # Simulate order being placed + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101.0"), + quantity=Decimal("0.5")) + + self.simulate_place_limit_order(self.strategy, self.market_info, limit_order) + + self.assertEqual(1, len(self.strategy.active_sells)) + self.assertTrue(f"sell://{self.trading_pair}" in self.strategy.active_sells[0].client_order_id) + self.assertEqual(limit_order.price, self.strategy.active_sells[0].price) + self.assertEqual(limit_order.quantity, self.strategy.active_sells[0].quantity) + + def test_logging_options(self): + self.assertEqual(AvellanedaMarketMakingStrategy.OPTION_LOG_ALL, self.strategy.logging_options) + + # Test setter method + self.strategy.logging_options = AvellanedaMarketMakingStrategy.OPTION_LOG_CREATE_ORDER + + self.assertEqual(AvellanedaMarketMakingStrategy.OPTION_LOG_CREATE_ORDER, self.strategy.logging_options) + + def test_execute_orders_proposal(self): + self.assertEqual(0, len(self.strategy.active_orders)) + + buys: List[PriceSize] = [PriceSize(price=Decimal("99"), size=Decimal("1"))] + sells: List[PriceSize] = [PriceSize(price=Decimal("101"), size=Decimal("1"))] + proposal: Proposal = Proposal(buys, sells) + + self.strategy.execute_orders_proposal(proposal) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.assertTrue(f"buy://{self.trading_pair}" in self.strategy.active_buys[0].client_order_id) + self.assertEqual(buys[0].price, self.strategy.active_buys[0].price) + self.assertEqual(buys[0].size, self.strategy.active_buys[0].quantity) + + self.assertTrue(f"sell://{self.trading_pair}" in self.strategy.active_sells[0].client_order_id) + self.assertEqual(sells[0].price, self.strategy.active_sells[0].price) + self.assertEqual(sells[0].size, self.strategy.active_sells[0].quantity) + + def test_cancel_order(self): + self.assertEqual(0, len(self.strategy.active_orders)) + + buys: List[PriceSize] = [PriceSize(price=Decimal("99"), size=Decimal("1"))] + sells: List[PriceSize] = [PriceSize(price=Decimal("101"), size=Decimal("1"))] + proposal: Proposal = Proposal(buys, sells) + + self.strategy.execute_orders_proposal(proposal) + + self.assertEqual(2, len(self.strategy.active_orders)) + + for order in self.strategy.active_orders: + self.strategy.cancel_order(order.client_order_id) + + self.assertEqual(0, len(self.strategy.active_orders)) + + def test_is_algorithm_ready(self): + self.assertFalse(self.strategy.is_algorithm_ready()) + + self.simulate_high_volatility(self.strategy) + self.simulate_low_liquidity(self.strategy) + + self.assertTrue(self.strategy.is_algorithm_ready()) + + def test_get_spread(self): + order_book: OrderBook = self.market.get_order_book(self.trading_pair) + expected_spread = order_book.get_price(True) - order_book.get_price(False) + + self.assertEqual(expected_spread, self.strategy.get_spread()) + + def test_get_volatility(self): + # Initial Volatility + self.assertTrue(math.isnan(self.strategy.get_volatility())) + + # Simulate volatility update + self.simulate_low_volatility(self.strategy) + + # Check updated volatility + self.assertAlmostEqual(self.expected_low_vol, self.strategy.get_volatility(), 1) + + def test_calculate_target_inventory(self): + # Calculate expected quantize order amount + current_price = self.market_info.get_mid_price() + + base_asset_amount = self.market.get_balance(self.trading_pair.split("-")[0]) + quote_asset_amount = self.market.get_balance(self.trading_pair.split("-")[1]) + base_value = base_asset_amount * current_price + inventory_value = base_value + quote_asset_amount + target_inventory_value = Decimal((inventory_value * self.inventory_target_base_pct / Decimal('100')) / current_price) + + expected_quantize_order_amount = self.market.quantize_order_amount(self.trading_pair, target_inventory_value) + + self.assertEqual(expected_quantize_order_amount, self.strategy.calculate_target_inventory()) + + def test_liquidity_estimation(self): + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + alpha, kappa = self.strategy.trading_intensity.current_value + + self.assertAlmostEqual(118.53441791741469, alpha, 3) + self.assertAlmostEqual(3.3607256761562003, kappa, 3) + + # Simulate high liquidity + self.simulate_low_liquidity(self.strategy) + + alpha, kappa = self.strategy.trading_intensity.current_value + + self.assertAlmostEqual(118.45210662343376, alpha, 3) + self.assertAlmostEqual(3.3468695409821243, kappa, 3) + + def test_calculate_reservation_price_and_optimal_spread_timeframe_constrained(self): + # Init params + start_time = ( + datetime.datetime.fromtimestamp(self.strategy.current_timestamp) - datetime.timedelta(minutes=30) + ).time().strftime("%H:%M:%S") + end_time = ( + datetime.datetime.fromtimestamp(self.strategy.current_timestamp) + datetime.timedelta(minutes=30) + ).time().strftime("%H:%M:%S") + self.config_map.execution_timeframe_mode = DailyBetweenTimesModel(start_time=start_time, end_time=end_time) + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Execute measurements and calculations + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Check reservation_price, optimal_ask and optimal_bid + self.assertAlmostEqual(Decimal("100.035"), self.strategy.reservation_price, 2) + self.assertAlmostEqual(Decimal("0.592"), self.strategy.optimal_spread, 2) + self.assertAlmostEqual(Decimal("100.331"), self.strategy.optimal_ask, 2) + self.assertAlmostEqual(Decimal("99.739"), self.strategy.optimal_bid, 2) + + def test_calculate_reservation_price_and_optimal_spread_timeframe_infinite(self): + # Init params + self.config_map.execution_timeframe_mode = InfiniteModel() + self.config_map.risk_factor = self.risk_factor_infinite + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Execute measurements and calculations + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Check reservation_price, optimal_ask and optimal_bid + self.assertAlmostEqual(Decimal("100.040"), self.strategy.reservation_price, 2) + self.assertAlmostEqual(Decimal("0.594"), self.strategy.optimal_spread, 2) + self.assertAlmostEqual(Decimal("100.337"), self.strategy.optimal_ask, 2) + self.assertAlmostEqual(Decimal("99.743"), self.strategy.optimal_bid, 2) + + def test_create_proposal_based_on_order_override(self): + # Initial check for empty order_override + expected_output: Tuple[List, List] = ([], []) + self.assertEqual(expected_output, self.strategy.create_proposal_based_on_order_override()) + + order_override = { + "order_1": ["sell", 2.5, 100], + "order_2": ["buy", 0.5, 100] + } + + # Re-configure strategy with order_ride configurations + self.config_map.order_override = order_override + + expected_proposal = (list(), list()) + for order in order_override.values(): + list_to_append = expected_proposal[0] if order[0] == "buy" else expected_proposal[1] + if "buy" == order[0]: + price = self.strategy.get_price() * (Decimal("1") - Decimal(str(order[1])) / Decimal("100")) + else: + price = self.strategy.get_price() * (Decimal("1") + Decimal(str(order[1])) / Decimal("100")) + + price = self.market.quantize_order_price(self.trading_pair, price) + size = self.market.quantize_order_amount(self.trading_pair, Decimal(str(order[2]))) + + list_to_append.append(PriceSize(price, size)) + + self.assertEqual(str(expected_proposal), str(self.strategy.create_proposal_based_on_order_override())) + + def test_get_level_spreads(self): + order_levels_mode = MultiOrderLevelModel() + order_levels_mode.order_levels = 4 + order_levels_mode.level_distances = 1 + + config_settings = { + "exchange": self.market.name, + "market": self.trading_pair, + "execution_timeframe_mode": "infinite", + "order_amount": self.order_amount, + "order_optimization_enabled": "yes", + "order_levels_mode": order_levels_mode, + "min_spread": self.min_spread, + "risk_factor": self.risk_factor_infinite, + "order_refresh_time": "60", + "inventory_target_base_pct": self.inventory_target_base_pct, + } + config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**config_settings)) + + # Re-initialize strategy with order_level configurations + self.strategy = AvellanedaMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_info=self.market_info, + ) + + # Create a new clock to start the strategy from scratch + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.market) + self.clock.add_iterator(self.strategy) + + self.strategy.avg_vol = self.avg_vol_indicator + self.clock.add_iterator(self.strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + # Simulate low volatility. + # Note: bid/ask_level_spreads Requires volatility, optimal_bid, optimal_ask to be defined + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Execute measurements and calculations + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + expected_bid_spreads = [Decimal('0E-28'), Decimal('0.03471008344015021195989165942'), Decimal('0.07680749440342730936221062482'), Decimal('0.1152112416051409640433159372')] + expected_ask_spreads = [Decimal('0E-28'), Decimal('0.03471008344015021195989165942'), Decimal('0.07680749440342730936221062482'), Decimal('0.1152112416051409640433159372')] + + bid_level_spreads, ask_level_spreads = self.strategy._get_level_spreads() + + for i, spread in enumerate(bid_level_spreads): + self.assertAlmostEqual(expected_bid_spreads[i], spread, 1) + + for i, spread in enumerate(ask_level_spreads): + self.assertAlmostEqual(expected_ask_spreads[i], spread, 1) + + # Simulate high volatility. TODO: Find a better max_spread parameter to better illustrate bid levels + # Note: bid/ask_level_spreads Requires volatility, optimal_bid, optimal_ask to be defined + self.simulate_high_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_low_liquidity(self.strategy) + + # Execute measurements and calculations + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + expected_bid_spreads = [Decimal('0E-28'), Decimal('0.03909242377646942266258131591'), Decimal('0.07818484755293884532516263182'), Decimal('0.1172772713294082679877439477')] + expected_ask_spreads = [Decimal('0E-28'), Decimal('0.03909242377646942266258131591'), Decimal('0.07818484755293884532516263182'), Decimal('0.1172772713294082679877439477')] + + bid_level_spreads, ask_level_spreads = self.strategy._get_level_spreads() + + for i, spread in enumerate(bid_level_spreads): + self.assertAlmostEqual(expected_bid_spreads[i], spread, 1) + + for i, spread in enumerate(ask_level_spreads): + self.assertAlmostEqual(expected_ask_spreads[i], spread, 1) + + def test_create_proposal_based_on_order_levels(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Test(1) Check order_levels default = 0 + empty_proposal = ([], []) + self.assertEqual(empty_proposal, self.strategy.create_proposal_based_on_order_levels()) + + # Re-initialize strategy with order_level configurations + order_levels_mode = MultiOrderLevelModel() + order_levels_mode.order_levels = 2 + order_levels_mode.level_distances = 1 + self.config_map.order_levels_mode = order_levels_mode + + # Calculate order levels + bid_level_spreads, ask_level_spreads = self.strategy._get_level_spreads() + + expected_buys = [] + expected_sells = [] + order_amount = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + for level in range(self.strategy.order_levels): + bid_price = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_bid - Decimal(str(bid_level_spreads[level]))) + ask_price = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_ask + Decimal(str(ask_level_spreads[level]))) + + expected_buys.append(PriceSize(bid_price, order_amount)) + expected_sells.append(PriceSize(ask_price, order_amount)) + + expected_proposal = (expected_buys, expected_sells) + + self.assertEqual(str(expected_proposal), str(self.strategy.create_proposal_based_on_order_levels())) + + def test_create_basic_proposal(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + expected_order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, + self.order_amount) + expected_bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_bid) + + expected_ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_ask) + + expected_proposal = ([PriceSize(expected_bid_price, expected_order_amount)], + [PriceSize(expected_ask_price, expected_order_amount)]) + + self.assertEqual(str(expected_proposal), str(self.strategy.create_basic_proposal())) + + def test_create_base_proposal(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # (1) Default + expected_order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, + self.order_amount) + expected_bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_bid) + + expected_ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_ask) + + expected_proposal: Proposal = Proposal([PriceSize(expected_bid_price, expected_order_amount)], + [PriceSize(expected_ask_price, expected_order_amount)]) + + self.assertEqual(str(expected_proposal), str(self.strategy.create_base_proposal())) + + # (2) With order_override + order_override = { + "order_1": ["sell", 2.5, 100], + "order_2": ["buy", 0.5, 100] + } + + # Re-configure strategy with order_ride configurations + self.config_map.order_override = order_override + + expected_buys = [] + expected_sells = [] + for order in order_override.values(): + list_to_append = expected_buys if order[0] == "buy" else expected_sells + if "buy" == order[0]: + price = self.strategy.get_price() * (Decimal("1") - Decimal(str(order[1])) / Decimal("100")) + else: + price = self.strategy.get_price() * (Decimal("1") + Decimal(str(order[1])) / Decimal("100")) + + price = self.market.quantize_order_price(self.trading_pair, price) + size = self.market.quantize_order_amount(self.trading_pair, Decimal(str(order[2]))) + + list_to_append.append(PriceSize(price, size)) + + expected_proposal: Proposal = Proposal(expected_buys, expected_sells) + + self.assertEqual(str(expected_proposal), str(self.strategy.create_base_proposal())) + + # Reset order_override configuration + self.config_map.order_override = {} + + # (3) With order_levels + order_levels_mode = MultiOrderLevelModel() + order_levels_mode.order_levels = 2 + order_levels_mode.level_distances = 1 + self.config_map.order_levels_mode = order_levels_mode + + # Calculate order levels + bid_level_spreads, ask_level_spreads = self.strategy._get_level_spreads() + + expected_buys = [] + expected_sells = [] + order_amount = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + for level in range(self.strategy.order_levels): + bid_price = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_bid - Decimal(str(bid_level_spreads[level]))) + ask_price = self.market.quantize_order_price(self.trading_pair, + self.strategy.optimal_ask + Decimal(str(ask_level_spreads[level]))) + + expected_buys.append(PriceSize(bid_price, order_amount)) + expected_sells.append(PriceSize(ask_price, order_amount)) + + expected_proposal: Proposal = Proposal(expected_buys, expected_sells) + self.assertEqual(str(expected_proposal), str(self.strategy.create_base_proposal())) + + def test_get_adjusted_available_balance(self): + expected_available_balance: Tuple[Decimal, Decimal] = (Decimal("1"), Decimal("500")) # Initial asset balance + self.assertEqual(expected_available_balance, self.strategy.get_adjusted_available_balance(self.strategy.active_orders)) + + # Simulate order being placed + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101.0"), + quantity=Decimal("1")) + + self.simulate_place_limit_order(self.strategy, self.market_info, limit_order) + + self.assertEqual(expected_available_balance, self.strategy.get_adjusted_available_balance(self.strategy.active_orders)) + + def test_apply_order_optimization(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Create a basic proposal. + order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_bid) + ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_ask) + + initial_proposal: Proposal = Proposal([PriceSize(bid_price, order_amount)], [PriceSize(ask_price, order_amount)]) + + # Intentionally make top_bid/ask_price lower/higher respectively. + ob_bids: List[OrderBookRow] = [OrderBookRow(bid_price * Decimal("0.5"), self.order_amount, 2)] + ob_asks: List[OrderBookRow] = [OrderBookRow(ask_price * Decimal("1.5"), self.order_amount, 2)] + self.market.order_books[self.trading_pair].apply_snapshot(ob_bids, ob_asks, 2) + + new_proposal: Proposal = deepcopy(initial_proposal) + self.strategy.apply_order_price_modifiers(new_proposal) + + self.assertNotEqual(initial_proposal, new_proposal) + + def test_apply_add_transaction_costs(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Create a basic proposal. + order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_bid) + ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_ask) + + initial_proposal: Proposal = Proposal([PriceSize(bid_price, order_amount)], [PriceSize(ask_price, order_amount)]) + + # Set TradeFees + # self.market.set_flat_fee(Decimal("0.25")) + + new_proposal: Proposal = deepcopy(initial_proposal) + self.strategy.apply_order_price_modifiers(new_proposal) + + self.assertNotEqual(initial_proposal, new_proposal) + + def test_apply_order_price_modifiers(self): + # >>>> Test Preparation Start + # self.market.set_flat_fee(Decimal("0.25")) + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Create a basic proposal. + order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_bid) + ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_ask) + + initial_proposal: Proposal = Proposal([PriceSize(bid_price, order_amount)], [PriceSize(ask_price, order_amount)]) + # <<<<< Test Preparation End + + # (1) Default: order_optimization = True, add_transaction_costs_to_orders = False + # self.strategy.add_transaction_costs_to_orders = True + + # Intentionally make top_bid/ask_price lower/higher respectively & set TradeFees + ob_bids: List[OrderBookRow] = [OrderBookRow(bid_price * Decimal("0.5"), self.order_amount, 2)] + ob_asks: List[OrderBookRow] = [OrderBookRow(ask_price * Decimal("1.5"), self.order_amount, 2)] + self.market.order_books[self.trading_pair].apply_snapshot(ob_bids, ob_asks, 2) + + expected_bid_price = self.market.quantize_order_price( + self.trading_pair, + bid_price * Decimal("0.5") * (Decimal("1") - Decimal("0.25"))) + + expected_ask_price = self.market.quantize_order_price( + self.trading_pair, + ask_price * Decimal("1.5") * (Decimal("1") + Decimal("0.25"))) + + new_proposal: Proposal = deepcopy(initial_proposal) + self.strategy.apply_order_price_modifiers(new_proposal) + + self.assertNotEqual(str(initial_proposal), new_proposal) + + new_bid_price = new_proposal.buys[0].price + new_ask_price = new_proposal.sells[0].price + + self.assertAlmostEqual(expected_bid_price, new_bid_price, 6) + self.assertAlmostEqual(expected_ask_price, new_ask_price, 6) + + # (2) With none enabled + self.config_map.order_optimization_enabled = self.config_map.add_transaction_costs = False + + new_proposal: Proposal = deepcopy(initial_proposal) + self.strategy.apply_order_price_modifiers(new_proposal) + + self.assertEqual(str(initial_proposal), str(new_proposal)) + + def test_apply_budget_constraint(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Create a basic proposal. + order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_bid) + ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_ask) + + initial_proposal: Proposal = Proposal([PriceSize(bid_price, order_amount)], [PriceSize(ask_price, order_amount)]) + + # Test (1) Base & Quote balance < Base & Quote sizes in Proposal + + # Modify base_balance and quote_balance + self.market.set_balance("COINALPHA", int(self.order_amount * Decimal("0.5"))) + self.market.set_balance("HBOT", int(self.order_amount * Decimal("0.5"))) + + # Calculate expected proposal + proposal = deepcopy(initial_proposal) + base_balance, quote_balance = self.strategy.get_adjusted_available_balance(self.strategy.active_orders) + buy_fee: AddedToCostTradeFee = self.market.get_fee(self.base_asset, + self.quote_asset, + OrderType.LIMIT, + TradeType.BUY, + proposal.buys[0].size, + proposal.buys[0].price) + buy_adjusted_amount: Decimal = quote_balance / (proposal.buys[0].price * (Decimal("1") + buy_fee.percent)) + expected_buy_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, buy_adjusted_amount) + expected_sell_amount = self.market.quantize_order_amount(self.trading_pair, base_balance) + expected_proposal: Proposal = Proposal( + [PriceSize(bid_price, expected_buy_amount)], + [PriceSize(ask_price, expected_sell_amount)] + ) + + self.strategy.apply_budget_constraint(proposal) + + self.assertEqual(str(expected_proposal), str(proposal)) + + # Test (2) Base & Quote balance = s_decimal_zero + + # Modify base_balance and quote_balance + self.market.set_balance("COINALPHA", 0) + self.market.set_balance("HBOT", 0) + + proposal = deepcopy(initial_proposal) + self.strategy.apply_budget_constraint(proposal) + + expected_proposal: Proposal = Proposal([], []) + + self.assertEqual(str(expected_proposal), str(proposal)) + + def test_apply_order_amount_eta_transformation(self): + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + # Create a basic proposal. + order_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, self.order_amount) + bid_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_bid) + ask_price: Decimal = self.market.quantize_order_price(self.trading_pair, self.strategy.optimal_ask) + + initial_proposal: Proposal = Proposal( + [PriceSize(bid_price, order_amount)], + [PriceSize(ask_price, order_amount)] + ) + + # Test (1) Check proposal when order_override is NOT None + proposal: Proposal = deepcopy(initial_proposal) + + order_override = { + "order_1": ["sell", 2.5, 100], + "order_2": ["buy", 0.5, 100] + } + + # Re-configure strategy with order_ride configurations + self.config_map.order_override = order_override + + self.strategy.apply_order_amount_eta_transformation(proposal) + + self.assertEqual(str(initial_proposal), str(proposal)) + + # Test (2) Check proposal when order_override is None + + # Re-configure strategy with order_ride configurations + self.config_map.order_override = None + + proposal: Proposal = deepcopy(initial_proposal) + + # Case(1): if q < 0 + eta: Decimal = self.strategy.eta + q: Decimal = self.market.get_balance(self.base_asset) - self.strategy.calculate_target_inventory() + + expected_bid_amount: Decimal = proposal.buys[0].size + expected_ask_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, + proposal.sells[0].size * Decimal.exp(eta * q)) + expected_proposal: Proposal = Proposal( + [PriceSize(proposal.buys[0].price, expected_bid_amount)], + [PriceSize(proposal.sells[0].price, expected_ask_amount)] + ) + + self.strategy.apply_order_amount_eta_transformation(proposal) + self.assertEqual(str(expected_proposal), str(proposal)) + + # Case(2): if q > 0 + self.market.set_balance("COINALPHA", 100) + eta: Decimal = self.strategy.eta + q: Decimal = self.market.get_balance(self.base_asset) - self.strategy.calculate_target_inventory() + + expected_bid_amount: Decimal = self.market.quantize_order_amount(self.trading_pair, + proposal.buys[0].size * Decimal.exp(-eta * q)) + expected_ask_amount: Decimal = proposal.sells[0].size + + expected_proposal: Proposal = Proposal( + [PriceSize(proposal.buys[0].price, expected_bid_amount)], + [PriceSize(proposal.sells[0].price, expected_ask_amount)] + ) + + self.strategy.apply_order_amount_eta_transformation(proposal) + self.assertEqual(str(expected_proposal), str(proposal)) + + def test_is_within_tolerance(self): + bid_price: Decimal = Decimal("99.5") + ask_price: Decimal = Decimal("101.5") + + buy_prices: List[Decimal] = [bid_price] + sell_prices: List[Decimal] = [ask_price] + + bid_price: Decimal = Decimal("98.5") + ask_price: Decimal = Decimal("100.5") + + proposal: Proposal = Proposal( + [PriceSize(bid_price, self.order_amount)], # Bids + [PriceSize(ask_price, self.order_amount)] # Sells + ) + proposal_buys = [buy.price for buy in proposal.buys] + proposal_sells = [sell.price for sell in proposal.sells] + + # Default order_refresh_tolerance_pct is 0. So it will always NOT be within tolerance + self.assertFalse(self.strategy.is_within_tolerance(buy_prices, proposal_buys)) + self.assertFalse(self.strategy.is_within_tolerance(sell_prices, proposal_sells)) + + self.config_map.order_refresh_tolerance_pct = Decimal("10.0") + + self.assertTrue(self.strategy.is_within_tolerance(buy_prices, proposal_buys)) + self.assertTrue(self.strategy.is_within_tolerance(sell_prices, proposal_sells)) + + def test_cancel_active_orders(self): + + bid_price: Decimal = Decimal("99.5") + ask_price: Decimal = Decimal("101.5") + proposal: Proposal = Proposal( + [PriceSize(bid_price, self.order_amount)], # Bids + [PriceSize(ask_price, self.order_amount)] # Sells + ) + + limit_buy_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=bid_price, + quantity=self.order_amount) + limit_sell_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=ask_price, + quantity=self.order_amount) + + # Case (1): No orders to cancel + self.strategy.cancel_active_orders(proposal) + self.assertEqual(0, len(self.strategy.active_orders)) + + # Case (2): Has active orders and within _order_refresh_tolerance_pct. + # Note: Order will NOT be cancelled + self.config_map.order_refresh_tolerance_pct = Decimal("10") + self.simulate_place_limit_order(self.strategy, self.market_info, limit_buy_order) + self.simulate_place_limit_order(self.strategy, self.market_info, limit_sell_order) + self.assertEqual(2, len(self.strategy.active_orders)) + + # Case (3a): Has active orders and EXCEED _order_refresh_tolerance_pct BUT cancel_timestamp > current_timestamp + # Note: Orders will NOT be cancelled + self.config_map.order_refresh_tolerance_pct = Decimal("0") + bid_price: Decimal = Decimal("98.5") + ask_price: Decimal = Decimal("100.5") + proposal: Proposal = Proposal( + [PriceSize(bid_price, self.order_amount)], # Bids + [PriceSize(ask_price, self.order_amount)] # Sells + ) + self.assertEqual(2, len(self.strategy.active_orders)) + + self.strategy.cancel_active_orders(proposal) + + self.assertEqual(2, len(self.strategy.active_orders)) + + # Case (3b): Has active orders and EXCEED _order_refresh_tolerance_pct AND cancel_timestamp <= current_timestamp + # Note: Orders will be cancelled + self.clock.backtest_til(self.strategy.current_timestamp + self.strategy.order_refresh_time + 1) + + self.strategy.cancel_active_orders(proposal) + + self.assertEqual(0, len(self.strategy.active_orders)) + + # Case (4): Has active orders and within _order_refresh_tolerance_pct BUT cancel_timestamp > current_timestamp + # Note: Order not cancelled + self.config_map.order_refresh_tolerance_pct = Decimal("10") + self.simulate_place_limit_order(self.strategy, self.market_info, limit_buy_order) + self.simulate_place_limit_order(self.strategy, self.market_info, limit_sell_order) + self.assertEqual(2, len(self.strategy.active_orders)) + + self.strategy.cancel_active_orders(proposal) + + self.assertEqual(2, len(self.strategy.active_orders)) + + # Case (5): Has active orders and within _order_refresh_tolerance_pct AND cancel_timestamp <= current_timestamp + self.config_map.order_refresh_tolerance_pct = s_decimal_neg_one + + self.clock.backtest_til(self.strategy.current_timestamp + self.strategy.order_refresh_time + 1) + + self.strategy.cancel_active_orders(proposal) + + self.assertEqual(0, len(self.strategy.active_orders)) + + def test_to_create_orders(self): + # Simulate order being placed. Placing an order updates create_timestamp = next_cycle + limit_buy_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("99"), + quantity=self.order_amount) + limit_sell_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("101"), + quantity=self.order_amount) + self.simulate_place_limit_order(self.strategy, self.market_info, limit_buy_order) + self.simulate_place_limit_order(self.strategy, self.market_info, limit_sell_order) + + # Simulate new proposal being created + bid_price: Decimal = Decimal("99.5") + ask_price: Decimal = Decimal("101.5") + proposal: Proposal = Proposal( + [PriceSize(bid_price, self.order_amount)], # Bids + [PriceSize(ask_price, self.order_amount)] # Sells + ) + + # Case (1) create_timestamp < current_timestamp + self.assertFalse(self.strategy.to_create_orders(proposal)) + + # Case (2) create_timestamp >= current_timestamp + order_refresh_time + self.clock.backtest_til(self.start_timestamp + self.strategy.order_refresh_time + 1) + self.simulate_cancelling_all_active_orders(self.strategy) + + self.assertTrue(self.strategy.to_create_orders(proposal)) + + def test_existing_hanging_orders_are_included_in_budget_constraint(self): + config_settings = { + "exchange": self.market.name, + "market": self.trading_pair, + "execution_timeframe_mode": "infinite", + "order_amount": self.order_amount, + "min_spread": self.min_spread, + "risk_factor": self.risk_factor_finite, + "hanging_orders_mode": {"hanging_orders_cancel_pct": 99}, + "order_refresh_time": "60", + "inventory_target_base_pct": self.inventory_target_base_pct, + "filled_order_delay": 30, + } + config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**config_settings)) + + self.market.set_balance("COINALPHA", 100) + self.market.set_balance("HBOT", 50000) + + # Create a new strategy, with hanging orders enabled + self.strategy = AvellanedaMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_info=self.market_info, + ) + + # Create a new clock to start the strategy from scratch + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.market) + self.clock.add_iterator(self.strategy) + + self.strategy.avg_vol = self.avg_vol_indicator + self.clock.add_iterator(self.strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size * 2) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + # Simulate market fill for limit sell. + self.simulate_limit_order_fill(self.market, sell_order) + + # The buy order should turn into a hanging when it reaches its refresh time + self.clock.backtest_til(self.start_timestamp + self.strategy.order_refresh_time + 2) + self.assertEqual(1, len(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)) + self.assertEqual(buy_order.client_order_id, + list(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)[0].order_id) + + current_base_balance, current_quote_balance = self.strategy.adjusted_available_balance_for_orders_budget_constrain() + expected_base_balance = (sum([order.quantity + for order in self.strategy.active_non_hanging_orders + if not order.is_buy]) + + self.market.get_available_balance(self.market_info.base_asset)) + expected_quote_balance = (sum([order.quantity * order.price + for order in self.strategy.active_non_hanging_orders + if order.is_buy]) + + self.market.get_available_balance(self.market_info.quote_asset)) + + self.assertEqual(expected_base_balance, current_base_balance) + self.assertEqual(expected_quote_balance, current_quote_balance) + + def test_not_filled_order_changed_to_hanging_order_after_refresh_time(self): + hanging_orders_model = TrackHangingOrdersModel() + hanging_orders_model.hanging_orders_cancel_pct = "99" + + # Refresh has to happend after filled_order_delay + refresh_time = 80 + filled_extension_time = 60 + + config_settings = { + "exchange": self.market.name, + "market": self.trading_pair, + "execution_timeframe_mode": "infinite", + "order_amount": self.order_amount, + "order_optimization_enabled": "yes", + "min_spread": self.min_spread, + "risk_factor": self.risk_factor_finite, + "order_refresh_time": refresh_time, + "inventory_target_base_pct": self.inventory_target_base_pct, + "hanging_orders_mode": hanging_orders_model, + "filled_order_delay": filled_extension_time + } + config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**config_settings)) + + self.market.set_balance("COINALPHA", 100) + self.market.set_balance("HBOT", 50000) + + # Create a new strategy, with hanging orders enabled + self.strategy = AvellanedaMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_info=self.market_info, + ) + + # Create a new clock to start the strategy from scratch + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.market) + self.clock.add_iterator(self.strategy) + + self.strategy.avg_vol = self.avg_vol_indicator + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size * 2) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + orders_creation_timestamp = self.strategy.current_timestamp + + # Advance the clock some ticks and simulate market fill for limit sell + self.clock.backtest_til(orders_creation_timestamp + 10) + self.simulate_limit_order_fill(self.market, sell_order) + self.assertEqual(buy_order.client_order_id, + self.strategy.active_non_hanging_orders[0].client_order_id) + + # The buy order should turn into a hanging when it reaches its refresh time + self.clock.backtest_til(orders_creation_timestamp + refresh_time - 1) + self.assertEqual(2, len(self.strategy.active_non_hanging_orders)) + + # After refresh time the buy order that was candidate to hanging order should be turned into a hanging order + self.clock.backtest_til(orders_creation_timestamp + refresh_time) + # New orders get created + self.assertEqual(2, len(self.strategy.active_non_hanging_orders)) + self.assertEqual(1, len(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)) + self.assertEqual(buy_order.client_order_id, + list(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)[0].order_id) + + # The new pair of orders should be created only after the fill delay time + self.clock.backtest_til(orders_creation_timestamp + 10 + filled_extension_time - 1) + # New orders get created + self.assertEqual(2, len(self.strategy.active_non_hanging_orders)) + self.clock.backtest_til(orders_creation_timestamp + 10 + filled_extension_time + 1) + self.assertEqual(2, len(self.strategy.active_non_hanging_orders)) + # The hanging order should still be present + self.assertEqual(1, len(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)) + self.assertEqual(buy_order.client_order_id, + list(self.strategy.hanging_orders_tracker.strategy_current_hanging_orders)[0].order_id) + + def test_no_new_orders_created_until_previous_orders_cancellation_confirmed(self): + + refresh_time = self.strategy.order_refresh_time + + self.strategy.avg_vol = self.avg_vol_indicator + self.config_map.order_refresh_tolerance_pct = -1 + + # Simulate low volatility + self.simulate_low_volatility(self.strategy) + + # Simulate high liquidity + self.simulate_high_liquidity(self.strategy) + + # Prepare market variables and parameters for calculation + self.strategy.measure_order_book_liquidity() + self.strategy.calculate_reservation_price_and_optimal_spread() + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + orders_creation_timestamp = self.strategy.current_timestamp + + # Add a fake in flight cancellation to simulate a confirmation has not arrived + self.strategy._sb_order_tracker.in_flight_cancels["OID-99"] = self.strategy.current_timestamp + + # After refresh time the two real orders should be cancelled, but no new order should be created + self.clock.backtest_til(orders_creation_timestamp + refresh_time) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + + # After a second refresh time no new order should be created + self.clock.backtest_til(orders_creation_timestamp + (2 * refresh_time)) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + + del self.strategy._sb_order_tracker.in_flight_cancels["OID-99"] + + # After removing the pending cancel, in the next tick the new orders should be created + self.clock.backtest_til(self.strategy.current_timestamp + 1) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + def test_adjusted_available_balance_considers_in_flight_cancel_orders(self): + base_balance = self.market.get_available_balance(self.base_asset) + quote_balance = self.market.get_available_balance(self.quote_asset) + + self.strategy._sb_order_tracker.start_tracking_limit_order( + market_pair=self.market_info, + order_id="OID-1", + is_buy=True, + price=Decimal(1000), + quantity=Decimal(1)) + self.strategy._sb_order_tracker.start_tracking_limit_order( + market_pair=self.market_info, + order_id="OID-2", + is_buy=False, + price=Decimal(2000), + quantity=Decimal(2)) + + self.strategy._sb_order_tracker.in_flight_cancels["OID-1"] = self.strategy.current_timestamp + + available_base_balance, available_quote_balance = self.strategy.adjusted_available_balance_for_orders_budget_constrain() + + self.assertEqual(available_base_balance, base_balance + Decimal(2)) + self.assertEqual(available_quote_balance, quote_balance + (Decimal(1) * Decimal(1000))) diff --git a/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_config_map_pydantic.py b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_config_map_pydantic.py new file mode 100644 index 0000000..21ea3a6 --- /dev/null +++ b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_config_map_pydantic.py @@ -0,0 +1,272 @@ +import asyncio +import unittest +from datetime import datetime, time +from decimal import Decimal +from pathlib import Path +from typing import Awaitable, Dict +from unittest.mock import patch + +import yaml +from pydantic import validate_model + +from hummingbot.client.config.config_helpers import ClientConfigAdapter, ConfigValidationError +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, + DailyBetweenTimesModel, + FromDateToDateModel, + IgnoreHangingOrdersModel, + InfiniteModel, + MultiOrderLevelModel, + SingleOrderLevelModel, + TrackHangingOrdersModel, +) + + +class AvellanedaMarketMakingConfigMapPydanticTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + cls.exchange = "binance" + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + # Make sure market trading pair validations are not executed with real info + with patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher.fetch_all"): + TradingPairFetcher._sf_shared_instance = None + config_settings = self.get_default_map() + self.config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**config_settings)) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_default_map(self) -> Dict[str, str]: + config_settings = { + "exchange": self.exchange, + "market": self.trading_pair, + "execution_timeframe_mode": { + "start_time": "09:30:00", + "end_time": "16:00:00", + }, + "order_amount": "10", + "order_optimization_enabled": "yes", + "risk_factor": "0.5", + "order_refresh_time": "60", + "inventory_target_base_pct": "50", + } + return config_settings + + def test_initial_sequential_build(self): + config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap.construct()) + config_settings = self.get_default_map() + + def build_config_map(cm: ClientConfigAdapter, cs: Dict): + """This routine can be used in the create command, with slight modifications.""" + for key in cm.keys(): + client_data = cm.get_client_data(key) + if client_data is not None and client_data.prompt_on_new: + self.assertIsInstance(client_data.prompt(cm), str) + if key == "execution_timeframe_model": + setattr(cm, key, "daily_between_times") # simulate user input + else: + setattr(cm, key, cs[key]) + new_value = getattr(cm, key) + if isinstance(new_value, ClientConfigAdapter): + build_config_map(new_value, cs[key]) + + build_config_map(config_map, config_settings) + hb_config = config_map.hb_config + validate_model(hb_config.__class__, hb_config.__dict__) + self.assertEqual(0, len(config_map.validate_model())) + + def test_order_amount_prompt(self): + prompt = self.async_run_with_timeout(self.config_map.get_client_prompt("order_amount")) + expected = f"What is the amount of {self.base_asset} per order?" + + self.assertEqual(expected, prompt) + + def test_maker_trading_pair_prompt(self): + exchange = self.config_map.exchange + example = AllConnectorSettings.get_example_pairs().get(exchange) + + prompt = self.async_run_with_timeout(self.config_map.get_client_prompt("market")) + expected = f"Enter the token trading pair you would like to trade on {exchange} (e.g. {example})" + + self.assertEqual(expected, prompt) + + def test_execution_time_prompts(self): + self.config_map.execution_timeframe_mode = FromDateToDateModel.Config.title + model = self.config_map.execution_timeframe_mode + prompt = self.async_run_with_timeout(model.get_client_prompt("start_datetime")) + expected = "Please enter the start date and time (YYYY-MM-DD HH:MM:SS)" + self.assertEqual(expected, prompt) + prompt = self.async_run_with_timeout(model.get_client_prompt("end_datetime")) + expected = "Please enter the end date and time (YYYY-MM-DD HH:MM:SS)" + self.assertEqual(expected, prompt) + + self.config_map.execution_timeframe_mode = DailyBetweenTimesModel.Config.title + model = self.config_map.execution_timeframe_mode + prompt = self.async_run_with_timeout(model.get_client_prompt("start_time")) + expected = "Please enter the start time (HH:MM:SS)" + self.assertEqual(expected, prompt) + prompt = self.async_run_with_timeout(model.get_client_prompt("end_time")) + expected = "Please enter the end time (HH:MM:SS)" + self.assertEqual(expected, prompt) + + @patch("hummingbot.client.config.strategy_config_data_types.validate_market_trading_pair") + def test_validators(self, _): + self.config_map.execution_timeframe_mode = "infinite" + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, InfiniteModel) + + self.config_map.execution_timeframe_mode = "from_date_to_date" + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, FromDateToDateModel) + + self.config_map.execution_timeframe_mode = "daily_between_times" + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, DailyBetweenTimesModel) + + with self.assertRaises(ConfigValidationError) as e: + self.config_map.execution_timeframe_mode = "XXX" + + error_msg = ( + "Invalid timeframe, please choose value from ['infinite', 'from_date_to_date', 'daily_between_times']" + ) + self.assertEqual(error_msg, str(e.exception)) + + self.config_map.execution_timeframe_mode = "from_date_to_date" + model = self.config_map.execution_timeframe_mode + model.start_datetime = "2021-01-01 12:00:00" + model.end_datetime = "2021-01-01 15:00:00" + + self.assertEqual(datetime(2021, 1, 1, 12, 0, 0), model.start_datetime) + self.assertEqual(datetime(2021, 1, 1, 15, 0, 0), model.end_datetime) + + with self.assertRaises(ConfigValidationError) as e: + model.start_datetime = "2021-01-01 30:00:00" + + error_msg = "Incorrect date time format (expected is YYYY-MM-DD HH:MM:SS)" + self.assertEqual(error_msg, str(e.exception)) + + with self.assertRaises(ConfigValidationError) as e: + model.start_datetime = "12:00:00" + + error_msg = "Incorrect date time format (expected is YYYY-MM-DD HH:MM:SS)" + self.assertEqual(error_msg, str(e.exception)) + + self.config_map.execution_timeframe_mode = "daily_between_times" + model = self.config_map.execution_timeframe_mode + model.start_time = "12:00:00" + + self.assertEqual(time(12, 0, 0), model.start_time) + + with self.assertRaises(ConfigValidationError) as e: + model.start_time = "30:00:00" + + error_msg = "Incorrect time format (expected is HH:MM:SS)" + self.assertEqual(error_msg, str(e.exception)) + + with self.assertRaises(ConfigValidationError) as e: + model.start_time = "2021-01-01 12:00:00" + + error_msg = "Incorrect time format (expected is HH:MM:SS)" + self.assertEqual(error_msg, str(e.exception)) + + self.config_map.order_levels_mode = "multi_order_level" + model = self.config_map.order_levels_mode + + with self.assertRaises(ConfigValidationError) as e: + model.order_levels = 1 + + error_msg = "Value cannot be less than 2." + self.assertEqual(error_msg, str(e.exception)) + + model.order_levels = 3 + self.assertEqual(3, model.order_levels) + + self.config_map.hanging_orders_mode = "track_hanging_orders" + model = self.config_map.hanging_orders_mode + + with self.assertRaises(ConfigValidationError) as e: + model.hanging_orders_cancel_pct = "-1" + + error_msg = "Value must be between 0 and 100 (exclusive)." + self.assertEqual(error_msg, str(e.exception)) + + model.hanging_orders_cancel_pct = "3" + self.assertEqual(3, model.hanging_orders_cancel_pct) + + def test_load_configs_from_yaml(self): + cur_dir = Path(__file__).parent + f_path = cur_dir / "test_config.yml" + + with open(f_path, "r") as file: + data = yaml.safe_load(file) + + loaded_config_map = ClientConfigAdapter(AvellanedaMarketMakingConfigMap(**data)) + + self.assertEqual(self.config_map, loaded_config_map) + + def test_configuring_execution_timeframe_mode(self): + self.config_map.execution_timeframe_mode = InfiniteModel() + + self.config_map.execution_timeframe_mode = { + "start_datetime": "2022-01-01 10:00:00", + "end_datetime": "2022-01-02 10:00:00", + } + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, FromDateToDateModel) + self.assertEqual(self.config_map.execution_timeframe_mode.start_datetime, datetime(2022, 1, 1, 10)) + self.assertEqual(self.config_map.execution_timeframe_mode.end_datetime, datetime(2022, 1, 2, 10)) + + self.config_map.execution_timeframe_mode = { + "start_time": "10:00:00", + "end_time": "11:00:00", + } + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, DailyBetweenTimesModel) + self.assertEqual(self.config_map.execution_timeframe_mode.start_time, time(10)) + self.assertEqual(self.config_map.execution_timeframe_mode.end_time, time(11)) + + self.config_map.execution_timeframe_mode = {} + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.execution_timeframe_mode.hb_config, InfiniteModel) + + def test_configuring_order_levels_mode(self): + self.config_map.order_levels_mode = SingleOrderLevelModel() + + self.config_map.order_levels_mode = { + "order_levels": 2, + "level_distances": 1, + } + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.order_levels_mode.hb_config, MultiOrderLevelModel) + self.assertEqual(self.config_map.order_levels_mode.order_levels, 2) + self.assertEqual(self.config_map.order_levels_mode.level_distances, 1) + + self.config_map.order_levels_mode = {} + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.order_levels_mode.hb_config, SingleOrderLevelModel) + + def test_configuring_hanging_orders_mode(self): + self.config_map.hanging_orders_mode = IgnoreHangingOrdersModel() + + self.config_map.hanging_orders_mode = {"hanging_orders_cancel_pct": 1} + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.hanging_orders_mode.hb_config, TrackHangingOrdersModel) + self.assertEqual(self.config_map.hanging_orders_mode.hanging_orders_cancel_pct, Decimal("1")) + + self.config_map.hanging_orders_mode = {} + self.config_map.validate_model() + + self.assertIsInstance(self.config_map.hanging_orders_mode.hb_config, IgnoreHangingOrdersModel) diff --git a/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_start.py b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_start.py new file mode 100644 index 0000000..605dc21 --- /dev/null +++ b/test/hummingbot/strategy/avellaneda_market_making/test_avellaneda_market_making_start.py @@ -0,0 +1,98 @@ +import datetime +import logging +import unittest.mock +from decimal import Decimal + +import hummingbot.strategy.avellaneda_market_making.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.utils import combine_to_hb_trading_pair +from hummingbot.strategy.avellaneda_market_making.avellaneda_market_making_config_map_pydantic import ( + AvellanedaMarketMakingConfigMap, + FromDateToDateModel, + MultiOrderLevelModel, + TrackHangingOrdersModel, +) + + +class AvellanedaStartTest(unittest.TestCase): + # the level is required to receive logs from the data source logger + level = 0 + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.markets = {"binance": ExchangeBase(client_config_map=ClientConfigAdapter(ClientConfigMap()))} + self.notifications = [] + self.log_records = [] + self.base = "ETH" + self.quote = "BTC" + self.strategy_config_map = ClientConfigAdapter( + AvellanedaMarketMakingConfigMap( + exchange="binance", + market=combine_to_hb_trading_pair(self.base, self.quote), + execution_timeframe_mode=FromDateToDateModel( + start_datetime="2021-11-18 15:00:00", + end_datetime="2021-11-18 16:00:00", + ), + order_amount=60, + order_refresh_time=60, + hanging_orders_mode=TrackHangingOrdersModel( + hanging_orders_cancel_pct=1, + ), + order_levels_mode=MultiOrderLevelModel( + order_levels=4, + level_distances=1, + ), + min_spread=2, + risk_factor=1.11, + order_amount_shape_factor=0.33, + ) + ) + + self.raise_exception_for_market_initialization = False + self._logger = None + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + if self.raise_exception_for_market_initialization: + raise Exception("Exception for testing") + + def notify(self, message): + self.notifications.append(message) + + def logger(self): + if self._logger is None: + self._logger = logging.getLogger(self.__class__.__name__) + self._logger.addHandler(self) + return self._logger + + def handle(self, record): + self.log_records.append(record) + + @unittest.mock.patch('hummingbot.strategy.avellaneda_market_making.start.HummingbotApplication') + def test_parameters_strategy_creation(self, mock_hbot): + mock_hbot.main_application().strategy_file_name = "test.yml" + strategy_start.start(self) + self.assertEqual(self.strategy.execution_timeframe, "from_date_to_date") + self.assertEqual(self.strategy.start_time, datetime.datetime(2021, 11, 18, 15, 0)) + self.assertEqual(self.strategy.end_time, datetime.datetime(2021, 11, 18, 16, 0)) + self.assertEqual(self.strategy.min_spread, Decimal("2")) + self.assertEqual(self.strategy.gamma, Decimal("1.11")) + self.assertEqual(self.strategy.eta, Decimal("0.33")) + self.assertEqual(self.strategy.order_levels, Decimal("4")) + self.assertEqual(self.strategy.level_distances, Decimal("1")) + self.assertTrue(all(c is not None for c in (self.strategy.gamma, self.strategy.eta))) + strategy_start.start(self) + self.assertTrue(all(c is not None for c in (self.strategy.min_spread, self.strategy.gamma))) + + def test_strategy_creation_when_something_fails(self): + self.raise_exception_for_market_initialization = True + strategy_start.start(self) + self.assertEqual(len(self.notifications), 1) + self.assertEqual(self.notifications[0], "Exception for testing") + self.assertEqual(len(self.log_records), 1) + self.assertEqual(self.log_records[0].getMessage(), "Unknown error during initialization.") diff --git a/test/hummingbot/strategy/avellaneda_market_making/test_config.yml b/test/hummingbot/strategy/avellaneda_market_making/test_config.yml new file mode 100644 index 0000000..051b140 --- /dev/null +++ b/test/hummingbot/strategy/avellaneda_market_making/test_config.yml @@ -0,0 +1,10 @@ +exchange: binance +market: COINALPHA-HBOT +execution_timeframe_mode: + start_time: "09:30:00" + end_time: "16:00:00" +order_amount: 10 +order_optimization_enabled: true +risk_factor: 0.5 +order_refresh_time: 60 +inventory_target_base_pct: 50 diff --git a/test/hummingbot/strategy/cross_exchange_market_making/__init__.py b/test/hummingbot/strategy/cross_exchange_market_making/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_config.yml b/test/hummingbot/strategy/cross_exchange_market_making/test_config.yml new file mode 100644 index 0000000..15475a4 --- /dev/null +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_config.yml @@ -0,0 +1,16 @@ +strategy: cross_exchange_market_making +maker_market: mock_paper_exchange +taker_market: mock_paper_exchange +maker_market_trading_pair: COINALPHA-HBOT +taker_market_trading_pair: COINALPHA-HBOT +min_profitability: 0 +order_amount: 10 +adjust_order_enabled: true +order_refresh_mode: active_order_refresh +top_depth_tolerance: 0.0 +anti_hysteresis_duration: 60.0 +order_size_taker_volume_factor: 25.0 +order_size_taker_balance_factor: 99.5 +order_size_portfolio_ratio_limit: 16.67 +conversion_rate_mode: {} +slippage_buffer: 5.0 diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py new file mode 100644 index 0000000..a66c2cb --- /dev/null +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py @@ -0,0 +1,1053 @@ +import asyncio +import unittest +from copy import deepcopy +from decimal import Decimal +from math import ceil, floor +from typing import Awaitable, List +from unittest.mock import patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TradeFeeSchema +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderBookTradeEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making import ( + CrossExchangeMarketMakingStrategy, + LogOption, +) +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + ActiveOrderRefreshMode, + CrossExchangeMarketMakingConfigMap, + TakerToMakerConversionRateMode, +) +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class HedgedMarketMakingUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + exchange_name_maker = "mock_paper_exchange" + exchange_name_taker = "mock_paper_exchange" + trading_pairs_maker: List[str] = ["COINALPHA-WETH", "COINALPHA", "WETH"] + trading_pairs_taker: List[str] = ["COINALPHA-ETH", "COINALPHA", "ETH"] + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def setUp(self, get_connector_settings_mock, get_exchange_names_mock): + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) + self.min_profitability = Decimal("0.5") + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.taker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.01, 10) + self.taker_market.set_balanced_order_book(self.trading_pairs_taker[0], 1.0, 0.5, 1.5, 0.001, 4) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("WETH", 5) + self.maker_market.set_balance("QETH", 5) + self.taker_market.set_balance("COINALPHA", 5) + self.taker_market.set_balance("ETH", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 5, 5, 5, 5)) + self.taker_market.set_quantization_param(QuantizationParams(self.trading_pairs_taker[0], 5, 5, 5, 5)) + + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + self.config_map_raw = CrossExchangeMarketMakingConfigMap( + maker_market=self.exchange_name_maker, + taker_market=self.exchange_name_taker, + maker_market_trading_pair=self.trading_pairs_maker[0], + taker_market_trading_pair=self.trading_pairs_taker[0], + min_profitability=Decimal(self.min_profitability), + slippage_buffer=Decimal("0"), + order_amount=Decimal("0"), + # Default values folllow + order_size_taker_volume_factor=Decimal("25"), + order_size_taker_balance_factor=Decimal("99.5"), + order_size_portfolio_ratio_limit=Decimal("30"), + adjust_order_enabled=True, + anti_hysteresis_duration=60.0, + order_refresh_mode=ActiveOrderRefreshMode(), + top_depth_tolerance=Decimal(0), + conversion_rate_mode=TakerToMakerConversionRateMode(), + ) + + self.config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("1.0") + self.config_map_raw.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal("1.0") + + self.config_map = ClientConfigAdapter(self.config_map_raw) + config_map_with_top_depth_tolerance_raw = deepcopy(self.config_map_raw) + config_map_with_top_depth_tolerance_raw.top_depth_tolerance = Decimal("1") + config_map_with_top_depth_tolerance = ClientConfigAdapter( + config_map_with_top_depth_tolerance_raw + ) + + logging_options = ( + LogOption.NULL_ORDER_SIZE, + LogOption.REMOVING_ORDER, + LogOption.ADJUST_ORDER, + LogOption.CREATE_ORDER, + LogOption.MAKER_ORDER_FILLED, + LogOption.STATUS_REPORT, + LogOption.MAKER_ORDER_HEDGED + ) + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=self.config_map, + market_pairs=[self.market_pair], + logging_options=logging_options, + ) + self.strategy_with_top_depth_tolerance: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy_with_top_depth_tolerance.init_params( + config_map=config_map_with_top_depth_tolerance, + market_pairs=[self.market_pair], + logging_options=logging_options, + ) + self.logging_options = logging_options + self.clock.add_iterator(self.maker_market) + self.clock.add_iterator(self.taker_market) + self.clock.add_iterator(self.strategy) + + self.maker_order_fill_logger: EventLogger = EventLogger() + self.taker_order_fill_logger: EventLogger = EventLogger() + self.maker_cancel_order_logger: EventLogger = EventLogger() + self.taker_cancel_order_logger: EventLogger = EventLogger() + self.maker_order_created_logger: EventLogger = EventLogger() + self.taker_order_created_logger: EventLogger = EventLogger() + self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) + self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) + self.maker_market.add_listener(MarketEvent.OrderCancelled, self.maker_cancel_order_logger) + self.taker_market.add_listener(MarketEvent.OrderCancelled, self.taker_cancel_order_logger) + self.maker_market.add_listener(MarketEvent.BuyOrderCreated, self.maker_order_created_logger) + self.maker_market.add_listener(MarketEvent.SellOrderCreated, self.maker_order_created_logger) + self.taker_market.add_listener(MarketEvent.BuyOrderCreated, self.taker_order_created_logger) + self.taker_market.add_listener(MarketEvent.SellOrderCreated, self.taker_order_created_logger) + + def tearDown(self): + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_mock_connector_settings(self): + + conf_var_connector_maker = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_maker.value = 'mock_paper_exchange' + + conf_var_connector_taker = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_taker.value = 'mock_paper_exchange' + + settings = { + "mock_paper_exchange": ConnectorSetting( + name='mock_paper_exchange', + type=ConnectorType.Exchange, + example_pair='ZRX-ETH', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'connector': conf_var_connector_maker + }, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + } + + return settings + + def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): + maker_trading_pair: str = self.trading_pairs_maker[0] + order_book: OrderBook = self.maker_market.get_order_book(maker_trading_pair) + trade_event: OrderBookTradeEvent = OrderBookTradeEvent( + maker_trading_pair, self.clock.current_timestamp, TradeType.BUY if is_buy else TradeType.SELL, price, quantity + ) + order_book.apply_trade(trade_event) + + @staticmethod + def simulate_order_book_widening(order_book: OrderBook, top_bid: float, top_ask: float): + bid_diffs: List[OrderBookRow] = [] + ask_diffs: List[OrderBookRow] = [] + update_id: int = order_book.last_diff_uid + 1 + for row in order_book.bid_entries(): + if row.price > top_bid: + bid_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + for row in order_book.ask_entries(): + if row.price < top_ask: + ask_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + order_book.apply_diffs(bid_diffs, ask_diffs, update_id) + + @staticmethod + def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + market.current_timestamp, + OrderType.LIMIT, + limit_order.trading_pair, + limit_order.quantity, + limit_order.price, + limit_order.client_order_id, + limit_order.creation_timestamp * 1e-6 + ) + ) + market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0)), + "exchid_" + limit_order.client_order_id + ), + ) + market.trigger_event( + MarketEvent.BuyOrderCompleted, + BuyOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT, + ), + ) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + market.trigger_event( + MarketEvent.BuyOrderCreated, + SellOrderCreatedEvent( + market.current_timestamp, + OrderType.LIMIT, + limit_order.trading_pair, + limit_order.quantity, + limit_order.price, + limit_order.client_order_id, + limit_order.creation_timestamp * 1e-6, + ) + ) + market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0)), + "exchid_" + limit_order.client_order_id + ), + ) + market.trigger_event( + MarketEvent.SellOrderCompleted, + SellOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT, + ), + ) + + @staticmethod + def emit_order_created_event(market: MockPaperExchange, order: LimitOrder): + event_cls = BuyOrderCreatedEvent if order.is_buy else SellOrderCreatedEvent + event_tag = MarketEvent.BuyOrderCreated if order.is_buy else MarketEvent.SellOrderCreated + market.trigger_event( + event_tag, + message=event_cls( + order.creation_timestamp, + OrderType.LIMIT, + order.trading_pair, + order.quantity, + order.price, + order.client_order_id, + order.creation_timestamp * 1e-6 + ) + ) + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + @patch('hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making.' + 'CrossExchangeMarketMakingStrategy.is_gateway_market') + def test_both_sides_profitable(self, + is_gateway_mock: unittest.mock.Mock, + get_connector_settings_mock, + get_exchange_names_mock): + is_gateway_mock.return_value = False + + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + self.clock.backtest_til(self.start_timestamp + 5) + if len(self.maker_order_created_logger.event_log) == 0: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + self.simulate_maker_market_trade(False, Decimal("10.0"), bid_order.price * Decimal("0.99")) + + self.assertEqual(1, len(self.maker_order_fill_logger.event_log)) + + is_gateway_mock.return_value = True + + self.clock.backtest_til(self.start_timestamp + 10) + if len(self.taker_order_created_logger.event_log) == 0: + self.async_run_with_timeout(self.taker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.clock.backtest_til(self.start_timestamp + 20) + if len(self.taker_order_fill_logger.event_log) == 0: + self.async_run_with_timeout(self.taker_order_fill_logger.wait_for(OrderFilledEvent)) + + self.assertEqual(1, len(self.taker_order_fill_logger.event_log)) + + maker_fill: OrderFilledEvent = self.maker_order_fill_logger.event_log[0] + taker_fill: OrderFilledEvent = self.taker_order_fill_logger.event_log[0] + self.assertEqual(TradeType.BUY, maker_fill.trade_type) + self.assertEqual(TradeType.SELL, taker_fill.trade_type) + self.assertAlmostEqual(Decimal("0.99452"), maker_fill.price) + self.assertAlmostEqual(Decimal("0.9995"), taker_fill.price) + self.assertAlmostEqual(Decimal("3.0"), maker_fill.amount) + self.assertAlmostEqual(Decimal("3.0"), taker_fill.amount) + + def test_top_depth_tolerance(self): # TODO + self.clock.remove_iterator(self.strategy) + self.clock.add_iterator(self.strategy_with_top_depth_tolerance) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_maker_asks[0][1] + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + ask_order.creation_timestamp * 1e-6, + ) + ) + + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.simulate_order_book_widening(self.taker_market.order_books[self.trading_pairs_taker[0]], 0.99, 1.01) + + self.clock.backtest_til(self.start_timestamp + 100) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.clock.backtest_til(self.start_timestamp + 101) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.maker_cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy_with_top_depth_tolerance.active_maker_bids)) + self.assertEqual(1, len(self.strategy_with_top_depth_tolerance.active_maker_asks)) + + bid_order = self.strategy_with_top_depth_tolerance.active_maker_bids[0][1] + ask_order = self.strategy_with_top_depth_tolerance.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98457"), bid_order.price) + self.assertEqual(Decimal("1.0156"), ask_order.price) + + def test_market_became_wider(self): + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.simulate_order_book_widening(self.taker_market.order_books[self.trading_pairs_taker[0]], 0.99, 1.01) + + self.clock.backtest_til(self.start_timestamp + 100) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.clock.backtest_til(self.start_timestamp + 101) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.maker_cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98457"), bid_order.price) + self.assertEqual(Decimal("1.0156"), ask_order.price) + + def test_market_became_narrower(self): + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + self.maker_market.order_books[self.trading_pairs_maker[0]].apply_diffs( + [OrderBookRow(0.996, 30, 2)], [OrderBookRow(1.004, 30, 2)], 2) + + self.clock.backtest_til(self.start_timestamp + 10) + + if len(self.maker_order_created_logger.event_log) == 0: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(0, len(self.maker_cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + + def test_order_fills_after_cancellation(self): # TODO + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.99452"), bid_order.price) + self.assertEqual(Decimal("1.0056"), ask_order.price) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + ask_order.creation_timestamp * 1e-6, + ) + ) + + self.simulate_order_book_widening(self.taker_market.order_books[self.trading_pairs_taker[0]], 0.99, 1.01) + + self.clock.backtest_til(self.start_timestamp + 10) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 11) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.maker_cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98457"), bid_order.price) + self.assertEqual(Decimal("1.0156"), ask_order.price) + + self.simulate_limit_order_fill(self.maker_market, bid_order) + self.simulate_limit_order_fill(self.maker_market, ask_order) + + prev_taker_orders_created_len = len(self.taker_order_created_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 20) + + if len(self.taker_order_created_logger.event_log) == prev_taker_orders_created_len: + self.async_run_with_timeout(self.taker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + prev_taker_orders_filled_len = len(self.taker_order_fill_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 30) + + if len(self.taker_order_fill_logger.event_log) == prev_taker_orders_filled_len: + self.async_run_with_timeout(self.taker_order_fill_logger.wait_for(OrderFilledEvent)) + + fill_events: List[OrderFilledEvent] = self.taker_order_fill_logger.event_log + + bid_hedges: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.SELL] + ask_hedges: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.BUY] + + self.assertEqual(1, len(bid_hedges)) + self.assertEqual(1, len(ask_hedges)) + self.assertGreater( + self.maker_market.get_balance(self.trading_pairs_maker[2]) + self.taker_market.get_balance(self.trading_pairs_taker[2]), + Decimal("10"), + ) + self.assertEqual(2, len(self.taker_order_fill_logger.event_log)) + taker_fill1: OrderFilledEvent = self.taker_order_fill_logger.event_log[1] + self.assertEqual(TradeType.SELL, taker_fill1.trade_type) + self.assertAlmostEqual(Decimal("0.9895"), taker_fill1.price) + self.assertAlmostEqual(Decimal("3.0"), taker_fill1.amount) + taker_fill2: OrderFilledEvent = self.taker_order_fill_logger.event_log[0] + self.assertEqual(TradeType.BUY, taker_fill2.trade_type) + self.assertAlmostEqual(Decimal("1.0104"), taker_fill2.price) + self.assertAlmostEqual(Decimal("3.0"), taker_fill2.amount) + + def test_with_conversion_rate_mode_not_set(self): + self.clock.remove_iterator(self.strategy) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *["COINALPHA-QETH", "COINALPHA", "QETH"]), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + self.maker_market.set_balanced_order_book("COINALPHA-QETH", 1.05, 0.55, 1.55, 0.01, 10) + + config_map_with_conversion_rate_mode_not_set = ClientConfigAdapter( + CrossExchangeMarketMakingConfigMap( + maker_market=self.exchange_name_maker, + taker_market=self.exchange_name_taker, + maker_market_trading_pair=self.trading_pairs_maker[0], + taker_market_trading_pair=self.trading_pairs_taker[0], + min_profitability=Decimal("1"), + order_amount = Decimal("1"), + ) + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map_with_conversion_rate_mode_not_set, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(0, len(self.strategy.active_maker_bids)) + self.assertEqual(0, len(self.strategy.active_maker_asks)) + + def test_with_conversion(self): + self.clock.remove_iterator(self.strategy) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *["COINALPHA-QETH", "COINALPHA", "QETH"]), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + self.maker_market.set_balanced_order_book("COINALPHA-QETH", 1.05, 0.55, 1.55, 0.01, 10) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.min_profitability = Decimal("1") + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("0.95") + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertAlmostEqual(Decimal("1.0417"), round(bid_order.price, 4)) + self.assertAlmostEqual(Decimal("1.0637"), round(ask_order.price, 4)) + self.assertAlmostEqual(Decimal("2.9286"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("2.9286"), round(ask_order.quantity, 4)) + + def test_maker_price(self): + task = self.ev_loop.create_task(self.strategy.calculate_effective_hedging_price(self.market_pair, False, 3)) + buy_taker_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.calculate_effective_hedging_price(self.market_pair, True, 3)) + sell_taker_price: Decimal = self.ev_loop.run_until_complete(task) + + self.assertEqual(Decimal("1.0005"), buy_taker_price) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + bid_maker_price = sell_taker_price * (1 - self.min_profitability / Decimal("100")) + price_quantum = self.maker_market.get_order_price_quantum(self.trading_pairs_maker[0], bid_maker_price) + bid_maker_price = (floor(bid_maker_price / price_quantum)) * price_quantum + ask_maker_price = buy_taker_price * (1 + self.min_profitability / Decimal("100")) + price_quantum = self.maker_market.get_order_price_quantum(self.trading_pairs_maker[0], ask_maker_price) + ask_maker_price = (ceil(ask_maker_price / price_quantum) * price_quantum) + self.assertEqual(round(bid_maker_price, 4), round(bid_order.price, 4)) + self.assertEqual(round(ask_maker_price, 4), round(ask_order.price, 4)) + self.assertEqual(Decimal("3.0"), bid_order.quantity) + self.assertEqual(Decimal("3.0"), ask_order.quantity) + + def test_with_adjust_orders_enabled(self): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.1, 10) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = True + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("WETH", 5) + self.maker_market.set_balance("QETH", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + # place above top bid (at 0.95) + self.assertAlmostEqual(Decimal("0.9501"), bid_order.price) + # place below top ask (at 1.05) + self.assertAlmostEqual(Decimal("1.049"), ask_order.price) + self.assertAlmostEqual(Decimal("3"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("3"), round(ask_order.quantity, 4)) + + def test_with_adjust_orders_disabled(self): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.1, 10) + self.taker_market.set_balanced_order_book(self.trading_pairs_taker[0], 1.0, 0.5, 1.5, 0.001, 20) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = False + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("WETH", 5) + self.maker_market.set_balance("QETH", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.9945"), bid_order.price) + self.assertEqual(Decimal("1.006"), ask_order.price) + self.assertAlmostEqual(Decimal("3"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("3"), round(ask_order.quantity, 4)) + + def test_price_and_size_limit_calculation(self): + self.taker_market.set_balanced_order_book(self.trading_pairs_taker[0], 1.0, 0.5, 1.5, 0.001, 20) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, True)) + bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, True, bid_size)) + bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, False)) + ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, False, ask_size)) + ask_price: Decimal = self.ev_loop.run_until_complete(task) + + self.assertEqual((Decimal("0.99452"), Decimal("3.0000")), (bid_price, bid_size)) + self.assertEqual((Decimal("1.0056"), Decimal("3.0000")), (ask_price, ask_size)) + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def test_price_and_size_limit_calculation_with_slippage_buffer(self, + get_connector_settings_mock, + get_exchange_names_mock): + self.taker_market.set_balance("ETH", 3) + self.taker_market.set_balanced_order_book( + self.trading_pairs_taker[0], + mid_price=Decimal("1.0"), + min_price=Decimal("0.5"), + max_price=Decimal("1.5"), + price_step_size=Decimal("0.1"), + volume_step_size=Decimal("100"), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_taker_volume_factor = Decimal("100") + config_map_raw.order_size_taker_balance_factor = Decimal("100") + config_map_raw.order_size_portfolio_ratio_limit = Decimal("100") + config_map_raw.min_profitability = Decimal("25") + config_map_raw.slippage_buffer = Decimal("0") + config_map_raw.order_amount = Decimal("4") + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + config_map_with_slippage_buffer = ClientConfigAdapter( + CrossExchangeMarketMakingConfigMap( + maker_market=self.exchange_name_maker, + taker_market=self.exchange_name_taker, + maker_market_trading_pair=self.trading_pairs_maker[0], + taker_market_trading_pair=self.trading_pairs_taker[0], + order_amount=Decimal("4"), + min_profitability=Decimal("25"), + order_size_taker_volume_factor=Decimal("100"), + order_size_taker_balance_factor=Decimal("100"), + order_size_portfolio_ratio_limit=Decimal("100"), + slippage_buffer=Decimal("25"), + ) + ) + strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + strategy_with_slippage_buffer.init_params( + config_map=config_map_with_slippage_buffer, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, True)) + bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, True, bid_size)) + bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, False)) + ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, False, ask_size)) + ask_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_size(self.market_pair, True)) + slippage_bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_price( + self.market_pair, True, slippage_bid_size + )) + slippage_bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_size(self.market_pair, False)) + slippage_ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_price( + self.market_pair, False, slippage_ask_size + )) + slippage_ask_price: Decimal = self.ev_loop.run_until_complete(task) + + self.assertEqual(Decimal("4"), bid_size) # the user size + self.assertEqual(Decimal("0.76"), bid_price) # price = bid_VWAP(4) / profitability = 0.95 / 1.25 + self.assertEqual(Decimal("2.8571"), ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1) + self.assertEqual(Decimal("1.3125"), ask_price) # price = ask_VWAP(2.8571) * profitability = 1.05 * 1.25 + self.assertEqual(Decimal("4"), slippage_bid_size) # the user size + self.assertEqual(Decimal("0.76"), slippage_bid_price) # price = bid_VWAP(4) / profitability = 0.9 / 1.25 + self.assertEqual(Decimal("2.2857"), slippage_ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1.25) + self.assertEqual(Decimal("1.3125"), slippage_ask_price) # price = ask_VWAP(2.2857) * profitability = 1.05 * 1.25 + + def test_check_if_sufficient_balance_adjusts_including_slippage(self): + self.taker_market.set_balance("COINALPHA", 4) + self.taker_market.set_balance("ETH", 3) + self.taker_market.set_balanced_order_book( + self.trading_pairs_taker[0], + mid_price=Decimal("1.0"), + min_price=Decimal("0.5"), + max_price=Decimal("1.5"), + price_step_size=Decimal("0.1"), + volume_step_size=Decimal("1"), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_taker_volume_factor = Decimal("100") + config_map_raw.order_size_taker_balance_factor = Decimal("100") + config_map_raw.order_size_portfolio_ratio_limit = Decimal("100") + config_map_raw.min_profitability = Decimal("25") + config_map_raw.slippage_buffer = Decimal("25") + config_map_raw.order_amount = Decimal("4") + + config_map = ClientConfigAdapter( + config_map_raw + ) + + strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + strategy_with_slippage_buffer.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.clock.remove_iterator(self.strategy) + self.clock.add_iterator(strategy_with_slippage_buffer) + self.clock.backtest_til(self.start_timestamp + 1) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(active_maker_bids)) + self.assertEqual(1, len(active_maker_asks)) + + active_bid = active_maker_bids[0][1] + active_ask = active_maker_asks[0][1] + + self.emit_order_created_event(self.maker_market, active_bid) + self.emit_order_created_event(self.maker_market, active_ask) + + self.clock.backtest_til(self.start_timestamp + 2) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.clock.backtest_til(self.start_timestamp + 3) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(active_maker_bids)) + self.assertEqual(1, len(active_maker_asks)) + + active_bid = active_maker_bids[0][1] + active_ask = active_maker_asks[0][1] + bids_quantum = self.taker_market.get_order_size_quantum( + self.trading_pairs_taker[0], active_bid.quantity + ) + asks_quantum = self.taker_market.get_order_size_quantum( + self.trading_pairs_taker[0], active_ask.quantity + ) + + self.taker_market.set_balance("COINALPHA", Decimal("4") - bids_quantum) + self.taker_market.set_balance("ETH", Decimal("3") - asks_quantum * 1) + + self.clock.backtest_til(self.start_timestamp + 4) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(0, len(active_maker_bids)) # cancelled + self.assertEqual(0, len(active_maker_asks)) # cancelled + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 5) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + new_active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + new_active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(new_active_maker_bids)) + self.assertEqual(1, len(new_active_maker_asks)) + + new_active_bid = new_active_maker_bids[0][1] + new_active_ask = new_active_maker_asks[0][1] + + self.assertEqual(Decimal(str(active_bid.quantity - bids_quantum)), new_active_bid.quantity) + self.assertEqual(Decimal(str(active_ask.quantity - asks_quantum)), new_active_ask.quantity) + + def test_empty_maker_orderbook(self): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + + # Orderbook is empty + self.maker_market.new_empty_order_book(self.trading_pairs_maker[0]) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = False + config_map_raw.order_amount = Decimal("1") + + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("WETH", 5) + self.maker_market.set_balance("QETH", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + # Places orders based on taker orderbook + self.assertEqual(Decimal("0.9945"), bid_order.price) + self.assertEqual(Decimal("1.006"), ask_order.price) + self.assertAlmostEqual(Decimal("1"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("1"), round(ask_order.quantity, 4)) diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_config_map_pydantic.py b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_config_map_pydantic.py new file mode 100644 index 0000000..5d6030a --- /dev/null +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_config_map_pydantic.py @@ -0,0 +1,193 @@ +import json +import unittest +from decimal import Decimal +from pathlib import Path +from typing import Dict +from unittest.mock import patch + +import yaml + +from hummingbot.client import settings +from hummingbot.client.config.config_helpers import ClientConfigAdapter, ConfigValidationError +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import AllConnectorSettings, ConnectorSetting, ConnectorType +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + ActiveOrderRefreshMode, + CrossExchangeMarketMakingConfigMap, + OracleConversionRateMode, + PassiveOrderRefreshMode, + TakerToMakerConversionRateMode, +) + + +class CrossExchangeMarketMakingConfigMapPydanticTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + cls.maker_exchange = "mock_paper_exchange" + cls.taker_exchange = "mock_paper_exchange" + + # Reset the list of connectors (there could be changes introduced by other tests when running the suite + AllConnectorSettings.create_connector_settings() + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def setUp(self, get_connector_settings_mock, get_exchange_names_mock) -> None: + super().setUp() + config_settings = self.get_default_map() + + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + self.config_map = ClientConfigAdapter(CrossExchangeMarketMakingConfigMap(**config_settings)) + + def get_default_map(self) -> Dict[str, str]: + config_settings = { + "maker_market": self.maker_exchange, + "taker_market": self.taker_exchange, + "maker_market_trading_pair": self.trading_pair, + "taker_market_trading_pair": self.trading_pair, + "order_amount": "10", + "min_profitability": "0", + } + return config_settings + + def get_mock_connector_settings(self): + + conf_var_connector_maker = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_maker.value = 'mock_paper_exchange' + + conf_var_connector_taker = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_taker.value = 'mock_paper_exchange' + + settings = { + "mock_paper_exchange": ConnectorSetting( + name='mock_paper_exchange', + type=ConnectorType.Exchange, + example_pair='ZRX-ETH', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'connector': conf_var_connector_maker + }, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + } + + return settings + + def test_top_depth_tolerance_prompt(self): + self.config_map.maker_market_trading_pair = self.trading_pair + prompt = self.config_map.top_depth_tolerance_prompt(self.config_map) + expected = f"What is your top depth tolerance? (in {self.base_asset})" + + self.assertEqual(expected, prompt) + + def test_order_amount_prompt(self): + self.config_map.maker_market_trading_pair = self.trading_pair + prompt = self.config_map.order_amount_prompt(self.config_map) + expected = f"What is the amount of {self.base_asset} per order?" + + self.assertEqual(expected, prompt) + + @patch("hummingbot.client.config.strategy_config_data_types.validate_market_trading_pair") + def test_validators(self, _): + self.config_map.order_refresh_mode = "active_order_refresh" + self.assertIsInstance(self.config_map.order_refresh_mode.hb_config, ActiveOrderRefreshMode) + + self.config_map.order_refresh_mode = "passive_order_refresh" + self.config_map.order_refresh_mode.cancel_order_threshold = Decimal("1.0") + self.config_map.order_refresh_mode.cancel_order_threshold = Decimal("2.0") + self.assertIsInstance(self.config_map.order_refresh_mode.hb_config, PassiveOrderRefreshMode) + + with self.assertRaises(ConfigValidationError) as e: + self.config_map.order_refresh_mode = "XXX" + + error_msg = ( + "Invalid order refresh mode, please choose value from ['passive_order_refresh', 'active_order_refresh']." + ) + self.assertEqual(error_msg, str(e.exception)) + + self.config_map.conversion_rate_mode = "rate_oracle_conversion_rate" + self.assertIsInstance(self.config_map.conversion_rate_mode.hb_config, OracleConversionRateMode) + + self.config_map.conversion_rate_mode = "fixed_conversion_rate" + self.config_map.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("1.0") + self.config_map.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal("2.0") + self.assertIsInstance(self.config_map.conversion_rate_mode.hb_config, TakerToMakerConversionRateMode) + + with self.assertRaises(ConfigValidationError) as e: + self.config_map.conversion_rate_mode = "XXX" + + error_msg = ( + "Invalid conversion rate mode, please choose value from ['rate_oracle_conversion_rate', 'fixed_conversion_rate']." + ) + self.assertEqual(error_msg, str(e.exception)) + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def test_load_configs_from_yaml(self, get_connector_settings_mock, get_exchange_names_mock): + + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + cur_dir = Path(__file__).parent + f_path = cur_dir / "test_config.yml" + + with open(f_path, "r") as file: + data = yaml.safe_load(file) + + loaded_config_map = ClientConfigAdapter(CrossExchangeMarketMakingConfigMap(**data)) + + self.assertEqual(self.config_map, loaded_config_map) + + def test_maker_field_jason_schema_includes_all_connectors_for_exchange_field(self): + schema = CrossExchangeMarketMakingConfigMap.schema_json() + schema_dict = json.loads(schema) + + self.assertIn("MakerMarkets", schema_dict["definitions"]) + expected_connectors = { + connector_setting.name for connector_setting in + AllConnectorSettings.get_connector_settings().values() + if connector_setting.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + } + print(expected_connectors) + expected_connectors = list(expected_connectors.union(settings.PAPER_TRADE_EXCHANGES)) + expected_connectors.sort() + print(expected_connectors) + print(schema_dict["definitions"]["MakerMarkets"]["enum"]) + self.assertEqual(expected_connectors, schema_dict["definitions"]["MakerMarkets"]["enum"]) + + def test_taker_field_jason_schema_includes_all_connectors_for_exchange_field(self): + # Reset the list of connectors (there could be changes introduced by other tests when running the suite + AllConnectorSettings.create_connector_settings() + + # force reset the list of possible connectors + self.config_map.taker_market = settings.PAPER_TRADE_EXCHANGES[0] + + schema = CrossExchangeMarketMakingConfigMap.schema_json() + schema_dict = json.loads(schema) + + self.assertIn("TakerMarkets", schema_dict["definitions"]) + expected_connectors = { + connector_setting.name for connector_setting in + AllConnectorSettings.get_connector_settings().values() + if connector_setting.type in [ConnectorType.Exchange, ConnectorType.CLOB_SPOT, ConnectorType.CLOB_PERP] + } + expected_connectors = list(expected_connectors.union(settings.PAPER_TRADE_EXCHANGES)) + expected_connectors.sort() + self.assertEqual(expected_connectors, schema_dict["definitions"]["TakerMarkets"]["enum"]) diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_gateway.py b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_gateway.py new file mode 100644 index 0000000..94d694c --- /dev/null +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_gateway.py @@ -0,0 +1,1248 @@ +import asyncio +import unittest +from copy import deepcopy +from decimal import Decimal +from math import ceil +from typing import Awaitable, List, Union +from unittest.mock import patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeSchema +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + MarketEvent, + OrderBookTradeEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making import ( + CrossExchangeMarketMakingStrategy, + LogOption, +) +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + ActiveOrderRefreshMode, + CrossExchangeMarketMakingConfigMap, + TakerToMakerConversionRateMode, +) +from hummingbot.strategy.maker_taker_market_pair import MakerTakerMarketPair +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +s_decimal_0 = Decimal(0) + + +class MockAMM(ConnectorBase): + def __init__(self, name, client_config_map: "ClientConfigAdapter"): + self._name = name + super().__init__(client_config_map) + self._buy_prices = {} + self._sell_prices = {} + self._network_transaction_fee = TokenAmount("COINALPHA", s_decimal_0) + + @property + def name(self): + return self._name + + @property + def network_transaction_fee(self) -> TokenAmount: + return self._network_transaction_fee + + @network_transaction_fee.setter + def network_transaction_fee(self, fee: TokenAmount): + self._network_transaction_fee = fee + + @property + def connector_name(self): + return "uniswap" + + async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + if is_buy: + return self._buy_prices[trading_pair] + else: + return self._sell_prices[trading_pair] + + async def get_order_price(self, trading_pair: str, is_buy: bool, amount: Decimal) -> Decimal: + return await self.get_quote_price(trading_pair, is_buy, amount) + + def set_prices(self, trading_pair, is_buy, price): + if is_buy: + self._buy_prices[trading_pair] = Decimal(str(price)) + else: + self._sell_prices[trading_pair] = Decimal(str(price)) + + def set_balance(self, token, balance): + self._account_balances[token] = Decimal(str(balance)) + self._account_available_balances[token] = Decimal(str(balance)) + + def buy(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal): + return self.place_order(True, trading_pair, amount, price) + + def sell(self, trading_pair: str, amount: Decimal, order_type: OrderType, price: Decimal): + return self.place_order(False, trading_pair, amount, price) + + def place_order(self, is_buy: bool, trading_pair: str, amount: Decimal, price: Decimal): + side = "buy" if is_buy else "sell" + order_id = f"{side}-{trading_pair}-{get_tracking_nonce()}" + event_tag = MarketEvent.BuyOrderCreated if is_buy else MarketEvent.SellOrderCreated + event_class = BuyOrderCreatedEvent if is_buy else SellOrderCreatedEvent + self.trigger_event(event_tag, + event_class( + self.current_timestamp, + OrderType.LIMIT, + trading_pair, + amount, + price, + order_id, + self.current_timestamp)) + return order_id + + def get_taker_order_type(self): + return OrderType.LIMIT + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("0.01") + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + return Decimal("0.01") + + def estimate_fee_pct(self, is_maker: bool): + return Decimal("0") + + def ready(self): + return True + + async def check_network(self) -> NetworkStatus: + return NetworkStatus.CONNECTED + + async def cancel_outdated_orders(self, _: int) -> List: + return [] + + +class HedgedMarketMakingUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + exchange_name_maker = "mock_paper_exchange" + exchange_name_taker = "mock_paper_decentralized_exchange" + trading_pairs_maker: List[str] = ["COINALPHA-HBOT", "COINALPHA", "HBOT"] + trading_pairs_taker: List[str] = ["WCOINALPHA-WHBOT", "WCOINALPHA", "WHBOT"] + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.ev_loop = asyncio.get_event_loop() + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def setUp(self, get_connector_settings_mock, get_connector_spec_from_market_name_mock): + get_connector_spec_from_market_name_mock.return_value = self.get_mock_gateway_settings() + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) + self.min_profitability = Decimal("0.5") + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.taker_market: MockAMM = MockAMM( + name="mock_paper_decentralized_exchange", + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.01, 10) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.05 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.95 + ) + + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("HBOT", 5) + self.maker_market.set_balance("QCOINALPHA", 5) + self.taker_market.set_balance("WCOINALPHA", 5) + self.taker_market.set_balance("WHBOT", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 5, 5, 5, 5)) + + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + self.config_map_raw = CrossExchangeMarketMakingConfigMap( + maker_market=self.exchange_name_maker, + taker_market=self.exchange_name_taker, + maker_market_trading_pair=self.trading_pairs_maker[0], + taker_market_trading_pair=self.trading_pairs_taker[0], + min_profitability=Decimal(self.min_profitability), + slippage_buffer=Decimal("0"), + order_amount=Decimal("0"), + # Default values folllow + order_size_taker_volume_factor=Decimal("25"), + order_size_taker_balance_factor=Decimal("99.5"), + order_size_portfolio_ratio_limit=Decimal("30"), + adjust_order_enabled=True, + anti_hysteresis_duration=60.0, + order_refresh_mode=ActiveOrderRefreshMode(), + top_depth_tolerance=Decimal(0), + conversion_rate_mode=TakerToMakerConversionRateMode(), + ) + self.config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("1.0") + self.config_map_raw.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal("1.0") + self.config_map = ClientConfigAdapter(self.config_map_raw) + config_map_with_top_depth_tolerance_raw = deepcopy(self.config_map_raw) + config_map_with_top_depth_tolerance_raw.top_depth_tolerance = Decimal("1") + config_map_with_top_depth_tolerance = ClientConfigAdapter( + config_map_with_top_depth_tolerance_raw + ) + + logging_options = ( + LogOption.NULL_ORDER_SIZE, + LogOption.REMOVING_ORDER, + LogOption.ADJUST_ORDER, + LogOption.CREATE_ORDER, + LogOption.MAKER_ORDER_FILLED, + LogOption.STATUS_REPORT, + LogOption.MAKER_ORDER_HEDGED + ) + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=self.config_map, + market_pairs=[self.market_pair], + logging_options=logging_options + ) + self.strategy_with_top_depth_tolerance: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy_with_top_depth_tolerance.init_params( + config_map=config_map_with_top_depth_tolerance, + market_pairs=[self.market_pair], + logging_options=logging_options + ) + self.logging_options = logging_options + self.clock.add_iterator(self.maker_market) + self.clock.add_iterator(self.taker_market) + self.clock.add_iterator(self.strategy) + + self.maker_order_fill_logger: EventLogger = EventLogger() + self.taker_order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.maker_order_created_logger: EventLogger = EventLogger() + self.taker_order_created_logger: EventLogger = EventLogger() + self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) + self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) + self.maker_market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + self.maker_market.add_listener(MarketEvent.BuyOrderCreated, self.maker_order_created_logger) + self.maker_market.add_listener(MarketEvent.SellOrderCreated, self.maker_order_created_logger) + self.taker_market.add_listener(MarketEvent.BuyOrderCreated, self.taker_order_created_logger) + self.taker_market.add_listener(MarketEvent.SellOrderCreated, self.taker_order_created_logger) + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) + return ret + + def get_mock_connector_settings(self): + + conf_var_connector_cex = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_cex.value = 'mock_paper_exchange' + + conf_var_connector_dex = ConfigVar(key='mock_paper_decentralized_exchange', prompt="") + conf_var_connector_dex.value = 'mock_paper_decentralized_exchange' + + settings = { + "mock_paper_exchange": ConnectorSetting( + name='mock_paper_exchange', + type=ConnectorType.Exchange, + example_pair='ZRX-COINALPHA', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'connector': conf_var_connector_cex + }, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False), + "mock_paper_decentralized_exchange": ConnectorSetting( + name='mock_paper_decentralized_exchange', + type=ConnectorType.AMM, + example_pair='WCOINALPHA-USDC', + centralised=False, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.0'), + taker_percent_fee_decimal=Decimal('0.0'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={}, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + } + + return settings + + def get_mock_gateway_settings(self): + + settings = { + 'connector': 'mock_paper_decentralized_exchange', + 'chain': 'ethereum', + 'network': 'kovan', + 'trading_type': 'AMM', + 'chain_type': 'EVM', + 'wallet_address': '0xXXXXX', + 'additional_spenders': [] + } + + return settings + + def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): + maker_trading_pair: str = self.trading_pairs_maker[0] + order_book: OrderBook = self.maker_market.get_order_book(maker_trading_pair) + trade_event: OrderBookTradeEvent = OrderBookTradeEvent( + maker_trading_pair, self.clock.current_timestamp, TradeType.BUY if is_buy else TradeType.SELL, price, quantity + ) + order_book.apply_trade(trade_event) + + @staticmethod + def simulate_order_book_widening(order_book: OrderBook, top_bid: float, top_ask: float): + bid_diffs: List[OrderBookRow] = [] + ask_diffs: List[OrderBookRow] = [] + update_id: int = order_book.last_diff_uid + 1 + for row in order_book.bid_entries(): + if row.price > top_bid: + bid_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + for row in order_book.ask_entries(): + if row.price < top_ask: + ask_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + order_book.apply_diffs(bid_diffs, ask_diffs, update_id) + + @staticmethod + def simulate_limit_order_fill(market: Union[MockPaperExchange, MockAMM], limit_order: LimitOrder): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + market.current_timestamp, + OrderType.LIMIT, + limit_order.trading_pair, + limit_order.quantity, + limit_order.price, + limit_order.client_order_id, + limit_order.creation_timestamp * 1e-6 + ) + ) + market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0)), + "exchid_" + limit_order.client_order_id + ), + ) + market.trigger_event( + MarketEvent.BuyOrderCompleted, + BuyOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT, + ), + ) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + market.trigger_event( + MarketEvent.BuyOrderCreated, + SellOrderCreatedEvent( + market.current_timestamp, + OrderType.LIMIT, + limit_order.trading_pair, + limit_order.quantity, + limit_order.price, + limit_order.client_order_id, + limit_order.creation_timestamp * 1e-6, + ) + ) + market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0)), + "exchid_" + limit_order.client_order_id + ), + ) + market.trigger_event( + MarketEvent.SellOrderCompleted, + SellOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT, + ), + ) + + @staticmethod + def emit_order_created_event(market: Union[MockPaperExchange, MockAMM], order: LimitOrder): + event_cls = BuyOrderCreatedEvent if order.is_buy else SellOrderCreatedEvent + event_tag = MarketEvent.BuyOrderCreated if order.is_buy else MarketEvent.SellOrderCreated + market.trigger_event( + event_tag, + message=event_cls( + order.creation_timestamp, + OrderType.LIMIT, + order.trading_pair, + order.quantity, + order.price, + order.client_order_id, + order.creation_timestamp * 1e-6 + ) + ) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market") + @patch.object(MockAMM, "cancel_outdated_orders") + def test_both_sides_profitable(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + is_gateway_mock: unittest.mock.Mock, + get_connector_settings_mock, + get_connector_spec_from_market_name_mock): + is_gateway_mock.return_value = True + + get_connector_spec_from_market_name_mock.return_value = self.get_mock_gateway_settings() + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + self.clock.backtest_til(self.start_timestamp + 5) + if len(self.maker_order_created_logger.event_log) == 0: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + self.simulate_maker_market_trade(False, Decimal("10.0"), bid_order.price * Decimal("0.99")) + + self.clock.backtest_til(self.start_timestamp + 10) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.clock.backtest_til(self.start_timestamp + 15) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.maker_order_fill_logger.event_log)) + # Order fills not emitted by the gateway for now + # self.assertEqual(1, len(self.taker_order_fill_logger.event_lo + + maker_fill: OrderFilledEvent = self.maker_order_fill_logger.event_log[0] + # Order fills not emitted by the gateway for now + # taker_fill: OrderFilledEvent = self.taker_order_fill_logger.event_log[0] + self.assertEqual(TradeType.BUY, maker_fill.trade_type) + # self.assertEqual(TradeType.SELL, taker_fill.trade_type) + self.assertAlmostEqual(Decimal("0.94527"), maker_fill.price) + # self.assertAlmostEqual(Decimal("0.9995"), taker_fill.price) + self.assertAlmostEqual(Decimal("3.0000"), maker_fill.amount) + # self.assertAlmostEqual(Decimal("3.0"), taker_fill.amount) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_top_depth_tolerance(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): # TODO + self.clock.remove_iterator(self.strategy) + self.clock.add_iterator(self.strategy_with_top_depth_tolerance) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_maker_asks[0][1] + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + ask_order.creation_timestamp * 1e-6, + ) + ) + + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.01 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.99 + ) + + self.clock.backtest_til(self.start_timestamp + 100) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.clock.backtest_til(self.start_timestamp + 101) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy_with_top_depth_tolerance.active_maker_bids)) + self.assertEqual(1, len(self.strategy_with_top_depth_tolerance.active_maker_asks)) + + bid_order = self.strategy_with_top_depth_tolerance.active_maker_bids[0][1] + ask_order = self.strategy_with_top_depth_tolerance.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98507"), bid_order.price) + self.assertEqual(Decimal("1.0151"), ask_order.price) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_market_became_wider(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.01 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.99 + ) + + self.clock.backtest_til(self.start_timestamp + 100) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.clock.backtest_til(self.start_timestamp + 101) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98507"), bid_order.price) + self.assertEqual(Decimal("1.0151"), ask_order.price) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_market_became_narrower(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + self.maker_market.order_books[self.trading_pairs_maker[0]].apply_diffs( + [OrderBookRow(0.996, 30, 2)], [OrderBookRow(1.004, 30, 2)], 2) + + self.clock.backtest_til(self.start_timestamp + 10) + + if len(self.maker_order_created_logger.event_log) == 0: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(0, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_order_fills_after_cancellation(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): # TODO + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.94527"), bid_order.price) + self.assertEqual(Decimal("1.0553"), ask_order.price) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + self.taker_market.trigger_event( + MarketEvent.BuyOrderCreated, + BuyOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + bid_order.trading_pair, + bid_order.quantity, + bid_order.price, + bid_order.client_order_id, + bid_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.trigger_event( + MarketEvent.SellOrderCreated, + SellOrderCreatedEvent( + self.start_timestamp + 5, + OrderType.LIMIT, + ask_order.trading_pair, + ask_order.quantity, + ask_order.price, + ask_order.client_order_id, + ask_order.creation_timestamp * 1e-6, + ) + ) + + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.01 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.99 + ) + + self.clock.backtest_til(self.start_timestamp + 10) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 11) + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(SellOrderCreatedEvent)) + + self.assertEqual(2, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + + bid_order = self.strategy.active_maker_bids[0][1] + ask_order = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.98507"), bid_order.price) + self.assertEqual(Decimal("1.0151"), ask_order.price) + + self.simulate_limit_order_fill(self.maker_market, bid_order) + self.simulate_limit_order_fill(self.maker_market, ask_order) + + self.clock.backtest_til(self.start_timestamp + 20) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + self.clock.backtest_til(self.start_timestamp + 30) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + # Order fills not emitted by the gateway for now + # fill_events: List[OrderFilledEvent] = self.taker_order_fill_logger.event_log + + # bid_hedges: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.SELL] + # ask_hedges: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.BUY] + # self.assertEqual(1, len(bid_hedges)) + # self.assertEqual(1, len(ask_hedges)) + # self.assertGreater( + # self.maker_market.get_balance(self.trading_pairs_maker[2]) + self.taker_market.get_balance(self.trading_pairs_taker[2]), + # Decimal("10"), + # ) + # Order fills not emitted by the gateway for now + # self.assertEqual(2, len(self.taker_order_fill_logger.event_log)) + # taker_fill1: OrderFilledEvent = self.taker_order_fill_logger.event_log[0] + # self.assertEqual(TradeType.SELL, taker_fill1.trade_type) + # self.assertAlmostEqual(Decimal("0.9895"), taker_fill1.price) + # self.assertAlmostEqual(Decimal("3.0"), taker_fill1.amount) + # taker_fill2: OrderFilledEvent = self.taker_order_fill_logger.event_log[1] + # self.assertEqual(TradeType.BUY, taker_fill2.trade_type) + # self.assertAlmostEqual(Decimal("1.0105"), taker_fill2.price) + # self.assertAlmostEqual(Decimal("3.0"), taker_fill2.amount) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market") + @patch.object(MockAMM, "cancel_outdated_orders") + def test_with_conversion(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + is_gateway_mock: unittest.mock.Mock): + is_gateway_mock.return_value = True + + self.clock.remove_iterator(self.strategy) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *["QCOINALPHA-HBOT", "QCOINALPHA", "HBOT"]), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + self.maker_market.set_balanced_order_book("QCOINALPHA-HBOT", 1.05, 0.55, 1.55, 0.01, 10) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.conversion_rate_mode = TakerToMakerConversionRateMode() + config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("0.95") + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = True + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertAlmostEqual(Decimal("0.9950"), round(bid_order.price, 4)) + self.assertAlmostEqual(Decimal("1.1108"), round(ask_order.price, 4)) + self.assertAlmostEqual(Decimal("2.9286"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("2.9286"), round(ask_order.quantity, 4)) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_maker_price(self, cancel_outdated_orders_func: unittest.mock.AsyncMock, _: unittest.mock.Mock): + task = self.ev_loop.create_task(self.strategy.calculate_effective_hedging_price(self.market_pair, False, 3)) + buy_taker_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.calculate_effective_hedging_price(self.market_pair, True, 3)) + sell_taker_price: Decimal = self.ev_loop.run_until_complete(task) + + price_quantum = Decimal("0.0001") + self.assertEqual(Decimal("1.0500"), buy_taker_price) + self.assertEqual(Decimal("0.9500"), sell_taker_price) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + bid_maker_price = sell_taker_price * (1 - self.min_profitability / Decimal("100")) + bid_maker_price = (ceil(bid_maker_price / price_quantum)) * price_quantum + ask_maker_price = buy_taker_price * (1 + self.min_profitability / Decimal("100")) + ask_maker_price = (ceil(ask_maker_price / price_quantum) * price_quantum) + self.assertEqual(round(bid_maker_price, 4), round(bid_order.price, 4)) + self.assertEqual(round(ask_maker_price, 4), round(ask_order.price, 4)) + self.assertEqual(Decimal("3.0000"), bid_order.quantity) + self.assertEqual(Decimal("3.0000"), ask_order.quantity) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_with_adjust_orders_enabled(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.1, 10) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = False + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("HBOT", 5) + self.maker_market.set_balance("QCOINALPHA", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + # place above top bid (at 0.95) + self.assertAlmostEqual(Decimal("0.9452"), bid_order.price) + # place below top ask (at 1.05) + self.assertAlmostEqual(Decimal("1.056"), ask_order.price) + self.assertAlmostEqual(Decimal("3.0000"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("3.0000"), round(ask_order.quantity, 4)) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_with_adjust_orders_disabled(self, cancel_outdated_orders_func: unittest.mock.AsyncMock, _: unittest.mock.Mock): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + + self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.1, 10) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.05 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.95 + ) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = True + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("HBOT", 5) + self.maker_market.set_balance("QCOINALPHA", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + self.assertEqual(Decimal("0.9452"), bid_order.price) + self.assertEqual(Decimal("1.056"), ask_order.price) + self.assertAlmostEqual(Decimal("3.0000"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("3.0000"), round(ask_order.quantity, 4)) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_price_and_size_limit_calculation(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.05 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.95 + ) + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, True)) + bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, True, bid_size)) + bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, False)) + ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, False, ask_size)) + ask_price: Decimal = self.ev_loop.run_until_complete(task) + + self.assertEqual((Decimal("0.94527"), Decimal("3.0000")), (bid_price, bid_size)) + self.assertEqual((Decimal("1.0553"), Decimal("3.0000")), (ask_price, ask_size)) + + @patch("hummingbot.client.settings.GatewayConnectionSetting.get_connector_spec_from_market_name") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_price_and_size_limit_calculation_with_slippage_buffer(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock, + get_connector_settings_mock, + get_connector_spec_from_market_name_mock): + self.taker_market.set_balance("COINALPHA", 3) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.05 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.95 + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_taker_volume_factor = Decimal("100") + config_map_raw.order_size_taker_balance_factor = Decimal("100") + config_map_raw.order_size_portfolio_ratio_limit = Decimal("100") + config_map_raw.min_profitability = Decimal("25") + config_map_raw.slippage_buffer = Decimal("0") + config_map_raw.order_amount = Decimal("4") + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + + get_connector_spec_from_market_name_mock.return_value = self.get_mock_gateway_settings() + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + + config_map_with_slippage_buffer_raw = CrossExchangeMarketMakingConfigMap( + maker_market=self.exchange_name_maker, + taker_market=self.exchange_name_taker, + maker_market_trading_pair=self.trading_pairs_maker[0], + taker_market_trading_pair=self.trading_pairs_taker[0], + order_amount=Decimal("4"), + min_profitability=Decimal("25"), + order_size_taker_volume_factor=Decimal("100"), + order_size_taker_balance_factor=Decimal("100"), + order_size_portfolio_ratio_limit=Decimal("100"), + conversion_rate_mode=TakerToMakerConversionRateMode(), + slippage_buffer=Decimal("25"), + ) + config_map_with_slippage_buffer_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("1.0") + config_map_with_slippage_buffer_raw.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal("1.0") + config_map_with_slippage_buffer = ClientConfigAdapter(config_map_with_slippage_buffer_raw) + + strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + strategy_with_slippage_buffer.init_params( + config_map=config_map_with_slippage_buffer, + market_pairs=[self.market_pair], + logging_options=self.logging_options, + ) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, True)) + bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, True, bid_size)) + bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_size(self.market_pair, False)) + ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(self.strategy.get_market_making_price(self.market_pair, False, ask_size)) + ask_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_size(self.market_pair, True)) + slippage_bid_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_price( + self.market_pair, True, slippage_bid_size + )) + slippage_bid_price: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_size(self.market_pair, False)) + slippage_ask_size: Decimal = self.ev_loop.run_until_complete(task) + + task = self.ev_loop.create_task(strategy_with_slippage_buffer.get_market_making_price( + self.market_pair, False, slippage_ask_size + )) + slippage_ask_price: Decimal = self.ev_loop.run_until_complete(task) + + self.assertEqual(Decimal("4"), bid_size) # the user size + self.assertEqual(Decimal("0.76000"), bid_price) # price = bid_VWAP(4) / profitability = 0.95 / 1.25 + self.assertEqual(Decimal("4.0000"), ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1) + self.assertEqual(Decimal("1.3125"), ask_price) # price = ask_VWAP(2.8571) * profitability = 1.05 * 1.25 + self.assertEqual(Decimal("4"), slippage_bid_size) # the user size + self.assertEqual(Decimal("0.76000"), slippage_bid_price) # price = bid_VWAP(4) / profitability = 0.9 / 1.25 + self.assertEqual(Decimal("3.8095"), slippage_ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1.25) + self.assertEqual(Decimal("1.3125"), slippage_ask_price) # price = ask_VWAP(2.2857) * profitability = 1.05 * 1.25 + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_check_if_sufficient_balance_adjusts_including_slippage(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.taker_market.set_balance("WCOINALPHA", 4) + self.taker_market.set_balance("WHBOT", 3) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + True, + 1.15 + ) + self.taker_market.set_prices( + self.trading_pairs_taker[0], + False, + 0.85 + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.order_size_taker_volume_factor = Decimal("100") + config_map_raw.order_size_taker_balance_factor = Decimal("100") + config_map_raw.order_size_portfolio_ratio_limit = Decimal("100") + config_map_raw.min_profitability = Decimal("25") + config_map_raw.slippage_buffer = Decimal("25") + config_map_raw.order_amount = Decimal("4") + config_map = ClientConfigAdapter( + config_map_raw + ) + + strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + strategy_with_slippage_buffer.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options + ) + self.clock.remove_iterator(self.strategy) + self.clock.add_iterator(strategy_with_slippage_buffer) + self.clock.backtest_til(self.start_timestamp + 1) + self.ev_loop.run_until_complete(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(active_maker_bids)) + self.assertEqual(1, len(active_maker_asks)) + + active_bid = active_maker_bids[0][1] + active_ask = active_maker_asks[0][1] + + self.emit_order_created_event(self.maker_market, active_bid) + self.emit_order_created_event(self.maker_market, active_ask) + + self.clock.backtest_til(self.start_timestamp + 2) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.clock.backtest_til(self.start_timestamp + 3) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(active_maker_bids)) + self.assertEqual(1, len(active_maker_asks)) + + active_bid = active_maker_bids[0][1] + active_ask = active_maker_asks[0][1] + bids_quantum = self.taker_market.get_order_size_quantum( + self.trading_pairs_taker[0], active_bid.quantity + ) + asks_quantum = self.taker_market.get_order_size_quantum( + self.trading_pairs_taker[0], active_ask.quantity + ) + + self.taker_market.set_balance("WCOINALPHA", Decimal("4") - bids_quantum) + self.taker_market.set_balance("WHBOT", Decimal("3") - asks_quantum * 1) + + self.clock.backtest_til(self.start_timestamp + 4) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + + active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(0, len(active_maker_bids)) # cancelled + self.assertEqual(0, len(active_maker_asks)) # cancelled + + prev_maker_orders_created_len = len(self.maker_order_created_logger.event_log) + + self.clock.backtest_til(self.start_timestamp + 5) + + if len(self.maker_order_created_logger.event_log) == prev_maker_orders_created_len: + self.async_run_with_timeout(self.maker_order_created_logger.wait_for(BuyOrderCreatedEvent)) + + new_active_maker_bids = strategy_with_slippage_buffer.active_maker_bids + new_active_maker_asks = strategy_with_slippage_buffer.active_maker_asks + + self.assertEqual(1, len(new_active_maker_bids)) + self.assertEqual(1, len(new_active_maker_asks)) + + new_active_bid = new_active_maker_bids[0][1] + new_active_ask = new_active_maker_asks[0][1] + + # Quantum is 0.01, therefore needs to be rounded to 2 decimal places + self.assertEqual(Decimal(str(round(active_bid.quantity - bids_quantum))), round(new_active_bid.quantity)) + self.assertEqual(Decimal(str(round(active_ask.quantity - asks_quantum))), round(new_active_ask.quantity)) + + @patch("hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making." + "CrossExchangeMarketMakingStrategy.is_gateway_market", return_value=True) + @patch.object(MockAMM, "cancel_outdated_orders") + def test_empty_maker_orderbook(self, + cancel_outdated_orders_func: unittest.mock.AsyncMock, + _: unittest.mock.Mock): + self.clock.remove_iterator(self.strategy) + self.clock.remove_iterator(self.maker_market) + self.maker_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap())) + + # Orderbook is empty + self.maker_market.new_empty_order_book(self.trading_pairs_maker[0]) + self.market_pair: MakerTakerMarketPair = MakerTakerMarketPair( + MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), + MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), + ) + + config_map_raw = deepcopy(self.config_map_raw) + config_map_raw.min_profitability = Decimal("0.5") + config_map_raw.adjust_order_enabled = False + config_map_raw.order_amount = Decimal("1") + + config_map = ClientConfigAdapter( + config_map_raw + ) + + self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() + self.strategy.init_params( + config_map=config_map, + market_pairs=[self.market_pair], + logging_options=self.logging_options + ) + self.maker_market.set_balance("COINALPHA", 5) + self.maker_market.set_balance("HBOT", 5) + self.maker_market.set_balance("QCOINALPHA", 5) + self.maker_market.set_quantization_param(QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) + self.clock.add_iterator(self.strategy) + self.clock.add_iterator(self.maker_market) + self.clock.backtest_til(self.start_timestamp + 5) + self.ev_loop.run_until_complete(asyncio.sleep(0.5)) + self.assertEqual(1, len(self.strategy.active_maker_bids)) + self.assertEqual(1, len(self.strategy.active_maker_asks)) + bid_order: LimitOrder = self.strategy.active_maker_bids[0][1] + ask_order: LimitOrder = self.strategy.active_maker_asks[0][1] + # Places orders based on taker orderbook + self.assertEqual(Decimal("0.9452"), bid_order.price) + self.assertEqual(Decimal("1.056"), ask_order.price) + self.assertAlmostEqual(Decimal("1"), round(bid_order.quantity, 4)) + self.assertAlmostEqual(Decimal("1"), round(ask_order.quantity, 4)) diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_start.py b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_start.py new file mode 100644 index 0000000..2a7ef7d --- /dev/null +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making_start.py @@ -0,0 +1,60 @@ +import unittest.mock +from decimal import Decimal + +import hummingbot.strategy.cross_exchange_market_making.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.cross_exchange_market_making.cross_exchange_market_making_config_map_pydantic import ( + CrossExchangeMarketMakingConfigMap, + TakerToMakerConversionRateMode, +) + + +class XEMMStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.client_config_map.strategy_report_interval = 60. + self.markets = { + "binance": ExchangeBase(client_config_map=self.client_config_map), + "kucoin": ExchangeBase(client_config_map=self.client_config_map)} + self.notifications = [] + self.log_errors = [] + + config_map_raw = CrossExchangeMarketMakingConfigMap( + maker_market="binance", + taker_market="kucoin", + maker_market_trading_pair="ETH-USDT", + taker_market_trading_pair="ETH-USDT", + order_amount=1.0, + min_profitability=2.0, + conversion_rate_mode=TakerToMakerConversionRateMode(), + ) + + config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal("1.0") + config_map_raw.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal("1.0") + + self.strategy_config_map = ClientConfigAdapter(config_map_raw) + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + self.assertEqual(self.strategy.order_amount, Decimal("1")) + self.assertEqual(self.strategy.min_profitability, Decimal("0.02")) diff --git a/test/hummingbot/strategy/hedge/__init__.py b/test/hummingbot/strategy/hedge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/hedge/test_config.yml b/test/hummingbot/strategy/hedge/test_config.yml new file mode 100644 index 0000000..38d8d50 --- /dev/null +++ b/test/hummingbot/strategy/hedge/test_config.yml @@ -0,0 +1,35 @@ +strategy: hedge +value_mode: true +hedge_ratio: 1.0 +hedge_interval: 60 +min_trade_size: 0.0 +slippage: 0.02 +hedge_connector: bybit_perpetual_testnet +hedge_markets: +- BTC-USDT +hedge_offsets: +- 0.0 +hedge_leverage: 1 +hedge_position_mode: ONEWAY +connector_0: + connector: binance + markets: + - BTC-USDT + offsets: + - 0.0 +connector_1: + connector: null + markets: null + offsets: null +connector_2: + connector: null + markets: null + offsets: null +connector_3: + connector: null + markets: null + offsets: null +connector_4: + connector: null + markets: null + offsets: null diff --git a/test/hummingbot/strategy/hedge/test_hedge.py b/test/hummingbot/strategy/hedge/test_hedge.py new file mode 100644 index 0000000..6249f93 --- /dev/null +++ b/test/hummingbot/strategy/hedge/test_hedge.py @@ -0,0 +1,217 @@ +import unittest +from decimal import Decimal +from test.mock.mock_perp_connector import MockPerpConnector + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.core.data_type.common import PositionMode, PositionSide +from hummingbot.strategy.hedge.hedge import HedgeStrategy +from hummingbot.strategy.hedge.hedge_config_map_pydantic import HedgeConfigMap +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class HedgeConfigMapPydanticTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + def setUp(self) -> None: + super().setUp() + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + base_asset = "BTC" + quote_asset = "USDT" + self.markets = { + "kucoin": MockPaperExchange(client_config_map=self.client_config_map), + "binance": MockPaperExchange(client_config_map=self.client_config_map), + "binance_perpetual": MockPerpConnector( + client_config_map=self.client_config_map, + buy_collateral_token=quote_asset, + sell_collateral_token=quote_asset + ), + } + + trading_pair = f"{base_asset}-{quote_asset}" + perp_leverage = 25 + order_book_config = { + "trading_pair": trading_pair, + "mid_price": 100, + "min_price": 1, + "max_price": 200, + "price_step_size": 1, + "volume_step_size": 1, + + } + self.markets["kucoin"].set_balance(base_asset, 1) + self.markets["kucoin"].set_balanced_order_book(**order_book_config) + self.markets["binance"].set_balance(base_asset, 0.5) + self.markets["binance"].set_balanced_order_book(**order_book_config) + self.markets["binance_perpetual"].set_balance(base_asset, 0) + self.markets["binance_perpetual"].set_balance(quote_asset, 100000) + self.markets["binance_perpetual"].set_balanced_order_book(**order_book_config) + self.markets["binance_perpetual"].set_leverage(trading_pair, perp_leverage) + self.markets["binance_perpetual"].set_position_mode(PositionMode.ONEWAY) + self.markets["binance_perpetual"]._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.BOTH, + Decimal("0"), + Decimal("95"), + Decimal("-1"), + self.markets["binance_perpetual"].get_leverage(trading_pair) + ) + self.market_trading_pairs = { + "kucoin": MarketTradingPairTuple( + self.markets["kucoin"], + trading_pair, + *trading_pair.split("-") + ), + "binance": MarketTradingPairTuple( + self.markets["binance"], + trading_pair, + *trading_pair.split("-") + ), + "binance_perpetual": MarketTradingPairTuple( + self.markets["binance_perpetual"], + trading_pair, + *trading_pair.split("-") + ), + } + self.config_map = self.get_default_map() + self.offsets = { + self.market_trading_pairs["kucoin"]: Decimal("0"), + self.market_trading_pairs["binance"]: Decimal("0"), + self.market_trading_pairs["binance_perpetual"]: Decimal("0"), + } + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + self.start_timestamp: float = start.timestamp() + end_timestamp: float = float("nan") + self.clock: Clock = Clock(ClockMode.BACKTEST, 1, self.start_timestamp, end_timestamp) + self.clock.add_iterator(self.markets["kucoin"]) + self.clock.add_iterator(self.markets["binance"]) + self.clock.add_iterator(self.markets["binance_perpetual"]) + self.clock.backtest_til(self.start_timestamp + 1) + + def get_default_map(self) -> HedgeConfigMap: + config_settings = { + 'strategy': 'hedge', + 'value_mode': True, + 'hedge_ratio': 1.0, + 'hedge_interval': 60.0, + 'min_trade_size': 0.0, + 'slippage': 0.02, + 'hedge_offsets': [0], + 'hedge_leverage': 25, + 'hedge_position_mode': 'ONEWAY', + "hedge_connector": 'binance_perpetual', + "hedge_markets": 'BTC-USDT', + "connector_0": 'n', + "connector_1": 'n', + "connector_2": 'n', + "connector_3": 'n', + "connector_4": 'n', + } + return HedgeConfigMap(**config_settings) + + def test_hedge_ratio(self): + ... + + def test_offsets(self): + # value mode = True + strategy = HedgeStrategy( + config_map = self.config_map, + hedge_market_pairs = [self.market_trading_pairs["binance_perpetual"]], + market_pairs = [self.market_trading_pairs["kucoin"], self.market_trading_pairs["binance"]], + offsets = self.offsets, + ) + + self.assertEqual(strategy._offsets, self.offsets) + is_buy, value = strategy.get_hedge_direction_and_value() + + self.assertEqual(is_buy, False) + self.assertEqual(value, Decimal("150")) + strategy._offsets[self.market_trading_pairs["kucoin"]] = Decimal("1") + is_buy, value = strategy.get_hedge_direction_and_value() + self.assertEqual(is_buy, False) + self.assertEqual(value, Decimal("250")) + strategy._offsets[self.market_trading_pairs["kucoin"]] = Decimal("-1") + is_buy, value = strategy.get_hedge_direction_and_value() + self.assertEqual(is_buy, False) + self.assertEqual(value, Decimal("50")) + # value mode = False + self.config_map.value_mode = False + strategy = HedgeStrategy( + config_map = self.config_map, + hedge_market_pairs = [self.market_trading_pairs["binance_perpetual"]], + market_pairs = [self.market_trading_pairs["kucoin"], self.market_trading_pairs["binance"]], + offsets = self.offsets, + ) + for hedge_market, market_list in strategy._market_pair_by_asset.items(): + is_buy, amount_to_hedge = strategy.get_hedge_direction_and_amount_by_asset(hedge_market, market_list) + self.assertEqual(is_buy, False) + self.assertEqual(amount_to_hedge, Decimal("0.5")) + strategy._offsets[self.market_trading_pairs["kucoin"]] = Decimal("1") + for hedge_market, market_list in strategy._market_pair_by_asset.items(): + is_buy, amount_to_hedge = strategy.get_hedge_direction_and_amount_by_asset(hedge_market, market_list) + self.assertEqual(is_buy, False) + self.assertEqual(amount_to_hedge, Decimal("2.5")) + + def test_hedge_by_value(self): + self.config_map.slippage = Decimal("-0.2") + strategy = HedgeStrategy( + config_map = self.config_map, + hedge_market_pairs = [self.market_trading_pairs["binance_perpetual"]], + market_pairs = [self.market_trading_pairs["kucoin"], self.market_trading_pairs["binance"]], + offsets = self.offsets, + ) + self.clock.add_iterator(strategy) + self.clock.add_iterator(strategy.order_tracker) + strategy.order_tracker.start(self.clock) + strategy.start(self.clock, self.start_timestamp) + + is_buy, value_to_hedge = strategy.get_hedge_direction_and_value() + self.assertEqual(is_buy, False) + self.assertEqual(value_to_hedge, Decimal("150")) + price, amount = strategy.calculate_hedge_price_and_amount(is_buy, value_to_hedge) + self.assertEqual(price, Decimal("120")) + self.assertEqual(amount, Decimal("1.5")) + order_candidates = strategy.get_perpetual_order_candidates( + self.market_trading_pairs["binance_perpetual"], + is_buy, + price, + amount + ) + strategy.place_orders(strategy._hedge_market_pair, order_candidates) + self.assertEqual(len(strategy.active_orders), 1) + order_candidates = strategy.get_spot_order_candidates( + self.market_trading_pairs["kucoin"], + is_buy, + price, + amount + ) + strategy.place_orders(self.market_trading_pairs["kucoin"], order_candidates) + self.assertEqual(len(strategy.active_orders), 2) + + def test_hedge_by_amount(self): + trading_pair = self.market_trading_pairs["binance_perpetual"].trading_pair + self.markets["binance_perpetual"]._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.BOTH, + Decimal("0"), + Decimal("95"), + Decimal("-10"), + self.markets["binance_perpetual"].get_leverage(trading_pair) + ) + self.config_map.slippage = Decimal("-0.2") + self.config_map.value_mode = False + strategy = HedgeStrategy( + config_map = self.config_map, + hedge_market_pairs = [self.market_trading_pairs["binance_perpetual"]], + market_pairs = [self.market_trading_pairs["kucoin"], self.market_trading_pairs["binance"]], + offsets = self.offsets, + ) + self.assertIsNone(strategy.hedge_by_amount()) diff --git a/test/hummingbot/strategy/hedge/test_hedge_config_map.py b/test/hummingbot/strategy/hedge/test_hedge_config_map.py new file mode 100644 index 0000000..74f7573 --- /dev/null +++ b/test/hummingbot/strategy/hedge/test_hedge_config_map.py @@ -0,0 +1,127 @@ +import unittest +from decimal import Decimal +from pathlib import Path +from typing import Dict +from unittest.mock import patch + +import yaml + +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.settings import ConnectorSetting, ConnectorType +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.strategy.hedge.hedge_config_map_pydantic import HedgeConfigMap, MarketConfigMap + + +class HedgeConfigMapPydanticTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.base_asset = "BTC" + cls.quote_asset = "USDT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + cls.hedge_connector = "bybit_perpetual_testnet" + cls.connector = "binance" + + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_connector_settings") + def setUp(self, get_connector_settings_mock, get_exchange_names_mock) -> None: + super().setUp() + config_settings = self.get_default_map() + get_exchange_names_mock.return_value = set(self.get_mock_connector_settings().keys()) + get_connector_settings_mock.return_value = self.get_mock_connector_settings() + self.config_map = ClientConfigAdapter(HedgeConfigMap(**config_settings)) + + def get_mock_connector_settings(self): + conf_var_connector_maker = ConfigVar(key='mock_paper_exchange', prompt="") + conf_var_connector_maker.value = 'mock_paper_exchange' + + settings = { + "mock_paper_exchange": ConnectorSetting( + name='mock_paper_exchange', + type=ConnectorType.Exchange, + example_pair='BTC-ETH', + centralised=True, + use_ethereum_wallet=False, + trade_fee_schema=TradeFeeSchema( + percent_fee_token=None, + maker_percent_fee_decimal=Decimal('0.001'), + taker_percent_fee_decimal=Decimal('0.001'), + buy_percent_fee_deducted_from_returns=False, + maker_fixed_fees=[], + taker_fixed_fees=[]), + config_keys={ + 'connector': conf_var_connector_maker + }, + is_sub_domain=False, + parent_name=None, + domain_parameter=None, + use_eth_gas_lookup=False) + } + + return settings + + def get_default_map(self) -> Dict[str, str]: + config_settings = { + "hedge_connector": self.hedge_connector, + "hedge_markets": self.trading_pair, + "connector_0": { + "connector": self.connector, + "markets": [self.trading_pair], + "offsets": [0]}, + "connector_1": 'n', + "connector_2": 'n', + "connector_3": 'n', + "connector_4": 'n', + } + return config_settings + + def test_hedge_markets_prompt(self): + self.config_map.hedge_connector = self.connector + self.config_map.hedge_markets = self.trading_pair + self.config_map.value_mode = True + self.assertEqual( + self.config_map.hedge_markets_prompt(self.config_map)[:12], + "Value mode: ", + ) + self.config_map.value_mode = False + self.assertEqual( + self.config_map.hedge_markets_prompt(self.config_map)[:13], + "Amount mode: " + + ) + + def test_hedge_offsets_prompt(self): + self.config_map.hedge_connector = self.connector + self.config_map.hedge_markets = self.trading_pair + self.config_map.value_mode = True + base = self.trading_pair.split("-")[0] + self.assertEqual( + self.config_map.hedge_offsets_prompt(self.config_map), + f"Enter the offset for {base}. (Example: 0.1 = +0.1{base} used in calculation of hedged value)" + ) + self.config_map.value_mode = False + self.assertEqual( + self.config_map.hedge_offsets_prompt(self.config_map), + "Enter the offsets to use to hedge the markets comma seperated. " + "(Example: 0.1,-0.2 = +0.1BTC,-0.2ETH, 0LTC will be offset for the exchange amount " + "if markets is BTC-USDT,ETH-USDT,LTC-USDT)" + ) + + def test_trading_pair_prompt(self): + connector_map = MarketConfigMap( + connector=self.connector, + markets = self.trading_pair, + offsets = [Decimal("0")] + ) + connector_map.trading_pair_prompt(connector_map) + + def test_load_configs_from_yaml(self): + cur_dir = Path(__file__).parent + f_path = cur_dir / "test_config.yml" + + with open(f_path, "r") as file: + data = yaml.safe_load(file) + + loaded_config_map = ClientConfigAdapter(HedgeConfigMap(**data)) + self.assertIsInstance(loaded_config_map, ClientConfigAdapter) diff --git a/test/hummingbot/strategy/hedge/test_hedge_start.py b/test/hummingbot/strategy/hedge/test_hedge_start.py new file mode 100644 index 0000000..7069d8a --- /dev/null +++ b/test/hummingbot/strategy/hedge/test_hedge_start.py @@ -0,0 +1,78 @@ +import unittest.mock +from decimal import Decimal + +import hummingbot.strategy.hedge.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.hedge.hedge_config_map_pydantic import EmptyMarketConfigMap, HedgeConfigMap, MarketConfigMap +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class HedgeStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.client_config_map.strategy_report_interval = 60. + self.markets = { + "binance": ExchangeBase(client_config_map=self.client_config_map), + "kucoin": ExchangeBase(client_config_map=self.client_config_map), + "ascend_ex": ExchangeBase(client_config_map=self.client_config_map) + } + self.notifications = [] + self.log_errors = [] + + config_map_raw = HedgeConfigMap( + value_mode=True, + hedge_ratio=Decimal("1"), + hedge_interval=60, + min_trade_size=Decimal("0"), + slippage=Decimal("0.02"), + hedge_connector="binance", + hedge_markets=["BTC-USDT"], + hedge_offsets=[Decimal("0.01")], + hedge_leverage=1, + hedge_position_mode="ONEWAY", + connector_0=MarketConfigMap( + connector="kucoin", + markets=["ETH-USDT"], + offsets=[Decimal("0.02")], + ), + connector_1=MarketConfigMap( + connector="ascend_ex", + markets=["ETH-USDT", "BTC-USDT"], + offsets=[Decimal("0.03")], + ), + connector_2=EmptyMarketConfigMap(), + connector_3=EmptyMarketConfigMap(), + connector_4=EmptyMarketConfigMap(), + + ) + self.strategy_config_map = ClientConfigAdapter(config_map_raw) + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + expected_offsets = { + MarketTradingPairTuple(self.markets["binance"], "BTC-USDT", "BTC", "USDT"): Decimal("0.01"), + MarketTradingPairTuple(self.markets["kucoin"], "ETH-USDT", "ETH", "USDT"): Decimal("0.02"), + MarketTradingPairTuple(self.markets["ascend_ex"], "ETH-USDT", "ETH", "USDT"): Decimal("0.03"), + MarketTradingPairTuple(self.markets["ascend_ex"], "BTC-USDT", "BTC", "USDT"): Decimal("0"), + + } + self.assertEqual(self.strategy._offsets, expected_offsets) + self.assertEqual(self.strategy._hedge_market_pairs, self.market_trading_pair_tuples[0:1]) + self.assertEqual(self.strategy._market_pairs, self.market_trading_pair_tuples[1:]) diff --git a/test/hummingbot/strategy/liquidity_mining/__init__.py b/test/hummingbot/strategy/liquidity_mining/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining.py b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining.py new file mode 100644 index 0000000..5478562 --- /dev/null +++ b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining.py @@ -0,0 +1,592 @@ +import unittest.mock +from decimal import Decimal +from typing import Dict, List, Optional + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookTradeEvent +from hummingbot.strategy.liquidity_mining.data_types import PriceSize, Proposal +from hummingbot.strategy.liquidity_mining.liquidity_mining import LiquidityMiningStrategy +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + + +class LiquidityMiningTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + market_infos: Dict[str, MarketTradingPairTuple] = {} + + @staticmethod + def create_market(trading_pairs: List[str], mid_price, balances: Dict[str, int]) -> \ + (MockPaperExchange, Dict[str, MarketTradingPairTuple]): + """ + Create a BacktestMarket and marketinfo dictionary to be used by the liquidity mining strategy + """ + market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + market_infos: Dict[str, MarketTradingPairTuple] = {} + + for trading_pair in trading_pairs: + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + market.set_balanced_order_book(trading_pair=trading_pair, + mid_price=mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + market.set_quantization_param(QuantizationParams(trading_pair, 6, 6, 6, 6)) + market_infos[trading_pair] = MarketTradingPairTuple(market, trading_pair, base_asset, quote_asset) + + for asset, value in balances.items(): + market.set_balance(asset, value) + + return market, market_infos + + @staticmethod + def create_empty_ob_market(trading_pairs: List[str], mid_price, balances: Dict[str, int]) -> \ + (MockPaperExchange, Dict[str, MarketTradingPairTuple]): + """ + Create a BacktestMarket and marketinfo dictionary to be used by the liquidity mining strategy + """ + market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + market_infos: Dict[str, MarketTradingPairTuple] = {} + + _ = mid_price + for trading_pair in trading_pairs: + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + market.new_empty_order_book(trading_pair=trading_pair, ) + market.set_quantization_param(QuantizationParams(trading_pair, 6, 6, 6, 6)) + market_infos[trading_pair] = MarketTradingPairTuple(market, trading_pair, base_asset, quote_asset) + + for asset, value in balances.items(): + market.set_balance(asset, value) + + return market, market_infos + + def setUp(self) -> None: + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + + self.mid_price = 100 + self.bid_spread = 0.01 + self.ask_spread = 0.01 + self.order_refresh_time = 1 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BTC"])) + market, market_infos = self.create_market(trading_pairs, self.mid_price, {"USDT": 5000, "ETH": 500, "BTC": 100}) + self.market = market + self.market_infos = market_infos + + self.clock.add_iterator(self.market) + self.order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + self.default_strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + self.default_strategy.init_params( + client_config_map=client_config_map, + exchange=self.market, + market_infos=self.market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + max_order_age=3, + ) + + def simulate_maker_market_trade( + self, is_buy: bool, quantity: Decimal, price: Decimal, trading_pair: str, + market: Optional[MockPaperExchange] = None, + ): + """ + simulate making a trade, broadcasts a trade event + """ + if market is None: + market = self.market + order_book: OrderBook = market.get_order_book(trading_pair) + trade_event = OrderBookTradeEvent( + trading_pair, + self.clock.current_timestamp, + TradeType.BUY if is_buy else TradeType.SELL, + price, + quantity + ) + order_book.apply_trade(trade_event) + + @staticmethod + def has_limit_order_type(limit_orders: List[LimitOrder], trading_pair: str, is_buy: bool) -> bool: + for limit_order in limit_orders: + if limit_order.trading_pair == trading_pair and limit_order.is_buy == is_buy: + return True + return False + + @staticmethod + def has_limit_order(limit_orders, trading_pair, is_buy, price, quantity): + """ + An internal method to simplify asserting if a limit order exists + """ + for limit_order in limit_orders: + if limit_order.trading_pair == trading_pair and \ + abs(float(limit_order.price - price)) <= 0.01 and \ + abs(float(limit_order.quantity - quantity)) <= 0.01: + tag = limit_order.client_order_id.split('://')[0] + if tag == 'buy' and is_buy: + return True + if tag == 'sell' and not is_buy: + return True + return False + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_simulate_maker_market_trade(self, estimate_fee_mock): + """ + Test that we can set up a liquidity mining strategy, and a trade + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate + self.clock.add_iterator(self.default_strategy) + self.clock.backtest_til(self.start_timestamp) + + # assert that there are no active trades on initialization and before clock has moved forward + self.assertEqual(0, len(self.default_strategy.active_orders)) + + # advance by one tick, the strategy will initiate two orders per pair + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(4, len(self.default_strategy.active_orders)) + + # assert that a buy and sell order is made for each pair + self.assertTrue( + self.has_limit_order(self.default_strategy.active_orders, 'ETH-USDT', True, Decimal(99.95), Decimal(2.0))) + self.assertTrue( + self.has_limit_order(self.default_strategy.active_orders, 'ETH-USDT', False, Decimal(100.05), Decimal(2.0))) + self.assertTrue( + self.has_limit_order(self.default_strategy.active_orders, 'ETH-BTC', True, Decimal(99.95), Decimal(1.0005))) + self.assertTrue( + self.has_limit_order(self.default_strategy.active_orders, 'ETH-BTC', False, Decimal(100.05), Decimal(2))) + + # Simulate buy order fill + self.clock.backtest_til(self.start_timestamp + 8) + self.simulate_maker_market_trade(False, Decimal("50"), Decimal("1"), "ETH-USDT") + self.assertEqual(3, len(self.default_strategy.active_orders)) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_multiple_markets(self, estimate_fee_mock): + """ + Liquidity Mining supports one base asset but multiple quote assets. This shows that the user can successfully + provide liquidity for two different pairs and the market can execute the other side of them. + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate + self.clock.add_iterator(self.default_strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + # ETH-USDT + self.simulate_maker_market_trade(False, 50, 1, "ETH-USDT") + self.clock.backtest_til(self.start_timestamp + 8) + + # ETH-BTC + self.simulate_maker_market_trade(False, 50, 1, "ETH-BTC") + self.clock.backtest_til(self.start_timestamp + 16) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_tolerance_level(self, estimate_fee_mock): + """ + Test tolerance level + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate strategy and add active orders + self.clock.add_iterator(self.default_strategy) + self.clock.backtest_til(self.start_timestamp + 9) + + # the order tolerance is 1% + # set the orders to the same values + proposal = Proposal("ETH-USDT", PriceSize(100, 1), PriceSize(100, 1)) + self.assertTrue(self.default_strategy.is_within_tolerance(self.default_strategy.active_orders, proposal)) + + # update orders to withint the tolerance + proposal = Proposal("ETH-USDT", PriceSize(109, 1), PriceSize(91, 1)) + self.assertTrue(self.default_strategy.is_within_tolerance(self.default_strategy.active_orders, proposal)) + + # push the orders beyond the tolerance, this proposal should return False + proposal = Proposal("ETH-USDT", PriceSize(150, 1), PriceSize(50, 1)) + self.assertFalse(self.default_strategy.is_within_tolerance(self.default_strategy.active_orders, proposal)) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_budget_allocation(self, estimate_fee_mock): + """ + Liquidity mining strategy budget allocation is different from pmm, it depends on the token base and it splits + its budget between the quote tokens. + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate + usdt_balance = 1000 + busd_balance = 900 + eth_balance = 100 + btc_balance = 10 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"])) + market, market_infos = self.create_market(trading_pairs, 100, + {"USDT": usdt_balance, "BUSD": busd_balance, "ETH": eth_balance, + "BTC": btc_balance}) + + strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + ) + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 10) + + # there should be a buy and sell budget for each pair + self.assertEqual(len(strategy.sell_budgets), 3) + self.assertEqual(len(strategy.buy_budgets), 3) + + # the buy budgets use all of the available balance for the quote tokens + self.assertEqual(strategy.buy_budgets["ETH-USDT"], usdt_balance) + self.assertEqual(strategy.buy_budgets["ETH-BTC"], btc_balance) + self.assertEqual(strategy.buy_budgets["ETH-BUSD"], busd_balance) + + # the sell budget tries to evenly split the base token between the quote tokens + self.assertLess(strategy.sell_budgets["ETH-USDT"], eth_balance * 0.4) + self.assertLess(strategy.sell_budgets["ETH-BTC"], eth_balance * 0.4) + self.assertLess(strategy.sell_budgets["ETH-BUSD"], eth_balance * 0.4) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_budget_allocation_empty_ob(self, estimate_fee_mock): + """ + Liquidity mining strategy budget allocation is different from pmm, it depends on the token base and it splits + its budget between the quote tokens. + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate + usdt_balance = 1000 + busd_balance = 900 + eth_balance = 100 + btc_balance = 10 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"])) + market, market_infos = self.create_empty_ob_market(trading_pairs, 100, + {"USDT": usdt_balance, "BUSD": busd_balance, + "ETH": eth_balance, "BTC": btc_balance}) + + strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + ) + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 10) + + # there should be a buy and sell budget for each pair + self.assertEqual(len(strategy.sell_budgets), 0) + self.assertEqual(len(strategy.buy_budgets), 0) + + # No buy_budget calculated since no order books + self.assertFalse("ETH-USDT" in strategy.buy_budgets) + self.assertFalse("ETH-BTC" in strategy.buy_budgets) + self.assertFalse("ETH-BUSD" in strategy.buy_budgets) + + # No buy_budget calculated since no order books + self.assertFalse("ETH-USDT" in strategy.sell_budgets) + self.assertFalse("ETH-BTC" in strategy.sell_budgets) + self.assertFalse("ETH-BUSD" in strategy.sell_budgets) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_budget_allocation_partially_empty_ob(self, estimate_fee_mock): + """ + Liquidity mining strategy budget allocation is different from pmm, it depends on the token base and it splits + its budget between the quote tokens. + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate + usdt_balance = 1000 + busd_balance = 900 + eth_balance = 100 + btc_balance = 10 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD"])) + _, market_eob_infos = self.create_empty_ob_market(trading_pairs, 100, + {"USDT": usdt_balance, "BUSD": busd_balance, + "ETH": eth_balance}) + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["BTC"])) + market, market_infos = self.create_market(trading_pairs, 100, {"BTC": btc_balance}) + market_infos.update(market_eob_infos) + + strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + ) + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 10) + + # there should be a buy and sell budget for each pair + self.assertEqual(len(strategy.sell_budgets), 1) + self.assertEqual(len(strategy.buy_budgets), 1) + + # Only BTC buy_budget calculated since no order books + self.assertFalse("ETH-USDT" in strategy.buy_budgets) + self.assertTrue("ETH-BTC" in strategy.buy_budgets) + self.assertFalse("ETH-BUSD" in strategy.buy_budgets) + + # Only BTC buy_budget calculated since no order books + self.assertFalse("ETH-USDT" in strategy.sell_budgets) + self.assertTrue("ETH-BTC" in strategy.sell_budgets) + self.assertFalse("ETH-BUSD" in strategy.sell_budgets) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_inventory_skew(self, estimate_fee_mock): + """ + When inventory_skew_enabled is true, the strategy will try to balance the amounts of base to match it + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate with similar balances so the skew is obvious + usdt_balance = 1000 + busd_balance = 1000 + eth_balance = 1000 + btc_balance = 1000 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT", "BUSD", "BTC"])) + market, market_infos = self.create_market(trading_pairs, 100, + {"USDT": usdt_balance, "BUSD": busd_balance, "ETH": eth_balance, + "BTC": btc_balance}) + + skewed_base_strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + skewed_base_strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=True, + target_base_pct=Decimal(0.1), # less base, more quote + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + ) + + unskewed_strategy = LiquidityMiningStrategy() + unskewed_strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + order_refresh_time=5, + target_base_pct=Decimal(0.1), # this does nothing when inventory_skew_enabled is False + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + ) + + self.clock.add_iterator(skewed_base_strategy) + self.clock.backtest_til(self.start_timestamp + 10) + self.clock.add_iterator(unskewed_strategy) + self.clock.backtest_til(self.start_timestamp + 20) + + # iterate through pairs in skewed and unskewed strategy + for unskewed_order in unskewed_strategy.active_orders: + for skewed_base_order in skewed_base_strategy.active_orders: + # if the trading_pair and trade type are the same, compare them + if skewed_base_order.trading_pair == unskewed_order.trading_pair and \ + skewed_base_order.is_buy == unskewed_order.is_buy: + if skewed_base_order.is_buy: + # the skewed strategy tries to buy more quote thant the unskewed one + self.assertGreater(skewed_base_order.price, unskewed_order.price) + else: + # trying to keep less base + self.assertLessEqual(skewed_base_order.price, unskewed_order.price) + + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.MarketTradingPairTuple.get_mid_price') + @unittest.mock.patch('hummingbot.strategy.liquidity_mining.liquidity_mining.build_trade_fee') + def test_volatility(self, estimate_fee_mock, get_mid_price_mock): + """ + Assert that volatility information is updated after the expected number of intervals + """ + estimate_fee_mock.return_value = AddedToCostTradeFee( + percent=0, flat_fees=[TokenAmount('ETH', Decimal(0.00005))] + ) + + # initiate with similar balances so the skew is obvious + usdt_balance = 1000 + eth_balance = 1000 + + trading_pairs = list(map(lambda quote_asset: "ETH-" + quote_asset, ["USDT"])) + market, market_infos = self.create_market(trading_pairs, 100, {"USDT": usdt_balance, "ETH": eth_balance}) + + strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + strategy.init_params( + client_config_map=client_config_map, + exchange=market, + market_infos=market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), # less base, more quote + order_refresh_time=1, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + # volatility_interval=2, + # avg_volatility_period=2, + # volatility_to_spread_multiplier=2, + ) + + get_mid_price_mock.return_value = Decimal(100.0) + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + # update prices to create volatility after 2 intervals + get_mid_price_mock.return_value = Decimal(105.0) + self.clock.backtest_til(self.start_timestamp + 2) + + get_mid_price_mock.return_value = Decimal(110) + self.clock.backtest_til(self.start_timestamp + 3) + + # assert that volatility is none zero + self.assertAlmostEqual(float(strategy.market_status_df().loc[0, 'Volatility'].strip('%')), 10.00, delta=0.1) + + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotApplication.main_application') + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotCLI') + def test_strategy_with_default_cfg_does_not_send_in_app_notifications(self, cli_class_mock, + main_application_function_mock): + messages = [] + cli_logs = [] + + cli_instance = cli_class_mock.return_value + cli_instance.log.side_effect = lambda message: cli_logs.append(message) + + notifier_mock = unittest.mock.MagicMock() + notifier_mock.add_msg_to_queue.side_effect = lambda message: messages.append(message) + + hummingbot_application = HummingbotApplication() + hummingbot_application.notifiers.append(notifier_mock) + main_application_function_mock.return_value = hummingbot_application + + self.clock.add_iterator(self.default_strategy) + self.clock.backtest_til(self.start_timestamp + 10) + + self.default_strategy.notify_hb_app("Test message") + self.default_strategy.notify_hb_app_with_timestamp("Test message") + + self.assertEqual(len(cli_logs), 0) + self.assertEqual(len(messages), 0) + + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotApplication.main_application') + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotCLI') + def test_strategy_sends_in_app_notifications(self, cli_class_mock, main_application_function_mock): + messages = [] + cli_logs = [] + + cli_instance = cli_class_mock.return_value + cli_instance.log.side_effect = lambda message: cli_logs.append(message) + + notifier_mock = unittest.mock.MagicMock() + notifier_mock.add_msg_to_queue.side_effect = lambda message: messages.append(message) + + hummingbot_application = HummingbotApplication() + hummingbot_application.notifiers.append(notifier_mock) + main_application_function_mock.return_value = hummingbot_application + + strategy = self.default_strategy = LiquidityMiningStrategy() + client_config_map = ClientConfigMap() + self.default_strategy.init_params( + client_config_map=client_config_map, + exchange=self.market, + market_infos=self.market_infos, + token="ETH", + order_amount=Decimal(2), + spread=Decimal(0.0005), + inventory_skew_enabled=False, + target_base_pct=Decimal(0.5), + order_refresh_time=5, + order_refresh_tolerance_pct=Decimal(0.1), # tolerance of 10 % change + max_order_age=3, + hb_app_notification=True + ) + + timestamp = self.start_timestamp + 10 + self.clock.add_iterator(strategy) + self.clock.backtest_til(timestamp) + + self.default_strategy.notify_hb_app("Test message") + self.default_strategy.notify_hb_app_with_timestamp("Test message 2") + + self.assertIn("Test message", cli_logs) + self.assertIn("Test message", messages) + + self.assertIn(f"({pd.Timestamp.fromtimestamp(timestamp)}) Test message 2", cli_logs) + self.assertIn(f"({pd.Timestamp.fromtimestamp(timestamp)}) Test message 2", messages) diff --git a/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_config_map.py b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_config_map.py new file mode 100644 index 0000000..c9f5012 --- /dev/null +++ b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_config_map.py @@ -0,0 +1,80 @@ +from unittest import TestCase + +import hummingbot.strategy.liquidity_mining.liquidity_mining_config_map as liquidity_mining_config_map_module +from hummingbot.strategy.liquidity_mining.liquidity_mining_config_map import ( + liquidity_mining_config_map as strategy_cmap +) +from test.hummingbot.strategy import assign_config_default + + +class LiquidityMiningConfigMapTests(TestCase): + + def test_markets_validation(self): + # Correct markets + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,ETH-USDT"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT, ETH-USDT"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT , ETH-USDT"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("btc-usdt"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-usdt"), None) + self.assertEqual(liquidity_mining_config_map_module.market_validate("btc-USDT"), None) + + # Incorrect markets + self.assertEqual(liquidity_mining_config_map_module.market_validate(""), "Invalid market(s). The given entry is empty.") + + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,"), "Invalid markets. The given entry contains an empty market.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,,"), "Invalid markets. The given entry contains an empty market.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,,ETH-USDT"), "Invalid markets. The given entry contains an empty market.") + + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT-ETH"), "Invalid market. BTC-USDT-ETH doesn't contain exactly 2 tickers.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,BTC-USDT-ETH"), "Invalid market. BTC-USDT-ETH doesn't contain exactly 2 tickers.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("btc-usdt-eth"), "Invalid market. BTC-USDT-ETH doesn't contain exactly 2 tickers.") + + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC- "), "Invalid market. Ticker has an invalid length.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,BTC- "), "Invalid market. Ticker has an invalid length.") + + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-US#DT"), "Invalid market. Ticker US#DT contains invalid characters.") + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,BTC-ETH^"), "Invalid market. Ticker ETH^ contains invalid characters.") + + self.assertEqual(liquidity_mining_config_map_module.market_validate("BTC-USDT,BTC-ETH,BTC-USDT"), "Duplicate market BTC-USDT.") + + def test_token_validation(self): + assign_config_default(strategy_cmap) + + # Correct tokens + strategy_cmap.get("markets").value = "BTC-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "BTC-USDT,ETH-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "BTC-USDT, ETH-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "BTC-USDT , ETH-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "btc-usdt" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "BTC-usdt" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + strategy_cmap.get("markets").value = "btc-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("BTC"), None) + self.assertEqual(liquidity_mining_config_map_module.token_validate("btc"), None) + + # Incorrect tokens + strategy_cmap.get("markets").value = "BTC-USDT" + self.assertEqual(liquidity_mining_config_map_module.token_validate("ETH"), "Invalid token. ETH is not one of BTC,USDT") + self.assertEqual(liquidity_mining_config_map_module.token_validate("eth"), "Invalid token. ETH is not one of BTC,USDT") + + strategy_cmap.get("markets").value = "btc-usdt" + self.assertEqual(liquidity_mining_config_map_module.token_validate("ETH"), "Invalid token. ETH is not one of BTC,USDT") + self.assertEqual(liquidity_mining_config_map_module.token_validate("eth"), "Invalid token. ETH is not one of BTC,USDT") diff --git a/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_start.py b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_start.py new file mode 100644 index 0000000..20109cb --- /dev/null +++ b/test/hummingbot/strategy/liquidity_mining/test_liquidity_mining_start.py @@ -0,0 +1,70 @@ +import unittest.mock +from decimal import Decimal +from test.hummingbot.strategy import assign_config_default + +import hummingbot.strategy.liquidity_mining.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.liquidity_mining.liquidity_mining_config_map import ( + liquidity_mining_config_map as strategy_cmap, +) + + +class LiquidityMiningStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.markets = {"binance": ExchangeBase(client_config_map=ClientConfigAdapter(ClientConfigMap()))} + self.notifications = [] + self.log_errors = [] + assign_config_default(strategy_cmap) + strategy_cmap.get("exchange").value = "binance" + strategy_cmap.get("markets").value = "BTC-USDT,ETH-USDT" + strategy_cmap.get("token").value = "USDT" + strategy_cmap.get("order_amount").value = Decimal("1") + strategy_cmap.get("spread").value = Decimal("2") + + strategy_cmap.get("inventory_skew_enabled").value = False + strategy_cmap.get("target_base_pct").value = Decimal("50") + strategy_cmap.get("order_refresh_time").value = 60. + strategy_cmap.get("order_refresh_tolerance_pct").value = Decimal("1.5") + strategy_cmap.get("inventory_range_multiplier").value = Decimal("2") + strategy_cmap.get("volatility_interval").value = 30 + strategy_cmap.get("avg_volatility_period").value = 5 + strategy_cmap.get("volatility_to_spread_multiplier").value = Decimal("1.1") + strategy_cmap.get("max_spread").value = Decimal("4") + strategy_cmap.get("max_order_age").value = 300. + self.client_config_map = ClientConfigMap() + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + self.assertEqual(self.strategy._order_amount, Decimal("1")) + self.assertEqual(self.strategy._spread, Decimal("0.02")) + + self.assertEqual(self.strategy._inventory_skew_enabled, False) + self.assertEqual(self.strategy._target_base_pct, Decimal("0.5")) + self.assertEqual(self.strategy._order_refresh_time, 60.) + self.assertEqual(self.strategy._order_refresh_tolerance_pct, Decimal("0.015")) + self.assertEqual(self.strategy._inventory_range_multiplier, Decimal("2")) + self.assertEqual(self.strategy._volatility_interval, 30) + self.assertEqual(self.strategy._avg_volatility_period, 5) + self.assertEqual(self.strategy._volatility_to_spread_multiplier, Decimal("1.1")) + self.assertEqual(self.strategy._max_spread, Decimal("0.04")) + self.assertEqual(self.strategy._max_order_age, 300.) diff --git a/test/hummingbot/strategy/perpetual_market_making/__init__.py b/test/hummingbot/strategy/perpetual_market_making/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making.py b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making.py new file mode 100644 index 0000000..a82a0f0 --- /dev/null +++ b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making.py @@ -0,0 +1,812 @@ +from decimal import Decimal +from test.mock.mock_perp_connector import MockPerpConnector +from unittest import TestCase +from unittest.mock import patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.core.data_type.common import OrderType, PositionMode, PositionSide, PriceType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TradeFeeSchema +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + OrderFilledEvent, + PositionModeChangeEvent, + SellOrderCompletedEvent, +) +from hummingbot.strategy.data_types import PriceSize, Proposal +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.perpetual_market_making import PerpetualMarketMakingStrategy +from hummingbot.strategy.strategy_base import StrategyBase + + +class PerpetualMarketMakingTests(TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + + level = 0 + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.trading_pair: str = "COINALPHA-HBOT" + cls.base_asset, cls.quote_asset = cls.trading_pair.split("-") + cls.initial_mid_price: int = 100 + cls.clock_tick_size: int = 1 + cls.stop_loss_spread = Decimal("0.2") + cls.stop_loss_slippage_buffer = Decimal("0.1") + cls.long_profit_taking_spread = Decimal("0.5") + cls.short_profit_taking_spread = Decimal("0.4") + cls.trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.01"), + taker_percent_fee_decimal=Decimal("0.01"), + ) + + def setUp(self): + super().setUp() + self.log_records = [] + self.market: MockPerpConnector = MockPerpConnector( + client_config_map=ClientConfigAdapter(ClientConfigMap()), + trade_fee_schema=self.trade_fee_schema) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, + price_precision=6, + price_decimals=2, + order_size_precision=6, + order_size_decimals=2, + ) + ) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.trading_pair, self.base_asset, self.quote_asset + ) + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("COINALPHA", 1000) + self.market.set_balance("HBOT", 50000) + + new_strategy = PerpetualMarketMakingStrategy() + new_strategy.init_params( + market_info=self.market_info, + leverage=10, + position_mode=PositionMode.ONEWAY.name.title(), + bid_spread=Decimal("0.5"), + ask_spread=Decimal("0.4"), + order_amount=Decimal("100"), + long_profit_taking_spread=self.long_profit_taking_spread, + short_profit_taking_spread=self.short_profit_taking_spread, + stop_loss_spread=self.stop_loss_spread, + time_between_stop_loss_orders=10.0, + stop_loss_slippage_buffer=self.stop_loss_slippage_buffer, + ) + new_strategy._position_mode_ready = True + + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + + self.clock.add_iterator(self.market) + self._configure_strategy(new_strategy) + self.clock.backtest_til(self.start_timestamp) + + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + def tearDown(self) -> None: + self.strategy.stop(self.clock) + super().tearDown() + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + def _configure_strategy(self, strategy: StrategyBase): + self.strategy = strategy + self.strategy.logger().setLevel(1) + self.strategy.logger().addHandler(self) + self.strategy._strategy_ready = True + self.clock.add_iterator(self.strategy) + self.strategy.start(self.clock, self.start_timestamp) + + @staticmethod + def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY if limit_order.is_buy else TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal("0")) + )) + event_type = MarketEvent.BuyOrderCompleted if limit_order.is_buy else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if limit_order.is_buy else SellOrderCompletedEvent + market.trigger_event(event_type, event_class( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + + def test_apply_budget_constraint(self): + self.strategy = PerpetualMarketMakingStrategy() + self.strategy.init_params( + market_info=self.market_info, + leverage=2, + position_mode=PositionMode.HEDGE.name.title(), + bid_spread=Decimal("1"), + ask_spread=Decimal("1"), + order_amount=Decimal("2"), + long_profit_taking_spread=Decimal("1"), + short_profit_taking_spread=Decimal("1"), + stop_loss_spread=Decimal("1"), + time_between_stop_loss_orders=10.0, + stop_loss_slippage_buffer=self.stop_loss_slippage_buffer, + ) + + self.market.set_balance(self.base_asset, Decimal("2")) + self.market.set_balance(self.quote_asset, Decimal("10")) + + buys = [ + PriceSize(price=Decimal("5"), size=Decimal("1")), + PriceSize(price=Decimal("6"), size=Decimal("1")), + ] + sells = [ + PriceSize(price=Decimal("7"), size=Decimal("1")), + PriceSize(price=Decimal("8"), size=Decimal("1")), + ] + proposal = Proposal(buys, sells) + + self.strategy.apply_budget_constraint(proposal) + + new_buys = proposal.buys + new_sells = proposal.sells + + self.assertEqual(2, len(new_buys)) # cumulative 11 for leverage of 20 + self.assertEqual(buys[0], new_buys[0]) + self.assertEqual(buys[1], new_buys[1]) + self.assertEqual(1, len(new_sells)) # cumulative 18 for leverage of 20 + self.assertEqual(sells[0], new_sells[0]) + + def test_create_stop_loss_proposal_for_long_position(self): + position = Position( + trading_pair=self.trading_pair, + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price + Decimal(30), + amount=Decimal(1), + leverage=Decimal(10)) + positions = [position] + + proposal = self.strategy.stop_loss_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(proposal.buys)) + self.assertEqual(position.entry_price * + (Decimal(1) - self.stop_loss_spread) * + (Decimal(1) - self.stop_loss_slippage_buffer), + proposal.sells[0].price) + self.assertEqual(abs(position.amount), proposal.sells[0].size) + + def test_create_stop_loss_proposal_for_short_position(self): + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price - Decimal(30), + amount=Decimal(-1), + leverage=Decimal(10)) + positions = [position] + proposal = self.strategy.stop_loss_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(self.market.limit_orders)) + self.assertEqual(0, len(proposal.sells)) + self.assertEqual(position.entry_price * + (Decimal(1) + self.stop_loss_spread) * + (Decimal(1) + self.stop_loss_slippage_buffer), proposal.buys[0].price) + self.assertEqual(abs(position.amount), proposal.buys[0].size) + + def test_stop_loss_order_recreated_after_wait_time_for_long_position(self): + initial_stop_loss_price = self.initial_mid_price - Decimal("0.1") + initial_stop_loss_order_id = self.strategy.sell_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=initial_stop_loss_price) + + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price + Decimal(30), + amount=Decimal(1), + leverage=Decimal(10)) + self.market.account_positions[self.trading_pair] = position + + # Simulate first stop loss was created at timestamp 1000 + self.strategy._exit_orders[initial_stop_loss_order_id] = self.start_timestamp + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_orders)) + self.assertEqual(initial_stop_loss_order_id, self.strategy.active_orders[0].client_order_id) + + self.clock.backtest_til(self.start_timestamp + 9) + + self.assertEqual(1, len(self.strategy.active_orders)) + self.assertEqual(initial_stop_loss_order_id, self.strategy.active_orders[0].client_order_id) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.assertEqual(initial_stop_loss_order_id, self.cancel_order_logger.event_log[0].order_id) + self.assertEqual(1, len(self.strategy.active_orders)) + new_stop_loss_order = self.strategy.active_orders[0] + self.assertNotEqual(initial_stop_loss_order_id, new_stop_loss_order.client_order_id) + self.assertFalse(new_stop_loss_order.is_buy) + self.assertEqual(position.amount, new_stop_loss_order.quantity) + self.assertEqual(initial_stop_loss_price * (Decimal(1) - self.stop_loss_slippage_buffer), + new_stop_loss_order.price) + + def test_stop_loss_order_recreated_after_wait_time_for_short_position(self): + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price - Decimal(30), + amount=Decimal(-1), + leverage=Decimal(10)) + self.market.account_positions[self.trading_pair] = position + + initial_stop_loss_price = self.initial_mid_price + Decimal("0.1") + initial_stop_loss_order_id = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=initial_stop_loss_price) + + # Simulate first stop loss was created at timestamp 1000 + self.strategy._exit_orders[initial_stop_loss_order_id] = self.start_timestamp + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_orders)) + self.assertEqual(initial_stop_loss_order_id, self.strategy.active_orders[0].client_order_id) + + self.clock.backtest_til(self.start_timestamp + 9) + + self.assertEqual(1, len(self.strategy.active_orders)) + self.assertEqual(initial_stop_loss_order_id, self.strategy.active_orders[0].client_order_id) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.assertEqual(initial_stop_loss_order_id, self.cancel_order_logger.event_log[0].order_id) + self.assertEqual(1, len(self.strategy.active_orders)) + new_stop_loss_order = self.strategy.active_orders[0] + self.assertNotEqual(initial_stop_loss_order_id, new_stop_loss_order.client_order_id) + self.assertTrue(new_stop_loss_order.is_buy) + self.assertEqual(abs(position.amount), new_stop_loss_order.quantity) + self.assertEqual(initial_stop_loss_price * (Decimal(1) + self.stop_loss_slippage_buffer), + new_stop_loss_order.price) + + def test_create_profit_taking_proposal_logs_when_one_way_mode_and_multiple_positions(self): + positions = [ + Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=Decimal(50000), + amount=Decimal(1), + leverage=Decimal(10) + ), + Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.SHORT, + unrealized_pnl=Decimal(1000), + entry_price=Decimal(50000), + amount=Decimal(1), + leverage=Decimal(10) + )] + self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertTrue( + self._is_logged( + "ERROR", + "More than one open position in ONEWAY position mode. " + "Kindly ensure you do not interact with the exchange through other platforms and" + " restart this strategy.")) + + def test_create_profit_taking_proposal_for_one_way_cancels_other_possible_exit_orders(self): + order_id = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=Decimal(50000)) + positions = [ + Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=Decimal(50000), + amount=Decimal(-1), + leverage=Decimal(10) + )] + + self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(self.market.limit_orders)) + self.assertTrue( + self._is_logged("INFO", f"Initiated cancelation of buy order {order_id} in favour of take profit order.")) + + order_id = self.strategy.sell_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=Decimal(50000)) + positions = [ + Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=Decimal(50000), + amount=Decimal(1), + leverage=Decimal(10) + )] + + self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(self.market.limit_orders)) + self.assertTrue( + self._is_logged("INFO", f"Initiated cancelation of sell order {order_id} in favour of take profit order.")) + + def test_create_profit_taking_proposal_for_long_position(self): + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price - Decimal(20), + amount=Decimal(1), + leverage=Decimal(10)) + positions = [position] + + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price - 10, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + close_proposal = self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(close_proposal.buys)) + self.assertEqual(position.entry_price * (Decimal(1) + self.long_profit_taking_spread), + close_proposal.sells[0].price) + self.assertEqual(Decimal("1"), close_proposal.sells[0].size) + + def test_create_profit_taking_proposal_for_short_position(self): + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price + Decimal(20), + amount=Decimal(-1), + leverage=Decimal(10)) + positions = [position] + + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price - 10, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + close_proposal = self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(0, len(close_proposal.sells)) + self.assertEqual(position.entry_price * (Decimal(1) - self.short_profit_taking_spread), + close_proposal.buys[0].price) + self.assertEqual(Decimal("1"), close_proposal.buys[0].size) + + def test_create_profit_taking_proposal_for_long_position_cancel_old_exit_orders(self): + order_id = self.strategy.sell_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=Decimal(self.initial_mid_price)) + self.strategy._exit_orders[order_id] = 1000 + + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price - Decimal(20), + amount=Decimal(1), + leverage=Decimal(10)) + positions = [position] + + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price - 10, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(order_id, self.cancel_order_logger.event_log[0].order_id) + self.assertTrue( + self._is_logged("INFO", + f"Initiated cancelation of previous take profit order {order_id} " + f"in favour of new take profit order.")) + self.assertEqual(0, len(self.strategy.active_orders)) + + def test_create_profit_taking_proposal_for_short_position_cancel_old_exit_orders(self): + order_id = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + amount=Decimal(1), + order_type=OrderType.LIMIT, + price=Decimal(self.initial_mid_price)) + self.strategy._exit_orders[order_id] = 1000 + + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.initial_mid_price + Decimal(20), + amount=Decimal(-1), + leverage=Decimal(10)) + positions = [position] + + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price + 10, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + + self.strategy.profit_taking_proposal(PositionMode.ONEWAY, positions) + + self.assertEqual(order_id, self.cancel_order_logger.event_log[0].order_id) + self.assertTrue( + self._is_logged("INFO", + f"Initiated cancelation of previous take profit order {order_id} " + f"in favour of new take profit order.")) + self.assertEqual(0, len(self.strategy.active_orders)) + + def test_tick_creates_buy_and_sell_pairs_when_no_position_opened(self): + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.assertEqual(self.trading_pair, buy_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) - self.strategy.bid_spread), + buy_order.price) + self.assertEqual(Decimal(100), buy_order.quantity) + self.assertEqual(self.trading_pair, sell_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) + self.strategy.ask_spread), + sell_order.price) + self.assertEqual(Decimal(100), sell_order.quantity) + + def test_active_orders_are_recreated_on_refresh_time(self): + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.clock.backtest_til(self.strategy.current_timestamp + self.strategy.order_refresh_time) + + # All orders should have been cancelled + self.assertEqual(0, len(self.strategy.active_orders)) + + # In the next tick the new orders should be created + self.clock.backtest_til(self.strategy.current_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + self.assertEqual(self.trading_pair, buy_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) - self.strategy.bid_spread), + buy_order.price) + self.assertEqual(Decimal(100), buy_order.quantity) + self.assertEqual(self.trading_pair, sell_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) + self.strategy.ask_spread), + sell_order.price) + self.assertEqual(Decimal(100), sell_order.quantity) + + def test_active_orders_are_not_refreshed_if_covered_by_refresh_tolerance(self): + self.strategy.order_refresh_tolerance_pct = Decimal("0.2") + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.clock.backtest_til(self.strategy.current_timestamp + self.strategy.order_refresh_time) + + # The orders should not be cancelled + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(buy_order, self.strategy.active_buys[0]) + self.assertEqual(1, len(self.strategy.active_sells)) + self.assertEqual(sell_order, self.strategy.active_sells[0]) + + def test_orders_creation_with_order_override(self): + new_strategy = PerpetualMarketMakingStrategy() + new_strategy.init_params( + market_info=self.market_info, + leverage=10, + position_mode=PositionMode.ONEWAY.name.title(), + bid_spread=Decimal("0.5"), + ask_spread=Decimal("0.4"), + order_amount=Decimal("100"), + long_profit_taking_spread=self.long_profit_taking_spread, + short_profit_taking_spread=self.short_profit_taking_spread, + stop_loss_spread=self.stop_loss_spread, + time_between_stop_loss_orders=10.0, + stop_loss_slippage_buffer=self.stop_loss_slippage_buffer, + order_override={"buy": ["buy", "10", "50"], "sell": ["sell", "20", "40"]} + ) + new_strategy._position_mode_ready = True + + self.clock.remove_iterator(self.strategy) + self._configure_strategy(new_strategy) + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.assertEqual(self.trading_pair, buy_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) - Decimal("0.1")), + buy_order.price) + self.assertEqual(Decimal(50), buy_order.quantity) + self.assertEqual(self.trading_pair, sell_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) + Decimal("0.2")), + sell_order.price) + self.assertEqual(Decimal(40), sell_order.quantity) + + def test_orders_not_created_if_not_enough_balance(self): + self.strategy.order_amount = Decimal("20000") + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + self.assertTrue( + self._is_logged( + "INFO", + "Insufficient balance: BUY order (price: 50.00, size: 20000.0) is omitted.")) + self.assertTrue( + self._is_logged( + "INFO", + "Insufficient balance: SELL order (price: 140.00, size: 20000.0) is omitted.")) + self.assertTrue( + self._is_logged( + "WARNING", + "You are also at a possible risk of being liquidated if there happens to be an open loss.")) + + def test_orders_creation_with_order_optimization_enabled(self): + self.strategy.order_optimization_enabled = True + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.assertEqual(self.trading_pair, buy_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) - self.strategy.bid_spread), + buy_order.price) + self.assertEqual(Decimal(100), buy_order.quantity) + self.assertEqual(self.trading_pair, sell_order.trading_pair) + self.assertEqual( + self.strategy.get_price() * (Decimal(1) + self.strategy.ask_spread), + sell_order.price) + self.assertEqual(Decimal(100), sell_order.quantity) + + @patch("hummingbot.client.hummingbot_application.HummingbotApplication") + def test_strategy_logs_fill_and_complete_events_details(self, _): + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + buy_order = self.strategy.active_buys[0] + sell_order = self.strategy.active_sells[0] + + self.simulate_limit_order_fill(self.market, buy_order) + + self.assertTrue( + self._is_logged( + "INFO", + f"({self.trading_pair}) Maker buy order of {buy_order.quantity} {self.base_asset} filled." + ) + ) + self.assertTrue( + self._is_logged( + "INFO", + f"({self.trading_pair}) Maker buy order {buy_order.client_order_id} " + f"({buy_order.quantity} {self.base_asset} @ " + f"{buy_order.price} {self.quote_asset}) has been completely filled." + ) + ) + + self.simulate_limit_order_fill(self.market, sell_order) + + self.assertTrue( + self._is_logged( + "INFO", + f"({self.trading_pair}) Maker sell order of {sell_order.quantity} {self.base_asset} filled." + ) + ) + self.assertTrue( + self._is_logged( + "INFO", + f"({self.trading_pair}) Maker sell order {sell_order.client_order_id} " + f"({sell_order.quantity} {self.base_asset} @ " + f"{sell_order.price} {self.quote_asset}) has been completely filled." + ) + ) + + def test_status_text_when_no_open_positions_and_two_orders(self): + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + expected_status = ("\n Markets:" + "\n Exchange Market Best Bid Best Ask Ref Price (MidPrice)" + "\n mock_perp_connector COINALPHA-HBOT 99.5 100.5 100" + "\n\n Assets:" + "\n HBOT" + "\n Total Balance 50000" + "\n Available Balance 45000" + "\n\n Orders:" + "\n Level Type Price Spread Amount (Orig) Amount (Adj) Age" + "\n 1 sell 140 40.00% 100 100 00:00:00" + "\n 1 buy 50 50.00% 100 100 00:00:00" + "\n\n No active positions.") + status = self.strategy.format_status() + + self.assertEqual(expected_status, status) + + def test_status_text_with_one_open_position_and_no_orders_alive(self): + position = Position( + trading_pair=(self.trading_pair), + position_side=PositionSide.LONG, + unrealized_pnl=Decimal(1000), + entry_price=self.market.get_price(self.trading_pair, True), + amount=Decimal(1), + leverage=Decimal(10)) + self.market.account_positions[self.trading_pair] = position + + self.clock.backtest_til(self.start_timestamp + 1) + + expected_status = ("\n Markets:" + "\n Exchange Market Best Bid Best Ask Ref Price (MidPrice)" + "\n mock_perp_connector COINALPHA-HBOT 99.5 100.5 100" + "\n\n Assets:" + "\n HBOT" + "\n Total Balance 50000" + "\n Available Balance 50000" + "\n\n No active maker orders." + "\n\n Positions:" + "\n Symbol Type Entry Price Amount Leverage Unrealized PnL" + "\n COINALPHA-HBOT LONG 100.50 1 10 0.00") + status = self.strategy.format_status() + + self.assertEqual(expected_status, status) + + def test_get_price_type(self): + self.assertEqual(PriceType.MidPrice, self.strategy.get_price_type("mid_price")) + self.assertEqual(PriceType.BestBid, self.strategy.get_price_type("best_bid")) + self.assertEqual(PriceType.BestAsk, self.strategy.get_price_type("best_ask")) + self.assertEqual(PriceType.LastTrade, self.strategy.get_price_type("last_price")) + self.assertEqual(PriceType.LastOwnTrade, self.strategy.get_price_type("last_own_trade_price")) + self.assertEqual(PriceType.Custom, self.strategy.get_price_type("custom")) + + self.assertRaises(ValueError, self.strategy.get_price_type, "invalid_text") + + @patch("hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode") + def test_position_mode_change_success(self, set_position_mode_mock): + self.strategy._position_mode_ready = False + + self.assertEqual(0, self.strategy._position_mode_not_ready_counter) + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, self.strategy._position_mode_not_ready_counter) + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.assertEqual(0, self.strategy._position_mode_not_ready_counter) + + self.strategy.did_change_position_mode_succeed( + PositionModeChangeEvent( + timestamp=self.start_timestamp + 11, + trading_pair=self.trading_pair, + position_mode=PositionMode.ONEWAY, + ) + ) + + self.assertTrue(self.strategy._position_mode_ready) + + @patch("hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode") + def test_position_mode_change_failure(self, set_position_mode_mock): + self.strategy._position_mode_ready = False + + self.assertEqual(0, self.strategy._position_mode_not_ready_counter) + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, self.strategy._position_mode_not_ready_counter) + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.assertEqual(0, self.strategy._position_mode_not_ready_counter) + + self.strategy.did_change_position_mode_fail( + PositionModeChangeEvent( + timestamp=self.start_timestamp + 11, + trading_pair=self.trading_pair, + position_mode=PositionMode.ONEWAY, + message="Error message", + ) + ) + + self.assertFalse(self.strategy._position_mode_ready) diff --git a/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py new file mode 100644 index 0000000..8b19b4f --- /dev/null +++ b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py @@ -0,0 +1,201 @@ +import unittest +from copy import deepcopy +from unittest.mock import MagicMock, PropertyMock, patch + +import hummingbot.strategy.perpetual_market_making.perpetual_market_making_config_map as config_map_module +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.core.utils.trading_pair_fetcher import TradingPairFetcher +from hummingbot.strategy.perpetual_market_making.perpetual_market_making_config_map import ( + maker_trading_pair_prompt, + on_validate_price_source, + order_amount_prompt, + perpetual_market_making_config_map as perpetual_mm_config_map, + validate_price_type, +) + + +class TestPMMConfigMap(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.derivative = "binance_perpetual" + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.config_backup = deepcopy(perpetual_mm_config_map) + + def tearDown(self) -> None: + self.reset_config_map() + super().tearDown() + + def reset_config_map(self): + for key, value in self.config_backup.items(): + perpetual_mm_config_map[key] = value + + def test_on_validate_price_source_non_external_market_reset(self): + perpetual_mm_config_map["price_source_derivative"].value = "an_extmkt" + perpetual_mm_config_map["price_source_market"].value = self.trading_pair + + on_validate_price_source(value="current_market") + + self.assertIsNone(perpetual_mm_config_map["price_source_derivative"].value) + self.assertIsNone(perpetual_mm_config_map["price_source_market"].value) + + def test_on_validate_price_source_non_custom_api_reset(self): + perpetual_mm_config_map["price_source_custom_api"].value = "https://someurl.com" + + on_validate_price_source(value="current_market") + + self.assertIsNone(perpetual_mm_config_map["price_source_custom_api"].value) + + def test_on_validate_price_source_custom_api_set_price_type(self): + on_validate_price_source(value="custom_api") + + self.assertEqual(perpetual_mm_config_map["price_type"].value, "custom") + + def test_validate_price_type_non_custom_api(self): + perpetual_mm_config_map["price_source"].value = "current_market" + + error = validate_price_type(value="mid_price") + self.assertIsNone(error) + error = validate_price_type(value="last_price") + self.assertIsNone(error) + error = validate_price_type(value="last_own_trade_price") + self.assertIsNone(error) + error = validate_price_type(value="best_bid") + self.assertIsNone(error) + error = validate_price_type(value="best_ask") + self.assertIsNone(error) + + error = validate_price_type(value="custom") + self.assertIsNotNone(error) + + def test_validate_price_type_custom_api(self): + perpetual_mm_config_map["price_source"].value = "custom_api" + + error = validate_price_type(value="mid_price") + self.assertIsNotNone(error) + error = validate_price_type(value="last_price") + self.assertIsNotNone(error) + error = validate_price_type(value="last_own_trade_price") + self.assertIsNotNone(error) + error = validate_price_type(value="best_bid") + self.assertIsNotNone(error) + error = validate_price_type(value="best_ask") + self.assertIsNotNone(error) + + error = validate_price_type(value="custom") + self.assertIsNone(error) + + def test_order_amount_prompt(self): + perpetual_mm_config_map["market"].value = self.trading_pair + prompt = order_amount_prompt() + expected = f"What is the amount of {self.base_asset} per order? >>> " + + self.assertEqual(expected, prompt) + + def test_maker_trading_prompt(self): + perpetual_mm_config_map["derivative"].value = self.derivative + example = AllConnectorSettings.get_example_pairs().get(self.derivative) + + prompt = maker_trading_pair_prompt() + expected = f"Enter the token trading pair you would like to trade on {self.derivative} (e.g. {example}) >>> " + + self.assertEqual(expected, prompt) + + def test_validate_derivative_trading_pair(self): + fetcher_mock = MagicMock() + type(fetcher_mock).ready = PropertyMock(return_value=True) + type(fetcher_mock).trading_pairs = PropertyMock(return_value={"test_market": ["BTC-USDT"]}) + TradingPairFetcher._sf_shared_instance = fetcher_mock + + perpetual_mm_config_map.get("derivative").value = "test_market" + + result = config_map_module.validate_derivative_trading_pair("BTC-USDT") + self.assertIsNone(result) + + result = config_map_module.validate_derivative_trading_pair("NON-EXISTENT") + self.assertEqual("NON-EXISTENT is not an active market on test_market.", result) + + def test_validate_derivative_position_mode(self): + self.assertIsNone(config_map_module.validate_derivative_position_mode("One-way")) + self.assertIsNone(config_map_module.validate_derivative_position_mode("Hedge")) + + self.assertEqual( + "Position mode can either be One-way or Hedge mode", + config_map_module.validate_derivative_position_mode("Invalid")) + + def test_validate_price_source(self): + self.assertIsNone(config_map_module.validate_price_source("current_market")) + self.assertIsNone(config_map_module.validate_price_source("external_market")) + self.assertIsNone(config_map_module.validate_price_source("custom_api")) + + self.assertEqual( + "Invalid price source type.", + config_map_module.validate_price_source("invalid_market") + ) + + def test_price_source_market_prompt(self): + perpetual_mm_config_map.get("price_source_derivative").value = "test_market" + self.assertEqual( + "Enter the token trading pair on test_market >>> ", + config_map_module.price_source_market_prompt() + ) + + @patch("hummingbot.client.settings.AllConnectorSettings.get_derivative_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + def test_price_source_derivative_validator(self, get_derivatives_mock, get_exchange_mock): + get_derivatives_mock.return_value = ["derivative_connector"] + get_exchange_mock.return_value = ["exchange_connector"] + + perpetual_mm_config_map.get("derivative").value = "test_market" + self.assertEqual( + "Price source derivative cannot be the same as maker derivative.", + config_map_module.validate_price_source_derivative("test_market") + ) + + self.assertEqual( + "Price source must must be a valid exchange or derivative connector.", + config_map_module.validate_price_source_derivative("invalid") + ) + + self.assertIsNone(config_map_module.validate_price_source_derivative("derivative_connector")) + + def test_on_validate_price_source_derivative(self): + perpetual_mm_config_map.get("price_source_market").value = "MARKET" + config_map_module.on_validated_price_source_derivative("Something") + self.assertEqual("MARKET", perpetual_mm_config_map.get("price_source_market").value) + + config_map_module.on_validated_price_source_derivative(None) + self.assertIsNone(perpetual_mm_config_map.get("price_source_market").value) + + def test_validate_price_source_market(self): + fetcher_mock = MagicMock() + type(fetcher_mock).ready = PropertyMock(return_value=True) + type(fetcher_mock).trading_pairs = PropertyMock(return_value={"test_market": ["BTC-USDT"]}) + TradingPairFetcher._sf_shared_instance = fetcher_mock + + perpetual_mm_config_map.get("price_source_derivative").value = "test_market" + + result = config_map_module.validate_price_source_market("BTC-USDT") + self.assertIsNone(result) + + result = config_map_module.validate_price_source_market("NON-EXISTENT") + self.assertEqual("NON-EXISTENT is not an active market on test_market.", result) + + def test_validate_price_floor_ceiling(self): + result = config_map_module.validate_price_floor_ceiling("Not a number") + self.assertEqual("Not a number is not in decimal format.", result) + + result = config_map_module.validate_price_floor_ceiling("-0.5") + self.assertEqual("Value must be more than 0 or -1 to disable this feature.", result) + result = config_map_module.validate_price_floor_ceiling("0") + self.assertEqual("Value must be more than 0 or -1 to disable this feature.", result) + + result = config_map_module.validate_price_floor_ceiling("-1") + self.assertIsNone(result) + result = config_map_module.validate_price_floor_ceiling("0.1") + self.assertIsNone(result) diff --git a/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_start.py b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_start.py new file mode 100644 index 0000000..b2a1427 --- /dev/null +++ b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_start.py @@ -0,0 +1,52 @@ +import unittest.mock +from decimal import Decimal +from test.hummingbot.strategy import assign_config_default + +import hummingbot.strategy.perpetual_market_making.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.perpetual_market_making.perpetual_market_making_config_map import ( + perpetual_market_making_config_map as c_map, +) + + +class PerpetualMarketMakingStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.markets = {"binance": ExchangeBase(client_config_map=ClientConfigAdapter(ClientConfigMap()))} + self.notifications = [] + self.log_errors = [] + assign_config_default(c_map) + c_map.get("derivative").value = "binance" + c_map.get("market").value = "ETH-USDT" + + c_map.get("leverage").value = Decimal("5") + c_map.get("order_amount").value = Decimal("1") + c_map.get("order_refresh_time").value = 60. + c_map.get("bid_spread").value = Decimal("1") + c_map.get("ask_spread").value = Decimal("2") + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + self.assertEqual(self.strategy.order_amount, Decimal("1")) + self.assertEqual(self.strategy.order_refresh_time, 60.) + self.assertEqual(self.strategy.bid_spread, Decimal("0.01")) + self.assertEqual(self.strategy.ask_spread, Decimal("0.02")) diff --git a/test/hummingbot/strategy/pure_market_making/__init__.py b/test/hummingbot/strategy/pure_market_making/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/pure_market_making/test_inventory_cost_price_delegate.py b/test/hummingbot/strategy/pure_market_making/test_inventory_cost_price_delegate.py new file mode 100644 index 0000000..ed85a61 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_inventory_cost_price_delegate.py @@ -0,0 +1,148 @@ +import unittest +from decimal import Decimal + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import OrderFilledEvent +from hummingbot.model.inventory_cost import InventoryCost +from hummingbot.model.sql_connection_manager import SQLConnectionManager, SQLConnectionType +from hummingbot.strategy.pure_market_making.inventory_cost_price_delegate import InventoryCostPriceDelegate + + +class TestInventoryCostPriceDelegate(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.trade_fill_sql = SQLConnectionManager( + ClientConfigAdapter(ClientConfigMap()), SQLConnectionType.TRADE_FILLS, db_path="" + ) + cls.trading_pair = "BTC-USDT" + cls.base_asset, cls.quote_asset = cls.trading_pair.split("-") + + def setUp(self): + with self.trade_fill_sql.get_new_session() as session: + for table in [InventoryCost.__table__]: + with session.begin(): + session.execute(table.delete()) + self.delegate = InventoryCostPriceDelegate( + self.trade_fill_sql, self.trading_pair + ) + + def test_process_order_fill_event_buy(self): + amount = Decimal("1") + price = Decimal("9000") + event = OrderFilledEvent( + timestamp=1, + order_id="order1", + trading_pair=self.trading_pair, + trade_type=TradeType.BUY, + order_type=OrderType.LIMIT, + price=price, + amount=amount, + trade_fee=AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]), + ) + # first event creates DB record + self.delegate.process_order_fill_event(event) + with self.trade_fill_sql.get_new_session() as session: + count = session.query(InventoryCost).count() + self.assertEqual(count, 1) + + # second event causes update to existing record + self.delegate.process_order_fill_event(event) + record = InventoryCost.get_record( + session, self.base_asset, self.quote_asset + ) + self.assertEqual(record.base_volume, amount * 2) + self.assertEqual(record.quote_volume, price * 2) + + def test_process_order_fill_event_sell(self): + amount = Decimal("1") + price = Decimal("9000") + + # Test when no records + self.assertIsNone(self.delegate.get_price()) + + with self.trade_fill_sql.get_new_session() as session: + with session.begin(): + record = InventoryCost( + base_asset=self.base_asset, + quote_asset=self.quote_asset, + base_volume=amount, + quote_volume=amount * price, + ) + session.add(record) + + amount_sell = Decimal("0.5") + price_sell = Decimal("10000") + event = OrderFilledEvent( + timestamp=1, + order_id="order1", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + order_type=OrderType.LIMIT, + price=price_sell, + amount=amount_sell, + trade_fee=AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]), + ) + + self.delegate.process_order_fill_event(event) + with self.trade_fill_sql.get_new_session() as session: + record = InventoryCost.get_record( + session, self.base_asset, self.quote_asset + ) + # Remaining base volume reduced by sold amount + self.assertEqual(record.base_volume, amount - amount_sell) + # Remaining quote volume has been reduced using original price + self.assertEqual(record.quote_volume, amount_sell * price) + + def test_process_order_fill_event_sell_no_initial_cost_set(self): + amount = Decimal("1") + price = Decimal("9000") + event = OrderFilledEvent( + timestamp=1, + order_id="order1", + trading_pair=self.trading_pair, + trade_type=TradeType.SELL, + order_type=OrderType.LIMIT, + price=price, + amount=amount, + trade_fee=AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]), + ) + with self.assertRaises(RuntimeError): + self.delegate.process_order_fill_event(event) + + def test_get_price_by_type(self): + amount = Decimal("1") + price = Decimal("9000") + + # Test when no records + self.assertIsNone(self.delegate.get_price()) + + with self.trade_fill_sql.get_new_session() as session: + with session.begin(): + record = InventoryCost( + base_asset=self.base_asset, + quote_asset=self.quote_asset, + base_volume=amount, + quote_volume=amount * price, + ) + session.add(record) + + delegate_price = self.delegate.get_price() + self.assertEqual(delegate_price, price) + + def test_get_price_by_type_zero_division(self): + # Test for situation when position was fully closed with profit + amount = Decimal("0") + + with self.trade_fill_sql.get_new_session() as session: + with session.begin(): + record = InventoryCost( + base_asset=self.base_asset, + quote_asset=self.quote_asset, + base_volume=amount, + quote_volume=amount, + ) + session.add(record) + self.assertIsNone(self.delegate.get_price()) diff --git a/test/hummingbot/strategy/pure_market_making/test_inventory_skew_calculator.py b/test/hummingbot/strategy/pure_market_making/test_inventory_skew_calculator.py new file mode 100644 index 0000000..aa176c1 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_inventory_skew_calculator.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +import unittest + +from hummingbot.strategy.pure_market_making.data_types import InventorySkewBidAskRatios +from hummingbot.strategy.pure_market_making.inventory_skew_calculator import \ + calculate_bid_ask_ratios_from_base_asset_ratio + + +class InventorySkewCalculatorUnitTest(unittest.TestCase): + def setUp(self): + self.base_asset: float = 85000 + self.quote_asset: float = 10000 + self.price: float = 0.0036 + self.target_ratio: float = 0.03 + self.base_range: float = 20000.0 + + def test_cap_on_max_base_range(self): + self.base_asset = 100 + self.quote_asset = 10 + self.price = 1 + self.target_ratio = 0.35 + self.base_range = 200 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(2, bid_ask_ratios.ask_ratio) + + self.base_asset = 10 + self.quote_asset = 100 + self.price = 1 + self.target_ratio = 0.75 + self.base_range = 200 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(2, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0, bid_ask_ratios.ask_ratio) + + def test_balanced_portfolio(self): + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(1.04416666, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.95583333, bid_ask_ratios.ask_ratio) + + def test_heavily_skewed_portfolio(self): + self.base_asset = 8500.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(2.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.0, bid_ask_ratios.ask_ratio) + + self.base_asset = 200000.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(0.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(2.0, bid_ask_ratios.ask_ratio) + + self.base_asset = 1000000.0 + self.quote_asset = 0.0 + self.assertAlmostEqual(0.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(2.0, bid_ask_ratios.ask_ratio) + + def test_moderately_skewed_portfolio(self): + self.base_asset = 95000.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(0.55916666, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(1.440833333, bid_ask_ratios.ask_ratio) + + self.base_asset = 70000.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(1.77166666, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.22833333, bid_ask_ratios.ask_ratio) + + def test_empty_portfolio(self): + self.base_asset = 0.0 + self.quote_asset = 0.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(0.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.0, bid_ask_ratios.ask_ratio) + + self.quote_asset = 10000.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(2.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.0, bid_ask_ratios.ask_ratio) + + self.base_asset = 85000.0 + self.base_range = 0.0 + bid_ask_ratios: InventorySkewBidAskRatios = calculate_bid_ask_ratios_from_base_asset_ratio( + self.base_asset, self.quote_asset, self.price, self.target_ratio, self.base_range + ) + self.assertAlmostEqual(0.0, bid_ask_ratios.bid_ratio) + self.assertAlmostEqual(0.0, bid_ask_ratios.ask_ratio) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/hummingbot/strategy/pure_market_making/test_moving_price_band.py b/test/hummingbot/strategy/pure_market_making/test_moving_price_band.py new file mode 100644 index 0000000..c4048c4 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_moving_price_band.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +import unittest +from decimal import Decimal +from hummingbot.strategy.pure_market_making.moving_price_band import MovingPriceBand + + +class MovingPriceBandUnitTest(unittest.TestCase): + def setUp(self): + self.current_timestamp = 1 + self.moving_price_band = MovingPriceBand( + price_floor_pct=Decimal("-1"), + price_ceiling_pct=Decimal("1"), + price_band_refresh_time=86400, + ) + self.price = 100 + + def test_update(self): + self.moving_price_band.update(self.current_timestamp, self.price) + self.assertEqual(self.moving_price_band.price_floor, Decimal("99")) + self.assertEqual(self.moving_price_band.price_ceiling, Decimal("101")) + self.assertGreater(self.moving_price_band._set_time, 0) + + def test_check_and_update_price_band(self): + self.moving_price_band.update(self.current_timestamp, self.price) + self.moving_price_band.check_and_update_price_band(self.current_timestamp, Decimal("100")) + self.assertEqual(self.moving_price_band.price_floor, Decimal("99")) + self.assertEqual(self.moving_price_band.price_ceiling, Decimal("101")) + self.moving_price_band.check_and_update_price_band(self.current_timestamp, Decimal("200")) + self.assertEqual(self.moving_price_band.price_floor, Decimal("99")) + self.assertEqual(self.moving_price_band.price_ceiling, Decimal("101")) + self.current_timestamp = 86401 + self.moving_price_band.check_and_update_price_band(self.current_timestamp, Decimal("200")) + self.assertEqual(self.moving_price_band.price_floor, Decimal("198")) + self.assertEqual(self.moving_price_band.price_ceiling, Decimal("202")) + + def test_switch_moving_price_band(self): + self.moving_price_band.switch(True) + self.assertEqual(self.moving_price_band.enabled, True) + self.moving_price_band.switch(False) + self.assertEqual(self.moving_price_band.enabled, False) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/hummingbot/strategy/pure_market_making/test_pmm.py b/test/hummingbot/strategy/pure_market_making/test_pmm.py new file mode 100644 index 0000000..6686024 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pmm.py @@ -0,0 +1,1297 @@ +import unittest +from decimal import Decimal +from test.mock.mock_asset_price_delegate import MockAssetPriceDelegate +from typing import List, Optional + +import pandas as pd + +from hummingbot.client.command.config_command import ConfigCommand +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import PriceType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookTradeEvent, OrderCancelledEvent +from hummingbot.model.sql_connection_manager import SQLConnectionManager, SQLConnectionType +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.pure_market_making.inventory_cost_price_delegate import InventoryCostPriceDelegate +from hummingbot.strategy.pure_market_making.pure_market_making import PureMarketMakingStrategy + + +# Update the orderbook so that the top bids and asks are lower than actual for a wider bid ask spread +# this basially removes the orderbook entries above top bid and below top ask +def simulate_order_book_widening(order_book: OrderBook, top_bid: float, top_ask: float): + bid_diffs: List[OrderBookRow] = [] + ask_diffs: List[OrderBookRow] = [] + update_id: int = order_book.last_diff_uid + 1 + for row in order_book.bid_entries(): + if row.price > top_bid: + bid_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + for row in order_book.ask_entries(): + if row.price < top_ask: + ask_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + order_book.apply_diffs(bid_diffs, ask_diffs, update_id) + + +class PMMUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair = "HBOT-ETH" + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + + def setUp(self): + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.bid_spread = 0.01 + self.ask_spread = 0.01 + self.order_refresh_time = 30 + self.market.set_balanced_order_book(self.trading_pair, + mid_price=self.mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("HBOT", 500) + self.market.set_balance("ETH", 5000) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, + self.base_asset, self.quote_asset) + self.clock.add_iterator(self.market) + self.order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + self.one_level_strategy = PureMarketMakingStrategy() + self.one_level_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1 + ) + self.one_level_strategy.order_tracker._set_current_timestamp(1640001112.223) + + self.multi_levels_strategy = PureMarketMakingStrategy() + self.multi_levels_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + order_levels=3, + order_level_spread=Decimal("0.01"), + order_level_amount=Decimal("1"), + minimum_spread=-1, + ) + + self.order_override_strategy = PureMarketMakingStrategy() + self.order_override_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + order_levels=3, + order_level_spread=Decimal("0.01"), + order_level_amount=Decimal("1"), + minimum_spread=-1, + order_override={"order_one": ["buy", 0.5, 0.7], "order_two": ["buy", 1.3, 1.1], "order_three": ["sell", 1.1, 2]}, + ) + + self.custom_asset_price_delegate = MockAssetPriceDelegate(self.market, mock_price=Decimal("100.0")) + self.custom_price_source_strategy = PureMarketMakingStrategy() + self.custom_price_source_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1, + asset_price_delegate=self.custom_asset_price_delegate, + price_type="custom", + ) + + self.ext_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.ext_market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.ext_market, self.trading_pair, self.base_asset, self.quote_asset + ) + self.ext_market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=50, min_price=1, max_price=400, price_step_size=1, + volume_step_size=10) + self.order_book_asset_del = OrderBookAssetPriceDelegate(self.ext_market, self.trading_pair) + trade_fill_sql = SQLConnectionManager( + ClientConfigAdapter(ClientConfigMap()), SQLConnectionType.TRADE_FILLS, db_path="" + ) + self.inventory_cost_price_del = InventoryCostPriceDelegate(trade_fill_sql, self.trading_pair) + + self.split_order_level_strategy = PureMarketMakingStrategy() + self.split_order_level_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1, + split_order_levels_enabled=True, + bid_order_level_spreads= [Decimal("1"), Decimal("2")], + ask_order_level_spreads= [Decimal("1"), Decimal("2")], + order_override={"split_level_0": ['buy', Decimal("1"), Decimal("1")], + "split_level_1": ['buy', Decimal("2"), Decimal("2")], + "split_level_2": ['sell', Decimal("1"), Decimal("1")] + } + ) + self.split_order_level_strategy.order_tracker._set_current_timestamp(1640001112.223) + + def simulate_maker_market_trade( + self, is_buy: bool, quantity: Decimal, price: Decimal, market: Optional[MockPaperExchange] = None, + ): + if market is None: + market = self.market + order_book = market.get_order_book(self.trading_pair) + trade_event = OrderBookTradeEvent( + self.trading_pair, + self.clock.current_timestamp, + TradeType.BUY if is_buy else TradeType.SELL, + price, + quantity + ) + order_book.apply_trade(trade_event) + + def test_basic_one_level(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + buy_1 = strategy.active_buys[0] + self.assertEqual(99, buy_1.price) + self.assertEqual(1, buy_1.quantity) + sell_1 = strategy.active_sells[0] + self.assertEqual(101, sell_1.price) + self.assertEqual(1, sell_1.quantity) + + # After order_refresh_time, a new set of orders is created + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertNotEqual(buy_1.client_order_id, strategy.active_buys[0].client_order_id) + self.assertNotEqual(sell_1.client_order_id, strategy.active_sells[0].client_order_id) + + # Simulate buy order filled + self.clock.backtest_til(self.start_timestamp + 8) + self.simulate_maker_market_trade(False, 100, 98.9) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + # After filled_ore + self.clock.backtest_til(self.start_timestamp + 14) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + def test_basic_one_level_price_type_own_last_trade(self): + strategy = PureMarketMakingStrategy() + strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1, + price_type='last_own_trade_price', + ) + self.clock.add_iterator(strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + buy_1 = strategy.active_buys[0] + self.assertEqual(99, buy_1.price) + self.assertEqual(1, buy_1.quantity) + sell_1 = strategy.active_sells[0] + self.assertEqual(101, sell_1.price) + self.assertEqual(1, sell_1.quantity) + + # Simulate buy order filled + self.simulate_maker_market_trade(False, 100, 98.9) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + # Order has been filled + self.clock.backtest_til(self.start_timestamp + 7) + buy_1 = strategy.active_buys[0] + self.assertEqual(Decimal('98.01'), buy_1.price) + self.assertEqual(1, buy_1.quantity) + sell_1 = strategy.active_sells[0] + self.assertEqual(Decimal('99.99'), sell_1.price) + self.assertEqual(1, sell_1.quantity) + + def test_basic_one_level_price_type(self): + strategies = [] + + for price_type in ["last_price", "best_bid", "best_ask"]: + strategy = PureMarketMakingStrategy() + strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1, + price_type=price_type, + ) + strategies.append(strategy) + self.clock.add_iterator(strategy) + + last_strategy, bid_strategy, ask_strategy = strategies + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(last_strategy.active_buys)) + self.assertEqual(1, len(last_strategy.active_sells)) + buy_1 = last_strategy.active_buys[0] + self.assertEqual(99, buy_1.price) + self.assertEqual(1, buy_1.quantity) + sell_1 = last_strategy.active_sells[0] + self.assertEqual(101, sell_1.price) + self.assertEqual(1, sell_1.quantity) + + # Simulate buy order filled + self.simulate_maker_market_trade(False, 100, 98.9) + self.assertEqual(0, len(last_strategy.active_buys)) + self.assertEqual(1, len(last_strategy.active_sells)) + + # After filled_ore + self.clock.backtest_til(self.start_timestamp + 7) + buy_1 = last_strategy.active_buys[0] + self.assertEqual(Decimal('97.911'), buy_1.price) + self.assertEqual(1, buy_1.quantity) + sell_1 = last_strategy.active_sells[0] + self.assertEqual(Decimal('99.889'), sell_1.price) + self.assertEqual(1, sell_1.quantity) + + buy_bid = bid_strategy.active_buys[0] + buy_target = self.market_info.get_price_by_type(PriceType.BestBid) * Decimal("0.99") + self.assertEqual(buy_target, buy_bid.price) + sell_bid = bid_strategy.active_sells[0] + sell_target = self.market_info.get_price_by_type(PriceType.BestBid) * Decimal("1.01") + self.assertEqual(sell_target, sell_bid.price) + + buy_ask = ask_strategy.active_buys[0] + buy_target = self.market_info.get_price_by_type(PriceType.BestAsk) * Decimal("0.99") + self.assertEqual(buy_target, buy_ask.price) + sell_ask = ask_strategy.active_sells[0] + sell_target = self.market_info.get_price_by_type(PriceType.BestAsk) * Decimal("1.01") + self.assertEqual(sell_target, sell_ask.price) + + def test_basic_multiple_levels(self): + strategy = self.multi_levels_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + buys = strategy.active_buys + sells = strategy.active_sells + self.assertEqual(3, len(buys)) + self.assertEqual(3, len(sells)) + self.assertEqual(Decimal("99"), buys[0].price) + self.assertEqual(Decimal("1"), buys[0].quantity) + self.assertEqual(Decimal("98"), buys[1].price) + self.assertEqual(Decimal("2"), buys[1].quantity) + self.assertEqual(Decimal("101"), sells[0].price) + self.assertEqual(Decimal("1"), sells[0].quantity) + self.assertEqual(Decimal("103"), sells[2].price) + self.assertEqual(Decimal("3"), sells[2].quantity) + + # After order_refresh_time, a new set of orders is created + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + self.assertNotEqual(buys[0].client_order_id, strategy.active_buys[0].client_order_id) + self.assertNotEqual(sells[0].client_order_id, strategy.active_sells[0].client_order_id) + + # Simulate buy order filled + self.clock.backtest_til(self.start_timestamp + 8) + self.simulate_maker_market_trade(False, 100, 97.9) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + # After filled_ore + self.clock.backtest_til(self.start_timestamp + 14) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + def test_apply_budget_constraint_to_proposal(self): + strategy = self.multi_levels_strategy + self.clock.add_iterator(strategy) + self.market.set_balance("HBOT", Decimal("50")) + self.market.set_balance("ETH", Decimal("0")) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + for order in strategy.active_sells: + strategy.cancel_order(order.client_order_id) + + self.market.set_balance("HBOT", 0) + self.market.set_balance("ETH", Decimal("5000")) + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + for order in strategy.active_buys: + strategy.cancel_order(order.client_order_id) + + self.market.set_balance("HBOT", Decimal("6.0")) + self.market.set_balance("ETH", Decimal("586.0")) + self.clock.backtest_til(self.start_timestamp + 20) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + self.assertEqual(Decimal("97"), strategy.active_buys[-1].price) + self.assertEqual(Decimal("3"), strategy.active_buys[-1].quantity) + self.assertEqual(Decimal("103"), strategy.active_sells[-1].price) + self.assertEqual(Decimal("3"), strategy.active_sells[-1].quantity) + + def test_order_quantity_available_balance(self): + """ + When balance is below the specified order amount, checks if orders created + use the remaining available balance for the order size. + """ + strategy = PureMarketMakingStrategy() + strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_refresh_time=5, + order_amount=Decimal("100"), + order_levels=3 + ) + + self.clock.add_iterator(strategy) + self.market.set_balance("HBOT", Decimal("10")) + self.market.set_balance("ETH", Decimal("1000")) + self.clock.backtest_til(self.start_timestamp + 1) + + # Check if order size on both sides is equal to the remaining balance + self.assertEqual(Decimal("10.1010"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("10"), strategy.active_sells[0].quantity) + + # Order levels created + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + strategy.cancel_order(strategy.active_buys[0].client_order_id) + strategy.cancel_order(strategy.active_sells[0].client_order_id) + + # Do not create order on side with 0 balance + self.market.set_balance("HBOT", 0) + self.market.set_balance("ETH", Decimal("1000")) + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + def test_market_become_wider(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(Decimal("99"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101"), strategy.active_sells[0].price) + self.assertEqual(Decimal("1.0"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("1.0"), strategy.active_sells[0].quantity) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], 90, 110) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(2, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + self.assertEqual(Decimal("99"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101"), strategy.active_sells[0].price) + self.assertEqual(Decimal("1.0"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("1.0"), strategy.active_sells[0].quantity) + + def test_market_became_narrower(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(Decimal("99"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101"), strategy.active_sells[0].price) + self.assertEqual(Decimal("1.0"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("1.0"), strategy.active_sells[0].quantity) + + self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99.5, 30, 2)], [OrderBookRow(100.5, 30, 2)], 2) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(2, len(self.cancel_order_logger.event_log)) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + self.assertEqual(Decimal("99"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101"), strategy.active_sells[0].price) + self.assertEqual(Decimal("1.0"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("1.0"), strategy.active_sells[0].quantity) + + def test_price_band_price_ceiling_breach(self): + strategy = self.multi_levels_strategy + strategy.price_ceiling = Decimal("105") + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], self.mid_price, 115, ) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + def test_price_band_price_floor_breach(self): + strategy = self.multi_levels_strategy + strategy.price_floor = Decimal("95") + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], 85, self.mid_price) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + def test_moving_price_band_price_ceiling_breach(self): + strategy = self.multi_levels_strategy + strategy.moving_price_band_enabled = True + strategy.price_floor_pct = Decimal("-1") + strategy.price_ceiling_pct = Decimal("1") + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], self.mid_price, 115, ) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + def test_moving_price_band_price_floor_breach(self): + strategy = self.multi_levels_strategy + strategy.moving_price_band_enabled = True + strategy.price_floor_pct = Decimal("-1") + strategy.price_ceiling_pct = Decimal("1") + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], 85, self.mid_price) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + def test_add_transaction_costs(self): + strategy = self.multi_levels_strategy + strategy.add_transaction_costs_to_orders = True + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + # Todo: currently hummingsim market doesn't store fee in a percentage value, so we cannot test further on this. + + def test_filled_order_delay(self): + strategy = self.one_level_strategy + strategy.filled_order_delay = 60.0 + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + self.clock.backtest_til(self.start_timestamp + 7) + # Ask is filled and due to delay is not replenished immediately + self.simulate_maker_market_trade(True, 100, Decimal("101.1")) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + self.clock.backtest_til(self.start_timestamp + 15) + # After order_refresh_time, buy order gets canceled + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + # still no orders + self.clock.backtest_til(self.start_timestamp + 30) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + # still no orders + self.clock.backtest_til(self.start_timestamp + 45) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + # Orders are placed after replenish delay + self.clock.backtest_til(self.start_timestamp + 69) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + # Prices are not adjusted according to filled price as per settings + self.assertEqual(Decimal("99"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101"), strategy.active_sells[0].price) + self.assertEqual(Decimal("1.0"), strategy.active_buys[0].quantity) + self.assertEqual(Decimal("1.0"), strategy.active_sells[0].quantity) + self.order_fill_logger.clear() + + def test_filled_order_delay_mulitiple_orders(self): + strategy = self.multi_levels_strategy + strategy.filled_order_delay = 10.0 + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + self.simulate_maker_market_trade(True, 100, Decimal("101.1")) + + # Ask is filled and due to delay is not replenished immediately + self.clock.backtest_til(self.start_timestamp + 2) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(2, len(strategy.active_sells)) + + # After order_refresh_time, buy order gets canceled + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + # Orders are placed after replenish delay + self.clock.backtest_til(self.start_timestamp + 12) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + self.order_fill_logger.clear() + + def test_order_optimization(self): + # Widening the order book, top bid is now 97.5 and top ask 102.5 + simulate_order_book_widening(self.market.order_books[self.trading_pair], 98, 102) + strategy = self.one_level_strategy + strategy.order_optimization_enabled = True + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(Decimal("97.5001"), strategy.active_buys[0].price) + self.assertEqual(Decimal("102.499"), strategy.active_sells[0].price) + + def test_order_optimization_with_multiple_order_levels(self): + # Widening the order book, top bid is now 97.5 and top ask 102.5 + simulate_order_book_widening(self.market.order_books[self.trading_pair], 98, 102) + strategy = self.multi_levels_strategy + strategy.order_optimization_enabled = True + strategy.order_level_spread = Decimal("0.025") + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + self.assertEqual(Decimal("97.5001"), strategy.active_buys[0].price) + self.assertEqual(Decimal("102.499"), strategy.active_sells[0].price) + self.assertEqual(strategy.active_buys[1].price / strategy.active_buys[0].price, Decimal("0.975")) + self.assertEqual(strategy.active_buys[2].price / strategy.active_buys[0].price, Decimal("0.95")) + self.assertEqual(strategy.active_sells[1].price / strategy.active_sells[0].price, Decimal("1.025")) + self.assertEqual(strategy.active_sells[2].price / strategy.active_sells[0].price, Decimal("1.05")) + + def test_hanging_orders(self): + strategy = self.one_level_strategy + strategy.order_refresh_time = 4.0 + strategy.filled_order_delay = 8.0 + strategy.hanging_orders_enabled = True + strategy.hanging_orders_cancel_pct = Decimal("0.05") + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + self.simulate_maker_market_trade(False, 100, 98.9) + + # Bid is filled and due to delay is not replenished immediately + # Ask order is now hanging but is active + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + 1) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(1, len(strategy.hanging_order_ids)) + hanging_sell = strategy.active_sells[0] + self.assertEqual(hanging_sell.client_order_id, strategy.hanging_order_ids[0]) + + # At order_refresh_time, hanging order remains. + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time * 2 + 1) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + # At filled_order_delay, a new set of bid and ask orders (one each) is created + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time * 2 + 2) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(2, len(strategy.active_sells)) + + self.assertIn(hanging_sell.client_order_id, [order.client_order_id for order in strategy.active_sells]) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], 80, 100) + # As book bids moving lower, the ask hanging order price spread is now more than the hanging_orders_cancel_pct + # Hanging order is canceled and removed from the active list + self.clock.backtest_til(self.start_timestamp + 11 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertNotIn(strategy.active_sells[0].client_order_id, strategy.hanging_order_ids) + + self.order_fill_logger.clear() + + def test_hanging_order_max_order_age(self): + # Note: Due to orders created by backtest market don't have timestamp in their IDs, thus max order age cannot + # be calculated. We manually add the order to to_recreate_hanging_orders and trigger order cancel event here to + # simulate the recreate. + strategy = self.one_level_strategy + strategy.hanging_orders_enabled = True + + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + self.simulate_maker_market_trade(False, 100, 98.9) + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + 1) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(1, len(strategy.hanging_order_ids)) + hanging_sell: LimitOrder = strategy.active_sells[0] + self.assertEqual(hanging_sell.client_order_id, strategy.hanging_order_ids[0]) + + self.market.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent( + self.market.current_timestamp, + hanging_sell.client_order_id + )) + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time * 2 + 1) + self.assertEqual(1, len(strategy.active_sells)) + new_hang: LimitOrder = [o for o in strategy.active_sells if o.price == hanging_sell.price + and o.quantity == hanging_sell.quantity] + + self.assertNotEqual(hanging_sell.client_order_id, new_hang[0].client_order_id) + + def test_hanging_orders_multiple_orders(self): + strategy = self.multi_levels_strategy + strategy.order_refresh_time = 4.0 + strategy.filled_order_delay = 8.0 + strategy.hanging_orders_enabled = True + strategy.hanging_orders_cancel_pct = Decimal("0.05") + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + self.simulate_maker_market_trade(False, 100, 98.9) + + # Bid is filled and due to delay is not replenished immediately + # Ask order is active + # Hanging orders are not yet created + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time / 2) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(2, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + self.assertEqual(0, len(strategy.hanging_order_ids)) + + # At order_refresh_time, hanging orders are created, active orders are cancelled + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + 1) + + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(1, len(strategy.hanging_order_ids)) + + # At filled_order_delay, a new set of bid and ask orders (one each) is created + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + strategy.filled_order_delay + 1) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(4, len(strategy.active_sells)) + + self.assertTrue(all(id in (order.client_order_id for order in strategy.active_sells) + for id in strategy.hanging_order_ids)) + + simulate_order_book_widening(self.market.order_books[self.trading_pair], 80, 100) + # As book bids moving lower, the ask hanging order price spread is now more than the hanging_orders_cancel_pct + # Hanging order is canceled and removed from the active list + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time * 2 + strategy.filled_order_delay + 1) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + self.assertFalse(any(o.client_order_id in strategy.hanging_order_ids for o in strategy.active_sells)) + + self.order_fill_logger.clear() + + def test_inventory_skew(self): + strategy = self.one_level_strategy + strategy.inventory_skew_enabled = True + strategy.inventory_target_base_pct = Decimal("0.9") + strategy.inventory_range_multiplier = Decimal("5.0") + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(Decimal("101"), first_ask_order.price) + self.assertEqual(Decimal("0.5"), first_bid_order.quantity) + self.assertEqual(Decimal("1.5"), first_ask_order.quantity) + + self.simulate_maker_market_trade(True, 5.0, 101.1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + self.clock.backtest_til(self.start_timestamp + 2) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + + maker_fill = self.order_fill_logger.event_log[0] + self.assertEqual(TradeType.SELL, maker_fill.trade_type) + self.assertAlmostEqual(101, maker_fill.price) + self.assertAlmostEqual(Decimal("1.5"), Decimal(str(maker_fill.amount)), places=4) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(Decimal("101"), first_ask_order.price) + self.assertEqual(Decimal("0.651349"), first_bid_order.quantity) + self.assertEqual(Decimal("1.34865"), first_ask_order.quantity) + + def test_inventory_skew_multiple_orders(self): + strategy = PureMarketMakingStrategy() + strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + order_levels=5, + order_level_spread=Decimal("0.01"), + order_level_amount=Decimal("0.5"), + inventory_skew_enabled=True, + inventory_target_base_pct=Decimal("0.9"), + inventory_range_multiplier=Decimal("0.5"), + minimum_spread=-1, + ) + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(Decimal("101"), first_ask_order.price) + self.assertEqual(Decimal("0.5"), first_bid_order.quantity) + self.assertEqual(Decimal("1.5"), first_ask_order.quantity) + + last_bid_order = strategy.active_buys[-1] + last_ask_order = strategy.active_sells[-1] + last_bid_price = Decimal(100 * (1 - 0.01 - (0.01 * 4))).quantize(Decimal("0.001")) + last_ask_price = Decimal(100 * (1 + 0.01 + (0.01 * 4))).quantize(Decimal("0.001")) + self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) + self.assertAlmostEqual(last_ask_price, last_ask_order.price, 3) + self.assertEqual(Decimal("1.5"), last_bid_order.quantity) + self.assertEqual(Decimal("4.5"), last_ask_order.quantity) + + self.simulate_maker_market_trade(True, 5.0, 101.1) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(4, len(strategy.active_sells)) + + self.clock.backtest_til(self.start_timestamp + 3) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + + maker_fill = self.order_fill_logger.event_log[0] + self.assertEqual(TradeType.SELL, maker_fill.trade_type) + self.assertAlmostEqual(101, maker_fill.price) + self.assertAlmostEqual(Decimal("1.5"), Decimal(str(maker_fill.amount)), places=4) + + # The default filled_order_delay is 60, so gotta wait 60 + 2 here. + self.clock.backtest_til(self.start_timestamp + 7 * self.clock_tick_size + 1) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + last_bid_order = strategy.active_buys[-1] + last_ask_order = strategy.active_sells[-1] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(Decimal("101"), first_ask_order.price) + self.assertEqual(Decimal("0.651349"), first_bid_order.quantity) + self.assertEqual(Decimal("1.34865"), first_ask_order.quantity) + last_bid_price = Decimal(100 * (1 - 0.01 - (0.01 * 4))).quantize(Decimal("0.001")) + last_ask_price = Decimal(100 * (1 + 0.01 + (0.01 * 4))).quantize(Decimal("0.001")) + self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) + self.assertAlmostEqual(last_ask_price, last_ask_order.price, 3) + self.assertEqual(Decimal("1.95404"), last_bid_order.quantity) + self.assertEqual(Decimal("4.04595"), last_ask_order.quantity) + + def test_inventory_skew_multiple_orders_status(self): + strategy = PureMarketMakingStrategy() + strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5.0, + filled_order_delay=5.0, + order_refresh_tolerance_pct=-1, + order_levels=5, + order_level_spread=Decimal("0.01"), + order_level_amount=Decimal("0.5"), + inventory_skew_enabled=True, + inventory_target_base_pct=Decimal("0.9"), + inventory_range_multiplier=Decimal("0.5"), + minimum_spread=-1, + ) + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + + status_df: pd.DataFrame = strategy.inventory_skew_stats_data_frame() + self.assertEqual("50.0%", status_df.iloc[4, 1]) + self.assertEqual("150.0%", status_df.iloc[4, 2]) + + def test_inventory_cost_price_del(self): + strategy = self.one_level_strategy + strategy.inventory_cost_price_delegate = self.inventory_cost_price_del + self.clock.add_iterator(strategy) + self.market.set_balance("HBOT", 0) + self.clock.backtest_til(self.start_timestamp + 1) + + # Expecting to have orders set according to mid_price as there is no inventory cost data yet + first_bid_order = strategy.active_buys[0] + self.assertEqual(Decimal("99"), first_bid_order.price) + + self.simulate_maker_market_trade( + is_buy=False, quantity=Decimal("10"), price=Decimal("98.9"), + ) + new_mid_price = Decimal("96") + self.market.set_balanced_order_book( + trading_pair=self.trading_pair, + mid_price=new_mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10, + ) + self.clock.backtest_til(self.start_timestamp + 7) + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + expected_bid_price = new_mid_price / Decimal(1 + self.bid_spread) + self.assertAlmostEqual(expected_bid_price, first_bid_order.price, places=1) + expected_ask_price = Decimal("99") * Decimal(1 + self.ask_spread) + self.assertAlmostEqual(expected_ask_price, first_ask_order.price) + + def test_order_book_asset_del(self): + strategy = self.one_level_strategy + strategy.asset_price_delegate = self.order_book_asset_del + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.simulate_maker_market_trade( + is_buy=True, quantity=Decimal("1"), price=Decimal("123"), market=self.ext_market, + ) + + bid = self.order_book_asset_del.get_price_by_type(PriceType.BestBid) + ask = self.order_book_asset_del.get_price_by_type(PriceType.BestAsk) + mid_price = self.order_book_asset_del.get_price_by_type(PriceType.MidPrice) + last_trade = self.order_book_asset_del.get_price_by_type(PriceType.LastTrade) + + self.assertEqual((bid + ask) / 2, mid_price) + self.assertEqual(last_trade, Decimal("123")) + assert isinstance(bid, Decimal) + assert isinstance(ask, Decimal) + assert isinstance(mid_price, Decimal) + assert isinstance(last_trade, Decimal) + + def test_external_exchange_price_source(self): + strategy = self.one_level_strategy + strategy.asset_price_delegate = self.order_book_asset_del + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(strategy.active_buys)) + # There should be no sell order, since its price will be below first bid order on the order book. + self.assertEqual(0, len(strategy.active_sells)) + + # check price data from external exchange is used for order placement + bid_order = strategy.active_buys[0] + self.assertEqual(Decimal("49.5"), bid_order.price) + self.assertEqual(Decimal("1.0"), bid_order.quantity) + + def test_external_exchange_price_source_empty_orderbook(self): + simulate_order_book_widening(self.market.order_books[self.trading_pair], 0, 10000) + self.assertEqual(0, len(list(self.market.order_books[self.trading_pair].bid_entries()))) + self.assertEqual(0, len(list(self.market.order_books[self.trading_pair].ask_entries()))) + strategy = self.one_level_strategy + strategy.asset_price_delegate = self.order_book_asset_del + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + # check price data from external exchange is used for order placement + bid_order = strategy.active_buys[0] + self.assertEqual(Decimal("49.5"), bid_order.price) + self.assertEqual(Decimal("1.0"), bid_order.quantity) + ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("50.5"), ask_order.price) + self.assertEqual(Decimal("1.0"), ask_order.quantity) + + def test_multi_order_external_exchange_price_source(self): + strategy = self.multi_levels_strategy + strategy.asset_price_delegate = self.order_book_asset_del + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + # There should be no sell order, since its price will be below first bid order on the order book. + self.assertEqual(0, len(strategy.active_sells)) + + # check price data from external exchange is used for order placement + bid_order = strategy.active_buys[0] + self.assertEqual(Decimal("49.5"), bid_order.price) + self.assertEqual(Decimal("1.0"), bid_order.quantity) + + last_bid_order = strategy.active_buys[-1] + last_bid_price = Decimal(50 * (1 - 0.01 - (0.01 * 2))).quantize(Decimal("0.001")) + self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) + self.assertEqual(Decimal("3.0"), last_bid_order.quantity) + + def test_multi_order_external_exchange_price_source_empty_order_book(self): + simulate_order_book_widening(self.market.order_books[self.trading_pair], 0, 10000) + self.assertEqual(0, len(list(self.market.order_books[self.trading_pair].bid_entries()))) + self.assertEqual(0, len(list(self.market.order_books[self.trading_pair].ask_entries()))) + strategy = self.multi_levels_strategy + strategy.asset_price_delegate = self.order_book_asset_del + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + # check price data from external exchange is used for order placement + bid_order = strategy.active_buys[0] + self.assertEqual(Decimal("49.5"), bid_order.price) + self.assertEqual(Decimal("1.0"), bid_order.quantity) + + last_bid_order = strategy.active_buys[-1] + last_bid_price = Decimal(50 * (1 - 0.01 - (0.01 * 2))).quantize(Decimal("0.001")) + self.assertAlmostEqual(last_bid_price, last_bid_order.price, 3) + self.assertEqual(Decimal("3.0"), last_bid_order.quantity) + + def test_config_spread_on_the_fly_multiple_orders(self): + strategy = self.multi_levels_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(3, len(strategy.active_buys)) + self.assertEqual(3, len(strategy.active_sells)) + + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(Decimal("101"), first_ask_order.price) + + last_bid_order = strategy.active_buys[-1] + last_ask_order = strategy.active_sells[-1] + self.assertAlmostEqual(Decimal("97"), last_bid_order.price, 2) + self.assertAlmostEqual(Decimal("103"), last_ask_order.price, 2) + + ConfigCommand.update_running_mm(strategy, "bid_spread", Decimal('2')) + ConfigCommand.update_running_mm(strategy, "ask_spread", Decimal('2')) + for order in strategy.active_sells: + strategy.cancel_order(order.client_order_id) + for order in strategy.active_buys: + strategy.cancel_order(order.client_order_id) + self.clock.backtest_til(self.start_timestamp + 7) + first_bid_order = strategy.active_buys[0] + first_ask_order = strategy.active_sells[0] + self.assertEqual(Decimal("98"), first_bid_order.price) + self.assertEqual(Decimal("102"), first_ask_order.price) + + last_bid_order = strategy.active_buys[-1] + last_ask_order = strategy.active_sells[-1] + self.assertAlmostEqual(Decimal("96"), last_bid_order.price, 2) + self.assertAlmostEqual(Decimal("104"), last_ask_order.price, 2) + + def test_order_override(self): + strategy = self.order_override_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + buys = strategy.active_buys + sells = strategy.active_sells + self.assertEqual(2, len(buys)) + self.assertEqual(1, len(sells)) + self.assertEqual(Decimal("99.5"), buys[0].price) + self.assertEqual(Decimal("0.7"), buys[0].quantity) + self.assertEqual(Decimal("98.7"), buys[1].price) + self.assertEqual(Decimal("1.1"), buys[1].quantity) + self.assertEqual(Decimal("101.1"), sells[0].price) + self.assertEqual(Decimal("2"), sells[0].quantity) + + def test_custom_price_source(self): + strategy = self.custom_price_source_strategy + self.clock.add_iterator(strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + buy = strategy.active_buys[0] + self.assertEqual(Decimal("99"), buy.price) + self.assertEqual(1, buy.quantity) + sell = strategy.active_sells[0] + self.assertEqual(Decimal("101"), sell.price) + self.assertEqual(1, sell.quantity) + + self.custom_asset_price_delegate.set_mock_price(mock_price=Decimal("101.0")) + + self.clock.backtest_til(self.start_timestamp + 7) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + buy = strategy.active_buys[0] + self.assertEqual(Decimal("99.99"), buy.price) + self.assertEqual(1, buy.quantity) + sell = strategy.active_sells[0] + self.assertEqual(Decimal("102.01"), sell.price) + self.assertEqual(1, sell.quantity) + + def test_no_new_orders_created_until_previous_orders_cancellation_confirmed(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + + refresh_time = strategy.order_refresh_time + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + orders_creation_timestamp = strategy.current_timestamp + + # Add a fake in flight cancellation to simulate a confirmation has not arrived + strategy._sb_order_tracker.in_flight_cancels["OID-99"] = strategy.current_timestamp + + # After refresh time the two real orders should be cancelled, but no new order should be created + self.clock.backtest_til(orders_creation_timestamp + refresh_time) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + # After a second refresh time no new order should be created + self.clock.backtest_til(orders_creation_timestamp + (2 * refresh_time)) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + + del strategy._sb_order_tracker.in_flight_cancels["OID-99"] + + # After removing the pending cancel, in the next tick the new orders should be created + self.clock.backtest_til(strategy.current_timestamp + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + + def test_adjusted_available_balance_considers_in_flight_cancel_orders(self): + base_balance = self.market.get_available_balance(self.base_asset) + quote_balance = self.market.get_available_balance(self.quote_asset) + + strategy = self.one_level_strategy + + strategy._sb_order_tracker.start_tracking_limit_order( + market_pair=self.market_info, + order_id="OID-1", + is_buy=True, + price=Decimal(1000), + quantity=Decimal(1)) + strategy._sb_order_tracker.start_tracking_limit_order( + market_pair=self.market_info, + order_id="OID-2", + is_buy=False, + price=Decimal(2000), + quantity=Decimal(2)) + + strategy._sb_order_tracker.in_flight_cancels["OID-1"] = strategy.current_timestamp + + available_base_balance, available_quote_balance = strategy.adjusted_available_balance_for_orders_budget_constrain() + + self.assertEqual(available_base_balance, base_balance + Decimal(2)) + self.assertEqual(available_quote_balance, quote_balance + (Decimal(1) * Decimal(1000))) + + def test_split_order_levels(self): + # Widening the order book, top bid is now 97.5 and top ask 102.5 + simulate_order_book_widening(self.market.order_books[self.trading_pair], 98, 102) + strategy = self.split_order_level_strategy + strategy.order_optimization_enabled = False + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(2, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(Decimal("99.000"), strategy.active_buys[0].price) + self.assertEqual(Decimal("101.000"), strategy.active_sells[0].price) + self.assertEqual(Decimal("98.000"), strategy.active_buys[1].price) + + def test_order_optimization_with_split_order_levels(self): + # Widening the order book, top bid is now 97.5 and top ask 102.5 + simulate_order_book_widening(self.market.order_books[self.trading_pair], 98, 102) + strategy = self.split_order_level_strategy + strategy.order_optimization_enabled = True + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.assertEqual(2, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(Decimal("97.5001"), strategy.active_buys[0].price) + self.assertEqual(Decimal("102.499"), strategy.active_sells[0].price) + self.assertEqual(strategy.active_buys[1].price / strategy.active_buys[0].price, Decimal("0.98") / Decimal("0.99")) + + +class PureMarketMakingMinimumSpreadUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair = "COINALPHA-WETH" + maker_trading_pairs: List[str] = ["COINALPHA-WETH", "COINALPHA", "WETH"] + + def setUp(self): + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.mid_price, min_price=1, + max_price=200, price_step_size=1, volume_step_size=10) + self.market.set_balance("COINALPHA", 500) + self.market.set_balance("WETH", 5000) + self.market.set_balance("QETH", 500) + self.market.set_quantization_param( + QuantizationParams( + self.maker_trading_pairs[0], 6, 6, 6, 6 + ) + ) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.maker_trading_pairs[0], + self.maker_trading_pairs[1], self.maker_trading_pairs[2] + ) + + self.strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() + self.strategy.init_params( + self.market_info, + bid_spread=Decimal(.05), + ask_spread=Decimal(.05), + order_amount=Decimal(1), + order_refresh_time=30, + minimum_spread=0, + ) + + def test_minimum_spread_param(self): + strategy = self.strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + old_bid = strategy.active_buys[0] + old_ask = strategy.active_sells[0] + # t = 2, No Change => orders should stay the same + self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) + self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) + # Minimum Spread Threshold Cancellation + # t = 3, Mid Market Price Moves Down - Below Min Spread (Old Bid) => Buy Order Cancelled + self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(50, 1000, 2)], + [OrderBookRow(50, 1000, 2)], 2) + self.clock.backtest_til(self.start_timestamp + 3 * self.clock_tick_size) + self.assertEqual(0, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + # t = 30, New Set of Orders + self.clock.backtest_til(self.start_timestamp + 32 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + new_bid = strategy.active_buys[0] + new_ask = strategy.active_sells[0] + self.assertNotEqual(old_bid.client_order_id, new_bid.client_order_id) + self.assertNotEqual(old_ask.client_order_id, new_ask.client_order_id) + old_ask = new_ask + old_bid = new_bid + # t = 35, No Change + self.clock.backtest_til(self.start_timestamp + 36 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) + self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) + # t = 36, Mid Market Price Moves Up - Below Min Spread (Old Ask) => Sell Order Cancelled + # Clear Order Book (setting all orders above price 0, to quantity 0) + simulate_order_book_widening(self.market.order_books[self.trading_pair], 0, 0) + # New Mid-Market Price + self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99, 1000, 3)], + [OrderBookRow(101, 1000, 3)], 3) + # Check That Order Book Manipulations Didn't Affect Strategy Orders Yet + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) + self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) + # Simulate Minimum Spread Threshold Cancellation + self.clock.backtest_til(self.start_timestamp + 40 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) diff --git a/test/hummingbot/strategy/pure_market_making/test_pmm_config_map.py b/test/hummingbot/strategy/pure_market_making/test_pmm_config_map.py new file mode 100644 index 0000000..d63dac0 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pmm_config_map.py @@ -0,0 +1,128 @@ +import unittest +from copy import deepcopy + +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.strategy.pure_market_making.pure_market_making_config_map import ( + pure_market_making_config_map as pmm_config_map, + on_validate_price_source, + validate_price_type, + order_amount_prompt, + maker_trading_pair_prompt, + validate_price_source_exchange, + validate_decimal_list +) + + +class TestPMMConfigMap(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.exchange = "binance" + cls.base_asset = "COINALPHA" + cls.quote_asset = "HBOT" + cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" + + def setUp(self) -> None: + super().setUp() + self.config_backup = deepcopy(pmm_config_map) + + def tearDown(self) -> None: + self.reset_config_map() + super().tearDown() + + def reset_config_map(self): + for key, value in self.config_backup.items(): + pmm_config_map[key] = value + + def test_on_validate_price_source_non_external_market_reset(self): + pmm_config_map["price_source_exchange"].value = "an_extmkt" + pmm_config_map["price_source_market"].value = self.trading_pair + pmm_config_map["take_if_crossed"].value = False + + on_validate_price_source(value="current_market") + + self.assertIsNone(pmm_config_map["price_source_exchange"].value) + self.assertIsNone(pmm_config_map["price_source_market"].value) + self.assertIsNone(pmm_config_map["take_if_crossed"].value) + + def test_on_validate_price_source_non_custom_api_reset(self): + pmm_config_map["price_source_custom_api"].value = "https://someurl.com" + + on_validate_price_source(value="current_market") + + self.assertIsNone(pmm_config_map["price_source_custom_api"].value) + + def test_on_validate_price_source_custom_api_set_price_type(self): + on_validate_price_source(value="custom_api") + + self.assertEqual(pmm_config_map["price_type"].value, "custom") + + def test_validate_price_type_non_custom_api(self): + pmm_config_map["price_source"].value = "current_market" + + error = validate_price_type(value="mid_price") + self.assertIsNone(error) + error = validate_price_type(value="last_price") + self.assertIsNone(error) + error = validate_price_type(value="last_own_trade_price") + self.assertIsNone(error) + error = validate_price_type(value="best_bid") + self.assertIsNone(error) + error = validate_price_type(value="best_ask") + self.assertIsNone(error) + error = validate_price_type(value="inventory_cost") + self.assertIsNone(error) + + error = validate_price_type(value="custom") + self.assertIsNotNone(error) + + def test_validate_price_type_custom_api(self): + pmm_config_map["price_source"].value = "custom_api" + + error = validate_price_type(value="mid_price") + self.assertIsNotNone(error) + error = validate_price_type(value="last_price") + self.assertIsNotNone(error) + error = validate_price_type(value="last_own_trade_price") + self.assertIsNotNone(error) + error = validate_price_type(value="best_bid") + self.assertIsNotNone(error) + error = validate_price_type(value="best_ask") + self.assertIsNotNone(error) + error = validate_price_type(value="inventory_cost") + self.assertIsNotNone(error) + + error = validate_price_type(value="custom") + self.assertIsNone(error) + + def test_order_amount_prompt(self): + pmm_config_map["market"].value = self.trading_pair + prompt = order_amount_prompt() + expected = f"What is the amount of {self.base_asset} per order? >>> " + + self.assertEqual(expected, prompt) + + def test_maker_trading_pair_prompt(self): + pmm_config_map["exchange"].value = self.exchange + example = AllConnectorSettings.get_example_pairs().get(self.exchange) + + prompt = maker_trading_pair_prompt() + expected = f"Enter the token trading pair you would like to trade on {self.exchange} (e.g. {example}) >>> " + + self.assertEqual(expected, prompt) + + def test_validate_price_source_exchange(self): + pmm_config_map["exchange"].value = self.exchange + self.assertEqual(validate_price_source_exchange(value='binance'), + 'Price source exchange cannot be the same as maker exchange.') + self.assertIsNone(validate_price_source_exchange(value='kucoin')) + self.assertIsNone(validate_price_source_exchange(value='binance_perpetual')) + + def test_validate_decimal_list(self): + error = validate_decimal_list(value="1") + self.assertIsNone(error) + error = validate_decimal_list(value="1,2") + self.assertIsNone(error) + error = validate_decimal_list(value="asd") + expected = "Please enter valid decimal numbers" + self.assertEqual(expected, error) diff --git a/test/hummingbot/strategy/pure_market_making/test_pmm_ping_pong.py b/test/hummingbot/strategy/pure_market_making/test_pmm_ping_pong.py new file mode 100644 index 0000000..1a5c6f3 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pmm_ping_pong.py @@ -0,0 +1,226 @@ +import logging +import unittest +from decimal import Decimal + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookTradeEvent +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.pure_market_making.pure_market_making import PureMarketMakingStrategy + +logging.basicConfig(level=logging.ERROR) + + +class PMMRefreshToleranceUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair = "HBOT-ETH" + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + + def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): + order_book = self.market.get_order_book(self.trading_pair) + trade_event = OrderBookTradeEvent( + self.trading_pair, + self.clock.current_timestamp, + TradeType.BUY if is_buy else TradeType.SELL, + price, + quantity + ) + order_book.apply_trade(trade_event) + + def setUp(self): + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.bid_spread = 0.01 + self.ask_spread = 0.01 + self.order_refresh_time = 30 + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("HBOT", 500) + self.market.set_balance("ETH", 5000) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, + self.base_asset, self.quote_asset) + self.clock.add_iterator(self.market) + self.maker_order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + def test_strategy_ping_pong_on_ask_fill(self): + self.strategy = PureMarketMakingStrategy() + self.strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5, + filled_order_delay=5, + order_refresh_tolerance_pct=-1, + ping_pong_enabled=True, + ) + self.clock.add_iterator(self.strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) + + self.clock.backtest_til( + self.start_timestamp + 2 * self.clock_tick_size + ) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + old_bid = self.strategy.active_buys[0] + + self.clock.backtest_til( + self.start_timestamp + 7 * self.clock_tick_size + ) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + # After new order create cycle (after filled_order_delay), check if a new order is created + self.assertTrue(old_bid.client_order_id != self.strategy.active_buys[0].client_order_id) + + self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) + + self.clock.backtest_til( + self.start_timestamp + 15 * self.clock_tick_size + ) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + def test_strategy_ping_pong_on_bid_fill(self): + self.strategy = PureMarketMakingStrategy() + self.strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=5, + filled_order_delay=5, + order_refresh_tolerance_pct=-1, + ping_pong_enabled=True, + ) + self.clock.add_iterator(self.strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) + + self.clock.backtest_til( + self.start_timestamp + 2 * self.clock_tick_size + ) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + old_ask = self.strategy.active_sells[0] + + self.clock.backtest_til( + self.start_timestamp + 7 * self.clock_tick_size + ) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + # After new order create cycle (after filled_order_delay), check if a new order is created + self.assertTrue(old_ask.client_order_id != self.strategy.active_sells[0].client_order_id) + + self.simulate_maker_market_trade(True, Decimal(100), Decimal("101.1")) + + self.clock.backtest_til( + self.start_timestamp + 15 * self.clock_tick_size + ) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + def test_multiple_orders_ping_pong(self): + self.strategy = PureMarketMakingStrategy() + self.strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_levels=5, + order_level_amount=Decimal("1"), + order_level_spread=Decimal("0.01"), + order_refresh_time=5, + order_refresh_tolerance_pct=-1, + filled_order_delay=5, + ping_pong_enabled=True, + ) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + + self.assertEqual(5, len(self.strategy.active_buys)) + self.assertEqual(5, len(self.strategy.active_sells)) + + self.simulate_maker_market_trade(True, Decimal(100), Decimal("102.50")) + # After market trade happens, 2 of the asks orders are filled. + self.assertEqual(5, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + self.clock.backtest_til( + self.start_timestamp + 2 * self.clock_tick_size + ) + # Not refreshing time yet, still same active orders + self.assertEqual(5, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + old_bids = self.strategy.active_buys + old_asks = self.strategy.active_sells + self.clock.backtest_til( + self.start_timestamp + 7 * self.clock_tick_size + ) + # After order refresh, same numbers of orders but it's a new set. + self.assertEqual(5, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + self.assertNotEqual([o.client_order_id for o in old_asks], + [o.client_order_id for o in self.strategy.active_sells]) + self.assertNotEqual([o.client_order_id for o in old_bids], + [o.client_order_id for o in self.strategy.active_buys]) + + # Simulate sell trade, the first bid gets taken out + self.simulate_maker_market_trade(False, Decimal(100), Decimal("98.9")) + self.assertEqual(4, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + self.clock.backtest_til( + self.start_timestamp + 13 * self.clock_tick_size + ) + + # After refresh, same numbers of orders + self.assertEqual(4, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + + # Another bid order is filled. + self.simulate_maker_market_trade(False, Decimal(100), Decimal("97.9")) + self.assertEqual(3, len(self.strategy.active_buys)) + self.assertEqual(3, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 20 * self.clock_tick_size + ) + + # After refresh, numbers of orders back to order_levels of 5 + self.assertEqual(5, len(self.strategy.active_buys)) + self.assertEqual(5, len(self.strategy.active_sells)) diff --git a/test/hummingbot/strategy/pure_market_making/test_pmm_refresh_tolerance.py b/test/hummingbot/strategy/pure_market_making/test_pmm_refresh_tolerance.py new file mode 100644 index 0000000..ead02ea --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pmm_refresh_tolerance.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +import logging +import unittest +from decimal import Decimal + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookTradeEvent +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.pure_market_making.pure_market_making import PureMarketMakingStrategy + +logging.basicConfig(level=logging.ERROR) + + +class PMMRefreshToleranceUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair = "HBOT-ETH" + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + + def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): + order_book = self.market.get_order_book(self.trading_pair) + trade_event = OrderBookTradeEvent( + self.trading_pair, + self.clock.current_timestamp, + TradeType.BUY if is_buy else TradeType.SELL, + price, + quantity + ) + order_book.apply_trade(trade_event) + + def setUp(self): + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.bid_spread = 0.01 + self.ask_spread = 0.01 + self.order_refresh_time = 30 + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("HBOT", 500) + self.market.set_balance("ETH", 5000) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, + self.base_asset, self.quote_asset) + self.clock.add_iterator(self.market) + self.maker_order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + self.one_level_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() + self.one_level_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=4, + filled_order_delay=8, + hanging_orders_enabled=True, + hanging_orders_cancel_pct=0.05, + order_refresh_tolerance_pct=0 + ) + self.multi_levels_strategy: PureMarketMakingStrategy = PureMarketMakingStrategy() + self.multi_levels_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_levels=5, + order_level_spread=Decimal("0.01"), + order_refresh_time=4, + filled_order_delay=8, + order_refresh_tolerance_pct=0 + ) + self.hanging_order_multiple_strategy = PureMarketMakingStrategy() + self.hanging_order_multiple_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_levels=5, + order_level_spread=Decimal("0.01"), + order_refresh_time=4, + filled_order_delay=8, + order_refresh_tolerance_pct=0, + hanging_orders_enabled=True + ) + + def test_active_orders_are_cancelled_when_mid_price_moves(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + old_bid = strategy.active_buys[0] + old_ask = strategy.active_sells[0] + # Not the order refresh time yet, orders should remain the same + self.clock.backtest_til(self.start_timestamp + 3 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) + self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) + self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99.5, 30, 2)], + [OrderBookRow(100.1, 30, 2)], 2) + self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) + new_bid = strategy.active_buys[0] + new_ask = strategy.active_sells[0] + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + self.assertNotEqual(old_ask, new_ask) + self.assertNotEqual(old_bid, new_bid) + + def test_active_orders_are_kept_when_within_tolerance(self): + strategy = self.one_level_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + old_bid = strategy.active_buys[0] + old_ask = strategy.active_sells[0] + self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + new_bid = strategy.active_buys[0] + new_ask = strategy.active_sells[0] + self.assertEqual(old_ask, new_ask) + self.assertEqual(old_bid, new_bid) + self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(1, len(strategy.active_sells)) + new_bid = strategy.active_buys[0] + new_ask = strategy.active_sells[0] + self.assertEqual(old_ask, new_ask) + self.assertEqual(old_bid, new_bid) + + def test_multi_levels_active_orders_are_cancelled_when_mid_price_moves(self): + strategy = self.multi_levels_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + old_buys = strategy.active_buys + old_sells = strategy.active_sells + self.market.order_books[self.trading_pair].apply_diffs([OrderBookRow(99.5, 30, 2)], + [OrderBookRow(100.1, 30, 2)], 2) + self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) + new_buys = strategy.active_buys + new_sells = strategy.active_sells + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + self.assertNotEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) + self.assertNotEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) + + def test_multiple_active_orders_are_kept_when_within_tolerance(self): + strategy = self.multi_levels_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + old_buys = strategy.active_buys + old_sells = strategy.active_sells + self.clock.backtest_til(self.start_timestamp + 6 * self.clock_tick_size) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + new_buys = strategy.active_buys + new_sells = strategy.active_sells + self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) + self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) + self.clock.backtest_til(self.start_timestamp + 10 * self.clock_tick_size) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + new_buys = strategy.active_buys + new_sells = strategy.active_sells + self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) + self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) + + def test_hanging_orders_multiple_orders_with_refresh_tolerance(self): + strategy = self.hanging_order_multiple_strategy + self.clock.add_iterator(strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + + self.simulate_maker_market_trade(True, Decimal("100"), Decimal("101.1")) + + # Before refresh_time hanging orders are not yet created + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time / 2) + self.assertEqual(1, len(self.maker_order_fill_logger.event_log)) + self.assertEqual(5, len(strategy.active_buys)) + self.assertEqual(4, len(strategy.active_sells)) + self.assertEqual(0, len(strategy.hanging_order_ids)) + + # At order_refresh_time (4 seconds), hanging order are created + # Ask is filled and due to delay is not replenished immediately + # Bid orders are now hanging and active + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + 1) + self.assertEqual(1, len(strategy.active_buys)) + self.assertEqual(0, len(strategy.active_sells)) + self.assertEqual(1, len(strategy.hanging_order_ids)) + + # At filled_order_delay (8 seconds), new sets of bid and ask orders are created + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + strategy.filled_order_delay + 1) + self.assertEqual(6, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + self.assertEqual(1, len(strategy.hanging_order_ids)) + + # Check all hanging order ids are indeed in active bids list + self.assertTrue(all(h in [order.client_order_id for order in strategy.active_buys] + for h in strategy.hanging_order_ids)) + + old_buys = [o for o in strategy.active_buys if o.client_order_id not in strategy.hanging_order_ids] + old_sells = [o for o in strategy.active_sells if o.client_order_id not in strategy.hanging_order_ids] + + self.clock.backtest_til(self.start_timestamp + strategy.order_refresh_time + strategy.filled_order_delay + 1) + self.assertEqual(6, len(strategy.active_buys)) + self.assertEqual(5, len(strategy.active_sells)) + + new_buys = [o for o in strategy.active_buys if o.client_order_id not in strategy.hanging_order_ids] + new_sells = [o for o in strategy.active_sells if o.client_order_id not in strategy.hanging_order_ids] + self.assertEqual([o.client_order_id for o in old_sells], [o.client_order_id for o in new_sells]) + self.assertEqual([o.client_order_id for o in old_buys], [o.client_order_id for o in new_buys]) diff --git a/test/hummingbot/strategy/pure_market_making/test_pmm_take_if_cross.py b/test/hummingbot/strategy/pure_market_making/test_pmm_take_if_cross.py new file mode 100644 index 0000000..abd1d10 --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pmm_take_if_cross.py @@ -0,0 +1,181 @@ +import logging +import unittest +from decimal import Decimal +from typing import List + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import MarketEvent, OrderBookTradeEvent +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate +from hummingbot.strategy.pure_market_making.pure_market_making import PureMarketMakingStrategy + +logging.basicConfig(level=logging.ERROR) + + +# Update the orderbook so that the top bids and asks are lower than actual for a wider bid ask spread +# this basially removes the orderbook entries above top bid and below top ask +def simulate_order_book_widening(order_book: OrderBook, top_bid: float, top_ask: float): + bid_diffs: List[OrderBookRow] = [] + ask_diffs: List[OrderBookRow] = [] + update_id: int = order_book.last_diff_uid + 1 + for row in order_book.bid_entries(): + if row.price > top_bid: + bid_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + for row in order_book.ask_entries(): + if row.price < top_ask: + ask_diffs.append(OrderBookRow(row.price, 0, update_id)) + else: + break + order_book.apply_diffs(bid_diffs, ask_diffs, update_id) + + +class PureMMTakeIfCrossUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair = "HBOT-ETH" + base_asset = trading_pair.split("-")[0] + quote_asset = trading_pair.split("-")[1] + + def setUp(self): + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.bid_spread = 0.01 + self.ask_spread = 0.01 + self.order_refresh_time = 30 + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("HBOT", 500) + self.market.set_balance("ETH", 5000) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, + self.base_asset, self.quote_asset) + self.clock.add_iterator(self.market) + self.order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.market.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + self.ext_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.ext_market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.ext_market, self.trading_pair, self.base_asset, self.quote_asset + ) + self.ext_market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=100, min_price=1, max_price=400, price_step_size=1, + volume_step_size=100) + self.order_book_asset_del = OrderBookAssetPriceDelegate(self.ext_market, self.trading_pair) + + self.one_level_strategy = PureMarketMakingStrategy() + self.one_level_strategy.init_params( + self.market_info, + bid_spread=Decimal("0.01"), + ask_spread=Decimal("0.01"), + order_amount=Decimal("1"), + order_refresh_time=3.0, + filled_order_delay=3.0, + order_refresh_tolerance_pct=-1, + minimum_spread=-1, + asset_price_delegate=self.order_book_asset_del, + take_if_crossed=True + ) + + def simulate_maker_market_trade(self, is_buy: bool, quantity: Decimal, price: Decimal): + order_book = self.market.get_order_book(self.trading_pair) + trade_event = OrderBookTradeEvent( + self.trading_pair, + self.clock.current_timestamp, + TradeType.BUY if is_buy else TradeType.SELL, + price, + quantity + ) + order_book.apply_trade(trade_event) + + def test_strategy_take_if_crossed_bid_order(self): + simulate_order_book_widening(self.ext_market.get_order_book(self.trading_pair), 120.0, 130.0) + self.strategy = self.one_level_strategy + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(0, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 2 * self.clock_tick_size + ) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 7 * self.clock_tick_size + ) + self.assertEqual(2, len(self.order_fill_logger.event_log)) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 10 * self.clock_tick_size + ) + self.assertEqual(3, len(self.order_fill_logger.event_log)) + self.assertEqual(0, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + self.order_fill_logger.clear() + + def test_strategy_take_if_crossed_ask_order(self): + simulate_order_book_widening(self.ext_market.get_order_book(self.trading_pair), 80.0, 90.0) + self.strategy = self.one_level_strategy + self.clock.add_iterator(self.strategy) + + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.assertEqual(0, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(1, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 2 * self.clock_tick_size + ) + self.assertEqual(1, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 6 * self.clock_tick_size + ) + self.assertEqual(2, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + + self.clock.backtest_til( + self.start_timestamp + 10 * self.clock_tick_size + ) + self.assertEqual(3, len(self.order_fill_logger.event_log)) + self.assertEqual(1, len(self.strategy.active_buys)) + self.assertEqual(0, len(self.strategy.active_sells)) + self.order_fill_logger.clear() diff --git a/test/hummingbot/strategy/pure_market_making/test_pure_market_making_start.py b/test/hummingbot/strategy/pure_market_making/test_pure_market_making_start.py new file mode 100644 index 0000000..ef0c4cf --- /dev/null +++ b/test/hummingbot/strategy/pure_market_making/test_pure_market_making_start.py @@ -0,0 +1,107 @@ +import unittest.mock +from decimal import Decimal +from test.hummingbot.strategy import assign_config_default + +import hummingbot.strategy.pure_market_making.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import PriceType +from hummingbot.strategy.pure_market_making.pure_market_making_config_map import pure_market_making_config_map as c_map + + +class PureMarketMakingStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.markets = {"binance": ExchangeBase(client_config_map=self.client_config_map)} + self.notifications = [] + self.log_errors = [] + assign_config_default(c_map) + c_map.get("exchange").value = "binance" + c_map.get("market").value = "ETH-USDT" + + c_map.get("order_amount").value = Decimal("1") + c_map.get("order_refresh_time").value = 60. + c_map.get("max_order_age").value = 300. + c_map.get("bid_spread").value = Decimal("1") + c_map.get("ask_spread").value = Decimal("2") + c_map.get("minimum_spread").value = Decimal("0.5") + c_map.get("price_ceiling").value = Decimal("100") + c_map.get("price_floor").value = Decimal("50") + c_map.get("ping_pong_enabled").value = False + c_map.get("order_levels").value = 2 + c_map.get("order_level_amount").value = Decimal("0.5") + c_map.get("order_level_spread").value = Decimal("0.2") + c_map.get("inventory_skew_enabled").value = True + c_map.get("inventory_target_base_pct").value = Decimal("50") + c_map.get("inventory_range_multiplier").value = Decimal("2") + c_map.get("filled_order_delay").value = 45. + c_map.get("hanging_orders_enabled").value = True + c_map.get("hanging_orders_cancel_pct").value = Decimal("6") + c_map.get("order_optimization_enabled").value = False + c_map.get("ask_order_optimization_depth").value = Decimal("0.01") + c_map.get("bid_order_optimization_depth").value = Decimal("0.02") + c_map.get("add_transaction_costs").value = False + c_map.get("price_source").value = "external_market" + c_map.get("price_type").value = "best_bid" + c_map.get("price_source_exchange").value = "ascend_ex" + c_map.get("price_source_market").value = "ETH-DAI" + c_map.get("price_source_custom_api").value = "localhost.test" + c_map.get("order_refresh_tolerance_pct").value = Decimal("2") + c_map.get("order_override").value = None + c_map.get("split_order_levels_enabled").value = True + c_map.get("bid_order_level_spreads").value = "1,2" + c_map.get("ask_order_level_spreads").value = "1,2" + c_map.get("bid_order_level_amounts").value = "1,2" + c_map.get("ask_order_level_amounts").value = None + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + pass + + def notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + self.assertEqual(self.strategy.order_amount, Decimal("1")) + self.assertEqual(self.strategy.order_refresh_time, 60.) + self.assertEqual(self.strategy.max_order_age, 300.) + self.assertEqual(self.strategy.bid_spread, Decimal("0.01")) + self.assertEqual(self.strategy.ask_spread, Decimal("0.02")) + self.assertEqual(self.strategy.minimum_spread, Decimal("0.005")) + self.assertEqual(self.strategy.price_ceiling, Decimal("100")) + self.assertEqual(self.strategy.price_floor, Decimal("50")) + self.assertEqual(self.strategy.ping_pong_enabled, False) + self.assertEqual(self.strategy.order_levels, 2) + self.assertEqual(self.strategy.order_level_amount, Decimal("0.5")) + self.assertEqual(self.strategy.order_level_spread, Decimal("0.002")) + self.assertEqual(self.strategy.inventory_skew_enabled, True) + self.assertEqual(self.strategy.inventory_target_base_pct, Decimal("0.5")) + self.assertEqual(self.strategy.inventory_range_multiplier, Decimal("2")) + self.assertEqual(self.strategy.filled_order_delay, 45.) + self.assertEqual(self.strategy.hanging_orders_enabled, True) + self.assertEqual(self.strategy.hanging_orders_cancel_pct, Decimal("0.06")) + self.assertEqual(self.strategy.order_optimization_enabled, False) + self.assertEqual(self.strategy.ask_order_optimization_depth, Decimal("0.01")) + self.assertEqual(self.strategy.bid_order_optimization_depth, Decimal("0.02")) + self.assertEqual(self.strategy.add_transaction_costs_to_orders, False) + self.assertEqual(self.strategy.price_type, PriceType.BestBid) + self.assertEqual(self.strategy.order_refresh_tolerance_pct, Decimal("0.02")) + self.assertEqual(self.strategy.split_order_levels_enabled, True) + self.assertEqual(self.strategy.bid_order_level_spreads, [Decimal("1"), Decimal("2")]) + self.assertEqual(self.strategy.ask_order_level_spreads, [Decimal("1"), Decimal("2")]) + self.assertEqual(self.strategy.order_override, {"split_level_0": ['buy', Decimal("1"), Decimal("1")], + "split_level_1": ['buy', Decimal("2"), Decimal("2")], + }) diff --git a/test/hummingbot/strategy/spot_perpetual_arbitrage/__init__.py b/test/hummingbot/strategy/spot_perpetual_arbitrage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/spot_perpetual_arbitrage/test_arb_proposal.py b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_arb_proposal.py new file mode 100644 index 0000000..fe6050a --- /dev/null +++ b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_arb_proposal.py @@ -0,0 +1,57 @@ +import unittest +from decimal import Decimal +from os.path import join, realpath +from unittest.mock import MagicMock + +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.spot_perpetual_arbitrage.arb_proposal import ArbProposal, ArbProposalSide + +import sys; sys.path.insert(0, realpath(join(__file__, "../../"))) + +import logging; logging.basicConfig(level=logging.ERROR) + + +class TestSpotPerpetualArbitrage(unittest.TestCase): + trading_pair = "BTC-USDT" + base_token, quote_token = trading_pair.split("-") + + def test_arb_proposal(self): + spot_connector = MagicMock() + spot_connector.display_name = "Binance" + spot_market_info = MarketTradingPairTuple(spot_connector, self.trading_pair, self.base_token, self.quote_token) + perp_connector = MagicMock() + perp_connector.display_name = "Binance Perpetual" + perp_market_info = MarketTradingPairTuple(perp_connector, self.trading_pair, self.base_token, self.quote_token) + spot_side = ArbProposalSide( + spot_market_info, + True, + Decimal(100) + ) + perp_side = ArbProposalSide( + perp_market_info, + False, + Decimal(110) + ) + proposal = ArbProposal(spot_side, perp_side, Decimal("1")) + self.assertEqual(Decimal("0.1"), proposal.profit_pct()) + expected_str = "Spot: Binance: Buy BTC at 100 USDT.\n" \ + "Perpetual: Binance perpetual: Sell BTC at 110 USDT.\n" \ + "Order amount: 1\n" \ + "Profit: 10.00%" + self.assertEqual(expected_str, str(proposal)) + perp_side = ArbProposalSide( + perp_market_info, + True, + Decimal(110) + ) + with self.assertRaises(Exception) as context: + proposal = ArbProposal(spot_side, perp_side, Decimal("1")) + self.assertEqual('Spot and perpetual arb proposal cannot be on the same side.', str(context.exception)) + + unset_perp_side = ArbProposalSide( + perp_market_info, + False, + None + ) + incomplete_proposal = ArbProposal(spot_side, unset_perp_side, Decimal("1")) + self.assertEqual(Decimal("0"), incomplete_proposal.profit_pct()) diff --git a/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage.py b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage.py new file mode 100644 index 0000000..3efbd84 --- /dev/null +++ b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage.py @@ -0,0 +1,481 @@ +import asyncio +import unittest +from decimal import Decimal +from test.mock.mock_perp_connector import MockPerpConnector +from unittest.mock import patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.derivative.position import Position +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, PositionMode, PositionSide +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + PositionModeChangeEvent, + SellOrderCompletedEvent, +) +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.spot_perpetual_arbitrage.arb_proposal import ArbProposal, ArbProposalSide +from hummingbot.strategy.spot_perpetual_arbitrage.spot_perpetual_arbitrage import ( + SpotPerpetualArbitrageStrategy, + StrategyState, +) + +trading_pair = "HBOT-USDT" +base_asset = trading_pair.split("-")[0] +quote_asset = trading_pair.split("-")[1] + + +class TestSpotPerpetualArbitrage(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + level = 0 + log_records = [] + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and message in record.getMessage() + for record in self.log_records) + + def setUp(self): + self.log_records = [] + self.order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.clock: Clock = Clock(ClockMode.BACKTEST, 1, self.start_timestamp, self.end_timestamp) + self.spot_connector: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.spot_connector.set_balanced_order_book(trading_pair=trading_pair, + mid_price=100, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.spot_connector.set_balance(base_asset, 5) + self.spot_connector.set_balance(quote_asset, 500) + self.spot_connector.set_quantization_param( + QuantizationParams( + trading_pair, 6, 6, 6, 6 + ) + ) + self.spot_market_info = MarketTradingPairTuple(self.spot_connector, trading_pair, + base_asset, quote_asset) + + self.perp_connector: MockPerpConnector = MockPerpConnector( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.perp_connector.set_leverage(trading_pair, 5) + self.perp_connector.set_balanced_order_book(trading_pair=trading_pair, + mid_price=110, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.perp_connector.set_balance(base_asset, 5) + self.perp_connector.set_balance(quote_asset, 500) + self.perp_connector.set_quantization_param( + QuantizationParams( + trading_pair, 6, 6, 6, 6 + ) + ) + self.perp_market_info = MarketTradingPairTuple(self.perp_connector, trading_pair, + base_asset, quote_asset) + + self.clock.add_iterator(self.spot_connector) + self.clock.add_iterator(self.perp_connector) + + self.spot_connector.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.spot_connector.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + self.perp_connector.add_listener(MarketEvent.OrderFilled, self.order_fill_logger) + self.perp_connector.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + self.strategy = SpotPerpetualArbitrageStrategy() + self.strategy.init_params( + spot_market_info=self.spot_market_info, + perp_market_info=self.perp_market_info, + order_amount=Decimal("1"), + perp_leverage=5, + min_opening_arbitrage_pct=Decimal("0.05"), + min_closing_arbitrage_pct=Decimal("0.01"), + next_arbitrage_opening_delay=10, + ) + self.strategy.logger().setLevel(1) + self.strategy.logger().addHandler(self) + self._last_tick = 0 + + def test_strategy_fails_to_initialize_position_mode(self): + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 1) + self.strategy._strategy_initialized = True + self.strategy._position_mode_ready = True + # Setting ONEWAY in initial settings failed + self.perp_connector.set_position_mode(PositionMode.HEDGE) + self.clock.backtest_til(self.start_timestamp + 2) + self.assertTrue(self._is_logged("INFO", "Markets are ready.")) + self.assertTrue(self._is_logged("INFO", "Trading started.")) + self.assertTrue(self._is_logged("INFO", "This strategy supports only Oneway position mode. Attempting to switch ...")) + # assert the strategy stopped here + # self.assertIsNone(self.strategy.clock) + + def test_strategy_starts_with_multiple_active_position(self): + # arbitrary adding multiple actuve positions here, which should never happen in real trading on oneway mode + self.strategy._position_mode_ready = True + self.perp_connector._account_positions[trading_pair + "SHORT"] = Position( + trading_pair, + PositionSide.SHORT, + Decimal("0"), + Decimal("95"), + Decimal("-1"), + self.perp_connector.get_leverage(trading_pair) + ) + self.perp_connector._account_positions[trading_pair + "LONG"] = Position( + trading_pair, + PositionSide.LONG, + Decimal("0"), + Decimal("95"), + Decimal("1"), + self.perp_connector.get_leverage(trading_pair) + ) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 2) + self.assertTrue(self._is_logged("INFO", "Markets are ready.")) + self.assertTrue(self._is_logged("INFO", "Trading started.")) + # self.assertIsNone(self.strategy.clock) + + def test_strategy_starts_with_existing_position(self): + """ + Tests if the strategy can start + """ + + self.strategy._position_mode_ready = True + self.clock.add_iterator(self.strategy) + self.perp_connector._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.SHORT, + Decimal("0"), + Decimal("95"), + Decimal("-1"), + self.perp_connector.get_leverage(trading_pair) + ) + self.clock.backtest_til(self.start_timestamp + 2) + self.assertTrue(self._is_logged("INFO", "Markets are ready.")) + self.assertTrue(self._is_logged("INFO", "Trading started.")) + self.assertTrue(self._is_logged("INFO", f"There is an existing {trading_pair} " + f"{PositionSide.SHORT.name} position. The bot resumes " + f"operation to close out the arbitrage position")) + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + self.clock.backtest_til(self.start_timestamp + 2) + + def test_strategy_starts_with_existing_position_unmatched_pos_amount(self): + """ + Tests if the strategy start then stop when there is an existing position where position amount doesn't match + strategy order amount + """ + self.strategy._position_mode_ready = True + self.clock.add_iterator(self.strategy) + self.perp_connector._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.SHORT, + Decimal("0"), + Decimal("95"), + Decimal("-10"), + self.perp_connector.get_leverage(trading_pair) + ) + self.clock.backtest_til(self.start_timestamp + 2) + self.assertTrue(self._is_logged("INFO", "Markets are ready.")) + self.assertTrue(self._is_logged("INFO", "Trading started.")) + self.assertTrue(self._is_logged("INFO", f"There is an existing {trading_pair} " + f"{PositionSide.SHORT.name} position with unmatched position amount. " + f"Please manually close out the position before starting this " + f"strategy.")) + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + self.clock.backtest_til(self.start_timestamp + 2) + # assert the strategy stopped here + self.assertIsNone(self.strategy.clock) + + def test_create_base_proposals(self): + asyncio.get_event_loop().run_until_complete(self._test_create_base_proposals()) + + async def _test_create_base_proposals(self): + self.clock.add_iterator(self.strategy) + props = await self.strategy.create_base_proposals() + self.assertEqual(2, len(props)) + self.assertEqual(True, props[0].spot_side.is_buy) + self.assertEqual(Decimal("100.5"), props[0].spot_side.order_price) + self.assertEqual(False, props[0].perp_side.is_buy) + self.assertEqual(Decimal("109.5"), props[0].perp_side.order_price) + self.assertEqual(Decimal("1"), props[0].order_amount) + + self.assertEqual(False, props[1].spot_side.is_buy) + self.assertEqual(Decimal("99.5"), props[1].spot_side.order_price) + self.assertEqual(True, props[1].perp_side.is_buy) + self.assertEqual(Decimal("110.5"), props[1].perp_side.order_price) + self.assertEqual(Decimal("1"), props[1].order_amount) + + def test_apply_slippage_buffers(self): + proposal = ArbProposal(ArbProposalSide(self.spot_market_info, True, Decimal("100")), + ArbProposalSide(self.perp_market_info, False, Decimal("100")), + Decimal("1")) + self.strategy._spot_market_slippage_buffer = Decimal("0.01") + self.strategy._perp_market_slippage_buffer = Decimal("0.02") + self.strategy.apply_slippage_buffers(proposal) + self.assertEqual(Decimal("101"), proposal.spot_side.order_price) + self.assertEqual(Decimal("98"), proposal.perp_side.order_price) + + def test_check_budget_available(self): + self.spot_connector.set_balance(base_asset, 0) + self.spot_connector.set_balance(quote_asset, 0) + self.perp_connector.set_balance(base_asset, 0) + self.perp_connector.set_balance(quote_asset, 10) + # Since spot has 0 HBOT and 0 USDT, not enough to do any trade + self.assertFalse(self.strategy.check_budget_available()) + + self.spot_connector.set_balance(base_asset, 10) + self.spot_connector.set_balance(quote_asset, 10) + self.perp_connector.set_balance(base_asset, 10) + self.perp_connector.set_balance(quote_asset, 0) + # Since perp has 0, not enough to do any trade + self.assertFalse(self.strategy.check_budget_available()) + + self.spot_connector.set_balance(base_asset, 10) + self.spot_connector.set_balance(quote_asset, 10) + self.perp_connector.set_balance(base_asset, 10) + self.perp_connector.set_balance(quote_asset, 10) + # All assets are available + self.assertTrue(self.strategy.check_budget_available()) + + def test_check_budget_constraint(self): + proposal = ArbProposal(ArbProposalSide(self.spot_market_info, False, Decimal("100")), + ArbProposalSide(self.perp_market_info, True, Decimal("100")), + Decimal("1")) + self.spot_connector.set_balance(base_asset, 0.5) + self.spot_connector.set_balance(quote_asset, 0) + self.perp_connector.set_balance(base_asset, 0) + self.perp_connector.set_balance(quote_asset, 21) + # Since spot has 0.5 HBOT, not enough to sell on 1 order amount + self.assertFalse(self.strategy.check_budget_constraint(proposal)) + + self.spot_connector.set_balance(base_asset, 1) + self.assertTrue(self.strategy.check_budget_constraint(proposal)) + + # on perpetual you need at least 100/5 to open a position + self.perp_connector.set_balance(quote_asset, 10) + self.assertFalse(self.strategy.check_budget_constraint(proposal)) + + # There is no balance required to close a position + self.perp_connector._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.SHORT, + Decimal("0"), + Decimal("95"), + Decimal("-1"), + self.perp_connector.get_leverage(trading_pair) + ) + self.assertTrue(self.strategy.check_budget_constraint(proposal)) + + def test_no_arbitrage_opportunity(self): + self.perp_connector.set_balanced_order_book(trading_pair=trading_pair, + mid_price=100, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + 1) + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders + self.assertTrue(len(taker_orders) == 0) + + def test_arbitrage_buy_spot_sell_perp(self): + self.strategy._position_mode_ready = True + self.clock.add_iterator(self.strategy) + self.assertEqual(StrategyState.Closed, self.strategy.strategy_state) + self.turn_clock(2) + # self.clock.backtest_til(self.start_timestamp + 1) + # asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + self.assertTrue(self._is_logged("INFO", "Arbitrage position opening opportunity found.")) + self.assertTrue(self._is_logged("INFO", "Profitability (8.96%) is now above min_opening_arbitrage_pct.")) + self.assertTrue(self._is_logged("INFO", "Placing BUY order for 1 HBOT at mock_paper_exchange at 100.500 price")) + self.assertTrue(self._is_logged("INFO", "Placing SELL order for 1 HBOT at mock_perp_connector at 109.500 price " + "to OPEN position.")) + placed_orders = self.strategy.tracked_market_orders + self.assertEqual(2, len(placed_orders)) + spot_order = [order for market, order in placed_orders if market == self.spot_connector][0] + self.assertTrue(spot_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount))) + perp_order = [order for market, order in placed_orders if market == self.perp_connector][0] + self.assertFalse(perp_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount))) + self.assertEqual(StrategyState.Opening, self.strategy.strategy_state) + + self.trigger_order_complete(True, self.spot_connector, Decimal("1"), Decimal("100.5"), spot_order.order_id) + self.trigger_order_complete(False, self.perp_connector, Decimal("1"), Decimal("109.5"), perp_order.order_id) + self.perp_connector._account_positions[trading_pair] = Position( + trading_pair, + PositionSide.SHORT, + Decimal("0"), + Decimal("109.5"), + Decimal("-1"), + self.perp_connector.get_leverage(trading_pair) + ) + self.turn_clock(1) + status = asyncio.get_event_loop().run_until_complete(self.strategy.format_status()) + expected_status = (""" + Markets: + Exchange Market Sell Price Buy Price Mid Price + mock_paper_exchange HBOT-USDT 99.5 100.5 100 + mock_perp_connector HBOT-USDT 109.5 110.5 110 + + Positions: + Symbol Type Entry Price Amount Leverage Unrealized PnL + HBOT-USDT SHORT 109.5 -1 5 0 + + Assets: + Exchange Asset Total Balance Available Balance + 0 mock_paper_exchange HBOT 5 5 + 1 mock_paper_exchange USDT 500 500 + 2 mock_perp_connector HBOT 5 5 + 3 mock_perp_connector USDT 500 500 + + Opportunity: + buy at mock_paper_exchange, sell at mock_perp_connector: 8.96% + sell at mock_paper_exchange, buy at mock_perp_connector: -9.95%""") + + self.assertEqual(expected_status, status) + + self.assertEqual(StrategyState.Opened, self.strategy.strategy_state) + self.perp_connector.set_balanced_order_book(trading_pair=trading_pair, + mid_price=90, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.turn_clock(1) + placed_orders = self.strategy.tracked_market_orders + self.assertEqual(4, len(placed_orders)) + spot_order = [o for m, o in placed_orders if m == self.spot_connector and o.order_id != spot_order.order_id][0] + self.assertFalse(spot_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount))) + perp_order = [o for m, o in placed_orders if m == self.perp_connector and o.order_id != perp_order.order_id][0] + self.assertTrue(perp_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount))) + self.assertEqual(StrategyState.Closing, self.strategy.strategy_state) + + self.trigger_order_complete(False, self.spot_connector, Decimal("1"), Decimal("99.5"), spot_order.order_id) + self.trigger_order_complete(True, self.perp_connector, Decimal("1"), Decimal("90.5"), perp_order.order_id) + self.perp_connector._account_positions.clear() + self.turn_clock(1) + # Due to the next_arbitrage_opening_delay, new arb position is not opened yet + self.assertEqual(StrategyState.Closed, self.strategy.strategy_state) + # Set balance on perpetual to 0 to test the strategy shouldn't submit orders + self.spot_connector.set_balance(base_asset, 0) + self.turn_clock(12) + self.assertEqual(StrategyState.Closed, self.strategy.strategy_state) + self.assertEqual(4, len(self.strategy.tracked_market_orders)) + + self.spot_connector.set_balance(base_asset, 10) + self.turn_clock(1) + # After next_arbitrage_opening_delay, new arb orders are submitted + self.assertEqual(StrategyState.Opening, self.strategy.strategy_state) + self.assertEqual(6, len(self.strategy.tracked_market_orders)) + + def test_arbitrage_sell_spot_buy_perp_opening(self): + self.strategy._position_mode_ready = True + self.perp_connector.set_balanced_order_book(trading_pair=trading_pair, + mid_price=90, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.clock.add_iterator(self.strategy) + self.assertEqual(StrategyState.Closed, self.strategy.strategy_state) + self.turn_clock(2) + # self.clock.backtest_til(self.start_timestamp + 1) + # asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + self.assertTrue(self._is_logged("INFO", "Arbitrage position opening opportunity found.")) + self.assertTrue(self._is_logged("INFO", "Profitability (9.94%) is now above min_opening_arbitrage_pct.")) + self.assertTrue(self._is_logged("INFO", "Placing SELL order for 1 HBOT at mock_paper_exchange at 99.5000 price")) + self.assertTrue(self._is_logged("INFO", "Placing BUY order for 1 HBOT at mock_perp_connector at 90.5000 price to " + "OPEN position.")) + placed_orders = self.strategy.tracked_market_orders + self.assertEqual(2, len(placed_orders)) + spot_order = [order for market, order in placed_orders if market == self.spot_connector][0] + self.assertFalse(spot_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(spot_order.amount))) + perp_order = [order for market, order in placed_orders if market == self.perp_connector][0] + self.assertTrue(perp_order.is_buy) + self.assertEqual(Decimal("1"), Decimal(str(perp_order.amount))) + self.assertEqual(StrategyState.Opening, self.strategy.strategy_state) + + def turn_clock(self, no_ticks: int): + for i in range(self._last_tick, self._last_tick + no_ticks + 1): + self.clock.backtest_til(self.start_timestamp + i) + asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.01)) + self._last_tick += no_ticks + + @staticmethod + def trigger_order_complete(is_buy: bool, connector: ConnectorBase, amount: Decimal, price: Decimal, + order_id: str): + # This function triggers order complete event for our mock connector, this is to simulate scenarios more + # precisely taker orders are fully filled. + event_tag = MarketEvent.BuyOrderCompleted if is_buy else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if is_buy else SellOrderCompletedEvent + connector.trigger_event(event_tag, + event_class(connector.current_timestamp, order_id, base_asset, quote_asset, + amount, amount * price, OrderType.LIMIT)) + + @patch("hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode") + def test_position_mode_change_success(self, set_position_mode_mock): + self.strategy._position_mode_ready = False + + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.strategy.did_change_position_mode_succeed( + PositionModeChangeEvent( + timestamp=self.start_timestamp + 11, + trading_pair=trading_pair, + position_mode=PositionMode.ONEWAY, + ) + ) + + self.assertTrue(self.strategy._position_mode_ready) + + @patch("hummingbot.connector.perpetual_trading.PerpetualTrading.set_position_mode") + def test_position_mode_change_failure(self, set_position_mode_mock): + self.strategy._position_mode_ready = False + + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 1) + + self.assertFalse(self.strategy._position_mode_ready) + + self.clock.backtest_til(self.start_timestamp + 10) + + self.strategy.did_change_position_mode_fail( + PositionModeChangeEvent( + timestamp=self.start_timestamp + 11, + trading_pair=trading_pair, + position_mode=PositionMode.ONEWAY, + message="Error message", + ) + ) + + self.assertFalse(self.strategy._position_mode_ready) diff --git a/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_config_map.py b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_config_map.py new file mode 100644 index 0000000..39174f2 --- /dev/null +++ b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_config_map.py @@ -0,0 +1,49 @@ +import unittest + +from copy import deepcopy + +from hummingbot.client.settings import AllConnectorSettings +from hummingbot.strategy.spot_perpetual_arbitrage.spot_perpetual_arbitrage_config_map import ( + spot_perpetual_arbitrage_config_map, + spot_market_prompt, + perpetual_market_prompt, +) + + +class SpotPerpetualArbitrageConfigMapTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.spot_exchange = "binance" + cls.perp_exchange = "binance_perpetual" + + def setUp(self) -> None: + super().setUp() + self.config_backup = deepcopy(spot_perpetual_arbitrage_config_map) + + def tearDown(self) -> None: + self.reset_config_map() + super().tearDown() + + def reset_config_map(self): + for key, value in self.config_backup.items(): + spot_perpetual_arbitrage_config_map[key] = value + + def test_spot_market_prompt(self): + spot_perpetual_arbitrage_config_map["spot_connector"].value = self.spot_exchange + example = AllConnectorSettings.get_example_pairs().get(self.spot_exchange) + + prompt = spot_market_prompt() + expected = f"Enter the token trading pair you would like to trade on {self.spot_exchange} (e.g. {example}) >>> " + + self.assertEqual(expected, prompt) + + def test_perpetual_market_prompt(self): + spot_perpetual_arbitrage_config_map["perpetual_connector"].value = self.perp_exchange + example = AllConnectorSettings.get_example_pairs().get(self.perp_exchange) + + prompt = perpetual_market_prompt() + expected = f"Enter the token trading pair you would like to trade on {self.perp_exchange} (e.g. {example}) >>> " + + self.assertEqual(expected, prompt) diff --git a/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_start.py b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_start.py new file mode 100644 index 0000000..cbfff97 --- /dev/null +++ b/test/hummingbot/strategy/spot_perpetual_arbitrage/test_spot_perpetual_arbitrage_start.py @@ -0,0 +1,57 @@ +import unittest.mock +from decimal import Decimal +from test.hummingbot.strategy import assign_config_default +from test.mock.mock_perp_connector import MockPerpConnector + +import hummingbot.strategy.spot_perpetual_arbitrage.start as strategy_start +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.strategy.spot_perpetual_arbitrage.spot_perpetual_arbitrage_config_map import ( + spot_perpetual_arbitrage_config_map as strategy_cmap, +) + + +class SpotPerpetualArbitrageStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy = None + self.client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.markets = { + "binance": ExchangeBase(client_config_map=self.client_config_map), + "kucoin": MockPerpConnector(client_config_map=self.client_config_map)} + self.notifications = [] + self.log_errors = [] + assign_config_default(strategy_cmap) + strategy_cmap.get("spot_connector").value = "binance" + strategy_cmap.get("spot_market").value = "BTC-USDT" + strategy_cmap.get("perpetual_connector").value = "kucoin" + strategy_cmap.get("perpetual_market").value = "BTC-USDT" + + strategy_cmap.get("order_amount").value = Decimal("1") + strategy_cmap.get("perpetual_leverage").value = Decimal("2") + strategy_cmap.get("min_opening_arbitrage_pct").value = Decimal("10") + strategy_cmap.get("min_closing_arbitrage_pct").value = Decimal("1") + + def _initialize_market_assets(self, market, trading_pairs): + return [("ETH", "USDT")] + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + def test_strategy_creation(self): + strategy_start.start(self) + self.assertEqual(self.strategy._order_amount, Decimal("1")) + self.assertEqual(self.strategy._perp_leverage, Decimal("2")) + self.assertEqual(self.strategy._min_opening_arbitrage_pct, Decimal("0.1")) + self.assertEqual(self.strategy._min_closing_arbitrage_pct, Decimal("0.01")) diff --git a/test/hummingbot/strategy/test_conditional_execution_state.py b/test/hummingbot/strategy/test_conditional_execution_state.py new file mode 100644 index 0000000..6367030 --- /dev/null +++ b/test/hummingbot/strategy/test_conditional_execution_state.py @@ -0,0 +1,100 @@ +from datetime import datetime +from unittest import TestCase +from unittest.mock import MagicMock + +from hummingbot.strategy.conditional_execution_state import RunAlwaysExecutionState, RunInTimeConditionalExecutionState + + +class RunAlwaysExecutionStateTests(TestCase): + + def test_always_process_tick(self): + strategy = MagicMock() + state = RunAlwaysExecutionState() + state.process_tick(datetime.now, strategy) + + strategy.process_tick.assert_called() + strategy.cancel_active_orders.assert_not_called() + + +class RunInTimeSpanExecutionStateTests(TestCase): + + def setUp(self) -> None: + super().setUp() + + self.debug_logs = [] + + def debug(self, message: str): + self.debug_logs.append(message) + + def test_process_tick_when_current_time_in_span(self): + start_timestamp = datetime.fromisoformat("2021-06-22 09:00:00") + end_timestamp = datetime.fromisoformat("2021-06-22 10:00:00") + state = RunInTimeConditionalExecutionState(start_timestamp=start_timestamp, end_timestamp=end_timestamp) + + strategy = MagicMock() + strategy.logger().debug.side_effect = self.debug + + state.process_tick(datetime.fromisoformat("2021-06-22 08:59:59").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 1) + self.assertEqual(self.debug_logs[0], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") + + state.process_tick(datetime.fromisoformat("2021-06-22 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + state.process_tick(datetime.fromisoformat("2021-06-22 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + strategy.process_tick.reset_mock() + state.process_tick(datetime.fromisoformat("2021-06-22 10:00:01").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 2) + self.assertEqual(self.debug_logs[1], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") + + state = RunInTimeConditionalExecutionState(start_timestamp=start_timestamp.time(), end_timestamp=end_timestamp.time()) + + state.process_tick(datetime.fromisoformat("2021-06-22 08:59:59").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 3) + self.assertEqual(self.debug_logs[0], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") + + state.process_tick(datetime.fromisoformat("2021-06-22 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + state.process_tick(datetime.fromisoformat("2021-06-22 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + strategy.process_tick.reset_mock() + state.process_tick(datetime.fromisoformat("2021-06-22 10:00:01").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 4) + self.assertEqual(self.debug_logs[1], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") + + state.process_tick(datetime.fromisoformat("2021-06-30 08:59:59").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 5) + self.assertEqual(self.debug_logs[0], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") + + state.process_tick(datetime.fromisoformat("2021-06-30 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + state.process_tick(datetime.fromisoformat("2021-06-30 09:00:00").timestamp(), strategy) + strategy.process_tick.assert_called() + + strategy.process_tick.reset_mock() + state.process_tick(datetime.fromisoformat("2021-06-30 10:00:01").timestamp(), strategy) + strategy.process_tick.assert_not_called() + strategy.cancel_active_orders.assert_called() + self.assertEqual(len(self.debug_logs), 6) + self.assertEqual(self.debug_logs[1], "Time span execution: tick will not be processed " + f"(executing between {start_timestamp} and {end_timestamp})") diff --git a/test/hummingbot/strategy/test_directional_strategy_base.py b/test/hummingbot/strategy/test_directional_strategy_base.py new file mode 100644 index 0000000..484c3fb --- /dev/null +++ b/test/hummingbot/strategy/test_directional_strategy_base.py @@ -0,0 +1,135 @@ +import unittest +from unittest.mock import MagicMock, PropertyMock, patch + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase + + +class DirectionalStrategyBaseTest(unittest.TestCase): + level = 0 + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + def setUp(self): + self.log_records = [] + self.start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + self.end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + self.start_timestamp: float = self.start.timestamp() + self.end_timestamp: float = self.end.timestamp() + self.connector_name: str = "mock_paper_exchange" + self.trading_pair: str = "HBOT-USDT" + self.base_asset, self.quote_asset = self.trading_pair.split("-") + self.base_balance: int = 500 + self.quote_balance: int = 5000 + self.initial_mid_price: int = 100 + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.connector: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.connector.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=100, + min_price=50, + max_price=150, + price_step_size=1, + volume_step_size=10) + self.connector.set_balance(self.base_asset, self.base_balance) + self.connector.set_balance(self.quote_asset, self.quote_balance) + self.connector.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.clock.add_iterator(self.connector) + DirectionalStrategyBase.markets = {self.connector_name: {self.trading_pair}} + DirectionalStrategyBase.candles = [] + DirectionalStrategyBase.exchange = self.connector_name + DirectionalStrategyBase.trading_pair = self.trading_pair + self.strategy = DirectionalStrategyBase({self.connector_name: self.connector}) + self.strategy.logger().setLevel(1) + self.strategy.logger().addHandler(self) + + def test_start(self): + self.assertFalse(self.strategy.ready_to_trade) + self.strategy.start(Clock(ClockMode.BACKTEST), self.start_timestamp) + self.strategy.tick(self.start_timestamp + 10) + self.assertTrue(self.strategy.ready_to_trade) + + def test_all_candles_ready(self): + self.assertTrue(self.strategy.all_candles_ready) + + def test_is_perpetual(self): + self.assertFalse(self.strategy.is_perpetual) + + def test_candles_formatted_list(self): + columns = ["timestamp", "open", "low", "high", "close", "volume"] + candles_df = pd.DataFrame(columns=columns, + data=[[self.start_timestamp, 1, 2, 3, 4, 5], + [self.start_timestamp + 1, 2, 3, 4, 5, 6]]) + candles_status = self.strategy.candles_formatted_list(candles_df, columns) + self.assertTrue("timestamp" in candles_status[0]) + + def test_get_active_executors(self): + self.assertEqual(0, len(self.strategy.get_active_executors())) + + def test_format_status_not_started(self): + self.assertEqual("Market connectors are not ready.", self.strategy.format_status()) + + @patch("hummingbot.strategy.directional_strategy_base.DirectionalStrategyBase.get_signal") + def test_format_status(self, signal_mock): + signal_mock.return_value = 0 + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + position_executor_mock = MagicMock() + position_executor_mock.to_format_status = MagicMock(return_value=["mock_position_executor"]) + self.strategy.stored_executors.append(position_executor_mock) + self.strategy.active_executors.append(position_executor_mock) + self.assertTrue("mock_position_executor" in self.strategy.format_status()) + + @patch("hummingbot.strategy.directional_strategy_base.DirectionalStrategyBase.get_signal", new_callable=MagicMock) + def test_get_position_config_signal_zero(self, signal): + signal.return_value = 0 + self.assertIsNone(self.strategy.get_position_config()) + + @patch("hummingbot.strategy.directional_strategy_base.DirectionalStrategyBase.get_signal", new_callable=MagicMock) + def test_get_position_config_signal_positive(self, signal): + signal.return_value = 1 + self.assertIsNotNone(self.strategy.get_position_config()) + + def test_time_between_signals_condition(self): + self.strategy.cooldown_after_execution = 10 + stored_executor_mock = MagicMock() + stored_executor_mock.close_timestamp = self.start_timestamp + self.strategy.stored_executors = [stored_executor_mock] + # First scenario waiting for delay + type(self.strategy).current_timestamp = PropertyMock(return_value=self.start_timestamp + 5) + self.assertFalse(self.strategy.time_between_signals_condition) + + # Second scenario delay passed + type(self.strategy).current_timestamp = PropertyMock(return_value=self.start_timestamp + 15) + self.assertTrue(self.strategy.time_between_signals_condition) + + # Third scenario no stored executors + self.strategy.stored_executors = [] + self.assertTrue(self.strategy.time_between_signals_condition) + + def test_max_active_executors_condition(self): + self.strategy.max_executors = 1 + active_executor_mock = MagicMock() + active_executor_mock.is_closed = False + self.strategy.active_executors = [active_executor_mock] + self.assertFalse(self.strategy.max_active_executors_condition) + self.strategy.active_executors = [] + self.assertTrue(self.strategy.max_active_executors_condition) diff --git a/test/hummingbot/strategy/test_hanging_orders_tracker.py b/test/hummingbot/strategy/test_hanging_orders_tracker.py new file mode 100644 index 0000000..92576ae --- /dev/null +++ b/test/hummingbot/strategy/test_hanging_orders_tracker.py @@ -0,0 +1,499 @@ +import unittest +from datetime import datetime +from decimal import Decimal +from unittest.mock import MagicMock, PropertyMock + +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.event.events import BuyOrderCompletedEvent, MarketEvent, OrderCancelledEvent +from hummingbot.strategy.data_types import OrderType +from hummingbot.strategy.hanging_orders_tracker import CreatedPairOfOrders, HangingOrdersTracker + + +class TestHangingOrdersTracker(unittest.TestCase): + level = 0 + log_records = [] + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + + self.current_market_price = Decimal("100.0") + self.strategy = self.create_mock_strategy() + self.tracker = HangingOrdersTracker(self.strategy, hanging_orders_cancel_pct=Decimal("0.1")) + + self.tracker.logger().setLevel(1) + self.tracker.logger().addHandler(self) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + @staticmethod + def quantize_order_amount(trading_pair: str, amount: Decimal): + return amount.quantize(Decimal("0.00001")) + + @staticmethod + def quantize_order_price(trading_pair: str, price: Decimal): + return price.quantize(Decimal("0.00001")) + + def create_mock_strategy(self): + market = MagicMock() + market.quantize_order_amount.side_effect = self.quantize_order_amount + market.quantize_order_price.side_effect = self.quantize_order_price + market.get_maker_order_type.return_value = OrderType.LIMIT + + market_info = MagicMock() + market_info.market = market + + strategy = MagicMock() + type(strategy).max_order_age = PropertyMock(return_value=1800.0) + + strategy.get_price.return_value = self.current_market_price + type(strategy).market_info = PropertyMock(return_value=market_info) + type(strategy).trading_pair = PropertyMock(return_value="BTC-USDT") + + return strategy + + def test_tracker_initialized(self): + self.assertEqual(self.tracker.trading_pair, "BTC-USDT") + self.assertEqual(self.tracker.original_orders, set()) + self.assertEqual(self.tracker.strategy_current_hanging_orders, set()) + self.assertEqual(self.tracker.current_created_pairs_of_orders, list()) + + def test_add_remove_limit_order(self): + order_to_add = LimitOrder("Order-number-1", "BTC-USDT", True, "BTC", "USDT", Decimal(100), Decimal(1)) + self.tracker.add_order(order_to_add) + self.assertEqual(len(self.tracker.original_orders), 1) + order_that_doesnt_belong = LimitOrder("Order-number-2", "BTC-USDT", True, "BTC", "USDT", Decimal(100), + Decimal(1)) + self.tracker.remove_order(order_that_doesnt_belong) + self.assertEqual(len(self.tracker.original_orders), 1) + self.tracker.remove_order(order_to_add) + self.assertEqual(len(self.tracker.original_orders), 0) + self.tracker.add_order(LimitOrder("Order-number-3", "BTC-USDT", True, "BTC", "USDT", Decimal(100), Decimal(1))) + self.tracker.add_order(LimitOrder("Order-number-4", "BTC-USDT", True, "BTC", "USDT", Decimal(100), Decimal(1))) + self.tracker.remove_all_orders() + self.assertEqual(len(self.tracker.original_orders), 0) + + def test_renew_hanging_orders_past_max_order_age(self): + cancelled_orders_ids = [] + strategy_active_orders = [] + type(self.strategy).current_timestamp = PropertyMock(return_value=1234967891) + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + self.strategy.cancel_order.side_effect = lambda order_id: cancelled_orders_ids.append(order_id) + self.strategy.buy_with_specific_market.return_value = "Order-1234569990000000" + + # Order just executed + new_order = LimitOrder("Order-1234567890000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234567890000000) + # Order executed 1900 seconds ago + old_order = LimitOrder("Order-1234565991000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(105), + Decimal(1), + creation_timestamp=1234565991000000) + + self.tracker.add_order(new_order) + strategy_active_orders.append(new_order) + self.tracker.add_order(old_order) + strategy_active_orders.append(old_order) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + self.assertTrue(any(order.trading_pair == "BTC-USDT" and order.price == Decimal(105) + for order + in self.tracker.strategy_current_hanging_orders)) + + # When calling the renew logic, the old order should start the renew process (it should be canceled) + # but it will only stop being a current hanging order once the cancel confirmation arrives + self.tracker.process_tick() + self.assertTrue(old_order.client_order_id in cancelled_orders_ids) + self.assertTrue(any(order.trading_pair == "BTC-USDT" and order.price == Decimal(105) + for order + in self.tracker.strategy_current_hanging_orders)) + + # When the cancel is confirmed the order should no longer be considered a hanging order + strategy_active_orders.remove(old_order) + self.tracker._did_cancel_order(MarketEvent.OrderCancelled, + self, + OrderCancelledEvent(old_order.client_order_id, old_order.client_order_id)) + self.assertTrue(self._is_logged("INFO", f"(BTC-USDT) Hanging order {old_order.client_order_id} " + f"has been canceled as part of the renew process. " + f"Now the replacing order will be created.")) + self.assertFalse(any(order.order_id == old_order.client_order_id for order + in self.tracker.strategy_current_hanging_orders)) + self.assertTrue(any(order.order_id == "Order-1234569990000000" for order + in self.tracker.strategy_current_hanging_orders)) + + def test_order_being_renewed_is_canceled_only_one_time(self): + cancelled_orders_ids = [] + strategy_active_orders = [] + type(self.strategy).current_timestamp = PropertyMock(return_value=1234967891) + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + self.strategy.cancel_order.side_effect = lambda order_id: cancelled_orders_ids.append(order_id) + self.strategy.buy_with_specific_market.return_value = "Order-1234569990000000" + + # Order just executed + new_order = LimitOrder("Order-1234567890000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234567890000000) + # Order executed 1900 seconds ago + old_order = LimitOrder("Order-1234565991000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(105), + Decimal(1), + creation_timestamp=1234565991000000) + + self.tracker.add_order(new_order) + strategy_active_orders.append(new_order) + self.tracker.add_order(old_order) + strategy_active_orders.append(old_order) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + self.assertTrue(any(order.trading_pair == "BTC-USDT" and order.price == Decimal(105) + for order + in self.tracker.strategy_current_hanging_orders)) + + # When calling the renew logic, the old order should start the renew process (it should be canceled) + # but it will only stop being a current hanging order once the cancel confirmation arrives + self.tracker.process_tick() + self.assertTrue(old_order.client_order_id in cancelled_orders_ids) + # Now we suppose that a new tick happens before the cancellation confirmation arrives + self.tracker.process_tick() + # The cancel request should not have been sent a second time + self.assertEqual(1, cancelled_orders_ids.count(old_order.client_order_id)) + + def test_hanging_order_removed_when_cancelled(self): + strategy_active_orders = [] + + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + + new_order = LimitOrder("Order-1234567890000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234567890000000) + + self.tracker.add_order(new_order) + strategy_active_orders.append(new_order) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + # Now we simulate the order is cancelled + self.tracker._did_cancel_order(MarketEvent.OrderCancelled.value, + self, + OrderCancelledEvent(datetime.now().timestamp(), + new_order.client_order_id, + new_order.client_order_id)) + + self.assertTrue(self._is_logged("INFO", "(BTC-USDT) Hanging order Order-1234567890000000 canceled.")) + self.assertTrue(len(self.tracker.strategy_current_hanging_orders) == 0) + self.assertNotIn(new_order, self.tracker.original_orders) + + def test_non_grouped_hanging_order_and_original_order_removed_when_hanging_order_completed(self): + strategy_active_orders = [] + newly_created_buy_orders_ids = ["Order-1234570000000000", + "Order-1234570020000000", + "Order-1234570040000000", + "Order-1234570060000000"] + newly_created_sell_orders_ids = ["Order-1234570010000000", + "Order-1234570030000000", + "Order-1234570050000000", + "Order-1234570070000000"] + + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + self.strategy.buy_with_specific_market.side_effect = newly_created_buy_orders_ids + self.strategy.sell_with_specific_market.side_effect = newly_created_sell_orders_ids + + buy_order_1 = LimitOrder("Order-1234569960000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234569960000000) + sell_order_1 = LimitOrder("Order-1234569970000000", + "BTC-USDT", + False, + "BTC", + "USDT", + Decimal(110), + Decimal(1), + creation_timestamp=1234569970000000) + + self.tracker.add_order(buy_order_1) + strategy_active_orders.append(buy_order_1) + self.tracker.add_order(sell_order_1) + strategy_active_orders.append(sell_order_1) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + buy_hanging_order = next(order for order in self.tracker.strategy_current_hanging_orders if order.is_buy) + sell_hanging_order = next(order for order in self.tracker.strategy_current_hanging_orders if not order.is_buy) + + self.assertEqual(buy_order_1.client_order_id, buy_hanging_order.order_id) + self.assertEqual(sell_order_1.client_order_id, sell_hanging_order.order_id) + self.assertEqual(2, len(self.tracker.original_orders)) + self.assertEqual(2, len(self.tracker.strategy_current_hanging_orders)) + + # Now we simulate the buy hanging order being fully filled + strategy_active_orders.remove(buy_order_1) + self.tracker._did_complete_buy_order(MarketEvent.BuyOrderCompleted, + self, + BuyOrderCompletedEvent( + timestamp=datetime.now().timestamp(), + order_id=buy_order_1.client_order_id, + base_asset="BTC", + quote_asset="USDT", + base_asset_amount=buy_order_1.quantity, + quote_asset_amount=buy_order_1.quantity * buy_order_1.price, + order_type=OrderType.LIMIT)) + + self.assertEqual(1, len(self.tracker.strategy_current_hanging_orders)) + self.assertNotIn(buy_hanging_order, self.tracker.strategy_current_hanging_orders) + self.assertEqual(1, len(self.tracker.original_orders)) + self.assertNotIn(buy_order_1, self.tracker.original_orders) + self.assertTrue(self.tracker.is_order_id_in_completed_hanging_orders(buy_hanging_order.order_id)) + self.assertFalse(self.tracker.is_order_id_in_completed_hanging_orders(sell_hanging_order.order_id)) + + def test_limit_order_added_to_non_grouping_tracker_is_potential_hanging_order(self): + strategy_active_orders = [] + newly_created_buy_orders_ids = ["Order-1234570000000000", + "Order-1234570020000000", + "Order-1234570040000000", + "Order-1234570060000000"] + newly_created_sell_orders_ids = ["Order-1234570010000000", + "Order-1234570030000000", + "Order-1234570050000000", + "Order-1234570070000000"] + + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + self.strategy.buy_with_specific_market.side_effect = newly_created_buy_orders_ids + self.strategy.sell_with_specific_market.side_effect = newly_created_sell_orders_ids + + buy_order_1 = LimitOrder("Order-1234569960000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234569960000000) + buy_order_2 = LimitOrder("Order-1234569980000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(105), + Decimal(1), + creation_timestamp=1234569980000000) + + sell_order_1 = LimitOrder("Order-1234569970000000", + "BTC-USDT", + False, + "BTC", + "USDT", + Decimal(110), + Decimal(1), + creation_timestamp=1234569970000000) + + self.tracker.add_order(buy_order_1) + strategy_active_orders.append(buy_order_1) + self.tracker.add_order(buy_order_2) + strategy_active_orders.append(buy_order_2) + self.tracker.add_order(sell_order_1) + strategy_active_orders.append(sell_order_1) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + self.assertTrue(self.tracker.is_potential_hanging_order(buy_order_1)) + self.assertTrue(self.tracker.is_potential_hanging_order(buy_order_2)) + self.assertTrue(self.tracker.is_potential_hanging_order(sell_order_1)) + + def test_non_grouping_tracker_cancels_order_when_removing_far_from_price(self): + cancelled_orders_ids = [] + strategy_active_orders = [] + + newly_created_buy_orders_ids = ["Order-1234570000000000", + "Order-1234570020000000", + "Order-1234570040000000", + "Order-1234570060000000"] + newly_created_sell_orders_ids = ["Order-1234570010000000", + "Order-1234570030000000", + "Order-1234570050000000", + "Order-1234570070000000"] + + type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders) + self.strategy.cancel_order.side_effect = lambda order_id: cancelled_orders_ids.append(order_id) + self.strategy.buy_with_specific_market.side_effect = newly_created_buy_orders_ids + self.strategy.sell_with_specific_market.side_effect = newly_created_sell_orders_ids + + buy_order_1 = LimitOrder("Order-1234569960000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234569960000000) + buy_order_2 = LimitOrder("Order-1234569980000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(120), + Decimal(1), + creation_timestamp=1234569980000000) + + self.tracker.add_order(buy_order_1) + strategy_active_orders.append(buy_order_1) + self.tracker.add_order(buy_order_2) + strategy_active_orders.append(buy_order_2) + + self.tracker.update_strategy_orders_with_equivalent_orders() + + # The hanging orders are created + hanging_order_1 = next(hanging_order for hanging_order in self.tracker.strategy_current_hanging_orders + if hanging_order.order_id == buy_order_1.client_order_id) + hanging_order_2 = next(hanging_order for hanging_order in self.tracker.strategy_current_hanging_orders + if hanging_order.order_id == buy_order_2.client_order_id) + + # After removing orders far from price, the order 2 should be canceled but still be a hanging order + self.tracker.remove_orders_far_from_price() + self.assertEqual(1, len(cancelled_orders_ids)) + self.assertIn(hanging_order_2.order_id, cancelled_orders_ids) + self.assertTrue(self.tracker.is_potential_hanging_order(buy_order_2)) + # We simulate a new request to remove orders far from price before the cancellation confirmation arrives + # The order should not be cancelled again. Both the order and the hanging order should still be present + self.tracker.remove_orders_far_from_price() + self.assertEqual(1, cancelled_orders_ids.count(buy_order_2.client_order_id)) + self.assertIn(hanging_order_2.order_id, cancelled_orders_ids) + self.assertTrue(self.tracker.is_potential_hanging_order(buy_order_2)) + + # We emulate the reception of the cancellation confirmation. After that the hanging order should not be present + # in the tracker, and the original order should not be considered a potential hanging order. + strategy_active_orders.remove(buy_order_2) + self.tracker._did_cancel_order(MarketEvent.OrderCancelled, + self, + OrderCancelledEvent(buy_order_2.client_order_id, buy_order_2.client_order_id)) + + self.assertNotIn(hanging_order_2, self.tracker.strategy_current_hanging_orders) + self.assertFalse(self.tracker.is_potential_hanging_order(buy_order_2)) + self.assertIn(hanging_order_1, self.tracker.strategy_current_hanging_orders) + self.assertTrue(self.tracker.is_potential_hanging_order(buy_order_1)) + + def test_add_orders_from_partially_executed_pairs(self): + active_orders = [] + type(self.strategy).active_orders = PropertyMock(return_value=active_orders) + + buy_order_1 = LimitOrder("Order-1234569960000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(101), + Decimal(1), + creation_timestamp=1234569960000000) + buy_order_2 = LimitOrder("Order-1234569961000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(102), + Decimal(2), + creation_timestamp=1234569961000000) + buy_order_3 = LimitOrder("Order-1234569962000000", + "BTC-USDT", + True, + "BTC", + "USDT", + Decimal(103), + Decimal(3), + creation_timestamp=1234569962000000) + sell_order_1 = LimitOrder("Order-1234569980000000", + "BTC-USDT", + False, + "BTC", + "USDT", + Decimal(120), + Decimal(1), + creation_timestamp=1234569980000000) + sell_order_2 = LimitOrder("Order-1234569981000000", + "BTC-USDT", + False, + "BTC", + "USDT", + Decimal(122), + Decimal(2), + creation_timestamp=1234569981000000) + sell_order_3 = LimitOrder("Order-1234569982000000", + "BTC-USDT", + False, + "BTC", + "USDT", + Decimal(123), + Decimal(3), + creation_timestamp=1234569982000000) + + non_executed_pair = CreatedPairOfOrders(buy_order_1, sell_order_1) + partially_executed_pair = CreatedPairOfOrders(buy_order_2, sell_order_2) + partially_executed_pair.filled_buy = True + executed_pair = CreatedPairOfOrders(buy_order_3, sell_order_3) + executed_pair.filled_buy = True + executed_pair.filled_sell = True + + active_orders.append(buy_order_1) + active_orders.append(buy_order_2) + active_orders.append(buy_order_3) + active_orders.append(sell_order_1) + active_orders.append(sell_order_2) + active_orders.append(sell_order_3) + + self.tracker.add_current_pairs_of_proposal_orders_executed_by_strategy(non_executed_pair) + self.tracker.add_current_pairs_of_proposal_orders_executed_by_strategy(partially_executed_pair) + self.tracker.add_current_pairs_of_proposal_orders_executed_by_strategy(executed_pair) + + self.tracker._add_hanging_orders_based_on_partially_executed_pairs() + + self.assertNotIn(buy_order_1, self.tracker.original_orders) + self.assertNotIn(buy_order_2, self.tracker.original_orders) + self.assertNotIn(buy_order_3, self.tracker.original_orders) + self.assertNotIn(sell_order_1, self.tracker.original_orders) + self.assertIn(sell_order_2, self.tracker.original_orders) + self.assertNotIn(sell_order_3, self.tracker.original_orders) + + def test_add_order_as_hanging_order(self): + order = LimitOrder("Order-number-1", "BTC-USDT", True, "BTC", "USDT", Decimal(100), Decimal(1)) + self.tracker.add_as_hanging_order(order) + + self.assertIn(order, self.tracker.original_orders) + self.assertEqual(1, len(self.tracker.strategy_current_hanging_orders)) + + hanging_order = next((hanging_order for hanging_order in self.tracker.strategy_current_hanging_orders)) + + self.assertEqual(order.client_order_id, hanging_order.order_id) diff --git a/test/hummingbot/strategy/test_market_trading_pair_tuple.py b/test/hummingbot/strategy/test_market_trading_pair_tuple.py new file mode 100644 index 0000000..d1a1ef2 --- /dev/null +++ b/test/hummingbot/strategy/test_market_trading_pair_tuple.py @@ -0,0 +1,318 @@ +import math +import time +import unittest +from decimal import Decimal +from typing import List + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_row import OrderBookRow +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + OrderBookTradeEvent, + OrderFilledEvent, + SellOrderCompletedEvent, +) +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple + +s_decimal_0 = Decimal(0) + + +class MarketTradingPairTupleUnitTest(unittest.TestCase): + + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + trading_pair: str = "COINALPHA-HBOT" + base_asset, quote_asset = trading_pair.split("-") + base_balance: int = 500 + quote_balance: int = 5000 + initial_mid_price: int = 100 + clock_tick_size = 10 + + def setUp(self): + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=100, + min_price=50, + max_price=150, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("COINALPHA", self.base_balance) + self.market.set_balance("HBOT", self.quote_balance) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + + self.market_info = MarketTradingPairTuple(self.market, self.trading_pair, self.base_asset, self.quote_asset) + + @staticmethod + def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder, timestamp: float = 0): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + trade_event: OrderBookTradeEvent = OrderBookTradeEvent( + trading_pair=limit_order.trading_pair, + timestamp=timestamp, + type=TradeType.BUY if limit_order.is_buy else TradeType.SELL, + price=limit_order.price, + amount=limit_order.quantity + ) + + market.get_order_book(limit_order.trading_pair).apply_trade(trade_event) + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0.0)) + )) + market.trigger_event(MarketEvent.BuyOrderCompleted, BuyOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal(0.0)) + )) + market.trigger_event(MarketEvent.SellOrderCompleted, SellOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + + @staticmethod + def simulate_order_book_update(market_info: MarketTradingPairTuple, n: int, is_bid: bool): + # Removes first n bid/ask entries + update_id = int(time.time()) + + if is_bid: + new_bids: List[OrderBookRow] = [ + OrderBookRow(row.price, 0, row.update_id + 1) + for i, row in enumerate(market_info.order_book.bid_entries()) + if i < n + ] + new_asks = [] + else: + new_asks: List[OrderBookRow] = [ + OrderBookRow(row.price, 0, row.update_id + 1) + for i, row in enumerate(market_info.order_book.ask_entries()) + if i < n + ] + new_bids = [] + + market_info.order_book.apply_diffs(new_bids, new_asks, update_id) + + def test_order_book(self): + # Calculate expected OrderBook volume + expected_bid_volume: Decimal = Decimal("0") + expected_ask_volume: Decimal = Decimal("0") + + # Calculate bid volume + current_price = 100 - 1 / 2 + current_size = 10 + while current_price >= 50: + expected_bid_volume += Decimal(str(current_size)) + current_price -= 1 + current_size += 10 + + # Calculate ask volume + current_price = 100 + 1 / 2 + current_size = 10 + while current_price <= 150: + expected_ask_volume += Decimal(str(current_size)) + current_price += 1 + current_size += 10 + + # Check order book by comparing the total volume + # TODO: Determine a better approach to comparing orderbooks + current_bid_volume: Decimal = sum([Decimal(entry.amount) for entry in self.market_info.order_book.bid_entries()]) + current_ask_volume: Decimal = sum([Decimal(entry.amount) for entry in self.market_info.order_book.ask_entries()]) + + self.assertEqual(expected_bid_volume, current_bid_volume) + self.assertEqual(expected_ask_volume, current_ask_volume) + + def test_quote_balance(self): + # Check initial balance + expected_quote_balance = self.quote_balance + self.assertEqual(self.quote_balance, self.market_info.quote_balance) + + # Simulate an order fill + fill_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("101.0"), + quantity=Decimal("10")) + self.simulate_limit_order_fill(self.market_info.market, fill_order) + + # Updates expected quote balance + expected_quote_balance = self.quote_balance - (fill_order.price * fill_order.quantity) + self.assertNotEqual(self.quote_balance, self.market_info.quote_balance) + self.assertEqual(expected_quote_balance, self.market_info.quote_balance) + + def test_base_balance(self): + # Check initial balance + expected_base_balance = self.base_balance + self.assertEqual(self.base_balance, self.market_info.base_balance) + + # Simulate order fill + fill_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=Decimal("101.0"), + quantity=Decimal("10")) + self.simulate_limit_order_fill(self.market_info.market, fill_order) + + # Updates expected base balance + expected_base_balance = self.base_balance + fill_order.quantity + self.assertNotEqual(self.base_balance, self.market_info.base_balance) + self.assertEqual(expected_base_balance, self.market_info.base_balance) + + def test_get_mid_price(self): + # Check initial mid price + self.assertIs + self.assertEqual(Decimal(str(self.initial_mid_price)), self.market_info.get_mid_price()) + + # Calculate new mid price after removing first n bid entries in orderbook + n_entires: int = 10 + bid_entries, ask_entries = self.market_info.order_book.snapshot + best_bid: Decimal = Decimal(bid_entries.drop(list(range(n_entires))).iloc[1]["price"]) + best_ask: Decimal = Decimal(ask_entries.iloc[1]["price"]) + + expected_mid_price = (best_bid + best_ask) / Decimal("2") + + # Simulate n bid entries being removed + self.simulate_order_book_update(self.market_info, n_entires, True) + + self.assertNotEqual(Decimal(str(self.initial_mid_price)), self.market_info.get_mid_price()) + self.assertEqual(expected_mid_price, self.market_info.get_mid_price()) + + def test_get_price(self): + # Check buy price + expected_buy_price: Decimal = min([entry.price for entry in self.market.order_book_ask_entries(self.trading_pair)]) + self.assertEqual(expected_buy_price, self.market_info.get_price(is_buy=True)) + + # Check sell price + expected_sell_price: Decimal = max([entry.price for entry in self.market.order_book_bid_entries(self.trading_pair)]) + self.assertEqual(expected_sell_price, self.market_info.get_price(is_buy=False)) + + def test_get_price_by_type(self): + # Check PriceType.BestAsk + expected_best_ask: Decimal = max([entry.price for entry in self.market.order_book_bid_entries(self.trading_pair)]) + self.assertEqual(expected_best_ask, self.market_info.get_price_by_type(PriceType.BestBid)) + + # Check PriceType.BestAsk + expected_best_ask: Decimal = min([entry.price for entry in self.market.order_book_ask_entries(self.trading_pair)]) + self.assertEqual(expected_best_ask, self.market_info.get_price_by_type(PriceType.BestAsk)) + + # Check PriceType.MidPrice + expected_mid_price: Decimal = Decimal(self.initial_mid_price) + self.assertEqual(expected_mid_price, self.market_info.get_price_by_type(PriceType.MidPrice)) + + # Check initial PriceType.LastTrade + self.assertTrue(math.isnan(self.market_info.get_price_by_type(PriceType.LastTrade))) + + # Simulate fill buy order + expected_trade_price = Decimal("101.0") + fill_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.base_asset, + quote_currency=self.quote_asset, + price=expected_trade_price, + quantity=Decimal("10")) + self.simulate_limit_order_fill(self.market_info.market, fill_order) + + # Check for updated trade price + self.assertEqual(expected_trade_price, self.market_info.get_price_by_type(PriceType.LastTrade)) + + def test_vwap_for_volume(self): + # Check VWAP on BUY sell + order_volume = 15 + filled_orders: List[OrderBookRow] = self.market.get_order_book(self.trading_pair).simulate_buy(order_volume) + expected_vwap: Decimal = sum([Decimal(o.price) * Decimal(o.amount) for o in filled_orders]) / order_volume + + self.assertAlmostEqual(expected_vwap, self.market_info.get_vwap_for_volume(True, order_volume).result_price, 3) + + # Check VWAP on SELL side + order_volume = 15 + filled_orders: List[OrderBookRow] = self.market.get_order_book(self.trading_pair).simulate_sell(order_volume) + expected_vwap: Decimal = sum([Decimal(o.price) * Decimal(o.amount) for o in filled_orders]) / order_volume + + self.assertAlmostEqual(expected_vwap, self.market_info.get_vwap_for_volume(False, order_volume).result_price, 3) + + def test_get_price_for_volume(self): + # Check price on BUY sell + order_volume = 15 + filled_orders: List[OrderBookRow] = self.market.get_order_book(self.trading_pair).simulate_buy(order_volume) + expected_buy_price: Decimal = max([Decimal(o.price) for o in filled_orders]) + + self.assertAlmostEqual(expected_buy_price, self.market_info.get_price_for_volume(True, order_volume).result_price, 3) + + # Check price on SELL side + order_volume = 15 + filled_orders: List[OrderBookRow] = self.market.get_order_book(self.trading_pair).simulate_sell(order_volume) + expected_sell_price: Decimal = min([Decimal(o.price) for o in filled_orders]) + + self.assertAlmostEqual(expected_sell_price, self.market_info.get_price_for_volume(False, order_volume).result_price, 3) + + def test_order_book_bid_entries(self): + # Check all entries. + order_book: OrderBook = self.market.get_order_book(self.trading_pair) + bid_entries: List[OrderBookRow] = order_book.bid_entries() + + self.assertTrue(set(bid_entries).intersection(set(self.market_info.order_book_bid_entries()))) + + def test_order_book_ask_entries(self): + # Check all entries. + order_book: OrderBook = self.market.get_order_book(self.trading_pair) + ask_entries: List[OrderBookRow] = order_book.ask_entries() + + self.assertTrue(set(ask_entries).intersection(set(self.market_info.order_book_ask_entries()))) diff --git a/test/hummingbot/strategy/test_order_tracker.py b/test/hummingbot/strategy/test_order_tracker.py new file mode 100644 index 0000000..5dc1c34 --- /dev/null +++ b/test/hummingbot/strategy/test_order_tracker.py @@ -0,0 +1,487 @@ +import asyncio +import time +import unittest +from decimal import Decimal +from typing import List, Union + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_tracker import OrderTracker + + +class OrderTrackerUnitTests(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + clock_tick_size = 10 + + @classmethod + def setUpClass(cls): + cls.ev_loop = asyncio.get_event_loop() + cls.trading_pair = "COINALPHA-HBOT" + + cls.limit_orders: List[LimitOrder] = [ + LimitOrder(client_order_id=f"LIMIT//-{i}-{int(time.time()*1e6)}", + trading_pair=cls.trading_pair, + is_buy=True if i % 2 == 0 else False, + base_currency=cls.trading_pair.split("-")[0], + quote_currency=cls.trading_pair.split("-")[1], + price=Decimal(f"{100 - i}") if i % 2 == 0 else Decimal(f"{100 + i}"), + quantity=Decimal(f"{10 * (i + 1)}"), + creation_timestamp=int(time.time() * 1e6) + ) + for i in range(20) + ] + cls.market_orders: List[MarketOrder] = [ + MarketOrder(order_id=f"MARKET//-{i}-{int(time.time()*1e3)}", + trading_pair=cls.trading_pair, + is_buy=True if i % 2 == 0 else False, + base_asset=cls.trading_pair.split("-")[0], + quote_asset=cls.trading_pair.split("-")[1], + amount=float(f"{10 * (i + 1)}"), + timestamp=time.time() + ) + for i in range(20) + ] + + cls.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + cls.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + cls.market, cls.trading_pair, *cls.trading_pair.split("-") + ) + + def setUp(self): + self.order_tracker: OrderTracker = OrderTracker() + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.clock.add_iterator(self.order_tracker) + self.clock.backtest_til(self.start_timestamp) + + @staticmethod + def simulate_place_order(order_tracker: OrderTracker, order: Union[LimitOrder, MarketOrder], market_info: MarketTradingPairTuple): + """ + Simulates an order being succesfully placed. + """ + if isinstance(order, LimitOrder): + order_tracker.add_create_order_pending(order.client_order_id) + order_tracker.start_tracking_limit_order(market_pair=market_info, + order_id=order.client_order_id, + is_buy=order.is_buy, + price=order.price, + quantity=order.quantity + ) + else: + order_tracker.add_create_order_pending(order.order_id) + order_tracker.start_tracking_market_order(market_pair=market_info, + order_id=order.order_id, + is_buy=order.is_buy, + quantity=order.amount + ) + + @staticmethod + def simulate_order_created(order_tracker: OrderTracker, order: Union[LimitOrder, MarketOrder]): + order_id = order.client_order_id if isinstance(order, LimitOrder) else order.order_id + order_tracker.remove_create_order_pending(order_id) + + @staticmethod + def simulate_stop_tracking_order(order_tracker: OrderTracker, order: Union[LimitOrder, MarketOrder], market_info: MarketTradingPairTuple): + """ + Simulates an order being cancelled or filled completely. + """ + if isinstance(order, LimitOrder): + order_tracker.stop_tracking_limit_order(market_pair=market_info, + order_id=order.client_order_id, + ) + else: + order_tracker.stop_tracking_market_order(market_pair=market_info, + order_id=order.order_id + ) + + @staticmethod + def simulate_cancel_order(order_tracker: OrderTracker, order: Union[LimitOrder, MarketOrder]): + """ + Simulates order being cancelled. + """ + order_id = order.client_order_id if isinstance(order, LimitOrder) else order.order_id + if order_id: + order_tracker.check_and_track_cancel(order_id) + + def test_active_limit_orders(self): + # Check initial output + self.assertTrue(len(self.order_tracker.active_limit_orders) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.active_limit_orders) == len(self.limit_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.limit_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + self.assertTrue(len(self.order_tracker.active_limit_orders) == len(self.limit_orders) - 1) + + def test_shadow_limit_orders(self): + # Check initial output + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == len(self.limit_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.limit_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == len(self.limit_orders) - 1) + + def test_market_pair_to_active_orders(self): + # Check initial output + self.assertTrue(len(self.order_tracker.market_pair_to_active_orders) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.market_pair_to_active_orders[self.market_info]) == len(self.limit_orders)) + + def test_active_bids(self): + # Check initial output + self.assertTrue(len(self.order_tracker.active_bids) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.active_bids) == len(self.limit_orders) / 2) + + def test_active_asks(self): + # Check initial output + self.assertTrue(len(self.order_tracker.active_asks) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.active_asks) == len(self.limit_orders) / 2) + + def test_tracked_limit_orders(self): + # Check initial output + self.assertTrue(len(self.order_tracker.tracked_limit_orders) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.tracked_limit_orders) == len(self.limit_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.limit_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + # Note: This includes all orders(open, cancelled, filled, partially filled). + # Hence it should not differ from initial list of orders + self.assertTrue(len(self.order_tracker.tracked_limit_orders) == len(self.limit_orders)) + + def test_tracked_limit_orders_data_frame(self): + # Check initial output + self.assertTrue(len(self.order_tracker.tracked_limit_orders_data_frame) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.tracked_limit_orders_data_frame) == len(self.limit_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.limit_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + # Note: This includes all orders(open, cancelled, filled, partially filled). + # Hence it should not differ from initial list of orders + self.assertTrue(len(self.order_tracker.tracked_limit_orders_data_frame) == len(self.limit_orders)) + + def test_tracked_market_orders(self): + # Check initial output + self.assertTrue(len(self.order_tracker.tracked_market_orders) == 0) + + # Simulate orders being placed and tracked + for order in self.market_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.tracked_market_orders) == len(self.market_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.market_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + # Note: This includes all orders(open, cancelled, filled, partially filled). + # Hence it should not differ from initial list of orders + self.assertTrue(len(self.order_tracker.tracked_market_orders) == len(self.market_orders)) + + def test_tracked_market_order_data_frame(self): + # Check initial output + self.assertTrue(len(self.order_tracker.tracked_market_orders_data_frame) == 0) + + # Simulate orders being placed and tracked + for order in self.market_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.tracked_market_orders_data_frame) == len(self.market_orders)) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.market_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + # Note: This includes all orders(open, cancelled, filled, partially filled). + # Hence it should not differ from initial list of orders + self.assertTrue(len(self.order_tracker.tracked_market_orders_data_frame) == len(self.market_orders)) + + def test_in_flight_cancels(self): + # Check initial output + self.assertTrue(len(self.order_tracker.in_flight_cancels) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + # Simulates order cancellation request being sent to exchange + order_to_cancel = self.limit_orders[0] + self.simulate_cancel_order(self.order_tracker, order_to_cancel) + + self.assertTrue(len(self.order_tracker.in_flight_cancels) == 1) + + def test_in_flight_pending_created(self): + # Check initial output + self.assertTrue(len(self.order_tracker.in_flight_pending_created) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + + self.assertTrue(len(self.order_tracker.in_flight_pending_created) == len(self.limit_orders)) + + for order in self.limit_orders: + self.simulate_order_created(self.order_tracker, order) + + self.assertTrue(len(self.order_tracker.in_flight_pending_created) == 0) + + def test_get_limit_orders(self): + # Check initial output + self.assertTrue(len(list(self.order_tracker.get_limit_orders().values())) == 0) + + # Simulate orders being placed and tracked + for order in self.limit_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + + self.assertTrue(len(self.order_tracker.get_limit_orders()[self.market_info].keys()) == len(self.limit_orders)) + + def test_get_market_orders(self): + # Check initial output + self.assertTrue(len(list(self.order_tracker.get_market_orders().values())) == 0) + + # Simulate orders being placed and tracked + for order in self.market_orders: + self.simulate_place_order(self.order_tracker, order, self.market_info) + + self.assertTrue(len(self.order_tracker.get_market_orders()[self.market_info].keys()) == len(self.market_orders)) + + def test_get_shadow_limit_orders(self): + # Check initial output + self.assertTrue(self.market_info not in self.order_tracker.get_shadow_limit_orders()) + + # Simulates order being placed and tracked + order: LimitOrder = self.limit_orders[0] + self.simulate_place_order(self.order_tracker, order, self.market_info) + + # Compare order details and output + other_order = self.order_tracker.get_shadow_limit_orders()[self.market_info][order.client_order_id] + self.assertEqual(order.trading_pair, other_order.trading_pair) + self.assertEqual(order.price, other_order.price) + self.assertEqual(order.quantity, other_order.quantity) + self.assertEqual(order.is_buy, other_order.is_buy) + + # Simulate order being cancelled + self.simulate_cancel_order(self.order_tracker, order) + self.simulate_stop_tracking_order(self.order_tracker, order, self.market_info) + + # Check that order is not yet removed from shadow_limit_orders + other_order = self.order_tracker.get_shadow_limit_orders()[self.market_info][order.client_order_id] + self.assertEqual(order.trading_pair, other_order.trading_pair) + self.assertEqual(order.price, other_order.price) + self.assertEqual(order.quantity, other_order.quantity) + self.assertEqual(order.is_buy, other_order.is_buy) + + # Simulates current_timestamp > SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION + self.clock.backtest_til(self.start_timestamp + OrderTracker.SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION + 1) + self.order_tracker.check_and_cleanup_shadow_records() + + # Check that check_and_cleanup_shadow_records clears shadow_limit_orders + self.assertTrue(self.market_info not in self.order_tracker.get_shadow_limit_orders()) + + def test_has_in_flight_cancel(self): + # Check initial output + self.assertFalse(self.order_tracker.has_in_flight_cancel("ORDER_ID_DO_NOT_EXIST")) + + # Simulates order being placed and tracked + order: LimitOrder = self.limit_orders[0] + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + # Order not yet cancelled. + self.assertFalse(self.order_tracker.has_in_flight_cancel(order.client_order_id)) + + # Simulate order being cancelled + self.simulate_cancel_order(self.order_tracker, order) + + # Order inflight cancel timestamp has not yet expired + self.assertTrue(self.order_tracker.has_in_flight_cancel(order.client_order_id)) + + # Simulate in-flight cancel has expired + self.clock.backtest_til(self.start_timestamp + OrderTracker.CANCEL_EXPIRY_DURATION + 1) + + self.assertFalse(self.order_tracker.has_in_flight_cancel(order.client_order_id)) + + # Simulates order being placed and tracked + order: LimitOrder = self.limit_orders[0] + self.simulate_place_order(self.order_tracker, order, self.market_info) + self.simulate_order_created(self.order_tracker, order) + + # Simulate order being cancelled and no longer tracked + self.simulate_cancel_order(self.order_tracker, order) + self.simulate_stop_tracking_order(self.order_tracker, order, self.market_info) + + # Check that once the order is no longer tracker, it will no longer have a pending cancel + self.assertFalse(self.order_tracker.has_in_flight_cancel(order.client_order_id)) + + def test_get_market_pair_from_order_id(self): + # Initial validation + order: LimitOrder = self.limit_orders[0] + + self.assertNotEqual(self.market_info, self.order_tracker.get_market_pair_from_order_id(order.client_order_id)) + + # Simulate order being placed and tracked + self.simulate_place_order(self.order_tracker, order, self.market_info) + + self.assertEqual(self.market_info, self.order_tracker.get_market_pair_from_order_id(order.client_order_id)) + + def test_get_shadow_market_pair_from_order_id(self): + # Simulate order being placed and tracked + order: LimitOrder = self.limit_orders[0] + self.assertNotEqual(self.market_info, self.order_tracker.get_shadow_market_pair_from_order_id(order.client_order_id)) + + self.simulate_place_order(self.order_tracker, order, self.market_info) + + self.assertEqual(self.market_info, self.order_tracker.get_shadow_market_pair_from_order_id(order.client_order_id)) + + def test_get_limit_order(self): + # Initial validation + order: LimitOrder = self.limit_orders[0] + + # Order not yet placed + self.assertNotEqual(order, self.order_tracker.get_limit_order(self.market_info, order.client_order_id)) + + # Simulate order being placed and tracked + self.simulate_place_order(self.order_tracker, order, self.market_info) + + # Unrecognized Order + self.assertNotEqual(order, self.order_tracker.get_limit_order(self.market_info, "UNRECOGNIZED_ORDER")) + + # Matching Order + other_order = self.order_tracker.get_limit_order(self.market_info, order.client_order_id) + self.assertEqual(order.trading_pair, other_order.trading_pair) + self.assertEqual(order.price, other_order.price) + self.assertEqual(order.quantity, other_order.quantity) + self.assertEqual(order.is_buy, other_order.is_buy) + + def test_get_market_order(self): + # Initial validation + order: MarketOrder = MarketOrder(order_id=f"MARKET//-{self.clock.current_timestamp}", + trading_pair=self.trading_pair, + is_buy=True, + base_asset=self.trading_pair.split("-")[0], + quote_asset=self.trading_pair.split("-")[1], + amount=float(10), + timestamp=self.clock.current_timestamp + ) + + # Order not yet placed + self.assertNotEqual(order, self.order_tracker.get_market_order(self.market_info, order.order_id)) + + # Simulate order being placed and tracked + self.simulate_place_order(self.order_tracker, order, self.market_info) + + # Unrecognized Order + self.assertNotEqual(order, self.order_tracker.get_market_order(self.market_info, "UNRECOGNIZED_ORDER")) + + # Matching Order + self.assertEqual(str(order), str(self.order_tracker.get_market_order(self.market_info, order.order_id))) + + def test_get_shadow_limit_order(self): + # Initial validation + order: LimitOrder = self.limit_orders[0] + + # Order not yet placed + self.assertNotEqual(order, self.order_tracker.get_shadow_limit_order(order.client_order_id)) + + # Simulate order being placed and tracked + self.simulate_place_order(self.order_tracker, order, self.market_info) + + # Unrecognized Order + self.assertNotEqual(order, self.order_tracker.get_shadow_limit_order("UNRECOGNIZED_ORDER")) + + # Matching Order + shadow_order = self.order_tracker.get_shadow_limit_order(order.client_order_id) + self.assertEqual(order.trading_pair, shadow_order.trading_pair) + self.assertEqual(order.price, shadow_order.price) + self.assertEqual(order.quantity, shadow_order.quantity) + self.assertEqual(order.is_buy, shadow_order.is_buy) + + # Simulate order cancel + self.simulate_cancel_order(self.order_tracker, order) + + self.assertNotEqual(order, self.order_tracker.get_shadow_limit_order(order.client_order_id)) + + def test_check_and_cleanup_shadow_records(self): + order: LimitOrder = self.limit_orders[0] + + # Simulate order being placed and tracked + self.simulate_place_order(self.order_tracker, order, self.market_info) + + # Check for shadow_tracked_limit_order + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == 1) + + # Simulate order cancel and stop tracking order + self.simulate_cancel_order(self.order_tracker, order) + self.simulate_stop_tracking_order(self.order_tracker, order, self.market_info) + + # Check for shadow_tracked_limit_order + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == 1) + + # Simulates current_timestamp > SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION + self.clock.backtest_til(self.start_timestamp + OrderTracker.SHADOW_MAKER_ORDER_KEEP_ALIVE_DURATION + 1) + self.order_tracker.check_and_cleanup_shadow_records() + + # Check that check_and_cleanup_shadow_records clears shadow_limit_orders + self.assertTrue(len(self.order_tracker.shadow_limit_orders) == 0) diff --git a/test/hummingbot/strategy/test_script_strategy_base.py b/test/hummingbot/strategy/test_script_strategy_base.py new file mode 100644 index 0000000..8ada099 --- /dev/null +++ b/test/hummingbot/strategy/test_script_strategy_base.py @@ -0,0 +1,155 @@ +import unittest +from decimal import Decimal +from typing import List + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock +from hummingbot.core.clock_mode import ClockMode +from hummingbot.core.event.events import OrderType +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MockScriptStrategy(ScriptStrategyBase): + pass + + +class ScriptStrategyBaseTest(unittest.TestCase): + level = 0 + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + def setUp(self): + self.log_records = [] + self.start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + self.end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + self.start_timestamp: float = self.start.timestamp() + self.end_timestamp: float = self.end.timestamp() + self.connector_name: str = "mock_paper_exchange" + self.trading_pair: str = "HBOT-USDT" + self.base_asset, self.quote_asset = self.trading_pair.split("-") + self.base_balance: int = 500 + self.quote_balance: int = 5000 + self.initial_mid_price: int = 100 + self.clock_tick_size = 1 + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.connector: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.connector.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=100, + min_price=50, + max_price=150, + price_step_size=1, + volume_step_size=10) + self.connector.set_balance(self.base_asset, self.base_balance) + self.connector.set_balance(self.quote_asset, self.quote_balance) + self.connector.set_quantization_param( + QuantizationParams( + self.trading_pair, 6, 6, 6, 6 + ) + ) + self.clock.add_iterator(self.connector) + ScriptStrategyBase.markets = {self.connector_name: {self.trading_pair}} + self.strategy = ScriptStrategyBase({self.connector_name: self.connector}) + self.strategy.logger().setLevel(1) + self.strategy.logger().addHandler(self) + + def test_start(self): + self.assertFalse(self.strategy.ready_to_trade) + self.strategy.start(Clock(ClockMode.BACKTEST), self.start_timestamp) + self.strategy.tick(self.start_timestamp + 10) + self.assertTrue(self.strategy.ready_to_trade) + + def test_get_assets(self): + self.strategy.markets = {"con_a": {"HBOT-USDT", "BTC-USDT"}, "con_b": {"HBOT-BTC", "HBOT-ETH"}} + self.assertRaises(KeyError, self.strategy.get_assets, "con_c") + assets = self.strategy.get_assets("con_a") + self.assertEqual(3, len(assets)) + self.assertEqual("BTC", assets[0]) + self.assertEqual("HBOT", assets[1]) + self.assertEqual("USDT", assets[2]) + + assets = self.strategy.get_assets("con_b") + self.assertEqual(3, len(assets)) + self.assertEqual("BTC", assets[0]) + self.assertEqual("ETH", assets[1]) + self.assertEqual("HBOT", assets[2]) + + def test_get_market_trading_pair_tuples(self): + market_infos: List[MarketTradingPairTuple] = self.strategy.get_market_trading_pair_tuples() + self.assertEqual(1, len(market_infos)) + market_info = market_infos[0] + self.assertEqual(market_info.market, self.connector) + self.assertEqual(market_info.trading_pair, self.trading_pair) + self.assertEqual(market_info.base_asset, self.base_asset) + self.assertEqual(market_info.quote_asset, self.quote_asset) + + def test_active_orders(self): + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.strategy.buy(self.connector_name, self.trading_pair, Decimal("1"), OrderType.LIMIT, Decimal("90")) + self.strategy.sell(self.connector_name, self.trading_pair, Decimal("1.1"), OrderType.LIMIT, Decimal("110")) + orders = self.strategy.get_active_orders(self.connector_name) + self.assertEqual(2, len(orders)) + self.assertTrue(orders[0].is_buy) + self.assertEqual(Decimal("1"), orders[0].quantity) + self.assertEqual(Decimal("90"), orders[0].price) + self.assertFalse(orders[1].is_buy) + self.assertEqual(Decimal("1.1"), orders[1].quantity) + self.assertEqual(Decimal("110"), orders[1].price) + + def test_format_status(self): + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) + self.strategy.buy(self.connector_name, self.trading_pair, Decimal("1"), OrderType.LIMIT, Decimal("90")) + self.strategy.sell(self.connector_name, self.trading_pair, Decimal("1.1"), OrderType.LIMIT, Decimal("110")) + expected_status = """ + Balances: + Exchange Asset Total Balance Available Balance + mock_paper_exchange HBOT 500 498.9 + mock_paper_exchange USDT 5000 4910 + + Orders: + Exchange Market Side Price Amount Age + mock_paper_exchange HBOT-USDT buy 90 1""" + self.assertTrue(expected_status in self.strategy.format_status()) + self.assertTrue("mock_paper_exchange HBOT-USDT sell 110 1.1 " in self.strategy.format_status()) + + def test_cancel_buy_order(self): + self.clock.add_iterator(self.strategy) + self.clock.backtest_til(self.start_timestamp) + + order_id = self.strategy.buy( + connector_name=self.connector_name, + trading_pair=self.trading_pair, + amount=Decimal("100"), + order_type=OrderType.LIMIT, + price=Decimal("1000"), + ) + + self.assertIn(order_id, + [order.client_order_id for order in self.strategy.get_active_orders(self.connector_name)]) + + self.strategy.cancel( + connector_name=self.connector_name, + trading_pair=self.trading_pair, + order_id=order_id + ) + + self.assertTrue( + self._is_logged( + log_level="INFO", + message=f"({self.trading_pair}) Canceling the limit order {order_id}." + ) + ) diff --git a/test/hummingbot/strategy/test_strategy_base.py b/test/hummingbot/strategy/test_strategy_base.py new file mode 100644 index 0000000..399379d --- /dev/null +++ b/test/hummingbot/strategy/test_strategy_base.py @@ -0,0 +1,442 @@ +import asyncio +import logging +import time +import unittest +import unittest.mock +from datetime import datetime +from decimal import Decimal +from typing import Any, Dict, List, Tuple, Union + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.client.hummingbot_application import HummingbotApplication +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.event.events import MarketEvent, OrderFilledEvent +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_tracker import OrderTracker +from hummingbot.strategy.strategy_base import StrategyBase + +ms_logger = None + + +class ExtendedMockPaperExchange(MockPaperExchange): + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super().__init__(client_config_map) + + self._in_flight_orders = {} + + @property + def limit_orders(self) -> List[LimitOrder]: + return [ + in_flight_order.to_limit_order() + for in_flight_order in self._in_flight_orders.values() + ] + + def restored_market_states(self, saved_states: Dict[str, any]): + self._in_flight_orders.update({ + key: value + for key, value in saved_states.items() + }) + + +class MockStrategy(StrategyBase): + + @classmethod + def logger(cls) -> logging.Logger: + global ms_logger + if ms_logger is None: + ms_logger = logging.getLogger(__name__) + return ms_logger + + +class StrategyBaseUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.ev_loop = asyncio.get_event_loop() + cls.trading_pair = "COINALPHA-HBOT" + + def setUp(self): + self.market: ExtendedMockPaperExchange = ExtendedMockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.trading_pair, *self.trading_pair.split("-") + ) + + self.mid_price = 100 + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.mid_price, min_price=1, + max_price=200, price_step_size=1, volume_step_size=10) + self.market.set_balance("COINALPHA", 500) + self.market.set_balance("WETH", 5000) + self.market.set_balance("QETH", 500) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair.split("-")[0], 6, 6, 6, 6 + ) + ) + + self.strategy: StrategyBase = MockStrategy() + self.strategy.add_markets([self.market]) + self.strategy.order_tracker._set_current_timestamp(1640001112.223) + + @staticmethod + def simulate_order_filled(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + + market_info.market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + time.time(), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + order.trading_pair, + TradeType.BUY if order.is_buy else TradeType.SELL, + OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET, + order.price, + order.quantity if isinstance(order, LimitOrder) else order.amount, + Decimal("1") + ) + ) + + def test_active_markets(self): + self.assertEqual(1, len(self.strategy.active_markets)) + + def test_order_tracker(self): + self.assertIsInstance(self.strategy.order_tracker, OrderTracker) + + def test_trades(self): + self.assertEqual(0, len(self.strategy.trades)) + + # Simulate order being placed and filled + limit_order = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + self.simulate_order_filled(self.market_info, limit_order) + + self.assertEqual(1, len(self.strategy.trades)) + + def test_add_markets(self): + + self.assertEqual(1, len(self.strategy.active_markets)) + + new_market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.strategy.add_markets([new_market]) + + self.assertEqual(2, len(self.strategy.active_markets)) + + def test_remove_markets(self): + self.assertEqual(1, len(self.strategy.active_markets)) + + self.strategy.remove_markets([self.market]) + + self.assertEqual(0, len(self.strategy.active_markets)) + + def test_cum_flat_fees(self): + + fee_asset = self.trading_pair.split("-")[1] + trades: List[Tuple[str, Decimal]] = [ + (fee_asset, Decimal(f"{i}")) + for i in range(5) + ] + + expected_total_fees = sum([Decimal(f"{i}") for i in range(5)]) + + self.assertEqual(expected_total_fees, self.strategy.cum_flat_fees(fee_asset, trades)) + + def test_buy_with_specific_market(self): + limit_order: LimitOrder = LimitOrder( + client_order_id="limit_test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + limit_order_id: str = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.LIMIT, + price=limit_order.price, + amount=limit_order.quantity, + ) + + tracked_limit_order: LimitOrder = self.strategy.order_tracker.get_limit_order(self.market_info, limit_order_id) + + # Note: order_id generate here is random + self.assertIsNotNone(limit_order_id) + + self.assertEqual(limit_order.is_buy, tracked_limit_order.is_buy) + self.assertEqual(limit_order.trading_pair, tracked_limit_order.trading_pair) + self.assertEqual(limit_order.price, tracked_limit_order.price) + self.assertEqual(limit_order.quantity, tracked_limit_order.quantity) + + market_order: MarketOrder = MarketOrder( + order_id="market_test", + trading_pair=self.trading_pair, + is_buy=True, + base_asset=self.trading_pair.split("-")[0], + quote_asset=self.trading_pair.split("-")[1], + amount=Decimal("100"), + timestamp =int(time.time() * 1e3) + ) + + # Note: order_id generate here is random + market_order_id: str = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.MARKET, + amount=market_order.amount + ) + + tracked_market_order: MarketOrder = self.strategy.order_tracker.get_market_order(self.market_info, market_order_id) + + # Note: order_id generate here is random + self.assertIsNotNone(market_order_id) + + self.assertEqual(market_order.is_buy, tracked_market_order.is_buy) + self.assertEqual(market_order.trading_pair, tracked_market_order.trading_pair) + self.assertEqual(market_order.amount, tracked_market_order.amount) + + def test_sell_with_specific_market(self): + limit_order: LimitOrder = LimitOrder( + client_order_id="limit_test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + limit_order_id: str = self.strategy.sell_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.LIMIT, + price=limit_order.price, + amount=limit_order.quantity, + ) + + tracked_limit_order: LimitOrder = self.strategy.order_tracker.get_limit_order(self.market_info, limit_order_id) + + # Note: order_id generate here is random + self.assertIsNotNone(limit_order_id) + + self.assertEqual(limit_order.is_buy, tracked_limit_order.is_buy) + self.assertEqual(limit_order.trading_pair, tracked_limit_order.trading_pair) + self.assertEqual(limit_order.price, tracked_limit_order.price) + self.assertEqual(limit_order.quantity, tracked_limit_order.quantity) + + market_order: MarketOrder = MarketOrder( + order_id="market_test", + trading_pair=self.trading_pair, + is_buy=False, + base_asset=self.trading_pair.split("-")[0], + quote_asset=self.trading_pair.split("-")[1], + amount=Decimal("100"), + timestamp =int(time.time() * 1e3) + ) + + # Note: order_id generate here is random + market_order_id: str = self.strategy.sell_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.MARKET, + amount=market_order.amount + ) + + tracked_market_order: MarketOrder = self.strategy.order_tracker.get_market_order(self.market_info, market_order_id) + + # Note: order_id generate here is random + self.assertIsNotNone(market_order_id) + + self.assertEqual(market_order.is_buy, tracked_market_order.is_buy) + self.assertEqual(market_order.trading_pair, tracked_market_order.trading_pair) + self.assertEqual(market_order.amount, tracked_market_order.amount) + + def test_cancel_order(self): + self.assertEqual(0, len(self.strategy.order_tracker.in_flight_cancels)) + + limit_order: LimitOrder = LimitOrder( + client_order_id="limit_test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + limit_order_id: str = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.LIMIT, + price=limit_order.price, + amount=limit_order.quantity, + ) + + self.strategy.cancel_order(self.market_info, limit_order_id) + self.assertEqual(0, len(self.strategy.order_tracker.in_flight_cancels)) + + def test_start_tracking_limit_order(self): + self.assertEqual(0, len(self.strategy.order_tracker.tracked_limit_orders)) + + limit_order: LimitOrder = LimitOrder( + client_order_id="limit_test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.LIMIT, + price=limit_order.price, + amount=limit_order.quantity, + ) + + self.assertEqual(1, len(self.strategy.order_tracker.tracked_limit_orders)) + + def test_stop_tracking_limit_order(self): + self.assertEqual(0, len(self.strategy.order_tracker.tracked_limit_orders)) + + limit_order: LimitOrder = LimitOrder( + client_order_id="limit_test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + limit_order_id: str = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.LIMIT, + price=limit_order.price, + amount=limit_order.quantity, + ) + + self.assertEqual(1, len(self.strategy.order_tracker.tracked_limit_orders)) + + self.strategy.cancel_order(self.market_info, limit_order_id) + self.assertEqual(0, len(self.strategy.order_tracker.tracked_limit_orders)) + + def test_start_tracking_market_order(self): + self.assertEqual(0, len(self.strategy.order_tracker.tracked_limit_orders)) + + market_order: MarketOrder = MarketOrder( + order_id="market_test", + trading_pair=self.trading_pair, + is_buy=True, + base_asset=self.trading_pair.split("-")[0], + quote_asset=self.trading_pair.split("-")[1], + amount=Decimal("100"), + timestamp =int(time.time() * 1e3) + ) + + # Note: order_id generate here is random + self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.MARKET, + amount=market_order.amount + ) + + self.assertEqual(1, len(self.strategy.order_tracker.tracked_market_orders)) + + def test_stop_tracking_market_order(self): + self.assertEqual(0, len(self.strategy.order_tracker.tracked_limit_orders)) + + market_order: MarketOrder = MarketOrder( + order_id="market_test", + trading_pair=self.trading_pair, + is_buy=True, + base_asset=self.trading_pair.split("-")[0], + quote_asset=self.trading_pair.split("-")[1], + amount=Decimal("100"), + timestamp =int(time.time() * 1e3) + ) + + # Note: order_id generate here is random + market_order_id: str = self.strategy.buy_with_specific_market( + market_trading_pair_tuple=self.market_info, + order_type=OrderType.MARKET, + amount=market_order.amount + ) + self.strategy.cancel_order(self.market_info, market_order_id) + # Note: MarketOrder is assumed to be filled once placed. + self.assertEqual(1, len(self.strategy.order_tracker.tracked_market_orders)) + + def test_track_restored_order(self): + + self.assertEqual(0, len(self.market.limit_orders)) + + saved_states: Dict[str, Any] = { + f"LIMIT_ORDER_ID_{i}": InFlightOrderBase( + client_order_id=f"LIMIT_ORDER_ID_{i}", + exchange_order_id=f"LIMIT_ORDER_ID_{i}", + trading_pair=self.trading_pair, + order_type=OrderType.LIMIT, + trade_type=TradeType.BUY, + price=Decimal(f"{i+1}"), + amount=Decimal(f"{10 * (i+1)}"), + creation_timestamp=1640001112.0, + initial_state="OPEN" + ) + for i in range(10) + } + + self.market.restored_market_states(saved_states) + + self.assertEqual(10, len(self.strategy.track_restored_orders(self.market_info))) + + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotApplication.main_application') + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotCLI') + def test_notify_hb_app(self, cli_class_mock, main_application_function_mock): + messages = [] + cli_logs = [] + + cli_instance = cli_class_mock.return_value + cli_instance.log.side_effect = lambda message: cli_logs.append(message) + + notifier_mock = unittest.mock.MagicMock() + notifier_mock.add_msg_to_queue.side_effect = lambda message: messages.append(message) + + hummingbot_application = HummingbotApplication() + hummingbot_application.notifiers.append(notifier_mock) + main_application_function_mock.return_value = hummingbot_application + + self.strategy.notify_hb_app("Test message") + + self.assertIn("Test message", cli_logs) + self.assertIn("Test message", messages) + + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotApplication.main_application') + @unittest.mock.patch('hummingbot.client.hummingbot_application.HummingbotCLI') + def test_notify_hb_app_with_timestamp(self, cli_class_mock, main_application_function_mock): + messages = [] + cli_logs = [] + + cli_instance = cli_class_mock.return_value + cli_instance.log.side_effect = lambda message: cli_logs.append(message) + + notifier_mock = unittest.mock.MagicMock() + notifier_mock.add_msg_to_queue.side_effect = lambda message: messages.append(message) + + hummingbot_application = HummingbotApplication() + hummingbot_application.notifiers.append(notifier_mock) + main_application_function_mock.return_value = hummingbot_application + + time_of_tick = datetime(year=2021, month=6, day=17, hour=0, minute=0, second=0, microsecond=0) + + self.strategy.tick(time_of_tick.timestamp()) + self.strategy.notify_hb_app_with_timestamp("Test message") + + self.assertIn("(2021-06-17 00:00:00) Test message", cli_logs) + self.assertIn("(2021-06-17 00:00:00) Test message", messages) diff --git a/test/hummingbot/strategy/test_strategy_py_base.py b/test/hummingbot/strategy/test_strategy_py_base.py new file mode 100644 index 0000000..f633621 --- /dev/null +++ b/test/hummingbot/strategy/test_strategy_py_base.py @@ -0,0 +1,319 @@ +import asyncio +import time +import unittest +from collections import deque +from decimal import Decimal +from typing import Union + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.market_order import MarketOrder +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + BuyOrderCreatedEvent, + FundingPaymentCompletedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderFilledEvent, + SellOrderCompletedEvent, + SellOrderCreatedEvent, +) +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.strategy_py_base import StrategyPyBase + + +class MockPyStrategy(StrategyPyBase): + + def __init__(self): + super().__init__() + + # Used the check the events are recorded + self.events_queue = deque() + + def did_create_buy_order(self, order_created_event: BuyOrderCreatedEvent): + self.events_queue.append(order_created_event) + + def did_create_sell_order(self, order_created_event: SellOrderCreatedEvent): + self.events_queue.append(order_created_event) + + def did_fill_order(self, order_filled_event: OrderFilledEvent): + self.events_queue.append(order_filled_event) + + def did_fail_order(self, order_failed_event: MarketOrderFailureEvent): + self.events_queue.append(order_failed_event) + + def did_cancel_order(self, cancelled_event: OrderCancelledEvent): + self.events_queue.append(cancelled_event) + + def did_expire_order(self, expired_event: OrderExpiredEvent): + self.events_queue.append(expired_event) + + def did_complete_buy_order(self, order_completed_event: BuyOrderCompletedEvent): + self.events_queue.append(order_completed_event) + + def did_complete_sell_order(self, order_completed_event: SellOrderCompletedEvent): + self.events_queue.append(order_completed_event) + + def did_complete_funding_payment(self, funding_payment_completed_event: FundingPaymentCompletedEvent): + self.events_queue.append(funding_payment_completed_event) + + +class StrategyPyBaseUnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.ev_loop = asyncio.get_event_loop() + cls.trading_pair = "COINALPHA-HBOT" + + def setUp(self): + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.trading_pair, *self.trading_pair.split("-") + ) + + self.strategy: StrategyPyBase = MockPyStrategy() + self.strategy.add_markets([self.market]) + + @staticmethod + def simulate_order_created(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + event_tag = MarketEvent.BuyOrderCreated if order.is_buy else MarketEvent.SellOrderCreated + event_class = BuyOrderCreatedEvent if order.is_buy else SellOrderCreatedEvent + + market_info.market.trigger_event( + event_tag, + event_class( + int(time.time() * 1e3), + OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET, + order.trading_pair, + order.quantity if isinstance(order, LimitOrder) else order.amount, + order.price, + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + time.time() + ) + ) + + @staticmethod + def simulate_order_filled(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + market_info.market.trigger_event( + MarketEvent.OrderFilled, + OrderFilledEvent( + time.time(), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + order.trading_pair, + TradeType.BUY if order.is_buy else TradeType.SELL, + OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET, + order.price, + order.quantity if isinstance(order, LimitOrder) else order.amount, + Decimal("1") + ) + ) + + @staticmethod + def simulate_order_failed(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + market_info.market.trigger_event( + MarketEvent.OrderFailure, + MarketOrderFailureEvent( + int(time.time() * 1e3), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET + ) + ) + + @staticmethod + def simulate_cancel_order(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + market_info.market.trigger_event( + MarketEvent.OrderCancelled, + OrderCancelledEvent( + int(time.time() * 1e3), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + ) + ) + + @staticmethod + def simulate_order_expired(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + market_info.market.trigger_event( + MarketEvent.OrderExpired, + OrderExpiredEvent( + int(time.time() * 1e3), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + ) + ) + + @staticmethod + def simulate_order_completed(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]): + event_tag = MarketEvent.BuyOrderCompleted if order.is_buy else MarketEvent.SellOrderCompleted + event_class = BuyOrderCompletedEvent if order.is_buy else SellOrderCompletedEvent + + market_info.market.trigger_event( + event_tag, + event_class( + int(time.time() * 1e3), + order.client_order_id if isinstance(order, LimitOrder) else order.order_id, + order.trading_pair.split("-")[0], + order.trading_pair.split("-")[1], + Decimal("1") if order.is_buy else Decimal("0"), + Decimal("0") if order.is_buy else Decimal("1"), + OrderType.LIMIT if isinstance(order, LimitOrder) else OrderType.MARKET, + ) + ) + + @staticmethod + def simulate_funding_payment_completed(market_info: MarketTradingPairTuple): + + example_rate: Decimal = Decimal("100") + + # Example API response for funding payment details + response = { + "symbol": "BTCUSDT", + "incomeType": "COMMISSION", + "income": "-0.01000000", + "asset": "USDT", + "info": "COMMISSION", + "time": 1570636800000, + "tranId": "9689322392", + "tradeId": "2059192" + } + + market_info.market.trigger_event( + MarketEvent.FundingPaymentCompleted, + FundingPaymentCompletedEvent( + response["time"], + market_info.market.name, + example_rate, + response["symbol"], + response["income"] + ) + ) + + def test_did_create_buy_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + self.simulate_order_created(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, BuyOrderCreatedEvent) + + def test_did_create_sell_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_created(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, SellOrderCreatedEvent) + + def test_did_fill_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_filled(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, OrderFilledEvent) + + def test_did_cancel_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_cancel_order(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, OrderCancelledEvent) + + def test_did_fail_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_failed(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, MarketOrderFailureEvent) + + def test_did_expire_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_expired(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, OrderExpiredEvent) + + def test_did_complete_buy_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=True, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_completed(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, BuyOrderCompletedEvent) + + def test_did_complete_sell_order(self): + limit_order: LimitOrder = LimitOrder(client_order_id="test", + trading_pair=self.trading_pair, + is_buy=False, + base_currency=self.trading_pair.split("-")[0], + quote_currency=self.trading_pair.split("-")[1], + price=Decimal("100"), + quantity=Decimal("50")) + + self.simulate_order_completed(self.market_info, limit_order) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, SellOrderCompletedEvent) + + def test_did_complete_funding_payment(self): + self.simulate_funding_payment_completed(self.market_info) + + event = self.strategy.events_queue.popleft() + + self.assertIsInstance(event, FundingPaymentCompletedEvent) diff --git a/test/hummingbot/strategy/twap/__init__.py b/test/hummingbot/strategy/twap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/twap/test_twap.py b/test/hummingbot/strategy/twap/test_twap.py new file mode 100644 index 0000000..9529caf --- /dev/null +++ b/test/hummingbot/strategy/twap/test_twap.py @@ -0,0 +1,489 @@ +import math +import unittest +from datetime import datetime +from decimal import Decimal +from typing import List + +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee +from hummingbot.core.event.event_logger import EventLogger +from hummingbot.core.event.events import ( + BuyOrderCompletedEvent, + MarketEvent, + MarketOrderFailureEvent, + OrderCancelledEvent, + OrderExpiredEvent, + OrderFilledEvent, + SellOrderCompletedEvent, +) +from hummingbot.strategy.conditional_execution_state import RunInTimeConditionalExecutionState +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.twap import TwapTradeStrategy + + +class TWAPUnitTest(unittest.TestCase): + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + end: pd.Timestamp = pd.Timestamp("2019-01-01 01:00:00", tz="UTC") + start_timestamp: float = start.timestamp() + end_timestamp: float = end.timestamp() + maker_trading_pairs: List[str] = ["COINALPHA-WETH", "COINALPHA", "WETH"] + clock_tick_size = 10 + + level = 0 + log_records = [] + + def setUp(self): + + super().setUp() + self.log_records = [] + + self.clock: Clock = Clock(ClockMode.BACKTEST, self.clock_tick_size, self.start_timestamp, self.end_timestamp) + self.market: MockPaperExchange = MockPaperExchange( + client_config_map=ClientConfigAdapter(ClientConfigMap()) + ) + self.mid_price = 100 + self.order_delay_time = 15 + self.cancel_order_wait_time = 45 + self.market.set_balanced_order_book(trading_pair=self.maker_trading_pairs[0], + mid_price=self.mid_price, min_price=1, + max_price=200, price_step_size=1, volume_step_size=10) + self.market.set_balance("COINALPHA", 500) + self.market.set_balance("WETH", 50000) + self.market.set_balance("QETH", 500) + self.market.set_quantization_param( + QuantizationParams( + self.maker_trading_pairs[0], 6, 6, 6, 6 + ) + ) + + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple(*([self.market] + self.maker_trading_pairs)) + + # Define strategies to test + self.limit_buy_strategy: TwapTradeStrategy = TwapTradeStrategy( + [self.market_info], + order_price=Decimal("99"), + cancel_order_wait_time=self.cancel_order_wait_time, + is_buy=True, + order_delay_time=self.order_delay_time, + target_asset_amount=Decimal("2.0"), + order_step_size=Decimal("1.0") + ) + self.limit_sell_strategy: TwapTradeStrategy = TwapTradeStrategy( + [self.market_info], + order_price=Decimal("101"), + cancel_order_wait_time=self.cancel_order_wait_time, + is_buy=False, + order_delay_time=self.order_delay_time, + target_asset_amount=Decimal("5.0"), + order_step_size=Decimal("1.67") + ) + + self.clock.add_iterator(self.market) + self.maker_order_fill_logger: EventLogger = EventLogger() + self.cancel_order_logger: EventLogger = EventLogger() + self.buy_order_completed_logger: EventLogger = EventLogger() + self.sell_order_completed_logger: EventLogger = EventLogger() + + self.market.add_listener(MarketEvent.BuyOrderCompleted, self.buy_order_completed_logger) + self.market.add_listener(MarketEvent.SellOrderCompleted, self.sell_order_completed_logger) + self.market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) + self.market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage().startswith(message) + for record in self.log_records) + + @staticmethod + def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder): + quote_currency_traded: Decimal = limit_order.price * limit_order.quantity + base_currency_traded: Decimal = limit_order.quantity + quote_currency: str = limit_order.quote_currency + base_currency: str = limit_order.base_currency + + if limit_order.is_buy: + market.set_balance(quote_currency, market.get_balance(quote_currency) - quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) + base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.BUY, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal("0")) + )) + market.trigger_event(MarketEvent.BuyOrderCompleted, BuyOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + else: + market.set_balance(quote_currency, market.get_balance(quote_currency) + quote_currency_traded) + market.set_balance(base_currency, market.get_balance(base_currency) - base_currency_traded) + market.trigger_event(MarketEvent.OrderFilled, OrderFilledEvent( + market.current_timestamp, + limit_order.client_order_id, + limit_order.trading_pair, + TradeType.SELL, + OrderType.LIMIT, + limit_order.price, + limit_order.quantity, + AddedToCostTradeFee(Decimal("0")) + )) + market.trigger_event(MarketEvent.SellOrderCompleted, SellOrderCompletedEvent( + market.current_timestamp, + limit_order.client_order_id, + base_currency, + quote_currency, + base_currency_traded, + quote_currency_traded, + OrderType.LIMIT + )) + + def test_limit_buy_order(self): + self.clock.add_iterator(self.limit_buy_strategy) + + # test whether number of orders is one at start + # check whether the order is buy + # check whether the price is correct + # check whether amount is correct + order_time_1 = self.start_timestamp + self.clock_tick_size + self.clock.backtest_til(order_time_1) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + first_bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(1, first_bid_order.quantity) + + # test whether number of orders is two after time delay + # check whether the order is buy + # check whether the price is correct + # check whether amount is correct + order_time_2 = order_time_1 + self.clock_tick_size * math.ceil(self.order_delay_time / self.clock_tick_size) + self.clock.backtest_til(order_time_2) + self.assertEqual(2, len(self.limit_buy_strategy.active_bids)) + second_bid_order: LimitOrder = self.limit_buy_strategy.active_bids[1][1] + self.assertEqual(Decimal("99"), second_bid_order.price) + self.assertEqual(1, second_bid_order.quantity) + + # Check whether order is cancelled after cancel_order_wait_time + cancel_time_1 = order_time_1 + self.cancel_order_wait_time + self.clock.backtest_til(cancel_time_1) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + self.assertEqual(self.limit_buy_strategy.active_bids[0][1], second_bid_order) + + cancel_time_2 = order_time_2 + self.cancel_order_wait_time + self.clock.backtest_til(cancel_time_2) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + self.assertNotEqual(self.limit_buy_strategy.active_bids[0][1], first_bid_order) + self.assertNotEqual(self.limit_buy_strategy.active_bids[0][1], second_bid_order) + + def test_limit_sell_order(self): + self.clock.add_iterator(self.limit_sell_strategy) + # check no orders are placed before time delay + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(0, len(self.limit_sell_strategy.active_asks)) + + # test whether number of orders is one at start + # check whether the order is sell + # check whether the price is correct + # check whether amount is correct + order_time_1 = self.start_timestamp + self.clock_tick_size + self.clock.backtest_til(order_time_1) + self.assertEqual(1, len(self.limit_sell_strategy.active_asks)) + ask_order: LimitOrder = self.limit_sell_strategy.active_asks[0][1] + self.assertEqual(Decimal("101"), ask_order.price) + self.assertEqual(Decimal("1.67000"), ask_order.quantity) + + # test whether number of orders is two after time delay + # check whether the order is sell + # check whether the price is correct + # check whether amount is correct + order_time_2 = order_time_1 + self.clock_tick_size * math.ceil(self.order_delay_time / self.clock_tick_size) + self.clock.backtest_til(order_time_2) + self.assertEqual(2, len(self.limit_sell_strategy.active_asks)) + ask_order: LimitOrder = self.limit_sell_strategy.active_asks[1][1] + self.assertEqual(Decimal("101"), ask_order.price) + self.assertEqual(Decimal("1.67000"), ask_order.quantity) + + # test whether number of orders is three after two time delays + # check whether the order is sell + # check whether the price is correct + # check whether amount is correct + order_time_3 = order_time_2 + self.clock_tick_size * math.ceil(self.order_delay_time / self.clock_tick_size) + self.clock.backtest_til(order_time_3) + self.assertEqual(3, len(self.limit_sell_strategy.active_asks)) + ask_order: LimitOrder = self.limit_sell_strategy.active_asks[2][1] + self.assertEqual(Decimal("101"), ask_order.price) + self.assertEqual(Decimal("1.66000"), ask_order.quantity) + + def test_order_filled_events(self): + self.clock.add_iterator(self.limit_buy_strategy) + self.clock.add_iterator(self.limit_sell_strategy) + # check no orders are placed before time delay + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + + # test whether number of orders is one + # check whether the order is sell + # check whether the price is correct + # check whether amount is correct + self.clock.backtest_til(self.start_timestamp + math.ceil(self.clock_tick_size / self.order_delay_time)) + self.assertEqual(1, len(self.limit_sell_strategy.active_asks)) + ask_order: LimitOrder = self.limit_sell_strategy.active_asks[0][1] + self.assertEqual(Decimal("101"), ask_order.price) + self.assertEqual(Decimal("1.67000"), ask_order.quantity) + + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] + self.assertEqual(Decimal("99"), bid_order.price) + self.assertEqual(1, bid_order.quantity) + + # Simulate market fill for limit buy and limit sell + self.simulate_limit_order_fill(self.market, bid_order) + self.simulate_limit_order_fill(self.market, ask_order) + + fill_events = self.maker_order_fill_logger.event_log + self.assertEqual(2, len(fill_events)) + bid_fills: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.SELL] + ask_fills: List[OrderFilledEvent] = [evt for evt in fill_events if evt.trade_type is TradeType.BUY] + self.assertEqual(1, len(bid_fills)) + self.assertEqual(1, len(ask_fills)) + + def test_with_insufficient_balance(self): + # Set base balance to zero and check if sell strategies don't place orders + self.clock.add_iterator(self.limit_buy_strategy) + self.market.set_balance("WETH", 0) + end_ts = self.start_timestamp + self.clock_tick_size + self.order_delay_time + self.clock.backtest_til(end_ts) + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + market_buy_events: List[BuyOrderCompletedEvent] = [t for t in self.buy_order_completed_logger.event_log + if isinstance(t, BuyOrderCompletedEvent)] + self.assertEqual(0, len(market_buy_events)) + + self.clock.add_iterator(self.limit_sell_strategy) + self.market.set_balance("COINALPHA", 0) + end_ts += self.clock_tick_size + self.order_delay_time + self.clock.backtest_til(end_ts) + self.assertEqual(0, len(self.limit_sell_strategy.active_asks)) + market_sell_events: List[SellOrderCompletedEvent] = [t for t in self.sell_order_completed_logger.event_log + if isinstance(t, SellOrderCompletedEvent)] + self.assertEqual(0, len(market_sell_events)) + + def test_remaining_quantity_updated_after_cancel_order_event(self): + self.limit_buy_strategy.logger().setLevel(1) + self.limit_buy_strategy.logger().addHandler(self) + + self.clock.add_iterator(self.limit_buy_strategy) + # check no orders are placed before time delay + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + + # one order created after first tick + self.clock.backtest_til(self.start_timestamp + math.ceil(self.clock_tick_size / self.order_delay_time)) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] + self.assertEqual(1, bid_order.quantity) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 1) + + # Simulate order cancel + self.market.trigger_event(MarketEvent.OrderCancelled, OrderCancelledEvent( + self.market.current_timestamp, + bid_order.client_order_id)) + + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 2) + + self.assertTrue(self._is_logged('INFO', + f"Updating status after order cancel (id: {bid_order.client_order_id})")) + + def test_remaining_quantity_updated_after_failed_order_event(self): + self.limit_buy_strategy.logger().setLevel(1) + self.limit_buy_strategy.logger().addHandler(self) + + self.clock.add_iterator(self.limit_buy_strategy) + # check no orders are placed before time delay + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + + # one order created after first tick + self.clock.backtest_til(self.start_timestamp + math.ceil(self.clock_tick_size / self.order_delay_time)) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] + self.assertEqual(1, bid_order.quantity) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 1) + + # Simulate order cancel + self.market.trigger_event(MarketEvent.OrderFailure, MarketOrderFailureEvent( + self.market.current_timestamp, + bid_order.client_order_id, + OrderType.LIMIT)) + + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 2) + + self.assertTrue(self._is_logged('INFO', + f"Updating status after order fail (id: {bid_order.client_order_id})")) + + def test_remaining_quantity_updated_after_expired_order_event(self): + self.limit_buy_strategy.logger().setLevel(1) + self.limit_buy_strategy.logger().addHandler(self) + + self.clock.add_iterator(self.limit_buy_strategy) + # check no orders are placed before time delay + self.clock.backtest_til(self.start_timestamp) + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + + # one order created after first tick + self.clock.backtest_til(self.start_timestamp + math.ceil(self.clock_tick_size / self.order_delay_time)) + self.assertEqual(1, len(self.limit_buy_strategy.active_bids)) + bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1] + self.assertEqual(1, bid_order.quantity) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 1) + + # Simulate order cancel + self.market.trigger_event(MarketEvent.OrderExpired, OrderExpiredEvent( + self.market.current_timestamp, + bid_order.client_order_id)) + + self.assertEqual(0, len(self.limit_buy_strategy.active_bids)) + self.assertEqual(self.limit_buy_strategy._quantity_remaining, 2) + + self.assertTrue(self._is_logged('INFO', + f"Updating status after order expire (id: {bid_order.client_order_id})")) + + def test_status_after_first_order_filled(self): + self.clock.add_iterator(self.limit_sell_strategy) + self.clock.backtest_til(self.start_timestamp) + + order_time_1 = self.start_timestamp + self.clock_tick_size + self.clock.backtest_til(order_time_1) + ask_order: LimitOrder = self.limit_sell_strategy.active_asks[0][1] + self.simulate_limit_order_fill(self.market, ask_order) + + order_time_2 = order_time_1 + self.clock_tick_size * math.ceil(self.order_delay_time / self.clock_tick_size) + self.clock.backtest_til(order_time_2) + + base_balance = self.market_info.base_balance + available_base_balance = self.market.get_available_balance(self.market_info.base_asset) + quote_balance = self.market_info.quote_balance + available_quote_balance = self.market.get_available_balance(self.market_info.quote_asset) + + buy_not_started_status = self.limit_buy_strategy.format_status() + expected_buy_status = ("\n Configuration:\n" + " Total amount: 2.00 COINALPHA" + " Order price: 99.00 WETH" + " Order size: 1 COINALPHA\n" + " Execution type: run continuously\n\n" + " Markets:\n" + " Exchange Market Best Bid Price Best Ask Price Mid Price\n" + " 0 mock_paper_exchange COINALPHA-WETH 99.5 100.5 100\n\n" + " Assets:\n" + " Exchange Asset Total Balance Available Balance\n" + " 0 mock_paper_exchange COINALPHA " + f"{base_balance:.2f} " + f"{available_base_balance:.2f}\n" + " 1 mock_paper_exchange WETH " + f"{quote_balance:.2f} " + f"{available_quote_balance:.2f}\n\n" + " No active maker orders.\n\n" + " Average filled orders price: 0 WETH\n" + " Pending amount: 2.00 COINALPHA") + + sell_started_status = self.limit_sell_strategy.format_status() + expected_sell_start = ("\n Configuration:\n" + " Total amount: 5.00 COINALPHA" + " Order price: 101.0 WETH" + " Order size: 1.67 COINALPHA\n" + " Execution type: run continuously\n\n" + " Markets:\n" + " Exchange Market Best Bid Price Best Ask Price Mid Price\n" + " 0 mock_paper_exchange COINALPHA-WETH 99.5 100.5 100\n\n" + " Assets:\n" + " Exchange Asset Total Balance Available Balance\n" + " 0 mock_paper_exchange COINALPHA " + f"{base_balance:.2f} " + f"{available_base_balance:.2f}\n" + " 1 mock_paper_exchange WETH " + f"{quote_balance:.2f} " + f"{available_quote_balance:.2f}\n\n" + " Active orders:\n" + " Order ID Type Price Spread Amount") + expected_sell_end = "n/a\n\n Average filled orders price: 101.0 WETH\n Pending amount: 1.66 COINALPHA" + + self.assertEqual(expected_buy_status, buy_not_started_status) + self.assertTrue(sell_started_status.startswith(expected_sell_start)) + self.assertTrue(sell_started_status.endswith(expected_sell_end)) + + def test_strategy_time_span_execution(self): + span_start_time = self.start_timestamp + (self.clock_tick_size * 5) + span_end_time = self.start_timestamp + (self.clock_tick_size * 7) + strategy = TwapTradeStrategy( + [self.market_info], + order_price=Decimal("99"), + cancel_order_wait_time=self.cancel_order_wait_time, + is_buy=True, + order_delay_time=self.order_delay_time, + target_asset_amount=Decimal("100.0"), + order_step_size=Decimal("1.0"), + execution_state=RunInTimeConditionalExecutionState(start_timestamp=datetime.fromtimestamp(span_start_time), + end_timestamp=datetime.fromtimestamp(span_end_time)) + ) + + self.clock.add_iterator(strategy) + # check no orders are placed before span start + self.clock.backtest_til(span_start_time - self.clock_tick_size) + self.assertEqual(0, len(self.limit_sell_strategy.active_asks)) + + order_time_1 = span_start_time + self.clock_tick_size + self.clock.backtest_til(order_time_1) + self.assertEqual(1, len(strategy.active_bids)) + first_bid_order: LimitOrder = strategy.active_bids[0][1] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(1, first_bid_order.quantity) + + # check no orders are placed after span end + order_time_2 = span_end_time + (self.clock_tick_size * 10) + self.clock.backtest_til(order_time_2) + self.assertEqual(1, len(strategy.active_bids)) + + def test_strategy_delayed_start_execution(self): + delayed_start_time = self.start_timestamp + (self.clock_tick_size * 5) + strategy = TwapTradeStrategy( + [self.market_info], + order_price=Decimal("99"), + cancel_order_wait_time=self.cancel_order_wait_time, + is_buy=True, + order_delay_time=self.order_delay_time, + target_asset_amount=Decimal("100.0"), + order_step_size=Decimal("1.0"), + execution_state=RunInTimeConditionalExecutionState( + start_timestamp=datetime.fromtimestamp(delayed_start_time)) + ) + + self.clock.add_iterator(strategy) + # check no orders are placed before start + self.clock.backtest_til(delayed_start_time - self.clock_tick_size) + self.assertEqual(0, len(self.limit_sell_strategy.active_asks)) + + order_time_1 = delayed_start_time + self.clock_tick_size + self.clock.backtest_til(order_time_1) + self.assertEqual(1, len(strategy.active_bids)) + first_bid_order: LimitOrder = strategy.active_bids[0][1] + self.assertEqual(Decimal("99"), first_bid_order.price) + self.assertEqual(1, first_bid_order.quantity) diff --git a/test/hummingbot/strategy/twap/test_twap_config_map.py b/test/hummingbot/strategy/twap/test_twap_config_map.py new file mode 100644 index 0000000..9321098 --- /dev/null +++ b/test/hummingbot/strategy/twap/test_twap_config_map.py @@ -0,0 +1,89 @@ +import asyncio +from unittest import TestCase +from decimal import Decimal + +import hummingbot.strategy.twap.twap_config_map as twap_config_map_module + + +class TwapConfigMapTests(TestCase): + + def test_string_to_boolean_conversion(self): + true_variants = ["Yes", "YES", "yes", "y", "Y", + "true", "True", "TRUE", "t", "T", + "1"] + for variant in true_variants: + self.assertTrue(twap_config_map_module.str2bool(variant)) + + false_variants = ["No", "NO", "no", "n", "N", + "false", "False", "FALSE", "f", "F", + "0"] + for variant in false_variants: + self.assertFalse(twap_config_map_module.str2bool(variant)) + + def test_trading_pair_prompt(self): + twap_config_map_module.twap_config_map.get("connector").value = "binance" + self.assertEqual(twap_config_map_module.trading_pair_prompt(), + "Enter the token trading pair you would like to trade on binance (e.g. ZRX-ETH) >>> ") + + twap_config_map_module.twap_config_map.get("connector").value = "undefined-exchange" + self.assertEqual(twap_config_map_module.trading_pair_prompt(), + "Enter the token trading pair you would like to trade on undefined-exchange >>> ") + + def test_trading_pair_validation(self): + twap_config_map_module.twap_config_map.get("connector").value = "binance" + self.assertIsNone(twap_config_map_module.validate_market_trading_pair_tuple("BTC-USDT")) + + def test_target_asset_amount_prompt(self): + twap_config_map_module.twap_config_map.get("trading_pair").value = "BTC-USDT" + twap_config_map_module.twap_config_map.get("trade_side").value = "buy" + self.assertEqual(twap_config_map_module.target_asset_amount_prompt(), + "What is the total amount of BTC to be traded? (Default is 1.0) >>> ") + + twap_config_map_module.twap_config_map.get("trade_side").value = "sell" + self.assertEqual(twap_config_map_module.target_asset_amount_prompt(), + "What is the total amount of BTC to be traded? (Default is 1.0) >>> ") + + def test_trade_side_config(self): + config_var = twap_config_map_module.twap_config_map.get("trade_side") + + self.assertTrue(config_var.required) + + prompt_text = asyncio.get_event_loop().run_until_complete(config_var.get_prompt()) + self.assertEqual(prompt_text, "What operation will be executed? (buy/sell) >>> ") + + def test_trade_side_only_accepts_buy_or_sell(self): + config_var = twap_config_map_module.twap_config_map.get("trade_side") + + validate_result = asyncio.get_event_loop().run_until_complete(config_var.validate("invalid value")) + self.assertEqual(validate_result, "Invalid operation type.") + + validate_result = asyncio.get_event_loop().run_until_complete(config_var.validate("buy")) + self.assertIsNone(validate_result) + + validate_result = asyncio.get_event_loop().run_until_complete(config_var.validate("sell")) + self.assertIsNone(validate_result) + + def test_order_delay_default(self): + twap_config_map_module.twap_config_map.get("start_datetime").value = "2021-10-01 00:00:00" + twap_config_map_module.twap_config_map.get("end_datetime").value = "2021-10-02 00:00:00" + twap_config_map_module.twap_config_map.get("target_asset_amount").value = Decimal("1.0") + twap_config_map_module.twap_config_map.get("order_step_size").value = Decimal("1.0") + + twap_config_map_module.set_order_delay_default() + + self.assertEqual(twap_config_map_module.twap_config_map.get("order_delay_time").default, 86400.0) + + def test_order_step_size(self): + # Test order_step_size with a non-decimal value + text = twap_config_map_module.validate_order_step_size("a!") + self.assertEqual(text, "a! is not in decimal format.") + + # Test order_step_size below zero value + negative_value = twap_config_map_module.validate_order_step_size("-1") + self.assertEqual(negative_value, "Value must be more than 0.") + + # Test order_step_size value greater than target_asset_amount + twap_config_map_module.twap_config_map.get("target_asset_amount").value = Decimal("1.0") + validate_order_step_size = twap_config_map_module.validate_order_step_size("1.1") + self.assertEqual(validate_order_step_size, + "Order step size cannot be greater than the total trade amount.") diff --git a/test/hummingbot/strategy/twap/test_twap_start.py b/test/hummingbot/strategy/twap/test_twap_start.py new file mode 100644 index 0000000..0906425 --- /dev/null +++ b/test/hummingbot/strategy/twap/test_twap_start.py @@ -0,0 +1,108 @@ +import unittest.mock +from decimal import Decimal + +import hummingbot.strategy.twap.start as twap_start_module +import hummingbot.strategy.twap.twap_config_map as twap_config_map_module + + +class TwapStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + + self.strategy = None + self.markets = {"binance": None} + self.notifications = [] + self.log_errors = [] + + twap_config_map_module.twap_config_map.get("strategy").value = "twap" + twap_config_map_module.twap_config_map.get("connector").value = "binance" + twap_config_map_module.twap_config_map.get("order_step_size").value = Decimal(1) + twap_config_map_module.twap_config_map.get("trade_side").value = "buy" + twap_config_map_module.twap_config_map.get("target_asset_amount").value = Decimal(10) + twap_config_map_module.twap_config_map.get("order_delay_time").value = 10 + twap_config_map_module.twap_config_map.get("trading_pair").value = "ETH-USDT" + twap_config_map_module.twap_config_map.get("order_price").value = Decimal(2500) + twap_config_map_module.twap_config_map.get("cancel_order_wait_time").value = 60 + twap_config_map_module.twap_config_map.get("is_time_span_execution").value = False + twap_config_map_module.twap_config_map.get("is_delayed_start_execution").value = False + + self.raise_exception_for_market_initialization = False + self.raise_exception_for_market_assets_initialization = False + + def _initialize_market_assets(self, market, trading_pairs): + if self.raise_exception_for_market_assets_initialization: + raise ValueError("ValueError for testing") + return [trading_pair.split('-') for trading_pair in trading_pairs] + + def _initialize_markets(self, market_names): + if self.raise_exception_for_market_initialization: + raise Exception("Exception for testing") + + def notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + @unittest.mock.patch('hummingbot.strategy.twap.twap.TwapTradeStrategy.add_markets') + def test_twap_strategy_creation(self, add_markets_mock): + twap_start_module.start(self) + + self.assertTrue(self.strategy._is_buy) + self.assertEqual(self.strategy._target_asset_amount, Decimal(10)) + self.assertEqual(self.strategy._order_step_size, Decimal(1)) + self.assertEqual(self.strategy._order_price, Decimal(2500)) + self.assertEqual(self.strategy._order_delay_time, 10) + self.assertEqual(self.strategy._cancel_order_wait_time, Decimal(60)) + + @unittest.mock.patch('hummingbot.strategy.twap.twap.TwapTradeStrategy.add_markets') + def test_twap_strategy_creation_with_time_span_execution(self, add_markets_mock): + twap_config_map_module.twap_config_map.get("is_time_span_execution").value = True + twap_config_map_module.twap_config_map.get("start_datetime").value = "2021-06-23 10:00:00" + twap_config_map_module.twap_config_map.get("end_datetime").value = "2021-06-23 11:00:00" + twap_config_map_module.twap_config_map.get("order_delay_time").value = 360 + + twap_start_module.start(self) + + self.assertTrue(self.strategy._is_buy) + self.assertEqual(self.strategy._target_asset_amount, Decimal(10)) + self.assertEqual(self.strategy._order_step_size, Decimal(1)) + self.assertEqual(self.strategy._order_price, Decimal(2500)) + self.assertEqual(self.strategy._order_delay_time, 360) + self.assertEqual(self.strategy._cancel_order_wait_time, Decimal(60)) + + @unittest.mock.patch('hummingbot.strategy.twap.twap.TwapTradeStrategy.add_markets') + def test_twap_strategy_creation_with_delayed_start_execution(self, add_markets_mock): + twap_config_map_module.twap_config_map.get("is_delayed_start_execution").value = True + twap_config_map_module.twap_config_map.get("start_datetime").value = "2021-06-23 10:00:00" + + twap_start_module.start(self) + + self.assertTrue(self.strategy._is_buy) + self.assertEqual(self.strategy._target_asset_amount, Decimal(10)) + self.assertEqual(self.strategy._order_step_size, Decimal(1)) + self.assertEqual(self.strategy._order_price, Decimal(2500)) + self.assertEqual(self.strategy._order_delay_time, 10) + self.assertEqual(self.strategy._cancel_order_wait_time, Decimal(60)) + + def test_twap_strategy_creation_when_market_assets_initialization_fails(self): + self.raise_exception_for_market_assets_initialization = True + + twap_start_module.start(self) + + self.assertEqual(len(self.notifications), 1) + self.assertEqual(self.notifications[0], "ValueError for testing") + + def test_twap_strategy_creation_when_something_fails(self): + self.raise_exception_for_market_initialization = True + + twap_start_module.start(self) + + self.assertEqual(len(self.notifications), 1) + self.assertEqual(self.notifications[0], "Exception for testing") + self.assertEqual(len(self.log_errors), 1) + self.assertEqual(self.log_errors[0], "Unknown error during initialization.") diff --git a/test/hummingbot/strategy/twap/test_twap_trade_strategy.py b/test/hummingbot/strategy/twap/test_twap_trade_strategy.py new file mode 100644 index 0000000..3362456 --- /dev/null +++ b/test/hummingbot/strategy/twap/test_twap_trade_strategy.py @@ -0,0 +1,200 @@ +import time +from datetime import datetime +from decimal import Decimal +from test.hummingbot.strategy.twap.twap_test_support import MockExchange +from unittest import TestCase + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.strategy.conditional_execution_state import RunInTimeConditionalExecutionState +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.twap import TwapTradeStrategy + + +class TwapTradeStrategyTest(TestCase): + + level = 0 + log_records = [] + + def setUp(self) -> None: + super().setUp() + self.log_records = [] + + def handle(self, record): + self.log_records.append(record) + + def _is_logged(self, log_level: str, message: str) -> bool: + return any(record.levelname == log_level and record.getMessage() == message + for record in self.log_records) + + def test_creation_without_market_info_fails(self): + with self.assertRaises(ValueError) as ex_context: + TwapTradeStrategy(market_infos=[], + is_buy=True, + target_asset_amount=1, + order_step_size=1, + order_price=1) + + self.assertEqual(str(ex_context.exception), "market_infos must not be empty.") + + def test_start(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=1, + order_step_size=1, + order_price=1) + strategy.logger().setLevel(1) + strategy.logger().addHandler(self) + + start_timestamp = time.time() + strategy.start(Clock(ClockMode.BACKTEST), start_timestamp) + + self.assertTrue(self._is_logged('INFO', 'Waiting for 10.0 to place orders')) + + def test_tick_logs_warning_when_market_not_ready(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + exchange.ready = False + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=1, + order_step_size=1, + order_price=1) + strategy.logger().setLevel(1) + strategy.logger().addHandler(self) + + start_timestamp = time.time() + strategy.start(Clock(ClockMode.BACKTEST), start_timestamp) + strategy.tick(start_timestamp + 1000) + + self.assertTrue(self._is_logged('WARNING', "Markets are not ready. No market making trades are permitted.")) + + def test_tick_logs_warning_when_market_not_connected(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + exchange.ready = True + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=1, + order_step_size=1, + order_price=1) + strategy.logger().setLevel(1) + strategy.logger().addHandler(self) + + start_timestamp = time.time() + strategy.start(Clock(ClockMode.BACKTEST), start_timestamp) + strategy.tick(start_timestamp + 1000) + + self.assertTrue(self._is_logged('WARNING', + ("WARNING: Some markets are not connected or are down at the moment. " + "Market making may be dangerous when markets or networks are unstable."))) + + def test_status(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + exchange.buy_price = Decimal("25100") + exchange.sell_price = Decimal("24900") + exchange.update_account_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + exchange.update_account_available_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=Decimal(100), + order_step_size=Decimal(10), + order_price=Decimal(25000)) + + status = strategy.format_status() + expected_status = ("\n Configuration:\n" + " Total amount: 100 ETH Order price: 25000 USDT Order size: 10.00 ETH\n" + " Execution type: run continuously\n\n" + " Markets:\n" + " Exchange Market Best Bid Price Best Ask Price Mid Price\n" + " 0 MockExchange ETH-USDT 24900 25100 25000\n\n" + " Assets:\n" + " Exchange Asset Total Balance Available Balance\n" + " 0 MockExchange ETH 100000 100000\n" + " 1 MockExchange USDT 10000 10000\n\n" + " No active maker orders.\n\n" + " Average filled orders price: 0 USDT\n" + " Pending amount: 100 ETH\n\n" + "*** WARNINGS ***\n" + " Markets are offline for the ETH-USDT pair. " + "Continued trading with these markets may be dangerous.\n") + + self.assertEqual(expected_status, status) + + def test_status_with_time_span_execution(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + exchange.buy_price = Decimal("25100") + exchange.sell_price = Decimal("24900") + exchange.update_account_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + exchange.update_account_available_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + start_time_string = "2021-06-24 10:00:00" + end_time_string = "2021-06-24 10:30:00" + execution_type = RunInTimeConditionalExecutionState(start_timestamp=datetime.fromisoformat(start_time_string), + end_timestamp=datetime.fromisoformat(end_time_string)) + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=Decimal(100), + order_step_size=Decimal(10), + order_price=Decimal(25000), + execution_state=execution_type) + + status = strategy.format_status() + expected_status = ("\n Configuration:\n" + " Total amount: 100 ETH Order price: 25000 USDT Order size: 10.00 ETH\n" + f" Execution type: run between {start_time_string} and {end_time_string}\n\n" + " Markets:\n" + " Exchange Market Best Bid Price Best Ask Price Mid Price\n" + " 0 MockExchange ETH-USDT 24900 25100 25000\n\n" + " Assets:\n" + " Exchange Asset Total Balance Available Balance\n" + " 0 MockExchange ETH 100000 100000\n" + " 1 MockExchange USDT 10000 10000\n\n" + " No active maker orders.\n\n" + " Average filled orders price: 0 USDT\n" + " Pending amount: 100 ETH\n\n" + "*** WARNINGS ***\n" + " Markets are offline for the ETH-USDT pair. " + "Continued trading with these markets may be dangerous.\n") + + self.assertEqual(expected_status, status) + + def test_status_with_delayed_start_execution(self): + exchange = MockExchange(client_config_map=ClientConfigAdapter(ClientConfigMap())) + exchange.buy_price = Decimal("25100") + exchange.sell_price = Decimal("24900") + exchange.update_account_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + exchange.update_account_available_balance({"ETH": Decimal("100000"), "USDT": Decimal(10000)}) + marketTuple = MarketTradingPairTuple(exchange, "ETH-USDT", "ETH", "USDT") + start_time_string = "2021-06-24 10:00:00" + execution_type = RunInTimeConditionalExecutionState(start_timestamp=datetime.fromisoformat(start_time_string)) + strategy = TwapTradeStrategy(market_infos=[marketTuple], + is_buy=True, + target_asset_amount=Decimal(100), + order_step_size=Decimal(10), + order_price=Decimal(25000), + execution_state=execution_type) + + status = strategy.format_status() + expected_status = ("\n Configuration:\n" + " Total amount: 100 ETH Order price: 25000 USDT Order size: 10.00 ETH\n" + f" Execution type: run from {start_time_string}\n\n" + " Markets:\n" + " Exchange Market Best Bid Price Best Ask Price Mid Price\n" + " 0 MockExchange ETH-USDT 24900 25100 25000\n\n" + " Assets:\n" + " Exchange Asset Total Balance Available Balance\n" + " 0 MockExchange ETH 100000 100000\n" + " 1 MockExchange USDT 10000 10000\n\n" + " No active maker orders.\n\n" + " Average filled orders price: 0 USDT\n" + " Pending amount: 100 ETH\n\n" + "*** WARNINGS ***\n" + " Markets are offline for the ETH-USDT pair. " + "Continued trading with these markets may be dangerous.\n") + + self.assertEqual(expected_status, status) diff --git a/test/hummingbot/strategy/twap/twap_test_support.py b/test/hummingbot/strategy/twap/twap_test_support.py new file mode 100644 index 0000000..2b29046 --- /dev/null +++ b/test/hummingbot/strategy/twap/twap_test_support.py @@ -0,0 +1,110 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Dict, List, Optional + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.connector.in_flight_order_base import InFlightOrderBase +from hummingbot.core.data_type.cancellation_result import CancellationResult +from hummingbot.core.data_type.common import OrderType, TradeType +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + +s_decimal_NaN = Decimal("nan") + + +class MockExchange(ExchangeBase): + + def __init__(self, client_config_map: "ClientConfigAdapter"): + super(MockExchange, self).__init__(client_config_map) + self._buy_price = Decimal(1) + self._sell_price = Decimal(1) + + @property + def buy_price(self) -> Decimal: + return self._buy_price + + @buy_price.setter + def buy_price(self, price: Decimal): + self._buy_price = price + + @property + def sell_price(self) -> Decimal: + return self._sell_price + + @sell_price.setter + def sell_price(self, price: Decimal): + self._sell_price = price + + @property + def status_dict(self) -> Dict[str, bool]: + pass + + @property + def in_flight_orders(self) -> Dict[str, InFlightOrderBase]: + pass + + async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]: + pass + + def stop_tracking_order(self, order_id: str): + pass + + @property + def order_books(self) -> Dict[str, OrderBook]: + pass + + @property + def limit_orders(self) -> List[LimitOrder]: + pass + + def c_stop_tracking_order(self, order_id): + pass + + def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, + **kwargs) -> str: + pass + + def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET, price: Decimal = s_decimal_NaN, + **kwargs) -> str: + pass + + def cancel(self, trading_pair: str, client_order_id: str): + pass + + def get_order_book(self, trading_pair: str) -> OrderBook: + pass + + def get_fee(self, base_currency: str, quote_currency: str, order_type: OrderType, order_side: TradeType, + amount: Decimal, price: Decimal = s_decimal_NaN, is_maker: Optional[bool] = None + ) -> AddedToCostTradeFee: + pass + + _ready = False + + @property + def ready(self): + return self._ready + + @ready.setter + def ready(self, status: bool): + self._ready = status + + def get_price(self, trading_pair: str, is_buy_price: bool) -> Decimal: + return self.buy_price if is_buy_price else self.sell_price + + def update_account_balance(self, asset_balance: Dict[str, Decimal]): + if not self._account_balances: + self._account_balances = {} + + for asset, balance in asset_balance.items(): + self._account_balances[asset] = self._account_balances.get(asset, Decimal(0)) + balance + + def update_account_available_balance(self, asset_balance: Dict[str, Decimal]): + if not self._account_available_balances: + self._account_available_balances = {} + + for asset, balance in asset_balance.items(): + self._account_available_balances[asset] = self._account_available_balances.get(asset, Decimal(0)) + balance diff --git a/test/hummingbot/strategy/uniswap_v3_lp/__init__.py b/test/hummingbot/strategy/uniswap_v3_lp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp.py b/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp.py new file mode 100644 index 0000000..edeaced --- /dev/null +++ b/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp.py @@ -0,0 +1,219 @@ +import asyncio +import contextlib +import unittest +from decimal import Decimal +from typing import List, Optional +from unittest.mock import patch + +from aiounittest import async_test + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.connector.gateway.amm_lp.gateway_in_flight_lp_order import GatewayInFlightLPOrder +from hummingbot.core.clock import Clock, ClockMode +from hummingbot.core.data_type.in_flight_order import OrderState +from hummingbot.core.data_type.trade_fee import TokenAmount, TradeFeeSchema +from hummingbot.core.event.events import LPType +from hummingbot.core.network_iterator import NetworkStatus +from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.core.utils.tracking_nonce import get_tracking_nonce +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp import UniswapV3LpStrategy + +TRADING_PAIR: str = "HBOT-USDT" +BASE_ASSET: str = TRADING_PAIR.split("-")[0] +QUOTE_ASSET: str = TRADING_PAIR.split("-")[1] + +ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() +s_decimal_0 = Decimal(0) + + +class MockAMMLP(ConnectorBase): + def __init__(self, name): + self._name = name + super().__init__(ClientConfigAdapter(ClientConfigMap())) + self._pool_price = {} + self._in_flight_orders = {} + self._network_transaction_fee = TokenAmount("ETH", s_decimal_0) + + @property + def name(self): + return self._name + + @property + def network_transaction_fee(self) -> TokenAmount: + return self._network_transaction_fee + + @network_transaction_fee.setter + def network_transaction_fee(self, fee: TokenAmount): + self._network_transaction_fee = fee + + @property + def connector_name(self): + return "uniswapLP" + + @staticmethod + def is_approval_order(in_flight_order: GatewayInFlightLPOrder) -> bool: + return False + + @property + def amm_lp_orders(self): + return [ + in_flight_order + for in_flight_order in self._in_flight_orders.values() + if not self.is_approval_order(in_flight_order) + and not in_flight_order.is_pending_cancel_confirmation + ] + + async def get_price(self, trading_pair: str, fee: str) -> Decimal: + return self._pool_price[trading_pair] + + def set_price(self, trading_pair, price): + self._pool_price[trading_pair] = [Decimal(str(price))] + + def set_balance(self, token, balance): + self._account_balances[token] = Decimal(str(balance)) + self._account_available_balances[token] = Decimal(str(balance)) + + def add_liquidity(self, trading_pair: str, amount_0: Decimal, amount_1: Decimal, lower_price: Decimal, upper_price: Decimal, fee: str, **request_args) -> str: + order_id = f"add-{trading_pair}-{get_tracking_nonce()}" + self._in_flight_orders[order_id] = GatewayInFlightLPOrder(client_order_id=order_id, + exchange_order_id="", + trading_pair=trading_pair, + lp_type=LPType.ADD, + lower_price=lower_price, + upper_price=upper_price, + amount_0=amount_0, + amount_1=amount_1, + token_id=1234, + creation_timestamp=self.current_timestamp, + gas_price=Decimal("1")) + self._in_flight_orders[order_id].current_state = OrderState.CREATED + return order_id + + def remove_liquidity(self, trading_pair: str, token_id: int, reduce_percent: Optional[int] = 100, **request_args) -> str: + order_id = f"remove-{trading_pair}-{get_tracking_nonce()}" + self._in_flight_orders[order_id] = GatewayInFlightLPOrder(client_order_id=order_id, + exchange_order_id="", + trading_pair=trading_pair, + lp_type=LPType.REMOVE, + lower_price=s_decimal_0, + upper_price=s_decimal_0, + amount_0=s_decimal_0, + amount_1=s_decimal_0, + token_id=1234, + creation_timestamp=self.current_timestamp, + gas_price=Decimal("1")) + return order_id + + def collect_fees(self, trading_pair: str, token_id: int, **request_args) -> str: + order_id = f"collect-{trading_pair}-{get_tracking_nonce()}" + self._in_flight_orders[order_id] = GatewayInFlightLPOrder(client_order_id=order_id, + exchange_order_id="", + trading_pair=trading_pair, + lp_type=LPType.COLLECT, + lower_price=s_decimal_0, + upper_price=s_decimal_0, + amount_0=s_decimal_0, + amount_1=s_decimal_0, + token_id=1234, + creation_timestamp=self.current_timestamp, + gas_price=Decimal("1")) + return order_id + + def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal: + return Decimal("0.01") + + def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal: + return Decimal("0.01") + + def ready(self): + return True + + async def check_network(self) -> NetworkStatus: + return NetworkStatus.CONNECTED + + async def cancel_outdated_orders(self, _: int) -> List: + return [] + + +class UniswapV3LpUnitTest(unittest.TestCase): + def setUp(self): + self.clock: Clock = Clock(ClockMode.REALTIME) + self.stack: contextlib.ExitStack = contextlib.ExitStack() + self.lp: MockAMMLP = MockAMMLP("onion") + self.lp.set_balance(BASE_ASSET, 500) + self.lp.set_balance(QUOTE_ASSET, 500) + self.market_info = MarketTradingPairTuple(self.lp, TRADING_PAIR, BASE_ASSET, QUOTE_ASSET) + + # Set some default price. + self.lp.set_price(TRADING_PAIR, 1) + + self.strategy = UniswapV3LpStrategy( + self.market_info, + "LOW", + Decimal("0.2"), + Decimal("1"), + Decimal("10"), + ) + self.clock.add_iterator(self.lp) + self.clock.add_iterator(self.strategy) + + self.stack.enter_context(self.clock) + self.stack.enter_context(patch( + "hummingbot.client.config.trade_fee_schema_loader.TradeFeeSchemaLoader.configured_schema_for_exchange", + return_value=TradeFeeSchema() + )) + self.clock_task: asyncio.Task = safe_ensure_future(self.clock.run()) + + def tearDown(self) -> None: + self.stack.close() + self.clock_task.cancel() + try: + ev_loop.run_until_complete(self.clock_task) + except asyncio.CancelledError: + pass + + @async_test(loop=ev_loop) + async def test_propose_position_boundary(self): + lower_price, upper_price = await self.strategy.propose_position_boundary() + self.assertEqual(lower_price, Decimal("0.9")) + self.assertEqual(upper_price, Decimal("1.1")) + + @async_test(loop=ev_loop) + async def test_format_status(self): + self.lp.set_price(TRADING_PAIR, 0) + await asyncio.sleep(2) + expected_status = """ Markets: + Exchange Market Pool Price + onion HBOT-USDT 0E-8 + + No active positions. + + Assets: + Exchange Asset Total Balance Available Balance + 0 onion HBOT 500 500 + 1 onion USDT 500 500""" + current_status = await self.strategy.format_status() + print(current_status) + self.assertTrue(expected_status in current_status) + + @async_test(loop=ev_loop) + async def test_any_active_position(self): + await asyncio.sleep(2) + self.assertTrue(self.strategy.any_active_position(Decimal("1"))) + + @async_test(loop=ev_loop) + async def test_positions_are_created_with_price(self): + await asyncio.sleep(2) + self.assertEqual(len(self.strategy.active_positions), 1) + self.lp.set_price(TRADING_PAIR, 2) + await asyncio.sleep(2) + self.assertEqual(len(self.strategy.active_positions), 2) + self.lp.set_price(TRADING_PAIR, 3) + await asyncio.sleep(2) + self.assertEqual(len(self.strategy.active_positions), 3) + self.lp.set_price(TRADING_PAIR, 2) # price falls back + await asyncio.sleep(2) + self.assertEqual(len(self.strategy.active_positions), 3) # no new position created when there's an active position diff --git a/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp_start.py b/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp_start.py new file mode 100644 index 0000000..2f4c77e --- /dev/null +++ b/test/hummingbot/strategy/uniswap_v3_lp/test_uniswap_v3_lp_start.py @@ -0,0 +1,46 @@ +import unittest.mock +from decimal import Decimal +from test.hummingbot.strategy import assign_config_default + +import hummingbot.strategy.uniswap_v3_lp.start as uniswap_v3_lp_start +from hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp import UniswapV3LpStrategy +from hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp_config_map import uniswap_v3_lp_config_map + + +class UniswapV3LpStartTest(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + self.strategy: UniswapV3LpStrategy = None + self.markets = {"uniswapLP": None} + self.notifications = [] + self.log_errors = [] + assign_config_default(uniswap_v3_lp_config_map) + uniswap_v3_lp_config_map.get("strategy").value = "uniswap_v3_lp" + uniswap_v3_lp_config_map.get("connector").value = "uniswapLP" + uniswap_v3_lp_config_map.get("market").value = "ETH-USDT" + uniswap_v3_lp_config_map.get("fee_tier").value = "LOW" + uniswap_v3_lp_config_map.get("price_spread").value = Decimal("1") + uniswap_v3_lp_config_map.get("amount").value = Decimal("1") + uniswap_v3_lp_config_map.get("min_profitability").value = Decimal("10") + + def _initialize_market_assets(self, market, trading_pairs): + pass + + def _initialize_markets(self, market_names): + pass + + def _notify(self, message): + self.notifications.append(message) + + def logger(self): + return self + + def error(self, message, exc_info): + self.log_errors.append(message) + + @unittest.mock.patch('hummingbot.strategy.uniswap_v3_lp.uniswap_v3_lp.UniswapV3LpStrategy.add_markets') + def test_uniswap_v3_lp_strategy_creation(self, mock): + uniswap_v3_lp_start.start(self) + self.assertEqual(self.strategy._amount, Decimal(1)) + self.assertEqual(self.strategy._min_profitability, Decimal("10")) diff --git a/test/hummingbot/strategy/utils/__init__.py b/test/hummingbot/strategy/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/utils/test_ring_buffer.py b/test/hummingbot/strategy/utils/test_ring_buffer.py new file mode 100644 index 0000000..42847ba --- /dev/null +++ b/test/hummingbot/strategy/utils/test_ring_buffer.py @@ -0,0 +1,109 @@ +import unittest +from hummingbot.strategy.__utils__.ring_buffer import RingBuffer +import numpy as np +from decimal import Decimal + + +class RingBufferTest(unittest.TestCase): + BUFFER_LENGTH = 30 + + def setUp(self) -> None: + self.buffer = RingBuffer(self.BUFFER_LENGTH) + + def fill_buffer_with_zeros(self): + for i in range(self.BUFFER_LENGTH): + self.buffer.add_value(0) + + def test_add_value(self): + self.buffer.add_value(1) + self.assertEqual(self.buffer.get_as_numpy_array().size, 1) + + def test_is_full(self): + self.assertFalse(self.buffer.is_full) # Current occupation = 0 + self.buffer.add_value(1) + self.assertFalse(self.buffer.is_full) # Current occupation = 1 + for i in range(self.BUFFER_LENGTH - 2): + self.buffer.add_value(i) + self.assertFalse(self.buffer.is_full) # Current occupation = BUFFER_LENGTH-1 + self.buffer.add_value(1) + self.assertTrue(self.buffer.is_full) # Current occupation = BUFFER_LENGTH + + def test_add_when_full(self): + for i in range(self.BUFFER_LENGTH): + self.buffer.add_value(1) + self.assertTrue(self.buffer.is_full) + # Filled with ones, total sum equals BUFFER_LENGTH + self.assertEqual(np.sum(self.buffer.get_as_numpy_array()), self.BUFFER_LENGTH) + # Add zeros till length/2 check total sum has decreased accordingly + mid_point = self.BUFFER_LENGTH // 2 + for i in range(mid_point): + self.buffer.add_value(0) + self.assertEqual(np.sum(self.buffer.get_as_numpy_array()), self.BUFFER_LENGTH - mid_point) + # Add remaining zeros to complete length, sum should go to zero + for i in range(self.BUFFER_LENGTH - mid_point): + self.buffer.add_value(0) + self.assertEqual(np.sum(self.buffer.get_as_numpy_array()), 0) + + def test_mean(self): + # When not full, mean=nan + self.assertTrue(np.isnan(self.buffer.mean_value)) + for i in range(self.BUFFER_LENGTH // 2): + self.buffer.add_value(1) + # Still not full, mean=nan + self.assertTrue(np.isnan(self.buffer.mean_value)) + for i in range(self.BUFFER_LENGTH - self.BUFFER_LENGTH // 2): + self.buffer.add_value(1) + # Once full, mean != nan + self.assertEqual(self.buffer.mean_value, 1.0) + + def test_mean_with_alternated_samples(self): + for i in range(self.BUFFER_LENGTH * 3): + self.buffer.add_value(2 * ((-1) ** i)) + if self.buffer.is_full: + self.assertEqual(self.buffer.mean_value, 0) + + def test_std_dev_and_variance(self): + # When not full, stddev=var=nan + self.assertTrue(np.isnan(self.buffer.std_dev)) + self.assertTrue(np.isnan(self.buffer.variance)) + for i in range(self.BUFFER_LENGTH // 2): + self.buffer.add_value(1) + # Still not full, stddev=var=nan + self.assertTrue(np.isnan(self.buffer.std_dev)) + self.assertTrue(np.isnan(self.buffer.variance)) + for i in range(self.BUFFER_LENGTH - self.BUFFER_LENGTH // 2): + self.buffer.add_value(1) + # Once full, std_dev = variance = 0 in this case + self.assertEqual(self.buffer.std_dev, 0) + self.assertEqual(self.buffer.variance, 0) + + def test_std_dev_and_variance_with_alternated_samples(self): + for i in range(self.BUFFER_LENGTH * 3): + self.buffer.add_value(2 * ((-1)**i)) + if self.buffer.is_full: + self.assertEqual(self.buffer.std_dev, 2) + self.assertEqual(self.buffer.variance, 4) + + def test_get_last_value(self): + self.assertTrue(np.isnan(self.buffer.get_last_value())) + expected_values = [-2, -1.0, 0, 3, 1e10] + for value in expected_values: + self.buffer.add_value(value) + self.assertEqual(self.buffer.get_last_value(), value) + + # Decimals are casted when added to numpy array as np.float64. No exact match + value = Decimal(3.141592653) + self.buffer.add_value(value) + self.assertAlmostEqual(float(value), self.buffer.get_last_value(), 6) + + def test_numpy_array(self): + buffer = RingBuffer(4) + + for i in range(3): + buffer.add_value(i) + + self.assertTrue(np.array_equal(buffer.get_as_numpy_array(), np.array([0, 1, 2]))) + buffer.add_value(3) + self.assertTrue(np.array_equal(buffer.get_as_numpy_array(), np.array([0, 1, 2, 3]))) + buffer.add_value(4) + self.assertTrue(np.array_equal(buffer.get_as_numpy_array(), np.array([1, 2, 3, 4]))) diff --git a/test/hummingbot/strategy/utils/test_utils.py b/test/hummingbot/strategy/utils/test_utils.py new file mode 100644 index 0000000..97c3739 --- /dev/null +++ b/test/hummingbot/strategy/utils/test_utils.py @@ -0,0 +1,25 @@ +from decimal import Decimal +from unittest import TestCase +from unittest.mock import patch + +from hummingbot.core.data_type.limit_order import LimitOrder +from hummingbot.strategy.utils import order_age + + +class StrategyUtilsTests(TestCase): + + @patch("hummingbot.strategy.utils._time") + def test_order_age(self, time_mock): + time_mock.return_value = 1640001112.223 + order = LimitOrder( + client_order_id="OID1", + trading_pair="COINALPHA-HBOT", + is_buy=True, + base_currency="COINALPHA", + quote_currency="HBOT", + price=Decimal(1000), + quantity=Decimal(1), + creation_timestamp=1640001110000000) + + age = order_age(order) + self.assertEqual(int(time_mock.return_value - 1640001110), age) diff --git a/test/hummingbot/strategy/utils/trailing_indicators/__init__.py b/test/hummingbot/strategy/utils/trailing_indicators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/hummingbot/strategy/utils/trailing_indicators/test_historical_volatility.py b/test/hummingbot/strategy/utils/trailing_indicators/test_historical_volatility.py new file mode 100644 index 0000000..4e7d81d --- /dev/null +++ b/test/hummingbot/strategy/utils/trailing_indicators/test_historical_volatility.py @@ -0,0 +1,68 @@ +import unittest +import numpy as np +from hummingbot.strategy.__utils__.trailing_indicators.historical_volatility import HistoricalVolatilityIndicator + + +class HistoricalVolatilityTest(unittest.TestCase): + INITIAL_RANDOM_SEED = 123456789 + BUFFER_LENGTH = 1000 + + def setUp(self) -> None: + np.random.seed(self.INITIAL_RANDOM_SEED) + + def test_calculate_volatility_without_smoothing(self): + original_price = 100 + volatility = 0.1 + returns = np.random.normal(0, volatility, self.BUFFER_LENGTH - 1) + samples = [original_price] + for r in returns: + samples.append(samples[-1] * np.exp(r)) + self.indicator = HistoricalVolatilityIndicator(self.BUFFER_LENGTH, 1) + + for sample in samples: + self.indicator.add_sample(sample) + + self.assertAlmostEqual(self.indicator.current_value * original_price, volatility * original_price, 0) + + def test_calculate_volatility_with_smoothing(self): + original_price = 100 + volatility = 0.1 + returns = np.random.normal(0, volatility, self.BUFFER_LENGTH - 1) + samples = [original_price] + for r in returns: + samples.append(samples[-1] * np.exp(r)) + self.indicator = HistoricalVolatilityIndicator(self.BUFFER_LENGTH, 20) + + for sample in samples: + self.indicator.add_sample(sample) + + self.assertAlmostEqual(self.indicator.current_value * original_price, volatility * original_price, 0) + + def test_compare_volatility_with_smoothing(self): + original_price = 100 + volatility = 0.1 + returns = np.random.normal(0, volatility, self.BUFFER_LENGTH - 1) + samples = [original_price] + for r in returns: + samples.append(samples[-1] * np.exp(r)) + self.indicator_normal = HistoricalVolatilityIndicator(self.BUFFER_LENGTH, 1) + # Using 20 samples of smoothing + self.indicator_smoothed = HistoricalVolatilityIndicator(self.BUFFER_LENGTH, 20) + + output_normal = [] + output_smoothed = [] + for sample in samples: + self.indicator_normal.add_sample(sample) + self.indicator_smoothed.add_sample(sample) + if self.indicator_normal.is_processing_buffer_full and self.indicator_smoothed.is_processing_buffer_full: + output_normal.append(self.indicator_normal.current_value) + output_smoothed.append(self.indicator_smoothed.current_value) + + # Now we want to assert output from smoothed indicator is more "smoothed" than normal one. + # How do we do this? By measuring the energy of the first derivative of each output. + # Energy(diff) = Sum(diff(output)**2) + + energy_normal = sum(x ** 2 for x in np.diff(output_normal)) + energy_smoothed = sum(x ** 2 for x in np.diff(output_smoothed)) + + self.assertGreater(energy_normal, energy_smoothed) diff --git a/test/hummingbot/strategy/utils/trailing_indicators/test_instant_volatility.py b/test/hummingbot/strategy/utils/trailing_indicators/test_instant_volatility.py new file mode 100644 index 0000000..d63ab22 --- /dev/null +++ b/test/hummingbot/strategy/utils/trailing_indicators/test_instant_volatility.py @@ -0,0 +1,22 @@ +import unittest +import numpy as np +from hummingbot.strategy.__utils__.trailing_indicators.instant_volatility import InstantVolatilityIndicator + + +class InstantVolatilityTest(unittest.TestCase): + INITIAL_RANDOM_SEED = 3141592653 + BUFFER_LENGTH = 10000 + + def setUp(self) -> None: + np.random.seed(self.INITIAL_RANDOM_SEED) + + def test_calculate_volatility(self): + original_price = 100 + volatility = 0.1 + samples = np.random.normal(original_price, volatility * original_price, self.BUFFER_LENGTH) + self.indicator = InstantVolatilityIndicator(self.BUFFER_LENGTH, 1) + + for sample in samples: + self.indicator.add_sample(sample) + + self.assertAlmostEqual(self.indicator.current_value, 14.068197250366211, 4) diff --git a/test/hummingbot/strategy/utils/trailing_indicators/test_trading_intensity.py b/test/hummingbot/strategy/utils/trailing_indicators/test_trading_intensity.py new file mode 100644 index 0000000..10b244c --- /dev/null +++ b/test/hummingbot/strategy/utils/trailing_indicators/test_trading_intensity.py @@ -0,0 +1,261 @@ +import math +import unittest +from decimal import Decimal + +import numpy as np +import pandas as pd + +from hummingbot.client.config.client_config_map import ClientConfigMap +from hummingbot.client.config.config_helpers import ClientConfigAdapter +from hummingbot.connector.exchange.paper_trade.paper_trade_exchange import QuantizationParams +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.data_type.common import TradeType +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.trade_fee import TradeFeeSchema +from hummingbot.core.event.events import OrderBookTradeEvent +from hummingbot.strategy.__utils__.trailing_indicators.trading_intensity import TradingIntensityIndicator +from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple +from hummingbot.strategy.order_book_asset_price_delegate import OrderBookAssetPriceDelegate + + +class TradingIntensityTest(unittest.TestCase): + INITIAL_RANDOM_SEED = 3141592653 + BUFFER_LENGTH = 50 + + start: pd.Timestamp = pd.Timestamp("2019-01-01", tz="UTC") + start_timestamp: float = start.timestamp() + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.trading_pair: str = "COINALPHA-HBOT" + cls.base_asset, cls.quote_asset = cls.trading_pair.split("-") + cls.initial_mid_price: int = 100 + + def setUp(self) -> None: + np.random.seed(self.INITIAL_RANDOM_SEED) + + trade_fee_schema = TradeFeeSchema( + maker_percent_fee_decimal=Decimal("0.25"), taker_percent_fee_decimal=Decimal("0.25") + ) + client_config_map = ClientConfigAdapter(ClientConfigMap()) + self.market: MockPaperExchange = MockPaperExchange(client_config_map, trade_fee_schema) + self.market_info: MarketTradingPairTuple = MarketTradingPairTuple( + self.market, self.trading_pair, *self.trading_pair.split("-") + ) + self.market.set_balanced_order_book(trading_pair=self.trading_pair, + mid_price=self.initial_mid_price, + min_price=1, + max_price=200, + price_step_size=1, + volume_step_size=10) + self.market.set_balance("COINALPHA", 1) + self.market.set_balance("HBOT", 500) + self.market.set_quantization_param( + QuantizationParams( + self.trading_pair.split("-")[0], 6, 6, 6, 6 + ) + ) + + self.price_delegate = OrderBookAssetPriceDelegate(self.market_info.market, self.trading_pair) + + self.indicator = TradingIntensityIndicator( + order_book=self.market_info.order_book, + price_delegate=self.price_delegate, + sampling_length=self.BUFFER_LENGTH) + + @staticmethod + def make_order_books(original_price_mid, original_spread, original_amount, volatility, spread_stdev, amount_stdev, samples): + # 0.1% quantization of prices in the orderbook + PRICE_STEP_FRACTION = 0.01 + + # Generate BBO quotes + samples_mid = np.random.normal(original_price_mid, volatility * original_price_mid, samples) + samples_spread = np.random.normal(original_spread, spread_stdev, samples) + + samples_price_bid = np.subtract(samples_mid, np.divide(samples_spread, 2)) + samples_price_ask = np.add(samples_mid, np.divide(samples_spread, 2)) + + samples_amount_bid = np.random.normal(original_amount, amount_stdev, samples) + samples_amount_ask = np.random.normal(original_amount, amount_stdev, samples) + + # A full orderbook is not necessary, only up to the BBO max deviation + price_depth_max = max(max(samples_price_bid) - min(samples_price_bid), max(samples_price_ask) - min(samples_price_ask)) + + bid_dfs = [] + ask_dfs = [] + + # Generate an orderbook for every tick + for price_bid, amount_bid, price_ask, amount_ask in zip(samples_price_bid, samples_amount_bid, samples_price_ask, samples_amount_ask): + bid_df, ask_df = TradingIntensityTest.make_order_book(price_bid, amount_bid, price_ask, amount_ask, price_depth_max, original_price_mid * PRICE_STEP_FRACTION, amount_stdev) + bid_dfs += [bid_df] + ask_dfs += [ask_df] + + return bid_dfs, ask_dfs + + @staticmethod + def make_order_book(price_bid, amount_bid, price_ask, amount_ask, price_depth, price_step, amount_stdev, ): + + prices_bid = np.linspace(price_bid, price_bid - price_depth, math.ceil(price_depth / price_step)) + amounts_bid = np.random.normal(amount_bid, amount_stdev, len(prices_bid)) + amounts_bid[0] = amount_bid + + prices_ask = np.linspace(price_ask, price_ask + price_depth, math.ceil(price_depth / price_step)) + amounts_ask = np.random.normal(amount_ask, amount_stdev, len(prices_ask)) + amounts_ask[0] = amount_ask + + data_bid = {'price': prices_bid, 'amount': amounts_bid} + bid_df = pd.DataFrame(data=data_bid) + + data_ask = {'price': prices_ask, 'amount': amounts_ask} + ask_df = pd.DataFrame(data=data_ask) + + return bid_df, ask_df + + @staticmethod + def make_trades(bids_df, asks_df): + # Estimate market orders that happened + # Assume every movement in the BBO is caused by a market order and its size is the volume differential + + bid_df_prev = None + ask_df_prev = None + bid_prev = None + ask_prev = None + price_prev = None + + trades = [] + + start = pd.Timestamp("2019-01-01", tz="UTC") + start_timestamp = start.timestamp() + timestamp = start_timestamp + + for bid_df, ask_df in zip(bids_df, asks_df): + + trades += [[]] + + bid = bid_df["price"].iloc[0] + ask = ask_df["price"].iloc[0] + + if bid_prev is not None and ask_prev is not None and price_prev is not None: + # Higher bids were filled - someone matched them - a determined seller + # Equal bids - if amount lower - partially filled + for index, row in bid_df_prev[bid_df_prev['price'] >= bid].iterrows(): + if row['price'] == bid: + if bid_df["amount"].iloc[0] < row['amount']: + amount = row['amount'] - bid_df["amount"].iloc[0] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.SELL + ) + trades[-1] += [new_trade] + else: + amount = row['amount'] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.SELL + ) + trades[-1] += [new_trade] + + # Lower asks were filled - someone matched them - a determined buyer + # Equal asks - if amount lower - partially filled + for index, row in ask_df_prev[ask_df_prev['price'] <= ask].iterrows(): + if row['price'] == ask: + if ask_df["amount"].iloc[0] < row['amount']: + amount = row['amount'] - ask_df["amount"].iloc[0] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.BUY + ) + trades[-1] += [new_trade] + else: + amount = row['amount'] + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=row['price'], + amount=amount, + type=TradeType.BUY + ) + trades[-1] += [new_trade] + + # Store previous values + bid_df_prev = bid_df + ask_df_prev = ask_df + bid_prev = bid_df["price"].iloc[0] + ask_prev = ask_df["price"].iloc[0] + price_prev = (bid_prev + ask_prev) / 2 + + timestamp += 1 + + return trades + + def test_calculate_trading_intensity_random(self): + N_SAMPLES = 300 + + original_price_mid = 100 + original_spread = Decimal("10") + volatility = Decimal("5") / Decimal("100") + original_amount = Decimal("1") + + spread_stdev = original_spread * Decimal("0.01") + amount_stdev = original_amount * Decimal("0.01") + + # Generate orderbooks for all ticks + bids_df, asks_df = TradingIntensityTest.make_order_books(original_price_mid, original_spread, original_amount, volatility, spread_stdev, amount_stdev, N_SAMPLES) + trades = TradingIntensityTest.make_trades(bids_df, asks_df) + + timestamp = self.start_timestamp + for bid_df, ask_df, trades_tick in zip(bids_df, asks_df, trades): + bid = bid_df["price"].iloc[0] + ask = ask_df["price"].iloc[0] + mid = (bid + ask) / 2 + for trade in trades_tick: + self.indicator.register_trade(trade) + self.indicator.calculate(timestamp) + self.indicator.last_quotes = [{"timestamp": timestamp, "price": mid}] + self.indicator.last_quotes + timestamp += 1 + + self.assertAlmostEqual(self.indicator.current_value[0], 1.0032422566402444, 4) + self.assertAlmostEqual(self.indicator.current_value[1], 0.0001595577045670909, 4) + + def test_calculate_trading_intensity_deterministic(self): + def curve_fn(t_, a_, b_): # see curve fit in `TradingIntensityIndicator.c_estimate_intensity` + return a_ * np.exp(-b_ * t_) + + last_price = 1 + trade_price_levels = [2, 3, 4, 5] + a = 2 + b = 0.1 + ts = [curve_fn(p - last_price, a, b) for p in trade_price_levels] + + timestamp = self.start_timestamp + + trading_intensity_indicator = TradingIntensityIndicator(OrderBook(), self.price_delegate, 1) + trading_intensity_indicator.last_quotes = [{"timestamp": timestamp, "price": last_price}] + + timestamp += 1 + + for p, t in zip(trade_price_levels, ts): + new_trade = OrderBookTradeEvent( + trading_pair="COINALPHAHBOT", + timestamp=timestamp, + price=p, + amount=t, + type=TradeType.SELL, + ) + trading_intensity_indicator.register_trade(new_trade) + + trading_intensity_indicator.calculate(timestamp) + alpha, kappa = trading_intensity_indicator.current_value + + self.assertAlmostEqual(a, alpha, 10) + self.assertAlmostEqual(b, kappa, 10) diff --git a/test/hummingbot/test_hummingbot_application.py b/test/hummingbot/test_hummingbot_application.py new file mode 100644 index 0000000..5ac836e --- /dev/null +++ b/test/hummingbot/test_hummingbot_application.py @@ -0,0 +1,35 @@ +import unittest +from unittest.mock import MagicMock, patch + +from hummingbot.client.hummingbot_application import HummingbotApplication + + +class HummingbotApplicationTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.app = HummingbotApplication() + + @patch("hummingbot.model.sql_connection_manager.SQLConnectionManager.get_trade_fills_instance") + def test_set_strategy_file_name(self, mock: MagicMock): + strategy_name = "some-strategy" + file_name = f"{strategy_name}.yml" + self.app.strategy_file_name = file_name + + self.assertEqual(file_name, self.app.strategy_file_name) + mock.assert_called_with(self.app.client_config_map, strategy_name) + + @patch("hummingbot.model.sql_connection_manager.SQLConnectionManager.get_trade_fills_instance") + def test_set_strategy_file_name_to_none(self, mock: MagicMock): + strategy_name = "some-strategy" + file_name = f"{strategy_name}.yml" + + self.app.strategy_file_name = None + + self.assertEqual(None, self.app.strategy_file_name) + mock.assert_not_called() + + self.app.strategy_file_name = file_name + self.app.strategy_file_name = None + + self.assertEqual(None, self.app.strategy_file_name) + self.assertEqual(1, mock.call_count) diff --git a/test/isolated_asyncio_wrapper_test_case.py b/test/isolated_asyncio_wrapper_test_case.py new file mode 100644 index 0000000..702d019 --- /dev/null +++ b/test/isolated_asyncio_wrapper_test_case.py @@ -0,0 +1,280 @@ +import asyncio +import functools +import unittest +from asyncio import Task +from collections.abc import Set +from typing import Any, Awaitable, Callable, Coroutine, List, Optional, TypeVar + +T = TypeVar("T") + + +def async_to_sync(func: Callable[..., Coroutine[Any, Any, T]]) -> Callable[..., T]: + """ + Decorator to convert an async function into a function that can be called in sync context. + + If there's an existing running loop, it uses `run_until_complete()` to execute the coroutine. + Otherwise, it uses `asyncio.run()`. + + :param func: The async function to be converted. + :type func: Callable[..., Any] + :return: The wrapped synchronous function. + :rtype: Callable[..., Any] + + Usage: + + .. code-block:: python + + from my_decorators import async_to_sync_in_loop + + class MyClass: + @async_to_sync_in_loop + async def async_method(self) -> str: + await asyncio.sleep(1) + return "Hello, World!" + + my_instance = MyClass() + result = my_instance.async_method() + print(result) # Output: Hello, World! + """ + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> T: + try: + loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + except RuntimeError: + return asyncio.run(func(*args, **kwargs)) + + result: T = loop.run_until_complete(func(*args, **kwargs)) + return result + + return wrapper + + +class IsolatedAsyncioWrapperTestCase(unittest.IsolatedAsyncioTestCase): + """ + Custom test case class that wraps `unittest.IsolatedAsyncioTestCase`. + + This class provides additional functionality to set up and tear down the asyncio event loop for each test case. + It ensures that each test case runs in an isolated asyncio event loop, preventing interference between test cases. + + Example usage: + ```python + class MyTestCase(IsolatedAsyncioWrapperTestCase): + async def test_my_async_function(self): + # Test your async function here + ... + ``` + """ + main_event_loop = None + + @classmethod + def setUpClass(cls) -> None: + # Save the current event loop + try: + # This will trigger a RuntimeError if no event loop is running or no event loop is set. + # Meaning, set_event_loop(None) has been called. + cls.main_event_loop = asyncio.get_event_loop() + except RuntimeError: + # If no event loop exists, create one + cls.main_event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.main_event_loop) + assert cls.main_event_loop is not None + super().setUpClass() + + def setUp(self) -> None: + self.local_event_loop = asyncio.get_event_loop() + assert self.local_event_loop is not self.main_event_loop + super().setUp() + + def tearDown(self) -> None: + super().tearDown() + if self.main_event_loop is not None and not self.main_event_loop.is_closed(): + asyncio.set_event_loop(self.main_event_loop) + assert asyncio.get_event_loop() is self.main_event_loop + else: + asyncio.set_event_loop(asyncio.new_event_loop()) + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + # Ok, asyncio.IsolatedAsyncioTestCase kills the main event loop no matter it's initial state. + # We need to restore it here, otherwise any tests after this one will fail if it relies on the main event loop. + if cls.main_event_loop is not None and not cls.main_event_loop.is_closed(): + asyncio.set_event_loop(cls.main_event_loop) + else: + asyncio.set_event_loop(asyncio.new_event_loop()) + assert asyncio.get_event_loop() is not None + + def run_async_with_timeout(self, coroutine: Awaitable, timeout: float = 1.0) -> Any: + """ + Run the given coroutine with a timeout. + + :param Awaitable coroutine: The coroutine to be executed. + :param float timeout: The timeout value in seconds. + :return: The result of the coroutine. + :rtype: Any + """ + return self.local_event_loop.run_until_complete(asyncio.wait_for(coroutine, timeout=timeout)) + + @staticmethod + async def await_task_completion(tasks_name: Optional[str | List[str]]) -> None: + """ + Await the completion of the given task. + + Warning: This method relies on undocumented method of Task (get_coro()), + as well as internals of Python coroutines (cr_code.co_name). + + :param str tasks_name: The task name (or names) to be awaited. + :return: The result of the task. + """ + + def get_coro_func_name(task): + coro = task.get_coro() + return coro.cr_code.co_name + + if tasks_name is None: + return + if isinstance(tasks_name, str): + tasks_name = [tasks_name] + tasks: Set[Task] = asyncio.all_tasks() + tasks = {task for task in tasks for task_name in tasks_name if task_name == get_coro_func_name(task)} + + if tasks: + await asyncio.wait(tasks) + + +class LocalClassEventLoopWrapperTestCase(unittest.TestCase): + """ + Custom test case class that wraps `unittest.TestCase`. + + This class provides additional functionality to manage the main event loop and a local event loop for tests. + It ensures that each test case runs in a local asyncio event loop, preventing interference between test suites. + + Example usage: + ```python + class MyTestCase(LocalClassEventLoopWrapperTestCase): + def test_my_async_function(self): + self.local_event_loop.run_until_complete(asyncio.sleep(0.1)) + ... + ``` + + Note: + - It is important to make sure that all async functions in the test case are prefixed with the `async` keyword. + - This class assumes that the tests are defined as methods in a subclass. + + Attributes: + - `main_event_loop`: The reference to the main asyncio event loop. + - `local_event_loop`: The local asyncio event loop used for each test case. + """ + main_event_loop: Optional[asyncio.AbstractEventLoop] = None + local_event_loop: Optional[asyncio.AbstractEventLoop] = None + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + try: + cls.main_event_loop = asyncio.get_event_loop() + except RuntimeError: + cls.main_event_loop = asyncio.new_event_loop() + + cls.local_event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.local_event_loop) + + @classmethod + def tearDownClass(cls) -> None: + if cls.local_event_loop is not None: + tasks: Set[Task] = asyncio.all_tasks(cls.local_event_loop) + for task in tasks: + task.cancel() + cls.local_event_loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + cls.local_event_loop.run_until_complete(cls.local_event_loop.shutdown_asyncgens()) + cls.local_event_loop.close() + cls.local_event_loop = None + + asyncio.set_event_loop(cls.main_event_loop) + cls.main_event_loop = None + super().tearDownClass() + + def run_async_with_timeout(self, coroutine: Awaitable, timeout: float = 1.0) -> Any: + """ + Run the given coroutine with a timeout. + + :param Awaitable coroutine: The coroutine to be executed. + :param float timeout: The timeout value in seconds. + :return: The result of the coroutine. + :rtype: Any + """ + return self.local_event_loop.run_until_complete(asyncio.wait_for(coroutine, timeout=timeout)) + + +class LocalTestEventLoopWrapperTestCase(unittest.TestCase): + """ + Custom test case class that wraps `unittest.TestCase`. + + This class provides additional functionality to manage the main event loop and a local event loop for each test. + It ensures that each test case runs in a local asyncio event loop, preventing interference between test suites. + + Example usage: + ```python + class MyTestCase(LocalTestEventLoopWrapperTestCase): + def test_my_async_function(self): + self.local_event_loop.run_until_complete(asyncio.sleep(0.1)) + ... + ``` + + Note: + - It is important to make sure that all async functions in the test case are prefixed with the `async` keyword. + - This class assumes that the tests are defined as methods in a subclass. + + Attributes: + - `main_event_loop`: The reference to the main asyncio event loop. + - `local_event_loop`: The local asyncio event loop used for each test case. + """ + main_event_loop: Optional[asyncio.AbstractEventLoop] = None + local_event_loop: Optional[asyncio.AbstractEventLoop] = None + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + try: + cls.main_event_loop = asyncio.get_event_loop() + except RuntimeError: + cls.main_event_loop = None + + def setUp(self) -> None: + super().setUp() + self.local_event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.local_event_loop) + self.assertEqual(asyncio.get_event_loop(), self.local_event_loop) + + def tearDown(self) -> None: + if self.local_event_loop is not None: + tasks: Set[Task] = asyncio.all_tasks(self.local_event_loop) + for task in tasks: + task.cancel() + self.local_event_loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + self.local_event_loop.run_until_complete(self.local_event_loop.shutdown_asyncgens()) + self.local_event_loop.close() + self.local_event_loop = None + super().tearDown() + + @classmethod + def tearDownClass(cls) -> None: + if cls.main_event_loop is not None and not cls.main_event_loop.is_closed(): + asyncio.set_event_loop(cls.main_event_loop) + else: + asyncio.set_event_loop(asyncio.new_event_loop()) + + cls.main_event_loop = None + super().tearDownClass() + + def run_async_with_timeout(self, coroutine: Awaitable, timeout: float = 1.0) -> Any: + """ + Run the given coroutine with a timeout. + + :param Awaitable coroutine: The coroutine to be executed. + :param float timeout: The timeout value in seconds. + :return: The result of the coroutine. + :rtype: Any + """ + return self.local_event_loop.run_until_complete(asyncio.wait_for(coroutine, timeout=timeout)) diff --git a/test/logger_mixin_for_test.py b/test/logger_mixin_for_test.py new file mode 100644 index 0000000..cfaf025 --- /dev/null +++ b/test/logger_mixin_for_test.py @@ -0,0 +1,148 @@ +import asyncio +import logging +from logging import Handler, LogRecord +from types import UnionType +from typing import Callable, List, Protocol + +from async_timeout import timeout + +from hummingbot.logger import HummingbotLogger + + +class LogLevel: + NOTSET = 0 + DEBUG = 10 + INFO = 20 + WARNING = 30 + ERROR = 40 + CRITICAL = 50 + + +_IntOrStr: UnionType = int | str + + +class LoggerMixinProtocol(Protocol): + level: _IntOrStr + log_records: List[LogRecord] + + +class _LoggerProtocol(LoggerMixinProtocol, Protocol): + def setLevel(self, level: _IntOrStr): + ... + + def addHandler(self, handler: Handler): + ... + + +class LoggerMixinForTest(LoggerMixinProtocol): + """ + Test logger mixin class that can be used to capture log records during testing. + + This mixin provides methods to handle log records and check if specific messages at certain levels are logged. + + Example usage: + ```python + class MyTestCase(unittest.TestCase, LoggerMixinForTest): + def test_something(self): + self.logger.info("Testing...") + self.assertTrue(self.is_logged("Testing...", logging.INFO)) + ``` + + Attributes: + - `level`: The default log level for the logger. + """ + level: _IntOrStr = LogLevel.NOTSET + + def _initialize(self: _LoggerProtocol): + """ + Initialize the test logger mixin by setting the default log level and initializing the log records list. + """ + self.level: _IntOrStr = 1 + self.log_records: List[LogRecord] = [] + + @staticmethod + def _to_loglevel(log_level: _IntOrStr) -> str: + """ + Convert a log level to a string. + :params int | str log_level: The log level to convert. + """ + if isinstance(log_level, int): + log_level = logging.getLevelName(log_level) + return log_level + + def set_loggers(self, loggers: List[HummingbotLogger] | HummingbotLogger): + """ + Set up the test logger mixin by adding the test logger to the provided loggers list. + :params List[HummingbotLogger] | HummingbotLogger loggers: The loggers to add to the LoggerMixinForTest. + """ + # __init__() may not be called if the class is used as a mixin + if not hasattr(self, "log_records"): + self._initialize() + + if isinstance(loggers, HummingbotLogger): + loggers = [loggers] + + for logger in loggers: + if logger is not None: + logger.setLevel(self.level) + logger.addHandler(self) + + def handle(self, record: LogRecord): + """ + Handle a log record by appending it to the log records list. + :params LogRecord record: The log record to handle. + """ + self.log_records.append(record) + + def is_logged(self, log_level: _IntOrStr, message: str) -> bool: + """ + Check if a certain message has been logged at a certain level. + :params int | str log_level: The log level to check. + :params str message: The message to check. + """ + log_level = self._to_loglevel(log_level) + return any( + record.getMessage() == message and record.levelname == log_level + for record in self.log_records + ) + + def is_partially_logged(self, log_level: _IntOrStr, message: str) -> bool: + """ + Check if a certain message has been 'partially' logged at a certain level. + This is useful for checking if a message has been logged with a dynamic value. + :params int | str log_level: The log level to check. + :params str message: The message to check. + """ + log_level = self._to_loglevel(log_level) + return any( + message in record.getMessage() and record.levelname == log_level + for record in self.log_records + ) + + async def wait_for_logged(self, + log_level: _IntOrStr, + message: str, + partial: bool = False, + wait_s: float = 3) -> None: + """ + Wait for a certain message to be logged at a certain level. + :params int | str log_level: The log level to check. + :params str message: The message to check. + :params bool partial: Whether to check if the message is partially logged. + :params float wait_s: The number of seconds to wait before timing out. + """ + log_level = self._to_loglevel(log_level) + log_method: Callable[[str | int, str], bool] = self.is_partially_logged if partial else self.is_logged + try: + async with timeout(wait_s): + while not log_method(log_level, message): + await asyncio.sleep(0.1) + except asyncio.TimeoutError as e: + # Used within a class derived from unittest.TestCase + if callable(getattr(self, "fail", None)): + getattr(self, "fail")(f"Message: {message} was not logged.\n" + f"Received Logs: {[record.getMessage() for record in self.log_records]}") + else: + print(f"Message: {message} was not logged.") + print(f"Received Logs: {[record.getMessage() for record in self.log_records]}") + raise e diff --git a/test/mock/__init__.py b/test/mock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/mock/http_recorder.py b/test/mock/http_recorder.py new file mode 100644 index 0000000..60860af --- /dev/null +++ b/test/mock/http_recorder.py @@ -0,0 +1,279 @@ +import time +from contextlib import contextmanager +from enum import Enum +from typing import Any, Callable, Dict, Generator, Optional, Type, cast +from weakref import ReferenceType, ref + +from aiohttp import ClientResponse, ClientSession +from sqlalchemy import JSON, BigInteger, Column, Enum as SQLEnum, Integer, Text, and_, create_engine +from sqlalchemy.engine.base import Engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import Query, Session, sessionmaker + +from hummingbot.model.transaction_base import TransactionBase + +Base = declarative_base() + + +class HttpRequestMethod(Enum): + POST = 1 + GET = 2 + PUT = 3 + PATCH = 4 + DELETE = 5 + + +class HttpRequestType(Enum): + PLAIN = 1 + WITH_PARAMS = 2 + WITH_JSON = 3 + + +class HttpResponseType(Enum): + HEADER_ONLY = 1 + WITH_TEXT = 2 + WITH_JSON = 3 + + +class HttpPlayback(Base): + __tablename__ = "HttpPlayback" + + id = Column(Integer, primary_key=True, autoincrement=True) + timestamp = Column(BigInteger, nullable=False) + url = Column(Text, index=True, nullable=False) + method = Column(SQLEnum(HttpRequestMethod), nullable=False) + request_type = Column(SQLEnum(HttpRequestType), nullable=False) + request_params = Column(JSON) + request_json = Column(JSON) + response_type = Column(SQLEnum(HttpResponseType), nullable=False) + response_code = Column(Integer, nullable=False) + response_text = Column(Text) + response_json = Column(JSON) + + +class HttpRecorderClientResponse(ClientResponse): + _database_id: Optional[int] + _parent_recorder_ref: Optional[ReferenceType] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._database_id = None + self._parent_recorder_ref = None + + @property + def database_id(self) -> Optional[int]: + return self._database_id + + @database_id.setter + def database_id(self, value: int): + self._database_id = value + + @property + def parent_recorder(self) -> Optional["HttpRecorder"]: + if self._parent_recorder_ref is not None: + return self._parent_recorder_ref() + return None + + @parent_recorder.setter + def parent_recorder(self, value: "HttpRecorder"): + self._parent_recorder_ref = ref(value) + + def get_playback_entry(self, session: Session) -> HttpPlayback: + return session.query(HttpPlayback).filter(HttpPlayback.id == self.database_id).one() + + async def text(self, *args, **kwargs) -> str: + response_text: str = await super().text(*args, **kwargs) + with self.parent_recorder.begin() as session: + session: Session = session + playback_entry: HttpPlayback = self.get_playback_entry(session) + playback_entry.response_text = HttpResponseType.WITH_TEXT + playback_entry.response_text = response_text + return response_text + + async def json(self, *args, **kwargs) -> Any: + response_obj: Any = await super().json(*args, **kwargs) + with self.parent_recorder.begin() as session: + session: Session = session + playback_entry: HttpPlayback = self.get_playback_entry(session) + playback_entry.response_type = HttpResponseType.WITH_JSON + playback_entry.response_json = response_obj + return response_obj + + +class HttpPlayerBase(TransactionBase): + def __init__(self, db_path: str): + self._db_path: str = db_path + self._db_engine: Engine = create_engine(f"sqlite:///{db_path}") + self._session_factory: Callable[[], Session] = sessionmaker(bind=self._db_engine) + Base.metadata.create_all(self._db_engine) + + def get_new_session(self) -> Session: + return self._session_factory() + + @contextmanager + def patch_aiohttp_client(self) -> Generator[Type[ClientSession], None, None]: + try: + ClientSession._original_request_func = ClientSession._request + ClientSession._request = lambda s, *args, **kwargs: self.aiohttp_request_method(s, *args, **kwargs) + yield ClientSession + finally: + ClientSession._request = ClientSession._original_request_func + del ClientSession._original_request_func + + +class HttpRecorder(HttpPlayerBase): + """ + Records HTTP conversations made over any aiohttp.ClientSession object, and records them to an SQLite database file + for replaying. + + Usage: + recorder = HttpRecorder('test.db') + with recorder.patch_aiohttp_client: + # all aiohttp conversations inside this block will be recorded to test.db + async with aiohttp.ClientSession() as client: + async with client.get("https://api.binance.com/api/v3/time") as resp: + data = await resp.json() # the request and response are recorded to test.db + ... + """ + async def aiohttp_request_method( + self, + client: ClientSession, + method: str, + url: str, + **kwargs) -> HttpRecorderClientResponse: + try: + if hasattr(client, "_reentrant_ref_count"): + client._reentrant_ref_count += 1 + else: + client._reentrant_ref_count = 1 + client._original_response_class = client._response_class + client._response_class = HttpRecorderClientResponse + request_type: HttpRequestType = HttpRequestType.PLAIN + request_params: Optional[Dict[str, str]] = None + request_json: Optional[Any] = None + if "params" in kwargs: + request_type = HttpRequestType.WITH_PARAMS + request_params = kwargs.get("params") + if "json" in kwargs: + request_type = HttpRequestType.WITH_JSON + request_json = kwargs.get("json") + response: HttpRecorderClientResponse = await client._original_request_func(method, url, **kwargs) + response.parent_recorder = self + with self.begin() as session: + session: Session = session + playback_entry: HttpPlayback = HttpPlayback( + timestamp=int(time.time() * 1e3), + url=url, + method=method, + request_type=request_type, + request_params=request_params, + request_json=request_json, + response_type=HttpResponseType.HEADER_ONLY, + response_code=response.status + ) + session.add(playback_entry) + session.flush() + response.database_id = playback_entry.id + return response + finally: + client._reentrant_ref_count -= 1 + if client._reentrant_ref_count < 1: + client._response_class = client._original_response_class + del client._original_response_class + del client._reentrant_ref_count + + +class HttpPlayerResponse: + def __init__(self, method: str, url: str, status: int, response_text: Optional[str], response_json: Optional[Any]): + self.method = method + self.url = url + self.status = status + self._response_text: Optional[str] = response_text + self._response_json: Optional[Any] = response_json + + async def text(self) -> str: + if self._response_text is None: + raise EnvironmentError("No response text has been recorded for replaying.") + return self._response_text + + async def json(self) -> Any: + if self._response_json is None: + raise EnvironmentError("No response json has been recorded for replaying.") + return self._response_json + + def release(self): + """ + This is needed to satisfy ClientSession logic. + """ + pass + + +class HttpPlayer(HttpPlayerBase): + """ + Given a HTTP conversation record db, patch aiohttp.ClientSession such that it will only replay matched recorded + conversations. + + When aiohttp.ClientSession makes any request inside `patch_aiohttp_client()`, the player will search for a matching + response by URL, request params and request JSON. If no matching response is found, then an exception will be + raised. + + Usage: + recorder = HttpPlayer('test.db') + with recorder.patch_aiohttp_client: + # all aiohttp responses within this block will be replays from past records in test.db. + async with aiohttp.ClientSession() as client: + async with client.get("https://api.binance.com/api/v3/time") as resp: + data = await resp.json() # the data returned will be the recorded response + ... + """ + _replay_timestamp_ms: Optional[int] + + def __init__(self, db_path: str): + super().__init__(db_path) + self._replay_timestamp_ms = None + + @property + def replay_timestamp_ms(self) -> Optional[int]: + return self._replay_timestamp_ms + + @replay_timestamp_ms.setter + def replay_timestamp_ms(self, value: Optional[int]): + self._replay_timestamp_ms = value + + async def aiohttp_request_method( + self, + _: ClientSession, + method: str, + url: str, + **kwargs) -> HttpPlayerResponse: + with self.begin() as session: + session: Session = session + query: Query = (HttpPlayback.url == url) + query = cast(Query, and_(query, HttpPlayback.method == method)) + if "params" in kwargs: + query = cast(Query, and_(query, HttpPlayback.request_params == kwargs["params"])) + if "json" in kwargs: + query = cast(Query, and_(query, HttpPlayback.request_json == kwargs["json"])) + if self._replay_timestamp_ms is not None: + query = cast(Query, and_(query, HttpPlayback.timestamp >= self._replay_timestamp_ms)) + playback_entry: Optional[HttpPlayback] = ( + session.query(HttpPlayback).filter(query).first() + ) + + # Loosen the query conditions if the first, precise query didn't work. + if playback_entry is None: + query = (HttpPlayback.url == url) + query = cast(Query, and_(query, HttpPlayback.method == method)) + if self._replay_timestamp_ms is not None: + query = cast(Query, and_(query, HttpPlayback.timestamp >= self._replay_timestamp_ms)) + playback_entry = ( + session.query(HttpPlayback).filter(query).first() + ) + + return HttpPlayerResponse( + method, + url, + playback_entry.response_code, + playback_entry.response_text, + playback_entry.response_json + ) diff --git a/test/mock/mock_api_order_book_data_source.py b/test/mock/mock_api_order_book_data_source.py new file mode 100644 index 0000000..25be8d2 --- /dev/null +++ b/test/mock/mock_api_order_book_data_source.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +import aiohttp +import asyncio +from aiohttp.test_utils import TestClient +import logging +import pandas as pd +import time +from typing import ( + Any, + AsyncIterable, + Dict, + List, + Optional +) +import websockets +from websockets.exceptions import ConnectionClosed + +from hummingbot.core.data_type.order_book import OrderBook +from hummingbot.core.data_type.order_book_message import OrderBookMessage +from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource +from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry +from hummingbot.logger import HummingbotLogger + + +class MockAPIOrderBookDataSource(OrderBookTrackerDataSource): + + MESSAGE_TIMEOUT = 30.0 + PING_TIMEOUT = 10.0 + + _maobds_logger: Optional[HummingbotLogger] = None + + @classmethod + def logger(cls) -> HummingbotLogger: + if cls._maobds_logger is None: + cls._maobds_logger = logging.getLogger(__name__) + return cls._maobds_logger + + def __init__(self, client: TestClient, order_book_class: OrderBook, trading_pairs: Optional[List[str]] = None): + super().__init__() + self._client: TestClient = client + self._order_book_class = order_book_class + self._trading_pairs: Optional[List[str]] = trading_pairs + self._diff_messages: asyncio.Queue = asyncio.Queue() + self._snapshot_messages: asyncio.Queue = asyncio.Queue() + + async def get_trading_pairs(self) -> List[str]: + if not self._trading_pairs: + try: + self._trading_pairs = await self.fetch_trading_pairs() + except Exception: + self._trading_pairs = [] + self.logger().network( + "Error getting active exchange information.", + exc_info=True, + app_warning_msg="Error getting active exchange information. Check network connection." + ) + return self._trading_pairs + + @staticmethod + async def fetch_trading_pairs() -> List[str]: + raise NotImplementedError("Trading Pairs are required for mock data source") + + @staticmethod + async def get_snapshot(client: aiohttp.ClientSession, trading_pair: str) -> Dict[str, Any]: + # when type is set to "step0", the default value of "depth" is 150 + async with client.get("/mockSnapshot") as response: + response: aiohttp.ClientResponse = response + if response.status != 200: + raise IOError(f"Error fetching market snapshot for {trading_pair}. " + f"HTTP status is {response.status}.") + parsed_response = await response.json() + return parsed_response + + async def get_tracking_pairs(self) -> Dict[str, OrderBookTrackerEntry]: + # Get the currently active markets + trading_pairs: List[str] = await self.get_trading_pairs() + retval: Dict[str, OrderBookTrackerEntry] = {} + + number_of_pairs: int = len(trading_pairs) + for index, trading_pair in enumerate(trading_pairs): + try: + snapshot: Dict[str, Any] = await self.get_snapshot(self._client, trading_pair) + snapshot_msg: OrderBookMessage = self._order_book_class.snapshot_message_from_exchange( + snapshot, + metadata={"trading_pair": trading_pair} + ) + order_book: OrderBook = self.order_book_create_function() + order_book.apply_snapshot(snapshot_msg.bids, snapshot_msg.asks, snapshot_msg.update_id) + retval[trading_pair] = OrderBookTrackerEntry(trading_pair, snapshot_msg.timestamp, order_book) + self.logger().info(f"Initialized order book for {trading_pair}. " + f"{index + 1}/{number_of_pairs} completed.") + await asyncio.sleep(0.1) + except Exception: + self.logger().error(f"Error getting snapshot for {trading_pair}. ", exc_info=True) + await asyncio.sleep(5) + return retval + + async def _inner_messages(self, + ws: websockets.WebSocketClientProtocol) -> AsyncIterable[str]: + # Terminate the recv() loop as soon as the next message timed out, so the outer loop can reconnect. + try: + while True: + try: + msg: str = await asyncio.wait_for(ws.recv(), timeout=self.MESSAGE_TIMEOUT) + yield msg + except asyncio.TimeoutError: + pong_waiter = await ws.ping() + await asyncio.wait_for(pong_waiter, timeout=self.PING_TIMEOUT) + except asyncio.TimeoutError: + self.logger().warning("WebSocket ping timed out. Going to reconnect...") + return + except ConnectionClosed: + return + finally: + await ws.close() + + async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + pass + + def inject_mock_diff_message(self, msg: Dict[str, Any]): + self._diff_messages.put_nowait(msg) + + def inject_mock_snapshot_message(self, msg: Dict[str, Any]): + self._snapshot_messages.put_nowait(msg) + + async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + msg = await self._diff_messages.get() + order_book_message: OrderBookMessage = self._order_book_class.diff_message_from_exchange(msg) + output.put_nowait(order_book_message) + + async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): + while True: + try: + trading_pairs: List[str] = await self.get_trading_pairs() + for trading_pair in trading_pairs: + try: + snapshot: Dict[str, Any] = await self.get_snapshot(self._client, trading_pair) + snapshot_message: OrderBookMessage = self._order_book_class.snapshot_message_from_exchange( + snapshot, + metadata={"trading_pair": trading_pair} + ) + output.put_nowait(snapshot_message) + self.logger().debug(f"Saved order book snapshot for {trading_pair}") + await asyncio.sleep(5.0) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await asyncio.sleep(5.0) + this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace(minute=0, second=0, microsecond=0) + next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) + delta: float = next_hour.timestamp() - time.time() + await asyncio.sleep(delta) + except asyncio.CancelledError: + raise + except Exception: + self.logger().error("Unexpected error.", exc_info=True) + await asyncio.sleep(5.0) diff --git a/test/mock/mock_asset_price_delegate.py b/test/mock/mock_asset_price_delegate.py new file mode 100644 index 0000000..e1886c5 --- /dev/null +++ b/test/mock/mock_asset_price_delegate.py @@ -0,0 +1,29 @@ +from decimal import Decimal + +from hummingbot.connector.exchange_base import ExchangeBase +from hummingbot.core.data_type.common import PriceType +from hummingbot.strategy.asset_price_delegate import AssetPriceDelegate + + +class MockAssetPriceDelegate(AssetPriceDelegate): + def __init__(self, market: ExchangeBase, mock_price: Decimal): + self._market = market + self._mock_price = mock_price + + def set_mock_price(self, mock_price: Decimal): + self._mock_price = mock_price + + def get_mid_price(self) -> Decimal: + return self.c_get_mid_price() + + def get_price_by_type(self, _: PriceType) -> Decimal: + return self.c_get_mid_price() + + def c_get_mid_price(self): + return self._mock_price + + def ready(self) -> bool: + return True + + def market(self) -> ExchangeBase: + return self._market diff --git a/test/mock/mock_cli.py b/test/mock/mock_cli.py new file mode 100644 index 0000000..fd68072 --- /dev/null +++ b/test/mock/mock_cli.py @@ -0,0 +1,57 @@ +import asyncio +from typing import TYPE_CHECKING, Optional +from unittest.mock import AsyncMock, MagicMock, patch + +if TYPE_CHECKING: + from hummingbot.client.ui.hummingbot_cli import HummingbotCLI + + +class CLIMockingAssistant: + def __init__(self, app: "HummingbotCLI"): + self._app = app + self._prompt_patch = patch( + "hummingbot.client.ui.hummingbot_cli.HummingbotCLI.prompt" + ) + self._prompt_mock: Optional[AsyncMock] = None + self._prompt_replies = asyncio.Queue() + self._log_patch = patch( + "hummingbot.client.ui.hummingbot_cli.HummingbotCLI.log" + ) + self._log_mock: Optional[MagicMock] = None + self._log_calls = [] + self._to_stop_config_msg = "to_stop_config" + + self.ev_loop = asyncio.get_event_loop() + + def start(self): + self._prompt_mock = self._prompt_patch.start() + self._prompt_mock.side_effect = self._get_next_prompt_reply + self._log_mock = self._log_patch.start() + self._log_mock.side_effect = self._register_log_call + + def stop(self): + self._prompt_patch.stop() + self._log_patch.stop() + + def queue_prompt_reply(self, msg: str): + self._prompt_replies.put_nowait(msg) + + def queue_prompt_to_stop_config(self): + self._prompt_replies.put_nowait(self._to_stop_config_msg) + + def check_log_called_with(self, msg: str) -> bool: + called_with = msg in self._log_calls + return called_with + + async def _get_next_prompt_reply(self, prompt: str, is_password: bool = False): + msg = await self._prompt_replies.get() + if msg == self._to_stop_config_msg: + self._app.to_stop_config = True + msg = " " + return msg + + def _register_log_call(self, text: str, save_log: bool = True): + self._log_calls.append(text) + + def toggle_logs(self): + self._app.toggle_right_pane() diff --git a/test/mock/mock_events.py b/test/mock/mock_events.py new file mode 100644 index 0000000..06b12b4 --- /dev/null +++ b/test/mock/mock_events.py @@ -0,0 +1,11 @@ +from enum import Enum +from typing import NamedTuple + + +class MockEventType(Enum): + EVENT_ZERO = 0 + EVENT_ONE = 1 + + +class MockEvent(NamedTuple): + payload: int diff --git a/test/mock/mock_perp_connector.py b/test/mock/mock_perp_connector.py new file mode 100644 index 0000000..61d0d0f --- /dev/null +++ b/test/mock/mock_perp_connector.py @@ -0,0 +1,79 @@ +from decimal import Decimal +from typing import TYPE_CHECKING, Optional + +from hummingbot.connector.derivative.perpetual_budget_checker import PerpetualBudgetChecker +from hummingbot.connector.perpetual_trading import PerpetualTrading +from hummingbot.connector.test_support.mock_paper_exchange import MockPaperExchange +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, TradeType +from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TradeFeeSchema +from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee + +if TYPE_CHECKING: + from hummingbot.client.config.config_helpers import ClientConfigAdapter + + +class MockPerpConnector(MockPaperExchange, PerpetualTrading): + def __init__( + self, + client_config_map: "ClientConfigAdapter", + trade_fee_schema: Optional[TradeFeeSchema] = None, + buy_collateral_token: Optional[str] = None, + sell_collateral_token: Optional[str] = None, + ): + MockPaperExchange.__init__( + self, + client_config_map=client_config_map, + trade_fee_schema=trade_fee_schema) + PerpetualTrading.__init__(self, [self.trading_pair]) + self._budget_checker = PerpetualBudgetChecker(exchange=self) + self._funding_payment_span = [0, 10] + self._buy_collateral_token = buy_collateral_token + self._sell_collateral_token = sell_collateral_token + + def supported_position_modes(self): + return [PositionMode.ONEWAY, PositionMode.HEDGE] + + @property + def name(self): + return "mock_perp_connector" + + @property + def budget_checker(self) -> PerpetualBudgetChecker: + return self._budget_checker + + def get_buy_collateral_token(self, trading_pair: str) -> str: + token = ( + super().get_buy_collateral_token(trading_pair) + if self._buy_collateral_token is None + else self._buy_collateral_token + ) + return token + + def get_sell_collateral_token(self, trading_pair: str) -> str: + token = ( + super().get_sell_collateral_token(trading_pair) + if self._sell_collateral_token is None + else self._sell_collateral_token + ) + return token + + def get_fee(self, + base_currency: str, + quote_currency: str, + order_type: OrderType, + order_side: TradeType, + amount: Decimal, + price: Decimal = Decimal("0"), + is_maker: Optional[bool] = None, + position_action: PositionAction = PositionAction.OPEN) -> AddedToCostTradeFee: + return build_perpetual_trade_fee( + exchange=self.name, + is_maker=is_maker, + position_action=position_action, + base_currency=base_currency, + quote_currency=quote_currency, + order_type=order_type, + order_side=order_side, + amount=amount, + price=price, + ) diff --git a/test/test_isolated_asyncio_wrapper_test_case.py b/test/test_isolated_asyncio_wrapper_test_case.py new file mode 100644 index 0000000..b5df46b --- /dev/null +++ b/test/test_isolated_asyncio_wrapper_test_case.py @@ -0,0 +1,169 @@ +import asyncio +import concurrent.futures +import threading +import unittest +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase, async_to_sync + + +class TestIsolatedAsyncioWrapperTestCase(unittest.IsolatedAsyncioTestCase): + def test_setUpClass_with_existing_loop(self): + self.main_loop = asyncio.get_event_loop() + + IsolatedAsyncioWrapperTestCase.setUpClass() + self.assertIsNotNone(IsolatedAsyncioWrapperTestCase.main_event_loop) + self.assertEqual(self.main_loop, IsolatedAsyncioWrapperTestCase.main_event_loop) + + self.main_loop = None + + def test_setUpClass_with_new_loop(self): + self.main_loop = asyncio.get_event_loop() + self.local_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.local_loop) + + IsolatedAsyncioWrapperTestCase.setUpClass() + self.assertIsNotNone(IsolatedAsyncioWrapperTestCase.main_event_loop) + self.assertEqual(self.local_loop, IsolatedAsyncioWrapperTestCase.main_event_loop) + + self.local_loop.close() + asyncio.set_event_loop(self.main_loop) + self.main_loop = None + + def test_setUpClass_without_existing_loop(self): + def run_test_in_thread(future): + asyncio.set_event_loop(None) + + try: + IsolatedAsyncioWrapperTestCase.setUpClass() + self.assertIsNotNone(IsolatedAsyncioWrapperTestCase.main_event_loop) + self.assertEqual(asyncio.get_event_loop(), IsolatedAsyncioWrapperTestCase.main_event_loop) + except Exception as e: + future.set_exception(e) + else: + future.set_result(None) + + future = concurrent.futures.Future() + thread = threading.Thread(target=run_test_in_thread, args=(future,)) + thread.start() + thread.join() + future.result() + + def test_tearDownClass_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.main_loop) + + IsolatedAsyncioWrapperTestCase.main_event_loop = self.main_loop + IsolatedAsyncioWrapperTestCase.tearDownClass() + self.assertEqual(self.main_loop, asyncio.get_event_loop()) + + self.main_loop.close() + asyncio.set_event_loop(None) + self.main_loop = None + + def test_tearDownClass_without_existing_loop(self): + # Close the main event loop if it exists + def run_test_in_thread(future): + try: + asyncio.set_event_loop(None) + asyncio.get_event_loop() + except RuntimeError: + pass + + try: + IsolatedAsyncioWrapperTestCase.tearDownClass() + asyncio.get_event_loop() + except Exception as e: + future.set_exception(e) + else: + future.set_result(None) + + future = concurrent.futures.Future() + thread = threading.Thread(target=run_test_in_thread, args=(future,)) + thread.start() + thread.join() + future.result() + + async def _dummy_coro(self, name, delay): + """A dummy coroutine that simply sleeps for a delay.""" + await asyncio.sleep(delay) + + async def _dummy_coro_to_await(self, name, delay): + """A dummy coroutine that simply sleeps for a delay.""" + await asyncio.sleep(delay) + + async def test_await_task_completion(self): + # Create some tasks with different coroutine names + task1 = asyncio.create_task(self._dummy_coro("task1", 0.5)) + task2 = asyncio.create_task(self._dummy_coro("task2", 0.75)) + task3 = asyncio.create_task(self._dummy_coro_to_await("task3", 2)) + + # Use the await_task_completion method to wait for task1 and task2 to complete + self.assertFalse(task1.done()) + self.assertFalse(task2.done()) + self.assertFalse(task3.done()) + + await IsolatedAsyncioWrapperTestCase.await_task_completion(["_dummy_coro"]) + + # At this point, task1 and task2 should be done, but task3 should still be running + self.assertTrue(task1.done()) + self.assertTrue(task2.done()) + self.assertFalse(task3.done()) + + # Now wait for task3 to complete as well + await IsolatedAsyncioWrapperTestCase.await_task_completion("_dummy_coro_to_await") + self.assertTrue(task3.done()) + + +class TestAsyncToSyncInLoop(unittest.TestCase): + @async_to_sync + async def async_add(self, a: int, b: int) -> int: + await asyncio.sleep(0.1) + return a + b + + def test_async_add(self): + result = self.async_add(1, 2) + self.assertEqual(result, 3) + + @async_to_sync + async def async_raise_exception(self) -> None: + await asyncio.sleep(0.1) + raise ValueError("Test exception") + + def test_async_raise_exception(self): + with self.assertRaises(ValueError) as context: + self.async_raise_exception() + self.assertEqual(str(context.exception), "Test exception") + + def test_main_event_loop_unchanged(self): + # Save the current event loop + try: + main_loop = asyncio.get_event_loop() + except RuntimeError: + # If no event loop exists, create one + main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(main_loop) + + # Run a function decorated with @async_to_sync + self.async_add(1, 2) + + # Check that the current event loop is still the same + self.assertEqual(main_loop, asyncio.get_event_loop()) + + def test_main_event_loop_unchanged_after_exception(self): + # Save the current event loop + try: + main_loop = asyncio.get_event_loop() + except RuntimeError: + # If no event loop exists, create one + main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(main_loop) + + # Run a function decorated with @async_to_sync that raises an exception + with self.assertRaises(ValueError): + self.async_raise_exception() + + # Check that the current event loop is still the same + self.assertEqual(main_loop, asyncio.get_event_loop()) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_local_class_event_loop_wrapper_test_case.py b/test/test_local_class_event_loop_wrapper_test_case.py new file mode 100644 index 0000000..94fad4f --- /dev/null +++ b/test/test_local_class_event_loop_wrapper_test_case.py @@ -0,0 +1,82 @@ +import asyncio +import time +import unittest +from test.isolated_asyncio_wrapper_test_case import IsolatedAsyncioWrapperTestCase, LocalClassEventLoopWrapperTestCase + + +class TestLocalClassEventLoopWrapperTestCase(unittest.TestCase): + def setUp(self): + self.test_case = LocalClassEventLoopWrapperTestCase() + self.test_case.setUpClass() + + def tearDown(self): + self.test_case.tearDownClass() + + def test_setUpClass_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.main_loop) + + LocalClassEventLoopWrapperTestCase.setUpClass() + self.assertIsNotNone(LocalClassEventLoopWrapperTestCase.main_event_loop) + self.assertEqual(self.main_loop, LocalClassEventLoopWrapperTestCase.main_event_loop) + self.assertNotEqual(self.main_loop, LocalClassEventLoopWrapperTestCase.local_event_loop) + + self.main_loop = None + + def test_setUpClass_without_existing_loop(self): + # Close the main event loop if it exists + asyncio.set_event_loop(None) + + # Call setUpClass and verify that it does not create a new event loop + LocalClassEventLoopWrapperTestCase.setUpClass() + self.assertIsNotNone(LocalClassEventLoopWrapperTestCase.main_event_loop) + self.assertEqual(asyncio.get_event_loop(), LocalClassEventLoopWrapperTestCase.local_event_loop) + + def test_tearDownClass_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + + LocalClassEventLoopWrapperTestCase.main_event_loop = self.main_loop + LocalClassEventLoopWrapperTestCase.local_event_loop = asyncio.new_event_loop() + asyncio.set_event_loop(LocalClassEventLoopWrapperTestCase.local_event_loop) + + LocalClassEventLoopWrapperTestCase.tearDownClass() + self.assertIsNone(LocalClassEventLoopWrapperTestCase.main_event_loop) + self.assertIsNone(LocalClassEventLoopWrapperTestCase.local_event_loop) + self.assertEqual(self.main_loop, asyncio.get_event_loop()) + + self.main_loop.close() + asyncio.set_event_loop(None) + self.main_loop = None + + def test_exception_in_test_case(self): + IsolatedAsyncioWrapperTestCase.setUpClass() + + try: + # Simulate a test case that raises an exception + raise Exception("Test exception") + except Exception: + pass + + # Despite the exception, tearDownClass should still correctly clean up the event loop + LocalClassEventLoopWrapperTestCase.tearDownClass() + self.assertIsNone(LocalClassEventLoopWrapperTestCase.main_event_loop) + + def test_run_async_with_timeout(self): + self.test_case.setUp() + + # Test a coroutine that finishes before the timeout + start_time = time.time() + result = self.test_case.run_async_with_timeout(asyncio.sleep(0.1), timeout=1.0) + end_time = time.time() + self.assertIsNone(result) + self.assertLess(end_time - start_time, 1.0) + + # Test a coroutine that doesn't finish before the timeout + with self.assertRaises(asyncio.TimeoutError): + self.test_case.run_async_with_timeout(asyncio.sleep(2.0), timeout=1.0) + + self.test_case.tearDown() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_local_test_event_loop_wrapper_test_case.py b/test/test_local_test_event_loop_wrapper_test_case.py new file mode 100644 index 0000000..7538f47 --- /dev/null +++ b/test/test_local_test_event_loop_wrapper_test_case.py @@ -0,0 +1,101 @@ +import asyncio +import time +import unittest +from test.isolated_asyncio_wrapper_test_case import LocalTestEventLoopWrapperTestCase + + +class TestLocalTestEventLoopWrapperTestCase(unittest.TestCase): + def setUp(self): + self.test_case = LocalTestEventLoopWrapperTestCase() + self.test_case.setUpClass() + + def tearDown(self): + self.test_case.tearDownClass() + + def test_setUp_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.main_loop) + + self.test_case.setUp() + self.assertIsNotNone(self.test_case.local_event_loop) + self.assertEqual(self.test_case.local_event_loop, asyncio.get_event_loop()) + self.assertNotEqual(self.main_loop, self.test_case.local_event_loop) + + self.main_loop.close() + asyncio.set_event_loop(None) + self.main_loop = None + + def test_setUp_without_existing_loop(self): + # Close the main event loop if it exists + try: + loop = asyncio.get_event_loop() + loop.close() + asyncio.set_event_loop(None) + except RuntimeError: + pass + + self.test_case.setUp() + self.assertIsNotNone(self.test_case.local_event_loop) + self.assertEqual(self.test_case.local_event_loop, asyncio.get_event_loop()) + + def test_tearDown_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.main_loop) + + self.test_case.local_event_loop = self.main_loop + self.test_case.tearDown() + self.assertIsNone(self.test_case.local_event_loop) + self.assertEqual(self.main_loop, asyncio.get_event_loop()) + + self.main_loop.close() + asyncio.set_event_loop(None) + self.main_loop = None + + def test_tearDown_without_existing_loop(self): + # Close the main event loop if it exists + try: + loop = asyncio.get_event_loop() + loop.close() + asyncio.set_event_loop(None) + except RuntimeError: + pass + + self.test_case.tearDown() + self.assertIsNone(self.test_case.local_event_loop) + + # Verify that get_event_loop still raises a RuntimeError, indicating that no event loop exists + with self.assertRaises(RuntimeError): + asyncio.get_event_loop() + + def test_tearDownClass_with_existing_loop(self): + self.main_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.main_loop) + + LocalTestEventLoopWrapperTestCase.main_event_loop = self.main_loop + self.test_case.tearDownClass() + self.assertIsNone(LocalTestEventLoopWrapperTestCase.main_event_loop) + self.assertEqual(self.main_loop, asyncio.get_event_loop()) + + self.main_loop.close() + asyncio.set_event_loop(None) + self.main_loop = None + + def test_run_async_with_timeout(self): + self.test_case.setUp() + + # Test a coroutine that finishes before the timeout + start_time = time.time() + result = self.test_case.run_async_with_timeout(asyncio.sleep(0.1), timeout=1.0) + end_time = time.time() + self.assertIsNone(result) + self.assertLess(end_time - start_time, 1.0) + + # Test a coroutine that doesn't finish before the timeout + with self.assertRaises(asyncio.TimeoutError): + self.test_case.run_async_with_timeout(asyncio.sleep(2.0), timeout=1.0) + + self.test_case.tearDown() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_logger_mixin_for_test.py b/test/test_logger_mixin_for_test.py new file mode 100644 index 0000000..6e076c2 --- /dev/null +++ b/test/test_logger_mixin_for_test.py @@ -0,0 +1,131 @@ +import asyncio +import unittest +from logging import Logger, LogRecord +from test.logger_mixin_for_test import LoggerMixinForTest, LogLevel + +from hummingbot.logger import HummingbotLogger + + +class TestTestLoggerMixin(unittest.TestCase): + def setUp(self): + super().setUp() + self.logger = LoggerMixinForTest() + self._original_async_loop = asyncio.get_event_loop() + self.async_loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.async_loop) + + def tearDown(self) -> None: + super().tearDown() + self.async_loop.stop() + self.async_loop.close() + asyncio.set_event_loop(self._original_async_loop) + + def test_handle(self): + self.logger.log_records = [] + record = LogRecord(name="test", level=LogLevel.INFO, pathname="", lineno=0, msg="test message", args=None, + exc_info=None) + self.logger.handle(record) + self.assertEqual(len(self.logger.log_records), 1) + self.assertEqual(self.logger.log_records[0].getMessage(), "test message") + + def test_is_logged(self): + self.logger.log_records = [] + record = LogRecord(name="test", level=LogLevel.INFO, pathname="", lineno=0, msg="test message", args=None, + exc_info=None) + self.logger.handle(record) + self.assertTrue(self.logger.is_logged(LogLevel.INFO, "test message", )) + self.assertFalse(self.logger.is_logged(LogLevel.ERROR, "test message", )) + self.assertFalse(self.logger.is_logged(LogLevel.INFO, "other message", )) + + self.assertTrue(self.logger.is_logged("INFO", "test message", )) + self.assertFalse(self.logger.is_logged("ERROR", "test message", )) + self.assertFalse(self.logger.is_logged("INFO", "other message", )) + + def test_is_partially_logged(self): + self.logger.log_records = [] + record = LogRecord(name="test", level=LogLevel.INFO, pathname="", lineno=0, msg="test message", args=None, + exc_info=None) + self.logger.handle(record) + self.assertTrue(self.logger.is_partially_logged(LogLevel.INFO, "test")) + self.assertFalse(self.logger.is_partially_logged(LogLevel.ERROR, "test")) + self.assertFalse(self.logger.is_partially_logged(LogLevel.INFO, "other")) + + def test_set_loggers_single_logger(self): + logger = HummingbotLogger("TestLogger") + logger.level = LogLevel.INFO + self.assertNotEqual(1, logger.level) + self.assertEqual(0, len(logger.handlers)) + + self.logger.set_loggers([logger]) + self.assertEqual(1, logger.level) + self.assertEqual(1, len(logger.handlers)) + self.assertEqual(self.logger, logger.handlers[0]) + + def test_set_loggers_multiple_logger(self): + loggers = [] + for i in range(5): + logger = HummingbotLogger(f"TestLogger{i}") + logger.level = LogLevel.INFO + loggers.append(logger) + self.assertNotEqual(1, logger.level) + self.assertEqual(0, len(logger.handlers)) + + self.logger.set_loggers(loggers) + + for logger in loggers: + self.assertEqual(1, len(logger.handlers)) + self.assertEqual(1, logger.level) + self.assertEqual(self.logger, logger.handlers[0]) + + def test_set_loggers_other_logger(self): + logger = Logger("TestLogger") + logger.level = LogLevel.INFO + self.assertNotEqual(logger.level, self.logger.level) + self.assertEqual(0, len(logger.handlers)) + + self.logger.set_loggers([logger]) + self.assertEqual(1, logger.level) + self.assertEqual(1, len(logger.handlers)) + self.assertEqual(self.logger, logger.handlers[0]) + + def test_set_loggers_some_none(self): + loggers = [HummingbotLogger("Test"), None] + self.logger.set_loggers(loggers) + + def test_wait_for_logged(self): + async def async_test(): + self.logger.log_records = [] + record = LogRecord(name="test", level=LogLevel.INFO, pathname="", lineno=0, msg="test message", args=None, + exc_info=None) + self.logger.handle(record) + + # Test a message that has been logged + logged = await self.logger.wait_for_logged(LogLevel.INFO, "test message", False, wait_s=0.1) + self.assertIsNone(logged) + + # Test a message that has not been logged + with self.assertRaises(asyncio.TimeoutError): + await self.logger.wait_for_logged(LogLevel.INFO, "other message", False, wait_s=0.1) + + asyncio.get_event_loop().run_until_complete(async_test()) + + def test_wait_for_logged_partial(self): + async def async_test(): + self.logger.log_records = [] + record = LogRecord(name="test", level=LogLevel.INFO, pathname="", lineno=0, msg="test message", args=None, + exc_info=None) + self.logger.handle(record) + + # Test a message that has been logged + logged = await self.logger.wait_for_logged(LogLevel.INFO, "test", True, wait_s=0.1) + self.assertIsNone(logged) + + # Test a message that has not been logged + with self.assertRaises(asyncio.TimeoutError): + await self.logger.wait_for_logged(LogLevel.INFO, "other", True, wait_s=0.1) + + asyncio.get_event_loop().run_until_complete(async_test()) + + +if __name__ == "__main__": + unittest.main() diff --git a/uninstall b/uninstall new file mode 100755 index 0000000..ff5cbdb --- /dev/null +++ b/uninstall @@ -0,0 +1,22 @@ +#!/bin/bash + +cd $(dirname $0) + +# Compatibility logic for older Anaconda versions. +if [ "${CONDA_EXE} " == " " ]; then + CONDA_EXE=$((find /opt/conda/bin/conda || find ~/anaconda3/bin/conda || \ + find /usr/local/anaconda3/bin/conda || find ~/miniconda3/bin/conda || \ + find /root/miniconda/bin/conda || find ~/Anaconda3/Scripts/conda) 2>/dev/null) +fi + +if [ "${CONDA_EXE}_" == "_" ]; then + echo "Please install Anaconda w/ Python 3.7+ first" + echo "See: https://www.continuum.io/downloads" + exit 1 +fi + +if ${CONDA_EXE} env list | egrep -qe "^hummingbot"; then + ${CONDA_EXE} env remove -n hummingbot +else + echo "Environment already removed." +fi